Introduce MinidumpWritable, its dependencies, and their tests.

MinidumpWritable is the base class for all minidump-writing operations.

R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/432003005
This commit is contained in:
Mark Mentovai 2014-08-01 12:48:28 -04:00
parent dfe81014f7
commit 995012534e
15 changed files with 2516 additions and 1 deletions

2
DEPS
View File

@ -28,7 +28,7 @@ deps = {
'39bb8956231c997babf0f25befdfb531f4d0b43c', # svn r1958
'crashpad/third_party/mini_chromium/mini_chromium':
Var('chromium_git') + '/chromium/mini_chromium@' +
'7e95e5859f79ef7fe4163e797f99768e13b86132',
'58160e47f55227d1361c65346e0ad4bf26dfaefe',
}
hooks = [

View File

@ -20,6 +20,8 @@
'suppress_wildcard': 1,
'dependencies': [
'compat/compat.gyp:*',
'minidump/minidump.gyp:*',
'util/util.gyp:*',
],
'sources': [
'crashpad.doxy.h',

53
minidump/minidump.gyp Normal file
View File

@ -0,0 +1,53 @@
# 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.
{
'targets': [
{
'target_name': 'minidump',
'type': 'static_library',
'dependencies': [
'../compat/compat.gyp:compat',
'../third_party/mini_chromium/mini_chromium/base/base.gyp:base',
'../util/util.gyp:util',
],
'export_dependent_settings': [
'../compat/compat.gyp:compat',
],
'include_dirs': [
'..',
],
'sources': [
'minidump_writable.cc',
'minidump_writable.h',
],
},
{
'target_name': 'minidump_test',
'type': 'executable',
'dependencies': [
'minidump',
'../third_party/gtest/gtest.gyp:gtest',
'../third_party/mini_chromium/mini_chromium/base/base.gyp:base',
],
'include_dirs': [
'..',
],
'sources': [
'../third_party/gtest/gtest/src/gtest_main.cc',
'minidump_writable_test.cc',
],
},
],
}

View File

@ -0,0 +1,264 @@
// 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 "minidump/minidump_writable.h"
#include "base/logging.h"
#include "util/numeric/safe_assignment.h"
namespace {
const size_t kMaximumAlignment = 16;
} // namespace
namespace crashpad {
namespace internal {
bool MinidumpWritable::WriteEverything(FileWriterInterface* file_writer) {
DCHECK_EQ(state_, kStateMutable);
if (!Freeze()) {
return false;
}
DCHECK_EQ(state_, kStateFrozen);
off_t offset = 0;
std::vector<MinidumpWritable*> write_sequence;
size_t size = WillWriteAtOffset(kPhaseEarly, &offset, &write_sequence);
if (size == kInvalidSize) {
return false;
}
offset += size;
if (WillWriteAtOffset(kPhaseLate, &offset, &write_sequence) == kInvalidSize) {
return false;
}
DCHECK_EQ(state_, kStateWritable);
DCHECK_EQ(write_sequence.front(), this);
for (MinidumpWritable* writable : write_sequence) {
if (!writable->WritePaddingAndObject(file_writer)) {
return false;
}
}
DCHECK_EQ(state_, kStateWritten);
return true;
}
void MinidumpWritable::RegisterRVA(RVA* rva) {
DCHECK_LE(state_, kStateFrozen);
registered_rvas_.push_back(rva);
}
void MinidumpWritable::RegisterLocationDescriptor(
MINIDUMP_LOCATION_DESCRIPTOR* location_descriptor) {
DCHECK_LE(state_, kStateFrozen);
registered_location_descriptors_.push_back(location_descriptor);
}
const size_t MinidumpWritable::kInvalidSize =
std::numeric_limits<size_t>::max();
MinidumpWritable::MinidumpWritable()
: registered_rvas_(),
registered_location_descriptors_(),
leading_pad_bytes_(0),
state_(kStateMutable) {
}
MinidumpWritable::~MinidumpWritable() {
}
bool MinidumpWritable::Freeze() {
DCHECK_EQ(state_, kStateMutable);
state_ = kStateFrozen;
std::vector<MinidumpWritable*> children = Children();
for (MinidumpWritable* child : children) {
if (!child->Freeze()) {
return false;
}
}
return true;
}
size_t MinidumpWritable::Alignment() {
DCHECK_GE(state_, kStateFrozen);
return 4;
}
std::vector<MinidumpWritable*> MinidumpWritable::Children() {
DCHECK_GE(state_, kStateFrozen);
return std::vector<MinidumpWritable*>();
}
MinidumpWritable::Phase MinidumpWritable::WritePhase() {
return kPhaseEarly;
}
size_t MinidumpWritable::WillWriteAtOffset(
Phase phase,
off_t* offset,
std::vector<MinidumpWritable*>* write_sequence) {
off_t local_offset = *offset;
CHECK_GE(local_offset, 0);
size_t leading_pad_bytes_this_phase;
size_t size;
if (phase == WritePhase()) {
DCHECK_EQ(state_, kStateFrozen);
// Add this object to the sequence of MinidumpWritable objects to be
// written.
write_sequence->push_back(this);
size = SizeOfObject();
if (size > 0) {
// Honor this objects request to be aligned to a specific byte boundary.
// Once the alignment is corrected, this object knows exactly what file
// offset it will be written at.
size_t alignment = Alignment();
CHECK_LE(alignment, kMaximumAlignment);
leading_pad_bytes_this_phase =
(alignment - (local_offset % alignment)) % alignment;
local_offset += leading_pad_bytes_this_phase;
*offset = local_offset;
} else {
// If the object is size 0, alignment is of no concern.
leading_pad_bytes_this_phase = 0;
}
leading_pad_bytes_ = leading_pad_bytes_this_phase;
// Now that the file offset that this object will be written at is known,
// let the subclass implementation know in case its interested.
if (!WillWriteAtOffsetImpl(local_offset)) {
return kInvalidSize;
}
// Populate the RVA fields in other objects that have registered to point to
// this one. Typically, a parent object will have registered to point to its
// children, but this can also occur where no parent-child relationship
// exists.
if (!registered_rvas_.empty() ||
!registered_location_descriptors_.empty()) {
RVA local_rva;
if (!AssignIfInRange(&local_rva, local_offset)) {
LOG(ERROR) << "offset " << local_offset << " out of range";
return kInvalidSize;
}
for (RVA* rva : registered_rvas_) {
*rva = local_rva;
}
if (!registered_location_descriptors_.empty()) {
typeof(registered_location_descriptors_[0]->DataSize) local_size;
if (!AssignIfInRange(&local_size, size)) {
LOG(ERROR) << "size " << size << " out of range";
return kInvalidSize;
}
for (MINIDUMP_LOCATION_DESCRIPTOR* location_descriptor :
registered_location_descriptors_) {
location_descriptor->DataSize = local_size;
location_descriptor->Rva = local_rva;
}
}
}
// This object is now considered writable. However, if it contains RVA or
// MINIDUMP_LOCATION_DESCRIPTOR fields, they may not be fully updated yet,
// because its the repsonsibility of these fields pointees to update them.
// Once WillWriteAtOffset has completed running for both phases on an entire
// tree, and the entire tree has moved into kStateFrozen, all RVA and
// MINIDUMP_LOCATION_DESCRIPTOR fields within that tree will be populated.
state_ = kStateWritable;
} else {
if (phase == kPhaseEarly) {
DCHECK_EQ(state_, kStateFrozen);
} else {
DCHECK_EQ(state_, kStateWritable);
}
size = 0;
leading_pad_bytes_this_phase = 0;
}
// Loop over children regardless of whether this object itself will write
// during this phase. An objects children are not required to be written
// during the same phase as their parent.
std::vector<MinidumpWritable*> children = Children();
for (MinidumpWritable* child : children) {
// Use “auto” here because its impossible to know whether size_t (size) or
// off_t (local_offset) is the wider type, and thus what type the result of
// adding these two variables will have.
auto unaligned_child_offset = local_offset + size;
off_t child_offset;
if (!AssignIfInRange(&child_offset, unaligned_child_offset)) {
LOG(ERROR) << "offset " << unaligned_child_offset << " out of range";
return kInvalidSize;
}
size_t child_size =
child->WillWriteAtOffset(phase, &child_offset, write_sequence);
if (child_size == kInvalidSize) {
return kInvalidSize;
}
size += child_size;
}
return leading_pad_bytes_this_phase + size;
}
bool MinidumpWritable::WillWriteAtOffsetImpl(off_t offset) {
return true;
}
bool MinidumpWritable::WritePaddingAndObject(FileWriterInterface* file_writer) {
DCHECK_EQ(state_, kStateWritable);
// The number of elements in kZeroes must be at least one less than the
// maximum Alignment() ever encountered.
const uint8_t kZeroes[kMaximumAlignment - 1] = {};
DCHECK_LE(leading_pad_bytes_, arraysize(kZeroes));
if (leading_pad_bytes_) {
if (!file_writer->Write(&kZeroes, leading_pad_bytes_)) {
return false;
}
}
if (!WriteObject(file_writer)) {
return false;
}
state_ = kStateWritten;
return true;
}
} // namespace internal
} // namespace crashpad

View File

@ -0,0 +1,273 @@
// 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_MINIDUMP_MINIDUMP_WRITABLE_H_
#define CRASHPAD_MINIDUMP_MINIDUMP_WRITABLE_H_
#include <dbghelp.h>
#include <stdint.h>
#include <sys/types.h>
#include <limits>
#include <vector>
#include "base/basictypes.h"
#include "util/file/file_writer.h"
namespace crashpad {
namespace internal {
//! \brief The base class for all content that might be written to a minidump
//! file.
class MinidumpWritable {
public:
//! \brief Writes an object and all of its children to a minidump file.
//!
//! Use this on the root object of a tree of MinidumpWritable objects,
//! typically on a MinidumpFileWriter object.
//!
//! \param[in] file_writer The file writer to receive the minidump files
//! content.
//!
//! \return `true` on success. `false` on failure, with an appropriate message
//! logged.
//!
//! \note Valid in #kStateMutable, and transitions the object and the entire
//! tree beneath it through all states to #kStateWritten.
//!
//! \note This method should rarely be overridden.
virtual bool WriteEverything(FileWriterInterface* file_writer);
//! \brief Registers a file offset pointer as one that should point to the
//! object on which this method is called.
//!
//! Once the file offset at which an object will be written is known (when it
//! enters #kStateWritable), registered RVA pointers will be updated.
//!
//! \param[in] rva A pointer to storage for the file offset that should
//! contain this objects writable file offset, once it is known.
//!
//! \note Valid in #kStateFrozen or any preceding state.
//
// This is public instead of protected because objects of derived classes need
// to be able to register their own pointers with distinct objects.
void RegisterRVA(RVA* rva);
//! \brief Registers a location descriptor as one that should point to the
//! object on which this method is called.
//!
//! Once an objects size and the file offset at it will be written is known
//! (when it enters #kStateFrozen), the relevant data in registered location
//! descriptors will be updated.
//!
//! \param[in] location_descriptor A pointer to a location descriptor that
//! should contain this objects writable size and file offset, once they
//! are known.
//!
//! \note Valid in #kStateFrozen or any preceding state.
//
// This is public instead of protected because objects of derived classes need
// to be able to register their own pointers with distinct objects.
void RegisterLocationDescriptor(
MINIDUMP_LOCATION_DESCRIPTOR* location_descriptor);
protected:
//! \brief Identifies the state of an object.
//!
//! Objects will normally transition through each of these states as they are
//! created, populated with data, and then written to a minidump file.
enum State {
//! \brief The objects properties can be modified.
kStateMutable = 0,
//! \brief The object is “frozen”.
//!
//! Its properties cannot be modified. Pointers to file offsets of other
//! structures may not yet be valid.
kStateFrozen,
//! \brief The object is writable.
//!
//! The file offset at which it will be written is known. Pointers to file
//! offsets of other structures are valid when all objects in a tree are in
//! this state.
kStateWritable,
//! \brief The object has been written to a minidump file.
kStateWritten,
};
//! \brief Identifies the phase during which an object will be written to a
//! minidump file.
enum Phase {
//! \brief Objects that are written to a minidump file “early”.
//!
//! The normal sequence is for an object to write itself and then write all
//! of its children.
kPhaseEarly = 0,
//! \brief Objects that are written to a minidump file “late”.
//!
//! Some objects, such as those capturing memory region snapshots, are
//! written to minidump files after all other objects. This “late” phase
//! identifies such objects. This is useful to improve spatial locality in
//! in minidump files in accordance with expected access patterns: unlike
//! most other data, memory snapshots are large and the entire snapshots do
//! not need to be consulted in order to process a minidump file.
kPhaseLate,
};
//! \brief A size value used to signal failure by methods that return
//! `size_t`.
static const size_t kInvalidSize;
MinidumpWritable();
~MinidumpWritable();
//! \brief The state of the object.
State state() const { return state_; }
//! \brief Transitions the object from #kStateMutable to #kStateFrozen.
//!
//! The default implementation marks the object as frozen and recursively
//! calls Freeze() on all of its children. Subclasses may override this method
//! to perform processing that should only be done once callers have finished
//! populating an object with data. Typically, a subclass implementation would
//! call RegisterRVA() or RegisterLocationDescriptor() on other objects as
//! appropriate, because at the time Freeze() runs, the in-memory locations of
//! RVAs and location descriptors are known and will not change for the
//! remaining duration of an objects lifetime.
//!
//! \return `true` on success. `false` on failure, with an appropriate message
//! logged.
virtual bool Freeze();
//! \brief Returns the amount of space that this object will consume when
//! written to a minidump file, in bytes, not including any leading or
//! trailing padding necessary to maintain proper alignment.
//!
//! \note Valid in #kStateFrozen or any subsequent state.
virtual size_t SizeOfObject() = 0;
//! \brief Returns the objects desired byte-boundary alignment.
//!
//! The default implementation returns `4`. Subclasses may override this as
//! needed.
//!
//! \note Valid in #kStateFrozen or any subsequent state.
virtual size_t Alignment();
//! \brief Returns the objects children.
//!
//! \note Valid in #kStateFrozen or any subsequent state.
virtual std::vector<MinidumpWritable*> Children();
//! \brief Returns the objects desired write phase.
//!
//! The default implementation returns #kPhaseEarly. Subclasses may override
//! this method to alter their write phase.
//!
//! \note Valid in any state.
virtual Phase WritePhase();
//! \brief Prepares the object to be written at a known file offset,
//! transitioning it from #kStateFrozen to #kStateWritable.
//!
//! This method is responsible for determining the final file offset of the
//! object, which may be increased from \a offset to meet alignment
//! requirements. It calls WillWriteAtOffsetImpl() for the benefit of
//! subclasses. It populates all RVAs and location descriptors registered with
//! it via RegisterRVA() and RegisterLocationDescriptor(). It also recurses
//! into all known children.
//!
//! \param[in] phase The phase during which the object will be written. If
//! this does not match Phase(), processing is suppressed, although
//! recursive processing will still occur on all children. This addresses
//! the case where parents and children do not write in the same phase.
//! \param[in] offset The file offset at which the object will be written. The
//! offset may need to be adjusted for alignment.
//! \param[out] write_sequence This object will append itself to this list,
//! such that on return from a recursive tree of WillWriteAtOffset()
//! calls, elements of the vector will be organized in the sequence that
//! the objects will be written to the minidump file.
//!
//! \return The file size consumed by this object and all children, including
//! any padding inserted to meet alignment requirements. On failure,
//! #kInvalidSize, with an appropriate message logged.
//!
//! \note This method cannot be overridden. Subclasses that need to perform
//! processing when an object transitions to #kStateWritable should
//! implement WillWriteAtOffsetImpl(), which is called by this method.
size_t WillWriteAtOffset(Phase phase,
off_t* offset,
std::vector<MinidumpWritable*>* write_sequence);
//! \brief Called once an objects writable file offset is determined, as it
//! transitions into #kStateWritable.
//!
//! Subclasses can override this method if they need to provide additional
//! processing once their writable file offset is known. Typically, this will
//! be done by subclasses that handle certain RVAs themselves instead of using
//! the RegisterRVA() interface.
//!
//! \param[in] offset The file offset at which the object will be written. The
//! value passed to this method will already have been adjusted to meet
//! alignment requirements.
//!
//! \return `true` on success. `false` on error, indicating that the minidump
//! file should not be written.
//!
//! \note Valid in #kStateFrozen. The object will transition to
//! #kStateWritable after this method returns.
virtual bool WillWriteAtOffsetImpl(off_t offset);
//! \brief Writes the object, transitioning it from #kStateWritable to
//! #kStateWritten.
//!
//! Writes any padding necessary to meet alignment requirements, and then
//! calls WriteObject() to write the objects content.
//!
//! \param[in] file_writer The file writer to receive the objects content.
//!
//! \return `true` on success. `false` on error with an appropriate message
//! logged.
//!
//! \note This method cannot be overridden. Subclasses must override
//! WriteObject().
bool WritePaddingAndObject(FileWriterInterface* file_writer);
//! \brief Writes the objects content.
//!
//! \param[in] file_writer The file writer to receive the objects content.
//!
//! \return `true` on success. `false` on error, indicating that the content
//! could not be written to the minidump file.
//!
//! \note Valid in #kStateWritable. The object will transition to
//! #kStateWritten after this method returns.
virtual bool WriteObject(FileWriterInterface* file_writer) = 0;
private:
std::vector<RVA*> registered_rvas_;
std::vector<MINIDUMP_LOCATION_DESCRIPTOR*> registered_location_descriptors_;
size_t leading_pad_bytes_;
State state_;
DISALLOW_COPY_AND_ASSIGN(MinidumpWritable);
};
} // namespace internal
} // namespace crashpad
#endif // CRASHPAD_MINIDUMP_MINIDUMP_WRITABLE_H_

View File

@ -0,0 +1,835 @@
// 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 "minidump/minidump_writable.h"
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "gtest/gtest.h"
#include "util/file/string_file_writer.h"
namespace {
using namespace crashpad;
using namespace testing;
class BaseTestMinidumpWritable : public crashpad::internal::MinidumpWritable {
public:
BaseTestMinidumpWritable()
: MinidumpWritable(),
children_(),
expected_offset_(-1),
alignment_(0),
phase_(kPhaseEarly),
has_alignment_(false),
has_phase_(false),
verified_(false) {}
~BaseTestMinidumpWritable() { EXPECT_TRUE(verified_); }
void SetAlignment(size_t alignment) {
alignment_ = alignment;
has_alignment_ = true;
}
void AddChild(BaseTestMinidumpWritable* child) { children_.push_back(child); }
void SetPhaseLate() {
phase_ = kPhaseLate;
has_phase_ = true;
}
void Verify() {
verified_ = true;
EXPECT_EQ(kStateWritten, state());
for (BaseTestMinidumpWritable* child : children_) {
child->Verify();
}
}
protected:
virtual bool Freeze() override {
EXPECT_EQ(kStateMutable, state());
bool rv = MinidumpWritable::Freeze();
EXPECT_TRUE(rv);
EXPECT_EQ(kStateFrozen, state());
return rv;
}
virtual size_t Alignment() override {
EXPECT_GE(state(), kStateFrozen);
return has_alignment_ ? alignment_ : MinidumpWritable::Alignment();
}
virtual std::vector<MinidumpWritable*> Children() override {
EXPECT_GE(state(), kStateFrozen);
if (!children_.empty()) {
std::vector<MinidumpWritable*> children;
for (BaseTestMinidumpWritable* child : children_) {
children.push_back(child);
}
return children;
}
return MinidumpWritable::Children();
}
virtual Phase WritePhase() override {
return has_phase_ ? phase_ : MinidumpWritable::Phase();
}
virtual bool WillWriteAtOffsetImpl(off_t offset) override {
EXPECT_EQ(state(), kStateFrozen);
expected_offset_ = offset;
bool rv = MinidumpWritable::WillWriteAtOffsetImpl(offset);
EXPECT_TRUE(rv);
return rv;
}
virtual bool WriteObject(FileWriterInterface* file_writer) override {
EXPECT_EQ(state(), kStateWritable);
EXPECT_EQ(expected_offset_, file_writer->Seek(0, SEEK_CUR));
// Subclasses must override this.
return false;
}
private:
std::vector<BaseTestMinidumpWritable*> children_;
off_t expected_offset_;
size_t alignment_;
Phase phase_;
bool has_alignment_;
bool has_phase_;
bool verified_;
DISALLOW_COPY_AND_ASSIGN(BaseTestMinidumpWritable);
};
class TestStringMinidumpWritable final : public BaseTestMinidumpWritable {
public:
TestStringMinidumpWritable() : BaseTestMinidumpWritable(), data_() {}
~TestStringMinidumpWritable() {}
void SetData(const std::string& string) { data_ = string; }
protected:
virtual size_t SizeOfObject() override {
EXPECT_GE(state(), kStateFrozen);
return data_.size();
}
virtual bool WriteObject(FileWriterInterface* file_writer) override {
BaseTestMinidumpWritable::WriteObject(file_writer);
EXPECT_TRUE(file_writer->Write(&data_[0], data_.size()));
return true;
}
private:
std::string data_;
DISALLOW_COPY_AND_ASSIGN(TestStringMinidumpWritable);
};
TEST(MinidumpWritable, MinidumpWritable) {
StringFileWriter writer;
{
SCOPED_TRACE("empty");
writer.Reset();
TestStringMinidumpWritable string_writable;
EXPECT_TRUE(string_writable.WriteEverything(&writer));
EXPECT_TRUE(writer.string().empty());
string_writable.Verify();
}
{
SCOPED_TRACE("childless");
writer.Reset();
TestStringMinidumpWritable string_writable;
string_writable.SetData("a");
EXPECT_TRUE(string_writable.WriteEverything(&writer));
EXPECT_EQ(1u, writer.string().size());
EXPECT_EQ("a", writer.string());
string_writable.Verify();
}
{
SCOPED_TRACE("parent-child");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("b");
TestStringMinidumpWritable child;
child.SetData("c");
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ(std::string("b\0\0\0c", 5), writer.string());
parent.Verify();
}
{
SCOPED_TRACE("base alignment 2");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("de");
TestStringMinidumpWritable child;
child.SetData("f");
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ(std::string("de\0\0f", 5), writer.string());
parent.Verify();
}
{
SCOPED_TRACE("base alignment 3");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("ghi");
TestStringMinidumpWritable child;
child.SetData("j");
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ(std::string("ghi\0j", 5), writer.string());
parent.Verify();
}
{
SCOPED_TRACE("base alignment 4");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("klmn");
TestStringMinidumpWritable child;
child.SetData("o");
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ("klmno", writer.string());
parent.Verify();
}
{
SCOPED_TRACE("base alignment 5");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("pqrst");
TestStringMinidumpWritable child;
child.SetData("u");
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(9u, writer.string().size());
EXPECT_EQ(std::string("pqrst\0\0\0u", 9), writer.string());
parent.Verify();
}
{
SCOPED_TRACE("two children");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("parent");
TestStringMinidumpWritable child_0;
child_0.SetData("child_0");
parent.AddChild(&child_0);
TestStringMinidumpWritable child_1;
child_1.SetData("child_1");
parent.AddChild(&child_1);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(23u, writer.string().size());
EXPECT_EQ(std::string("parent\0\0child_0\0child_1", 23), writer.string());
parent.Verify();
}
{
SCOPED_TRACE("grandchild");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("parent");
TestStringMinidumpWritable child;
child.SetData("child");
parent.AddChild(&child);
TestStringMinidumpWritable grandchild;
grandchild.SetData("grandchild");
child.AddChild(&grandchild);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(26u, writer.string().size());
EXPECT_EQ(std::string("parent\0\0child\0\0\0grandchild", 26),
writer.string());
parent.Verify();
}
{
SCOPED_TRACE("grandchild with empty parent");
writer.Reset();
TestStringMinidumpWritable parent;
TestStringMinidumpWritable child;
child.SetData("child");
parent.AddChild(&child);
TestStringMinidumpWritable grandchild;
grandchild.SetData("grandchild");
child.AddChild(&grandchild);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(18u, writer.string().size());
EXPECT_EQ(std::string("child\0\0\0grandchild", 18), writer.string());
parent.Verify();
}
{
SCOPED_TRACE("grandchild with empty child");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("parent");
TestStringMinidumpWritable child;
parent.AddChild(&child);
TestStringMinidumpWritable grandchild;
grandchild.SetData("grandchild");
child.AddChild(&grandchild);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(18u, writer.string().size());
EXPECT_EQ(std::string("parent\0\0grandchild", 18), writer.string());
parent.Verify();
}
{
SCOPED_TRACE("grandchild with empty grandchild");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("parent");
TestStringMinidumpWritable child;
child.SetData("child");
parent.AddChild(&child);
TestStringMinidumpWritable grandchild;
child.AddChild(&grandchild);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(13u, writer.string().size());
EXPECT_EQ(std::string("parent\0\0child", 13), writer.string());
parent.Verify();
}
{
SCOPED_TRACE("grandchild with late-phase grandchild");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("parent");
TestStringMinidumpWritable child;
child.SetData("child");
parent.AddChild(&child);
TestStringMinidumpWritable grandchild;
grandchild.SetData("grandchild");
grandchild.SetPhaseLate();
child.AddChild(&grandchild);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(26u, writer.string().size());
EXPECT_EQ(std::string("parent\0\0child\0\0\0grandchild", 26),
writer.string());
parent.Verify();
}
{
SCOPED_TRACE("grandchild with late-phase child");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("parent");
TestStringMinidumpWritable child;
child.SetData("child");
child.SetPhaseLate();
parent.AddChild(&child);
TestStringMinidumpWritable grandchild;
grandchild.SetData("grandchild");
child.AddChild(&grandchild);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(25u, writer.string().size());
EXPECT_EQ(std::string("parent\0\0grandchild\0\0child", 25),
writer.string());
parent.Verify();
}
{
SCOPED_TRACE("family tree");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("P..");
TestStringMinidumpWritable child_0;
child_0.SetData("C0.");
parent.AddChild(&child_0);
TestStringMinidumpWritable child_1;
child_1.SetData("C1.");
parent.AddChild(&child_1);
TestStringMinidumpWritable grandchild_00;
grandchild_00.SetData("G00");
child_0.AddChild(&grandchild_00);
TestStringMinidumpWritable grandchild_01;
grandchild_01.SetData("G01");
child_0.AddChild(&grandchild_01);
TestStringMinidumpWritable grandchild_10;
grandchild_10.SetData("G10");
child_1.AddChild(&grandchild_10);
TestStringMinidumpWritable grandchild_11;
grandchild_11.SetData("G11");
child_1.AddChild(&grandchild_11);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(27u, writer.string().size());
EXPECT_EQ(std::string("P..\0C0.\0G00\0G01\0C1.\0G10\0G11", 27),
writer.string());
parent.Verify();
}
{
SCOPED_TRACE("family tree with C0 late");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("P..");
TestStringMinidumpWritable child_0;
child_0.SetData("C0.");
child_0.SetPhaseLate();
parent.AddChild(&child_0);
TestStringMinidumpWritable child_1;
child_1.SetData("C1.");
parent.AddChild(&child_1);
TestStringMinidumpWritable grandchild_00;
grandchild_00.SetData("G00");
child_0.AddChild(&grandchild_00);
TestStringMinidumpWritable grandchild_01;
grandchild_01.SetData("G01");
child_0.AddChild(&grandchild_01);
TestStringMinidumpWritable grandchild_10;
grandchild_10.SetData("G10");
child_1.AddChild(&grandchild_10);
TestStringMinidumpWritable grandchild_11;
grandchild_11.SetData("G11");
child_1.AddChild(&grandchild_11);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(27u, writer.string().size());
EXPECT_EQ(std::string("P..\0G00\0G01\0C1.\0G10\0G11\0C0.", 27),
writer.string());
parent.Verify();
}
{
SCOPED_TRACE("family tree with G0 late");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("P..");
TestStringMinidumpWritable child_0;
child_0.SetData("C0.");
parent.AddChild(&child_0);
TestStringMinidumpWritable child_1;
child_1.SetData("C1.");
parent.AddChild(&child_1);
TestStringMinidumpWritable grandchild_00;
grandchild_00.SetData("G00");
grandchild_00.SetPhaseLate();
child_0.AddChild(&grandchild_00);
TestStringMinidumpWritable grandchild_01;
grandchild_01.SetData("G01");
grandchild_01.SetPhaseLate();
child_0.AddChild(&grandchild_01);
TestStringMinidumpWritable grandchild_10;
grandchild_10.SetData("G10");
child_1.AddChild(&grandchild_10);
TestStringMinidumpWritable grandchild_11;
grandchild_11.SetData("G11");
child_1.AddChild(&grandchild_11);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(27u, writer.string().size());
EXPECT_EQ(std::string("P..\0C0.\0C1.\0G10\0G11\0G00\0G01", 27),
writer.string());
parent.Verify();
}
{
SCOPED_TRACE("align 1");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("p");
TestStringMinidumpWritable child;
child.SetData("c");
child.SetAlignment(1);
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(2u, writer.string().size());
EXPECT_EQ("pc", writer.string());
parent.Verify();
}
{
SCOPED_TRACE("align 2");
writer.Reset();
TestStringMinidumpWritable parent;
parent.SetData("p");
TestStringMinidumpWritable child;
child.SetData("c");
child.SetAlignment(2);
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
EXPECT_EQ(3u, writer.string().size());
EXPECT_EQ(std::string("p\0c", 3), writer.string());
parent.Verify();
}
}
class TestRVAMinidumpWritable final : public BaseTestMinidumpWritable {
public:
TestRVAMinidumpWritable() : BaseTestMinidumpWritable(), rva_() {}
~TestRVAMinidumpWritable() {}
void SetRVA(MinidumpWritable* other) { other->RegisterRVA(&rva_); }
protected:
virtual size_t SizeOfObject() override {
EXPECT_GE(state(), kStateFrozen);
return sizeof(rva_);
}
virtual bool WriteObject(FileWriterInterface* file_writer) override {
BaseTestMinidumpWritable::WriteObject(file_writer);
EXPECT_TRUE(file_writer->Write(&rva_, sizeof(rva_)));
return true;
}
private:
RVA rva_;
DISALLOW_COPY_AND_ASSIGN(TestRVAMinidumpWritable);
};
RVA RVAAtIndex(const std::string& string, size_t index) {
return *reinterpret_cast<const RVA*>(&string[index * sizeof(RVA)]);
}
TEST(MinidumpWritable, RVA) {
StringFileWriter writer;
{
SCOPED_TRACE("unset");
writer.Reset();
TestRVAMinidumpWritable rva_writable;
EXPECT_TRUE(rva_writable.WriteEverything(&writer));
ASSERT_EQ(sizeof(RVA), writer.string().size());
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
rva_writable.Verify();
}
{
SCOPED_TRACE("self");
writer.Reset();
TestRVAMinidumpWritable rva_writable;
rva_writable.SetRVA(&rva_writable);
EXPECT_TRUE(rva_writable.WriteEverything(&writer));
ASSERT_EQ(sizeof(RVA), writer.string().size());
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
rva_writable.Verify();
}
{
SCOPED_TRACE("parent-child self");
writer.Reset();
TestRVAMinidumpWritable parent;
parent.SetRVA(&parent);
TestRVAMinidumpWritable child;
child.SetRVA(&child);
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
ASSERT_EQ(2 * sizeof(RVA), writer.string().size());
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 1));
parent.Verify();
}
{
SCOPED_TRACE("parent-child only");
writer.Reset();
TestRVAMinidumpWritable parent;
TestRVAMinidumpWritable child;
parent.SetRVA(&child);
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
ASSERT_EQ(2 * sizeof(RVA), writer.string().size());
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 1));
parent.Verify();
}
{
SCOPED_TRACE("parent-child circular");
writer.Reset();
TestRVAMinidumpWritable parent;
TestRVAMinidumpWritable child;
parent.SetRVA(&child);
child.SetRVA(&parent);
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
ASSERT_EQ(2 * sizeof(RVA), writer.string().size());
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 1));
parent.Verify();
}
{
SCOPED_TRACE("grandchildren");
writer.Reset();
TestRVAMinidumpWritable parent;
TestRVAMinidumpWritable child;
parent.SetRVA(&child);
parent.AddChild(&child);
TestRVAMinidumpWritable grandchild_0;
grandchild_0.SetRVA(&child);
child.AddChild(&grandchild_0);
TestRVAMinidumpWritable grandchild_1;
grandchild_1.SetRVA(&child);
child.AddChild(&grandchild_1);
TestRVAMinidumpWritable grandchild_2;
grandchild_2.SetRVA(&child);
child.AddChild(&grandchild_2);
EXPECT_TRUE(parent.WriteEverything(&writer));
ASSERT_EQ(5 * sizeof(RVA), writer.string().size());
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 0));
EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 1));
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 2));
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 3));
EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 4));
parent.Verify();
}
}
class TestLocationDescriptorMinidumpWritable final
: public BaseTestMinidumpWritable {
public:
TestLocationDescriptorMinidumpWritable()
: BaseTestMinidumpWritable(), location_descriptor_(), string_() {}
~TestLocationDescriptorMinidumpWritable() {}
void SetLocationDescriptor(MinidumpWritable* other) {
other->RegisterLocationDescriptor(&location_descriptor_);
}
void SetString(const std::string& string) { string_ = string; }
protected:
virtual size_t SizeOfObject() override {
EXPECT_GE(state(), kStateFrozen);
// NUL-terminate.
return sizeof(location_descriptor_) + string_.size() + 1;
}
virtual bool WriteObject(FileWriterInterface* file_writer) override {
BaseTestMinidumpWritable::WriteObject(file_writer);
WritableIoVec iov;
iov.iov_base = &location_descriptor_;
iov.iov_len = sizeof(location_descriptor_);
std::vector<WritableIoVec> iovecs(1, iov);
// NUL-terminate.
iov.iov_base = &string_[0];
iov.iov_len = string_.size() + 1;
iovecs.push_back(iov);
EXPECT_TRUE(file_writer->WriteIoVec(&iovecs));
return true;
}
private:
MINIDUMP_LOCATION_DESCRIPTOR location_descriptor_;
std::string string_;
DISALLOW_COPY_AND_ASSIGN(TestLocationDescriptorMinidumpWritable);
};
struct LocationDescriptorAndData {
MINIDUMP_LOCATION_DESCRIPTOR location_descriptor;
char string[1];
};
const LocationDescriptorAndData* LDDAtIndex(const std::string& string,
size_t index) {
return reinterpret_cast<const LocationDescriptorAndData*>(&string[index]);
}
TEST(MinidumpWritable, LocationDescriptor) {
StringFileWriter writer;
{
SCOPED_TRACE("unset");
writer.Reset();
TestLocationDescriptorMinidumpWritable location_descriptor_writable;
EXPECT_TRUE(location_descriptor_writable.WriteEverything(&writer));
ASSERT_EQ(9u, writer.string().size());
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
EXPECT_EQ(0u, ldd->location_descriptor.DataSize);
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
location_descriptor_writable.Verify();
}
{
SCOPED_TRACE("self");
writer.Reset();
TestLocationDescriptorMinidumpWritable location_descriptor_writable;
location_descriptor_writable.SetLocationDescriptor(
&location_descriptor_writable);
EXPECT_TRUE(location_descriptor_writable.WriteEverything(&writer));
ASSERT_EQ(9u, writer.string().size());
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
EXPECT_EQ(9u, ldd->location_descriptor.DataSize);
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
location_descriptor_writable.Verify();
}
{
SCOPED_TRACE("self with data");
writer.Reset();
TestLocationDescriptorMinidumpWritable location_descriptor_writable;
location_descriptor_writable.SetLocationDescriptor(
&location_descriptor_writable);
location_descriptor_writable.SetString("zz");
EXPECT_TRUE(location_descriptor_writable.WriteEverything(&writer));
ASSERT_EQ(11u, writer.string().size());
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
EXPECT_EQ(11u, ldd->location_descriptor.DataSize);
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
EXPECT_STREQ("zz", ldd->string);
location_descriptor_writable.Verify();
}
{
SCOPED_TRACE("parent-child self");
writer.Reset();
TestLocationDescriptorMinidumpWritable parent;
parent.SetLocationDescriptor(&parent);
parent.SetString("yy");
TestLocationDescriptorMinidumpWritable child;
child.SetLocationDescriptor(&child);
child.SetString("x");
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
ASSERT_EQ(22u, writer.string().size());
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
EXPECT_EQ(11u, ldd->location_descriptor.DataSize);
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
EXPECT_STREQ("yy", ldd->string);
ldd = LDDAtIndex(writer.string(), 12);
EXPECT_EQ(10u, ldd->location_descriptor.DataSize);
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
EXPECT_STREQ("x", ldd->string);
parent.Verify();
}
{
SCOPED_TRACE("parent-child only");
writer.Reset();
TestLocationDescriptorMinidumpWritable parent;
TestLocationDescriptorMinidumpWritable child;
parent.SetLocationDescriptor(&child);
parent.SetString("www");
child.SetString("vv");
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
ASSERT_EQ(23u, writer.string().size());
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
EXPECT_EQ(11u, ldd->location_descriptor.DataSize);
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
EXPECT_STREQ("www", ldd->string);
ldd = LDDAtIndex(writer.string(), 12);
EXPECT_EQ(0u, ldd->location_descriptor.DataSize);
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
EXPECT_STREQ("vv", ldd->string);
parent.Verify();
}
{
SCOPED_TRACE("parent-child circular");
writer.Reset();
TestLocationDescriptorMinidumpWritable parent;
TestLocationDescriptorMinidumpWritable child;
parent.SetLocationDescriptor(&child);
parent.SetString("uuuu");
child.SetLocationDescriptor(&parent);
child.SetString("tttt");
parent.AddChild(&child);
EXPECT_TRUE(parent.WriteEverything(&writer));
ASSERT_EQ(29u, writer.string().size());
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
EXPECT_EQ(13u, ldd->location_descriptor.DataSize);
EXPECT_EQ(16u, ldd->location_descriptor.Rva);
EXPECT_STREQ("uuuu", ldd->string);
ldd = LDDAtIndex(writer.string(), 16);
EXPECT_EQ(13u, ldd->location_descriptor.DataSize);
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
EXPECT_STREQ("tttt", ldd->string);
parent.Verify();
}
{
SCOPED_TRACE("grandchildren");
writer.Reset();
TestLocationDescriptorMinidumpWritable parent;
TestLocationDescriptorMinidumpWritable child;
parent.SetLocationDescriptor(&child);
parent.SetString("s");
parent.AddChild(&child);
child.SetString("r");
TestLocationDescriptorMinidumpWritable grandchild_0;
grandchild_0.SetLocationDescriptor(&child);
grandchild_0.SetString("q");
child.AddChild(&grandchild_0);
TestLocationDescriptorMinidumpWritable grandchild_1;
grandchild_1.SetLocationDescriptor(&child);
grandchild_1.SetString("p");
child.AddChild(&grandchild_1);
TestLocationDescriptorMinidumpWritable grandchild_2;
grandchild_2.SetLocationDescriptor(&child);
grandchild_2.SetString("o");
child.AddChild(&grandchild_2);
EXPECT_TRUE(parent.WriteEverything(&writer));
ASSERT_EQ(58u, writer.string().size());
const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0);
EXPECT_EQ(10u, ldd->location_descriptor.DataSize);
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
EXPECT_STREQ("s", ldd->string);
ldd = LDDAtIndex(writer.string(), 12);
EXPECT_EQ(0u, ldd->location_descriptor.DataSize);
EXPECT_EQ(0u, ldd->location_descriptor.Rva);
EXPECT_STREQ("r", ldd->string);
ldd = LDDAtIndex(writer.string(), 24);
EXPECT_EQ(10u, ldd->location_descriptor.DataSize);
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
EXPECT_STREQ("q", ldd->string);
ldd = LDDAtIndex(writer.string(), 36);
EXPECT_EQ(10u, ldd->location_descriptor.DataSize);
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
EXPECT_STREQ("p", ldd->string);
ldd = LDDAtIndex(writer.string(), 48);
EXPECT_EQ(10u, ldd->location_descriptor.DataSize);
EXPECT_EQ(12u, ldd->location_descriptor.Rva);
EXPECT_STREQ("o", ldd->string);
parent.Verify();
}
}
} // namespace

