Add DirectoryReader to iterate over files in a directory

This change also adds functions to create directories, remove files and
directories, and check for the existence of files and directories.

Change-Id: I62b78219ae2b277d6976d2d90ec86fcabd0ef073
Reviewed-on: https://chromium-review.googlesource.com/696132
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Joshua Peraza 2017-10-16 12:31:13 -07:00 committed by Commit Bot
parent 906fce1d01
commit 474c7331a6
22 changed files with 1024 additions and 121 deletions

View File

@ -14,7 +14,6 @@
#include "snapshot/linux/process_reader.h"
#include <dirent.h>
#include <errno.h>
#include <sched.h>
#include <stdio.h>
@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <windows.h>
#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_

View File

@ -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 <dirent.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#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

View File

@ -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 <set>
#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<base::FilePath>& files,
const std::set<base::FilePath>& 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<base::FilePath> 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<base::FilePath> 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

View File

@ -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 <string.h>
#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

78
util/file/filesystem.h Normal file
View File

@ -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_

View File

@ -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 <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#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

View File

@ -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

View File

@ -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 <unistd.h>
#include "base/posix/eintr_wrapper.h"
#elif defined(OS_WIN)
#include <windows.h>
#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

View File

@ -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_

120
util/file/filesystem_win.cc Normal file
View File

@ -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 <windows.h>
#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

View File

@ -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 <type_traits>
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 From>
typename std::underlying_type<From>::type AsUnderlyingType(From from) {
return static_cast<typename std::underlying_type<From>::type>(from);
}
} // namespace crashpad
#endif // CRASHPAD_UTIL_MISC_AS_UNDERLYING_TYPE_H_

View File

@ -14,13 +14,10 @@
#include "util/posix/close_multiple.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
@ -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 <sys/sysctl.h>
@ -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";
return false;
}
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;
DirectoryReader reader;
if (!reader.Open(base::FilePath(kFDDir))) {
return false;
}
int directory_fd = reader.DirectoryFD();
DCHECK_GE(directory_fd, 0);
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;
}

View File

@ -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";
}

View File

@ -17,13 +17,14 @@
#include <dirent.h>
#include <memory>
#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<DIR, internal::ScopedDIRCloser>;
using ScopedDIR = base::ScopedGeneric<DIR*, internal::ScopedDIRCloseTraits>;
} // namespace crashpad

View File

@ -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',

View File

@ -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',

View File

@ -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

View File

@ -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<HANDLE, internal::ScopedFileHANDLECloseTraits>;
using ScopedKernelHANDLE =
base::ScopedGeneric<HANDLE, internal::ScopedKernelHANDLECloseTraits>;
using ScopedSearchHANDLE =
base::ScopedGeneric<HANDLE, internal::ScopedSearchHANDLECloseTraits>;
} // namespace crashpad