p2p: Add P2PManager class

This class makes it simple to use p2p both as a client and a server.

For now, this feature is hidden behind a flag. The intent is that this
flag can be toggled with the crosh command - for now, only the reading
of the flag is implemented - see chromium:260441 for the remaining
work.

BUG=chromium:260426,chromium:260441
TEST=New unit tests + unit tests pass
Change-Id: If1879f4535c8e7e3af7c6d378e6df4054264b794
Reviewed-on: https://chromium-review.googlesource.com/64824
Reviewed-by: David Zeuthen <zeuthen@chromium.org>
Commit-Queue: David Zeuthen <zeuthen@chromium.org>
Tested-by: David Zeuthen <zeuthen@chromium.org>
diff --git a/SConstruct b/SConstruct
index dc4dd6c..215d4b1 100644
--- a/SConstruct
+++ b/SConstruct
@@ -275,6 +275,7 @@
                    omaha_request_action.cc
                    omaha_request_params.cc
                    omaha_response_handler_action.cc
+                   p2p_manager.cc
                    payload_signer.cc
                    payload_state.cc
                    postinstall_runner_action.cc
@@ -324,6 +325,7 @@
                             omaha_request_action_unittest.cc
                             omaha_request_params_unittest.cc
                             omaha_response_handler_action_unittest.cc
+                            p2p_manager_unittest.cc
                             payload_signer_unittest.cc
                             payload_state_unittest.cc
                             postinstall_runner_action_unittest.cc
diff --git a/constants.cc b/constants.cc
index 874a004..31454cc 100644
--- a/constants.cc
+++ b/constants.cc
@@ -38,6 +38,7 @@
 const char kPrefsManifestMetadataSize[] = "manifest-metadata-size";
 const char kPrefsNumReboots[] = "num-reboots";
 const char kPrefsNumResponsesSeen[] = "num-responses-seen";
+const char kPrefsP2PEnabled[] = "p2p-enabled";
 const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number";
 const char kPrefsPreviousVersion[] = "previous-version";
 const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
diff --git a/constants.h b/constants.h
index 7b3dca9..66c12e7 100644
--- a/constants.h
+++ b/constants.h
@@ -41,6 +41,7 @@
 extern const char kPrefsManifestMetadataSize[];
 extern const char kPrefsNumReboots[];
 extern const char kPrefsNumResponsesSeen[];
+extern const char kPrefsP2PEnabled[];
 extern const char kPrefsPayloadAttemptNumber[];
 extern const char kPrefsPreviousVersion[];
 extern const char kPrefsResumedUpdateFailures[];
diff --git a/fake_p2p_manager_configuration.h b/fake_p2p_manager_configuration.h
new file mode 100644
index 0000000..9cc6481
--- /dev/null
+++ b/fake_p2p_manager_configuration.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2013 The Chromium OS 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 CHROMEOS_PLATFORM_UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H__
+
+#include "update_engine/p2p_manager.h"
+#include "update_engine/utils.h"
+
+#include <glib.h>
+
+#include <base/logging.h>
+#include <base/stringprintf.h>
+
+namespace chromeos_update_engine {
+
+// Configuration for P2PManager for use in unit tests. Instead of
+// /var/cache/p2p, a temporary directory is used.
+class FakeP2PManagerConfiguration : public P2PManager::Configuration {
+public:
+  FakeP2PManagerConfiguration()
+    : p2p_client_cmdline_format_("p2p-client --get-url=%s --minimum-size=%zu") {
+    EXPECT_TRUE(utils::MakeTempDirectory("/tmp/p2p-tc.XXXXXX", &p2p_dir_));
+    SetInitctlStartCommandLine("initctl start p2p");
+    SetInitctlStopCommandLine("initctl stop p2p");
+  }
+
+  ~FakeP2PManagerConfiguration() {
+    if (p2p_dir_.size() > 0 && !utils::RecursiveUnlinkDir(p2p_dir_)) {
+      PLOG(ERROR) << "Unable to unlink files and directory in " << p2p_dir_;
+    }
+  }
+
+  // P2PManager::Configuration override
+  virtual FilePath GetP2PDir() {
+    return FilePath(p2p_dir_);
+  };
+
+  // P2PManager::Configuration override
+  virtual std::vector<std::string> GetInitctlArgs(bool is_start) {
+    return is_start ? initctl_start_args_ : initctl_stop_args_;
+  }
+
+  // P2PManager::Configuration override
+  virtual std::vector<std::string> GetP2PClientArgs(const std::string &file_id,
+                                                    size_t minimum_size) {
+    std::string formatted_command_line =
+        base::StringPrintf(p2p_client_cmdline_format_.c_str(),
+                           file_id.c_str(), minimum_size);
+    return ParseCommandLine(formatted_command_line);
+  }
+
+  // Use |command_line| instead of "initctl start p2p" when attempting
+  // to start the p2p service.
+  void SetInitctlStartCommandLine(const std::string &command_line) {
+    initctl_start_args_ = ParseCommandLine(command_line);
+  }
+
+  // Use |command_line| instead of "initctl stop p2p" when attempting
+  // to stop the p2p service.
+  void SetInitctlStopCommandLine(const std::string &command_line) {
+    initctl_stop_args_ = ParseCommandLine(command_line);
+  }
+
+  // Use |command_line_format| instead of "p2p-client --get-url=%s
+  // --minimum-size=%zu" when attempting to look up a file using
+  // p2p-client(1).
+  //
+  // The passed |command_line_format| argument should be a
+  // printf()-style format string taking two arguments, the first
+  // being the a C string for the p2p file id (e.g. %s) and the second
+  // being a size_t with the minimum_size.
+  void SetP2PClientCommandLine(const std::string &command_line_format) {
+    p2p_client_cmdline_format_ = command_line_format;
+  }
+
+private:
+  // Helper for parsing and splitting |command_line| into an argument
+  // vector in much the same way a shell would except for not
+  // supporting wildcards, globs, operators etc. See
+  // g_shell_parse_argv() for more details. If an error occurs, the
+  // empty vector is returned.
+  std::vector<std::string> ParseCommandLine(const std::string &command_line) {
+    gint argc;
+    gchar **argv;
+    std::vector<std::string> ret;
+
+    if (!g_shell_parse_argv(command_line.c_str(),
+                            &argc,
+                            &argv,
+                            NULL)) {
+      LOG(ERROR) << "Error splitting '" << command_line << "'";
+      return ret;
+    }
+    for (int n = 0; n < argc; n++)
+      ret.push_back(argv[n]);
+    g_strfreev(argv);
+    return ret;
+  }
+
+  // The temporary directory used for p2p.
+  std::string p2p_dir_;
+
+  // Argument vector for starting p2p.
+  std::vector<std::string> initctl_start_args_;
+
+  // Argument vector for stopping p2p.
+  std::vector<std::string> initctl_stop_args_;
+
+  // A printf()-style format string for generating the p2p-client format.
+  // See the SetP2PClientCommandLine() for details.
+  std::string p2p_client_cmdline_format_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeP2PManagerConfiguration);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H__
diff --git a/p2p_manager.cc b/p2p_manager.cc
new file mode 100644
index 0000000..e56f739
--- /dev/null
+++ b/p2p_manager.cc
@@ -0,0 +1,743 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This provides access to timestamps with nano-second resolution in
+// struct stat, See NOTES in stat(2) for details.
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+
+#include "update_engine/p2p_manager.h"
+
+#include <attr/xattr.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <glib.h>
+#include <linux/falloc.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <unistd.h>
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include <base/file_path.h>
+#include <base/logging.h>
+#include <base/stringprintf.h>
+
+#include "update_engine/utils.h"
+
+using base::FilePath;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using std::map;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The default p2p directory.
+const char kDefaultP2PDir[] = "/var/cache/p2p";
+
+// The p2p xattr used for conveying the final size of a file - see the
+// p2p ddoc for details.
+const char kCrosP2PFileSizeXAttrName[] = "user.cros-p2p-filesize";
+
+} // namespace
+
+// The default P2PManager::Configuration implementation.
+class ConfigurationImpl : public P2PManager::Configuration {
+public:
+  ConfigurationImpl() {}
+
+  virtual ~ConfigurationImpl() {}
+
+  virtual FilePath GetP2PDir() {
+    return FilePath(kDefaultP2PDir);
+  }
+
+  virtual vector<string> GetInitctlArgs(bool is_start) {
+    vector<string> args;
+    args.push_back("initctl");
+    args.push_back(is_start ? "start" : "stop");
+    args.push_back("p2p");
+    return args;
+  }
+
+  virtual vector<string> GetP2PClientArgs(const string &file_id,
+                                          size_t minimum_size) {
+    vector<string> args;
+    args.push_back("p2p-client");
+    args.push_back(string("--get-url=") + file_id);
+    args.push_back(StringPrintf("--minimum-size=%zu", minimum_size));
+    return args;
+  }
+
+private:
+  DISALLOW_COPY_AND_ASSIGN(ConfigurationImpl);
+};
+
+// The default P2PManager implementation.
+class P2PManagerImpl : public P2PManager {
+public:
+  P2PManagerImpl(Configuration *configuration,
+                 PrefsInterface *prefs,
+                 const string& file_extension,
+                 const int num_files_to_keep);
+
+  // P2PManager methods.
+  virtual void SetConfiguration(Configuration *configuration);
+  virtual bool IsP2PEnabled();
+  virtual bool EnsureP2PRunning();
+  virtual bool EnsureP2PNotRunning();
+  virtual bool PerformHousekeeping();
+  virtual void LookupUrlForFile(const string& file_id,
+                                size_t minimum_size,
+                                TimeDelta max_time_to_wait,
+                                LookupCallback callback);
+  virtual bool FileShare(const string& file_id,
+                         size_t expected_size);
+  virtual FilePath FileGetPath(const string& file_id);
+  virtual ssize_t FileGetSize(const string& file_id);
+  virtual ssize_t FileGetExpectedSize(const string& file_id);
+  virtual bool FileGetVisible(const string& file_id,
+                              bool *out_result);
+  virtual bool FileMakeVisible(const string& file_id);
+  virtual int CountSharedFiles();
+
+private:
+  // Enumeration for specifying visibility.
+  enum Visibility {
+    kVisible,
+    kNonVisible
+  };
+
+  // Returns "." + |file_extension_| + ".p2p" if |visibility| is
+  // |kVisible|. Returns the same concatenated with ".tmp" otherwise.
+  string GetExt(Visibility visibility);
+
+  // Gets the on-disk path for |file_id| depending on if the file
+  // is visible or not.
+  FilePath GetPath(const string& file_id, Visibility visibility);
+
+  // Utility function used by EnsureP2PRunning() and EnsureP2PNotRunning().
+  bool EnsureP2P(bool should_be_running);
+
+  // Configuration object.
+  scoped_ptr<Configuration> configuration_;
+
+  // Object for persisted state.
+  PrefsInterface* prefs_;
+
+  // A short string unique to the application (for example "cros_au")
+  // used to mark a file as being owned by a particular application.
+  const string file_extension_;
+
+  // If non-zero, this number denotes how many files in /var/cache/p2p
+  // owned by the application (cf. |file_extension_|) to keep after
+  // performing housekeeping.
+  const int num_files_to_keep_;
+
+  // The string ".p2p".
+  static const char kP2PExtension[];
+
+  // The string ".tmp".
+  static const char kTmpExtension[];
+
+  DISALLOW_COPY_AND_ASSIGN(P2PManagerImpl);
+};
+
+const char P2PManagerImpl::kP2PExtension[] = ".p2p";
+
+const char P2PManagerImpl::kTmpExtension[] = ".tmp";
+
+P2PManagerImpl::P2PManagerImpl(Configuration *configuration,
+                               PrefsInterface *prefs,
+                               const string& file_extension,
+                               const int num_files_to_keep)
+  : prefs_(prefs),
+    file_extension_(file_extension),
+    num_files_to_keep_(num_files_to_keep) {
+  configuration_.reset(configuration != NULL ? configuration :
+                       new ConfigurationImpl());
+}
+
+void P2PManagerImpl::SetConfiguration(Configuration *configuration) {
+  configuration_.reset(configuration);
+}
+
+bool P2PManagerImpl::IsP2PEnabled() {
+  // TODO(deymo,zeuthen)(chromium:260441): See the bug for the bigger
+  // picture. For now we just read the state variable so in order for
+  // p2p to work the developer will have to manually create the prefs
+  // file. Once the fix for bug 260441 has been merged, this can be
+  // toggled using the crosh command, as intended.
+  bool enabled = false;
+  if (prefs_ == NULL) {
+    LOG(INFO) << "No prefs object.";
+  } else if (!prefs_->Exists(kPrefsP2PEnabled)) {
+    LOG(INFO) << "The " << kPrefsP2PEnabled << " pref does not exist.";
+  } else if (!prefs_->GetBoolean(kPrefsP2PEnabled, &enabled)) {
+    LOG(ERROR) << "Error getting " << kPrefsP2PEnabled << " pref.";
+  }
+  LOG(INFO) << "Returning value " << enabled << " for whether p2p is enabled.";
+  return enabled;
+}
+
+bool P2PManagerImpl::EnsureP2P(bool should_be_running) {
+  gchar *standard_error = NULL;
+  GError *error = NULL;
+  gint exit_status = 0;
+
+  vector<string> args = configuration_->GetInitctlArgs(should_be_running);
+  scoped_ptr<gchar*, GLibStrvFreeDeleter> argv(
+      utils::StringVectorToGStrv(args));
+  if (!g_spawn_sync(NULL, // working_directory
+                    argv.get(),
+                    NULL, // envp
+                    static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH),
+                    NULL, NULL,      // child_setup, user_data
+                    NULL,            // standard_output
+                    &standard_error,
+                    &exit_status,
+                    &error)) {
+    LOG(ERROR) << "Error spawning " << utils::StringVectorToString(args)
+               << ": " << utils::GetAndFreeGError(&error);
+    return false;
+  }
+  scoped_ptr<gchar, GLibFreeDeleter> standard_error_deleter(standard_error);
+
+  if (!WIFEXITED(exit_status)) {
+    LOG(ERROR) << "Error spawning '" << utils::StringVectorToString(args)
+               << "': WIFEXITED is false";
+    return false;
+  }
+
+  // If initctl(8) exits normally with exit status 0 ("success"), it
+  // meant that it did what we requested.
+  if (WEXITSTATUS(exit_status) == 0) {
+    return true;
+  }
+
+  // Otherwise, screenscape stderr from initctl(8). Ugh, yes, this is
+  // ugly but since the program lacks verbs/actions such as
+  //
+  //  ensure-started (or start-or-return-success-if-already-started)
+  //  ensure-stopped (or stop-or-return-success-if-not-running)
+  //
+  // this is what we have to do.
+  //
+  // TODO(zeuthen,chromium:277051): Avoid doing this.
+  const gchar *expected_error_message = should_be_running ?
+    "initctl: Job is already running: p2p\n" :
+    "initctl: Unknown instance \n";
+  if (g_strcmp0(standard_error, expected_error_message) == 0) {
+    return true;
+  }
+
+  return false;
+}
+
+bool P2PManagerImpl::EnsureP2PRunning() {
+  return EnsureP2P(true);
+}
+
+bool P2PManagerImpl::EnsureP2PNotRunning() {
+  return EnsureP2P(false);
+}
+
+// Returns True if the timestamp in the first pair is greater than the
+// timestamp in the latter. If used with std::sort() this will yield a
+// sequence of elements where newer (high timestamps) elements precede
+// older ones (low timestamps).
+static bool MatchCompareFunc(const pair<FilePath, Time>& a,
+                             const pair<FilePath, Time>& b) {
+  return a.second > b.second;
+}
+
+string P2PManagerImpl::GetExt(Visibility visibility) {
+  string ext = string(".") + file_extension_ + kP2PExtension;
+  switch (visibility) {
+  case kVisible:
+    break;
+  case kNonVisible:
+    ext += kTmpExtension;
+    break;
+  // Don't add a default case to let the compiler warn about newly
+  // added enum values.
+  }
+  return ext;
+}
+
+FilePath P2PManagerImpl::GetPath(const string& file_id, Visibility visibility) {
+  return configuration_->GetP2PDir().Append(file_id + GetExt(visibility));
+}
+
+bool P2PManagerImpl::PerformHousekeeping() {
+  GDir* dir = NULL;
+  GError* error = NULL;
+  const char* name = NULL;
+  vector<pair<FilePath, Time> > matches;
+
+  // Go through all files in the p2p dir and pick the ones that match
+  // and get their ctime.
+  FilePath p2p_dir = configuration_->GetP2PDir();
+  dir = g_dir_open(p2p_dir.value().c_str(), 0, &error);
+  if (dir == NULL) {
+    LOG(ERROR) << "Error opening directory " << p2p_dir.value() << ": "
+               << utils::GetAndFreeGError(&error);
+    return false;
+  }
+
+  if (num_files_to_keep_ == 0)
+    return true;
+
+  string ext_visible = GetExt(kVisible);
+  string ext_non_visible = GetExt(kNonVisible);
+  while ((name = g_dir_read_name(dir)) != NULL) {
+    if (!(g_str_has_suffix(name, ext_visible.c_str()) ||
+          g_str_has_suffix(name, ext_non_visible.c_str())))
+      continue;
+
+    struct stat statbuf;
+    FilePath file = p2p_dir.Append(name);
+    if (stat(file.value().c_str(), &statbuf) != 0) {
+      PLOG(ERROR) << "Error getting file status for " << file.value();
+      continue;
+    }
+
+    Time time = utils::TimeFromStructTimespec(&statbuf.st_ctim);
+    matches.push_back(std::make_pair(file, time));
+  }
+  g_dir_close(dir);
+
+  // Sort list of matches, newest (biggest time) to oldest (lowest time).
+  std::sort(matches.begin(), matches.end(), MatchCompareFunc);
+
+  // Delete starting at element num_files_to_keep_.
+  vector<pair<FilePath, Time> >::const_iterator i;
+  for (i = matches.begin() + num_files_to_keep_; i < matches.end(); ++i) {
+    const FilePath& file = i->first;
+    LOG(INFO) << "Deleting p2p file " << file.value();
+    if (unlink(file.value().c_str()) != 0) {
+      PLOG(ERROR) << "Error deleting p2p file " << file.value();
+      return false;
+    }
+  }
+
+  return true;
+}
+
+// Helper class for implementing LookupUrlForFile().
+class LookupData {
+public:
+  LookupData(P2PManager::LookupCallback callback)
+    : callback_(callback),
+      pid_(0),
+      stdout_fd_(-1),
+      stdout_channel_source_id_(0),
+      child_watch_source_id_(0),
+      timeout_source_id_(0),
+      reported_(false) {}
+
+  ~LookupData() {
+    if (child_watch_source_id_ != 0)
+      g_source_remove(child_watch_source_id_);
+    if (stdout_channel_source_id_ != 0)
+      g_source_remove(stdout_channel_source_id_);
+    if (timeout_source_id_ != 0)
+      g_source_remove(timeout_source_id_);
+    if (stdout_fd_ != -1)
+      close(stdout_fd_);
+    if (pid_ != 0)
+      kill(pid_, SIGTERM);
+  }
+
+  void InitiateLookup(gchar **argv, TimeDelta timeout) {
+    // NOTE: if we fail early (i.e. in this method), we need to schedule
+    // an idle to report the error. This is because we guarantee that
+    // the callback is always called from from the GLib mainloop (this
+    // guarantee is useful for testing).
+
+    GError *error = NULL;
+    if (!g_spawn_async_with_pipes(NULL, // working_directory
+                                  argv,
+                                  NULL, // envp
+                                  static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH |
+                                                     G_SPAWN_DO_NOT_REAP_CHILD),
+                                  NULL, // child_setup
+                                  this,
+                                  &pid_,
+                                  NULL, // standard_input
+                                  &stdout_fd_,
+                                  NULL, // standard_error
+                                  &error)) {
+      LOG(ERROR) << "Error spawning p2p-client: "
+                 << utils::GetAndFreeGError(&error);
+      ReportErrorAndDeleteInIdle();
+      return;
+    }
+
+    GIOChannel* io_channel = g_io_channel_unix_new(stdout_fd_);
+    stdout_channel_source_id_ = g_io_add_watch(
+        io_channel,
+        static_cast<GIOCondition>(G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP),
+        OnIOChannelActivity, this);
+    CHECK(stdout_channel_source_id_ != 0);
+    g_io_channel_unref(io_channel);
+
+    child_watch_source_id_ = g_child_watch_add(pid_, OnChildWatchActivity,
+                                               this);
+    CHECK(child_watch_source_id_ != 0);
+
+    if (timeout.ToInternalValue() > 0) {
+      timeout_source_id_ = g_timeout_add(timeout.InMilliseconds(),
+                                         OnTimeout, this);
+      CHECK(timeout_source_id_ != 0);
+    }
+  }
+
+private:
+  void ReportErrorAndDeleteInIdle() {
+    g_idle_add(static_cast<GSourceFunc>(OnIdleForReportErrorAndDelete), this);
+  }
+
+  static gboolean OnIdleForReportErrorAndDelete(gpointer user_data) {
+    LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
+    lookup_data->ReportError();
+    delete lookup_data;
+    return FALSE; // Remove source.
+  }
+
+  void IssueCallback(const string& url) {
+    if (!callback_.is_null())
+      callback_.Run(url);
+  }
+
+  void ReportError() {
+    if (reported_)
+      return;
+    IssueCallback("");
+    reported_ = true;
+  }
+
+  void ReportSuccess() {
+    if (reported_)
+      return;
+
+    string url = stdout_;
+    size_t newline_pos = url.find('\n');
+    if (newline_pos != string::npos)
+      url.resize(newline_pos);
+
+    // Since p2p-client(1) is constructing this URL itself strictly
+    // speaking there's no need to validate it... but, anyway, can't
+    // hurt.
+    if (url.compare(0, 7, "http://") == 0) {
+      IssueCallback(url);
+    } else {
+      LOG(ERROR) << "p2p URL '" << url << "' does not look right. Ignoring.";
+      ReportError();
+    }
+
+    reported_ = true;
+  }
+
+  static gboolean OnIOChannelActivity(GIOChannel *source,
+                                      GIOCondition condition,
+                                      gpointer user_data) {
+    LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
+    gchar* str = NULL;
+    GError* error = NULL;
+    GIOStatus status = g_io_channel_read_line(source,
+                                              &str,
+                                              NULL,  // len
+                                              NULL,  // line_terminator
+                                              &error);
+    if (status != G_IO_STATUS_NORMAL) {
+      // Ignore EOF since we usually get that before SIGCHLD and we
+      // need to examine exit status there.
+      if (status != G_IO_STATUS_EOF) {
+        LOG(ERROR) << "Error reading a line from p2p-client: "
+                   << utils::GetAndFreeGError(&error);
+        lookup_data->ReportError();
+        delete lookup_data;
+      }
+    } else {
+      if (str != NULL) {
+        lookup_data->stdout_ += str;
+        g_free(str);
+      }
+    }
+    return TRUE; // Don't remove source.
+  }
+
+  static void OnChildWatchActivity(GPid pid,
+                                   gint status,
+                                   gpointer user_data) {
+    LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
+
+    if (!WIFEXITED(status)) {
+      LOG(ERROR) << "Child didn't exit normally";
+      lookup_data->ReportError();
+    } else if (WEXITSTATUS(status) != 0) {
+      LOG(INFO) << "Child exited with non-zero exit code "
+                << WEXITSTATUS(status);
+      lookup_data->ReportError();
+    } else {
+      lookup_data->ReportSuccess();
+    }
+    delete lookup_data;
+  }
+
+  static gboolean OnTimeout(gpointer user_data) {
+    LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
+    lookup_data->ReportError();
+    delete lookup_data;
+    return TRUE; // Don't remove source.
+  }
+
+  P2PManager::LookupCallback callback_;
+  GPid pid_;
+  gint stdout_fd_;
+  guint stdout_channel_source_id_;
+  guint child_watch_source_id_;
+  guint timeout_source_id_;
+  string stdout_;
+  bool reported_;
+};
+
+void P2PManagerImpl::LookupUrlForFile(const string& file_id,
+                                      size_t minimum_size,
+                                      TimeDelta max_time_to_wait,
+                                      LookupCallback callback) {
+  LookupData *lookup_data = new LookupData(callback);
+  string file_id_with_ext = file_id + "." + file_extension_;
+  vector<string> args = configuration_->GetP2PClientArgs(file_id_with_ext,
+                                                         minimum_size);
+  gchar **argv = utils::StringVectorToGStrv(args);
+  lookup_data->InitiateLookup(argv, max_time_to_wait);
+  g_strfreev(argv);
+}
+
+bool P2PManagerImpl::FileShare(const string& file_id,
+                               size_t expected_size) {
+  // Check if file already exist.
+  FilePath path = FileGetPath(file_id);
+  if (!path.empty()) {
+    // File exists - double check its expected size though.
+    ssize_t file_expected_size = FileGetExpectedSize(file_id);
+    if (file_expected_size == -1 ||
+        static_cast<size_t>(file_expected_size) != expected_size) {
+      LOG(ERROR) << "Existing p2p file " << path.value()
+                 << " with expected_size=" << file_expected_size
+                 << " does not match the passed in"
+                 << " expected_size=" << expected_size;
+      return false;
+    }
+    return true;
+  }
+
+  // Before creating the file, bail if statvfs(3) indicates that at
+  // least twice the size is not available in P2P_DIR.
+  struct statvfs statvfsbuf;
+  FilePath p2p_dir = configuration_->GetP2PDir();
+  if (statvfs(p2p_dir.value().c_str(), &statvfsbuf) != 0) {
+    PLOG(ERROR) << "Error calling statvfs() for dir " << p2p_dir.value();
+    return false;
+  }
+  size_t free_bytes =
+      static_cast<size_t>(statvfsbuf.f_bsize) * statvfsbuf.f_bavail;
+  if (free_bytes < 2 * expected_size) {
+    // This can easily happen and is worth reporting.
+    LOG(INFO) << "Refusing to allocate p2p file of " << expected_size
+              << " bytes since the directory " << p2p_dir.value()
+              << " only has " << free_bytes
+              << " bytes available and this is less than twice the"
+              << " requested size.";
+    return false;
+  }
+
+  // Okie-dokey looks like enough space is available - create the file.
+  path = GetPath(file_id, kNonVisible);
+  int fd = open(path.value().c_str(), O_CREAT | O_RDWR, 0644);
+  if (fd == -1) {
+    PLOG(ERROR) << "Error creating file with path " << path.value();
+    return false;
+  }
+  ScopedFdCloser fd_closer(&fd);
+
+  // If the final size is known, allocate the file (e.g. reserve disk
+  // space) and set the user.cros-p2p-filesize xattr.
+  if (expected_size != 0) {
+    if (fallocate(fd,
+                  FALLOC_FL_KEEP_SIZE, // Keep file size as 0.
+                  0,
+                  expected_size) != 0) {
+      // ENOSPC can happen (funky race though, cf. the statvfs() check
+      // above), handle it gracefully, e.g. use logging level INFO.
+      //
+      // NOTE: we *could* handle ENOSYS gracefully (ie. we could
+      // ignore it) but currently we don't because running out of
+      // space later sounds absolutely horrible. Better to fail fast.
+      PLOG(INFO) << "Error allocating " << expected_size
+                 << " bytes for file " << path.value();
+      if (unlink(path.value().c_str()) != 0) {
+        PLOG(ERROR) << "Error deleting file with path " << path.value();
+      }
+      return false;
+    }
+
+    string decimal_size = StringPrintf("%zu", expected_size);
+    if (fsetxattr(fd, kCrosP2PFileSizeXAttrName,
+                  decimal_size.c_str(), decimal_size.size(), 0) != 0) {
+      PLOG(ERROR) << "Error setting xattr " << path.value();
+      return false;
+    }
+  }
+
+  return true;
+}
+
+FilePath P2PManagerImpl::FileGetPath(const string& file_id) {
+  struct stat statbuf;
+  FilePath path;
+
+  path = GetPath(file_id, kVisible);
+  if (stat(path.value().c_str(), &statbuf) == 0) {
+    return path;
+  }
+
+  path = GetPath(file_id, kNonVisible);
+  if (stat(path.value().c_str(), &statbuf) == 0) {
+    return path;
+  }
+
+  path.clear();
+  return path;
+}
+
+bool P2PManagerImpl::FileGetVisible(const string& file_id,
+                                    bool *out_result) {
+  FilePath path = FileGetPath(file_id);
+  if (path.empty()) {
+    LOG(ERROR) << "No file for id " << file_id;
+    return false;
+  }
+  if (out_result != NULL)
+    *out_result = path.MatchesExtension(kP2PExtension);
+  return true;
+}
+
+bool P2PManagerImpl::FileMakeVisible(const string& file_id) {
+  FilePath path = FileGetPath(file_id);
+  if (path.empty()) {
+    LOG(ERROR) << "No file for id " << file_id;
+    return false;
+  }
+
+  // Already visible?
+  if (path.MatchesExtension(kP2PExtension))
+    return true;
+
+  LOG_ASSERT(path.MatchesExtension(kTmpExtension));
+  FilePath new_path = path.RemoveExtension();
+  LOG_ASSERT(new_path.MatchesExtension(kP2PExtension));
+  if (rename(path.value().c_str(), new_path.value().c_str()) != 0) {
+    PLOG(ERROR) << "Error renaming " << path.value()
+                << " to " << new_path.value();
+    return false;
+  }
+
+  return true;
+}
+
+ssize_t P2PManagerImpl::FileGetSize(const string& file_id) {
+  FilePath path = FileGetPath(file_id);
+  if (path.empty())
+    return -1;
+
+  struct stat statbuf;
+  if (stat(path.value().c_str(), &statbuf) != 0) {
+    PLOG(ERROR) << "Error getting file status for " << path.value();
+    return -1;
+  }
+
+  return statbuf.st_size;
+}
+
+ssize_t P2PManagerImpl::FileGetExpectedSize(const string& file_id) {
+  FilePath path = FileGetPath(file_id);
+  if (path.empty())
+    return -1;
+
+  char ea_value[64] = { 0 };
+  ssize_t ea_size;
+  ea_size = getxattr(path.value().c_str(), kCrosP2PFileSizeXAttrName,
+                     &ea_value, sizeof(ea_value) - 1);
+  if (ea_size == -1) {
+    PLOG(ERROR) << "Error calling getxattr() on file " << path.value();
+    return -1;
+  }
+
+  char* endp = NULL;
+  long long int val = strtoll(ea_value, &endp, 0);
+  if (*endp != '\0') {
+    LOG(ERROR) << "Error parsing the value '" << ea_value
+               << "' of the xattr " << kCrosP2PFileSizeXAttrName
+               << " as an integer";
+    return -1;
+  }
+
+  return val;
+}
+
+int P2PManagerImpl::CountSharedFiles() {
+  GDir* dir;
+  GError* error = NULL;
+  const char* name;
+  int num_files = 0;
+
+  FilePath p2p_dir = configuration_->GetP2PDir();
+  dir = g_dir_open(p2p_dir.value().c_str(), 0, &error);
+  if (dir == NULL) {
+    LOG(ERROR) << "Error opening directory " << p2p_dir.value() << ": "
+               << utils::GetAndFreeGError(&error);
+    return -1;
+  }
+
+  string ext_visible = GetExt(kVisible);
+  string ext_non_visible = GetExt(kNonVisible);
+  while ((name = g_dir_read_name(dir)) != NULL) {
+    if (g_str_has_suffix(name, ext_visible.c_str()) ||
+        g_str_has_suffix(name, ext_non_visible.c_str())) {
+      num_files += 1;
+    }
+  }
+  g_dir_close(dir);
+
+  return num_files;
+}
+
+P2PManager* P2PManager::Construct(Configuration *configuration,
+                                  PrefsInterface *prefs,
+                                  const string& file_extension,
+                                  const int num_files_to_keep) {
+  return new P2PManagerImpl(configuration,
+                            prefs,
+                            file_extension,
+                            num_files_to_keep);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/p2p_manager.h b/p2p_manager.h
new file mode 100644
index 0000000..a38f648
--- /dev/null
+++ b/p2p_manager.h
@@ -0,0 +1,164 @@
+// Copyright (c) 2013 The Chromium OS 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 CHROMEOS_PLATFORM_UPDATE_ENGINE_P2P_MANAGER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_P2P_MANAGER_H__
+
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/file_path.h>
+#include <base/memory/ref_counted.h>
+#include <base/time.h>
+
+#include "update_engine/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+// Interface for sharing and discovering files via p2p.
+class P2PManager {
+public:
+  // Interface used for P2PManager implementations. The sole reason
+  // for this interface is unit testing.
+  class Configuration {
+  public:
+    virtual ~Configuration() {}
+
+    // Gets the path to the p2p dir being used, e.g. /var/cache/p2p.
+    virtual base::FilePath GetP2PDir() = 0;
+
+    // Gets the argument vector for starting (if |is_start| is True)
+    // resp. stopping (if |is_start| is False) the p2p service
+    // e.g. ["initctl", "start", "p2p"] or ["initctl", "stop", "p2p"].
+    virtual std::vector<std::string> GetInitctlArgs(bool is_start) = 0;
+
+    // Gets the argument vector for invoking p2p-client, e.g.
+    // "p2p-client --get-url=file_id_we_want --minimum-size=42123".
+    virtual std::vector<std::string> GetP2PClientArgs(
+        const std::string& file_id, size_t minimum_size) = 0;
+  };
+
+  virtual ~P2PManager() {}
+
+  // The type for the callback used in LookupUrlForFile().
+  // If the lookup failed, |url| is empty.
+  typedef base::Callback<void(const std::string& url)> LookupCallback;
+
+  // Returns true if - and only if - P2P should be used on this
+  // device. This value is derived from a variety of sources including
+  // enterprise policy.
+  virtual bool IsP2PEnabled() = 0;
+
+  // Ensures that the p2p subsystem is running (e.g. starts it if it's
+  // not already running) and blocks until this is so. Returns false
+  // if an error occurred.
+  virtual bool EnsureP2PRunning() = 0;
+
+  // Ensures that the p2p subsystem is not running (e.g. stops it if
+  // it's running) and blocks until this is so. Returns false if an
+  // error occurred.
+  virtual bool EnsureP2PNotRunning() = 0;
+
+  // Cleans up files in /var/cache/p2p owned by this application as
+  // per the |file_extension| and |num_files_to_keep| values passed
+  // when the object was constructed. This may be called even if
+  // the p2p subsystem is not running.
+  virtual bool PerformHousekeeping() = 0;
+
+  // Asynchronously finds a peer that serves the file identified by
+  // |file_id|. If |minimum_size| is non-zero, will find a peer that
+  // has at least that many bytes. When the result is ready |callback|
+  // is called from the default GLib mainloop.
+  //
+  // This operation may take a very long time to complete because part
+  // of the p2p protocol involves waiting for the LAN-wide sum of all
+  // num-connections to drop below a given threshold. However, if
+  // |max_time_to_wait| is non-zero, the operation is guaranteed to
+  // not exceed this duration.
+  //
+  // If the file is not available on the LAN (or if mDNS/DNS-SD is
+  // filtered), this is guaranteed to not take longer than 5 seconds.
+  virtual void LookupUrlForFile(const std::string& file_id,
+                                size_t minimum_size,
+                                base::TimeDelta max_time_to_wait,
+                                LookupCallback callback) = 0;
+
+  // Shares a file identified by |file_id| in the directory
+  // /var/cache/p2p. Initially the file will not be visible, that is,
+  // it will have a .tmp extension and not be shared via p2p. Use the
+  // FileMakeVisible() method to change this.
+  //
+  // If you know the final size of the file, pass it in the
+  // |expected_size| parameter. Otherwise pass zero. If non-zero, the
+  // amount of free space in /var/cache/p2p is checked and if there is
+  // less than twice the amount of space available, this method
+  // fails. Additionally, disk space will be reserved via fallocate(2)
+  // and |expected_size| is written to the user.cros-p2p-filesize
+  // xattr of the created file.
+  //
+  // If the file already exists, true is returned. Any on-disk xattr
+  // is not updated.
+  virtual bool FileShare(const std::string& file_id,
+                         size_t expected_size) = 0;
+
+  // Gets a fully qualified path for the file identified by |file_id|.
+  // If the file has not been shared already using the FileShare()
+  // method, an empty FilePath is returned - use FilePath::empty() to
+  // find out.
+  virtual base::FilePath FileGetPath(const std::string& file_id) = 0;
+
+  // Gets the actual size of the file identified by |file_id|. This is
+  // equivalent to reading the value of the st_size field of the
+  // struct stat on the file given by FileGetPath(). Returns -1 if an
+  // error occurs.
+  //
+  // For a file just created with FileShare() this will return 0.
+  virtual ssize_t FileGetSize(const std::string& file_id) = 0;
+
+  // Gets the expected size of the file identified by |file_id|. This
+  // is equivalent to reading the value of the user.cros-p2p-filesize
+  // xattr on the file given by FileGetPath(). Returns -1 if an error
+  // occurs.
+  //
+  // For a file just created with FileShare() this will return the
+  // value of the |expected_size| parameter passed to that method.
+  virtual ssize_t FileGetExpectedSize(const std::string& file_id) = 0;
+
+  // Gets whether the file identified by |file_id| is publicly
+  // visible. If |out_result| is not NULL, the result is returned
+  // there. Returns false if an error occurs.
+  virtual bool FileGetVisible(const std::string& file_id,
+                              bool *out_result) = 0;
+
+  // Makes the file identified by |file_id| publicly visible
+  // (e.g. removes the .tmp extension). If the file is already
+  // visible, this method does nothing. Returns False if
+  // the method fails or there is no file for |file_id|.
+  virtual bool FileMakeVisible(const std::string& file_id) = 0;
+
+  // Counts the number of shared files used by this application
+  // (cf. the |file_extension parameter|. Returns -1 if an error
+  // occurred.
+  virtual int CountSharedFiles() = 0;
+
+  // Creates a suitable P2PManager instance and initializes the object
+  // so it's ready for use. The |file_extension| parameter is used to
+  // identify your application, use e.g. "cros_au".  If
+  // |configuration| is non-NULL, the P2PManager will take ownership
+  // of the Configuration object and use it (hence, it must be
+  // heap-allocated).
+  //
+  // The |num_files_to_keep| parameter specifies how many files to
+  // keep after performing housekeeping (cf. the PerformHousekeeping()
+  // method). If zero is passed, no files will be deleted.
+  static P2PManager* Construct(Configuration *configuration,
+                               PrefsInterface *prefs,
+                               const std::string& file_extension,
+                               const int num_files_to_keep);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_P2P_MANAGER_H__
diff --git a/p2p_manager_unittest.cc b/p2p_manager_unittest.cc
new file mode 100644
index 0000000..e41d1ca
--- /dev/null
+++ b/p2p_manager_unittest.cc
@@ -0,0 +1,370 @@
+// Copyright (c) 2012 The Chromium OS 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 <glib.h>
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <attr/xattr.h>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/stringprintf.h"
+
+#include "update_engine/p2p_manager.h"
+#include "update_engine/fake_p2p_manager_configuration.h"
+#include "update_engine/prefs.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+using base::TimeDelta;
+
+namespace chromeos_update_engine {
+
+// Test fixture that sets up a testing configuration (with e.g. a
+// temporary p2p dir) for P2PManager and cleans up when the test is
+// done.
+class P2PManagerTest : public testing::Test {
+protected:
+  P2PManagerTest() {}
+  virtual ~P2PManagerTest() {}
+
+  // Derived from testing::Test.
+  virtual void SetUp() {
+    test_conf_ = new FakeP2PManagerConfiguration();
+  }
+  virtual void TearDown() {}
+
+  // The P2PManager::Configuration instance used for testing.
+  FakeP2PManagerConfiguration *test_conf_;
+};
+
+
+// Check that result of IsP2PEnabled() correspond to the
+// kPrefsP2PEnabled state variable.
+TEST_F(P2PManagerTest, P2PEnabled) {
+  string temp_dir;
+  Prefs prefs;
+  EXPECT_TRUE(utils::MakeTempDirectory("/tmp/PayloadStateP2PTests.XXXXXX",
+                                       &temp_dir));
+  prefs.Init(FilePath(temp_dir));
+
+  scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_,
+                                                       &prefs, "cros_au", 3));
+  EXPECT_FALSE(manager->IsP2PEnabled());
+  prefs.SetBoolean(kPrefsP2PEnabled, true);
+  EXPECT_TRUE(manager->IsP2PEnabled());
+  prefs.SetBoolean(kPrefsP2PEnabled, false);
+  EXPECT_FALSE(manager->IsP2PEnabled());
+
+  EXPECT_TRUE(utils::RecursiveUnlinkDir(temp_dir));
+}
+
+// Check that we keep the $N newest files with the .$EXT.p2p extension.
+TEST_F(P2PManagerTest, HouseKeeping) {
+  scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_,
+                                                       NULL, "cros_au", 3));
+  EXPECT_EQ(manager->CountSharedFiles(), 0);
+
+  // Generate files both matching our pattern and not matching them.
+  for (int n = 0; n < 5; n++) {
+    EXPECT_EQ(0, System(StringPrintf("touch %s/file_%d.cros_au.p2p",
+                                     test_conf_->GetP2PDir().value().c_str(),
+                                     n)));
+
+    EXPECT_EQ(0, System(StringPrintf("touch %s/file_%d.OTHER.p2p",
+                                     test_conf_->GetP2PDir().value().c_str(),
+                                     n)));
+
+    // Sleep one micro-second to ensure that the files all have
+    // different timestamps (time resolution for ext4 is one
+    // nano-second so sleeping a single usec is more than enough).
+    g_usleep(1);
+  }
+  // CountSharedFiles() only counts 'cros_au' files.
+  EXPECT_EQ(manager->CountSharedFiles(), 5);
+
+  EXPECT_TRUE(manager->PerformHousekeeping());
+
+  // At this point - after HouseKeeping - we should only have
+  // eight files left.
+  for (int n = 0; n < 5; n++) {
+    string file_name;
+    bool expect;
+
+    expect = (n >= 2);
+    file_name = StringPrintf("%s/file_%d.cros_au.p2p",
+                             test_conf_->GetP2PDir().value().c_str(), n);
+    EXPECT_EQ(!!g_file_test(file_name.c_str(), G_FILE_TEST_EXISTS), expect);
+
+    file_name = StringPrintf("%s/file_%d.OTHER.p2p",
+                             test_conf_->GetP2PDir().value().c_str(), n);
+    EXPECT_TRUE(g_file_test(file_name.c_str(), G_FILE_TEST_EXISTS));
+  }
+  // CountSharedFiles() only counts 'cros_au' files.
+  EXPECT_EQ(manager->CountSharedFiles(), 3);
+}
+
+static bool CheckP2PFile(const string& p2p_dir, const string& file_name,
+                         ssize_t expected_size, ssize_t expected_size_xattr) {
+  string path = p2p_dir + "/" + file_name;
+  struct stat statbuf;
+  char ea_value[64] = { 0 };
+  ssize_t ea_size;
+
+  if (stat(path.c_str(), &statbuf) != 0) {
+    LOG(ERROR) << "File " << path << " does not exist";
+    return false;
+  }
+
+  if (expected_size != 0) {
+    if (statbuf.st_size != expected_size) {
+      LOG(ERROR) << "Expected size " << expected_size
+                 << " but size was " << statbuf.st_size;
+      return false;
+    }
+  }
+
+  if (expected_size_xattr == 0) {
+    ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize",
+                       &ea_value, sizeof ea_value - 1);
+    if (ea_size == -1 && errno == ENOATTR) {
+      // This is valid behavior as we support files without the xattr set.
+    } else {
+      PLOG(ERROR) << "getxattr() didn't fail with ENOATTR as expected, "
+                  << "ea_size=" << ea_size << ", errno=" << errno;
+      return false;
+    }
+  } else {
+    ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize",
+                       &ea_value, sizeof ea_value - 1);
+    if (ea_size < 0) {
+      LOG(ERROR) << "Error getting xattr attribute";
+      return false;
+    }
+    char* endp = NULL;
+    long long int val = strtoll(ea_value, &endp, 0);
+    if (endp == NULL || *endp != '\0') {
+      LOG(ERROR) << "Error parsing xattr '" << ea_value
+                 << "' as an integer";
+      return false;
+    }
+    if (val != expected_size_xattr) {
+      LOG(ERROR) << "Expected xattr size " << expected_size_xattr
+                 << " but size was " << val;
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static bool CreateP2PFile(string p2p_dir, string file_name,
+                          size_t size, size_t size_xattr) {
+  string path = p2p_dir + "/" + file_name;
+
+  int fd = open(path.c_str(), O_CREAT|O_RDWR, 0644);
+  if (fd == -1) {
+    PLOG(ERROR) << "Error creating file with path " << path;
+    return false;
+  }
+  if (ftruncate(fd, size) != 0) {
+    PLOG(ERROR) << "Error truncating " << path << " to size " << size;
+    close(fd);
+    return false;
+  }
+
+  if (size_xattr != 0) {
+    string decimal_size = StringPrintf("%zu", size_xattr);
+    if (fsetxattr(fd, "user.cros-p2p-filesize",
+                  decimal_size.c_str(), decimal_size.size(), 0) != 0) {
+      PLOG(ERROR) << "Error setting xattr on " << path;
+      close(fd);
+      return false;
+    }
+  }
+
+  close(fd);
+  return true;
+}
+
+// Check that sharing a *new* file works.
+TEST_F(P2PManagerTest, ShareFile) {
+  scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_,
+                                                       NULL, "cros_au", 3));
+  EXPECT_TRUE(manager->FileShare("foo", 10 * 1000 * 1000));
+  EXPECT_EQ(manager->FileGetPath("foo"),
+            test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+  EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+                           "foo.cros_au.p2p.tmp", 0, 10 * 1000 * 1000));
+
+  // Sharing it again - with the same expected size - should return true
+  EXPECT_TRUE(manager->FileShare("foo", 10 * 1000 * 1000));
+
+  // ... but if we use the wrong size, it should fail
+  EXPECT_FALSE(manager->FileShare("foo", 10 * 1000 * 1000 + 1));
+}
+
+// Check that making a shared file visible, does what is expected.
+TEST_F(P2PManagerTest, MakeFileVisible) {
+  scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_,
+                                                       NULL, "cros_au", 3));
+  // First, check that it's not visible.
+  manager->FileShare("foo", 10*1000*1000);
+  EXPECT_EQ(manager->FileGetPath("foo"),
+            test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+  EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+                           "foo.cros_au.p2p.tmp", 0, 10 * 1000 * 1000));
+  // Make the file visible and check that it changed its name. Do it
+  // twice to check that FileMakeVisible() is idempotent.
+  for (int n = 0; n < 2; n++) {
+    manager->FileMakeVisible("foo");
+    EXPECT_EQ(manager->FileGetPath("foo"),
+              test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+    EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+                             "foo.cros_au.p2p", 0, 10 * 1000 * 1000));
+  }
+}
+
+// Check that we return the right values for existing files in P2P_DIR.
+TEST_F(P2PManagerTest, ExistingFiles) {
+  scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_,
+                                                       NULL, "cros_au", 3));
+  bool visible;
+
+  // Check that errors are returned if the file does not exist
+  EXPECT_EQ(manager->FileGetPath("foo"), FilePath());
+  EXPECT_EQ(manager->FileGetSize("foo"), -1);
+  EXPECT_EQ(manager->FileGetExpectedSize("foo"), -1);
+  EXPECT_FALSE(manager->FileGetVisible("foo", NULL));
+  // ... then create the file ...
+  EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(),
+                            "foo.cros_au.p2p", 42, 43));
+  // ... and then check that the expected values are returned
+  EXPECT_EQ(manager->FileGetPath("foo"),
+            test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+  EXPECT_EQ(manager->FileGetSize("foo"), 42);
+  EXPECT_EQ(manager->FileGetExpectedSize("foo"), 43);
+  EXPECT_TRUE(manager->FileGetVisible("foo", &visible));
+  EXPECT_TRUE(visible);
+
+  // One more time, this time with a .tmp variant. First ensure it errors out..
+  EXPECT_EQ(manager->FileGetPath("bar"), FilePath());
+  EXPECT_EQ(manager->FileGetSize("bar"), -1);
+  EXPECT_EQ(manager->FileGetExpectedSize("bar"), -1);
+  EXPECT_FALSE(manager->FileGetVisible("bar", NULL));
+  // ... then create the file ...
+  EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(),
+                            "bar.cros_au.p2p.tmp", 44, 45));
+  // ... and then check that the expected values are returned
+  EXPECT_EQ(manager->FileGetPath("bar"),
+            test_conf_->GetP2PDir().Append("bar.cros_au.p2p.tmp"));
+  EXPECT_EQ(manager->FileGetSize("bar"), 44);
+  EXPECT_EQ(manager->FileGetExpectedSize("bar"), 45);
+  EXPECT_TRUE(manager->FileGetVisible("bar", &visible));
+  EXPECT_FALSE(visible);
+}
+
+// This is a little bit ugly but short of mocking a 'p2p' service this
+// will have to do. E.g. we essentially simulate the various
+// behaviours of initctl(8) that we rely on.
+TEST_F(P2PManagerTest, StartP2P) {
+  scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_,
+                                                       NULL, "cros_au", 3));
+
+  // Check that we can start the service
+  test_conf_->SetInitctlStartCommandLine("true");
+  EXPECT_TRUE(manager->EnsureP2PRunning());
+  test_conf_->SetInitctlStartCommandLine("false");
+  EXPECT_FALSE(manager->EnsureP2PRunning());
+  test_conf_->SetInitctlStartCommandLine(
+      "sh -c 'echo \"initctl: Job is already running: p2p\" >&2; false'");
+  EXPECT_TRUE(manager->EnsureP2PRunning());
+  test_conf_->SetInitctlStartCommandLine(
+      "sh -c 'echo something else >&2; false'");
+  EXPECT_FALSE(manager->EnsureP2PRunning());
+}
+
+// Same comment as for StartP2P
+TEST_F(P2PManagerTest, StopP2P) {
+  scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_,
+                                                       NULL, "cros_au", 3));
+
+  // Check that we can start the service
+  test_conf_->SetInitctlStopCommandLine("true");
+  EXPECT_TRUE(manager->EnsureP2PNotRunning());
+  test_conf_->SetInitctlStopCommandLine("false");
+  EXPECT_FALSE(manager->EnsureP2PNotRunning());
+  test_conf_->SetInitctlStopCommandLine(
+      "sh -c 'echo \"initctl: Unknown instance \" >&2; false'");
+  EXPECT_TRUE(manager->EnsureP2PNotRunning());
+  test_conf_->SetInitctlStopCommandLine(
+      "sh -c 'echo something else >&2; false'");
+  EXPECT_FALSE(manager->EnsureP2PNotRunning());
+}
+
+static void ExpectUrl(const string& expected_url,
+                      GMainLoop* loop,
+                      const string& url) {
+  EXPECT_EQ(url, expected_url);
+  g_main_loop_quit(loop);
+}
+
+// Like StartP2P, we're mocking the different results that p2p-client
+// can return. It's not pretty but it works.
+TEST_F(P2PManagerTest, LookupURL) {
+  scoped_ptr<P2PManager> manager(P2PManager::Construct(test_conf_,
+                                                       NULL, "cros_au", 3));
+  GMainLoop *loop = g_main_loop_new(NULL, FALSE);
+
+  // Emulate p2p-client returning valid URL with "fooX", 42 and "cros_au"
+  // being propagated in the right places.
+  test_conf_->SetP2PClientCommandLine("echo 'http://1.2.3.4/%s_%zu'");
+  manager->LookupUrlForFile("fooX", 42, TimeDelta(),
+                            base::Bind(ExpectUrl,
+                                       "http://1.2.3.4/fooX.cros_au_42", loop));
+  g_main_loop_run(loop);
+
+  // Emulate p2p-client returning invalid URL.
+  test_conf_->SetP2PClientCommandLine("echo 'not_a_valid_url'");
+  manager->LookupUrlForFile("foobar", 42, TimeDelta(),
+                            base::Bind(ExpectUrl, "", loop));
+  g_main_loop_run(loop);
+
+  // Emulate p2p-client conveying failure.
+  test_conf_->SetP2PClientCommandLine("false");
+  manager->LookupUrlForFile("foobar", 42, TimeDelta(),
+                            base::Bind(ExpectUrl, "", loop));
+  g_main_loop_run(loop);
+
+  // Emulate p2p-client not existing.
+  test_conf_->SetP2PClientCommandLine("/path/to/non/existent/helper/program");
+  manager->LookupUrlForFile("foobar", 42,
+                            TimeDelta(),
+                            base::Bind(ExpectUrl, "", loop));
+  g_main_loop_run(loop);
+
+  // Emulate p2p-client crashing.
+  test_conf_->SetP2PClientCommandLine("sh -c 'kill -SEGV $$'");
+  manager->LookupUrlForFile("foobar", 42, TimeDelta(),
+                            base::Bind(ExpectUrl, "", loop));
+  g_main_loop_run(loop);
+
+  // Emulate p2p-client exceeding its timeout.
+  test_conf_->SetP2PClientCommandLine("sh -c 'echo http://1.2.3.4/; sleep 2'");
+  manager->LookupUrlForFile("foobar", 42, TimeDelta::FromMilliseconds(500),
+                            base::Bind(ExpectUrl, "", loop));
+  g_main_loop_run(loop);
+
+  g_main_loop_unref(loop);
+}
+
+} // namespace chromeos_update_engine
diff --git a/utils.cc b/utils.cc
index 248c665..8b4bffe 100644
--- a/utils.cc
+++ b/utils.cc
@@ -1026,6 +1026,36 @@
   return true;
 }
 
+Time TimeFromStructTimespec(struct timespec *ts) {
+  int64 us = static_cast<int64>(ts->tv_sec) * Time::kMicrosecondsPerSecond +
+      static_cast<int64>(ts->tv_nsec) / Time::kNanosecondsPerMicrosecond;
+  return Time::UnixEpoch() + TimeDelta::FromMicroseconds(us);
+}
+
+gchar** StringVectorToGStrv(const vector<string> &vector) {
+  GPtrArray *p = g_ptr_array_new();
+  for (std::vector<string>::const_iterator i = vector.begin();
+       i != vector.end(); ++i) {
+    g_ptr_array_add(p, g_strdup(i->c_str()));
+  }
+  g_ptr_array_add(p, NULL);
+  return reinterpret_cast<gchar**>(g_ptr_array_free(p, FALSE));
+}
+
+string StringVectorToString(const vector<string> &vector) {
+  string str = "[";
+  for (std::vector<string>::const_iterator i = vector.begin();
+       i != vector.end(); ++i) {
+    if (i != vector.begin())
+      str += ", ";
+    str += '"';
+    str += *i;
+    str += '"';
+  }
+  str += "]";
+  return str;
+}
+
 }  // namespace utils
 
 }  // namespace chromeos_update_engine
diff --git a/utils.h b/utils.h
index 412252c..79b6917 100644
--- a/utils.h
+++ b/utils.h
@@ -35,6 +35,20 @@
 // boot mode. Returns false if the boot mode is developer.
 bool IsNormalBootMode();
 
