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