OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/file_util.h" | 5 #include "base/file_util.h" |
6 | 6 |
7 #include <dirent.h> | 7 #include <dirent.h> |
8 #include <errno.h> | 8 #include <errno.h> |
9 #include <fcntl.h> | 9 #include <fcntl.h> |
10 #include <fnmatch.h> | 10 #include <fnmatch.h> |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
88 *real_path = FilePath(buf); | 88 *real_path = FilePath(buf); |
89 return true; | 89 return true; |
90 } | 90 } |
91 | 91 |
92 // Helper for VerifyPathControlledByUser. | 92 // Helper for VerifyPathControlledByUser. |
93 bool VerifySpecificPathControlledByUser(const FilePath& path, | 93 bool VerifySpecificPathControlledByUser(const FilePath& path, |
94 uid_t owner_uid, | 94 uid_t owner_uid, |
95 const std::set<gid_t>& group_gids) { | 95 const std::set<gid_t>& group_gids) { |
96 stat_wrapper_t stat_info; | 96 stat_wrapper_t stat_info; |
97 if (CallLstat(path.value().c_str(), &stat_info) != 0) { | 97 if (CallLstat(path.value().c_str(), &stat_info) != 0) { |
98 PLOG(ERROR) << "Failed to get information on path " | 98 DPLOG(ERROR) << "Failed to get information on path " |
99 << path.value(); | 99 << path.value(); |
100 return false; | 100 return false; |
101 } | 101 } |
102 | 102 |
103 if (S_ISLNK(stat_info.st_mode)) { | 103 if (S_ISLNK(stat_info.st_mode)) { |
104 LOG(ERROR) << "Path " << path.value() | 104 DLOG(ERROR) << "Path " << path.value() |
105 << " is a symbolic link."; | 105 << " is a symbolic link."; |
106 return false; | 106 return false; |
107 } | 107 } |
108 | 108 |
109 if (stat_info.st_uid != owner_uid) { | 109 if (stat_info.st_uid != owner_uid) { |
110 LOG(ERROR) << "Path " << path.value() | 110 DLOG(ERROR) << "Path " << path.value() |
111 << " is owned by the wrong user."; | 111 << " is owned by the wrong user."; |
112 return false; | 112 return false; |
113 } | 113 } |
114 | 114 |
115 if ((stat_info.st_mode & S_IWGRP) && | 115 if ((stat_info.st_mode & S_IWGRP) && |
116 !ContainsKey(group_gids, stat_info.st_gid)) { | 116 !ContainsKey(group_gids, stat_info.st_gid)) { |
117 LOG(ERROR) << "Path " << path.value() | 117 DLOG(ERROR) << "Path " << path.value() |
118 << " is writable by an unprivileged group."; | 118 << " is writable by an unprivileged group."; |
119 return false; | 119 return false; |
120 } | 120 } |
121 | 121 |
122 if (stat_info.st_mode & S_IWOTH) { | 122 if (stat_info.st_mode & S_IWOTH) { |
123 LOG(ERROR) << "Path " << path.value() | 123 DLOG(ERROR) << "Path " << path.value() |
124 << " is writable by any user."; | 124 << " is writable by any user."; |
125 return false; | 125 return false; |
126 } | 126 } |
127 | 127 |
128 return true; | 128 return true; |
129 } | 129 } |
130 | 130 |
131 } // namespace | 131 } // namespace |
132 | 132 |
133 static std::string TempFileName() { | 133 static std::string TempFileName() { |
134 #if defined(OS_MACOSX) | 134 #if defined(OS_MACOSX) |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
166 struct dirent ent_buf; | 166 struct dirent ent_buf; |
167 struct dirent* ent; | 167 struct dirent* ent; |
168 while (readdir_r(dir, &ent_buf, &ent) == 0 && ent) { | 168 while (readdir_r(dir, &ent_buf, &ent) == 0 && ent) { |
169 if ((strcmp(ent->d_name, ".") == 0) || | 169 if ((strcmp(ent->d_name, ".") == 0) || |
170 (strcmp(ent->d_name, "..") == 0)) | 170 (strcmp(ent->d_name, "..") == 0)) |
171 continue; | 171 continue; |
172 | 172 |
173 stat_wrapper_t st; | 173 stat_wrapper_t st; |
174 int test = CallStat(path.Append(ent->d_name).value().c_str(), &st); | 174 int test = CallStat(path.Append(ent->d_name).value().c_str(), &st); |
175 if (test != 0) { | 175 if (test != 0) { |
176 PLOG(ERROR) << "stat64 failed"; | 176 DPLOG(ERROR) << "stat64 failed"; |
177 continue; | 177 continue; |
178 } | 178 } |
179 // Here, we use Time::TimeT(), which discards microseconds. This | 179 // Here, we use Time::TimeT(), which discards microseconds. This |
180 // means that files which are newer than |comparison_time| may | 180 // means that files which are newer than |comparison_time| may |
181 // be considered older. If we don't discard microseconds, it | 181 // be considered older. If we don't discard microseconds, it |
182 // introduces another issue. Suppose the following case: | 182 // introduces another issue. Suppose the following case: |
183 // | 183 // |
184 // 1. Get |comparison_time| by Time::Now() and the value is 10.1 (secs). | 184 // 1. Get |comparison_time| by Time::Now() and the value is 10.1 (secs). |
185 // 2. Create a file and the current time is 10.3 (secs). | 185 // 2. Create a file and the current time is 10.3 (secs). |
186 // | 186 // |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
315 if (recursive) | 315 if (recursive) |
316 traverse_type = static_cast<FileEnumerator::FileType>( | 316 traverse_type = static_cast<FileEnumerator::FileType>( |
317 traverse_type | FileEnumerator::DIRECTORIES); | 317 traverse_type | FileEnumerator::DIRECTORIES); |
318 FileEnumerator traversal(from_path, recursive, traverse_type); | 318 FileEnumerator traversal(from_path, recursive, traverse_type); |
319 | 319 |
320 // We have to mimic windows behavior here. |to_path| may not exist yet, | 320 // We have to mimic windows behavior here. |to_path| may not exist yet, |
321 // start the loop with |to_path|. | 321 // start the loop with |to_path|. |
322 FileEnumerator::FindInfo info; | 322 FileEnumerator::FindInfo info; |
323 FilePath current = from_path; | 323 FilePath current = from_path; |
324 if (stat(from_path.value().c_str(), &info.stat) < 0) { | 324 if (stat(from_path.value().c_str(), &info.stat) < 0) { |
325 LOG(ERROR) << "CopyDirectory() couldn't stat source directory: " << | 325 DLOG(ERROR) << "CopyDirectory() couldn't stat source directory: " |
326 from_path.value() << " errno = " << errno; | 326 << from_path.value() << " errno = " << errno; |
327 success = false; | 327 success = false; |
328 } | 328 } |
329 struct stat to_path_stat; | 329 struct stat to_path_stat; |
330 FilePath from_path_base = from_path; | 330 FilePath from_path_base = from_path; |
331 if (recursive && stat(to_path.value().c_str(), &to_path_stat) == 0 && | 331 if (recursive && stat(to_path.value().c_str(), &to_path_stat) == 0 && |
332 S_ISDIR(to_path_stat.st_mode)) { | 332 S_ISDIR(to_path_stat.st_mode)) { |
333 // If the destination already exists and is a directory, then the | 333 // If the destination already exists and is a directory, then the |
334 // top level of source needs to be copied. | 334 // top level of source needs to be copied. |
335 from_path_base = from_path.DirName(); | 335 from_path_base = from_path.DirName(); |
336 } | 336 } |
337 | 337 |
338 // The Windows version of this function assumes that non-recursive calls | 338 // The Windows version of this function assumes that non-recursive calls |
339 // will always have a directory for from_path. | 339 // will always have a directory for from_path. |
340 DCHECK(recursive || S_ISDIR(info.stat.st_mode)); | 340 DCHECK(recursive || S_ISDIR(info.stat.st_mode)); |
341 | 341 |
342 while (success && !current.empty()) { | 342 while (success && !current.empty()) { |
343 // current is the source path, including from_path, so paste | 343 // current is the source path, including from_path, so paste |
344 // the suffix after from_path onto to_path to create the target_path. | 344 // the suffix after from_path onto to_path to create the target_path. |
345 std::string suffix(¤t.value().c_str()[from_path_base.value().size()]); | 345 std::string suffix(¤t.value().c_str()[from_path_base.value().size()]); |
346 // Strip the leading '/' (if any). | 346 // Strip the leading '/' (if any). |
347 if (!suffix.empty()) { | 347 if (!suffix.empty()) { |
348 DCHECK_EQ('/', suffix[0]); | 348 DCHECK_EQ('/', suffix[0]); |
349 suffix.erase(0, 1); | 349 suffix.erase(0, 1); |
350 } | 350 } |
351 const FilePath target_path = to_path.Append(suffix); | 351 const FilePath target_path = to_path.Append(suffix); |
352 | 352 |
353 if (S_ISDIR(info.stat.st_mode)) { | 353 if (S_ISDIR(info.stat.st_mode)) { |
354 if (mkdir(target_path.value().c_str(), info.stat.st_mode & 01777) != 0 && | 354 if (mkdir(target_path.value().c_str(), info.stat.st_mode & 01777) != 0 && |
355 errno != EEXIST) { | 355 errno != EEXIST) { |
356 LOG(ERROR) << "CopyDirectory() couldn't create directory: " << | 356 DLOG(ERROR) << "CopyDirectory() couldn't create directory: " |
357 target_path.value() << " errno = " << errno; | 357 << target_path.value() << " errno = " << errno; |
358 success = false; | 358 success = false; |
359 } | 359 } |
360 } else if (S_ISREG(info.stat.st_mode)) { | 360 } else if (S_ISREG(info.stat.st_mode)) { |
361 if (!CopyFile(current, target_path)) { | 361 if (!CopyFile(current, target_path)) { |
362 LOG(ERROR) << "CopyDirectory() couldn't create file: " << | 362 DLOG(ERROR) << "CopyDirectory() couldn't create file: " |
363 target_path.value(); | 363 << target_path.value(); |
364 success = false; | 364 success = false; |
365 } | 365 } |
366 } else { | 366 } else { |
367 LOG(WARNING) << "CopyDirectory() skipping non-regular file: " << | 367 DLOG(WARNING) << "CopyDirectory() skipping non-regular file: " |
368 current.value(); | 368 << current.value(); |
369 } | 369 } |
370 | 370 |
371 current = traversal.Next(); | 371 current = traversal.Next(); |
372 traversal.GetFindInfo(&info); | 372 traversal.GetFindInfo(&info); |
373 } | 373 } |
374 | 374 |
375 return success; | 375 return success; |
376 } | 376 } |
377 | 377 |
378 bool PathExists(const FilePath& path) { | 378 bool PathExists(const FilePath& path) { |
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
504 bool CreateTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) { | 504 bool CreateTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) { |
505 base::ThreadRestrictions::AssertIOAllowed(); // For call to close(). | 505 base::ThreadRestrictions::AssertIOAllowed(); // For call to close(). |
506 int fd = CreateAndOpenFdForTemporaryFile(dir, temp_file); | 506 int fd = CreateAndOpenFdForTemporaryFile(dir, temp_file); |
507 return ((fd >= 0) && !HANDLE_EINTR(close(fd))); | 507 return ((fd >= 0) && !HANDLE_EINTR(close(fd))); |
508 } | 508 } |
509 | 509 |
510 static bool CreateTemporaryDirInDirImpl(const FilePath& base_dir, | 510 static bool CreateTemporaryDirInDirImpl(const FilePath& base_dir, |
511 const FilePath::StringType& name_tmpl, | 511 const FilePath::StringType& name_tmpl, |
512 FilePath* new_dir) { | 512 FilePath* new_dir) { |
513 base::ThreadRestrictions::AssertIOAllowed(); // For call to mkdtemp(). | 513 base::ThreadRestrictions::AssertIOAllowed(); // For call to mkdtemp(). |
514 CHECK(name_tmpl.find("XXXXXX") != FilePath::StringType::npos) | 514 DCHECK(name_tmpl.find("XXXXXX") != FilePath::StringType::npos) |
515 << "Directory name template must contain \"XXXXXX\"."; | 515 << "Directory name template must contain \"XXXXXX\"."; |
516 | 516 |
517 FilePath sub_dir = base_dir.Append(name_tmpl); | 517 FilePath sub_dir = base_dir.Append(name_tmpl); |
518 std::string sub_dir_string = sub_dir.value(); | 518 std::string sub_dir_string = sub_dir.value(); |
519 | 519 |
520 // this should be OK since mkdtemp just replaces characters in place | 520 // this should be OK since mkdtemp just replaces characters in place |
521 char* buffer = const_cast<char*>(sub_dir_string.c_str()); | 521 char* buffer = const_cast<char*>(sub_dir_string.c_str()); |
522 char* dtemp = mkdtemp(buffer); | 522 char* dtemp = mkdtemp(buffer); |
523 if (!dtemp) { | 523 if (!dtemp) { |
524 DPLOG(ERROR) << "mkdtemp"; | 524 DPLOG(ERROR) << "mkdtemp"; |
525 return false; | 525 return false; |
(...skipping 290 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
816 FilePath full_name = source.Append(dent->d_name); | 816 FilePath full_name = source.Append(dent->d_name); |
817 int ret; | 817 int ret; |
818 if (show_links) | 818 if (show_links) |
819 ret = lstat(full_name.value().c_str(), &info.stat); | 819 ret = lstat(full_name.value().c_str(), &info.stat); |
820 else | 820 else |
821 ret = stat(full_name.value().c_str(), &info.stat); | 821 ret = stat(full_name.value().c_str(), &info.stat); |
822 if (ret < 0) { | 822 if (ret < 0) { |
823 // Print the stat() error message unless it was ENOENT and we're | 823 // Print the stat() error message unless it was ENOENT and we're |
824 // following symlinks. | 824 // following symlinks. |
825 if (!(errno == ENOENT && !show_links)) { | 825 if (!(errno == ENOENT && !show_links)) { |
826 PLOG(ERROR) << "Couldn't stat " | 826 DPLOG(ERROR) << "Couldn't stat " |
827 << source.Append(dent->d_name).value(); | 827 << source.Append(dent->d_name).value(); |
828 } | 828 } |
829 memset(&info.stat, 0, sizeof(info.stat)); | 829 memset(&info.stat, 0, sizeof(info.stat)); |
830 } | 830 } |
831 entries->push_back(info); | 831 entries->push_back(info); |
832 } | 832 } |
833 | 833 |
834 closedir(dir); | 834 closedir(dir); |
835 return true; | 835 return true; |
836 } | 836 } |
837 | 837 |
838 /////////////////////////////////////////////// | 838 /////////////////////////////////////////////// |
839 // MemoryMappedFile | 839 // MemoryMappedFile |
840 | 840 |
841 MemoryMappedFile::MemoryMappedFile() | 841 MemoryMappedFile::MemoryMappedFile() |
842 : file_(base::kInvalidPlatformFileValue), | 842 : file_(base::kInvalidPlatformFileValue), |
843 data_(NULL), | 843 data_(NULL), |
844 length_(0) { | 844 length_(0) { |
845 } | 845 } |
846 | 846 |
847 bool MemoryMappedFile::MapFileToMemoryInternal() { | 847 bool MemoryMappedFile::MapFileToMemoryInternal() { |
848 base::ThreadRestrictions::AssertIOAllowed(); | 848 base::ThreadRestrictions::AssertIOAllowed(); |
849 | 849 |
850 struct stat file_stat; | 850 struct stat file_stat; |
851 if (fstat(file_, &file_stat) == base::kInvalidPlatformFileValue) { | 851 if (fstat(file_, &file_stat) == base::kInvalidPlatformFileValue) { |
852 LOG(ERROR) << "Couldn't fstat " << file_ << ", errno " << errno; | 852 DLOG(ERROR) << "Couldn't fstat " << file_ << ", errno " << errno; |
853 return false; | 853 return false; |
854 } | 854 } |
855 length_ = file_stat.st_size; | 855 length_ = file_stat.st_size; |
856 | 856 |
857 data_ = static_cast<uint8*>( | 857 data_ = static_cast<uint8*>( |
858 mmap(NULL, length_, PROT_READ, MAP_SHARED, file_, 0)); | 858 mmap(NULL, length_, PROT_READ, MAP_SHARED, file_, 0)); |
859 if (data_ == MAP_FAILED) | 859 if (data_ == MAP_FAILED) |
860 LOG(ERROR) << "Couldn't mmap " << file_ << ", errno " << errno; | 860 DLOG(ERROR) << "Couldn't mmap " << file_ << ", errno " << errno; |
861 | 861 |
862 return data_ != MAP_FAILED; | 862 return data_ != MAP_FAILED; |
863 } | 863 } |
864 | 864 |
865 void MemoryMappedFile::CloseHandles() { | 865 void MemoryMappedFile::CloseHandles() { |
866 base::ThreadRestrictions::AssertIOAllowed(); | 866 base::ThreadRestrictions::AssertIOAllowed(); |
867 | 867 |
868 if (data_ != NULL) | 868 if (data_ != NULL) |
869 munmap(data_, length_); | 869 munmap(data_, length_); |
870 if (file_ != base::kInvalidPlatformFileValue) | 870 if (file_ != base::kInvalidPlatformFileValue) |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
916 return true; | 916 return true; |
917 } | 917 } |
918 #endif | 918 #endif |
919 | 919 |
920 FilePath GetHomeDir() { | 920 FilePath GetHomeDir() { |
921 const char* home_dir = getenv("HOME"); | 921 const char* home_dir = getenv("HOME"); |
922 if (home_dir && home_dir[0]) | 922 if (home_dir && home_dir[0]) |
923 return FilePath(home_dir); | 923 return FilePath(home_dir); |
924 | 924 |
925 #if defined(OS_ANDROID) | 925 #if defined(OS_ANDROID) |
926 LOG(WARNING) << "OS_ANDROID: Home directory lookup not yet implemented."; | 926 DLOG(WARNING) << "OS_ANDROID: Home directory lookup not yet implemented."; |
927 #else | 927 #else |
928 // g_get_home_dir calls getpwent, which can fall through to LDAP calls. | 928 // g_get_home_dir calls getpwent, which can fall through to LDAP calls. |
929 base::ThreadRestrictions::AssertIOAllowed(); | 929 base::ThreadRestrictions::AssertIOAllowed(); |
930 | 930 |
931 home_dir = g_get_home_dir(); | 931 home_dir = g_get_home_dir(); |
932 if (home_dir && home_dir[0]) | 932 if (home_dir && home_dir[0]) |
933 return FilePath(home_dir); | 933 return FilePath(home_dir); |
934 #endif | 934 #endif |
935 | 935 |
936 FilePath rv; | 936 FilePath rv; |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
987 | 987 |
988 return result; | 988 return result; |
989 } | 989 } |
990 #endif // defined(OS_MACOSX) | 990 #endif // defined(OS_MACOSX) |
991 | 991 |
992 bool VerifyPathControlledByUser(const FilePath& base, | 992 bool VerifyPathControlledByUser(const FilePath& base, |
993 const FilePath& path, | 993 const FilePath& path, |
994 uid_t owner_uid, | 994 uid_t owner_uid, |
995 const std::set<gid_t>& group_gids) { | 995 const std::set<gid_t>& group_gids) { |
996 if (base != path && !base.IsParent(path)) { | 996 if (base != path && !base.IsParent(path)) { |
997 LOG(ERROR) << "|base| must be a subdirectory of |path|. base = \"" | 997 DLOG(ERROR) << "|base| must be a subdirectory of |path|. base = \"" |
998 << base.value() << "\", path = \"" << path.value() << "\""; | 998 << base.value() << "\", path = \"" << path.value() << "\""; |
999 return false; | 999 return false; |
1000 } | 1000 } |
1001 | 1001 |
1002 std::vector<FilePath::StringType> base_components; | 1002 std::vector<FilePath::StringType> base_components; |
1003 std::vector<FilePath::StringType> path_components; | 1003 std::vector<FilePath::StringType> path_components; |
1004 | 1004 |
1005 base.GetComponents(&base_components); | 1005 base.GetComponents(&base_components); |
1006 path.GetComponents(&path_components); | 1006 path.GetComponents(&path_components); |
1007 | 1007 |
1008 std::vector<FilePath::StringType>::const_iterator ib, ip; | 1008 std::vector<FilePath::StringType>::const_iterator ib, ip; |
1009 for (ib = base_components.begin(), ip = path_components.begin(); | 1009 for (ib = base_components.begin(), ip = path_components.begin(); |
1010 ib != base_components.end(); ++ib, ++ip) { | 1010 ib != base_components.end(); ++ib, ++ip) { |
1011 // |base| must be a subpath of |path|, so all components should match. | 1011 // |base| must be a subpath of |path|, so all components should match. |
1012 // If these CHECKs fail, look at the test that base is a parent of | 1012 // If these CHECKs fail, look at the test that base is a parent of |
1013 // path at the top of this function. | 1013 // path at the top of this function. |
1014 CHECK(ip != path_components.end()); | 1014 DCHECK(ip != path_components.end()); |
1015 CHECK(*ip == *ib); | 1015 DCHECK(*ip == *ib); |
1016 } | 1016 } |
1017 | 1017 |
1018 FilePath current_path = base; | 1018 FilePath current_path = base; |
1019 if (!VerifySpecificPathControlledByUser(current_path, owner_uid, group_gids)) | 1019 if (!VerifySpecificPathControlledByUser(current_path, owner_uid, group_gids)) |
1020 return false; | 1020 return false; |
1021 | 1021 |
1022 for (; ip != path_components.end(); ++ip) { | 1022 for (; ip != path_components.end(); ++ip) { |
1023 current_path = current_path.Append(*ip); | 1023 current_path = current_path.Append(*ip); |
1024 if (!VerifySpecificPathControlledByUser( | 1024 if (!VerifySpecificPathControlledByUser( |
1025 current_path, owner_uid, group_gids)) | 1025 current_path, owner_uid, group_gids)) |
(...skipping 13 matching lines...) Expand all Loading... |
1039 "wheel" | 1039 "wheel" |
1040 }; | 1040 }; |
1041 | 1041 |
1042 // Reading the groups database may touch the file system. | 1042 // Reading the groups database may touch the file system. |
1043 base::ThreadRestrictions::AssertIOAllowed(); | 1043 base::ThreadRestrictions::AssertIOAllowed(); |
1044 | 1044 |
1045 std::set<gid_t> allowed_group_ids; | 1045 std::set<gid_t> allowed_group_ids; |
1046 for (int i = 0, ie = arraysize(kAdminGroupNames); i < ie; ++i) { | 1046 for (int i = 0, ie = arraysize(kAdminGroupNames); i < ie; ++i) { |
1047 struct group *group_record = getgrnam(kAdminGroupNames[i]); | 1047 struct group *group_record = getgrnam(kAdminGroupNames[i]); |
1048 if (!group_record) { | 1048 if (!group_record) { |
1049 PLOG(ERROR) << "Could not get the group ID of group \"" | 1049 DPLOG(ERROR) << "Could not get the group ID of group \"" |
1050 << kAdminGroupNames[i] << "\"."; | 1050 << kAdminGroupNames[i] << "\"."; |
1051 continue; | 1051 continue; |
1052 } | 1052 } |
1053 | 1053 |
1054 allowed_group_ids.insert(group_record->gr_gid); | 1054 allowed_group_ids.insert(group_record->gr_gid); |
1055 } | 1055 } |
1056 | 1056 |
1057 return VerifyPathControlledByUser( | 1057 return VerifyPathControlledByUser( |
1058 kFileSystemRoot, path, kRootUid, allowed_group_ids); | 1058 kFileSystemRoot, path, kRootUid, allowed_group_ids); |
1059 } | 1059 } |
1060 #endif // defined(OS_MACOSX) | 1060 #endif // defined(OS_MACOSX) |
1061 | 1061 |
1062 } // namespace file_util | 1062 } // namespace file_util |
OLD | NEW |