1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/file_util.h"
6
7#if defined(OS_WIN)
8#include <io.h>
9#endif
10#include <stdio.h>
11
12#include <fstream>
13
14#include "base/file_path.h"
15#include "base/logging.h"
16#include "base/string_piece.h"
17#include "base/string_util.h"
18#include "base/utf_string_conversions.h"
19
20namespace {
21
22const FilePath::CharType kExtensionSeparator = FILE_PATH_LITERAL('.');
23
24}  // namespace
25
26namespace file_util {
27
28bool EndsWithSeparator(const FilePath& path) {
29  FilePath::StringType value = path.value();
30  if (value.empty())
31    return false;
32
33  return FilePath::IsSeparator(value[value.size() - 1]);
34}
35
36bool EnsureEndsWithSeparator(FilePath* path) {
37  if (!DirectoryExists(*path))
38    return false;
39
40  if (EndsWithSeparator(*path))
41    return true;
42
43  FilePath::StringType& path_str =
44      const_cast<FilePath::StringType&>(path->value());
45  path_str.append(&FilePath::kSeparators[0], 1);
46
47  return true;
48}
49
50FilePath::StringType GetFileExtensionFromPath(const FilePath& path) {
51  FilePath::StringType file_name = path.BaseName().value();
52  const FilePath::StringType::size_type last_dot =
53      file_name.rfind(kExtensionSeparator);
54  return FilePath::StringType(last_dot == FilePath::StringType::npos ?
55                              FILE_PATH_LITERAL("") :
56                              file_name, last_dot+1);
57}
58
59void InsertBeforeExtension(FilePath* path, const FilePath::StringType& suffix) {
60  FilePath::StringType& value =
61      const_cast<FilePath::StringType&>(path->value());
62
63  const FilePath::StringType::size_type last_dot =
64      value.rfind(kExtensionSeparator);
65  const FilePath::StringType::size_type last_separator =
66      value.find_last_of(FilePath::StringType(FilePath::kSeparators));
67
68  if (last_dot == FilePath::StringType::npos ||
69      (last_separator != std::wstring::npos && last_dot < last_separator)) {
70    // The path looks something like "C:\pics.old\jojo" or "C:\pics\jojo".
71    // We should just append the suffix to the entire path.
72    value.append(suffix);
73    return;
74  }
75
76  value.insert(last_dot, suffix);
77}
78
79bool ContentsEqual(const FilePath& filename1, const FilePath& filename2) {
80  // We open the file in binary format even if they are text files because
81  // we are just comparing that bytes are exactly same in both files and not
82  // doing anything smart with text formatting.
83  std::ifstream file1(filename1.value().c_str(),
84                      std::ios::in | std::ios::binary);
85  std::ifstream file2(filename2.value().c_str(),
86                      std::ios::in | std::ios::binary);
87
88  // Even if both files aren't openable (and thus, in some sense, "equal"),
89  // any unusable file yields a result of "false".
90  if (!file1.is_open() || !file2.is_open())
91    return false;
92
93  const int BUFFER_SIZE = 2056;
94  char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
95  do {
96    file1.read(buffer1, BUFFER_SIZE);
97    file2.read(buffer2, BUFFER_SIZE);
98
99    if ((file1.eof() != file2.eof()) ||
100        (file1.gcount() != file2.gcount()) ||
101        (memcmp(buffer1, buffer2, file1.gcount()))) {
102      file1.close();
103      file2.close();
104      return false;
105    }
106  } while (!file1.eof() || !file2.eof());
107
108  file1.close();
109  file2.close();
110  return true;
111}
112
113bool TextContentsEqual(const FilePath& filename1, const FilePath& filename2) {
114  std::ifstream file1(filename1.value().c_str(), std::ios::in);
115  std::ifstream file2(filename2.value().c_str(), std::ios::in);
116
117  // Even if both files aren't openable (and thus, in some sense, "equal"),
118  // any unusable file yields a result of "false".
119  if (!file1.is_open() || !file2.is_open())
120    return false;
121
122  do {
123    std::string line1, line2;
124    getline(file1, line1);
125    getline(file2, line2);
126
127    // Check for mismatched EOF states, or any error state.
128    if ((file1.eof() != file2.eof()) ||
129        file1.bad() || file2.bad()) {
130      return false;
131    }
132
133    // Trim all '\r' and '\n' characters from the end of the line.
134    std::string::size_type end1 = line1.find_last_not_of("\r\n");
135    if (end1 == std::string::npos)
136      line1.clear();
137    else if (end1 + 1 < line1.length())
138      line1.erase(end1 + 1);
139
140    std::string::size_type end2 = line2.find_last_not_of("\r\n");
141    if (end2 == std::string::npos)
142      line2.clear();
143    else if (end2 + 1 < line2.length())
144      line2.erase(end2 + 1);
145
146    if (line1 != line2)
147      return false;
148  } while (!file1.eof() || !file2.eof());
149
150  return true;
151}
152
153bool ReadFileToString(const FilePath& path, std::string* contents) {
154  FILE* file = OpenFile(path, "rb");
155  if (!file) {
156    return false;
157  }
158
159  char buf[1 << 16];
160  size_t len;
161  while ((len = fread(buf, 1, sizeof(buf), file)) > 0) {
162    if (contents)
163      contents->append(buf, len);
164  }
165  CloseFile(file);
166
167  return true;
168}
169
170bool IsDirectoryEmpty(const FilePath& dir_path) {
171  FileEnumerator files(dir_path, false,
172      static_cast<FileEnumerator::FILE_TYPE>(
173          FileEnumerator::FILES | FileEnumerator::DIRECTORIES));
174  if (files.Next().value().empty())
175    return true;
176  return false;
177}
178
179FILE* CreateAndOpenTemporaryFile(FilePath* path) {
180  FilePath directory;
181  if (!GetTempDir(&directory))
182    return NULL;
183
184  return CreateAndOpenTemporaryFileInDir(directory, path);
185}
186
187bool GetFileSize(const FilePath& file_path, int64* file_size) {
188  base::PlatformFileInfo info;
189  if (!GetFileInfo(file_path, &info))
190    return false;
191  *file_size = info.size;
192  return true;
193}
194
195bool IsDot(const FilePath& path) {
196  return FILE_PATH_LITERAL(".") == path.BaseName().value();
197}
198
199bool IsDotDot(const FilePath& path) {
200  return FILE_PATH_LITERAL("..") == path.BaseName().value();
201}
202
203bool TouchFile(const FilePath& path,
204               const base::Time& last_accessed,
205               const base::Time& last_modified) {
206  base::PlatformFile file =
207      base::CreatePlatformFile(path,
208                               base::PLATFORM_FILE_OPEN |
209                               base::PLATFORM_FILE_WRITE_ATTRIBUTES,
210                               NULL, NULL);
211  if (file != base::kInvalidPlatformFileValue) {
212    bool result = base::TouchPlatformFile(file, last_accessed, last_modified);
213    base::ClosePlatformFile(file);
214    return result;
215  }
216
217  return false;
218}
219
220bool SetLastModifiedTime(const FilePath& path,
221                         const base::Time& last_modified) {
222  return TouchFile(path, last_modified, last_modified);
223}
224
225bool CloseFile(FILE* file) {
226  if (file == NULL)
227    return true;
228  return fclose(file) == 0;
229}
230
231bool TruncateFile(FILE* file) {
232  if (file == NULL)
233    return false;
234  long current_offset = ftell(file);
235  if (current_offset == -1)
236    return false;
237#if defined(OS_WIN)
238  int fd = _fileno(file);
239  if (_chsize(fd, current_offset) != 0)
240    return false;
241#else
242  int fd = fileno(file);
243  if (ftruncate(fd, current_offset) != 0)
244    return false;
245#endif
246  return true;
247}
248
249bool ContainsPath(const FilePath &parent, const FilePath& child) {
250  FilePath abs_parent = FilePath(parent);
251  FilePath abs_child = FilePath(child);
252
253  if (!file_util::AbsolutePath(&abs_parent) ||
254      !file_util::AbsolutePath(&abs_child))
255    return false;
256
257#if defined(OS_WIN)
258  // file_util::AbsolutePath() does not flatten case on Windows, so we must do
259  // a case-insensitive compare.
260  if (!StartsWith(abs_child.value(), abs_parent.value(), false))
261#else
262  if (!StartsWithASCII(abs_child.value(), abs_parent.value(), true))
263#endif
264    return false;
265
266  // file_util::AbsolutePath() normalizes '/' to '\' on Windows, so we only need
267  // to check kSeparators[0].
268  if (abs_child.value().length() <= abs_parent.value().length() ||
269      abs_child.value()[abs_parent.value().length()] !=
270          FilePath::kSeparators[0])
271    return false;
272
273  return true;
274}
275
276int64 ComputeDirectorySize(const FilePath& root_path) {
277  int64 running_size = 0;
278  FileEnumerator file_iter(root_path, true, FileEnumerator::FILES);
279  for (FilePath current = file_iter.Next(); !current.empty();
280       current = file_iter.Next()) {
281    FileEnumerator::FindInfo info;
282    file_iter.GetFindInfo(&info);
283#if defined(OS_WIN)
284    LARGE_INTEGER li = { info.nFileSizeLow, info.nFileSizeHigh };
285    running_size += li.QuadPart;
286#else
287    running_size += info.stat.st_size;
288#endif
289  }
290  return running_size;
291}
292
293int64 ComputeFilesSize(const FilePath& directory,
294                       const FilePath::StringType& pattern) {
295  int64 running_size = 0;
296  FileEnumerator file_iter(directory, false, FileEnumerator::FILES, pattern);
297  for (FilePath current = file_iter.Next(); !current.empty();
298       current = file_iter.Next()) {
299    FileEnumerator::FindInfo info;
300    file_iter.GetFindInfo(&info);
301#if defined(OS_WIN)
302    LARGE_INTEGER li = { info.nFileSizeLow, info.nFileSizeHigh };
303    running_size += li.QuadPart;
304#else
305    running_size += info.stat.st_size;
306#endif
307  }
308  return running_size;
309}
310
311///////////////////////////////////////////////
312// MemoryMappedFile
313
314MemoryMappedFile::~MemoryMappedFile() {
315  CloseHandles();
316}
317
318bool MemoryMappedFile::Initialize(const FilePath& file_name) {
319  if (IsValid())
320    return false;
321
322  if (!MapFileToMemory(file_name)) {
323    CloseHandles();
324    return false;
325  }
326
327  return true;
328}
329
330bool MemoryMappedFile::Initialize(base::PlatformFile file) {
331  if (IsValid())
332    return false;
333
334  file_ = file;
335
336  if (!MapFileToMemoryInternal()) {
337    CloseHandles();
338    return false;
339  }
340
341  return true;
342}
343
344bool MemoryMappedFile::IsValid() const {
345  return data_ != NULL;
346}
347
348bool MemoryMappedFile::MapFileToMemory(const FilePath& file_name) {
349  file_ = base::CreatePlatformFile(
350      file_name, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
351      NULL, NULL);
352
353  if (file_ == base::kInvalidPlatformFileValue) {
354    LOG(ERROR) << "Couldn't open " << file_name.value();
355    return false;
356  }
357
358  return MapFileToMemoryInternal();
359}
360
361// Deprecated functions ----------------------------------------------------
362
363#if defined(OS_WIN)
364void AppendToPath(std::wstring* path, const std::wstring& new_ending) {
365  if (!path) {
366    NOTREACHED();
367    return;  // Don't crash in this function in release builds.
368  }
369
370  if (!EndsWithSeparator(FilePath(*path)))
371    path->push_back(FilePath::kSeparators[0]);
372  path->append(new_ending);
373}
374
375bool CopyDirectory(const std::wstring& from_path, const std::wstring& to_path,
376                   bool recursive) {
377  return CopyDirectory(FilePath::FromWStringHack(from_path),
378                       FilePath::FromWStringHack(to_path),
379                       recursive);
380}
381bool Delete(const std::wstring& path, bool recursive) {
382  return Delete(FilePath::FromWStringHack(path), recursive);
383}
384std::wstring GetFileExtensionFromPath(const std::wstring& path) {
385  FilePath::StringType extension =
386      GetFileExtensionFromPath(FilePath::FromWStringHack(path));
387  return extension;
388}
389FILE* OpenFile(const std::wstring& filename, const char* mode) {
390  return OpenFile(FilePath::FromWStringHack(filename), mode);
391}
392int ReadFile(const std::wstring& filename, char* data, int size) {
393  return ReadFile(FilePath::FromWStringHack(filename), data, size);
394}
395int WriteFile(const std::wstring& filename, const char* data, int size) {
396  return WriteFile(FilePath::FromWStringHack(filename), data, size);
397}
398#endif  // OS_WIN
399
400///////////////////////////////////////////////
401// FileEnumerator
402//
403// Note: the main logic is in file_util_<platform>.cc
404
405bool FileEnumerator::ShouldSkip(const FilePath& path) {
406  FilePath::StringType basename = path.BaseName().value();
407  return IsDot(path) || (IsDotDot(path) && !(INCLUDE_DOT_DOT & file_type_));
408}
409
410}  // namespace
411