diff options
author | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-06 21:23:07 +0000 |
---|---|---|
committer | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-06 21:23:07 +0000 |
commit | 49930c3ae4d0bb40b94cfde0425e8c825a526144 (patch) | |
tree | 010ee4721b79bdfd7a22ef0ff7efe56630582b39 /base/file_util_posix.cc | |
parent | 6ae23b8bfb3ee39e3a158b4b83a2230dcfe06c45 (diff) | |
download | chromium_src-49930c3ae4d0bb40b94cfde0425e8c825a526144.zip chromium_src-49930c3ae4d0bb40b94cfde0425e8c825a526144.tar.gz chromium_src-49930c3ae4d0bb40b94cfde0425e8c825a526144.tar.bz2 |
Define _FILE_OFFSET_BITS=64 in order to support large files (>2GB).
_FILE_OFFSET_BITS=64 is incompatible with fts (issue 17492), so
file_util::Delete, file_util::CopyDirectory file_util::FileEnumerator are
reimplemented without fts. Delete and CopyDirectory are now implemented using
FileEnumerator.
Patch from vandebo@google.com, original review:
http://codereview.chromium.org/160479
BUG=13718,17492
TEST=none
Review URL: http://codereview.chromium.org/165085
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@22670 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/file_util_posix.cc')
-rw-r--r-- | base/file_util_posix.cc | 382 |
1 files changed, 177 insertions, 205 deletions
diff --git a/base/file_util_posix.cc b/base/file_util_posix.cc index 6642eaa..fb5a7c4 100644 --- a/base/file_util_posix.cc +++ b/base/file_util_posix.cc @@ -8,7 +8,6 @@ #include <errno.h> #include <fcntl.h> #include <fnmatch.h> -#include <fts.h> #include <libgen.h> #include <stdio.h> #include <string.h> @@ -35,19 +34,6 @@ namespace { -bool IsDirectory(const FTSENT* file) { - switch (file->fts_info) { - case FTS_D: - case FTS_DC: - case FTS_DNR: - case FTS_DOT: - case FTS_DP: - return true; - default: - return false; - } -} - class LocaleAwareComparator { public: LocaleAwareComparator() { @@ -92,24 +78,6 @@ class LocaleAwareComparator { DISALLOW_COPY_AND_ASSIGN(LocaleAwareComparator); }; -int CompareFiles(const FTSENT** a, const FTSENT** b) { - // Order lexicographically with directories before other files. - const bool a_is_dir = IsDirectory(*a); - const bool b_is_dir = IsDirectory(*b); - if (a_is_dir != b_is_dir) - return a_is_dir ? -1 : 1; - - // On linux, the file system encoding is not defined. We assume - // SysNativeMBToWide takes care of it. - // - // ICU's collator can take strings in OS native encoding. But we convert the - // strings to UTF-16 ourselves to ensure conversion consistency. - // TODO(yuzo): Perhaps we should define SysNativeMBToUTF16? - return Singleton<LocaleAwareComparator>()->Compare( - WideToUTF16(base::SysNativeMBToWide((*a)->fts_name)), - WideToUTF16(base::SysNativeMBToWide((*b)->fts_name))); -} - } // namespace namespace file_util { @@ -146,6 +114,10 @@ int CountFilesCreatedAfter(const FilePath& path, DIR* dir = opendir(path.value().c_str()); if (dir) { +#if !defined(OS_LINUX) && !defined(OS_MACOSX) + #error Depending on the definition of struct dirent, additional space for \ + pathname may be needed +#endif struct dirent ent_buf; struct dirent* ent; while (readdir_r(dir, &ent_buf, &ent) == 0 && ent) { @@ -201,43 +173,28 @@ bool Delete(const FilePath& path, bool recursive) { return (rmdir(path_str) == 0); bool success = true; - int ftsflags = FTS_PHYSICAL | FTS_NOSTAT; - char top_dir[PATH_MAX]; - if (base::strlcpy(top_dir, path_str, - arraysize(top_dir)) >= arraysize(top_dir)) { - return false; + std::stack<std::string> directories; + directories.push(path.value()); + FileEnumerator traversal(path, true, static_cast<FileEnumerator::FILE_TYPE>( + FileEnumerator::FILES | FileEnumerator::DIRECTORIES | + FileEnumerator::SHOW_SYM_LINKS)); + for (FilePath current = traversal.Next(); success && !current.empty(); + current = traversal.Next()) { + FileEnumerator::FindInfo info; + traversal.GetFindInfo(&info); + + if (S_ISDIR(info.stat.st_mode)) + directories.push(current.value()); + else + success = (unlink(current.value().c_str()) == 0); } - char* dir_list[2] = { top_dir, NULL }; - FTS* fts = fts_open(dir_list, ftsflags, NULL); - if (fts) { - FTSENT* fts_ent = fts_read(fts); - while (success && fts_ent != NULL) { - switch (fts_ent->fts_info) { - case FTS_DNR: - case FTS_ERR: - // log error - success = false; - continue; - break; - case FTS_DP: - success = (rmdir(fts_ent->fts_accpath) == 0); - break; - case FTS_D: - break; - case FTS_NSOK: - case FTS_F: - case FTS_SL: - case FTS_SLNONE: - success = (unlink(fts_ent->fts_accpath) == 0); - break; - default: - DCHECK(false); - break; - } - fts_ent = fts_read(fts); - } - fts_close(fts); + + while (success && !directories.empty()) { + FilePath dir = FilePath(directories.top()); + directories.pop(); + success = (rmdir(dir.value().c_str()) == 0); } + return success; } @@ -272,92 +229,76 @@ bool CopyDirectory(const FilePath& from_path, return false; } - char* dir_list[] = { top_dir, NULL }; - FTS* fts = fts_open(dir_list, FTS_PHYSICAL | FTS_NOSTAT, NULL); - if (!fts) { - LOG(ERROR) << "fts_open failed: " << strerror(errno); + // This function does not properly handle destinations within the source + FilePath real_to_path = to_path; + if (PathExists(real_to_path)) { + if (!AbsolutePath(&real_to_path)) + return false; + } else { + real_to_path = real_to_path.DirName(); + if (!AbsolutePath(&real_to_path)) + return false; + } + FilePath real_from_path = from_path; + if (!AbsolutePath(&real_from_path)) + return false; + if (real_to_path.value().size() >= real_from_path.value().size() && + real_to_path.value().compare(0, real_from_path.value().size(), + real_from_path.value()) == 0) return false; + + bool success = true; + FileEnumerator::FILE_TYPE traverse_type = + static_cast<FileEnumerator::FILE_TYPE>(FileEnumerator::FILES | + FileEnumerator::SHOW_SYM_LINKS); + if (recursive) + traverse_type = static_cast<FileEnumerator::FILE_TYPE>( + traverse_type | FileEnumerator::DIRECTORIES); + FileEnumerator traversal(from_path, recursive, traverse_type); + + // to_path may not exist yet, start the loop with to_path + FileEnumerator::FindInfo info; + FilePath current = from_path; + if (stat(from_path.value().c_str(), &info.stat) < 0) { + LOG(ERROR) << "CopyDirectory() couldn't stat source directory: " << + from_path.value() << " errno = " << errno; + success = false; } - int error = 0; - FTSENT* ent; - while (!error && (ent = fts_read(fts)) != NULL) { - // ent->fts_path is the source path, including from_path, so paste + while (success && !current.empty()) { + // current is the source path, including from_path, so paste // the suffix after from_path onto to_path to create the target_path. - std::string suffix(&ent->fts_path[from_path.value().size()]); + std::string suffix(¤t.value().c_str()[from_path.value().size()]); // Strip the leading '/' (if any). if (!suffix.empty()) { DCHECK_EQ('/', suffix[0]); suffix.erase(0, 1); } const FilePath target_path = to_path.Append(suffix); - switch (ent->fts_info) { - case FTS_D: // Preorder directory. - // If we encounter a subdirectory in a non-recursive copy, prune it - // from the traversal. - if (!recursive && ent->fts_level > 0) { - if (fts_set(fts, ent, FTS_SKIP) != 0) - error = errno; - continue; - } - - // Try creating the target dir, continuing on it if it exists already. - if (mkdir(target_path.value().c_str(), 0700) != 0) { - if (errno != EEXIST) - error = errno; - } - break; - case FTS_F: // Regular file. - case FTS_NSOK: // File, no stat info requested. - errno = 0; - if (!CopyFile(FilePath(ent->fts_path), target_path)) - error = errno ? errno : EINVAL; - break; - case FTS_DP: // Postorder directory. - case FTS_DOT: // "." or ".." - // Skip it. - continue; - case FTS_DC: // Directory causing a cycle. - // Skip this branch. - if (fts_set(fts, ent, FTS_SKIP) != 0) - error = errno; - break; - case FTS_DNR: // Directory cannot be read. - case FTS_ERR: // Error. - case FTS_NS: // Stat failed. - // Abort with the error. - error = ent->fts_errno; - break; - case FTS_SL: // Symlink. - case FTS_SLNONE: // Symlink with broken target. - LOG(WARNING) << "CopyDirectory() skipping symbolic link: " << - ent->fts_path; - continue; - case FTS_DEFAULT: // Some other sort of file. - LOG(WARNING) << "CopyDirectory() skipping file of unknown type: " << - ent->fts_path; - continue; - default: - NOTREACHED(); - continue; // Hope for the best! + + if (S_ISDIR(info.stat.st_mode)) { + if (mkdir(target_path.value().c_str(), info.stat.st_mode & 01777) != 0 && + errno != EEXIST) { + LOG(ERROR) << "CopyDirectory() couldn't create directory: " << + target_path.value() << " errno = " << errno; + success = false; + } + } else if (S_ISREG(info.stat.st_mode)) { + if (!CopyFile(current, target_path)) { + LOG(ERROR) << "CopyDirectory() couldn't create file: " << + target_path.value(); + success = false; + } + } else { + LOG(WARNING) << "CopyDirectory() skipping non-regular file: " << + current.value(); } - } - // fts_read may have returned NULL and set errno to indicate an error. - if (!error && errno != 0) - error = errno; - - if (!fts_close(fts)) { - // If we already have an error, let's use that error instead of the error - // fts_close set. - if (!error) - error = errno; - } - if (error) { - LOG(ERROR) << "CopyDirectory(): " << strerror(error); - return false; + current = traversal.Next(); + traversal.GetFindInfo(&info); } - return true; + + return success; } bool PathExists(const FilePath& path) { @@ -602,10 +543,11 @@ bool SetCurrentDirectory(const FilePath& path) { FileEnumerator::FileEnumerator(const FilePath& root_path, bool recursive, FileEnumerator::FILE_TYPE file_type) - : recursive_(recursive), + : root_path_(root_path), + recursive_(recursive), file_type_(file_type), is_in_find_op_(false), - fts_(NULL) { + current_directory_entry_(0) { // INCLUDE_DOT_DOT must not be specified if recursive. DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); pending_paths_.push(root_path); @@ -615,101 +557,131 @@ FileEnumerator::FileEnumerator(const FilePath& root_path, bool recursive, FileEnumerator::FILE_TYPE file_type, const FilePath::StringType& pattern) - : recursive_(recursive), + : root_path_(root_path), + recursive_(recursive), file_type_(file_type), - pattern_(root_path.value()), + pattern_(root_path.Append(pattern)), is_in_find_op_(false), - fts_(NULL) { + current_directory_entry_(0) { // INCLUDE_DOT_DOT must not be specified if recursive. DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); - // The Windows version of this code only matches against items in the top-most - // directory, and we're comparing fnmatch against full paths, so this is the - // easiest way to get the right pattern. - pattern_ = pattern_.Append(pattern); + // 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.size() == 0) + pattern_ = FilePath(); pending_paths_.push(root_path); } FileEnumerator::~FileEnumerator() { - if (fts_) - fts_close(fts_); } void FileEnumerator::GetFindInfo(FindInfo* info) { DCHECK(info); - if (!is_in_find_op_) + if (current_directory_entry_ >= directory_entries_.size()) return; - memcpy(&(info->stat), fts_ent_->fts_statp, sizeof(info->stat)); - info->filename.assign(fts_ent_->fts_name); + DirectoryEntryInfo* cur_entry = &directory_entries_[current_directory_entry_]; + memcpy(&(info->stat), &(cur_entry->stat), sizeof(info->stat)); + info->filename.assign(cur_entry->filename.value()); } -// As it stands, this method calls itself recursively when the next item of -// the fts enumeration doesn't match (type, pattern, etc.). In the case of -// large directories with many files this can be quite deep. -// TODO(erikkay) - get rid of this recursive pattern FilePath FileEnumerator::Next() { - if (!is_in_find_op_) { + ++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(); - // The last find FindFirstFile operation is done, prepare a new one. root_path_ = pending_paths_.top(); root_path_ = root_path_.StripTrailingSeparators(); pending_paths_.pop(); - // Start a new find operation. - int ftsflags = FTS_LOGICAL | FTS_SEEDOT; - char top_dir[PATH_MAX]; - base::strlcpy(top_dir, root_path_.value().c_str(), arraysize(top_dir)); - char* dir_list[2] = { top_dir, NULL }; - fts_ = fts_open(dir_list, ftsflags, CompareFiles); - if (!fts_) - return Next(); - is_in_find_op_ = true; - } + std::vector<DirectoryEntryInfo> entries; + if (!ReadDirectory(&entries, root_path_, file_type_ & SHOW_SYM_LINKS)) + continue; - fts_ent_ = fts_read(fts_); - if (fts_ent_ == NULL) { - fts_close(fts_); - fts_ = NULL; - is_in_find_op_ = false; - return Next(); - } + // The API says that order is not guaranteed, but order affects UX + std::sort(entries.begin(), entries.end(), CompareFiles); + + 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; - // Level 0 is the top, which is always skipped. - if (fts_ent_->fts_level == 0) - return Next(); - - // Patterns are only matched on the items in the top-most directory. - // (see Windows implementation) - if (fts_ent_->fts_level == 1 && pattern_.value().length() > 0) { - if (fnmatch(pattern_.value().c_str(), fts_ent_->fts_path, 0) != 0) { - if (fts_ent_->fts_info == FTS_D) - fts_set(fts_, fts_ent_, FTS_SKIP); - return Next(); + if (pattern_.value().size() && + fnmatch(pattern_.value().c_str(), full_path.value().c_str(), + FNM_NOESCAPE)) + continue; + + if (recursive_ && S_ISDIR(i->stat.st_mode)) + pending_paths_.push(full_path); + + if ((S_ISDIR(i->stat.st_mode) && (file_type_ & DIRECTORIES)) || + (!S_ISDIR(i->stat.st_mode) && (file_type_ & FILES))) + directory_entries_.push_back(*i); } } - FilePath cur_file(fts_ent_->fts_path); - if (ShouldSkip(cur_file)) - return Next(); - - if (fts_ent_->fts_info == FTS_D) { - // If not recursive, then prune children. - if (!recursive_) - fts_set(fts_, fts_ent_, FTS_SKIP); - return (file_type_ & FileEnumerator::DIRECTORIES) ? cur_file : Next(); - } else if (fts_ent_->fts_info == FTS_F) { - return (file_type_ & FileEnumerator::FILES) ? cur_file : Next(); - } else if (fts_ent_->fts_info == FTS_DOT) { - if ((file_type_ & FileEnumerator::DIRECTORIES) && IsDotDot(cur_file)) { - return cur_file; + return root_path_.Append(directory_entries_[current_directory_entry_ + ].filename); +} + +bool FileEnumerator::ReadDirectory(std::vector<DirectoryEntryInfo>* entries, + const FilePath& source, bool show_links) { + DIR* dir = opendir(source.value().c_str()); + if (!dir) + return false; + +#if !defined(OS_LINUX) && !defined(OS_MACOSX) + #error Depending on the definition of struct dirent, additional space for \ + pathname may be needed +#endif + struct dirent dent_buf; + struct dirent* dent; + while (readdir_r(dir, &dent_buf, &dent) == 0 && dent) { + DirectoryEntryInfo info; + FilePath full_name; + int stat_value; + + info.filename = FilePath(dent->d_name); + full_name = source.Append(dent->d_name); + if (show_links) + stat_value = lstat(full_name.value().c_str(), &info.stat); + else + stat_value = stat(full_name.value().c_str(), &info.stat); + if (stat_value < 0) { + LOG(ERROR) << "Couldn't stat file: " << + source.Append(dent->d_name).value().c_str() << " errno = " << errno; + memset(&info.stat, 0, sizeof(info.stat)); } - return Next(); + entries->push_back(info); } - // TODO(erikkay) - verify that the other fts_info types aren't interesting - return Next(); + + closedir(dir); + return true; +} + +bool FileEnumerator::CompareFiles(const DirectoryEntryInfo& a, + const DirectoryEntryInfo& b) { + // Order lexicographically with directories before other files. + if (S_ISDIR(a.stat.st_mode) != S_ISDIR(b.stat.st_mode)) + return S_ISDIR(a.stat.st_mode); + + // On linux, the file system encoding is not defined. We assume + // SysNativeMBToWide takes care of it. + // + // ICU's collator can take strings in OS native encoding. But we convert the + // strings to UTF-16 ourselves to ensure conversion consistency. + // TODO(yuzo): Perhaps we should define SysNativeMBToUTF16? + return Singleton<LocaleAwareComparator>()->Compare( + WideToUTF16(base::SysNativeMBToWide(a.filename.value().c_str())), + WideToUTF16(base::SysNativeMBToWide(b.filename.value().c_str()))) < 0; } /////////////////////////////////////////////// |