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