file_stream_posix.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
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// For 64-bit file access (off_t = off64_t, lseek64, etc).
6#define _FILE_OFFSET_BITS 64
7
8#include "net/base/file_stream.h"
9
10#include <sys/types.h>
11#include <sys/stat.h>
12#include <fcntl.h>
13#include <unistd.h>
14#include <errno.h>
15
16#include "base/basictypes.h"
17#include "base/callback.h"
18#include "base/eintr_wrapper.h"
19#include "base/file_path.h"
20#include "base/logging.h"
21#include "base/message_loop.h"
22#include "base/metrics/histogram.h"
23#include "base/string_util.h"
24#include "base/waitable_event.h"
25#include "base/worker_pool.h"
26#include "net/base/net_errors.h"
27
28// We cast back and forth, so make sure it's the size we're expecting.
29#if defined(__BIONIC__) && defined(ANDROID)
30COMPILE_ASSERT(sizeof(int32) == sizeof(off_t), off_t_32_bit);
31#else
32COMPILE_ASSERT(sizeof(int64) == sizeof(off_t), off_t_64_bit);
33#endif
34
35// Make sure our Whence mappings match the system headers.
36COMPILE_ASSERT(net::FROM_BEGIN   == SEEK_SET &&
37               net::FROM_CURRENT == SEEK_CUR &&
38               net::FROM_END     == SEEK_END, whence_matches_system);
39
40namespace net {
41namespace {
42
43// Map from errno to net error codes.
44int64 MapErrorCode(int err) {
45  switch (err) {
46    case ENOENT:
47      return ERR_FILE_NOT_FOUND;
48    case EACCES:
49      return ERR_ACCESS_DENIED;
50    default:
51      LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED";
52      return ERR_FAILED;
53  }
54}
55
56// ReadFile() is a simple wrapper around read() that handles EINTR signals and
57// calls MapErrorCode() to map errno to net error codes.
58int ReadFile(base::PlatformFile file, char* buf, int buf_len) {
59  // read(..., 0) returns 0 to indicate end-of-file.
60
61  // Loop in the case of getting interrupted by a signal.
62  ssize_t res = HANDLE_EINTR(read(file, buf, static_cast<size_t>(buf_len)));
63  if (res == static_cast<ssize_t>(-1))
64    return MapErrorCode(errno);
65  return static_cast<int>(res);
66}
67
68// WriteFile() is a simple wrapper around write() that handles EINTR signals and
69// calls MapErrorCode() to map errno to net error codes.  It tries to write to
70// completion.
71int WriteFile(base::PlatformFile file, const char* buf, int buf_len) {
72  ssize_t res = HANDLE_EINTR(write(file, buf, buf_len));
73  if (res == -1)
74    return MapErrorCode(errno);
75  return res;
76}
77
78// FlushFile() is a simple wrapper around fsync() that handles EINTR signals and
79// calls MapErrorCode() to map errno to net error codes.  It tries to flush to
80// completion.
81int FlushFile(base::PlatformFile file) {
82  ssize_t res = HANDLE_EINTR(fsync(file));
83  if (res == -1)
84    return MapErrorCode(errno);
85  return res;
86}
87
88// BackgroundReadTask is a simple task that reads a file and then runs
89// |callback|.  AsyncContext will post this task to the WorkerPool.
90class BackgroundReadTask : public Task {
91 public:
92  BackgroundReadTask(base::PlatformFile file, char* buf, int buf_len,
93                     CompletionCallback* callback);
94  ~BackgroundReadTask();
95
96  virtual void Run();
97
98 private:
99  const base::PlatformFile file_;
100  char* const buf_;
101  const int buf_len_;
102  CompletionCallback* const callback_;
103
104  DISALLOW_COPY_AND_ASSIGN(BackgroundReadTask);
105};
106
107BackgroundReadTask::BackgroundReadTask(
108    base::PlatformFile file, char* buf, int buf_len,
109    CompletionCallback* callback)
110    : file_(file), buf_(buf), buf_len_(buf_len), callback_(callback) {}
111
112BackgroundReadTask::~BackgroundReadTask() {}
113
114void BackgroundReadTask::Run() {
115  int result = ReadFile(file_, buf_, buf_len_);
116  callback_->Run(result);
117}
118
119// BackgroundWriteTask is a simple task that writes to a file and then runs
120// |callback|.  AsyncContext will post this task to the WorkerPool.
121class BackgroundWriteTask : public Task {
122 public:
123  BackgroundWriteTask(base::PlatformFile file, const char* buf, int buf_len,
124                      CompletionCallback* callback);
125  ~BackgroundWriteTask();
126
127  virtual void Run();
128
129 private:
130  const base::PlatformFile file_;
131  const char* const buf_;
132  const int buf_len_;
133  CompletionCallback* const callback_;
134
135  DISALLOW_COPY_AND_ASSIGN(BackgroundWriteTask);
136};
137
138BackgroundWriteTask::BackgroundWriteTask(
139    base::PlatformFile file, const char* buf, int buf_len,
140    CompletionCallback* callback)
141    : file_(file), buf_(buf), buf_len_(buf_len), callback_(callback) {}
142
143BackgroundWriteTask::~BackgroundWriteTask() {}
144
145void BackgroundWriteTask::Run() {
146  int result = WriteFile(file_, buf_, buf_len_);
147  callback_->Run(result);
148}
149
150}  // namespace
151
152// CancelableCallbackTask takes ownership of the Callback.  This task gets
153// posted to the MessageLoopForIO instance.
154class CancelableCallbackTask : public CancelableTask {
155 public:
156  explicit CancelableCallbackTask(Callback0::Type* callback)
157      : canceled_(false), callback_(callback) {}
158
159  virtual void Run() {
160    if (!canceled_)
161      callback_->Run();
162  }
163
164  virtual void Cancel() {
165    canceled_ = true;
166  }
167
168 private:
169  bool canceled_;
170  scoped_ptr<Callback0::Type> callback_;
171};
172
173// FileStream::AsyncContext ----------------------------------------------
174
175class FileStream::AsyncContext {
176 public:
177  AsyncContext();
178  ~AsyncContext();
179
180  // These methods post synchronous read() and write() calls to a WorkerThread.
181  void InitiateAsyncRead(
182      base::PlatformFile file, char* buf, int buf_len,
183      CompletionCallback* callback);
184  void InitiateAsyncWrite(
185      base::PlatformFile file, const char* buf, int buf_len,
186      CompletionCallback* callback);
187
188  CompletionCallback* callback() const { return callback_; }
189
190  // Called by the WorkerPool thread executing the IO after the IO completes.
191  // This method queues RunAsynchronousCallback() on the MessageLoop and signals
192  // |background_io_completed_callback_|, in case the destructor is waiting.  In
193  // that case, the destructor will call RunAsynchronousCallback() instead, and
194  // cancel |message_loop_task_|.
195  // |result| is the result of the Read/Write task.
196  void OnBackgroundIOCompleted(int result);
197
198 private:
199  // Always called on the IO thread, either directly by a task on the
200  // MessageLoop or by ~AsyncContext().
201  void RunAsynchronousCallback();
202
203  // The MessageLoopForIO that this AsyncContext is running on.
204  MessageLoopForIO* const message_loop_;
205  CompletionCallback* callback_;  // The user provided callback.
206
207  // A callback wrapper around OnBackgroundIOCompleted().  Run by the WorkerPool
208  // thread doing the background IO on our behalf.
209  CompletionCallbackImpl<AsyncContext> background_io_completed_callback_;
210
211  // This is used to synchronize between the AsyncContext destructor (which runs
212  // on the IO thread and OnBackgroundIOCompleted() which runs on the WorkerPool
213  // thread.
214  base::WaitableEvent background_io_completed_;
215
216  // These variables are only valid when background_io_completed is signaled.
217  int result_;
218  CancelableCallbackTask* message_loop_task_;
219
220  bool is_closing_;
221
222  DISALLOW_COPY_AND_ASSIGN(AsyncContext);
223};
224
225FileStream::AsyncContext::AsyncContext()
226    : message_loop_(MessageLoopForIO::current()),
227      callback_(NULL),
228      background_io_completed_callback_(
229          this, &AsyncContext::OnBackgroundIOCompleted),
230      background_io_completed_(true, false),
231      message_loop_task_(NULL),
232      is_closing_(false) {}
233
234FileStream::AsyncContext::~AsyncContext() {
235  is_closing_ = true;
236  if (callback_) {
237    // If |callback_| is non-NULL, that implies either the worker thread is
238    // still running the IO task, or the completion callback is queued up on the
239    // MessageLoopForIO, but AsyncContext() got deleted before then.
240    const bool need_to_wait = !background_io_completed_.IsSignaled();
241    base::TimeTicks start = base::TimeTicks::Now();
242    RunAsynchronousCallback();
243    if (need_to_wait) {
244      // We want to see if we block the message loop for too long.
245      UMA_HISTOGRAM_TIMES("AsyncIO.FileStreamClose",
246                          base::TimeTicks::Now() - start);
247    }
248  }
249}
250
251void FileStream::AsyncContext::InitiateAsyncRead(
252    base::PlatformFile file, char* buf, int buf_len,
253    CompletionCallback* callback) {
254  DCHECK(!callback_);
255  callback_ = callback;
256
257  WorkerPool::PostTask(FROM_HERE,
258                       new BackgroundReadTask(
259                           file, buf, buf_len,
260                           &background_io_completed_callback_),
261                       true /* task_is_slow */);
262}
263
264void FileStream::AsyncContext::InitiateAsyncWrite(
265    base::PlatformFile file, const char* buf, int buf_len,
266    CompletionCallback* callback) {
267  DCHECK(!callback_);
268  callback_ = callback;
269
270  WorkerPool::PostTask(FROM_HERE,
271                       new BackgroundWriteTask(
272                           file, buf, buf_len,
273                           &background_io_completed_callback_),
274                       true /* task_is_slow */);
275}
276
277void FileStream::AsyncContext::OnBackgroundIOCompleted(int result) {
278  result_ = result;
279  message_loop_task_ = new CancelableCallbackTask(
280      NewCallback(this, &AsyncContext::RunAsynchronousCallback));
281  message_loop_->PostTask(FROM_HERE, message_loop_task_);
282  background_io_completed_.Signal();
283}
284
285void FileStream::AsyncContext::RunAsynchronousCallback() {
286  // Wait() here ensures that all modifications from the WorkerPool thread are
287  // now visible.
288  background_io_completed_.Wait();
289
290  // Either we're in the MessageLoop's task, in which case Cancel() doesn't do
291  // anything, or we're in ~AsyncContext(), in which case this prevents the call
292  // from happening again.  Must do it here after calling Wait().
293  message_loop_task_->Cancel();
294  message_loop_task_ = NULL;
295
296  if (is_closing_) {
297    callback_ = NULL;
298    return;
299  }
300
301  DCHECK(callback_);
302  CompletionCallback* temp = NULL;
303  std::swap(temp, callback_);
304  background_io_completed_.Reset();
305  temp->Run(result_);
306}
307
308// FileStream ------------------------------------------------------------
309
310FileStream::FileStream()
311    : file_(base::kInvalidPlatformFileValue),
312      open_flags_(0),
313      auto_closed_(true) {
314  DCHECK(!IsOpen());
315}
316
317FileStream::FileStream(base::PlatformFile file, int flags)
318    : file_(file),
319      open_flags_(flags),
320      auto_closed_(false) {
321  // If the file handle is opened with base::PLATFORM_FILE_ASYNC, we need to
322  // make sure we will perform asynchronous File IO to it.
323  if (flags & base::PLATFORM_FILE_ASYNC) {
324    async_context_.reset(new AsyncContext());
325  }
326}
327
328FileStream::~FileStream() {
329  if (auto_closed_)
330    Close();
331}
332
333void FileStream::Close() {
334  // Abort any existing asynchronous operations.
335  async_context_.reset();
336
337  if (file_ != base::kInvalidPlatformFileValue) {
338    if (close(file_) != 0) {
339      NOTREACHED();
340    }
341    file_ = base::kInvalidPlatformFileValue;
342  }
343}
344
345int FileStream::Open(const FilePath& path, int open_flags) {
346  if (IsOpen()) {
347    DLOG(FATAL) << "File is already open!";
348    return ERR_UNEXPECTED;
349  }
350
351  open_flags_ = open_flags;
352  file_ = base::CreatePlatformFile(path, open_flags_, NULL, NULL);
353  if (file_ == base::kInvalidPlatformFileValue) {
354    return MapErrorCode(errno);
355  }
356
357  if (open_flags_ & base::PLATFORM_FILE_ASYNC) {
358    async_context_.reset(new AsyncContext());
359  }
360
361  return OK;
362}
363
364bool FileStream::IsOpen() const {
365  return file_ != base::kInvalidPlatformFileValue;
366}
367
368int64 FileStream::Seek(Whence whence, int64 offset) {
369  if (!IsOpen())
370    return ERR_UNEXPECTED;
371
372  // If we're in async, make sure we don't have a request in flight.
373  DCHECK(!async_context_.get() || !async_context_->callback());
374
375  off_t res = lseek(file_, static_cast<off_t>(offset),
376                    static_cast<int>(whence));
377  if (res == static_cast<off_t>(-1))
378    return MapErrorCode(errno);
379
380  return res;
381}
382
383int64 FileStream::Available() {
384  if (!IsOpen())
385    return ERR_UNEXPECTED;
386
387  int64 cur_pos = Seek(FROM_CURRENT, 0);
388  if (cur_pos < 0)
389    return cur_pos;
390
391  struct stat info;
392  if (fstat(file_, &info) != 0)
393    return MapErrorCode(errno);
394
395  int64 size = static_cast<int64>(info.st_size);
396  DCHECK_GT(size, cur_pos);
397
398  return size - cur_pos;
399}
400
401int FileStream::Read(
402    char* buf, int buf_len, CompletionCallback* callback) {
403  if (!IsOpen())
404    return ERR_UNEXPECTED;
405
406  // read(..., 0) will return 0, which indicates end-of-file.
407  DCHECK(buf_len > 0);
408  DCHECK(open_flags_ & base::PLATFORM_FILE_READ);
409
410  if (async_context_.get()) {
411    DCHECK(open_flags_ & base::PLATFORM_FILE_ASYNC);
412    // If we're in async, make sure we don't have a request in flight.
413    DCHECK(!async_context_->callback());
414    async_context_->InitiateAsyncRead(file_, buf, buf_len, callback);
415    return ERR_IO_PENDING;
416  } else {
417    return ReadFile(file_, buf, buf_len);
418  }
419}
420
421int FileStream::ReadUntilComplete(char *buf, int buf_len) {
422  int to_read = buf_len;
423  int bytes_total = 0;
424
425  do {
426    int bytes_read = Read(buf, to_read, NULL);
427    if (bytes_read <= 0) {
428      if (bytes_total == 0)
429        return bytes_read;
430
431      return bytes_total;
432    }
433
434    bytes_total += bytes_read;
435    buf += bytes_read;
436    to_read -= bytes_read;
437  } while (bytes_total < buf_len);
438
439  return bytes_total;
440}
441
442int FileStream::Write(
443    const char* buf, int buf_len, CompletionCallback* callback) {
444  // write(..., 0) will return 0, which indicates end-of-file.
445  DCHECK(buf_len > 0);
446
447  if (!IsOpen())
448    return ERR_UNEXPECTED;
449
450  if (async_context_.get()) {
451    DCHECK(open_flags_ & base::PLATFORM_FILE_ASYNC);
452    // If we're in async, make sure we don't have a request in flight.
453    DCHECK(!async_context_->callback());
454    async_context_->InitiateAsyncWrite(file_, buf, buf_len, callback);
455    return ERR_IO_PENDING;
456  } else {
457    return WriteFile(file_, buf, buf_len);
458  }
459}
460
461int FileStream::Flush() {
462  if (!IsOpen())
463    return ERR_UNEXPECTED;
464
465  return FlushFile(file_);
466}
467
468int64 FileStream::Truncate(int64 bytes) {
469  if (!IsOpen())
470    return ERR_UNEXPECTED;
471
472  // We better be open for reading.
473  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
474
475  // Seek to the position to truncate from.
476  int64 seek_position = Seek(FROM_BEGIN, bytes);
477  if (seek_position != bytes)
478    return ERR_UNEXPECTED;
479
480  // And truncate the file.
481  int result = ftruncate(file_, bytes);
482  return result == 0 ? seek_position : MapErrorCode(errno);
483}
484
485}  // namespace net
486