diff --git a/snapshot/linux/process_reader.cc b/snapshot/linux/process_reader.cc index 9055b991..904fdced 100644 --- a/snapshot/linux/process_reader.cc +++ b/snapshot/linux/process_reader.cc @@ -14,7 +14,6 @@ #include "snapshot/linux/process_reader.h" -#include #include #include #include @@ -27,8 +26,9 @@ #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "build/build_config.h" +#include "util/file/directory_reader.h" #include "util/linux/proc_stat_reader.h" -#include "util/posix/scoped_dir.h" +#include "util/misc/as_underlying_type.h" namespace crashpad { @@ -264,15 +264,6 @@ void ProcessReader::InitializeThreads() { return; } - char path[32]; - snprintf(path, arraysize(path), "/proc/%d/task", pid); - DIR* dir = opendir(path); - if (!dir) { - PLOG(ERROR) << "opendir"; - return; - } - ScopedDIR scoped_dir(dir); - Thread main_thread; main_thread.tid = pid; if (main_thread.InitializePtrace(connection_)) { @@ -282,15 +273,19 @@ void ProcessReader::InitializeThreads() { LOG(WARNING) << "Couldn't initialize main thread."; } + char path[32]; + snprintf(path, arraysize(path), "/proc/%d/task", pid); bool main_thread_found = false; - dirent* dir_entry; - while ((dir_entry = readdir(scoped_dir.get()))) { - if (strncmp(dir_entry->d_name, ".", arraysize(dir_entry->d_name)) == 0 || - strncmp(dir_entry->d_name, "..", arraysize(dir_entry->d_name)) == 0) { - continue; - } + DirectoryReader reader; + if (!reader.Open(base::FilePath(path))) { + return; + } + base::FilePath tid_str; + DirectoryReader::Result result; + while ((result = reader.NextFile(&tid_str)) == + DirectoryReader::Result::kSuccess) { pid_t tid; - if (!base::StringToInt(dir_entry->d_name, &tid)) { + if (!base::StringToInt(tid_str.value(), &tid)) { LOG(ERROR) << "format error"; continue; } @@ -308,6 +303,8 @@ void ProcessReader::InitializeThreads() { threads_.push_back(thread); } } + DCHECK_EQ(AsUnderlyingType(result), + AsUnderlyingType(DirectoryReader::Result::kNoMoreFiles)); DCHECK(main_thread_found); } diff --git a/test/scoped_temp_dir.cc b/test/scoped_temp_dir.cc index 5c151cc0..7bc87031 100644 --- a/test/scoped_temp_dir.cc +++ b/test/scoped_temp_dir.cc @@ -14,6 +14,11 @@ #include "test/scoped_temp_dir.h" +#include "gtest/gtest.h" +#include "util/file/directory_reader.h" +#include "util/file/file_io.h" +#include "util/file/filesystem.h" + namespace crashpad { namespace test { @@ -24,5 +29,25 @@ ScopedTempDir::~ScopedTempDir() { RecursivelyDeleteTemporaryDirectory(path()); } +// static +void ScopedTempDir::RecursivelyDeleteTemporaryDirectory( + const base::FilePath& path) { + DirectoryReader reader; + ASSERT_TRUE(reader.Open(path)); + DirectoryReader::Result result; + base::FilePath entry; + while ((result = reader.NextFile(&entry)) == + DirectoryReader::Result::kSuccess) { + const base::FilePath entry_path(path.Append(entry)); + if (IsDirectory(entry_path, false)) { + RecursivelyDeleteTemporaryDirectory(entry_path); + } else { + EXPECT_TRUE(LoggingRemoveFile(entry_path)); + } + } + EXPECT_EQ(result, DirectoryReader::Result::kNoMoreFiles); + EXPECT_TRUE(LoggingRemoveDirectory(path)); +} + } // namespace test } // namespace crashpad diff --git a/test/scoped_temp_dir_posix.cc b/test/scoped_temp_dir_posix.cc index 29577529..7c930901 100644 --- a/test/scoped_temp_dir_posix.cc +++ b/test/scoped_temp_dir_posix.cc @@ -56,32 +56,5 @@ base::FilePath ScopedTempDir::CreateTemporaryDirectory() { return base::FilePath(dir); } -// static -void ScopedTempDir::RecursivelyDeleteTemporaryDirectory( - const base::FilePath& path) { - DIR* dir = opendir(path.value().c_str()); - ASSERT_TRUE(dir) << ErrnoMessage("opendir") << " " << path.value(); - - dirent* entry; - while ((entry = readdir(dir))) { - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { - continue; - } - - base::FilePath entry_path = path.Append(entry->d_name); - if (entry->d_type == DT_DIR) { - RecursivelyDeleteTemporaryDirectory(entry_path); - } else { - EXPECT_EQ(unlink(entry_path.value().c_str()), 0) - << ErrnoMessage("unlink") << " " << entry_path.value(); - } - } - - EXPECT_EQ(closedir(dir), 0) << ErrnoMessage("closedir") << " " - << path.value(); - EXPECT_EQ(rmdir(path.value().c_str()), 0) << ErrnoMessage("rmdir") << " " - << path.value(); -} - } // namespace test } // namespace crashpad diff --git a/test/scoped_temp_dir_win.cc b/test/scoped_temp_dir_win.cc index d9707af6..9c220a12 100644 --- a/test/scoped_temp_dir_win.cc +++ b/test/scoped_temp_dir_win.cc @@ -72,33 +72,5 @@ base::FilePath ScopedTempDir::CreateTemporaryDirectory() { return base::FilePath(); } -// static -void ScopedTempDir::RecursivelyDeleteTemporaryDirectory( - const base::FilePath& path) { - const base::string16 all_files_mask(L"\\*"); - - base::string16 search_mask = path.value() + all_files_mask; - WIN32_FIND_DATA find_data; - HANDLE search_handle = FindFirstFile(search_mask.c_str(), &find_data); - if (search_handle == INVALID_HANDLE_VALUE) - ASSERT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND); - do { - if (wcscmp(find_data.cFileName, L".") == 0 || - wcscmp(find_data.cFileName, L"..") == 0) { - continue; - } - base::FilePath entry_path = path.Append(find_data.cFileName); - ASSERT_FALSE(find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT); - if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - RecursivelyDeleteTemporaryDirectory(entry_path); - else - EXPECT_TRUE(DeleteFile(entry_path.value().c_str())); - } while (FindNextFile(search_handle, &find_data)); - EXPECT_EQ(GetLastError(), ERROR_NO_MORE_FILES); - - EXPECT_TRUE(FindClose(search_handle)); - EXPECT_TRUE(RemoveDirectory(path.value().c_str())); -} - } // namespace test } // namespace crashpad diff --git a/util/file/directory_reader.h b/util/file/directory_reader.h new file mode 100644 index 00000000..d3976929 --- /dev/null +++ b/util/file/directory_reader.h @@ -0,0 +1,87 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_FILE_DIRECTORY_READER_H_ +#define CRASHPAD_UTIL_FILE_DIRECTORY_READER_H_ + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "build/build_config.h" + +#if defined(OS_POSIX) +#include "util/posix/scoped_dir.h" +#elif defined(OS_WIN) +#include + +#include "util/win/scoped_handle.h" +#endif // OS_POSIX + +namespace crashpad { + +//! \brief Iterates over the file and directory names in a directory. +//! +//! The names enumerated are relative to the specified directory and do not +//! include ".", "..", or files and directories in subdirectories. +class DirectoryReader { + public: + //! \brief The result of a call to NextFile(). + enum class Result { + //! \brief An error occurred and a message was logged. + kError = -1, + + //! \brief A file was found. + kSuccess, + + //! \brief No more files were found. + kNoMoreFiles, + }; + + DirectoryReader(); + ~DirectoryReader(); + + //! \brief Opens the directory specified by \a path for reading. + //! + //! \param[in] path The path to the directory to read. + //! \return `true` on success. `false` on failure with a message logged. + bool Open(const base::FilePath& path); + + //! \brief Advances the reader to the next file in the directory. + //! + //! \param[out] filename The filename of the next file. + //! \return a #Result value. \a filename is only valid when Result::kSuccess + //! is returned. If Result::kError is returned, a message will be + //! logged. + Result NextFile(base::FilePath* filename); + +#if defined(OS_POSIX) || DOXYGEN + //! \brief Returns the file descriptor associated with this reader, logging a + //! message and returning -1 on error. + int DirectoryFD(); +#endif + + private: +#if defined(OS_POSIX) + ScopedDIR dir_; +#elif defined(OS_WIN) + WIN32_FIND_DATA find_data_; + ScopedSearchHANDLE handle_; + bool first_entry_; +#endif // OS_POSIX + + DISALLOW_COPY_AND_ASSIGN(DirectoryReader); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_FILE_DIRECTORY_READER_H_ diff --git a/util/file/directory_reader_posix.cc b/util/file/directory_reader_posix.cc new file mode 100644 index 00000000..b33e19bb --- /dev/null +++ b/util/file/directory_reader_posix.cc @@ -0,0 +1,79 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/directory_reader.h" + +#include +#include +#include +#include + +#include "base/logging.h" + +namespace crashpad { + +#define HANDLE_EINTR_IF_EQ(x, val) \ + ({ \ + decltype(x) eintr_wrapper_result; \ + do { \ + eintr_wrapper_result = (x); \ + } while (eintr_wrapper_result == (val) && errno == EINTR); \ + eintr_wrapper_result; \ + }) + +DirectoryReader::DirectoryReader() : dir_() {} + +DirectoryReader::~DirectoryReader() {} + +bool DirectoryReader::Open(const base::FilePath& path) { + dir_.reset(HANDLE_EINTR_IF_EQ(opendir(path.value().c_str()), nullptr)); + if (!dir_.is_valid()) { + PLOG(ERROR) << "opendir"; + return false; + } + return true; +} + +DirectoryReader::Result DirectoryReader::NextFile(base::FilePath* filename) { + DCHECK(dir_.is_valid()); + + errno = 0; + dirent* entry = HANDLE_EINTR_IF_EQ(readdir(dir_.get()), nullptr); + if (!entry) { + if (errno) { + PLOG(ERROR) << "readdir"; + return Result::kError; + } else { + return Result::kNoMoreFiles; + } + } + + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + return NextFile(filename); + } + + *filename = base::FilePath(entry->d_name); + return Result::kSuccess; +} + +int DirectoryReader::DirectoryFD() { + DCHECK(dir_.is_valid()); + int rv = dirfd(dir_.get()); + if (rv < 0) { + PLOG(ERROR) << "dirfd"; + } + return rv; +} + +} // namespace crashpad diff --git a/util/file/directory_reader_test.cc b/util/file/directory_reader_test.cc new file mode 100644 index 00000000..da4bfb73 --- /dev/null +++ b/util/file/directory_reader_test.cc @@ -0,0 +1,118 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/directory_reader.h" + +#include + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "gtest/gtest.h" +#include "test/scoped_temp_dir.h" +#include "util/file/file_io.h" +#include "util/file/filesystem.h" +#include "util/file/filesystem_test_util.h" + +namespace crashpad { +namespace test { +namespace { + +void ExpectFiles(const std::set& files, + const std::set& expected) { + EXPECT_EQ(files.size(), expected.size()); + + for (const auto& filename : expected) { + SCOPED_TRACE( + base::StringPrintf("Filename: %" PRFilePath, filename.value().c_str())); + EXPECT_NE(files.find(filename), files.end()); + } +} + +TEST(DirectoryReader, BadPaths) { + DirectoryReader reader; + EXPECT_FALSE(reader.Open(base::FilePath())); + + ScopedTempDir temp_dir; + base::FilePath file(temp_dir.path().Append(FILE_PATH_LITERAL("file"))); + ASSERT_TRUE(CreateFile(file)); + EXPECT_FALSE(reader.Open(file)); + + base::FilePath link(temp_dir.path().Append(FILE_PATH_LITERAL("link"))); + ASSERT_TRUE(CreateSymbolicLink(file, link)); + EXPECT_FALSE(reader.Open(link)); + + ASSERT_TRUE(LoggingRemoveFile(file)); + EXPECT_FALSE(reader.Open(link)); + + EXPECT_FALSE( + reader.Open(temp_dir.path().Append(FILE_PATH_LITERAL("doesntexist")))); +} + +TEST(DirectoryReader, EmptyDirectory) { + ScopedTempDir temp_dir; + DirectoryReader reader; + + ASSERT_TRUE(reader.Open(temp_dir.path())); + base::FilePath filename; + EXPECT_EQ(reader.NextFile(&filename), DirectoryReader::Result::kNoMoreFiles); +} + +TEST(DirectoryReader, FilesAndDirectories) { + ScopedTempDir temp_dir; + std::set expected_files; + + base::FilePath file(FILE_PATH_LITERAL("file")); + ASSERT_TRUE(CreateFile(temp_dir.path().Append(file))); + EXPECT_TRUE(expected_files.insert(file).second); + + base::FilePath link(FILE_PATH_LITERAL("link")); + ASSERT_TRUE(CreateSymbolicLink(temp_dir.path().Append(file), + temp_dir.path().Append(link))); + EXPECT_TRUE(expected_files.insert(link).second); + + base::FilePath dangling(FILE_PATH_LITERAL("dangling")); + ASSERT_TRUE( + CreateSymbolicLink(base::FilePath(FILE_PATH_LITERAL("not_a_file")), + temp_dir.path().Append(dangling))); + EXPECT_TRUE(expected_files.insert(dangling).second); + + base::FilePath directory(FILE_PATH_LITERAL("directory")); + ASSERT_TRUE(LoggingCreateDirectory(temp_dir.path().Append(directory), + FilePermissions::kWorldReadable, + false)); + EXPECT_TRUE(expected_files.insert(directory).second); + + base::FilePath nested_file(FILE_PATH_LITERAL("nested_file")); + ASSERT_TRUE( + CreateFile(temp_dir.path().Append(directory).Append(nested_file))); + + std::set files; + DirectoryReader reader; + ASSERT_TRUE(reader.Open(temp_dir.path())); + DirectoryReader::Result result; + base::FilePath filename; + while ((result = reader.NextFile(&filename)) == + DirectoryReader::Result::kSuccess) { + EXPECT_TRUE(files.insert(filename).second); + } + EXPECT_EQ(result, DirectoryReader::Result::kNoMoreFiles); + EXPECT_EQ(reader.NextFile(&filename), DirectoryReader::Result::kNoMoreFiles); + ExpectFiles(files, expected_files); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/file/directory_reader_win.cc b/util/file/directory_reader_win.cc new file mode 100644 index 00000000..503a5190 --- /dev/null +++ b/util/file/directory_reader_win.cc @@ -0,0 +1,76 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/directory_reader.h" + +#include + +#include "base/logging.h" + +namespace crashpad { + +DirectoryReader::DirectoryReader() + : find_data_(), handle_(), first_entry_(false) {} + +DirectoryReader::~DirectoryReader() {} + +bool DirectoryReader::Open(const base::FilePath& path) { + if (path.empty()) { + LOG(ERROR) << "Empty directory path"; + return false; + } + + handle_.reset( + FindFirstFileEx(path.Append(FILE_PATH_LITERAL("*")).value().c_str(), + FindExInfoBasic, + &find_data_, + FindExSearchNameMatch, + nullptr, + FIND_FIRST_EX_LARGE_FETCH)); + + if (!handle_.is_valid()) { + PLOG(ERROR) << "FindFirstFile"; + return false; + } + + first_entry_ = true; + return true; +} + +DirectoryReader::Result DirectoryReader::NextFile(base::FilePath* filename) { + DCHECK(handle_.is_valid()); + + if (!first_entry_) { + if (!FindNextFile(handle_.get(), &find_data_)) { + if (GetLastError() != ERROR_NO_MORE_FILES) { + PLOG(ERROR) << "FindNextFile"; + return Result::kError; + } else { + return Result::kNoMoreFiles; + } + } + } else { + first_entry_ = false; + } + + if (wcscmp(find_data_.cFileName, L".") == 0 || + wcscmp(find_data_.cFileName, L"..") == 0) { + return NextFile(filename); + } + + *filename = base::FilePath(find_data_.cFileName); + return Result::kSuccess; +} + +} // namespace crashpad diff --git a/util/file/filesystem.h b/util/file/filesystem.h new file mode 100644 index 00000000..12823372 --- /dev/null +++ b/util/file/filesystem.h @@ -0,0 +1,78 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_FILE_FILESYSTEM_H_ +#define CRASHPAD_UTIL_FILE_FILESYSTEM_H_ + +#include "base/files/file_path.h" + +#include "util/file/file_io.h" + +namespace crashpad { + +//! \brief Creates a directory, logging a message on failure. +//! +//! \param[in] path The path to the directory to create. +//! \param[in] permissions The permissions to use if the directory is created. +//! \param[in] may_reuse If `true`, this function will return `true` if a +//! directory or symbolic link to a directory with path \a path already +//! exists. If the directory already exists, it's permissions may differ +//! from \a permissions. +//! \return `true` if the directory is successfully created or it already +//! existed and \a may_reuse is `true`. Otherwise, `false`. +bool LoggingCreateDirectory(const base::FilePath& path, + FilePermissions permissions, + bool may_reuse); + +//! \brief Determines if a path refers to a regular file, logging a message on +//! failure. +//! +//! On POSIX, this function returns `true` if \a path refers to a file that is +//! not a symbolic link, directory, or other kind of special file. +//! +//! On Windows, this function returns `true` if \a path refers to a file that +//! is not a symbolic link or directory. +//! +//! \param[in] path The path to the file to check. +//! \return `true` if the file exists and is a regular file. Otherwise `false`. +bool IsRegularFile(const base::FilePath& path); + +//! \brief Determines if a path refers to a directory, logging a message on +//! failure. +//! +//! \param[in] path The path to check. +//! \param[in] allow_symlinks Whether to allow the final component in the path +//! to be a symbolic link to a directory. +//! \return `true` if the path exists and is a directory. Otherwise `false`. +bool IsDirectory(const base::FilePath& path, bool allow_symlinks); + +//! \brief Removes a file or a symbolic link to a file or directory, logging a +//! message on failure. +//! +//! \param[in] path The path to the file to remove. +//! \return `true` on success. `false` on failure with a message logged. +bool LoggingRemoveFile(const base::FilePath& path); + +//! \brief Non-recurseively removes an empty directory, logging a message on +//! failure. +//! +//! This function will not remove symbolic links to directories. +//! +//! \param[in] path The to the directory to remove. +//! \return `true` if the directory was removed. Otherwise, `false`. +bool LoggingRemoveDirectory(const base::FilePath& path); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_FILE_FILESYSTEM_H_ diff --git a/util/file/filesystem_posix.cc b/util/file/filesystem_posix.cc new file mode 100644 index 00000000..843a1b63 --- /dev/null +++ b/util/file/filesystem_posix.cc @@ -0,0 +1,86 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/filesystem.h" + +#include +#include +#include +#include + +#include "base/logging.h" + +namespace crashpad { + +bool LoggingCreateDirectory(const base::FilePath& path, + FilePermissions permissions, + bool may_reuse) { + if (mkdir(path.value().c_str(), + permissions == FilePermissions::kWorldReadable ? 0755 : 0700) == + 0) { + return true; + } + if (may_reuse && errno == EEXIST) { + if (!IsDirectory(path, true)) { + LOG(ERROR) << path.value() << " not a directory"; + return false; + } + return true; + } + PLOG(ERROR) << "mkdir " << path.value(); + return false; +} + +bool IsRegularFile(const base::FilePath& path) { + struct stat st; + if (lstat(path.value().c_str(), &st) != 0) { + PLOG_IF(ERROR, errno != ENOENT) << "stat " << path.value(); + return false; + } + return S_ISREG(st.st_mode); +} + +bool IsDirectory(const base::FilePath& path, bool allow_symlinks) { + struct stat st; + if (allow_symlinks) { + if (stat(path.value().c_str(), &st) != 0) { + PLOG(ERROR) << "stat " << path.value(); + return false; + } + } else { + if (lstat(path.value().c_str(), &st) != 0) { + PLOG(ERROR) << "lstat " << path.value(); + return false; + } + } + return S_ISDIR(st.st_mode); +} + +bool LoggingRemoveFile(const base::FilePath& path) { + if (unlink(path.value().c_str()) != 0) { + PLOG(ERROR) << "unlink " << path.value(); + return false; + } + return true; +} + +bool LoggingRemoveDirectory(const base::FilePath& path) { + if (rmdir(path.value().c_str()) != 0) { + PLOG(ERROR) << "rmdir " << path.value(); + return false; + } + return true; +} + +} // namespace crashpad diff --git a/util/file/filesystem_test.cc b/util/file/filesystem_test.cc new file mode 100644 index 00000000..dee1560d --- /dev/null +++ b/util/file/filesystem_test.cc @@ -0,0 +1,168 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/filesystem.h" + +#include "base/logging.h" +#include "gtest/gtest.h" +#include "test/file.h" +#include "test/scoped_temp_dir.h" +#include "util/file/filesystem_test_util.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(Filesystem, CreateDirectory) { + ScopedTempDir temp_dir; + + base::FilePath dir(temp_dir.path().Append(FILE_PATH_LITERAL("dir"))); + EXPECT_FALSE(IsDirectory(dir, false)); + + ASSERT_TRUE( + LoggingCreateDirectory(dir, FilePermissions::kWorldReadable, false)); + EXPECT_TRUE(IsDirectory(dir, false)); + + EXPECT_FALSE( + LoggingCreateDirectory(dir, FilePermissions::kWorldReadable, false)); + + base::FilePath file(dir.Append(FILE_PATH_LITERAL("file"))); + ASSERT_TRUE(CreateFile(file)); + + EXPECT_TRUE( + LoggingCreateDirectory(dir, FilePermissions::kWorldReadable, true)); + EXPECT_TRUE(IsRegularFile(file)); +} + +TEST(Filesystem, IsRegularFile) { + EXPECT_FALSE(IsRegularFile(base::FilePath())); + + ScopedTempDir temp_dir; + EXPECT_FALSE(IsRegularFile(temp_dir.path())); + + base::FilePath file(temp_dir.path().Append(FILE_PATH_LITERAL("file"))); + EXPECT_FALSE(IsRegularFile(file)); + + ASSERT_TRUE(CreateFile(file)); + EXPECT_TRUE(IsRegularFile(file)); + + base::FilePath link(temp_dir.path().Append(FILE_PATH_LITERAL("link"))); + ASSERT_TRUE(CreateSymbolicLink(file, link)); + EXPECT_FALSE(IsRegularFile(link)); + + ASSERT_TRUE(LoggingRemoveFile(file)); + EXPECT_FALSE(IsRegularFile(link)); + + base::FilePath dir_link( + temp_dir.path().Append((FILE_PATH_LITERAL("dir_link")))); + ASSERT_TRUE(CreateSymbolicLink(temp_dir.path(), dir_link)); + EXPECT_FALSE(IsRegularFile(dir_link)); +} + +TEST(Filesystem, IsDirectory) { + EXPECT_FALSE(IsDirectory(base::FilePath(), false)); + EXPECT_FALSE(IsDirectory(base::FilePath(), true)); + + ScopedTempDir temp_dir; + EXPECT_TRUE(IsDirectory(temp_dir.path(), false)); + + base::FilePath file(temp_dir.path().Append(FILE_PATH_LITERAL("file"))); + EXPECT_FALSE(IsDirectory(file, false)); + EXPECT_FALSE(IsDirectory(file, true)); + + ASSERT_TRUE(CreateFile(file)); + EXPECT_FALSE(IsDirectory(file, false)); + EXPECT_FALSE(IsDirectory(file, true)); + + base::FilePath link(temp_dir.path().Append(FILE_PATH_LITERAL("link"))); + ASSERT_TRUE(CreateSymbolicLink(file, link)); + EXPECT_FALSE(IsDirectory(link, false)); + EXPECT_FALSE(IsDirectory(link, true)); + + ASSERT_TRUE(LoggingRemoveFile(file)); + EXPECT_FALSE(IsDirectory(link, false)); + EXPECT_FALSE(IsDirectory(link, true)); + + base::FilePath dir_link( + temp_dir.path().Append(FILE_PATH_LITERAL("dir_link"))); + ASSERT_TRUE(CreateSymbolicLink(temp_dir.path(), dir_link)); + EXPECT_FALSE(IsDirectory(dir_link, false)); + EXPECT_TRUE(IsDirectory(dir_link, true)); +} + +TEST(Filesystem, RemoveFile) { + EXPECT_FALSE(LoggingRemoveFile(base::FilePath())); + + ScopedTempDir temp_dir; + EXPECT_FALSE(LoggingRemoveFile(temp_dir.path())); + + base::FilePath file(temp_dir.path().Append(FILE_PATH_LITERAL("file"))); + EXPECT_FALSE(LoggingRemoveFile(file)); + + ASSERT_TRUE(CreateFile(file)); + EXPECT_TRUE(IsRegularFile(file)); + + base::FilePath link(temp_dir.path().Append(FILE_PATH_LITERAL("link"))); + ASSERT_TRUE(CreateSymbolicLink(file, link)); + EXPECT_TRUE(LoggingRemoveFile(link)); + EXPECT_FALSE(FileExists(link)); + EXPECT_TRUE(FileExists(file)); + + base::FilePath dir(temp_dir.path().Append(FILE_PATH_LITERAL("dir"))); + ASSERT_TRUE( + LoggingCreateDirectory(dir, FilePermissions::kWorldReadable, false)); + EXPECT_FALSE(LoggingRemoveFile(dir)); + + ASSERT_TRUE(CreateSymbolicLink(dir, link)); + EXPECT_TRUE(LoggingRemoveFile(link)); + EXPECT_FALSE(FileExists(link)); + EXPECT_TRUE(FileExists(dir)); + + EXPECT_TRUE(LoggingRemoveFile(file)); + EXPECT_FALSE(IsRegularFile(file)); + EXPECT_FALSE(LoggingRemoveFile(file)); +} + +TEST(Filesystem, RemoveDirectory) { + EXPECT_FALSE(LoggingRemoveDirectory(base::FilePath())); + + ScopedTempDir temp_dir; + + base::FilePath dir(temp_dir.path().Append(FILE_PATH_LITERAL("dir"))); + ASSERT_TRUE( + LoggingCreateDirectory(dir, FilePermissions::kWorldReadable, false)); + + base::FilePath file(dir.Append(FILE_PATH_LITERAL("file"))); + EXPECT_FALSE(LoggingRemoveDirectory(file)); + + ASSERT_TRUE(CreateFile(file)); + EXPECT_FALSE(LoggingRemoveDirectory(file)); + EXPECT_FALSE(LoggingRemoveDirectory(dir)); + + base::FilePath link(temp_dir.path().Append(FILE_PATH_LITERAL("link"))); + ASSERT_TRUE(CreateSymbolicLink(file, link)); + EXPECT_FALSE(LoggingRemoveDirectory(link)); + EXPECT_TRUE(LoggingRemoveFile(link)); + + ASSERT_TRUE(CreateSymbolicLink(dir, link)); + EXPECT_FALSE(LoggingRemoveDirectory(link)); + EXPECT_TRUE(LoggingRemoveFile(link)); + + ASSERT_TRUE(LoggingRemoveFile(file)); + EXPECT_TRUE(LoggingRemoveDirectory(dir)); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/file/filesystem_test_util.cc b/util/file/filesystem_test_util.cc new file mode 100644 index 00000000..9113b87f --- /dev/null +++ b/util/file/filesystem_test_util.cc @@ -0,0 +1,63 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/filesystem_test_util.h" + +#include "base/logging.h" +#include "build/build_config.h" +#include "gtest/gtest.h" +#include "util/file/file_io.h" +#include "util/file/filesystem.h" + +#if defined(OS_POSIX) +#include + +#include "base/posix/eintr_wrapper.h" +#elif defined(OS_WIN) +#include +#endif + +namespace crashpad { +namespace test { + +bool CreateFile(const base::FilePath& file) { + ScopedFileHandle fd(LoggingOpenFileForWrite( + file, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)); + EXPECT_TRUE(fd.is_valid()); + return fd.is_valid(); +} + +bool CreateSymbolicLink(const base::FilePath& target_path, + const base::FilePath& symlink_path) { +#if defined(OS_POSIX) + int rv = HANDLE_EINTR( + symlink(target_path.value().c_str(), symlink_path.value().c_str())); + if (rv != 0) { + PLOG(ERROR) << "symlink"; + return false; + } +#elif defined(OS_WIN) + if (!::CreateSymbolicLink( + symlink_path.value().c_str(), + target_path.value().c_str(), + IsDirectory(target_path, true) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0)) { + PLOG(ERROR) << "CreateSymbolicLink"; + return false; + } +#endif // OS_POSIX + return true; +} + +} // namespace test +} // namespace crashpad diff --git a/util/file/filesystem_test_util.h b/util/file/filesystem_test_util.h new file mode 100644 index 00000000..19812f47 --- /dev/null +++ b/util/file/filesystem_test_util.h @@ -0,0 +1,31 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_FILE_FILESYSTEM_TEST_UTIL_H_ +#define CRASHPAD_UTIL_FILE_FILESYSTEM_TEST_UTIL_H_ + +#include "base/files/file_path.h" + +namespace crashpad { +namespace test { + +bool CreateFile(const base::FilePath& file); + +bool CreateSymbolicLink(const base::FilePath& target_path, + const base::FilePath& symlink_path); + +} // namespace test +} // namespace crashpad + +#endif // CRASHPAD_UTIL_FILE_FILESYSTEM_TEST_UTIL_H_ diff --git a/util/file/filesystem_win.cc b/util/file/filesystem_win.cc new file mode 100644 index 00000000..64531b32 --- /dev/null +++ b/util/file/filesystem_win.cc @@ -0,0 +1,120 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/filesystem.h" + +#include + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" + +namespace crashpad { + +namespace { + +bool IsSymbolicLink(const base::FilePath& path) { + WIN32_FIND_DATA find_data; + ScopedSearchHANDLE handle(FindFirstFileEx(path.value().c_str(), + FindExInfoBasic, + &find_data, + FindExSearchNameMatch, + nullptr, + 0)); + if (!handle.is_valid()) { + PLOG(ERROR) << "FindFirstFileEx " << base::UTF16ToUTF8(path.value()); + return false; + } + + return (find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && + find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK; +} + +bool LoggingRemoveDirectoryImpl(const base::FilePath& path) { + if (!RemoveDirectory(path.value().c_str())) { + PLOG(ERROR) << "RemoveDirectory " << base::UTF16ToUTF8(path.value()); + return false; + } + return true; +} + +} // namespace + +bool LoggingCreateDirectory(const base::FilePath& path, + FilePermissions permissions, + bool may_reuse) { + if (CreateDirectory(path.value().c_str(), nullptr)) { + return true; + } + if (may_reuse && GetLastError() == ERROR_ALREADY_EXISTS) { + if (!IsDirectory(path, true)) { + LOG(ERROR) << base::UTF16ToUTF8(path.value()) << " not a directory"; + return false; + } + return true; + } + PLOG(ERROR) << "CreateDirectory " << base::UTF16ToUTF8(path.value()); + return false; +} + +bool IsRegularFile(const base::FilePath& path) { + DWORD fileattr = GetFileAttributes(path.value().c_str()); + if (fileattr == INVALID_FILE_ATTRIBUTES) { + PLOG_IF(ERROR, GetLastError() != ERROR_FILE_NOT_FOUND) + << "GetFileAttributes " << base::UTF16ToUTF8(path.value()); + return false; + } + if ((fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0 || + (fileattr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + return false; + } + return true; +} + +bool IsDirectory(const base::FilePath& path, bool allow_symlinks) { + DWORD fileattr = GetFileAttributes(path.value().c_str()); + if (fileattr == INVALID_FILE_ATTRIBUTES) { + PLOG(ERROR) << "GetFileAttributes " << base::UTF16ToUTF8(path.value()); + return false; + } + if (!allow_symlinks && (fileattr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + return false; + } + return (fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +bool LoggingRemoveFile(const base::FilePath& path) { + // RemoveDirectory is used if the file is a symbolic link to a directory. + DWORD fileattr = GetFileAttributes(path.value().c_str()); + if (fileattr != INVALID_FILE_ATTRIBUTES && + (fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0 && + (fileattr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + return LoggingRemoveDirectoryImpl(path); + } + + if (!DeleteFile(path.value().c_str())) { + PLOG(ERROR) << "DeleteFile " << base::UTF16ToUTF8(path.value()); + return false; + } + return true; +} + +bool LoggingRemoveDirectory(const base::FilePath& path) { + if (IsSymbolicLink(path)) { + LOG(ERROR) << "Not a directory " << base::UTF16ToUTF8(path.value()); + return false; + } + return LoggingRemoveDirectoryImpl(path); +} + +} // namespace crashpad diff --git a/util/misc/as_underlying_type.h b/util/misc/as_underlying_type.h new file mode 100644 index 00000000..8afd0ef3 --- /dev/null +++ b/util/misc/as_underlying_type.h @@ -0,0 +1,33 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_MISC_AS_UNDERLYING_TYPE_H_ +#define CRASHPAD_UTIL_MISC_AS_UNDERLYING_TYPE_H_ + +#include + +namespace crashpad { + +//! \brief Casts a value to its underlying type. +//! +//! \param[in] from The value to be casted. +//! \return \a from casted to its underlying type. +template +typename std::underlying_type::type AsUnderlyingType(From from) { + return static_cast::type>(from); +} + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MISC_AS_UNDERLYING_TYPE_H_ diff --git a/util/posix/close_multiple.cc b/util/posix/close_multiple.cc index 3eab3c5f..02c8a767 100644 --- a/util/posix/close_multiple.cc +++ b/util/posix/close_multiple.cc @@ -14,13 +14,10 @@ #include "util/posix/close_multiple.h" -#include #include #include #include #include -#include -#include #include #include @@ -28,10 +25,10 @@ #include "base/files/scoped_file.h" #include "base/logging.h" #include "base/posix/eintr_wrapper.h" +#include "base/strings/string_number_conversions.h" #include "build/build_config.h" +#include "util/file/directory_reader.h" #include "util/misc/implicit_cast.h" -#include "util/numeric/safe_assignment.h" -#include "util/posix/scoped_dir.h" #if defined(OS_MACOSX) #include @@ -73,54 +70,35 @@ void CloseNowOrOnExec(int fd, bool ebadf_ok) { // system-specific FD directory to determine which file descriptors are open. // This is an advantage over looping over all possible file descriptors, because // no attempt needs to be made to close file descriptors that are not open. -bool CloseMultipleNowOrOnExecUsingFDDir(int fd, int preserve_fd) { +bool CloseMultipleNowOrOnExecUsingFDDir(int min_fd, int preserve_fd) { #if defined(OS_MACOSX) static constexpr char kFDDir[] = "/dev/fd"; #elif defined(OS_LINUX) || defined(OS_ANDROID) static constexpr char kFDDir[] = "/proc/self/fd"; #endif - DIR* dir = opendir(kFDDir); - if (!dir) { - PLOG(WARNING) << "opendir"; + DirectoryReader reader; + if (!reader.Open(base::FilePath(kFDDir))) { return false; } + int directory_fd = reader.DirectoryFD(); + DCHECK_GE(directory_fd, 0); - ScopedDIR dir_owner(dir); - - int dir_fd = dirfd(dir); - if (dir_fd == -1) { - PLOG(WARNING) << "dirfd"; - return false; - } - - dirent* entry; - while ((errno = 0, entry = readdir(dir)) != nullptr) { - const char* entry_name = entry->d_name; - if (strcmp(entry_name, ".") == 0 || strcmp(entry_name, "..") == 0) { - continue; - } - - char* end; - long entry_fd_long = strtol(entry_name, &end, 10); - if (entry_name[0] == '\0' || *end) { - LOG(ERROR) << "unexpected entry " << entry_name; - return false; - } - + base::FilePath entry_fd_path; + DirectoryReader::Result result; + while ((result = reader.NextFile(&entry_fd_path)) == + DirectoryReader::Result::kSuccess) { int entry_fd; - if (!AssignIfInRange(&entry_fd, entry_fd_long)) { - LOG(ERROR) << "out-of-range fd " << entry_name; + if (!base::StringToInt(entry_fd_path.value(), &entry_fd)) { return false; } - if (entry_fd >= fd && entry_fd != preserve_fd && entry_fd != dir_fd) { + if (entry_fd >= min_fd && entry_fd != preserve_fd && + entry_fd != directory_fd) { CloseNowOrOnExec(entry_fd, false); } } - - if (errno != 0) { - PLOG(WARNING) << "readdir"; + if (result == DirectoryReader::Result::kError) { return false; } diff --git a/util/posix/scoped_dir.cc b/util/posix/scoped_dir.cc index 555900a3..94e4eecf 100644 --- a/util/posix/scoped_dir.cc +++ b/util/posix/scoped_dir.cc @@ -20,7 +20,7 @@ namespace crashpad { namespace internal { -void ScopedDIRCloser::operator()(DIR* dir) const { +void ScopedDIRCloseTraits::Free(DIR* dir) { if (dir && IGNORE_EINTR(closedir(dir)) != 0) { PLOG(ERROR) << "closedir"; } diff --git a/util/posix/scoped_dir.h b/util/posix/scoped_dir.h index 2eade40e..3fcbdf9c 100644 --- a/util/posix/scoped_dir.h +++ b/util/posix/scoped_dir.h @@ -17,13 +17,14 @@ #include -#include +#include "base/scoped_generic.h" namespace crashpad { namespace internal { -struct ScopedDIRCloser { - void operator()(DIR* dir) const; +struct ScopedDIRCloseTraits { + static DIR* InvalidValue() { return nullptr; } + static void Free(DIR* dir); }; } // namespace internal @@ -31,7 +32,7 @@ struct ScopedDIRCloser { //! \brief Maintains a directory opened by `opendir`. //! //! On destruction, the directory will be closed by calling `closedir`. -using ScopedDIR = std::unique_ptr; +using ScopedDIR = base::ScopedGeneric; } // namespace crashpad diff --git a/util/util.gyp b/util/util.gyp index f7246305..847ac773 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -32,6 +32,9 @@ 'sources': [ 'file/delimited_file_reader.cc', 'file/delimited_file_reader.h', + 'file/directory_reader.h', + 'file/directory_reader_posix.cc', + 'file/directory_reader_win.cc', 'file/file_io.cc', 'file/file_io.h', 'file/file_io_posix.cc', @@ -40,6 +43,9 @@ 'file/file_reader.h', 'file/file_seeker.cc', 'file/file_seeker.h', + 'file/filesystem.h', + 'file/filesystem_posix.cc', + 'file/filesystem_win.cc', 'file/file_writer.cc', 'file/file_writer.h', 'file/string_file.cc', @@ -108,6 +114,7 @@ 'misc/address_sanitizer.h', 'misc/address_types.h', 'misc/arraysize_unsafe.h', + 'misc/as_underlying_type.h', 'misc/clock.h', 'misc/clock_mac.cc', 'misc/clock_posix.cc', diff --git a/util/util_test.gyp b/util/util_test.gyp index c43048b5..ede3259b 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -36,8 +36,12 @@ ], 'sources': [ 'file/delimited_file_reader_test.cc', + 'file/directory_reader_test.cc', 'file/file_io_test.cc', 'file/file_reader_test.cc', + 'file/filesystem_test.cc', + 'file/filesystem_test_util.cc', + 'file/filesystem_test_util.h', 'file/string_file_test.cc', 'linux/auxiliary_vector_test.cc', 'linux/memory_map_test.cc', diff --git a/util/win/scoped_handle.cc b/util/win/scoped_handle.cc index 57d28217..5e79e572 100644 --- a/util/win/scoped_handle.cc +++ b/util/win/scoped_handle.cc @@ -28,5 +28,9 @@ void ScopedKernelHANDLECloseTraits::Free(HANDLE handle) { PCHECK(CloseHandle(handle)) << "CloseHandle"; } +void ScopedSearchHANDLECloseTraits::Free(HANDLE handle) { + PCHECK(FindClose(handle)) << "FindClose"; +} + } // namespace internal } // namespace crashpad diff --git a/util/win/scoped_handle.h b/util/win/scoped_handle.h index 68be4f7b..a5893b84 100644 --- a/util/win/scoped_handle.h +++ b/util/win/scoped_handle.h @@ -24,16 +24,17 @@ namespace crashpad { namespace internal { struct ScopedFileHANDLECloseTraits { - static HANDLE InvalidValue() { - return INVALID_HANDLE_VALUE; - } + static HANDLE InvalidValue() { return INVALID_HANDLE_VALUE; } static void Free(HANDLE handle); }; struct ScopedKernelHANDLECloseTraits { - static HANDLE InvalidValue() { - return nullptr; - } + static HANDLE InvalidValue() { return nullptr; } + static void Free(HANDLE handle); +}; + +struct ScopedSearchHANDLECloseTraits { + static HANDLE InvalidValue() { return INVALID_HANDLE_VALUE; } static void Free(HANDLE handle); }; @@ -43,6 +44,8 @@ using ScopedFileHANDLE = base::ScopedGeneric; using ScopedKernelHANDLE = base::ScopedGeneric; +using ScopedSearchHANDLE = + base::ScopedGeneric; } // namespace crashpad