| // 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 <sys/stat.h> | |
| #include <unistd.h> | |
| #include "base/logging.h" | |
| #include "base/posix/eintr_wrapper.h" | |
| #include "base/strings/utf_string_conversions.h" | |
| #include "util/build_config.h" | |
| 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 { | |
| // Some systems don'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_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)); | |
| } | |
| #if !defined(OS_FUCHSIA) | |
| File::Error CallFcntlFlock(PlatformFile file, bool do_lock) { | |
| struct flock lock; | |
| lock.l_type = do_lock ? F_WRLCK : F_UNLCK; | |
| 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_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; | |
| } | |
| File::Error CallFcntlFlock(PlatformFile file, bool do_lock) { | |
| NOTIMPLEMENTED(); // NaCl doesn't implement flock struct. | |
| return File::FILE_ERROR_INVALID_OPERATION; | |
| } | |
| #endif // defined(OS_AIX) | |
| } // namespace | |
| void File::Info::FromStat(const struct stat& stat_info) { | |
| is_directory = S_ISDIR(stat_info.st_mode); | |
| is_symbolic_link = S_ISLNK(stat_info.st_mode); | |
| size = stat_info.st_size; | |
| #if defined(OS_MACOSX) | |
| 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; | |
| #elif defined(OS_AIX) || defined(OS_ZOS) | |
| 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; | |
| #elif defined(OS_POSIX) | |
| 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; | |
| #else | |
| #error | |
| #endif | |
| constexpr uint64_t kNano = 1'000'000'000; | |
| last_modified = last_modified_sec * kNano + last_modified_nsec; | |
| last_accessed = last_accessed_sec * kNano + last_accessed_nsec; | |
| creation_time = creation_time_sec * kNano + creation_time_nsec; | |
| } | |
| 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; | |
| file_.reset(); | |
| } | |
| int64_t File::Seek(Whence whence, int64_t offset) { | |
| DCHECK(IsValid()); | |
| 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)); | |
| } | |
| int File::Read(int64_t offset, char* data, int size) { | |
| DCHECK(IsValid()); | |
| if (size < 0) | |
| return -1; | |
| 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) { | |
| DCHECK(IsValid()); | |
| if (size < 0) | |
| return -1; | |
| 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) { | |
| DCHECK(IsValid()); | |
| return HANDLE_EINTR(pread(file_.get(), data, size, offset)); | |
| } | |
| int File::ReadAtCurrentPosNoBestEffort(char* data, int size) { | |
| DCHECK(IsValid()); | |
| if (size < 0) | |
| return -1; | |
| return HANDLE_EINTR(read(file_.get(), data, size)); | |
| } | |
| int File::Write(int64_t offset, const char* data, int size) { | |
| if (IsOpenAppend(file_.get())) | |
| return WriteAtCurrentPos(data, size); | |
| DCHECK(IsValid()); | |
| if (size < 0) | |
| return -1; | |
| int bytes_written = 0; | |
| int rv; | |
| do { | |
| rv = HANDLE_EINTR(pwrite(file_.get(), data + bytes_written, | |
| size - bytes_written, offset + bytes_written)); | |
| 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) { | |
| DCHECK(IsValid()); | |
| if (size < 0) | |
| return -1; | |
| 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) { | |
| DCHECK(IsValid()); | |
| if (size < 0) | |
| return -1; | |
| return HANDLE_EINTR(write(file_.get(), data, size)); | |
| } | |
| int64_t File::GetLength() { | |
| DCHECK(IsValid()); | |
| struct stat file_info; | |
| if (fstat(file_.get(), &file_info)) | |
| return -1; | |
| return file_info.st_size; | |
| } | |
| bool File::SetLength(int64_t length) { | |
| DCHECK(IsValid()); | |
| return !CallFtruncate(file_.get(), length); | |
| } | |
| bool File::GetInfo(Info* info) { | |
| DCHECK(IsValid()); | |
| struct stat file_info; | |
| if (fstat(file_.get(), &file_info)) | |
| return false; | |
| info->FromStat(file_info); | |
| return true; | |
| } | |
| #if !defined(OS_FUCHSIA) | |
| File::Error File::Lock() { | |
| return CallFcntlFlock(file_.get(), true); | |
| } | |
| File::Error File::Unlock() { | |
| return CallFcntlFlock(file_.get(), false); | |
| } | |
| #endif | |
| File File::Duplicate() const { | |
| if (!IsValid()) | |
| return File(); | |
| PlatformFile other_fd = HANDLE_EINTR(dup(GetPlatformFile())); | |
| if (other_fd == -1) | |
| return File(File::GetLastFileError()); | |
| File other(other_fd); | |
| return other; | |
| } | |
| // 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: | |
| case ETXTBSY: | |
| 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: | |
| // This function should only be called for errors. | |
| DCHECK_NE(0, saved_errno); | |
| return FILE_ERROR_FAILED; | |
| } | |
| } | |
| void File::DoInitialize(const FilePath& path, uint32_t flags) { | |
| DCHECK(!IsValid()); | |
| int open_flags = 0; | |
| if (flags & FLAG_CREATE) | |
| open_flags = O_CREAT | O_EXCL; | |
| if (flags & FLAG_CREATE_ALWAYS) { | |
| DCHECK(!open_flags); | |
| DCHECK(flags & FLAG_WRITE); | |
| open_flags = O_CREAT | O_TRUNC; | |
| } | |
| if (!open_flags && !(flags & FLAG_OPEN)) { | |
| 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) { | |
| open_flags |= O_RDONLY; | |
| } else { | |
| NOTREACHED(); | |
| } | |
| int mode = S_IRUSR | S_IWUSR; | |
| int descriptor = HANDLE_EINTR(open(path.value().c_str(), open_flags, mode)); | |
| if (descriptor < 0) { | |
| error_details_ = File::GetLastFileError(); | |
| return; | |
| } | |
| error_details_ = FILE_OK; | |
| file_.reset(descriptor); | |
| } | |
| bool File::Flush() { | |
| DCHECK(IsValid()); | |
| #if defined(OS_LINUX) | |
| return !HANDLE_EINTR(fdatasync(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); | |
| } | |
| } // namespace base |