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