1 /*
2 * Copyright (C) 2020 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 #include "host/commands/assemble_cvd/clean.h"
17
18 #include <dirent.h>
19 #include <errno.h>
20 #include <sys/stat.h>
21
22 #include <vector>
23
24 #include <android-base/file.h>
25 #include <android-base/logging.h>
26 #include <android-base/strings.h>
27
28 #include "common/libs/utils/in_sandbox.h"
29 #include "common/libs/utils/result.h"
30 #include "common/libs/utils/subprocess.h"
31 #include "host/libs/config/config_utils.h"
32
33 namespace cuttlefish {
34 namespace {
35
CleanPriorFiles(const std::string & path,const std::set<std::string> & preserving)36 Result<void> CleanPriorFiles(const std::string& path,
37 const std::set<std::string>& preserving) {
38 if (preserving.count(android::base::Basename(path))) {
39 LOG(DEBUG) << "Preserving: " << path;
40 return {};
41 }
42 struct stat statbuf;
43 if (lstat(path.c_str(), &statbuf) < 0) {
44 int error_num = errno;
45 if (error_num == ENOENT) {
46 return {};
47 } else {
48 return CF_ERRNO("Could not stat \"" << path);
49 }
50 }
51 if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
52 LOG(DEBUG) << "Deleting: " << path;
53 if (unlink(path.c_str()) < 0) {
54 return CF_ERRNO("Could not unlink \"" << path << "\"");
55 }
56 return {};
57 }
58 std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(path.c_str()), closedir);
59 if (!dir) {
60 return CF_ERRNO("Could not clean \"" << path << "\"");
61 }
62 for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
63 std::string entity_name(entity->d_name);
64 if (entity_name == "." || entity_name == "..") {
65 continue;
66 }
67 std::string entity_path = path + "/" + entity_name;
68 CF_EXPECT(CleanPriorFiles(entity_path.c_str(), preserving),
69 "CleanPriorFiles for \""
70 << path << "\" failed on recursing into \"" << entity_path
71 << "\"");
72 }
73 if (rmdir(path.c_str()) < 0) {
74 if (!(errno == EEXIST || errno == ENOTEMPTY || errno == EROFS ||
75 errno == EBUSY)) {
76 // If EEXIST or ENOTEMPTY, probably because a file was preserved. EROFS
77 // or EBUSY likely means a bind mount for host-sandboxing mode.
78 return CF_ERRF("Could not rmdir '{}': '{}'", path, strerror(errno));
79 }
80 }
81 return {};
82 }
83
CleanPriorFiles(const std::vector<std::string> & paths,const std::set<std::string> & preserving)84 Result<void> CleanPriorFiles(const std::vector<std::string>& paths,
85 const std::set<std::string>& preserving) {
86 std::vector<std::string> prior_dirs;
87 std::vector<std::string> prior_files;
88 for (const auto& path : paths) {
89 struct stat statbuf;
90 if (stat(path.c_str(), &statbuf) < 0) {
91 if (errno == ENOENT) {
92 continue; // it doesn't exist yet, so there is no work to do
93 }
94 return CF_ERRNO("Could not stat \"" << path << "\"");
95 }
96 bool is_directory = (statbuf.st_mode & S_IFMT) == S_IFDIR;
97 (is_directory ? prior_dirs : prior_files).emplace_back(path);
98 }
99 LOG(DEBUG) << fmt::format("Prior dirs: {}", fmt::join(prior_dirs, ", "));
100 LOG(DEBUG) << fmt::format("Prior files: {}", fmt::join(prior_files, ", "));
101
102 // TODO(schuffelen): Fix logic for host-sandboxing mode.
103 if (!InSandbox() && (prior_dirs.size() > 0 || prior_files.size() > 0)) {
104 Command lsof("lsof");
105 lsof.AddParameter("-t");
106 for (const auto& prior_dir : prior_dirs) {
107 lsof.AddParameter("+D").AddParameter(prior_dir);
108 }
109 for (const auto& prior_file : prior_files) {
110 lsof.AddParameter(prior_file);
111 }
112
113 std::string lsof_out;
114 std::string lsof_err;
115 int rval =
116 RunWithManagedStdio(std::move(lsof), nullptr, &lsof_out, &lsof_err);
117 if (rval != 0 && !lsof_err.empty()) {
118 LOG(ERROR) << "Failed to run `lsof`, received message: " << lsof_err;
119 }
120 auto pids = android::base::Split(lsof_out, "\n");
121 CF_EXPECTF(
122 lsof_out.empty(),
123 "Instance directory files in use. Try `cvd reset`? Observed PIDs: {}",
124 fmt::join(pids, ", "));
125 }
126
127 for (const auto& path : paths) {
128 CF_EXPECT(CleanPriorFiles(path, preserving),
129 "CleanPriorFiles failed for \"" << path << "\"");
130 }
131 return {};
132 }
133
134 } // namespace
135
CleanPriorFiles(const std::set<std::string> & preserving,const std::vector<std::string> & clean_dirs)136 Result<void> CleanPriorFiles(const std::set<std::string>& preserving,
137 const std::vector<std::string>& clean_dirs) {
138 std::vector<std::string> paths = {
139 // The global link to the config file
140 GetGlobalConfigFileLink(),
141 };
142 paths.insert(paths.end(), clean_dirs.begin(), clean_dirs.end());
143 using android::base::Join;
144 CF_EXPECT(CleanPriorFiles(paths, preserving),
145 "CleanPriorFiles("
146 << "paths = {" << Join(paths, ", ") << "}, "
147 << "preserving = {" << Join(preserving, ", ") << "}) failed");
148 return {};
149 }
150
151 } // namespace cuttlefish
152