| // Copyright 2019 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "brillo/files/file_util.h" |
| |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <iterator> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/types/expected_macros.h> |
| #include <brillo/syslog_logging.h> |
| |
| namespace brillo { |
| |
| namespace { |
| |
| enum class FSObjectType { |
| RegularFile = 0, |
| Directory, |
| }; |
| |
| SafeFD::SafeFDResult OpenOrRemake(SafeFD* parent, |
| const std::string& name, |
| FSObjectType type, |
| int permissions, |
| uid_t uid, |
| gid_t gid, |
| int flags) { |
| SafeFD::Error err = IsValidFilename(name); |
| if (SafeFD::IsError(err)) { |
| return std::make_pair(SafeFD(), err); |
| } |
| |
| SafeFD::SafeFDResult (SafeFD::*maker)(const base::FilePath&, mode_t, uid_t, |
| gid_t, int); |
| if (type == FSObjectType::Directory) { |
| maker = &SafeFD::MakeDir; |
| } else { |
| maker = &SafeFD::MakeFile; |
| } |
| |
| SafeFD child; |
| std::tie(child, err) = |
| (parent->*maker)(base::FilePath(name), permissions, uid, gid, flags); |
| if (child.is_valid()) { |
| return std::make_pair(std::move(child), err); |
| } |
| |
| // Rmdir should be used on directories. However, kWrongType indicates when |
| // a directory was expected and a non-directory was found or when a |
| // directory was found but not expected, so XOR was used. |
| if ((type == FSObjectType::Directory) ^ (err == SafeFD::Error::kWrongType)) { |
| err = parent->Rmdir(name, true /*recursive*/); |
| } else { |
| err = parent->Unlink(name); |
| } |
| if (SafeFD::IsError(err)) { |
| PLOG(ERROR) << "Failed to clean up \"" << name << "\""; |
| return std::make_pair(SafeFD(), err); |
| } |
| |
| std::tie(child, err) = |
| (parent->*maker)(base::FilePath(name), permissions, uid, gid, flags); |
| return std::make_pair(std::move(child), err); |
| } |
| |
| bool AllAreSeparators(const std::string& input) { |
| for (auto it : input) { |
| if (!base::FilePath::IsSeparator(it)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| base::FilePath MakeAbsolute(const base::FilePath& path) { |
| // realpath isn't used here because it resolves symlinks. |
| if (path.IsAbsolute()) { |
| return path; |
| } |
| |
| // The root path is used as a fallback in the case GetCurrentDirectory fails |
| // which in theory should never happen. |
| base::FilePath working_dir("/"); |
| base::GetCurrentDirectory(&working_dir); |
| return working_dir.Append(path); |
| } |
| |
| bool DeleteInternal(const base::FilePath& path, bool deep) { |
| const auto abs_path = SimplifyPath(MakeAbsolute(path)); |
| |
| // Delete operations using SafeFD are applied to the parent directory. |
| const auto parent = abs_path.DirName(); |
| // Handle the case path doesn't have a parent and the CWD is returned. |
| if (!parent.IsParent(abs_path)) { |
| return false; |
| } |
| SafeFD fd; |
| SafeFD::Error err; |
| std::tie(fd, err) = SafeFD::Root().first.OpenExistingDir(parent); |
| if (!fd.is_valid()) { |
| if (err == SafeFD::Error::kDoesNotExist) { |
| return true; |
| } |
| LOG(ERROR) << "Failed to open " << parent; |
| return false; |
| } |
| |
| return DeletePath(&fd, abs_path.BaseName().value(), deep); |
| } |
| |
| } // namespace |
| |
| base::FilePath SimplifyPath(const base::FilePath& path) { |
| std::vector<std::string> components; |
| if (path.empty()) { |
| return path; |
| } |
| |
| base::FilePath current; |
| base::FilePath base; |
| |
| size_t reserve = 0; |
| size_t parent_dir = 0; |
| // Capture path components. |
| for (current = path; current != current.DirName(); |
| current = current.DirName()) { |
| base = current.BaseName(); |
| |
| // Skip path separators and "." |
| if (AllAreSeparators(base.value()) || |
| base.value() == base::FilePath::kCurrentDirectory) { |
| continue; |
| } |
| |
| // Count parent directory operators. |
| if (base.value() == base::FilePath::kParentDirectory) { |
| ++parent_dir; |
| continue; |
| } |
| |
| // Skip path components negated by parent directory operators. |
| if (parent_dir > 0) { |
| --parent_dir; |
| continue; |
| } |
| |
| components.push_back(base.value()); |
| reserve += components.back().size(); |
| } |
| |
| // Handle relative paths |
| base = current.BaseName(); |
| if (base.value() == base::FilePath::kCurrentDirectory || |
| base.value().empty()) { |
| for (size_t x = 0; x < parent_dir; ++x) { |
| components.push_back(base::FilePath::kParentDirectory); |
| reserve += components.back().size(); |
| } |
| // Handle absolute paths |
| } else if (base.value() == "/") { |
| if (components.empty()) { |
| return base::FilePath("/"); |
| } |
| // Use an empty string since the path separator will still be added. |
| components.push_back(""); |
| reserve += components.back().size(); |
| // This shouldn't happen unless the code is being used on Windows. |
| } else { |
| CHECK(false) << "Got unexpected path base"; |
| } |
| |
| // Count separators |
| reserve += components.size() - 1; |
| // JoinString isn't used because it doesn't accept reverse iterators. |
| std::string result; |
| result.reserve(reserve); |
| |
| auto riter = components.rbegin(); |
| DCHECK(riter != components.rend()); |
| |
| result.append(riter->data(), riter->size()); |
| ++riter; |
| |
| for (; riter != components.rend(); ++riter) { |
| result.append("/", 1); |
| result.append(riter->data(), riter->size()); |
| } |
| |
| // Check that we pre-allocated correctly. |
| DCHECK_EQ(reserve, result.size()); |
| |
| return base::FilePath(result); |
| } |
| |
| SafeFD::Error IsValidFilename(const std::string& filename) { |
| if (filename == "." || filename == ".." || |
| filename.find("/") != std::string::npos) { |
| return SafeFD::Error::kBadArgument; |
| } |
| return SafeFD::Error::kNoError; |
| } |
| |
| base::FilePath GetFDPath(int fd) { |
| const base::FilePath proc_fd(base::StringPrintf("/proc/self/fd/%d", fd)); |
| base::FilePath resolved; |
| if (!base::ReadSymbolicLink(proc_fd, &resolved)) { |
| LOG(ERROR) << "Failed to read " << proc_fd.value(); |
| return base::FilePath(); |
| } |
| return resolved; |
| } |
| |
| SafeFD::SafeFDResult OpenOrRemakeDir(SafeFD* parent, |
| const std::string& name, |
| int permissions, |
| uid_t uid, |
| gid_t gid, |
| int flags) { |
| return OpenOrRemake(parent, name, FSObjectType::Directory, permissions, uid, |
| gid, flags); |
| } |
| |
| SafeFD::SafeFDResult OpenOrRemakeFile(SafeFD* parent, |
| const std::string& name, |
| int permissions, |
| uid_t uid, |
| gid_t gid, |
| int flags) { |
| return OpenOrRemake(parent, name, FSObjectType::RegularFile, permissions, uid, |
| gid, flags); |
| } |
| |
| bool DeletePath(SafeFD* parent, const std::string& name, bool deep) { |
| ASSIGN_OR_RETURN(struct stat child_info, parent->Stat(name), |
| [&](const auto& err) { |
| if (errno == ENOENT) { |
| return true; |
| } |
| PLOG(ERROR) << "fstatat failed for file \"" << name; |
| return false; |
| }); |
| |
| SafeFD::Error err; |
| if (S_ISDIR(child_info.st_mode)) { |
| err = parent->Rmdir(name, deep /* recursive */); |
| } else { |
| err = parent->Unlink(name); |
| } |
| return !SafeFD::IsError(err) || err == SafeFD::Error::kDoesNotExist || |
| errno == ENOENT; |
| } |
| |
| bool DeleteFile(const base::FilePath& path) { |
| return DeleteInternal(path, false /* deep */); |
| } |
| |
| bool DeletePathRecursively(const base::FilePath& path) { |
| return DeleteInternal(path, true /* deep */); |
| } |
| |
| } // namespace brillo |