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 <regex>
23 #include <vector>
24 
25 #include <android-base/logging.h>
26 #include <android-base/strings.h>
27 
28 #include "common/libs/utils/files.h"
29 #include "common/libs/utils/result.h"
30 #include "common/libs/utils/subprocess.h"
31 #include "host/commands/assemble_cvd/flags.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(cpp_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)) {
75       // If EEXIST or ENOTEMPTY, probably because a file was preserved
76       return CF_ERRNO("Could not rmdir \"" << path << "\"");
77     }
78   }
79   return {};
80 }
81 
CleanPriorFiles(const std::vector<std::string> & paths,const std::set<std::string> & preserving)82 Result<void> CleanPriorFiles(const std::vector<std::string>& paths,
83                              const std::set<std::string>& preserving) {
84   std::vector<std::string> prior_dirs;
85   std::vector<std::string> prior_files;
86   for (const auto& path : paths) {
87     struct stat statbuf;
88     if (stat(path.c_str(), &statbuf) < 0) {
89       if (errno == ENOENT) {
90         continue;  // it doesn't exist yet, so there is no work to do
91       }
92       return CF_ERRNO("Could not stat \"" << path << "\"");
93     }
94     bool is_directory = (statbuf.st_mode & S_IFMT) == S_IFDIR;
95     (is_directory ? prior_dirs : prior_files).emplace_back(path);
96   }
97   LOG(DEBUG) << fmt::format("Prior dirs: {}", fmt::join(prior_dirs, ", "));
98   LOG(DEBUG) << fmt::format("Prior files: {}", fmt::join(prior_files, ", "));
99 
100   if (prior_dirs.size() > 0 || prior_files.size() > 0) {
101     Command lsof("lsof");
102     lsof.AddParameter("-t");
103     for (const auto& prior_dir : prior_dirs) {
104       lsof.AddParameter("+D").AddParameter(prior_dir);
105     }
106     for (const auto& prior_file : prior_files) {
107       lsof.AddParameter(prior_file);
108     }
109 
110     std::string lsof_out;
111     std::string lsof_err;
112     int rval =
113         RunWithManagedStdio(std::move(lsof), nullptr, &lsof_out, &lsof_err);
114     if (rval != 0 && !lsof_err.empty()) {
115       LOG(ERROR) << "Failed to run `lsof`, received message: " << lsof_err;
116     }
117     auto pids = android::base::Split(lsof_out, "\n");
118     CF_EXPECTF(
119         lsof_out.empty(),
120         "Instance directory files in use. Try `cvd reset`? Observed PIDs: {}",
121         fmt::join(pids, ", "));
122   }
123 
124   for (const auto& path : paths) {
125     CF_EXPECT(CleanPriorFiles(path, preserving),
126               "CleanPriorFiles failed for \"" << path << "\"");
127   }
128   return {};
129 }
130 
131 } // namespace
132 
CleanPriorFiles(const std::set<std::string> & preserving,const std::vector<std::string> & clean_dirs)133 Result<void> CleanPriorFiles(const std::set<std::string>& preserving,
134                              const std::vector<std::string>& clean_dirs) {
135   std::vector<std::string> paths = {
136       // The environment file
137       GetCuttlefishEnvPath(),
138       // The global link to the config file
139       GetGlobalConfigFileLink(),
140   };
141   paths.insert(paths.end(), clean_dirs.begin(), clean_dirs.end());
142   using android::base::Join;
143   CF_EXPECT(CleanPriorFiles(paths, preserving),
144             "CleanPriorFiles("
145                 << "paths = {" << Join(paths, ", ") << "}, "
146                 << "preserving = {" << Join(preserving, ", ") << "}) failed");
147   return {};
148 }
149 
150 } // namespace cuttlefish
151