sandboxed-api/sandboxed_api/util/fileops.cc
Christian Blichmann dbaf95c724 Move utility code into sandboxed_api/util
This change should make it less confusing where utility code comes from.
Having it in two places made sense when we were debating whether to publish
Sandbox2 separately, but not any longer.

Follow-up changes will move `sandbox2/util.h` and rename the remaining
`sandbox2/util` folder.

PiperOrigin-RevId: 351601640
Change-Id: I6256845261f610e590c25e2c59851cc51da2d778
2021-01-13 09:25:52 -08:00

302 lines
8.5 KiB
C++

// Copyright 2019 Google LLC
//
// 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
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// 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.
#include "sandboxed_api/util/fileops.h"
#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"
#include "sandboxed_api/util/strerror.h"
namespace sapi::file_util::fileops {
FDCloser::~FDCloser() { Close(); }
bool FDCloser::Close() {
int fd = Release();
if (fd == kCanonicalInvalidFd) {
return false;
}
return close(fd) == 0 || errno == EINTR;
}
int FDCloser::Release() {
int ret = fd_;
fd_ = kCanonicalInvalidFd;
return ret;
}
bool GetCWD(std::string* result) {
// Calling getcwd() with a nullptr buffer is a commonly implemented extension.
std::unique_ptr<char, void (*)(char*)> cwd(getcwd(nullptr, 0),
[](char* p) { free(p); });
if (!cwd) {
return false;
}
*result = cwd.get();
return true;
}
std::string GetCWD() {
std::string cwd;
GetCWD(&cwd);
return cwd;
}
// Makes a path absolute with respect to base. Returns true on success. Result
// may be an alias of base or filename.
bool MakeAbsolute(const std::string& filename, const std::string& base,
std::string* result) {
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) {
// Point idx at the last non-slash in the string. This should mark the last
// character of the base name.
auto idx = file.find_last_not_of('/');
// If no non-slash is found, we have all slashes or an empty string. Return
// 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
// just return the empty string.
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
? path
: absl::ClippedSubstr(path, last_slash + 1));
}
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;
}
bool ListDirectoryEntries(const std::string& directory,
std::vector<std::string>* entries,
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;
}
struct dirent64* entry{};
errno = 0;
int bufferlen =
std::max<int>(offsetof(struct dirent64, d_name) +
pathconf(directory.c_str(), _PC_NAME_MAX) + 1,
sizeof(struct dirent64));
std::unique_ptr<struct dirent64, void (*)(struct dirent64*)> dirent_buffer{
static_cast<struct dirent64*>(malloc(bufferlen)),
[](struct dirent64* p) { free(p); }};
while (readdir64_r(dir.get(), dirent_buffer.get(), &entry) == 0 && entry) {
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;
}
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;
}
bool CopyFile(const std::string& old_path, const std::string& new_path,
int new_mode) {
{
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();
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;
}
} // namespace sapi::file_util::fileops