+// Converts a struct timespec representing a number of seconds since
+// the Unix epoch to a base::Time. Sub-microsecond time is rounded
+// down.
+base::Time TimeFromStructTimespec(struct timespec *ts);
+
+// Converts a vector of strings to a NULL-terminated array of
+// strings. The resulting array should be freed with g_strfreev()
+// when are you done with it.
+gchar** StringVectorToGStrv(const std::vector<std::string> &vector);
+
+// Formats |vector| as a string of the form ["<elem1>", "<elem2>"].
+// Does no escaping, only use this for presentation in error messages.
+std::string StringVectorToString(const std::vector<std::string> &vector);
+
 // Returns the HWID or an empty string on error.
 std::string GetHardwareClass();
 
@@ -470,6 +484,24 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedActionCompleter);
 };
 
+// A base::FreeDeleter that frees memory using g_free(). Useful when
+// integrating with GLib since it can be used with scoped_ptr to
+// automatically free memory when going out of scope.
+struct GLibFreeDeleter : public base::FreeDeleter {
+  inline void operator()(void *ptr) const {
+    g_free(reinterpret_cast<gpointer>(ptr));
+  }
+};
+
+// A base::FreeDeleter that frees memory using g_strfreev(). Useful
+// when integrating with GLib since it can be used with scoped_ptr to
+// automatically free memory when going out of scope.
+struct GLibStrvFreeDeleter : public base::FreeDeleter {
+  inline void operator()(void *ptr) const {
+    g_strfreev(reinterpret_cast<gchar**>(ptr));
+  }
+};
+
 }  // namespace chromeos_update_engine
 
 #define TEST_AND_RETURN_FALSE_ERRNO(_x)                                        \
diff --git a/utils_unittest.cc b/utils_unittest.cc
index 5098912..ae7343d 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -352,4 +352,21 @@
             "2d7h33m20.001s");
 }
 
+TEST(UtilsTest, TimeFromStructTimespecTest) {
+  struct timespec ts;
+
+  // Unix epoch (Thursday 00:00:00 UTC on Jan 1, 1970)
+  ts = (struct timespec) {.tv_sec = 0, .tv_nsec = 0};
+  EXPECT_EQ(base::Time::UnixEpoch(), utils::TimeFromStructTimespec(&ts));
+
+  // 42 ms after the Unix billennium (Sunday 01:46:40 UTC on September 9, 2001)
+  ts = (struct timespec) {.tv_sec = 1000 * 1000 * 1000,
+                          .tv_nsec = 42 * 1000 * 1000};
+  base::Time::Exploded exploded = (base::Time::Exploded) {
+    .year = 2001, .month = 9, .day_of_week = 0, .day_of_month = 9,
+    .hour = 1, .minute = 46, .second = 40, .millisecond = 42};
+  EXPECT_EQ(base::Time::FromUTCExploded(exploded),
+            utils::TimeFromStructTimespec(&ts));
+}
+
 }  // namespace chromeos_update_engine