• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "host/libs/command_util/snapshot_utils.h"
18 
19 #include <unistd.h>
20 #include <utime.h>
21 
22 #include <cstdlib>
23 #include <cstring>
24 #include <string>
25 #include <unordered_map>
26 
27 #include <android-base/file.h>
28 #include <android-base/logging.h>
29 #include <android-base/strings.h>
30 
31 #include "common/libs/fs/shared_fd.h"
32 #include "common/libs/utils/environment.h"
33 #include "common/libs/utils/files.h"
34 #include "common/libs/utils/json.h"
35 #include "common/libs/utils/result.h"
36 
37 namespace cuttlefish {
38 namespace {
39 
IsFifo(const struct stat & file_stat)40 bool IsFifo(const struct stat& file_stat) {
41   return S_ISFIFO(file_stat.st_mode);
42 }
43 
IsSocket(const struct stat & file_stat)44 bool IsSocket(const struct stat& file_stat) {
45   return S_ISSOCK(file_stat.st_mode);
46 }
47 
IsSymlink(const struct stat & file_stat)48 bool IsSymlink(const struct stat& file_stat) {
49   return S_ISLNK(file_stat.st_mode);
50 }
51 
IsRegular(const struct stat & file_stat)52 bool IsRegular(const struct stat& file_stat) {
53   return S_ISREG(file_stat.st_mode);
54 }
55 
56 // assumes that src_dir_path and dest_dir_path exist and both are
57 // existing directories or links to the directories. Also they are
58 // different directories.
CopyDirectoryImpl(const std::string & src_dir_path,const std::string & dest_dir_path,const std::function<bool (const std::string &)> & predicate)59 Result<void> CopyDirectoryImpl(
60     const std::string& src_dir_path, const std::string& dest_dir_path,
61     const std::function<bool(const std::string&)>& predicate) {
62   // create an empty dest_dir_path with the same permission as src_dir_path
63   // and then, recursively copy the contents
64   LOG(DEBUG) << "Making sure " << dest_dir_path
65              << " exists and is effectively a directory.";
66   CF_EXPECTF(EnsureDirectoryExists(dest_dir_path),
67              "Directory {} cannot to be created; it does not exist, either.",
68              dest_dir_path);
69   const auto src_contents = CF_EXPECT(DirectoryContents(src_dir_path));
70   for (const auto& src_base_path : src_contents) {
71     if (!predicate(src_dir_path + "/" + src_base_path)) {
72       continue;
73     }
74     std::string src_path = src_dir_path + "/" + src_base_path;
75     std::string dest_path = dest_dir_path + "/" + src_base_path;
76 
77     LOG(DEBUG) << "Handling... " << src_path;
78 
79     struct stat src_stat;
80     CF_EXPECTF(lstat(src_path.data(), &src_stat) != -1, "Failed in lstat({})",
81                src_path);
82     if (IsSymlink(src_stat)) {
83       std::string target;
84       CF_EXPECTF(android::base::Readlink(src_path, &target),
85                  "Readlink failed for {}", src_path);
86       LOG(DEBUG) << "Creating link from " << dest_path << " to " << target;
87       if (FileExists(dest_path, /* follow_symlink */ false)) {
88         CF_EXPECTF(RemoveFile(dest_path), "Failed to unlink/remove file \"{}\"",
89                    dest_path);
90       }
91       CF_EXPECTF(symlink(target.data(), dest_path.data()) == 0,
92                  "Creating symbolic link from {} to {} failed: {}", dest_path,
93                  target, strerror(errno));
94       continue;
95     }
96 
97     if (IsFifo(src_stat) || IsSocket(src_stat)) {
98       LOG(DEBUG) << "Ignoring a named pipe or socket " << src_path;
99       continue;
100     }
101 
102     if (DirectoryExists(src_path)) {
103       LOG(DEBUG) << "Recursively calling CopyDirectoryImpl(" << src_path << ", "
104                  << dest_path << ")";
105       CF_EXPECT(CopyDirectoryImpl(src_path, dest_path, predicate));
106       LOG(DEBUG) << "Returned from Recursive call CopyDirectoryImpl("
107                  << src_path << ", " << dest_path << ")";
108       continue;
109     }
110 
111     CF_EXPECTF(IsRegular(src_stat),
112                "File {} must be directory, link, socket, pipe or regular."
113                "{} is none of those",
114                src_path, src_path);
115 
116     CF_EXPECTF(Copy(src_path, dest_path), "Copy from {} to {} failed", src_path,
117                dest_path);
118 
119     auto dest_fd = SharedFD::Open(dest_path, O_RDONLY);
120     CF_EXPECT(dest_fd->IsOpen(), "Failed to open \"" << dest_path << "\"");
121     // Copy the mtime from the src file. The mtime of the disk image files can
122     // be important because we later validate that the disk overlays are not
123     // older than the disk components.
124     const struct timespec times[2] = {
125 #if defined(__APPLE__)
126       src_stat.st_atimespec,
127       src_stat.st_mtimespec
128 #else
129       src_stat.st_atim,
130       src_stat.st_mtim,
131 #endif
132     };
133     if (dest_fd->Futimens(times) != 0) {
134       return CF_ERR("futimens(\""
135                     << dest_path << "\", ...) failed: " << dest_fd->StrError());
136     }
137   }
138   return {};
139 }
140 
141 /*
142  * Returns Realpath(path) if successful, or the absolute path of "path"
143  *
144  * If emulating absolute path fails, "path" is returned as is.
145  */
RealpathOrSelf(const std::string & path)146 std::string RealpathOrSelf(const std::string& path) {
147   std::string output;
148   if (android::base::Realpath(path, &output)) {
149     return output;
150   }
151   struct InputPathForm input_form {
152     .path_to_convert = path, .follow_symlink = true,
153   };
154   auto absolute_path = EmulateAbsolutePath(input_form);
155   return absolute_path.ok() ? *absolute_path : path;
156 }
157 
158 }  // namespace
159 
CopyDirectoryRecursively(const std::string & src_dir_path,const std::string & dest_dir_path,const bool verify_dest_dir_empty,std::function<bool (const std::string &)> predicate)160 Result<void> CopyDirectoryRecursively(
161     const std::string& src_dir_path, const std::string& dest_dir_path,
162     const bool verify_dest_dir_empty,
163     std::function<bool(const std::string&)> predicate) {
164   CF_EXPECTF(FileExists(src_dir_path),
165              "A file/directory \"{}\" does not exist.", src_dir_path);
166   CF_EXPECTF(DirectoryExists(src_dir_path), "\"{}\" is not a directory.",
167              src_dir_path);
168   if (verify_dest_dir_empty) {
169     CF_EXPECTF(!FileExists(dest_dir_path, /* follow symlink */ false),
170                "Delete the destination directory \"{}\" first", dest_dir_path);
171   }
172 
173   std::string dest_final_target = RealpathOrSelf(dest_dir_path);
174   std::string src_final_target = RealpathOrSelf(src_dir_path);
175   if (dest_final_target == src_final_target) {
176     LOG(DEBUG) << "\"" << src_dir_path << "\" and \"" << dest_dir_path
177                << "\" are effectively the same.";
178     return {};
179   }
180 
181   LOG(INFO) << "Copy from \"" << src_final_target << "\" to \""
182             << dest_final_target << "\"";
183 
184   /**
185    * On taking snapshot, we should delete dest_dir first. On Restoring,
186    * we don't delete the runtime directory, eventually. We could, however,
187    * start with deleting it.
188    */
189   CF_EXPECT(CopyDirectoryImpl(src_final_target, dest_final_target, predicate));
190   return {};
191 }
192 
InstanceGuestSnapshotPath(const Json::Value & meta_json,const std::string & instance_id)193 Result<std::string> InstanceGuestSnapshotPath(const Json::Value& meta_json,
194                                               const std::string& instance_id) {
195   CF_EXPECTF(meta_json.isMember(kSnapshotPathField),
196              "The given json is missing : {}", kSnapshotPathField);
197   const std::string snapshot_path = meta_json[kSnapshotPathField].asString();
198 
199   const std::vector<std::string> guest_snapshot_path_selectors{
200       kGuestSnapshotField, instance_id};
201   const auto guest_snapshot_dir = CF_EXPECTF(
202       GetValue<std::string>(meta_json, guest_snapshot_path_selectors),
203       "root[\"{}\"][\"{}\"] is missing in \"{}\"", kGuestSnapshotField,
204       instance_id, kMetaInfoJsonFileName);
205   auto snapshot_path_direct_parent = snapshot_path + "/" + guest_snapshot_dir;
206   LOG(DEBUG) << "Returning snapshot path : " << snapshot_path_direct_parent;
207   return snapshot_path_direct_parent;
208 }
209 
CreateMetaInfo(const CuttlefishConfig & cuttlefish_config,const std::string & snapshot_path)210 Result<Json::Value> CreateMetaInfo(const CuttlefishConfig& cuttlefish_config,
211                                    const std::string& snapshot_path) {
212   Json::Value meta_info;
213   meta_info[kSnapshotPathField] = snapshot_path;
214 
215   const auto cuttlefish_home = StringFromEnv("HOME", "");
216   CF_EXPECT(!cuttlefish_home.empty(),
217             "\"HOME\" environment variable must be set.");
218   meta_info[kCfHomeField] = cuttlefish_home;
219 
220   const auto instances = cuttlefish_config.Instances();
221   // "id" -> relative path of instance_dir from cuttlefish_home
222   //         + kGuestSnapshotField
223   // e.g. "2" -> cuttlefish/instances/cvd-2/guest_snapshot
224   std::unordered_map<std::string, std::string>
225       id_to_relative_guest_snapshot_dir;
226   for (const auto& instance : instances) {
227     const std::string instance_snapshot_dir =
228         instance.instance_dir() + "/" + kGuestSnapshotField;
229     std::string_view sv_relative_path(instance_snapshot_dir);
230 
231     CF_EXPECTF(android::base::ConsumePrefix(&sv_relative_path, cuttlefish_home),
232                "Instance Guest Snapshot Directory \"{}\""
233                "is not a subdirectory of \"{}\"",
234                instance_snapshot_dir, cuttlefish_home);
235     if (!sv_relative_path.empty() && sv_relative_path.at(0) == '/') {
236       sv_relative_path.remove_prefix(1);
237     }
238     id_to_relative_guest_snapshot_dir[instance.id()] =
239         std::string(sv_relative_path);
240   }
241 
242   Json::Value snapshot_mapping;
243   // 2 -> cuttlefish/instances/cvd-2
244   // relative path to cuttlefish_home
245   for (const auto& [id_str, relative_guest_snapshot_dir] :
246        id_to_relative_guest_snapshot_dir) {
247     snapshot_mapping[id_str] = relative_guest_snapshot_dir;
248   }
249   meta_info[kGuestSnapshotField] = snapshot_mapping;
250   return meta_info;
251 }
252 
SnapshotMetaJsonPath(const std::string & snapshot_path)253 std::string SnapshotMetaJsonPath(const std::string& snapshot_path) {
254   return snapshot_path + "/" + kMetaInfoJsonFileName;
255 }
256 
LoadMetaJson(const std::string & snapshot_path)257 Result<Json::Value> LoadMetaJson(const std::string& snapshot_path) {
258   auto meta_json_path = SnapshotMetaJsonPath(snapshot_path);
259   auto meta_json = CF_EXPECT(LoadFromFile(meta_json_path));
260   return meta_json;
261 }
262 
GuestSnapshotDirectories(const std::string & snapshot_path)263 Result<std::vector<std::string>> GuestSnapshotDirectories(
264     const std::string& snapshot_path) {
265   auto meta_json = CF_EXPECT(LoadMetaJson(snapshot_path));
266   CF_EXPECT(meta_json.isMember(kGuestSnapshotField));
267   const auto& guest_snapshot_dir_jsons = meta_json[kGuestSnapshotField];
268   std::vector<std::string> id_strs = guest_snapshot_dir_jsons.getMemberNames();
269   std::vector<std::string> guest_snapshot_paths;
270   for (const auto& id_str : id_strs) {
271     CF_EXPECT(guest_snapshot_dir_jsons.isMember(id_str));
272     std::string path_suffix = guest_snapshot_dir_jsons[id_str].asString();
273     guest_snapshot_paths.push_back(snapshot_path + "/" + path_suffix);
274   }
275   return guest_snapshot_paths;
276 }
277 
278 }  // namespace cuttlefish
279