xref: /aosp_15_r20/external/sandboxed-api/sandboxed_api/util/fileops.cc (revision ec63e07ab9515d95e79c211197c445ef84cefa6a)
1 // Copyright 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "sandboxed_api/util/fileops.h"
16 
17 #include <dirent.h>    // DIR
18 #include <limits.h>    // PATH_MAX
19 #include <sys/stat.h>  // stat64
20 #include <unistd.h>
21 
22 #include <cerrno>
23 #include <cstdlib>
24 #include <fstream>
25 #include <memory>
26 #include <string>
27 #include <utility>
28 #include <vector>
29 
30 #include "absl/strings/str_cat.h"
31 #include "absl/strings/string_view.h"
32 #include "absl/strings/strip.h"
33 #include "sandboxed_api/util/strerror.h"
34 
35 namespace sapi::file_util::fileops {
36 
~FDCloser()37 FDCloser::~FDCloser() { Close(); }
38 
Close()39 bool FDCloser::Close() {
40   int fd = Release();
41   if (fd == kCanonicalInvalidFd) {
42     return false;
43   }
44   return close(fd) == 0 || errno == EINTR;
45 }
46 
Release()47 int FDCloser::Release() {
48   int ret = fd_;
49   fd_ = kCanonicalInvalidFd;
50   return ret;
51 }
52 
GetCWD(std::string * result)53 bool GetCWD(std::string* result) {
54   // Calling getcwd() with a nullptr buffer is a commonly implemented extension.
55   std::unique_ptr<char, void (*)(char*)> cwd(getcwd(nullptr, 0),
56                                              [](char* p) { free(p); });
57   if (!cwd) {
58     return false;
59   }
60   *result = cwd.get();
61   return true;
62 }
63 
GetCWD()64 std::string GetCWD() {
65   std::string cwd;
66   GetCWD(&cwd);
67   return cwd;
68 }
69 
70 // Makes a path absolute with respect to base. Returns true on success. Result
71 // may be an alias of base or filename.
MakeAbsolute(const std::string & filename,const std::string & base,std::string * result)72 bool MakeAbsolute(const std::string& filename, const std::string& base,
73                   std::string* result) {
74   if (filename.empty()) {
75     return false;
76   }
77   if (filename[0] == '/') {
78     if (result != &filename) {
79       *result = filename;
80     }
81     return true;
82   }
83 
84   std::string actual_base = base;
85   if (actual_base.empty() && !GetCWD(&actual_base)) {
86     return false;
87   }
88 
89   actual_base = std::string(absl::StripSuffix(actual_base, "/"));
90 
91   if (filename == ".") {
92     if (actual_base.empty()) {
93       *result = "/";
94     } else {
95       *result = actual_base;
96     }
97   } else {
98     *result = actual_base + "/" + filename;
99   }
100   return true;
101 }
102 
MakeAbsolute(const std::string & filename,const std::string & base)103 std::string MakeAbsolute(const std::string& filename, const std::string& base) {
104   std::string result;
105   return !MakeAbsolute(filename, base, &result) ? "" : result;
106 }
107 
RemoveLastPathComponent(const std::string & file,std::string * output)108 bool RemoveLastPathComponent(const std::string& file, std::string* output) {
109   // Point idx at the last non-slash in the string. This should mark the last
110   // character of the base name.
111   auto idx = file.find_last_not_of('/');
112   // If no non-slash is found, we have all slashes or an empty string. Return
113   // the appropriate value and false to indicate there was no path component to
114   // remove.
115   if (idx == std::string::npos) {
116     if (file.empty()) {
117       output->clear();
118     } else {
119       *output = "/";
120     }
121     return false;
122   }
123 
124   // Otherwise, we have to trim the last path component. Find where it begins.
125   // Point idx at the last slash before the base name.
126   idx = file.find_last_of('/', idx);
127   // If we don't find a slash, then we have something of the form "file/*", so
128   // just return the empty string.
129   if (idx == std::string::npos) {
130     output->clear();
131   } else {
132     // Then find the last character that isn't a slash, in case the slash is
133     // repeated.
134     // Point idx at the character at the last character of the path component
135     // that precedes the base name. I.e. if you have /foo/bar, idx will point
136     // at the last "o" in foo. We remove everything after this index.
137     idx = file.find_last_not_of('/', idx);
138     // If none is found, then set idx to 0 so the below code will leave the
139     // first slash.
140     if (idx == std::string::npos) idx = 0;
141     // This is an optimization to prevent a copy if output and file are
142     // aliased.
143     if (&file == output) {
144       output->erase(idx + 1, std::string::npos);
145     } else {
146       output->assign(file, 0, idx + 1);
147     }
148   }
149   return true;
150 }
151 
ReadLink(const std::string & filename)152 std::string ReadLink(const std::string& filename) {
153   std::string result(PATH_MAX, '\0');
154   const auto size = readlink(filename.c_str(), &result[0], PATH_MAX);
155   if (size < 0) {
156     return "";
157   }
158   result.resize(size);
159   return result;
160 }
161 
ReadLinkAbsolute(const std::string & filename,std::string * result)162 bool ReadLinkAbsolute(const std::string& filename, std::string* result) {
163   std::string base_dir;
164 
165   // Do this first. Otherwise, if &filename == result, we won't be able to find
166   // it after the ReadLink call.
167   RemoveLastPathComponent(filename, &base_dir);
168 
169   std::string link = ReadLink(filename);
170   if (link.empty()) {
171     return false;
172   }
173   *result = std::move(link);
174 
175   // Need two calls in case filename itself is relative.
176   return MakeAbsolute(MakeAbsolute(*result, base_dir), "", result);
177 }
178 
Basename(absl::string_view path)179 std::string Basename(absl::string_view path) {
180   const auto last_slash = path.find_last_of('/');
181   return std::string(last_slash == std::string::npos
182                          ? path
183                          : absl::ClippedSubstr(path, last_slash + 1));
184 }
185 
StripBasename(absl::string_view path)186 std::string StripBasename(absl::string_view path) {
187   const auto last_slash = path.find_last_of('/');
188   if (last_slash == std::string::npos) {
189     return "";
190   }
191   if (last_slash == 0) {
192     return "/";
193   }
194   return std::string(path.substr(0, last_slash));
195 }
196 
Exists(const std::string & filename,bool fully_resolve)197 bool Exists(const std::string& filename, bool fully_resolve) {
198   struct stat64 st;
199   return (fully_resolve ? stat64(filename.c_str(), &st)
200                         : lstat64(filename.c_str(), &st)) != -1;
201 }
202 
ListDirectoryEntries(const std::string & directory,std::vector<std::string> * entries,std::string * error)203 bool ListDirectoryEntries(const std::string& directory,
204                           std::vector<std::string>* entries,
205                           std::string* error) {
206   errno = 0;
207   std::unique_ptr<DIR, void (*)(DIR*)> dir{opendir(directory.c_str()),
208                                            [](DIR* d) { closedir(d); }};
209   if (!dir) {
210     *error = absl::StrCat("opendir(", directory, "): ", StrError(errno));
211     return false;
212   }
213 
214   errno = 0;
215   struct dirent* entry;
216   while ((entry = readdir(dir.get())) != nullptr) {
217     const std::string name(entry->d_name);
218     if (name != "." && name != "..") {
219       entries->push_back(name);
220     }
221   }
222   if (errno != 0) {
223     *error = absl::StrCat("readdir(", directory, "): ", StrError(errno));
224     return false;
225   }
226   return true;
227 }
228 
CreateDirectoryRecursively(const std::string & path,int mode)229 bool CreateDirectoryRecursively(const std::string& path, int mode) {
230   if (mkdir(path.c_str(), mode) == 0 || errno == EEXIST) {
231     return true;
232   }
233 
234   // We couldn't create the dir for reasons we can't handle.
235   if (errno != ENOENT) {
236     return false;
237   }
238 
239   // The ENOENT case, the parent directory doesn't exist yet.
240   // Let's create it.
241   const std::string dir = StripBasename(path);
242   if (dir == "/" || dir.empty()) {
243     return false;
244   }
245   if (!CreateDirectoryRecursively(dir, mode)) {
246     return false;
247   }
248 
249   // Now the parent dir exists, retry creating the directory.
250   return mkdir(path.c_str(), mode) == 0;
251 }
252 
DeleteRecursively(const std::string & filename)253 bool DeleteRecursively(const std::string& filename) {
254   std::vector<std::string> to_delete;
255   to_delete.push_back(filename);
256 
257   while (!to_delete.empty()) {
258     const std::string delfile = to_delete.back();
259 
260     struct stat64 st;
261     if (lstat64(delfile.c_str(), &st) == -1) {
262       if (errno == ENOENT) {
263         // Most likely the first file. Either that or someone is deleting the
264         // files out from under us.
265         to_delete.pop_back();
266         continue;
267       }
268       return false;
269     }
270 
271     if (S_ISDIR(st.st_mode)) {
272       if (rmdir(delfile.c_str()) != 0 && errno != ENOENT) {
273         if (errno == ENOTEMPTY) {
274           std::string error;
275           std::vector<std::string> entries;
276           if (!ListDirectoryEntries(delfile, &entries, &error)) {
277             return false;
278           }
279           for (const auto& entry : entries) {
280             to_delete.push_back(delfile + "/" + entry);
281           }
282         } else {
283           return false;
284         }
285       } else {
286         to_delete.pop_back();
287       }
288     } else {
289       if (unlink(delfile.c_str()) != 0 && errno != ENOENT) {
290         return false;
291       }
292       to_delete.pop_back();
293     }
294   }
295   return true;
296 }
297 
CopyFile(const std::string & old_path,const std::string & new_path,int new_mode)298 bool CopyFile(const std::string& old_path, const std::string& new_path,
299               int new_mode) {
300   {
301     std::ifstream input(old_path, std::ios_base::binary);
302     std::ofstream output(new_path,
303                          std::ios_base::trunc | std::ios_base::binary);
304     output << input.rdbuf();
305     output.close();
306     if (!input || !output) {
307       return false;
308     }
309   }
310   return chmod(new_path.c_str(), new_mode) == 0;
311 }
312 
WriteToFD(int fd,const char * data,size_t size)313 bool WriteToFD(int fd, const char* data, size_t size) {
314   while (size > 0) {
315     ssize_t result = TEMP_FAILURE_RETRY(write(fd, data, size));
316     if (result <= 0) {
317       return false;
318     }
319     size -= result;
320     data += result;
321   }
322   return true;
323 }
324 
325 }  // namespace sapi::file_util::fileops
326