chromium/base/files/file_util_posix.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/files/file_util.h"

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include <bit>
#include <iomanip>
#include <memory>
#include <optional>
#include <string_view>

#include "base/base_export.h"
#include "base/base_switches.h"
#include "base/bits.h"
#include "base/command_line.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/containers/heap_array.h"
#include "base/containers/stack.h"
#include "base/environment.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/cstring_view.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/time/time.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"

#if BUILDFLAG(IS_APPLE)
#include <AvailabilityMacros.h>

#include "base/apple/foundation_util.h"
#endif

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
#include <sys/sendfile.h>
#endif

#if BUILDFLAG(IS_ANDROID)
#include "base/android/content_uri_utils.h"
#include "base/os_compat_android.h"
#endif

#if !BUILDFLAG(IS_IOS)
#include <grp.h>
#endif

// We need to do this on AIX due to some inconsistencies in how AIX
// handles XOPEN_SOURCE and ALL_SOURCE.
#if BUILDFLAG(IS_AIX)
extern "C" char* mkdtemp(char* path);
#endif

namespace base {
namespace {

#if BUILDFLAG(IS_MAC)
// Helper for VerifyPathControlledByUser.
bool VerifySpecificPathControlledByUser(const FilePath& path,
                                        uid_t owner_uid,
                                        const std::set<gid_t>& group_gids) {
  stat_wrapper_t stat_info;
  if (File::Lstat(path, &stat_info) != 0) {
    DPLOG(ERROR) << "Failed to get information on path " << path.value();
    return false;
  }

  if (S_ISLNK(stat_info.st_mode)) {
    DLOG(ERROR) << "Path " << path.value() << " is a symbolic link.";
    return false;
  }

  if (stat_info.st_uid != owner_uid) {
    DLOG(ERROR) << "Path " << path.value() << " is owned by the wrong user.";
    return false;
  }

  if ((stat_info.st_mode & S_IWGRP) &&
      !Contains(group_gids, stat_info.st_gid)) {
    DLOG(ERROR) << "Path " << path.value()
                << " is writable by an unprivileged group.";
    return false;
  }

  if (stat_info.st_mode & S_IWOTH) {
    DLOG(ERROR) << "Path " << path.value() << " is writable by any user.";
    return false;
  }

  return true;
}
#endif

base::FilePath GetTempTemplate() {}

bool AdvanceEnumeratorWithStat(FileEnumerator* traversal,
                               FilePath* out_next_path,
                               stat_wrapper_t* out_next_stat) {}

bool DoCopyDirectory(const FilePath& from_path,
                     const FilePath& to_path,
                     bool recursive,
                     bool open_exclusive) {}

struct CloseDir {};

// Deletes the file or removes the directory specified by `path` and `at_fd`. If
// `path` is absolute, then `at_fd` is simply ignored. If `path` is relative,
// then it is considered relative to the directory designated by the file
// descriptor `at_fd`. If `path` is relative and `at_fd` has the special value
// AT_FDCWD, then `path` is considered relative to the current working directory
// of the running process.
bool DoDeleteFile(const PlatformFile at_fd,
                  const char* const path,
                  const bool recursive) {}

// TODO(erikkay): The Windows version of this accepts paths like "foo/bar/*"
// which works both with and without the recursive flag.  I'm not sure we need
// that functionality. If not, remove from file_util_win.cc, otherwise add it
// here.
bool DoDeleteFile(const FilePath& path, bool recursive) {}

#if !BUILDFLAG(IS_APPLE)
// Appends |mode_char| to |mode| before the optional character set encoding; see
// https://www.gnu.org/software/libc/manual/html_node/Opening-Streams.html for
// details.
std::string AppendModeCharacter(std::string_view mode, char mode_char) {}
#endif

#if !BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_APPLE) && \
    !(BUILDFLAG(IS_ANDROID) && __ANDROID_API__ >= 21)
bool PreReadFileSlow(const FilePath& file_path, int64_t max_bytes) {
  DCHECK_GE(max_bytes, 0);

  File file(file_path, File::FLAG_OPEN | File::FLAG_READ);
  if (!file.IsValid()) {
    return false;
  }

  constexpr size_t kBufferSize = 1024 * 1024;
  auto buffer = base::HeapArray<uint8_t>::Uninit(kBufferSize);

  while (max_bytes > 0) {
    const size_t read_size = base::checked_cast<size_t>(
        std::min<uint64_t>(static_cast<uint64_t>(max_bytes), buffer.size()));
    std::optional<size_t> read_bytes =
        file.ReadAtCurrentPos(buffer.first(read_size));
    if (!read_bytes.has_value()) {
      return false;
    }
    if (read_bytes.value() == 0) {
      break;
    }
    max_bytes -= read_bytes.value();
  }

  return true;
}
#endif

}  // namespace

FilePath MakeAbsoluteFilePath(const FilePath& input) {}

std::optional<FilePath> MakeAbsoluteFilePathNoResolveSymbolicLinks(
    const FilePath& input) {}

bool DeleteFile(const FilePath& path) {}

bool DeletePathRecursively(const FilePath& path) {}

bool ReplaceFile(const FilePath& from_path,
                 const FilePath& to_path,
                 File::Error* error) {}

bool CopyDirectory(const FilePath& from_path,
                   const FilePath& to_path,
                   bool recursive) {}

bool CopyDirectoryExcl(const FilePath& from_path,
                       const FilePath& to_path,
                       bool recursive) {}

bool CreatePipe(ScopedFD* read_fd, ScopedFD* write_fd, bool non_blocking) {}

bool CreateLocalNonBlockingPipe(span<int, 2u> fds) {}

bool SetNonBlocking(int fd) {}

bool SetCloseOnExec(int fd) {}

bool RemoveCloseOnExec(int fd) {}

bool PathExists(const FilePath& path) {}

bool PathIsReadable(const FilePath& path) {}

bool PathIsWritable(const FilePath& path) {}

bool DirectoryExists(const FilePath& path) {}

bool ReadFromFD(int fd, span<char> buffer) {}

ScopedFD CreateAndOpenFdForTemporaryFileInDir(const FilePath& directory,
                                              FilePath* path) {}

#if !BUILDFLAG(IS_FUCHSIA)
bool CreateSymbolicLink(const FilePath& target_path,
                        const FilePath& symlink_path) {}

bool ReadSymbolicLink(const FilePath& symlink_path, FilePath* target_path) {}

std::optional<FilePath> ReadSymbolicLinkAbsolute(const FilePath& symlink_path) {}

bool GetPosixFilePermissions(const FilePath& path, int* mode) {}

bool SetPosixFilePermissions(const FilePath& path, int mode) {}

bool ExecutableExistsInPath(Environment* env,
                            const FilePath::StringType& executable) {}

#endif  // !BUILDFLAG(IS_FUCHSIA)

#if !BUILDFLAG(IS_APPLE)
// This is implemented in file_util_apple.mm for Mac.
bool GetTempDir(FilePath* path) {}
#endif  // !BUILDFLAG(IS_APPLE)

#if !BUILDFLAG(IS_APPLE)  // Mac implementation is in file_util_apple.mm.
FilePath GetHomeDir() {}
#endif  // !BUILDFLAG(IS_APPLE)

File CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) {}

