1// Copyright (c) 2012 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 "storage/browser/fileapi/native_file_util.h"
6
7#include "base/files/file.h"
8#include "base/files/file_enumerator.h"
9#include "base/files/file_util.h"
10#include "base/memory/scoped_ptr.h"
11#include "storage/browser/fileapi/file_system_operation_context.h"
12#include "storage/browser/fileapi/file_system_url.h"
13
14namespace storage {
15
16namespace {
17
18// Sets permissions on directory at |dir_path| based on the target platform.
19// Returns true on success, or false otherwise.
20//
21// TODO(benchan): Find a better place outside webkit to host this function.
22bool SetPlatformSpecificDirectoryPermissions(const base::FilePath& dir_path) {
23#if defined(OS_CHROMEOS)
24    // System daemons on Chrome OS may run as a user different than the Chrome
25    // process but need to access files under the directories created here.
26    // Because of that, grant the execute permission on the created directory
27    // to group and other users.
28    if (HANDLE_EINTR(chmod(dir_path.value().c_str(),
29                           S_IRWXU | S_IXGRP | S_IXOTH)) != 0) {
30      return false;
31    }
32#endif
33    // Keep the directory permissions unchanged on non-Chrome OS platforms.
34    return true;
35}
36
37// Copies a file |from| to |to|, and ensure the written content is synced to
38// the disk. This is essentially base::CopyFile followed by fsync().
39bool CopyFileAndSync(const base::FilePath& from, const base::FilePath& to) {
40  base::File infile(from, base::File::FLAG_OPEN | base::File::FLAG_READ);
41  if (!infile.IsValid()) {
42    return false;
43  }
44
45  base::File outfile(to,
46                     base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
47  if (!outfile.IsValid()) {
48    return false;
49  }
50
51  const int kBufferSize = 32768;
52  std::vector<char> buffer(kBufferSize);
53
54  for (;;) {
55    int bytes_read = infile.ReadAtCurrentPos(&buffer[0], kBufferSize);
56    if (bytes_read < 0)
57      return false;
58    if (bytes_read == 0)
59      break;
60    for (int bytes_written = 0; bytes_written < bytes_read; ) {
61      int bytes_written_partial = outfile.WriteAtCurrentPos(
62          &buffer[bytes_written], bytes_read - bytes_written);
63      if (bytes_written_partial < 0)
64        return false;
65      bytes_written += bytes_written_partial;
66    }
67  }
68
69  return outfile.Flush();
70}
71
72}  // namespace
73
74using base::PlatformFile;
75
76class NativeFileEnumerator : public FileSystemFileUtil::AbstractFileEnumerator {
77 public:
78  NativeFileEnumerator(const base::FilePath& root_path,
79                       bool recursive,
80                       int file_type)
81    : file_enum_(root_path, recursive, file_type) {
82  }
83
84  virtual ~NativeFileEnumerator() {}
85
86  virtual base::FilePath Next() OVERRIDE;
87  virtual int64 Size() OVERRIDE;
88  virtual base::Time LastModifiedTime() OVERRIDE;
89  virtual bool IsDirectory() OVERRIDE;
90
91 private:
92  base::FileEnumerator file_enum_;
93  base::FileEnumerator::FileInfo file_util_info_;
94};
95
96base::FilePath NativeFileEnumerator::Next() {
97  base::FilePath rv = file_enum_.Next();
98  if (!rv.empty())
99    file_util_info_ = file_enum_.GetInfo();
100  return rv;
101}
102
103int64 NativeFileEnumerator::Size() {
104  return file_util_info_.GetSize();
105}
106
107base::Time NativeFileEnumerator::LastModifiedTime() {
108  return file_util_info_.GetLastModifiedTime();
109}
110
111bool NativeFileEnumerator::IsDirectory() {
112  return file_util_info_.IsDirectory();
113}
114
115NativeFileUtil::CopyOrMoveMode NativeFileUtil::CopyOrMoveModeForDestination(
116    const FileSystemURL& dest_url, bool copy) {
117  if (copy) {
118    return dest_url.mount_option().copy_sync_option() == COPY_SYNC_OPTION_SYNC ?
119        COPY_SYNC : COPY_NOSYNC;
120  }
121  return MOVE;
122}
123
124base::File NativeFileUtil::CreateOrOpen(const base::FilePath& path,
125                                        int file_flags) {
126  if (!base::DirectoryExists(path.DirName())) {
127    // If its parent does not exist, should return NOT_FOUND error.
128    return base::File(base::File::FILE_ERROR_NOT_FOUND);
129  }
130
131  // TODO(rvargas): Check |file_flags| instead. See bug 356358.
132  if (base::DirectoryExists(path))
133    return base::File(base::File::FILE_ERROR_NOT_A_FILE);
134
135  return base::File(path, file_flags);
136}
137
138base::File::Error NativeFileUtil::EnsureFileExists(
139    const base::FilePath& path,
140    bool* created) {
141  if (!base::DirectoryExists(path.DirName()))
142    // If its parent does not exist, should return NOT_FOUND error.
143    return base::File::FILE_ERROR_NOT_FOUND;
144
145  // Tries to create the |path| exclusively.  This should fail
146  // with base::File::FILE_ERROR_EXISTS if the path already exists.
147  base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_READ);
148
149  if (file.IsValid()) {
150    if (created)
151      *created = file.created();
152    return base::File::FILE_OK;
153  }
154
155  base::File::Error error_code = file.error_details();
156  if (error_code == base::File::FILE_ERROR_EXISTS) {
157    // Make sure created_ is false.
158    if (created)
159      *created = false;
160    error_code = base::File::FILE_OK;
161  }
162  return error_code;
163}
164
165base::File::Error NativeFileUtil::CreateDirectory(
166    const base::FilePath& path,
167    bool exclusive,
168    bool recursive) {
169  // If parent dir of file doesn't exist.
170  if (!recursive && !base::PathExists(path.DirName()))
171    return base::File::FILE_ERROR_NOT_FOUND;
172
173  bool path_exists = base::PathExists(path);
174  if (exclusive && path_exists)
175    return base::File::FILE_ERROR_EXISTS;
176
177  // If file exists at the path.
178  if (path_exists && !base::DirectoryExists(path))
179    return base::File::FILE_ERROR_EXISTS;
180
181  if (!base::CreateDirectory(path))
182    return base::File::FILE_ERROR_FAILED;
183
184  if (!SetPlatformSpecificDirectoryPermissions(path)) {
185    // Since some file systems don't support permission setting, we do not treat
186    // an error from the function as the failure of copying. Just log it.
187    LOG(WARNING) << "Setting directory permission failed: "
188        << path.AsUTF8Unsafe();
189  }
190
191  return base::File::FILE_OK;
192}
193
194base::File::Error NativeFileUtil::GetFileInfo(
195    const base::FilePath& path,
196    base::File::Info* file_info) {
197  if (!base::PathExists(path))
198    return base::File::FILE_ERROR_NOT_FOUND;
199
200  if (!base::GetFileInfo(path, file_info))
201    return base::File::FILE_ERROR_FAILED;
202  return base::File::FILE_OK;
203}
204
205scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator>
206    NativeFileUtil::CreateFileEnumerator(const base::FilePath& root_path,
207                                         bool recursive) {
208  return make_scoped_ptr(new NativeFileEnumerator(
209      root_path, recursive,
210      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES))
211      .PassAs<FileSystemFileUtil::AbstractFileEnumerator>();
212}
213
214base::File::Error NativeFileUtil::Touch(
215    const base::FilePath& path,
216    const base::Time& last_access_time,
217    const base::Time& last_modified_time) {
218  if (!base::TouchFile(path, last_access_time, last_modified_time))
219    return base::File::FILE_ERROR_FAILED;
220  return base::File::FILE_OK;
221}
222
223base::File::Error NativeFileUtil::Truncate(const base::FilePath& path,
224                                           int64 length) {
225  base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
226  if (!file.IsValid())
227    return file.error_details();
228
229  if (!file.SetLength(length))
230    return base::File::FILE_ERROR_FAILED;
231
232  return base::File::FILE_OK;
233}
234
235bool NativeFileUtil::PathExists(const base::FilePath& path) {
236  return base::PathExists(path);
237}
238
239bool NativeFileUtil::DirectoryExists(const base::FilePath& path) {
240  return base::DirectoryExists(path);
241}
242
243base::File::Error NativeFileUtil::CopyOrMoveFile(
244    const base::FilePath& src_path,
245    const base::FilePath& dest_path,
246    FileSystemOperation::CopyOrMoveOption option,
247    CopyOrMoveMode mode) {
248  base::File::Info info;
249  base::File::Error error = NativeFileUtil::GetFileInfo(src_path, &info);
250  if (error != base::File::FILE_OK)
251    return error;
252  if (info.is_directory)
253    return base::File::FILE_ERROR_NOT_A_FILE;
254  base::Time last_modified = info.last_modified;
255
256  error = NativeFileUtil::GetFileInfo(dest_path, &info);
257  if (error != base::File::FILE_OK &&
258      error != base::File::FILE_ERROR_NOT_FOUND)
259    return error;
260  if (info.is_directory)
261    return base::File::FILE_ERROR_INVALID_OPERATION;
262  if (error == base::File::FILE_ERROR_NOT_FOUND) {
263    error = NativeFileUtil::GetFileInfo(dest_path.DirName(), &info);
264    if (error != base::File::FILE_OK)
265      return error;
266    if (!info.is_directory)
267      return base::File::FILE_ERROR_NOT_FOUND;
268  }
269
270  switch (mode) {
271    case COPY_NOSYNC:
272      if (!base::CopyFile(src_path, dest_path))
273        return base::File::FILE_ERROR_FAILED;
274      break;
275    case COPY_SYNC:
276      if (!CopyFileAndSync(src_path, dest_path))
277        return base::File::FILE_ERROR_FAILED;
278      break;
279    case MOVE:
280      if (!base::Move(src_path, dest_path))
281        return base::File::FILE_ERROR_FAILED;
282      break;
283  }
284
285  // Preserve the last modified time. Do not return error here even if
286  // the setting is failed, because the copy itself is successfully done.
287  if (option == FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED)
288    base::TouchFile(dest_path, last_modified, last_modified);
289
290  return base::File::FILE_OK;
291}
292
293base::File::Error NativeFileUtil::DeleteFile(const base::FilePath& path) {
294  if (!base::PathExists(path))
295    return base::File::FILE_ERROR_NOT_FOUND;
296  if (base::DirectoryExists(path))
297    return base::File::FILE_ERROR_NOT_A_FILE;
298  if (!base::DeleteFile(path, false))
299    return base::File::FILE_ERROR_FAILED;
300  return base::File::FILE_OK;
301}
302
303base::File::Error NativeFileUtil::DeleteDirectory(const base::FilePath& path) {
304  if (!base::PathExists(path))
305    return base::File::FILE_ERROR_NOT_FOUND;
306  if (!base::DirectoryExists(path))
307    return base::File::FILE_ERROR_NOT_A_DIRECTORY;
308  if (!base::IsDirectoryEmpty(path))
309    return base::File::FILE_ERROR_NOT_EMPTY;
310  if (!base::DeleteFile(path, false))
311    return base::File::FILE_ERROR_FAILED;
312  return base::File::FILE_OK;
313}
314
315}  // namespace storage
316