blob: ddb9bf0e566dbf406a0e7b6f7ff96e96ac336b45 [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
Alex Vakulenko75039d72014-03-25 12:36:28 -070031#include <base/files/file_path.h>
David Zeuthen27a48bc2013-08-06 12:06:29 -070032#include <base/logging.h>
Alex Vakulenko75039d72014-03-25 12:36:28 -070033#include <base/strings/stringprintf.h>
David Zeuthen27a48bc2013-08-06 12:06:29 -070034
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
Alex Vakulenko75039d72014-03-25 12:36:28 -070066 virtual base::FilePath GetP2PDir() {
67 return base::FilePath(kDefaultP2PDir);
David Zeuthen27a48bc2013-08-06 12:06:29 -070068 }
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);
Alex Vakulenko75039d72014-03-25 12:36:28 -070083 args.push_back(base::StringPrintf("--minimum-size=%zu", minimum_size));
David Zeuthen27a48bc2013-08-06 12:06:29 -070084 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.
David Zeuthen92d9c8b2013-09-11 10:58:11 -0700100 virtual void SetDevicePolicy(const policy::DevicePolicy* device_policy);
David Zeuthen27a48bc2013-08-06 12:06:29 -0700101 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);
Alex Vakulenko75039d72014-03-25 12:36:28 -0700111 virtual base::FilePath FileGetPath(const string& file_id);
David Zeuthen27a48bc2013-08-06 12:06:29 -0700112 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.
Alex Vakulenko75039d72014-03-25 12:36:28 -0700132 base::FilePath GetPath(const string& file_id, Visibility visibility);
David Zeuthen27a48bc2013-08-06 12:06:29 -0700133
134 // Utility function used by EnsureP2PRunning() and EnsureP2PNotRunning().
135 bool EnsureP2P(bool should_be_running);
136
David Zeuthen92d9c8b2013-09-11 10:58:11 -0700137 // The device policy being used or NULL if no policy is being used.
138 const policy::DevicePolicy* device_policy_;
139
David Zeuthen27a48bc2013-08-06 12:06:29 -0700140 // Configuration object.
141 scoped_ptr<Configuration> configuration_;
142
143 // Object for persisted state.
144 PrefsInterface* prefs_;
145
146 // A short string unique to the application (for example "cros_au")
147 // used to mark a file as being owned by a particular application.
148 const string file_extension_;
149
150 // If non-zero, this number denotes how many files in /var/cache/p2p
151 // owned by the application (cf. |file_extension_|) to keep after
152 // performing housekeeping.
153 const int num_files_to_keep_;
154
155 // The string ".p2p".
156 static const char kP2PExtension[];
157
158 // The string ".tmp".
159 static const char kTmpExtension[];
160
161 DISALLOW_COPY_AND_ASSIGN(P2PManagerImpl);
162};
163
164const char P2PManagerImpl::kP2PExtension[] = ".p2p";
165
166const char P2PManagerImpl::kTmpExtension[] = ".tmp";
167
168P2PManagerImpl::P2PManagerImpl(Configuration *configuration,
169 PrefsInterface *prefs,
170 const string& file_extension,
171 const int num_files_to_keep)
David Zeuthen92d9c8b2013-09-11 10:58:11 -0700172 : device_policy_(NULL),
173 prefs_(prefs),
David Zeuthen27a48bc2013-08-06 12:06:29 -0700174 file_extension_(file_extension),
175 num_files_to_keep_(num_files_to_keep) {
176 configuration_.reset(configuration != NULL ? configuration :
177 new ConfigurationImpl());
178}
179
David Zeuthen92d9c8b2013-09-11 10:58:11 -0700180void P2PManagerImpl::SetDevicePolicy(
181 const policy::DevicePolicy* device_policy) {
182 device_policy_ = device_policy;
David Zeuthen27a48bc2013-08-06 12:06:29 -0700183}
184
185bool P2PManagerImpl::IsP2PEnabled() {
David Zeuthen92d9c8b2013-09-11 10:58:11 -0700186 bool p2p_enabled = false;
187
188 // The logic we want here is additive, e.g. p2p can be enabled by
189 // either the crosh flag OR by Enterprise Policy, e.g. the following
190 // truth table:
191 //
192 // crosh_flag == FALSE && enterprise_policy == FALSE -> use_p2p == FALSE
193 // crosh_flag == FALSE && enterprise_policy == TRUE -> use_p2p == TRUE
194 // crosh_flag == TRUE && enterprise_policy == FALSE -> use_p2p == TRUE
195 // crosh_flag == TRUE && enterprise_policy == TRUE -> use_p2p == TRUE
196
197 if (device_policy_ != NULL) {
198 if (device_policy_->GetAuP2PEnabled(&p2p_enabled)) {
199 if (p2p_enabled) {
200 LOG(INFO) << "Enterprise Policy indicates that p2p is enabled.";
201 return true;
202 }
203 }
David Zeuthen27a48bc2013-08-06 12:06:29 -0700204 }
David Zeuthen92d9c8b2013-09-11 10:58:11 -0700205
206 if (prefs_ != NULL &&
207 prefs_->Exists(kPrefsP2PEnabled) &&
208 prefs_->GetBoolean(kPrefsP2PEnabled, &p2p_enabled) &&
209 p2p_enabled) {
210 LOG(INFO) << "The crosh flag indicates that p2p is enabled.";
211 return true;
212 }
213
214 LOG(INFO) << "Neither Enterprise Policy nor crosh flag indicates that p2p "
215 << "is enabled.";
216 return false;
David Zeuthen27a48bc2013-08-06 12:06:29 -0700217}
218
219bool P2PManagerImpl::EnsureP2P(bool should_be_running) {
220 gchar *standard_error = NULL;
221 GError *error = NULL;
222 gint exit_status = 0;
223
224 vector<string> args = configuration_->GetInitctlArgs(should_be_running);
225 scoped_ptr<gchar*, GLibStrvFreeDeleter> argv(
226 utils::StringVectorToGStrv(args));
227 if (!g_spawn_sync(NULL, // working_directory
228 argv.get(),
229 NULL, // envp
230 static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH),
231 NULL, NULL, // child_setup, user_data
232 NULL, // standard_output
233 &standard_error,
234 &exit_status,
235 &error)) {
236 LOG(ERROR) << "Error spawning " << utils::StringVectorToString(args)
237 << ": " << utils::GetAndFreeGError(&error);
238 return false;
239 }
240 scoped_ptr<gchar, GLibFreeDeleter> standard_error_deleter(standard_error);
241
242 if (!WIFEXITED(exit_status)) {
243 LOG(ERROR) << "Error spawning '" << utils::StringVectorToString(args)
244 << "': WIFEXITED is false";
245 return false;
246 }
247
248 // If initctl(8) exits normally with exit status 0 ("success"), it
249 // meant that it did what we requested.
250 if (WEXITSTATUS(exit_status) == 0) {
251 return true;
252 }
253
254 // Otherwise, screenscape stderr from initctl(8). Ugh, yes, this is
255 // ugly but since the program lacks verbs/actions such as
256 //
257 // ensure-started (or start-or-return-success-if-already-started)
258 // ensure-stopped (or stop-or-return-success-if-not-running)
259 //
260 // this is what we have to do.
261 //
262 // TODO(zeuthen,chromium:277051): Avoid doing this.
263 const gchar *expected_error_message = should_be_running ?
264 "initctl: Job is already running: p2p\n" :
265 "initctl: Unknown instance \n";
266 if (g_strcmp0(standard_error, expected_error_message) == 0) {
267 return true;
268 }
269
270 return false;
271}
272
273bool P2PManagerImpl::EnsureP2PRunning() {
274 return EnsureP2P(true);
275}
276
277bool P2PManagerImpl::EnsureP2PNotRunning() {
278 return EnsureP2P(false);
279}
280
281// Returns True if the timestamp in the first pair is greater than the
282// timestamp in the latter. If used with std::sort() this will yield a
283// sequence of elements where newer (high timestamps) elements precede
284// older ones (low timestamps).
285static bool MatchCompareFunc(const pair<FilePath, Time>& a,
286 const pair<FilePath, Time>& b) {
287 return a.second > b.second;
288}
289
290string P2PManagerImpl::GetExt(Visibility visibility) {
291 string ext = string(".") + file_extension_ + kP2PExtension;
292 switch (visibility) {
293 case kVisible:
294 break;
295 case kNonVisible:
296 ext += kTmpExtension;
297 break;
298 // Don't add a default case to let the compiler warn about newly
299 // added enum values.
300 }
301 return ext;
302}
303
304FilePath P2PManagerImpl::GetPath(const string& file_id, Visibility visibility) {
305 return configuration_->GetP2PDir().Append(file_id + GetExt(visibility));
306}
307
308bool P2PManagerImpl::PerformHousekeeping() {
309 GDir* dir = NULL;
310 GError* error = NULL;
311 const char* name = NULL;
312 vector<pair<FilePath, Time> > matches;
313
314 // Go through all files in the p2p dir and pick the ones that match
315 // and get their ctime.
Alex Vakulenko75039d72014-03-25 12:36:28 -0700316 base::FilePath p2p_dir = configuration_->GetP2PDir();
David Zeuthen27a48bc2013-08-06 12:06:29 -0700317 dir = g_dir_open(p2p_dir.value().c_str(), 0, &error);
318 if (dir == NULL) {
319 LOG(ERROR) << "Error opening directory " << p2p_dir.value() << ": "
320 << utils::GetAndFreeGError(&error);
321 return false;
322 }
323
324 if (num_files_to_keep_ == 0)
325 return true;
326
327 string ext_visible = GetExt(kVisible);
328 string ext_non_visible = GetExt(kNonVisible);
329 while ((name = g_dir_read_name(dir)) != NULL) {
330 if (!(g_str_has_suffix(name, ext_visible.c_str()) ||
331 g_str_has_suffix(name, ext_non_visible.c_str())))
332 continue;
333
334 struct stat statbuf;
Alex Vakulenko75039d72014-03-25 12:36:28 -0700335 base::FilePath file = p2p_dir.Append(name);
David Zeuthen27a48bc2013-08-06 12:06:29 -0700336 if (stat(file.value().c_str(), &statbuf) != 0) {
337 PLOG(ERROR) << "Error getting file status for " << file.value();
338 continue;
339 }
340
341 Time time = utils::TimeFromStructTimespec(&statbuf.st_ctim);
342 matches.push_back(std::make_pair(file, time));
343 }
344 g_dir_close(dir);
345
346 // Sort list of matches, newest (biggest time) to oldest (lowest time).
347 std::sort(matches.begin(), matches.end(), MatchCompareFunc);
348
349 // Delete starting at element num_files_to_keep_.
350 vector<pair<FilePath, Time> >::const_iterator i;
351 for (i = matches.begin() + num_files_to_keep_; i < matches.end(); ++i) {
Alex Vakulenko75039d72014-03-25 12:36:28 -0700352 const base::FilePath& file = i->first;
David Zeuthen27a48bc2013-08-06 12:06:29 -0700353 LOG(INFO) << "Deleting p2p file " << file.value();
354 if (unlink(file.value().c_str()) != 0) {
355 PLOG(ERROR) << "Error deleting p2p file " << file.value();
356 return false;
357 }
358 }
359
360 return true;
361}
362
363// Helper class for implementing LookupUrlForFile().
364class LookupData {
365public:
366 LookupData(P2PManager::LookupCallback callback)
367 : callback_(callback),
368 pid_(0),
369 stdout_fd_(-1),
370 stdout_channel_source_id_(0),
371 child_watch_source_id_(0),
372 timeout_source_id_(0),
373 reported_(false) {}
374
375 ~LookupData() {
376 if (child_watch_source_id_ != 0)
377 g_source_remove(child_watch_source_id_);
378 if (stdout_channel_source_id_ != 0)
379 g_source_remove(stdout_channel_source_id_);
380 if (timeout_source_id_ != 0)
381 g_source_remove(timeout_source_id_);
382 if (stdout_fd_ != -1)
383 close(stdout_fd_);
384 if (pid_ != 0)
385 kill(pid_, SIGTERM);
386 }
387
388 void InitiateLookup(gchar **argv, TimeDelta timeout) {
389 // NOTE: if we fail early (i.e. in this method), we need to schedule
390 // an idle to report the error. This is because we guarantee that
391 // the callback is always called from from the GLib mainloop (this
392 // guarantee is useful for testing).
393
394 GError *error = NULL;
395 if (!g_spawn_async_with_pipes(NULL, // working_directory
396 argv,
397 NULL, // envp
398 static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH |
399 G_SPAWN_DO_NOT_REAP_CHILD),
400 NULL, // child_setup
401 this,
402 &pid_,
403 NULL, // standard_input
404 &stdout_fd_,
405 NULL, // standard_error
406 &error)) {
407 LOG(ERROR) << "Error spawning p2p-client: "
408 << utils::GetAndFreeGError(&error);
409 ReportErrorAndDeleteInIdle();
410 return;
411 }
412
413 GIOChannel* io_channel = g_io_channel_unix_new(stdout_fd_);
414 stdout_channel_source_id_ = g_io_add_watch(
415 io_channel,
416 static_cast<GIOCondition>(G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP),
417 OnIOChannelActivity, this);
418 CHECK(stdout_channel_source_id_ != 0);
419 g_io_channel_unref(io_channel);
420
421 child_watch_source_id_ = g_child_watch_add(pid_, OnChildWatchActivity,
422 this);
423 CHECK(child_watch_source_id_ != 0);
424
425 if (timeout.ToInternalValue() > 0) {
426 timeout_source_id_ = g_timeout_add(timeout.InMilliseconds(),
427 OnTimeout, this);
428 CHECK(timeout_source_id_ != 0);
429 }
430 }
431
432private:
433 void ReportErrorAndDeleteInIdle() {
434 g_idle_add(static_cast<GSourceFunc>(OnIdleForReportErrorAndDelete), this);
435 }
436
437 static gboolean OnIdleForReportErrorAndDelete(gpointer user_data) {
438 LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
439 lookup_data->ReportError();
440 delete lookup_data;
441 return FALSE; // Remove source.
442 }
443
444 void IssueCallback(const string& url) {
445 if (!callback_.is_null())
446 callback_.Run(url);
447 }
448
449 void ReportError() {
450 if (reported_)
451 return;
452 IssueCallback("");
453 reported_ = true;
454 }
455
456 void ReportSuccess() {
457 if (reported_)
458 return;
459
460 string url = stdout_;
461 size_t newline_pos = url.find('\n');
462 if (newline_pos != string::npos)
463 url.resize(newline_pos);
464
465 // Since p2p-client(1) is constructing this URL itself strictly
466 // speaking there's no need to validate it... but, anyway, can't
467 // hurt.
468 if (url.compare(0, 7, "http://") == 0) {
469 IssueCallback(url);
470 } else {
471 LOG(ERROR) << "p2p URL '" << url << "' does not look right. Ignoring.";
472 ReportError();
473 }
474
475 reported_ = true;
476 }
477
478 static gboolean OnIOChannelActivity(GIOChannel *source,
479 GIOCondition condition,
480 gpointer user_data) {
481 LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
482 gchar* str = NULL;
483 GError* error = NULL;
484 GIOStatus status = g_io_channel_read_line(source,
485 &str,
486 NULL, // len
487 NULL, // line_terminator
488 &error);
489 if (status != G_IO_STATUS_NORMAL) {
490 // Ignore EOF since we usually get that before SIGCHLD and we
491 // need to examine exit status there.
492 if (status != G_IO_STATUS_EOF) {
493 LOG(ERROR) << "Error reading a line from p2p-client: "
494 << utils::GetAndFreeGError(&error);
495 lookup_data->ReportError();
496 delete lookup_data;
497 }
498 } else {
499 if (str != NULL) {
500 lookup_data->stdout_ += str;
501 g_free(str);
502 }
503 }
504 return TRUE; // Don't remove source.
505 }
506
507 static void OnChildWatchActivity(GPid pid,
508 gint status,
509 gpointer user_data) {
510 LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
511
512 if (!WIFEXITED(status)) {
513 LOG(ERROR) << "Child didn't exit normally";
514 lookup_data->ReportError();
515 } else if (WEXITSTATUS(status) != 0) {
516 LOG(INFO) << "Child exited with non-zero exit code "
517 << WEXITSTATUS(status);
518 lookup_data->ReportError();
519 } else {
520 lookup_data->ReportSuccess();
521 }
522 delete lookup_data;
523 }
524
525 static gboolean OnTimeout(gpointer user_data) {
526 LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
527 lookup_data->ReportError();
528 delete lookup_data;
529 return TRUE; // Don't remove source.
530 }
531
532 P2PManager::LookupCallback callback_;
533 GPid pid_;
534 gint stdout_fd_;
535 guint stdout_channel_source_id_;
536 guint child_watch_source_id_;
537 guint timeout_source_id_;
538 string stdout_;
539 bool reported_;
540};
541
542void P2PManagerImpl::LookupUrlForFile(const string& file_id,
543 size_t minimum_size,
544 TimeDelta max_time_to_wait,
545 LookupCallback callback) {
546 LookupData *lookup_data = new LookupData(callback);
547 string file_id_with_ext = file_id + "." + file_extension_;
548 vector<string> args = configuration_->GetP2PClientArgs(file_id_with_ext,
549 minimum_size);
550 gchar **argv = utils::StringVectorToGStrv(args);
551 lookup_data->InitiateLookup(argv, max_time_to_wait);
552 g_strfreev(argv);
553}
554
555bool P2PManagerImpl::FileShare(const string& file_id,
556 size_t expected_size) {
557 // Check if file already exist.
Alex Vakulenko75039d72014-03-25 12:36:28 -0700558 base::FilePath path = FileGetPath(file_id);
David Zeuthen27a48bc2013-08-06 12:06:29 -0700559 if (!path.empty()) {
560 // File exists - double check its expected size though.
561 ssize_t file_expected_size = FileGetExpectedSize(file_id);
562 if (file_expected_size == -1 ||
563 static_cast<size_t>(file_expected_size) != expected_size) {
564 LOG(ERROR) << "Existing p2p file " << path.value()
565 << " with expected_size=" << file_expected_size
566 << " does not match the passed in"
567 << " expected_size=" << expected_size;
568 return false;
569 }
570 return true;
571 }
572
573 // Before creating the file, bail if statvfs(3) indicates that at
574 // least twice the size is not available in P2P_DIR.
575 struct statvfs statvfsbuf;
Alex Vakulenko75039d72014-03-25 12:36:28 -0700576 base::FilePath p2p_dir = configuration_->GetP2PDir();
David Zeuthen27a48bc2013-08-06 12:06:29 -0700577 if (statvfs(p2p_dir.value().c_str(), &statvfsbuf) != 0) {
578 PLOG(ERROR) << "Error calling statvfs() for dir " << p2p_dir.value();
579 return false;
580 }
581 size_t free_bytes =
582 static_cast<size_t>(statvfsbuf.f_bsize) * statvfsbuf.f_bavail;
583 if (free_bytes < 2 * expected_size) {
584 // This can easily happen and is worth reporting.
585 LOG(INFO) << "Refusing to allocate p2p file of " << expected_size
586 << " bytes since the directory " << p2p_dir.value()
587 << " only has " << free_bytes
588 << " bytes available and this is less than twice the"
589 << " requested size.";
590 return false;
591 }
592
593 // Okie-dokey looks like enough space is available - create the file.
594 path = GetPath(file_id, kNonVisible);
595 int fd = open(path.value().c_str(), O_CREAT | O_RDWR, 0644);
596 if (fd == -1) {
597 PLOG(ERROR) << "Error creating file with path " << path.value();
598 return false;
599 }
600 ScopedFdCloser fd_closer(&fd);
601
602 // If the final size is known, allocate the file (e.g. reserve disk
603 // space) and set the user.cros-p2p-filesize xattr.
604 if (expected_size != 0) {
605 if (fallocate(fd,
606 FALLOC_FL_KEEP_SIZE, // Keep file size as 0.
607 0,
608 expected_size) != 0) {
David Zeuthen910ec5b2013-09-26 12:10:58 -0700609 if (errno == ENOSYS || errno == EOPNOTSUPP) {
610 // If the filesystem doesn't support the fallocate, keep
611 // going. This is helpful when running unit tests on build
612 // machines with ancient filesystems and/or OSes.
613 PLOG(WARNING) << "Ignoring fallocate(2) failure";
614 } else {
615 // ENOSPC can happen (funky race though, cf. the statvfs() check
616 // above), handle it gracefully, e.g. use logging level INFO.
617 PLOG(INFO) << "Error allocating " << expected_size
618 << " bytes for file " << path.value();
619 if (unlink(path.value().c_str()) != 0) {
620 PLOG(ERROR) << "Error deleting file with path " << path.value();
621 }
622 return false;
David Zeuthen27a48bc2013-08-06 12:06:29 -0700623 }
David Zeuthen27a48bc2013-08-06 12:06:29 -0700624 }
625
Alex Vakulenko75039d72014-03-25 12:36:28 -0700626 string decimal_size = base::StringPrintf("%zu", expected_size);
David Zeuthen27a48bc2013-08-06 12:06:29 -0700627 if (fsetxattr(fd, kCrosP2PFileSizeXAttrName,
628 decimal_size.c_str(), decimal_size.size(), 0) != 0) {
629 PLOG(ERROR) << "Error setting xattr " << path.value();
630 return false;
631 }
632 }
633
634 return true;
635}
636
637FilePath P2PManagerImpl::FileGetPath(const string& file_id) {
638 struct stat statbuf;
Alex Vakulenko75039d72014-03-25 12:36:28 -0700639 base::FilePath path;
David Zeuthen27a48bc2013-08-06 12:06:29 -0700640
641 path = GetPath(file_id, kVisible);
642 if (stat(path.value().c_str(), &statbuf) == 0) {
643 return path;
644 }
645
646 path = GetPath(file_id, kNonVisible);
647 if (stat(path.value().c_str(), &statbuf) == 0) {
648 return path;
649 }
650
651 path.clear();
652 return path;
653}
654
655bool P2PManagerImpl::FileGetVisible(const string& file_id,
656 bool *out_result) {
Alex Vakulenko75039d72014-03-25 12:36:28 -0700657 base::FilePath path = FileGetPath(file_id);
David Zeuthen27a48bc2013-08-06 12:06:29 -0700658 if (path.empty()) {
659 LOG(ERROR) << "No file for id " << file_id;
660 return false;
661 }
662 if (out_result != NULL)
663 *out_result = path.MatchesExtension(kP2PExtension);
664 return true;
665}
666
667bool P2PManagerImpl::FileMakeVisible(const string& file_id) {
Alex Vakulenko75039d72014-03-25 12:36:28 -0700668 base::FilePath path = FileGetPath(file_id);
David Zeuthen27a48bc2013-08-06 12:06:29 -0700669 if (path.empty()) {
670 LOG(ERROR) << "No file for id " << file_id;
671 return false;
672 }
673
674 // Already visible?
675 if (path.MatchesExtension(kP2PExtension))
676 return true;
677
678 LOG_ASSERT(path.MatchesExtension(kTmpExtension));
Alex Vakulenko75039d72014-03-25 12:36:28 -0700679 base::FilePath new_path = path.RemoveExtension();
David Zeuthen27a48bc2013-08-06 12:06:29 -0700680 LOG_ASSERT(new_path.MatchesExtension(kP2PExtension));
681 if (rename(path.value().c_str(), new_path.value().c_str()) != 0) {
682 PLOG(ERROR) << "Error renaming " << path.value()
683 << " to " << new_path.value();
684 return false;
685 }
686
687 return true;
688}
689
690ssize_t P2PManagerImpl::FileGetSize(const string& file_id) {
Alex Vakulenko75039d72014-03-25 12:36:28 -0700691 base::FilePath path = FileGetPath(file_id);
David Zeuthen27a48bc2013-08-06 12:06:29 -0700692 if (path.empty())
693 return -1;
694
695 struct stat statbuf;
696 if (stat(path.value().c_str(), &statbuf) != 0) {
697 PLOG(ERROR) << "Error getting file status for " << path.value();
698 return -1;
699 }
700
701 return statbuf.st_size;
702}
703
704ssize_t P2PManagerImpl::FileGetExpectedSize(const string& file_id) {
Alex Vakulenko75039d72014-03-25 12:36:28 -0700705 base::FilePath path = FileGetPath(file_id);
David Zeuthen27a48bc2013-08-06 12:06:29 -0700706 if (path.empty())
707 return -1;
708
709 char ea_value[64] = { 0 };
710 ssize_t ea_size;
711 ea_size = getxattr(path.value().c_str(), kCrosP2PFileSizeXAttrName,
712 &ea_value, sizeof(ea_value) - 1);
713 if (ea_size == -1) {
714 PLOG(ERROR) << "Error calling getxattr() on file " << path.value();
715 return -1;
716 }
717
718 char* endp = NULL;
719 long long int val = strtoll(ea_value, &endp, 0);
720 if (*endp != '\0') {
721 LOG(ERROR) << "Error parsing the value '" << ea_value
722 << "' of the xattr " << kCrosP2PFileSizeXAttrName
723 << " as an integer";
724 return -1;
725 }
726
727 return val;
728}
729
730int P2PManagerImpl::CountSharedFiles() {
731 GDir* dir;
732 GError* error = NULL;
733 const char* name;
734 int num_files = 0;
735
Alex Vakulenko75039d72014-03-25 12:36:28 -0700736 base::FilePath p2p_dir = configuration_->GetP2PDir();
David Zeuthen27a48bc2013-08-06 12:06:29 -0700737 dir = g_dir_open(p2p_dir.value().c_str(), 0, &error);
738 if (dir == NULL) {
739 LOG(ERROR) << "Error opening directory " << p2p_dir.value() << ": "
740 << utils::GetAndFreeGError(&error);
741 return -1;
742 }
743
744 string ext_visible = GetExt(kVisible);
745 string ext_non_visible = GetExt(kNonVisible);
746 while ((name = g_dir_read_name(dir)) != NULL) {
747 if (g_str_has_suffix(name, ext_visible.c_str()) ||
748 g_str_has_suffix(name, ext_non_visible.c_str())) {
749 num_files += 1;
750 }
751 }
752 g_dir_close(dir);
753
754 return num_files;
755}
756
757P2PManager* P2PManager::Construct(Configuration *configuration,
758 PrefsInterface *prefs,
759 const string& file_extension,
760 const int num_files_to_keep) {
761 return new P2PManagerImpl(configuration,
762 prefs,
763 file_extension,
764 num_files_to_keep);
765}
766
767} // namespace chromeos_update_engine