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