75
util/file/fd_io.cc Normal file
View File

@ -0,0 +1,75 @@
// 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/fd_io.h"
#include <unistd.h>
#include "base/posix/eintr_wrapper.h"
namespace {
struct ReadTraits {
typedef void* VoidBufferType;
typedef char* CharBufferType;
static ssize_t Operate(int fd, CharBufferType buffer, size_t size) {
return read(fd, buffer, size);
}
};
struct WriteTraits {
typedef const void* VoidBufferType;
typedef const char* CharBufferType;
static ssize_t Operate(int fd, CharBufferType buffer, size_t size) {
return write(fd, buffer, size);
}
};
template <typename Traits>
ssize_t ReadOrWrite(int fd,
typename Traits::VoidBufferType buffer,
size_t size) {
typename Traits::CharBufferType buffer_c =
reinterpret_cast<typename Traits::CharBufferType>(buffer);
ssize_t total_bytes = 0;
while (size > 0) {
ssize_t 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 {
ssize_t ReadFD(int fd, void* buffer, size_t size) {
return ReadOrWrite<ReadTraits>(fd, buffer, size);
}
ssize_t WriteFD(int fd, const void* buffer, size_t size) {
return ReadOrWrite<WriteTraits>(fd, buffer, size);
}
} // namespace crashpad

50
util/file/fd_io.h Normal file
View File

@ -0,0 +1,50 @@
// 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 <sys/types.h>
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
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
ssize_t WriteFD(int fd, const void* buffer, size_t size);
} // namespace crashpad
#endif // CRASHPAD_UTIL_FILE_FD_IO_H_

149
util/file/file_writer.cc Normal file
View File

@ -0,0 +1,149 @@
// 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_writer.h"
#include <algorithm>
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "util/file/fd_io.h"
namespace crashpad {
// Ensure type compatibility between WritableIoVec and iovec.
COMPILE_ASSERT(sizeof(WritableIoVec) == sizeof(iovec), WritableIoVec_size);
COMPILE_ASSERT(offsetof(WritableIoVec, iov_base) == offsetof(iovec, iov_base),
WritableIoVec_base_offset);
COMPILE_ASSERT(offsetof(WritableIoVec, iov_len) == offsetof(iovec, iov_len),
WritableIoVec_len_offset);
FileWriter::FileWriter() : fd_() {
}
FileWriter::~FileWriter() {
}
bool FileWriter::Open(const base::FilePath& path, int oflag, mode_t mode) {
CHECK(!fd_.is_valid());
DCHECK((oflag & O_WRONLY) || (oflag & O_RDWR));
fd_.reset(HANDLE_EINTR(open(path.value().c_str(), oflag, mode)));
if (!fd_.is_valid()) {
PLOG(ERROR) << "open " << path.value();
return false;
}
return true;
}
void FileWriter::Close() {
CHECK(fd_.is_valid());
fd_.reset();
}
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);
if (written < 0) {
PLOG(ERROR) << "write";
return false;
} else if (written == 0) {
LOG(ERROR) << "write: returned 0";
return false;
}
return true;
}
bool FileWriter::WriteIoVec(std::vector<WritableIoVec>* iovecs) {
DCHECK(fd_.is_valid());
ssize_t size = 0;
for (const WritableIoVec& iov : *iovecs) {
// TODO(mark): Check to avoid overflow of ssize_t, and fail with EINVAL.
size += iov.iov_len;
}
// Get an iovec*, because thats what writev wants. The only difference
// between WritableIoVec and iovec is that WritableIoVecs iov_base is a
// pointer to a const buffer, where iovecs iov_base isnt. writev doesnt
// actually write to the data, so this cast is safe here. iovecs iov_base is
// non-const because the same structure is used for readv and writev, and
// readv needs to write to the buffer that iov_base points to.
iovec* iov = reinterpret_cast<iovec*>(&(*iovecs)[0]);
size_t remaining_iovecs = iovecs->size();
while (size > 0) {
size_t writev_iovec_count =
std::min(remaining_iovecs, static_cast<size_t>(IOV_MAX));
ssize_t written = HANDLE_EINTR(writev(fd_.get(), iov, writev_iovec_count));
if (written < 0) {
PLOG(ERROR) << "writev";
return false;
} else if (written == 0) {
LOG(ERROR) << "writev: returned 0";
return false;
}
size -= written;
DCHECK_GE(size, 0);
if (size == 0) {
remaining_iovecs = 0;
break;
}
while (written > 0) {
size_t wrote_this_iovec =
std::min(static_cast<size_t>(written), iov->iov_len);
written -= wrote_this_iovec;
if (wrote_this_iovec < iov->iov_len) {
iov->iov_base =
reinterpret_cast<char*>(iov->iov_base) + wrote_this_iovec;
iov->iov_len -= wrote_this_iovec;
} else {
++iov;
--remaining_iovecs;
}
}
}
DCHECK_EQ(remaining_iovecs, 0u);
#ifndef NDEBUG
// The interface says that |iovecs| is not sacred, so scramble it to make sure
// that nobody depends on it.
memset(&(*iovecs)[0], 0xa5, sizeof((*iovecs)[0]) * iovecs->size());
#endif
return true;
}
off_t FileWriter::Seek(off_t offset, int whence) {
DCHECK(fd_.is_valid());
off_t rv = lseek(fd_.get(), offset, whence);
if (rv < 0) {
PLOG(ERROR) << "lseek";
}
return rv;
}
} // namespace crashpad

