michael@0: // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/file_util.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #ifndef ANDROID michael@0: #include michael@0: #endif michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #define _DARWIN_USE_64_BIT_INODE // Use 64-bit inode data structures michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "base/basictypes.h" michael@0: #include "base/eintr_wrapper.h" michael@0: #include "base/file_path.h" michael@0: #include "base/logging.h" michael@0: #include "base/string_util.h" michael@0: #include "base/time.h" michael@0: michael@0: namespace file_util { michael@0: michael@0: #if defined(GOOGLE_CHROME_BUILD) michael@0: static const char* kTempFileName = "com.google.chrome.XXXXXX"; michael@0: #else michael@0: static const char* kTempFileName = "org.chromium.XXXXXX"; michael@0: #endif michael@0: michael@0: bool AbsolutePath(FilePath* path) { michael@0: char full_path[PATH_MAX]; michael@0: if (realpath(path->value().c_str(), full_path) == NULL) michael@0: return false; michael@0: *path = FilePath(full_path); michael@0: return true; michael@0: } michael@0: michael@0: // TODO(erikkay): The Windows version of this accepts paths like "foo/bar/*" michael@0: // which works both with and without the recursive flag. I'm not sure we need michael@0: // that functionality. If not, remove from file_util_win.cc, otherwise add it michael@0: // here. michael@0: bool Delete(const FilePath& path, bool recursive) { michael@0: const char* path_str = path.value().c_str(); michael@0: struct stat file_info; michael@0: int test = stat(path_str, &file_info); michael@0: if (test != 0) { michael@0: // The Windows version defines this condition as success. michael@0: bool ret = (errno == ENOENT || errno == ENOTDIR); michael@0: return ret; michael@0: } michael@0: if (!S_ISDIR(file_info.st_mode)) michael@0: return (unlink(path_str) == 0); michael@0: if (!recursive) michael@0: return (rmdir(path_str) == 0); michael@0: michael@0: #ifdef ANDROID michael@0: // XXX Need ftsless impl for bionic michael@0: return false; michael@0: #else michael@0: bool success = true; michael@0: int ftsflags = FTS_PHYSICAL | FTS_NOSTAT; michael@0: char top_dir[PATH_MAX]; michael@0: if (base::strlcpy(top_dir, path_str, michael@0: arraysize(top_dir)) >= arraysize(top_dir)) { michael@0: return false; michael@0: } michael@0: char* dir_list[2] = { top_dir, NULL }; michael@0: FTS* fts = fts_open(dir_list, ftsflags, NULL); michael@0: if (fts) { michael@0: FTSENT* fts_ent = fts_read(fts); michael@0: while (success && fts_ent != NULL) { michael@0: switch (fts_ent->fts_info) { michael@0: case FTS_DNR: michael@0: case FTS_ERR: michael@0: // log error michael@0: success = false; michael@0: continue; michael@0: break; michael@0: case FTS_DP: michael@0: success = (rmdir(fts_ent->fts_accpath) == 0); michael@0: break; michael@0: case FTS_D: michael@0: break; michael@0: case FTS_NSOK: michael@0: case FTS_F: michael@0: case FTS_SL: michael@0: case FTS_SLNONE: michael@0: success = (unlink(fts_ent->fts_accpath) == 0); michael@0: break; michael@0: default: michael@0: DCHECK(false); michael@0: break; michael@0: } michael@0: fts_ent = fts_read(fts); michael@0: } michael@0: fts_close(fts); michael@0: } michael@0: return success; michael@0: #endif michael@0: } michael@0: michael@0: bool Move(const FilePath& from_path, const FilePath& to_path) { michael@0: if (rename(from_path.value().c_str(), to_path.value().c_str()) == 0) michael@0: return true; michael@0: michael@0: if (!CopyDirectory(from_path, to_path, true)) michael@0: return false; michael@0: michael@0: Delete(from_path, true); michael@0: return true; michael@0: } michael@0: michael@0: bool CopyDirectory(const FilePath& from_path, michael@0: const FilePath& to_path, michael@0: bool recursive) { michael@0: // Some old callers of CopyDirectory want it to support wildcards. michael@0: // After some discussion, we decided to fix those callers. michael@0: // Break loudly here if anyone tries to do this. michael@0: // TODO(evanm): remove this once we're sure it's ok. michael@0: DCHECK(to_path.value().find('*') == std::string::npos); michael@0: DCHECK(from_path.value().find('*') == std::string::npos); michael@0: michael@0: char top_dir[PATH_MAX]; michael@0: if (base::strlcpy(top_dir, from_path.value().c_str(), michael@0: arraysize(top_dir)) >= arraysize(top_dir)) { michael@0: return false; michael@0: } michael@0: michael@0: #ifdef ANDROID michael@0: // XXX Need ftsless impl for bionic michael@0: return false; michael@0: #else michael@0: char* dir_list[] = { top_dir, NULL }; michael@0: FTS* fts = fts_open(dir_list, FTS_PHYSICAL | FTS_NOSTAT, NULL); michael@0: if (!fts) { michael@0: CHROMIUM_LOG(ERROR) << "fts_open failed: " << strerror(errno); michael@0: return false; michael@0: } michael@0: michael@0: int error = 0; michael@0: FTSENT* ent; michael@0: while (!error && (ent = fts_read(fts)) != NULL) { michael@0: // ent->fts_path is the source path, including from_path, so paste michael@0: // the suffix after from_path onto to_path to create the target_path. michael@0: std::string suffix(&ent->fts_path[from_path.value().size()]); michael@0: // Strip the leading '/' (if any). michael@0: if (!suffix.empty()) { michael@0: DCHECK_EQ('/', suffix[0]); michael@0: suffix.erase(0, 1); michael@0: } michael@0: const FilePath target_path = to_path.Append(suffix); michael@0: switch (ent->fts_info) { michael@0: case FTS_D: // Preorder directory. michael@0: // If we encounter a subdirectory in a non-recursive copy, prune it michael@0: // from the traversal. michael@0: if (!recursive && ent->fts_level > 0) { michael@0: if (fts_set(fts, ent, FTS_SKIP) != 0) michael@0: error = errno; michael@0: continue; michael@0: } michael@0: michael@0: // Try creating the target dir, continuing on it if it exists already. michael@0: // Rely on the user's umask to produce correct permissions. michael@0: if (mkdir(target_path.value().c_str(), 0777) != 0) { michael@0: if (errno != EEXIST) michael@0: error = errno; michael@0: } michael@0: break; michael@0: case FTS_F: // Regular file. michael@0: case FTS_NSOK: // File, no stat info requested. michael@0: errno = 0; michael@0: if (!CopyFile(FilePath(ent->fts_path), target_path)) michael@0: error = errno ? errno : EINVAL; michael@0: break; michael@0: case FTS_DP: // Postorder directory. michael@0: case FTS_DOT: // "." or ".." michael@0: // Skip it. michael@0: continue; michael@0: case FTS_DC: // Directory causing a cycle. michael@0: // Skip this branch. michael@0: if (fts_set(fts, ent, FTS_SKIP) != 0) michael@0: error = errno; michael@0: break; michael@0: case FTS_DNR: // Directory cannot be read. michael@0: case FTS_ERR: // Error. michael@0: case FTS_NS: // Stat failed. michael@0: // Abort with the error. michael@0: error = ent->fts_errno; michael@0: break; michael@0: case FTS_SL: // Symlink. michael@0: case FTS_SLNONE: // Symlink with broken target. michael@0: CHROMIUM_LOG(WARNING) << "CopyDirectory() skipping symbolic link: " << michael@0: ent->fts_path; michael@0: continue; michael@0: case FTS_DEFAULT: // Some other sort of file. michael@0: CHROMIUM_LOG(WARNING) << "CopyDirectory() skipping file of unknown type: " << michael@0: ent->fts_path; michael@0: continue; michael@0: default: michael@0: NOTREACHED(); michael@0: continue; // Hope for the best! michael@0: } michael@0: } michael@0: // fts_read may have returned NULL and set errno to indicate an error. michael@0: if (!error && errno != 0) michael@0: error = errno; michael@0: michael@0: if (!fts_close(fts)) { michael@0: // If we already have an error, let's use that error instead of the error michael@0: // fts_close set. michael@0: if (!error) michael@0: error = errno; michael@0: } michael@0: michael@0: if (error) { michael@0: CHROMIUM_LOG(ERROR) << "CopyDirectory(): " << strerror(error); michael@0: return false; michael@0: } michael@0: return true; michael@0: #endif michael@0: } michael@0: michael@0: bool PathExists(const FilePath& path) { michael@0: struct stat file_info; michael@0: return (stat(path.value().c_str(), &file_info) == 0); michael@0: } michael@0: michael@0: bool PathIsWritable(const FilePath& path) { michael@0: FilePath test_path(path); michael@0: struct stat file_info; michael@0: if (stat(test_path.value().c_str(), &file_info) != 0) { michael@0: // If the path doesn't exist, test the parent dir. michael@0: test_path = test_path.DirName(); michael@0: // If the parent dir doesn't exist, then return false (the path is not michael@0: // directly writable). michael@0: if (stat(test_path.value().c_str(), &file_info) != 0) michael@0: return false; michael@0: } michael@0: if (S_IWOTH & file_info.st_mode) michael@0: return true; michael@0: if (getegid() == file_info.st_gid && (S_IWGRP & file_info.st_mode)) michael@0: return true; michael@0: if (geteuid() == file_info.st_uid && (S_IWUSR & file_info.st_mode)) michael@0: return true; michael@0: return false; michael@0: } michael@0: michael@0: bool DirectoryExists(const FilePath& path) { michael@0: struct stat file_info; michael@0: if (stat(path.value().c_str(), &file_info) == 0) michael@0: return S_ISDIR(file_info.st_mode); michael@0: return false; michael@0: } michael@0: michael@0: bool ReadFromFD(int fd, char* buffer, size_t bytes) { michael@0: size_t total_read = 0; michael@0: while (total_read < bytes) { michael@0: ssize_t bytes_read = michael@0: HANDLE_EINTR(read(fd, buffer + total_read, bytes - total_read)); michael@0: if (bytes_read <= 0) michael@0: break; michael@0: total_read += bytes_read; michael@0: } michael@0: return total_read == bytes; michael@0: } michael@0: michael@0: // Creates and opens a temporary file in |directory|, returning the michael@0: // file descriptor. |path| is set to the temporary file path. michael@0: // Note TODO(erikkay) comment in header for BlahFileName() calls; the michael@0: // intent is to rename these files BlahFile() (since they create michael@0: // files, not filenames). This function does NOT unlink() the file. michael@0: int CreateAndOpenFdForTemporaryFile(FilePath directory, FilePath* path) { michael@0: *path = directory.Append(kTempFileName); michael@0: const std::string& tmpdir_string = path->value(); michael@0: // this should be OK since mkstemp just replaces characters in place michael@0: char* buffer = const_cast(tmpdir_string.c_str()); michael@0: michael@0: return mkstemp(buffer); michael@0: } michael@0: michael@0: bool CreateTemporaryFileName(FilePath* path) { michael@0: FilePath directory; michael@0: if (!GetTempDir(&directory)) michael@0: return false; michael@0: int fd = CreateAndOpenFdForTemporaryFile(directory, path); michael@0: if (fd < 0) michael@0: return false; michael@0: close(fd); michael@0: return true; michael@0: } michael@0: michael@0: FILE* CreateAndOpenTemporaryShmemFile(FilePath* path) { michael@0: FilePath directory; michael@0: if (!GetShmemTempDir(&directory)) michael@0: return NULL; michael@0: michael@0: return CreateAndOpenTemporaryFileInDir(directory, path); michael@0: } michael@0: michael@0: FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* path) { michael@0: int fd = CreateAndOpenFdForTemporaryFile(dir, path); michael@0: if (fd < 0) michael@0: return NULL; michael@0: michael@0: return fdopen(fd, "a+"); michael@0: } michael@0: michael@0: bool CreateTemporaryFileNameInDir(const std::wstring& dir, michael@0: std::wstring* temp_file) { michael@0: // Not implemented yet. michael@0: NOTREACHED(); michael@0: return false; michael@0: } michael@0: michael@0: bool CreateNewTempDirectory(const FilePath::StringType& prefix, michael@0: FilePath* new_temp_path) { michael@0: FilePath tmpdir; michael@0: if (!GetTempDir(&tmpdir)) michael@0: return false; michael@0: tmpdir = tmpdir.Append(kTempFileName); michael@0: std::string tmpdir_string = tmpdir.value(); michael@0: #ifdef ANDROID michael@0: char* dtemp = NULL; michael@0: #else michael@0: // this should be OK since mkdtemp just replaces characters in place michael@0: char* buffer = const_cast(tmpdir_string.c_str()); michael@0: char* dtemp = mkdtemp(buffer); michael@0: #endif michael@0: if (!dtemp) michael@0: return false; michael@0: *new_temp_path = FilePath(dtemp); michael@0: return true; michael@0: } michael@0: michael@0: bool CreateDirectory(const FilePath& full_path) { michael@0: std::vector subpaths; michael@0: michael@0: // Collect a list of all parent directories. michael@0: FilePath last_path = full_path; michael@0: subpaths.push_back(full_path); michael@0: for (FilePath path = full_path.DirName(); michael@0: path.value() != last_path.value(); path = path.DirName()) { michael@0: subpaths.push_back(path); michael@0: last_path = path; michael@0: } michael@0: michael@0: // Iterate through the parents and create the missing ones. michael@0: for (std::vector::reverse_iterator i = subpaths.rbegin(); michael@0: i != subpaths.rend(); ++i) { michael@0: if (!DirectoryExists(*i)) { michael@0: if (mkdir(i->value().c_str(), 0777) != 0) michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool GetFileInfo(const FilePath& file_path, FileInfo* results) { michael@0: struct stat file_info; michael@0: if (stat(file_path.value().c_str(), &file_info) != 0) michael@0: return false; michael@0: results->is_directory = S_ISDIR(file_info.st_mode); michael@0: results->size = file_info.st_size; michael@0: return true; michael@0: } michael@0: michael@0: FILE* OpenFile(const std::string& filename, const char* mode) { michael@0: return OpenFile(FilePath(filename), mode); michael@0: } michael@0: michael@0: FILE* OpenFile(const FilePath& filename, const char* mode) { michael@0: return fopen(filename.value().c_str(), mode); michael@0: } michael@0: michael@0: int ReadFile(const FilePath& filename, char* data, int size) { michael@0: int fd = open(filename.value().c_str(), O_RDONLY); michael@0: if (fd < 0) michael@0: return -1; michael@0: michael@0: int ret_value = HANDLE_EINTR(read(fd, data, size)); michael@0: HANDLE_EINTR(close(fd)); michael@0: return ret_value; michael@0: } michael@0: michael@0: int WriteFile(const FilePath& filename, const char* data, int size) { michael@0: int fd = creat(filename.value().c_str(), 0666); michael@0: if (fd < 0) michael@0: return -1; michael@0: michael@0: // Allow for partial writes michael@0: ssize_t bytes_written_total = 0; michael@0: do { michael@0: ssize_t bytes_written_partial = michael@0: HANDLE_EINTR(write(fd, data + bytes_written_total, michael@0: size - bytes_written_total)); michael@0: if (bytes_written_partial < 0) { michael@0: HANDLE_EINTR(close(fd)); michael@0: return -1; michael@0: } michael@0: bytes_written_total += bytes_written_partial; michael@0: } while (bytes_written_total < size); michael@0: michael@0: HANDLE_EINTR(close(fd)); michael@0: return bytes_written_total; michael@0: } michael@0: michael@0: // Gets the current working directory for the process. michael@0: bool GetCurrentDirectory(FilePath* dir) { michael@0: char system_buffer[PATH_MAX] = ""; michael@0: if (!getcwd(system_buffer, sizeof(system_buffer))) { michael@0: NOTREACHED(); michael@0: return false; michael@0: } michael@0: *dir = FilePath(system_buffer); michael@0: return true; michael@0: } michael@0: michael@0: // Sets the current working directory for the process. michael@0: bool SetCurrentDirectory(const FilePath& path) { michael@0: int ret = chdir(path.value().c_str()); michael@0: return !ret; michael@0: } michael@0: michael@0: #if !defined(OS_MACOSX) michael@0: bool GetTempDir(FilePath* path) { michael@0: const char* tmp = getenv("TMPDIR"); michael@0: if (tmp) michael@0: *path = FilePath(tmp); michael@0: else michael@0: *path = FilePath("/tmp"); michael@0: return true; michael@0: } michael@0: michael@0: bool GetShmemTempDir(FilePath* path) { michael@0: #if defined(OS_LINUX) && !defined(ANDROID) michael@0: *path = FilePath("/dev/shm"); michael@0: return true; michael@0: #else michael@0: return GetTempDir(path); michael@0: #endif michael@0: } michael@0: michael@0: bool CopyFile(const FilePath& from_path, const FilePath& to_path) { michael@0: int infile = open(from_path.value().c_str(), O_RDONLY); michael@0: if (infile < 0) michael@0: return false; michael@0: michael@0: int outfile = creat(to_path.value().c_str(), 0666); michael@0: if (outfile < 0) { michael@0: close(infile); michael@0: return false; michael@0: } michael@0: michael@0: const size_t kBufferSize = 32768; michael@0: std::vector buffer(kBufferSize); michael@0: bool result = true; michael@0: michael@0: while (result) { michael@0: ssize_t bytes_read = HANDLE_EINTR(read(infile, &buffer[0], buffer.size())); michael@0: if (bytes_read < 0) { michael@0: result = false; michael@0: break; michael@0: } michael@0: if (bytes_read == 0) michael@0: break; michael@0: // Allow for partial writes michael@0: ssize_t bytes_written_per_read = 0; michael@0: do { michael@0: ssize_t bytes_written_partial = HANDLE_EINTR(write( michael@0: outfile, michael@0: &buffer[bytes_written_per_read], michael@0: bytes_read - bytes_written_per_read)); michael@0: if (bytes_written_partial < 0) { michael@0: result = false; michael@0: break; michael@0: } michael@0: bytes_written_per_read += bytes_written_partial; michael@0: } while (bytes_written_per_read < bytes_read); michael@0: } michael@0: michael@0: if (HANDLE_EINTR(close(infile)) < 0) michael@0: result = false; michael@0: if (HANDLE_EINTR(close(outfile)) < 0) michael@0: result = false; michael@0: michael@0: return result; michael@0: } michael@0: #endif // !defined(OS_MACOSX) michael@0: michael@0: } // namespace file_util