| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "storage/browser/file_system/native_file_util.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| |
| #include "base/containers/span.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "storage/browser/file_system/file_system_operation_context.h" |
| #include "storage/browser/file_system/file_system_url.h" |
| #include "storage/common/file_system/file_system_mount_option.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "base/android/content_uri_utils.h" |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "windows.h" |
| #endif // BUILDFLAG(IS_WIN) |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include <grp.h> |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| namespace storage { |
| namespace { |
| |
| // Copies a file |from| to |to|, and ensure the written content is synced to |
| // the disk. This is essentially base::CopyFile followed by fsync(). |
| bool CopyFileAndSync(const base::FilePath& from, const base::FilePath& to) { |
| base::File infile(from, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| if (!infile.IsValid()) { |
| return false; |
| } |
| |
| base::File outfile(to, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| if (!outfile.IsValid()) { |
| return false; |
| } |
| |
| const int kBufferSize = 32768; |
| std::vector<char> buffer(kBufferSize); |
| |
| for (;;) { |
| std::optional<size_t> bytes_read = |
| infile.ReadAtCurrentPos(base::as_writable_byte_span(buffer)); |
| if (!bytes_read.has_value()) { |
| return false; |
| } |
| if (bytes_read.value() == 0) { |
| break; |
| } |
| auto span_to_write = base::as_byte_span(buffer).first(bytes_read.value()); |
| while (!span_to_write.empty()) { |
| std::optional<size_t> bytes_written_partial = |
| outfile.WriteAtCurrentPos(span_to_write); |
| if (!bytes_written_partial.has_value()) { |
| return false; |
| } |
| span_to_write = span_to_write.subspan(bytes_written_partial.value()); |
| } |
| } |
| |
| return outfile.Flush(); |
| } |
| |
| } // namespace |
| |
| using base::PlatformFile; |
| |
| class NativeFileEnumerator : public FileSystemFileUtil::AbstractFileEnumerator { |
| public: |
| NativeFileEnumerator(const base::FilePath& root_path, |
| bool recursive, |
| int file_type) |
| : file_enum_(root_path, recursive, file_type) {} |
| |
| ~NativeFileEnumerator() override = default; |
| |
| base::FilePath Next() override; |
| base::FilePath GetName() override; |
| int64_t Size() override; |
| base::Time LastModifiedTime() override; |
| bool IsDirectory() override; |
| |
| private: |
| base::FileEnumerator file_enum_; |
| base::FileEnumerator::FileInfo file_util_info_; |
| }; |
| |
| base::FilePath NativeFileEnumerator::Next() { |
| base::FilePath rv = file_enum_.Next(); |
| if (!rv.empty()) |
| file_util_info_ = file_enum_.GetInfo(); |
| return rv; |
| } |
| |
| base::FilePath NativeFileEnumerator::GetName() { |
| return file_util_info_.GetName(); |
| } |
| |
| int64_t NativeFileEnumerator::Size() { |
| return file_util_info_.GetSize(); |
| } |
| |
| base::Time NativeFileEnumerator::LastModifiedTime() { |
| return file_util_info_.GetLastModifiedTime(); |
| } |
| |
| bool NativeFileEnumerator::IsDirectory() { |
| return file_util_info_.IsDirectory(); |
| } |
| |
| NativeFileUtil::CopyOrMoveMode NativeFileUtil::CopyOrMoveModeForDestination( |
| const FileSystemURL& dest_url, |
| bool copy) { |
| if (copy) { |
| return dest_url.mount_option().flush_policy() == |
| FlushPolicy::FLUSH_ON_COMPLETION |
| ? COPY_SYNC |
| : COPY_NOSYNC; |
| } |
| return MOVE; |
| } |
| |
| base::File NativeFileUtil::CreateOrOpen(const base::FilePath& path, |
| uint32_t file_flags) { |
| if (!base::DirectoryExists(path.DirName())) { |
| // If its parent does not exist, should return NOT_FOUND error. |
| return base::File(base::File::FILE_ERROR_NOT_FOUND); |
| } |
| |
| // TODO(rvargas): Check |file_flags| instead. See bug 356358. |
| if (base::DirectoryExists(path)) |
| return base::File(base::File::FILE_ERROR_NOT_A_FILE); |
| |
| // This file might be passed to an untrusted process. |
| file_flags = base::File::AddFlagsForPassingToUntrustedProcess(file_flags); |
| |
| return base::File(path, file_flags); |
| } |
| |
| base::File::Error NativeFileUtil::EnsureFileExists(const base::FilePath& path, |
| bool* created) { |
| #if !BUILDFLAG(IS_ANDROID) |
| if (!base::DirectoryExists(path.DirName())) { |
| // If its parent does not exist, should return NOT_FOUND error. |
| return base::File::FILE_ERROR_NOT_FOUND; |
| } |
| #endif |
| |
| // If |path| is a directory, return an error. |
| if (base::DirectoryExists(path)) { |
| return base::File::FILE_ERROR_NOT_A_FILE; |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| if (path.IsContentUri()) { |
| if (base::PathExists(path)) { |
| if (created) { |
| *created = false; |
| } |
| return base::File::FILE_OK; |
| } |
| base::FilePath result = |
| base::ContentUriGetDocumentFromQuery(path, /*create=*/true); |
| if (created) { |
| *created = !result.empty(); |
| } |
| if (result.empty()) { |
| return base::File::FILE_ERROR_FAILED; |
| } |
| return base::File::FILE_OK; |
| } |
| #endif |
| |
| // Tries to create the |path| exclusively. This should fail |
| // with base::File::FILE_ERROR_EXISTS if the path already exists. |
| base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_READ); |
| |
| if (file.IsValid()) { |
| if (created) |
| *created = file.created(); |
| return base::File::FILE_OK; |
| } |
| |
| base::File::Error error_code = file.error_details(); |
| if (error_code == base::File::FILE_ERROR_EXISTS) { |
| // Make sure created_ is false. |
| if (created) |
| *created = false; |
| error_code = base::File::FILE_OK; |
| } |
| return error_code; |
| } |
| |
| base::File::Error NativeFileUtil::CreateDirectory(const base::FilePath& path, |
| bool exclusive, |
| bool recursive) { |
| #if !BUILDFLAG(IS_ANDROID) |
| // If parent dir of file doesn't exist. |
| if (!recursive && !base::PathExists(path.DirName())) { |
| return base::File::FILE_ERROR_NOT_FOUND; |
| } |
| #endif |
| |
| bool path_exists = base::PathExists(path); |
| if (exclusive && path_exists) { |
| return base::File::FILE_ERROR_EXISTS; |
| } |
| |
| if (base::DirectoryExists(path)) { |
| return base::File::FILE_OK; |
| } |
| |
| // If file exists at the path. |
| if (path_exists) { |
| return base::File::FILE_ERROR_NOT_A_DIRECTORY; |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| if (path.IsContentUri()) { |
| const base::FilePath result = |
| base::ContentUriGetDocumentFromQuery(path, /*create=*/true); |
| return result.empty() ? base::File::FILE_ERROR_FAILED : base::File::FILE_OK; |
| } |
| #endif |
| |
| base::File::Error error; |
| return base::CreateDirectoryAndGetError(path, &error) ? base::File::FILE_OK |
| : error; |
| } |
| |
| base::File::Error NativeFileUtil::GetFileInfo(const base::FilePath& path, |
| base::File::Info* file_info) { |
| if (!base::PathExists(path)) |
| return base::File::FILE_ERROR_NOT_FOUND; |
| |
| if (!base::GetFileInfo(path, file_info)) |
| return base::File::FILE_ERROR_FAILED; |
| return base::File::FILE_OK; |
| } |
| |
| std::unique_ptr<FileSystemFileUtil::AbstractFileEnumerator> |
| NativeFileUtil::CreateFileEnumerator(const base::FilePath& root_path, |
| bool recursive) { |
| return std::make_unique<NativeFileEnumerator>( |
| root_path, recursive, |
| base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); |
| } |
| |
| base::File::Error NativeFileUtil::Touch(const base::FilePath& path, |
| const base::Time& last_access_time, |
| const base::Time& last_modified_time) { |
| if (!base::TouchFile(path, last_access_time, last_modified_time)) |
| return base::File::FILE_ERROR_FAILED; |
| return base::File::FILE_OK; |
| } |
| |
| base::File::Error NativeFileUtil::Truncate(const base::FilePath& path, |
| int64_t length) { |
| #if BUILDFLAG(IS_ANDROID) |
| if (path.IsContentUri()) { |
| if (length != 0) { |
| return base::File::FILE_ERROR_FAILED; |
| } |
| base::File file(path, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| return file.error_details(); |
| } |
| #endif |
| base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_WRITE); |
| if (!file.IsValid()) |
| return file.error_details(); |
| |
| if (!file.SetLength(length)) |
| return base::File::FILE_ERROR_FAILED; |
| |
| return base::File::FILE_OK; |
| } |
| |
| bool NativeFileUtil::PathExists(const base::FilePath& path) { |
| return base::PathExists(path); |
| } |
| |
| bool NativeFileUtil::DirectoryExists(const base::FilePath& path) { |
| return base::DirectoryExists(path); |
| } |
| |
| base::File::Error NativeFileUtil::CopyOrMoveFile( |
| const base::FilePath& src_path, |
| const base::FilePath& dest_path, |
| FileSystemOperation::CopyOrMoveOptionSet options, |
| CopyOrMoveMode mode) { |
| base::File::Info info; |
| base::File::Error error = NativeFileUtil::GetFileInfo(src_path, &info); |
| if (error != base::File::FILE_OK) |
| return error; |
| if (info.is_directory && mode != MOVE) |
| return base::File::FILE_ERROR_NOT_A_FILE; |
| bool src_is_directory = info.is_directory; |
| base::Time last_modified = info.last_modified; |
| |
| error = NativeFileUtil::GetFileInfo(dest_path, &info); |
| if (error != base::File::FILE_OK && error != base::File::FILE_ERROR_NOT_FOUND) |
| return error; |
| if (error == base::File::FILE_OK) { |
| if (info.is_directory != src_is_directory) |
| return base::File::FILE_ERROR_INVALID_OPERATION; |
| #if BUILDFLAG(IS_WIN) |
| // Overwriting an empty directory with another directory isn't supported |
| // natively on Windows, so treat this an unsupported. A higher layer is |
| // responsible for handling it. |
| if (info.is_directory) |
| return base::File::FILE_ERROR_NOT_A_FILE; |
| #endif |
| } |
| if (error == base::File::FILE_ERROR_NOT_FOUND) { |
| error = NativeFileUtil::GetFileInfo(dest_path.DirName(), &info); |
| if (error != base::File::FILE_OK) |
| return error; |
| if (!info.is_directory) |
| return base::File::FILE_ERROR_NOT_FOUND; |
| } |
| |
| // Cache permissions of dest file before copy/move overwrites the file. |
| bool should_retain_file_permissions = false; |
| #if BUILDFLAG(IS_POSIX) |
| int dest_mode; |
| if (options.Has(FileSystemOperation::CopyOrMoveOption:: |
| kPreserveDestinationPermissions)) { |
| // Will be false if the destination file doesn't exist. |
| should_retain_file_permissions = |
| base::GetPosixFilePermissions(dest_path, &dest_mode); |
| } |
| #elif BUILDFLAG(IS_WIN) |
| DWORD dest_attributes; |
| if (options.Has(FileSystemOperation::CopyOrMoveOption:: |
| kPreserveDestinationPermissions)) { |
| dest_attributes = ::GetFileAttributes(dest_path.value().c_str()); |
| should_retain_file_permissions = dest_attributes != INVALID_FILE_ATTRIBUTES; |
| } |
| #endif // BUILDFLAG(IS_POSIX) |
| |
| switch (mode) { |
| case COPY_NOSYNC: |
| if (!base::CopyFile(src_path, dest_path)) |
| return base::File::FILE_ERROR_FAILED; |
| break; |
| case COPY_SYNC: |
| if (!CopyFileAndSync(src_path, dest_path)) |
| return base::File::FILE_ERROR_FAILED; |
| break; |
| case MOVE: |
| if (!base::Move(src_path, dest_path)) |
| return base::File::FILE_ERROR_FAILED; |
| break; |
| } |
| |
| // Preserve the last modified time. Do not return error here even if |
| // the setting is failed, because the copy itself is successfully done. |
| if (options.Has( |
| FileSystemOperation::CopyOrMoveOption::kPreserveLastModified)) { |
| base::TouchFile(dest_path, last_modified, last_modified); |
| } |
| |
| if (should_retain_file_permissions) { |
| #if BUILDFLAG(IS_POSIX) |
| base::SetPosixFilePermissions(dest_path, dest_mode); |
| #elif BUILDFLAG(IS_WIN) |
| ::SetFileAttributes(dest_path.value().c_str(), dest_attributes); |
| #endif // BUILDFLAG(IS_POSIX) |
| } |
| |
| return base::File::FILE_OK; |
| } |
| |
| base::File::Error NativeFileUtil::DeleteFile(const base::FilePath& path) { |
| if (!base::PathExists(path)) |
| return base::File::FILE_ERROR_NOT_FOUND; |
| if (base::DirectoryExists(path)) |
| return base::File::FILE_ERROR_NOT_A_FILE; |
| if (!base::DeleteFile(path)) |
| return base::File::FILE_ERROR_FAILED; |
| return base::File::FILE_OK; |
| } |
| |
| base::File::Error NativeFileUtil::DeleteDirectory(const base::FilePath& path) { |
| if (!base::PathExists(path)) |
| return base::File::FILE_ERROR_NOT_FOUND; |
| if (!base::DirectoryExists(path)) |
| return base::File::FILE_ERROR_NOT_A_DIRECTORY; |
| if (!base::IsDirectoryEmpty(path)) |
| return base::File::FILE_ERROR_NOT_EMPTY; |
| if (!base::DeleteFile(path)) |
| return base::File::FILE_ERROR_FAILED; |
| return base::File::FILE_OK; |
| } |
| |
| } // namespace storage |