diff options
Diffstat (limited to 'test/util/fs_util.cc')
-rw-r--r-- | test/util/fs_util.cc | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/test/util/fs_util.cc b/test/util/fs_util.cc new file mode 100644 index 000000000..e7e8be1d8 --- /dev/null +++ b/test/util/fs_util.cc @@ -0,0 +1,585 @@ +// Copyright 2018 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 "test/util/fs_util.h" + +#include <dirent.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "gmock/gmock.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "test/util/cleanup.h" +#include "test/util/file_descriptor.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +namespace { +PosixError WriteContentsToFD(int fd, absl::string_view contents) { + int written = 0; + while (static_cast<absl::string_view::size_type>(written) < contents.size()) { + int wrote = write(fd, contents.data() + written, contents.size() - written); + if (wrote < 0) { + if (errno == EINTR) { + continue; + } + return PosixError( + errno, absl::StrCat("WriteContentsToFD fd: ", fd, " write failure.")); + } + written += wrote; + } + return NoError(); +} +} // namespace + +namespace internal { + +// Given a collection of file paths, append them all together, +// ensuring that the proper path separators are inserted between them. +std::string JoinPathImpl(std::initializer_list<absl::string_view> paths) { + std::string result; + + if (paths.size() != 0) { + // This size calculation is worst-case: it assumes one extra "/" for every + // path other than the first. + size_t total_size = paths.size() - 1; + for (const absl::string_view path : paths) total_size += path.size(); + result.resize(total_size); + + auto begin = result.begin(); + auto out = begin; + bool trailing_slash = false; + for (absl::string_view path : paths) { + if (path.empty()) continue; + if (path.front() == '/') { + if (trailing_slash) { + path.remove_prefix(1); + } + } else { + if (!trailing_slash && out != begin) *out++ = '/'; + } + const size_t this_size = path.size(); + memcpy(&*out, path.data(), this_size); + out += this_size; + trailing_slash = out[-1] == '/'; + } + result.erase(out - begin); + } + return result; +} +} // namespace internal + +// Returns a status or the current working directory. +PosixErrorOr<std::string> GetCWD() { + char buffer[PATH_MAX + 1] = {}; + if (getcwd(buffer, PATH_MAX) == nullptr) { + return PosixError(errno, "GetCWD() failed"); + } + + return std::string(buffer); +} + +PosixErrorOr<struct stat> Stat(absl::string_view path) { + struct stat stat_buf; + int res = stat(std::string(path).c_str(), &stat_buf); + if (res < 0) { + return PosixError(errno, absl::StrCat("stat ", path)); + } + return stat_buf; +} + +PosixErrorOr<bool> Exists(absl::string_view path) { + struct stat stat_buf; + int res = stat(std::string(path).c_str(), &stat_buf); + if (res < 0) { + if (errno == ENOENT) { + return false; + } + return PosixError(errno, absl::StrCat("stat ", path)); + } + return true; +} + +PosixErrorOr<bool> IsDirectory(absl::string_view path) { + ASSIGN_OR_RETURN_ERRNO(struct stat stat_buf, Stat(path)); + if (S_ISDIR(stat_buf.st_mode)) { + return true; + } + + return false; +} + +PosixError Delete(absl::string_view path) { + int res = unlink(std::string(path).c_str()); + if (res < 0) { + return PosixError(errno, absl::StrCat("unlink ", path)); + } + + return NoError(); +} + +PosixError Truncate(absl::string_view path, int length) { + int res = truncate(std::string(path).c_str(), length); + if (res < 0) { + return PosixError(errno, + absl::StrCat("truncate ", path, " to length ", length)); + } + + return NoError(); +} + +PosixError Chmod(absl::string_view path, int mode) { + int res = chmod(std::string(path).c_str(), mode); + if (res < 0) { + return PosixError(errno, absl::StrCat("chmod ", path)); + } + + return NoError(); +} + +PosixError Mkdir(absl::string_view path, int mode) { + int res = mkdir(std::string(path).c_str(), mode); + if (res < 0) { + return PosixError(errno, absl::StrCat("mkdir ", path, " mode ", mode)); + } + + return NoError(); +} + +PosixError Rmdir(absl::string_view path) { + int res = rmdir(std::string(path).c_str()); + if (res < 0) { + return PosixError(errno, absl::StrCat("rmdir ", path)); + } + + return NoError(); +} + +PosixError SetContents(absl::string_view path, absl::string_view contents) { + ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); + if (!exists) { + return PosixError( + ENOENT, absl::StrCat("SetContents file ", path, " doesn't exist.")); + } + + ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_WRONLY | O_TRUNC)); + return WriteContentsToFD(fd.get(), contents); +} + +// Create a file with the given contents (if it does not already exist with the +// given mode) and then set the contents. +PosixError CreateWithContents(absl::string_view path, + absl::string_view contents, int mode) { + ASSIGN_OR_RETURN_ERRNO( + auto fd, Open(std::string(path), O_WRONLY | O_CREAT | O_TRUNC, mode)); + return WriteContentsToFD(fd.get(), contents); +} + +PosixError GetContents(absl::string_view path, std::string* output) { + ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_RDONLY)); + output->clear(); + + // Keep reading until we hit an EOF or an error. + return GetContentsFD(fd.get(), output); +} + +PosixErrorOr<std::string> GetContents(absl::string_view path) { + std::string ret; + RETURN_IF_ERRNO(GetContents(path, &ret)); + return ret; +} + +PosixErrorOr<std::string> GetContentsFD(int fd) { + std::string ret; + RETURN_IF_ERRNO(GetContentsFD(fd, &ret)); + return ret; +} + +PosixError GetContentsFD(int fd, std::string* output) { + // Keep reading until we hit an EOF or an error. + while (true) { + char buf[16 * 1024] = {}; // Read in 16KB chunks. + int bytes_read = read(fd, buf, sizeof(buf)); + if (bytes_read < 0) { + if (errno == EINTR) { + continue; + } + return PosixError(errno, "GetContentsFD read failure."); + } + + if (bytes_read == 0) { + break; // EOF. + } + + output->append(buf, bytes_read); + } + return NoError(); +} + +PosixErrorOr<std::string> ReadLink(absl::string_view path) { + char buf[PATH_MAX + 1] = {}; + int ret = readlink(std::string(path).c_str(), buf, PATH_MAX); + if (ret < 0) { + return PosixError(errno, absl::StrCat("readlink ", path)); + } + + return std::string(buf, ret); +} + +PosixError WalkTree( + absl::string_view path, bool recursive, + const std::function<void(absl::string_view, const struct stat&)>& cb) { + DIR* dir = opendir(std::string(path).c_str()); + if (dir == nullptr) { + return PosixError(errno, absl::StrCat("opendir ", path)); + } + auto dir_closer = Cleanup([&dir]() { closedir(dir); }); + while (true) { + // Readdir(3): If the end of the directory stream is reached, NULL is + // returned and errno is not changed. If an error occurs, NULL is returned + // and errno is set appropriately. To distinguish end of stream and from an + // error, set errno to zero before calling readdir() and then check the + // value of errno if NULL is returned. + errno = 0; + struct dirent* dp = readdir(dir); + if (dp == nullptr) { + if (errno != 0) { + return PosixError(errno, absl::StrCat("readdir ", path)); + } + break; // We're done. + } + + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { + // Skip dots. + continue; + } + + auto full_path = JoinPath(path, dp->d_name); + ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(full_path)); + if (S_ISDIR(s.st_mode) && recursive) { + RETURN_IF_ERRNO(WalkTree(full_path, recursive, cb)); + } else { + cb(full_path, s); + } + } + // We're done walking so let's invoke our cleanup callback now. + dir_closer.Release()(); + + // And we have to dispatch the callback on the base directory. + ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(path)); + cb(path, s); + + return NoError(); +} + +PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath, + bool skipdots) { + std::vector<std::string> files; + + DIR* dir = opendir(std::string(abspath).c_str()); + if (dir == nullptr) { + return PosixError(errno, absl::StrCat("opendir ", abspath)); + } + auto dir_closer = Cleanup([&dir]() { closedir(dir); }); + while (true) { + // Readdir(3): If the end of the directory stream is reached, NULL is + // returned and errno is not changed. If an error occurs, NULL is returned + // and errno is set appropriately. To distinguish end of stream and from an + // error, set errno to zero before calling readdir() and then check the + // value of errno if NULL is returned. + errno = 0; + struct dirent* dp = readdir(dir); + if (dp == nullptr) { + if (errno != 0) { + return PosixError(errno, absl::StrCat("readdir ", abspath)); + } + break; // We're done. + } + + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { + if (skipdots) { + continue; + } + } + files.push_back(std::string(dp->d_name)); + } + + return files; +} + +PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs, + int* undeleted_files) { + ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); + if (!exists) { + return PosixError(ENOENT, absl::StrCat(path, " does not exist")); + } + + ASSIGN_OR_RETURN_ERRNO(bool dir, IsDirectory(path)); + if (!dir) { + // Nothing recursive needs to happen we can just call Delete. + auto status = Delete(path); + if (!status.ok() && undeleted_files) { + (*undeleted_files)++; + } + return status; + } + + return WalkTree(path, /*recursive=*/true, + [&](absl::string_view absolute_path, const struct stat& s) { + if (S_ISDIR(s.st_mode)) { + auto rm_status = Rmdir(absolute_path); + if (!rm_status.ok() && undeleted_dirs) { + (*undeleted_dirs)++; + } + } else { + auto delete_status = Delete(absolute_path); + if (!delete_status.ok() && undeleted_files) { + (*undeleted_files)++; + } + } + }); +} + +PosixError RecursivelyCreateDir(absl::string_view path) { + if (path.empty() || path == "/") { + return PosixError(EINVAL, "Cannot create root!"); + } + + // Does it already exist, if so we're done. + ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); + if (exists) { + return NoError(); + } + + // Do we need to create directories under us? + auto dirname = Dirname(path); + ASSIGN_OR_RETURN_ERRNO(exists, Exists(dirname)); + if (!exists) { + RETURN_IF_ERRNO(RecursivelyCreateDir(dirname)); + } + + return Mkdir(path); +} + +// Makes a path absolute with respect to an optional base. If no base is +// provided it will use the current working directory. +PosixErrorOr<std::string> MakeAbsolute(absl::string_view filename, + absl::string_view base) { + if (filename.empty()) { + return PosixError(EINVAL, "filename cannot be empty."); + } + + if (filename[0] == '/') { + // This path is already absolute. + return std::string(filename); + } + + std::string actual_base; + if (!base.empty()) { + actual_base = std::string(base); + } else { + auto cwd_or = GetCWD(); + RETURN_IF_ERRNO(cwd_or.error()); + actual_base = cwd_or.ValueOrDie(); + } + + // Reverse iterate removing trailing slashes, effectively right trim '/'. + for (int i = actual_base.size() - 1; i >= 0 && actual_base[i] == '/'; --i) { + actual_base.erase(i, 1); + } + + if (filename == ".") { + return actual_base.empty() ? "/" : actual_base; + } + + return absl::StrCat(actual_base, "/", filename); +} + +std::string CleanPath(const absl::string_view unclean_path) { + std::string path = std::string(unclean_path); + const char *src = path.c_str(); + std::string::iterator dst = path.begin(); + + // Check for absolute path and determine initial backtrack limit. + const bool is_absolute_path = *src == '/'; + if (is_absolute_path) { + *dst++ = *src++; + while (*src == '/') ++src; + } + std::string::const_iterator backtrack_limit = dst; + + // Process all parts + while (*src) { + bool parsed = false; + + if (src[0] == '.') { + // 1dot ".<whateverisnext>", check for END or SEP. + if (src[1] == '/' || !src[1]) { + if (*++src) { + ++src; + } + parsed = true; + } else if (src[1] == '.' && (src[2] == '/' || !src[2])) { + // 2dot END or SEP (".." | "../<whateverisnext>"). + src += 2; + if (dst != backtrack_limit) { + // We can backtrack the previous part + for (--dst; dst != backtrack_limit && dst[-1] != '/'; --dst) { + // Empty. + } + } else if (!is_absolute_path) { + // Failed to backtrack and we can't skip it either. Rewind and copy. + src -= 2; + *dst++ = *src++; + *dst++ = *src++; + if (*src) { + *dst++ = *src; + } + // We can never backtrack over a copied "../" part so set new limit. + backtrack_limit = dst; + } + if (*src) { + ++src; + } + parsed = true; + } + } + + // If not parsed, copy entire part until the next SEP or EOS. + if (!parsed) { + while (*src && *src != '/') { + *dst++ = *src++; + } + if (*src) { + *dst++ = *src++; + } + } + + // Skip consecutive SEP occurrences + while (*src == '/') { + ++src; + } + } + + // Calculate and check the length of the cleaned path. + int path_length = dst - path.begin(); + if (path_length != 0) { + // Remove trailing '/' except if it is root path ("/" ==> path_length := 1) + if (path_length > 1 && path[path_length - 1] == '/') { + --path_length; + } + path.resize(path_length); + } else { + // The cleaned path is empty; assign "." as per the spec. + path.assign(1, '.'); + } + return path; +} + +PosixErrorOr<std::string> GetRelativePath(absl::string_view source, + absl::string_view dest) { + if (!absl::StartsWith(source, "/") || !absl::StartsWith(dest, "/")) { + // At least one of the inputs is not an absolute path. + return PosixError( + EINVAL, + "GetRelativePath: At least one of the inputs is not an absolute path."); + } + const std::string clean_source = CleanPath(source); + const std::string clean_dest = CleanPath(dest); + auto source_parts = absl::StrSplit(clean_source, '/', absl::SkipEmpty()); + auto dest_parts = absl::StrSplit(clean_dest, '/', absl::SkipEmpty()); + auto source_iter = source_parts.begin(); + auto dest_iter = dest_parts.begin(); + + // Advance past common prefix. + while (source_iter != source_parts.end() && dest_iter != dest_parts.end() && + *source_iter == *dest_iter) { + ++source_iter; + ++dest_iter; + } + + // Build result backtracking. + std::string result = ""; + while (source_iter != source_parts.end()) { + absl::StrAppend(&result, "../"); + ++source_iter; + } + + // Add remaining path to dest. + while (dest_iter != dest_parts.end()) { + absl::StrAppend(&result, *dest_iter, "/"); + ++dest_iter; + } + + if (result.empty()) { + return std::string("."); + } + + // Remove trailing slash. + result.erase(result.size() - 1); + return result; +} + +absl::string_view Dirname(absl::string_view path) { + return SplitPath(path).first; +} + +absl::string_view Basename(absl::string_view path) { + return SplitPath(path).second; +} + +std::pair<absl::string_view, absl::string_view> SplitPath( + absl::string_view path) { + std::string::size_type pos = path.find_last_of('/'); + + // Handle the case with no '/' in 'path'. + if (pos == absl::string_view::npos) + return std::make_pair(path.substr(0, 0), path); + + // Handle the case with a single leading '/' in 'path'. + if (pos == 0) + return std::make_pair(path.substr(0, 1), absl::ClippedSubstr(path, 1)); + + return std::make_pair(path.substr(0, pos), + absl::ClippedSubstr(path, pos + 1)); +} + +std::string JoinPath(absl::string_view path1, absl::string_view path2) { + if (path1.empty()) return std::string(path2); + if (path2.empty()) return std::string(path1); + if (path1.back() == '/') { + if (path2.front() == '/') + return absl::StrCat(path1, absl::ClippedSubstr(path2, 1)); + } else { + if (path2.front() != '/') return absl::StrCat(path1, "/", path2); + } + return absl::StrCat(path1, path2); +} + +PosixErrorOr<std::string> ProcessExePath(int pid) { + if (pid <= 0) { + return PosixError(EINVAL, "Invalid pid specified"); + } + + return ReadLink(absl::StrCat("/proc/", pid, "/exe")); +} + +} // namespace testing +} // namespace gvisor |