Make file_io reads more rational and predictable

ReadFile() attempted to continue reading after a short read. In most
cases, this is fine. However, ReadFile() would keep trying to fill a
partially-filled buffer until experiencing a 0-length read(), signaling
end-of-file. For certain weird file descriptors like terminal input, EOF
is an ephemeral condition, and attempting to read beyond EOF doesn’t
actually return 0 (EOF) provided that they remain open, it will block
waiting for more input. Consequently, ReadFile() and anything based on
ReadFile() had an undocumented and quirky interface, which was that any
short read that it returned (not an underlying short read) actually
indicated EOF.

This facet of ReadFile() was unexpected, so it’s being removed. The new
behavior is that ReadFile() will return an underlying short read. The
behavior of FileReaderInterface::Read() is updated in accordance with
this change.

Upon experiencing a short read, the caller can determine the best
action. Most callers were already prepared for this behavior. Outside of
util/file, only crashpad_database_util properly implemented EOF
detection according to previous semantics, and adapting it to new
semantics is trivial.

Callers who require an exact-length read can use the new
ReadFileExactly(), or the newly renamed LoggingReadFileExactly() or
CheckedReadFileExactly(). These functions will retry following a short
read. The renamed functions were previously called LoggingReadFile() and
CheckedReadFile(), but those names implied that they were simply
wrapping ReadFile(), which is not the case. They wrapped ReadFile() and
further, insisted on a full read. Since ReadFile()’s semantics are now
changing but these functions’ are not, they’re now even more distinct
from ReadFile(), and must be renamed to avoid confusion.

Test: *
Change-Id: I06b77e0d6ad8719bd2eb67dab93a8740542dd908
Reviewed-on: https://chromium-review.googlesource.com/456676
Reviewed-by: Robert Sesek <rsesek@chromium.org>
This commit is contained in:
Mark Mentovai 2017-03-16 13:36:38 -04:00
parent 51b21d8874
commit 00b6442752
31 changed files with 246 additions and 176 deletions

View File

