Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,35 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_DIR_READER_FALLBACK_H_
#define BASE_FILES_DIR_READER_FALLBACK_H_
namespace base {
class DirReaderFallback {
public:
// Open a directory. If |IsValid| is true, then |Next| can be called to start
// the iteration at the beginning of the directory.
explicit DirReaderFallback(const char* directory_path) {}
// After construction, IsValid returns true iff the directory was
// successfully opened.
bool IsValid() const { return false; }
// Move to the next entry returning false if the iteration is complete.
bool Next() { return false; }
// Return the name of the current directory entry.
const char* name() { return nullptr;}
// Return the file descriptor which is being used.
int fd() const { return -1; }
// Returns true if this is a no-op fallback class (for testing).
static bool IsFallback() { return true; }
};
} // namespace base
#endif // BASE_FILES_DIR_READER_FALLBACK_H_

View file

@ -0,0 +1,101 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_DIR_READER_LINUX_H_
#define BASE_FILES_DIR_READER_LINUX_H_
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "base/logging.h"
#include "base/macros.h"
#include "base/posix/eintr_wrapper.h"
// See the comments in dir_reader_posix.h about this.
namespace base {
struct linux_dirent {
uint64_t d_ino;
int64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[0];
};
class DirReaderLinux {
public:
explicit DirReaderLinux(const char* directory_path)
: fd_(open(directory_path, O_RDONLY | O_DIRECTORY)),
offset_(0),
size_(0) {
memset(buf_, 0, sizeof(buf_));
}
~DirReaderLinux() {
if (fd_ >= 0) {
if (IGNORE_EINTR(close(fd_)))
RAW_LOG(ERROR, "Failed to close directory handle");
}
}
bool IsValid() const {
return fd_ >= 0;
}
// Move to the next entry returning false if the iteration is complete.
bool Next() {
if (size_) {
linux_dirent* dirent = reinterpret_cast<linux_dirent*>(&buf_[offset_]);
offset_ += dirent->d_reclen;
}
if (offset_ != size_)
return true;
const int r = syscall(__NR_getdents64, fd_, buf_, sizeof(buf_));
if (r == 0)
return false;
if (r == -1) {
DPLOG(FATAL) << "getdents64 failed";
return false;
}
size_ = r;
offset_ = 0;
return true;
}
const char* name() const {
if (!size_)
return nullptr;
const linux_dirent* dirent =
reinterpret_cast<const linux_dirent*>(&buf_[offset_]);
return dirent->d_name;
}
int fd() const {
return fd_;
}
static bool IsFallback() {
return false;
}
private:
const int fd_;
alignas(linux_dirent) unsigned char buf_[512];
size_t offset_;
size_t size_;
DISALLOW_COPY_AND_ASSIGN(DirReaderLinux);
};
} // namespace base
#endif // BASE_FILES_DIR_READER_LINUX_H_

View file

@ -0,0 +1,36 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_DIR_READER_POSIX_H_
#define BASE_FILES_DIR_READER_POSIX_H_
#include "build/build_config.h"
// This header provides a class, DirReaderPosix, which allows one to open and
// read from directories without allocating memory. For the interface, see
// the generic fallback in dir_reader_fallback.h.
// Mac note: OS X has getdirentries, but it only works if we restrict Chrome to
// 32-bit inodes. There is a getdirentries64 syscall in 10.6, but it's not
// wrapped and the direct syscall interface is unstable. Using an unstable API
// seems worse than falling back to enumerating all file descriptors so we will
// probably never implement this on the Mac.
#if defined(OS_LINUX) || defined(OS_ANDROID)
#include "base/files/dir_reader_linux.h"
#else
#include "base/files/dir_reader_fallback.h"
#endif
namespace base {
#if defined(OS_LINUX) || defined(OS_ANDROID)
typedef DirReaderLinux DirReaderPosix;
#else
typedef DirReaderFallback DirReaderPosix;
#endif
} // namespace base
#endif // BASE_FILES_DIR_READER_POSIX_H_

View file

@ -0,0 +1,166 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file.h"
#include <utility>
#include "base/files/file_path.h"
#include "base/files/file_tracing.h"
#include "base/metrics/histogram.h"
#include "base/numerics/safe_conversions.h"
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <errno.h>
#endif
namespace base {
File::Info::Info() = default;
File::Info::~Info() = default;
File::File() = default;
#if !defined(OS_NACL)
File::File(const FilePath& path, uint32_t flags) : error_details_(FILE_OK) {
Initialize(path, flags);
}
#endif
File::File(ScopedPlatformFile platform_file)
: File(std::move(platform_file), false) {}
File::File(PlatformFile platform_file) : File(platform_file, false) {}
File::File(ScopedPlatformFile platform_file, bool async)
: file_(std::move(platform_file)), error_details_(FILE_OK), async_(async) {
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
DCHECK_GE(file_.get(), -1);
#endif
}
File::File(PlatformFile platform_file, bool async)
: file_(platform_file),
error_details_(FILE_OK),
async_(async) {
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
DCHECK_GE(platform_file, -1);
#endif
}
File::File(Error error_details) : error_details_(error_details) {}
File::File(File&& other)
: file_(other.TakePlatformFile()),
tracing_path_(other.tracing_path_),
error_details_(other.error_details()),
created_(other.created()),
async_(other.async_) {}
File::~File() {
// Go through the AssertIOAllowed logic.
Close();
}
File& File::operator=(File&& other) {
Close();
SetPlatformFile(other.TakePlatformFile());
tracing_path_ = other.tracing_path_;
error_details_ = other.error_details();
created_ = other.created();
async_ = other.async_;
return *this;
}
#if !defined(OS_NACL)
void File::Initialize(const FilePath& path, uint32_t flags) {
if (path.ReferencesParent()) {
#if defined(OS_WIN)
::SetLastError(ERROR_ACCESS_DENIED);
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
errno = EACCES;
#else
#error Unsupported platform
#endif
error_details_ = FILE_ERROR_ACCESS_DENIED;
return;
}
if (FileTracing::IsCategoryEnabled())
tracing_path_ = path;
SCOPED_FILE_TRACE("Initialize");
DoInitialize(path, flags);
}
#endif
bool File::ReadAndCheck(int64_t offset, span<uint8_t> data) {
int size = checked_cast<int>(data.size());
return Read(offset, reinterpret_cast<char*>(data.data()), size) == size;
}
bool File::ReadAtCurrentPosAndCheck(span<uint8_t> data) {
int size = checked_cast<int>(data.size());
return ReadAtCurrentPos(reinterpret_cast<char*>(data.data()), size) == size;
}
bool File::WriteAndCheck(int64_t offset, span<const uint8_t> data) {
int size = checked_cast<int>(data.size());
return Write(offset, reinterpret_cast<const char*>(data.data()), size) ==
size;
}
bool File::WriteAtCurrentPosAndCheck(span<const uint8_t> data) {
int size = checked_cast<int>(data.size());
return WriteAtCurrentPos(reinterpret_cast<const char*>(data.data()), size) ==
size;
}
// static
std::string File::ErrorToString(Error error) {
switch (error) {
case FILE_OK:
return "FILE_OK";
case FILE_ERROR_FAILED:
return "FILE_ERROR_FAILED";
case FILE_ERROR_IN_USE:
return "FILE_ERROR_IN_USE";
case FILE_ERROR_EXISTS:
return "FILE_ERROR_EXISTS";
case FILE_ERROR_NOT_FOUND:
return "FILE_ERROR_NOT_FOUND";
case FILE_ERROR_ACCESS_DENIED:
return "FILE_ERROR_ACCESS_DENIED";
case FILE_ERROR_TOO_MANY_OPENED:
return "FILE_ERROR_TOO_MANY_OPENED";
case FILE_ERROR_NO_MEMORY:
return "FILE_ERROR_NO_MEMORY";
case FILE_ERROR_NO_SPACE:
return "FILE_ERROR_NO_SPACE";
case FILE_ERROR_NOT_A_DIRECTORY:
return "FILE_ERROR_NOT_A_DIRECTORY";
case FILE_ERROR_INVALID_OPERATION:
return "FILE_ERROR_INVALID_OPERATION";
case FILE_ERROR_SECURITY:
return "FILE_ERROR_SECURITY";
case FILE_ERROR_ABORT:
return "FILE_ERROR_ABORT";
case FILE_ERROR_NOT_A_FILE:
return "FILE_ERROR_NOT_A_FILE";
case FILE_ERROR_NOT_EMPTY:
return "FILE_ERROR_NOT_EMPTY";
case FILE_ERROR_INVALID_URL:
return "FILE_ERROR_INVALID_URL";
case FILE_ERROR_IO:
return "FILE_ERROR_IO";
case FILE_ERROR_MAX:
break;
}
NOTREACHED();
return "";
}
} // namespace base

View file

@ -0,0 +1,402 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_FILE_H_
#define BASE_FILES_FILE_H_
#include <stdint.h>
#include <string>
#include "base/base_export.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_tracing.h"
#include "base/files/platform_file.h"
#include "base/macros.h"
#include "base/time/time.h"
#include "build/build_config.h"
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <sys/stat.h>
#endif
namespace base {
#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) || \
defined(OS_FUCHSIA) || (defined(OS_ANDROID) && __ANDROID_API__ < 21)
typedef struct stat stat_wrapper_t;
#elif defined(OS_POSIX)
typedef struct stat64 stat_wrapper_t;
#endif
// Thin wrapper around an OS-level file.
// Note that this class does not provide any support for asynchronous IO, other
// than the ability to create asynchronous handles on Windows.
//
// Note about const: this class does not attempt to determine if the underlying
// file system object is affected by a particular method in order to consider
// that method const or not. Only methods that deal with member variables in an
// obvious non-modifying way are marked as const. Any method that forward calls
// to the OS is not considered const, even if there is no apparent change to
// member variables.
class BASE_EXPORT File {
public:
// FLAG_(OPEN|CREATE).* are mutually exclusive. You should specify exactly one
// of the five (possibly combining with other flags) when opening or creating
// a file.
// FLAG_(WRITE|APPEND) are mutually exclusive. This is so that APPEND behavior
// will be consistent with O_APPEND on POSIX.
// FLAG_EXCLUSIVE_(READ|WRITE) only grant exclusive access to the file on
// creation on POSIX; for existing files, consider using Lock().
enum Flags {
FLAG_OPEN = 1 << 0, // Opens a file, only if it exists.
FLAG_CREATE = 1 << 1, // Creates a new file, only if it does not
// already exist.
FLAG_OPEN_ALWAYS = 1 << 2, // May create a new file.
FLAG_CREATE_ALWAYS = 1 << 3, // May overwrite an old file.
FLAG_OPEN_TRUNCATED = 1 << 4, // Opens a file and truncates it, only if it
// exists.
FLAG_READ = 1 << 5,
FLAG_WRITE = 1 << 6,
FLAG_APPEND = 1 << 7,
FLAG_EXCLUSIVE_READ = 1 << 8, // EXCLUSIVE is opposite of Windows SHARE.
FLAG_EXCLUSIVE_WRITE = 1 << 9,
FLAG_ASYNC = 1 << 10,
FLAG_TEMPORARY = 1 << 11, // Used on Windows only.
FLAG_HIDDEN = 1 << 12, // Used on Windows only.
FLAG_DELETE_ON_CLOSE = 1 << 13,
FLAG_WRITE_ATTRIBUTES = 1 << 14, // Used on Windows only.
FLAG_SHARE_DELETE = 1 << 15, // Used on Windows only.
FLAG_TERMINAL_DEVICE = 1 << 16, // Serial port flags.
FLAG_BACKUP_SEMANTICS = 1 << 17, // Used on Windows only.
FLAG_EXECUTE = 1 << 18, // Used on Windows only.
FLAG_SEQUENTIAL_SCAN = 1 << 19, // Used on Windows only.
FLAG_CAN_DELETE_ON_CLOSE = 1 << 20, // Requests permission to delete a file
// via DeleteOnClose() (Windows only).
// See DeleteOnClose() for details.
};
// This enum has been recorded in multiple histograms using PlatformFileError
// enum. If the order of the fields needs to change, please ensure that those
// histograms are obsolete or have been moved to a different enum.
//
// FILE_ERROR_ACCESS_DENIED is returned when a call fails because of a
// filesystem restriction. FILE_ERROR_SECURITY is returned when a browser
// policy doesn't allow the operation to be executed.
enum Error {
FILE_OK = 0,
FILE_ERROR_FAILED = -1,
FILE_ERROR_IN_USE = -2,
FILE_ERROR_EXISTS = -3,
FILE_ERROR_NOT_FOUND = -4,
FILE_ERROR_ACCESS_DENIED = -5,
FILE_ERROR_TOO_MANY_OPENED = -6,
FILE_ERROR_NO_MEMORY = -7,
FILE_ERROR_NO_SPACE = -8,
FILE_ERROR_NOT_A_DIRECTORY = -9,
FILE_ERROR_INVALID_OPERATION = -10,
FILE_ERROR_SECURITY = -11,
FILE_ERROR_ABORT = -12,
FILE_ERROR_NOT_A_FILE = -13,
FILE_ERROR_NOT_EMPTY = -14,
FILE_ERROR_INVALID_URL = -15,
FILE_ERROR_IO = -16,
// Put new entries here and increment FILE_ERROR_MAX.
FILE_ERROR_MAX = -17
};
// This explicit mapping matches both FILE_ on Windows and SEEK_ on Linux.
enum Whence {
FROM_BEGIN = 0,
FROM_CURRENT = 1,
FROM_END = 2
};
// Used to hold information about a given file.
// If you add more fields to this structure (platform-specific fields are OK),
// make sure to update all functions that use it in file_util_{win|posix}.cc,
// too, and the ParamTraits<base::File::Info> implementation in
// ipc/ipc_message_utils.cc.
struct BASE_EXPORT Info {
Info();
~Info();
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Fills this struct with values from |stat_info|.
void FromStat(const stat_wrapper_t& stat_info);
#endif
// The size of the file in bytes. Undefined when is_directory is true.
int64_t size = 0;
// True if the file corresponds to a directory.
bool is_directory = false;
// True if the file corresponds to a symbolic link. For Windows currently
// not supported and thus always false.
bool is_symbolic_link = false;
// The last modified time of a file.
Time last_modified;
// The last accessed time of a file.
Time last_accessed;
// The creation time of a file.
Time creation_time;
};
File();
// Creates or opens the given file. This will fail with 'access denied' if the
// |path| contains path traversal ('..') components.
File(const FilePath& path, uint32_t flags);
// Takes ownership of |platform_file| and sets async to false.
explicit File(ScopedPlatformFile platform_file);
explicit File(PlatformFile platform_file);
// Takes ownership of |platform_file| and sets async to the given value.
// This constructor exists because on Windows you can't check if platform_file
// is async or not.
File(ScopedPlatformFile platform_file, bool async);
File(PlatformFile platform_file, bool async);
// Creates an object with a specific error_details code.
explicit File(Error error_details);
File(File&& other);
~File();
File& operator=(File&& other);
// Creates or opens the given file.
void Initialize(const FilePath& path, uint32_t flags);
// Returns |true| if the handle / fd wrapped by this object is valid. This
// method doesn't interact with the file system (and is safe to be called from
// ThreadRestrictions::SetIOAllowed(false) threads).
bool IsValid() const;
// Returns true if a new file was created (or an old one truncated to zero
// length to simulate a new file, which can happen with
// FLAG_CREATE_ALWAYS), and false otherwise.
bool created() const { return created_; }
// Returns the OS result of opening this file. Note that the way to verify
// the success of the operation is to use IsValid(), not this method:
// File file(path, flags);
// if (!file.IsValid())
// return;
Error error_details() const { return error_details_; }
PlatformFile GetPlatformFile() const;
PlatformFile TakePlatformFile();
// Destroying this object closes the file automatically.
void Close();
// Changes current position in the file to an |offset| relative to an origin
// defined by |whence|. Returns the resultant current position in the file
// (relative to the start) or -1 in case of error.
int64_t Seek(Whence whence, int64_t offset);
// Simplified versions of Read() and friends (see below) that check the int
// return value and just return a boolean. They return true if and only if
// the function read in / wrote out exactly |size| bytes of data.
bool ReadAndCheck(int64_t offset, span<uint8_t> data);
bool ReadAtCurrentPosAndCheck(span<uint8_t> data);
bool WriteAndCheck(int64_t offset, span<const uint8_t> data);
bool WriteAtCurrentPosAndCheck(span<const uint8_t> data);
// Reads the given number of bytes (or until EOF is reached) starting with the
// given offset. Returns the number of bytes read, or -1 on error. Note that
// this function makes a best effort to read all data on all platforms, so it
// is not intended for stream oriented files but instead for cases when the
// normal expectation is that actually |size| bytes are read unless there is
// an error.
int Read(int64_t offset, char* data, int size);
// Same as above but without seek.
int ReadAtCurrentPos(char* data, int size);
// Reads the given number of bytes (or until EOF is reached) starting with the
// given offset, but does not make any effort to read all data on all
// platforms. Returns the number of bytes read, or -1 on error.
int ReadNoBestEffort(int64_t offset, char* data, int size);
// Same as above but without seek.
int ReadAtCurrentPosNoBestEffort(char* data, int size);
// Writes the given buffer into the file at the given offset, overwritting any
// data that was previously there. Returns the number of bytes written, or -1
// on error. Note that this function makes a best effort to write all data on
// all platforms. |data| can be nullptr when |size| is 0.
// Ignores the offset and writes to the end of the file if the file was opened
// with FLAG_APPEND.
int Write(int64_t offset, const char* data, int size);
// Save as above but without seek.
int WriteAtCurrentPos(const char* data, int size);
// Save as above but does not make any effort to write all data on all
// platforms. Returns the number of bytes written, or -1 on error.
int WriteAtCurrentPosNoBestEffort(const char* data, int size);
// Returns the current size of this file, or a negative number on failure.
int64_t GetLength();
// Truncates the file to the given length. If |length| is greater than the
// current size of the file, the file is extended with zeros. If the file
// doesn't exist, |false| is returned.
bool SetLength(int64_t length);
// Instructs the filesystem to flush the file to disk. (POSIX: fsync, Windows:
// FlushFileBuffers).
// Calling Flush() does not guarantee file integrity and thus is not a valid
// substitute for file integrity checks and recovery codepaths for malformed
// files. It can also be *really* slow, so avoid blocking on Flush(),
// especially please don't block shutdown on Flush().
// Latency percentiles of Flush() across all platforms as of July 2016:
// 50 % > 5 ms
// 10 % > 58 ms
// 1 % > 357 ms
// 0.1 % > 1.8 seconds
// 0.01 % > 7.6 seconds
bool Flush();
// Updates the file times.
bool SetTimes(Time last_access_time, Time last_modified_time);
// Returns some basic information for the given file.
bool GetInfo(Info* info);
#if !defined(OS_FUCHSIA) // Fuchsia's POSIX API does not support file locking.
enum class LockMode {
kShared,
kExclusive,
};
// Attempts to take an exclusive write lock on the file. Returns immediately
// (i.e. does not wait for another process to unlock the file). If the lock
// was obtained, the result will be FILE_OK. A lock only guarantees
// that other processes may not also take a lock on the same file with the
// same API - it may still be opened, renamed, unlinked, etc.
//
// Common semantics:
// * Locks are held by processes, but not inherited by child processes.
// * Locks are released by the OS on file close or process termination.
// * Locks are reliable only on local filesystems.
// * Duplicated file handles may also write to locked files.
// Windows-specific semantics:
// * Locks are mandatory for read/write APIs, advisory for mapping APIs.
// * Within a process, locking the same file (by the same or new handle)
// will fail.
// POSIX-specific semantics:
// * Locks are advisory only.
// * Within a process, locking the same file (by the same or new handle)
// will succeed. The new lock replaces the old lock.
// * Closing any descriptor on a given file releases the lock.
Error Lock(LockMode mode = LockMode::kExclusive);
// Unlock a file previously locked.
Error Unlock();
#endif // !defined(OS_FUCHSIA)
// Returns a new object referencing this file for use within the current
// process. Handling of FLAG_DELETE_ON_CLOSE varies by OS. On POSIX, the File
// object that was created or initialized with this flag will have unlinked
// the underlying file when it was created or opened. On Windows, the
// underlying file is deleted when the last handle to it is closed.
File Duplicate() const;
bool async() const { return async_; }
#if defined(OS_WIN)
// Sets or clears the DeleteFile disposition on the file. Returns true if
// the disposition was set or cleared, as indicated by |delete_on_close|.
//
// Microsoft Windows deletes a file only when the DeleteFile disposition is
// set on a file when the last handle to the last underlying kernel File
// object is closed. This disposition is be set by:
// - Calling the Win32 DeleteFile function with the path to a file.
// - Opening/creating a file with FLAG_DELETE_ON_CLOSE and then closing all
// handles to that File object.
// - Opening/creating a file with FLAG_CAN_DELETE_ON_CLOSE and subsequently
// calling DeleteOnClose(true).
//
// In all cases, all pre-existing handles to the file must have been opened
// with FLAG_SHARE_DELETE. Once the disposition has been set by any of the
// above means, no new File objects can be created for the file.
//
// So:
// - Use FLAG_SHARE_DELETE when creating/opening a file to allow another
// entity on the system to cause it to be deleted when it is closed. (Note:
// another entity can delete the file the moment after it is closed, so not
// using this permission doesn't provide any protections.)
// - Use FLAG_DELETE_ON_CLOSE for any file that is to be deleted after use.
// The OS will ensure it is deleted even in the face of process termination.
// Note that it's possible for deletion to be cancelled via another File
// object referencing the same file using DeleteOnClose(false) to clear the
// DeleteFile disposition after the original File is closed.
// - Use FLAG_CAN_DELETE_ON_CLOSE in conjunction with DeleteOnClose() to alter
// the DeleteFile disposition on an open handle. This fine-grained control
// allows for marking a file for deletion during processing so that it is
// deleted in the event of untimely process termination, and then clearing
// this state once the file is suitable for persistence.
bool DeleteOnClose(bool delete_on_close);
#endif
#if defined(OS_WIN)
static Error OSErrorToFileError(DWORD last_error);
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
static Error OSErrorToFileError(int saved_errno);
#endif
// Gets the last global error (errno or GetLastError()) and converts it to the
// closest base::File::Error equivalent via OSErrorToFileError(). The returned
// value is only trustworthy immediately after another base::File method
// fails. base::File never resets the global error to zero.
static Error GetLastFileError();
// Converts an error value to a human-readable form. Used for logging.
static std::string ErrorToString(Error error);
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Wrapper for stat() or stat64().
static int Stat(const char* path, stat_wrapper_t* sb);
static int Fstat(int fd, stat_wrapper_t* sb);
static int Lstat(const char* path, stat_wrapper_t* sb);
#endif
private:
friend class FileTracing::ScopedTrace;
// Creates or opens the given file. Only called if |path| has no
// traversal ('..') components.
void DoInitialize(const FilePath& path, uint32_t flags);
void SetPlatformFile(PlatformFile file);
ScopedPlatformFile file_;
// A path to use for tracing purposes. Set if file tracing is enabled during
// |Initialize()|.
FilePath tracing_path_;
// Object tied to the lifetime of |this| that enables/disables tracing.
FileTracing::ScopedEnabler trace_enabler_;
Error error_details_ = FILE_ERROR_FAILED;
bool created_ = false;
bool async_ = false;
DISALLOW_COPY_AND_ASSIGN(File);
};
} // namespace base
#endif // BASE_FILES_FILE_H_

View file

@ -0,0 +1,273 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_descriptor_watcher_posix.h"
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop_current.h"
#include "base/message_loop/message_pump_for_io.h"
#include "base/no_destructor.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_local.h"
#include "base/threading/thread_restrictions.h"
namespace base {
namespace {
// Per-thread FileDescriptorWatcher registration.
ThreadLocalPointer<FileDescriptorWatcher>& GetTlsFdWatcher() {
static NoDestructor<ThreadLocalPointer<FileDescriptorWatcher>> tls_fd_watcher;
return *tls_fd_watcher;
}
} // namespace
class FileDescriptorWatcher::Controller::Watcher
: public MessagePumpForIO::FdWatcher,
public MessageLoopCurrent::DestructionObserver {
public:
Watcher(WeakPtr<Controller> controller, MessagePumpForIO::Mode mode, int fd);
~Watcher() override;
void StartWatching();
private:
friend class FileDescriptorWatcher;
// MessagePumpForIO::FdWatcher:
void OnFileCanReadWithoutBlocking(int fd) override;
void OnFileCanWriteWithoutBlocking(int fd) override;
// MessageLoopCurrent::DestructionObserver:
void WillDestroyCurrentMessageLoop() override;
// The MessagePumpForIO's watch handle (stops the watch when destroyed).
MessagePumpForIO::FdWatchController fd_watch_controller_;
// Runs tasks on the sequence on which this was instantiated (i.e. the
// sequence on which the callback must run).
const scoped_refptr<SequencedTaskRunner> callback_task_runner_ =
SequencedTaskRunnerHandle::Get();
// The Controller that created this Watcher. This WeakPtr is bound to the
// |controller_| thread and can only be used by this Watcher to post back to
// |callback_task_runner_|.
WeakPtr<Controller> controller_;
// Whether this Watcher is notified when |fd_| becomes readable or writable
// without blocking.
const MessagePumpForIO::Mode mode_;
// The watched file descriptor.
const int fd_;
// Except for the constructor, every method of this class must run on the same
// MessagePumpForIO thread.
ThreadChecker thread_checker_;
// Whether this Watcher was registered as a DestructionObserver on the
// MessagePumpForIO thread.
bool registered_as_destruction_observer_ = false;
DISALLOW_COPY_AND_ASSIGN(Watcher);
};
FileDescriptorWatcher::Controller::Watcher::Watcher(
WeakPtr<Controller> controller,
MessagePumpForIO::Mode mode,
int fd)
: fd_watch_controller_(FROM_HERE),
controller_(controller),
mode_(mode),
fd_(fd) {
DCHECK(callback_task_runner_);
thread_checker_.DetachFromThread();
}
FileDescriptorWatcher::Controller::Watcher::~Watcher() {
DCHECK(thread_checker_.CalledOnValidThread());
MessageLoopCurrentForIO::Get()->RemoveDestructionObserver(this);
}
void FileDescriptorWatcher::Controller::Watcher::StartWatching() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(MessageLoopCurrentForIO::IsSet());
const bool watch_success =
MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
fd_, false, mode_, &fd_watch_controller_, this);
DCHECK(watch_success) << "Failed to watch fd=" << fd_;
if (!registered_as_destruction_observer_) {
MessageLoopCurrentForIO::Get()->AddDestructionObserver(this);
registered_as_destruction_observer_ = true;
}
}
void FileDescriptorWatcher::Controller::Watcher::OnFileCanReadWithoutBlocking(
int fd) {
DCHECK_EQ(fd_, fd);
DCHECK_EQ(MessagePumpForIO::WATCH_READ, mode_);
DCHECK(thread_checker_.CalledOnValidThread());
// Run the callback on the sequence on which the watch was initiated.
callback_task_runner_->PostTask(
FROM_HERE, BindOnce(&Controller::RunCallback, controller_));
}
void FileDescriptorWatcher::Controller::Watcher::OnFileCanWriteWithoutBlocking(
int fd) {
DCHECK_EQ(fd_, fd);
DCHECK_EQ(MessagePumpForIO::WATCH_WRITE, mode_);
DCHECK(thread_checker_.CalledOnValidThread());
// Run the callback on the sequence on which the watch was initiated.
callback_task_runner_->PostTask(
FROM_HERE, BindOnce(&Controller::RunCallback, controller_));
}
void FileDescriptorWatcher::Controller::Watcher::
WillDestroyCurrentMessageLoop() {
DCHECK(thread_checker_.CalledOnValidThread());
if (callback_task_runner_->RunsTasksInCurrentSequence()) {
// |controller_| can be accessed directly when Watcher runs on the same
// thread.
controller_->watcher_.reset();
} else {
// If the Watcher and the Controller live on different threads, delete
// |this| synchronously. Pending tasks bound to an unretained Watcher* will
// not run since this loop is dead. The associated Controller still
// technically owns this via unique_ptr but it never uses it directly and
// will ultimately send it to this thread for deletion (and that also won't
// run since the loop being dead).
delete this;
}
}
FileDescriptorWatcher::Controller::Controller(MessagePumpForIO::Mode mode,
int fd,
const RepeatingClosure& callback)
: callback_(callback),
io_thread_task_runner_(GetTlsFdWatcher().Get()->io_thread_task_runner()) {
DCHECK(!callback_.is_null());
DCHECK(io_thread_task_runner_);
watcher_ = std::make_unique<Watcher>(weak_factory_.GetWeakPtr(), mode, fd);
StartWatching();
}
FileDescriptorWatcher::Controller::~Controller() {
DCHECK(sequence_checker_.CalledOnValidSequence());
if (io_thread_task_runner_->BelongsToCurrentThread()) {
// If the MessagePumpForIO and the Controller live on the same thread.
watcher_.reset();
} else {
// Synchronously wait until |watcher_| is deleted on the MessagePumpForIO
// thread. This ensures that the file descriptor is never accessed after
// this destructor returns.
//
// Use a ScopedClosureRunner to ensure that |done| is signaled even if the
// thread doesn't run any more tasks (if PostTask returns true, it means
// that the task was queued, but it doesn't mean that a RunLoop will run the
// task before the queue is deleted).
//
// We considered associating "generations" to file descriptors to avoid the
// synchronous wait. For example, if the IO thread gets a "cancel" for fd=6,
// generation=1 after getting a "start watching" for fd=6, generation=2, it
// can ignore the "Cancel". However, "generations" didn't solve this race:
//
// T1 (client) Start watching fd = 6 with WatchReadable()
// Stop watching fd = 6
// Close fd = 6
// Open a new file, fd = 6 gets reused.
// T2 (io) Watcher::StartWatching()
// Incorrectly starts watching fd = 6 which now refers to a
// different file than when WatchReadable() was called.
WaitableEvent done;
io_thread_task_runner_->PostTask(
FROM_HERE, BindOnce(
[](Watcher* watcher, ScopedClosureRunner closure) {
// Since |watcher| is a raw pointer, it isn't deleted
// if this callback is deleted before it gets to run.
delete watcher;
// |closure| runs at the end of this scope.
},
Unretained(watcher_.release()),
ScopedClosureRunner(BindOnce(&WaitableEvent::Signal,
Unretained(&done)))));
ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow;
done.Wait();
}
// Since WeakPtrs are invalidated by the destructor, any pending RunCallback()
// won't be invoked after this returns.
}
void FileDescriptorWatcher::Controller::StartWatching() {
DCHECK(sequence_checker_.CalledOnValidSequence());
if (io_thread_task_runner_->BelongsToCurrentThread()) {
// If the MessagePumpForIO and the Controller live on the same thread.
watcher_->StartWatching();
} else {
// It is safe to use Unretained() below because |watcher_| can only be
// deleted by a delete task posted to |io_thread_task_runner_| by this
// Controller's destructor. Since this delete task hasn't been posted yet,
// it can't run before the task posted below.
io_thread_task_runner_->PostTask(
FROM_HERE,
BindOnce(&Watcher::StartWatching, Unretained(watcher_.get())));
}
}
void FileDescriptorWatcher::Controller::RunCallback() {
DCHECK(sequence_checker_.CalledOnValidSequence());
WeakPtr<Controller> weak_this = weak_factory_.GetWeakPtr();
callback_.Run();
// If |this| wasn't deleted, re-enable the watch.
if (weak_this)
StartWatching();
}
FileDescriptorWatcher::FileDescriptorWatcher(
scoped_refptr<SingleThreadTaskRunner> io_thread_task_runner)
: io_thread_task_runner_(std::move(io_thread_task_runner)) {
DCHECK(!GetTlsFdWatcher().Get());
GetTlsFdWatcher().Set(this);
}
FileDescriptorWatcher::~FileDescriptorWatcher() {
GetTlsFdWatcher().Set(nullptr);
}
std::unique_ptr<FileDescriptorWatcher::Controller>
FileDescriptorWatcher::WatchReadable(int fd, const RepeatingClosure& callback) {
return WrapUnique(new Controller(MessagePumpForIO::WATCH_READ, fd, callback));
}
std::unique_ptr<FileDescriptorWatcher::Controller>
FileDescriptorWatcher::WatchWritable(int fd, const RepeatingClosure& callback) {
return WrapUnique(
new Controller(MessagePumpForIO::WATCH_WRITE, fd, callback));
}
#if DCHECK_IS_ON()
void FileDescriptorWatcher::AssertAllowed() {
DCHECK(GetTlsFdWatcher().Get());
}
#endif
} // namespace base

View file

