diff --git a/webrtc/BUILD.gn b/webrtc/BUILD.gn index eef896c520..600050bf10 100644 --- a/webrtc/BUILD.gn +++ b/webrtc/BUILD.gn @@ -457,6 +457,7 @@ if (rtc_include_tests) { "base/event_tracer_unittest.cc", "base/event_unittest.cc", "base/exp_filter_unittest.cc", + "base/file_unittest.cc", "base/filerotatingstream_unittest.cc", "base/fileutils_unittest.cc", "base/helpers_unittest.cc", diff --git a/webrtc/base/BUILD.gn b/webrtc/base/BUILD.gn index 075e4ccaf4..aeb2f35558 100644 --- a/webrtc/base/BUILD.gn +++ b/webrtc/base/BUILD.gn @@ -124,6 +124,8 @@ rtc_static_library("rtc_base_approved") { "event_tracer.h", "exp_filter.cc", "exp_filter.h", + "file.cc", + "file.h", "format_macros.h", "location.cc", "location.h", @@ -177,6 +179,14 @@ rtc_static_library("rtc_base_approved") { libs += [ "log" ] } + if (is_posix) { + sources += [ "file_posix.cc" ] + } + + if (is_win) { + sources += [ "file_win.cc" ] + } + if (build_with_chromium) { # Dependency on chromium's logging (in //base). deps += [ "//base:base" ] diff --git a/webrtc/base/base.gyp b/webrtc/base/base.gyp index b941544ea4..5ff461d4ed 100644 --- a/webrtc/base/base.gyp +++ b/webrtc/base/base.gyp @@ -54,6 +54,8 @@ 'event_tracer.h', 'exp_filter.cc', 'exp_filter.h', + 'file.cc', + 'file.h', 'format_macros.h', 'location.h', 'location.cc', @@ -103,6 +105,16 @@ 'trace_event.h', ], 'conditions': [ + ['os_posix==1', { + 'sources': [ + 'file_posix.cc', + ], + }], + ['OS=="win"', { + 'sources': [ + 'file_win.cc', + ], + }], ['build_with_chromium==1', { 'dependencies': [ '<(DEPTH)/base/base.gyp:base', diff --git a/webrtc/base/file.cc b/webrtc/base/file.cc new file mode 100644 index 0000000000..9a865884ea --- /dev/null +++ b/webrtc/base/file.cc @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/file.h" + +namespace rtc { + +File::File(PlatformFile file) : file_(file) {} + +File::~File() { + Close(); +} + +File::File(File&& other) : file_(other.file_) { + other.file_ = kInvalidPlatformFileValue; +} + +File& File::operator=(File&& other) { + Close(); + file_ = other.file_; + other.file_ = kInvalidPlatformFileValue; + return *this; +} + +bool File::IsOpen() { + return file_ != kInvalidPlatformFileValue; +} + +} // namespace rtc diff --git a/webrtc/base/file.h b/webrtc/base/file.h new file mode 100644 index 0000000000..3401c28abe --- /dev/null +++ b/webrtc/base/file.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_FILE_H_ +#define WEBRTC_BASE_FILE_H_ + +#include + +#include + +#include "webrtc/base/platform_file.h" +#include "webrtc/base/constructormagic.h" + +namespace rtc { + +// This class wraps the platform specific APIs for simple file interactions. +// +// The various read and write methods are best effort, i.e. if an underlying +// call does not manage to read/write all the data more calls will be performed, +// until an error is detected or all data is read/written. +class File { + public: + // Wraps the given PlatformFile. This class is then responsible for closing + // the file, which will be done in the destructor if Close is never called. + explicit File(PlatformFile); + ~File(); + + File(File&& other); + File& operator=(File&& other); + + static File Open(const std::string& path); + + size_t Write(const uint8_t* data, size_t length); + size_t Read(uint8_t* buffer, size_t length); + + // The current position in the file after a call to these methods is platform + // dependent (MSVC gives position offset+length, most other + // compilers/platforms do not alter the position), i.e. do not depend on it, + // do a Seek before any subsequent Read/Write. + size_t WriteAt(const uint8_t* data, size_t length, size_t offset); + size_t ReadAt(uint8_t* buffer, size_t length, size_t offset); + + // Attempt to position the file at the given offset from the start. + // Returns true if successful, false otherwise. + bool Seek(size_t offset); + + // Attempt to close the file. Returns true if successful, false otherwise, + // most notably when the file is already closed. + bool Close(); + + bool IsOpen(); + + private: + PlatformFile file_; + RTC_DISALLOW_COPY_AND_ASSIGN(File); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FILE_H_ diff --git a/webrtc/base/file_posix.cc b/webrtc/base/file_posix.cc new file mode 100644 index 0000000000..2dde7938c2 --- /dev/null +++ b/webrtc/base/file_posix.cc @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/file.h" + +#include +#include +#include +#include +#include + +#include + +#include "webrtc/base/checks.h" + +namespace rtc { + +File File::Open(const std::string& path) { + return File(::open(path.c_str(), O_RDWR)); +} + +size_t File::Write(const uint8_t* data, size_t length) { + size_t total_written = 0; + do { + ssize_t written; + do { + written = ::write(file_, data + total_written, length - total_written); + } while (written == -1 && errno == EINTR); + if (written == -1) + break; + total_written += written; + } while (total_written < length); + return total_written; +} + +size_t File::Read(uint8_t* buffer, size_t length) { + size_t total_read = 0; + do { + ssize_t read; + do { + read = ::read(file_, buffer + total_read, length - total_read); + } while (read == -1 && errno == EINTR); + if (read == -1) + break; + total_read += read; + } while (total_read < length); + return total_read; +} + +size_t File::WriteAt(const uint8_t* data, size_t length, size_t offset) { + size_t total_written = 0; + do { + ssize_t written; + do { + written = ::pwrite(file_, data + total_written, length - total_written, + offset + total_written); + } while (written == -1 && errno == EINTR); + if (written == -1) + break; + total_written += written; + } while (total_written < length); + return total_written; +} + +size_t File::ReadAt(uint8_t* buffer, size_t length, size_t offset) { + size_t total_read = 0; + do { + ssize_t read; + do { + read = ::pread(file_, buffer + total_read, length - total_read, + offset + total_read); + } while (read == -1 && errno == EINTR); + if (read == -1) + break; + total_read += read; + } while (total_read < length); + return total_read; +} + +bool File::Seek(size_t offset) { + RTC_DCHECK_LE(offset, static_cast(std::numeric_limits::max())); + return lseek(file_, static_cast(offset), SEEK_SET) != -1; +} + +bool File::Close() { + if (file_ == rtc::kInvalidPlatformFileValue) + return false; + bool ret = close(file_) == 0; + file_ = rtc::kInvalidPlatformFileValue; + return ret; +} + +} // namespace rtc diff --git a/webrtc/base/file_unittest.cc b/webrtc/base/file_unittest.cc new file mode 100644 index 0000000000..458b6da75f --- /dev/null +++ b/webrtc/base/file_unittest.cc @@ -0,0 +1,166 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include + +#include +#include +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/file.h" +#include "webrtc/test/testsupport/fileutils.h" + +#if defined(WEBRTC_WIN) + +#include "webrtc/base/win32.h" + +#else // if defined(WEBRTC_WIN) + +#include + +#endif + +namespace rtc { + +void RemoveFile(const std::string& path) { +#if defined(WEBRTC_WIN) + ::DeleteFile(ToUtf16(path).c_str()); +#else + ::unlink(path.c_str()); +#endif +} + +int LastError() { +#if defined(WEBRTC_WIN) + return ::GetLastError(); +#else + return errno; +#endif +} + +bool VerifyBuffer(uint8_t* buffer, size_t length, uint8_t start_value) { + for (size_t i = 0; i < length; ++i) { + uint8_t val = start_value++; + EXPECT_EQ(val, buffer[i]); + if (buffer[i] != val) + return false; + } + // Prevent the same buffer from being verified multiple times simply + // because some operation that should have written to it failed + memset(buffer, 0, length); + return true; +} + +class FileTest : public ::testing::Test { + protected: + std::string path_; + void SetUp() { + path_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), "test_file"); + ASSERT_FALSE(path_.empty()); + } + rtc::File OpenTempFile() { return rtc::File::Open(path_); } + void TearDown() { RemoveFile(path_); } +}; + +TEST_F(FileTest, DoubleClose) { + File file = OpenTempFile(); + ASSERT_TRUE(file.IsOpen()) << "Error: " << LastError(); + + EXPECT_TRUE(file.Close()); + EXPECT_FALSE(file.Close()); +} + +TEST_F(FileTest, SimpleReadWrite) { + File file = OpenTempFile(); + ASSERT_TRUE(file.IsOpen()) << "Error: " << LastError(); + + uint8_t data[100] = {0}; + uint8_t out[100] = {0}; + for (int i = 0; i < 100; ++i) { + data[i] = i; + } + + EXPECT_EQ(10u, file.Write(data, 10)); + + EXPECT_TRUE(file.Seek(0)); + EXPECT_EQ(10u, file.Read(out, 10)); + EXPECT_TRUE(VerifyBuffer(out, 10, 0)); + + EXPECT_TRUE(file.Seek(0)); + EXPECT_EQ(100u, file.Write(data, 100)); + + EXPECT_TRUE(file.Seek(0)); + EXPECT_EQ(100u, file.Read(out, 100)); + EXPECT_TRUE(VerifyBuffer(out, 100, 0)); + + EXPECT_TRUE(file.Seek(1)); + EXPECT_EQ(50u, file.Write(data, 50)); + EXPECT_EQ(50u, file.Write(data + 50, 50)); + + EXPECT_TRUE(file.Seek(1)); + EXPECT_EQ(100u, file.Read(out, 100)); + EXPECT_TRUE(VerifyBuffer(out, 100, 0)); +} + +TEST_F(FileTest, ReadWriteClose) { + File file = OpenTempFile(); + ASSERT_TRUE(file.IsOpen()) << "Error: " << LastError(); + + uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + uint8_t out[10] = {0}; + EXPECT_EQ(10u, file.Write(data, 10)); + EXPECT_TRUE(file.Close()); + + File file2 = OpenTempFile(); + ASSERT_TRUE(file2.IsOpen()) << "Error: " << LastError(); + EXPECT_EQ(10u, file2.Read(out, 10)); + EXPECT_TRUE(VerifyBuffer(out, 10, 0)); +} + +TEST_F(FileTest, RandomAccessRead) { + File file = OpenTempFile(); + ASSERT_TRUE(file.IsOpen()) << "Error: " << LastError(); + + uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + uint8_t out[10] = {0}; + EXPECT_EQ(10u, file.Write(data, 10)); + + EXPECT_EQ(4u, file.ReadAt(out, 4, 0)); + EXPECT_TRUE(VerifyBuffer(out, 4, 0)); + + EXPECT_EQ(4u, file.ReadAt(out, 4, 4)); + EXPECT_TRUE(VerifyBuffer(out, 4, 4)); + + EXPECT_EQ(5u, file.ReadAt(out, 5, 5)); + EXPECT_TRUE(VerifyBuffer(out, 5, 5)); +} + +TEST_F(FileTest, RandomAccessReadWrite) { + File file = OpenTempFile(); + ASSERT_TRUE(file.IsOpen()) << "Error: " << LastError(); + + uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + uint8_t out[10] = {0}; + EXPECT_EQ(10u, file.Write(data, 10)); + EXPECT_TRUE(file.Seek(4)); + + EXPECT_EQ(4u, file.WriteAt(data, 4, 4)); + EXPECT_EQ(4u, file.ReadAt(out, 4, 4)); + EXPECT_TRUE(VerifyBuffer(out, 4, 0)); + + EXPECT_EQ(2u, file.WriteAt(data, 2, 8)); + EXPECT_EQ(2u, file.ReadAt(out, 2, 8)); + EXPECT_TRUE(VerifyBuffer(out, 2, 0)); +} + +} // namespace rtc diff --git a/webrtc/base/file_win.cc b/webrtc/base/file_win.cc new file mode 100644 index 0000000000..72a680c88f --- /dev/null +++ b/webrtc/base/file_win.cc @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/file.h" + +#include +#include "webrtc/base/win32.h" + +#include // NOLINT: win32.h should be considered a system header + +#include "webrtc/base/checks.h" + +namespace rtc { + +File File::Open(const std::string& path) { + HANDLE handle = + ::CreateFile(ToUtf16(path).c_str(), GENERIC_READ | GENERIC_WRITE, 0, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + return File(handle); +} + +size_t File::Write(const uint8_t* data, size_t length) { + RTC_DCHECK_LT(length, std::numeric_limits::max()); + size_t total_written = 0; + do { + DWORD written; + if (!::WriteFile(file_, data + total_written, + static_cast(length - total_written), &written, + nullptr)) { + break; + } + total_written += written; + } while (total_written < length); + return total_written; +} + +size_t File::Read(uint8_t* buffer, size_t length) { + RTC_DCHECK_LT(length, std::numeric_limits::max()); + size_t total_read = 0; + do { + DWORD read; + if (!::ReadFile(file_, buffer + total_read, + static_cast(length - total_read), &read, nullptr)) { + break; + } + total_read += read; + } while (total_read < length); + return total_read; +} + +size_t File::WriteAt(const uint8_t* data, size_t length, size_t offset) { + RTC_DCHECK_LT(length, std::numeric_limits::max()); + size_t total_written = 0; + do { + DWORD written; + + LARGE_INTEGER offset_li; + offset_li.QuadPart = offset + total_written; + + OVERLAPPED overlapped = {0}; + overlapped.Offset = offset_li.LowPart; + overlapped.OffsetHigh = offset_li.HighPart; + + if (!::WriteFile(file_, data + total_written, + static_cast(length - total_written), &written, + &overlapped)) { + break; + } + + total_written += written; + } while (total_written < length); + return total_written; +} + +size_t File::ReadAt(uint8_t* buffer, size_t length, size_t offset) { + RTC_DCHECK_LT(length, std::numeric_limits::max()); + size_t total_read = 0; + do { + DWORD read; + + LARGE_INTEGER offset_li; + offset_li.QuadPart = offset + total_read; + + OVERLAPPED overlapped = {0}; + overlapped.Offset = offset_li.LowPart; + overlapped.OffsetHigh = offset_li.HighPart; + + if (!::ReadFile(file_, buffer + total_read, + static_cast(length - total_read), &read, + &overlapped)) { + break; + } + + total_read += read; + } while (total_read < length); + return total_read; +} + +bool File::Seek(size_t offset) { + LARGE_INTEGER distance; + distance.QuadPart = offset; + return SetFilePointerEx(file_, distance, nullptr, FILE_BEGIN) != 0; +} + +bool File::Close() { + if (file_ == kInvalidPlatformFileValue) + return false; + bool ret = CloseHandle(file_) != 0; + file_ = kInvalidPlatformFileValue; + return ret; +} + +} // namespace rtc diff --git a/webrtc/webrtc_tests.gypi b/webrtc/webrtc_tests.gypi index 1765ba7953..f93b5c0a84 100644 --- a/webrtc/webrtc_tests.gypi +++ b/webrtc/webrtc_tests.gypi @@ -39,6 +39,7 @@ 'base/event_tracer_unittest.cc', 'base/event_unittest.cc', 'base/exp_filter_unittest.cc', + 'base/file_unittest.cc', 'base/filerotatingstream_unittest.cc', 'base/fileutils_unittest.cc', 'base/helpers_unittest.cc',