mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-20 02:23:47 +00:00
util/file: Handle oversized reads and writes gracefully
file_io and the FileReader family had a few loose ends regarding big reads and writes. It’s not likely that we’ve experienced these conditions yet, but they’d be likely to appear in a potential future involving full memory dumps. This specifies the behavior with large reads and writes, consolidates some logic, and improves some interfaces. ReadFile() should always return without retrying after a short read, and in fact does return after short reads since 00b64427523b. It is straightforward to limit the maximum read size based on a parameter limitation of the underlying operation, or a limitation of the type used for FileOperationResult. In contrast, WriteFile() should always retry after a short write, including a write shortened because of a parameter limitation of the underlying operation, or a limitation of the type used for FileOperationResult. This allows its return value to be simplified to a “bool”. The platform-specific WriteFile() code has been moved to internal::NativeWriteFile(), and the platform-independent loop that retries following a short write has been refactored into internal::WriteAllInternal so that it can be used by a new test. The platform-agnostic ReadFileExactlyInternal() implementation has been refactored into internal::ReadExactlyInternal so that it can be used by a new test and by FileReaderInterface::ReadExactly(), which had a nearly identical implementation. Test: crashpad_util_test FileIO.ReadExactly_*:FileIO.WriteAll_*:FileReader.ReadExactly_* Change-Id: I487450322ab049c6f2acd4061ea814037cc9a864 Reviewed-on: https://chromium-review.googlesource.com/456824 Reviewed-by: Scott Graham <scottmg@chromium.org>
This commit is contained in:
parent
39f13a77a4
commit
3983b80ca2
@ -15,67 +15,123 @@
|
|||||||
#include "util/file/file_io.h"
|
#include "util/file/file_io.h"
|
||||||
|
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
|
#include "base/macros.h"
|
||||||
#include "base/numerics/safe_conversions.h"
|
#include "base/numerics/safe_conversions.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
bool ReadFileExactlyInternal(FileHandle file,
|
class FileIOReadExactly final : public internal::ReadExactlyInternal {
|
||||||
void* buffer,
|
public:
|
||||||
size_t size,
|
explicit FileIOReadExactly(FileHandle file)
|
||||||
bool can_log) {
|
: ReadExactlyInternal(), file_(file) {}
|
||||||
FileOperationResult expect = base::checked_cast<FileOperationResult>(size);
|
~FileIOReadExactly() {}
|
||||||
char* buffer_c = static_cast<char*>(buffer);
|
|
||||||
|
|
||||||
FileOperationResult total_bytes = 0;
|
private:
|
||||||
while (size > 0) {
|
// ReadExactlyInternal:
|
||||||
FileOperationResult bytes = ReadFile(file, buffer, size);
|
FileOperationResult Read(void* buffer, size_t size, bool can_log) override {
|
||||||
if (bytes < 0) {
|
FileOperationResult rv = ReadFile(file_, buffer, size);
|
||||||
PLOG_IF(ERROR, can_log) << kNativeReadFunctionName;
|
if (rv < 0) {
|
||||||
|
PLOG_IF(ERROR, can_log) << internal::kNativeReadFunctionName;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileHandle file_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(FileIOReadExactly);
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileIOWriteAll final : public internal::WriteAllInternal {
|
||||||
|
public:
|
||||||
|
explicit FileIOWriteAll(FileHandle file) : WriteAllInternal(), file_(file) {}
|
||||||
|
~FileIOWriteAll() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// WriteAllInternal:
|
||||||
|
FileOperationResult Write(const void* buffer, size_t size) override {
|
||||||
|
return internal::NativeWriteFile(file_, buffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileHandle file_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(FileIOWriteAll);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
bool ReadExactlyInternal::ReadExactly(void* buffer, size_t size, bool can_log) {
|
||||||
|
char* buffer_c = static_cast<char*>(buffer);
|
||||||
|
size_t total_bytes = 0;
|
||||||
|
size_t remaining = size;
|
||||||
|
while (remaining > 0) {
|
||||||
|
FileOperationResult bytes_read = Read(buffer_c, remaining, can_log);
|
||||||
|
if (bytes_read < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DCHECK_LE(static_cast<size_t>(bytes), size);
|
DCHECK_LE(static_cast<size_t>(bytes_read), remaining);
|
||||||
|
|
||||||
if (bytes == 0) {
|
if (bytes_read == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_c += bytes;
|
buffer_c += bytes_read;
|
||||||
size -= bytes;
|
remaining -= bytes_read;
|
||||||
total_bytes += bytes;
|
total_bytes += bytes_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total_bytes != expect) {
|
if (total_bytes != size) {
|
||||||
LOG_IF(ERROR, can_log) << kNativeReadFunctionName << ": expected " << expect
|
LOG_IF(ERROR, can_log) << "ReadExactly: expected " << size << ", observed "
|
||||||
<< ", observed " << total_bytes;
|
<< total_bytes;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
bool WriteAllInternal::WriteAll(const void* buffer, size_t size) {
|
||||||
|
const char* buffer_c = static_cast<const char*>(buffer);
|
||||||
|
|
||||||
|
while (size > 0) {
|
||||||
|
FileOperationResult bytes_written = Write(buffer_c, size);
|
||||||
|
if (bytes_written < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DCHECK_NE(bytes_written, 0);
|
||||||
|
|
||||||
|
buffer_c += bytes_written;
|
||||||
|
size -= bytes_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
bool ReadFileExactly(FileHandle file, void* buffer, size_t size) {
|
bool ReadFileExactly(FileHandle file, void* buffer, size_t size) {
|
||||||
return ReadFileExactlyInternal(file, buffer, size, false);
|
FileIOReadExactly read_exactly(file);
|
||||||
|
return read_exactly.ReadExactly(buffer, size, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoggingReadFileExactly(FileHandle file, void* buffer, size_t size) {
|
bool LoggingReadFileExactly(FileHandle file, void* buffer, size_t size) {
|
||||||
return ReadFileExactlyInternal(file, buffer, size, true);
|
FileIOReadExactly read_exactly(file);
|
||||||
|
return read_exactly.ReadExactly(buffer, size, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteFile(FileHandle file, const void* buffer, size_t size) {
|
||||||
|
FileIOWriteAll write_all(file);
|
||||||
|
return write_all.WriteAll(buffer, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoggingWriteFile(FileHandle file, const void* buffer, size_t size) {
|
bool LoggingWriteFile(FileHandle file, const void* buffer, size_t size) {
|
||||||
FileOperationResult expect = base::checked_cast<FileOperationResult>(size);
|
if (!WriteFile(file, buffer, size)) {
|
||||||
FileOperationResult rv = WriteFile(file, buffer, size);
|
PLOG(ERROR) << internal::kNativeWriteFunctionName;
|
||||||
if (rv < 0) {
|
|
||||||
PLOG(ERROR) << kNativeWriteFunctionName;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (rv != expect) {
|
|
||||||
LOG(ERROR) << kNativeWriteFunctionName << ": expected " << expect
|
|
||||||
<< ", observed " << rv;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,9 +150,9 @@ void CheckedReadFileAtEOF(FileHandle file) {
|
|||||||
char c;
|
char c;
|
||||||
FileOperationResult rv = ReadFile(file, &c, 1);
|
FileOperationResult rv = ReadFile(file, &c, 1);
|
||||||
if (rv < 0) {
|
if (rv < 0) {
|
||||||
PCHECK(rv == 0) << kNativeReadFunctionName;
|
PCHECK(rv == 0) << internal::kNativeReadFunctionName;
|
||||||
} else {
|
} else {
|
||||||
CHECK_EQ(rv, 0) << kNativeReadFunctionName;
|
CHECK_EQ(rv, 0) << internal::kNativeReadFunctionName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +105,8 @@ enum class StdioStream {
|
|||||||
kStandardError,
|
kStandardError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
//! \brief The name of the native read function used by ReadFile().
|
//! \brief The name of the native read function used by ReadFile().
|
||||||
//!
|
//!
|
||||||
//! This value may be useful for logging.
|
//! This value may be useful for logging.
|
||||||
@ -119,7 +121,91 @@ extern const char kNativeReadFunctionName[];
|
|||||||
//! \sa kNativeReadFunctionName
|
//! \sa kNativeReadFunctionName
|
||||||
extern const char kNativeWriteFunctionName[];
|
extern const char kNativeWriteFunctionName[];
|
||||||
|
|
||||||
//! \brief Reads from a file, retrying when interrupted on POSIX.
|
//! \brief The internal implementation of ReadFileExactly() and its wrappers.
|
||||||
|
//!
|
||||||
|
//! The logic is exposed so that it may be reused by FileReaderInterface, and
|
||||||
|
//! so that it may be tested without requiring large files to be read. It is not
|
||||||
|
//! intended to be used more generally. Use ReadFileExactly(),
|
||||||
|
//! LoggingReadFileExactly(), CheckedReadFileExactly(), or
|
||||||
|
//! FileReaderInterface::ReadExactly() instead.
|
||||||
|
class ReadExactlyInternal {
|
||||||
|
public:
|
||||||
|
//! \brief Calls Read(), retrying following a short read, ensuring that
|
||||||
|
//! exactly \a size bytes are read.
|
||||||
|
//!
|
||||||
|
//! \return `true` on success. `false` if the underlying Read() fails or if
|
||||||
|
//! fewer than \a size bytes were read. When returning `false`, if \a
|
||||||
|
//! can_log is `true`, logs a message.
|
||||||
|
bool ReadExactly(void* buffer, size_t size, bool can_log);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ReadExactlyInternal() {}
|
||||||
|
~ReadExactlyInternal() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! \brief Wraps a read operation, such as ReadFile().
|
||||||
|
//!
|
||||||
|
//! \return The number of bytes read and placed into \a buffer, or `-1` on
|
||||||
|
//! error. When returning `-1`, if \a can_log is `true`, logs a message.
|
||||||
|
virtual FileOperationResult Read(void* buffer, size_t size, bool can_log) = 0;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(ReadExactlyInternal);
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \brief The internal implementation of WriteFile() and its wrappers.
|
||||||
|
//!
|
||||||
|
//! The logic is exposed so that it may be tested without requiring large files
|
||||||
|
//! to be written. It is not intended to be used more generally. Use
|
||||||
|
//! WriteFile(), LoggingWriteFile(), CheckedWriteFile(), or
|
||||||
|
//! FileWriterInterface::Write() instead.
|
||||||
|
class WriteAllInternal {
|
||||||
|
public:
|
||||||
|
//! \brief Calls Write(), retrying following a short write, ensuring that
|
||||||
|
//! exactly \a size bytes are written.
|
||||||
|
//!
|
||||||
|
//! \return `true` on success. `false` if the underlying Write() fails or if
|
||||||
|
//! fewer than \a size bytes were written.
|
||||||
|
bool WriteAll(const void* buffer, size_t size);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
WriteAllInternal() {}
|
||||||
|
~WriteAllInternal() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! \brief Wraps a write operation, such as NativeWriteFile().
|
||||||
|
//!
|
||||||
|
//! \return The number of bytes written from \a buffer, or `-1` on error.
|
||||||
|
virtual FileOperationResult Write(const void* buffer, size_t size) = 0;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(WriteAllInternal);
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \brief Writes to a file, retrying when interrupted on POSIX.
|
||||||
|
//!
|
||||||
|
//! Fewer than \a size bytes may be written to \a file. This can happen if the
|
||||||
|
//! underlying write operation returns before writing the entire buffer, or if
|
||||||
|
//! the buffer is too large to write in a single operation, possibly due to a
|
||||||
|
//! limitation of a data type used to express the number of bytes written.
|
||||||
|
//!
|
||||||
|
//! This function adapts native write operations for uniform use by WriteFile().
|
||||||
|
//! This function should only be called by WriteFile(). Other code should call
|
||||||
|
//! WriteFile() or another function that wraps WriteFile().
|
||||||
|
//!
|
||||||
|
//! \param[in] file The file to write to.
|
||||||
|
//! \param[in] buffer A buffer containing data to be written.
|
||||||
|
//! \param[in] size The number of bytes from \a buffer to write.
|
||||||
|
//!
|
||||||
|
//! \return The number of bytes actually written from \a buffer to \a file on
|
||||||
|
//! success. `-1` on error, with `errno` or `GetLastError()` set
|
||||||
|
//! appropriately.
|
||||||
|
FileOperationResult NativeWriteFile(FileHandle file,
|
||||||
|
const void* buffer,
|
||||||
|
size_t size);
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
//! \brief Reads from a file, retrying when interrupted before reading any data
|
||||||
|
//! on POSIX.
|
||||||
//!
|
//!
|
||||||
//! This function reads into \a buffer. Fewer than \a size bytes may be read.
|
//! This function reads into \a buffer. Fewer than \a size bytes may be read.
|
||||||
//! On Windows, reading from sockets is not currently supported.
|
//! On Windows, reading from sockets is not currently supported.
|
||||||
@ -135,27 +221,24 @@ extern const char kNativeWriteFunctionName[];
|
|||||||
//! \sa CheckedReadFileAtEOF
|
//! \sa CheckedReadFileAtEOF
|
||||||
FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size);
|
FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size);
|
||||||
|
|
||||||
//! \brief Writes to a file, retrying when interrupted or following a short
|
//! \brief Writes to a file, retrying when interrupted on POSIX or following a
|
||||||
//! write on POSIX.
|
//! short write.
|
||||||
//!
|
//!
|
||||||
//! This function writes to \a file, stopping only when \a size bytes have been
|
//! This function writes to \a file, stopping only when \a size bytes have been
|
||||||
//! written.
|
//! written.
|
||||||
//!
|
//!
|
||||||
//! \return The number of bytes written from \a buffer, or `-1` on error, with
|
//! \return `true` on success. `false` on error, with `errno` or
|
||||||
//! `errno` or `GetLastError()` set appropriately. On error, a portion of
|
//! `GetLastError()` set appropriately. On error, a portion of \a buffer may
|
||||||
//! \a buffer may have been written to \a file.
|
//! have been written to \a file.
|
||||||
//!
|
//!
|
||||||
//! \sa ReadFile
|
//! \sa ReadFile
|
||||||
//! \sa LoggingWriteFile
|
//! \sa LoggingWriteFile
|
||||||
//! \sa CheckedWriteFile
|
//! \sa CheckedWriteFile
|
||||||
FileOperationResult WriteFile(FileHandle file, const void* buffer, size_t size);
|
bool WriteFile(FileHandle file, const void* buffer, size_t size);
|
||||||
|
|
||||||
//! \brief Wraps ReadFile(), retrying following a short read, ensuring that
|
//! \brief Wraps ReadFile(), retrying following a short read, ensuring that
|
||||||
//! exactly \a size bytes are read.
|
//! exactly \a size bytes are read.
|
||||||
//!
|
//!
|
||||||
//! If \a size is out of the range of possible ReadFile() return values, this
|
|
||||||
//! function causes execution to terminate without returning.
|
|
||||||
//!
|
|
||||||
//! \return `true` on success. If the underlying ReadFile() fails, or if fewer
|
//! \return `true` on success. If the underlying ReadFile() fails, or if fewer
|
||||||
//! than \a size bytes were read, this function logs a message and
|
//! than \a size bytes were read, this function logs a message and
|
||||||
//! returns `false`.
|
//! returns `false`.
|
||||||
@ -170,9 +253,6 @@ bool ReadFileExactly(FileHandle file, void* buffer, size_t size);
|
|||||||
//! \brief Wraps ReadFile(), retrying following a short read, ensuring that
|
//! \brief Wraps ReadFile(), retrying following a short read, ensuring that
|
||||||
//! exactly \a size bytes are read.
|
//! exactly \a size bytes are read.
|
||||||
//!
|
//!
|
||||||
//! If \a size is out of the range of possible ReadFile() return values, this
|
|
||||||
//! function causes execution to terminate without returning.
|
|
||||||
//!
|
|
||||||
//! \return `true` on success. If the underlying ReadFile() fails, or if fewer
|
//! \return `true` on success. If the underlying ReadFile() fails, or if fewer
|
||||||
//! than \a size bytes were read, this function logs a message and
|
//! than \a size bytes were read, this function logs a message and
|
||||||
//! returns `false`.
|
//! returns `false`.
|
||||||
@ -186,9 +266,6 @@ bool LoggingReadFileExactly(FileHandle file, void* buffer, size_t size);
|
|||||||
|
|
||||||
//! \brief Wraps WriteFile(), ensuring that exactly \a size bytes are written.
|
//! \brief Wraps WriteFile(), ensuring that exactly \a size bytes are written.
|
||||||
//!
|
//!
|
||||||
//! If \a size is out of the range of possible WriteFile() return values, this
|
|
||||||
//! function causes execution to terminate without returning.
|
|
||||||
//!
|
|
||||||
//! \return `true` on success. If the underlying WriteFile() fails, or if fewer
|
//! \return `true` on success. If the underlying WriteFile() fails, or if fewer
|
||||||
//! than \a size bytes were written, this function logs a message and
|
//! than \a size bytes were written, this function logs a message and
|
||||||
//! returns `false`.
|
//! returns `false`.
|
||||||
@ -200,9 +277,8 @@ bool LoggingWriteFile(FileHandle file, const void* buffer, size_t size);
|
|||||||
|
|
||||||
//! \brief Wraps ReadFile(), ensuring that exactly \a size bytes are read.
|
//! \brief Wraps ReadFile(), ensuring that exactly \a size bytes are read.
|
||||||
//!
|
//!
|
||||||
//! If \a size is out of the range of possible ReadFile() return values, if the
|
//! If the underlying ReadFile() fails, or if fewer than \a size bytes were
|
||||||
//! underlying ReadFile() fails, or if fewer than \a size bytes were read, this
|
//! read, this function causes execution to terminate without returning.
|
||||||
//! function causes execution to terminate without returning.
|
|
||||||
//!
|
//!
|
||||||
//! \sa CheckedWriteFile
|
//! \sa CheckedWriteFile
|
||||||
//! \sa ReadFile
|
//! \sa ReadFile
|
||||||
@ -212,9 +288,8 @@ void CheckedReadFileExactly(FileHandle file, void* buffer, size_t size);
|
|||||||
|
|
||||||
//! \brief Wraps WriteFile(), ensuring that exactly \a size bytes are written.
|
//! \brief Wraps WriteFile(), ensuring that exactly \a size bytes are written.
|
||||||
//!
|
//!
|
||||||
//! If \a size is out of the range of possible WriteFile() return values, if the
|
//! if the underlying WriteFile() fails, or if fewer than \a size bytes were
|
||||||
//! underlying WriteFile() fails, or if fewer than \a size bytes were written,
|
//! written, this function causes execution to terminate without returning.
|
||||||
//! this function causes execution to terminate without returning.
|
|
||||||
//!
|
//!
|
||||||
//! \sa CheckedReadFileExactly
|
//! \sa CheckedReadFileExactly
|
||||||
//! \sa WriteFile
|
//! \sa WriteFile
|
||||||
|
@ -19,15 +19,49 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
#include "base/files/file_path.h"
|
#include "base/files/file_path.h"
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
#include "base/numerics/safe_conversions.h"
|
|
||||||
#include "base/posix/eintr_wrapper.h"
|
#include "base/posix/eintr_wrapper.h"
|
||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
struct ReadTraits {
|
||||||
|
using BufferType = void*;
|
||||||
|
static FileOperationResult Operate(int fd, BufferType buffer, size_t size) {
|
||||||
|
return read(fd, buffer, size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WriteTraits {
|
||||||
|
using BufferType = const void*;
|
||||||
|
static FileOperationResult Operate(int fd, BufferType buffer, size_t size) {
|
||||||
|
return write(fd, buffer, size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
FileOperationResult ReadOrWrite(int fd,
|
||||||
|
typename Traits::BufferType buffer,
|
||||||
|
size_t size) {
|
||||||
|
constexpr size_t kMaxReadWriteSize =
|
||||||
|
static_cast<size_t>(std::numeric_limits<ssize_t>::max());
|
||||||
|
const size_t requested_bytes = std::min(size, kMaxReadWriteSize);
|
||||||
|
|
||||||
|
FileOperationResult transacted_bytes =
|
||||||
|
HANDLE_EINTR(Traits::Operate(fd, buffer, requested_bytes));
|
||||||
|
if (transacted_bytes < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DCHECK_LE(static_cast<size_t>(transacted_bytes), requested_bytes);
|
||||||
|
return transacted_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
FileHandle OpenFileForOutput(int rdwr_or_wronly,
|
FileHandle OpenFileForOutput(int rdwr_or_wronly,
|
||||||
const base::FilePath& path,
|
const base::FilePath& path,
|
||||||
FileWriteMode mode,
|
FileWriteMode mode,
|
||||||
@ -60,44 +94,21 @@ FileHandle OpenFileForOutput(int rdwr_or_wronly,
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
const char kNativeReadFunctionName[] = "read";
|
const char kNativeReadFunctionName[] = "read";
|
||||||
const char kNativeWriteFunctionName[] = "write";
|
const char kNativeWriteFunctionName[] = "write";
|
||||||
|
|
||||||
// TODO(mark): Handle > ssize_t-sized reads and writes if necessary. The
|
FileOperationResult NativeWriteFile(FileHandle file,
|
||||||
// standard leaves this implementation-defined. Some systems return EINVAL in
|
const void* buffer,
|
||||||
// this case. ReadFile() and WriteFile() could enforce this behavior.
|
size_t size) {
|
||||||
|
return ReadOrWrite<WriteTraits>(file, buffer, size);
|
||||||
FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) {
|
|
||||||
FileOperationResult bytes = HANDLE_EINTR(read(file, buffer, size));
|
|
||||||
if (bytes < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
DCHECK_LE(static_cast<size_t>(bytes), size);
|
|
||||||
return bytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FileOperationResult WriteFile(FileHandle file,
|
} // namespace internal
|
||||||
const void* buffer,
|
|
||||||
size_t size) {
|
|
||||||
const char* buffer_c = static_cast<const char*>(buffer);
|
|
||||||
|
|
||||||
FileOperationResult total_bytes = 0;
|
FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) {
|
||||||
while (size > 0) {
|
return ReadOrWrite<ReadTraits>(file, buffer, size);
|
||||||
FileOperationResult bytes = HANDLE_EINTR(write(file, buffer_c, size));
|
|
||||||
if (bytes < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
DCHECK_NE(bytes, 0);
|
|
||||||
DCHECK_LE(static_cast<size_t>(bytes), size);
|
|
||||||
|
|
||||||
buffer_c += bytes;
|
|
||||||
size -= bytes;
|
|
||||||
total_bytes += bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
return total_bytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FileHandle OpenFileForRead(const base::FilePath& path) {
|
FileHandle OpenFileForRead(const base::FilePath& path) {
|
||||||
|
@ -16,9 +16,13 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#include "base/atomicops.h"
|
#include "base/atomicops.h"
|
||||||
#include "base/files/file_path.h"
|
#include "base/files/file_path.h"
|
||||||
#include "base/macros.h"
|
#include "base/macros.h"
|
||||||
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include "test/errors.h"
|
#include "test/errors.h"
|
||||||
#include "test/file.h"
|
#include "test/file.h"
|
||||||
@ -30,6 +34,342 @@ namespace crashpad {
|
|||||||
namespace test {
|
namespace test {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using testing::_;
|
||||||
|
using testing::InSequence;
|
||||||
|
using testing::Return;
|
||||||
|
|
||||||
|
class MockReadExactly : public internal::ReadExactlyInternal {
|
||||||
|
public:
|
||||||
|
MockReadExactly() : ReadExactlyInternal() {}
|
||||||
|
~MockReadExactly() {}
|
||||||
|
|
||||||
|
// Since it’s more convenient for the test to use uintptr_t than void*,
|
||||||
|
// ReadExactlyInt() and ReadInt() adapt the types.
|
||||||
|
|
||||||
|
bool ReadExactlyInt(uintptr_t data, size_t size, bool can_log) {
|
||||||
|
return ReadExactly(reinterpret_cast<void*>(data), size, can_log);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOCK_METHOD3(ReadInt, FileOperationResult(uintptr_t, size_t, bool));
|
||||||
|
|
||||||
|
// ReadExactlyInternal:
|
||||||
|
FileOperationResult Read(void* data, size_t size, bool can_log) {
|
||||||
|
return ReadInt(reinterpret_cast<uintptr_t>(data), size, can_log);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(MockReadExactly);
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_Zero) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(_, _, false)).Times(0);
|
||||||
|
EXPECT_TRUE(read_exactly.ReadExactlyInt(100, 0, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_SingleSmallSuccess) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(1000, 1, false)).WillOnce(Return(1));
|
||||||
|
EXPECT_TRUE(read_exactly.ReadExactlyInt(1000, 1, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_SingleSmallSuccessCanLog) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(1000, 1, true)).WillOnce(Return(1));
|
||||||
|
EXPECT_TRUE(read_exactly.ReadExactlyInt(1000, 1, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_SingleSmallFailure) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(1000, 1, false)).WillOnce(Return(-1));
|
||||||
|
EXPECT_FALSE(read_exactly.ReadExactlyInt(1000, 1, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_SingleSmallFailureCanLog) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(1000, 1, true)).WillOnce(Return(-1));
|
||||||
|
EXPECT_FALSE(read_exactly.ReadExactlyInt(1000, 1, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_DoubleSmallSuccess) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x1000, 2, false)).WillOnce(Return(1));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x1001, 1, false)).WillOnce(Return(1));
|
||||||
|
EXPECT_TRUE(read_exactly.ReadExactlyInt(0x1000, 2, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_DoubleSmallShort) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x20000, 2, false)).WillOnce(Return(1));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x20001, 1, false)).WillOnce(Return(0));
|
||||||
|
EXPECT_FALSE(read_exactly.ReadExactlyInt(0x20000, 2, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_DoubleSmallShortCanLog) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x20000, 2, true)).WillOnce(Return(1));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x20001, 1, true)).WillOnce(Return(0));
|
||||||
|
EXPECT_FALSE(read_exactly.ReadExactlyInt(0x20000, 2, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_Medium) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x80000000, 0x20000000, false))
|
||||||
|
.WillOnce(Return(0x10000000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x90000000, 0x10000000, false))
|
||||||
|
.WillOnce(Return(0x8000000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x98000000, 0x8000000, false))
|
||||||
|
.WillOnce(Return(0x4000000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9c000000, 0x4000000, false))
|
||||||
|
.WillOnce(Return(0x2000000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9e000000, 0x2000000, false))
|
||||||
|
.WillOnce(Return(0x1000000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9f000000, 0x1000000, false))
|
||||||
|
.WillOnce(Return(0x800000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9f800000, 0x800000, false))
|
||||||
|
.WillOnce(Return(0x400000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9fc00000, 0x400000, false))
|
||||||
|
.WillOnce(Return(0x200000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9fe00000, 0x200000, false))
|
||||||
|
.WillOnce(Return(0x100000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ff00000, 0x100000, false))
|
||||||
|
.WillOnce(Return(0x80000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ff80000, 0x80000, false))
|
||||||
|
.WillOnce(Return(0x40000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ffc0000, 0x40000, false))
|
||||||
|
.WillOnce(Return(0x20000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ffe0000, 0x20000, false))
|
||||||
|
.WillOnce(Return(0x10000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9fff0000, 0x10000, false))
|
||||||
|
.WillOnce(Return(0x8000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9fff8000, 0x8000, false))
|
||||||
|
.WillOnce(Return(0x4000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9fffc000, 0x4000, false))
|
||||||
|
.WillOnce(Return(0x2000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9fffe000, 0x2000, false))
|
||||||
|
.WillOnce(Return(0x1000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ffff000, 0x1000, false))
|
||||||
|
.WillOnce(Return(0x800));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ffff800, 0x800, false))
|
||||||
|
.WillOnce(Return(0x400));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ffffc00, 0x400, false))
|
||||||
|
.WillOnce(Return(0x200));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ffffe00, 0x200, false))
|
||||||
|
.WillOnce(Return(0x100));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9fffff00, 0x100, false))
|
||||||
|
.WillOnce(Return(0x80));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9fffff80, 0x80, false))
|
||||||
|
.WillOnce(Return(0x40));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9fffffc0, 0x40, false))
|
||||||
|
.WillOnce(Return(0x20));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9fffffe0, 0x20, false))
|
||||||
|
.WillOnce(Return(0x10));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ffffff0, 0x10, false))
|
||||||
|
.WillOnce(Return(0x8));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ffffff8, 0x8, false))
|
||||||
|
.WillOnce(Return(0x4));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ffffffc, 0x4, false))
|
||||||
|
.WillOnce(Return(0x2));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9ffffffe, 0x2, false))
|
||||||
|
.WillOnce(Return(0x1));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x9fffffff, 0x1, false))
|
||||||
|
.WillOnce(Return(0x1));
|
||||||
|
EXPECT_TRUE(read_exactly.ReadExactlyInt(0x80000000, 0x20000000, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_LargeSuccess) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
constexpr size_t max = std::numeric_limits<uint32_t>::max();
|
||||||
|
constexpr size_t increment = std::numeric_limits<int32_t>::max();
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0, max, false)).WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(increment, max - increment, false))
|
||||||
|
.WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(2 * increment, 1, false))
|
||||||
|
.WillOnce(Return(1));
|
||||||
|
EXPECT_TRUE(read_exactly.ReadExactlyInt(0, max, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_LargeShort) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0, 0xffffffff, false))
|
||||||
|
.WillOnce(Return(0x7fffffff));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x7fffffff, 0x80000000, false))
|
||||||
|
.WillOnce(Return(0x10000000));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x8fffffff, 0x70000000, false))
|
||||||
|
.WillOnce(Return(0));
|
||||||
|
EXPECT_FALSE(read_exactly.ReadExactlyInt(0, 0xffffffff, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_LargeFailure) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0, 0xffffffff, false))
|
||||||
|
.WillOnce(Return(0x7fffffff));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0x7fffffff, 0x80000000, false))
|
||||||
|
.WillOnce(Return(-1));
|
||||||
|
EXPECT_FALSE(read_exactly.ReadExactlyInt(0, 0xffffffff, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, ReadExactly_TripleMax) {
|
||||||
|
MockReadExactly read_exactly;
|
||||||
|
InSequence in_sequence;
|
||||||
|
constexpr size_t max = std::numeric_limits<size_t>::max();
|
||||||
|
constexpr size_t increment =
|
||||||
|
std::numeric_limits<std::make_signed<size_t>::type>::max();
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(0, max, false)).WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(increment, max - increment, false))
|
||||||
|
.WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(read_exactly, ReadInt(2 * increment, 1, false))
|
||||||
|
.WillOnce(Return(1));
|
||||||
|
EXPECT_TRUE(read_exactly.ReadExactlyInt(0, max, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockWriteAll : public internal::WriteAllInternal {
|
||||||
|
public:
|
||||||
|
MockWriteAll() : WriteAllInternal() {}
|
||||||
|
~MockWriteAll() {}
|
||||||
|
|
||||||
|
// Since it’s more convenient for the test to use uintptr_t than const void*,
|
||||||
|
// WriteAllInt() and WriteInt() adapt the types.
|
||||||
|
|
||||||
|
bool WriteAllInt(uintptr_t data, size_t size) {
|
||||||
|
return WriteAll(reinterpret_cast<const void*>(data), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOCK_METHOD2(WriteInt, FileOperationResult(uintptr_t, size_t));
|
||||||
|
|
||||||
|
// WriteAllInternal:
|
||||||
|
FileOperationResult Write(const void* data, size_t size) {
|
||||||
|
return WriteInt(reinterpret_cast<uintptr_t>(data), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(MockWriteAll);
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(FileIO, WriteAll_Zero) {
|
||||||
|
MockWriteAll write_all;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(write_all, WriteInt(_, _)).Times(0);
|
||||||
|
EXPECT_TRUE(write_all.WriteAllInt(100, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, WriteAll_SingleSmallSuccess) {
|
||||||
|
MockWriteAll write_all;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(write_all, WriteInt(1000, 1)).WillOnce(Return(1));
|
||||||
|
EXPECT_TRUE(write_all.WriteAllInt(1000, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, WriteAll_SingleSmallFailure) {
|
||||||
|
MockWriteAll write_all;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(write_all, WriteInt(1000, 1)).WillOnce(Return(-1));
|
||||||
|
EXPECT_FALSE(write_all.WriteAllInt(1000, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, WriteAll_DoubleSmall) {
|
||||||
|
MockWriteAll write_all;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x1000, 2)).WillOnce(Return(1));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x1001, 1)).WillOnce(Return(1));
|
||||||
|
EXPECT_TRUE(write_all.WriteAllInt(0x1000, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, WriteAll_Medium) {
|
||||||
|
MockWriteAll write_all;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x80000000, 0x20000000))
|
||||||
|
.WillOnce(Return(0x10000000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x90000000, 0x10000000))
|
||||||
|
.WillOnce(Return(0x8000000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x98000000, 0x8000000))
|
||||||
|
.WillOnce(Return(0x4000000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9c000000, 0x4000000))
|
||||||
|
.WillOnce(Return(0x2000000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9e000000, 0x2000000))
|
||||||
|
.WillOnce(Return(0x1000000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9f000000, 0x1000000))
|
||||||
|
.WillOnce(Return(0x800000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9f800000, 0x800000))
|
||||||
|
.WillOnce(Return(0x400000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9fc00000, 0x400000))
|
||||||
|
.WillOnce(Return(0x200000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9fe00000, 0x200000))
|
||||||
|
.WillOnce(Return(0x100000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ff00000, 0x100000))
|
||||||
|
.WillOnce(Return(0x80000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ff80000, 0x80000))
|
||||||
|
.WillOnce(Return(0x40000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ffc0000, 0x40000))
|
||||||
|
.WillOnce(Return(0x20000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ffe0000, 0x20000))
|
||||||
|
.WillOnce(Return(0x10000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9fff0000, 0x10000))
|
||||||
|
.WillOnce(Return(0x8000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9fff8000, 0x8000)).WillOnce(Return(0x4000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9fffc000, 0x4000)).WillOnce(Return(0x2000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9fffe000, 0x2000)).WillOnce(Return(0x1000));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ffff000, 0x1000)).WillOnce(Return(0x800));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ffff800, 0x800)).WillOnce(Return(0x400));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ffffc00, 0x400)).WillOnce(Return(0x200));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ffffe00, 0x200)).WillOnce(Return(0x100));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9fffff00, 0x100)).WillOnce(Return(0x80));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9fffff80, 0x80)).WillOnce(Return(0x40));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9fffffc0, 0x40)).WillOnce(Return(0x20));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9fffffe0, 0x20)).WillOnce(Return(0x10));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ffffff0, 0x10)).WillOnce(Return(0x8));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ffffff8, 0x8)).WillOnce(Return(0x4));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ffffffc, 0x4)).WillOnce(Return(0x2));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9ffffffe, 0x2)).WillOnce(Return(0x1));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x9fffffff, 0x1)).WillOnce(Return(0x1));
|
||||||
|
EXPECT_TRUE(write_all.WriteAllInt(0x80000000, 0x20000000));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, WriteAll_LargeSuccess) {
|
||||||
|
MockWriteAll write_all;
|
||||||
|
InSequence in_sequence;
|
||||||
|
constexpr size_t max = std::numeric_limits<uint32_t>::max();
|
||||||
|
constexpr size_t increment = std::numeric_limits<int32_t>::max();
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0, max)).WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(increment, max - increment))
|
||||||
|
.WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(2 * increment, 1)).WillOnce(Return(1));
|
||||||
|
EXPECT_TRUE(write_all.WriteAllInt(0, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, WriteAll_LargeFailure) {
|
||||||
|
MockWriteAll write_all;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0, 0xffffffff)).WillOnce(Return(0x7fffffff));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0x7fffffff, 0x80000000)).WillOnce(Return(-1));
|
||||||
|
EXPECT_FALSE(write_all.WriteAllInt(0, 0xffffffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileIO, WriteAll_TripleMax) {
|
||||||
|
MockWriteAll write_all;
|
||||||
|
InSequence in_sequence;
|
||||||
|
constexpr size_t max = std::numeric_limits<size_t>::max();
|
||||||
|
constexpr size_t increment =
|
||||||
|
std::numeric_limits<std::make_signed<size_t>::type>::max();
|
||||||
|
EXPECT_CALL(write_all, WriteInt(0, max)).WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(increment, max - increment))
|
||||||
|
.WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(write_all, WriteInt(2 * increment, 1)).WillOnce(Return(1));
|
||||||
|
EXPECT_TRUE(write_all.WriteAllInt(0, max));
|
||||||
|
}
|
||||||
|
|
||||||
void TestOpenFileForWrite(FileHandle (*opener)(const base::FilePath&,
|
void TestOpenFileForWrite(FileHandle (*opener)(const base::FilePath&,
|
||||||
FileWriteMode,
|
FileWriteMode,
|
||||||
FilePermissions)) {
|
FilePermissions)) {
|
||||||
|
@ -14,9 +14,11 @@
|
|||||||
|
|
||||||
#include "util/file/file_io.h"
|
#include "util/file/file_io.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
#include "base/files/file_path.h"
|
#include "base/files/file_path.h"
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
#include "base/numerics/safe_conversions.h"
|
|
||||||
#include "base/strings/utf_string_conversions.h"
|
#include "base/strings/utf_string_conversions.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -37,6 +39,16 @@ namespace crashpad {
|
|||||||
|
|
||||||
namespace {
|
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,
|
FileHandle OpenFileForOutput(DWORD access,
|
||||||
const base::FilePath& path,
|
const base::FilePath& path,
|
||||||
FileWriteMode mode,
|
FileWriteMode mode,
|
||||||
@ -70,18 +82,39 @@ FileHandle OpenFileForOutput(DWORD access,
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
const char kNativeReadFunctionName[] = "ReadFile";
|
const char kNativeReadFunctionName[] = "ReadFile";
|
||||||
const char kNativeWriteFunctionName[] = "WriteFile";
|
const char kNativeWriteFunctionName[] = "WriteFile";
|
||||||
|
|
||||||
// TODO(scottmg): Handle > DWORD-sized reads and writes if necessary.
|
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) {
|
FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) {
|
||||||
DCHECK(!IsSocketHandle(file));
|
DCHECK(!IsSocketHandle(file));
|
||||||
|
|
||||||
|
const DWORD read_size = static_cast<DWORD>(std::min(size, kMaxReadWriteSize));
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
DWORD size_dword = base::checked_cast<DWORD>(size);
|
|
||||||
DWORD bytes_read;
|
DWORD bytes_read;
|
||||||
BOOL success = ::ReadFile(file, buffer, size_dword, &bytes_read, nullptr);
|
BOOL success = ::ReadFile(file, buffer, read_size, &bytes_read, nullptr);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
if (GetLastError() == ERROR_BROKEN_PIPE) {
|
if (GetLastError() == ERROR_BROKEN_PIPE) {
|
||||||
// When reading a pipe and the write handle has been closed, ReadFile
|
// When reading a pipe and the write handle has been closed, ReadFile
|
||||||
@ -93,7 +126,7 @@ FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CHECK_NE(bytes_read, static_cast<DWORD>(-1));
|
CHECK_NE(bytes_read, static_cast<DWORD>(-1));
|
||||||
DCHECK_LE(bytes_read, size_dword);
|
DCHECK_LE(bytes_read, read_size);
|
||||||
if (bytes_read != 0 || GetFileType(file) != FILE_TYPE_PIPE) {
|
if (bytes_read != 0 || GetFileType(file) != FILE_TYPE_PIPE) {
|
||||||
// Zero bytes read for a file indicates reaching EOF. Zero bytes read from
|
// 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
|
// a pipe indicates only that there was a zero byte WriteFile issued on
|
||||||
@ -103,21 +136,6 @@ FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileOperationResult WriteFile(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.
|
|
||||||
DWORD size_dword = base::checked_cast<DWORD>(size);
|
|
||||||
DWORD bytes_written;
|
|
||||||
BOOL rv = ::WriteFile(file, buffer, size_dword, &bytes_written, nullptr);
|
|
||||||
if (!rv)
|
|
||||||
return -1;
|
|
||||||
CHECK_NE(bytes_written, static_cast<DWORD>(-1));
|
|
||||||
CHECK_EQ(bytes_written, size_dword);
|
|
||||||
return bytes_written;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileHandle OpenFileForRead(const base::FilePath& path) {
|
FileHandle OpenFileForRead(const base::FilePath& path) {
|
||||||
return CreateFile(path.value().c_str(),
|
return CreateFile(path.value().c_str(),
|
||||||
GENERIC_READ,
|
GENERIC_READ,
|
||||||
|
@ -20,36 +20,31 @@
|
|||||||
|
|
||||||
namespace crashpad {
|
namespace crashpad {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class FileReaderReadExactly final : public internal::ReadExactlyInternal {
|
||||||
|
public:
|
||||||
|
explicit FileReaderReadExactly(FileReaderInterface* file_reader)
|
||||||
|
: ReadExactlyInternal(), file_reader_(file_reader) {}
|
||||||
|
~FileReaderReadExactly() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// ReadExactlyInternal:
|
||||||
|
FileOperationResult Read(void* buffer, size_t size, bool can_log) override {
|
||||||
|
DCHECK(can_log);
|
||||||
|
return file_reader_->Read(buffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReaderInterface* file_reader_; // weak
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(FileReaderReadExactly);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
bool FileReaderInterface::ReadExactly(void* data, size_t size) {
|
bool FileReaderInterface::ReadExactly(void* data, size_t size) {
|
||||||
FileOperationResult expect = base::checked_cast<FileOperationResult>(size);
|
FileReaderReadExactly read_exactly(this);
|
||||||
char* data_c = static_cast<char*>(data);
|
return read_exactly.ReadExactly(data, size, true);
|
||||||
|
|
||||||
FileOperationResult total_bytes = 0;
|
|
||||||
while (size > 0) {
|
|
||||||
FileOperationResult bytes = Read(data, size);
|
|
||||||
if (bytes < 0) {
|
|
||||||
// Read() will have logged its own error.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DCHECK_LE(static_cast<size_t>(bytes), size);
|
|
||||||
|
|
||||||
if (bytes == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
data_c += bytes;
|
|
||||||
size -= bytes;
|
|
||||||
total_bytes += bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (total_bytes != expect) {
|
|
||||||
LOG(ERROR) << "ReadExactly(): expected " << expect << ", observed "
|
|
||||||
<< total_bytes;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WeakFileHandleFileReader::WeakFileHandleFileReader(FileHandle file_handle)
|
WeakFileHandleFileReader::WeakFileHandleFileReader(FileHandle file_handle)
|
||||||
@ -65,7 +60,7 @@ FileOperationResult WeakFileHandleFileReader::Read(void* data, size_t size) {
|
|||||||
base::checked_cast<FileOperationResult>(size);
|
base::checked_cast<FileOperationResult>(size);
|
||||||
FileOperationResult rv = ReadFile(file_handle_, data, size);
|
FileOperationResult rv = ReadFile(file_handle_, data, size);
|
||||||
if (rv < 0) {
|
if (rv < 0) {
|
||||||
PLOG(ERROR) << kNativeReadFunctionName;
|
PLOG(ERROR) << internal::kNativeReadFunctionName;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
205
util/file/file_reader_test.cc
Normal file
205
util/file/file_reader_test.cc
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// Copyright 2017 The Crashpad Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// 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_reader.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using testing::_;
|
||||||
|
using testing::InSequence;
|
||||||
|
using testing::Return;
|
||||||
|
|
||||||
|
class MockFileReader : public FileReaderInterface {
|
||||||
|
public:
|
||||||
|
MockFileReader() : FileReaderInterface() {}
|
||||||
|
~MockFileReader() override {}
|
||||||
|
|
||||||
|
// Since it’s more convenient for the test to use uintptr_t than void*,
|
||||||
|
// ReadExactlyInt() and ReadInt() adapt the types.
|
||||||
|
|
||||||
|
bool ReadExactlyInt(uintptr_t data, size_t size) {
|
||||||
|
return ReadExactly(reinterpret_cast<void*>(data), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOCK_METHOD2(ReadInt, FileOperationResult(uintptr_t, size_t));
|
||||||
|
|
||||||
|
// FileReaderInterface:
|
||||||
|
FileOperationResult Read(void* data, size_t size) {
|
||||||
|
return ReadInt(reinterpret_cast<uintptr_t>(data), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileSeekerInterface:
|
||||||
|
MOCK_METHOD2(Seek, FileOffset(FileOffset, int));
|
||||||
|
|
||||||
|
private:
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(MockFileReader);
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(FileReader, ReadExactly_Zero) {
|
||||||
|
MockFileReader file_reader;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(_, _)).Times(0);
|
||||||
|
EXPECT_CALL(file_reader, Seek(_, _)).Times(0);
|
||||||
|
EXPECT_TRUE(file_reader.ReadExactlyInt(100, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileReader, ReadExactly_SingleSmallSuccess) {
|
||||||
|
MockFileReader file_reader;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(1000, 1)).WillOnce(Return(1));
|
||||||
|
EXPECT_CALL(file_reader, Seek(_, _)).Times(0);
|
||||||
|
EXPECT_TRUE(file_reader.ReadExactlyInt(1000, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileReader, ReadExactly_SingleSmallFailure) {
|
||||||
|
MockFileReader file_reader;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(1000, 1)).WillOnce(Return(-1));
|
||||||
|
EXPECT_CALL(file_reader, Seek(_, _)).Times(0);
|
||||||
|
EXPECT_FALSE(file_reader.ReadExactlyInt(1000, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileReader, ReadExactly_DoubleSmallSuccess) {
|
||||||
|
MockFileReader file_reader;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x1000, 2)).WillOnce(Return(1));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x1001, 1)).WillOnce(Return(1));
|
||||||
|
EXPECT_CALL(file_reader, Seek(_, _)).Times(0);
|
||||||
|
EXPECT_TRUE(file_reader.ReadExactlyInt(0x1000, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileReader, ReadExactly_DoubleSmallShort) {
|
||||||
|
MockFileReader file_reader;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x20000, 2)).WillOnce(Return(1));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x20001, 1)).WillOnce(Return(0));
|
||||||
|
EXPECT_CALL(file_reader, Seek(_, _)).Times(0);
|
||||||
|
EXPECT_FALSE(file_reader.ReadExactlyInt(0x20000, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileReader, ReadExactly_Medium) {
|
||||||
|
MockFileReader file_reader;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x80000000, 0x20000000))
|
||||||
|
.WillOnce(Return(0x10000000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x90000000, 0x10000000))
|
||||||
|
.WillOnce(Return(0x8000000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x98000000, 0x8000000))
|
||||||
|
.WillOnce(Return(0x4000000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9c000000, 0x4000000))
|
||||||
|
.WillOnce(Return(0x2000000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9e000000, 0x2000000))
|
||||||
|
.WillOnce(Return(0x1000000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9f000000, 0x1000000))
|
||||||
|
.WillOnce(Return(0x800000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9f800000, 0x800000))
|
||||||
|
.WillOnce(Return(0x400000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9fc00000, 0x400000))
|
||||||
|
.WillOnce(Return(0x200000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9fe00000, 0x200000))
|
||||||
|
.WillOnce(Return(0x100000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ff00000, 0x100000))
|
||||||
|
.WillOnce(Return(0x80000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ff80000, 0x80000))
|
||||||
|
.WillOnce(Return(0x40000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ffc0000, 0x40000))
|
||||||
|
.WillOnce(Return(0x20000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ffe0000, 0x20000))
|
||||||
|
.WillOnce(Return(0x10000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9fff0000, 0x10000))
|
||||||
|
.WillOnce(Return(0x8000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9fff8000, 0x8000))
|
||||||
|
.WillOnce(Return(0x4000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9fffc000, 0x4000))
|
||||||
|
.WillOnce(Return(0x2000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9fffe000, 0x2000))
|
||||||
|
.WillOnce(Return(0x1000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ffff000, 0x1000)).WillOnce(Return(0x800));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ffff800, 0x800)).WillOnce(Return(0x400));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ffffc00, 0x400)).WillOnce(Return(0x200));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ffffe00, 0x200)).WillOnce(Return(0x100));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9fffff00, 0x100)).WillOnce(Return(0x80));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9fffff80, 0x80)).WillOnce(Return(0x40));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9fffffc0, 0x40)).WillOnce(Return(0x20));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9fffffe0, 0x20)).WillOnce(Return(0x10));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ffffff0, 0x10)).WillOnce(Return(0x8));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ffffff8, 0x8)).WillOnce(Return(0x4));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ffffffc, 0x4)).WillOnce(Return(0x2));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9ffffffe, 0x2)).WillOnce(Return(0x1));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x9fffffff, 0x1)).WillOnce(Return(0x1));
|
||||||
|
EXPECT_CALL(file_reader, Seek(_, _)).Times(0);
|
||||||
|
EXPECT_TRUE(file_reader.ReadExactlyInt(0x80000000, 0x20000000));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileReader, ReadExactly_LargeSuccess) {
|
||||||
|
MockFileReader file_reader;
|
||||||
|
InSequence in_sequence;
|
||||||
|
constexpr size_t max = std::numeric_limits<uint32_t>::max();
|
||||||
|
constexpr size_t increment = std::numeric_limits<int32_t>::max();
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0, max)).WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(increment, max - increment))
|
||||||
|
.WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(2 * increment, 1)).WillOnce(Return(1));
|
||||||
|
EXPECT_CALL(file_reader, Seek(_, _)).Times(0);
|
||||||
|
EXPECT_TRUE(file_reader.ReadExactlyInt(0, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileReader, ReadExactly_LargeShort) {
|
||||||
|
MockFileReader file_reader;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0, 0xffffffff)).WillOnce(Return(0x7fffffff));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x7fffffff, 0x80000000))
|
||||||
|
.WillOnce(Return(0x10000000));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x8fffffff, 0x70000000)).WillOnce(Return(0));
|
||||||
|
EXPECT_CALL(file_reader, Seek(_, _)).Times(0);
|
||||||
|
EXPECT_FALSE(file_reader.ReadExactlyInt(0, 0xffffffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileReader, ReadExactly_LargeFailure) {
|
||||||
|
MockFileReader file_reader;
|
||||||
|
InSequence in_sequence;
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0, 0xffffffff)).WillOnce(Return(0x7fffffff));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0x7fffffff, 0x80000000))
|
||||||
|
.WillOnce(Return(-1));
|
||||||
|
EXPECT_CALL(file_reader, Seek(_, _)).Times(0);
|
||||||
|
EXPECT_FALSE(file_reader.ReadExactlyInt(0, 0xffffffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileReader, ReadExactly_TripleMax) {
|
||||||
|
MockFileReader file_reader;
|
||||||
|
InSequence in_sequence;
|
||||||
|
constexpr size_t max = std::numeric_limits<size_t>::max();
|
||||||
|
constexpr size_t increment =
|
||||||
|
std::numeric_limits<std::make_signed<size_t>::type>::max();
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(0, max)).WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(increment, max - increment))
|
||||||
|
.WillOnce(Return(increment));
|
||||||
|
EXPECT_CALL(file_reader, ReadInt(2 * increment, 1)).WillOnce(Return(1));
|
||||||
|
EXPECT_CALL(file_reader, Seek(_, _)).Times(0);
|
||||||
|
EXPECT_TRUE(file_reader.ReadExactlyInt(0, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
@ -37,6 +37,7 @@
|
|||||||
'sources': [
|
'sources': [
|
||||||
'file/delimited_file_reader_test.cc',
|
'file/delimited_file_reader_test.cc',
|
||||||
'file/file_io_test.cc',
|
'file/file_io_test.cc',
|
||||||
|
'file/file_reader_test.cc',
|
||||||
'file/string_file_test.cc',
|
'file/string_file_test.cc',
|
||||||
'mac/launchd_test.mm',
|
'mac/launchd_test.mm',
|
||||||
'mac/mac_util_test.mm',
|
'mac/mac_util_test.mm',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user