@ -0,0 +1,138 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_FILE_DESCRIPTOR_WATCHER_POSIX_H_
#define BASE_FILES_FILE_DESCRIPTOR_WATCHER_POSIX_H_
#include <memory>
#include "base/base_export.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_pump_for_io.h"
#include "base/sequence_checker.h"
#include "base/single_thread_task_runner.h"
namespace base {
class SingleThreadTaskRunner;
// The FileDescriptorWatcher API allows callbacks to be invoked when file
// descriptors are readable or writable without blocking.
//
// To enable this API in unit tests, use a TaskEnvironment with
// MainThreadType::IO.
//
// Note: Prefer FileDescriptorWatcher to MessageLoopForIO::WatchFileDescriptor()
// for non-critical IO. FileDescriptorWatcher works on threads/sequences without
// MessagePumps but involves going through the task queue after being notified
// by the OS (a desirablable property for non-critical IO that shouldn't preempt
// the main queue).
class BASE_EXPORT FileDescriptorWatcher {
public:
// Instantiated and returned by WatchReadable() or WatchWritable(). The
// constructor registers a callback to be invoked when a file descriptor is
// readable or writable without blocking and the destructor unregisters it.
class Controller {
public:
// Unregisters the callback registered by the constructor.
~Controller();
private:
friend class FileDescriptorWatcher;
class Watcher;
// Registers |callback| to be invoked when |fd| is readable or writable
// without blocking (depending on |mode|).
Controller(MessagePumpForIO::Mode mode,
int fd,
const RepeatingClosure& callback);
// Starts watching the file descriptor.
void StartWatching();
// Runs |callback_|.
void RunCallback();
// The callback to run when the watched file descriptor is readable or
// writable without blocking.
RepeatingClosure callback_;
// TaskRunner associated with the MessageLoopForIO that watches the file
// descriptor.
const scoped_refptr<SingleThreadTaskRunner> io_thread_task_runner_;
// Notified by the MessageLoopForIO associated with
// |io_thread_task_runner_| when the watched file descriptor is
// readable or writable without blocking. Posts a task to run RunCallback()
// on the sequence on which the Controller was instantiated. When the
// Controller is deleted, ownership of |watcher_| is transfered to a delete
// task posted to the MessageLoopForIO. This ensures that |watcher_| isn't
// deleted while it is being used by the MessageLoopForIO.
std::unique_ptr<Watcher> watcher_;
// Validates that the Controller is used on the sequence on which it was
// instantiated.
SequenceChecker sequence_checker_;
WeakPtrFactory<Controller> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(Controller);
};
// Registers |io_thread_task_runner| to watch file descriptors for which
// callbacks are registered from the current thread via WatchReadable() or
// WatchWritable(). |io_thread_task_runner| must post tasks to a thread which
// runs a MessagePumpForIO. If it is not the current thread, it must be highly
// responsive (i.e. not used to run other expensive tasks such as potentially
// blocking I/O) since ~Controller waits for a task posted to it.
explicit FileDescriptorWatcher(
scoped_refptr<SingleThreadTaskRunner> io_thread_task_runner);
~FileDescriptorWatcher();
// Registers |callback| to be posted on the current sequence when |fd| is
// readable or writable without blocking. |callback| is unregistered when the
// returned Controller is deleted (deletion must happen on the current
// sequence).
// Usage note: To call these methods, a FileDescriptorWatcher must have been
// instantiated on the current thread and SequencedTaskRunnerHandle::IsSet()
// must return true (these conditions are met at least on all ThreadPool
// threads as well as on threads backed by a MessageLoopForIO). |fd| must
// outlive the returned Controller.
// Shutdown note: notifications aren't guaranteed to be emitted once the bound
// (current) SequencedTaskRunner enters its shutdown phase (i.e.
// ThreadPool::Shutdown() or Thread::Stop()) regardless of the
// SequencedTaskRunner's TaskShutdownBehavior.
static std::unique_ptr<Controller> WatchReadable(
int fd,
const RepeatingClosure& callback);
static std::unique_ptr<Controller> WatchWritable(
int fd,
const RepeatingClosure& callback);
// Asserts that usage of this API is allowed on this thread.
static void AssertAllowed()
#if DCHECK_IS_ON()
;
#else
{
}
#endif
private:
scoped_refptr<SingleThreadTaskRunner> io_thread_task_runner() const {
return io_thread_task_runner_;
}
const scoped_refptr<SingleThreadTaskRunner> io_thread_task_runner_;
DISALLOW_COPY_AND_ASSIGN(FileDescriptorWatcher);
};
} // namespace base
#endif // BASE_FILES_FILE_DESCRIPTOR_WATCHER_POSIX_H_

View file

@ -0,0 +1,25 @@
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
namespace base {
FileEnumerator::FileInfo::~FileInfo() = default;
bool FileEnumerator::ShouldSkip(const FilePath& path) {
FilePath::StringType basename = path.BaseName().value();
return basename == FILE_PATH_LITERAL(".") ||
(basename == FILE_PATH_LITERAL("..") &&
!(INCLUDE_DOT_DOT & file_type_));
}
bool FileEnumerator::IsTypeMatched(bool is_dir) const {
return (file_type_ &
(is_dir ? FileEnumerator::DIRECTORIES : FileEnumerator::FILES)) != 0;
}
} // namespace base

View file

@ -0,0 +1,208 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_FILE_ENUMERATOR_H_
#define BASE_FILES_FILE_ENUMERATOR_H_
#include <stddef.h>
#include <stdint.h>
#include <vector>
#include "base/base_export.h"
#include "base/containers/stack.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "build/build_config.h"
#if defined(OS_WIN)
#include <windows.h>
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <unistd.h>
#include <unordered_set>
#include "base/files/file.h"
#endif
namespace base {
// A class for enumerating the files in a provided path. The order of the
// results is not guaranteed.
//
// This is blocking. Do not use on critical threads.
//
// Example:
//
// base::FileEnumerator enum(my_dir, false, base::FileEnumerator::FILES,
// FILE_PATH_LITERAL("*.txt"));
// for (base::FilePath name = enum.Next(); !name.empty(); name = enum.Next())
// ...
class BASE_EXPORT FileEnumerator {
public:
// Note: copy & assign supported.
class BASE_EXPORT FileInfo {
public:
FileInfo();
~FileInfo();
bool IsDirectory() const;
// The name of the file. This will not include any path information. This
// is in constrast to the value returned by FileEnumerator.Next() which
// includes the |root_path| passed into the FileEnumerator constructor.
FilePath GetName() const;
int64_t GetSize() const;
Time GetLastModifiedTime() const;
#if defined(OS_WIN)
// Note that the cAlternateFileName (used to hold the "short" 8.3 name)
// of the WIN32_FIND_DATA will be empty. Since we don't use short file
// names, we tell Windows to omit it which speeds up the query slightly.
const WIN32_FIND_DATA& find_data() const { return find_data_; }
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
const stat_wrapper_t& stat() const { return stat_; }
#endif
private:
friend class FileEnumerator;
#if defined(OS_WIN)
WIN32_FIND_DATA find_data_;
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
stat_wrapper_t stat_;
FilePath filename_;
#endif
};
enum FileType {
FILES = 1 << 0,
DIRECTORIES = 1 << 1,
INCLUDE_DOT_DOT = 1 << 2,
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
SHOW_SYM_LINKS = 1 << 4,
#endif
};
// Search policy for intermediate folders.
enum class FolderSearchPolicy {
// Recursive search will pass through folders whose names match the
// pattern. Inside each one, all files will be returned. Folders with names
// that do not match the pattern will be ignored within their interior.
MATCH_ONLY,
// Recursive search will pass through every folder and perform pattern
// matching inside each one.
ALL,
};
// Determines how a FileEnumerator handles errors encountered during
// enumeration. When no ErrorPolicy is explicitly set, FileEnumerator defaults
// to IGNORE_ERRORS.
enum class ErrorPolicy {
// Errors are ignored if possible and FileEnumerator returns as many files
// as it is able to enumerate.
IGNORE_ERRORS,
// Any error encountered during enumeration will terminate the enumeration
// immediately. An error code indicating the nature of a failure can be
// retrieved from |GetError()|.
STOP_ENUMERATION,
};
// |root_path| is the starting directory to search for. It may or may not end
// in a slash.
//
// If |recursive| is true, this will enumerate all matches in any
// subdirectories matched as well. It does a breadth-first search, so all
// files in one directory will be returned before any files in a
// subdirectory.
//
// |file_type|, a bit mask of FileType, specifies whether the enumerator
// should match files, directories, or both.
//
// |pattern| is an optional pattern for which files to match. This
// works like shell globbing. For example, "*.txt" or "Foo???.doc".
// However, be careful in specifying patterns that aren't cross platform
// since the underlying code uses OS-specific matching routines. In general,
// Windows matching is less featureful than others, so test there first.
// If unspecified, this will match all files.
FileEnumerator(const FilePath& root_path, bool recursive, int file_type);
FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern);
FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern,
FolderSearchPolicy folder_search_policy);
FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern,
FolderSearchPolicy folder_search_policy,
ErrorPolicy error_policy);
~FileEnumerator();
// Returns the next file or an empty string if there are no more results.
//
// The returned path will incorporate the |root_path| passed in the
// constructor: "<root_path>/file_name.txt". If the |root_path| is absolute,
// then so will be the result of Next().
FilePath Next();
// Write the file info into |info|.
FileInfo GetInfo() const;
// Once |Next()| returns an empty path, enumeration has been terminated. If
// termination was normal (i.e. no more results to enumerate) or ErrorPolicy
// is set to IGNORE_ERRORS, this returns FILE_OK. Otherwise it returns an
// error code reflecting why enumeration was stopped early.
File::Error GetError() const { return error_; }
private:
// Returns true if the given path should be skipped in enumeration.
bool ShouldSkip(const FilePath& path);
bool IsTypeMatched(bool is_dir) const;
bool IsPatternMatched(const FilePath& src) const;
#if defined(OS_WIN)
// True when find_data_ is valid.
bool has_find_data_ = false;
WIN32_FIND_DATA find_data_;
HANDLE find_handle_ = INVALID_HANDLE_VALUE;
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// The files in the current directory
std::vector<FileInfo> directory_entries_;
// Set of visited directories. Used to prevent infinite looping along
// circular symlinks.
std::unordered_set<ino_t> visited_directories_;
// The next entry to use from the directory_entries_ vector
size_t current_directory_entry_;
#endif
FilePath root_path_;
const bool recursive_;
const int file_type_;
FilePath::StringType pattern_;
const FolderSearchPolicy folder_search_policy_;
const ErrorPolicy error_policy_;
File::Error error_ = File::FILE_OK;
// A stack that keeps track of which subdirectories we still need to
// enumerate in the breadth-first search.
base::stack<FilePath> pending_paths_;
DISALLOW_COPY_AND_ASSIGN(FileEnumerator);
};
} // namespace base
#endif // BASE_FILES_FILE_ENUMERATOR_H_

View file

@ -0,0 +1,246 @@
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_enumerator.h"
#include <dirent.h>
#include <errno.h>
#include <fnmatch.h>
#include <stdint.h>
#include <string.h>
#include "base/logging.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"
namespace base {
namespace {
void GetStat(const FilePath& path, bool show_links, stat_wrapper_t* st) {
DCHECK(st);
const int res = show_links ? File::Lstat(path.value().c_str(), st)
: File::Stat(path.value().c_str(), st);
if (res < 0) {
// Print the stat() error message unless it was ENOENT and we're following
// symlinks.
if (!(errno == ENOENT && !show_links))
DPLOG(ERROR) << "Couldn't stat" << path.value();
memset(st, 0, sizeof(*st));
}
}
#if defined(OS_FUCHSIA)
bool ShouldShowSymLinks(int file_type) {
return false;
}
#else
bool ShouldShowSymLinks(int file_type) {
return file_type & FileEnumerator::SHOW_SYM_LINKS;
}
#endif // defined(OS_FUCHSIA)
#if defined(OS_FUCHSIA)
bool ShouldTrackVisitedDirectories(int file_type) {
return false;
}
#else
bool ShouldTrackVisitedDirectories(int file_type) {
return !(file_type & FileEnumerator::SHOW_SYM_LINKS);
}
#endif // defined(OS_FUCHSIA)
} // namespace
// FileEnumerator::FileInfo ----------------------------------------------------
FileEnumerator::FileInfo::FileInfo() {
memset(&stat_, 0, sizeof(stat_));
}
bool FileEnumerator::FileInfo::IsDirectory() const {
return S_ISDIR(stat_.st_mode);
}
FilePath FileEnumerator::FileInfo::GetName() const {
return filename_;
}
int64_t FileEnumerator::FileInfo::GetSize() const {
return stat_.st_size;
}
base::Time FileEnumerator::FileInfo::GetLastModifiedTime() const {
return base::Time::FromTimeT(stat_.st_mtime);
}
// FileEnumerator --------------------------------------------------------------
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type)
: FileEnumerator(root_path,
recursive,
file_type,
FilePath::StringType(),
FolderSearchPolicy::MATCH_ONLY) {}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern)
: FileEnumerator(root_path,
recursive,
file_type,
pattern,
FolderSearchPolicy::MATCH_ONLY) {}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern,
FolderSearchPolicy folder_search_policy)
: FileEnumerator(root_path,
recursive,
file_type,
pattern,
folder_search_policy,
ErrorPolicy::IGNORE_ERRORS) {}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern,
FolderSearchPolicy folder_search_policy,
ErrorPolicy error_policy)
: current_directory_entry_(0),
root_path_(root_path),
recursive_(recursive),
file_type_(file_type),
pattern_(pattern),
folder_search_policy_(folder_search_policy),
error_policy_(error_policy) {
// INCLUDE_DOT_DOT must not be specified if recursive.
DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
if (recursive && ShouldTrackVisitedDirectories(file_type_)) {
stat_wrapper_t st;
GetStat(root_path, false, &st);
visited_directories_.insert(st.st_ino);
}
pending_paths_.push(root_path);
}
FileEnumerator::~FileEnumerator() = default;
FilePath FileEnumerator::Next() {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
++current_directory_entry_;
// While we've exhausted the entries in the current directory, do the next
while (current_directory_entry_ >= directory_entries_.size()) {
if (pending_paths_.empty())
return FilePath();
root_path_ = pending_paths_.top();
root_path_ = root_path_.StripTrailingSeparators();
pending_paths_.pop();
DIR* dir = opendir(root_path_.value().c_str());
if (!dir) {
if (errno == 0 || error_policy_ == ErrorPolicy::IGNORE_ERRORS)
continue;
error_ = File::OSErrorToFileError(errno);
return FilePath();
}
directory_entries_.clear();
#if defined(OS_FUCHSIA)
// Fuchsia does not support .. on the file system server side, see
// https://fuchsia.googlesource.com/docs/+/master/dotdot.md and
// https://crbug.com/735540. However, for UI purposes, having the parent
// directory show up in directory listings makes sense, so we add it here to
// match the expectation on other operating systems. In cases where this
// is useful it should be resolvable locally.
FileInfo dotdot;
dotdot.stat_.st_mode = S_IFDIR;
dotdot.filename_ = FilePath("..");
if (!ShouldSkip(dotdot.filename_)) {
directory_entries_.push_back(std::move(dotdot));
}
#endif // OS_FUCHSIA
current_directory_entry_ = 0;
struct dirent* dent;
// NOTE: Per the readdir() documentation, when the end of the directory is
// reached with no errors, null is returned and errno is not changed.
// Therefore we must reset errno to zero before calling readdir() if we
// wish to know whether a null result indicates an error condition.
while (errno = 0, dent = readdir(dir)) {
FileInfo info;
info.filename_ = FilePath(dent->d_name);
if (ShouldSkip(info.filename_))
continue;
const bool is_pattern_matched = IsPatternMatched(info.filename_);
// MATCH_ONLY policy enumerates files and directories which matching
// pattern only. So we can early skip further checks.
if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY &&
!is_pattern_matched)
continue;
// Do not call OS stat/lstat if there is no sense to do it. If pattern is
// not matched (file will not appear in results) and search is not
// recursive (possible directory will not be added to pending paths) -
// there is no sense to obtain item below.
if (!recursive_ && !is_pattern_matched)
continue;
const FilePath full_path = root_path_.Append(info.filename_);
GetStat(full_path, ShouldShowSymLinks(file_type_), &info.stat_);
const bool is_dir = info.IsDirectory();
// Recursive mode: schedule traversal of a directory if either
// SHOW_SYM_LINKS is on or we haven't visited the directory yet.
if (recursive_ && is_dir &&
(!ShouldTrackVisitedDirectories(file_type_) ||
visited_directories_.insert(info.stat_.st_ino).second)) {
pending_paths_.push(full_path);
}
if (is_pattern_matched && IsTypeMatched(is_dir))
directory_entries_.push_back(std::move(info));
}
int readdir_errno = errno;
closedir(dir);
if (readdir_errno != 0 && error_policy_ != ErrorPolicy::IGNORE_ERRORS) {
error_ = File::OSErrorToFileError(readdir_errno);
return FilePath();
}
// MATCH_ONLY policy enumerates files in matched subfolders by "*" pattern.
// ALL policy enumerates files in all subfolders by origin pattern.
if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY)
pattern_.clear();
}
return root_path_.Append(
directory_entries_[current_directory_entry_].filename_);
}
FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
return directory_entries_[current_directory_entry_];
}
bool FileEnumerator::IsPatternMatched(const FilePath& path) const {
return pattern_.empty() ||
!fnmatch(pattern_.c_str(), path.value().c_str(), FNM_NOESCAPE);
}
} // namespace base

View file

@ -0,0 +1,217 @@
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_enumerator.h"
#include <stdint.h>
#include <string.h>
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/win/shlwapi.h"
namespace base {
namespace {
FilePath BuildSearchFilter(FileEnumerator::FolderSearchPolicy policy,
const FilePath& root_path,
const FilePath::StringType& pattern) {
// MATCH_ONLY policy filters incoming files by pattern on OS side. ALL policy
// collects all files and filters them manually.
switch (policy) {
case FileEnumerator::FolderSearchPolicy::MATCH_ONLY:
return root_path.Append(pattern);
case FileEnumerator::FolderSearchPolicy::ALL:
return root_path.Append(FILE_PATH_LITERAL("*"));
}
NOTREACHED();
return {};
}
} // namespace
// FileEnumerator::FileInfo ----------------------------------------------------
FileEnumerator::FileInfo::FileInfo() {
memset(&find_data_, 0, sizeof(find_data_));
}
bool FileEnumerator::FileInfo::IsDirectory() const {
return (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
FilePath FileEnumerator::FileInfo::GetName() const {
return FilePath(find_data_.cFileName);
}
int64_t FileEnumerator::FileInfo::GetSize() const {
ULARGE_INTEGER size;
size.HighPart = find_data_.nFileSizeHigh;
size.LowPart = find_data_.nFileSizeLow;
DCHECK_LE(size.QuadPart,
static_cast<ULONGLONG>(std::numeric_limits<int64_t>::max()));
return static_cast<int64_t>(size.QuadPart);
}
Time FileEnumerator::FileInfo::GetLastModifiedTime() const {
return Time::FromFileTime(find_data_.ftLastWriteTime);
}
// FileEnumerator --------------------------------------------------------------
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type)
: FileEnumerator(root_path,
recursive,
file_type,
FilePath::StringType(),
FolderSearchPolicy::MATCH_ONLY) {}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern)
: FileEnumerator(root_path,
recursive,
file_type,
pattern,
FolderSearchPolicy::MATCH_ONLY) {}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern,
FolderSearchPolicy folder_search_policy)
: FileEnumerator(root_path,
recursive,
file_type,
pattern,
folder_search_policy,
ErrorPolicy::IGNORE_ERRORS) {}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern,
FolderSearchPolicy folder_search_policy,
ErrorPolicy error_policy)
: recursive_(recursive),
file_type_(file_type),
pattern_(!pattern.empty() ? pattern : FILE_PATH_LITERAL("*")),
folder_search_policy_(folder_search_policy),
error_policy_(error_policy) {
// INCLUDE_DOT_DOT must not be specified if recursive.
DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
memset(&find_data_, 0, sizeof(find_data_));
pending_paths_.push(root_path);
}
FileEnumerator::~FileEnumerator() {
if (find_handle_ != INVALID_HANDLE_VALUE)
FindClose(find_handle_);
}
FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
if (!has_find_data_) {
NOTREACHED();
return FileInfo();
}
FileInfo ret;
memcpy(&ret.find_data_, &find_data_, sizeof(find_data_));
return ret;
}
FilePath FileEnumerator::Next() {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
while (has_find_data_ || !pending_paths_.empty()) {
if (!has_find_data_) {
// The last find FindFirstFile operation is done, prepare a new one.
root_path_ = pending_paths_.top();
pending_paths_.pop();
// Start a new find operation.
const FilePath src =
BuildSearchFilter(folder_search_policy_, root_path_, pattern_);
find_handle_ = FindFirstFileEx(src.value().c_str(),
FindExInfoBasic, // Omit short name.
&find_data_, FindExSearchNameMatch,
nullptr, FIND_FIRST_EX_LARGE_FETCH);
has_find_data_ = true;
} else {
// Search for the next file/directory.
if (!FindNextFile(find_handle_, &find_data_)) {
FindClose(find_handle_);
find_handle_ = INVALID_HANDLE_VALUE;
}
}
DWORD last_error = GetLastError();
if (INVALID_HANDLE_VALUE == find_handle_) {
has_find_data_ = false;
// MATCH_ONLY policy clears pattern for matched subfolders. ALL policy
// applies pattern for all subfolders.
if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY) {
// This is reached when we have finished a directory and are advancing
// to the next one in the queue. We applied the pattern (if any) to the
// files in the root search directory, but for those directories which
// were matched, we want to enumerate all files inside them. This will
// happen when the handle is empty.
pattern_ = FILE_PATH_LITERAL("*");
}
if (last_error == ERROR_NO_MORE_FILES ||
error_policy_ == ErrorPolicy::IGNORE_ERRORS) {
continue;
}
error_ = File::OSErrorToFileError(last_error);
return FilePath();
}
const FilePath filename(find_data_.cFileName);
if (ShouldSkip(filename))
continue;
const bool is_dir =
(find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
const FilePath abs_path = root_path_.Append(filename);
// Check if directory should be processed recursive.
if (is_dir && recursive_) {
// If |cur_file| is a directory, and we are doing recursive searching,
// add it to pending_paths_ so we scan it after we finish scanning this
// directory. However, don't do recursion through reparse points or we
// may end up with an infinite cycle.
DWORD attributes = GetFileAttributes(abs_path.value().c_str());
if (!(attributes & FILE_ATTRIBUTE_REPARSE_POINT))
pending_paths_.push(abs_path);
}
if (IsTypeMatched(is_dir) && IsPatternMatched(filename))
return abs_path;
}
return FilePath();
}
bool FileEnumerator::IsPatternMatched(const FilePath& src) const {
switch (folder_search_policy_) {
case FolderSearchPolicy::MATCH_ONLY:
// MATCH_ONLY policy filters by pattern on search request, so all found
// files already fits to pattern.
return true;
case FolderSearchPolicy::ALL:
// ALL policy enumerates all files, we need to check pattern match
// manually.
return PathMatchSpec(src.value().c_str(), pattern_.c_str()) == TRUE;
}
NOTREACHED();
return false;
}
} // namespace base

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,484 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// FilePath is a container for pathnames stored in a platform's native string
// type, providing containers for manipulation in according with the
// platform's conventions for pathnames. It supports the following path
// types:
//
// POSIX Windows
// --------------- ----------------------------------
// Fundamental type char[] wchar_t[]
// Encoding unspecified* UTF-16
// Separator / \, tolerant of /
// Drive letters no case-insensitive A-Z followed by :
// Alternate root // (surprise!) \\, for UNC paths
//
// * The encoding need not be specified on POSIX systems, although some
// POSIX-compliant systems do specify an encoding. Mac OS X uses UTF-8.
// Chrome OS also uses UTF-8.
// Linux does not specify an encoding, but in practice, the locale's
// character set may be used.
//
// For more arcane bits of path trivia, see below.
//
// FilePath objects are intended to be used anywhere paths are. An
// application may pass FilePath objects around internally, masking the
// underlying differences between systems, only differing in implementation
// where interfacing directly with the system. For example, a single
// OpenFile(const FilePath &) function may be made available, allowing all
// callers to operate without regard to the underlying implementation. On
// POSIX-like platforms, OpenFile might wrap fopen, and on Windows, it might
// wrap _wfopen_s, perhaps both by calling file_path.value().c_str(). This
// allows each platform to pass pathnames around without requiring conversions
// between encodings, which has an impact on performance, but more imporantly,
// has an impact on correctness on platforms that do not have well-defined
// encodings for pathnames.
//
// Several methods are available to perform common operations on a FilePath
// object, such as determining the parent directory (DirName), isolating the
// final path component (BaseName), and appending a relative pathname string
// to an existing FilePath object (Append). These methods are highly
// recommended over attempting to split and concatenate strings directly.
// These methods are based purely on string manipulation and knowledge of
// platform-specific pathname conventions, and do not consult the filesystem
// at all, making them safe to use without fear of blocking on I/O operations.
// These methods do not function as mutators but instead return distinct
// instances of FilePath objects, and are therefore safe to use on const
// objects. The objects themselves are safe to share between threads.
//
// To aid in initialization of FilePath objects from string literals, a
// FILE_PATH_LITERAL macro is provided, which accounts for the difference
// between char[]-based pathnames on POSIX systems and wchar_t[]-based
// pathnames on Windows.
//
// As a precaution against premature truncation, paths can't contain NULs.
//
// Because a FilePath object should not be instantiated at the global scope,
// instead, use a FilePath::CharType[] and initialize it with
// FILE_PATH_LITERAL. At runtime, a FilePath object can be created from the
// character array. Example:
//
// | const FilePath::CharType kLogFileName[] = FILE_PATH_LITERAL("log.txt");
// |
// | void Function() {
// | FilePath log_file_path(kLogFileName);
// | [...]
// | }
//
// WARNING: FilePaths should ALWAYS be displayed with LTR directionality, even
// when the UI language is RTL. This means you always need to pass filepaths
// through base::i18n::WrapPathWithLTRFormatting() before displaying it in the
// RTL UI.
//
// This is a very common source of bugs, please try to keep this in mind.
//
// ARCANE BITS OF PATH TRIVIA
//
// - A double leading slash is actually part of the POSIX standard. Systems
// are allowed to treat // as an alternate root, as Windows does for UNC
// (network share) paths. Most POSIX systems don't do anything special
// with two leading slashes, but FilePath handles this case properly
// in case it ever comes across such a system. FilePath needs this support
// for Windows UNC paths, anyway.
// References:
// The Open Group Base Specifications Issue 7, sections 3.267 ("Pathname")
// and 4.12 ("Pathname Resolution"), available at:
// http://www.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_267
// http://www.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_12
//
// - Windows treats c:\\ the same way it treats \\. This was intended to
// allow older applications that require drive letters to support UNC paths
// like \\server\share\path, by permitting c:\\server\share\path as an
// equivalent. Since the OS treats these paths specially, FilePath needs
// to do the same. Since Windows can use either / or \ as the separator,
// FilePath treats c://, c:\\, //, and \\ all equivalently.
// Reference:
// The Old New Thing, "Why is a drive letter permitted in front of UNC
// paths (sometimes)?", available at:
// http://blogs.msdn.com/oldnewthing/archive/2005/11/22/495740.aspx
#ifndef BASE_FILES_FILE_PATH_H_
#define BASE_FILES_FILE_PATH_H_
#include <stddef.h>
#include <functional>
#include <iosfwd>
#include <string>
#include <vector>
#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/strings/string_piece.h"
#include "build/build_config.h"
// Windows-style drive letter support and pathname separator characters can be
// enabled and disabled independently, to aid testing. These #defines are
// here so that the same setting can be used in both the implementation and
// in the unit test.
#if defined(OS_WIN)
#define FILE_PATH_USES_DRIVE_LETTERS
#define FILE_PATH_USES_WIN_SEPARATORS
#endif // OS_WIN
// To print path names portably use PRFilePath (based on PRIuS and friends from
// C99 and format_macros.h) like this:
// base::StringPrintf("Path is %" PRFilePath ".\n", path.value().c_str());
#if defined(OS_WIN)
#define PRFilePath "ls"
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#define PRFilePath "s"
#endif // OS_WIN
// Macros for string literal initialization of FilePath::CharType[].
#if defined(OS_WIN)
#define FILE_PATH_LITERAL(x) L##x
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#define FILE_PATH_LITERAL(x) x
#endif // OS_WIN
namespace base {
class Pickle;
class PickleIterator;
// An abstraction to isolate users from the differences between native
// pathnames on different platforms.
class BASE_EXPORT FilePath {
public:
#if defined(OS_WIN)
// On Windows, for Unicode-aware applications, native pathnames are wchar_t
// arrays encoded in UTF-16.
typedef std::wstring StringType;
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// On most platforms, native pathnames are char arrays, and the encoding
// may or may not be specified. On Mac OS X, native pathnames are encoded
// in UTF-8.
typedef std::string StringType;
#endif // OS_WIN
typedef BasicStringPiece<StringType> StringPieceType;
typedef StringType::value_type CharType;
// Null-terminated array of separators used to separate components in
// hierarchical paths. Each character in this array is a valid separator,
// but kSeparators[0] is treated as the canonical separator and will be used
// when composing pathnames.
static const CharType kSeparators[];
// base::size(kSeparators).
static const size_t kSeparatorsLength;
// A special path component meaning "this directory."
static const CharType kCurrentDirectory[];
// A special path component meaning "the parent directory."
static const CharType kParentDirectory[];
// The character used to identify a file extension.
static const CharType kExtensionSeparator;
FilePath();
FilePath(const FilePath& that);
explicit FilePath(StringPieceType path);
~FilePath();
FilePath& operator=(const FilePath& that);
// Constructs FilePath with the contents of |that|, which is left in valid but
// unspecified state.
FilePath(FilePath&& that) noexcept;
// Replaces the contents with those of |that|, which is left in valid but
// unspecified state.
FilePath& operator=(FilePath&& that);
bool operator==(const FilePath& that) const;
bool operator!=(const FilePath& that) const;
// Required for some STL containers and operations
bool operator<(const FilePath& that) const {
return path_ < that.path_;
}
const StringType& value() const { return path_; }
bool empty() const { return path_.empty(); }
void clear() { path_.clear(); }
// Returns true if |character| is in kSeparators.
static bool IsSeparator(CharType character);
// Returns a vector of all of the components of the provided path. It is
// equivalent to calling DirName().value() on the path's root component,
// and BaseName().value() on each child component.
//
// To make sure this is lossless so we can differentiate absolute and
// relative paths, the root slash will be included even though no other
// slashes will be. The precise behavior is:
//
// Posix: "/foo/bar" -> [ "/", "foo", "bar" ]
// Windows: "C:\foo\bar" -> [ "C:", "\\", "foo", "bar" ]
void GetComponents(std::vector<FilePath::StringType>* components) const;
// Returns true if this FilePath is a parent or ancestor of the |child|.
// Absolute and relative paths are accepted i.e. /foo is a parent to /foo/bar,
// and foo is a parent to foo/bar. Any ancestor is considered a parent i.e. /a
// is a parent to both /a/b and /a/b/c. Does not convert paths to absolute,
// follow symlinks or directory navigation (e.g. ".."). A path is *NOT* its
// own parent.
bool IsParent(const FilePath& child) const;
// If IsParent(child) holds, appends to path (if non-NULL) the
// relative path to child and returns true. For example, if parent
// holds "/Users/johndoe/Library/Application Support", child holds
// "/Users/johndoe/Library/Application Support/Google/Chrome/Default", and
// *path holds "/Users/johndoe/Library/Caches", then after
// parent.AppendRelativePath(child, path) is called *path will hold
// "/Users/johndoe/Library/Caches/Google/Chrome/Default". Otherwise,
// returns false.
bool AppendRelativePath(const FilePath& child, FilePath* path) const;
// Returns a FilePath corresponding to the directory containing the path
// named by this object, stripping away the file component. If this object
// only contains one component, returns a FilePath identifying
// kCurrentDirectory. If this object already refers to the root directory,
// returns a FilePath identifying the root directory. Please note that this
// doesn't resolve directory navigation, e.g. the result for "../a" is "..".
FilePath DirName() const WARN_UNUSED_RESULT;
// Returns a FilePath corresponding to the last path component of this
// object, either a file or a directory. If this object already refers to
// the root directory, returns a FilePath identifying the root directory;
// this is the only situation in which BaseName will return an absolute path.
FilePath BaseName() const WARN_UNUSED_RESULT;
// Returns ".jpg" for path "C:\pics\jojo.jpg", or an empty string if
// the file has no extension. If non-empty, Extension() will always start
// with precisely one ".". The following code should always work regardless
// of the value of path. For common double-extensions like .tar.gz and
// .user.js, this method returns the combined extension. For a single
// component, use FinalExtension().
// new_path = path.RemoveExtension().value().append(path.Extension());
// ASSERT(new_path == path.value());
// NOTE: this is different from the original file_util implementation which
// returned the extension without a leading "." ("jpg" instead of ".jpg")
StringType Extension() const WARN_UNUSED_RESULT;
// Returns the path's file extension, as in Extension(), but will
// never return a double extension.
//
// TODO(davidben): Check all our extension-sensitive code to see if
// we can rename this to Extension() and the other to something like
// LongExtension(), defaulting to short extensions and leaving the
// long "extensions" to logic like base::GetUniquePathNumber().
StringType FinalExtension() const WARN_UNUSED_RESULT;
// Returns "C:\pics\jojo" for path "C:\pics\jojo.jpg"
// NOTE: this is slightly different from the similar file_util implementation
// which returned simply 'jojo'.
FilePath RemoveExtension() const WARN_UNUSED_RESULT;
// Removes the path's file extension, as in RemoveExtension(), but
// ignores double extensions.
FilePath RemoveFinalExtension() const WARN_UNUSED_RESULT;
// Inserts |suffix| after the file name portion of |path| but before the
// extension. Returns "" if BaseName() == "." or "..".
// Examples:
// path == "C:\pics\jojo.jpg" suffix == " (1)", returns "C:\pics\jojo (1).jpg"
// path == "jojo.jpg" suffix == " (1)", returns "jojo (1).jpg"
// path == "C:\pics\jojo" suffix == " (1)", returns "C:\pics\jojo (1)"
// path == "C:\pics.old\jojo" suffix == " (1)", returns "C:\pics.old\jojo (1)"
FilePath InsertBeforeExtension(
StringPieceType suffix) const WARN_UNUSED_RESULT;
FilePath InsertBeforeExtensionASCII(
StringPiece suffix) const WARN_UNUSED_RESULT;
// Adds |extension| to |file_name|. Returns the current FilePath if
// |extension| is empty. Returns "" if BaseName() == "." or "..".
FilePath AddExtension(StringPieceType extension) const WARN_UNUSED_RESULT;
// Like above, but takes the extension as an ASCII string. See AppendASCII for
// details on how this is handled.
FilePath AddExtensionASCII(StringPiece extension) const WARN_UNUSED_RESULT;
// Replaces the extension of |file_name| with |extension|. If |file_name|
// does not have an extension, then |extension| is added. If |extension| is
// empty, then the extension is removed from |file_name|.
// Returns "" if BaseName() == "." or "..".
FilePath ReplaceExtension(StringPieceType extension) const WARN_UNUSED_RESULT;
// Returns true if the file path matches the specified extension. The test is
// case insensitive. Don't forget the leading period if appropriate.
bool MatchesExtension(StringPieceType extension) const;
// Returns a FilePath by appending a separator and the supplied path
// component to this object's path. Append takes care to avoid adding
// excessive separators if this object's path already ends with a separator.
// If this object's path is kCurrentDirectory, a new FilePath corresponding
// only to |component| is returned. |component| must be a relative path;
// it is an error to pass an absolute path.
FilePath Append(StringPieceType component) const WARN_UNUSED_RESULT;
FilePath Append(const FilePath& component) const WARN_UNUSED_RESULT;
// Although Windows StringType is std::wstring, since the encoding it uses for
// paths is well defined, it can handle ASCII path components as well.
// Mac uses UTF8, and since ASCII is a subset of that, it works there as well.
// On Linux, although it can use any 8-bit encoding for paths, we assume that
// ASCII is a valid subset, regardless of the encoding, since many operating
// system paths will always be ASCII.
FilePath AppendASCII(StringPiece component) const WARN_UNUSED_RESULT;
// Returns true if this FilePath contains an absolute path. On Windows, an
// absolute path begins with either a drive letter specification followed by
// a separator character, or with two separator characters. On POSIX
// platforms, an absolute path begins with a separator character.
bool IsAbsolute() const;
// Returns true if the patch ends with a path separator character.
bool EndsWithSeparator() const WARN_UNUSED_RESULT;
// Returns a copy of this FilePath that ends with a trailing separator. If
// the input path is empty, an empty FilePath will be returned.
FilePath AsEndingWithSeparator() const WARN_UNUSED_RESULT;
// Returns a copy of this FilePath that does not end with a trailing
// separator.
FilePath StripTrailingSeparators() const WARN_UNUSED_RESULT;
// Returns true if this FilePath contains an attempt to reference a parent
// directory (e.g. has a path component that is "..").
bool ReferencesParent() const;
// Return a Unicode human-readable version of this path.
// Warning: you can *not*, in general, go from a display name back to a real
// path. Only use this when displaying paths to users, not just when you
// want to stuff a string16 into some other API.
string16 LossyDisplayName() const;
// Return the path as ASCII, or the empty string if the path is not ASCII.
// This should only be used for cases where the FilePath is representing a
// known-ASCII filename.
std::string MaybeAsASCII() const;
// Return the path as UTF-8.
//
// This function is *unsafe* as there is no way to tell what encoding is
// used in file names on POSIX systems other than Mac and Chrome OS,
// although UTF-8 is practically used everywhere these days. To mitigate
// the encoding issue, this function internally calls
// SysNativeMBToWide() on POSIX systems other than Mac and Chrome OS,
// per assumption that the current locale's encoding is used in file
// names, but this isn't a perfect solution.
//
// Once it becomes safe to to stop caring about non-UTF-8 file names,
// the SysNativeMBToWide() hack will be removed from the code, along
// with "Unsafe" in the function name.
std::string AsUTF8Unsafe() const;
// Similar to AsUTF8Unsafe, but returns UTF-16 instead.
string16 AsUTF16Unsafe() const;
// Returns a FilePath object from a path name in UTF-8. This function
// should only be used for cases where you are sure that the input
// string is UTF-8.
//
// Like AsUTF8Unsafe(), this function is unsafe. This function
// internally calls SysWideToNativeMB() on POSIX systems other than Mac
// and Chrome OS, to mitigate the encoding issue. See the comment at
// AsUTF8Unsafe() for details.
static FilePath FromUTF8Unsafe(StringPiece utf8);
// Similar to FromUTF8Unsafe, but accepts UTF-16 instead.
static FilePath FromUTF16Unsafe(StringPiece16 utf16);
void WriteToPickle(Pickle* pickle) const;
bool ReadFromPickle(PickleIterator* iter);
// Normalize all path separators to backslash on Windows
// (if FILE_PATH_USES_WIN_SEPARATORS is true), or do nothing on POSIX systems.
FilePath NormalizePathSeparators() const;
// Normalize all path separattors to given type on Windows
// (if FILE_PATH_USES_WIN_SEPARATORS is true), or do nothing on POSIX systems.
FilePath NormalizePathSeparatorsTo(CharType separator) const;
// Compare two strings in the same way the file system does.
// Note that these always ignore case, even on file systems that are case-
// sensitive. If case-sensitive comparison is ever needed, add corresponding
// methods here.
// The methods are written as a static method so that they can also be used
// on parts of a file path, e.g., just the extension.
// CompareIgnoreCase() returns -1, 0 or 1 for less-than, equal-to and
// greater-than respectively.
static int CompareIgnoreCase(StringPieceType string1,
StringPieceType string2);
static bool CompareEqualIgnoreCase(StringPieceType string1,
StringPieceType string2) {
return CompareIgnoreCase(string1, string2) == 0;
}
static bool CompareLessIgnoreCase(StringPieceType string1,
StringPieceType string2) {
return CompareIgnoreCase(string1, string2) < 0;
}
#if defined(OS_MACOSX)
// Returns the string in the special canonical decomposed form as defined for
// HFS, which is close to, but not quite, decomposition form D. See
// http://developer.apple.com/mac/library/technotes/tn/tn1150.html#UnicodeSubtleties
// for further comments.
// Returns the epmty string if the conversion failed.
static StringType GetHFSDecomposedForm(StringPieceType string);
// Special UTF-8 version of FastUnicodeCompare. Cf:
// http://developer.apple.com/mac/library/technotes/tn/tn1150.html#StringComparisonAlgorithm
// IMPORTANT: The input strings must be in the special HFS decomposed form!
// (cf. above GetHFSDecomposedForm method)
static int HFSFastUnicodeCompare(StringPieceType string1,
StringPieceType string2);
#endif
#if defined(OS_ANDROID)
// On android, file selection dialog can return a file with content uri
// scheme(starting with content://). Content uri needs to be opened with
// ContentResolver to guarantee that the app has appropriate permissions
// to access it.
// Returns true if the path is a content uri, or false otherwise.
bool IsContentUri() const;
#endif
private:
// Remove trailing separators from this object. If the path is absolute, it
// will never be stripped any more than to refer to the absolute root
// directory, so "////" will become "/", not "". A leading pair of
// separators is never stripped, to support alternate roots. This is used to
// support UNC paths on Windows.
void StripTrailingSeparatorsInternal();
StringType path_;
};
BASE_EXPORT std::ostream& operator<<(std::ostream& out,
const FilePath& file_path);
} // namespace base
namespace std {
template <>
struct hash<base::FilePath> {
typedef base::FilePath argument_type;
typedef std::size_t result_type;
result_type operator()(argument_type const& f) const {
return hash<base::FilePath::StringType>()(f.value());
}
};
} // namespace std
#endif // BASE_FILES_FILE_PATH_H_