130
util/file/file_writer.h Normal file
View File

@ -0,0 +1,130 @@
// 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_WRITER_H_
#define CRASHPAD_UTIL_FILE_FILE_WRITER_H_
#include <fcntl.h>
#include <stddef.h>
#include <sys/uio.h>
#include <unistd.h>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
namespace crashpad {
//! \brief A version of `iovec` with a `const` #iov_base field.
//!
//! This structure is intended to be used for write operations.
//
// Type compatibility with iovec is tested with static assertions in the
// implementation file.
struct WritableIoVec {
//! \brief The base address of a memory region for output.
const void* iov_base;
//! \brief The size of the memory pointed to by #iov_base.
size_t iov_len;
};
//! \brief An interface to write to files and other file-like objects with POSIX
//! semantics.
class FileWriterInterface {
public:
//! \brief Wraps `write()` or provides an alternate implementation with
//! identical semantics. This method will write the entire buffer,
//! continuing after a short write or after being interrupted.
//!
//! \return `true` if the operation succeeded, `false` if it failed, with an
//! error message logged.
virtual bool Write(const void* data, size_t size) = 0;
//! \brief Wraps `writev()` or provides an alternate implementation with
//! identical semantics. This method will write the entire buffer,
//! continuing after a short write or after being interrupted.
//!
//! \return `true` if the operation succeeded, `false` if it failed, with an
//! error message logged.
//!
//! \note The contents of \a iovecs are undefined when this method returns.
virtual bool WriteIoVec(std::vector<WritableIoVec>* iovecs) = 0;
//! \brief Wraps `lseek()` or provides an alternate implementation with
//! identical semantics.
//!
//! \return The return value of `lseek()`. `-1` on failure, with an error
//! message logged.
virtual off_t Seek(off_t offset, int whence) = 0;
protected:
~FileWriterInterface() {}
};
//! \brief A file writer implementation that wraps traditional POSIX file
//! operations on files accessed through the filesystem.
class FileWriter : public FileWriterInterface {
public:
FileWriter();
~FileWriter();
//! \brief Wraps `open()`.
//!
//! \return `true` if the operation succeeded, `false` if it failed, with an
//! error message logged.
//!
//! \note After a successful call, this method cannot be called again until
//! after Close().
bool Open(const base::FilePath& path, int oflag, mode_t mode);
//! \brief Wraps `close().`
//!
//! \note It is only valid to call this method on an object that has had a
//! successful Open() that has not yet been matched by a subsequent call
//! to this method.
void Close();
// FileWriterInterface:
//! \copydoc FileWriterInterface::Write()
//!
//! \note It is only valid to call this method between a successful Open() and
//! a Close().
virtual bool Write(const void* data, size_t size) override;
//! \copydoc FileWriterInterface::WriteIoVec()
//!
//! \note It is only valid to call this method between a successful Open() and
//! a Close().
virtual bool WriteIoVec(std::vector<WritableIoVec>* iovecs) override;
//! \copydoc FileWriterInterface::Seek()
//!
//! \note It is only valid to call this method between a successful Open() and
//! a Close().
virtual off_t Seek(off_t offset, int whence) override;
private:
base::ScopedFD fd_;
DISALLOW_COPY_AND_ASSIGN(FileWriter);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_FILE_FILE_WRITER_H_

View File

@ -0,0 +1,137 @@
// 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/string_file_writer.h"
#include <string.h>
#include "base/logging.h"
#include "base/numerics/safe_math.h"
#include "util/numeric/safe_assignment.h"
namespace crashpad {
StringFileWriter::StringFileWriter() : string_(), offset_(0) {
}
StringFileWriter::~StringFileWriter() {
}
void StringFileWriter::Reset() {
string_.clear();
offset_ = 0;
}
bool StringFileWriter::Write(const void* data, size_t size) {
DCHECK(offset_.IsValid());
const size_t offset = offset_.ValueOrDie();
if (offset > string_.size()) {
string_.resize(offset);
}
base::CheckedNumeric<ssize_t> new_offset = offset_;
new_offset += size;
if (!new_offset.IsValid()) {
LOG(ERROR) << "Write(): file too large";
return false;
}
string_.replace(offset, size, reinterpret_cast<const char*>(data), size);
offset_ = new_offset;
return true;
}
bool StringFileWriter::WriteIoVec(std::vector<WritableIoVec>* iovecs) {
DCHECK(offset_.IsValid());
if (iovecs->empty()) {
LOG(ERROR) << "WriteIoVec(): no iovecs";
return false;
}
// Avoid writing anything at all if it would cause an overflow.
base::CheckedNumeric<ssize_t> new_offset = offset_;
for (const WritableIoVec& iov : *iovecs) {
new_offset += iov.iov_len;
if (!new_offset.IsValid()) {
LOG(ERROR) << "WriteIoVec(): file too large";
return false;
}
}
for (const WritableIoVec& iov : *iovecs) {
if (!Write(iov.iov_base, iov.iov_len)) {
return false;
}
}
#ifndef NDEBUG
// The interface says that |iovecs| is not sacred, so scramble it to make sure
// that nobody depends on it.
memset(&(*iovecs)[0], 0xa5, sizeof((*iovecs)[0]) * iovecs->size());
#endif
return true;
}
off_t StringFileWriter::Seek(off_t offset, int whence) {
DCHECK(offset_.IsValid());
size_t base_offset;
switch (whence) {
case SEEK_SET:
base_offset = 0;
break;
case SEEK_CUR:
base_offset = offset_.ValueOrDie();
break;
case SEEK_END:
base_offset = string_.size();
break;
default:
LOG(ERROR) << "Seek(): invalid whence " << whence;
return -1;
}
off_t offset_offt;
if (!AssignIfInRange(&offset_offt, base_offset)) {
LOG(ERROR) << "Seek(): base_offset " << base_offset << " invalid for off_t";
return -1;
}
base::CheckedNumeric<off_t> new_offset(offset_offt);
new_offset += offset;
if (!new_offset.IsValid()) {
LOG(ERROR) << "Seek(): new_offset invalid";
return -1;
}
if (!AssignIfInRange(&offset_offt, new_offset.ValueOrDie())) {
LOG(ERROR) << "Seek(): new_offset " << new_offset.ValueOrDie()
<< " invalid for size_t";
return -1;
}
offset_ = offset_offt;
return offset_.ValueOrDie();
}
} // namespace crashpad

View File

@ -0,0 +1,70 @@
// 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_STRING_FILE_WRITER_H_
#define CRASHPAD_UTIL_FILE_STRING_FILE_WRITER_H_
#include <sys/types.h>
#include <string>
#include "base/basictypes.h"
#include "base/numerics/safe_math.h"
#include "util/file/file_writer.h"
namespace crashpad {
//! \brief A file writer backed by a virtual file, as opposed to a file on disk
//! or other operating system file descriptor-based file.
//!
//! The virtual file is a buffer in memory. This class is convenient for use
//! with other code that normally expects to write files, when it is impractical
//! or inconvenient to write a file. It is expected that tests, in particular,
//! will benefit from using this class.
class StringFileWriter : public FileWriterInterface {
public:
StringFileWriter();
~StringFileWriter();
//! \brief Returns a string containing the virtual files contents.
const std::string& string() const { return string_; }
//! \brief Resets the virtual files contents to be empty, and resets its file
//! position to `0`.
void Reset();
// FileWriterInterface:
virtual bool Write(const void* data, size_t size) override;
virtual bool WriteIoVec(std::vector<WritableIoVec>* iovecs) override;
virtual off_t Seek(off_t offset, int whence) override;
private:
//! \brief The virtual files contents.
std::string string_;
//! \brief The file offset of the virtual file.
//!
//! \note This is stored in a `size_t` to match the characteristics of
//! #string_, the `std::string` used to store the virtual files contents.
//! This type will have different characteristics than the `off_t` used to
//! report file offsets. The implementation must take care when converting
//! between these distinct types.
base::CheckedNumeric<size_t> offset_;
DISALLOW_COPY_AND_ASSIGN(StringFileWriter);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_FILE_STRING_FILE_WRITER_H_

View File

@ -0,0 +1,378 @@
// 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/string_file_writer.h"
#include <algorithm>
#include <limits>
#include "gtest/gtest.h"
namespace {
using namespace crashpad;
TEST(StringFileWriter, EmptyFile) {
StringFileWriter writer;
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("", 0));
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
}
TEST(StringFileWriter, OneByteFile) {
StringFileWriter writer;
EXPECT_TRUE(writer.Write("a", 1));
EXPECT_EQ(1u, writer.string().size());
EXPECT_EQ("a", writer.string());
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(0, writer.Seek(0, SEEK_SET));
EXPECT_TRUE(writer.Write("b", 1));
EXPECT_EQ(1u, writer.string().size());
EXPECT_EQ("b", writer.string());
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(0, writer.Seek(0, SEEK_SET));
EXPECT_TRUE(writer.Write("\0", 1));
EXPECT_EQ(1u, writer.string().size());
EXPECT_EQ('\0', writer.string()[0]);
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
}
TEST(StringFileWriter, Reset) {
StringFileWriter writer;
EXPECT_TRUE(writer.Write("abc", 3));
EXPECT_EQ(3u, writer.string().size());
EXPECT_EQ("abc", writer.string());
EXPECT_EQ(3, writer.Seek(0, SEEK_CUR));
writer.Reset();
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("de", 2));
EXPECT_EQ(2u, writer.string().size());
EXPECT_EQ("de", writer.string());
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
writer.Reset();
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("fghi", 4));
EXPECT_EQ(4u, writer.string().size());
EXPECT_EQ("fghi", writer.string());
EXPECT_EQ(4, writer.Seek(0, SEEK_CUR));
writer.Reset();
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
// Test resetting after a sparse write.
EXPECT_EQ(1, writer.Seek(1, SEEK_SET));
EXPECT_TRUE(writer.Write("j", 1));
EXPECT_EQ(2u, writer.string().size());
EXPECT_EQ(std::string("\0j", 2), writer.string());
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
writer.Reset();
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
}
TEST(StringFileWriter, WriteInvalid) {
StringFileWriter writer;
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
EXPECT_FALSE(writer.Write(
"", static_cast<size_t>(std::numeric_limits<ssize_t>::max()) + 1));
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("a", 1));
EXPECT_FALSE(writer.Write(
"", static_cast<size_t>(std::numeric_limits<ssize_t>::max())));
EXPECT_EQ(1u, writer.string().size());
EXPECT_EQ("a", writer.string());
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
}
TEST(StringFileWriter, WriteIoVec) {
StringFileWriter writer;
std::vector<WritableIoVec> iovecs;
WritableIoVec iov;
iov.iov_base = "";
iov.iov_len = 0;
iovecs.push_back(iov);
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
iovecs.clear();
iov.iov_base = "a";
iov.iov_len = 1;
iovecs.push_back(iov);
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
EXPECT_EQ(1u, writer.string().size());
EXPECT_EQ("a", writer.string());
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
iovecs.clear();
iovecs.push_back(iov);
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
EXPECT_EQ(2u, writer.string().size());
EXPECT_EQ("aa", writer.string());
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
iovecs.clear();
iovecs.push_back(iov);
iov.iov_base = "bc";
iov.iov_len = 2;
iovecs.push_back(iov);
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ("aaabc", writer.string());
EXPECT_EQ(5, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("def", 3));
EXPECT_EQ(8u, writer.string().size());
EXPECT_EQ("aaabcdef", writer.string());
EXPECT_EQ(8, writer.Seek(0, SEEK_CUR));
iovecs.clear();
iov.iov_base = "ghij";
iov.iov_len = 4;
iovecs.push_back(iov);
iov.iov_base = "klmno";
iov.iov_len = 5;
iovecs.push_back(iov);
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
EXPECT_EQ(17u, writer.string().size());
EXPECT_EQ("aaabcdefghijklmno", writer.string());
EXPECT_EQ(17, writer.Seek(0, SEEK_CUR));
writer.Reset();
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
iovecs.clear();
iov.iov_base = "abcd";
iov.iov_len = 4;
iovecs.resize(16, iov);
EXPECT_TRUE(writer.WriteIoVec(&iovecs));
EXPECT_EQ(64u, writer.string().size());
EXPECT_EQ("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd",
writer.string());
EXPECT_EQ(64, writer.Seek(0, SEEK_CUR));
}
TEST(StringFileWriter, WriteIoVecInvalid) {
StringFileWriter writer;
std::vector<WritableIoVec> iovecs;
EXPECT_FALSE(writer.WriteIoVec(&iovecs));
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
WritableIoVec iov;
EXPECT_EQ(1, writer.Seek(1, SEEK_CUR));
iov.iov_base = "a";
iov.iov_len = std::numeric_limits<ssize_t>::max();
iovecs.push_back(iov);
EXPECT_FALSE(writer.WriteIoVec(&iovecs));
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
iovecs.clear();
iov.iov_base = "a";
iov.iov_len = 1;
iovecs.push_back(iov);
iov.iov_len = std::numeric_limits<ssize_t>::max() - 1;
iovecs.push_back(iov);
EXPECT_FALSE(writer.WriteIoVec(&iovecs));
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
}
TEST(StringFileWriter, Seek) {
StringFileWriter writer;
EXPECT_TRUE(writer.Write("abcd", 4));
EXPECT_EQ(4u, writer.string().size());
EXPECT_EQ("abcd", writer.string());
EXPECT_EQ(4, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(0, writer.Seek(0, SEEK_SET));
EXPECT_TRUE(writer.Write("efgh", 4));
EXPECT_EQ(4u, writer.string().size());
EXPECT_EQ("efgh", writer.string());
EXPECT_EQ(4, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(0, writer.Seek(0, SEEK_SET));
EXPECT_TRUE(writer.Write("ijk", 3));
EXPECT_EQ(4u, writer.string().size());
EXPECT_EQ("ijkh", writer.string());
EXPECT_EQ(3, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(0, writer.Seek(0, SEEK_SET));
EXPECT_TRUE(writer.Write("lmnop", 5));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ("lmnop", writer.string());
EXPECT_EQ(5, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(1, writer.Seek(1, SEEK_SET));
EXPECT_TRUE(writer.Write("q", 1));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ("lqnop", writer.string());
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(1, writer.Seek(-1, SEEK_CUR));
EXPECT_TRUE(writer.Write("r", 1));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ("lrnop", writer.string());
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("s", 1));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ("lrsop", writer.string());
EXPECT_EQ(3, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(2, writer.Seek(-1, SEEK_CUR));
EXPECT_TRUE(writer.Write("t", 1));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ("lrtop", writer.string());
EXPECT_EQ(3, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(4, writer.Seek(-1, SEEK_END));
EXPECT_TRUE(writer.Write("u", 1));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ("lrtou", writer.string());
EXPECT_EQ(5, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(0, writer.Seek(-5, SEEK_END));
EXPECT_TRUE(writer.Write("v", 1));
EXPECT_EQ(5u, writer.string().size());
EXPECT_EQ("vrtou", writer.string());
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(5, writer.Seek(0, SEEK_END));
EXPECT_TRUE(writer.Write("w", 1));
EXPECT_EQ(6u, writer.string().size());
EXPECT_EQ("vrtouw", writer.string());
EXPECT_EQ(6, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(8, writer.Seek(2, SEEK_END));
EXPECT_EQ(6u, writer.string().size());
EXPECT_EQ("vrtouw", writer.string());
EXPECT_EQ(6, writer.Seek(0, SEEK_END));
EXPECT_TRUE(writer.Write("x", 1));
EXPECT_EQ(7u, writer.string().size());
EXPECT_EQ("vrtouwx", writer.string());
EXPECT_EQ(7, writer.Seek(0, SEEK_CUR));
}
TEST(StringFileWriter, SeekSparse) {
StringFileWriter writer;
EXPECT_EQ(3, writer.Seek(3, SEEK_SET));
EXPECT_TRUE(writer.string().empty());
EXPECT_EQ(3, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("abc", 3));
EXPECT_EQ(6u, writer.string().size());
EXPECT_EQ(std::string("\0\0\0abc", 6), writer.string());
EXPECT_EQ(6, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(9, writer.Seek(3, SEEK_END));
EXPECT_EQ(6u, writer.string().size());
EXPECT_EQ(9, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("def", 3));
EXPECT_EQ(12u, writer.string().size());
EXPECT_EQ(std::string("\0\0\0abc\0\0\0def", 12), writer.string());
EXPECT_EQ(12, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(7, writer.Seek(-5, SEEK_END));
EXPECT_EQ(12u, writer.string().size());
EXPECT_EQ(7, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("g", 1));
EXPECT_EQ(12u, writer.string().size());
EXPECT_EQ(std::string("\0\0\0abc\0g\0def", 12), writer.string());
EXPECT_EQ(8, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(15, writer.Seek(7, SEEK_CUR));
EXPECT_EQ(12u, writer.string().size());
EXPECT_EQ(15, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("hij", 3));
EXPECT_EQ(18u, writer.string().size());
EXPECT_EQ(std::string("\0\0\0abc\0g\0def\0\0\0hij", 18), writer.string());
EXPECT_EQ(18, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(1, writer.Seek(-17, SEEK_CUR));
EXPECT_EQ(18u, writer.string().size());
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("k", 1));
EXPECT_EQ(18u, writer.string().size());
EXPECT_EQ(std::string("\0k\0abc\0g\0def\0\0\0hij", 18), writer.string());
EXPECT_EQ(2, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.Write("l", 1));
EXPECT_TRUE(writer.Write("mnop", 4));
EXPECT_EQ(18u, writer.string().size());
EXPECT_EQ(std::string("\0klmnopg\0def\0\0\0hij", 18), writer.string());
EXPECT_EQ(7, writer.Seek(0, SEEK_CUR));
}
TEST(StringFileWriter, SeekInvalid) {
StringFileWriter writer;
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
EXPECT_EQ(1, writer.Seek(1, SEEK_SET));
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
EXPECT_LT(writer.Seek(-1, SEEK_SET), 0);
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
EXPECT_LT(writer.Seek(std::numeric_limits<ssize_t>::min(), SEEK_SET), 0);
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
EXPECT_LT(writer.Seek(std::numeric_limits<off_t>::min(), SEEK_SET), 0);
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.string().empty());
COMPILE_ASSERT(SEEK_SET != 3 && SEEK_CUR != 3 && SEEK_END != 3,
three_must_be_invalid_for_whence);
EXPECT_LT(writer.Seek(0, 3), 0);
writer.Reset();
EXPECT_EQ(0, writer.Seek(0, SEEK_CUR));
EXPECT_TRUE(writer.string().empty());
const off_t kMaxOffset =
std::min(static_cast<uint64_t>(std::numeric_limits<off_t>::max()),
static_cast<uint64_t>(std::numeric_limits<size_t>::max()));
EXPECT_EQ(kMaxOffset, writer.Seek(kMaxOffset, SEEK_SET));
EXPECT_EQ(kMaxOffset, writer.Seek(0, SEEK_CUR));
EXPECT_LT(writer.Seek(1, SEEK_CUR), 0);
EXPECT_EQ(1, writer.Seek(1, SEEK_SET));
EXPECT_EQ(1, writer.Seek(0, SEEK_CUR));
EXPECT_LT(writer.Seek(kMaxOffset, SEEK_CUR), 0);
}
} // namespace

View File

@ -0,0 +1,44 @@
// 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_NUMERIC_SAFE_ASSIGNMENT_H_
#define CRASHPAD_UTIL_NUMERIC_SAFE_ASSIGNMENT_H_
#include "base/numerics/safe_conversions.h"
namespace crashpad {
//! \brief Performs an assignment if it can be done safely, and signals if it
//! cannot be done safely.
//!
//! \param[out] destination A pointer to the variable to be assigned to.
//! \param[in] source The value to assign.
//!
//! \return `true` if \a source is in the range supported by the type of \a
//! *destination, with the assignment to \a *destination having been
//! performed. `false` if the assignment cannot be completed safely because
//! \a source is outside of this range.
template <typename Destination, typename Source>
bool AssignIfInRange(Destination* destination, Source source) {
if (!base::IsValueInRangeForNumericType<Destination>(source)) {
return false;
}
*destination = source;
return true;
}
} // namespace crashpad
#endif // CRASHPAD_UTIL_NUMERIC_SAFE_ASSIGNMENT_H_

55
util/util.gyp Normal file
View File

@ -0,0 +1,55 @@
# 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.
{
'targets': [
{
'target_name': 'util',
'type': 'static_library',
'dependencies': [
'../compat/compat.gyp:compat',
'../third_party/mini_chromium/mini_chromium/base/base.gyp:base',
],
'include_dirs': [
'..',
'<(INTERMEDIATE_DIR)',
],
'sources': [
'file/fd_io.cc',
'file/fd_io.h',
'file/file_writer.cc',
'file/file_writer.h',
'file/string_file_writer.cc',
'file/string_file_writer.h',
],
},
{
'target_name': 'util_test',
'type': 'executable',
'dependencies': [
'util',
'../compat/compat.gyp:compat',
'../third_party/gtest/gtest.gyp:gtest',
'../third_party/mini_chromium/mini_chromium/base/base.gyp:base',
],
'include_dirs': [
'..',
],
'sources': [
'../third_party/gtest/gtest/src/gtest_main.cc',
'file/string_file_writer_test.cc',
],
},
],
}