bool CreateTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) {}

FilePath FormatTemporaryFileName(FilePath::StringPieceType identifier) {}

ScopedFILE CreateAndOpenTemporaryStreamInDir(const FilePath& dir,
                                             FilePath* path) {}

static bool CreateTemporaryDirInDirImpl(const FilePath& base_dir,
                                        const FilePath& name_tmpl,
                                        FilePath* new_dir) {}

bool CreateTemporaryDirInDir(const FilePath& base_dir,
                             FilePath::StringPieceType prefix,
                             FilePath* new_dir) {}

bool CreateNewTempDirectory(const FilePath::StringType& prefix,
                            FilePath* new_temp_path) {}

bool CreateDirectoryAndGetError(const FilePath& full_path, File::Error* error) {}

// ReadFileToStringNonBlockingNonBlocking will read a file to a string. This
// method should only be used on files which are known to be non-blocking such
// as procfs or sysfs nodes. Additionally, the file is opened as O_NONBLOCK so
// it WILL NOT block even if opened on a blocking file. It will return true if
// the file read until EOF and it will return false otherwise, errno will remain
// set on error conditions. |ret| will be populated with the contents of the
// file.
bool ReadFileToStringNonBlocking(const base::FilePath& file, std::string* ret) {}

bool NormalizeFilePath(const FilePath& path, FilePath* normalized_path) {}

// TODO(rkc): Refactor GetFileInfo and FileEnumerator to handle symlinks
// correctly. http://code.google.com/p/chromium-os/issues/detail?id=15948
bool IsLink(const FilePath& file_path) {}

bool GetFileInfo(const FilePath& file_path, File::Info* results) {}