View file

@ -0,0 +1,25 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include "base/files/file_path.h"
#include "base/stl_util.h"
namespace base {
#if defined(FILE_PATH_USES_WIN_SEPARATORS)
const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("\\/");
#else // FILE_PATH_USES_WIN_SEPARATORS
const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("/");
#endif // FILE_PATH_USES_WIN_SEPARATORS
const size_t FilePath::kSeparatorsLength = base::size(kSeparators);
const FilePath::CharType FilePath::kCurrentDirectory[] = FILE_PATH_LITERAL(".");
const FilePath::CharType FilePath::kParentDirectory[] = FILE_PATH_LITERAL("..");
const FilePath::CharType FilePath::kExtensionSeparator = FILE_PATH_LITERAL('.');
} // namespace base

View file

@ -0,0 +1,46 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Cross platform methods for FilePathWatcher. See the various platform
// specific implementation files, too.
#include "base/files/file_path_watcher.h"
#include "base/logging.h"
#include "build/build_config.h"
namespace base {
FilePathWatcher::~FilePathWatcher() {
DCHECK(sequence_checker_.CalledOnValidSequence());
impl_->Cancel();
}
// static
bool FilePathWatcher::RecursiveWatchAvailable() {
#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) || \
defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_AIX)
return true;
#else
// FSEvents isn't available on iOS.
return false;
#endif
}
FilePathWatcher::PlatformDelegate::PlatformDelegate(): cancelled_(false) {
}
FilePathWatcher::PlatformDelegate::~PlatformDelegate() {
DCHECK(is_cancelled());
}
bool FilePathWatcher::Watch(const FilePath& path,
bool recursive,
const Callback& callback) {
DCHECK(sequence_checker_.CalledOnValidSequence());
DCHECK(path.IsAbsolute());
return impl_->Watch(path, recursive, callback);
}
} // namespace base

View file

@ -0,0 +1,111 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This module provides a way to monitor a file or directory for changes.
#ifndef BASE_FILES_FILE_PATH_WATCHER_H_
#define BASE_FILES_FILE_PATH_WATCHER_H_
#include <memory>
#include "base/base_export.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
namespace base {
// This class lets you register interest in changes on a FilePath.
// The callback will get called whenever the file or directory referenced by the
// FilePath is changed, including created or deleted. Due to limitations in the
// underlying OS APIs, FilePathWatcher has slightly different semantics on OS X
// than on Windows or Linux. FilePathWatcher on Linux and Windows will detect
// modifications to files in a watched directory. FilePathWatcher on Mac will
// detect the creation and deletion of files in a watched directory, but will
// not detect modifications to those files. See file_path_watcher_kqueue.cc for
// details.
//
// Must be destroyed on the sequence that invokes Watch().
class BASE_EXPORT FilePathWatcher {
public:
// Callback type for Watch(). |path| points to the file that was updated,
// and |error| is true if the platform specific code detected an error. In
// that case, the callback won't be invoked again.
using Callback =
base::RepeatingCallback<void(const FilePath& path, bool error)>;
// Used internally to encapsulate different members on different platforms.
class PlatformDelegate {
public:
PlatformDelegate();
virtual ~PlatformDelegate();
// Start watching for the given |path| and notify |delegate| about changes.
virtual bool Watch(const FilePath& path,
bool recursive,
const Callback& callback) WARN_UNUSED_RESULT = 0;
// Stop watching. This is called from FilePathWatcher's dtor in order to
// allow to shut down properly while the object is still alive.
virtual void Cancel() = 0;
protected:
friend class FilePathWatcher;
scoped_refptr<SequencedTaskRunner> task_runner() const {
return task_runner_;
}
void set_task_runner(scoped_refptr<SequencedTaskRunner> runner) {
task_runner_ = std::move(runner);
}
// Must be called before the PlatformDelegate is deleted.
void set_cancelled() {
cancelled_ = true;
}
bool is_cancelled() const {
return cancelled_;
}
private:
scoped_refptr<SequencedTaskRunner> task_runner_;
bool cancelled_;
DISALLOW_COPY_AND_ASSIGN(PlatformDelegate);
};
FilePathWatcher();
~FilePathWatcher();
// Returns true if the platform and OS version support recursive watches.
static bool RecursiveWatchAvailable();
// Invokes |callback| whenever updates to |path| are detected. This should be
// called at most once. Set |recursive| to true to watch |path| and its
// children. The callback will be invoked on the same sequence. Returns true
// on success.
//
// On POSIX, this must be called from a thread that supports
// FileDescriptorWatcher.
//
// Recursive watch is not supported on all platforms and file systems.
// Watch() will return false in the case of failure.
bool Watch(const FilePath& path, bool recursive, const Callback& callback);
private:
std::unique_ptr<PlatformDelegate> impl_;
SequenceChecker sequence_checker_;
DISALLOW_COPY_AND_ASSIGN(FilePathWatcher);
};
} // namespace base
#endif // BASE_FILES_FILE_PATH_WATCHER_H_

View file

@ -0,0 +1,285 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_path_watcher_fsevents.h"
#include <dispatch/dispatch.h>
#include <list>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/sequenced_task_runner_handle.h"
namespace base {
namespace {
// The latency parameter passed to FSEventsStreamCreate().
const CFAbsoluteTime kEventLatencySeconds = 0.3;
// Resolve any symlinks in the path.
FilePath ResolvePath(const FilePath& path) {
const unsigned kMaxLinksToResolve = 255;
std::vector<FilePath::StringType> component_vector;
path.GetComponents(&component_vector);
std::list<FilePath::StringType>
components(component_vector.begin(), component_vector.end());
FilePath result;
unsigned resolve_count = 0;
while (resolve_count < kMaxLinksToResolve && !components.empty()) {
FilePath component(*components.begin());
components.pop_front();
FilePath current;
if (component.IsAbsolute()) {
current = component;
} else {
current = result.Append(component);
}
FilePath target;
if (ReadSymbolicLink(current, &target)) {
if (target.IsAbsolute())
result.clear();
std::vector<FilePath::StringType> target_components;
target.GetComponents(&target_components);
components.insert(components.begin(), target_components.begin(),
target_components.end());
resolve_count++;
} else {
result = current;
}
}
if (resolve_count >= kMaxLinksToResolve)
result.clear();
return result;
}
} // namespace
FilePathWatcherFSEvents::FilePathWatcherFSEvents()
: queue_(dispatch_queue_create(
base::StringPrintf("org.chromium.base.FilePathWatcher.%p", this)
.c_str(),
DISPATCH_QUEUE_SERIAL)),
fsevent_stream_(nullptr),
weak_factory_(this) {}
FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {
DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
DCHECK(callback_.is_null())
<< "Cancel() must be called before FilePathWatcher is destroyed.";
}
bool FilePathWatcherFSEvents::Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) {
DCHECK(!callback.is_null());
DCHECK(callback_.is_null());
// This class could support non-recursive watches, but that is currently
// left to FilePathWatcherKQueue.
if (!recursive)
return false;
set_task_runner(SequencedTaskRunnerHandle::Get());
callback_ = callback;
FSEventStreamEventId start_event = FSEventsGetCurrentEventId();
// The block runtime would implicitly capture the reference, not the object
// it's referencing. Copy the path into a local, so that the value is
// captured by the block's scope.
const FilePath path_copy(path);
dispatch_async(queue_, ^{
StartEventStream(start_event, path_copy);
});
return true;
}
void FilePathWatcherFSEvents::Cancel() {
set_cancelled();
callback_.Reset();
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
// Switch to the dispatch queue to tear down the event stream. As the queue is
// owned by |this|, and this method is called from the destructor, execute the
// block synchronously.
dispatch_sync(queue_, ^{
if (fsevent_stream_) {
DestroyEventStream();
target_.clear();
resolved_target_.clear();
}
});
}
// static
void FilePathWatcherFSEvents::FSEventsCallback(
ConstFSEventStreamRef stream,
void* event_watcher,
size_t num_events,
void* event_paths,
const FSEventStreamEventFlags flags[],
const FSEventStreamEventId event_ids[]) {
FilePathWatcherFSEvents* watcher =
reinterpret_cast<FilePathWatcherFSEvents*>(event_watcher);
bool root_changed = watcher->ResolveTargetPath();
std::vector<FilePath> paths;
FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream);
for (size_t i = 0; i < num_events; i++) {
if (flags[i] & kFSEventStreamEventFlagRootChanged)
root_changed = true;
if (event_ids[i])
root_change_at = std::min(root_change_at, event_ids[i]);
paths.push_back(FilePath(
reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators());
}
// Reinitialize the event stream if we find changes to the root. This is
// necessary since FSEvents doesn't report any events for the subtree after
// the directory to be watched gets created.
if (root_changed) {
// Resetting the event stream from within the callback fails (FSEvents spews
// bad file descriptor errors), so do the reset asynchronously.
//
// We can't dispatch_async a call to UpdateEventStream() directly because
// there would be no guarantee that |watcher| still exists when it runs.
//
// Instead, bounce on task_runner() and use a WeakPtr to verify that
// |watcher| still exists. If it does, dispatch_async a call to
// UpdateEventStream(). Because the destructor of |watcher| runs on
// task_runner() and calls dispatch_sync, it is guaranteed that |watcher|
// still exists when UpdateEventStream() runs.
watcher->task_runner()->PostTask(
FROM_HERE, BindOnce(
[](WeakPtr<FilePathWatcherFSEvents> weak_watcher,
FSEventStreamEventId root_change_at) {
if (!weak_watcher)
return;
FilePathWatcherFSEvents* watcher = weak_watcher.get();
dispatch_async(watcher->queue_, ^{
watcher->UpdateEventStream(root_change_at);
});
},
watcher->weak_factory_.GetWeakPtr(), root_change_at));
}
watcher->OnFilePathsChanged(paths);
}
void FilePathWatcherFSEvents::OnFilePathsChanged(
const std::vector<FilePath>& paths) {
DCHECK(!resolved_target_.empty());
task_runner()->PostTask(
FROM_HERE,
BindOnce(&FilePathWatcherFSEvents::DispatchEvents,
weak_factory_.GetWeakPtr(), paths, target_, resolved_target_));
}
void FilePathWatcherFSEvents::DispatchEvents(const std::vector<FilePath>& paths,
const FilePath& target,
const FilePath& resolved_target) {
DCHECK(task_runner()->RunsTasksInCurrentSequence());
// Don't issue callbacks after Cancel() has been called.
if (is_cancelled() || callback_.is_null()) {
return;
}
for (const FilePath& path : paths) {
if (resolved_target.IsParent(path) || resolved_target == path) {
callback_.Run(target, false);
return;
}
}
}
void FilePathWatcherFSEvents::UpdateEventStream(
FSEventStreamEventId start_event) {
// It can happen that the watcher gets canceled while tasks that call this
// function are still in flight, so abort if this situation is detected.
if (resolved_target_.empty())
return;
if (fsevent_stream_)
DestroyEventStream();
ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString(
NULL, resolved_target_.value().c_str(), kCFStringEncodingMacHFS));
ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString(
NULL, resolved_target_.DirName().value().c_str(),
kCFStringEncodingMacHFS));
CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() };
ScopedCFTypeRef<CFArrayRef> watched_paths(
CFArrayCreate(NULL, reinterpret_cast<const void**>(paths_array),
base::size(paths_array), &kCFTypeArrayCallBacks));
FSEventStreamContext context;
context.version = 0;
context.info = this;
context.retain = NULL;
context.release = NULL;
context.copyDescription = NULL;
fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context,
watched_paths,
start_event,
kEventLatencySeconds,
kFSEventStreamCreateFlagWatchRoot);
FSEventStreamSetDispatchQueue(fsevent_stream_, queue_);
if (!FSEventStreamStart(fsevent_stream_)) {
task_runner()->PostTask(FROM_HERE,
BindOnce(&FilePathWatcherFSEvents::ReportError,
weak_factory_.GetWeakPtr(), target_));
}
}
bool FilePathWatcherFSEvents::ResolveTargetPath() {
FilePath resolved = ResolvePath(target_).StripTrailingSeparators();
bool changed = resolved != resolved_target_;
resolved_target_ = resolved;
if (resolved_target_.empty()) {
task_runner()->PostTask(FROM_HERE,
BindOnce(&FilePathWatcherFSEvents::ReportError,
weak_factory_.GetWeakPtr(), target_));
}
return changed;
}
void FilePathWatcherFSEvents::ReportError(const FilePath& target) {
DCHECK(task_runner()->RunsTasksInCurrentSequence());
if (!callback_.is_null()) {
callback_.Run(target, true);
}
}
void FilePathWatcherFSEvents::DestroyEventStream() {
FSEventStreamStop(fsevent_stream_);
FSEventStreamInvalidate(fsevent_stream_);
FSEventStreamRelease(fsevent_stream_);
fsevent_stream_ = NULL;
}
void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event,
const FilePath& path) {
DCHECK(resolved_target_.empty());
target_ = path;
ResolveTargetPath();
UpdateEventStream(start_event);
}
} // namespace base

View file

@ -0,0 +1,99 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_FILE_PATH_WATCHER_FSEVENTS_H_
#define BASE_FILES_FILE_PATH_WATCHER_FSEVENTS_H_
#include <CoreServices/CoreServices.h>
#include <stddef.h>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
#include "base/mac/scoped_dispatch_object.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
namespace base {
// Mac-specific file watcher implementation based on FSEvents.
// There are trade-offs between the FSEvents implementation and a kqueue
// implementation. The biggest issues are that FSEvents on 10.6 sometimes drops
// events and kqueue does not trigger for modifications to a file in a watched
// directory. See file_path_watcher_mac.cc for the code that decides when to
// use which one.
class FilePathWatcherFSEvents : public FilePathWatcher::PlatformDelegate {
public:
FilePathWatcherFSEvents();
~FilePathWatcherFSEvents() override;
// FilePathWatcher::PlatformDelegate overrides.
bool Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) override;
void Cancel() override;
private:
static void FSEventsCallback(ConstFSEventStreamRef stream,
void* event_watcher,
size_t num_events,
void* event_paths,
const FSEventStreamEventFlags flags[],
const FSEventStreamEventId event_ids[]);
// Called from FSEventsCallback whenever there is a change to the paths.
void OnFilePathsChanged(const std::vector<FilePath>& paths);
// Called on the task_runner() thread to dispatch path events. Can't access
// target_ and resolved_target_ directly as those are modified on the
// libdispatch thread.
void DispatchEvents(const std::vector<FilePath>& paths,
const FilePath& target,
const FilePath& resolved_target);
// (Re-)Initialize the event stream to start reporting events from
// |start_event|.
void UpdateEventStream(FSEventStreamEventId start_event);
// Returns true if resolving the target path got a different result than
// last time it was done.
bool ResolveTargetPath();
// Report an error watching the given target.
void ReportError(const FilePath& target);
// Destroy the event stream.
void DestroyEventStream();
// Start watching the FSEventStream.
void StartEventStream(FSEventStreamEventId start_event, const FilePath& path);
// Callback to notify upon changes.
// (Only accessed from the task_runner() thread.)
FilePathWatcher::Callback callback_;
// The dispatch queue on which the the event stream is scheduled.
ScopedDispatchObject<dispatch_queue_t> queue_;
// Target path to watch (passed to callback).
// (Only accessed from the libdispatch queue.)
FilePath target_;
// Target path with all symbolic links resolved.
// (Only accessed from the libdispatch queue.)
FilePath resolved_target_;
// Backend stream we receive event callbacks from (strong reference).
// (Only accessed from the libdispatch queue.)
FSEventStreamRef fsevent_stream_;
WeakPtrFactory<FilePathWatcherFSEvents> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(FilePathWatcherFSEvents);
};
} // namespace base
#endif // BASE_FILES_FILE_PATH_WATCHER_FSEVENTS_H_

View file

@ -0,0 +1,55 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_path_watcher.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
namespace base {
namespace {
class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate {
public:
FilePathWatcherImpl() {}
~FilePathWatcherImpl() override {}
bool Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) override;
void Cancel() override;
private:
FilePathWatcher::Callback callback_;
FilePath target_;
DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
};
bool FilePathWatcherImpl::Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) {
DCHECK(!callback.is_null());
DCHECK(callback_.is_null());
callback_ = callback;
NOTIMPLEMENTED();
return false;
}
void FilePathWatcherImpl::Cancel() {
NOTIMPLEMENTED();
}
} // namespace
FilePathWatcher::FilePathWatcher() {
sequence_checker_.DetachFromSequence();
impl_ = std::make_unique<FilePathWatcherImpl>();
}
} // namespace base

View file

@ -0,0 +1,377 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_path_watcher_kqueue.h"
#include <fcntl.h>
#include <stddef.h>
#include <sys/param.h>
#include "base/bind.h"
#include "base/file_descriptor_posix.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/sequenced_task_runner_handle.h"
// On some platforms these are not defined.
#if !defined(EV_RECEIPT)
#define EV_RECEIPT 0
#endif
#if !defined(O_EVTONLY)
#define O_EVTONLY O_RDONLY
#endif
namespace base {
FilePathWatcherKQueue::FilePathWatcherKQueue() : kqueue_(-1) {}
FilePathWatcherKQueue::~FilePathWatcherKQueue() {
DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
}
void FilePathWatcherKQueue::ReleaseEvent(struct kevent& event) {
CloseFileDescriptor(&event.ident);
EventData* entry = EventDataForKevent(event);
delete entry;
event.udata = NULL;
}
int FilePathWatcherKQueue::EventsForPath(FilePath path, EventVector* events) {
// Make sure that we are working with a clean slate.
DCHECK(events->empty());
std::vector<FilePath::StringType> components;
path.GetComponents(&components);
if (components.size() < 1) {
return -1;
}
int last_existing_entry = 0;
FilePath built_path;
bool path_still_exists = true;
for (std::vector<FilePath::StringType>::iterator i = components.begin();
i != components.end(); ++i) {
if (i == components.begin()) {
built_path = FilePath(*i);
} else {
built_path = built_path.Append(*i);
}
uintptr_t fd = kNoFileDescriptor;
if (path_still_exists) {
fd = FileDescriptorForPath(built_path);
if (fd == kNoFileDescriptor) {
path_still_exists = false;
} else {
++last_existing_entry;
}
}
FilePath::StringType subdir = (i != (components.end() - 1)) ? *(i + 1) : "";
EventData* data = new EventData(built_path, subdir);
struct kevent event;
EV_SET(&event, fd, EVFILT_VNODE, (EV_ADD | EV_CLEAR | EV_RECEIPT),
(NOTE_DELETE | NOTE_WRITE | NOTE_ATTRIB |
NOTE_RENAME | NOTE_REVOKE | NOTE_EXTEND), 0, data);
events->push_back(event);
}
return last_existing_entry;
}
uintptr_t FilePathWatcherKQueue::FileDescriptorForPath(const FilePath& path) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
int fd = HANDLE_EINTR(open(path.value().c_str(), O_EVTONLY));
if (fd == kInvalidFd)
return kNoFileDescriptor;
return fd;
}
void FilePathWatcherKQueue::CloseFileDescriptor(uintptr_t* fd) {
if (*fd == kNoFileDescriptor) {
return;
}
if (IGNORE_EINTR(close(*fd)) != 0) {
DPLOG(ERROR) << "close";
}
*fd = kNoFileDescriptor;
}
bool FilePathWatcherKQueue::AreKeventValuesValid(struct kevent* kevents,
int count) {
if (count < 0) {
DPLOG(ERROR) << "kevent";
return false;
}
bool valid = true;
for (int i = 0; i < count; ++i) {
if (kevents[i].flags & EV_ERROR && kevents[i].data) {
// Find the kevent in |events_| that matches the kevent with the error.
EventVector::iterator event = events_.begin();
for (; event != events_.end(); ++event) {
if (event->ident == kevents[i].ident) {
break;
}
}
std::string path_name;
if (event != events_.end()) {
EventData* event_data = EventDataForKevent(*event);
if (event_data != NULL) {
path_name = event_data->path_.value();
}
}
if (path_name.empty()) {
path_name = base::StringPrintf(
"fd %ld", reinterpret_cast<long>(&kevents[i].ident));
}
DLOG(ERROR) << "Error: " << kevents[i].data << " for " << path_name;
valid = false;
}
}
return valid;
}
void FilePathWatcherKQueue::HandleAttributesChange(
const EventVector::iterator& event,
bool* target_file_affected,
bool* update_watches) {
EventVector::iterator next_event = event + 1;
EventData* next_event_data = EventDataForKevent(*next_event);
// Check to see if the next item in path is still accessible.
uintptr_t have_access = FileDescriptorForPath(next_event_data->path_);
if (have_access == kNoFileDescriptor) {
*target_file_affected = true;
*update_watches = true;
EventVector::iterator local_event(event);
for (; local_event != events_.end(); ++local_event) {
// Close all nodes from the event down. This has the side effect of
// potentially rendering other events in |updates| invalid.
// There is no need to remove the events from |kqueue_| because this
// happens as a side effect of closing the file descriptor.
CloseFileDescriptor(&local_event->ident);
}
} else {
CloseFileDescriptor(&have_access);
}
}
void FilePathWatcherKQueue::HandleDeleteOrMoveChange(
const EventVector::iterator& event,
bool* target_file_affected,
bool* update_watches) {
*target_file_affected = true;
*update_watches = true;
EventVector::iterator local_event(event);
for (; local_event != events_.end(); ++local_event) {
// Close all nodes from the event down. This has the side effect of
// potentially rendering other events in |updates| invalid.
// There is no need to remove the events from |kqueue_| because this
// happens as a side effect of closing the file descriptor.
CloseFileDescriptor(&local_event->ident);
}
}
void FilePathWatcherKQueue::HandleCreateItemChange(
const EventVector::iterator& event,
bool* target_file_affected,
bool* update_watches) {
// Get the next item in the path.
EventVector::iterator next_event = event + 1;
// Check to see if it already has a valid file descriptor.
if (!IsKeventFileDescriptorOpen(*next_event)) {
EventData* next_event_data = EventDataForKevent(*next_event);
// If not, attempt to open a file descriptor for it.
next_event->ident = FileDescriptorForPath(next_event_data->path_);
if (IsKeventFileDescriptorOpen(*next_event)) {
*update_watches = true;
if (next_event_data->subdir_.empty()) {
*target_file_affected = true;
}
}
}
}
bool FilePathWatcherKQueue::UpdateWatches(bool* target_file_affected) {
// Iterate over events adding kevents for items that exist to the kqueue.
// Then check to see if new components in the path have been created.
// Repeat until no new components in the path are detected.
// This is to get around races in directory creation in a watched path.
bool update_watches = true;
while (update_watches) {
size_t valid;
for (valid = 0; valid < events_.size(); ++valid) {
if (!IsKeventFileDescriptorOpen(events_[valid])) {
break;
}
}
if (valid == 0) {
// The root of the file path is inaccessible?
return false;
}
EventVector updates(valid);
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
int count = HANDLE_EINTR(kevent(kqueue_, &events_[0], valid, &updates[0],
valid, NULL));
if (!AreKeventValuesValid(&updates[0], count)) {
return false;
}
update_watches = false;
for (; valid < events_.size(); ++valid) {
EventData* event_data = EventDataForKevent(events_[valid]);
events_[valid].ident = FileDescriptorForPath(event_data->path_);
if (IsKeventFileDescriptorOpen(events_[valid])) {
update_watches = true;
if (event_data->subdir_.empty()) {
*target_file_affected = true;
}
} else {
break;
}
}
}
return true;
}
bool FilePathWatcherKQueue::Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) {
DCHECK(target_.value().empty()); // Can only watch one path.
DCHECK(!callback.is_null());
DCHECK_EQ(kqueue_, -1);
// Recursive watch is not supported using kqueue.
DCHECK(!recursive);
callback_ = callback;
target_ = path;
set_task_runner(SequencedTaskRunnerHandle::Get());
kqueue_ = kqueue();
if (kqueue_ == -1) {
DPLOG(ERROR) << "kqueue";
return false;
}
int last_entry = EventsForPath(target_, &events_);
DCHECK_NE(last_entry, 0);
EventVector responses(last_entry);
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
int count = HANDLE_EINTR(kevent(kqueue_, &events_[0], last_entry,
&responses[0], last_entry, NULL));
if (!AreKeventValuesValid(&responses[0], count)) {
// Calling Cancel() here to close any file descriptors that were opened.
// This would happen in the destructor anyways, but FilePathWatchers tend to
// be long lived, and if an error has occurred, there is no reason to waste
// the file descriptors.
Cancel();
return false;
}
// It's safe to use Unretained() because the watch is cancelled and the
// callback cannot be invoked after |kqueue_watch_controller_| (which is a
// member of |this|) has been deleted.
kqueue_watch_controller_ = FileDescriptorWatcher::WatchReadable(
kqueue_, BindRepeating(&FilePathWatcherKQueue::OnKQueueReadable,
Unretained(this)));
return true;
}
void FilePathWatcherKQueue::Cancel() {
if (!task_runner()) {
set_cancelled();
return;
}
DCHECK(task_runner()->RunsTasksInCurrentSequence());
if (!is_cancelled()) {
set_cancelled();
kqueue_watch_controller_.reset();
if (IGNORE_EINTR(close(kqueue_)) != 0) {
DPLOG(ERROR) << "close kqueue";
}
kqueue_ = -1;
std::for_each(events_.begin(), events_.end(), ReleaseEvent);
events_.clear();
callback_.Reset();
}
}
void FilePathWatcherKQueue::OnKQueueReadable() {
DCHECK(task_runner()->RunsTasksInCurrentSequence());
DCHECK(events_.size());
// Request the file system update notifications that have occurred and return
// them in |updates|. |count| will contain the number of updates that have
// occurred.
EventVector updates(events_.size());
struct timespec timeout = {0, 0};
int count = HANDLE_EINTR(kevent(kqueue_, NULL, 0, &updates[0], updates.size(),
&timeout));
// Error values are stored within updates, so check to make sure that no
// errors occurred.
if (!AreKeventValuesValid(&updates[0], count)) {
callback_.Run(target_, true /* error */);
Cancel();
return;
}
bool update_watches = false;
bool send_notification = false;
// Iterate through each of the updates and react to them.
for (int i = 0; i < count; ++i) {
// Find our kevent record that matches the update notification.
EventVector::iterator event = events_.begin();
for (; event != events_.end(); ++event) {
if (!IsKeventFileDescriptorOpen(*event) ||
event->ident == updates[i].ident) {
break;
}
}
if (event == events_.end() || !IsKeventFileDescriptorOpen(*event)) {
// The event may no longer exist in |events_| because another event
// modified |events_| in such a way to make it invalid. For example if
// the path is /foo/bar/bam and foo is deleted, NOTE_DELETE events for
// foo, bar and bam will be sent. If foo is processed first, then
// the file descriptors for bar and bam will already be closed and set
// to -1 before they get a chance to be processed.
continue;
}
EventData* event_data = EventDataForKevent(*event);
// If the subdir is empty, this is the last item on the path and is the
// target file.
bool target_file_affected = event_data->subdir_.empty();
if ((updates[i].fflags & NOTE_ATTRIB) && !target_file_affected) {
HandleAttributesChange(event, &target_file_affected, &update_watches);
}
if (updates[i].fflags & (NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME)) {
HandleDeleteOrMoveChange(event, &target_file_affected, &update_watches);
}
if ((updates[i].fflags & NOTE_WRITE) && !target_file_affected) {
HandleCreateItemChange(event, &target_file_affected, &update_watches);
}
send_notification |= target_file_affected;
}
if (update_watches) {
if (!UpdateWatches(&send_notification)) {
callback_.Run(target_, true /* error */);
Cancel();
}
}
if (send_notification) {
callback_.Run(target_, false);
}
}
} // namespace base

