2020-01-17 21:05:03 +08:00
|
|
|
// Copyright 2019 Google LLC
|
2019-03-19 00:21:48 +08:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
2022-01-28 17:38:27 +08:00
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
2019-03-19 00:21:48 +08:00
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2021-01-14 01:25:25 +08:00
|
|
|
#include "sandboxed_api/util/fileops.h"
|
2019-03-19 00:21:48 +08:00
|
|
|
|
|
|
|
#include <dirent.h> // DIR
|
|
|
|
#include <limits.h> // PATH_MAX
|
|
|
|
#include <sys/stat.h> // stat64
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include <fstream>
|
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
#include "absl/strings/str_cat.h"
|
|
|
|
#include "absl/strings/strip.h"
|
2021-01-14 01:25:25 +08:00
|
|
|
#include "sandboxed_api/util/strerror.h"
|
2019-03-19 00:21:48 +08:00
|
|
|
|
2021-01-14 01:25:25 +08:00
|
|
|
namespace sapi::file_util::fileops {
|
2019-03-19 00:21:48 +08:00
|
|
|
|
|
|
|
FDCloser::~FDCloser() { Close(); }
|
|
|
|
|
|
|
|
bool FDCloser::Close() {
|
|
|
|
int fd = Release();
|
2021-01-11 19:33:03 +08:00
|
|
|
if (fd == kCanonicalInvalidFd) {
|
2019-03-19 00:21:48 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return close(fd) == 0 || errno == EINTR;
|
|
|
|
}
|
|
|
|
|
|
|
|
int FDCloser::Release() {
|
|
|
|
int ret = fd_;
|
2021-01-11 19:33:03 +08:00
|
|
|
fd_ = kCanonicalInvalidFd;
|
2019-03-19 00:21:48 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GetCWD(std::string* result) {
|
|
|
|
// Calling getcwd() with a nullptr buffer is a commonly implemented extension.
|
2019-06-25 20:48:56 +08:00
|
|
|
std::unique_ptr<char, void (*)(char*)> cwd(getcwd(nullptr, 0),
|
|
|
|
[](char* p) { free(p); });
|
2019-03-19 00:21:48 +08:00
|
|
|
if (!cwd) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
*result = cwd.get();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-06-25 20:48:56 +08:00
|
|
|
std::string GetCWD() {
|
|
|
|
std::string cwd;
|
|
|
|
GetCWD(&cwd);
|
|
|
|
return cwd;
|
|
|
|
}
|
|
|
|
|
2019-03-19 00:21:48 +08:00
|
|
|
// Makes a path absolute with respect to base. Returns true on success. Result
|
|
|
|
// may be an alias of base or filename.
|
2019-03-26 22:54:02 +08:00
|
|
|
bool MakeAbsolute(const std::string& filename, const std::string& base,
|
|
|
|
std::string* result) {
|
2019-03-19 00:21:48 +08:00
|
|
|
if (filename.empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (filename[0] == '/') {
|
|
|
|
if (result != &filename) {
|
|
|
|
*result = filename;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string actual_base = base;
|
|
|
|
if (actual_base.empty() && !GetCWD(&actual_base)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
actual_base = std::string(absl::StripSuffix(actual_base, "/"));
|
|
|
|
|
|
|
|
if (filename == ".") {
|
|
|
|
if (actual_base.empty()) {
|
|
|
|
*result = "/";
|
|
|
|
} else {
|
|
|
|
*result = actual_base;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*result = actual_base + "/" + filename;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string MakeAbsolute(const std::string& filename, const std::string& base) {
|
|
|
|
std::string result;
|
|
|
|
return !MakeAbsolute(filename, base, &result) ? "" : result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool RemoveLastPathComponent(const std::string& file, std::string* output) {
|
2019-03-26 22:54:02 +08:00
|
|
|
// Point idx at the last non-slash in the string. This should mark the last
|
2019-03-19 00:21:48 +08:00
|
|
|
// character of the base name.
|
|
|
|
auto idx = file.find_last_not_of('/');
|
2019-03-26 22:54:02 +08:00
|
|
|
// If no non-slash is found, we have all slashes or an empty string. Return
|
2019-03-19 00:21:48 +08:00
|
|
|
// the appropriate value and false to indicate there was no path component to
|
|
|
|
// remove.
|
|
|
|
if (idx == std::string::npos) {
|
|
|
|
if (file.empty()) {
|
|
|
|
output->clear();
|
|
|
|
} else {
|
|
|
|
*output = "/";
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we have to trim the last path component. Find where it begins.
|
|
|
|
// Point idx at the last slash before the base name.
|
|
|
|
idx = file.find_last_of('/', idx);
|
|
|
|
// If we don't find a slash, then we have something of the form "file/*", so
|
2019-03-26 22:54:02 +08:00
|
|
|
// just return the empty string.
|
2019-03-19 00:21:48 +08:00
|
|
|
if (idx == std::string::npos) {
|
|
|
|
output->clear();
|
|
|
|
} else {
|
|
|
|
// Then find the last character that isn't a slash, in case the slash is
|
|
|
|
// repeated.
|
|
|
|
// Point idx at the character at the last character of the path component
|
|
|
|
// that precedes the base name. I.e. if you have /foo/bar, idx will point
|
|
|
|
// at the last "o" in foo. We remove everything after this index.
|
|
|
|
idx = file.find_last_not_of('/', idx);
|
|
|
|
// If none is found, then set idx to 0 so the below code will leave the
|
|
|
|
// first slash.
|
|
|
|
if (idx == std::string::npos) idx = 0;
|
|
|
|
// This is an optimization to prevent a copy if output and file are
|
|
|
|
// aliased.
|
|
|
|
if (&file == output) {
|
|
|
|
output->erase(idx + 1, std::string::npos);
|
|
|
|
} else {
|
|
|
|
output->assign(file, 0, idx + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string ReadLink(const std::string& filename) {
|
|
|
|
std::string result(PATH_MAX, '\0');
|
|
|
|
const auto size = readlink(filename.c_str(), &result[0], PATH_MAX);
|
|
|
|
if (size < 0) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
result.resize(size);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ReadLinkAbsolute(const std::string& filename, std::string* result) {
|
|
|
|
std::string base_dir;
|
|
|
|
|
|
|
|
// Do this first. Otherwise, if &filename == result, we won't be able to find
|
|
|
|
// it after the ReadLink call.
|
|
|
|
RemoveLastPathComponent(filename, &base_dir);
|
|
|
|
|
|
|
|
std::string link = ReadLink(filename);
|
|
|
|
if (link.empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
*result = std::move(link);
|
|
|
|
|
|
|
|
// Need two calls in case filename itself is relative.
|
|
|
|
return MakeAbsolute(MakeAbsolute(*result, base_dir), "", result);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Basename(absl::string_view path) {
|
|
|
|
const auto last_slash = path.find_last_of('/');
|
|
|
|
return std::string(last_slash == std::string::npos
|
2019-03-26 22:54:02 +08:00
|
|
|
? path
|
|
|
|
: absl::ClippedSubstr(path, last_slash + 1));
|
2019-03-19 00:21:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string StripBasename(absl::string_view path) {
|
|
|
|
const auto last_slash = path.find_last_of('/');
|
|
|
|
if (last_slash == std::string::npos) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
if (last_slash == 0) {
|
|
|
|
return "/";
|
|
|
|
}
|
|
|
|
return std::string(path.substr(0, last_slash));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Exists(const std::string& filename, bool fully_resolve) {
|
|
|
|
struct stat64 st;
|
|
|
|
return (fully_resolve ? stat64(filename.c_str(), &st)
|
|
|
|
: lstat64(filename.c_str(), &st)) != -1;
|
|
|
|
}
|
|
|
|
|
2019-03-26 22:54:02 +08:00
|
|
|
bool ListDirectoryEntries(const std::string& directory,
|
|
|
|
std::vector<std::string>* entries,
|
2019-03-19 00:21:48 +08:00
|
|
|
std::string* error) {
|
|
|
|
errno = 0;
|
|
|
|
std::unique_ptr<DIR, void (*)(DIR*)> dir{opendir(directory.c_str()),
|
|
|
|
[](DIR* d) { closedir(d); }};
|
|
|
|
if (!dir) {
|
|
|
|
*error = absl::StrCat("opendir(", directory, "): ", StrError(errno));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
errno = 0;
|
2022-01-21 23:48:14 +08:00
|
|
|
struct dirent* entry;
|
|
|
|
while ((entry = readdir(dir.get())) != nullptr) {
|
2019-03-19 00:21:48 +08:00
|
|
|
const std::string name(entry->d_name);
|
|
|
|
if (name != "." && name != "..") {
|
|
|
|
entries->push_back(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (errno != 0) {
|
|
|
|
*error = absl::StrCat("readdir(", directory, "): ", StrError(errno));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-02-17 02:43:25 +08:00
|
|
|
bool CreateDirectoryRecursively(const std::string& path, int mode) {
|
|
|
|
if (mkdir(path.c_str(), mode) == 0 || errno == EEXIST) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We couldn't create the dir for reasons we can't handle.
|
|
|
|
if (errno != ENOENT) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The ENOENT case, the parent directory doesn't exist yet.
|
|
|
|
// Let's create it.
|
|
|
|
const std::string dir = StripBasename(path);
|
|
|
|
if (dir == "/" || dir.empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!CreateDirectoryRecursively(dir, mode)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now the parent dir exists, retry creating the directory.
|
|
|
|
return mkdir(path.c_str(), mode) == 0;
|
|
|
|
}
|
|
|
|
|
2019-03-19 00:21:48 +08:00
|
|
|
bool DeleteRecursively(const std::string& filename) {
|
|
|
|
std::vector<std::string> to_delete;
|
|
|
|
to_delete.push_back(filename);
|
|
|
|
|
|
|
|
while (!to_delete.empty()) {
|
|
|
|
const std::string delfile = to_delete.back();
|
|
|
|
|
|
|
|
struct stat64 st;
|
|
|
|
if (lstat64(delfile.c_str(), &st) == -1) {
|
|
|
|
if (errno == ENOENT) {
|
|
|
|
// Most likely the first file. Either that or someone is deleting the
|
|
|
|
// files out from under us.
|
|
|
|
to_delete.pop_back();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
|
|
if (rmdir(delfile.c_str()) != 0 && errno != ENOENT) {
|
|
|
|
if (errno == ENOTEMPTY) {
|
|
|
|
std::string error;
|
|
|
|
std::vector<std::string> entries;
|
|
|
|
if (!ListDirectoryEntries(delfile, &entries, &error)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (const auto& entry : entries) {
|
|
|
|
to_delete.push_back(delfile + "/" + entry);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
to_delete.pop_back();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (unlink(delfile.c_str()) != 0 && errno != ENOENT) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
to_delete.pop_back();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-03-26 22:54:02 +08:00
|
|
|
bool CopyFile(const std::string& old_path, const std::string& new_path,
|
|
|
|
int new_mode) {
|
2019-03-19 00:21:48 +08:00
|
|
|
{
|
|
|
|
std::ifstream input(old_path, std::ios_base::binary);
|
|
|
|
std::ofstream output(new_path,
|
|
|
|
std::ios_base::trunc | std::ios_base::binary);
|
|
|
|
output << input.rdbuf();
|
2023-01-23 16:06:19 +08:00
|
|
|
output.close();
|
2019-03-19 00:21:48 +08:00
|
|
|
if (!input || !output) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return chmod(new_path.c_str(), new_mode) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WriteToFD(int fd, const char* data, size_t size) {
|
|
|
|
while (size > 0) {
|
|
|
|
ssize_t result = TEMP_FAILURE_RETRY(write(fd, data, size));
|
|
|
|
if (result <= 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
size -= result;
|
|
|
|
data += result;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-01-14 01:25:25 +08:00
|
|
|
} // namespace sapi::file_util::fileops
|