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 "base/files/file.h"
6
7#include <errno.h>
8#include <fcntl.h>
9#include <sys/stat.h>
10#include <unistd.h>
11
12#include "base/files/file_path.h"
13#include "base/logging.h"
14#include "base/metrics/sparse_histogram.h"
15// TODO(rvargas): remove this (needed for kInvalidPlatformFileValue).
16#include "base/platform_file.h"
17#include "base/posix/eintr_wrapper.h"
18#include "base/strings/utf_string_conversions.h"
19#include "base/threading/thread_restrictions.h"
20
21#if defined(OS_ANDROID)
22#include "base/os_compat_android.h"
23#endif
24
25namespace base {
26
27// Make sure our Whence mappings match the system headers.
28COMPILE_ASSERT(File::FROM_BEGIN   == SEEK_SET &&
29               File::FROM_CURRENT == SEEK_CUR &&
30               File::FROM_END     == SEEK_END, whence_matches_system);
31
32namespace {
33
34#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL)
35typedef struct stat stat_wrapper_t;
36static int CallFstat(int fd, stat_wrapper_t *sb) {
37  base::ThreadRestrictions::AssertIOAllowed();
38  return fstat(fd, sb);
39}
40#else
41typedef struct stat64 stat_wrapper_t;
42static int CallFstat(int fd, stat_wrapper_t *sb) {
43  base::ThreadRestrictions::AssertIOAllowed();
44  return fstat64(fd, sb);
45}
46#endif
47
48// NaCl doesn't provide the following system calls, so either simulate them or
49// wrap them in order to minimize the number of #ifdef's in this file.
50#if !defined(OS_NACL)
51static bool IsOpenAppend(PlatformFile file) {
52  return (fcntl(file, F_GETFL) & O_APPEND) != 0;
53}
54
55static int CallFtruncate(PlatformFile file, int64 length) {
56  return HANDLE_EINTR(ftruncate(file, length));
57}
58
59static int CallFsync(PlatformFile file) {
60  return HANDLE_EINTR(fsync(file));
61}
62
63static int CallFutimes(PlatformFile file, const struct timeval times[2]) {
64#ifdef __USE_XOPEN2K8
65  // futimens should be available, but futimes might not be
66  // http://pubs.opengroup.org/onlinepubs/9699919799/
67
68  timespec ts_times[2];
69  ts_times[0].tv_sec  = times[0].tv_sec;
70  ts_times[0].tv_nsec = times[0].tv_usec * 1000;
71  ts_times[1].tv_sec  = times[1].tv_sec;
72  ts_times[1].tv_nsec = times[1].tv_usec * 1000;
73
74  return futimens(file, ts_times);
75#else
76  return futimes(file, times);
77#endif
78}
79
80static File::Error CallFctnlFlock(PlatformFile file, bool do_lock) {
81  struct flock lock;
82  lock.l_type = F_WRLCK;
83  lock.l_whence = SEEK_SET;
84  lock.l_start = 0;
85  lock.l_len = 0;  // Lock entire file.
86  if (HANDLE_EINTR(fcntl(file, do_lock ? F_SETLK : F_UNLCK, &lock)) == -1)
87    return File::OSErrorToFileError(errno);
88  return File::FILE_OK;
89}
90#else  // defined(OS_NACL)
91
92static bool IsOpenAppend(PlatformFile file) {
93  // NaCl doesn't implement fcntl. Since NaCl's write conforms to the POSIX
94  // standard and always appends if the file is opened with O_APPEND, just
95  // return false here.
96  return false;
97}
98
99static int CallFtruncate(PlatformFile file, int64 length) {
100  NOTIMPLEMENTED();  // NaCl doesn't implement ftruncate.
101  return 0;
102}
103
104static int CallFsync(PlatformFile file) {
105  NOTIMPLEMENTED();  // NaCl doesn't implement fsync.
106  return 0;
107}
108
109static int CallFutimes(PlatformFile file, const struct timeval times[2]) {
110  NOTIMPLEMENTED();  // NaCl doesn't implement futimes.
111  return 0;
112}
113
114static File::Error CallFctnlFlock(PlatformFile file, bool do_lock) {
115  NOTIMPLEMENTED();  // NaCl doesn't implement flock struct.
116  return File::FILE_ERROR_INVALID_OPERATION;
117}
118#endif  // defined(OS_NACL)
119
120}  // namespace
121
122// NaCl doesn't implement system calls to open files directly.
123#if !defined(OS_NACL)
124// TODO(erikkay): does it make sense to support FLAG_EXCLUSIVE_* here?
125void File::CreateBaseFileUnsafe(const FilePath& name, uint32 flags) {
126  base::ThreadRestrictions::AssertIOAllowed();
127  DCHECK(!IsValid());
128  DCHECK(!(flags & FLAG_ASYNC));
129
130  int open_flags = 0;
131  if (flags & FLAG_CREATE)
132    open_flags = O_CREAT | O_EXCL;
133
134  created_ = false;
135
136  if (flags & FLAG_CREATE_ALWAYS) {
137    DCHECK(!open_flags);
138    open_flags = O_CREAT | O_TRUNC;
139  }
140
141  if (flags & FLAG_OPEN_TRUNCATED) {
142    DCHECK(!open_flags);
143    DCHECK(flags & FLAG_WRITE);
144    open_flags = O_TRUNC;
145  }
146
147  if (!open_flags && !(flags & FLAG_OPEN) && !(flags & FLAG_OPEN_ALWAYS)) {
148    NOTREACHED();
149    errno = EOPNOTSUPP;
150    error_ = FILE_ERROR_FAILED;
151    return;
152  }
153
154  if (flags & FLAG_WRITE && flags & FLAG_READ) {
155    open_flags |= O_RDWR;
156  } else if (flags & FLAG_WRITE) {
157    open_flags |= O_WRONLY;
158  } else if (!(flags & FLAG_READ) &&
159             !(flags & FLAG_WRITE_ATTRIBUTES) &&
160             !(flags & FLAG_APPEND) &&
161             !(flags & FLAG_OPEN_ALWAYS)) {
162    NOTREACHED();
163  }
164
165  if (flags & FLAG_TERMINAL_DEVICE)
166    open_flags |= O_NOCTTY | O_NDELAY;
167
168  if (flags & FLAG_APPEND && flags & FLAG_READ)
169    open_flags |= O_APPEND | O_RDWR;
170  else if (flags & FLAG_APPEND)
171    open_flags |= O_APPEND | O_WRONLY;
172
173  COMPILE_ASSERT(O_RDONLY == 0, O_RDONLY_must_equal_zero);
174
175  int mode = S_IRUSR | S_IWUSR;
176#if defined(OS_CHROMEOS)
177  mode |= S_IRGRP | S_IROTH;
178#endif
179
180  int descriptor = HANDLE_EINTR(open(name.value().c_str(), open_flags, mode));
181
182  if (flags & FLAG_OPEN_ALWAYS) {
183    if (descriptor < 0) {
184      open_flags |= O_CREAT;
185      if (flags & FLAG_EXCLUSIVE_READ || flags & FLAG_EXCLUSIVE_WRITE)
186        open_flags |= O_EXCL;   // together with O_CREAT implies O_NOFOLLOW
187
188      descriptor = HANDLE_EINTR(open(name.value().c_str(), open_flags, mode));
189      if (descriptor >= 0)
190        created_ = true;
191    }
192  }
193
194  if (descriptor >= 0 && (flags & (FLAG_CREATE_ALWAYS | FLAG_CREATE)))
195    created_ = true;
196
197  if ((descriptor >= 0) && (flags & FLAG_DELETE_ON_CLOSE))
198    unlink(name.value().c_str());
199
200  if (descriptor >= 0)
201    error_ = FILE_OK;
202  else
203    error_ = File::OSErrorToFileError(errno);
204
205  file_ = descriptor;
206}
207#endif  // !defined(OS_NACL)
208
209bool File::IsValid() const {
210  return file_ >= 0;
211}
212
213PlatformFile File::TakePlatformFile() {
214  PlatformFile file = file_;
215  file_ = kInvalidPlatformFileValue;
216  return file;
217}
218
219void File::Close() {
220  base::ThreadRestrictions::AssertIOAllowed();
221  if (!IsValid())
222    return;
223
224  if (!IGNORE_EINTR(close(file_)))
225    file_ = kInvalidPlatformFileValue;
226}
227
228int64 File::Seek(Whence whence, int64 offset) {
229  base::ThreadRestrictions::AssertIOAllowed();
230  DCHECK(IsValid());
231  if (file_ < 0 || offset < 0)
232    return -1;
233
234  return lseek(file_, static_cast<off_t>(offset), static_cast<int>(whence));
235}
236
237int File::Read(int64 offset, char* data, int size) {
238  base::ThreadRestrictions::AssertIOAllowed();
239  DCHECK(IsValid());
240  if (size < 0)
241    return -1;
242
243  int bytes_read = 0;
244  int rv;
245  do {
246    rv = HANDLE_EINTR(pread(file_, data + bytes_read,
247                            size - bytes_read, offset + bytes_read));
248    if (rv <= 0)
249      break;
250
251    bytes_read += rv;
252  } while (bytes_read < size);
253
254  return bytes_read ? bytes_read : rv;
255}
256
257int File::ReadAtCurrentPos(char* data, int size) {
258  base::ThreadRestrictions::AssertIOAllowed();
259  DCHECK(IsValid());
260  if (size < 0)
261    return -1;
262
263  int bytes_read = 0;
264  int rv;
265  do {
266    rv = HANDLE_EINTR(read(file_, data, size));
267    if (rv <= 0)
268      break;
269
270    bytes_read += rv;
271  } while (bytes_read < size);
272
273  return bytes_read ? bytes_read : rv;
274}
275
276int File::ReadNoBestEffort(int64 offset, char* data, int size) {
277  base::ThreadRestrictions::AssertIOAllowed();
278  DCHECK(IsValid());
279
280  return HANDLE_EINTR(pread(file_, data, size, offset));
281}
282
283int File::ReadAtCurrentPosNoBestEffort(char* data, int size) {
284  base::ThreadRestrictions::AssertIOAllowed();
285  DCHECK(IsValid());
286  if (size < 0)
287    return -1;
288
289  return HANDLE_EINTR(read(file_, data, size));
290}
291
292int File::Write(int64 offset, const char* data, int size) {
293  base::ThreadRestrictions::AssertIOAllowed();
294
295  if (IsOpenAppend(file_))
296    return WriteAtCurrentPos(data, size);
297
298  DCHECK(IsValid());
299  if (size < 0)
300    return -1;
301
302  int bytes_written = 0;
303  int rv;
304  do {
305    rv = HANDLE_EINTR(pwrite(file_, data + bytes_written,
306                             size - bytes_written, offset + bytes_written));
307    if (rv <= 0)
308      break;
309
310    bytes_written += rv;
311  } while (bytes_written < size);
312
313  return bytes_written ? bytes_written : rv;
314}
315
316int File::WriteAtCurrentPos(const char* data, int size) {
317  base::ThreadRestrictions::AssertIOAllowed();
318  DCHECK(IsValid());
319  if (size < 0)
320    return -1;
321
322  int bytes_written = 0;
323  int rv;
324  do {
325    rv = HANDLE_EINTR(write(file_, data, size));
326    if (rv <= 0)
327      break;
328
329    bytes_written += rv;
330  } while (bytes_written < size);
331
332  return bytes_written ? bytes_written : rv;
333}
334
335int File::WriteAtCurrentPosNoBestEffort(const char* data, int size) {
336  base::ThreadRestrictions::AssertIOAllowed();
337  DCHECK(IsValid());
338  if (size < 0)
339    return -1;
340
341  return HANDLE_EINTR(write(file_, data, size));
342}
343
344bool File::Truncate(int64 length) {
345  base::ThreadRestrictions::AssertIOAllowed();
346  DCHECK(IsValid());
347  return !CallFtruncate(file_, length);
348}
349
350bool File::Flush() {
351  base::ThreadRestrictions::AssertIOAllowed();
352  DCHECK(IsValid());
353  return !CallFsync(file_);
354}
355
356bool File::SetTimes(Time last_access_time, Time last_modified_time) {
357  base::ThreadRestrictions::AssertIOAllowed();
358  DCHECK(IsValid());
359
360  timeval times[2];
361  times[0] = last_access_time.ToTimeVal();
362  times[1] = last_modified_time.ToTimeVal();
363
364  return !CallFutimes(file_, times);
365}
366
367bool File::GetInfo(Info* info) {
368  DCHECK(IsValid());
369
370  stat_wrapper_t file_info;
371  if (CallFstat(file_, &file_info))
372    return false;
373
374  info->is_directory = S_ISDIR(file_info.st_mode);
375  info->is_symbolic_link = S_ISLNK(file_info.st_mode);
376  info->size = file_info.st_size;
377
378#if defined(OS_LINUX)
379  const time_t last_modified_sec = file_info.st_mtim.tv_sec;
380  const int64 last_modified_nsec = file_info.st_mtim.tv_nsec;
381  const time_t last_accessed_sec = file_info.st_atim.tv_sec;
382  const int64 last_accessed_nsec = file_info.st_atim.tv_nsec;
383  const time_t creation_time_sec = file_info.st_ctim.tv_sec;
384  const int64 creation_time_nsec = file_info.st_ctim.tv_nsec;
385#elif defined(OS_ANDROID)
386  const time_t last_modified_sec = file_info.st_mtime;
387  const int64 last_modified_nsec = file_info.st_mtime_nsec;
388  const time_t last_accessed_sec = file_info.st_atime;
389  const int64 last_accessed_nsec = file_info.st_atime_nsec;
390  const time_t creation_time_sec = file_info.st_ctime;
391  const int64 creation_time_nsec = file_info.st_ctime_nsec;
392#elif defined(OS_MACOSX) || defined(OS_IOS) || defined(OS_BSD)
393  const time_t last_modified_sec = file_info.st_mtimespec.tv_sec;
394  const int64 last_modified_nsec = file_info.st_mtimespec.tv_nsec;
395  const time_t last_accessed_sec = file_info.st_atimespec.tv_sec;
396  const int64 last_accessed_nsec = file_info.st_atimespec.tv_nsec;
397  const time_t creation_time_sec = file_info.st_ctimespec.tv_sec;
398  const int64 creation_time_nsec = file_info.st_ctimespec.tv_nsec;
399#else
400  // TODO(gavinp): Investigate a good high resolution option for OS_NACL.
401  const time_t last_modified_sec = file_info.st_mtime;
402  const int64 last_modified_nsec = 0;
403  const time_t last_accessed_sec = file_info.st_atime;
404  const int64 last_accessed_nsec = 0;
405  const time_t creation_time_sec = file_info.st_ctime;
406  const int64 creation_time_nsec = 0;
407#endif
408
409  info->last_modified =
410      base::Time::FromTimeT(last_modified_sec) +
411      base::TimeDelta::FromMicroseconds(last_modified_nsec /
412                                        base::Time::kNanosecondsPerMicrosecond);
413  info->last_accessed =
414      base::Time::FromTimeT(last_accessed_sec) +
415      base::TimeDelta::FromMicroseconds(last_accessed_nsec /
416                                        base::Time::kNanosecondsPerMicrosecond);
417  info->creation_time =
418      base::Time::FromTimeT(creation_time_sec) +
419      base::TimeDelta::FromMicroseconds(creation_time_nsec /
420                                        base::Time::kNanosecondsPerMicrosecond);
421  return true;
422}
423
424File::Error File::Lock() {
425  return CallFctnlFlock(file_, true);
426}
427
428File::Error File::Unlock() {
429  return CallFctnlFlock(file_, false);
430}
431
432// Static.
433File::Error File::OSErrorToFileError(int saved_errno) {
434  switch (saved_errno) {
435    case EACCES:
436    case EISDIR:
437    case EROFS:
438    case EPERM:
439      return FILE_ERROR_ACCESS_DENIED;
440#if !defined(OS_NACL)  // ETXTBSY not defined by NaCl.
441    case ETXTBSY:
442      return FILE_ERROR_IN_USE;
443#endif
444    case EEXIST:
445      return FILE_ERROR_EXISTS;
446    case ENOENT:
447      return FILE_ERROR_NOT_FOUND;
448    case EMFILE:
449      return FILE_ERROR_TOO_MANY_OPENED;
450    case ENOMEM:
451      return FILE_ERROR_NO_MEMORY;
452    case ENOSPC:
453      return FILE_ERROR_NO_SPACE;
454    case ENOTDIR:
455      return FILE_ERROR_NOT_A_DIRECTORY;
456    default:
457#if !defined(OS_NACL)  // NaCl build has no metrics code.
458      UMA_HISTOGRAM_SPARSE_SLOWLY("PlatformFile.UnknownErrors.Posix",
459                                  saved_errno);
460#endif
461      return FILE_ERROR_FAILED;
462  }
463}
464
465void File::SetPlatformFile(PlatformFile file) {
466  DCHECK_EQ(file_, kInvalidPlatformFileValue);
467  file_ = file;
468}
469
470}  // namespace base
471