View file

@ -0,0 +1,125 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_FILE_PATH_WATCHER_KQUEUE_H_
#define BASE_FILES_FILE_PATH_WATCHER_KQUEUE_H_
#include <sys/event.h>
#include <memory>
#include <vector>
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
#include "base/macros.h"
namespace base {
// Mac-specific file watcher implementation based on kqueue.
// The Linux and Windows versions are able to detect:
// - file creation/deletion/modification in a watched directory
// - file creation/deletion/modification for a watched file
// - modifications to the paths to a watched object that would affect the
// object such as renaming/attibute changes etc.
// The kqueue implementation will handle all of the items in the list above
// except for detecting modifications to files in a watched directory. It will
// detect the creation and deletion of files, just not the modification of
// files. It does however detect the attribute changes that the FSEvents impl
// would miss.
class FilePathWatcherKQueue : public FilePathWatcher::PlatformDelegate {
public:
FilePathWatcherKQueue();
~FilePathWatcherKQueue() override;
// FilePathWatcher::PlatformDelegate overrides.
bool Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) override;
void Cancel() override;
private:
class EventData {
public:
EventData(const FilePath& path, const FilePath::StringType& subdir)
: path_(path), subdir_(subdir) { }
FilePath path_; // Full path to this item.
FilePath::StringType subdir_; // Path to any sub item.
};
typedef std::vector<struct kevent> EventVector;
// Called when data is available in |kqueue_|.
void OnKQueueReadable();
// Returns true if the kevent values are error free.
bool AreKeventValuesValid(struct kevent* kevents, int count);
// Respond to a change of attributes of the path component represented by
// |event|. Sets |target_file_affected| to true if |target_| is affected.
// Sets |update_watches| to true if |events_| need to be updated.
void HandleAttributesChange(const EventVector::iterator& event,
bool* target_file_affected,
bool* update_watches);
// Respond to a move or deletion of the path component represented by
// |event|. Sets |target_file_affected| to true if |target_| is affected.
// Sets |update_watches| to true if |events_| need to be updated.
void HandleDeleteOrMoveChange(const EventVector::iterator& event,
bool* target_file_affected,
bool* update_watches);
// Respond to a creation of an item in the path component represented by
// |event|. Sets |target_file_affected| to true if |target_| is affected.
// Sets |update_watches| to true if |events_| need to be updated.
void HandleCreateItemChange(const EventVector::iterator& event,
bool* target_file_affected,
bool* update_watches);
// Update |events_| with the current status of the system.
// Sets |target_file_affected| to true if |target_| is affected.
// Returns false if an error occurs.
bool UpdateWatches(bool* target_file_affected);
// Fills |events| with one kevent per component in |path|.
// Returns the number of valid events created where a valid event is
// defined as one that has a ident (file descriptor) field != -1.
static int EventsForPath(FilePath path, EventVector *events);
// Release a kevent generated by EventsForPath.
static void ReleaseEvent(struct kevent& event);
// Returns a file descriptor that will not block the system from deleting
// the file it references.
static uintptr_t FileDescriptorForPath(const FilePath& path);
static const uintptr_t kNoFileDescriptor = static_cast<uintptr_t>(-1);
// Closes |*fd| and sets |*fd| to -1.
static void CloseFileDescriptor(uintptr_t* fd);
// Returns true if kevent has open file descriptor.
static bool IsKeventFileDescriptorOpen(const struct kevent& event) {
return event.ident != kNoFileDescriptor;
}
static EventData* EventDataForKevent(const struct kevent& event) {
return reinterpret_cast<EventData*>(event.udata);
}
EventVector events_;
FilePathWatcher::Callback callback_;
FilePath target_;
int kqueue_;
// Throughout the lifetime of this, OnKQueueReadable() will be called when
// data is available in |kqueue_|.
std::unique_ptr<FileDescriptorWatcher::Controller> kqueue_watch_controller_;
DISALLOW_COPY_AND_ASSIGN(FilePathWatcherKQueue);
};
} // namespace base
#endif // BASE_FILES_FILE_PATH_WATCHER_KQUEUE_H_

View file

@ -0,0 +1,749 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_path_watcher.h"
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <unistd.h>
#include <algorithm>
#include <fstream>
#include <map>
#include <memory>
#include <set>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/posix/eintr_wrapper.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/synchronization/lock.h"
#include "base/threading/platform_thread.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
namespace base {
namespace {
// The /proc path to max_user_watches.
constexpr char kInotifyMaxUserWatchesPath[] =
"/proc/sys/fs/inotify/max_user_watches";
// This is a soft limit. If there are more than |kExpectedFilePathWatches|
// FilePathWatchers for a user, than they might affect each other's inotify
// watchers limit.
constexpr int kExpectedFilePathWatchers = 16;
// The default max inotify watchers limit per user, if reading
// /proc/sys/fs/inotify/max_user_watches fails.
constexpr int kDefaultInotifyMaxUserWatches = 8192;
class FilePathWatcherImpl;
class InotifyReader;
// Get the maximum number of inotify watches can be used by a FilePathWatcher
// instance. This is based on /proc/sys/fs/inotify/max_user_watches entry.
int GetMaxNumberOfInotifyWatches() {
const static int max = []() {
int max_number_of_inotify_watches = 0;
std::ifstream in(kInotifyMaxUserWatchesPath);
if (!in.is_open() || !(in >> max_number_of_inotify_watches)) {
LOG(ERROR) << "Failed to read " << kInotifyMaxUserWatchesPath;
return kDefaultInotifyMaxUserWatches / kExpectedFilePathWatchers;
}
return max_number_of_inotify_watches / kExpectedFilePathWatchers;
}();
return max;
}
class InotifyReaderThreadDelegate final : public PlatformThread::Delegate {
public:
explicit InotifyReaderThreadDelegate(int inotify_fd)
: inotify_fd_(inotify_fd) {}
~InotifyReaderThreadDelegate() override = default;
private:
void ThreadMain() override;
const int inotify_fd_;
DISALLOW_COPY_AND_ASSIGN(InotifyReaderThreadDelegate);
};
// Singleton to manage all inotify watches.
// TODO(tony): It would be nice if this wasn't a singleton.
// http://crbug.com/38174
class InotifyReader {
public:
using Watch = int; // Watch descriptor used by AddWatch() and RemoveWatch().
static constexpr Watch kInvalidWatch = -1;
static constexpr Watch kWatchLimitExceeded = -2;
// Watch directory |path| for changes. |watcher| will be notified on each
// change. Returns |kInvalidWatch| on failure.
Watch AddWatch(const FilePath& path, FilePathWatcherImpl* watcher);
// Remove |watch| if it's valid.
void RemoveWatch(Watch watch, FilePathWatcherImpl* watcher);
// Callback for InotifyReaderTask.
void OnInotifyEvent(const inotify_event* event);
private:
friend struct LazyInstanceTraitsBase<InotifyReader>;
InotifyReader();
// There is no destructor because |g_inotify_reader| is a
// base::LazyInstace::Leaky object. Having a destructor causes build
// issues with GCC 6 (http://crbug.com/636346).
// Returns true on successful thread creation.
bool StartThread();
// Lock to protect |watchers_|.
Lock lock_;
// We keep track of which delegates want to be notified on which watches.
std::unordered_map<Watch, std::set<FilePathWatcherImpl*>> watchers_;
// File descriptor returned by inotify_init.
const int inotify_fd_;
// Thread delegate for the Inotify thread.
InotifyReaderThreadDelegate thread_delegate_;
// Flag set to true when startup was successful.
bool valid_ = false;
DISALLOW_COPY_AND_ASSIGN(InotifyReader);
};
class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate {
public:
FilePathWatcherImpl();
~FilePathWatcherImpl() override;
// Called for each event coming from the watch. |fired_watch| identifies the
// watch that fired, |child| indicates what has changed, and is relative to
// the currently watched path for |fired_watch|.
//
// |created| is true if the object appears.
// |deleted| is true if the object disappears.
// |is_dir| is true if the object is a directory.
void OnFilePathChanged(InotifyReader::Watch fired_watch,
const FilePath::StringType& child,
bool created,
bool deleted,
bool is_dir);
// Increase the number of inotify watches associated to this
// FilePathWatcherImpl instance.
bool IncreaseWatch();
// Decrease the number of inotify watches associated to this
// FilePathWatcherImpl instance.
void DecreaseWatch();
private:
void OnFilePathChangedOnOriginSequence(InotifyReader::Watch fired_watch,
const FilePath::StringType& child,
bool created,
bool deleted,
bool is_dir);
// Start watching |path| for changes and notify |delegate| on each change.
// Returns true if watch for |path| has been added successfully.
bool Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) override;
// Cancel the watch. This unregisters the instance with InotifyReader.
void Cancel() override;
// Inotify watches are installed for all directory components of |target_|.
// A WatchEntry instance holds:
// - |watch|: the watch descriptor for a component.
// - |subdir|: the subdirectory that identifies the next component.
// - For the last component, there is no next component, so it is empty.
// - |linkname|: the target of the symlink.
// - Only if the target being watched is a symbolic link.
struct WatchEntry {
explicit WatchEntry(const FilePath::StringType& dirname)
: watch(InotifyReader::kInvalidWatch),
subdir(dirname) {}
InotifyReader::Watch watch;
FilePath::StringType subdir;
FilePath::StringType linkname;
};
// Reconfigure to watch for the most specific parent directory of |target_|
// that exists. Also calls UpdateRecursiveWatches() below.
void UpdateWatches();
// Reconfigure to recursively watch |target_| and all its sub-directories.
// - This is a no-op if the watch is not recursive.
// - If |target_| does not exist, then clear all the recursive watches.
// - Assuming |target_| exists, passing kInvalidWatch as |fired_watch| forces
// addition of recursive watches for |target_|.
// - Otherwise, only the directory associated with |fired_watch| and its
// sub-directories will be reconfigured.
void UpdateRecursiveWatches(InotifyReader::Watch fired_watch, bool is_dir);
// Enumerate recursively through |path| and add / update watches.
void UpdateRecursiveWatchesForPath(const FilePath& path);
// Do internal bookkeeping to update mappings between |watch| and its
// associated full path |path|.
void TrackWatchForRecursion(InotifyReader::Watch watch, const FilePath& path);
// Remove all the recursive watches.
void RemoveRecursiveWatches();
// |path| is a symlink to a non-existent target. Attempt to add a watch to
// the link target's parent directory. Update |watch_entry| on success.
void AddWatchForBrokenSymlink(const FilePath& path, WatchEntry* watch_entry);
bool HasValidWatchVector() const;
// Callback to notify upon changes.
FilePathWatcher::Callback callback_;
// The file or directory we're supposed to watch.
FilePath target_;
bool recursive_ = false;
// The vector of watches and next component names for all path components,
// starting at the root directory. The last entry corresponds to the watch for
// |target_| and always stores an empty next component name in |subdir|.
std::vector<WatchEntry> watches_;
// The number of inotify watches currently associated with this instance.
int number_of_inotify_watches_ = 0;
std::unordered_map<InotifyReader::Watch, FilePath> recursive_paths_by_watch_;
std::map<FilePath, InotifyReader::Watch> recursive_watches_by_path_;
// Read only while INotifyReader::lock_ is held, and used to post asynchronous
// notifications to the Watcher on its home task_runner(). Ideally this should
// be const, but since it is initialized from |weak_factory_|, which must
// appear after it, that is not possible.
WeakPtr<FilePathWatcherImpl> weak_ptr_;
WeakPtrFactory<FilePathWatcherImpl> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
};
LazyInstance<InotifyReader>::Leaky g_inotify_reader = LAZY_INSTANCE_INITIALIZER;
void InotifyReaderThreadDelegate::ThreadMain() {
PlatformThread::SetName("inotify_reader");
// Make sure the file descriptors are good for use with select().
CHECK_LE(0, inotify_fd_);
CHECK_GT(FD_SETSIZE, inotify_fd_);
while (true) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(inotify_fd_, &rfds);
// Wait until some inotify events are available.
int select_result =
HANDLE_EINTR(select(inotify_fd_ + 1, &rfds, nullptr, nullptr, nullptr));
if (select_result < 0) {
DPLOG(WARNING) << "select failed";
return;
}
// Adjust buffer size to current event queue size.
int buffer_size;
int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD, &buffer_size));
if (ioctl_result != 0) {
DPLOG(WARNING) << "ioctl failed";
return;
}
std::vector<char> buffer(buffer_size);
ssize_t bytes_read =
HANDLE_EINTR(read(inotify_fd_, &buffer[0], buffer_size));
if (bytes_read < 0) {
DPLOG(WARNING) << "read from inotify fd failed";
return;
}
ssize_t i = 0;
while (i < bytes_read) {
inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]);
size_t event_size = sizeof(inotify_event) + event->len;
DCHECK(i + event_size <= static_cast<size_t>(bytes_read));
g_inotify_reader.Get().OnInotifyEvent(event);
i += event_size;
}
}
}
InotifyReader::InotifyReader()
: inotify_fd_(inotify_init()), thread_delegate_(inotify_fd_) {
if (inotify_fd_ < 0) {
PLOG(ERROR) << "inotify_init() failed";
return;
}
if (!StartThread())
return;
valid_ = true;
}
bool InotifyReader::StartThread() {
// This object is LazyInstance::Leaky, so thread_delegate_ will outlive the
// thread.
return PlatformThread::CreateNonJoinable(0, &thread_delegate_);
}
InotifyReader::Watch InotifyReader::AddWatch(
const FilePath& path, FilePathWatcherImpl* watcher) {
if (!valid_)
return kInvalidWatch;
AutoLock auto_lock(lock_);
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::WILL_BLOCK);
if (!watcher->IncreaseWatch())
return kWatchLimitExceeded;
Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(),
IN_ATTRIB | IN_CREATE | IN_DELETE |
IN_CLOSE_WRITE | IN_MOVE |
IN_ONLYDIR);
if (watch == kInvalidWatch) {
// This watch shouldn't be counted.
watcher->DecreaseWatch();
return kInvalidWatch;
}
watchers_[watch].insert(watcher);
return watch;
}
void InotifyReader::RemoveWatch(Watch watch, FilePathWatcherImpl* watcher) {
if (!valid_ || (watch == kInvalidWatch))
return;
AutoLock auto_lock(lock_);
watchers_[watch].erase(watcher);
watcher->DecreaseWatch();
if (watchers_[watch].empty()) {
watchers_.erase(watch);
ScopedBlockingCall scoped_blocking_call(FROM_HERE,
BlockingType::WILL_BLOCK);
inotify_rm_watch(inotify_fd_, watch);
}
}
void InotifyReader::OnInotifyEvent(const inotify_event* event) {
if (event->mask & IN_IGNORED)
return;
FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL(""));
AutoLock auto_lock(lock_);
auto& watcher_set = watchers_[event->wd];
for (FilePathWatcherImpl* watcher : watcher_set) {
watcher->OnFilePathChanged(
event->wd, child, event->mask & (IN_CREATE | IN_MOVED_TO),
event->mask & (IN_DELETE | IN_MOVED_FROM), event->mask & IN_ISDIR);
}
}
FilePathWatcherImpl::FilePathWatcherImpl() {
weak_ptr_ = weak_factory_.GetWeakPtr();
}
FilePathWatcherImpl::~FilePathWatcherImpl() {
DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
}
void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch,
const FilePath::StringType& child,
bool created,
bool deleted,
bool is_dir) {
DCHECK(!task_runner()->RunsTasksInCurrentSequence());
// This method is invoked on the Inotify thread. Switch to task_runner() to
// access |watches_| safely. Use a WeakPtr to prevent the callback from
// running after |this| is destroyed (i.e. after the watch is cancelled).
task_runner()->PostTask(
FROM_HERE,
BindOnce(&FilePathWatcherImpl::OnFilePathChangedOnOriginSequence,
weak_ptr_, fired_watch, child, created, deleted, is_dir));
}
void FilePathWatcherImpl::OnFilePathChangedOnOriginSequence(
InotifyReader::Watch fired_watch,
const FilePath::StringType& child,
bool created,
bool deleted,
bool is_dir) {
DCHECK(task_runner()->RunsTasksInCurrentSequence());
DCHECK(!watches_.empty());
DCHECK(HasValidWatchVector());
// Used below to avoid multiple recursive updates.
bool did_update = false;
// Find the entry in |watches_| that corresponds to |fired_watch|.
for (size_t i = 0; i < watches_.size(); ++i) {
const WatchEntry& watch_entry = watches_[i];
if (fired_watch != watch_entry.watch)
continue;
// Check whether a path component of |target_| changed.
bool change_on_target_path =
child.empty() ||
(child == watch_entry.linkname) ||
(child == watch_entry.subdir);
// Check if the change references |target_| or a direct child of |target_|.
bool target_changed;
if (watch_entry.subdir.empty()) {
// The fired watch is for a WatchEntry without a subdir. Thus for a given
// |target_| = "/path/to/foo", this is for "foo". Here, check either:
// - the target has no symlink: it is the target and it changed.
// - the target has a symlink, and it matches |child|.
target_changed = (watch_entry.linkname.empty() ||
child == watch_entry.linkname);
} else {
// The fired watch is for a WatchEntry with a subdir. Thus for a given
// |target_| = "/path/to/foo", this is for {"/", "/path", "/path/to"}.
// So we can safely access the next WatchEntry since we have not reached
// the end yet. Check |watch_entry| is for "/path/to", i.e. the next
// element is "foo".
bool next_watch_may_be_for_target = watches_[i + 1].subdir.empty();
if (next_watch_may_be_for_target) {
// The current |watch_entry| is for "/path/to", so check if the |child|
// that changed is "foo".
target_changed = watch_entry.subdir == child;
} else {
// The current |watch_entry| is not for "/path/to", so the next entry
// cannot be "foo". Thus |target_| has not changed.
target_changed = false;
}
}
// Update watches if a directory component of the |target_| path
// (dis)appears. Note that we don't add the additional restriction of
// checking the event mask to see if it is for a directory here as changes
// to symlinks on the target path will not have IN_ISDIR set in the event
// masks. As a result we may sometimes call UpdateWatches() unnecessarily.
if (change_on_target_path && (created || deleted) && !did_update) {
UpdateWatches();
did_update = true;
}
// Report the following events:
// - The target or a direct child of the target got changed (in case the
// watched path refers to a directory).
// - One of the parent directories got moved or deleted, since the target
// disappears in this case.
// - One of the parent directories appears. The event corresponding to
// the target appearing might have been missed in this case, so recheck.
if (target_changed ||
(change_on_target_path && deleted) ||
(change_on_target_path && created && PathExists(target_))) {
if (!did_update) {
UpdateRecursiveWatches(fired_watch, is_dir);
did_update = true;
}
callback_.Run(target_, false /* error */);
return;
}
}
if (Contains(recursive_paths_by_watch_, fired_watch)) {
if (!did_update)
UpdateRecursiveWatches(fired_watch, is_dir);
callback_.Run(target_, false /* error */);
}
}
bool FilePathWatcherImpl::IncreaseWatch() {
if (number_of_inotify_watches_ >= GetMaxNumberOfInotifyWatches()) {
// Notify that error happened since we are hitting the inotify watches
// limit.
callback_.Run(target_, true /* error */);
return false;
}
++number_of_inotify_watches_;
return true;
}
void FilePathWatcherImpl::DecreaseWatch() {
--number_of_inotify_watches_;
// Sanity check:
DCHECK_GE(number_of_inotify_watches_, 0);
}
bool FilePathWatcherImpl::Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) {
DCHECK(target_.empty());
set_task_runner(SequencedTaskRunnerHandle::Get());
callback_ = callback;
target_ = path;
recursive_ = recursive;
std::vector<FilePath::StringType> comps;
target_.GetComponents(&comps);
DCHECK(!comps.empty());
for (size_t i = 1; i < comps.size(); ++i)
watches_.push_back(WatchEntry(comps[i]));
watches_.push_back(WatchEntry(FilePath::StringType()));
UpdateWatches();
return true;
}
void FilePathWatcherImpl::Cancel() {
if (!callback_) {
// Watch() was never called.
set_cancelled();
return;
}
DCHECK(task_runner()->RunsTasksInCurrentSequence());
DCHECK(!is_cancelled());
set_cancelled();
callback_.Reset();
for (const auto& watch : watches_)
g_inotify_reader.Get().RemoveWatch(watch.watch, this);
watches_.clear();
target_.clear();
RemoveRecursiveWatches();
}
void FilePathWatcherImpl::UpdateWatches() {
// Ensure this runs on the task_runner() exclusively in order to avoid
// concurrency issues.
DCHECK(task_runner()->RunsTasksInCurrentSequence());
DCHECK(HasValidWatchVector());
// Walk the list of watches and update them as we go.
FilePath path(FILE_PATH_LITERAL("/"));
for (WatchEntry& watch_entry : watches_) {
InotifyReader::Watch old_watch = watch_entry.watch;
watch_entry.watch = InotifyReader::kInvalidWatch;
watch_entry.linkname.clear();
watch_entry.watch = g_inotify_reader.Get().AddWatch(path, this);
if (watch_entry.watch == InotifyReader::kWatchLimitExceeded)
break;
if (watch_entry.watch == InotifyReader::kInvalidWatch) {
// Ignore the error code (beyond symlink handling) to attempt to add
// watches on accessible children of unreadable directories. Note that
// this is a best-effort attempt; we may not catch events in this
// scenario.
if (IsLink(path))
AddWatchForBrokenSymlink(path, &watch_entry);
}
if (old_watch != watch_entry.watch)
g_inotify_reader.Get().RemoveWatch(old_watch, this);
path = path.Append(watch_entry.subdir);
}
UpdateRecursiveWatches(InotifyReader::kInvalidWatch,
false /* is directory? */);
}
void FilePathWatcherImpl::UpdateRecursiveWatches(
InotifyReader::Watch fired_watch,
bool is_dir) {
DCHECK(HasValidWatchVector());
if (!recursive_)
return;
if (!DirectoryExists(target_)) {
RemoveRecursiveWatches();
return;
}
// Check to see if this is a forced update or if some component of |target_|
// has changed. For these cases, redo the watches for |target_| and below.
if (!Contains(recursive_paths_by_watch_, fired_watch) &&
fired_watch != watches_.back().watch) {
UpdateRecursiveWatchesForPath(target_);
return;
}
// Underneath |target_|, only directory changes trigger watch updates.
if (!is_dir)
return;
const FilePath& changed_dir = Contains(recursive_paths_by_watch_, fired_watch)
? recursive_paths_by_watch_[fired_watch]
: target_;
auto start_it = recursive_watches_by_path_.lower_bound(changed_dir);
auto end_it = start_it;
for (; end_it != recursive_watches_by_path_.end(); ++end_it) {
const FilePath& cur_path = end_it->first;
if (!changed_dir.IsParent(cur_path))
break;
if (!DirectoryExists(cur_path))
g_inotify_reader.Get().RemoveWatch(end_it->second, this);
// Keep it in sync with |recursive_watches_by_path_| crbug.com/995196.
recursive_paths_by_watch_.erase(end_it->second);
}
recursive_watches_by_path_.erase(start_it, end_it);
UpdateRecursiveWatchesForPath(changed_dir);
}
void FilePathWatcherImpl::UpdateRecursiveWatchesForPath(const FilePath& path) {
DCHECK(recursive_);
DCHECK(!path.empty());
DCHECK(DirectoryExists(path));
// Note: SHOW_SYM_LINKS exposes symlinks as symlinks, so they are ignored
// rather than followed. Following symlinks can easily lead to the undesirable
// situation where the entire file system is being watched.
FileEnumerator enumerator(
path,
true /* recursive enumeration */,
FileEnumerator::DIRECTORIES | FileEnumerator::SHOW_SYM_LINKS);
for (FilePath current = enumerator.Next();
!current.empty();
current = enumerator.Next()) {
DCHECK(enumerator.GetInfo().IsDirectory());
if (!Contains(recursive_watches_by_path_, current)) {
// Add new watches.
InotifyReader::Watch watch =
g_inotify_reader.Get().AddWatch(current, this);
if (watch == InotifyReader::kWatchLimitExceeded)
break;
TrackWatchForRecursion(watch, current);
} else {
// Update existing watches.
InotifyReader::Watch old_watch = recursive_watches_by_path_[current];
DCHECK_NE(InotifyReader::kInvalidWatch, old_watch);
InotifyReader::Watch watch =
g_inotify_reader.Get().AddWatch(current, this);
if (watch == InotifyReader::kWatchLimitExceeded)
break;
if (watch != old_watch) {
g_inotify_reader.Get().RemoveWatch(old_watch, this);
recursive_paths_by_watch_.erase(old_watch);
recursive_watches_by_path_.erase(current);
TrackWatchForRecursion(watch, current);
}
}
}
}
void FilePathWatcherImpl::TrackWatchForRecursion(InotifyReader::Watch watch,
const FilePath& path) {
DCHECK(recursive_);
DCHECK(!path.empty());
DCHECK(target_.IsParent(path));
if (watch == InotifyReader::kInvalidWatch)
return;
DCHECK(!Contains(recursive_paths_by_watch_, watch));
DCHECK(!Contains(recursive_watches_by_path_, path));
recursive_paths_by_watch_[watch] = path;
recursive_watches_by_path_[path] = watch;
}
void FilePathWatcherImpl::RemoveRecursiveWatches() {
if (!recursive_)
return;
for (const auto& it : recursive_paths_by_watch_)
g_inotify_reader.Get().RemoveWatch(it.first, this);
recursive_paths_by_watch_.clear();
recursive_watches_by_path_.clear();
}
void FilePathWatcherImpl::AddWatchForBrokenSymlink(const FilePath& path,
WatchEntry* watch_entry) {
DCHECK_EQ(InotifyReader::kInvalidWatch, watch_entry->watch);
FilePath link;
if (!ReadSymbolicLink(path, &link))
return;
if (!link.IsAbsolute())
link = path.DirName().Append(link);
// Try watching symlink target directory. If the link target is "/", then we
// shouldn't get here in normal situations and if we do, we'd watch "/" for
// changes to a component "/" which is harmless so no special treatment of
// this case is required.
InotifyReader::Watch watch =
g_inotify_reader.Get().AddWatch(link.DirName(), this);
if (watch == InotifyReader::kInvalidWatch) {
// TODO(craig) Symlinks only work if the parent directory for the target
// exist. Ideally we should make sure we've watched all the components of
// the symlink path for changes. See crbug.com/91561 for details.
DPLOG(WARNING) << "Watch failed for " << link.DirName().value();
return;
}
watch_entry->watch = watch;
watch_entry->linkname = link.BaseName().value();
}
bool FilePathWatcherImpl::HasValidWatchVector() const {
if (watches_.empty())
return false;
for (size_t i = 0; i < watches_.size() - 1; ++i) {
if (watches_[i].subdir.empty())
return false;
}
return watches_.back().subdir.empty();
}
} // namespace
FilePathWatcher::FilePathWatcher() {
sequence_checker_.DetachFromSequence();
impl_ = std::make_unique<FilePathWatcherImpl>();
}
} // namespace base

View file

@ -0,0 +1,63 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "base/files/file_path_watcher.h"
#include "base/files/file_path_watcher_kqueue.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#if !defined(OS_IOS)
#include "base/files/file_path_watcher_fsevents.h"
#endif
namespace base {
namespace {
class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate {
public:
FilePathWatcherImpl() = default;
~FilePathWatcherImpl() override = default;
bool Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) override {
// Use kqueue for non-recursive watches and FSEvents for recursive ones.
DCHECK(!impl_.get());
if (recursive) {
if (!FilePathWatcher::RecursiveWatchAvailable())
return false;
#if !defined(OS_IOS)
impl_ = std::make_unique<FilePathWatcherFSEvents>();
#endif // OS_IOS
} else {
impl_ = std::make_unique<FilePathWatcherKQueue>();
}
DCHECK(impl_.get());
return impl_->Watch(path, recursive, callback);
}
void Cancel() override {
if (impl_.get())
impl_->Cancel();
set_cancelled();
}
private:
std::unique_ptr<PlatformDelegate> impl_;
DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
};
} // namespace
FilePathWatcher::FilePathWatcher() {
sequence_checker_.DetachFromSequence();
impl_ = std::make_unique<FilePathWatcherImpl>();
}
} // namespace base

View file

@ -0,0 +1,41 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file exists for Unix systems which don't have the inotify headers, and
// thus cannot build file_watcher_inotify.cc
#include "base/files/file_path_watcher.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
namespace base {
namespace {
class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate {
public:
FilePathWatcherImpl() = default;
~FilePathWatcherImpl() override = default;
bool Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) override {
return false;
}
void Cancel() override {}
private:
DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
};
} // namespace
FilePathWatcher::FilePathWatcher() {
sequence_checker_.DetachFromSequence();
impl_ = std::make_unique<FilePathWatcherImpl>();
}
} // namespace base

View file

