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