blob: d536475485645fa0c232ac6017af0c8182569b5f [file] [log] [blame]
thestig@chromium.orgf1a9ce12012-03-03 10:54:35 +09001// Copyright (c) 2012 The Chromium Authors. All rights reserved.
license.botf003cfe2008-08-24 09:55:55 +09002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
initial.commit3f4a7322008-07-27 06:49:38 +09004
5#include "base/file_util.h"
6
jeremy@chromium.orga5f404d2009-01-28 04:08:39 +09007#if defined(OS_WIN)
8#include <io.h>
9#endif
mark@chromium.orgd1bafc62008-10-02 02:40:13 +090010#include <stdio.h>
11
initial.commit3f4a7322008-07-27 06:49:38 +090012#include <fstream>
initial.commit3f4a7322008-07-27 06:49:38 +090013
evanm@google.com874d1672008-10-31 08:54:04 +090014#include "base/file_path.h"
initial.commit3f4a7322008-07-27 06:49:38 +090015#include "base/logging.h"
jam@chromium.orgc693fe32012-02-01 08:16:39 +090016#include "base/stringprintf.h"
estade@chromium.org97e37822008-11-27 13:03:57 +090017#include "base/string_piece.h"
brettw@chromium.org50c94652009-10-07 11:10:20 +090018#include "base/string_util.h"
19#include "base/utf_string_conversions.h"
estade@chromium.org97e37822008-11-27 13:03:57 +090020
estade@chromium.org63343202008-12-05 05:46:06 +090021namespace {
22
23const FilePath::CharType kExtensionSeparator = FILE_PATH_LITERAL('.');
24
jam@chromium.orgc693fe32012-02-01 08:16:39 +090025// The maximum number of 'uniquified' files we will try to create.
26// This is used when the filename we're trying to download is already in use,
27// so we create a new unique filename by appending " (nnn)" before the
28// extension, where 1 <= nnn <= kMaxUniqueFiles.
29// Also used by code that cleans up said files.
30static const int kMaxUniqueFiles = 100;
31
willchan@chromium.org2f0b26b2009-04-25 02:44:39 +090032} // namespace
estade@chromium.org63343202008-12-05 05:46:06 +090033
initial.commit3f4a7322008-07-27 06:49:38 +090034namespace file_util {
35
aa@chromium.org8dac1e42012-02-05 12:22:38 +090036bool g_bug108724_debug = false;
37
estade@chromium.org97e37822008-11-27 13:03:57 +090038bool EndsWithSeparator(const FilePath& path) {
39 FilePath::StringType value = path.value();
40 if (value.empty())
41 return false;
42
43 return FilePath::IsSeparator(value[value.size() - 1]);
initial.commit3f4a7322008-07-27 06:49:38 +090044}
45
estade@chromium.orgd778b8f2008-11-26 07:04:37 +090046bool EnsureEndsWithSeparator(FilePath* path) {
47 if (!DirectoryExists(*path))
48 return false;
49
50 if (EndsWithSeparator(*path))
51 return true;
52
53 FilePath::StringType& path_str =
54 const_cast<FilePath::StringType&>(path->value());
55 path_str.append(&FilePath::kSeparators[0], 1);
56
57 return true;
58}
59
estade@chromium.org63343202008-12-05 05:46:06 +090060void InsertBeforeExtension(FilePath* path, const FilePath::StringType& suffix) {
61 FilePath::StringType& value =
62 const_cast<FilePath::StringType&>(path->value());
63
64 const FilePath::StringType::size_type last_dot =
65 value.rfind(kExtensionSeparator);
66 const FilePath::StringType::size_type last_separator =
67 value.find_last_of(FilePath::StringType(FilePath::kSeparators));
68
69 if (last_dot == FilePath::StringType::npos ||
70 (last_separator != std::wstring::npos && last_dot < last_separator)) {
71 // The path looks something like "C:\pics.old\jojo" or "C:\pics\jojo".
72 // We should just append the suffix to the entire path.
73 value.append(suffix);
74 return;
75 }
76
77 value.insert(last_dot, suffix);
78}
79
evanm@google.com874d1672008-10-31 08:54:04 +090080bool ContentsEqual(const FilePath& filename1, const FilePath& filename2) {
initial.commit3f4a7322008-07-27 06:49:38 +090081 // We open the file in binary format even if they are text files because
82 // we are just comparing that bytes are exactly same in both files and not
83 // doing anything smart with text formatting.
evanm@google.com874d1672008-10-31 08:54:04 +090084 std::ifstream file1(filename1.value().c_str(),
erikkay@google.com9fc57d02008-08-09 05:16:08 +090085 std::ios::in | std::ios::binary);
evanm@google.com874d1672008-10-31 08:54:04 +090086 std::ifstream file2(filename2.value().c_str(),
erikkay@google.com9fc57d02008-08-09 05:16:08 +090087 std::ios::in | std::ios::binary);
estade@chromium.org97e37822008-11-27 13:03:57 +090088
initial.commit3f4a7322008-07-27 06:49:38 +090089 // Even if both files aren't openable (and thus, in some sense, "equal"),
90 // any unusable file yields a result of "false".
91 if (!file1.is_open() || !file2.is_open())
92 return false;
93
94 const int BUFFER_SIZE = 2056;
95 char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
96 do {
97 file1.read(buffer1, BUFFER_SIZE);
98 file2.read(buffer2, BUFFER_SIZE);
99
mark@chromium.org95c9ec92009-06-27 06:17:24 +0900100 if ((file1.eof() != file2.eof()) ||
initial.commit3f4a7322008-07-27 06:49:38 +0900101 (file1.gcount() != file2.gcount()) ||
102 (memcmp(buffer1, buffer2, file1.gcount()))) {
103 file1.close();
104 file2.close();
105 return false;
106 }
mark@chromium.org95c9ec92009-06-27 06:17:24 +0900107 } while (!file1.eof() || !file2.eof());
initial.commit3f4a7322008-07-27 06:49:38 +0900108
109 file1.close();
110 file2.close();
111 return true;
112}
113
mark@chromium.org95c9ec92009-06-27 06:17:24 +0900114bool TextContentsEqual(const FilePath& filename1, const FilePath& filename2) {
115 std::ifstream file1(filename1.value().c_str(), std::ios::in);
116 std::ifstream file2(filename2.value().c_str(), std::ios::in);
117
118 // Even if both files aren't openable (and thus, in some sense, "equal"),
119 // any unusable file yields a result of "false".
120 if (!file1.is_open() || !file2.is_open())
121 return false;
122
123 do {
124 std::string line1, line2;
125 getline(file1, line1);
126 getline(file2, line2);
127
128 // Check for mismatched EOF states, or any error state.
129 if ((file1.eof() != file2.eof()) ||
130 file1.bad() || file2.bad()) {
131 return false;
132 }
133
134 // Trim all '\r' and '\n' characters from the end of the line.
135 std::string::size_type end1 = line1.find_last_not_of("\r\n");
136 if (end1 == std::string::npos)
137 line1.clear();
138 else if (end1 + 1 < line1.length())
139 line1.erase(end1 + 1);
140
141 std::string::size_type end2 = line2.find_last_not_of("\r\n");
142 if (end2 == std::string::npos)
143 line2.clear();
144 else if (end2 + 1 < line2.length())
145 line2.erase(end2 + 1);
146
147 if (line1 != line2)
148 return false;
149 } while (!file1.eof() || !file2.eof());
150
151 return true;
152}
153
erikkay@google.com52460fb2009-01-28 09:22:46 +0900154bool ReadFileToString(const FilePath& path, std::string* contents) {
mark@chromium.orgd1bafc62008-10-02 02:40:13 +0900155 FILE* file = OpenFile(path, "rb");
156 if (!file) {
initial.commit3f4a7322008-07-27 06:49:38 +0900157 return false;
mark@chromium.orgd1bafc62008-10-02 02:40:13 +0900158 }
initial.commit3f4a7322008-07-27 06:49:38 +0900159
160 char buf[1 << 16];
161 size_t len;
162 while ((len = fread(buf, 1, sizeof(buf), file)) > 0) {
evan@chromium.orga75b2e82010-05-19 20:07:55 +0900163 if (contents)
164 contents->append(buf, len);
initial.commit3f4a7322008-07-27 06:49:38 +0900165 }
mark@chromium.orgd1bafc62008-10-02 02:40:13 +0900166 CloseFile(file);
initial.commit3f4a7322008-07-27 06:49:38 +0900167
168 return true;
169}
170
tfarina@chromium.org34828222010-05-26 10:40:12 +0900171bool IsDirectoryEmpty(const FilePath& dir_path) {
172 FileEnumerator files(dir_path, false,
tfarina@chromium.orga3a4db72011-08-15 22:09:27 +0900173 static_cast<FileEnumerator::FileType>(
tfarina@chromium.org34828222010-05-26 10:40:12 +0900174 FileEnumerator::FILES | FileEnumerator::DIRECTORIES));
175 if (files.Next().value().empty())
176 return true;
177 return false;
178}
179
phajdan.jr@chromium.org8139fe12009-04-28 15:50:36 +0900180FILE* CreateAndOpenTemporaryFile(FilePath* path) {
181 FilePath directory;
182 if (!GetTempDir(&directory))
evan@chromium.org2abe0b42010-06-11 07:56:23 +0900183 return NULL;
phajdan.jr@chromium.org8139fe12009-04-28 15:50:36 +0900184
185 return CreateAndOpenTemporaryFileInDir(directory, path);
186}
187
dkegel@google.com44982682008-11-05 06:00:46 +0900188bool GetFileSize(const FilePath& file_path, int64* file_size) {
dumi@chromium.org97ae2612010-09-03 11:28:37 +0900189 base::PlatformFileInfo info;
darin@google.com7f479f22008-09-26 10:04:08 +0900190 if (!GetFileInfo(file_path, &info))
191 return false;
192 *file_size = info.size;
193 return true;
194}
195
estade@chromium.orga99d0e12010-02-12 08:27:47 +0900196bool IsDot(const FilePath& path) {
197 return FILE_PATH_LITERAL(".") == path.BaseName().value();
198}
199
200bool IsDotDot(const FilePath& path) {
201 return FILE_PATH_LITERAL("..") == path.BaseName().value();
202}
203
dumi@chromium.orgc941a182010-09-24 08:28:22 +0900204bool TouchFile(const FilePath& path,
205 const base::Time& last_accessed,
206 const base::Time& last_modified) {
207 base::PlatformFile file =
208 base::CreatePlatformFile(path,
209 base::PLATFORM_FILE_OPEN |
210 base::PLATFORM_FILE_WRITE_ATTRIBUTES,
211 NULL, NULL);
212 if (file != base::kInvalidPlatformFileValue) {
213 bool result = base::TouchPlatformFile(file, last_accessed, last_modified);
214 base::ClosePlatformFile(file);
215 return result;
216 }
217
218 return false;
219}
220
221bool SetLastModifiedTime(const FilePath& path,
222 const base::Time& last_modified) {
223 return TouchFile(path, last_modified, last_modified);
224}
225
mark@chromium.orgd1bafc62008-10-02 02:40:13 +0900226bool CloseFile(FILE* file) {
sidchat@google.comd3b26432008-10-22 02:14:45 +0900227 if (file == NULL)
228 return true;
mark@chromium.orgd1bafc62008-10-02 02:40:13 +0900229 return fclose(file) == 0;
230}
231
jeremy@chromium.orga5f404d2009-01-28 04:08:39 +0900232bool TruncateFile(FILE* file) {
233 if (file == NULL)
234 return false;
235 long current_offset = ftell(file);
236 if (current_offset == -1)
237 return false;
238#if defined(OS_WIN)
239 int fd = _fileno(file);
240 if (_chsize(fd, current_offset) != 0)
241 return false;
242#else
243 int fd = fileno(file);
244 if (ftruncate(fd, current_offset) != 0)
245 return false;
246#endif
247 return true;
248}
249
jam@chromium.orgc693fe32012-02-01 08:16:39 +0900250int GetUniquePathNumber(
251 const FilePath& path,
252 const FilePath::StringType& suffix) {
253 bool have_suffix = !suffix.empty();
254 if (!PathExists(path) &&
255 (!have_suffix || !PathExists(FilePath(path.value() + suffix)))) {
256 return 0;
257 }
258
259 FilePath new_path;
260 for (int count = 1; count <= kMaxUniqueFiles; ++count) {
261 new_path = path.InsertBeforeExtensionASCII(StringPrintf(" (%d)", count));
262 if (!PathExists(new_path) &&
263 (!have_suffix || !PathExists(FilePath(new_path.value() + suffix)))) {
264 return count;
265 }
266 }
267
268 return -1;
269}
270
aa@chromium.orga4dbdf22009-01-10 07:14:27 +0900271bool ContainsPath(const FilePath &parent, const FilePath& child) {
272 FilePath abs_parent = FilePath(parent);
273 FilePath abs_child = FilePath(child);
274
275 if (!file_util::AbsolutePath(&abs_parent) ||
276 !file_util::AbsolutePath(&abs_child))
277 return false;
278
279#if defined(OS_WIN)
280 // file_util::AbsolutePath() does not flatten case on Windows, so we must do
281 // a case-insensitive compare.
282 if (!StartsWith(abs_child.value(), abs_parent.value(), false))
283#else
284 if (!StartsWithASCII(abs_child.value(), abs_parent.value(), true))
285#endif
286 return false;
287
288 // file_util::AbsolutePath() normalizes '/' to '\' on Windows, so we only need
289 // to check kSeparators[0].
290 if (abs_child.value().length() <= abs_parent.value().length() ||
291 abs_child.value()[abs_parent.value().length()] !=
292 FilePath::kSeparators[0])
293 return false;
294
295 return true;
296}
297
cpu@chromium.org83f07be2010-03-25 06:56:26 +0900298int64 ComputeDirectorySize(const FilePath& root_path) {
299 int64 running_size = 0;
300 FileEnumerator file_iter(root_path, true, FileEnumerator::FILES);
301 for (FilePath current = file_iter.Next(); !current.empty();
302 current = file_iter.Next()) {
303 FileEnumerator::FindInfo info;
304 file_iter.GetFindInfo(&info);
305#if defined(OS_WIN)
306 LARGE_INTEGER li = { info.nFileSizeLow, info.nFileSizeHigh };
307 running_size += li.QuadPart;
308#else
309 running_size += info.stat.st_size;
310#endif
311 }
312 return running_size;
313}
314
rvargas@google.comaa24e112010-06-12 07:53:43 +0900315int64 ComputeFilesSize(const FilePath& directory,
316 const FilePath::StringType& pattern) {
317 int64 running_size = 0;
318 FileEnumerator file_iter(directory, false, FileEnumerator::FILES, pattern);
319 for (FilePath current = file_iter.Next(); !current.empty();
320 current = file_iter.Next()) {
321 FileEnumerator::FindInfo info;
322 file_iter.GetFindInfo(&info);
323#if defined(OS_WIN)
324 LARGE_INTEGER li = { info.nFileSizeLow, info.nFileSizeHigh };
325 running_size += li.QuadPart;
326#else
327 running_size += info.stat.st_size;
328#endif
329 }
330 return running_size;
331}
332
estade@chromium.org2c233532008-12-13 08:43:03 +0900333///////////////////////////////////////////////
334// MemoryMappedFile
335
336MemoryMappedFile::~MemoryMappedFile() {
337 CloseHandles();
338}
339
erg@google.com37c078e2011-01-11 09:50:59 +0900340bool MemoryMappedFile::Initialize(const FilePath& file_name) {
341 if (IsValid())
342 return false;
343
344 if (!MapFileToMemory(file_name)) {
345 CloseHandles();
346 return false;
347 }
348
349 return true;
350}
351
estade@chromium.org3ac32062009-11-17 07:55:17 +0900352bool MemoryMappedFile::Initialize(base::PlatformFile file) {
353 if (IsValid())
354 return false;
355
356 file_ = file;
357
358 if (!MapFileToMemoryInternal()) {
359 CloseHandles();
360 return false;
361 }
362
363 return true;
364}
365
tommi@chromium.org39209952011-02-24 06:42:48 +0900366bool MemoryMappedFile::IsValid() const {
erg@google.com37c078e2011-01-11 09:50:59 +0900367 return data_ != NULL;
estade@chromium.org2c233532008-12-13 08:43:03 +0900368}
369
estade@chromium.org3ac32062009-11-17 07:55:17 +0900370bool MemoryMappedFile::MapFileToMemory(const FilePath& file_name) {
dumi@chromium.org50f197d2010-09-01 04:30:27 +0900371 file_ = base::CreatePlatformFile(
372 file_name, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
373 NULL, NULL);
estade@chromium.org3ac32062009-11-17 07:55:17 +0900374
375 if (file_ == base::kInvalidPlatformFileValue) {
brettw@chromium.org5faed3c2011-10-27 06:48:00 +0900376 DLOG(ERROR) << "Couldn't open " << file_name.value();
estade@chromium.org3ac32062009-11-17 07:55:17 +0900377 return false;
378 }
379
380 return MapFileToMemoryInternal();
381}
382
yuzo@chromium.org2da0f822009-06-09 14:57:38 +0900383///////////////////////////////////////////////
384// FileEnumerator
385//
386// Note: the main logic is in file_util_<platform>.cc
387
388bool FileEnumerator::ShouldSkip(const FilePath& path) {
389 FilePath::StringType basename = path.BaseName().value();
390 return IsDot(path) || (IsDotDot(path) && !(INCLUDE_DOT_DOT & file_type_));
391}
392
initial.commit3f4a7322008-07-27 06:49:38 +0900393} // namespace