@ -0,0 +1,301 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_path_watcher.h"
#include "base/bind.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "base/win/object_watcher.h"
#include <windows.h>
namespace base {
namespace {
class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
public base::win::ObjectWatcher::Delegate {
public:
FilePathWatcherImpl()
: handle_(INVALID_HANDLE_VALUE),
recursive_watch_(false) {}
~FilePathWatcherImpl() override;
// FilePathWatcher::PlatformDelegate:
bool Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) override;
void Cancel() override;
// base::win::ObjectWatcher::Delegate:
void OnObjectSignaled(HANDLE object) override;
private:
// Setup a watch handle for directory |dir|. Set |recursive| to true to watch
// the directory sub trees. Returns true if no fatal error occurs. |handle|
// will receive the handle value if |dir| is watchable, otherwise
// INVALID_HANDLE_VALUE.
static bool SetupWatchHandle(const FilePath& dir,
bool recursive,
HANDLE* handle) WARN_UNUSED_RESULT;
// (Re-)Initialize the watch handle.
bool UpdateWatch() WARN_UNUSED_RESULT;
// Destroy the watch handle.
void DestroyWatch();
// Callback to notify upon changes.
FilePathWatcher::Callback callback_;
// Path we're supposed to watch (passed to callback).
FilePath target_;
// Set to true in the destructor.
bool* was_deleted_ptr_ = nullptr;
// Handle for FindFirstChangeNotification.
HANDLE handle_;
// ObjectWatcher to watch handle_ for events.
base::win::ObjectWatcher watcher_;
// Set to true to watch the sub trees of the specified directory file path.
bool recursive_watch_;
// Keep track of the last modified time of the file. We use nulltime
// to represent the file not existing.
Time last_modified_;
// The time at which we processed the first notification with the
// |last_modified_| time stamp.
Time first_notification_;
DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
};
FilePathWatcherImpl::~FilePathWatcherImpl() {
DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
if (was_deleted_ptr_)
*was_deleted_ptr_ = true;
}
bool FilePathWatcherImpl::Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) {
DCHECK(target_.value().empty()); // Can only watch one path.
set_task_runner(SequencedTaskRunnerHandle::Get());
callback_ = callback;
target_ = path;
recursive_watch_ = recursive;
File::Info file_info;
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
if (GetFileInfo(target_, &file_info)) {
last_modified_ = file_info.last_modified;
first_notification_ = Time::Now();
}
if (!UpdateWatch())
return false;
watcher_.StartWatchingOnce(handle_, this);
return true;
}
void FilePathWatcherImpl::Cancel() {
if (callback_.is_null()) {
// Watch was never called, or the |task_runner_| has already quit.
set_cancelled();
return;
}
DCHECK(task_runner()->RunsTasksInCurrentSequence());
set_cancelled();
if (handle_ != INVALID_HANDLE_VALUE)
DestroyWatch();
callback_.Reset();
}
void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) {
DCHECK(task_runner()->RunsTasksInCurrentSequence());
DCHECK_EQ(object, handle_);
DCHECK(!was_deleted_ptr_);
bool was_deleted = false;
was_deleted_ptr_ = &was_deleted;
if (!UpdateWatch()) {
callback_.Run(target_, true /* error */);
return;
}
// Check whether the event applies to |target_| and notify the callback.
File::Info file_info;
bool file_exists = false;
{
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
file_exists = GetFileInfo(target_, &file_info);
}
if (recursive_watch_) {
// Only the mtime of |target_| is tracked but in a recursive watch,
// some other file or directory may have changed so all notifications
// are passed through. It is possible to figure out which file changed
// using ReadDirectoryChangesW() instead of FindFirstChangeNotification(),
// but that function is quite complicated:
// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html
callback_.Run(target_, false);
} else if (file_exists && (last_modified_.is_null() ||
last_modified_ != file_info.last_modified)) {
last_modified_ = file_info.last_modified;
first_notification_ = Time::Now();
callback_.Run(target_, false);
} else if (file_exists && last_modified_ == file_info.last_modified &&
!first_notification_.is_null()) {
// The target's last modification time is equal to what's on record. This
// means that either an unrelated event occurred, or the target changed
// again (file modification times only have a resolution of 1s). Comparing
// file modification times against the wall clock is not reliable to find
// out whether the change is recent, since this code might just run too
// late. Moreover, there's no guarantee that file modification time and wall
// clock times come from the same source.
//
// Instead, the time at which the first notification carrying the current
// |last_notified_| time stamp is recorded. Later notifications that find
// the same file modification time only need to be forwarded until wall
// clock has advanced one second from the initial notification. After that
// interval, client code is guaranteed to having seen the current revision
// of the file.
if (Time::Now() - first_notification_ > TimeDelta::FromSeconds(1)) {
// Stop further notifications for this |last_modification_| time stamp.
first_notification_ = Time();
}
callback_.Run(target_, false);
} else if (!file_exists && !last_modified_.is_null()) {
last_modified_ = Time();
callback_.Run(target_, false);
}
// The watch may have been cancelled by the callback.
if (!was_deleted) {
watcher_.StartWatchingOnce(handle_, this);
was_deleted_ptr_ = nullptr;
}
}
// static
bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir,
bool recursive,
HANDLE* handle) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
*handle = FindFirstChangeNotification(
dir.value().c_str(), recursive,
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY);
if (*handle != INVALID_HANDLE_VALUE) {
// Make sure the handle we got points to an existing directory. It seems
// that windows sometimes hands out watches to directories that are
// about to go away, but doesn't sent notifications if that happens.
if (!DirectoryExists(dir)) {
FindCloseChangeNotification(*handle);
*handle = INVALID_HANDLE_VALUE;
}
return true;
}
// If FindFirstChangeNotification failed because the target directory
// doesn't exist, access is denied (happens if the file is already gone but
// there are still handles open), or the target is not a directory, try the
// immediate parent directory instead.
DWORD error_code = GetLastError();
if (error_code != ERROR_FILE_NOT_FOUND &&
error_code != ERROR_PATH_NOT_FOUND &&
error_code != ERROR_ACCESS_DENIED &&
error_code != ERROR_SHARING_VIOLATION &&
error_code != ERROR_DIRECTORY) {
DPLOG(ERROR) << "FindFirstChangeNotification failed for "
<< dir.value();
return false;
}
return true;
}
bool FilePathWatcherImpl::UpdateWatch() {
if (handle_ != INVALID_HANDLE_VALUE)
DestroyWatch();
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
// Start at the target and walk up the directory chain until we succesfully
// create a watch handle in |handle_|. |child_dirs| keeps a stack of child
// directories stripped from target, in reverse order.
std::vector<FilePath> child_dirs;
FilePath watched_path(target_);
while (true) {
if (!SetupWatchHandle(watched_path, recursive_watch_, &handle_))
return false;
// Break if a valid handle is returned. Try the parent directory otherwise.
if (handle_ != INVALID_HANDLE_VALUE)
break;
// Abort if we hit the root directory.
child_dirs.push_back(watched_path.BaseName());
FilePath parent(watched_path.DirName());
if (parent == watched_path) {
DLOG(ERROR) << "Reached the root directory";
return false;
}
watched_path = parent;
}
// At this point, handle_ is valid. However, the bottom-up search that the
// above code performs races against directory creation. So try to walk back
// down and see whether any children appeared in the mean time.
while (!child_dirs.empty()) {
watched_path = watched_path.Append(child_dirs.back());
child_dirs.pop_back();
HANDLE temp_handle = INVALID_HANDLE_VALUE;
if (!SetupWatchHandle(watched_path, recursive_watch_, &temp_handle))
return false;
if (temp_handle == INVALID_HANDLE_VALUE)
break;
FindCloseChangeNotification(handle_);
handle_ = temp_handle;
}
return true;
}
void FilePathWatcherImpl::DestroyWatch() {
watcher_.StopWatching();
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
FindCloseChangeNotification(handle_);
handle_ = INVALID_HANDLE_VALUE;
}
} // namespace
FilePathWatcher::FilePathWatcher() {
sequence_checker_.DetachFromSequence();
impl_ = std::make_unique<FilePathWatcherImpl>();
}
} // namespace base

View file

@ -0,0 +1,613 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file.h"
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <unistd.h>
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"
#if defined(OS_ANDROID)
#include "base/os_compat_android.h"
#endif
namespace base {
// Make sure our Whence mappings match the system headers.
static_assert(File::FROM_BEGIN == SEEK_SET && File::FROM_CURRENT == SEEK_CUR &&
File::FROM_END == SEEK_END,
"whence mapping must match the system headers");
namespace {
// NaCl doesn't provide the following system calls, so either simulate them or
// wrap them in order to minimize the number of #ifdef's in this file.
#if !defined(OS_NACL) && !defined(OS_AIX)
bool IsOpenAppend(PlatformFile file) {
return (fcntl(file, F_GETFL) & O_APPEND) != 0;
}
int CallFtruncate(PlatformFile file, int64_t length) {
return HANDLE_EINTR(ftruncate(file, length));
}
int CallFutimes(PlatformFile file, const struct timeval times[2]) {
#ifdef __USE_XOPEN2K8
// futimens should be available, but futimes might not be
// http://pubs.opengroup.org/onlinepubs/9699919799/
timespec ts_times[2];
ts_times[0].tv_sec = times[0].tv_sec;
ts_times[0].tv_nsec = times[0].tv_usec * 1000;
ts_times[1].tv_sec = times[1].tv_sec;
ts_times[1].tv_nsec = times[1].tv_usec * 1000;
return futimens(file, ts_times);
#else
return futimes(file, times);
#endif
}
#if !defined(OS_FUCHSIA)
short FcntlFlockType(base::Optional<File::LockMode> mode) {
if (!mode.has_value())
return F_UNLCK;
switch (mode.value()) {
case File::LockMode::kShared:
return F_RDLCK;
case File::LockMode::kExclusive:
return F_WRLCK;
}
NOTREACHED();
}
File::Error CallFcntlFlock(PlatformFile file,
base::Optional<File::LockMode> mode) {
struct flock lock;
lock.l_type = FcntlFlockType(std::move(mode));
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; // Lock entire file.
if (HANDLE_EINTR(fcntl(file, F_SETLK, &lock)) == -1)
return File::GetLastFileError();
return File::FILE_OK;
}
#endif
#else // defined(OS_NACL) && !defined(OS_AIX)
bool IsOpenAppend(PlatformFile file) {
// NaCl doesn't implement fcntl. Since NaCl's write conforms to the POSIX
// standard and always appends if the file is opened with O_APPEND, just
// return false here.
return false;
}
int CallFtruncate(PlatformFile file, int64_t length) {
NOTIMPLEMENTED(); // NaCl doesn't implement ftruncate.
return 0;
}
int CallFutimes(PlatformFile file, const struct timeval times[2]) {
NOTIMPLEMENTED(); // NaCl doesn't implement futimes.
return 0;
}
File::Error CallFcntlFlock(PlatformFile file,
base::Optional<File::LockMode> mode) {
NOTIMPLEMENTED(); // NaCl doesn't implement flock struct.
return File::FILE_ERROR_INVALID_OPERATION;
}
#endif // defined(OS_NACL)
} // namespace
void File::Info::FromStat(const stat_wrapper_t& stat_info) {
is_directory = S_ISDIR(stat_info.st_mode);
is_symbolic_link = S_ISLNK(stat_info.st_mode);
size = stat_info.st_size;
// Get last modification time, last access time, and creation time from
// |stat_info|.
// Note: st_ctime is actually last status change time when the inode was last
// updated, which happens on any metadata change. It is not the file's
// creation time. However, other than on Mac & iOS where the actual file
// creation time is included as st_birthtime, the rest of POSIX platforms have
// no portable way to get the creation time.
#if defined(OS_LINUX) || defined(OS_FUCHSIA)
time_t last_modified_sec = stat_info.st_mtim.tv_sec;
int64_t last_modified_nsec = stat_info.st_mtim.tv_nsec;
time_t last_accessed_sec = stat_info.st_atim.tv_sec;
int64_t last_accessed_nsec = stat_info.st_atim.tv_nsec;
time_t creation_time_sec = stat_info.st_ctim.tv_sec;
int64_t creation_time_nsec = stat_info.st_ctim.tv_nsec;
#elif defined(OS_ANDROID)
time_t last_modified_sec = stat_info.st_mtime;
int64_t last_modified_nsec = stat_info.st_mtime_nsec;
time_t last_accessed_sec = stat_info.st_atime;
int64_t last_accessed_nsec = stat_info.st_atime_nsec;
time_t creation_time_sec = stat_info.st_ctime;
int64_t creation_time_nsec = stat_info.st_ctime_nsec;
#elif defined(OS_MACOSX) || defined(OS_IOS)
time_t last_modified_sec = stat_info.st_mtimespec.tv_sec;
int64_t last_modified_nsec = stat_info.st_mtimespec.tv_nsec;
time_t last_accessed_sec = stat_info.st_atimespec.tv_sec;
int64_t last_accessed_nsec = stat_info.st_atimespec.tv_nsec;
time_t creation_time_sec = stat_info.st_birthtimespec.tv_sec;
int64_t creation_time_nsec = stat_info.st_birthtimespec.tv_nsec;
#elif defined(OS_BSD)
time_t last_modified_sec = stat_info.st_mtimespec.tv_sec;
int64_t last_modified_nsec = stat_info.st_mtimespec.tv_nsec;
time_t last_accessed_sec = stat_info.st_atimespec.tv_sec;
int64_t last_accessed_nsec = stat_info.st_atimespec.tv_nsec;
time_t creation_time_sec = stat_info.st_ctimespec.tv_sec;
int64_t creation_time_nsec = stat_info.st_ctimespec.tv_nsec;
#else
time_t last_modified_sec = stat_info.st_mtime;
int64_t last_modified_nsec = 0;
time_t last_accessed_sec = stat_info.st_atime;
int64_t last_accessed_nsec = 0;
time_t creation_time_sec = stat_info.st_ctime;
int64_t creation_time_nsec = 0;
#endif
last_modified =
Time::FromTimeT(last_modified_sec) +
TimeDelta::FromMicroseconds(last_modified_nsec /
Time::kNanosecondsPerMicrosecond);
last_accessed =
Time::FromTimeT(last_accessed_sec) +
TimeDelta::FromMicroseconds(last_accessed_nsec /
Time::kNanosecondsPerMicrosecond);
creation_time =
Time::FromTimeT(creation_time_sec) +
TimeDelta::FromMicroseconds(creation_time_nsec /
Time::kNanosecondsPerMicrosecond);
}
bool File::IsValid() const {
return file_.is_valid();
}
PlatformFile File::GetPlatformFile() const {
return file_.get();
}
PlatformFile File::TakePlatformFile() {
return file_.release();
}
void File::Close() {
if (!IsValid())
return;
SCOPED_FILE_TRACE("Close");
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
file_.reset();
}
int64_t File::Seek(Whence whence, int64_t offset) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
SCOPED_FILE_TRACE_WITH_SIZE("Seek", offset);
#if defined(OS_ANDROID)
static_assert(sizeof(int64_t) == sizeof(off64_t), "off64_t must be 64 bits");
return lseek64(file_.get(), static_cast<off64_t>(offset),
static_cast<int>(whence));
#else
static_assert(sizeof(int64_t) == sizeof(off_t), "off_t must be 64 bits");
return lseek(file_.get(), static_cast<off_t>(offset),
static_cast<int>(whence));
#endif
}
int File::Read(int64_t offset, char* data, int size) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
if (size < 0)
return -1;
SCOPED_FILE_TRACE_WITH_SIZE("Read", size);
int bytes_read = 0;
int rv;
do {
rv = HANDLE_EINTR(pread(file_.get(), data + bytes_read,
size - bytes_read, offset + bytes_read));
if (rv <= 0)
break;
bytes_read += rv;
} while (bytes_read < size);
return bytes_read ? bytes_read : rv;
}
int File::ReadAtCurrentPos(char* data, int size) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
if (size < 0)
return -1;
SCOPED_FILE_TRACE_WITH_SIZE("ReadAtCurrentPos", size);
int bytes_read = 0;
int rv;
do {
rv = HANDLE_EINTR(read(file_.get(), data + bytes_read, size - bytes_read));
if (rv <= 0)
break;
bytes_read += rv;
} while (bytes_read < size);
return bytes_read ? bytes_read : rv;
}
int File::ReadNoBestEffort(int64_t offset, char* data, int size) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
SCOPED_FILE_TRACE_WITH_SIZE("ReadNoBestEffort", size);
return HANDLE_EINTR(pread(file_.get(), data, size, offset));
}
int File::ReadAtCurrentPosNoBestEffort(char* data, int size) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
if (size < 0)
return -1;
SCOPED_FILE_TRACE_WITH_SIZE("ReadAtCurrentPosNoBestEffort", size);
return HANDLE_EINTR(read(file_.get(), data, size));
}
int File::Write(int64_t offset, const char* data, int size) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
if (IsOpenAppend(file_.get()))
return WriteAtCurrentPos(data, size);
DCHECK(IsValid());
if (size < 0)
return -1;
SCOPED_FILE_TRACE_WITH_SIZE("Write", size);
int bytes_written = 0;
int rv;
do {
#if defined(OS_ANDROID)
// In case __USE_FILE_OFFSET64 is not used, we need to call pwrite64()
// instead of pwrite().
static_assert(sizeof(int64_t) == sizeof(off64_t),
"off64_t must be 64 bits");
rv = HANDLE_EINTR(pwrite64(file_.get(), data + bytes_written,
size - bytes_written, offset + bytes_written));
#else
rv = HANDLE_EINTR(pwrite(file_.get(), data + bytes_written,
size - bytes_written, offset + bytes_written));
#endif
if (rv <= 0)
break;
bytes_written += rv;
} while (bytes_written < size);
return bytes_written ? bytes_written : rv;
}
int File::WriteAtCurrentPos(const char* data, int size) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
if (size < 0)
return -1;
SCOPED_FILE_TRACE_WITH_SIZE("WriteAtCurrentPos", size);
int bytes_written = 0;
int rv;
do {
rv = HANDLE_EINTR(write(file_.get(), data + bytes_written,
size - bytes_written));
if (rv <= 0)
break;
bytes_written += rv;
} while (bytes_written < size);
return bytes_written ? bytes_written : rv;
}
int File::WriteAtCurrentPosNoBestEffort(const char* data, int size) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
if (size < 0)
return -1;
SCOPED_FILE_TRACE_WITH_SIZE("WriteAtCurrentPosNoBestEffort", size);
return HANDLE_EINTR(write(file_.get(), data, size));
}
int64_t File::GetLength() {
DCHECK(IsValid());
SCOPED_FILE_TRACE("GetLength");
stat_wrapper_t file_info;
if (Fstat(file_.get(), &file_info))
return -1;
return file_info.st_size;
}
bool File::SetLength(int64_t length) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
SCOPED_FILE_TRACE_WITH_SIZE("SetLength", length);
return !CallFtruncate(file_.get(), length);
}
bool File::SetTimes(Time last_access_time, Time last_modified_time) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
SCOPED_FILE_TRACE("SetTimes");
timeval times[2];
times[0] = last_access_time.ToTimeVal();
times[1] = last_modified_time.ToTimeVal();
return !CallFutimes(file_.get(), times);
}
bool File::GetInfo(Info* info) {
DCHECK(IsValid());
SCOPED_FILE_TRACE("GetInfo");
stat_wrapper_t file_info;
if (Fstat(file_.get(), &file_info))
return false;
info->FromStat(file_info);
return true;
}
#if !defined(OS_FUCHSIA)
File::Error File::Lock(File::LockMode mode) {
SCOPED_FILE_TRACE("Lock");
return CallFcntlFlock(file_.get(), mode);
}
File::Error File::Unlock() {
SCOPED_FILE_TRACE("Unlock");
return CallFcntlFlock(file_.get(), base::Optional<File::LockMode>());
}
#endif
File File::Duplicate() const {
if (!IsValid())
return File();
SCOPED_FILE_TRACE("Duplicate");
ScopedPlatformFile other_fd(HANDLE_EINTR(dup(GetPlatformFile())));
if (!other_fd.is_valid())
return File(File::GetLastFileError());
return File(std::move(other_fd), async());
}
// Static.
File::Error File::OSErrorToFileError(int saved_errno) {
switch (saved_errno) {
case EACCES:
case EISDIR:
case EROFS:
case EPERM:
return FILE_ERROR_ACCESS_DENIED;
case EBUSY:
#if !defined(OS_NACL) // ETXTBSY not defined by NaCl.
case ETXTBSY:
#endif
return FILE_ERROR_IN_USE;
case EEXIST:
return FILE_ERROR_EXISTS;
case EIO:
return FILE_ERROR_IO;
case ENOENT:
return FILE_ERROR_NOT_FOUND;
case ENFILE: // fallthrough
case EMFILE:
return FILE_ERROR_TOO_MANY_OPENED;
case ENOMEM:
return FILE_ERROR_NO_MEMORY;
case ENOSPC:
return FILE_ERROR_NO_SPACE;
case ENOTDIR:
return FILE_ERROR_NOT_A_DIRECTORY;
default:
#if !defined(OS_NACL) // NaCl build has no metrics code.
UmaHistogramSparse("PlatformFile.UnknownErrors.Posix", saved_errno);
#endif
// This function should only be called for errors.
DCHECK_NE(0, saved_errno);
return FILE_ERROR_FAILED;
}
}
// NaCl doesn't implement system calls to open files directly.
#if !defined(OS_NACL)
// TODO(erikkay): does it make sense to support FLAG_EXCLUSIVE_* here?
void File::DoInitialize(const FilePath& path, uint32_t flags) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(!IsValid());
int open_flags = 0;
if (flags & FLAG_CREATE)
open_flags = O_CREAT | O_EXCL;
created_ = false;
if (flags & FLAG_CREATE_ALWAYS) {
DCHECK(!open_flags);
DCHECK(flags & FLAG_WRITE);
open_flags = O_CREAT | O_TRUNC;
}
if (flags & FLAG_OPEN_TRUNCATED) {
DCHECK(!open_flags);
DCHECK(flags & FLAG_WRITE);
open_flags = O_TRUNC;
}
if (!open_flags && !(flags & FLAG_OPEN) && !(flags & FLAG_OPEN_ALWAYS)) {
NOTREACHED();
errno = EOPNOTSUPP;
error_details_ = FILE_ERROR_FAILED;
return;
}
if (flags & FLAG_WRITE && flags & FLAG_READ) {
open_flags |= O_RDWR;
} else if (flags & FLAG_WRITE) {
open_flags |= O_WRONLY;
} else if (!(flags & FLAG_READ) &&
!(flags & FLAG_WRITE_ATTRIBUTES) &&
!(flags & FLAG_APPEND) &&
!(flags & FLAG_OPEN_ALWAYS)) {
NOTREACHED();
}
if (flags & FLAG_TERMINAL_DEVICE)
open_flags |= O_NOCTTY | O_NDELAY;
if (flags & FLAG_APPEND && flags & FLAG_READ)
open_flags |= O_APPEND | O_RDWR;
else if (flags & FLAG_APPEND)
open_flags |= O_APPEND | O_WRONLY;
static_assert(O_RDONLY == 0, "O_RDONLY must equal zero");
int mode = S_IRUSR | S_IWUSR;
#if defined(OS_CHROMEOS)
mode |= S_IRGRP | S_IROTH;
#endif
int descriptor = HANDLE_EINTR(open(path.value().c_str(), open_flags, mode));
if (flags & FLAG_OPEN_ALWAYS) {
if (descriptor < 0) {
open_flags |= O_CREAT;
if (flags & FLAG_EXCLUSIVE_READ || flags & FLAG_EXCLUSIVE_WRITE)
open_flags |= O_EXCL; // together with O_CREAT implies O_NOFOLLOW
descriptor = HANDLE_EINTR(open(path.value().c_str(), open_flags, mode));
if (descriptor >= 0)
created_ = true;
}
}
if (descriptor < 0) {
error_details_ = File::GetLastFileError();
return;
}
if (flags & (FLAG_CREATE_ALWAYS | FLAG_CREATE))
created_ = true;
if (flags & FLAG_DELETE_ON_CLOSE)
unlink(path.value().c_str());
async_ = ((flags & FLAG_ASYNC) == FLAG_ASYNC);
error_details_ = FILE_OK;
file_.reset(descriptor);
}
#endif // !defined(OS_NACL)
bool File::Flush() {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
SCOPED_FILE_TRACE("Flush");
#if defined(OS_NACL)
NOTIMPLEMENTED(); // NaCl doesn't implement fsync.
return true;
#elif defined(OS_LINUX) || defined(OS_ANDROID)
return !HANDLE_EINTR(fdatasync(file_.get()));
#elif defined(OS_MACOSX) || defined(OS_IOS)
// On macOS and iOS, fsync() is guaranteed to send the file's data to the
// underlying storage device, but may return before the device actually writes
// the data to the medium. When used by database systems, this may result in
// unexpected data loss.
// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fsync.2.html
if (!HANDLE_EINTR(fcntl(file_.get(), F_FULLFSYNC)))
return true;
// Some filesystms do not support fcntl(F_FULLFSYNC). We handle these cases by
// falling back to fsync(). Unfortunately, lack of F_FULLFSYNC support results
// in various error codes, so we cannot use the error code as a definitive
// indicator that F_FULLFSYNC was not supported. So, if fcntl() errors out for
// any reason, we may end up making an unnecessary system call.
//
// See the CL description at https://crrev.com/c/1400159 for details.
return !HANDLE_EINTR(fsync(file_.get()));
#else
return !HANDLE_EINTR(fsync(file_.get()));
#endif
}
void File::SetPlatformFile(PlatformFile file) {
DCHECK(!file_.is_valid());
file_.reset(file);
}
// static
File::Error File::GetLastFileError() {
return base::File::OSErrorToFileError(errno);
}
#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) || \
defined(OS_FUCHSIA) || (defined(OS_ANDROID) && __ANDROID_API__ < 21)
int File::Stat(const char* path, stat_wrapper_t* sb) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
return stat(path, sb);
}
int File::Fstat(int fd, stat_wrapper_t* sb) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
return fstat(fd, sb);
}
int File::Lstat(const char* path, stat_wrapper_t* sb) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
return lstat(path, sb);
}
#else
int File::Stat(const char* path, stat_wrapper_t* sb) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
return stat64(path, sb);
}
int File::Fstat(int fd, stat_wrapper_t* sb) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
return fstat64(fd, sb);
}
int File::Lstat(const char* path, stat_wrapper_t* sb) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
return lstat64(path, sb);
}
#endif
} // namespace base

View file