@ -77,8 +77,8 @@ std::string ReadRestOfFileAsString(FileHandle file) {
DCHECK_GT(end, read_from); DCHECK_GT(end, read_from);
size_t data_length = static_cast<size_t>(end - read_from); size_t data_length = static_cast<size_t>(end - read_from);
std::string buffer(data_length, '\0'); std::string buffer(data_length, '\0');
return LoggingReadFile(file, &buffer[0], data_length) ? buffer return LoggingReadFileExactly(file, &buffer[0], data_length) ? buffer
: std::string(); : std::string();
} }
// Helper structures, and conversions ------------------------------------------ // Helper structures, and conversions ------------------------------------------
@ -417,7 +417,7 @@ void Metadata::Read() {
} }
MetadataFileHeader header; MetadataFileHeader header;
if (!LoggingReadFile(handle_.get(), &header, sizeof(header))) { if (!LoggingReadFileExactly(handle_.get(), &header, sizeof(header))) {
LOG(ERROR) << "failed to read header"; LOG(ERROR) << "failed to read header";
return; return;
} }
@ -438,7 +438,7 @@ void Metadata::Read() {
std::vector<ReportDisk> reports; std::vector<ReportDisk> reports;
if (header.num_records > 0) { if (header.num_records > 0) {
std::vector<MetadataFileReportRecord> records(header.num_records); std::vector<MetadataFileReportRecord> records(header.num_records);
if (!LoggingReadFile( if (!LoggingReadFileExactly(
handle_.get(), &records[0], records_size.ValueOrDie())) { handle_.get(), &records[0], records_size.ValueOrDie())) {
LOG(ERROR) << "failed to read records"; LOG(ERROR) << "failed to read records";
return; return;

View File

@ -246,13 +246,10 @@ bool Settings::ReadSettings(FileHandle handle,
if (LoggingSeekFile(handle, 0, SEEK_SET) != 0) if (LoggingSeekFile(handle, 0, SEEK_SET) != 0)
return false; return false;
bool read_result; bool read_result =
if (log_read_error) { log_read_error
read_result = LoggingReadFile(handle, out_data, sizeof(*out_data)); ? LoggingReadFileExactly(handle, out_data, sizeof(*out_data))
} else { : ReadFileExactly(handle, out_data, sizeof(*out_data));
read_result =
ReadFile(handle, out_data, sizeof(*out_data)) == sizeof(*out_data);
}
if (!read_result) if (!read_result)
return false; return false;

View File

@ -90,7 +90,8 @@ int CrashOtherProgram(int argc, wchar_t* argv[]) {
// Wait until it's ready. // Wait until it's ready.
char c; char c;
if (!LoggingReadFile(child.stdout_read_handle(), &c, sizeof(c)) || c != ' ') { if (!LoggingReadFileExactly(child.stdout_read_handle(), &c, sizeof(c)) ||
c != ' ') {
LOG(ERROR) << "failed child communication"; LOG(ERROR) << "failed child communication";
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -28,7 +28,7 @@ class ModuleAnnotationsMultiprocessTest final : public WinMultiprocess {
void WinMultiprocessParent() override { void WinMultiprocessParent() override {
// Read the child executable module. // Read the child executable module.
HMODULE module = nullptr; HMODULE module = nullptr;
CheckedReadFile(ReadPipeHandle(), &module, sizeof(module)); CheckedReadFileExactly(ReadPipeHandle(), &module, sizeof(module));
// Reopen the child process with necessary access. // Reopen the child process with necessary access.
HANDLE process_handle = HANDLE process_handle =
@ -70,7 +70,7 @@ class ModuleAnnotationsMultiprocessTest final : public WinMultiprocess {
// Wait until a signal from the parent process to terminate. // Wait until a signal from the parent process to terminate.
char c; char c;
CheckedReadFile(ReadPipeHandle(), &c, sizeof(c)); CheckedReadFileExactly(ReadPipeHandle(), &c, sizeof(c));
} }
}; };

View File

@ -245,7 +245,7 @@ class TestMachOImageAnnotationsReader final
// Wait for the child process to indicate that its done setting up its // Wait for the child process to indicate that its done setting up its
// annotations via the CrashpadInfo interface. // annotations via the CrashpadInfo interface.
char c; char c;
CheckedReadFile(ReadPipeHandle(), &c, sizeof(c)); CheckedReadFileExactly(ReadPipeHandle(), &c, sizeof(c));
// Verify the “simple map” annotations set via the CrashpadInfo interface. // Verify the “simple map” annotations set via the CrashpadInfo interface.
const std::vector<ProcessReader::Module>& modules = const std::vector<ProcessReader::Module>& modules =
@ -333,7 +333,7 @@ class TestMachOImageAnnotationsReader final
CheckedWriteFile(WritePipeHandle(), &c, sizeof(c)); CheckedWriteFile(WritePipeHandle(), &c, sizeof(c));
// Wait for the parent to indicate that its safe to crash. // Wait for the parent to indicate that its safe to crash.
CheckedReadFile(ReadPipeHandle(), &c, sizeof(c)); CheckedReadFileExactly(ReadPipeHandle(), &c, sizeof(c));
// Direct an exception message to the exception server running in the // Direct an exception message to the exception server running in the
// parent. // parent.

View File

@ -105,7 +105,7 @@ class ProcessReaderChild final : public MachMultiprocess {
FileHandle read_handle = ReadPipeHandle(); FileHandle read_handle = ReadPipeHandle();
mach_vm_address_t address; mach_vm_address_t address;
CheckedReadFile(read_handle, &address, sizeof(address)); CheckedReadFileExactly(read_handle, &address, sizeof(address));
std::string read_string; std::string read_string;
ASSERT_TRUE(process_reader.Memory()->ReadCString(address, &read_string)); ASSERT_TRUE(process_reader.Memory()->ReadCString(address, &read_string));
@ -448,15 +448,15 @@ class ProcessReaderThreadedChild final : public MachMultiprocess {
thread_index < thread_count_ + 1; thread_index < thread_count_ + 1;
++thread_index) { ++thread_index) {
uint64_t thread_id; uint64_t thread_id;
CheckedReadFile(read_handle, &thread_id, sizeof(thread_id)); CheckedReadFileExactly(read_handle, &thread_id, sizeof(thread_id));
TestThreadPool::ThreadExpectation expectation; TestThreadPool::ThreadExpectation expectation;
CheckedReadFile(read_handle, CheckedReadFileExactly(read_handle,
&expectation.stack_address, &expectation.stack_address,
sizeof(expectation.stack_address)); sizeof(expectation.stack_address));
CheckedReadFile(read_handle, CheckedReadFileExactly(read_handle,
&expectation.suspend_count, &expectation.suspend_count,
sizeof(expectation.suspend_count)); sizeof(expectation.suspend_count));
// There cant be any duplicate thread IDs. // There cant be any duplicate thread IDs.
EXPECT_EQ(0u, thread_map.count(thread_id)); EXPECT_EQ(0u, thread_map.count(thread_id));
@ -730,7 +730,8 @@ class ProcessReaderModulesChild final : public MachMultiprocess {
FileHandle read_handle = ReadPipeHandle(); FileHandle read_handle = ReadPipeHandle();
uint32_t expect_modules; uint32_t expect_modules;
CheckedReadFile(read_handle, &expect_modules, sizeof(expect_modules)); CheckedReadFileExactly(
read_handle, &expect_modules, sizeof(expect_modules));
ASSERT_EQ(expect_modules, modules.size()); ASSERT_EQ(expect_modules, modules.size());
@ -740,16 +741,17 @@ class ProcessReaderModulesChild final : public MachMultiprocess {
"index %zu, name %s", index, modules[index].name.c_str())); "index %zu, name %s", index, modules[index].name.c_str()));
uint32_t expect_name_length; uint32_t expect_name_length;
CheckedReadFile( CheckedReadFileExactly(
read_handle, &expect_name_length, sizeof(expect_name_length)); read_handle, &expect_name_length, sizeof(expect_name_length));
// The NUL terminator is not read. // The NUL terminator is not read.
std::string expect_name(expect_name_length, '\0'); std::string expect_name(expect_name_length, '\0');
CheckedReadFile(read_handle, &expect_name[0], expect_name_length); CheckedReadFileExactly(read_handle, &expect_name[0], expect_name_length);
EXPECT_EQ(expect_name, modules[index].name); EXPECT_EQ(expect_name, modules[index].name);
mach_vm_address_t expect_address; mach_vm_address_t expect_address;
CheckedReadFile(read_handle, &expect_address, sizeof(expect_address)); CheckedReadFileExactly(
read_handle, &expect_address, sizeof(expect_address));
ASSERT_TRUE(modules[index].reader); ASSERT_TRUE(modules[index].reader);
EXPECT_EQ(expect_address, modules[index].reader->Address()); EXPECT_EQ(expect_address, modules[index].reader->Address());

View File

@ -43,7 +43,7 @@ int wmain(int argc, wchar_t* argv[]) {
HANDLE in = GetStdHandle(STD_INPUT_HANDLE); HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
PCHECK(in != INVALID_HANDLE_VALUE) << "GetStdHandle"; PCHECK(in != INVALID_HANDLE_VALUE) << "GetStdHandle";
CheckedReadFile(in, &c, sizeof(c)); CheckedReadFileExactly(in, &c, sizeof(c));
CHECK(c == 'd' || c == ' '); CHECK(c == 'd' || c == ' ');
// If 'd' we crash with a debug break, otherwise exit normally. // If 'd' we crash with a debug break, otherwise exit normally.

View File

@ -42,7 +42,7 @@ int wmain(int argc, wchar_t* argv[]) {
HANDLE in = GetStdHandle(STD_INPUT_HANDLE); HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
PCHECK(in != INVALID_HANDLE_VALUE) << "GetStdHandle"; PCHECK(in != INVALID_HANDLE_VALUE) << "GetStdHandle";
crashpad::CheckedReadFile(in, &c, sizeof(c)); crashpad::CheckedReadFileExactly(in, &c, sizeof(c));
CHECK(c == 'd' || c == ' '); CHECK(c == 'd' || c == ' ');
// If 'd' we crash with a debug break, otherwise exit normally. // If 'd' we crash with a debug break, otherwise exit normally.

View File

@ -148,9 +148,9 @@ void TestCrashingChild(const base::string16& directory_modification) {
// The child tells us (approximately) where it will crash. // The child tells us (approximately) where it will crash.
WinVMAddress break_near_address; WinVMAddress break_near_address;
LoggingReadFile(child.stdout_read_handle(), LoggingReadFileExactly(child.stdout_read_handle(),
&break_near_address, &break_near_address,
sizeof(break_near_address)); sizeof(break_near_address));
delegate.set_break_near(break_near_address); delegate.set_break_near(break_near_address);
// Wait for the child to crash and the exception information to be validated. // Wait for the child to crash and the exception information to be validated.
@ -250,9 +250,9 @@ void TestDumpWithoutCrashingChild(
// The child tells us (approximately) where it will capture a dump. // The child tells us (approximately) where it will capture a dump.
WinVMAddress dump_near_address; WinVMAddress dump_near_address;
LoggingReadFile(child.stdout_read_handle(), LoggingReadFileExactly(child.stdout_read_handle(),
&dump_near_address, &dump_near_address,
sizeof(dump_near_address)); sizeof(dump_near_address));
delegate.set_dump_near(dump_near_address); delegate.set_dump_near(dump_near_address);
// Wait for the child to crash and the exception information to be validated. // Wait for the child to crash and the exception information to be validated.

View File

@ -58,7 +58,7 @@ void TestExtraMemoryRanges(TestType type,
// Wait for the child process to indicate that it's done setting up its // Wait for the child process to indicate that it's done setting up its
// annotations via the CrashpadInfo interface. // annotations via the CrashpadInfo interface.
char c; char c;
CheckedReadFile(child.stdout_read_handle(), &c, sizeof(c)); CheckedReadFileExactly(child.stdout_read_handle(), &c, sizeof(c));
ProcessSnapshotWin snapshot; ProcessSnapshotWin snapshot;
ASSERT_TRUE(snapshot.Initialize( ASSERT_TRUE(snapshot.Initialize(

View File

@ -62,7 +62,7 @@ void TestAnnotationsOnCrash(TestType type,
// Wait for the child process to indicate that it's done setting up its // Wait for the child process to indicate that it's done setting up its
// annotations via the CrashpadInfo interface. // annotations via the CrashpadInfo interface.
char c; char c;
CheckedReadFile(child.stdout_read_handle(), &c, sizeof(c)); CheckedReadFileExactly(child.stdout_read_handle(), &c, sizeof(c));
ProcessReaderWin process_reader; ProcessReaderWin process_reader;
ASSERT_TRUE(process_reader.Initialize(child.process_handle(), ASSERT_TRUE(process_reader.Initialize(child.process_handle(),

View File

@ -69,7 +69,7 @@ class ProcessReaderChild final : public WinMultiprocess {
#endif #endif
WinVMAddress address; WinVMAddress address;
CheckedReadFile(ReadPipeHandle(), &address, sizeof(address)); CheckedReadFileExactly(ReadPipeHandle(), &address, sizeof(address));
char buffer[sizeof(kTestMemory)]; char buffer[sizeof(kTestMemory)];
ASSERT_TRUE( ASSERT_TRUE(
@ -142,7 +142,7 @@ class ProcessReaderChildThreadSuspendCount final : public WinMultiprocess {
void WinMultiprocessParent() override { void WinMultiprocessParent() override {
char c; char c;
CheckedReadFile(ReadPipeHandle(), &c, sizeof(c)); CheckedReadFileExactly(ReadPipeHandle(), &c, sizeof(c));
ASSERT_EQ(' ', c); ASSERT_EQ(' ', c);
{ {

View File

@ -48,7 +48,8 @@ void TestImageReaderChild(const base::string16& directory_modification) {
child.Start(); child.Start();
char c; char c;
ASSERT_TRUE(LoggingReadFile(child.stdout_read_handle(), &c, sizeof(c))); ASSERT_TRUE(
LoggingReadFileExactly(child.stdout_read_handle(), &c, sizeof(c)));
ASSERT_EQ(' ', c); ASSERT_EQ(' ', c);
ScopedProcessSuspend suspend(child.process_handle()); ScopedProcessSuspend suspend(child.process_handle());

View File

@ -39,7 +39,7 @@ class TestMultiprocessExec final : public MultiprocessExec {
char c = 'z'; char c = 'z';
ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, 1)); ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, 1));
ASSERT_TRUE(LoggingReadFile(ReadPipeHandle(), &c, 1)); ASSERT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, 1));
EXPECT_EQ('Z', c); EXPECT_EQ('Z', c);
} }

View File

@ -39,11 +39,11 @@ class TestMultiprocess final : public Multiprocess {
void MultiprocessParent() override { void MultiprocessParent() override {
FileHandle read_handle = ReadPipeHandle(); FileHandle read_handle = ReadPipeHandle();
char c; char c;
CheckedReadFile(read_handle, &c, 1); CheckedReadFileExactly(read_handle, &c, 1);
EXPECT_EQ('M', c); EXPECT_EQ('M', c);
pid_t pid; pid_t pid;
CheckedReadFile(read_handle, &pid, sizeof(pid)); CheckedReadFileExactly(read_handle, &pid, sizeof(pid));
EXPECT_EQ(pid, ChildPID()); EXPECT_EQ(pid, ChildPID());
c = 'm'; c = 'm';
@ -63,7 +63,7 @@ class TestMultiprocess final : public Multiprocess {
pid_t pid = getpid(); pid_t pid = getpid();
CheckedWriteFile(write_handle, &pid, sizeof(pid)); CheckedWriteFile(write_handle, &pid, sizeof(pid));
CheckedReadFile(ReadPipeHandle(), &c, 1); CheckedReadFileExactly(ReadPipeHandle(), &c, 1);
EXPECT_EQ('m', c); EXPECT_EQ('m', c);
} }

View File

@ -203,7 +203,7 @@ std::unique_ptr<WinChildProcess::Handles> WinChildProcess::Launch() {
// immediately, and test code expects process initialization to have // immediately, and test code expects process initialization to have
// completed so it can, for example, read the process memory. // completed so it can, for example, read the process memory.
char c; char c;
if (!LoggingReadFile(handles_for_parent->read.get(), &c, sizeof(c))) { if (!LoggingReadFileExactly(handles_for_parent->read.get(), &c, sizeof(c))) {
ADD_FAILURE() << "LoggedReadFile"; ADD_FAILURE() << "LoggedReadFile";
return std::unique_ptr<Handles>(); return std::unique_ptr<Handles>();
} }

View File

@ -597,7 +597,7 @@ int DatabaseUtilMain(int argc, char* argv[]) {
!LoggingWriteFile(new_report->handle, buf, read_result)) { !LoggingWriteFile(new_report->handle, buf, read_result)) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
} while (read_result == sizeof(buf)); } while (read_result > 0);
call_error_writing_crash_report.Disarm(); call_error_writing_crash_report.Disarm();

View File

@ -19,38 +19,71 @@
namespace crashpad { namespace crashpad {
bool LoggingReadFile(FileHandle file, void* buffer, size_t size) { namespace {
bool ReadFileExactlyInternal(FileHandle file,
void* buffer,
size_t size,
bool can_log) {
FileOperationResult expect = base::checked_cast<FileOperationResult>(size); FileOperationResult expect = base::checked_cast<FileOperationResult>(size);
FileOperationResult rv = ReadFile(file, buffer, size); char* buffer_c = static_cast<char*>(buffer);
if (rv < 0) {
PLOG(ERROR) << "read"; FileOperationResult total_bytes = 0;
return false; while (size > 0) {
FileOperationResult bytes = ReadFile(file, buffer, size);
if (bytes < 0) {
PLOG_IF(ERROR, can_log) << kNativeReadFunctionName;
return false;
}
DCHECK_LE(static_cast<size_t>(bytes), size);
if (bytes == 0) {
break;
}
buffer_c += bytes;
size -= bytes;
total_bytes += bytes;
} }
if (rv != expect) {
LOG(ERROR) << "read: expected " << expect << ", observed " << rv; if (total_bytes != expect) {
LOG_IF(ERROR, can_log) << kNativeReadFunctionName << ": expected " << expect
<< ", observed " << total_bytes;
return false; return false;
} }
return true; return true;
} }
} // namespace
bool ReadFileExactly(FileHandle file, void* buffer, size_t size) {
return ReadFileExactlyInternal(file, buffer, size, false);
}
bool LoggingReadFileExactly(FileHandle file, void* buffer, size_t size) {
return ReadFileExactlyInternal(file, buffer, size, true);
}
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); FileOperationResult expect = base::checked_cast<FileOperationResult>(size);
FileOperationResult rv = WriteFile(file, buffer, size); FileOperationResult rv = WriteFile(file, buffer, size);
if (rv < 0) { if (rv < 0) {
PLOG(ERROR) << "write"; PLOG(ERROR) << kNativeWriteFunctionName;
return false; return false;
} }
if (rv != expect) { if (rv != expect) {
LOG(ERROR) << "write: expected " << expect << ", observed " << rv; LOG(ERROR) << kNativeWriteFunctionName << ": expected " << expect
<< ", observed " << rv;
return false; return false;
} }
return true; return true;
} }
void CheckedReadFile(FileHandle file, void* buffer, size_t size) { void CheckedReadFileExactly(FileHandle file, void* buffer, size_t size) {
CHECK(LoggingReadFile(file, buffer, size)); CHECK(LoggingReadFileExactly(file, buffer, size));
} }
void CheckedWriteFile(FileHandle file, const void* buffer, size_t size) { void CheckedWriteFile(FileHandle file, const void* buffer, size_t size) {
@ -61,9 +94,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) << "read"; PCHECK(rv == 0) << kNativeReadFunctionName;
} else { } else {
CHECK_EQ(rv, 0) << "read"; CHECK_EQ(rv, 0) << kNativeReadFunctionName;
} }
} }

View File

@ -114,20 +114,33 @@ enum class FileLocking : bool {
kExclusive, kExclusive,
}; };
//! \brief Reads from a file, retrying when interrupted on POSIX or following a //! \brief The name of the native read function used by ReadFile().
//! short read.
//! //!
//! This function reads into \a buffer, stopping only when \a size bytes have //! This value may be useful for logging.
//! been read or when end-of-file has been reached. On Windows, reading from //!
//! sockets is not currently supported. //! \sa kNativeWriteFunctionName
extern const char kNativeReadFunctionName[];
//! \brief The name of the native write function used by WriteFile().
//!
//! This value may be useful for logging.
//!
//! \sa kNativeReadFunctionName
extern const char kNativeWriteFunctionName[];
//! \brief Reads from a file, retrying when interrupted on POSIX.
//!
//! This function reads into \a buffer. Fewer than \a size bytes may be read.
//! On Windows, reading from sockets is not currently supported.
//! //!
//! \return The number of bytes read and placed into \a buffer, or `-1` on //! \return The number of bytes read and placed into \a buffer, or `-1` on
//! error, with `errno` or `GetLastError()` set appropriately. On error, a //! error, with `errno` or `GetLastError()` set appropriately. On error, a
//! portion of \a file may have been read into \a buffer. //! portion of \a file may have been read into \a buffer.
//! //!
//! \sa WriteFile //! \sa WriteFile
//! \sa LoggingReadFile //! \sa ReadFileExactly
//! \sa CheckedReadFile //! \sa LoggingReadFileExactly
//! \sa CheckedReadFileExactly
//! \sa CheckedReadFileAtEOF //! \sa CheckedReadFileAtEOF
FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size); FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size);
@ -146,27 +159,50 @@ FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size);
//! \sa CheckedWriteFile //! \sa CheckedWriteFile
FileOperationResult WriteFile(FileHandle file, const void* buffer, size_t size); FileOperationResult WriteFile(FileHandle file, const void* buffer, size_t size);
//! \brief Wraps ReadFile(), ensuring that exactly \a size bytes are read. //! \brief Wraps ReadFile(), retrying following a short read, ensuring that
//! exactly \a size bytes are read.
//! //!
//! \return `true` on success. If \a size is out of the range of possible //! If \a size is out of the range of possible ReadFile() return values, this
//! ReadFile() return values, if the underlying ReadFile() fails, or if //! function causes execution to terminate without returning.
//! other than \a size bytes were read, this function logs a message and //!
//! \return `true` on success. If the underlying ReadFile() fails, or if fewer
//! than \a size bytes were read, this function logs a message and
//! returns `false`. //! returns `false`.
//! //!
//! \sa LoggingWriteFile //! \sa LoggingWriteFile
//! \sa ReadFile //! \sa ReadFile
//! \sa CheckedReadFile //! \sa LoggingReadFileExactly
//! \sa CheckedReadFileExactly
//! \sa CheckedReadFileAtEOF //! \sa CheckedReadFileAtEOF
bool LoggingReadFile(FileHandle file, void* buffer, size_t size); bool ReadFileExactly(FileHandle file, void* buffer, size_t size);
//! \brief Wraps ReadFile(), retrying following a short read, ensuring that
//! 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
//! than \a size bytes were read, this function logs a message and
//! returns `false`.
//!
//! \sa LoggingWriteFile
//! \sa ReadFile
//! \sa ReadFileExactly
//! \sa CheckedReadFileExactly
//! \sa CheckedReadFileAtEOF
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.
//! //!
//! \return `true` on success. If \a size is out of the range of possible //! If \a size is out of the range of possible WriteFile() return values, this
//! WriteFile() return values, if the underlying WriteFile() fails, or if //! function causes execution to terminate without returning.
//! other than \a size bytes were written, this function logs a message and //!
//! \return `true` on success. If the underlying WriteFile() fails, or if fewer
//! than \a size bytes were written, this function logs a message and
//! returns `false`. //! returns `false`.
//! //!
//! \sa LoggingReadFile //! \sa LoggingReadFileExactly
//! \sa WriteFile //! \sa WriteFile
//! \sa CheckedWriteFile //! \sa CheckedWriteFile
bool LoggingWriteFile(FileHandle file, const void* buffer, size_t size); bool LoggingWriteFile(FileHandle file, const void* buffer, size_t size);
@ -174,22 +210,22 @@ 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 \a size is out of the range of possible ReadFile() return values, if the
//! underlying ReadFile() fails, or if other than \a size bytes were read, this //! underlying ReadFile() fails, or if fewer than \a size bytes were read, this
//! function causes execution to terminate without returning. //! function causes execution to terminate without returning.
//! //!
//! \sa CheckedWriteFile //! \sa CheckedWriteFile
//! \sa ReadFile //! \sa ReadFile
//! \sa LoggingReadFile //! \sa LoggingReadFileExactly
//! \sa CheckedReadFileAtEOF //! \sa CheckedReadFileAtEOF
void CheckedReadFile(FileHandle file, void* buffer, size_t size); 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 \a size is out of the range of possible WriteFile() return values, if the
//! underlying WriteFile() fails, or if other than \a size bytes were written, //! underlying WriteFile() fails, or if fewer than \a size bytes were written,
//! this function causes execution to terminate without returning. //! this function causes execution to terminate without returning.
//! //!
//! \sa CheckedReadFile //! \sa CheckedReadFileExactly
//! \sa WriteFile //! \sa WriteFile
//! \sa LoggingWriteFile //! \sa LoggingWriteFile
void CheckedWriteFile(FileHandle file, const void* buffer, size_t size); void CheckedWriteFile(FileHandle file, const void* buffer, size_t size);
@ -200,7 +236,7 @@ void CheckedWriteFile(FileHandle file, const void* buffer, size_t size);
//! If the underlying ReadFile() fails, or if a byte actually is read, this //! If the underlying ReadFile() fails, or if a byte actually is read, this
//! function causes execution to terminate without returning. //! function causes execution to terminate without returning.
//! //!
//! \sa CheckedReadFile //! \sa CheckedReadFileExactly
//! \sa ReadFile //! \sa ReadFile
void CheckedReadFileAtEOF(FileHandle file); void CheckedReadFileAtEOF(FileHandle file);

View File

@ -24,54 +24,6 @@
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "base/posix/eintr_wrapper.h" #include "base/posix/eintr_wrapper.h"
namespace {
struct ReadTraits {
using VoidBufferType = void*;
using CharBufferType = char*;
static crashpad::FileOperationResult Operate(int fd,
CharBufferType buffer,
size_t size) {
return read(fd, buffer, size);
}
};
struct WriteTraits {
using VoidBufferType = const void*;
using CharBufferType = const char*;
static crashpad::FileOperationResult Operate(int fd,
CharBufferType buffer,
size_t size) {
return write(fd, buffer, size);
}
};
template <typename Traits>
crashpad::FileOperationResult
ReadOrWrite(int fd, typename Traits::VoidBufferType buffer, size_t size) {
typename Traits::CharBufferType buffer_c =
reinterpret_cast<typename Traits::CharBufferType>(buffer);
crashpad::FileOperationResult total_bytes = 0;
while (size > 0) {
crashpad::FileOperationResult bytes =
HANDLE_EINTR(Traits::Operate(fd, buffer_c, size));
if (bytes < 0) {
return bytes;
} else if (bytes == 0) {
break;
}
buffer_c += bytes;
size -= bytes;
total_bytes += bytes;
}
return total_bytes;
}
} // namespace
namespace crashpad { namespace crashpad {
namespace { namespace {
@ -108,14 +60,44 @@ FileHandle OpenFileForOutput(int rdwr_or_wronly,
} // namespace } // namespace
const char kNativeReadFunctionName[] = "read";
const char kNativeWriteFunctionName[] = "write";
// TODO(mark): Handle > ssize_t-sized reads and writes if necessary. The
// standard leaves this implementation-defined. Some systems return EINVAL in
// this case. ReadFile() and WriteFile() could enforce this behavior.
FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) { FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) {
return ReadOrWrite<ReadTraits>(file, buffer, 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, FileOperationResult WriteFile(FileHandle file,
const void* buffer, const void* buffer,
size_t size) { size_t size) {
return ReadOrWrite<WriteTraits>(file, buffer, size); const char* buffer_c = static_cast<const char*>(buffer);
FileOperationResult total_bytes = 0;
while (size > 0) {
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) {

View File

@ -70,37 +70,37 @@ FileHandle OpenFileForOutput(DWORD access,
} // namespace } // namespace
// TODO(scottmg): Handle > DWORD sized writes if necessary. const char kNativeReadFunctionName[] = "ReadFile";
const char kNativeWriteFunctionName[] = "WriteFile";
// TODO(scottmg): Handle > DWORD-sized reads and writes if necessary.
FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) { FileOperationResult ReadFile(FileHandle file, void* buffer, size_t size) {
DCHECK(!IsSocketHandle(file)); DCHECK(!IsSocketHandle(file));
DWORD size_dword = base::checked_cast<DWORD>(size);
DWORD total_read = 0; while (true) {
char* buffer_c = reinterpret_cast<char*>(buffer); DWORD size_dword = base::checked_cast<DWORD>(size);
while (size_dword > 0) {
DWORD bytes_read; DWORD bytes_read;
BOOL success = ::ReadFile(file, buffer_c, size_dword, &bytes_read, nullptr); BOOL success = ::ReadFile(file, buffer, size_dword, &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
// fails with ERROR_BROKEN_PIPE, but only once all pending data has been // fails with ERROR_BROKEN_PIPE, but only once all pending data has been
// read. // read. Treat this as EOF.
break; return 0;
} else if (GetLastError() != ERROR_MORE_DATA) {
return -1;
} }
} else if (bytes_read == 0 && GetFileType(file) != FILE_TYPE_PIPE) { return -1;
}
CHECK_NE(bytes_read, static_cast<DWORD>(-1));
DCHECK_LE(bytes_read, size_dword);
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
// the other end, so continue reading. // the other end, so continue reading.
break; return bytes_read;
} }
buffer_c += bytes_read;
size_dword -= bytes_read;
total_read += bytes_read;
} }
return total_read;
} }
FileOperationResult WriteFile(FileHandle file, FileOperationResult WriteFile(FileHandle file,
@ -113,6 +113,7 @@ FileOperationResult WriteFile(FileHandle file,
BOOL rv = ::WriteFile(file, buffer, size_dword, &bytes_written, nullptr); BOOL rv = ::WriteFile(file, buffer, size_dword, &bytes_written, nullptr);
if (!rv) if (!rv)
return -1; return -1;
CHECK_NE(bytes_written, static_cast<DWORD>(-1));
CHECK_EQ(bytes_written, size_dword); CHECK_EQ(bytes_written, size_dword);
return bytes_written; return bytes_written;
} }

View File

@ -22,12 +22,30 @@ namespace crashpad {
bool FileReaderInterface::ReadExactly(void* data, size_t size) { bool FileReaderInterface::ReadExactly(void* data, size_t size) {
FileOperationResult expect = base::checked_cast<FileOperationResult>(size); FileOperationResult expect = base::checked_cast<FileOperationResult>(size);
FileOperationResult rv = Read(data, size); char* data_c = static_cast<char*>(data);
if (rv < 0) {
// Read() will have logged its own error. FileOperationResult total_bytes = 0;
return false; while (size > 0) {
} else if (rv != expect) { FileOperationResult bytes = Read(data, size);
LOG(ERROR) << "ReadExactly(): expected " << expect << ", observed " << rv; 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 false;
} }
@ -44,13 +62,10 @@ WeakFileHandleFileReader::~WeakFileHandleFileReader() {
FileOperationResult WeakFileHandleFileReader::Read(void* data, size_t size) { FileOperationResult WeakFileHandleFileReader::Read(void* data, size_t size) {
DCHECK_NE(file_handle_, kInvalidFileHandle); DCHECK_NE(file_handle_, kInvalidFileHandle);
// Dont use LoggingReadFile(), which insists on a full read and only returns
// a bool. This method permits short reads and returns the number of bytes
// read.
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) << "read"; PLOG(ERROR) << kNativeReadFunctionName;
return -1; return -1;
} }

View File

@ -42,7 +42,7 @@ class FileReaderInterface : public virtual FileSeekerInterface {
//! \brief Wraps Read(), ensuring that the read succeeded and exactly \a size //! \brief Wraps Read(), ensuring that the read succeeded and exactly \a size
//! bytes were read. //! bytes were read.
//! //!
//! Semantically, this behaves as LoggingReadFile(). //! Semantically, this behaves as LoggingReadFileExactly().
//! //!
//! \return `true` if the operation succeeded, `false` if it failed, with an //! \return `true` if the operation succeeded, `false` if it failed, with an
//! error message logged. Short reads are treated as failures. //! error message logged. Short reads are treated as failures.

View File

@ -403,20 +403,20 @@ bool ChildPortHandshake::RunClientInternal_ReadPipe(int client_read_fd,
child_port_token_t* token, child_port_token_t* token,
std::string* service_name) { std::string* service_name) {
// Read the token from the pipe. // Read the token from the pipe.
if (!LoggingReadFile(client_read_fd, token, sizeof(*token))) { if (!LoggingReadFileExactly(client_read_fd, token, sizeof(*token))) {
return false; return false;
} }
// Read the service name from the pipe. // Read the service name from the pipe.
uint32_t service_name_length; uint32_t service_name_length;
if (!LoggingReadFile( if (!LoggingReadFileExactly(
client_read_fd, &service_name_length, sizeof(service_name_length))) { client_read_fd, &service_name_length, sizeof(service_name_length))) {
return false; return false;
} }
service_name->resize(service_name_length); service_name->resize(service_name_length);
if (!service_name->empty() && if (!service_name->empty() &&
!LoggingReadFile( !LoggingReadFileExactly(
client_read_fd, &(*service_name)[0], service_name_length)) { client_read_fd, &(*service_name)[0], service_name_length)) {
return false; return false;
} }

View File

@ -244,7 +244,7 @@ class TestExceptionPorts : public MachMultiprocess,
CheckedWriteFile(test_exception_ports_->WritePipeHandle(), &c, 1); CheckedWriteFile(test_exception_ports_->WritePipeHandle(), &c, 1);
// Wait for the parent process to say that its end is set up. // Wait for the parent process to say that its end is set up.
CheckedReadFile(test_exception_ports_->ReadPipeHandle(), &c, 1); CheckedReadFileExactly(test_exception_ports_->ReadPipeHandle(), &c, 1);
EXPECT_EQ('\0', c); EXPECT_EQ('\0', c);
// Regardless of where ExceptionPorts::SetExceptionPort() ran, // Regardless of where ExceptionPorts::SetExceptionPort() ran,
@ -352,7 +352,7 @@ class TestExceptionPorts : public MachMultiprocess,
// Wait for the child process to be ready. It needs to have all of its // Wait for the child process to be ready. It needs to have all of its
// threads set up before proceeding if in kSetOutOfProcess mode. // threads set up before proceeding if in kSetOutOfProcess mode.
char c; char c;
CheckedReadFile(ReadPipeHandle(), &c, 1); CheckedReadFileExactly(ReadPipeHandle(), &c, 1);
EXPECT_EQ('\0', c); EXPECT_EQ('\0', c);
mach_port_t local_port = LocalPort(); mach_port_t local_port = LocalPort();

View File

@ -344,7 +344,7 @@ class TestMachMessageServer : public MachMessageServer::Interface,
if (options_.parent_wait_for_child_pipe) { if (options_.parent_wait_for_child_pipe) {
// Wait until the child is done sending what its going to send. // Wait until the child is done sending what its going to send.
char c; char c;
CheckedReadFile(ReadPipeHandle(), &c, 1); CheckedReadFileExactly(ReadPipeHandle(), &c, 1);
EXPECT_EQ('\0', c); EXPECT_EQ('\0', c);
} }
@ -397,7 +397,7 @@ class TestMachMessageServer : public MachMessageServer::Interface,
if (options_.child_wait_for_parent_pipe_early) { if (options_.child_wait_for_parent_pipe_early) {
// Wait until the parent is done setting things up on its end. // Wait until the parent is done setting things up on its end.
char c; char c;
CheckedReadFile(ReadPipeHandle(), &c, 1); CheckedReadFileExactly(ReadPipeHandle(), &c, 1);
EXPECT_EQ('\0', c); EXPECT_EQ('\0', c);
} }
@ -431,7 +431,7 @@ class TestMachMessageServer : public MachMessageServer::Interface,
if (options_.child_wait_for_parent_pipe_late) { if (options_.child_wait_for_parent_pipe_late) {
char c; char c;
CheckedReadFile(ReadPipeHandle(), &c, 1); CheckedReadFileExactly(ReadPipeHandle(), &c, 1);
ASSERT_EQ('\0', c); ASSERT_EQ('\0', c);
} }
} }

View File

@ -81,7 +81,7 @@ class HTTPTransportTestFixture : public MultiprocessExec {
// The child will write the HTTP server port number as a packed unsigned // The child will write the HTTP server port number as a packed unsigned
// short to stdout. // short to stdout.
uint16_t port; uint16_t port;
ASSERT_TRUE(LoggingReadFile(ReadPipeHandle(), &port, sizeof(port))); ASSERT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &port, sizeof(port)));
// Then the parent will tell the web server what response code to send // Then the parent will tell the web server what response code to send
// for the HTTP request. // for the HTTP request.

View File

@ -396,7 +396,8 @@ bool ExceptionHandlerServer::ServiceClientConnection(
const internal::PipeServiceContext& service_context) { const internal::PipeServiceContext& service_context) {
ClientToServerMessage message; ClientToServerMessage message;
if (!LoggingReadFile(service_context.pipe(), &message, sizeof(message))) if (!LoggingReadFileExactly(
service_context.pipe(), &message, sizeof(message)))
return false; return false;
switch (message.type) { switch (message.type) {

View File

@ -143,10 +143,11 @@ TEST_F(ExceptionHandlerServerTest, StopWhileConnected) {
std::wstring ReadWString(FileHandle handle) { std::wstring ReadWString(FileHandle handle) {
size_t length = 0; size_t length = 0;
EXPECT_TRUE(LoggingReadFile(handle, &length, sizeof(length))); EXPECT_TRUE(LoggingReadFileExactly(handle, &length, sizeof(length)));
std::wstring str(length, L'\0'); std::wstring str(length, L'\0');
if (length > 0) { if (length > 0) {
EXPECT_TRUE(LoggingReadFile(handle, &str[0], length * sizeof(str[0]))); EXPECT_TRUE(
LoggingReadFileExactly(handle, &str[0], length * sizeof(str[0])));
} }
return str; return str;
} }

View File

@ -156,7 +156,7 @@ void TestOtherProcess(const base::string16& directory_modification) {
// The child sends us a code address we can look up in the memory map. // The child sends us a code address we can look up in the memory map.
WinVMAddress code_address; WinVMAddress code_address;
CheckedReadFile( CheckedReadFileExactly(
child.stdout_read_handle(), &code_address, sizeof(code_address)); child.stdout_read_handle(), &code_address, sizeof(code_address));
ASSERT_TRUE(process_info.Initialize(child.process_handle())); ASSERT_TRUE(process_info.Initialize(child.process_handle()));

View File

@ -80,7 +80,7 @@ class ScopedProcessSuspendTest final : public WinChildProcess {
int Run() override { int Run() override {
char c; char c;
// Wait for notification from parent. // Wait for notification from parent.
EXPECT_TRUE(LoggingReadFile(ReadPipeHandle(), &c, sizeof(c))); EXPECT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, sizeof(c)));
EXPECT_EQ(' ', c); EXPECT_EQ(' ', c);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }