// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// For 64-bit file access (off_t = off64_t, lseek64, etc).
#define _FILE_OFFSET_BITS 64

#include "net/base/file_stream.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include "base/basictypes.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/eintr_wrapper.h"
#include "base/file_path.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/worker_pool.h"
#include "base/synchronization/waitable_event.h"
#include "net/base/file_stream_metrics.h"
#include "net/base/file_stream_net_log_parameters.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"

#if defined(OS_ANDROID)
// Android's bionic libc only supports the LFS transitional API.
#define off_t off64_t
#define lseek lseek64
#define stat stat64
#define fstat fstat64
#endif

namespace net {

// We cast back and forth, so make sure it's the size we're expecting.
COMPILE_ASSERT(sizeof(int64) == sizeof(off_t), off_t_64_bit);

// Make sure our Whence mappings match the system headers.
COMPILE_ASSERT(FROM_BEGIN   == SEEK_SET &&
               FROM_CURRENT == SEEK_CUR &&
               FROM_END     == SEEK_END, whence_matches_system);

namespace {

int RecordAndMapError(int error,
                      FileErrorSource source,
                      bool record_uma,
                      const net::BoundNetLog& bound_net_log) {
  net::Error net_error = MapSystemError(error);

  bound_net_log.AddEvent(
      net::NetLog::TYPE_FILE_STREAM_ERROR,
      base::Bind(&NetLogFileStreamErrorCallback,
                 source, error, net_error));

  RecordFileError(error, source, record_uma);

  return net_error;
}

// Opens a file with some network logging.
// The opened file and the result code are written to |file| and |result|.
void OpenFile(const FilePath& path,
              int open_flags,
              bool record_uma,
              base::PlatformFile* file,
              int* result,
              const net::BoundNetLog& bound_net_log) {
  std::string file_name = path.AsUTF8Unsafe();
  bound_net_log.BeginEvent(
      net::NetLog::TYPE_FILE_STREAM_OPEN,
      NetLog::StringCallback("file_name", &file_name));

  *result = OK;
  *file = base::CreatePlatformFile(path, open_flags, NULL, NULL);
  if (*file == base::kInvalidPlatformFileValue) {
    bound_net_log.EndEvent(net::NetLog::TYPE_FILE_STREAM_OPEN);
    *result = RecordAndMapError(errno, FILE_ERROR_SOURCE_OPEN, record_uma,
                                bound_net_log);
  }
}

// Opens a file using OpenFile() and then signals the completion.
void OpenFileAndSignal(const FilePath& path,
                       int open_flags,
                       bool record_uma,
                       base::PlatformFile* file,
                       int* result,
                       base::WaitableEvent* on_io_complete,
                       const net::BoundNetLog& bound_net_log) {
  OpenFile(path, open_flags, record_uma, file, result, bound_net_log);
  on_io_complete->Signal();
}

// Closes a file with some network logging.
void CloseFile(base::PlatformFile file,
               const net::BoundNetLog& bound_net_log) {
  bound_net_log.AddEvent(net::NetLog::TYPE_FILE_STREAM_CLOSE);
  if (file == base::kInvalidPlatformFileValue)
    return;

  if (!base::ClosePlatformFile(file))
    NOTREACHED();
  bound_net_log.EndEvent(net::NetLog::TYPE_FILE_STREAM_OPEN);
}

// Closes a file with CloseFile() and signals the completion.
void CloseFileAndSignal(base::PlatformFile* file,
                        base::WaitableEvent* on_io_complete,
                        const net::BoundNetLog& bound_net_log) {
  CloseFile(*file, bound_net_log);
  *file = base::kInvalidPlatformFileValue;
  on_io_complete->Signal();
}

// Adjusts the position from where the data is read.
void SeekFile(base::PlatformFile file,
              Whence whence,
              int64 offset,
              int64* result,
              bool record_uma,
              const net::BoundNetLog& bound_net_log) {
  off_t res = lseek(file, static_cast<off_t>(offset),
                    static_cast<int>(whence));
  if (res == static_cast<off_t>(-1)) {
    *result = RecordAndMapError(errno,
                                FILE_ERROR_SOURCE_SEEK,
                                record_uma,
                                bound_net_log);
    return;
  }
  *result = res;
}

// Seeks a file by calling SeekSync() and signals the completion.
void SeekFileAndSignal(base::PlatformFile file,
                       Whence whence,
                       int64 offset,
                       int64* result,
                       bool record_uma,
                       base::WaitableEvent* on_io_complete,
                       const net::BoundNetLog& bound_net_log) {
  SeekFile(file, whence, offset, result, record_uma, bound_net_log);
  on_io_complete->Signal();
}

// ReadFile() is a simple wrapper around read() that handles EINTR signals and
// calls MapSystemError() to map errno to net error codes.
void ReadFile(base::PlatformFile file,
              char* buf,
              int buf_len,
              bool record_uma,
              int* result,
              const net::BoundNetLog& bound_net_log) {
  base::ThreadRestrictions::AssertIOAllowed();
  // read(..., 0) returns 0 to indicate end-of-file.

  // Loop in the case of getting interrupted by a signal.
  ssize_t res = HANDLE_EINTR(read(file, buf, static_cast<size_t>(buf_len)));
  if (res == -1) {
    res = RecordAndMapError(errno, FILE_ERROR_SOURCE_READ,
                            record_uma, bound_net_log);
  }
  *result = res;
}

// Reads a file using ReadFile() and signals the completion.
void ReadFileAndSignal(base::PlatformFile file,
                       scoped_refptr<IOBuffer> buf,
                       int buf_len,
                       bool record_uma,
                       int* result,
                       base::WaitableEvent* on_io_complete,
                       const net::BoundNetLog& bound_net_log) {
  ReadFile(file, buf->data(), buf_len, record_uma, result, bound_net_log);
  on_io_complete->Signal();
}

// WriteFile() is a simple wrapper around write() that handles EINTR signals and
// calls MapSystemError() to map errno to net error codes.  It tries to write to
// completion.
void WriteFile(base::PlatformFile file,
               const char* buf,
               int buf_len,
               bool record_uma,
               int* result,
               const net::BoundNetLog& bound_net_log) {
  base::ThreadRestrictions::AssertIOAllowed();

  ssize_t res = HANDLE_EINTR(write(file, buf, buf_len));
  if (res == -1) {
    res = RecordAndMapError(errno, FILE_ERROR_SOURCE_WRITE, record_uma,
                            bound_net_log);
  }
  *result = res;
}

// Writes a file using WriteFile() and signals the completion.
void WriteFileAndSignal(base::PlatformFile file,
                        scoped_refptr<IOBuffer> buf,
                        int buf_len,
                        bool record_uma,
                        int* result,
                        base::WaitableEvent* on_io_complete,
                        const net::BoundNetLog& bound_net_log) {
  WriteFile(file, buf->data(), buf_len, record_uma, result, bound_net_log);
  on_io_complete->Signal();
}

// FlushFile() is a simple wrapper around fsync() that handles EINTR signals and
// calls MapSystemError() to map errno to net error codes.  It tries to flush to
// completion.
int FlushFile(base::PlatformFile file,
              bool record_uma,
              const net::BoundNetLog& bound_net_log) {
  base::ThreadRestrictions::AssertIOAllowed();
  ssize_t res = HANDLE_EINTR(fsync(file));
  if (res == -1) {
    res = RecordAndMapError(errno, FILE_ERROR_SOURCE_FLUSH, record_uma,
                            bound_net_log);
  }
  return res;
}

// Flushes a file using FlushFile() and signals the completion.
void FlushFileAndSignal(base::PlatformFile file,
                        int* result,
                        bool record_uma,
                        base::WaitableEvent* on_io_complete,
                        const net::BoundNetLog& bound_net_log) {
  *result = FlushFile(file, record_uma, bound_net_log);
  on_io_complete->Signal();
}

// Called when Read(), Write() or Seek() is completed.
// |result| contains the result or a network error code.
template <typename R>
void OnIOComplete(const base::WeakPtr<FileStreamPosix>& stream,
                  const base::Callback<void(R)>& callback,
                  R* result) {
  if (!stream.get())
    return;

  // Reset this before Run() as Run() may issue a new async operation.
  stream->ResetOnIOComplete();
  callback.Run(*result);
}

}  // namespace

// FileStreamPosix ------------------------------------------------------------

FileStreamPosix::FileStreamPosix(net::NetLog* net_log)
    : file_(base::kInvalidPlatformFileValue),
      open_flags_(0),
      auto_closed_(true),
      record_uma_(false),
      bound_net_log_(net::BoundNetLog::Make(net_log,
                                            net::NetLog::SOURCE_FILESTREAM)),
      weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
  bound_net_log_.BeginEvent(net::NetLog::TYPE_FILE_STREAM_ALIVE);
}

