blob: e56f739384d4e20e3e766dccbd894cdb53013c98 [file] [log] [blame]
David Zeuthen27a48bc2013-08-06 12:06:29 -07001// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This provides access to timestamps with nano-second resolution in
6// struct stat, See NOTES in stat(2) for details.
7#ifndef _BSD_SOURCE
8#define _BSD_SOURCE
9#endif
10
11#include "update_engine/p2p_manager.h"
12
13#include <attr/xattr.h>
14#include <dirent.h>
15#include <errno.h>
16#include <fcntl.h>
17#include <glib.h>
18#include <linux/falloc.h>
19#include <signal.h>
20#include <string.h>
21#include <sys/stat.h>
22#include <sys/statvfs.h>
23#include <sys/types.h>
24#include <unistd.h>
25#include <unistd.h>
26
27#include <map>
28#include <utility>
29#include <vector>
30
31#include <base/file_path.h>
32#include <base/logging.h>
33#include <base/stringprintf.h>
34
35#include "update_engine/utils.h"
36
37using base::FilePath;
38using base::StringPrintf;
39using base::Time;
40using base::TimeDelta;
41using std::map;
42using std::pair;
43using std::string;
44using std::vector;
45
46namespace chromeos_update_engine {
47
48namespace {
49
50// The default p2p directory.
51const char kDefaultP2PDir[] = "/var/cache/p2p";
52
53// The p2p xattr used for conveying the final size of a file - see the
54// p2p ddoc for details.
55const char kCrosP2PFileSizeXAttrName[] = "user.cros-p2p-filesize";
56
57} // namespace
58
59// The default P2PManager::Configuration implementation.
60class ConfigurationImpl : public P2PManager::Configuration {
61public:
62 ConfigurationImpl() {}
63
64 virtual ~ConfigurationImpl() {}
65
66 virtual FilePath GetP2PDir() {
67 return FilePath(kDefaultP2PDir);
68 }
69
70 virtual vector<string> GetInitctlArgs(bool is_start) {
71 vector<string> args;
72 args.push_back("initctl");
73 args.push_back(is_start ? "start" : "stop");
74 args.push_back("p2p");
75 return args;
76 }
77
78 virtual vector<string> GetP2PClientArgs(const string &file_id,
79 size_t minimum_size) {
80 vector<string> args;
81 args.push_back("p2p-client");
82 args.push_back(string("--get-url=") + file_id);
83 args.push_back(StringPrintf("--minimum-size=%zu", minimum_size));
84 return args;
85 }
86
87private:
88 DISALLOW_COPY_AND_ASSIGN(ConfigurationImpl);
89};
90
91// The default P2PManager implementation.
92class P2PManagerImpl : public P2PManager {
93public:
94 P2PManagerImpl(Configuration *configuration,
95 PrefsInterface *prefs,
96 const string& file_extension,
97 const int num_files_to_keep);
98
99 // P2PManager methods.
100 virtual void SetConfiguration(Configuration *configuration);
101 virtual bool IsP2PEnabled();
102 virtual bool EnsureP2PRunning();
103 virtual bool EnsureP2PNotRunning();
104 virtual bool PerformHousekeeping();
105 virtual void LookupUrlForFile(const string& file_id,
106 size_t minimum_size,
107 TimeDelta max_time_to_wait,
108 LookupCallback callback);
109 virtual bool FileShare(const string& file_id,
110 size_t expected_size);
111 virtual FilePath FileGetPath(const string& file_id);
112 virtual ssize_t FileGetSize(const string& file_id);
113 virtual ssize_t FileGetExpectedSize(const string& file_id);
114 virtual bool FileGetVisible(const string& file_id,
115 bool *out_result);
116 virtual bool FileMakeVisible(const string& file_id);
117 virtual int CountSharedFiles();
118
119private:
120 // Enumeration for specifying visibility.
121 enum Visibility {
122 kVisible,
123 kNonVisible
124 };
125
126 // Returns "." + |file_extension_| + ".p2p" if |visibility| is
127 // |kVisible|. Returns the same concatenated with ".tmp" otherwise.
128 string GetExt(Visibility visibility);
129
130 // Gets the on-disk path for |file_id| depending on if the file
131 // is visible or not.
132 FilePath GetPath(const string& file_id, Visibility visibility);
133
134 // Utility function used by EnsureP2PRunning() and EnsureP2PNotRunning().
135 bool EnsureP2P(bool should_be_running);
136
137 // Configuration object.
138 scoped_ptr<Configuration> configuration_;
139
140 // Object for persisted state.
141 PrefsInterface* prefs_;
142
143 // A short string unique to the application (for example "cros_au")
144 // used to mark a file as being owned by a particular application.
145 const string file_extension_;
146
147 // If non-zero, this number denotes how many files in /var/cache/p2p
148 // owned by the application (cf. |file_extension_|) to keep after
149 // performing housekeeping.
150 const int num_files_to_keep_;
151
152 // The string ".p2p".
153 static const char kP2PExtension[];
154
155 // The string ".tmp".
156 static const char kTmpExtension[];
157
158 DISALLOW_COPY_AND_ASSIGN(P2PManagerImpl);
159};
160
161const char P2PManagerImpl::kP2PExtension[] = ".p2p";
162
163const char P2PManagerImpl::kTmpExtension[] = ".tmp";
164
165P2PManagerImpl::P2PManagerImpl(Configuration *configuration,
166 PrefsInterface *prefs,
167 const string& file_extension,
168 const int num_files_to_keep)
169 : prefs_(prefs),
170 file_extension_(file_extension),
171 num_files_to_keep_(num_files_to_keep) {
172 configuration_.reset(configuration != NULL ? configuration :
173 new ConfigurationImpl());
174}
175
176void P2PManagerImpl::SetConfiguration(Configuration *configuration) {
177 configuration_.reset(configuration);
178}
179
180bool P2PManagerImpl::IsP2PEnabled() {
181 // TODO(deymo,zeuthen)(chromium:260441): See the bug for the bigger
182 // picture. For now we just read the state variable so in order for
183 // p2p to work the developer will have to manually create the prefs
184 // file. Once the fix for bug 260441 has been merged, this can be
185 // toggled using the crosh command, as intended.
186 bool enabled = false;
187 if (prefs_ == NULL) {
188 LOG(INFO) << "No prefs object.";
189 } else if (!prefs_->Exists(kPrefsP2PEnabled)) {
190 LOG(INFO) << "The " << kPrefsP2PEnabled << " pref does not exist.";
191 } else if (!prefs_->GetBoolean(kPrefsP2PEnabled, &enabled)) {
192 LOG(ERROR) << "Error getting " << kPrefsP2PEnabled << " pref.";
193 }
194 LOG(INFO) << "Returning value " << enabled << " for whether p2p is enabled.";
195 return enabled;
196}
197
198bool P2PManagerImpl::EnsureP2P(bool should_be_running) {
199 gchar *standard_error = NULL;
200 GError *error = NULL;
201 gint exit_status = 0;
202
203 vector<string> args = configuration_->GetInitctlArgs(should_be_running);
204 scoped_ptr<gchar*, GLibStrvFreeDeleter> argv(
205 utils::StringVectorToGStrv(args));
206 if (!g_spawn_sync(NULL, // working_directory
207 argv.get(),
208 NULL, // envp
209 static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH),
210 NULL, NULL, // child_setup, user_data
211 NULL, // standard_output
212 &standard_error,
213 &exit_status,
214 &error)) {
215 LOG(ERROR) << "Error spawning " << utils::StringVectorToString(args)
216 << ": " << utils::GetAndFreeGError(&error);
217 return false;
218 }
219 scoped_ptr<gchar, GLibFreeDeleter> standard_error_deleter(standard_error);
220
221 if (!WIFEXITED(exit_status)) {
222 LOG(ERROR) << "Error spawning '" << utils::StringVectorToString(args)
223 << "': WIFEXITED is false";
224 return false;
225 }
226
227 // If initctl(8) exits normally with exit status 0 ("success"), it
228 // meant that it did what we requested.
229 if (WEXITSTATUS(exit_status) == 0) {
230 return true;
231 }
232
233 // Otherwise, screenscape stderr from initctl(8). Ugh, yes, this is
234 // ugly but since the program lacks verbs/actions such as
235 //
236 // ensure-started (or start-or-return-success-if-already-started)
237 // ensure-stopped (or stop-or-return-success-if-not-running)
238 //
239 // this is what we have to do.
240 //
241 // TODO(zeuthen,chromium:277051): Avoid doing this.
242 const gchar *expected_error_message = should_be_running ?
243 "initctl: Job is already running: p2p\n" :
244 "initctl: Unknown instance \n";
245 if (g_strcmp0(standard_error, expected_error_message) == 0) {
246 return true;
247 }
248
249 return false;
250}
251
252bool P2PManagerImpl::EnsureP2PRunning() {
253 return EnsureP2P(true);
254}
255
256bool P2PManagerImpl::EnsureP2PNotRunning() {
257 return EnsureP2P(false);
258}
259
260// Returns True if the timestamp in the first pair is greater than the
261// timestamp in the latter. If used with std::sort() this will yield a
262// sequence of elements where newer (high timestamps) elements precede
263// older ones (low timestamps).
264static bool MatchCompareFunc(const pair<FilePath, Time>& a,
265 const pair<FilePath, Time>& b) {
266 return a.second > b.second;
267}
268
269string P2PManagerImpl::GetExt(Visibility visibility) {
270 string ext = string(".") + file_extension_ + kP2PExtension;
271 switch (visibility) {
272 case kVisible:
273 break;
274 case kNonVisible:
275 ext += kTmpExtension;
276 break;
277 // Don't add a default case to let the compiler warn about newly
278 // added enum values.
279 }
280 return ext;
281}
282
283FilePath P2PManagerImpl::GetPath(const string& file_id, Visibility visibility) {
284 return configuration_->GetP2PDir().Append(file_id + GetExt(visibility));
285}
286
287bool P2PManagerImpl::PerformHousekeeping() {
288 GDir* dir = NULL;
289 GError* error = NULL;
290 const char* name = NULL;
291 vector<pair<FilePath, Time> > matches;
292
293 // Go through all files in the p2p dir and pick the ones that match
294 // and get their ctime.
295 FilePath p2p_dir = configuration_->GetP2PDir();
296 dir = g_dir_open(p2p_dir.value().c_str(), 0, &error);
297 if (dir == NULL) {
298 LOG(ERROR) << "Error opening directory " << p2p_dir.value() << ": "
299 << utils::GetAndFreeGError(&error);
300 return false;
301 }
302
303 if (num_files_to_keep_ == 0)
304 return true;
305
306 string ext_visible = GetExt(kVisible);
307 string ext_non_visible = GetExt(kNonVisible);
308 while ((name = g_dir_read_name(dir)) != NULL) {
309 if (!(g_str_has_suffix(name, ext_visible.c_str()) ||
310 g_str_has_suffix(name, ext_non_visible.c_str())))
311 continue;
312
313 struct stat statbuf;
314 FilePath file = p2p_dir.Append(name);
315 if (stat(file.value().c_str(), &statbuf) != 0) {
316 PLOG(ERROR) << "Error getting file status for " << file.value();
317 continue;
318 }
319
320 Time time = utils::TimeFromStructTimespec(&statbuf.st_ctim);
321 matches.push_back(std::make_pair(file, time));
322 }
323 g_dir_close(dir);
324
325 // Sort list of matches, newest (biggest time) to oldest (lowest time).
326 std::sort(matches.begin(), matches.end(), MatchCompareFunc);
327
328 // Delete starting at element num_files_to_keep_.
329 vector<pair<FilePath, Time> >::const_iterator i;
330 for (i = matches.begin() + num_files_to_keep_; i < matches.end(); ++i) {
331 const FilePath& file = i->first;
332 LOG(INFO) << "Deleting p2p file " << file.value();
333 if (unlink(file.value().c_str()) != 0) {
334 PLOG(ERROR) << "Error deleting p2p file " << file.value();
335 return false;
336 }
337 }
338
339 return true;
340}
341
342// Helper class for implementing LookupUrlForFile().
343class LookupData {
344public:
345 LookupData(P2PManager::LookupCallback callback)
346 : callback_(callback),
347 pid_(0),
348 stdout_fd_(-1),
349 stdout_channel_source_id_(0),
350 child_watch_source_id_(0),
351 timeout_source_id_(0),
352 reported_(false) {}
353
354 ~LookupData() {
355 if (child_watch_source_id_ != 0)
356 g_source_remove(child_watch_source_id_);
357 if (stdout_channel_source_id_ != 0)
358 g_source_remove(stdout_channel_source_id_);
359 if (timeout_source_id_ != 0)
360 g_source_remove(timeout_source_id_);
361 if (stdout_fd_ != -1)
362 close(stdout_fd_);
363 if (pid_ != 0)
364 kill(pid_, SIGTERM);
365 }
366
367 void InitiateLookup(gchar **argv, TimeDelta timeout) {
368 // NOTE: if we fail early (i.e. in this method), we need to schedule
369 // an idle to report the error. This is because we guarantee that
370 // the callback is always called from from the GLib mainloop (this
371 // guarantee is useful for testing).
372
373 GError *error = NULL;
374 if (!g_spawn_async_with_pipes(NULL, // working_directory
375 argv,
376 NULL, // envp
377 static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH |
378 G_SPAWN_DO_NOT_REAP_CHILD),
379 NULL, // child_setup
380 this,
381 &pid_,
382 NULL, // standard_input
383 &stdout_fd_,
384 NULL, // standard_error
385 &error)) {
386 LOG(ERROR) << "Error spawning p2p-client: "
387 << utils::GetAndFreeGError(&error);
388 ReportErrorAndDeleteInIdle();
389 return;
390 }
391
392 GIOChannel* io_channel = g_io_channel_unix_new(stdout_fd_);
393 stdout_channel_source_id_ = g_io_add_watch(
394 io_channel,
395 static_cast<GIOCondition>(G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP),
396 OnIOChannelActivity, this);
397 CHECK(stdout_channel_source_id_ != 0);
398 g_io_channel_unref(io_channel);
399
400 child_watch_source_id_ = g_child_watch_add(pid_, OnChildWatchActivity,
401 this);
402 CHECK(child_watch_source_id_ != 0);
403
404 if (timeout.ToInternalValue() > 0) {
405 timeout_source_id_ = g_timeout_add(timeout.InMilliseconds(),
406 OnTimeout, this);
407 CHECK(timeout_source_id_ != 0);
408 }
409 }
410
411private:
412 void ReportErrorAndDeleteInIdle() {
413 g_idle_add(static_cast<GSourceFunc>(OnIdleForReportErrorAndDelete), this);
414 }
415
416 static gboolean OnIdleForReportErrorAndDelete(gpointer user_data) {
417 LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
418 lookup_data->ReportError();
419 delete lookup_data;
420 return FALSE; // Remove source.
421 }
422
423 void IssueCallback(const string& url) {
424 if (!callback_.is_null())
425 callback_.Run(url);
426 }
427
428 void ReportError() {
429 if (reported_)
430 return;
431 IssueCallback("");
432 reported_ = true;
433 }
434
435 void ReportSuccess() {
436 if (reported_)
437 return;
438
439 string url = stdout_;
440 size_t newline_pos = url.find('\n');
441 if (newline_pos != string::npos)
442 url.resize(newline_pos);
443
444 // Since p2p-client(1) is constructing this URL itself strictly
445 // speaking there's no need to validate it... but, anyway, can't
446 // hurt.
447 if (url.compare(0, 7, "http://") == 0) {
448 IssueCallback(url);
449 } else {
450 LOG(ERROR) << "p2p URL '" << url << "' does not look right. Ignoring.";
451 ReportError();
452 }
453
454 reported_ = true;
455 }
456
457 static gboolean OnIOChannelActivity(GIOChannel *source,
458 GIOCondition condition,
459 gpointer user_data) {
460 LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
461 gchar* str = NULL;
462 GError* error = NULL;
463 GIOStatus status = g_io_channel_read_line(source,
464 &str,
465 NULL, // len
466 NULL, // line_terminator
467 &error);
468 if (status != G_IO_STATUS_NORMAL) {
469 // Ignore EOF since we usually get that before SIGCHLD and we
470 // need to examine exit status there.
471 if (status != G_IO_STATUS_EOF) {
472 LOG(ERROR) << "Error reading a line from p2p-client: "
473 << utils::GetAndFreeGError(&error);
474 lookup_data->ReportError();
475 delete lookup_data;
476 }
477 } else {
478 if (str != NULL) {
479 lookup_data->stdout_ += str;
480 g_free(str);
481 }
482 }
483 return TRUE; // Don't remove source.
484 }
485
486 static void OnChildWatchActivity(GPid pid,
487 gint status,
488 gpointer user_data) {
489 LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
490
491 if (!WIFEXITED(status)) {
492 LOG(ERROR) << "Child didn't exit normally";
493 lookup_data->ReportError();
494 } else if (WEXITSTATUS(status) != 0) {
495 LOG(INFO) << "Child exited with non-zero exit code "
496 << WEXITSTATUS(status);
497 lookup_data->ReportError();
498 } else {
499 lookup_data->ReportSuccess();
500 }
501 delete lookup_data;
502 }
503
504 static gboolean OnTimeout(gpointer user_data) {
505 LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
506 lookup_data->ReportError();
507 delete lookup_data;
508 return TRUE; // Don't remove source.
509 }
510
511 P2PManager::LookupCallback callback_;
512 GPid pid_;
513 gint stdout_fd_;
514 guint stdout_channel_source_id_;
515 guint child_watch_source_id_;
516 guint timeout_source_id_;
517 string stdout_;
518 bool reported_;
519};
520
521void P2PManagerImpl::LookupUrlForFile(const string& file_id,
522 size_t minimum_size,
523 TimeDelta max_time_to_wait,
524 LookupCallback callback) {
525 LookupData *lookup_data = new LookupData(callback);
526 string file_id_with_ext = file_id + "." + file_extension_;
527 vector<string> args = configuration_->GetP2PClientArgs(file_id_with_ext,
528 minimum_size);
529 gchar **argv = utils::StringVectorToGStrv(args);
530 lookup_data->InitiateLookup(argv, max_time_to_wait);
531 g_strfreev(argv);
532}
533
534bool P2PManagerImpl::FileShare(const string& file_id,
535 size_t expected_size) {
536 // Check if file already exist.
537 FilePath path = FileGetPath(file_id);
538 if (!path.empty()) {
539 // File exists - double check its expected size though.
540 ssize_t file_expected_size = FileGetExpectedSize(file_id);
541 if (file_expected_size == -1 ||
542 static_cast<size_t>(file_expected_size) != expected_size) {
543 LOG(ERROR) << "Existing p2p file " << path.value()
544 << " with expected_size=" << file_expected_size
545 << " does not match the passed in"
546 << " expected_size=" << expected_size;
547 return false;
548 }
549 return true;
550 }
551
552 // Before creating the file, bail if statvfs(3) indicates that at
553 // least twice the size is not available in P2P_DIR.
554 struct statvfs statvfsbuf;
555 FilePath p2p_dir = configuration_->GetP2PDir();
556 if (statvfs(p2p_dir.value().c_str(), &statvfsbuf) != 0) {
557 PLOG(ERROR) << "Error calling statvfs() for dir " << p2p_dir.value();
558 return false;
559 }
560 size_t free_bytes =
561 static_cast<size_t>(statvfsbuf.f_bsize) * statvfsbuf.f_bavail;
562 if (free_bytes < 2 * expected_size) {
563 // This can easily happen and is worth reporting.
564 LOG(INFO) << "Refusing to allocate p2p file of " << expected_size
565 << " bytes since the directory " << p2p_dir.value()
566 << " only has " << free_bytes
567 << " bytes available and this is less than twice the"
568 << " requested size.";
569 return false;
570 }
571
572 // Okie-dokey looks like enough space is available - create the file.
573 path = GetPath(file_id, kNonVisible);
574 int fd = open(path.value().c_str(), O_CREAT | O_RDWR, 0644);
575 if (fd == -1) {
576 PLOG(ERROR) << "Error creating file with path " << path.value();
577 return false;
578 }
579 ScopedFdCloser fd_closer(&fd);
580
581 // If the final size is known, allocate the file (e.g. reserve disk
582 // space) and set the user.cros-p2p-filesize xattr.
583 if (expected_size != 0) {
584 if (fallocate(fd,
585 FALLOC_FL_KEEP_SIZE, // Keep file size as 0.
586 0,
587 expected_size) != 0) {
588 // ENOSPC can happen (funky race though, cf. the statvfs() check
589 // above), handle it gracefully, e.g. use logging level INFO.
590 //
591 // NOTE: we *could* handle ENOSYS gracefully (ie. we could
592 // ignore it) but currently we don't because running out of
593 // space later sounds absolutely horrible. Better to fail fast.
594 PLOG(INFO) << "Error allocating " << expected_size
595 << " bytes for file " << path.value();
596 if (unlink(path.value().c_str()) != 0) {
597 PLOG(ERROR) << "Error deleting file with path " << path.value();
598 }
599 return false;
600 }
601
602 string decimal_size = StringPrintf("%zu", expected_size);
603 if (fsetxattr(fd, kCrosP2PFileSizeXAttrName,
604 decimal_size.c_str(), decimal_size.size(), 0) != 0) {
605 PLOG(ERROR) << "Error setting xattr " << path.value();
606 return false;
607 }
608 }
609
610 return true;
611}
612
613FilePath P2PManagerImpl::FileGetPath(const string& file_id) {
614 struct stat statbuf;
615 FilePath path;
616
617 path = GetPath(file_id, kVisible);
618 if (stat(path.value().c_str(), &statbuf) == 0) {
619 return path;
620 }
621
622 path = GetPath(file_id, kNonVisible);
623 if (stat(path.value().c_str(), &statbuf) == 0) {
624 return path;
625 }
626
627 path.clear();
628 return path;
629}
630
631bool P2PManagerImpl::FileGetVisible(const string& file_id,
632 bool *out_result) {
633 FilePath path = FileGetPath(file_id);
634 if (path.empty()) {
635 LOG(ERROR) << "No file for id " << file_id;
636 return false;
637 }
638 if (out_result != NULL)
639 *out_result = path.MatchesExtension(kP2PExtension);
640 return true;
641}
642
643bool P2PManagerImpl::FileMakeVisible(const string& file_id) {
644 FilePath path = FileGetPath(file_id);
645 if (path.empty()) {
646 LOG(ERROR) << "No file for id " << file_id;
647 return false;
648 }
649
650 // Already visible?
651 if (path.MatchesExtension(kP2PExtension))
652 return true;
653
654 LOG_ASSERT(path.MatchesExtension(kTmpExtension));
655 FilePath new_path = path.RemoveExtension();
656 LOG_ASSERT(new_path.MatchesExtension(kP2PExtension));
657 if (rename(path.value().c_str(), new_path.value().c_str()) != 0) {
658 PLOG(ERROR) << "Error renaming " << path.value()
659 << " to " << new_path.value();
660 return false;
661 }
662
663 return true;
664}
665
666ssize_t P2PManagerImpl::FileGetSize(const string& file_id) {
667 FilePath path = FileGetPath(file_id);
668 if (path.empty())
669 return -1;
670
671 struct stat statbuf;
672 if (stat(path.value().c_str(), &statbuf) != 0) {
673 PLOG(ERROR) << "Error getting file status for " << path.value();
674 return -1;
675 }
676
677 return statbuf.st_size;
678}
679
680ssize_t P2PManagerImpl::FileGetExpectedSize(const string& file_id) {
681 FilePath path = FileGetPath(file_id);
682 if (path.empty())
683 return -1;
684
685 char ea_value[64] = { 0 };
686 ssize_t ea_size;
687 ea_size = getxattr(path.value().c_str(), kCrosP2PFileSizeXAttrName,
688 &ea_value, sizeof(ea_value) - 1);
689 if (ea_size == -1) {
690 PLOG(ERROR) << "Error calling getxattr() on file " << path.value();
691 return -1;
692 }
693
694 char* endp = NULL;
695 long long int val = strtoll(ea_value, &endp, 0);
696 if (*endp != '\0') {
697 LOG(ERROR) << "Error parsing the value '" << ea_value
698 << "' of the xattr " << kCrosP2PFileSizeXAttrName
699 << " as an integer";
700 return -1;
701 }
702
703 return val;
704}
705
706int P2PManagerImpl::CountSharedFiles() {
707 GDir* dir;
708 GError* error = NULL;
709 const char* name;
710 int num_files = 0;
711
712 FilePath p2p_dir = configuration_->GetP2PDir();
713 dir = g_dir_open(p2p_dir.value().c_str(), 0, &error);
714 if (dir == NULL) {
715 LOG(ERROR) << "Error opening directory " << p2p_dir.value() << ": "
716 << utils::GetAndFreeGError(&error);
717 return -1;
718 }
719
720 string ext_visible = GetExt(kVisible);
721 string ext_non_visible = GetExt(kNonVisible);
722 while ((name = g_dir_read_name(dir)) != NULL) {
723 if (g_str_has_suffix(name, ext_visible.c_str()) ||
724 g_str_has_suffix(name, ext_non_visible.c_str())) {
725 num_files += 1;
726 }
727 }
728 g_dir_close(dir);
729
730 return num_files;
731}
732
733P2PManager* P2PManager::Construct(Configuration *configuration,
734 PrefsInterface *prefs,
735 const string& file_extension,
736 const int num_files_to_keep) {
737 return new P2PManagerImpl(configuration,
738 prefs,
739 file_extension,
740 num_files_to_keep);
741}
742
743} // namespace chromeos_update_engine