/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "host/libs/command_util/snapshot_utils.h" #include #include #include #include #include #include #include #include #include #include "common/libs/fs/shared_fd.h" #include "common/libs/utils/files.h" #include "common/libs/utils/json.h" #include "common/libs/utils/result.h" namespace cuttlefish { namespace { bool IsFifo(const struct stat& file_stat) { return S_ISFIFO(file_stat.st_mode); } bool IsSocket(const struct stat& file_stat) { return S_ISSOCK(file_stat.st_mode); } bool IsSymlink(const struct stat& file_stat) { return S_ISLNK(file_stat.st_mode); } bool IsRegular(const struct stat& file_stat) { return S_ISREG(file_stat.st_mode); } // assumes that src_dir_path and dest_dir_path exist and both are // existing directories or links to the directories. Also they are // different directories. Result CopyDirectoryImpl( const std::string& src_dir_path, const std::string& dest_dir_path, const std::function& predicate) { // create an empty dest_dir_path with the same permission as src_dir_path // and then, recursively copy the contents LOG(DEBUG) << "Making sure " << dest_dir_path << " exists and is effectively a directory."; CF_EXPECTF(EnsureDirectoryExists(dest_dir_path), "Directory {} cannot to be created; it does not exist, either.", dest_dir_path); const auto src_contents = CF_EXPECT(DirectoryContents(src_dir_path)); for (const auto& src_base_path : src_contents) { if (!predicate(src_dir_path + "/" + src_base_path)) { continue; } if (src_base_path == "." || src_base_path == "..") { LOG(DEBUG) << "Skipping \"" << src_base_path << "\""; continue; } std::string src_path = src_dir_path + "/" + src_base_path; std::string dest_path = dest_dir_path + "/" + src_base_path; LOG(DEBUG) << "Handling... " << src_path; struct stat src_stat; CF_EXPECTF(lstat(src_path.data(), &src_stat) != -1, "Failed in lstat({})", src_path); if (IsSymlink(src_stat)) { std::string target; CF_EXPECTF(android::base::Readlink(src_path, &target), "Readlink failed for {}", src_path); LOG(DEBUG) << "Creating link from " << dest_path << " to " << target; if (FileExists(dest_path, /* follow_symlink */ false)) { CF_EXPECTF(RemoveFile(dest_path), "Failed to unlink/remove file \"{}\"", dest_path); } CF_EXPECTF(symlink(target.data(), dest_path.data()) == 0, "Creating symbolic link from {} to {} failed: {}", dest_path, target, strerror(errno)); continue; } if (IsFifo(src_stat) || IsSocket(src_stat)) { LOG(DEBUG) << "Ignoring a named pipe or socket " << src_path; continue; } if (DirectoryExists(src_path)) { LOG(DEBUG) << "Recursively calling CopyDirectoryImpl(" << src_path << ", " << dest_path << ")"; CF_EXPECT(CopyDirectoryImpl(src_path, dest_path, predicate)); LOG(DEBUG) << "Returned from Recursive call CopyDirectoryImpl(" << src_path << ", " << dest_path << ")"; continue; } CF_EXPECTF(IsRegular(src_stat), "File {} must be directory, link, socket, pipe or regular." "{} is none of those", src_path, src_path); CF_EXPECTF(Copy(src_path, dest_path), "Copy from {} to {} failed", src_path, dest_path); auto dest_fd = SharedFD::Open(dest_path, O_RDONLY); CF_EXPECT(dest_fd->IsOpen(), "Failed to open \"" << dest_path << "\""); // Copy the mtime from the src file. The mtime of the disk image files can // be important because we later validate that the disk overlays are not // older than the disk components. const struct timespec times[2] = { #if defined(__APPLE__) src_stat.st_atimespec, src_stat.st_mtimespec #else src_stat.st_atim, src_stat.st_mtim, #endif }; if (dest_fd->Futimens(times) != 0) { return CF_ERR("futimens(\"" << dest_path << "\", ...) failed: " << dest_fd->StrError()); } } return {}; } /* * Returns Realpath(path) if successful, or the absolute path of "path" * * If emulating absolute path fails, "path" is returned as is. */ std::string RealpathOrSelf(const std::string& path) { std::string output; if (android::base::Realpath(path, &output)) { return output; } struct InputPathForm input_form { .path_to_convert = path, .follow_symlink = true, }; auto absolute_path = EmulateAbsolutePath(input_form); return absolute_path.ok() ? *absolute_path : path; } } // namespace Result CopyDirectoryRecursively( const std::string& src_dir_path, const std::string& dest_dir_path, const bool verify_dest_dir_empty, std::function predicate) { CF_EXPECTF(FileExists(src_dir_path), "A file/directory \"{}\" does not exist.", src_dir_path); CF_EXPECTF(DirectoryExists(src_dir_path), "\"{}\" is not a directory.", src_dir_path); if (verify_dest_dir_empty) { CF_EXPECTF(!FileExists(dest_dir_path, /* follow symlink */ false), "Delete the destination directory \"{}\" first", dest_dir_path); } std::string dest_final_target = RealpathOrSelf(dest_dir_path); std::string src_final_target = RealpathOrSelf(src_dir_path); if (dest_final_target == src_final_target) { LOG(DEBUG) << "\"" << src_dir_path << "\" and \"" << dest_dir_path << "\" are effectively the same."; return {}; } LOG(INFO) << "Copy from \"" << src_final_target << "\" to \"" << dest_final_target << "\""; /** * On taking snapshot, we should delete dest_dir first. On Restoring, * we don't delete the runtime directory, eventually. We could, however, * start with deleting it. */ CF_EXPECT(CopyDirectoryImpl(src_final_target, dest_final_target, predicate)); return {}; } Result InstanceGuestSnapshotPath(const Json::Value& meta_json, const std::string& instance_id) { CF_EXPECTF(meta_json.isMember(kSnapshotPathField), "The given json is missing : {}", kSnapshotPathField); const std::string snapshot_path = meta_json[kSnapshotPathField].asString(); const std::vector guest_snapshot_path_selectors{ kGuestSnapshotField, instance_id}; const auto guest_snapshot_dir = CF_EXPECTF( GetValue(meta_json, guest_snapshot_path_selectors), "root[\"{}\"][\"{}\"] is missing in \"{}\"", kGuestSnapshotField, instance_id, kMetaInfoJsonFileName); auto snapshot_path_direct_parent = snapshot_path + "/" + guest_snapshot_dir; LOG(DEBUG) << "Returning snapshot path : " << snapshot_path_direct_parent; return snapshot_path_direct_parent; } Result CreateMetaInfo(const CuttlefishConfig& cuttlefish_config, const std::string& snapshot_path) { Json::Value meta_info; meta_info[kSnapshotPathField] = snapshot_path; const auto cuttlefish_home = StringFromEnv("HOME", ""); CF_EXPECT(!cuttlefish_home.empty(), "\"HOME\" environment variable must be set."); meta_info[kCfHomeField] = cuttlefish_home; const auto instances = cuttlefish_config.Instances(); // "id" -> relative path of instance_dir from cuttlefish_home // + kGuestSnapshotField // e.g. "2" -> cuttlefish/instances/cvd-2/guest_snapshot std::unordered_map id_to_relative_guest_snapshot_dir; for (const auto& instance : instances) { const std::string instance_snapshot_dir = instance.instance_dir() + "/" + kGuestSnapshotField; std::string_view sv_relative_path(instance_snapshot_dir); CF_EXPECTF(android::base::ConsumePrefix(&sv_relative_path, cuttlefish_home), "Instance Guest Snapshot Directory \"{}\"" "is not a subdirectory of \"{}\"", instance_snapshot_dir, cuttlefish_home); if (!sv_relative_path.empty() && sv_relative_path.at(0) == '/') { sv_relative_path.remove_prefix(1); } id_to_relative_guest_snapshot_dir[instance.id()] = std::string(sv_relative_path); } Json::Value snapshot_mapping; // 2 -> cuttlefish/instances/cvd-2 // relative path to cuttlefish_home for (const auto& [id_str, relative_guest_snapshot_dir] : id_to_relative_guest_snapshot_dir) { snapshot_mapping[id_str] = relative_guest_snapshot_dir; } meta_info[kGuestSnapshotField] = snapshot_mapping; return meta_info; } std::string SnapshotMetaJsonPath(const std::string& snapshot_path) { return snapshot_path + "/" + kMetaInfoJsonFileName; } Result LoadMetaJson(const std::string& snapshot_path) { auto meta_json_path = SnapshotMetaJsonPath(snapshot_path); auto meta_json = CF_EXPECT(LoadFromFile(meta_json_path)); return meta_json; } Result> GuestSnapshotDirectories( const std::string& snapshot_path) { auto meta_json = CF_EXPECT(LoadMetaJson(snapshot_path)); CF_EXPECT(meta_json.isMember(kGuestSnapshotField)); const auto& guest_snapshot_dir_jsons = meta_json[kGuestSnapshotField]; std::vector id_strs = guest_snapshot_dir_jsons.getMemberNames(); std::vector guest_snapshot_paths; for (const auto& id_str : id_strs) { CF_EXPECT(guest_snapshot_dir_jsons.isMember(id_str)); std::string path_suffix = guest_snapshot_dir_jsons[id_str].asString(); guest_snapshot_paths.push_back(snapshot_path + "/" + path_suffix); } return guest_snapshot_paths; } } // namespace cuttlefish