@ -0,0 +1,358 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_proxy.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/task_runner.h"
#include "base/task_runner_util.h"
namespace {
void FileDeleter(base::File file) {
}
} // namespace
namespace base {
class FileHelper {
public:
FileHelper(FileProxy* proxy, File file)
: file_(std::move(file)),
error_(File::FILE_ERROR_FAILED),
task_runner_(proxy->task_runner()),
proxy_(AsWeakPtr(proxy)) {
}
void PassFile() {
if (proxy_)
proxy_->SetFile(std::move(file_));
else if (file_.IsValid())
task_runner_->PostTask(FROM_HERE,
BindOnce(&FileDeleter, std::move(file_)));
}
protected:
File file_;
File::Error error_;
private:
scoped_refptr<TaskRunner> task_runner_;
WeakPtr<FileProxy> proxy_;
DISALLOW_COPY_AND_ASSIGN(FileHelper);
};
namespace {
class GenericFileHelper : public FileHelper {
public:
GenericFileHelper(FileProxy* proxy, File file)
: FileHelper(proxy, std::move(file)) {
}
void Close() {
file_.Close();
error_ = File::FILE_OK;
}
void SetTimes(Time last_access_time, Time last_modified_time) {
bool rv = file_.SetTimes(last_access_time, last_modified_time);
error_ = rv ? File::FILE_OK : File::FILE_ERROR_FAILED;
}
void SetLength(int64_t length) {
if (file_.SetLength(length))
error_ = File::FILE_OK;
}
void Flush() {
if (file_.Flush())
error_ = File::FILE_OK;
}
void Reply(FileProxy::StatusCallback callback) {
PassFile();
if (!callback.is_null())
std::move(callback).Run(error_);
}
private:
DISALLOW_COPY_AND_ASSIGN(GenericFileHelper);
};
class CreateOrOpenHelper : public FileHelper {
public:
CreateOrOpenHelper(FileProxy* proxy, File file)
: FileHelper(proxy, std::move(file)) {
}
void RunWork(const FilePath& file_path, int file_flags) {
file_.Initialize(file_path, file_flags);
error_ = file_.IsValid() ? File::FILE_OK : file_.error_details();
}
void Reply(FileProxy::StatusCallback callback) {
DCHECK(!callback.is_null());
PassFile();
std::move(callback).Run(error_);
}
private:
DISALLOW_COPY_AND_ASSIGN(CreateOrOpenHelper);
};
class CreateTemporaryHelper : public FileHelper {
public:
CreateTemporaryHelper(FileProxy* proxy, File file)
: FileHelper(proxy, std::move(file)) {
}
void RunWork(uint32_t additional_file_flags) {
// TODO(darin): file_util should have a variant of CreateTemporaryFile
// that returns a FilePath and a File.
if (!CreateTemporaryFile(&file_path_)) {
// TODO(davidben): base::CreateTemporaryFile should preserve the error
// code.
error_ = File::FILE_ERROR_FAILED;
return;
}
uint32_t file_flags = File::FLAG_WRITE | File::FLAG_TEMPORARY |
File::FLAG_CREATE_ALWAYS | additional_file_flags;
file_.Initialize(file_path_, file_flags);
if (file_.IsValid()) {
error_ = File::FILE_OK;
} else {
error_ = file_.error_details();
DeleteFile(file_path_, false);
file_path_.clear();
}
}
void Reply(FileProxy::CreateTemporaryCallback callback) {
DCHECK(!callback.is_null());
PassFile();
std::move(callback).Run(error_, file_path_);
}
private:
FilePath file_path_;
DISALLOW_COPY_AND_ASSIGN(CreateTemporaryHelper);
};
class GetInfoHelper : public FileHelper {
public:
GetInfoHelper(FileProxy* proxy, File file)
: FileHelper(proxy, std::move(file)) {
}
void RunWork() {
if (file_.GetInfo(&file_info_))
error_ = File::FILE_OK;
}
void Reply(FileProxy::GetFileInfoCallback callback) {
PassFile();
DCHECK(!callback.is_null());
std::move(callback).Run(error_, file_info_);
}
private:
File::Info file_info_;
DISALLOW_COPY_AND_ASSIGN(GetInfoHelper);
};
class ReadHelper : public FileHelper {
public:
ReadHelper(FileProxy* proxy, File file, int bytes_to_read)
: FileHelper(proxy, std::move(file)),
buffer_(new char[bytes_to_read]),
bytes_to_read_(bytes_to_read),
bytes_read_(0) {
}
void RunWork(int64_t offset) {
bytes_read_ = file_.Read(offset, buffer_.get(), bytes_to_read_);
error_ = (bytes_read_ < 0) ? File::FILE_ERROR_FAILED : File::FILE_OK;
}
void Reply(FileProxy::ReadCallback callback) {
PassFile();
DCHECK(!callback.is_null());
std::move(callback).Run(error_, buffer_.get(), bytes_read_);
}
private:
std::unique_ptr<char[]> buffer_;
int bytes_to_read_;
int bytes_read_;
DISALLOW_COPY_AND_ASSIGN(ReadHelper);
};
class WriteHelper : public FileHelper {
public:
WriteHelper(FileProxy* proxy,
File file,
const char* buffer, int bytes_to_write)
: FileHelper(proxy, std::move(file)),
buffer_(new char[bytes_to_write]),
bytes_to_write_(bytes_to_write),
bytes_written_(0) {
memcpy(buffer_.get(), buffer, bytes_to_write);
}
void RunWork(int64_t offset) {
bytes_written_ = file_.Write(offset, buffer_.get(), bytes_to_write_);
error_ = (bytes_written_ < 0) ? File::FILE_ERROR_FAILED : File::FILE_OK;
}
void Reply(FileProxy::WriteCallback callback) {
PassFile();
if (!callback.is_null())
std::move(callback).Run(error_, bytes_written_);
}
private:
std::unique_ptr<char[]> buffer_;
int bytes_to_write_;
int bytes_written_;
DISALLOW_COPY_AND_ASSIGN(WriteHelper);
};
} // namespace
FileProxy::FileProxy(TaskRunner* task_runner) : task_runner_(task_runner) {
}
FileProxy::~FileProxy() {
if (file_.IsValid())
task_runner_->PostTask(FROM_HERE, BindOnce(&FileDeleter, std::move(file_)));
}
bool FileProxy::CreateOrOpen(const FilePath& file_path,
uint32_t file_flags,
StatusCallback callback) {
DCHECK(!file_.IsValid());
CreateOrOpenHelper* helper = new CreateOrOpenHelper(this, File());
return task_runner_->PostTaskAndReply(
FROM_HERE,
BindOnce(&CreateOrOpenHelper::RunWork, Unretained(helper), file_path,
file_flags),
BindOnce(&CreateOrOpenHelper::Reply, Owned(helper), std::move(callback)));
}
bool FileProxy::CreateTemporary(uint32_t additional_file_flags,
CreateTemporaryCallback callback) {
DCHECK(!file_.IsValid());
CreateTemporaryHelper* helper = new CreateTemporaryHelper(this, File());
return task_runner_->PostTaskAndReply(
FROM_HERE,
BindOnce(&CreateTemporaryHelper::RunWork, Unretained(helper),
additional_file_flags),
BindOnce(&CreateTemporaryHelper::Reply, Owned(helper),
std::move(callback)));
}
bool FileProxy::IsValid() const {
return file_.IsValid();
}
void FileProxy::SetFile(File file) {
DCHECK(!file_.IsValid());
file_ = std::move(file);
}
File FileProxy::TakeFile() {
return std::move(file_);
}
File FileProxy::DuplicateFile() {
return file_.Duplicate();
}
PlatformFile FileProxy::GetPlatformFile() const {
return file_.GetPlatformFile();
}
bool FileProxy::Close(StatusCallback callback) {
DCHECK(file_.IsValid());
GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
return task_runner_->PostTaskAndReply(
FROM_HERE, BindOnce(&GenericFileHelper::Close, Unretained(helper)),
BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
}
bool FileProxy::GetInfo(GetFileInfoCallback callback) {
DCHECK(file_.IsValid());
GetInfoHelper* helper = new GetInfoHelper(this, std::move(file_));
return task_runner_->PostTaskAndReply(
FROM_HERE, BindOnce(&GetInfoHelper::RunWork, Unretained(helper)),
BindOnce(&GetInfoHelper::Reply, Owned(helper), std::move(callback)));
}
bool FileProxy::Read(int64_t offset, int bytes_to_read, ReadCallback callback) {
DCHECK(file_.IsValid());
if (bytes_to_read < 0)
return false;
ReadHelper* helper = new ReadHelper(this, std::move(file_), bytes_to_read);
return task_runner_->PostTaskAndReply(
FROM_HERE, BindOnce(&ReadHelper::RunWork, Unretained(helper), offset),
BindOnce(&ReadHelper::Reply, Owned(helper), std::move(callback)));
}
bool FileProxy::Write(int64_t offset,
const char* buffer,
int bytes_to_write,
WriteCallback callback) {
DCHECK(file_.IsValid());
if (bytes_to_write <= 0 || buffer == nullptr)
return false;
WriteHelper* helper =
new WriteHelper(this, std::move(file_), buffer, bytes_to_write);
return task_runner_->PostTaskAndReply(
FROM_HERE, BindOnce(&WriteHelper::RunWork, Unretained(helper), offset),
BindOnce(&WriteHelper::Reply, Owned(helper), std::move(callback)));
}
bool FileProxy::SetTimes(Time last_access_time,
Time last_modified_time,
StatusCallback callback) {
DCHECK(file_.IsValid());
GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
return task_runner_->PostTaskAndReply(
FROM_HERE,
BindOnce(&GenericFileHelper::SetTimes, Unretained(helper),
last_access_time, last_modified_time),
BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
}
bool FileProxy::SetLength(int64_t length, StatusCallback callback) {
DCHECK(file_.IsValid());
GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
return task_runner_->PostTaskAndReply(
FROM_HERE,
BindOnce(&GenericFileHelper::SetLength, Unretained(helper), length),
BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
}
bool FileProxy::Flush(StatusCallback callback) {
DCHECK(file_.IsValid());
GenericFileHelper* helper = new GenericFileHelper(this, std::move(file_));
return task_runner_->PostTaskAndReply(
FROM_HERE, BindOnce(&GenericFileHelper::Flush, Unretained(helper)),
BindOnce(&GenericFileHelper::Reply, Owned(helper), std::move(callback)));
}
} // namespace base

View file

@ -0,0 +1,142 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_FILE_PROXY_H_
#define BASE_FILES_FILE_PROXY_H_
#include <stdint.h>
#include "base/base_export.h"
#include "base/callback_forward.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
namespace base {
class TaskRunner;
class Time;
// This class provides asynchronous access to a File. All methods follow the
// same rules of the equivalent File method, as they are implemented by bouncing
// the operation to File using a TaskRunner.
//
// This class performs automatic proxying to close the underlying file at
// destruction.
//
// The TaskRunner is in charge of any sequencing of the operations, but a single
// operation can be proxied at a time, regardless of the use of a callback.
// In other words, having a sequence like
//
// proxy.Write(...);
// proxy.Write(...);
//
// means the second Write will always fail.
class BASE_EXPORT FileProxy : public SupportsWeakPtr<FileProxy> {
public:
// This callback is used by methods that report only an error code. It is
// valid to pass a null callback to some functions that takes a
// StatusCallback, in which case the operation will complete silently.
using StatusCallback = OnceCallback<void(File::Error)>;
using CreateTemporaryCallback =
OnceCallback<void(File::Error, const FilePath&)>;
using GetFileInfoCallback =
OnceCallback<void(File::Error, const File::Info&)>;
using ReadCallback =
OnceCallback<void(File::Error, const char* data, int bytes_read)>;
using WriteCallback = OnceCallback<void(File::Error, int bytes_written)>;
FileProxy();
explicit FileProxy(TaskRunner* task_runner);
~FileProxy();
// Creates or opens a file with the given flags. It is invalid to pass a null
// callback. If File::FLAG_CREATE is set in |file_flags| it always tries to
// create a new file at the given |file_path| and fails if the file already
// exists.
//
// This returns false if task posting to |task_runner| has failed.
bool CreateOrOpen(const FilePath& file_path,
uint32_t file_flags,
StatusCallback callback);
// Creates a temporary file for writing. The path and an open file are
// returned. It is invalid to pass a null callback. The additional file flags
// will be added on top of the default file flags which are:
// File::FLAG_CREATE_ALWAYS
// File::FLAG_WRITE
// File::FLAG_TEMPORARY.
//
// This returns false if task posting to |task_runner| has failed.
bool CreateTemporary(uint32_t additional_file_flags,
CreateTemporaryCallback callback);
// Returns true if the underlying |file_| is valid.
bool IsValid() const;
// Returns true if a new file was created (or an old one truncated to zero
// length to simulate a new file), and false otherwise.
bool created() const { return file_.created(); }
// Claims ownership of |file|. It is an error to call this method when
// IsValid() returns true.
void SetFile(File file);
File TakeFile();
// Returns a new File object that is a duplicate of the underlying |file_|.
// See the comment at File::Duplicate for caveats.
File DuplicateFile();
PlatformFile GetPlatformFile() const;
// Proxies File::Close. The callback can be null.
// This returns false if task posting to |task_runner| has failed.
bool Close(StatusCallback callback);
// Proxies File::GetInfo. The callback can't be null.
// This returns false if task posting to |task_runner| has failed.
bool GetInfo(GetFileInfoCallback callback);
// Proxies File::Read. The callback can't be null.
// This returns false if |bytes_to_read| is less than zero, or
// if task posting to |task_runner| has failed.
bool Read(int64_t offset, int bytes_to_read, ReadCallback callback);
// Proxies File::Write. The callback can be null.
// This returns false if |bytes_to_write| is less than or equal to zero,
// if |buffer| is NULL, or if task posting to |task_runner| has failed.
bool Write(int64_t offset,
const char* buffer,
int bytes_to_write,
WriteCallback callback);
// Proxies File::SetTimes. The callback can be null.
// This returns false if task posting to |task_runner| has failed.
bool SetTimes(Time last_access_time,
Time last_modified_time,
StatusCallback callback);
// Proxies File::SetLength. The callback can be null.
// This returns false if task posting to |task_runner| has failed.
bool SetLength(int64_t length, StatusCallback callback);
// Proxies File::Flush. The callback can be null.
// This returns false if task posting to |task_runner| has failed.
bool Flush(StatusCallback callback);
private:
friend class FileHelper;
TaskRunner* task_runner() { return task_runner_.get(); }
scoped_refptr<TaskRunner> task_runner_;
File file_;
DISALLOW_COPY_AND_ASSIGN(FileProxy);
};
} // namespace base
#endif // BASE_FILES_FILE_PROXY_H_

View file

@ -0,0 +1,65 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_tracing.h"
#include "base/atomicops.h"
#include "base/files/file.h"
using base::subtle::AtomicWord;
namespace base {
namespace {
AtomicWord g_provider;
}
FileTracing::Provider* GetProvider() {
AtomicWord provider = base::subtle::Acquire_Load(&g_provider);
return reinterpret_cast<FileTracing::Provider*>(provider);
}
// static
bool FileTracing::IsCategoryEnabled() {
FileTracing::Provider* provider = GetProvider();
return provider && provider->FileTracingCategoryIsEnabled();
}
// static
void FileTracing::SetProvider(FileTracing::Provider* provider) {
base::subtle::Release_Store(&g_provider,
reinterpret_cast<AtomicWord>(provider));
}
FileTracing::ScopedEnabler::ScopedEnabler() {
FileTracing::Provider* provider = GetProvider();
if (provider)
provider->FileTracingEnable(this);
}
FileTracing::ScopedEnabler::~ScopedEnabler() {
FileTracing::Provider* provider = GetProvider();
if (provider)
provider->FileTracingDisable(this);
}
FileTracing::ScopedTrace::ScopedTrace() : id_(nullptr) {}
FileTracing::ScopedTrace::~ScopedTrace() {
if (id_) {
FileTracing::Provider* provider = GetProvider();
if (provider)
provider->FileTracingEventEnd(name_, id_);
}
}
void FileTracing::ScopedTrace::Initialize(const char* name,
const File* file,
int64_t size) {
id_ = &file->trace_enabler_;
name_ = name;
GetProvider()->FileTracingEventBegin(name_, id_, file->tracing_path_, size);
}
} // namespace base

View file

@ -0,0 +1,95 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_FILE_TRACING_H_
#define BASE_FILES_FILE_TRACING_H_
#include <stdint.h>
#include "base/base_export.h"
#include "base/macros.h"
#define FILE_TRACING_PREFIX "File"
#define SCOPED_FILE_TRACE_WITH_SIZE(name, size) \
FileTracing::ScopedTrace scoped_file_trace; \
if (FileTracing::IsCategoryEnabled()) \
scoped_file_trace.Initialize(FILE_TRACING_PREFIX "::" name, this, size)
#define SCOPED_FILE_TRACE(name) SCOPED_FILE_TRACE_WITH_SIZE(name, 0)
namespace base {
class File;
class FilePath;
class BASE_EXPORT FileTracing {
public:
// Whether the file tracing category is enabled.
static bool IsCategoryEnabled();
class Provider {
public:
virtual ~Provider() = default;
// Whether the file tracing category is currently enabled.
virtual bool FileTracingCategoryIsEnabled() const = 0;
// Enables file tracing for |id|. Must be called before recording events.
virtual void FileTracingEnable(const void* id) = 0;
// Disables file tracing for |id|.
virtual void FileTracingDisable(const void* id) = 0;
// Begins an event for |id| with |name|. |path| tells where in the directory
// structure the event is happening (and may be blank). |size| is the number
// of bytes involved in the event.
virtual void FileTracingEventBegin(const char* name,
const void* id,
const FilePath& path,
int64_t size) = 0;
// Ends an event for |id| with |name|.
virtual void FileTracingEventEnd(const char* name, const void* id) = 0;
};
// Sets a global file tracing provider to query categories and record events.
static void SetProvider(Provider* provider);
// Enables file tracing while in scope.
class ScopedEnabler {
public:
ScopedEnabler();
~ScopedEnabler();
};
class ScopedTrace {
public:
ScopedTrace();
~ScopedTrace();
// Called only if the tracing category is enabled. |name| is the name of the
// event to trace (e.g. "Read", "Write") and must have an application
// lifetime (e.g. static or literal). |file| is the file being traced; must
// outlive this class. |size| is the size (in bytes) of this event.
void Initialize(const char* name, const File* file, int64_t size);
private:
// The ID of this trace. Based on the |file| passed to |Initialize()|. Must
// outlive this class.
const void* id_;
// The name of the event to trace (e.g. "Read", "Write"). Prefixed with
// "File".
const char* name_;
DISALLOW_COPY_AND_ASSIGN(ScopedTrace);
};
DISALLOW_COPY_AND_ASSIGN(FileTracing);
};
} // namespace base
#endif // BASE_FILES_FILE_TRACING_H_

View file

@ -0,0 +1,337 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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"
#if defined(OS_WIN)
#include <io.h>
#endif
#include <stdio.h>
#include <fstream>
#include <limits>
#include <memory>
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"
namespace base {
#if !defined(OS_NACL_NONSFI)
int64_t ComputeDirectorySize(const FilePath& root_path) {
int64_t running_size = 0;
FileEnumerator file_iter(root_path, true, FileEnumerator::FILES);
while (!file_iter.Next().empty())
running_size += file_iter.GetInfo().GetSize();
return running_size;
}
bool Move(const FilePath& from_path, const FilePath& to_path) {
if (from_path.ReferencesParent() || to_path.ReferencesParent())
return false;
return internal::MoveUnsafe(from_path, to_path);
}
bool ContentsEqual(const FilePath& filename1, const FilePath& filename2) {
// We open the file in binary format even if they are text files because
// we are just comparing that bytes are exactly same in both files and not
// doing anything smart with text formatting.
#if defined(OS_WIN)
std::ifstream file1(filename1.value().c_str(),
std::ios::in | std::ios::binary);
std::ifstream file2(filename2.value().c_str(),
std::ios::in | std::ios::binary);
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
std::ifstream file1(filename1.value(), std::ios::in | std::ios::binary);
std::ifstream file2(filename2.value(), std::ios::in | std::ios::binary);
#endif // OS_WIN
// Even if both files aren't openable (and thus, in some sense, "equal"),
// any unusable file yields a result of "false".
if (!file1.is_open() || !file2.is_open())
return false;
const int BUFFER_SIZE = 2056;
char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
do {
file1.read(buffer1, BUFFER_SIZE);
file2.read(buffer2, BUFFER_SIZE);
if ((file1.eof() != file2.eof()) ||
(file1.gcount() != file2.gcount()) ||
(memcmp(buffer1, buffer2, static_cast<size_t>(file1.gcount())))) {
file1.close();
file2.close();
return false;
}
} while (!file1.eof() || !file2.eof());
file1.close();
file2.close();
return true;
}
bool TextContentsEqual(const FilePath& filename1, const FilePath& filename2) {
#if defined(OS_WIN)
std::ifstream file1(filename1.value().c_str(), std::ios::in);
std::ifstream file2(filename2.value().c_str(), std::ios::in);
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
std::ifstream file1(filename1.value(), std::ios::in);
std::ifstream file2(filename2.value(), std::ios::in);
#endif // OS_WIN
// Even if both files aren't openable (and thus, in some sense, "equal"),
// any unusable file yields a result of "false".
if (!file1.is_open() || !file2.is_open())
return false;
do {
std::string line1, line2;
getline(file1, line1);
getline(file2, line2);
// Check for mismatched EOF states, or any error state.
if ((file1.eof() != file2.eof()) ||
file1.bad() || file2.bad()) {
return false;
}
// Trim all '\r' and '\n' characters from the end of the line.
std::string::size_type end1 = line1.find_last_not_of("\r\n");
if (end1 == std::string::npos)
line1.clear();
else if (end1 + 1 < line1.length())
line1.erase(end1 + 1);
std::string::size_type end2 = line2.find_last_not_of("\r\n");
if (end2 == std::string::npos)
line2.clear();
else if (end2 + 1 < line2.length())
line2.erase(end2 + 1);
if (line1 != line2)
return false;
} while (!file1.eof() || !file2.eof());
return true;
}
#endif // !defined(OS_NACL_NONSFI)
bool ReadFileToStringWithMaxSize(const FilePath& path,
std::string* contents,
size_t max_size) {
if (contents)
contents->clear();
if (path.ReferencesParent())
return false;
FILE* file = OpenFile(path, "rb");
if (!file) {
return false;
}
// Many files supplied in |path| have incorrect size (proc files etc).
// Hence, the file is read sequentially as opposed to a one-shot read, using
// file size as a hint for chunk size if available.
constexpr int64_t kDefaultChunkSize = 1 << 16;
int64_t chunk_size;
#if !defined(OS_NACL_NONSFI)
if (!GetFileSize(path, &chunk_size) || chunk_size <= 0)
chunk_size = kDefaultChunkSize - 1;
// We need to attempt to read at EOF for feof flag to be set so here we
// use |chunk_size| + 1.
chunk_size = std::min<uint64_t>(chunk_size, max_size) + 1;
#else
chunk_size = kDefaultChunkSize;
#endif // !defined(OS_NACL_NONSFI)
size_t bytes_read_this_pass;
size_t bytes_read_so_far = 0;
bool read_status = true;
std::string local_contents;
local_contents.resize(chunk_size);
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
while ((bytes_read_this_pass = fread(&local_contents[bytes_read_so_far], 1,
chunk_size, file)) > 0) {
if ((max_size - bytes_read_so_far) < bytes_read_this_pass) {
// Read more than max_size bytes, bail out.
bytes_read_so_far = max_size;
read_status = false;
break;
}
// In case EOF was not reached, iterate again but revert to the default
// chunk size.
if (bytes_read_so_far == 0)
chunk_size = kDefaultChunkSize;
bytes_read_so_far += bytes_read_this_pass;
// Last fread syscall (after EOF) can be avoided via feof, which is just a
// flag check.
if (feof(file))
break;
local_contents.resize(bytes_read_so_far + chunk_size);
}
read_status = read_status && !ferror(file);
CloseFile(file);
if (contents) {
contents->swap(local_contents);
contents->resize(bytes_read_so_far);
}
return read_status;
}
bool ReadFileToString(const FilePath& path, std::string* contents) {
return ReadFileToStringWithMaxSize(path, contents,
std::numeric_limits<size_t>::max());
}
#if !defined(OS_NACL_NONSFI)
bool IsDirectoryEmpty(const FilePath& dir_path) {
FileEnumerator files(dir_path, false,
FileEnumerator::FILES | FileEnumerator::DIRECTORIES);
if (files.Next().empty())
return true;
return false;
}
FILE* CreateAndOpenTemporaryFile(FilePath* path) {
FilePath directory;
if (!GetTempDir(&directory))
return nullptr;
return CreateAndOpenTemporaryFileInDir(directory, path);
}
bool CreateDirectory(const FilePath& full_path) {
return CreateDirectoryAndGetError(full_path, nullptr);
}
bool GetFileSize(const FilePath& file_path, int64_t* file_size) {
File::Info info;
if (!GetFileInfo(file_path, &info))
return false;
*file_size = info.size;
return true;
}
bool TouchFile(const FilePath& path,
const Time& last_accessed,
const Time& last_modified) {
int flags = File::FLAG_OPEN | File::FLAG_WRITE_ATTRIBUTES;
#if defined(OS_WIN)
// On Windows, FILE_FLAG_BACKUP_SEMANTICS is needed to open a directory.
if (DirectoryExists(path))
flags |= File::FLAG_BACKUP_SEMANTICS;
#elif defined(OS_FUCHSIA)
// On Fuchsia, we need O_RDONLY for directories, or O_WRONLY for files.
// TODO(https://crbug.com/947802): Find a cleaner workaround for this.
flags |= (DirectoryExists(path) ? File::FLAG_READ : File::FLAG_WRITE);
#endif
File file(path, flags);
if (!file.IsValid())
return false;
return file.SetTimes(last_accessed, last_modified);
}
#endif // !defined(OS_NACL_NONSFI)
bool CloseFile(FILE* file) {
if (file == nullptr)
return true;
return fclose(file) == 0;
}
#if !defined(OS_NACL_NONSFI)
bool TruncateFile(FILE* file) {
if (file == nullptr)
return false;
long current_offset = ftell(file);
if (current_offset == -1)
return false;
#if defined(OS_WIN)
int fd = _fileno(file);
if (_chsize(fd, current_offset) != 0)
return false;
#else
int fd = fileno(file);
if (ftruncate(fd, current_offset) != 0)
return false;
#endif
return true;
}
int GetUniquePathNumber(const FilePath& path) {
DCHECK(!path.empty());
if (!PathExists(path))
return 0;
std::string number;
for (int count = 1; count <= kMaxUniqueFiles; ++count) {
StringAppendF(&number, " (%d)", count);
if (!PathExists(path.InsertBeforeExtensionASCII(number)))
return count;
number.clear();
}
return -1;
}
FilePath GetUniquePath(const FilePath& path) {
DCHECK(!path.empty());
const int uniquifier = GetUniquePathNumber(path);
if (uniquifier > 0)
return path.InsertBeforeExtensionASCII(StringPrintf(" (%d)", uniquifier));
return uniquifier == 0 ? path : base::FilePath();
}
namespace internal {
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 |
File::FLAG_SEQUENTIAL_SCAN |
File::FLAG_SHARE_DELETE);
if (!file.IsValid())
return false;
constexpr int kBufferSize = 1024 * 1024;
// Ensures the buffer is deallocated at function exit.
std::unique_ptr<char[]> buffer_deleter(new char[kBufferSize]);
char* const buffer = buffer_deleter.get();
while (max_bytes > 0) {
// The static_cast<int> is safe because kBufferSize is int, and both values
// are non-negative. So, the minimum is guaranteed to fit in int.
const int read_size =
static_cast<int>(std::min<int64_t>(max_bytes, kBufferSize));
DCHECK_GE(read_size, 0);
DCHECK_LE(read_size, kBufferSize);
const int read_bytes = file.ReadAtCurrentPos(buffer, read_size);
if (read_bytes < 0)
return false;
if (read_bytes == 0)
break;
max_bytes -= read_bytes;
}
return true;
}
} // namespace internal
#endif // !defined(OS_NACL_NONSFI)
} // namespace base

View file

@ -0,0 +1,575 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file contains utility functions for dealing with the local
// filesystem.
#ifndef BASE_FILES_FILE_UTIL_H_
#define BASE_FILES_FILE_UTIL_H_
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <limits>
#include <set>
#include <string>
#include <vector>
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <sys/stat.h>
#include <unistd.h>
#endif
#include "base/base_export.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/strings/string16.h"
#include "build/build_config.h"
#if defined(OS_WIN)
#include "base/win/windows_types.h"
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#include "base/file_descriptor_posix.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#endif
namespace base {
class Environment;
class Time;
//-----------------------------------------------------------------------------
// Functions that involve filesystem access or modification:
// Returns an absolute version of a relative path. Returns an empty path on
// error. On POSIX, this function fails if the path does not exist. This
// function can result in I/O so it can be slow.
BASE_EXPORT FilePath MakeAbsoluteFilePath(const FilePath& input);
// Returns the total number of bytes used by all the files under |root_path|.
// If the path does not exist the function returns 0.
//
// This function is implemented using the FileEnumerator class so it is not
// particularly speedy in any platform.
BASE_EXPORT int64_t ComputeDirectorySize(const FilePath& root_path);
// Deletes the given path, whether it's a file or a directory.
// If it's a directory, it's perfectly happy to delete all of the
// directory's contents. Passing true to recursive deletes
// subdirectories and their contents as well.
// Returns true if successful, false otherwise. It is considered successful
// to attempt to delete a file that does not exist.
//
// In POSIX environment and if |path| is a symbolic link, this deletes only
// the symlink. (even if the symlink points to a non-existent file)
//
// WARNING: USING THIS WITH recursive==true IS EQUIVALENT
// TO "rm -rf", SO USE WITH CAUTION.
//
// Note: The |recursive| parameter is in the process of being removed. Use
// DeleteFileRecursively() instead. See https://crbug.com/1009837
BASE_EXPORT bool DeleteFile(const FilePath& path, bool recursive);
// Deletes the given path, whether it's a file or a directory.
// If it's a directory, it's perfectly happy to delete all of the
// directory's contents, including subdirectories and their contents.
// Returns true if successful, false otherwise. It is considered successful
// to attempt to delete a file that does not exist.
//
// In POSIX environment and if |path| is a symbolic link, this deletes only
// the symlink. (even if the symlink points to a non-existent file)
//
// WARNING: USING THIS EQUIVALENT TO "rm -rf", SO USE WITH CAUTION.
BASE_EXPORT bool DeleteFileRecursively(const FilePath& path);
#if defined(OS_WIN)
// Schedules to delete the given path, whether it's a file or a directory, until
// the operating system is restarted.
// Note:
// 1) The file/directory to be deleted should exist in a temp folder.
// 2) The directory to be deleted must be empty.
BASE_EXPORT bool DeleteFileAfterReboot(const FilePath& path);
#endif
// Moves the given path, whether it's a file or a directory.
// If a simple rename is not possible, such as in the case where the paths are
// on different volumes, this will attempt to copy and delete. Returns
// true for success.
// This function fails if either path contains traversal components ('..').
BASE_EXPORT bool Move(const FilePath& from_path, const FilePath& to_path);
// Renames file |from_path| to |to_path|. Both paths must be on the same
// volume, or the function will fail. Destination file will be created
// if it doesn't exist. Prefer this function over Move when dealing with
// temporary files. On Windows it preserves attributes of the target file.
// Returns true on success, leaving *error unchanged.
// Returns false on failure and sets *error appropriately, if it is non-NULL.
BASE_EXPORT bool ReplaceFile(const FilePath& from_path,
const FilePath& to_path,
File::Error* error);
// Copies a single file. Use CopyDirectory() to copy directories.
// This function fails if either path contains traversal components ('..').
// This function also fails if |to_path| is a directory.
//
// On POSIX, if |to_path| is a symlink, CopyFile() will follow the symlink. This
// may have security implications. Use with care.
//
// If |to_path| already exists and is a regular file, it will be overwritten,
// though its permissions will stay the same.
//
// If |to_path| does not exist, it will be created. The new file's permissions
// varies per platform:
//
// - This function keeps the metadata on Windows. The read only bit is not kept.
// - On Mac and iOS, |to_path| retains |from_path|'s permissions, except user
// read/write permissions are always set.
// - On Linux and Android, |to_path| has user read/write permissions only. i.e.
// Always 0600.
// - On ChromeOS, |to_path| has user read/write permissions and group/others
// read permissions. i.e. Always 0644.
BASE_EXPORT bool CopyFile(const FilePath& from_path, const FilePath& to_path);
// Copies the given path, and optionally all subdirectories and their contents
// as well.
//
// If there are files existing under to_path, always overwrite. Returns true
// if successful, false otherwise. Wildcards on the names are not supported.
//
// This function has the same metadata behavior as CopyFile().
//
// If you only need to copy a file use CopyFile, it's faster.
BASE_EXPORT bool CopyDirectory(const FilePath& from_path,
const FilePath& to_path,
bool recursive);
// Like CopyDirectory() except trying to overwrite an existing file will not
// work and will return false.
BASE_EXPORT bool CopyDirectoryExcl(const FilePath& from_path,
const FilePath& to_path,
bool recursive);
// Returns true if the given path exists on the local filesystem,
// false otherwise.
BASE_EXPORT bool PathExists(const FilePath& path);
// Returns true if the given path is writable by the user, false otherwise.
BASE_EXPORT bool PathIsWritable(const FilePath& path);
// Returns true if the given path exists and is a directory, false otherwise.
BASE_EXPORT bool DirectoryExists(const FilePath& path);
// Returns true if the contents of the two files given are equal, false
// otherwise. If either file can't be read, returns false.
BASE_EXPORT bool ContentsEqual(const FilePath& filename1,
const FilePath& filename2);
// Returns true if the contents of the two text files given are equal, false
// otherwise. This routine treats "\r\n" and "\n" as equivalent.
BASE_EXPORT bool TextContentsEqual(const FilePath& filename1,
const FilePath& filename2);
// Reads the file at |path| into |contents| and returns true on success and
// false on error. For security reasons, a |path| containing path traversal
// components ('..') is treated as a read error and |contents| is set to empty.
// In case of I/O error, |contents| holds the data that could be read from the
// file before the error occurred.
// |contents| may be NULL, in which case this function is useful for its side
// effect of priming the disk cache (could be used for unit tests).
BASE_EXPORT bool ReadFileToString(const FilePath& path, std::string* contents);
// Reads the file at |path| into |contents| and returns true on success and
// false on error. For security reasons, a |path| containing path traversal
// components ('..') is treated as a read error and |contents| is set to empty.
// In case of I/O error, |contents| holds the data that could be read from the
// file before the error occurred. When the file size exceeds |max_size|, the
// function returns false with |contents| holding the file truncated to
// |max_size|.
// |contents| may be NULL, in which case this function is useful for its side
// effect of priming the disk cache (could be used for unit tests).
BASE_EXPORT bool ReadFileToStringWithMaxSize(const FilePath& path,
std::string* contents,
size_t max_size);
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Read exactly |bytes| bytes from file descriptor |fd|, storing the result
// in |buffer|. This function is protected against EINTR and partial reads.
// Returns true iff |bytes| bytes have been successfully read from |fd|.
BASE_EXPORT bool ReadFromFD(int fd, char* buffer, size_t bytes);
// Performs the same function as CreateAndOpenTemporaryFileInDir(), but returns
// the file-descriptor wrapped in a ScopedFD, rather than wrapped in a FILE.
BASE_EXPORT ScopedFD CreateAndOpenFdForTemporaryFileInDir(const FilePath& dir,
FilePath* path);
#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
#if defined(OS_POSIX)
// Creates a symbolic link at |symlink| pointing to |target|. Returns
// false on failure.
BASE_EXPORT bool CreateSymbolicLink(const FilePath& target,
const FilePath& symlink);
// Reads the given |symlink| and returns where it points to in |target|.
// Returns false upon failure.
BASE_EXPORT bool ReadSymbolicLink(const FilePath& symlink, FilePath* target);
// Bits and masks of the file permission.
enum FilePermissionBits {
FILE_PERMISSION_MASK = S_IRWXU | S_IRWXG | S_IRWXO,
FILE_PERMISSION_USER_MASK = S_IRWXU,
FILE_PERMISSION_GROUP_MASK = S_IRWXG,
FILE_PERMISSION_OTHERS_MASK = S_IRWXO,
FILE_PERMISSION_READ_BY_USER = S_IRUSR,
FILE_PERMISSION_WRITE_BY_USER = S_IWUSR,
FILE_PERMISSION_EXECUTE_BY_USER = S_IXUSR,
FILE_PERMISSION_READ_BY_GROUP = S_IRGRP,
FILE_PERMISSION_WRITE_BY_GROUP = S_IWGRP,
FILE_PERMISSION_EXECUTE_BY_GROUP = S_IXGRP,
FILE_PERMISSION_READ_BY_OTHERS = S_IROTH,
FILE_PERMISSION_WRITE_BY_OTHERS = S_IWOTH,
FILE_PERMISSION_EXECUTE_BY_OTHERS = S_IXOTH,
};
// Reads the permission of the given |path|, storing the file permission
// bits in |mode|. If |path| is symbolic link, |mode| is the permission of
// a file which the symlink points to.
BASE_EXPORT bool GetPosixFilePermissions(const FilePath& path, int* mode);
// Sets the permission of the given |path|. If |path| is symbolic link, sets
// the permission of a file which the symlink points to.
BASE_EXPORT bool SetPosixFilePermissions(const FilePath& path, int mode);
// Returns true iff |executable| can be found in any directory specified by the
// environment variable in |env|.
BASE_EXPORT bool ExecutableExistsInPath(Environment* env,
const FilePath::StringType& executable);
#if defined(OS_LINUX) || defined(OS_AIX)
// Determine if files under a given |path| can be mapped and then mprotect'd
// PROT_EXEC. This depends on the mount options used for |path|, which vary
// among different Linux distributions and possibly local configuration. It also
// depends on details of kernel--ChromeOS uses the noexec option for /dev/shm
// but its kernel allows mprotect with PROT_EXEC anyway.
BASE_EXPORT bool IsPathExecutable(const FilePath& path);
#endif // OS_LINUX || OS_AIX
#endif // OS_POSIX
// Returns true if the given directory is empty
BASE_EXPORT bool IsDirectoryEmpty(const FilePath& dir_path);
// Get the temporary directory provided by the system.
//
// WARNING: In general, you should use CreateTemporaryFile variants below
// instead of this function. Those variants will ensure that the proper
// permissions are set so that other users on the system can't edit them while
// they're open (which can lead to security issues).
BASE_EXPORT bool GetTempDir(FilePath* path);
// Get the home directory. This is more complicated than just getenv("HOME")
// as it knows to fall back on getpwent() etc.
//
// You should not generally call this directly. Instead use DIR_HOME with the
// path service which will use this function but cache the value.
// Path service may also override DIR_HOME.
BASE_EXPORT FilePath GetHomeDir();
// Creates a temporary file. The full path is placed in |path|, and the
// function returns true if was successful in creating the file. The file will
// be empty and all handles closed after this function returns.
BASE_EXPORT bool CreateTemporaryFile(FilePath* path);
// Same as CreateTemporaryFile but the file is created in |dir|.
BASE_EXPORT bool CreateTemporaryFileInDir(const FilePath& dir,
FilePath* temp_file);
// Create and open a temporary file. File is opened for read/write.
// The full path is placed in |path|.
// Returns a handle to the opened file or NULL if an error occurred.
BASE_EXPORT FILE* CreateAndOpenTemporaryFile(FilePath* path);
// Similar to CreateAndOpenTemporaryFile, but the file is created in |dir|.
BASE_EXPORT FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir,
FilePath* path);
// Create a new directory. If prefix is provided, the new directory name is in
// the format of prefixyyyy.
// NOTE: prefix is ignored in the POSIX implementation.
// If success, return true and output the full path of the directory created.
BASE_EXPORT bool CreateNewTempDirectory(const FilePath::StringType& prefix,
FilePath* new_temp_path);
// Create a directory within another directory.
// Extra characters will be appended to |prefix| to ensure that the
// new directory does not have the same name as an existing directory.
BASE_EXPORT bool CreateTemporaryDirInDir(const FilePath& base_dir,
const FilePath::StringType& prefix,
FilePath* new_dir);
// Creates a directory, as well as creating any parent directories, if they
// don't exist. Returns 'true' on successful creation, or if the directory
// already exists. The directory is only readable by the current user.
// Returns true on success, leaving *error unchanged.
// Returns false on failure and sets *error appropriately, if it is non-NULL.
BASE_EXPORT bool CreateDirectoryAndGetError(const FilePath& full_path,
File::Error* error);
// Backward-compatible convenience method for the above.
BASE_EXPORT bool CreateDirectory(const FilePath& full_path);
// Returns the file size. Returns true on success.
BASE_EXPORT bool GetFileSize(const FilePath& file_path, int64_t* file_size);
// Sets |real_path| to |path| with symbolic links and junctions expanded.
// On windows, make sure the path starts with a lettered drive.
// |path| must reference a file. Function will fail if |path| points to
// a directory or to a nonexistent path. On windows, this function will
// fail if |real_path| would be longer than MAX_PATH characters.
BASE_EXPORT bool NormalizeFilePath(const FilePath& path, FilePath* real_path);
#if defined(OS_WIN)
// Given a path in NT native form ("\Device\HarddiskVolumeXX\..."),
// return in |drive_letter_path| the equivalent path that starts with
// a drive letter ("C:\..."). Return false if no such path exists.
BASE_EXPORT bool DevicePathToDriveLetterPath(const FilePath& device_path,
FilePath* drive_letter_path);
// Method that wraps the win32 GetLongPathName API, normalizing the specified
// path to its long form. An example where this is needed is when comparing
// temp file paths. If a username isn't a valid 8.3 short file name (even just a
// lengthy name like "user with long name"), Windows will set the TMP and TEMP
// environment variables to be 8.3 paths. ::GetTempPath (called in
// base::GetTempDir) just uses the value specified by TMP or TEMP, and so can
// return a short path. Returns an empty path on error.
BASE_EXPORT FilePath MakeLongFilePath(const FilePath& input);
// Creates a hard link named |to_file| to the file |from_file|. Both paths
// must be on the same volume, and |from_file| may not name a directory.
// Returns true if the hard link is created, false if it fails.
BASE_EXPORT bool CreateWinHardLink(const FilePath& to_file,
const FilePath& from_file);
#endif
// This function will return if the given file is a symlink or not.
BASE_EXPORT bool IsLink(const FilePath& file_path);
// Returns information about the given file path.
BASE_EXPORT bool GetFileInfo(const FilePath& file_path, File::Info* info);
// Sets the time of the last access and the time of the last modification.
BASE_EXPORT bool TouchFile(const FilePath& path,
const Time& last_accessed,
const Time& last_modified);
// Wrapper for fopen-like calls. Returns non-NULL FILE* on success. The
// underlying file descriptor (POSIX) or handle (Windows) is unconditionally
// configured to not be propagated to child processes.
BASE_EXPORT FILE* OpenFile(const FilePath& filename, const char* mode);
// Closes file opened by OpenFile. Returns true on success.
BASE_EXPORT bool CloseFile(FILE* file);
// Associates a standard FILE stream with an existing File. Note that this
// functions take ownership of the existing File.
BASE_EXPORT FILE* FileToFILE(File file, const char* mode);
// Truncates an open file to end at the location of the current file pointer.
// This is a cross-platform analog to Windows' SetEndOfFile() function.
BASE_EXPORT bool TruncateFile(FILE* file);
// Reads at most the given number of bytes from the file into the buffer.
// Returns the number of read bytes, or -1 on error.
BASE_EXPORT int ReadFile(const FilePath& filename, char* data, int max_size);
// Writes the given buffer into the file, overwriting any data that was
// previously there. Returns the number of bytes written, or -1 on error.
// If file doesn't exist, it gets created with read/write permissions for all.
BASE_EXPORT int WriteFile(const FilePath& filename, const char* data,
int size);
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Appends |data| to |fd|. Does not close |fd| when done. Returns true iff
// |size| bytes of |data| were written to |fd|.
BASE_EXPORT bool WriteFileDescriptor(const int fd, const char* data, int size);
// Allocates disk space for the file referred to by |fd| for the byte range
// starting at |offset| and continuing for |size| bytes. The file size will be
// changed if |offset|+|len| is greater than the file size. Zeros will fill the
// new space.
// After a successful call, subsequent writes into the specified range are
// guaranteed not to fail because of lack of disk space.
BASE_EXPORT bool AllocateFileRegion(File* file, int64_t offset, size_t size);
#endif
// Appends |data| to |filename|. Returns true iff |size| bytes of |data| were
// written to |filename|.
BASE_EXPORT bool AppendToFile(const FilePath& filename,
const char* data,
int size);
// Gets the current working directory for the process.
BASE_EXPORT bool GetCurrentDirectory(FilePath* path);
// Sets the current working directory for the process.
BASE_EXPORT bool SetCurrentDirectory(const FilePath& path);
// The largest value attempted by GetUniquePath{Number,}.
enum { kMaxUniqueFiles = 100 };
// Returns the number N that makes |path| unique when formatted as " (N)" in a
// suffix to its basename before any file extension, where N is a number between
// 1 and 100 (inclusive). Returns 0 if |path| does not exist (meaning that it is
// unique as-is), or -1 if no such number can be found.
BASE_EXPORT int GetUniquePathNumber(const FilePath& path);
// Returns |path| if it does not exist. Otherwise, returns |path| with the
// suffix " (N)" appended to its basename before any file extension, where N is
// a number between 1 and 100 (inclusive). Returns an empty path if no such
// number can be found.
BASE_EXPORT FilePath GetUniquePath(const FilePath& path);
// Sets the given |fd| to non-blocking mode.
// Returns true if it was able to set it in the non-blocking mode, otherwise
// false.
BASE_EXPORT bool SetNonBlocking(int fd);
// Hints the OS to prefetch the first |max_bytes| of |file_path| into its cache.
//
// If called at the appropriate time, this can reduce the latency incurred by
// feature code that needs to read the file.
//
// |max_bytes| specifies how many bytes should be pre-fetched. It may exceed the
// file's size. Passing in std::numeric_limits<int64_t>::max() is a convenient
// way to get the entire file pre-fetched.
//
// |is_executable| specifies whether the file is to be prefetched as
// executable code or as data. Windows treats the file backed pages in RAM
// differently, and specifying the wrong value results in two copies in RAM.
//
// Returns false if prefetching definitely failed. A return value of true does
// not guarantee that the entire desired range was prefetched.
//
// Calling this before using ::LoadLibrary() on Windows is more efficient memory
// wise, but we must be sure no other threads try to LoadLibrary() the file
// while we are doing the mapping and prefetching, or the process will get a
// private copy of the DLL via COW.
BASE_EXPORT bool PreReadFile(
const FilePath& file_path,
bool is_executable,
int64_t max_bytes = std::numeric_limits<int64_t>::max());
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Creates a pipe. Returns true on success, otherwise false.
// On success, |read_fd| will be set to the fd of the read side, and
// |write_fd| will be set to the one of write side. If |non_blocking|
// is set the pipe will be created with O_NONBLOCK|O_CLOEXEC flags set
// otherwise flag is set to zero (default).
BASE_EXPORT bool CreatePipe(ScopedFD* read_fd,
ScopedFD* write_fd,
bool non_blocking = false);
// Creates a non-blocking, close-on-exec pipe.
// This creates a non-blocking pipe that is not intended to be shared with any
// child process. This will be done atomically if the operating system supports
// it. Returns true if it was able to create the pipe, otherwise false.
BASE_EXPORT bool CreateLocalNonBlockingPipe(int fds[2]);
// Sets the given |fd| to close-on-exec mode.
// Returns true if it was able to set it in the close-on-exec mode, otherwise
// false.
BASE_EXPORT bool SetCloseOnExec(int fd);
// Test that |path| can only be changed by a given user and members of
// a given set of groups.
// Specifically, test that all parts of |path| under (and including) |base|:
// * Exist.
// * Are owned by a specific user.
// * Are not writable by all users.
// * Are owned by a member of a given set of groups, or are not writable by
// their group.
// * Are not symbolic links.
// This is useful for checking that a config file is administrator-controlled.
// |base| must contain |path|.
BASE_EXPORT bool VerifyPathControlledByUser(const base::FilePath& base,
const base::FilePath& path,
uid_t owner_uid,
const std::set<gid_t>& group_gids);
#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
#if defined(OS_MACOSX) && !defined(OS_IOS)
// Is |path| writable only by a user with administrator privileges?
// This function uses Mac OS conventions. The super user is assumed to have
// uid 0, and the administrator group is assumed to be named "admin".
// Testing that |path|, and every parent directory including the root of
// the filesystem, are owned by the superuser, controlled by the group
// "admin", are not writable by all users, and contain no symbolic links.
// Will return false if |path| does not exist.
BASE_EXPORT bool VerifyPathControlledByAdmin(const base::FilePath& path);
#endif // defined(OS_MACOSX) && !defined(OS_IOS)
// Returns the maximum length of path component on the volume containing
// the directory |path|, in the number of FilePath::CharType, or -1 on failure.
BASE_EXPORT int GetMaximumPathComponentLength(const base::FilePath& path);
#if defined(OS_LINUX) || defined(OS_AIX)
// Broad categories of file systems as returned by statfs() on Linux.
enum FileSystemType {
FILE_SYSTEM_UNKNOWN, // statfs failed.
FILE_SYSTEM_0, // statfs.f_type == 0 means unknown, may indicate AFS.
FILE_SYSTEM_ORDINARY, // on-disk filesystem like ext2
FILE_SYSTEM_NFS,
FILE_SYSTEM_SMB,
FILE_SYSTEM_CODA,
FILE_SYSTEM_MEMORY, // in-memory file system
FILE_SYSTEM_CGROUP, // cgroup control.
FILE_SYSTEM_OTHER, // any other value.
FILE_SYSTEM_TYPE_COUNT
};
// Attempts determine the FileSystemType for |path|.
// Returns false if |path| doesn't exist.
BASE_EXPORT bool GetFileSystemType(const FilePath& path, FileSystemType* type);
#endif
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Get a temporary directory for shared memory files. The directory may depend
// on whether the destination is intended for executable files, which in turn
// depends on how /dev/shmem was mounted. As a result, you must supply whether
// you intend to create executable shmem segments so this function can find
// an appropriate location.
BASE_EXPORT bool GetShmemTempDir(bool executable, FilePath* path);
#endif
// Internal --------------------------------------------------------------------
namespace internal {
// Same as Move but allows paths with traversal components.
// Use only with extreme care.
BASE_EXPORT bool MoveUnsafe(const FilePath& from_path,
const FilePath& to_path);
#if defined(OS_WIN)
// Copy from_path to to_path recursively and then delete from_path recursively.
// Returns true if all operations succeed.
// This function simulates Move(), but unlike Move() it works across volumes.
// This function is not transactional.
BASE_EXPORT bool CopyAndDeleteDirectory(const FilePath& from_path,
const FilePath& to_path);
#endif // defined(OS_WIN)
// Used by PreReadFile() when no kernel support for prefetching is available.
bool PreReadFileSlow(const FilePath& file_path, int64_t max_bytes);
} // namespace internal
} // namespace base
#endif // BASE_FILES_FILE_UTIL_H_