FileStreamPosix::FileStreamPosix(
    base::PlatformFile file, int flags, net::NetLog* net_log)
    : file_(file),
      open_flags_(flags),
      auto_closed_(false),
      record_uma_(false),
      bound_net_log_(net::BoundNetLog::Make(net_log,
                                            net::NetLog::SOURCE_FILESTREAM)),
      weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
  bound_net_log_.BeginEvent(net::NetLog::TYPE_FILE_STREAM_ALIVE);
}

FileStreamPosix::~FileStreamPosix() {
  if (open_flags_ & base::PLATFORM_FILE_ASYNC) {
    // Block until the last open/close/read/write operation is complete.
    // TODO(satorux): Ideally we should not block. crbug.com/115067
    WaitForIOCompletion();
  }

  if (auto_closed_) {
    if (open_flags_ & base::PLATFORM_FILE_ASYNC) {
      // Close the file in the background.
      if (IsOpen()) {
        const bool posted = base::WorkerPool::PostTask(
            FROM_HERE,
            base::Bind(&CloseFile, file_, bound_net_log_),
            true /* task_is_slow */);
        DCHECK(posted);
      }
    } else {
      CloseSync();
    }
  }

  bound_net_log_.EndEvent(net::NetLog::TYPE_FILE_STREAM_ALIVE);
}

