blob: cf121285d894f1eb5c951f267d4e61dc61d39808 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/file_util.h"
#if defined(OS_WIN)
#include <io.h>
#endif
#include <stdio.h>
#include <fstream>
#include "base/file_path.h"
#include "base/logging.h"
#include "base/stringprintf.h"
#include "base/string_piece.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
namespace {
const FilePath::CharType kExtensionSeparator = FILE_PATH_LITERAL('.');
// The maximum number of 'uniquified' files we will try to create.
// This is used when the filename we're trying to download is already in use,
// so we create a new unique filename by appending " (nnn)" before the
// extension, where 1 <= nnn <= kMaxUniqueFiles.
// Also used by code that cleans up said files.
static const int kMaxUniqueFiles = 100;
} // namespace
namespace file_util {
bool g_bug108724_debug = false;
bool EndsWithSeparator(const FilePath& path) {
FilePath::StringType value = path.value();
if (value.empty())
return false;
return FilePath::IsSeparator(value[value.size() - 1]);
}
bool EnsureEndsWithSeparator(FilePath* path) {
if (!DirectoryExists(*path))
return false;
if (EndsWithSeparator(*path))
return true;
FilePath::StringType& path_str =
const_cast<FilePath::StringType&>(path->value());
path_str.append(&FilePath::kSeparators[0], 1);
return true;
}
void InsertBeforeExtension(FilePath* path, const FilePath::StringType& suffix) {
FilePath::StringType& value =
const_cast<FilePath::StringType&>(path->value());
const FilePath::StringType::size_type last_dot =
value.rfind(kExtensionSeparator);
const FilePath::StringType::size_type last_separator =
value.find_last_of(FilePath::StringType(FilePath::kSeparators));
if (last_dot == FilePath::StringType::npos ||
(last_separator != std::wstring::npos && last_dot < last_separator)) {
// The path looks something like "C:\pics.old\jojo" or "C:\pics\jojo".
// We should just append the suffix to the entire path.
value.append(suffix);
return;
}
value.insert(last_dot, suffix);
}
bool ContentsEqual(const FilePath& filename1, const FilePath& filename2) {
// We open the file in binary format even if they are text files because
// we are just comparing that bytes are exactly same in both files and not
// doing anything smart with text formatting.
std::ifstream file1(filename1.value().c_str(),
std::ios::in | std::ios::binary);
std::ifstream file2(filename2.value().c_str(),
std::ios::in | std::ios::binary);
// Even if both files aren't openable (and thus, in some sense, "equal"),
// any unusable file yields a result of "false".
if (!file1.is_open() || !file2.is_open())
return false;
const int BUFFER_SIZE = 2056;
char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
do {
file1.read(buffer1, BUFFER_SIZE);
file2.read(buffer2, BUFFER_SIZE);
if ((file1.eof() != file2.eof()) ||
(file1.gcount() != file2.gcount()) ||
(memcmp(buffer1, buffer2, file1.gcount()))) {
file1.close();
file2.close();
return false;
}
} while (!file1.eof() || !file2.eof());
file1.close();
file2.close();
return true;
}
#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD)
bool TextContentsEqual(const FilePath& filename1, const FilePath& filename2) {
std::ifstream file1(filename1.value().c_str(), std::ios::in);
std::ifstream file2(filename2.value().c_str(), std::ios::in);
// Even if both files aren't openable (and thus, in some sense, "equal"),
// any unusable file yields a result of "false".
if (!file1.is_open() || !file2.is_open())
return false;
do {
std::string line1, line2;
getline(file1, line1);
getline(file2, line2);
// Check for mismatched EOF states, or any error state.
if ((file1.eof() != file2.eof()) ||
file1.bad() || file2.bad()) {
return false;
}
// Trim all '\r' and '\n' characters from the end of the line.
std::string::size_type end1 = line1.find_last_not_of("\r\n");
if (end1 == std::string::npos)
line1.clear();
else if (end1 + 1 < line1.length())
line1.erase(end1 + 1);
std::string::size_type end2 = line2.find_last_not_of("\r\n");
if (end2 == std::string::npos)
line2.clear();
else if (end2 + 1 < line2.length())
line2.erase(end2 + 1);
if (line1 != line2)
return false;
} while (!file1.eof() || !file2.eof());
return true;
}
#endif
bool ReadFileToString(const FilePath& path, std::string* contents) {
if (path.ReferencesParent())
return false;
#if defined(COBALT)
// Use a smaller buffer so we don't run out of stack space.
const size_t kReadBufferSize = 1 << 12;
#else
const size_t kReadBufferSize = 1 << 16;
#endif
#if defined(OS_STARBOARD)
base::PlatformFile file = base::CreatePlatformFile(
path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL, NULL);
if (file == base::kInvalidPlatformFileValue) {
return false;
}
char buf[kReadBufferSize];
size_t len;
while ((len = base::ReadPlatformFileAtCurrentPos(file, buf, sizeof(buf))) >
0) {
if (contents) {
contents->append(buf, len);
}
}
base::ClosePlatformFile(file);
return true;
#else
FILE* file = OpenFile(path, "rb");
if (!file) {
return false;
}
char buf[kReadBufferSize];
size_t len;
while ((len = fread(buf, 1, sizeof(buf), file)) > 0) {
if (contents)
contents->append(buf, len);
}
CloseFile(file);
return true;
#endif
}
bool IsDirectoryEmpty(const FilePath& dir_path) {
FileEnumerator files(dir_path, false,
FileEnumerator::FILES | FileEnumerator::DIRECTORIES);
if (files.Next().value().empty())
return true;
return false;
}
#if !defined(OS_STARBOARD)
FILE* CreateAndOpenTemporaryFile(FilePath* path) {
FilePath directory;
if (!GetTempDir(&directory))
return NULL;
return CreateAndOpenTemporaryFileInDir(directory, path);
}
#endif // !defined(OS_STARBOARD)
bool GetFileSize(const FilePath& file_path, int64* file_size) {
base::PlatformFileInfo info;
if (!GetFileInfo(file_path, &info))
return false;
*file_size = info.size;
return true;
}
bool IsDot(const FilePath& path) {
return FILE_PATH_LITERAL(".") == path.BaseName().value();
}
bool IsDotDot(const FilePath& path) {
return FILE_PATH_LITERAL("..") == path.BaseName().value();
}
#if !defined(OS_STARBOARD)
bool TouchFile(const FilePath& path,
const base::Time& last_accessed,
const base::Time& last_modified) {
int flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE_ATTRIBUTES;
#if defined(COBALT_WIN)
// Ensure that this makes it through to the CRT.
flags |= base::PLATFORM_FILE_WRITE;
#endif // COBALT_WIN
#if defined(OS_WIN)
// On Windows, FILE_FLAG_BACKUP_SEMANTICS is needed to open a directory.
if (DirectoryExists(path))
flags |= base::PLATFORM_FILE_BACKUP_SEMANTICS;
#endif // OS_WIN
const base::PlatformFile file =
base::CreatePlatformFile(path, flags, NULL, NULL);
if (file != base::kInvalidPlatformFileValue) {
bool result = base::TouchPlatformFile(file, last_accessed, last_modified);
base::ClosePlatformFile(file);
return result;
}
return false;
}
bool SetLastModifiedTime(const FilePath& path,
const base::Time& last_modified) {
return TouchFile(path, last_modified, last_modified);
}
bool CloseFile(FILE* file) {
if (file == NULL)
return true;
return fclose(file) == 0;
}
bool TruncateFile(FILE* file) {
if (file == NULL)
return false;
long current_offset = ftell(file);
if (current_offset == -1)
return false;
#if defined(OS_WIN)
int fd = _fileno(file);
if (_chsize(fd, current_offset) != 0)
return false;
#else
int fd = fileno(file);
if (ftruncate(fd, current_offset) != 0)
return false;
#endif
return true;
}
#endif // !defined(OS_STARBOARD)
int GetUniquePathNumber(
const FilePath& path,
const FilePath::StringType& suffix) {
bool have_suffix = !suffix.empty();
if (!PathExists(path) &&
(!have_suffix || !PathExists(FilePath(path.value() + suffix)))) {
return 0;
}
FilePath new_path;
for (int count = 1; count <= kMaxUniqueFiles; ++count) {
new_path = path.InsertBeforeExtensionASCII(StringPrintf(" (%d)", count));
if (!PathExists(new_path) &&
(!have_suffix || !PathExists(FilePath(new_path.value() + suffix)))) {
return count;
}
}
return -1;
}
bool ContainsPath(const FilePath &parent, const FilePath& child) {
FilePath abs_parent = FilePath(parent);
FilePath abs_child = FilePath(child);
if (!file_util::AbsolutePath(&abs_parent) ||
!file_util::AbsolutePath(&abs_child))
return false;
#if defined(OS_WIN)
// file_util::AbsolutePath() does not flatten case on Windows, so we must do
// a case-insensitive compare.
if (!StartsWith(abs_child.value(), abs_parent.value(), false))
#else
if (!StartsWithASCII(abs_child.value(), abs_parent.value(), true))
#endif
return false;
// file_util::AbsolutePath() normalizes '/' to '\' on Windows, so we only need
// to check kSeparators[0].
if (abs_child.value().length() <= abs_parent.value().length() ||
abs_child.value()[abs_parent.value().length()] !=
FilePath::kSeparators[0])
return false;
return true;
}
int64 ComputeDirectorySize(const FilePath& root_path) {
int64 running_size = 0;
FileEnumerator file_iter(root_path, true, FileEnumerator::FILES);
for (FilePath current = file_iter.Next(); !current.empty();
current = file_iter.Next()) {
FileEnumerator::FindInfo info;
file_iter.GetFindInfo(&info);
#if defined(OS_WIN)
LARGE_INTEGER li = { info.nFileSizeLow, info.nFileSizeHigh };
running_size += li.QuadPart;
#elif defined(OS_STARBOARD)
running_size += info.sb_info.size;
#else
running_size += info.stat.st_size;
#endif
}
return running_size;
}
int64 ComputeFilesSize(const FilePath& directory,
const FilePath::StringType& pattern) {
int64 running_size = 0;
FileEnumerator file_iter(directory, false, FileEnumerator::FILES, pattern);
for (FilePath current = file_iter.Next(); !current.empty();
current = file_iter.Next()) {
FileEnumerator::FindInfo info;
file_iter.GetFindInfo(&info);
#if defined(OS_WIN)
LARGE_INTEGER li = { info.nFileSizeLow, info.nFileSizeHigh };
running_size += li.QuadPart;
#elif defined(OS_STARBOARD)
running_size += info.sb_info.size;
#else
running_size += info.stat.st_size;
#endif
}
return running_size;
}
#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD)
///////////////////////////////////////////////
// MemoryMappedFile
MemoryMappedFile::~MemoryMappedFile() {
CloseHandles();
}
bool MemoryMappedFile::Initialize(const FilePath& file_name) {
if (IsValid())
return false;
if (!MapFileToMemory(file_name)) {
CloseHandles();
return false;
}
return true;
}
bool MemoryMappedFile::Initialize(base::PlatformFile file) {
if (IsValid())
return false;
file_ = file;
if (!MapFileToMemoryInternal()) {
CloseHandles();
return false;
}
return true;
}
bool MemoryMappedFile::IsValid() const {
return data_ != NULL;
}
bool MemoryMappedFile::MapFileToMemory(const FilePath& file_name) {
file_ = base::CreatePlatformFile(
file_name, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
NULL, NULL);
if (file_ == base::kInvalidPlatformFileValue) {
DLOG(ERROR) << "Couldn't open " << file_name.value();
return false;
}
return MapFileToMemoryInternal();
}
#endif
///////////////////////////////////////////////
// FileEnumerator
//
// Note: the main logic is in file_util_<platform>.cc
bool FileEnumerator::ShouldSkip(const FilePath& path) {
FilePath::StringType basename = path.BaseName().value();
return IsDot(path) || (IsDotDot(path) && !(INCLUDE_DOT_DOT & file_type_));
}
} // namespace