Prototype of Files service.

Defines two important interfaces, Directory and File. Provides a basic,
single-threaded implementation of the latter (with a few omissions), an
extremely incomplete implementation of the former (more complete
implementation to follow soon, with tests), and a root Files
interface/application.

Limitations:
* Directory isn't really implemented.
* Makes no security guarantees -- i.e., definitely insecure (doesn't
  prevent path traversal "out of" a "file system").
* Not implemented: file streaming (read/write), file mapping, file
  re-opening.
* Various other flags not implemented (e.g., recursive delete).
* Theoretical implementation: many things as yet totally untested.
* Single-threaded. Should be easy enough to move to a thread pool (but
  note that operations still need to be sequenced within each
  "object"/message pipe).
* Still not specified (but definitely desired): directory streaming.
* Still need more error codes.
* Still needs more comments and documentation.

Changes over previous iterations:
* Temporarily removed most of (totally untested) Directory
  implementation.
* "file manager" -> "files".
* Introduced a Timespec struct (and nanosecond resolution).
* Turned persistent, shared "user" directory into a "debug" directory.
* Various other requested features added/changes made.

Open questions:
* There's some duplication between Directory and File (e.g., they both
  have Stat()). Perhaps it'd be more POSIX-y to combine them?
* Semantics for "chroot"-type operations and "re-open" operations.
* (Lots of thinking with respect to security in general.)

R=qsr@chromium.org