void FileStreamPosix::Close(const CompletionCallback& callback) {
  DCHECK(open_flags_ & base::PLATFORM_FILE_ASYNC);

  DCHECK(!weak_ptr_factory_.HasWeakPtrs());
  DCHECK(!on_io_complete_.get());
  on_io_complete_.reset(new base::WaitableEvent(
      false  /* manual_reset */, false  /* initially_signaled */));

  // Passing &file_ to a thread pool looks unsafe but it's safe here as the
  // destructor ensures that the close operation is complete with
  // WaitForIOCompletion(). See also the destructor.
  const bool posted = base::WorkerPool::PostTaskAndReply(
      FROM_HERE,
      base::Bind(&CloseFileAndSignal, &file_, on_io_complete_.get(),
                 bound_net_log_),
      base::Bind(&FileStreamPosix::OnClosed,
                 weak_ptr_factory_.GetWeakPtr(),
                 callback),
      true /* task_is_slow */);

  DCHECK(posted);
}

void FileStreamPosix::CloseSync() {
  // TODO(satorux): Replace the following async stuff with a
  // DCHECK(open_flags & ASYNC) once once all async clients are migrated to
  // use Close(). crbug.com/114783

  // Abort any existing asynchronous operations.
  weak_ptr_factory_.InvalidateWeakPtrs();
  // Block until the last open/read/write operation is complete.
  // TODO(satorux): Ideally we should not block. crbug.com/115067
  WaitForIOCompletion();

  CloseFile(file_, bound_net_log_);
  file_ = base::kInvalidPlatformFileValue;
}