View file

@ -0,0 +1,16 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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 "base/files/file_path.h"
#include "base/path_service.h"
namespace base {
bool GetShmemTempDir(bool executable, base::FilePath* path) {
return PathService::Get(base::DIR_CACHE, path);
}
} // namespace base

View file

@ -0,0 +1,63 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// 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 <errno.h>
#include <linux/magic.h>
#include <sys/vfs.h>
#include "base/files/file_path.h"
namespace base {
bool GetFileSystemType(const FilePath& path, FileSystemType* type) {
struct statfs statfs_buf;
if (statfs(path.value().c_str(), &statfs_buf) < 0) {
if (errno == ENOENT)
return false;
*type = FILE_SYSTEM_UNKNOWN;
return true;
}
// Not all possible |statfs_buf.f_type| values are in linux/magic.h.
// Missing values are copied from the statfs man page.
switch (statfs_buf.f_type) {
case 0:
*type = FILE_SYSTEM_0;
break;
case EXT2_SUPER_MAGIC: // Also ext3 and ext4
case MSDOS_SUPER_MAGIC:
case REISERFS_SUPER_MAGIC:
case BTRFS_SUPER_MAGIC:
case 0x5346544E: // NTFS
case 0x58465342: // XFS
case 0x3153464A: // JFS
*type = FILE_SYSTEM_ORDINARY;
break;
case NFS_SUPER_MAGIC:
*type = FILE_SYSTEM_NFS;
break;
case SMB_SUPER_MAGIC:
case 0xFF534D42: // CIFS
*type = FILE_SYSTEM_SMB;
break;
case CODA_SUPER_MAGIC:
*type = FILE_SYSTEM_CODA;
break;
case HUGETLBFS_MAGIC:
case RAMFS_MAGIC:
case TMPFS_MAGIC:
*type = FILE_SYSTEM_MEMORY;
break;
case CGROUP_SUPER_MAGIC:
*type = FILE_SYSTEM_CGROUP;
break;
default:
*type = FILE_SYSTEM_OTHER;
}
return true;
}
} // namespace base

View file

@ -0,0 +1,66 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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"
#import <Foundation/Foundation.h>
#include <copyfile.h>
#include <stdlib.h>
#include <string.h>
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/mac/foundation_util.h"
#include "base/strings/string_util.h"
#include "base/threading/scoped_blocking_call.h"
namespace base {
bool CopyFile(const FilePath& from_path, const FilePath& to_path) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
if (from_path.ReferencesParent() || to_path.ReferencesParent())
return false;
return (copyfile(from_path.value().c_str(),
to_path.value().c_str(), NULL, COPYFILE_DATA) == 0);
}
bool GetTempDir(base::FilePath* path) {
// In order to facilitate hermetic runs on macOS, first check
// $MAC_CHROMIUM_TMPDIR. We check this instead of $TMPDIR because external
// programs currently set $TMPDIR with no effect, but when we respect it
// directly it can cause crashes (like crbug.com/698759).
const char* env_tmpdir = getenv("MAC_CHROMIUM_TMPDIR");
if (env_tmpdir) {
DCHECK_LT(strlen(env_tmpdir), 50u)
<< "too-long TMPDIR causes socket name length issues.";
*path = base::FilePath(env_tmpdir);
return true;
}
// If we didn't find it, fall back to the native function.
NSString* tmp = NSTemporaryDirectory();
if (tmp == nil)
return false;
*path = base::mac::NSStringToFilePath(tmp);
return true;
}
FilePath GetHomeDir() {
NSString* tmp = NSHomeDirectory();
if (tmp != nil) {
FilePath mac_home_dir = base::mac::NSStringToFilePath(tmp);
if (!mac_home_dir.empty())
return mac_home_dir;
}
// Fall back on temp dir if no home directory is defined.
FilePath rv;
if (GetTempDir(&rv))
return rv;
// Last resort.
return FilePath("/tmp");
}
} // namespace base

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,454 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file.h"
#include <io.h>
#include <stdint.h>
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_util.h"
#include "base/threading/scoped_blocking_call.h"
#include <windows.h>
namespace base {
// Make sure our Whence mappings match the system headers.
static_assert(File::FROM_BEGIN == FILE_BEGIN &&
File::FROM_CURRENT == FILE_CURRENT &&
File::FROM_END == FILE_END,
"whence mapping must match the system headers");
bool File::IsValid() const {
return file_.IsValid();
}
PlatformFile File::GetPlatformFile() const {
return file_.Get();
}
PlatformFile File::TakePlatformFile() {
return file_.Take();
}
void File::Close() {
if (!file_.IsValid())
return;
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
SCOPED_FILE_TRACE("Close");
file_.Close();
}
int64_t File::Seek(Whence whence, int64_t offset) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
SCOPED_FILE_TRACE_WITH_SIZE("Seek", offset);
LARGE_INTEGER distance, res;
distance.QuadPart = offset;
DWORD move_method = static_cast<DWORD>(whence);
if (!SetFilePointerEx(file_.Get(), distance, &res, move_method))
return -1;
return res.QuadPart;
}
int File::Read(int64_t offset, char* data, int size) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
DCHECK(!async_);
if (size < 0)
return -1;
SCOPED_FILE_TRACE_WITH_SIZE("Read", size);
LARGE_INTEGER offset_li;
offset_li.QuadPart = offset;
OVERLAPPED overlapped = {};
overlapped.Offset = offset_li.LowPart;
overlapped.OffsetHigh = offset_li.HighPart;
DWORD bytes_read;
if (::ReadFile(file_.Get(), data, size, &bytes_read, &overlapped))
return bytes_read;
if (ERROR_HANDLE_EOF == GetLastError())
return 0;
return -1;
}
int File::ReadAtCurrentPos(char* data, int size) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
DCHECK(!async_);
if (size < 0)
return -1;
SCOPED_FILE_TRACE_WITH_SIZE("ReadAtCurrentPos", size);
DWORD bytes_read;
if (::ReadFile(file_.Get(), data, size, &bytes_read, NULL))
return bytes_read;
if (ERROR_HANDLE_EOF == GetLastError())
return 0;
return -1;
}
int File::ReadNoBestEffort(int64_t offset, char* data, int size) {
// TODO(dbeam): trace this separately?
return Read(offset, data, size);
}
int File::ReadAtCurrentPosNoBestEffort(char* data, int size) {
// TODO(dbeam): trace this separately?
return ReadAtCurrentPos(data, size);
}
int File::Write(int64_t offset, const char* data, int size) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
DCHECK(!async_);
SCOPED_FILE_TRACE_WITH_SIZE("Write", size);
LARGE_INTEGER offset_li;
offset_li.QuadPart = offset;
OVERLAPPED overlapped = {};
overlapped.Offset = offset_li.LowPart;
overlapped.OffsetHigh = offset_li.HighPart;
DWORD bytes_written;
if (::WriteFile(file_.Get(), data, size, &bytes_written, &overlapped))
return bytes_written;
return -1;
}
int File::WriteAtCurrentPos(const char* data, int size) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
DCHECK(!async_);
if (size < 0)
return -1;
SCOPED_FILE_TRACE_WITH_SIZE("WriteAtCurrentPos", size);
DWORD bytes_written;
if (::WriteFile(file_.Get(), data, size, &bytes_written, NULL))
return bytes_written;
return -1;
}
int File::WriteAtCurrentPosNoBestEffort(const char* data, int size) {
return WriteAtCurrentPos(data, size);
}
int64_t File::GetLength() {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
SCOPED_FILE_TRACE("GetLength");
LARGE_INTEGER size;
if (!::GetFileSizeEx(file_.Get(), &size))
return -1;
return static_cast<int64_t>(size.QuadPart);
}
bool File::SetLength(int64_t length) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
SCOPED_FILE_TRACE_WITH_SIZE("SetLength", length);
// Get the current file pointer.
LARGE_INTEGER file_pointer;
LARGE_INTEGER zero;
zero.QuadPart = 0;
if (!::SetFilePointerEx(file_.Get(), zero, &file_pointer, FILE_CURRENT))
return false;
LARGE_INTEGER length_li;
length_li.QuadPart = length;
// If length > file size, SetFilePointerEx() should extend the file
// with zeroes on all Windows standard file systems (NTFS, FATxx).
if (!::SetFilePointerEx(file_.Get(), length_li, NULL, FILE_BEGIN))
return false;
// Set the new file length and move the file pointer to its old position.
// This is consistent with ftruncate()'s behavior, even when the file
// pointer points to a location beyond the end of the file.
// TODO(rvargas): Emulating ftruncate details seem suspicious and it is not
// promised by the interface (nor was promised by PlatformFile). See if this
// implementation detail can be removed.
return ((::SetEndOfFile(file_.Get()) != FALSE) &&
(::SetFilePointerEx(file_.Get(), file_pointer, NULL, FILE_BEGIN) !=
FALSE));
}
bool File::SetTimes(Time last_access_time, Time last_modified_time) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
SCOPED_FILE_TRACE("SetTimes");
FILETIME last_access_filetime = last_access_time.ToFileTime();
FILETIME last_modified_filetime = last_modified_time.ToFileTime();
return (::SetFileTime(file_.Get(), NULL, &last_access_filetime,
&last_modified_filetime) != FALSE);
}
bool File::GetInfo(Info* info) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
SCOPED_FILE_TRACE("GetInfo");
BY_HANDLE_FILE_INFORMATION file_info;
if (!GetFileInformationByHandle(file_.Get(), &file_info))
return false;
LARGE_INTEGER size;
size.HighPart = file_info.nFileSizeHigh;
size.LowPart = file_info.nFileSizeLow;
info->size = size.QuadPart;
info->is_directory =
(file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
info->is_symbolic_link = false; // Windows doesn't have symbolic links.
info->last_modified = Time::FromFileTime(file_info.ftLastWriteTime);
info->last_accessed = Time::FromFileTime(file_info.ftLastAccessTime);
info->creation_time = Time::FromFileTime(file_info.ftCreationTime);
return true;
}
namespace {
DWORD LockFileFlagsForMode(File::LockMode mode) {
DWORD flags = LOCKFILE_FAIL_IMMEDIATELY;
switch (mode) {
case File::LockMode::kShared:
return flags;
case File::LockMode::kExclusive:
return flags | LOCKFILE_EXCLUSIVE_LOCK;
}
NOTREACHED();
}
} // namespace
File::Error File::Lock(File::LockMode mode) {
DCHECK(IsValid());
SCOPED_FILE_TRACE("Lock");
OVERLAPPED overlapped = {};
BOOL result =
LockFileEx(file_.Get(), LockFileFlagsForMode(mode), /*dwReserved=*/0,
/*nNumberOfBytesToLockLow=*/MAXDWORD,
/*nNumberOfBytesToLockHigh=*/MAXDWORD, &overlapped);
if (!result)
return GetLastFileError();
return FILE_OK;
}
File::Error File::Unlock() {
DCHECK(IsValid());
SCOPED_FILE_TRACE("Unlock");
OVERLAPPED overlapped = {};
BOOL result =
UnlockFileEx(file_.Get(), /*dwReserved=*/0,
/*nNumberOfBytesToLockLow=*/MAXDWORD,
/*nNumberOfBytesToLockHigh=*/MAXDWORD, &overlapped);
if (!result)
return GetLastFileError();
return FILE_OK;
}
File File::Duplicate() const {
if (!IsValid())
return File();
SCOPED_FILE_TRACE("Duplicate");
HANDLE other_handle = nullptr;
if (!::DuplicateHandle(GetCurrentProcess(), // hSourceProcessHandle
GetPlatformFile(),
GetCurrentProcess(), // hTargetProcessHandle
&other_handle,
0, // dwDesiredAccess ignored due to SAME_ACCESS
FALSE, // !bInheritHandle
DUPLICATE_SAME_ACCESS)) {
return File(GetLastFileError());
}
return File(ScopedPlatformFile(other_handle), async());
}
bool File::DeleteOnClose(bool delete_on_close) {
FILE_DISPOSITION_INFO disposition = {delete_on_close};
return ::SetFileInformationByHandle(GetPlatformFile(), FileDispositionInfo,
&disposition, sizeof(disposition)) != 0;
}
// Static.
File::Error File::OSErrorToFileError(DWORD last_error) {
switch (last_error) {
case ERROR_SHARING_VIOLATION:
return FILE_ERROR_IN_USE;
case ERROR_ALREADY_EXISTS:
case ERROR_FILE_EXISTS:
return FILE_ERROR_EXISTS;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
return FILE_ERROR_NOT_FOUND;
case ERROR_ACCESS_DENIED:
return FILE_ERROR_ACCESS_DENIED;
case ERROR_TOO_MANY_OPEN_FILES:
return FILE_ERROR_TOO_MANY_OPENED;
case ERROR_OUTOFMEMORY:
case ERROR_NOT_ENOUGH_MEMORY:
return FILE_ERROR_NO_MEMORY;
case ERROR_HANDLE_DISK_FULL:
case ERROR_DISK_FULL:
case ERROR_DISK_RESOURCES_EXHAUSTED:
return FILE_ERROR_NO_SPACE;
case ERROR_USER_MAPPED_FILE:
return FILE_ERROR_INVALID_OPERATION;
case ERROR_NOT_READY:
case ERROR_SECTOR_NOT_FOUND:
case ERROR_DEV_NOT_EXIST:
case ERROR_IO_DEVICE:
case ERROR_FILE_CORRUPT:
case ERROR_DISK_CORRUPT:
return FILE_ERROR_IO;
default:
UmaHistogramSparse("PlatformFile.UnknownErrors.Windows", last_error);
// This function should only be called for errors.
DCHECK_NE(static_cast<DWORD>(ERROR_SUCCESS), last_error);
return FILE_ERROR_FAILED;
}
}
void File::DoInitialize(const FilePath& path, uint32_t flags) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(!IsValid());
DWORD disposition = 0;
if (flags & FLAG_OPEN)
disposition = OPEN_EXISTING;
if (flags & FLAG_CREATE) {
DCHECK(!disposition);
disposition = CREATE_NEW;
}
if (flags & FLAG_OPEN_ALWAYS) {
DCHECK(!disposition);
disposition = OPEN_ALWAYS;
}
if (flags & FLAG_CREATE_ALWAYS) {
DCHECK(!disposition);
DCHECK(flags & FLAG_WRITE);
disposition = CREATE_ALWAYS;
}
if (flags & FLAG_OPEN_TRUNCATED) {
DCHECK(!disposition);
DCHECK(flags & FLAG_WRITE);
disposition = TRUNCATE_EXISTING;
}
if (!disposition) {
::SetLastError(ERROR_INVALID_PARAMETER);
error_details_ = FILE_ERROR_FAILED;
NOTREACHED();
return;
}
DWORD access = 0;
if (flags & FLAG_WRITE)
access = GENERIC_WRITE;
if (flags & FLAG_APPEND) {
DCHECK(!access);
access = FILE_APPEND_DATA;
}
if (flags & FLAG_READ)
access |= GENERIC_READ;
if (flags & FLAG_WRITE_ATTRIBUTES)
access |= FILE_WRITE_ATTRIBUTES;
if (flags & FLAG_EXECUTE)
access |= GENERIC_EXECUTE;
if (flags & FLAG_CAN_DELETE_ON_CLOSE)
access |= DELETE;
DWORD sharing = (flags & FLAG_EXCLUSIVE_READ) ? 0 : FILE_SHARE_READ;
if (!(flags & FLAG_EXCLUSIVE_WRITE))
sharing |= FILE_SHARE_WRITE;
if (flags & FLAG_SHARE_DELETE)
sharing |= FILE_SHARE_DELETE;
DWORD create_flags = 0;
if (flags & FLAG_ASYNC)
create_flags |= FILE_FLAG_OVERLAPPED;
if (flags & FLAG_TEMPORARY)
create_flags |= FILE_ATTRIBUTE_TEMPORARY;
if (flags & FLAG_HIDDEN)
create_flags |= FILE_ATTRIBUTE_HIDDEN;
if (flags & FLAG_DELETE_ON_CLOSE)
create_flags |= FILE_FLAG_DELETE_ON_CLOSE;
if (flags & FLAG_BACKUP_SEMANTICS)
create_flags |= FILE_FLAG_BACKUP_SEMANTICS;
if (flags & FLAG_SEQUENTIAL_SCAN)
create_flags |= FILE_FLAG_SEQUENTIAL_SCAN;
file_.Set(CreateFile(path.value().c_str(), access, sharing, NULL, disposition,
create_flags, NULL));
if (file_.IsValid()) {
error_details_ = FILE_OK;
async_ = ((flags & FLAG_ASYNC) == FLAG_ASYNC);
if (flags & (FLAG_OPEN_ALWAYS))
created_ = (ERROR_ALREADY_EXISTS != GetLastError());
else if (flags & (FLAG_CREATE_ALWAYS | FLAG_CREATE))
created_ = true;
} else {
error_details_ = GetLastFileError();
}
}
bool File::Flush() {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(IsValid());
SCOPED_FILE_TRACE("Flush");
// On Windows 8 and above, FlushFileBuffers is guaranteed to flush the storage
// device's internal buffers (if they exist) before returning.
// https://blogs.msdn.microsoft.com/oldnewthing/20170510-00/?p=95505
return ::FlushFileBuffers(file_.Get()) != FALSE;
}
void File::SetPlatformFile(PlatformFile file) {
file_.Set(file);
}
// static
File::Error File::GetLastFileError() {
return File::OSErrorToFileError(GetLastError());
}
} // namespace base

View file

@ -0,0 +1,303 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/important_file_writer.h"
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/critical_closure.h"
#include "base/debug/alias.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task_runner.h"
#include "base/task_runner_util.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
namespace base {
namespace {
constexpr auto kDefaultCommitInterval = TimeDelta::FromSeconds(10);
// This enum is used to define the buckets for an enumerated UMA histogram.
// Hence,
// (a) existing enumerated constants should never be deleted or reordered, and
// (b) new constants should only be appended at the end of the enumeration.
enum TempFileFailure {
FAILED_CREATING,
FAILED_OPENING,
FAILED_CLOSING, // Unused.
FAILED_WRITING,
FAILED_RENAMING,
FAILED_FLUSHING,
TEMP_FILE_FAILURE_MAX
};
// Helper function to write samples to a histogram with a dynamically assigned
// histogram name. Works with different error code types convertible to int
// which is the actual argument type of UmaHistogramExactLinear.
template <typename SampleType>
void UmaHistogramExactLinearWithSuffix(const char* histogram_name,
StringPiece histogram_suffix,
SampleType add_sample,
SampleType max_sample) {
static_assert(std::is_convertible<SampleType, int>::value,
"SampleType should be convertible to int");
DCHECK(histogram_name);
std::string histogram_full_name(histogram_name);
if (!histogram_suffix.empty()) {
histogram_full_name.append(".");
histogram_full_name.append(histogram_suffix.data(),
histogram_suffix.length());
}
UmaHistogramExactLinear(histogram_full_name, static_cast<int>(add_sample),
static_cast<int>(max_sample));
}
void LogFailure(const FilePath& path,
StringPiece histogram_suffix,
TempFileFailure failure_code,
StringPiece message) {
UmaHistogramExactLinearWithSuffix("ImportantFile.TempFileFailures",
histogram_suffix, failure_code,
TEMP_FILE_FAILURE_MAX);
DPLOG(WARNING) << "temp file failure: " << path.value() << " : " << message;
}
// Helper function to call WriteFileAtomically() with a
// std::unique_ptr<std::string>.
void WriteScopedStringToFileAtomically(
const FilePath& path,
std::unique_ptr<std::string> data,
OnceClosure before_write_callback,
OnceCallback<void(bool success)> after_write_callback,
const std::string& histogram_suffix) {
if (!before_write_callback.is_null())
std::move(before_write_callback).Run();
bool result =
ImportantFileWriter::WriteFileAtomically(path, *data, histogram_suffix);
if (!after_write_callback.is_null())
std::move(after_write_callback).Run(result);
}
void DeleteTmpFile(const FilePath& tmp_file_path,
StringPiece histogram_suffix) {
if (!DeleteFile(tmp_file_path, false)) {
UmaHistogramExactLinearWithSuffix(
"ImportantFile.FileDeleteError", histogram_suffix,
-base::File::GetLastFileError(), -base::File::FILE_ERROR_MAX);
}
}
} // namespace
// static
bool ImportantFileWriter::WriteFileAtomically(const FilePath& path,
StringPiece data,
StringPiece histogram_suffix) {
#if defined(OS_WIN) && DCHECK_IS_ON()
// In https://crbug.com/920174, we have cases where CreateTemporaryFileInDir
// hits a DCHECK because creation fails with no indication why. Pull the path
// onto the stack so that we can see if it is malformed in some odd way.
wchar_t path_copy[MAX_PATH];
base::wcslcpy(path_copy, path.value().c_str(), base::size(path_copy));
base::debug::Alias(path_copy);
#endif // defined(OS_WIN) && DCHECK_IS_ON()
#if defined(OS_CHROMEOS)
// On Chrome OS, chrome gets killed when it cannot finish shutdown quickly,
// and this function seems to be one of the slowest shutdown steps.
// Include some info to the report for investigation. crbug.com/418627
// TODO(hashimoto): Remove this.
struct {
size_t data_size;
char path[128];
} file_info;
file_info.data_size = data.size();
strlcpy(file_info.path, path.value().c_str(), base::size(file_info.path));
debug::Alias(&file_info);
#endif
// Write the data to a temp file then rename to avoid data loss if we crash
// while writing the file. Ensure that the temp file is on the same volume
// as target file, so it can be moved in one step, and that the temp file
// is securely created.
FilePath tmp_file_path;
if (!CreateTemporaryFileInDir(path.DirName(), &tmp_file_path)) {
const auto last_file_error = base::File::GetLastFileError();
UmaHistogramExactLinearWithSuffix("ImportantFile.FileCreateError",
histogram_suffix, -last_file_error,
-base::File::FILE_ERROR_MAX);
LogFailure(path, histogram_suffix, FAILED_CREATING,
"could not create temporary file");
return false;
}
File tmp_file(tmp_file_path, File::FLAG_OPEN | File::FLAG_WRITE);
if (!tmp_file.IsValid()) {
UmaHistogramExactLinearWithSuffix(
"ImportantFile.FileOpenError", histogram_suffix,
-tmp_file.error_details(), -base::File::FILE_ERROR_MAX);
LogFailure(path, histogram_suffix, FAILED_OPENING,
"could not open temporary file");
DeleteFile(tmp_file_path, false);
return false;
}
// If this fails in the wild, something really bad is going on.
const int data_length = checked_cast<int32_t>(data.length());
int bytes_written = tmp_file.Write(0, data.data(), data_length);
if (bytes_written < data_length) {
UmaHistogramExactLinearWithSuffix(
"ImportantFile.FileWriteError", histogram_suffix,
-base::File::GetLastFileError(), -base::File::FILE_ERROR_MAX);
}
bool flush_success = tmp_file.Flush();
tmp_file.Close();
if (bytes_written < data_length) {
LogFailure(path, histogram_suffix, FAILED_WRITING,
"error writing, bytes_written=" + NumberToString(bytes_written));
DeleteTmpFile(tmp_file_path, histogram_suffix);
return false;
}
if (!flush_success) {
LogFailure(path, histogram_suffix, FAILED_FLUSHING, "error flushing");
DeleteTmpFile(tmp_file_path, histogram_suffix);
return false;
}
base::File::Error replace_file_error = base::File::FILE_OK;
if (!ReplaceFile(tmp_file_path, path, &replace_file_error)) {
UmaHistogramExactLinearWithSuffix("ImportantFile.FileRenameError",
histogram_suffix, -replace_file_error,
-base::File::FILE_ERROR_MAX);
LogFailure(path, histogram_suffix, FAILED_RENAMING,
"could not rename temporary file");
DeleteTmpFile(tmp_file_path, histogram_suffix);
return false;
}
return true;
}
ImportantFileWriter::ImportantFileWriter(
const FilePath& path,
scoped_refptr<SequencedTaskRunner> task_runner,
const char* histogram_suffix)
: ImportantFileWriter(path,
std::move(task_runner),
kDefaultCommitInterval,
histogram_suffix) {}
ImportantFileWriter::ImportantFileWriter(
const FilePath& path,
scoped_refptr<SequencedTaskRunner> task_runner,
TimeDelta interval,
const char* histogram_suffix)
: path_(path),
task_runner_(std::move(task_runner)),
serializer_(nullptr),
commit_interval_(interval),
histogram_suffix_(histogram_suffix ? histogram_suffix : "") {
DCHECK(task_runner_);
}
ImportantFileWriter::~ImportantFileWriter() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// We're usually a member variable of some other object, which also tends
// to be our serializer. It may not be safe to call back to the parent object
// being destructed.
DCHECK(!HasPendingWrite());
}
bool ImportantFileWriter::HasPendingWrite() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return timer().IsRunning();
}
void ImportantFileWriter::WriteNow(std::unique_ptr<std::string> data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsValueInRangeForNumericType<int32_t>(data->length())) {
NOTREACHED();
return;
}
RepeatingClosure task = AdaptCallbackForRepeating(
BindOnce(&WriteScopedStringToFileAtomically, path_, std::move(data),
std::move(before_next_write_callback_),
std::move(after_next_write_callback_), histogram_suffix_));
if (!task_runner_->PostTask(FROM_HERE, MakeCriticalClosure(task))) {
// Posting the task to background message loop is not expected
// to fail, but if it does, avoid losing data and just hit the disk
// on the current thread.
NOTREACHED();
std::move(task).Run();
}
ClearPendingWrite();
}
void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(serializer);
serializer_ = serializer;
if (!timer().IsRunning()) {
timer().Start(
FROM_HERE, commit_interval_,
BindOnce(&ImportantFileWriter::DoScheduledWrite, Unretained(this)));
}
}
void ImportantFileWriter::DoScheduledWrite() {
DCHECK(serializer_);
std::unique_ptr<std::string> data(new std::string);
if (serializer_->SerializeData(data.get())) {
WriteNow(std::move(data));
} else {
DLOG(WARNING) << "failed to serialize data to be saved in "
<< path_.value();
}
ClearPendingWrite();
}
void ImportantFileWriter::RegisterOnNextWriteCallbacks(
OnceClosure before_next_write_callback,
OnceCallback<void(bool success)> after_next_write_callback) {
before_next_write_callback_ = std::move(before_next_write_callback);
after_next_write_callback_ = std::move(after_next_write_callback);
}
void ImportantFileWriter::ClearPendingWrite() {
timer().Stop();
serializer_ = nullptr;
}
void ImportantFileWriter::SetTimerForTesting(OneShotTimer* timer_override) {
timer_override_ = timer_override;
}
} // namespace base

