Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 67 additions & 24 deletions base/cvd/cuttlefish/common/libs/utils/files.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,46 +115,89 @@ Result<bool> AreHardLinked(const std::string& source,
CF_EXPECT(FileInodeNumber(destination)));
}

Result<std::string> CreateHardLink(const std::string& target,
const std::string& hardlink,
const bool overwrite_existing) {
if (FileExists(hardlink)) {
if (CF_EXPECT(AreHardLinked(target, hardlink))) {
return hardlink;
Result<bool> AreFilesIdentical(const std::string& path1,
const std::string& path2) {
struct stat st1;
struct stat st2;
CF_EXPECTF(stat(path1.c_str(), &st1) == 0, "stat failed for {}", path1);
CF_EXPECTF(stat(path2.c_str(), &st2) == 0, "stat failed for {}", path2);
if (st1.st_size != st2.st_size) {
return false;
}

SharedFD fd1 = SharedFD::Open(path1, O_RDONLY);
SharedFD fd2 = SharedFD::Open(path2, O_RDONLY);
CF_EXPECTF(fd1->IsOpen(), "Failed to open \"{}\"", path1);
CF_EXPECTF(fd2->IsOpen(), "Failed to open \"{}\"", path2);

char buf1[4096];
char buf2[4096];
while (true) {
auto r1 = fd1->Read(buf1, sizeof(buf1));
auto r2 = fd2->Read(buf2, sizeof(buf2));
CF_EXPECTF(r1 >= 0, "Read failed for \"{}\"", path1);
CF_EXPECTF(r2 >= 0, "Read failed for \"{}\"", path2);
if (r1 != r2) {
return false;
}
if (r1 == 0) {
break;
}
if (memcmp(buf1, buf2, r1) != 0) {
return false;
}
}
return true;
}

Result<std::string> LinkOrCopy(const std::string& target,
const std::string& destination,
const bool overwrite_existing) {
if (FileExists(destination)) {
if (CF_EXPECT(AreHardLinked(target, destination))) {
return destination;
}
if (!overwrite_existing) {
if (CF_EXPECT(AreFilesIdentical(target, destination))) {
return destination;
}
return CF_ERRF(
"Cannot hardlink from \"{}\" to \"{}\", the second file already "
"exists and is not hardlinked to the first",
target, hardlink);
"Cannot link/copy from \"{}\" to \"{}\", the second file already "
"exists and is different from the first",
target, destination);
}
LOG(WARNING) << "Overwriting existing file \"" << hardlink << "\" with \""
<< target << "\" from the cache";
CF_EXPECTF(unlink(hardlink.c_str()) == 0,
"Failed to unlink \"{}\" with error: {}", hardlink,
LOG(WARNING) << "Overwriting existing file \"" << destination
<< "\" with \"" << target << "\" from the cache";
CF_EXPECTF(unlink(destination.c_str()) == 0,
"Failed to unlink \"{}\" with error: {}", destination,
strerror(errno));
}
CF_EXPECTF(link(target.c_str(), hardlink.c_str()) == 0,
"link() failed trying to create hardlink from \"{}\" to \"{}\" "
"with error: {}",
target, hardlink, strerror(errno));
VLOG(1) << "Created hard link from \"" << target << "\" to \"" << hardlink
if (link(target.c_str(), destination.c_str()) == 0) {
VLOG(1) << "Created hard link from \"" << target << "\" to \""
<< destination << "\"";
return destination;
}
CF_EXPECTF(Copy(target, destination), "Failed to copy \"{}\" to \"{}\"",
target, destination);
VLOG(1) << "Copied file from \"" << target << "\" to \"" << destination
<< "\"";
return hardlink;

return destination;
}

bool FileHasContent(const std::string& path) {
return FileSize(path) > 0;
}

Result<void> HardLinkDirecoryContentsRecursively(
Result<void> LinkOrCopyDirectoryContentsRecursively(
const std::string& source, const std::string& destination) {
CF_EXPECTF(IsDirectory(source), "Source '{}' is not a directory", source);

CF_EXPECT(EnsureDirectoryExists(destination, 0755));

auto linker = [&source, &destination](
const std::string& filepath) mutable -> Result<void> {
auto linker_or_copier =
[&source,
&destination](const std::string& filepath) mutable -> Result<void> {
const std::string src_path = filepath;
const std::string dst_path =
destination + "/" + filepath.substr(source.size() + 1);
Expand All @@ -163,10 +206,10 @@ Result<void> HardLinkDirecoryContentsRecursively(
return {};
}
const bool overwrite_existing = true;
CF_EXPECT(CreateHardLink(src_path, dst_path, overwrite_existing));
CF_EXPECT(LinkOrCopy(src_path, dst_path, overwrite_existing));
return {};
};
CF_EXPECT(WalkDirectory(source, linker));
CF_EXPECT(WalkDirectory(source, linker_or_copier));

return {};
}
Expand Down
9 changes: 5 additions & 4 deletions base/cvd/cuttlefish/common/libs/utils/files.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ inline Result<bool> CanRename(const std::string& source,
Result<ino_t> FileInodeNumber(const std::string& path);
Result<bool> AreHardLinked(const std::string& source,
const std::string& destination);
Result<std::string> CreateHardLink(const std::string& target,
const std::string& hardlink,
bool overwrite_existing = false);
Result<void> HardLinkDirecoryContentsRecursively(

Result<std::string> LinkOrCopy(const std::string& target,
const std::string& destination,
bool overwrite_existing = false);
Result<void> LinkOrCopyDirectoryContentsRecursively(
const std::string& source, const std::string& destination);
// Merges the contents of the source directory into the destination directory.
// The source directory is empty after this operation.
Expand Down
9 changes: 5 additions & 4 deletions base/cvd/cuttlefish/common/libs/utils/files_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,16 @@ class FilesTests : public ::testing::Test {
std::string dst_dir_;
};

TEST_F(FilesTests, HardLinkRecursivelyFailsIfSourceIsNotADirectory) {
Result<void> result = HardLinkDirecoryContentsRecursively(
TEST_F(FilesTests, LinkOrCopyRecursivelyFailsIfSourceIsNotADirectory) {
Result<void> result = LinkOrCopyDirectoryContentsRecursively(
src_dir_ + "/file1.txt", dst_dir_ + "/file1.txt");

EXPECT_THAT(result, IsError());
}

TEST_F(FilesTests, HardLinkRecursively) {
Result<void> result = HardLinkDirecoryContentsRecursively(src_dir_, dst_dir_);
TEST_F(FilesTests, LinkOrCopyRecursively) {
Result<void> result =
LinkOrCopyDirectoryContentsRecursively(src_dir_, dst_dir_);

EXPECT_THAT(result, IsOk());
Result<bool> resultHardLinked =
Expand Down
2 changes: 1 addition & 1 deletion base/cvd/cuttlefish/host/commands/cvd/fetch/downloaders.cc
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Result<Downloaders> Downloaders::Create(const BuildApiFlags& flags,
impl->android_creds_.get(), flags.wait_retry_period,
impl->cas_downloader_.get());

if (flags.enable_caching && CanCache(target_directory, cache_base_path)) {
if (flags.enable_caching) {
impl->caching_build_api_ = std::make_unique<CachingBuildApi>(
*impl->android_build_api_, cache_base_path);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ Result<std::vector<std::string>> ExtractImageContents(
CF_EXPECT(WalkDirectory(image_filepath, file_collector));

if (keep_archive) {
// Must use hard linking due to the way fetch_cvd uses the cache.
CF_EXPECT(HardLinkDirecoryContentsRecursively(image_filepath, target_dir));
// Prefer hard linking, but fallback to copy if necessary.
CF_EXPECT(
LinkOrCopyDirectoryContentsRecursively(image_filepath, target_dir));
} else {
CF_EXPECT(MoveDirectoryContents(image_filepath, target_dir));
// Ignore even if removing directory fails - harmless.
Expand Down
20 changes: 2 additions & 18 deletions base/cvd/cuttlefish/host/libs/web/caching_build_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,6 @@ bool IsInCache(const std::string& filepath) {

} // namespace

bool CanCache(const std::string& target_directory,
const std::string& cache_base_path) {
const Result<bool> result = CanHardLink(target_directory, cache_base_path);
if (result.ok() && *result) {
return true;
}
if (!result.ok()) {
LOG(ERROR) << "Error during hard link check: " << result.error();
}
LOG(WARNING)
<< "Caching disabled, unable to hard link between fetch directory \""
<< target_directory << "\" and cache directory \"" << cache_base_path
<< "\"";
return false;
}

CachingBuildApi::CachingBuildApi(BuildApi& build_api,
std::string cache_base_path)
: build_api_(build_api), cache_base_path_(std::move(cache_base_path)) {};
Expand All @@ -114,8 +98,8 @@ Result<std::string> CachingBuildApi::DownloadFile(
if (!IsInCache(paths.cache_artifact)) {
CF_EXPECT(build_api_.DownloadFile(build, paths.build_cache, artifact_name));
}
return CF_EXPECT(CreateHardLink(paths.cache_artifact, paths.target_artifact,
kOverwriteExistingFile));
return CF_EXPECT(LinkOrCopy(paths.cache_artifact, paths.target_artifact,
kOverwriteExistingFile));
}


Expand Down
3 changes: 0 additions & 3 deletions base/cvd/cuttlefish/host/libs/web/caching_build_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@

namespace cuttlefish {

bool CanCache(const std::string& target_directory,
const std::string& cache_base_path);

class CachingBuildApi : public BuildApi {
public:
CachingBuildApi(BuildApi& build_api, std::string cache_base_path);
Expand Down
24 changes: 22 additions & 2 deletions container/src/podcvd/internal/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func ExecFetchCmdOnDisposableHost(ccm CuttlefishContainerManager, cvdArgs *CvdAr
if err := os.MkdirAll(cvdDataHome, 0755); err != nil {
return fmt.Errorf("failed to eusure directory at %q: %w", cvdDataHome, err)
}
cacheDir := hostCacheDir()
if err := os.MkdirAll(cacheDir, 0755); err != nil {
return fmt.Errorf("failed to ensure cache directory at %q: %w", cacheDir, err)
}
targetDir := cvdArgs.GetStringFlagValueOnSubCommandArgs("target_directory")
if targetDir == "" {
return fmt.Errorf("target_directory is missing")
Expand All @@ -96,6 +100,7 @@ func ExecFetchCmdOnDisposableHost(ccm CuttlefishContainerManager, cvdArgs *CvdAr
"--label", fmt.Sprintf("%s=%s", labelCreatedBy, valueCreatedBy),
"-v", fmt.Sprintf("%s:/root/.local/share/cvd:ro", cvdDataHome),
"-v", fmt.Sprintf("%s:%s:rw", targetDir, targetDir),
"-v", fmt.Sprintf("%s:/var/tmp/cvd/0/cache:rw", cacheDir),
}
containerID, err := ccm.CreateAndStartContainer(context.Background(), extraFlags, "")
if err != nil {
Expand Down Expand Up @@ -132,6 +137,11 @@ func cvdDataHome() (string, error) {
return "", fmt.Errorf("failed to find cvd data home dir")
}

func hostCacheDir() string {
uid := os.Getuid()
return filepath.Join("/var/tmp/cvd", strconv.Itoa(uid), "cache")
}

func resolveHostPath(path string) string {
if strings.TrimSpace(path) == "" {
return ""
Expand Down Expand Up @@ -196,12 +206,13 @@ func mountablePathsFromConfigFile(cvdArgs *CvdArgs) []string {
return extractPaths(data)
}

func collectMountSpecs(pathsToMount []string, hostOut, productOut, cvdDataHome, podcvdHomeDir string) []string {
func collectMountSpecs(pathsToMount []string, hostOut, productOut, cvdDataHome, podcvdHomeDir, cacheDir string) []string {
bindMap := make(map[string]string)
bindMap["/host_out"] = fmt.Sprintf("%s:/host_out:O", hostOut)
bindMap["/product_out"] = fmt.Sprintf("%s:/product_out:O", productOut)
bindMap["/root/.local/share/cvd"] = fmt.Sprintf("%s:/root/.local/share/cvd:ro", cvdDataHome)
bindMap["/podcvd_home"] = fmt.Sprintf("%s:/podcvd_home:rw", podcvdHomeDir)
bindMap["/var/tmp/cvd/0/cache"] = fmt.Sprintf("%s:/var/tmp/cvd/0/cache:rw", cacheDir)
for _, p := range pathsToMount {
if spec, ok := bindMap[p]; ok {
host := strings.SplitN(spec, ":", 2)[0]
Expand Down Expand Up @@ -297,6 +308,10 @@ func createAndStartContainer(ccm CuttlefishContainerManager, cvdArgs *CvdArgs) (
if err := os.MkdirAll(cvdDataHome, 0755); err != nil {
return "", fmt.Errorf("failed to eusure directory at %q: %w", cvdDataHome, err)
}
cacheDir := hostCacheDir()
if err := os.MkdirAll(cacheDir, 0755); err != nil {
return "", fmt.Errorf("failed to ensure cache directory at %q: %w", cacheDir, err)
}
currentDir, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("failed to get current directory: %w", err)
Expand Down Expand Up @@ -346,7 +361,7 @@ func createAndStartContainer(ccm CuttlefishContainerManager, cvdArgs *CvdArgs) (
pathsToMount = append(pathsToMount, realPath)
}
}
mountSpecs := collectMountSpecs(pathsToMount, hostOut, productOut, cvdDataHome, podcvdHomeDir)
mountSpecs := collectMountSpecs(pathsToMount, hostOut, productOut, cvdDataHome, podcvdHomeDir, cacheDir)

extraFlags := []string{
"-e", "ANDROID_HOST_OUT=/host_out",
Expand Down Expand Up @@ -446,9 +461,14 @@ func createAndStartToolingContainer(ccm CuttlefishContainerManager) error {
if err := os.MkdirAll(cvdDataHome, 0755); err != nil {
return fmt.Errorf("failed to eusure directory at %q: %w", cvdDataHome, err)
}
cacheDir := hostCacheDir()
if err := os.MkdirAll(cacheDir, 0755); err != nil {
return fmt.Errorf("failed to ensure cache directory at %q: %w", cacheDir, err)
}
extraFlags := []string{
"--label", fmt.Sprintf("%s=%s", labelCreatedBy, valueCreatedBy),
"-v", fmt.Sprintf("%s:/root/.local/share/cvd:rw", cvdDataHome),
"-v", fmt.Sprintf("%s:/var/tmp/cvd/0/cache:rw", cacheDir),
}
if _, err := ccm.CreateAndStartContainer(context.Background(), extraFlags, ToolingContainerName); err != nil {
return err
Expand Down
37 changes: 33 additions & 4 deletions container/src/podcvd/internal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func Main(args []string) error {
if err := fleetAllCuttlefishHosts(ccm); err != nil {
return err
}
case "help", "lint", "login", "version":
case "cache", "help", "lint", "login", "version":
if err := handleToolingSubcommands(ccm, cvdArgs); err != nil {
return err
}
Expand All @@ -75,9 +75,6 @@ func Main(args []string) error {
}
case "setup":
return setupPodcvd()
case "cache":
// TODO(seungjaeyoo): Support other subcommands of cvd as well.
return fmt.Errorf("subcommand %q is not implemented yet", subcommand)
default:
return fmt.Errorf("unknown subcommand %q", subcommand)
}
Expand Down Expand Up @@ -354,6 +351,10 @@ func handleToolingSubcommands(ccm CuttlefishContainerManager, cvdArgs *CvdArgs)
}
subcommand := cvdArgs.SubCommandArgs[0]
switch subcommand {
case "cache":
if err := handleCacheExecution(ccm, cvdArgs); err != nil {
return err
}
case "lint":
if err := handleLintExecution(ccm, cvdArgs); err != nil {
return err
Expand All @@ -368,6 +369,34 @@ func handleToolingSubcommands(ccm CuttlefishContainerManager, cvdArgs *CvdArgs)
return nil
}

func handleCacheExecution(ccm CuttlefishContainerManager, cvdArgs *CvdArgs) error {
cacheDir := hostCacheDir()

if len(cvdArgs.SubCommandArgs) > 1 && cvdArgs.SubCommandArgs[1] == "empty" {
entries, err := os.ReadDir(cacheDir)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to empty cache directory %q: %w", cacheDir, err)
}
for _, entry := range entries {
if err := os.RemoveAll(filepath.Join(cacheDir, entry.Name())); err != nil {
return fmt.Errorf("failed to empty cache directory %q: %w", cacheDir, err)
}
}
fmt.Printf("Cache at %q has been emptied\n", cacheDir)
return nil
}

args := append([]string{"cvd"}, cvdArgs.SerializeCommonArgs()...)
args = append(args, cvdArgs.SubCommandArgs...)
var stdoutBuf bytes.Buffer
if err := ccm.ExecOnContainer(context.Background(), ToolingContainerName, args, os.Stdin, &stdoutBuf, os.Stderr); err != nil {
return err
}
translatedOutput := strings.ReplaceAll(stdoutBuf.String(), "/var/tmp/cvd/0/cache", cacheDir)
_, err := os.Stdout.WriteString(translatedOutput)
return err
}

func handleLintExecution(ccm CuttlefishContainerManager, cvdArgs *CvdArgs) error {
if len(cvdArgs.SubCommandArgs) < 2 {
return fmt.Errorf("missing JSON config file path")
Expand Down