int FileStreamPosix::Open(const FilePath& path, int open_flags,
                     const CompletionCallback& callback) {
  if (IsOpen()) {
    DLOG(FATAL) << "File is already open!";
    return ERR_UNEXPECTED;
  }

  open_flags_ = open_flags;
  DCHECK(open_flags_ & base::PLATFORM_FILE_ASYNC);

  DCHECK(!weak_ptr_factory_.HasWeakPtrs());
  DCHECK(!on_io_complete_.get());
  on_io_complete_.reset(new base::WaitableEvent(
      false  /* manual_reset */, false  /* initially_signaled */));

  // Passing &file_ to a thread pool looks unsafe but it's safe here as the
  // destructor ensures that the open operation is complete with
  // WaitForIOCompletion(). See also the destructor.
  int* result = new int(OK);
  const bool posted = base::WorkerPool::PostTaskAndReply(
      FROM_HERE,
      base::Bind(&OpenFileAndSignal,
                 path, open_flags, record_uma_, &file_, result,
                 on_io_complete_.get(), bound_net_log_),
      base::Bind(&OnIOComplete<int>, weak_ptr_factory_.GetWeakPtr(),
                 callback, base::Owned(result)),
      true /* task_is_slow */);
  DCHECK(posted);
  return ERR_IO_PENDING;
}

int FileStreamPosix::OpenSync(const FilePath& path, int open_flags) {
  if (IsOpen()) {
    DLOG(FATAL) << "File is already open!";
    return ERR_UNEXPECTED;
  }

  open_flags_ = open_flags;
  // TODO(satorux): Put a DCHECK once once all async clients are migrated
  // to use Open(). crbug.com/114783
  //
  // DCHECK(!(open_flags_ & base::PLATFORM_FILE_ASYNC));

  int result = OK;
  OpenFile(path, open_flags_, record_uma_, &file_, &result, bound_net_log_);
  return result;
}

bool FileStreamPosix::IsOpen() const {
  return file_ != base::kInvalidPlatformFileValue;
}

int FileStreamPosix::Seek(Whence whence, int64 offset,
                          const Int64CompletionCallback& callback) {
  if (!IsOpen())
    return ERR_UNEXPECTED;

  // Make sure we're async and we have no other in-flight async operations.
  DCHECK(open_flags_ & base::PLATFORM_FILE_ASYNC);
  DCHECK(!weak_ptr_factory_.HasWeakPtrs());
  DCHECK(!on_io_complete_.get());

  on_io_complete_.reset(new base::WaitableEvent(
      false  /* manual_reset */, false  /* initially_signaled */));

  int64* result = new int64(-1);
  const bool posted = base::WorkerPool::PostTaskAndReply(
      FROM_HERE,
      base::Bind(&SeekFileAndSignal, file_, whence, offset, result,
                 record_uma_, on_io_complete_.get(), bound_net_log_),
      base::Bind(&OnIOComplete<int64>,
                 weak_ptr_factory_.GetWeakPtr(),
                 callback, base::Owned(result)),
      true /* task is slow */);
  DCHECK(posted);
  return ERR_IO_PENDING;
}

int64 FileStreamPosix::SeekSync(Whence whence, int64 offset) {
  base::ThreadRestrictions::AssertIOAllowed();

  if (!IsOpen())
    return ERR_UNEXPECTED;

  // If we're in async, make sure we don't have a request in flight.
  DCHECK(!(open_flags_ & base::PLATFORM_FILE_ASYNC) ||
         !on_io_complete_.get());

  off_t result = -1;
  SeekFile(file_, whence, offset, &result, record_uma_, bound_net_log_);
  return result;
}

int64 FileStreamPosix::Available() {
  base::ThreadRestrictions::AssertIOAllowed();

  if (!IsOpen())
    return ERR_UNEXPECTED;

  int64 cur_pos = SeekSync(FROM_CURRENT, 0);
  if (cur_pos < 0)
    return cur_pos;

  struct stat info;
  if (fstat(file_, &info) != 0) {
    return RecordAndMapError(errno,
                             FILE_ERROR_SOURCE_GET_SIZE,
                             record_uma_,
                             bound_net_log_);
  }

  int64 size = static_cast<int64>(info.st_size);
  DCHECK_GT(size, cur_pos);

  return size - cur_pos;
}

