blob: ff0f70f273356379ea575842dd9328cebb61463b [file] [log] [blame]
// Copyright 2018 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/files/file_util.h"
#include <sys/stat.h>
#include <stack>
#include <string>
#include "base/base_paths.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/platform_file.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "starboard/configuration_constants.h"
#include "starboard/directory.h"
#include "starboard/file.h"
#include "starboard/system.h"
namespace base {
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) {
AssertBlockingAllowed();
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) {
AssertBlockingAllowed();
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 (!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 (!CreateDirectory(sub_dir)) {
DPLOG(ERROR) << "CreateDirectory";
return false;
}
*new_dir = sub_dir;
return true;
}
} // namespace
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 DeleteFile(const FilePath &path, bool recursive) {
AssertBlockingAllowed();
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::FileInfo info(traversal.GetInfo());
if (info.IsDirectory()) {
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) {
AssertBlockingAllowed();
base::File source_file(from_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!source_file.IsValid()) {
DPLOG(ERROR) << "CopyFile(): Unable to open source file: "
<< from_path.value();
return false;
}
base::File destination_file(
to_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!destination_file.IsValid()) {
DPLOG(ERROR) << "CopyFile(): Unable to open destination file: "
<< to_path.value();
return false;
}
const size_t kBufferSize = 32768;
std::vector<char> buffer(kBufferSize);
bool result = true;
while (result) {
int bytes_read = source_file.ReadAtCurrentPos(&buffer[0], buffer.size());
if (bytes_read < 0) {
result = false;
break;
}
if (bytes_read == 0) {
break;
}
int bytes_written =
destination_file.WriteAtCurrentPos(&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;
}
}
return result;
}
bool PathExists(const FilePath &path) {
AssertBlockingAllowed();
struct stat file_info;
return stat(path.value().c_str(), &file_info) == 0;
}
bool PathIsWritable(const FilePath &path) {
AssertBlockingAllowed();
return SbFileCanOpen(path.value().c_str(), kSbFileOpenAlways | kSbFileWrite);
}
bool DirectoryExists(const FilePath& path) {
AssertBlockingAllowed();
SbFileInfo info;
if (!SbFileGetPathInfo(path.value().c_str(), &info)) {
return false;
}
return info.is_directory;
}
bool GetTempDir(FilePath *path) {
std::vector<char> buffer(kSbFileMaxPath + 1, 0);
bool result =
SbSystemGetPath(kSbSystemPathTempDirectory, buffer.data(), buffer.size());
if (!result) {
return false;
}
*path = FilePath(buffer.data());
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_CACHE, &path);
DCHECK(result);
return path;
}
bool CreateTemporaryFile(FilePath *path) {
AssertBlockingAllowed();
DCHECK(path);
FilePath directory;
if (!GetTempDir(&directory)) {
return false;
}
return CreateTemporaryFileInDir(directory, path);
}
bool CreateTemporaryFileInDir(const FilePath &dir, FilePath *temp_file) {
AssertBlockingAllowed();
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 CreateDirectoryAndGetError(const FilePath &full_path, File::Error* error) {
AssertBlockingAllowed();
// Fast-path: can the full path be resolved from the full path?
if (mkdir(full_path.value().c_str(), 0700) == 0
|| DirectoryExists(full_path)) {
return true;
}
// Slow-path: iterate through the paths and resolve from the root
// to the leaf.
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 (mkdir(i->value().c_str(), 0700) != 0 &&
!SbDirectoryCanOpen(i->value().c_str())){
if (error)
*error = File::OSErrorToFileError(SbSystemGetLastError());
return false;
}
}
return true;
}
bool IsLink(const FilePath &file_path) {
AssertBlockingAllowed();
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, File::Info *results) {
AssertBlockingAllowed();
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::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(info.last_modified));
results->last_accessed = base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(info.last_accessed));
results->creation_time = base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(info.creation_time));
return true;
}
int ReadFile(const FilePath &filename, char *data, int size) {
AssertBlockingAllowed();
base::File file(filename, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid()) {
DLOG(ERROR) << "ReadFile(" << filename.value() << "): Unable to open.";
return -1;
}
// We use a best-effort read here.
return file.ReadAtCurrentPos(data, size);
}
int WriteFile(const FilePath &filename, const char *data, int size) {
AssertBlockingAllowed();
base::File file(
filename, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!file.IsValid()) {
DLOG(ERROR) << "WriteFile(" << filename.value() << "): Unable to open.";
return -1;
}
// We use a best-effort write here.
return file.WriteAtCurrentPos(data, size);
}
bool AppendToFile(const FilePath &filename, const char *data, int size) {
AssertBlockingAllowed();
base::File file(filename, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
if (!file.IsValid()) {
DLOG(ERROR) << "AppendToFile(" << filename.value() << "): Unable to open.";
return false;
}
if (file.Seek(base::File::FROM_END, 0) == -1) {
DLOG(ERROR) << "AppendToFile(" << filename.value()
<< "): Unable to truncate.";
return false;
}
return file.WriteAtCurrentPos(data, size) == size;
}
bool HasFileBeenModifiedSince(const FileEnumerator::FileInfo &file_info,
const base::Time &cutoff_time) {
return file_info.GetLastModifiedTime() >= cutoff_time;
}
bool GetCurrentDirectory(FilePath* dir) {
ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
// Not supported on Starboard.
NOTREACHED();
return false;
}
bool SetCurrentDirectory(const FilePath& path) {
ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
// Not supported on Starboard.
NOTREACHED();
return false;
}
FilePath MakeAbsoluteFilePath(const FilePath& input) {
AssertBlockingAllowed();
// Only absolute paths are supported in Starboard.
DCHECK(input.IsAbsolute());
return input;
}
namespace internal {
bool MoveUnsafe(const FilePath& from_path, const FilePath& to_path) {
AssertBlockingAllowed();
// Moving files is not supported in Starboard.
NOTREACHED();
return false;
}
} // internal
} // namespace base