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