int FileStreamPosix::Read(
    IOBuffer* in_buf, int buf_len, const CompletionCallback& callback) {
  if (!IsOpen())
    return ERR_UNEXPECTED;

  // read(..., 0) will return 0, which indicates end-of-file.
  DCHECK_GT(buf_len, 0);
  DCHECK(open_flags_ & base::PLATFORM_FILE_READ);
  DCHECK(open_flags_ & base::PLATFORM_FILE_ASYNC);

  // Make sure we don't have a request in flight.
  DCHECK(!weak_ptr_factory_.HasWeakPtrs());
  DCHECK(!on_io_complete_.get());

  on_io_complete_.reset(new base::WaitableEvent(
      false  /* manual_reset */, false  /* initially_signaled */));

  int* result = new int(OK);
  scoped_refptr<IOBuffer> buf = in_buf;
  const bool posted = base::WorkerPool::PostTaskAndReply(
      FROM_HERE,
      base::Bind(&ReadFileAndSignal, file_, buf, buf_len,
                 record_uma_, result, on_io_complete_.get(), bound_net_log_),
      base::Bind(&OnIOComplete<int>,
                 weak_ptr_factory_.GetWeakPtr(),
                 callback, base::Owned(result)),
      true /* task is slow */);
  DCHECK(posted);
  return ERR_IO_PENDING;
}

int FileStreamPosix::ReadSync(char* buf, int buf_len) {
  if (!IsOpen())
    return ERR_UNEXPECTED;

  DCHECK(!(open_flags_ & base::PLATFORM_FILE_ASYNC));
  // read(..., 0) will return 0, which indicates end-of-file.
  DCHECK_GT(buf_len, 0);
  DCHECK(open_flags_ & base::PLATFORM_FILE_READ);

  int result = OK;
  ReadFile(file_, buf, buf_len, record_uma_, &result, bound_net_log_);
  return result;
}

int FileStreamPosix::ReadUntilComplete(char *buf, int buf_len) {
  int to_read = buf_len;
  int bytes_total = 0;

  do {
    int bytes_read = ReadSync(buf, to_read);
    if (bytes_read <= 0) {
      if (bytes_total == 0)
        return bytes_read;

      return bytes_total;
    }

    bytes_total += bytes_read;
    buf += bytes_read;
    to_read -= bytes_read;
  } while (bytes_total < buf_len);

  return bytes_total;
}

int FileStreamPosix::Write(
    IOBuffer* in_buf, int buf_len, const CompletionCallback& callback) {
  if (!IsOpen())
    return ERR_UNEXPECTED;

  DCHECK(open_flags_ & base::PLATFORM_FILE_ASYNC);
  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
  // write(..., 0) will return 0, which indicates end-of-file.
  DCHECK_GT(buf_len, 0);

  // Make sure we don't have a request in flight.
  DCHECK(!weak_ptr_factory_.HasWeakPtrs());
  DCHECK(!on_io_complete_.get());
  on_io_complete_.reset(new base::WaitableEvent(
      false  /* manual_reset */, false  /* initially_signaled */));

  int* result = new int(OK);
  scoped_refptr<IOBuffer> buf = in_buf;
  const bool posted = base::WorkerPool::PostTaskAndReply(
      FROM_HERE,
      base::Bind(&WriteFileAndSignal, file_, buf, buf_len,
                 record_uma_, result, on_io_complete_.get(), bound_net_log_),
      base::Bind(&OnIOComplete<int>,
                 weak_ptr_factory_.GetWeakPtr(),
                 callback, base::Owned(result)),
      true /* task is slow */);
  DCHECK(posted);
  return ERR_IO_PENDING;
}

