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