blob: 0856c6959c6d3f5a96260ffed4da28cb8386cea6 [file] [log] [blame]
// Copyright 2015 Google Inc. 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.
// Adapted from file_util_posix.cc.
#include "base/file_util.h"
#include <stack>
#include <string>
#include "base/base_paths.h"
#include "base/basictypes.h"
#include "base/file_path.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/platform_file.h"
#include "base/threading/thread_restrictions.h"
#include "base/time.h"
#include "starboard/directory.h"
#include "starboard/file.h"
#include "starboard/system.h"
namespace {
// The list of portable filename characters as per POSIX. This should be the
// lowest-common-denominator of acceptable filename characters.
const char kPortableFilenameCharacters[] = {
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789"
"_-"
};
const int kPortableFilenameCharactersLength =
SB_ARRAY_SIZE_INT(kPortableFilenameCharacters) - 1;
// 8 characters = 65 ^ 8 possible filenames, which gives a nice wide space for
// avoiding collisions.
const char kTempSubstitution[] = "XXXXXXXX";
const int kTempSubstitutionLength = SB_ARRAY_SIZE_INT(kTempSubstitution) - 1;
std::string TempFileName() {
return std::string(".com.youtube.Cobalt.XXXXXXXX");
}
// Takes the template defined in |in_out_template| and destructively replaces
// any 'X' characters at the end with randomized portable filename characters.
void GenerateTempFileName(FilePath::StringType *in_out_template) {
size_t template_start = in_out_template->find(kTempSubstitution);
if (template_start == FilePath::StringType::npos) {
// No pattern.
return;
}
for (int i = 0; i < kTempSubstitutionLength; ++i) {
uint64_t random = SbSystemGetRandomUInt64();
int index = random % kPortableFilenameCharactersLength;
(*in_out_template)[template_start + i] = kPortableFilenameCharacters[index];
}
}
// Creates a random filename based on the TempFileName() pattern, creates the
// file, leaving it open, placing the path in |out_path| and returning the open
// SbFile.
SbFile CreateAndOpenTemporaryFile(FilePath directory, FilePath *out_path) {
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(out_path);
FilePath path = directory.Append(TempFileName());
FilePath::StringType tmpdir_string = path.value();
GenerateTempFileName(&tmpdir_string);
*out_path = FilePath(tmpdir_string);
return SbFileOpen(tmpdir_string.c_str(), kSbFileCreateOnly | kSbFileWrite,
NULL, NULL);
}
// Retries creating a temporary file until it can win the race to create a
// unique one.
SbFile CreateAndOpenTemporaryFileSafely(FilePath directory,
FilePath *out_path) {
SbFile file = kSbFileInvalid;
while (!SbFileIsValid(file)) {
file = CreateAndOpenTemporaryFile(directory, out_path);
}
return file;
}
bool CreateTemporaryDirInDirImpl(const FilePath &base_dir,
const FilePath::StringType &name_tmpl,
FilePath *new_dir) {
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(name_tmpl.find(kTempSubstitution) != FilePath::StringType::npos)
<< "Directory name template must contain \"XXXXXXXX\".";
FilePath dir_template = base_dir.Append(name_tmpl);
std::string dir_template_string = dir_template.value();
FilePath sub_dir;
while (true) {
std::string sub_dir_string = dir_template_string;
GenerateTempFileName(&sub_dir_string);
sub_dir = FilePath(sub_dir_string);
if (!file_util::DirectoryExists(sub_dir)) {
break;
}
}
// NOTE: This is not as secure as mkdtemp, because it doesn't atomically
// guarantee that there is no collision. But, with 8 X's it should be good
// enough for our purposes.
if (!file_util::CreateDirectory(sub_dir)) {
DPLOG(ERROR) << "CreateDirectory";
return false;
}
*new_dir = sub_dir;
return true;
}
} // namespace
namespace file_util {
bool AbsolutePath(FilePath* path) {
// We don't have cross-platform tools for this, so we will just return true if
// the path is already absolute.
return path->IsAbsolute();
}
bool Delete(const FilePath &path, bool recursive) {
base::ThreadRestrictions::AssertIOAllowed();
const char *path_str = path.value().c_str();
bool directory = SbDirectoryCanOpen(path_str);
if (!recursive || !directory) {
return SbFileDelete(path_str);
}
bool success = true;
std::stack<std::string> directories;
directories.push(path.value());
// NOTE: Right now, for Linux, SbFileGetInfo does not follow
// symlinks. This is good for avoiding deleting through symlinks, but makes it
// hard to use symlinks on platforms where they are supported. This seems
// safest for the lowest-common-denominator approach of pretending symlinks
// don't exist.
FileEnumerator traversal(
path, true, FileEnumerator::FILES | FileEnumerator::DIRECTORIES);
// Delete all files and push all directories in depth order onto the stack.
for (FilePath current = traversal.Next();
success && !current.empty();
current = traversal.Next()) {
FileEnumerator::FindInfo info;
traversal.GetFindInfo(&info);
if (FileEnumerator::IsDirectory(info)) {
directories.push(current.value());
} else {
success = SbFileDelete(current.value().c_str());
}
}
// Delete all directories in reverse-depth order, now that they have no more
// regular files.
while (success && !directories.empty()) {
success = SbFileDelete(directories.top().c_str());
directories.pop();
}
return success;
}
bool CopyFile(const FilePath &from_path, const FilePath &to_path) {
base::ThreadRestrictions::AssertIOAllowed();
base::PlatformFile source_file = base::CreatePlatformFile(
from_path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL,
NULL);
if (source_file == base::kInvalidPlatformFileValue) {
DPLOG(ERROR) << "CopyFile(): Unable to open source file: "
<< from_path.value();
return false;
}
base::PlatformFile destination_file = base::CreatePlatformFileUnsafe(
to_path, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE,
NULL, NULL);
if (destination_file == base::kInvalidPlatformFileValue) {
DPLOG(ERROR) << "CopyFile(): Unable to open destination file: "
<< to_path.value();
ignore_result(base::ClosePlatformFile(destination_file));
return false;
}
const size_t kBufferSize = 32768;
std::vector<char> buffer(kBufferSize);
bool result = true;
while (result) {
int bytes_read = base::ReadPlatformFileAtCurrentPos(source_file, &buffer[0],
buffer.size());
if (bytes_read < 0) {
result = false;
break;
}
if (bytes_read == 0) {
break;
}
int bytes_written = base::WritePlatformFileAtCurrentPos(
destination_file, &buffer[0], bytes_read);
if (bytes_written < bytes_read) {
DLOG(ERROR) << "CopyFile(): bytes_read (" << bytes_read
<< ") > bytes_written (" << bytes_written << ")";
// Because we use a best-effort write, if we wrote less than what was
// available, something went wrong.
result = false;
break;
}
}
if (!base::ClosePlatformFile(source_file)) {
result = false;
}
if (!base::ClosePlatformFile(destination_file)) {
result = false;
}
return result;
}
bool PathExists(const FilePath &path) {
base::ThreadRestrictions::AssertIOAllowed();
return SbFileExists(path.value().c_str());
}
bool PathIsWritable(const FilePath &path) {
base::ThreadRestrictions::AssertIOAllowed();
return SbFileCanOpen(path.value().c_str(), kSbFileOpenAlways | kSbFileWrite);
}
bool DirectoryExists(const FilePath& path) {
base::ThreadRestrictions::AssertIOAllowed();
SbFileInfo info;
if (!SbFileGetPathInfo(path.value().c_str(), &info)) {
return false;
}
return info.is_directory;
}
bool GetTempDir(FilePath *path) {
char buffer[SB_FILE_MAX_PATH + 1] = {0};
bool result = SbSystemGetPath(kSbSystemPathTempDirectory, buffer,
SB_ARRAY_SIZE_INT(buffer));
if (!result) {
return false;
}
*path = FilePath(buffer);
if (DirectoryExists(*path)) {
return true;
}
return CreateDirectory(*path);
}
bool GetShmemTempDir(FilePath *path, bool executable) {
return GetTempDir(path);
}
FilePath GetHomeDir() {
FilePath path;
bool result = PathService::Get(base::DIR_HOME, &path);
DCHECK(result);
return path;
}
bool CreateTemporaryFile(FilePath *path) {
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(path);
FilePath directory;
if (!GetTempDir(&directory)) {
return false;
}
return CreateTemporaryFileInDir(directory, path);
}
bool CreateTemporaryFileInDir(const FilePath &dir, FilePath *temp_file) {
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(temp_file);
SbFile file = CreateAndOpenTemporaryFileSafely(dir, temp_file);
return (SbFileIsValid(file) && SbFileClose(file));
}
bool CreateTemporaryDirInDir(const FilePath &base_dir,
const FilePath::StringType &prefix,
FilePath *new_dir) {
FilePath::StringType mkdtemp_template = prefix + kTempSubstitution;
return CreateTemporaryDirInDirImpl(base_dir, mkdtemp_template, new_dir);
}
bool CreateNewTempDirectory(const FilePath::StringType &prefix,
FilePath *new_temp_path) {
FilePath tmpdir;
if (!GetTempDir(&tmpdir)) {
return false;
}
return CreateTemporaryDirInDirImpl(tmpdir, TempFileName(), new_temp_path);
}
bool CreateDirectory(const FilePath &full_path) {
base::ThreadRestrictions::AssertIOAllowed();
std::vector<FilePath> subpaths;
// Collect a list of all parent directories.
FilePath last_path = full_path;
subpaths.push_back(full_path);
for (FilePath path = full_path.DirName();
path.value() != last_path.value();
path = path.DirName()) {
subpaths.push_back(path);
last_path = path;
}
// Iterate through the parents and create the missing ones.
for (std::vector<FilePath>::reverse_iterator i = subpaths.rbegin();
i != subpaths.rend(); ++i) {
if (DirectoryExists(*i)) {
continue;
}
if (!SbDirectoryCreate(i->value().c_str())) {
return false;
}
}
return true;
}
bool IsLink(const FilePath &file_path) {
base::ThreadRestrictions::AssertIOAllowed();
SbFileInfo info;
// If we can't SbfileGetPathInfo on the file, it's safe to assume that the
// file won't at least be a 'followable' link.
if (!SbFileGetPathInfo(file_path.value().c_str(), &info)) {
return false;
}
return info.is_symbolic_link;
}
bool GetFileInfo(const FilePath &file_path, base::PlatformFileInfo *results) {
base::ThreadRestrictions::AssertIOAllowed();
SbFileInfo info;
if (!SbFileGetPathInfo(file_path.value().c_str(), &info)) {
return false;
}
results->is_directory = info.is_directory;
results->size = info.size;
results->last_modified = base::Time::FromSbTime(info.last_modified);
results->last_accessed = base::Time::FromSbTime(info.last_accessed);
results->creation_time = base::Time::FromSbTime(info.creation_time);
return true;
}
int ReadFile(const FilePath &filename, char *data, int size) {
base::ThreadRestrictions::AssertIOAllowed();
base::PlatformFile file = base::CreatePlatformFile(
filename, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL,
NULL);
if (file == base::kInvalidPlatformFileValue) {
DLOG(ERROR) << "ReadFile(" << filename.value() << "): Unable to open.";
return -1;
}
// We use a best-effort read here.
int bytes_read = base::ReadPlatformFileAtCurrentPos(file, data, size);
if (!base::ClosePlatformFile(file)) {
DLOG(ERROR) << "ReadFile(" << filename.value() << "): Unable to close.";
return -1;
}
return bytes_read;
}
int WriteFile(const FilePath &filename, const char *data, int size) {
base::ThreadRestrictions::AssertIOAllowed();
base::PlatformFile file = base::CreatePlatformFile(
filename, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE,
NULL, NULL);
if (file == base::kInvalidPlatformFileValue) {
DLOG(ERROR) << "WriteFile(" << filename.value() << "): Unable to open.";
return -1;
}
// We use a best-effort write here.
int bytes_written = base::WritePlatformFileAtCurrentPos(file, data, size);
if (!base::ClosePlatformFile(file)) {
DLOG(ERROR) << "WriteFile(" << filename.value() << "): Unable to close.";
return -1;
}
return bytes_written;
}
int AppendToFile(const FilePath &filename, const char *data, int size) {
base::ThreadRestrictions::AssertIOAllowed();
base::PlatformFile file = base::CreatePlatformFile(
filename, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, NULL,
NULL);
if (file == base::kInvalidPlatformFileValue) {
DLOG(ERROR) << "AppendToFile(" << filename.value() << "): Unable to open.";
return -1;
}
if (!base::SeekPlatformFile(file, base::PLATFORM_FILE_FROM_END, 0)) {
DLOG(ERROR) << "AppendToFile(" << filename.value()
<< "): Unable to truncate.";
return -1;
}
int bytes_written = base::WritePlatformFileAtCurrentPos(file, data, size);
if (!base::ClosePlatformFile(file)) {
DLOG(ERROR) << "AppendToFile(" << filename.value() << "): Unable to close.";
return -1;
}
return bytes_written;
}
bool HasFileBeenModifiedSince(const FileEnumerator::FindInfo &find_info,
const base::Time &cutoff_time) {
return FileEnumerator::GetLastModifiedTime(find_info) >= cutoff_time;
}
///////////////////////////////////////////////
// FileEnumerator
FileEnumerator::FileEnumerator(const FilePath &root_path,
bool recursive,
int file_type)
: current_directory_entry_(0),
root_path_(root_path),
recursive_(recursive),
file_type_(file_type) {
// INCLUDE_DOT_DOT must not be specified if recursive.
DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
pending_paths_.push(root_path);
}
FileEnumerator::FileEnumerator(const FilePath &root_path,
bool recursive,
int file_type,
const FilePath::StringType &pattern)
: current_directory_entry_(0),
root_path_(root_path),
recursive_(recursive),
file_type_(file_type),
pattern_(root_path.Append(pattern).value()) {
// INCLUDE_DOT_DOT must not be specified if recursive.
DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
// The Windows version of this code appends the pattern to the root_path,
// potentially only matching against items in the top-most directory.
// Do the same here.
if (pattern.empty()) {
pattern_ = FilePath::StringType();
}
pending_paths_.push(root_path);
}
FileEnumerator::~FileEnumerator() {
}
FilePath FileEnumerator::Next() {
++current_directory_entry_;
// While we've exhausted the entries in the current directory, do the next
while (current_directory_entry_ >= directory_entries_.size()) {
if (pending_paths_.empty()) {
return FilePath();
}
root_path_ = pending_paths_.top();
root_path_ = root_path_.StripTrailingSeparators();
pending_paths_.pop();
std::vector<DirectoryEntryInfo> entries;
if (!ReadDirectory(&entries, root_path_)) {
continue;
}
directory_entries_.clear();
current_directory_entry_ = 0;
for (std::vector<DirectoryEntryInfo>::const_iterator i = entries.begin();
i != entries.end(); ++i) {
FilePath full_path = root_path_.Append(i->filename);
if (ShouldSkip(full_path)) {
continue;
}
if (pattern_.size()) {
NOTREACHED() << "Patterns not supported in Starboard.";
continue;
}
if (recursive_ && i->sb_info.is_directory) {
pending_paths_.push(full_path);
}
if ((i->sb_info.is_directory && (file_type_ & DIRECTORIES)) ||
(!i->sb_info.is_directory && (file_type_ & FILES))) {
directory_entries_.push_back(*i);
}
}
}
return
root_path_.Append(directory_entries_[current_directory_entry_].filename);
}
void FileEnumerator::GetFindInfo(FindInfo *info) {
DCHECK(info);
if (current_directory_entry_ >= directory_entries_.size()) {
return;
}
DirectoryEntryInfo *cur_entry = &directory_entries_[current_directory_entry_];
memcpy(&(info->sb_info), &(cur_entry->sb_info), sizeof(info->sb_info));
info->filename.assign(cur_entry->filename.value());
}
// static
bool FileEnumerator::IsDirectory(const FindInfo &info) {
return info.sb_info.is_directory;
}
// static
FilePath FileEnumerator::GetFilename(const FindInfo &find_info) {
return FilePath(find_info.filename);
}
// static
int64 FileEnumerator::GetFilesize(const FindInfo &find_info) {
return find_info.sb_info.size;
}
// static
base::Time FileEnumerator::GetLastModifiedTime(const FindInfo &find_info) {
return base::Time::FromSbTime(find_info.sb_info.last_modified);
}
// static
bool FileEnumerator::ReadDirectory(std::vector<DirectoryEntryInfo> *entries,
const FilePath &source) {
base::ThreadRestrictions::AssertIOAllowed();
SbDirectory dir = SbDirectoryOpen(source.value().c_str(), NULL);
if (!SbDirectoryIsValid(dir)) {
return false;
}
SbDirectoryEntry entry;
while (SbDirectoryGetNext(dir, &entry)) {
DirectoryEntryInfo info;
info.filename = FilePath(entry.name);
FilePath full_name = source.Append(entry.name);
// TODO: Make sure this follows symlinks on relevant platforms.
if (!SbFileGetPathInfo(full_name.value().c_str(), &info.sb_info)) {
DPLOG(ERROR) << "Couldn't SbFileGetInfo on " << full_name.value();
memset(&info.sb_info, 0, sizeof(info.sb_info));
}
entries->push_back(info);
}
ignore_result(SbDirectoryClose(dir));
return true;
}
} // namespace file_util