FILE* OpenFile(const FilePath& filename, const char* mode) {}

// NaCl doesn't implement system calls to open files directly.
#if !BUILDFLAG(IS_NACL)
FILE* FileToFILE(File file, const char* mode) {}

File FILEToFile(FILE* file_stream) {}
#endif  // !BUILDFLAG(IS_NACL)

std::optional<uint64_t> ReadFile(const FilePath& filename, span<char> buffer) {}

bool WriteFile(const FilePath& filename, span<const uint8_t> data) {}

bool WriteFileDescriptor(int fd, span<const uint8_t> data) {}

bool WriteFileDescriptor(int fd, std::string_view data) {}

bool AllocateFileRegion(File* file, int64_t offset, size_t size) {}

bool AppendToFile(const FilePath& filename, span<const uint8_t> data) {}

bool AppendToFile(const FilePath& filename, std::string_view data) {}

bool GetCurrentDirectory(FilePath* dir) {}

bool SetCurrentDirectory(const FilePath& path) {}

#if BUILDFLAG(IS_MAC)
bool VerifyPathControlledByUser(const FilePath& base,
                                const FilePath& path,
                                uid_t owner_uid,
                                const std::set<gid_t>& group_gids) {
  if (base != path && !base.IsParent(path)) {
    DLOG(ERROR) << "|base| must be a subdirectory of |path|.  base = \""
                << base.value() << "\", path = \"" << path.value() << "\"";
    return false;
  }

  std::vector<FilePath::StringType> base_components = base.GetComponents();
  std::vector<FilePath::StringType> path_components = path.GetComponents();
  std::vector<FilePath::StringType>::const_iterator ib, ip;
  for (ib = base_components.begin(), ip = path_components.begin();
       ib != base_components.end(); ++ib, ++ip) {
    // |base| must be a subpath of |path|, so all components should match.
    // If these CHECKs fail, look at the test that base is a parent of
    // path at the top of this function.
    CHECK(ip != path_components.end(), base::NotFatalUntil::M125);
    DCHECK(*ip == *ib);
  }

  FilePath current_path = base;
  if (!VerifySpecificPathControlledByUser(current_path, owner_uid,
                                          group_gids)) {
    return false;
  }

  for (; ip != path_components.end(); ++ip) {
    current_path = current_path.Append(*ip);
    if (!VerifySpecificPathControlledByUser(current_path, owner_uid,
                                            group_gids)) {
      return false;
    }
  }
  return true;
}

bool VerifyPathControlledByAdmin(const FilePath& path) {
  constexpr unsigned kRootUid = 0;
  const FilePath kFileSystemRoot("/");

  // The name of the administrator group on mac os.
  const char* const kAdminGroupNames[] = {"admin", "wheel"};

  // Reading the groups database may touch the file system.
  ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);

  std::set<gid_t> allowed_group_ids;
  for (const char* name : kAdminGroupNames) {
    struct group* group_record = getgrnam(name);
    if (!group_record) {
      DPLOG(ERROR) << "Could not get the group ID of group \"" << name << "\".";
      continue;
    }

    allowed_group_ids.insert(group_record->gr_gid);
  }

  return VerifyPathControlledByUser(kFileSystemRoot, path, kRootUid,
                                    allowed_group_ids);
}
#endif  // BUILDFLAG(IS_MAC)

int GetMaximumPathComponentLength(const FilePath& path) {}

#if !BUILDFLAG(IS_ANDROID)
// This is implemented in file_util_android.cc for that platform.
bool GetShmemTempDir(bool executable, FilePath* path) {}
#endif  // !BUILDFLAG(IS_ANDROID)

#if !BUILDFLAG(IS_APPLE)
// Mac has its own implementation, this is for all other Posix systems.
bool CopyFile(const FilePath& from_path, const FilePath& to_path) {}
#endif  // !BUILDFLAG(IS_APPLE)

bool PreReadFile(const FilePath& file_path,
                 bool is_executable,
                 bool sequential,
                 int64_t max_bytes) {}

// -----------------------------------------------------------------------------

namespace internal {

bool MoveUnsafe(const FilePath& from_path, const FilePath& to_path) {}

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
bool CopyFileContentsWithSendfile(File& infile,
                                  File& outfile,
                                  bool& retry_slow) {}
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||
        // BUILDFLAG(IS_ANDROID)

}  // namespace internal

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_AIX)
BASE_EXPORT bool IsPathExecutable(const FilePath& path) {}
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_AIX)

}  // namespace base