View file

@ -0,0 +1,161 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_IMPORTANT_FILE_WRITER_H_
#define BASE_FILES_IMPORTANT_FILE_WRITER_H_
#include <string>
#include "base/base_export.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_checker.h"
#include "base/strings/string_piece.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
namespace base {
class SequencedTaskRunner;
// Helper for atomically writing a file to ensure that it won't be corrupted by
// *application* crash during write (implemented as create, flush, rename).
//
// As an added benefit, ImportantFileWriter makes it less likely that the file
// is corrupted by *system* crash, though even if the ImportantFileWriter call
// has already returned at the time of the crash it is not specified which
// version of the file (old or new) is preserved. And depending on system
// configuration (hardware and software) a significant likelihood of file
// corruption may remain, thus using ImportantFileWriter is not a valid
// substitute for file integrity checks and recovery codepaths for malformed
// files.
//
// Also note that ImportantFileWriter can be *really* slow (cf. File::Flush()
// for details) and thus please don't block shutdown on ImportantFileWriter.
class BASE_EXPORT ImportantFileWriter {
public:
// Used by ScheduleSave to lazily provide the data to be saved. Allows us
// to also batch data serializations.
class BASE_EXPORT DataSerializer {
public:
// Should put serialized string in |data| and return true on successful
// serialization. Will be called on the same thread on which
// ImportantFileWriter has been created.
virtual bool SerializeData(std::string* data) = 0;
protected:
virtual ~DataSerializer() = default;
};
// Save |data| to |path| in an atomic manner. Blocks and writes data on the
// current thread. Does not guarantee file integrity across system crash (see
// the class comment above).
static bool WriteFileAtomically(const FilePath& path,
StringPiece data,
StringPiece histogram_suffix = StringPiece());
// Initialize the writer.
// |path| is the name of file to write.
// |task_runner| is the SequencedTaskRunner instance where on which we will
// execute file I/O operations.
// All non-const methods, ctor and dtor must be called on the same thread.
ImportantFileWriter(const FilePath& path,
scoped_refptr<SequencedTaskRunner> task_runner,
const char* histogram_suffix = nullptr);
// Same as above, but with a custom commit interval.
ImportantFileWriter(const FilePath& path,
scoped_refptr<SequencedTaskRunner> task_runner,
TimeDelta interval,
const char* histogram_suffix = nullptr);
// You have to ensure that there are no pending writes at the moment
// of destruction.
~ImportantFileWriter();
const FilePath& path() const { return path_; }
// Returns true if there is a scheduled write pending which has not yet
// been started.
bool HasPendingWrite() const;
// Save |data| to target filename. Does not block. If there is a pending write
// scheduled by ScheduleWrite(), it is cancelled.
void WriteNow(std::unique_ptr<std::string> data);
// Schedule a save to target filename. Data will be serialized and saved
// to disk after the commit interval. If another ScheduleWrite is issued
// before that, only one serialization and write to disk will happen, and
// the most recent |serializer| will be used. This operation does not block.
// |serializer| should remain valid through the lifetime of
// ImportantFileWriter.
void ScheduleWrite(DataSerializer* serializer);
// Serialize data pending to be saved and execute write on backend thread.
void DoScheduledWrite();
// Registers |before_next_write_callback| and |after_next_write_callback| to
// be synchronously invoked from WriteFileAtomically() before its next write
// and after its next write, respectively. The boolean passed to
// |after_next_write_callback| indicates whether the write was successful.
// Both callbacks must be thread safe as they will be called on |task_runner_|
// and may be called during Chrome shutdown.
// If called more than once before a write is scheduled on |task_runner|, the
// latest callbacks clobber the others.
void RegisterOnNextWriteCallbacks(
OnceClosure before_next_write_callback,
OnceCallback<void(bool success)> after_next_write_callback);
TimeDelta commit_interval() const {
return commit_interval_;
}
// Overrides the timer to use for scheduling writes with |timer_override|.
void SetTimerForTesting(OneShotTimer* timer_override);
private:
const OneShotTimer& timer() const {
return timer_override_ ? *timer_override_ : timer_;
}
OneShotTimer& timer() { return timer_override_ ? *timer_override_ : timer_; }
void ClearPendingWrite();
// Invoked synchronously on the next write event.
OnceClosure before_next_write_callback_;
OnceCallback<void(bool success)> after_next_write_callback_;
// Path being written to.
const FilePath path_;
// TaskRunner for the thread on which file I/O can be done.
const scoped_refptr<SequencedTaskRunner> task_runner_;
// Timer used to schedule commit after ScheduleWrite.
OneShotTimer timer_;
// An override for |timer_| used for testing.
OneShotTimer* timer_override_ = nullptr;
// Serializer which will provide the data to be saved.
DataSerializer* serializer_;
// Time delta after which scheduled data will be written to disk.
const TimeDelta commit_interval_;
// Custom histogram suffix.
const std::string histogram_suffix_;
SEQUENCE_CHECKER(sequence_checker_);
WeakPtrFactory<ImportantFileWriter> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(ImportantFileWriter);
};
} // namespace base
#endif // BASE_FILES_IMPORTANT_FILE_WRITER_H_

View file

@ -0,0 +1,144 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/memory_mapped_file.h"
#include <utility>
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/numerics/safe_math.h"
#include "base/system/sys_info.h"
#include "build/build_config.h"
namespace base {
const MemoryMappedFile::Region MemoryMappedFile::Region::kWholeFile = {0, 0};
bool MemoryMappedFile::Region::operator==(
const MemoryMappedFile::Region& other) const {
return other.offset == offset && other.size == size;
}
bool MemoryMappedFile::Region::operator!=(
const MemoryMappedFile::Region& other) const {
return other.offset != offset || other.size != size;
}
MemoryMappedFile::~MemoryMappedFile() {
CloseHandles();
}
#if !defined(OS_NACL)
bool MemoryMappedFile::Initialize(const FilePath& file_name, Access access) {
if (IsValid())
return false;
uint32_t flags = 0;
switch (access) {
case READ_ONLY:
flags = File::FLAG_OPEN | File::FLAG_READ;
break;
case READ_WRITE:
flags = File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE;
break;
case READ_WRITE_EXTEND:
// Can't open with "extend" because no maximum size is known.
NOTREACHED();
break;
#if defined(OS_WIN)
case READ_CODE_IMAGE:
flags |= File::FLAG_OPEN | File::FLAG_READ | File::FLAG_EXCLUSIVE_WRITE |
File::FLAG_EXECUTE;
break;
#endif
}
file_.Initialize(file_name, flags);
if (!file_.IsValid()) {
DLOG(ERROR) << "Couldn't open " << file_name.AsUTF8Unsafe();
return false;
}
if (!MapFileRegionToMemory(Region::kWholeFile, access)) {
CloseHandles();
return false;
}
return true;
}
bool MemoryMappedFile::Initialize(File file, Access access) {
DCHECK_NE(READ_WRITE_EXTEND, access);
return Initialize(std::move(file), Region::kWholeFile, access);
}
bool MemoryMappedFile::Initialize(File file,
const Region& region,
Access access) {
switch (access) {
case READ_WRITE_EXTEND:
DCHECK(Region::kWholeFile != region);
{
CheckedNumeric<int64_t> region_end(region.offset);
region_end += region.size;
if (!region_end.IsValid()) {
DLOG(ERROR) << "Region bounds exceed maximum for base::File.";
return false;
}
}
FALLTHROUGH;
case READ_ONLY:
case READ_WRITE:
// Ensure that the region values are valid.
if (region.offset < 0) {
DLOG(ERROR) << "Region bounds are not valid.";
return false;
}
break;
#if defined(OS_WIN)
case READ_CODE_IMAGE:
// Can't open with "READ_CODE_IMAGE", not supported outside Windows
// or with a |region|.
NOTREACHED();
break;
#endif
}
if (IsValid())
return false;
if (region != Region::kWholeFile)
DCHECK_GE(region.offset, 0);
file_ = std::move(file);
if (!MapFileRegionToMemory(region, access)) {
CloseHandles();
return false;
}
return true;
}
bool MemoryMappedFile::IsValid() const {
return data_ != nullptr;
}
// static
void MemoryMappedFile::CalculateVMAlignedBoundaries(int64_t start,
size_t size,
int64_t* aligned_start,
size_t* aligned_size,
int32_t* offset) {
// Sadly, on Windows, the mmap alignment is not just equal to the page size.
auto mask = SysInfo::VMAllocationGranularity() - 1;
DCHECK(IsValueInRangeForNumericType<int32_t>(mask));
*offset = start & mask;
*aligned_start = start & ~mask;
*aligned_size = (size + *offset + mask) & ~mask;
}
#endif // !defined(OS_NACL)
} // namespace base

View file

@ -0,0 +1,152 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_MEMORY_MAPPED_FILE_H_
#define BASE_FILES_MEMORY_MAPPED_FILE_H_
#include <stddef.h>
#include <stdint.h>
#include "base/base_export.h"
#include "base/files/file.h"
#include "base/macros.h"
#include "build/build_config.h"
#if defined(OS_WIN)
#include <windows.h>
#endif
namespace base {
class FilePath;
class BASE_EXPORT MemoryMappedFile {
public:
enum Access {
// Mapping a file into memory effectively allows for file I/O on any thread.
// The accessing thread could be paused while data from the file is paged
// into memory. Worse, a corrupted filesystem could cause a SEGV within the
// program instead of just an I/O error.
READ_ONLY,
// This provides read/write access to a file and must be used with care of
// the additional subtleties involved in doing so. Though the OS will do
// the writing of data on its own time, too many dirty pages can cause
// the OS to pause the thread while it writes them out. The pause can
// be as much as 1s on some systems.
READ_WRITE,
// This provides read/write access but with the ability to write beyond
// the end of the existing file up to a maximum size specified as the
// "region". Depending on the OS, the file may or may not be immediately
// extended to the maximum size though it won't be loaded in RAM until
// needed. Note, however, that the maximum size will still be reserved
// in the process address space.
READ_WRITE_EXTEND,
#if defined(OS_WIN)
// This provides read access, but as executable code used for prefetching
// DLLs into RAM to avoid inefficient hard fault patterns such as during
// process startup. The accessing thread could be paused while data from
// the file is read into memory (if needed).
READ_CODE_IMAGE,
#endif
};
// The default constructor sets all members to invalid/null values.
MemoryMappedFile();
~MemoryMappedFile();
// Used to hold information about a region [offset + size] of a file.
struct BASE_EXPORT Region {
static const Region kWholeFile;
bool operator==(const Region& other) const;
bool operator!=(const Region& other) const;
// Start of the region (measured in bytes from the beginning of the file).
int64_t offset;
// Length of the region in bytes.
size_t size;
};
// Opens an existing file and maps it into memory. |access| can be read-only
// or read/write but not read/write+extend. If this object already points
// to a valid memory mapped file then this method will fail and return
// false. If it cannot open the file, the file does not exist, or the
// memory mapping fails, it will return false.
WARN_UNUSED_RESULT bool Initialize(const FilePath& file_name, Access access);
WARN_UNUSED_RESULT bool Initialize(const FilePath& file_name) {
return Initialize(file_name, READ_ONLY);
}
// As above, but works with an already-opened file. |access| can be read-only
// or read/write but not read/write+extend. MemoryMappedFile takes ownership
// of |file| and closes it when done. |file| must have been opened with
// permissions suitable for |access|. If the memory mapping fails, it will
// return false.
WARN_UNUSED_RESULT bool Initialize(File file, Access access);
WARN_UNUSED_RESULT bool Initialize(File file) {
return Initialize(std::move(file), READ_ONLY);
}
// As above, but works with a region of an already-opened file. |access|
// must not be READ_CODE_IMAGE. If READ_WRITE_EXTEND is specified then
// |region| provides the maximum size of the file. If the memory mapping
// fails, it return false.
WARN_UNUSED_RESULT bool Initialize(File file,
const Region& region,
Access access);
WARN_UNUSED_RESULT bool Initialize(File file, const Region& region) {
return Initialize(std::move(file), region, READ_ONLY);
}
const uint8_t* data() const { return data_; }
uint8_t* data() { return data_; }
size_t length() const { return length_; }
// Is file_ a valid file handle that points to an open, memory mapped file?
bool IsValid() const;
private:
// Given the arbitrarily aligned memory region [start, size], returns the
// boundaries of the region aligned to the granularity specified by the OS,
// (a page on Linux, ~32k on Windows) as follows:
// - |aligned_start| is page aligned and <= |start|.
// - |aligned_size| is a multiple of the VM granularity and >= |size|.
// - |offset| is the displacement of |start| w.r.t |aligned_start|.
static void CalculateVMAlignedBoundaries(int64_t start,
size_t size,
int64_t* aligned_start,
size_t* aligned_size,
int32_t* offset);
#if defined(OS_WIN)
// Maps the executable file to memory, set |data_| to that memory address.
// Return true on success.
bool MapImageToMemory(Access access);
#endif
// Map the file to memory, set data_ to that memory address. Return true on
// success, false on any kind of failure. This is a helper for Initialize().
bool MapFileRegionToMemory(const Region& region, Access access);
// Closes all open handles.
void CloseHandles();
File file_;
uint8_t* data_;
size_t length_;
#if defined(OS_WIN)
win::ScopedHandle file_mapping_;
#endif
DISALLOW_COPY_AND_ASSIGN(MemoryMappedFile);
};
} // namespace base
#endif // BASE_FILES_MEMORY_MAPPED_FILE_H_

View file

@ -0,0 +1,111 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/memory_mapped_file.h"
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"
namespace base {
MemoryMappedFile::MemoryMappedFile() : data_(nullptr), length_(0) {}
#if !defined(OS_NACL)
bool MemoryMappedFile::MapFileRegionToMemory(
const MemoryMappedFile::Region& region,
Access access) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
off_t map_start = 0;
size_t map_size = 0;
int32_t data_offset = 0;
if (region == MemoryMappedFile::Region::kWholeFile) {
int64_t file_len = file_.GetLength();
if (file_len < 0) {
DPLOG(ERROR) << "fstat " << file_.GetPlatformFile();
return false;
}
if (!IsValueInRangeForNumericType<size_t>(file_len))
return false;
map_size = static_cast<size_t>(file_len);
length_ = map_size;
} else {
// The region can be arbitrarily aligned. mmap, instead, requires both the
// start and size to be page-aligned. Hence, we map here the page-aligned
// outer region [|aligned_start|, |aligned_start| + |size|] which contains
// |region| and then add up the |data_offset| displacement.
int64_t aligned_start = 0;
size_t aligned_size = 0;
CalculateVMAlignedBoundaries(region.offset,
region.size,
&aligned_start,
&aligned_size,
&data_offset);
// Ensure that the casts in the mmap call below are sane.
if (aligned_start < 0 ||
!IsValueInRangeForNumericType<off_t>(aligned_start)) {
DLOG(ERROR) << "Region bounds are not valid for mmap";
return false;
}
map_start = static_cast<off_t>(aligned_start);
map_size = aligned_size;
length_ = region.size;
}
int flags = 0;
switch (access) {
case READ_ONLY:
flags |= PROT_READ;
break;
case READ_WRITE:
flags |= PROT_READ | PROT_WRITE;
break;
case READ_WRITE_EXTEND:
flags |= PROT_READ | PROT_WRITE;
if (!AllocateFileRegion(&file_, region.offset, region.size))
return false;
break;
}
data_ = static_cast<uint8_t*>(mmap(nullptr, map_size, flags, MAP_SHARED,
file_.GetPlatformFile(), map_start));
if (data_ == MAP_FAILED) {
DPLOG(ERROR) << "mmap " << file_.GetPlatformFile();
return false;
}
data_ += data_offset;
return true;
}
#endif
void MemoryMappedFile::CloseHandles() {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
if (data_ != nullptr)
munmap(data_, length_);
file_.Close();
data_ = nullptr;
length_ = 0;
}
} // namespace base

View file

@ -0,0 +1,144 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/memory_mapped_file.h"
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include "base/files/file_path.h"
#include "base/strings/string16.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/win/pe_image.h"
#include <windows.h>
#include <winnt.h> // NOLINT(build/include_order)
namespace base {
MemoryMappedFile::MemoryMappedFile() : data_(NULL), length_(0) {
}
bool MemoryMappedFile::MapImageToMemory(Access access) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
// The arguments to the calls of ::CreateFile(), ::CreateFileMapping(), and
// ::MapViewOfFile() need to be self consistent as far as access rights and
// type of mapping or one or more of them will fail in non-obvious ways.
if (!file_.IsValid())
return false;
file_mapping_.Set(::CreateFileMapping(file_.GetPlatformFile(), nullptr,
PAGE_READONLY | SEC_IMAGE_NO_EXECUTE, 0,
0, NULL));
if (!file_mapping_.IsValid())
return false;
data_ = static_cast<uint8_t*>(
::MapViewOfFile(file_mapping_.Get(), FILE_MAP_READ, 0, 0, 0));
if (!data_)
return false;
// We need to know how large the mapped file is in some cases
base::win::PEImage pe_image(data_);
length_ = pe_image.GetNTHeaders()->OptionalHeader.SizeOfImage;
return true;
}
bool MemoryMappedFile::MapFileRegionToMemory(
const MemoryMappedFile::Region& region,
Access access) {
ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
DCHECK(access != READ_CODE_IMAGE || region == Region::kWholeFile);
if (!file_.IsValid())
return false;
int flags = 0;
ULARGE_INTEGER size = {};
switch (access) {
case READ_ONLY:
flags |= PAGE_READONLY;
break;
case READ_WRITE:
flags |= PAGE_READWRITE;
break;
case READ_WRITE_EXTEND:
flags |= PAGE_READWRITE;
size.QuadPart = region.size;
break;
case READ_CODE_IMAGE:
return MapImageToMemory(access);
}
file_mapping_.Set(::CreateFileMapping(file_.GetPlatformFile(), NULL, flags,
size.HighPart, size.LowPart, NULL));
if (!file_mapping_.IsValid())
return false;
LARGE_INTEGER map_start = {};
SIZE_T map_size = 0;
int32_t data_offset = 0;
if (region == MemoryMappedFile::Region::kWholeFile) {
DCHECK_NE(READ_WRITE_EXTEND, access);
int64_t file_len = file_.GetLength();
if (file_len <= 0 || !IsValueInRangeForNumericType<size_t>(file_len))
return false;
length_ = static_cast<size_t>(file_len);
} else {
// The region can be arbitrarily aligned. MapViewOfFile, instead, requires
// that the start address is aligned to the VM granularity (which is
// typically larger than a page size, for instance 32k).
// Also, conversely to POSIX's mmap, the |map_size| doesn't have to be
// aligned and must be less than or equal the mapped file size.
// We map here the outer region [|aligned_start|, |aligned_start+size|]
// which contains |region| and then add up the |data_offset| displacement.
int64_t aligned_start = 0;
size_t ignored = 0U;
CalculateVMAlignedBoundaries(region.offset, region.size, &aligned_start,
&ignored, &data_offset);
int64_t full_map_size = region.size + data_offset;
// Ensure that the casts below in the MapViewOfFile call are sane.
if (aligned_start < 0 || full_map_size < 0 ||
!IsValueInRangeForNumericType<SIZE_T>(
static_cast<uint64_t>(full_map_size))) {
DLOG(ERROR) << "Region bounds are not valid for MapViewOfFile";
return false;
}
map_start.QuadPart = aligned_start;
map_size = static_cast<SIZE_T>(full_map_size);
length_ = region.size;
}
data_ = static_cast<uint8_t*>(
::MapViewOfFile(file_mapping_.Get(),
(flags & PAGE_READONLY) ? FILE_MAP_READ : FILE_MAP_WRITE,
map_start.HighPart, map_start.LowPart, map_size));
if (data_ == NULL)
return false;
data_ += data_offset;
return true;
}
void MemoryMappedFile::CloseHandles() {
if (data_)
::UnmapViewOfFile(data_);
if (file_mapping_.IsValid())
file_mapping_.Close();
if (file_.IsValid())
file_.Close();
data_ = NULL;
length_ = 0;
}
} // namespace base

View file

@ -0,0 +1,43 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_PLATFORM_FILE_H_
#define BASE_FILES_PLATFORM_FILE_H_
#include "base/files/scoped_file.h"
#include "build/build_config.h"
#if defined(OS_WIN)
#include "base/win/scoped_handle.h"
#include "base/win/windows_types.h"
#endif
// This file defines platform-independent types for dealing with
// platform-dependent files. If possible, use the higher-level base::File class
// rather than these primitives.
namespace base {
#if defined(OS_WIN)
using PlatformFile = HANDLE;
using ScopedPlatformFile = ::base::win::ScopedHandle;
// It would be nice to make this constexpr but INVALID_HANDLE_VALUE is a
// ((void*)(-1)) which Clang rejects since reinterpret_cast is technically
// disallowed in constexpr. Visual Studio accepts this, however.
const PlatformFile kInvalidPlatformFile = INVALID_HANDLE_VALUE;
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
using PlatformFile = int;
using ScopedPlatformFile = ::base::ScopedFD;
constexpr PlatformFile kInvalidPlatformFile = -1;
#endif
} // namespace
#endif // BASE_FILES_PLATFORM_FILE_H_

View file

@ -0,0 +1,49 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "build/build_config.h"
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <errno.h>
#include <unistd.h>
#include "base/posix/eintr_wrapper.h"
#endif
namespace base {
namespace internal {
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// static
void ScopedFDCloseTraits::Free(int fd) {
// It's important to crash here.
// There are security implications to not closing a file descriptor
// properly. As file descriptors are "capabilities", keeping them open
// would make the current process keep access to a resource. Much of
// Chrome relies on being able to "drop" such access.
// It's especially problematic on Linux with the setuid sandbox, where
// a single open directory would bypass the entire security model.
int ret = IGNORE_EINTR(close(fd));
#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_FUCHSIA) || \
defined(OS_ANDROID)
// NB: Some file descriptors can return errors from close() e.g. network
// filesystems such as NFS and Linux input devices. On Linux, macOS, and
// Fuchsia's POSIX layer, errors from close other than EBADF do not indicate
// failure to actually close the fd.
if (ret != 0 && errno != EBADF)
ret = 0;
#endif
PCHECK(0 == ret);
}
#endif // OS_POSIX || OS_FUCHSIA
} // namespace internal
} // namespace base

View file

@ -0,0 +1,70 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_SCOPED_FILE_H_
#define BASE_FILES_SCOPED_FILE_H_
#include <stdio.h>
#include <memory>
#include "base/base_export.h"
#include "base/logging.h"
#include "base/scoped_generic.h"
#include "build/build_config.h"
namespace base {
namespace internal {
#if defined(OS_ANDROID)
// Use fdsan on android.
struct BASE_EXPORT ScopedFDCloseTraits : public ScopedGenericOwnershipTracking {
static int InvalidValue() { return -1; }
static void Free(int);
static void Acquire(const ScopedGeneric<int, ScopedFDCloseTraits>&, int);
static void Release(const ScopedGeneric<int, ScopedFDCloseTraits>&, int);
};
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
struct BASE_EXPORT ScopedFDCloseTraits {
static int InvalidValue() {
return -1;
}
static void Free(int fd);
};
#endif
// Functor for |ScopedFILE| (below).
struct ScopedFILECloser {
inline void operator()(FILE* x) const {
if (x)
fclose(x);
}
};
} // namespace internal
// -----------------------------------------------------------------------------
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// A low-level Posix file descriptor closer class. Use this when writing
// platform-specific code, especially that does non-file-like things with the
// FD (like sockets).
//
// If you're writing low-level Windows code, see base/win/scoped_handle.h
// which provides some additional functionality.
//
// If you're writing cross-platform code that deals with actual files, you
// should generally use base::File instead which can be constructed with a
// handle, and in addition to handling ownership, has convenient cross-platform
// file manipulation functions on it.
typedef ScopedGeneric<int, internal::ScopedFDCloseTraits> ScopedFD;
#endif
// Automatically closes |FILE*|s.
typedef std::unique_ptr<FILE, internal::ScopedFILECloser> ScopedFILE;
} // namespace base
#endif // BASE_FILES_SCOPED_FILE_H_

View file

@ -0,0 +1,40 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/scoped_file.h"
#include <stdint.h>
// Copied from <android/fdsan.h>.
// This can go away once this header is included in our copy of the NDK.
extern "C" {
void android_fdsan_exchange_owner_tag(int fd,
uint64_t expected_tag,
uint64_t new_tag)
__attribute__((__weak__));
}
namespace base {
namespace internal {
static uint64_t ScopedFDToTag(const ScopedFD& owner) {
return reinterpret_cast<uint64_t>(&owner);
}
// static
void ScopedFDCloseTraits::Acquire(const ScopedFD& owner, int fd) {
if (android_fdsan_exchange_owner_tag) {
android_fdsan_exchange_owner_tag(fd, 0, ScopedFDToTag(owner));
}
}
// static
void ScopedFDCloseTraits::Release(const ScopedFD& owner, int fd) {
if (android_fdsan_exchange_owner_tag) {
android_fdsan_exchange_owner_tag(fd, ScopedFDToTag(owner), 0);
}
}
} // namespace internal
} // namespace base

View file

@ -0,0 +1,106 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/scoped_temp_dir.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/macros.h"
namespace base {
namespace {
constexpr FilePath::CharType kScopedDirPrefix[] =
FILE_PATH_LITERAL("scoped_dir");
} // namespace
ScopedTempDir::ScopedTempDir() = default;
ScopedTempDir::ScopedTempDir(ScopedTempDir&& other) noexcept
: path_(other.Take()) {}
ScopedTempDir& ScopedTempDir::operator=(ScopedTempDir&& other) {
if (!path_.empty() && !Delete())
DLOG(WARNING) << "Could not delete temp dir in operator=().";
path_ = other.Take();
return *this;
}
ScopedTempDir::~ScopedTempDir() {
if (!path_.empty() && !Delete())
DLOG(WARNING) << "Could not delete temp dir in dtor.";
}
bool ScopedTempDir::CreateUniqueTempDir() {
if (!path_.empty())
return false;
// This "scoped_dir" prefix is only used on Windows and serves as a template
// for the unique name.
if (!CreateNewTempDirectory(kScopedDirPrefix, &path_))
return false;
return true;
}
bool ScopedTempDir::CreateUniqueTempDirUnderPath(const FilePath& base_path) {
if (!path_.empty())
return false;
// If |base_path| does not exist, create it.
if (!CreateDirectory(base_path))
return false;
// Create a new, uniquely named directory under |base_path|.
if (!CreateTemporaryDirInDir(base_path, kScopedDirPrefix, &path_))
return false;
return true;
}
bool ScopedTempDir::Set(const FilePath& path) {
if (!path_.empty())
return false;
if (!DirectoryExists(path) && !CreateDirectory(path))
return false;
path_ = path;
return true;
}
bool ScopedTempDir::Delete() {
if (path_.empty())
return false;
bool ret = DeleteFileRecursively(path_);
if (ret) {
// We only clear the path if deleted the directory.
path_.clear();
}
return ret;
}
FilePath ScopedTempDir::Take() {
return std::exchange(path_, FilePath());
}
const FilePath& ScopedTempDir::GetPath() const {
DCHECK(!path_.empty()) << "Did you call CreateUniqueTempDir* before?";
return path_;
}
bool ScopedTempDir::IsValid() const {
return !path_.empty() && DirectoryExists(path_);
}
// static
const FilePath::CharType* ScopedTempDir::GetTempDirPrefix() {
return kScopedDirPrefix;
}
} // namespace base

View file

@ -0,0 +1,72 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FILES_SCOPED_TEMP_DIR_H_
#define BASE_FILES_SCOPED_TEMP_DIR_H_
// An object representing a temporary / scratch directory that should be
// cleaned up (recursively) when this object goes out of scope. Since deletion
// occurs during the destructor, no further error handling is possible if the
// directory fails to be deleted. As a result, deletion is not guaranteed by
// this class. (However note that, whenever possible, by default
// CreateUniqueTempDir creates the directory in a location that is
// automatically cleaned up on reboot, or at other appropriate times.)
//
// Multiple calls to the methods which establish a temporary directory
// (CreateUniqueTempDir, CreateUniqueTempDirUnderPath, and Set) must have
// intervening calls to Delete or Take, or the calls will fail.
#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
namespace base {
class BASE_EXPORT ScopedTempDir {
public:
// No directory is owned/created initially.
ScopedTempDir();
ScopedTempDir(ScopedTempDir&&) noexcept;
ScopedTempDir& operator=(ScopedTempDir&&);
// Recursively delete path.
~ScopedTempDir();
// Creates a unique directory in TempPath, and takes ownership of it.
// See file_util::CreateNewTemporaryDirectory.
bool CreateUniqueTempDir() WARN_UNUSED_RESULT;
// Creates a unique directory under a given path, and takes ownership of it.
bool CreateUniqueTempDirUnderPath(const FilePath& path) WARN_UNUSED_RESULT;
// Takes ownership of directory at |path|, creating it if necessary.
// Don't call multiple times unless Take() has been called first.
bool Set(const FilePath& path) WARN_UNUSED_RESULT;
// Deletes the temporary directory wrapped by this object.
bool Delete() WARN_UNUSED_RESULT;
// Caller takes ownership of the temporary directory so it won't be destroyed
// when this object goes out of scope.
FilePath Take();
// Returns the path to the created directory. Call one of the
// CreateUniqueTempDir* methods before getting the path.
const FilePath& GetPath() const;
// Returns true if path_ is non-empty and exists.
bool IsValid() const;
// Returns the prefix used for temp directory names generated by
// ScopedTempDirs.
static const FilePath::CharType* GetTempDirPrefix();
private:
FilePath path_;
};
} // namespace base
#endif // BASE_FILES_SCOPED_TEMP_DIR_H_