| // Copyright 2014 The Crashpad Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "util/file/file_io.h" |
| |
| #include <algorithm> |
| #include <limits> |
| |
| #include "base/check_op.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/notreached.h" |
| #include "base/strings/utf_string_conversions.h" |
| |
| namespace { |
| |
| bool IsSocketHandle(HANDLE file) { |
| if (GetFileType(file) == FILE_TYPE_PIPE) { |
| // FILE_TYPE_PIPE means that it's a socket, a named pipe, or an anonymous |
| // pipe. If we are unable to retrieve the pipe information, we know it's a |
| // socket. |
| return !GetNamedPipeInfo(file, nullptr, nullptr, nullptr, nullptr); |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| namespace crashpad { |
| |
| namespace { |
| |
| // kMaxReadWriteSize needs to be limited to the range of DWORD for the calls to |
| // ::ReadFile() and ::WriteFile(), and also limited to the range of |
| // FileOperationResult to be able to adequately express the number of bytes read |
| // and written in the return values from ReadFile() and NativeWriteFile(). In a |
| // 64-bit build, the former will control, and the limit will be (2^32)-1. In a |
| // 32-bit build, the latter will control, and the limit will be (2^31)-1. |
| constexpr size_t kMaxReadWriteSize = std::min( |
| static_cast<size_t>(std::numeric_limits<DWORD>::max()), |
| static_cast<size_t>(std::numeric_limits<FileOperationResult>::max())); |
| |
| FileHandle OpenFileForOutput(DWORD access, |
| const base::FilePath& path, |
| FileWriteMode mode, |
| FilePermissions permissions) { |
| DCHECK(access & GENERIC_WRITE); |
| DCHECK_EQ(access & ~(GENERIC_READ | GENERIC_WRITE), 0u); |
| |
| DWORD disposition = 0; |
| switch (mode) { |
| case FileWriteMode::kReuseOrFail: |
| disposition = OPEN_EXISTING; |
| break; |
| case FileWriteMode::kReuseOrCreate: |
| disposition = OPEN_ALWAYS; |
| break; |
| case FileWriteMode::kTruncateOrCreate: |
| disposition = CREATE_ALWAYS; |
| break; |
| case FileWriteMode::kCreateOrFail: |
| disposition = CREATE_NEW; |
| break; |
| } |
| return CreateFile(path.value().c_str(), |
| access, |
| FILE_SHARE_READ | FILE_SHARE_WRITE, |
| nullptr, |
| disposition, |
| FILE_ATTRIBUTE_NORMAL, |
| nullptr); |
| } |
| |
| } // namespace |
| |
| namespace internal { |
| |
| FileOperationResult NativeWriteFile(FileHandle file, |
| const void* buffer, |
| size_t size) { |
| // TODO(scottmg): This might need to handle the limit for pipes across a |
| // network in the future. |
| |
| const DWORD write_size = |
| static_cast<DWORD>(std::min(size, kMaxReadWriteSize)); |
| |
| DWORD bytes_written; |
| if (!::WriteFile(file, buffer, write_size, &bytes_written, nullptr)) |
| return -1; |
| |
| CHECK_NE(bytes_written, static_cast<DWORD>(-1)); |
| DCHECK_LE(static_cast<size_t>(bytes_written), write_size); |
| return bytes_written; |
| } |
| |
| } // namespace internal |
| |
| FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) { |
| DCHECK(!IsSocketHandle(file)); |
| |
| const DWORD read_size = static_cast<DWORD>(std::min(size, kMaxReadWriteSize)); |
| |
| while (true) { |
| DWORD bytes_read; |
| BOOL success = ::ReadFile(file, buffer, read_size, &bytes_read, nullptr); |
| if (!success) { |
| if (GetLastError() == ERROR_BROKEN_PIPE) { |
| // When reading a pipe and the write handle has been closed, ReadFile |
| // fails with ERROR_BROKEN_PIPE, but only once all pending data has been |
| // read. Treat this as EOF. |
| return 0; |
| } |
| return -1; |
| } |
| |
| CHECK_NE(bytes_read, static_cast<DWORD>(-1)); |
| DCHECK_LE(bytes_read, read_size); |
| if (bytes_read != 0 || GetFileType(file) != FILE_TYPE_PIPE) { |
| // Zero bytes read for a file indicates reaching EOF. Zero bytes read from |
| // a pipe indicates only that there was a zero byte WriteFile issued on |
| // the other end, so continue reading. |
| return bytes_read; |
| } |
| } |
| } |
| |
| FileHandle OpenFileForRead(const base::FilePath& path) { |
| return CreateFile(path.value().c_str(), |
| GENERIC_READ, |
| FILE_SHARE_READ | FILE_SHARE_WRITE, |
| nullptr, |
| OPEN_EXISTING, |
| 0, |
| nullptr); |
| } |
| |
| FileHandle OpenFileForWrite(const base::FilePath& path, |
| FileWriteMode mode, |
| FilePermissions permissions) { |
| return OpenFileForOutput(GENERIC_WRITE, path, mode, permissions); |
| } |
| |
| FileHandle OpenFileForReadAndWrite(const base::FilePath& path, |
| FileWriteMode mode, |
| FilePermissions permissions) { |
| return OpenFileForOutput( |
| GENERIC_READ | GENERIC_WRITE, path, mode, permissions); |
| } |
| |
| FileHandle LoggingOpenFileForRead(const base::FilePath& path) { |
| FileHandle file = OpenFileForRead(path); |
| PLOG_IF(ERROR, file == INVALID_HANDLE_VALUE) |
| << "CreateFile " << base::WideToUTF8(path.value()); |
| return file; |
| } |
| |
| FileHandle LoggingOpenFileForWrite(const base::FilePath& path, |
| FileWriteMode mode, |
| FilePermissions permissions) { |
| FileHandle file = OpenFileForWrite(path, mode, permissions); |
| PLOG_IF(ERROR, file == INVALID_HANDLE_VALUE) |
| << "CreateFile " << base::WideToUTF8(path.value()); |
| return file; |
| } |
| |
| FileHandle LoggingOpenFileForReadAndWrite(const base::FilePath& path, |
| FileWriteMode mode, |
| FilePermissions permissions) { |
| FileHandle file = OpenFileForReadAndWrite(path, mode, permissions); |
| PLOG_IF(ERROR, file == INVALID_HANDLE_VALUE) |
| << "CreateFile " << base::WideToUTF8(path.value()); |
| return file; |
| } |
| |
| FileLockingResult LoggingLockFile(FileHandle file, |
| FileLocking locking, |
| FileLockingBlocking blocking) { |
| DWORD flags = |
| (locking == FileLocking::kExclusive) ? LOCKFILE_EXCLUSIVE_LOCK : 0; |
| if (blocking == FileLockingBlocking::kNonBlocking) |
| flags |= LOCKFILE_FAIL_IMMEDIATELY; |
| |
| // Note that the `Offset` fields of overlapped indicate the start location for |
| // locking (beginning of file in this case), and `hEvent` must be also be set |
| // to 0. |
| OVERLAPPED overlapped = {0}; |
| if (!LockFileEx(file, flags, 0, MAXDWORD, MAXDWORD, &overlapped)) { |
| if (GetLastError() == ERROR_LOCK_VIOLATION) { |
| return FileLockingResult::kWouldBlock; |
| } |
| PLOG(ERROR) << "LockFileEx"; |
| return FileLockingResult::kFailure; |
| } |
| return FileLockingResult::kSuccess; |
| } |
| |
| bool LoggingUnlockFile(FileHandle file) { |
| // Note that the `Offset` fields of overlapped indicate the start location for |
| // locking (beginning of file in this case), and `hEvent` must be also be set |
| // to 0. |
| OVERLAPPED overlapped = {0}; |
| if (!UnlockFileEx(file, 0, MAXDWORD, MAXDWORD, &overlapped)) { |
| PLOG(ERROR) << "UnlockFileEx"; |
| return false; |
| } |
| return true; |
| } |
| |
| FileOffset LoggingSeekFile(FileHandle file, FileOffset offset, int whence) { |
| DWORD method = 0; |
| switch (whence) { |
| case SEEK_SET: |
| method = FILE_BEGIN; |
| break; |
| case SEEK_CUR: |
| method = FILE_CURRENT; |
| break; |
| case SEEK_END: |
| method = FILE_END; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| LARGE_INTEGER distance_to_move; |
| distance_to_move.QuadPart = offset; |
| LARGE_INTEGER new_offset; |
| BOOL result = SetFilePointerEx(file, distance_to_move, &new_offset, method); |
| if (!result) { |
| PLOG(ERROR) << "SetFilePointerEx"; |
| return -1; |
| } |
| return new_offset.QuadPart; |
| } |
| |
| bool LoggingTruncateFile(FileHandle file) { |
| if (LoggingSeekFile(file, 0, SEEK_SET) != 0) |
| return false; |
| if (!SetEndOfFile(file)) { |
| PLOG(ERROR) << "SetEndOfFile"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool LoggingCloseFile(FileHandle file) { |
| BOOL rv = CloseHandle(file); |
| PLOG_IF(ERROR, !rv) << "CloseHandle"; |
| return !!rv; |
| } |
| |
| FileOffset LoggingFileSizeByHandle(FileHandle file) { |
| LARGE_INTEGER file_size; |
| if (!GetFileSizeEx(file, &file_size)) { |
| PLOG(ERROR) << "GetFileSizeEx"; |
| return -1; |
| } |
| return file_size.QuadPart; |
| } |
| |
| FileHandle StdioFileHandle(StdioStream stdio_stream) { |
| DWORD standard_handle; |
| switch (stdio_stream) { |
| case StdioStream::kStandardInput: |
| standard_handle = STD_INPUT_HANDLE; |
| break; |
| case StdioStream::kStandardOutput: |
| standard_handle = STD_OUTPUT_HANDLE; |
| break; |
| case StdioStream::kStandardError: |
| standard_handle = STD_ERROR_HANDLE; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| HANDLE handle = GetStdHandle(standard_handle); |
| PLOG_IF(ERROR, handle == INVALID_HANDLE_VALUE) << "GetStdHandle"; |
| return handle; |
| } |
| |
| } // namespace crashpad |