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/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