Index: base/file_util_posix.cc |
=================================================================== |
--- base/file_util_posix.cc (revision 22552) |
+++ base/file_util_posix.cc (working copy) |
@@ -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 @@ |
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 @@ |
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 @@ |
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 @@ |
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; |
+ current = traversal.Next(); |
+ traversal.GetFindInfo(&info); |
} |
- if (error) { |
- LOG(ERROR) << "CopyDirectory(): " << strerror(error); |
- return false; |
- } |
- return true; |
+ return success; |
} |
bool PathExists(const FilePath& path) { |
@@ -602,10 +543,11 @@ |
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,103 +557,133 @@ |
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); |
- // Level 0 is the top, which is always skipped. |
- if (fts_ent_->fts_level == 0) |
- return Next(); |
+ 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; |
- // 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(); |
+ return root_path_.Append(directory_entries_[current_directory_entry_ |
+ ].filename); |
+} |
- 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; |
+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; |
+} |
+ |
/////////////////////////////////////////////// |
// MemoryMappedFile |