int FileStreamPosix::WriteSync(
    const char* buf, int buf_len) {
  if (!IsOpen())
    return ERR_UNEXPECTED;

  DCHECK(!(open_flags_ & base::PLATFORM_FILE_ASYNC));
  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
  // write(..., 0) will return 0, which indicates end-of-file.
  DCHECK_GT(buf_len, 0);

  int result = OK;
  WriteFile(file_, buf, buf_len, record_uma_, &result, bound_net_log_);
  return result;
}

int64 FileStreamPosix::Truncate(int64 bytes) {
  base::ThreadRestrictions::AssertIOAllowed();

  if (!IsOpen())
    return ERR_UNEXPECTED;

  // We'd better be open for writing.
  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);

  // Seek to the position to truncate from.
  int64 seek_position = SeekSync(FROM_BEGIN, bytes);
  if (seek_position != bytes)
    return ERR_UNEXPECTED;

  // And truncate the file.
  int result = ftruncate(file_, bytes);
  if (result == 0)
    return seek_position;

  return RecordAndMapError(errno,
                           FILE_ERROR_SOURCE_SET_EOF,
                           record_uma_,
                           bound_net_log_);
}

int FileStreamPosix::Flush(const CompletionCallback& callback) {
  if (!IsOpen())
    return ERR_UNEXPECTED;

  // Make sure we're async and we have no other in-flight async operations.
  DCHECK(open_flags_ & base::PLATFORM_FILE_ASYNC);
  DCHECK(!weak_ptr_factory_.HasWeakPtrs());
  DCHECK(!on_io_complete_.get());

  on_io_complete_.reset(new base::WaitableEvent(
      false  /* manual_reset */, false  /* initially_signaled */));

  int* result = new int(OK);
  const bool posted = base::WorkerPool::PostTaskAndReply(
      FROM_HERE,
      base::Bind(&FlushFileAndSignal, file_, result,
                 record_uma_, on_io_complete_.get(), bound_net_log_),
      base::Bind(&OnIOComplete<int>,
                 weak_ptr_factory_.GetWeakPtr(),
                 callback, base::Owned(result)),
      true /* task is slow */);
  DCHECK(posted);
  return ERR_IO_PENDING;
}

int FileStreamPosix::FlushSync() {
  if (!IsOpen())
    return ERR_UNEXPECTED;

  return FlushFile(file_, record_uma_, bound_net_log_);
}

void FileStreamPosix::EnableErrorStatistics() {
  record_uma_ = true;
}

void FileStreamPosix::SetBoundNetLogSource(
    const net::BoundNetLog& owner_bound_net_log) {
  if ((owner_bound_net_log.source().id == net::NetLog::Source::kInvalidId) &&
      (bound_net_log_.source().id == net::NetLog::Source::kInvalidId)) {
    // Both |BoundNetLog|s are invalid.
    return;
  }

  // Should never connect to itself.
  DCHECK_NE(bound_net_log_.source().id, owner_bound_net_log.source().id);

  bound_net_log_.AddEvent(
      net::NetLog::TYPE_FILE_STREAM_BOUND_TO_OWNER,
      owner_bound_net_log.source().ToEventParametersCallback());

  owner_bound_net_log.AddEvent(
      net::NetLog::TYPE_FILE_STREAM_SOURCE,
      bound_net_log_.source().ToEventParametersCallback());
}

base::PlatformFile FileStreamPosix::GetPlatformFileForTesting() {
  return file_;
}

void FileStreamPosix::ResetOnIOComplete() {
  on_io_complete_.reset();
  weak_ptr_factory_.InvalidateWeakPtrs();
}

void FileStreamPosix::OnClosed(const CompletionCallback& callback) {
  file_ = base::kInvalidPlatformFileValue;

  // Reset this before Run() as Run() may issue a new async operation.
  ResetOnIOComplete();
  callback.Run(OK);
}

void FileStreamPosix::WaitForIOCompletion() {
  // http://crbug.com/115067
  base::ThreadRestrictions::ScopedAllowWait allow_wait;
  if (on_io_complete_.get()) {
    on_io_complete_->Wait();
    on_io_complete_.reset();
  }
}

}  // namespace net
