From 10165ce449849509b71687890e4ac32b1e5366af Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Wed, 17 Dec 2014 14:35:18 -0800 Subject: [PATCH] Cross platform low level file IO wrappers Rename fd_io to file_io, and ReadFD to ReadFile, etc. file_io.cc is the higher level versions that call the basic ReadFile/WriteFile and then file_io_posix.cc and file_io_win.cc are the implementations of those functions. The Windows path is as yet untested, lacking the ability to link the test binary. R=cpu@chromium.org, mark@chromium.org BUG=crashpad:1 Review URL: https://codereview.chromium.org/811823003 --- .../mach_o_image_annotations_reader_test.cc | 10 +- snapshot/mac/process_reader_test.cc | 71 ++++----- util/file/fd_io.h | 125 ---------------- util/file/file_io.cc | 74 ++++++++++ util/file/file_io.h | 139 ++++++++++++++++++ util/file/{fd_io.cc => file_io_posix.cc} | 66 +-------- util/file/file_io_win.cc | 81 ++++++++++ util/file/file_writer.cc | 4 +- util/mach/child_port_handshake.cc | 15 +- util/mach/exception_ports_test.cc | 12 +- util/mach/mach_message_server_test.cc | 14 +- util/mach/scoped_task_suspend_test.cc | 1 - util/net/http_body.cc | 8 +- util/net/http_transport_test.cc | 10 +- util/test/mac/mach_multiprocess.cc | 4 +- util/test/multiprocess_exec_test.cc | 6 +- util/test/multiprocess_test.cc | 30 ++-- util/util.gyp | 6 +- 18 files changed, 398 insertions(+), 278 deletions(-) delete mode 100644 util/file/fd_io.h create mode 100644 util/file/file_io.cc create mode 100644 util/file/file_io.h rename util/file/{fd_io.cc => file_io_posix.cc} (56%) create mode 100644 util/file/file_io_win.cc diff --git a/snapshot/mac/mach_o_image_annotations_reader_test.cc b/snapshot/mac/mach_o_image_annotations_reader_test.cc index c2ee3685..01ecc7a0 100644 --- a/snapshot/mac/mach_o_image_annotations_reader_test.cc +++ b/snapshot/mac/mach_o_image_annotations_reader_test.cc @@ -30,7 +30,7 @@ #include "client/simple_string_dictionary.h" #include "gtest/gtest.h" #include "snapshot/mac/process_reader.h" -#include "util/file/fd_io.h" +#include "util/file/file_io.h" #include "util/mac/mac_util.h" #include "util/mach/exc_server_variants.h" #include "util/mach/exception_ports.h" @@ -193,7 +193,7 @@ class TestMachOImageAnnotationsReader final // Wait for the child process to indicate that it’s done setting up its // annotations via the CrashpadInfo interface. char c; - CheckedReadFD(ReadPipeFD(), &c, sizeof(c)); + CheckedReadFile(ReadPipeFD(), &c, sizeof(c)); // Verify the “simple map” annotations set via the CrashpadInfo interface. const std::vector& modules = @@ -216,7 +216,7 @@ class TestMachOImageAnnotationsReader final EXPECT_EQ("", all_annotations_simple_map["#TEST# empty_value"]); // Tell the child process that it’s permitted to crash. - CheckedWriteFD(WritePipeFD(), &c, sizeof(c)); + CheckedWriteFile(WritePipeFD(), &c, sizeof(c)); if (test_type_ != kDontCrash) { // Handle the child’s crash. Further validation will be done in @@ -268,10 +268,10 @@ class TestMachOImageAnnotationsReader final // Tell the parent that the environment has been set up. char c = '\0'; - CheckedWriteFD(WritePipeFD(), &c, sizeof(c)); + CheckedWriteFile(WritePipeFD(), &c, sizeof(c)); // Wait for the parent to indicate that it’s safe to crash. - CheckedReadFD(ReadPipeFD(), &c, sizeof(c)); + CheckedReadFile(ReadPipeFD(), &c, sizeof(c)); // Direct an exception message to the exception server running in the // parent. diff --git a/snapshot/mac/process_reader_test.cc b/snapshot/mac/process_reader_test.cc index 7e8c65d3..51034882 100644 --- a/snapshot/mac/process_reader_test.cc +++ b/snapshot/mac/process_reader_test.cc @@ -31,7 +31,7 @@ #include "build/build_config.h" #include "gtest/gtest.h" #include "snapshot/mac/mach_o_image_reader.h" -#include "util/file/fd_io.h" +#include "util/file/file_io.h" #include "util/mach/mach_extensions.h" #include "util/stdlib/pointer_container.h" #include "util/synchronization/semaphore.h" @@ -91,7 +91,7 @@ class ProcessReaderChild final : public MachMultiprocess { int read_fd = ReadPipeFD(); mach_vm_address_t address; - CheckedReadFD(read_fd, &address, sizeof(address)); + CheckedReadFile(read_fd, &address, sizeof(address)); std::string read_string; ASSERT_TRUE(process_reader.Memory()->ReadCString(address, &read_string)); @@ -103,11 +103,11 @@ class ProcessReaderChild final : public MachMultiprocess { mach_vm_address_t address = reinterpret_cast(kTestMemory); - CheckedWriteFD(write_fd, &address, sizeof(address)); + CheckedWriteFile(write_fd, &address, sizeof(address)); // Wait for the parent to signal that it’s OK to exit by closing its end of // the pipe. - CheckedReadFDAtEOF(ReadPipeFD()); + CheckedReadFileAtEOF(ReadPipeFD()); } DISALLOW_COPY_AND_ASSIGN(ProcessReaderChild); @@ -434,15 +434,15 @@ class ProcessReaderThreadedChild final : public MachMultiprocess { thread_index < thread_count_ + 1; ++thread_index) { uint64_t thread_id; - CheckedReadFD(read_fd, &thread_id, sizeof(thread_id)); + CheckedReadFile(read_fd, &thread_id, sizeof(thread_id)); TestThreadPool::ThreadExpectation expectation; - CheckedReadFD(read_fd, - &expectation.stack_address, - sizeof(expectation.stack_address)); - CheckedReadFD(read_fd, - &expectation.suspend_count, - sizeof(expectation.suspend_count)); + CheckedReadFile(read_fd, + &expectation.stack_address, + sizeof(expectation.stack_address)); + CheckedReadFile(read_fd, + &expectation.suspend_count, + sizeof(expectation.suspend_count)); // There can’t be any duplicate thread IDs. EXPECT_EQ(0u, thread_map.count(thread_id)); @@ -467,18 +467,18 @@ class ProcessReaderThreadedChild final : public MachMultiprocess { // to inspect it. Write an entry for it. uint64_t thread_id = PthreadToThreadID(pthread_self()); - CheckedWriteFD(write_fd, &thread_id, sizeof(thread_id)); + CheckedWriteFile(write_fd, &thread_id, sizeof(thread_id)); TestThreadPool::ThreadExpectation expectation; expectation.stack_address = reinterpret_cast(&thread_id); expectation.suspend_count = 0; - CheckedWriteFD(write_fd, - &expectation.stack_address, - sizeof(expectation.stack_address)); - CheckedWriteFD(write_fd, - &expectation.suspend_count, - sizeof(expectation.suspend_count)); + CheckedWriteFile(write_fd, + &expectation.stack_address, + sizeof(expectation.stack_address)); + CheckedWriteFile(write_fd, + &expectation.suspend_count, + sizeof(expectation.suspend_count)); // Write an entry for everything in the thread pool. for (size_t thread_index = 0; @@ -487,18 +487,18 @@ class ProcessReaderThreadedChild final : public MachMultiprocess { uint64_t thread_id = thread_pool.GetThreadInfo(thread_index, &expectation); - CheckedWriteFD(write_fd, &thread_id, sizeof(thread_id)); - CheckedWriteFD(write_fd, - &expectation.stack_address, - sizeof(expectation.stack_address)); - CheckedWriteFD(write_fd, - &expectation.suspend_count, - sizeof(expectation.suspend_count)); + CheckedWriteFile(write_fd, &thread_id, sizeof(thread_id)); + CheckedWriteFile(write_fd, + &expectation.stack_address, + sizeof(expectation.stack_address)); + CheckedWriteFile(write_fd, + &expectation.suspend_count, + sizeof(expectation.suspend_count)); } // Wait for the parent to signal that it’s OK to exit by closing its end of // the pipe. - CheckedReadFDAtEOF(ReadPipeFD()); + CheckedReadFileAtEOF(ReadPipeFD()); } size_t thread_count_; @@ -597,7 +597,7 @@ class ProcessReaderModulesChild final : public MachMultiprocess { int read_fd = ReadPipeFD(); uint32_t expect_modules; - CheckedReadFD(read_fd, &expect_modules, sizeof(expect_modules)); + CheckedReadFile(read_fd, &expect_modules, sizeof(expect_modules)); ASSERT_EQ(expect_modules, modules.size()); @@ -606,16 +606,16 @@ class ProcessReaderModulesChild final : public MachMultiprocess { "index %zu, name %s", index, modules[index].name.c_str())); uint32_t expect_name_length; - CheckedReadFD( + CheckedReadFile( read_fd, &expect_name_length, sizeof(expect_name_length)); // The NUL terminator is not read. std::string expect_name(expect_name_length, '\0'); - CheckedReadFD(read_fd, &expect_name[0], expect_name_length); + CheckedReadFile(read_fd, &expect_name[0], expect_name_length); EXPECT_EQ(expect_name, modules[index].name); mach_vm_address_t expect_address; - CheckedReadFD(read_fd, &expect_address, sizeof(expect_address)); + CheckedReadFile(read_fd, &expect_address, sizeof(expect_address)); EXPECT_EQ(expect_address, modules[index].reader->Address()); if (index == 0 || index == modules.size() - 1) { @@ -648,7 +648,7 @@ class ProcessReaderModulesChild final : public MachMultiprocess { ++write_image_count; } - CheckedWriteFD(write_fd, &write_image_count, sizeof(write_image_count)); + CheckedWriteFile(write_fd, &write_image_count, sizeof(write_image_count)); for (size_t index = 0; index < write_image_count; ++index) { const char* dyld_image_name; @@ -665,18 +665,19 @@ class ProcessReaderModulesChild final : public MachMultiprocess { } uint32_t dyld_image_name_length = strlen(dyld_image_name); - CheckedWriteFD( + CheckedWriteFile( write_fd, &dyld_image_name_length, sizeof(dyld_image_name_length)); // The NUL terminator is not written. - CheckedWriteFD(write_fd, dyld_image_name, dyld_image_name_length); + CheckedWriteFile(write_fd, dyld_image_name, dyld_image_name_length); - CheckedWriteFD(write_fd, &dyld_image_address, sizeof(dyld_image_address)); + CheckedWriteFile( + write_fd, &dyld_image_address, sizeof(dyld_image_address)); } // Wait for the parent to signal that it’s OK to exit by closing its end of // the pipe. - CheckedReadFDAtEOF(ReadPipeFD()); + CheckedReadFileAtEOF(ReadPipeFD()); } DISALLOW_COPY_AND_ASSIGN(ProcessReaderModulesChild); diff --git a/util/file/fd_io.h b/util/file/fd_io.h deleted file mode 100644 index 37ad6f1c..00000000 --- a/util/file/fd_io.h +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2014 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. - -#ifndef CRASHPAD_UTIL_FILE_FD_IO_H_ -#define CRASHPAD_UTIL_FILE_FD_IO_H_ - -#include - -namespace crashpad { - -//! \brief Wraps `read()`, retrying when interrupted or following a short read. -//! -//! This function reads into \a buffer, stopping only when \a size bytes have -//! been read or when `read()` returns 0, indicating that end-of-file has been -//! reached. -//! -//! \return The number of bytes read and placed into \a buffer, or `-1` on -//! error, with `errno` set appropriately. On error, a portion of \a fd may -//! have been read into \a buffer. -//! -//! \sa WriteFD -//! \sa LoggingReadFD -//! \sa CheckedReadFD -//! \sa CheckedReadFDAtEOF -ssize_t ReadFD(int fd, void* buffer, size_t size); - -//! \brief Wraps `write()`, retrying when interrupted or following a short -//! write. -//! -//! This function writes to \a fd, stopping only when \a size bytes have been -//! written. -//! -//! \return The number of bytes written from \a buffer, or `-1` on error, with -//! `errno` set appropriately. On error, a portion of \a buffer may have -//! been written to \a fd. -//! -//! \sa ReadFD -//! \sa LoggingWriteFD -//! \sa CheckedWriteFD -ssize_t WriteFD(int fd, const void* buffer, size_t size); - -//! \brief Wraps ReadFD(), ensuring that exactly \a size bytes are read. -//! -//! \return `true` on success. If \a size is out of the range of possible -//! `read()` return values, if the underlying ReadFD() fails, or if other -//! than \a size bytes were read, this function logs a message and returns -//! `false`. -//! -//! \sa LoggingWriteFD -//! \sa ReadFD -//! \sa CheckedReadFD -//! \sa CheckedReadFDAtEOF -bool LoggingReadFD(int fd, void* buffer, size_t size); - -//! \brief Wraps WriteFD(), ensuring that exactly \a size bytes are written. -//! -//! \return `true` on success. If \a size is out of the range of possible -//! `write()` return values, if the underlying WriteFD() fails, or if other -//! than \a size bytes were written, this function logs a message and -//! returns `false`. -//! -//! \sa LoggingReadFD -//! \sa WriteFD -//! \sa CheckedWriteFD -bool LoggingWriteFD(int fd, const void* buffer, size_t size); - -//! \brief Wraps ReadFD(), ensuring that exactly \a size bytes are read. -//! -//! If \a size is out of the range of possible `read()` return values, if the -//! underlying ReadFD() fails, or if other than \a size bytes were read, this -//! function causes execution to terminate without returning. -//! -//! \sa CheckedWriteFD -//! \sa ReadFD -//! \sa LoggingReadFD -//! \sa CheckedReadFDAtEOF -void CheckedReadFD(int fd, void* buffer, size_t size); - -//! \brief Wraps WriteFD(), ensuring that exactly \a size bytes are written. -//! -//! If \a size is out of the range of possible `write()` return values, if the -//! underlying WriteFD() fails, or if other than \a size bytes were written, -//! this function causes execution to terminate without returning. -//! -//! \sa CheckedReadFD -//! \sa WriteFD -//! \sa LoggingWriteFD -void CheckedWriteFD(int fd, const void* buffer, size_t size); - -//! \brief Wraps ReadFD(), ensuring that it indicates end-of-file. -//! -//! Attempts to read a single byte from \a fd, expecting no data to be read. If -//! the underlying ReadFD() fails, or if a byte actually is read, this function -//! causes execution to terminate without returning. -//! -//! \sa CheckedReadFD -//! \sa ReadFD -void CheckedReadFDAtEOF(int fd); - -//! \brief Wraps `close()`, logging an error if the operation fails. -//! -//! \return On success, `true` is returned. On failure, an error is logged and -//! `false` is returned. -bool LoggingCloseFD(int fd); - -//! \brief Wraps `close()`, ensuring that it succeeds. -//! -//! If `close()` fails, this function causes execution to terminate without -//! returning. -void CheckedCloseFD(int fd); - -} // namespace crashpad - -#endif // CRASHPAD_UTIL_FILE_FD_IO_H_ diff --git a/util/file/file_io.cc b/util/file/file_io.cc new file mode 100644 index 00000000..6f2d0b58 --- /dev/null +++ b/util/file/file_io.cc @@ -0,0 +1,74 @@ +// Copyright 2014 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_io.h" + +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" + +namespace crashpad { + +bool LoggingReadFile(FileHandle file, void* buffer, size_t size) { + ssize_t expect = base::checked_cast(size); + ssize_t rv = ReadFile(file, buffer, size); + if (rv < 0) { + PLOG(ERROR) << "read"; + return false; + } + if (rv != expect) { + LOG(ERROR) << "read: expected " << expect << ", observed " << rv; + return false; + } + + return true; +} + +bool LoggingWriteFile(FileHandle file, const void* buffer, size_t size) { + ssize_t expect = base::checked_cast(size); + ssize_t rv = WriteFile(file, buffer, size); + if (rv < 0) { + PLOG(ERROR) << "write"; + return false; + } + if (rv != expect) { + LOG(ERROR) << "write: expected " << expect << ", observed " << rv; + return false; + } + + return true; +} + +void CheckedReadFile(FileHandle file, void* buffer, size_t size) { + CHECK(LoggingReadFile(file, buffer, size)); +} + +void CheckedWriteFile(FileHandle file, const void* buffer, size_t size) { + CHECK(LoggingWriteFile(file, buffer, size)); +} + +void CheckedReadFileAtEOF(FileHandle file) { + char c; + ssize_t rv = ReadFile(file, &c, 1); + if (rv < 0) { + PCHECK(rv == 0) << "read"; + } else { + CHECK_EQ(rv, 0) << "read"; + } +} + +void CheckedCloseFile(FileHandle file) { + CHECK(LoggingCloseFile(file)); +} + +} // namespace crashpad diff --git a/util/file/file_io.h b/util/file/file_io.h new file mode 100644 index 00000000..927bc3cb --- /dev/null +++ b/util/file/file_io.h @@ -0,0 +1,139 @@ +// Copyright 2014 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. + +#ifndef CRASHPAD_UTIL_FILE_FILE_IO_H_ +#define CRASHPAD_UTIL_FILE_FILE_IO_H_ + +#include + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif + +namespace crashpad { + +#if defined(OS_POSIX) +using FileHandle = int; +#elif defined(OS_WIN) +using FileHandle = HANDLE; +#endif + +//! \brief Reads from a file, retrying when interrupted on POSIX or following a +//! short read. +//! +//! This function reads into \a buffer, stopping only when \a size bytes have +//! been read or when end-of-file has been reached. On Windows, reading from +//! sockets is not currently supported. +//! +//! \return The number of bytes read and placed into \a buffer, or `-1` on +//! error, with `errno` or `GetLastError()` set appropriately. On error, a +//! portion of \a file may have been read into \a buffer. +//! +//! \sa WriteFile +//! \sa LoggingReadFile +//! \sa CheckedReadFile +//! \sa CheckedReadFileAtEOF +ssize_t ReadFile(FileHandle file, void* buffer, size_t size); + +//! \brief Writes to a file, retrying when interrupted or following a short +//! write on POSIX. +//! +//! This function writes to \a file, stopping only when \a size bytes have been +//! written. +//! +//! \return The number of bytes written from \a buffer, or `-1` on error, with +//! `errno` or `GetLastError()` set appropriately. On error, a portion of +//! \a buffer may have been written to \a file. +//! +//! \sa ReadFile +//! \sa LoggingWriteFile +//! \sa CheckedWriteFile +ssize_t WriteFile(FileHandle file, const void* buffer, size_t size); + +//! \brief Wraps ReadFile(), ensuring that exactly \a size bytes are read. +//! +//! \return `true` on success. 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 function logs a message and +//! returns `false`. +//! +//! \sa LoggingWriteFile +//! \sa ReadFile +//! \sa CheckedReadFile +//! \sa CheckedReadFileAtEOF +bool LoggingReadFile(FileHandle file, void* buffer, size_t size); + +//! \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 +//! WriteFile() return values, if the underlying WriteFile() fails, or if +//! other than \a size bytes were written, this function logs a message and +//! returns `false`. +//! +//! \sa LoggingReadFile +//! \sa WriteFile +//! \sa CheckedWriteFile +bool LoggingWriteFile(FileHandle file, const void* buffer, size_t size); + +//! \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 +//! underlying ReadFile() fails, or if other than \a size bytes were read, this +//! function causes execution to terminate without returning. +//! +//! \sa CheckedWriteFile +//! \sa ReadFile +//! \sa LoggingReadFile +//! \sa CheckedReadFileAtEOF +void CheckedReadFile(FileHandle file, void* buffer, size_t size); + +//! \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 +//! underlying WriteFile() fails, or if other than \a size bytes were written, +//! this function causes execution to terminate without returning. +//! +//! \sa CheckedReadFile +//! \sa WriteFile +//! \sa LoggingWriteFile +void CheckedWriteFile(FileHandle file, const void* buffer, size_t size); + +//! \brief Wraps ReadFile(), ensuring that it indicates end-of-file. +//! +//! Attempts to read a single byte from \a file, expecting no data to be read. +//! If the underlying ReadFile() fails, or if a byte actually is read, this +//! function causes execution to terminate without returning. +//! +//! \sa CheckedReadFile +//! \sa ReadFile +void CheckedReadFileAtEOF(FileHandle file); + +//! \brief Wraps `close()` or `CloseHandle()`, logging an error if the operation +//! fails. +//! +//! \return On success, `true` is returned. On failure, an error is logged and +//! `false` is returned. +bool LoggingCloseFile(FileHandle file); + +//! \brief Wraps `close()` or `CloseHandle()`, ensuring that it succeeds. +//! +//! If the underlying function fails, this function causes execution to +//! terminate without returning. +void CheckedCloseFile(FileHandle file); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_FILE_FILE_IO_H_ diff --git a/util/file/fd_io.cc b/util/file/file_io_posix.cc similarity index 56% rename from util/file/fd_io.cc rename to util/file/file_io_posix.cc index cc97400b..ff320090 100644 --- a/util/file/fd_io.cc +++ b/util/file/file_io_posix.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "util/file/fd_io.h" +#include "util/file/file_io.h" #include @@ -66,70 +66,18 @@ ssize_t ReadOrWrite(int fd, namespace crashpad { -ssize_t ReadFD(int fd, void* buffer, size_t size) { - return ReadOrWrite(fd, buffer, size); +ssize_t ReadFile(FileHandle file, void* buffer, size_t size) { + return ReadOrWrite(file, buffer, size); } -ssize_t WriteFD(int fd, const void* buffer, size_t size) { - return ReadOrWrite(fd, buffer, size); +ssize_t WriteFile(FileHandle file, const void* buffer, size_t size) { + return ReadOrWrite(file, buffer, size); } -bool LoggingReadFD(int fd, void* buffer, size_t size) { - ssize_t expect = base::checked_cast(size); - ssize_t rv = ReadFD(fd, buffer, size); - if (rv < 0) { - PLOG(ERROR) << "read"; - return false; - } - if (rv != expect) { - LOG(ERROR) << "read: expected " << expect << ", observed " << rv; - return false; - } - - return true; -} - -bool LoggingWriteFD(int fd, const void* buffer, size_t size) { - ssize_t expect = base::checked_cast(size); - ssize_t rv = WriteFD(fd, buffer, size); - if (rv < 0) { - PLOG(ERROR) << "write"; - return false; - } - if (rv != expect) { - LOG(ERROR) << "write: expected " << expect << ", observed " << rv; - return false; - } - - return true; -} - -void CheckedReadFD(int fd, void* buffer, size_t size) { - CHECK(LoggingReadFD(fd, buffer, size)); -} - -void CheckedWriteFD(int fd, const void* buffer, size_t size) { - CHECK(LoggingWriteFD(fd, buffer, size)); -} - -void CheckedReadFDAtEOF(int fd) { - char c; - ssize_t rv = ReadFD(fd, &c, 1); - if (rv < 0) { - PCHECK(rv == 0) << "read"; - } else { - CHECK_EQ(rv, 0) << "read"; - } -} - -bool LoggingCloseFD(int fd) { - int rv = IGNORE_EINTR(close(fd)); +bool LoggingCloseFile(FileHandle file) { + int rv = IGNORE_EINTR(close(file)); PLOG_IF(ERROR, rv != 0) << "close"; return rv == 0; } -void CheckedCloseFD(int fd) { - CHECK(LoggingCloseFD(fd)); -} - } // namespace crashpad diff --git a/util/file/file_io_win.cc b/util/file/file_io_win.cc new file mode 100644 index 00000000..b1640dd0 --- /dev/null +++ b/util/file/file_io_win.cc @@ -0,0 +1,81 @@ +// Copyright 2014 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_io.h" + +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" + +namespace { + +bool IsSocketHandle(HANDLE file) { + if (GetFileType(file) == FILE_TYPE_PIPE) { + // FILE_TYPE_PIPE means that it's a socket, a named pipe, or an anonymous + // pipe. If we are unable to retrieve the pipe information, we know it's a + // socket. + return !GetNamedPipeInfo(file, NULL, NULL, NULL, NULL); + } + return false; +} + +} // namespace + +namespace crashpad { + +// TODO(scottmg): Handle > DWORD sized writes if necessary. + +ssize_t ReadFile(FileHandle file, void* buffer, size_t size) { + DCHECK(!IsSocketHandle(file)); + DWORD size_dword = base::checked_cast(size); + DWORD total_read = 0; + char* buffer_c = reinterpret_cast(buffer); + while (size_dword > 0) { + DWORD bytes_read; + BOOL success = ::ReadFile(file, buffer_c, size_dword, &bytes_read, nullptr); + if (!success && GetLastError() != ERROR_MORE_DATA) { + return -1; + } else if (success && bytes_read == 0 && + GetFileType(file) != FILE_TYPE_PIPE) { + // Zero bytes read for a file indicates reaching EOF. Zero bytes read from + // a pipe indicates only that there was a zero byte WriteFile issued on + // the other end, so continue reading. + break; + } + + buffer_c += bytes_read; + size_dword -= bytes_read; + total_read += bytes_read; + } + return total_read; +} + +ssize_t 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(size); + DWORD bytes_written; + BOOL rv = ::WriteFile(file, buffer, size_dword, &bytes_written, nullptr); + if (!rv) + return -1; + CHECK_EQ(bytes_written, size_dword); + return bytes_written; +} + +bool LoggingCloseFile(FileHandle file) { + BOOL rv = CloseHandle(file); + PLOG_IF(ERROR, !rv) << "CloseHandle"; + return rv; +} + +} // namespace crashpad diff --git a/util/file/file_writer.cc b/util/file/file_writer.cc index 2a3d7c40..4f833051 100644 --- a/util/file/file_writer.cc +++ b/util/file/file_writer.cc @@ -20,7 +20,7 @@ #include "base/logging.h" #include "base/posix/eintr_wrapper.h" -#include "util/file/fd_io.h" +#include "util/file/file_io.h" namespace crashpad { @@ -61,7 +61,7 @@ bool FileWriter::Write(const void* data, size_t size) { DCHECK(fd_.is_valid()); // TODO(mark): Write no more than SSIZE_MAX bytes in a single call. - ssize_t written = WriteFD(fd_.get(), data, size); + ssize_t written = WriteFile(fd_.get(), data, size); if (written < 0) { PLOG(ERROR) << "write"; return false; diff --git a/util/mach/child_port_handshake.cc b/util/mach/child_port_handshake.cc index 4369da98..b45a7170 100644 --- a/util/mach/child_port_handshake.cc +++ b/util/mach/child_port_handshake.cc @@ -31,7 +31,7 @@ #include "base/posix/eintr_wrapper.h" #include "base/rand_util.h" #include "base/strings/stringprintf.h" -#include "util/file/fd_io.h" +#include "util/file/file_io.h" #include "util/mach/child_port.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" @@ -85,7 +85,7 @@ mach_port_t ChildPortHandshake::RunServer() { // Initialize the token and share it with the client via the pipe. token_ = base::RandUint64(); int pipe_write = pipe_write_owner.get(); - if (!LoggingWriteFD(pipe_write, &token_, sizeof(token_))) { + if (!LoggingWriteFile(pipe_write, &token_, sizeof(token_))) { LOG(WARNING) << "no client check-in"; return MACH_PORT_NULL; } @@ -113,13 +113,14 @@ mach_port_t ChildPortHandshake::RunServer() { // Share the service name with the client via the pipe. uint32_t service_name_length = service_name.size(); - if (!LoggingWriteFD( + if (!LoggingWriteFile( pipe_write, &service_name_length, sizeof(service_name_length))) { LOG(WARNING) << "no client check-in"; return MACH_PORT_NULL; } - if (!LoggingWriteFD(pipe_write, service_name.c_str(), service_name_length)) { + if (!LoggingWriteFile( + pipe_write, service_name.c_str(), service_name_length)) { LOG(WARNING) << "no client check-in"; return MACH_PORT_NULL; } @@ -310,17 +311,17 @@ void ChildPortHandshake::RunClientInternal_ReadPipe(int pipe_read, child_port_token_t* token, std::string* service_name) { // Read the token from the pipe. - CheckedReadFD(pipe_read, token, sizeof(*token)); + CheckedReadFile(pipe_read, token, sizeof(*token)); // Read the service name from the pipe. uint32_t service_name_length; - CheckedReadFD(pipe_read, &service_name_length, sizeof(service_name_length)); + CheckedReadFile(pipe_read, &service_name_length, sizeof(service_name_length)); DCHECK_LT(service_name_length, implicit_cast(BOOTSTRAP_MAX_NAME_LEN)); if (service_name_length > 0) { service_name->resize(service_name_length); - CheckedReadFD(pipe_read, &(*service_name)[0], service_name_length); + CheckedReadFile(pipe_read, &(*service_name)[0], service_name_length); } } diff --git a/util/mach/exception_ports_test.cc b/util/mach/exception_ports_test.cc index 538c2033..abba9bfe 100644 --- a/util/mach/exception_ports_test.cc +++ b/util/mach/exception_ports_test.cc @@ -25,7 +25,7 @@ #include "base/mac/scoped_mach_port.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" -#include "util/file/fd_io.h" +#include "util/file/file_io.h" #include "util/mach/exc_server_variants.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" @@ -242,10 +242,10 @@ class TestExceptionPorts : public MachMultiprocess, // Tell the parent process that everything is set up. char c = '\0'; - CheckedWriteFD(test_exception_ports_->WritePipeFD(), &c, 1); + CheckedWriteFile(test_exception_ports_->WritePipeFD(), &c, 1); // Wait for the parent process to say that its end is set up. - CheckedReadFD(test_exception_ports_->ReadPipeFD(), &c, 1); + CheckedReadFile(test_exception_ports_->ReadPipeFD(), &c, 1); EXPECT_EQ('\0', c); // Regardless of where ExceptionPorts::SetExceptionPort() ran, @@ -359,7 +359,7 @@ class TestExceptionPorts : public MachMultiprocess, // Wait for the child process to be ready. It needs to have all of its // threads set up before proceeding if in kSetOutOfProcess mode. char c; - CheckedReadFD(ReadPipeFD(), &c, 1); + CheckedReadFile(ReadPipeFD(), &c, 1); EXPECT_EQ('\0', c); mach_port_t local_port = LocalPort(); @@ -442,7 +442,7 @@ class TestExceptionPorts : public MachMultiprocess, // Let the child process know that everything in the parent process is set // up. c = '\0'; - CheckedWriteFD(WritePipeFD(), &c, 1); + CheckedWriteFile(WritePipeFD(), &c, 1); if (who_crashes_ != kNobodyCrashes) { UniversalMachExcServer universal_mach_exc_server(this); @@ -463,7 +463,7 @@ class TestExceptionPorts : public MachMultiprocess, // Wait for the child process to exit or terminate, as indicated by it // closing its pipe. This keeps LocalPort() alive in the child as // RemotePort(), for the child’s use in its TestGetExceptionPorts(). - CheckedReadFDAtEOF(ReadPipeFD()); + CheckedReadFileAtEOF(ReadPipeFD()); } void MachMultiprocessChild() override { diff --git a/util/mach/mach_message_server_test.cc b/util/mach/mach_message_server_test.cc index cc2e639e..0ad7efc8 100644 --- a/util/mach/mach_message_server_test.cc +++ b/util/mach/mach_message_server_test.cc @@ -22,7 +22,7 @@ #include "base/basictypes.h" #include "base/mac/scoped_mach_port.h" #include "gtest/gtest.h" -#include "util/file/fd_io.h" +#include "util/file/file_io.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" #include "util/test/mac/mach_errors.h" @@ -335,13 +335,13 @@ class TestMachMessageServer : public MachMessageServer::Interface, if (options_.child_wait_for_parent_pipe_early) { // Tell the child to begin sending messages. char c = '\0'; - CheckedWriteFD(WritePipeFD(), &c, 1); + CheckedWriteFile(WritePipeFD(), &c, 1); } if (options_.parent_wait_for_child_pipe) { // Wait until the child is done sending what it’s going to send. char c; - CheckedReadFD(ReadPipeFD(), &c, 1); + CheckedReadFile(ReadPipeFD(), &c, 1); EXPECT_EQ('\0', c); } @@ -386,7 +386,7 @@ class TestMachMessageServer : public MachMessageServer::Interface, if (options_.child_wait_for_parent_pipe_late) { // Let the child know it’s safe to exit. char c = '\0'; - CheckedWriteFD(WritePipeFD(), &c, 1); + CheckedWriteFile(WritePipeFD(), &c, 1); } } @@ -394,7 +394,7 @@ class TestMachMessageServer : public MachMessageServer::Interface, if (options_.child_wait_for_parent_pipe_early) { // Wait until the parent is done setting things up on its end. char c; - CheckedReadFD(ReadPipeFD(), &c, 1); + CheckedReadFile(ReadPipeFD(), &c, 1); EXPECT_EQ('\0', c); } @@ -428,7 +428,7 @@ class TestMachMessageServer : public MachMessageServer::Interface, if (options_.child_wait_for_parent_pipe_late) { char c; - CheckedReadFD(ReadPipeFD(), &c, 1); + CheckedReadFile(ReadPipeFD(), &c, 1); ASSERT_EQ('\0', c); } } @@ -550,7 +550,7 @@ class TestMachMessageServer : public MachMessageServer::Interface, // running MachMessageServer() once it’s received. void ChildNotifyParentViaPipe() { char c = '\0'; - CheckedWriteFD(WritePipeFD(), &c, 1); + CheckedWriteFile(WritePipeFD(), &c, 1); } // In the child process, sends a request message to the server and then diff --git a/util/mach/scoped_task_suspend_test.cc b/util/mach/scoped_task_suspend_test.cc index 58d028cb..01d967ff 100644 --- a/util/mach/scoped_task_suspend_test.cc +++ b/util/mach/scoped_task_suspend_test.cc @@ -17,7 +17,6 @@ #include #include "gtest/gtest.h" -#include "util/file/fd_io.h" #include "util/test/mac/mach_errors.h" #include "util/test/mac/mach_multiprocess.h" diff --git a/util/net/http_body.cc b/util/net/http_body.cc index 0e5daa34..e731c598 100644 --- a/util/net/http_body.cc +++ b/util/net/http_body.cc @@ -24,7 +24,7 @@ #include "base/logging.h" #include "base/posix/eintr_wrapper.h" #include "base/stl_util.h" -#include "util/file/fd_io.h" +#include "util/file/file_io.h" namespace crashpad { @@ -55,7 +55,7 @@ FileHTTPBodyStream::FileHTTPBodyStream(const base::FilePath& path) FileHTTPBodyStream::~FileHTTPBodyStream() { if (fd_ >= 0) { - LoggingCloseFD(fd_); + LoggingCloseFile(fd_); } } @@ -77,9 +77,9 @@ ssize_t FileHTTPBodyStream::GetBytesBuffer(uint8_t* buffer, size_t max_len) { break; } - ssize_t rv = ReadFD(fd_, buffer, max_len); + ssize_t rv = ReadFile(fd_, buffer, max_len); if (rv == 0) { - LoggingCloseFD(fd_); + LoggingCloseFile(fd_); fd_ = kClosedAtEOF; } else if (rv < 0) { PLOG(ERROR) << "read"; diff --git a/util/net/http_transport_test.cc b/util/net/http_transport_test.cc index 86e43af2..bbb24d38 100644 --- a/util/net/http_transport_test.cc +++ b/util/net/http_transport_test.cc @@ -24,7 +24,7 @@ #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" -#include "util/file/fd_io.h" +#include "util/file/file_io.h" #include "util/net/http_body.h" #include "util/net/http_headers.h" #include "util/net/http_multipart_builder.h" @@ -63,12 +63,12 @@ class HTTPTransportTestFixture : public MultiprocessExec { // The child will write the HTTP server port number as a packed unsigned // short to stdout. uint16_t port; - ASSERT_TRUE(LoggingReadFD(ReadPipeFD(), &port, sizeof(port))); + ASSERT_TRUE(LoggingReadFile(ReadPipeFD(), &port, sizeof(port))); // Then the parent will tell the web server what response code to send // for the HTTP request. - ASSERT_TRUE( - LoggingWriteFD(WritePipeFD(), &response_code_, sizeof(response_code_))); + ASSERT_TRUE(LoggingWriteFile( + WritePipeFD(), &response_code_, sizeof(response_code_))); // Now execute the HTTP request. scoped_ptr transport(HTTPTransport::Create()); @@ -85,7 +85,7 @@ class HTTPTransportTestFixture : public MultiprocessExec { std::string request; char buf[32]; ssize_t bytes_read; - while ((bytes_read = ReadFD(ReadPipeFD(), buf, sizeof(buf))) != 0) { + while ((bytes_read = ReadFile(ReadPipeFD(), buf, sizeof(buf))) != 0) { ASSERT_GE(bytes_read, 0); request.append(buf, bytes_read); } diff --git a/util/test/mac/mach_multiprocess.cc b/util/test/mac/mach_multiprocess.cc index 0b2da0d0..ca4e6350 100644 --- a/util/test/mac/mach_multiprocess.cc +++ b/util/test/mac/mach_multiprocess.cc @@ -26,7 +26,7 @@ #include "base/memory/scoped_ptr.h" #include "base/rand_util.h" #include "gtest/gtest.h" -#include "util/file/fd_io.h" +#include "util/file/file_io.h" #include "util/mach/mach_extensions.h" #include "util/misc/scoped_forbid_return.h" #include "util/test/errors.h" @@ -262,7 +262,7 @@ void MachMultiprocess::MultiprocessChild() { // Wait for the parent process to close its end of the pipe. The child process // needs to remain alive until then because the parent process will attempt to // verify it using the task port it has access to via ChildTask(). - CheckedReadFDAtEOF(ReadPipeFD()); + CheckedReadFileAtEOF(ReadPipeFD()); if (testing::Test::HasFailure()) { // Trigger the ScopedForbidReturn destructor. diff --git a/util/test/multiprocess_exec_test.cc b/util/test/multiprocess_exec_test.cc index ecadc444..085bf5d8 100644 --- a/util/test/multiprocess_exec_test.cc +++ b/util/test/multiprocess_exec_test.cc @@ -18,7 +18,7 @@ #include "base/basictypes.h" #include "gtest/gtest.h" -#include "util/file/fd_io.h" +#include "util/file/file_io.h" #include "util/test/executable_path.h" namespace crashpad { @@ -37,9 +37,9 @@ class TestMultiprocessExec final : public MultiprocessExec { // gracefully with a gtest assertion if the child does not execute properly. char c = 'z'; - ASSERT_TRUE(LoggingWriteFD(WritePipeFD(), &c, 1)); + ASSERT_TRUE(LoggingWriteFile(WritePipeFD(), &c, 1)); - ASSERT_TRUE(LoggingReadFD(ReadPipeFD(), &c, 1)); + ASSERT_TRUE(LoggingReadFile(ReadPipeFD(), &c, 1)); EXPECT_EQ('Z', c); } diff --git a/util/test/multiprocess_test.cc b/util/test/multiprocess_test.cc index 92dc6cfc..4d978e04 100644 --- a/util/test/multiprocess_test.cc +++ b/util/test/multiprocess_test.cc @@ -20,7 +20,7 @@ #include "base/basictypes.h" #include "gtest/gtest.h" -#include "util/file/fd_io.h" +#include "util/file/file_io.h" namespace crashpad { namespace test { @@ -38,31 +38,31 @@ class TestMultiprocess final : public Multiprocess { void MultiprocessParent() override { int read_fd = ReadPipeFD(); char c; - CheckedReadFD(read_fd, &c, 1); + CheckedReadFile(read_fd, &c, 1); EXPECT_EQ('M', c); pid_t pid; - CheckedReadFD(read_fd, &pid, sizeof(pid)); + CheckedReadFile(read_fd, &pid, sizeof(pid)); EXPECT_EQ(pid, ChildPID()); c = 'm'; - CheckedWriteFD(WritePipeFD(), &c, 1); + CheckedWriteFile(WritePipeFD(), &c, 1); // The child will close its end of the pipe and exit. Make sure that the // parent sees EOF. - CheckedReadFDAtEOF(read_fd); + CheckedReadFileAtEOF(read_fd); } void MultiprocessChild() override { int write_fd = WritePipeFD(); char c = 'M'; - CheckedWriteFD(write_fd, &c, 1); + CheckedWriteFile(write_fd, &c, 1); pid_t pid = getpid(); - CheckedWriteFD(write_fd, &pid, sizeof(pid)); + CheckedWriteFile(write_fd, &pid, sizeof(pid)); - CheckedReadFD(ReadPipeFD(), &c, 1); + CheckedReadFile(ReadPipeFD(), &c, 1); EXPECT_EQ('m', c); } @@ -178,18 +178,18 @@ class TestMultiprocessClosePipe final : public Multiprocess { // the read pipe in this process to show end-of-file. void VerifyPartner() { if (what_closes_ == kWriteCloses) { - CheckedReadFDAtEOF(ReadPipeFD()); + CheckedReadFileAtEOF(ReadPipeFD()); } else if (what_closes_ == kReadAndWriteClose) { - CheckedReadFDAtEOF(ReadPipeFD()); + CheckedReadFileAtEOF(ReadPipeFD()); char c = '\0'; // This will raise SIGPIPE. If fatal (the normal case), that will cause // process termination. If SIGPIPE is being handled somewhere, the write - // will still fail and set errno to EPIPE, and CheckedWriteFD() will abort - // execution. Regardless of how SIGPIPE is handled, the process will be - // terminated. Because the actual termination mechanism is not known, no - // regex can be specified. - EXPECT_DEATH(CheckedWriteFD(WritePipeFD(), &c, 1), ""); + // will still fail and set errno to EPIPE, and CheckedWriteFile() will + // abort execution. Regardless of how SIGPIPE is handled, the process will + // be terminated. Because the actual termination mechanism is not known, + // no regex can be specified. + EXPECT_DEATH(CheckedWriteFile(WritePipeFD(), &c, 1), ""); } } diff --git a/util/util.gyp b/util/util.gyp index 6ec762f3..fe2a5096 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -26,8 +26,10 @@ '<(INTERMEDIATE_DIR)', ], 'sources': [ - 'file/fd_io.cc', - 'file/fd_io.h', + 'file/file_io.cc', + 'file/file_io.h', + 'file/file_io_posix.cc', + 'file/file_io_win.cc', 'file/file_writer.cc', 'file/file_writer.h', 'file/string_file_writer.cc',