Review URL: https://codereview.chromium.org/875643004
diff --git a/services/files/BUILD.gn b/services/files/BUILD.gn
new file mode 100644
index 0000000..e5ae2a7
--- /dev/null
+++ b/services/files/BUILD.gn
@@ -0,0 +1,60 @@
+# Copyright 2014 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.
+
+import("//mojo/public/mojo_application.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojo_native_application("files") {
+  sources = [
+    "directory_impl.cc",
+    "directory_impl.h",
+    "file_impl.cc",
+    "file_impl.h",
+    "files_impl.cc",
+    "files_impl.h",
+    "futimens.h",
+    "futimens_android.cc",
+    "main.cc",
+    "util.cc",
+    "util.h",
+  ]
+
+  deps = [
+    ":bindings",
+    "//base",
+    "//mojo/application",
+    "//mojo/common",
+    "//mojo/public/cpp/application",
+    "//mojo/public/cpp/system",
+  ]
+}
+
+mojom("bindings") {
+  sources = [
+    "directory.mojom",
+    "file.mojom",
+    "files.mojom",
+    "types.mojom",
+  ]
+}
+
+mojo_native_application("apptests") {
+  output_name = "files_apptests"
+
+  testonly = true
+
+  sources = [
+    "files_apptest.cc",
+  ]
+
+  deps = [
+    ":bindings",
+    "//base",
+    "//mojo/application",
+    "//mojo/application:test_support",
+    "//mojo/public/cpp/bindings",
+  ]
+
+  data_deps = [ ":files" ]
+}
diff --git a/services/files/directory.mojom b/services/files/directory.mojom
new file mode 100644
index 0000000..8be25da
--- /dev/null
+++ b/services/files/directory.mojom
@@ -0,0 +1,63 @@
+// Copyright 2015 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.
+
+module mojo.files;
+
+import "services/files/file.mojom";
+import "services/files/types.mojom";
+
+// This interface provides access to a directory in a "file system", providing
+// operations such as creating/opening/removing/renaming files/directories
+// within it. Note that all relative |path| arguments are relative to "this"
+// directory (i.e., "this" directory functions as the current working directory
+// for the various operations).
+// TODO(vtl): Paths may be relative; should they allowed to be absolute?
+// (Currently not.)
+interface Directory {
+  // Operations about "this" |Directory|:
+
+  // Reads the contents of this directory.
+  // TODO(vtl): Clarify error codes versus |directory_contents|.
+  Read() => (Error error, array<DirectoryEntry>? directory_contents);
+
+  // Gets information about this directory. On success, |file_information| is
+  // non-null and will contain this information.
+  Stat() => (Error error, FileInformation? file_information);
+
+  // Updates this directory's atime and/or mtime to the time specified by
+  // |atime| (or |mtime|, respectively), which may also indicate "now". If
+  // |atime| or |mtime| is null, then the corresponding time is not modified.
+  Touch(TimespecOrNow? atime, TimespecOrNow? mtime) => (Error error);
+
+  // Operations *in* "this" |Directory|:
+
+  // Opens the file specified by |path| with the given |open_flags|. |file| is
+  // optional, mainly for consistency with |OpenDirectory()| (but may be useful,
+  // together with |kOpenFlagCreate|, for "touching" a file).
+  OpenFile(string path, File&? file, uint32 open_flags)
+      => (Error error);
+
+  // Opens the directory specified by |path|. |directory| is optional, so that
+  // this may be used as a simple "mkdir()" with |kOpenFlagCreate|.
+  OpenDirectory(string path,
+                Directory&? directory,
+                uint32 open_flags) => (Error error);
+
+  // Renames/moves the file/directory given by |path| to |new_path|.
+  Rename(string path, string new_path) => (Error error);
+
+  // Deletes the given path, which may be a file or a directory (see
+  // |kDeleteFlag...| for details).
+  Delete(string path, uint32 delete_flags) => (Error error);
+
+  // TODO(vtl): directory "streaming"?
+  // TODO(vtl): "make root" (i.e., prevent cd-ing, etc., to parent); note that
+  // this would require a much more complicated implementation (e.g., it needs
+  // to be "inherited" by OpenDirectory(), and the enforcement needs to be valid
+  // even if the opened directory is subsequently moved -- e.g., closer to the
+  // "root")
+  // TODO(vtl): Add a "watch"?
+  // TODO(vtl): Should we have a "close" method?
+  // TODO(vtl): Add Dup() and Reopen() (like File)?
+};
diff --git a/services/files/directory_impl.cc b/services/files/directory_impl.cc
new file mode 100644
index 0000000..c346ce6
--- /dev/null
+++ b/services/files/directory_impl.cc
@@ -0,0 +1,194 @@
+// Copyright 2015 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.
+
+#include "services/files/directory_impl.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/posix/eintr_wrapper.h"
+#include "build/build_config.h"
+#include "services/files/file_impl.h"
+#include "services/files/util.h"
+
+namespace mojo {
+namespace files {
+
+namespace {
+
+Error ValidateOpenFlags(uint32_t open_flags, bool is_directory) {
+  // Treat unknown flags as "unimplemented".
+  if ((open_flags &
+       ~(kOpenFlagRead | kOpenFlagWrite | kOpenFlagCreate | kOpenFlagExclusive |
+         kOpenFlagAppend | kOpenFlagTruncate)))
+    return ERROR_UNIMPLEMENTED;
+
+  // At least one of |kOpenFlagRead| or |kOpenFlagWrite| must be set.
+  if (!(open_flags & (kOpenFlagRead | kOpenFlagWrite)))
+    return ERROR_INVALID_ARGUMENT;
+
+  // |kOpenFlagCreate| requires |kOpenFlagWrite|.
+  if ((open_flags & kOpenFlagCreate) && !(open_flags & kOpenFlagWrite))
+    return ERROR_INVALID_ARGUMENT;
+
+  // |kOpenFlagExclusive| requires |kOpenFlagCreate|.
+  if ((open_flags & kOpenFlagExclusive) && !(open_flags & kOpenFlagCreate))
+    return ERROR_INVALID_ARGUMENT;
+
+  if (is_directory) {
+    // Check that file-only flags aren't set.
+    if ((open_flags & (kOpenFlagAppend | kOpenFlagTruncate)))
+      return ERROR_INVALID_ARGUMENT;
+    return ERROR_OK;
+  }
+
+  // File-only flags:
+
+  // |kOpenFlagAppend| requires |kOpenFlagWrite|.
+  if ((open_flags & kOpenFlagAppend) && !(open_flags & kOpenFlagWrite))
+    return ERROR_INVALID_ARGUMENT;
+
+  // |kOpenFlagTruncate| requires |kOpenFlagWrite|.
+  if ((open_flags & kOpenFlagTruncate) && !(open_flags & kOpenFlagWrite))
+    return ERROR_INVALID_ARGUMENT;
+
+  return ERROR_OK;
+}
+
+}  // namespace
+
+DirectoryImpl::DirectoryImpl(InterfaceRequest<Directory> request,
+                             base::ScopedFD dir_fd,
+                             scoped_ptr<base::ScopedTempDir> temp_dir)
+    : binding_(this, request.Pass()),
+      dir_fd_(dir_fd.Pass()),
+      temp_dir_(temp_dir.Pass()) {
+  DCHECK(dir_fd_.is_valid());
+}
+
+DirectoryImpl::~DirectoryImpl() {
+}
+
+void DirectoryImpl::Read(
+    const Callback<void(Error, Array<DirectoryEntryPtr>)>& callback) {
+  // TODO(vtl): FIXME sooner
+  NOTIMPLEMENTED();
+  callback.Run(ERROR_UNIMPLEMENTED, Array<DirectoryEntryPtr>());
+}
+
+void DirectoryImpl::Stat(
+    const Callback<void(Error, FileInformationPtr)>& callback) {
+  // TODO(vtl): FIXME sooner
+  NOTIMPLEMENTED();
+  callback.Run(ERROR_UNIMPLEMENTED, nullptr);
+}
+
+void DirectoryImpl::Touch(TimespecOrNowPtr atime,
+                          TimespecOrNowPtr mtime,
+                          const Callback<void(Error)>& callback) {
+  // TODO(vtl): FIXME sooner
+  NOTIMPLEMENTED();
+  callback.Run(ERROR_UNIMPLEMENTED);
+}
+
+// TODO(vtl): Move the implementation to a thread pool.
+void DirectoryImpl::OpenFile(const String& path,
+                             InterfaceRequest<File> file,
+                             uint32_t open_flags,
+                             const Callback<void(Error)>& callback) {
+  DCHECK(!path.is_null());
+  DCHECK(dir_fd_.is_valid());
+
+  if (Error error = IsPathValid(path)) {
+    callback.Run(error);
+    return;
+  }
+  // TODO(vtl): Make sure the path doesn't exit this directory (if appropriate).
+  // TODO(vtl): Maybe allow absolute paths?
+
+  if (Error error = ValidateOpenFlags(open_flags, false)) {
+    callback.Run(error);
+    return;
+  }
+
+  int flags = 0;
+  if ((open_flags & kOpenFlagRead))
+    flags |= (open_flags & kOpenFlagWrite) ? O_RDWR : O_RDONLY;
+  else
+    flags |= O_WRONLY;
+  if ((open_flags & kOpenFlagCreate))
+    flags |= O_CREAT;
+  if ((open_flags & kOpenFlagExclusive))
+    flags |= O_EXCL;
+  if ((open_flags & kOpenFlagAppend))
+    flags |= O_APPEND;
+  if ((open_flags & kOpenFlagTruncate))
+    flags |= O_TRUNC;
+
+  base::ScopedFD file_fd(
+      HANDLE_EINTR(openat(dir_fd_.get(), path.get().c_str(), flags, 0600)));
+  if (!file_fd.is_valid()) {
+    callback.Run(ErrnoToError(errno));
+    return;
+  }
+
+  if (file.is_pending())
+    new FileImpl(file.Pass(), file_fd.Pass());
+  callback.Run(ERROR_OK);
+}
+
+void DirectoryImpl::OpenDirectory(const String& path,
+                                  InterfaceRequest<Directory> directory,
+                                  uint32_t open_flags,
+                                  const Callback<void(Error)>& callback) {
+  // TODO(vtl): FIXME sooner
+  NOTIMPLEMENTED();
+  callback.Run(ERROR_UNIMPLEMENTED);
+}
+
+void DirectoryImpl::Rename(const String& path,
+                           const String& new_path,
+                           const Callback<void(Error)>& callback) {
+  DCHECK(!path.is_null());
+  DCHECK(!new_path.is_null());
+  DCHECK(dir_fd_.is_valid());
+
+  if (Error error = IsPathValid(path)) {
+    callback.Run(error);
+    return;
+  }
+  if (Error error = IsPathValid(new_path)) {
+    callback.Run(error);
+    return;
+  }
+  // TODO(vtl): see TODOs about |path| in OpenFile()
+
+  if (renameat(dir_fd_.get(), path.get().c_str(), dir_fd_.get(),
+               new_path.get().c_str())) {
+    callback.Run(ErrnoToError(errno));
+    return;
+  }
+
+  callback.Run(ERROR_OK);
+}
+
+void DirectoryImpl::Delete(const String& path,
+                           uint32_t delete_flags,
+                           const Callback<void(Error)>& callback) {
+  // TODO(vtl): FIXME sooner
+  NOTIMPLEMENTED();
+  callback.Run(ERROR_UNIMPLEMENTED);
+}
+
+}  // namespace files
+}  // namespace mojo
diff --git a/services/files/directory_impl.h b/services/files/directory_impl.h
new file mode 100644
index 0000000..933247a
--- /dev/null
+++ b/services/files/directory_impl.h
@@ -0,0 +1,64 @@
+// Copyright 2015 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.
+
+#ifndef SERVICES_FILES_DIRECTORY_IMPL_H_
+#define SERVICES_FILES_DIRECTORY_IMPL_H_
+
+#include "base/files/scoped_file.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "services/files/directory.mojom.h"
+
+namespace base {
+class ScopedTempDir;
+}  // namespace base
+
+namespace mojo {
+namespace files {
+
+class DirectoryImpl : public Directory {
+ public:
+  // Set |temp_dir| only if there's a temporary directory that should be deleted
+  // when this object is destroyed.
+  DirectoryImpl(InterfaceRequest<Directory> request,
+                base::ScopedFD dir_fd,
+                scoped_ptr<base::ScopedTempDir> temp_dir);
+  ~DirectoryImpl() override;
+
+  // |Directory| implementation:
+  void Read(
+      const Callback<void(Error, Array<DirectoryEntryPtr>)>& callback) override;
+  void Stat(const Callback<void(Error, FileInformationPtr)>& callback) override;
+  void Touch(TimespecOrNowPtr atime,
+             TimespecOrNowPtr mtime,
+             const Callback<void(Error)>& callback) override;
+  void OpenFile(const String& path,
+                InterfaceRequest<File> file,
+                uint32_t open_flags,
+                const Callback<void(Error)>& callback) override;
+  void OpenDirectory(const String& path,
+                     InterfaceRequest<Directory> directory,
+                     uint32_t open_flags,
+                     const Callback<void(Error)>& callback) override;
+  void Rename(const String& path,
+              const String& new_path,
+              const Callback<void(Error)>& callback) override;
+  void Delete(const String& path,
+              uint32_t delete_flags,
+              const Callback<void(Error)>& callback) override;
+
+ private:
+  StrongBinding<Directory> binding_;
+  base::ScopedFD dir_fd_;
+  scoped_ptr<base::ScopedTempDir> temp_dir_;
+
+  DISALLOW_COPY_AND_ASSIGN(DirectoryImpl);
+};
+
+}  // namespace files
+}  // namespace mojo
+
+#endif  // SERVICES_FILES_DIRECTORY_IMPL_H_
diff --git a/services/files/file.mojom b/services/files/file.mojom
new file mode 100644
index 0000000..14a3883
--- /dev/null
+++ b/services/files/file.mojom
@@ -0,0 +1,85 @@
+// Copyright 2015 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.
+
+// TODO(vtl): notes to self:
+//  - file offsets, file positions, and file sizes are int64 (though positions
+//    and sizes must always be non-negative)
+//  - buffer size parameters (for read/write) are uint32
+
+module mojo.files;
+
+import "services/files/types.mojom";
+
+// TODO(vtl): Write comments.
+interface File {
+  // Flushes/closes this file; no operations may be performed on this file after
+  // this. Note that any error code is strictly informational -- the close may
+  // not be retried.
+  Close() => (Error err);
+
+  // Reads (at most) |num_bytes_to_read| from the location specified by
+  // |offset|/|whence|. On success, |bytes_read| is set to the data read.
+  // TODO(vtl): Define/clarify behavior when less than |num_bytes_to_read| bytes
+  // are read.
+  // TODO(vtl): Clarify when (for what values of |offset|/|whence|) this
+  // modifies the file position. Or maybe there should be a flag?
+  Read(uint32 num_bytes_to_read, int64 offset, Whence whence)
+      => (Error error, array<uint8>? bytes_read);
+
+  // Writes |bytes_to_write| to the location specified by |offset|/|whence|.
+  // TODO(vtl): Clarify behavior when |num_bytes_written| is less than the size
+  // of |bytes_to_write|.
+  Write(array<uint8> bytes_to_write, int64 offset, Whence whence)
+      => (Error error, uint32 num_bytes_written);
+
+  // TODO(vtl): We definitely want 64 bits for |num_bytes_to_read|; but do we
+  // want it to be signed (this is consistent with |size| values, but
+  // inconsistent with 32-bit |num_bytes_to_read| values)? Do we want to have
+  // separate "read to end" versus "tail" (i.e., keep on reading as more data is
+  // appended) modes, and how would those be signalled?
+  ReadToStream(handle<data_pipe_producer> source,
+               int64 offset,
+               Whence whence,
+               int64 num_bytes_to_read) => (Error error);
+  WriteFromStream(handle<data_pipe_consumer> sink, int64 offset, Whence whence)
+      => (Error error);
+
+  // Gets the current file position. On success, |position| is the current
+  // offset (in bytes) from the beginning of the file).
+  Tell() => (Error error, int64 position);
+
+  // Sets the current file position to that specified by |offset|/|whence|. On
+  // success, |position| is the offset (in bytes) from the beginning of the
+  // file.
+  Seek(int64 offset, Whence whence) => (Error error, int64 position);
+
+  // Gets information about this file. On success, |file_information| is
+  // non-null and will contain this information.
+  Stat() => (Error error, FileInformation? file_information);
+
+  // Truncates this file to the size specified by |size| (in bytes).
+  Truncate(int64 size) => (Error error);
+
+  // Updates this file's atime and/or mtime to the time specified by |atime| (or
+  // |mtime|, respectively), which may also indicate "now". If |atime| or
+  // |mtime| is null, then the corresponding time is not modified.
+  Touch(TimespecOrNow? atime, TimespecOrNow? mtime) => (Error error);
+
+  // Creates a new |File| instance, which shares the same "file description".
+  // I.e., the access mode, etc. (as specified to |Directory::OpenFile()| by the
+  // |open_flags| argument) as well as file position.
+  Dup(File& file) => (Error error);
+
+  // TODO(vtl): What are the rules for reopening (w.r.t. changing mode/flags).
+  // E.g., obviously can go from "read-write" to "read", but reverse? (probably
+  // not), can remove "append"? (probably not?). Do we allow "truncate"?
+  Reopen(File& file, uint32 open_flags) => (Error error);
+
+  // TODO(vtl): probably should have access flags (but also exec?); how do these
+  // relate to access mode?
+  AsBuffer() => (Error error, handle<shared_buffer>? buffer);
+
+  // TODO(vtl): Add a "watch"?
+  // TODO(vtl): Add something analogous to fsync(2)?
+};
diff --git a/services/files/file_impl.cc b/services/files/file_impl.cc
new file mode 100644
index 0000000..721efb7
--- /dev/null
+++ b/services/files/file_impl.cc
@@ -0,0 +1,364 @@
+// Copyright 2015 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.
+
+#include "services/files/file_impl.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <limits>
+
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "services/files/futimens.h"
+#include "services/files/util.h"
+
+static_assert(sizeof(off_t) <= sizeof(int64_t), "off_t too big");
+static_assert(sizeof(size_t) >= sizeof(uint32_t), "size_t too small");
+
+namespace mojo {
+namespace files {
+
+const size_t kMaxReadSize = 1 * 1024 * 1024;  // 1 MB.
+
+FileImpl::FileImpl(InterfaceRequest<File> request, base::ScopedFD file_fd)
+    : binding_(this, request.Pass()), file_fd_(file_fd.Pass()) {
+  DCHECK(file_fd_.is_valid());
+}
+
+FileImpl::~FileImpl() {
+}
+
+void FileImpl::Close(const Callback<void(Error)>& callback) {
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED);
+    return;
+  }
+  int fd_to_try_to_close = file_fd_.release();
+  // POSIX.1 (2013) leaves the validity of the FD undefined on EINTR and EIO. On
+  // Linux, the FD is always invalidated, so we'll pretend that the close
+  // succeeded. (On other Unixes, the situation may be different and possibly
+  // totally broken; see crbug.com/269623.)
+  if (IGNORE_EINTR(close(fd_to_try_to_close)) != 0) {
+    // Save errno, since we do a few things and we don't want it trampled.
+    int error = errno;
+    CHECK_NE(error, EBADF);   // This should never happen.
+    DCHECK_NE(error, EINTR);  // We already ignored EINTR.
+    // I don't know what Linux does on EIO (or any other errors) -- POSIX leaves
+    // it undefined -- so report the error and hope that the FD was invalidated.
+    callback.Run(ErrnoToError(error));
+    return;
+  }
+
+  callback.Run(ERROR_OK);
+}
+
+// TODO(vtl): Move the implementation to a thread pool.
+void FileImpl::Read(uint32_t num_bytes_to_read,
+                    int64_t offset,
+                    Whence whence,
+                    const Callback<void(Error, Array<uint8_t>)>& callback) {
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED, Array<uint8_t>());
+    return;
+  }
+  if (num_bytes_to_read > kMaxReadSize) {
+    callback.Run(ERROR_OUT_OF_RANGE, Array<uint8_t>());
+    return;
+  }
+  if (Error error = IsOffsetValid(offset)) {
+    callback.Run(error, Array<uint8_t>());
+    return;
+  }
+  if (Error error = IsWhenceValid(whence)) {
+    callback.Run(error, Array<uint8_t>());
+    return;
+  }
+
+  if (offset != 0 || whence != WHENCE_FROM_CURRENT) {
+    // TODO(vtl): Use |pread()| below in the |WHENCE_FROM_START| case. This
+    // implementation is obviously not atomic. (If someone seeks simultaneously,
+    // we'll end up writing somewhere else. Or, well, we would if we were
+    // multithreaded.) Maybe we should do an |ftell()| and always use |pread()|.
+    // TODO(vtl): Possibly, at least sometimes we should not change the file
+    // position. See TODO in file.mojom.
+    if (lseek(file_fd_.get(), static_cast<off_t>(offset),
+              WhenceToStandardWhence(whence)) < 0) {
+      callback.Run(ErrnoToError(errno), Array<uint8_t>());
+      return;
+    }
+  }
+
+  Array<uint8_t> bytes_read(num_bytes_to_read);
+  ssize_t num_bytes_read = HANDLE_EINTR(
+      read(file_fd_.get(), &bytes_read.front(), num_bytes_to_read));
+  if (num_bytes_read < 0) {
+    callback.Run(ErrnoToError(errno), Array<uint8_t>());
+    return;
+  }
+
+  DCHECK_LE(static_cast<size_t>(num_bytes_read), num_bytes_to_read);
+  bytes_read.resize(static_cast<size_t>(num_bytes_read));
+  callback.Run(ERROR_OK, bytes_read.Pass());
+}
+
+// TODO(vtl): Move the implementation to a thread pool.
+void FileImpl::Write(Array<uint8_t> bytes_to_write,
+                     int64_t offset,
+                     Whence whence,
+                     const Callback<void(Error, uint32_t)>& callback) {
+  DCHECK(!bytes_to_write.is_null());
+
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED, 0);
+    return;
+  }
+  // Who knows what |write()| would return if the size is that big (and it
+  // actually wrote that much).
+  if (bytes_to_write.size() >
+      static_cast<size_t>(std::numeric_limits<ssize_t>::max())) {
+    callback.Run(ERROR_OUT_OF_RANGE, 0);
+    return;
+  }
+  if (Error error = IsOffsetValid(offset)) {
+    callback.Run(error, 0);
+    return;
+  }
+  if (Error error = IsWhenceValid(whence)) {
+    callback.Run(error, 0);
+    return;
+  }
+
+  if (offset != 0 || whence != WHENCE_FROM_CURRENT) {
+    // TODO(vtl): Use |pwrite()| below in the |WHENCE_FROM_START| case. This
+    // implementation is obviously not atomic. (If someone seeks simultaneously,
+    // we'll end up writing somewhere else. Or, well, we would if we were
+    // multithreaded.) Maybe we should do an |ftell()| and always use
+    // |pwrite()|.
+    // TODO(vtl): Possibly, at least sometimes we should not change the file
+    // position. See TODO in file.mojom.
+    if (lseek(file_fd_.get(), static_cast<off_t>(offset),
+              WhenceToStandardWhence(whence)) < 0) {
+      callback.Run(ErrnoToError(errno), 0);
+      return;
+    }
+  }
+
+  const void* buf =
+      (bytes_to_write.size() > 0) ? &bytes_to_write.front() : nullptr;
+  ssize_t num_bytes_written =
+      HANDLE_EINTR(write(file_fd_.get(), buf, bytes_to_write.size()));
+  if (num_bytes_written < 0) {
+    callback.Run(ErrnoToError(errno), 0);
+    return;
+  }
+
+  DCHECK_LE(static_cast<size_t>(num_bytes_written),
+            std::numeric_limits<uint32_t>::max());
+  callback.Run(ERROR_OK, static_cast<uint32_t>(num_bytes_written));
+}
+
+void FileImpl::ReadToStream(ScopedDataPipeProducerHandle source,
+                            int64_t offset,
+                            Whence whence,
+                            int64_t num_bytes_to_read,
+                            const Callback<void(Error)>& callback) {
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED);
+    return;
+  }
+  if (Error error = IsOffsetValid(offset)) {
+    callback.Run(error);
+    return;
+  }
+  if (Error error = IsWhenceValid(whence)) {
+    callback.Run(error);
+    return;
+  }
+
+  // TODO(vtl): FIXME soon
+  NOTIMPLEMENTED();
+  callback.Run(ERROR_UNIMPLEMENTED);
+}
+
+void FileImpl::WriteFromStream(ScopedDataPipeConsumerHandle sink,
+                               int64_t offset,
+                               Whence whence,
+                               const Callback<void(Error)>& callback) {
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED);
+    return;
+  }
+  if (Error error = IsOffsetValid(offset)) {
+    callback.Run(error);
+    return;
+  }
+  if (Error error = IsWhenceValid(whence)) {
+    callback.Run(error);
+    return;
+  }
+
+  // TODO(vtl): FIXME soon
+  NOTIMPLEMENTED();
+  callback.Run(ERROR_UNIMPLEMENTED);
+}
+
+void FileImpl::Tell(const Callback<void(Error, int64_t)>& callback) {
+  Seek(0, WHENCE_FROM_CURRENT, callback);
+}
+
+void FileImpl::Seek(int64_t offset,
+                    Whence whence,
+                    const Callback<void(Error, int64_t)>& callback) {
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED, 0);
+    return;
+  }
+  if (Error error = IsOffsetValid(offset)) {
+    callback.Run(error, 0);
+    return;
+  }
+  if (Error error = IsWhenceValid(whence)) {
+    callback.Run(error, 0);
+    return;
+  }
+
+  off_t position = lseek(file_fd_.get(), static_cast<off_t>(offset),
+                         WhenceToStandardWhence(whence));
+  if (position < 0) {
+    callback.Run(ErrnoToError(errno), 0);
+    return;
+  }
+
+  callback.Run(ERROR_OK, static_cast<int64>(position));
+}
+
+void FileImpl::Stat(const Callback<void(Error, FileInformationPtr)>& callback) {
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED, nullptr);
+    return;
+  }
+
+  struct stat buf;
+  if (fstat(file_fd_.get(), &buf) != 0) {
+    callback.Run(ErrnoToError(errno), nullptr);
+    return;
+  }
+
+  FileInformationPtr file_info(FileInformation::New());
+  file_info->size = static_cast<int64_t>(buf.st_size);
+  file_info->atime = Timespec::New();
+  file_info->mtime = Timespec::New();
+#if defined(OS_ANDROID)
+  file_info->atime->seconds = static_cast<int64_t>(buf.st_atime);
+  file_info->atime->nanoseconds = static_cast<int32_t>(buf.st_atime_nsec);
+  file_info->mtime->seconds = static_cast<int64_t>(buf.st_mtime);
+  file_info->mtime->nanoseconds = static_cast<int32_t>(buf.st_mtime_nsec);
+#else
+  file_info->atime->seconds = static_cast<int64_t>(buf.st_atim.tv_sec);
+  file_info->atime->nanoseconds = static_cast<int32_t>(buf.st_atim.tv_nsec);
+  file_info->mtime->seconds = static_cast<int64_t>(buf.st_mtim.tv_sec);
+  file_info->mtime->nanoseconds = static_cast<int32_t>(buf.st_mtim.tv_nsec);
+#endif
+
+  callback.Run(ERROR_OK, file_info.Pass());
+}
+
+void FileImpl::Truncate(int64_t size, const Callback<void(Error)>& callback) {
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED);
+    return;
+  }
+  if (size < 0) {
+    callback.Run(ERROR_INVALID_ARGUMENT);
+    return;
+  }
+  if (Error error = IsOffsetValid(size)) {
+    callback.Run(error);
+    return;
+  }
+
+  if (ftruncate(file_fd_.get(), static_cast<off_t>(size)) != 0) {
+    callback.Run(ErrnoToError(errno));
+    return;
+  }
+
+  callback.Run(ERROR_OK);
+}
+
+void FileImpl::Touch(TimespecOrNowPtr atime,
+                     TimespecOrNowPtr mtime,
+                     const Callback<void(Error)>& callback) {
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED);
+    return;
+  }
+
+  struct timespec times[2];
+  if (Error error = TimespecOrNowToStandardTimespec(atime.get(), &times[0])) {
+    callback.Run(error);
+    return;
+  }
+  if (Error error = TimespecOrNowToStandardTimespec(mtime.get(), &times[1])) {
+    callback.Run(error);
+    return;
+  }
+
+  if (futimens(file_fd_.get(), times) != 0) {
+    callback.Run(ErrnoToError(errno));
+    return;
+  }
+
+  callback.Run(ERROR_OK);
+}
+
+void FileImpl::Dup(InterfaceRequest<File> file,
+                   const Callback<void(Error)>& callback) {
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED);
+    return;
+  }
+
+  base::ScopedFD file_fd(dup(file_fd_.get()));
+  if (!file_fd.is_valid()) {
+    callback.Run(ErrnoToError(errno));
+    return;
+  }
+
+  new FileImpl(file.Pass(), file_fd.Pass());
+  callback.Run(ERROR_OK);
+}
+
+void FileImpl::Reopen(InterfaceRequest<File> file,
+                      uint32_t open_flags,
+                      const Callback<void(Error)>& callback) {
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED);
+    return;
+  }
+
+  // TODO(vtl): FIXME soon
+  NOTIMPLEMENTED();
+  callback.Run(ERROR_UNIMPLEMENTED);
+}
+
+void FileImpl::AsBuffer(
+    const Callback<void(Error, ScopedSharedBufferHandle)>& callback) {
+  if (!file_fd_.is_valid()) {
+    callback.Run(ERROR_CLOSED, ScopedSharedBufferHandle());
+    return;
+  }
+
+  // TODO(vtl): FIXME soon
+  NOTIMPLEMENTED();
+  callback.Run(ERROR_UNIMPLEMENTED, ScopedSharedBufferHandle());
+}
+
+}  // namespace files
+}  // namespace mojo
diff --git a/services/files/file_impl.h b/services/files/file_impl.h
new file mode 100644
index 0000000..dc6a18a
--- /dev/null
+++ b/services/files/file_impl.h
@@ -0,0 +1,69 @@
+// Copyright 2015 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.
+
+#ifndef SERVICES_FILES_FILE_IMPL_H_
+#define SERVICES_FILES_FILE_IMPL_H_
+
+#include "base/files/scoped_file.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "services/files/directory.mojom.h"
+
+namespace mojo {
+namespace files {
+
+class FileImpl : public File {
+ public:
+  // TODO(vtl): Will need more for, e.g., |Reopen()|.
+  FileImpl(InterfaceRequest<File> request, base::ScopedFD file_fd);
+  ~FileImpl() override;
+
+  // |File| implementation:
+  void Close(const Callback<void(Error)>& callback) override;
+  void Read(uint32_t num_bytes_to_read,
+            int64_t offset,
+            Whence whence,
+            const Callback<void(Error, Array<uint8_t>)>& callback) override;
+  void Write(Array<uint8_t> bytes_to_write,
+             int64_t offset,
+             Whence whence,
+             const Callback<void(Error, uint32_t)>& callback) override;
+  void ReadToStream(ScopedDataPipeProducerHandle source,
+                    int64_t offset,
+                    Whence whence,
+                    int64_t num_bytes_to_read,
+                    const Callback<void(Error)>& callback) override;
+  void WriteFromStream(ScopedDataPipeConsumerHandle sink,
+                       int64_t offset,
+                       Whence whence,
+                       const Callback<void(Error)>& callback) override;
+  void Tell(const Callback<void(Error, int64_t)>& callback) override;
+  void Seek(int64_t offset,
+            Whence whence,
+            const Callback<void(Error, int64_t)>& callback) override;
+  void Stat(const Callback<void(Error, FileInformationPtr)>& callback) override;
+  void Truncate(int64_t size, const Callback<void(Error)>& callback) override;
+  void Touch(TimespecOrNowPtr atime,
+             TimespecOrNowPtr mtime,
+             const Callback<void(Error)>& callback) override;
+  void Dup(InterfaceRequest<File> file,
+           const Callback<void(Error)>& callback) override;
+  void Reopen(InterfaceRequest<File> file,
+              uint32_t open_flags,
+              const Callback<void(Error)>& callback) override;
+  void AsBuffer(
+      const Callback<void(Error, ScopedSharedBufferHandle)>& callback) override;
+
+ private:
+  StrongBinding<File> binding_;
+  base::ScopedFD file_fd_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileImpl);
+};
+
+}  // namespace files
+}  // namespace mojo
+
+#endif  // SERVICES_FILES_FILE_IMPL_H_
diff --git a/services/files/files.mojom b/services/files/files.mojom
new file mode 100644
index 0000000..0e7b61d
--- /dev/null
+++ b/services/files/files.mojom
@@ -0,0 +1,22 @@
+// Copyright 2015 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.
+
+module mojo.files;
+
+import "services/files/directory.mojom";
+import "services/files/types.mojom";
+
+enum FileSystem {
+  // A per-instance (i.e., one for each call to |OpenFileSystem()|) temporary
+  // file system.
+  TEMPORARY,
+
+  // A persistent, shared file system (rooted at ~/MojoDebug, which must already
+  // exist) only available in Debug builds.
+  DEBUG,
+};
+
+interface Files {
+  OpenFileSystem(FileSystem file_system, Directory& directory) => (Error error);
+};
diff --git a/services/files/files_apptest.cc b/services/files/files_apptest.cc
new file mode 100644
index 0000000..c5dc5a6
--- /dev/null
+++ b/services/files/files_apptest.cc
@@ -0,0 +1,461 @@
+// Copyright 2015 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.
+
+#include <vector>
+
+#include "base/macros.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/type_converter.h"
+#include "services/files/directory.mojom.h"
+#include "services/files/file.mojom.h"
+#include "services/files/files.mojom.h"
+
+namespace mojo {
+namespace files {
+namespace {
+
+// TODO(vtl): Stuff copied from mojo/public/cpp/bindings/lib/template_util.h.
+template <class T, T v>
+struct IntegralConstant {
+  static const T value = v;
+};
+
+template <class T, T v>
+const T IntegralConstant<T, v>::value;
+
+typedef IntegralConstant<bool, true> TrueType;
+typedef IntegralConstant<bool, false> FalseType;
+
+template <class T>
+struct IsConst : FalseType {};
+template <class T>
+struct IsConst<const T> : TrueType {};
+
+template <bool B, typename T = void>
+struct EnableIf {};
+
+template <typename T>
+struct EnableIf<true, T> {
+  typedef T type;
+};
+
+typedef char YesType;
+
+struct NoType {
+  YesType dummy[2];
+};
+
+template <typename T>
+struct IsMoveOnlyType {
+  template <typename U>
+  static YesType Test(const typename U::MoveOnlyTypeForCPP03*);
+
+  template <typename U>
+  static NoType Test(...);
+
+  static const bool value =
+      sizeof(Test<T>(0)) == sizeof(YesType) && !IsConst<T>::value;
+};
+
+template <typename T>
+typename EnableIf<!IsMoveOnlyType<T>::value, T>::type& Forward(T& t) {
+  return t;
+}
+
+template <typename T>
+typename EnableIf<IsMoveOnlyType<T>::value, T>::type Forward(T& t) {
+  return t.Pass();
+}
+// TODO(vtl): (End of stuff copied from template_util.h.)
+
+template <typename T1>
+Callback<void(T1)> Capture(T1* t1) {
+  return [t1](T1 got_t1) { *t1 = Forward(got_t1); };
+}
+
+template <typename T1, typename T2>
+Callback<void(T1, T2)> Capture(T1* t1, T2* t2) {
+  return [t1, t2](T1 got_t1, T2 got_t2) {
+    *t1 = Forward(got_t1);
+    *t2 = Forward(got_t2);
+  };
+}
+
+class FilesAppTest : public test::ApplicationTestBase {
+ public:
+  FilesAppTest() {}
+  ~FilesAppTest() override {}
+
+  void SetUp() override {
+    test::ApplicationTestBase::SetUp();
+    application_impl()->ConnectToService("mojo:files", &files_);
+  }
+
+  FilesPtr& files() { return files_; }
+
+ private:
+  FilesPtr files_;
+
+  DISALLOW_COPY_AND_ASSIGN(FilesAppTest);
+};
+
+TEST_F(FilesAppTest, CreateWriteCloseRenameOpenRead) {
+  // Get a temporary root directory.
+  DirectoryPtr directory;
+  Error error = ERROR_INTERNAL;
+  files()->OpenFileSystem(FILE_SYSTEM_TEMPORARY, GetProxy(&directory),
+                          Capture(&error));
+  ASSERT_TRUE(files().WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  {
+    // Create my_file.
+    FilePtr file;
+    error = ERROR_INTERNAL;
+    directory->OpenFile("my_file", GetProxy(&file),
+                        kOpenFlagWrite | kOpenFlagCreate, Capture(&error));
+    ASSERT_TRUE(directory.WaitForIncomingMethodCall());
+    EXPECT_EQ(ERROR_OK, error);
+
+    // Write to it.
+    std::vector<uint8_t> bytes_to_write;
+    bytes_to_write.push_back(static_cast<uint8_t>('h'));
+    bytes_to_write.push_back(static_cast<uint8_t>('e'));
+    bytes_to_write.push_back(static_cast<uint8_t>('l'));
+    bytes_to_write.push_back(static_cast<uint8_t>('l'));
+    bytes_to_write.push_back(static_cast<uint8_t>('o'));
+    error = ERROR_INTERNAL;
+    uint32_t num_bytes_written = 0;
+    file->Write(Array<uint8_t>::From(bytes_to_write), 0, WHENCE_FROM_CURRENT,
+                Capture(&error, &num_bytes_written));
+    ASSERT_TRUE(file.WaitForIncomingMethodCall());
+    EXPECT_EQ(ERROR_OK, error);
+    EXPECT_EQ(bytes_to_write.size(), num_bytes_written);
+
+    // Close it.
+    error = ERROR_INTERNAL;
+    file->Close(Capture(&error));
+    ASSERT_TRUE(file.WaitForIncomingMethodCall());
+    EXPECT_EQ(ERROR_OK, error);
+  }
+
+  // Rename it.
+  error = ERROR_INTERNAL;
+  directory->Rename("my_file", "your_file", Capture(&error));
+  ASSERT_TRUE(directory.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  {
+    // Open my_file again.
+    FilePtr file;
+    error = ERROR_INTERNAL;
+    directory->OpenFile("your_file", GetProxy(&file), kOpenFlagRead,
+                        Capture(&error));
+    ASSERT_TRUE(directory.WaitForIncomingMethodCall());
+    EXPECT_EQ(ERROR_OK, error);
+
+    // Read from it.
+    Array<uint8_t> bytes_read;
+    error = ERROR_INTERNAL;
+    file->Read(3, 1, WHENCE_FROM_START, Capture(&error, &bytes_read));
+    ASSERT_TRUE(file.WaitForIncomingMethodCall());
+    EXPECT_EQ(ERROR_OK, error);
+    ASSERT_EQ(3u, bytes_read.size());
+    EXPECT_EQ(static_cast<uint8_t>('e'), bytes_read[0]);
+    EXPECT_EQ(static_cast<uint8_t>('l'), bytes_read[1]);
+    EXPECT_EQ(static_cast<uint8_t>('l'), bytes_read[2]);
+  }
+
+  // TODO(vtl): Test various open options.
+  // TODO(vtl): Test read/write offset options.
+}
+
+// Note: Ignore nanoseconds, since it may not always be supported. We expect at
+// least second-resolution support though.
+TEST_F(FilesAppTest, StatTouch) {
+  // Get a temporary root directory.
+  DirectoryPtr directory;
+  Error error = ERROR_INTERNAL;
+  files()->OpenFileSystem(FILE_SYSTEM_TEMPORARY, GetProxy(&directory),
+                          Capture(&error));
+  ASSERT_TRUE(files().WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  // Create my_file.
+  FilePtr file;
+  error = ERROR_INTERNAL;
+  directory->OpenFile("my_file", GetProxy(&file),
+                      kOpenFlagWrite | kOpenFlagCreate, Capture(&error));
+  ASSERT_TRUE(directory.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  // Stat it.
+  error = ERROR_INTERNAL;
+  FileInformationPtr file_info;
+  file->Stat(Capture(&error, &file_info));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  ASSERT_FALSE(file_info.is_null());
+  EXPECT_EQ(0, file_info->size);
+  ASSERT_FALSE(file_info->atime.is_null());
+  EXPECT_GT(file_info->atime->seconds, 0);  // Expect that it's not 1970-01-01.
+  ASSERT_FALSE(file_info->mtime.is_null());
+  EXPECT_GT(file_info->mtime->seconds, 0);
+  int64_t first_mtime = file_info->mtime->seconds;
+
+  // Touch only the atime.
+  error = ERROR_INTERNAL;
+  TimespecOrNowPtr t(TimespecOrNow::New());
+  t->now = false;
+  t->timespec = Timespec::New();
+  const int64_t kPartyTime1 = 1234567890;  // Party like it's 2009-02-13.
+  t->timespec->seconds = kPartyTime1;
+  file->Touch(t.Pass(), nullptr, Capture(&error));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  // Stat again.
+  error = ERROR_INTERNAL;
+  file_info.reset();
+  file->Stat(Capture(&error, &file_info));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  ASSERT_FALSE(file_info.is_null());
+  ASSERT_FALSE(file_info->atime.is_null());
+  EXPECT_EQ(kPartyTime1, file_info->atime->seconds);
+  ASSERT_FALSE(file_info->mtime.is_null());
+  EXPECT_EQ(first_mtime, file_info->mtime->seconds);
+
+  // Touch only the mtime.
+  t = TimespecOrNow::New();
+  t->now = false;
+  t->timespec = Timespec::New();
+  const int64_t kPartyTime2 = 1425059525;  // No time like the present.
+  t->timespec->seconds = kPartyTime2;
+  file->Touch(nullptr, t.Pass(), Capture(&error));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  // Stat again.
+  error = ERROR_INTERNAL;
+  file_info.reset();
+  file->Stat(Capture(&error, &file_info));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  ASSERT_FALSE(file_info.is_null());
+  ASSERT_FALSE(file_info->atime.is_null());
+  EXPECT_EQ(kPartyTime1, file_info->atime->seconds);
+  ASSERT_FALSE(file_info->mtime.is_null());
+  EXPECT_EQ(kPartyTime2, file_info->mtime->seconds);
+
+  // TODO(vtl): Also test non-zero file size.
+  // TODO(vtl): Also test Touch() "now" options.
+  // TODO(vtl): Also test touching both atime and mtime.
+
+  // Close it.
+  error = ERROR_INTERNAL;
+  file->Close(Capture(&error));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+}
+
+TEST_F(FilesAppTest, TellSeek) {
+  // Get a temporary root directory.
+  DirectoryPtr directory;
+  Error error = ERROR_INTERNAL;
+  files()->OpenFileSystem(FILE_SYSTEM_TEMPORARY, GetProxy(&directory),
+                          Capture(&error));
+  ASSERT_TRUE(files().WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  // Create my_file.
+  FilePtr file;
+  error = ERROR_INTERNAL;
+  directory->OpenFile("my_file", GetProxy(&file),
+                      kOpenFlagWrite | kOpenFlagCreate, Capture(&error));
+  ASSERT_TRUE(directory.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  // Write to it.
+  std::vector<uint8_t> bytes_to_write(1000, '!');
+  error = ERROR_INTERNAL;
+  uint32_t num_bytes_written = 0;
+  file->Write(Array<uint8_t>::From(bytes_to_write), 0, WHENCE_FROM_CURRENT,
+              Capture(&error, &num_bytes_written));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(bytes_to_write.size(), num_bytes_written);
+  const int size = static_cast<int>(num_bytes_written);
+
+  // Tell.
+  error = ERROR_INTERNAL;
+  int64_t position = -1;
+  file->Tell(Capture(&error, &position));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  // Should be at the end.
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(size, position);
+
+  // Seek back 100.
+  error = ERROR_INTERNAL;
+  position = -1;
+  file->Seek(-100, WHENCE_FROM_CURRENT, Capture(&error, &position));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(size - 100, position);
+
+  // Tell.
+  error = ERROR_INTERNAL;
+  position = -1;
+  file->Tell(Capture(&error, &position));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(size - 100, position);
+
+  // Seek to 123 from start.
+  error = ERROR_INTERNAL;
+  position = -1;
+  file->Seek(123, WHENCE_FROM_START, Capture(&error, &position));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(123, position);
+
+  // Tell.
+  error = ERROR_INTERNAL;
+  position = -1;
+  file->Tell(Capture(&error, &position));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(123, position);
+
+  // Seek to 123 back from end.
+  error = ERROR_INTERNAL;
+  position = -1;
+  file->Seek(-123, WHENCE_FROM_END, Capture(&error, &position));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(size - 123, position);
+
+  // Tell.
+  error = ERROR_INTERNAL;
+  position = -1;
+  file->Tell(Capture(&error, &position));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(size - 123, position);
+
+  // TODO(vtl): Check that seeking actually affects reading/writing.
+  // TODO(vtl): Check that seeking can extend the file?
+
+  // Close it.
+  error = ERROR_INTERNAL;
+  file->Close(Capture(&error));
+  ASSERT_TRUE(file.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+}
+
+TEST_F(FilesAppTest, Dup) {
+  // Get a temporary root directory.
+  DirectoryPtr directory;
+  Error error = ERROR_INTERNAL;
+  files()->OpenFileSystem(FILE_SYSTEM_TEMPORARY, GetProxy(&directory),
+                          Capture(&error));
+  ASSERT_TRUE(files().WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  // Create my_file.
+  FilePtr file1;
+  error = ERROR_INTERNAL;
+  directory->OpenFile("my_file", GetProxy(&file1),
+                      kOpenFlagRead | kOpenFlagWrite | kOpenFlagCreate,
+                      Capture(&error));
+  ASSERT_TRUE(directory.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  // Write to it.
+  std::vector<uint8_t> bytes_to_write;
+  bytes_to_write.push_back(static_cast<uint8_t>('h'));
+  bytes_to_write.push_back(static_cast<uint8_t>('e'));
+  bytes_to_write.push_back(static_cast<uint8_t>('l'));
+  bytes_to_write.push_back(static_cast<uint8_t>('l'));
+  bytes_to_write.push_back(static_cast<uint8_t>('o'));
+  error = ERROR_INTERNAL;
+  uint32_t num_bytes_written = 0;
+  file1->Write(Array<uint8_t>::From(bytes_to_write), 0, WHENCE_FROM_CURRENT,
+               Capture(&error, &num_bytes_written));
+  ASSERT_TRUE(file1.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(bytes_to_write.size(), num_bytes_written);
+  const int end_hello_pos = static_cast<int>(num_bytes_written);
+
+  // Dup it.
+  FilePtr file2;
+  error = ERROR_INTERNAL;
+  file1->Dup(GetProxy(&file2), Capture(&error));
+  ASSERT_TRUE(file1.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  // |file2| should have the same position.
+  error = ERROR_INTERNAL;
+  int64_t position = -1;
+  file2->Tell(Capture(&error, &position));
+  ASSERT_TRUE(file2.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(end_hello_pos, position);
+
+  // Write using |file2|.
+  std::vector<uint8_t> more_bytes_to_write;
+  more_bytes_to_write.push_back(static_cast<uint8_t>('w'));
+  more_bytes_to_write.push_back(static_cast<uint8_t>('o'));
+  more_bytes_to_write.push_back(static_cast<uint8_t>('r'));
+  more_bytes_to_write.push_back(static_cast<uint8_t>('l'));
+  more_bytes_to_write.push_back(static_cast<uint8_t>('d'));
+  error = ERROR_INTERNAL;
+  num_bytes_written = 0;
+  file2->Write(Array<uint8_t>::From(more_bytes_to_write), 0,
+               WHENCE_FROM_CURRENT, Capture(&error, &num_bytes_written));
+  ASSERT_TRUE(file2.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(more_bytes_to_write.size(), num_bytes_written);
+  const int end_world_pos = end_hello_pos + static_cast<int>(num_bytes_written);
+
+  // |file1| should have the same position.
+  error = ERROR_INTERNAL;
+  position = -1;
+  file1->Tell(Capture(&error, &position));
+  ASSERT_TRUE(file1.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  EXPECT_EQ(end_world_pos, position);
+
+  // Close |file1|.
+  error = ERROR_INTERNAL;
+  file1->Close(Capture(&error));
+  ASSERT_TRUE(file1.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  // Read everything using |file2|.
+  Array<uint8_t> bytes_read;
+  error = ERROR_INTERNAL;
+  file2->Read(1000, 0, WHENCE_FROM_START, Capture(&error, &bytes_read));
+  ASSERT_TRUE(file2.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+  ASSERT_EQ(static_cast<size_t>(end_world_pos), bytes_read.size());
+  // Just check the first and last bytes.
+  EXPECT_EQ(static_cast<uint8_t>('h'), bytes_read[0]);
+  EXPECT_EQ(static_cast<uint8_t>('d'), bytes_read[end_world_pos - 1]);
+
+  // Close |file2|.
+  error = ERROR_INTERNAL;
+  file2->Close(Capture(&error));
+  ASSERT_TRUE(file2.WaitForIncomingMethodCall());
+  EXPECT_EQ(ERROR_OK, error);
+
+  // TODO(vtl): Test that |file2| has the same open options as |file1|.
+}
+
+}  // namespace
+}  // namespace files
+}  // namespace mojo
diff --git a/services/files/files_impl.cc b/services/files/files_impl.cc
new file mode 100644
index 0000000..af48155
--- /dev/null
+++ b/services/files/files_impl.cc
@@ -0,0 +1,99 @@
+// Copyright 2015 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.
+
+#include "services/files/files_impl.h"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/posix/eintr_wrapper.h"
+#include "services/files/directory_impl.h"
+
+namespace mojo {
+namespace files {
+
+namespace {
+
+base::ScopedFD CreateAndOpenTemporaryDirectory(
+    scoped_ptr<base::ScopedTempDir>* temp_dir) {
+  (*temp_dir).reset(new base::ScopedTempDir());
+  CHECK((*temp_dir)->CreateUniqueTempDir());
+
+  base::ScopedFD temp_dir_fd(HANDLE_EINTR(
+      open((*temp_dir)->path().value().c_str(), O_RDONLY | O_DIRECTORY, 0)));
+  PCHECK(temp_dir_fd.is_valid());
+  DVLOG(1) << "Made a temporary directory: " << (*temp_dir)->path().value();
+  return temp_dir_fd.Pass();
+}
+
+#ifndef NDEBUG
+base::ScopedFD OpenMojoDebugDirectory() {
+  const char* home_dir_name = getenv("HOME");
+  if (!home_dir_name || !home_dir_name[0]) {
+    LOG(ERROR) << "HOME not set";
+    return base::ScopedFD();
+  }
+  base::FilePath mojo_debug_dir_name =
+      base::FilePath(home_dir_name).Append("MojoDebug");
+  return base::ScopedFD(HANDLE_EINTR(
+      open(mojo_debug_dir_name.value().c_str(), O_RDONLY | O_DIRECTORY, 0)));
+}
+#endif
+
+}  // namespace
+
+FilesImpl::FilesImpl(ApplicationConnection* connection,
+                     InterfaceRequest<Files> request)
+    : binding_(this, request.Pass()) {
+  // TODO(vtl): record other app's URL
+}
+
+FilesImpl::~FilesImpl() {
+}
+
+void FilesImpl::OpenFileSystem(FileSystem file_system,
+                               InterfaceRequest<Directory> directory,
+                               const Callback<void(Error)>& callback) {
+  base::ScopedFD dir_fd;
+  // Set only if the |DirectoryImpl| will own a temporary directory.
+  scoped_ptr<base::ScopedTempDir> temp_dir;
+  switch (file_system) {
+    case FILE_SYSTEM_TEMPORARY:
+      // TODO(vtl): ScopedGeneric (hence ScopedFD) doesn't have an operator=!
+      dir_fd.reset(CreateAndOpenTemporaryDirectory(&temp_dir).release());
+      DCHECK(temp_dir);
+      break;
+    case FILE_SYSTEM_DEBUG:
+#ifdef NDEBUG
+      LOG(WARNING) << "~/MojoDebug only available in Debug builds";
+#else
+      // TODO(vtl): ScopedGeneric (hence ScopedFD) doesn't have an operator=!
+      dir_fd.reset(OpenMojoDebugDirectory().release());
+#endif
+      if (!dir_fd.is_valid()) {
+        LOG(ERROR) << "~/MojoDebug unavailable";
+        callback.Run(ERROR_UNAVAILABLE);
+        return;
+      }
+      break;
+    default:
+      LOG(ERROR) << "Unknown file system type: " << file_system;
+      callback.Run(ERROR_UNIMPLEMENTED);
+      return;
+  }
+
+  new DirectoryImpl(directory.Pass(), dir_fd.Pass(), temp_dir.Pass());
+  callback.Run(ERROR_OK);
+}
+
+}  // namespace files
+}  // namespace mojo
diff --git a/services/files/files_impl.h b/services/files/files_impl.h
new file mode 100644
index 0000000..afd947b
--- /dev/null
+++ b/services/files/files_impl.h
@@ -0,0 +1,38 @@
+// Copyright 2015 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.
+
+#ifndef SERVICES_FILES_FILES_IMPL_H_
+#define SERVICES_FILES_FILES_IMPL_H_
+
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "services/files/files.mojom.h"
+
+namespace mojo {
+
+class ApplicationConnection;
+
+namespace files {
+
+class FilesImpl : public Files {
+ public:
+  FilesImpl(ApplicationConnection* connection, InterfaceRequest<Files> request);
+  ~FilesImpl() override;
+
+  // |Files| implementation:
+  void OpenFileSystem(FileSystem file_system,
+                      InterfaceRequest<Directory> directory,
+                      const Callback<void(Error)>& callback) override;
+
+ private:
+  StrongBinding<Files> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(FilesImpl);
+};
+
+}  // namespace files
+}  // namespace mojo
+
+#endif  // SERVICES_FILES_FILES_IMPL_H_
diff --git a/services/files/futimens.h b/services/files/futimens.h
new file mode 100644
index 0000000..9b7a8ae
--- /dev/null
+++ b/services/files/futimens.h
@@ -0,0 +1,26 @@
+// Copyright 2015 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.
+
+#ifndef SERVICES_FILES_FUTIMENS_H_
+#define SERVICES_FILES_FUTIMENS_H_
+
+#include <sys/stat.h>
+
+#include "build/build_config.h"
+
+// For Android: The NDK/C library we currently build against doesn't have
+// |futimens()|, but has |utimensat()|. Provide the former (in terms of the
+// latter) for now. Remove this file and futimens_android.cc when |futimens()|
+// becomes generally available (see
+// https://android-review.googlesource.com/#/c/63321/).
+
+#if defined(OS_ANDROID)
+extern "C" {
+
+int futimens(int fd, const struct timespec times[2]);
+
+}  // extern "C"
+#endif
+
+#endif  // SERVICES_FILES_FUTIMENS_H_
diff --git a/services/files/futimens_android.cc b/services/files/futimens_android.cc
new file mode 100644
index 0000000..035f61d
--- /dev/null
+++ b/services/files/futimens_android.cc
@@ -0,0 +1,13 @@
+// Copyright 2015 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.
+
+#include "services/files/futimens.h"
+
+extern "C" {
+
+int futimens(int fd, const struct timespec times[2]) {
+  return utimensat(fd, nullptr, times, 0);
+}
+
+}  // extern "C"
diff --git a/services/files/main.cc b/services/files/main.cc
new file mode 100644
index 0000000..9e457c0
--- /dev/null
+++ b/services/files/main.cc
@@ -0,0 +1,44 @@
+// Copyright 2015 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.
+
+#include "base/macros.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "services/files/files.mojom.h"
+#include "services/files/files_impl.h"
+
+namespace mojo {
+namespace files {
+
+class FilesApp : public ApplicationDelegate, public InterfaceFactory<Files> {
+ public:
+  FilesApp() {}
+  ~FilesApp() override {}
+
+ private:
+  // |ApplicationDelegate| override:
+  bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
+    connection->AddService<Files>(this);
+    return true;
+  }
+
+  // |InterfaceFactory<Files>| implementation:
+  void Create(ApplicationConnection* connection,
+              InterfaceRequest<Files> request) override {
+    new FilesImpl(connection, request.Pass());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(FilesApp);
+};
+
+}  // namespace files
+}  // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+  mojo::ApplicationRunnerChromium runner(new mojo::files::FilesApp());
+  return runner.Run(shell_handle);
+}
diff --git a/services/files/types.mojom b/services/files/types.mojom
new file mode 100644
index 0000000..4f5f3b4
--- /dev/null
+++ b/services/files/types.mojom
@@ -0,0 +1,99 @@
+// Copyright 2015 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.
+
+module mojo.files;
+
+// Error codes used by the file manager.
+// TODO(vtl): Add more (to, e.g., cover all of errno).
+enum Error {
+  OK = 0,
+  UNKNOWN,
+  INVALID_ARGUMENT,
+  PERMISSION_DENIED,
+  OUT_OF_RANGE,
+  UNIMPLEMENTED,
+  CLOSED,
+  UNAVAILABLE,
+  INTERNAL,
+};
+
+// Used to explain the meaning of an offset within a file.
+enum Whence {
+  // Offset is from current position in the file.
+  FROM_CURRENT = 0,
+  // Offset is relative to the beginning of the file.
+  FROM_START,
+  // Offset is relative to the end of the file.
+  FROM_END,
+};
+
+// Describes (idealized) wall-clock time, since Unix epoch (i.e., since
+// "1970-01-01 00:00 UTC", ignoring leap seconds and that UTC as we know it
+// started in 1972).
+// TODO(vtl): Should probably be moved out of mojo.files (maybe to mojo.time?).
+struct Timespec {
+  int64 seconds;
+  int32 nanoseconds;  // Always in the interval [0, 10^9).
+};
+
+// Used for |Touch()| calls. If |now| is set, |timespec| must be null (the time
+// "now" will be used). Otherwise, |timespec| must not be null.
+// TODO(vtl): Use a union instead, when that becomes possible.
+struct TimespecOrNow {
+  bool now;
+  Timespec? timespec;
+};
+
+// Describes various information about a file or directory (for |Stat()|). Note
+// that access/modification times may be set arbitrarily (by those with
+// appropriate capabilities) and may not reflect reality.
+struct FileInformation {
+  // Size of the file, in bytes. Zero for directories.
+  int64 size;
+  // Last access time, if available/supported.
+  Timespec? atime;
+  // Last modification time, if available/supported.
+  Timespec? mtime;
+};
+
+// File and directory open flags (at least one of |kOpenFlagRead| and
+// |kOpenFlagWrite| is required):
+// Opens the file/directory for reading.
+const uint32 kOpenFlagRead = 0x1;
+// Opens the file/directory for writing.
+const uint32 kOpenFlagWrite = 0x2;
+// Only meaningful together with |kOpenFlagWrite|: creates the file if
+// necessary.
+const uint32 kOpenFlagCreate = 0x4;
+// Only meaningful together with |kOpenFlagCreate|: requires file/directory to
+// be created, failing if it already exists.
+const uint32 kOpenFlagExclusive = 0x8;
+// Only meaningful for files, together with |kOpenFlagWrite|: writes will always
+// append to the file.
+const uint32 kOpenFlagAppend = 0x10;
+// Only meaningful for files, together with |kOpenFlagWrite|: truncates the
+// file.
+const uint32 kOpenFlagTruncate = 0x20;
+
+// File types.
+enum FileType {
+  UNKNOWN = 0,
+  REGULAR_FILE,
+  DIRECTORY,
+};
+
+// Describes a directory entry (i.e., a single member of a directory).
+struct DirectoryEntry {
+  FileType type;
+  string name;
+};
+
+// Deletion flags:
+// Only delete if the path refers to a file/non-directory (by default, will
+// delete files and directories).
+const uint32 kDeleteFlagFileOnly = 0x1;
+// Only delete if the path refers to a directory.
+const uint32 kDeleteFlagDirectoryOnly = 0x2;
+// Recursively delete (neither of the two flags above may be specified).
+const uint32 kDeleteFlagRecursive = 0x4;
diff --git a/services/files/util.cc b/services/files/util.cc
new file mode 100644
index 0000000..d2af8cb
--- /dev/null
+++ b/services/files/util.cc
@@ -0,0 +1,103 @@
+// Copyright 2015 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.
+
+#include "services/files/util.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include <limits>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "mojo/public/cpp/bindings/string.h"
+
+namespace mojo {
+namespace files {
+
+Error IsPathValid(const String& path) {
+  DCHECK(!path.is_null());
+  if (!base::IsStringUTF8(path.get()))
+    return ERROR_INVALID_ARGUMENT;
+  if (path.size() > 0 && path[0] == '/')
+    return ERROR_PERMISSION_DENIED;
+  return ERROR_OK;
+}
+
+Error IsWhenceValid(Whence whence) {
+  return (whence == WHENCE_FROM_CURRENT || whence == WHENCE_FROM_START ||
+          whence == WHENCE_FROM_END)
+             ? ERROR_OK
+             : ERROR_UNIMPLEMENTED;
+}
+
+Error IsOffsetValid(int64_t offset) {
+  return (offset >= std::numeric_limits<off_t>::min() &&
+          offset <= std::numeric_limits<off_t>::max())
+             ? ERROR_OK
+             : ERROR_OUT_OF_RANGE;
+}
+
+Error ErrnoToError(int errno_value) {
+  // TODO(vtl)
+  return ERROR_UNKNOWN;
+}
+
+int WhenceToStandardWhence(Whence whence) {
+  DCHECK_EQ(IsWhenceValid(whence), ERROR_OK);
+  switch (whence) {
+    case WHENCE_FROM_CURRENT:
+      return SEEK_CUR;
+    case WHENCE_FROM_START:
+      return SEEK_SET;
+    case WHENCE_FROM_END:
+      return SEEK_END;
+  }
+  NOTREACHED();
+  return 0;
+}
+
+Error TimespecToStandardTimespec(const Timespec* in, struct timespec* out) {
+  if (!in) {
+    out->tv_sec = 0;
+    out->tv_nsec = UTIME_OMIT;
+    return ERROR_OK;
+  }
+
+  static_assert(sizeof(int64_t) >= sizeof(time_t), "whoa, time_t is huge");
+  if (in->seconds < std::numeric_limits<time_t>::min() ||
+      in->seconds > std::numeric_limits<time_t>::max())
+    return ERROR_OUT_OF_RANGE;
+
+  if (in->nanoseconds < 0 || in->nanoseconds >= 1000000000)
+    return ERROR_INVALID_ARGUMENT;
+
+  out->tv_sec = static_cast<time_t>(in->seconds);
+  out->tv_nsec = static_cast<long>(in->nanoseconds);
+  return ERROR_OK;
+}
+
+Error TimespecOrNowToStandardTimespec(const TimespecOrNow* in,
+                                      struct timespec* out) {
+  if (!in) {
+    out->tv_sec = 0;
+    out->tv_nsec = UTIME_OMIT;
+    return ERROR_OK;
+  }
+
+  if (in->now) {
+    if (!in->timespec.is_null())
+      return ERROR_INVALID_ARGUMENT;
+    out->tv_sec = 0;
+    out->tv_nsec = UTIME_NOW;
+    return ERROR_OK;
+  }
+
+  return TimespecToStandardTimespec(in->timespec.get(), out);
+}
+
+}  // namespace files
+}  // namespace mojo
diff --git a/services/files/util.h b/services/files/util.h
new file mode 100644
index 0000000..324781d
--- /dev/null
+++ b/services/files/util.h
@@ -0,0 +1,55 @@
+// Copyright 2015 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.
+
+#ifndef SERVICES_FILES_UTIL_H_
+#define SERVICES_FILES_UTIL_H_
+
+#include "services/files/types.mojom.h"
+
+namespace mojo {
+
+class String;
+
+namespace files {
+
+// Validation functions (typically used to check arguments; they return
+// |ERROR_OK| if valid, else the standard/recommended error for the validation
+// error):
+
+// Checks if |path|, which must be non-null, is (looks like) a valid (relative)
+// path. (On failure, returns |ERROR_INVALID_ARGUMENT| if |path| is not UTF-8,
+// or |ERROR_PERMISSION_DENIED| if it is not relative.)
+Error IsPathValid(const String& path);
+
+// Checks if |whence| is a valid (known) |Whence| value. (On failure, returns
+// |ERROR_UNIMPLEMENTED|.)
+Error IsWhenceValid(Whence whence);
+
+// Checks if |offset| is a valid file offset (from some point); this is
+// implementation-dependent (typically checking if |offset| fits in an |off_t|).
+// (On failure, returns |ERROR_OUT_OF_RANGE|.)
+Error IsOffsetValid(int64_t offset);
+
+// Conversion functions:
+
+// Converts a standard errno value (|E...|) to an |Error| value.
+Error ErrnoToError(int errno_value);
+
+// Converts a |Whence| value to a standard whence value (|SEEK_...|).
+int WhenceToStandardWhence(Whence whence);
+
+// Converts a |Timespec| to a |struct timespec|. If |in| is null, |out->tv_nsec|
+// is set to |UTIME_OMIT|.
+Error TimespecToStandardTimespec(const Timespec* in, struct timespec* out);
+
+// Converts a |TimespecOrNow| to a |struct timespec|. If |in| is null,
+// |out->tv_nsec| is set to |UTIME_OMIT|; if |in->now| is set, |out->tv_nsec| is
+// set to |UTIME_NOW|.
+Error TimespecOrNowToStandardTimespec(const TimespecOrNow* in,
+                                      struct timespec* out);
+
+}  // namespace files
+}  // namespace mojo
+
+#endif  // SERVICES_FILES_UTIL_H_