Merge branch 'rewrite-update_engine' into merge-update_engine
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d6dd317
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+# Build/test generated files
+/*.pub.pem
+/app.info
+/delta_generator
+/html/
+/test_http_server
+/update_engine
+/update_engine.dbusclient.h
+/update_engine.dbusserver.h
+/update_engine_client
+/update_engine_unittests
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..fafbecc
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+deymo@chromium.org
+garnold@chromium.org
+zeuthen@chromium.org
diff --git a/UpdateEngine.conf b/UpdateEngine.conf
new file mode 100644
index 0000000..e9fe2d5
--- /dev/null
+++ b/UpdateEngine.conf
@@ -0,0 +1,67 @@
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <policy user="root">
+ <allow own="org.chromium.UpdateEngine" />
+ <allow send_destination="org.chromium.UpdateEngine" />
+ </policy>
+ <policy user="chronos">
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="AttemptUpdate"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="AttemptUpdateWithFlags"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="AttemptRollback"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="CanRollback"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetRollbackPartition"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetKernelDevices"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="ResetStatus"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetStatus"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="RebootIfNeeded"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="SetChannel"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetChannel"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="SetP2PUpdatePermission"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetP2PUpdatePermission"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="SetUpdateOverCellularPermission"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetUpdateOverCellularPermission"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetDurationSinceUpdate"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetPrevVersion"/>
+ <allow send_interface="org.chromium.UpdateEngineLibcrosProxyResolvedInterface" />
+ </policy>
+ <policy user="power">
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetStatus"/>
+ </policy>
+</busconfig>
diff --git a/WATCHLISTS b/WATCHLISTS
new file mode 100644
index 0000000..bcce0de
--- /dev/null
+++ b/WATCHLISTS
@@ -0,0 +1,14 @@
+# See http://dev.chromium.org/developers/contributing-code/watchlists for
+# a description of this file's format.
+# Please keep these keys in alphabetical order.
+
+{
+ 'WATCHLIST_DEFINITIONS': {
+ 'all': {
+ 'filepath': '.',
+ },
+ },
+ 'WATCHLISTS': {
+ 'all': ['adlr@chromium.org', 'petkov@chromium.org']
+ },
+}
diff --git a/action.h b/action.h
new file mode 100644
index 0000000..53786d1
--- /dev/null
+++ b/action.h
@@ -0,0 +1,206 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_ACTION_H_
+#define UPDATE_ENGINE_ACTION_H_
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+#include "update_engine/action_pipe.h"
+#include "update_engine/action_processor.h"
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+//
+// Readers may want to consult this wiki page from the Update Engine site:
+// http://code.google.com/p/update-engine/wiki/ActionProcessor
+// Although it's referring to the Objective-C KSAction* classes, much
+// applies here as well.
+//
+// How it works:
+//
+// First off, there is only one thread and all I/O should be asynchronous.
+// A glib main loop blocks whenever there is no work to be done. This happens
+// where there is no CPU work to be done and no I/O ready to transfer in or
+// out. Two kinds of events can wake up the main loop: timer alarm or file
+// descriptors. If either of these happens, glib finds out the owner of what
+// fired and calls the appropriate code to handle it. As such, all the code
+// in the Action* classes and the code that is calls is non-blocking.
+//
+// An ActionProcessor contains a queue of Actions to perform. When
+// ActionProcessor::StartProcessing() is called, it executes the first action.
+// Each action tells the processor when it has completed, which causes the
+// Processor to execute the next action. ActionProcessor may have a delegate
+// (an object of type ActionProcessorDelegate). If it does, the delegate
+// is called to be notified of events as they happen.
+//
+// ActionPipe classes
+//
+// See action_pipe.h
+//
+// ActionTraits
+//
+// We need to use an extra class ActionTraits. ActionTraits is a simple
+// templated class that contains only two typedefs: OutputObjectType and
+// InputObjectType. Each action class also has two typedefs of the same name
+// that are of the same type. So, to get the input/output types of, e.g., the
+// DownloadAction class, we look at the type of
+// DownloadAction::InputObjectType.
+//
+// Each concrete Action class derives from Action<T>. This means that during
+// template instantiation of Action<T>, T is declared but not defined, which
+// means that T::InputObjectType (and OutputObjectType) is not defined.
+// However, the traits class is constructed in such a way that it will be
+// template instantiated first, so Action<T> *can* find the types it needs by
+// consulting ActionTraits<T>::InputObjectType (and OutputObjectType).
+// This is why the ActionTraits classes are needed.
+
+namespace chromeos_update_engine {
+
+// It is handy to have a non-templated base class of all Actions.
+class AbstractAction {
+ public:
+ AbstractAction() : processor_(nullptr) {}
+ virtual ~AbstractAction() = default;
+
+ // Begin performing the action. Since this code is asynchronous, when this
+ // method returns, it means only that the action has started, not necessarily
+ // completed. However, it's acceptable for this method to perform the
+ // action synchronously; Action authors should understand the implications
+ // of synchronously performing, though, because this is a single-threaded
+ // app, the entire process will be blocked while the action performs.
+ //
+ // When the action is complete, it must call
+ // ActionProcessor::ActionComplete(this); to notify the processor that it's
+ // done.
+ virtual void PerformAction() = 0;
+
+ // Called on ActionProcess::ActionComplete() by ActionProcessor.
+ virtual void ActionCompleted(ErrorCode code) {}
+
+ // Called by the ActionProcessor to tell this Action which processor
+ // it belongs to.
+ void SetProcessor(ActionProcessor* processor) {
+ if (processor)
+ CHECK(!processor_);
+ else
+ CHECK(processor_);
+ processor_ = processor;
+ }
+
+ // Returns true iff the action is the current action of its ActionProcessor.
+ bool IsRunning() const {
+ if (!processor_)
+ return false;
+ return processor_->current_action() == this;
+ }
+
+ // Called on asynchronous actions if canceled. Actions may implement if
+ // there's any cleanup to do. There is no need to call
+ // ActionProcessor::ActionComplete() because the processor knows this
+ // action is terminating.
+ // Only the ActionProcessor should call this.
+ virtual void TerminateProcessing() {}
+
+ // These methods are useful for debugging. TODO(adlr): consider using
+ // std::type_info for this?
+ // Type() returns a string of the Action type. I.e., for DownloadAction,
+ // Type() would return "DownloadAction".
+ virtual std::string Type() const = 0;
+
+ protected:
+ // A weak pointer to the processor that owns this Action.
+ ActionProcessor* processor_;
+};
+
+// Forward declare a couple classes we use.
+template<typename T>
+class ActionPipe;
+template<typename T>
+class ActionTraits;
+
+template<typename SubClass>
+class Action : public AbstractAction {
+ public:
+ ~Action() override {}
+
+ // Attaches an input pipe to this Action. This is optional; an Action
+ // doesn't need to have an input pipe. The input pipe must be of the type
+ // of object that this class expects.
+ // This is generally called by ActionPipe::Bond()
+ void set_in_pipe(
+ // this type is a fancy way of saying: a shared_ptr to an
+ // ActionPipe<InputObjectType>.
+ const std::shared_ptr<ActionPipe<
+ typename ActionTraits<SubClass>::InputObjectType>>& in_pipe) {
+ in_pipe_ = in_pipe;
+ }
+
+ // Attaches an output pipe to this Action. This is optional; an Action
+ // doesn't need to have an output pipe. The output pipe must be of the type
+ // of object that this class expects.
+ // This is generally called by ActionPipe::Bond()
+ void set_out_pipe(
+ // this type is a fancy way of saying: a shared_ptr to an
+ // ActionPipe<OutputObjectType>.
+ const std::shared_ptr<ActionPipe<
+ typename ActionTraits<SubClass>::OutputObjectType>>& out_pipe) {
+ out_pipe_ = out_pipe;
+ }
+
+ // Returns true iff there is an associated input pipe. If there's an input
+ // pipe, there's an input object, but it may have been constructed with the
+ // default ctor if the previous action didn't call SetOutputObject().
+ bool HasInputObject() const { return in_pipe_.get(); }
+
+ // returns a const reference to the object in the input pipe.
+ const typename ActionTraits<SubClass>::InputObjectType& GetInputObject()
+ const {
+ CHECK(HasInputObject());
+ return in_pipe_->contents();
+ }
+
+ // Returns true iff there's an output pipe.
+ bool HasOutputPipe() const {
+ return out_pipe_.get();
+ }
+
+ // Copies the object passed into the output pipe. It will be accessible to
+ // the next Action via that action's input pipe (which is the same as this
+ // Action's output pipe).
+ void SetOutputObject(
+ const typename ActionTraits<SubClass>::OutputObjectType& out_obj) {
+ CHECK(HasOutputPipe());
+ out_pipe_->set_contents(out_obj);
+ }
+
+ // Returns a reference to the object sitting in the output pipe.
+ const typename ActionTraits<SubClass>::OutputObjectType& GetOutputObject() {
+ CHECK(HasOutputPipe());
+ return out_pipe_->contents();
+ }
+
+ protected:
+ // We use a shared_ptr to the pipe. shared_ptr objects destroy what they
+ // point to when the last such shared_ptr object dies. We consider the
+ // Actions on either end of a pipe to "own" the pipe. When the last Action
+ // of the two dies, the ActionPipe will die, too.
+ std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::InputObjectType>>
+ in_pipe_;
+ std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::OutputObjectType>>
+ out_pipe_;
+};
+
+}; // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_ACTION_H_
diff --git a/action_pipe.h b/action_pipe.h
new file mode 100644
index 0000000..b84b0f6
--- /dev/null
+++ b/action_pipe.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_ACTION_PIPE_H_
+#define UPDATE_ENGINE_ACTION_PIPE_H_
+
+#include <stdio.h>
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+
+// This class serves as a temporary holding area for an object passed out
+// from one Action and into another Action. It's templated so that it may
+// contain any type of object that an Action outputs/inputs. Actions
+// cannot be bonded (i.e., connected with a pipe) if their output/input
+// object types differ (a compiler error will result).
+//
+// An ActionPipe is generally created with the Bond() method and owned by
+// the two Action objects. a shared_ptr is used so that when the last Action
+// pointing to an ActionPipe dies, the ActionPipe dies, too.
+
+namespace chromeos_update_engine {
+
+// Used by Actions an InputObjectType or OutputObjectType to specify that
+// for that type, no object is taken/given.
+class NoneType {};
+
+template<typename T>
+class Action;
+
+template<typename ObjectType>
+class ActionPipe {
+ public:
+ virtual ~ActionPipe() {}
+
+ // This should be called by an Action on its input pipe.
+ // Returns a reference to the stored object.
+ const ObjectType& contents() const { return contents_; }
+
+ // This should be called by an Action on its output pipe.
+ // Stores a copy of the passed object in this pipe.
+ void set_contents(const ObjectType& contents) { contents_ = contents; }
+
+ // Bonds two Actions together with a new ActionPipe. The ActionPipe is
+ // jointly owned by the two Actions and will be automatically destroyed
+ // when the last Action is destroyed.
+ template<typename FromAction, typename ToAction>
+ static void Bond(FromAction* from, ToAction* to) {
+ std::shared_ptr<ActionPipe<ObjectType>> pipe(new ActionPipe<ObjectType>);
+ from->set_out_pipe(pipe);
+
+ to->set_in_pipe(pipe); // If you get an error on this line, then
+ // it most likely means that the From object's OutputObjectType is
+ // different from the To object's InputObjectType.
+ }
+
+ private:
+ ObjectType contents_;
+
+ // The ctor is private. This is because this class should construct itself
+ // via the static Bond() method.
+ ActionPipe() {}
+ DISALLOW_COPY_AND_ASSIGN(ActionPipe);
+};
+
+// Utility function
+template<typename FromAction, typename ToAction>
+void BondActions(FromAction* from, ToAction* to) {
+ // TODO(adlr): find something like this that the compiler accepts:
+ // COMPILE_ASSERT(typeof(typename FromAction::OutputObjectType) ==
+ // typeof(typename ToAction::InputObjectType),
+ // FromAction_OutputObjectType_doesnt_match_ToAction_InputObjectType);
+ ActionPipe<typename FromAction::OutputObjectType>::Bond(from, to);
+}
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_ACTION_PIPE_H_
diff --git a/action_pipe_unittest.cc b/action_pipe_unittest.cc
new file mode 100644
index 0000000..3cdd03d
--- /dev/null
+++ b/action_pipe_unittest.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/action_pipe.h"
+
+#include <string>
+#include <gtest/gtest.h>
+#include "update_engine/action.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionPipeTestAction;
+
+template<>
+class ActionTraits<ActionPipeTestAction> {
+ public:
+ typedef string OutputObjectType;
+ typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionPipeTestAction : public Action<ActionPipeTestAction> {
+ public:
+ typedef string InputObjectType;
+ typedef string OutputObjectType;
+ ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+ ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+ void PerformAction() {}
+ string Type() const { return "ActionPipeTestAction"; }
+};
+
+class ActionPipeTest : public ::testing::Test { };
+
+// This test creates two simple Actions and sends a message via an ActionPipe
+// from one to the other.
+TEST(ActionPipeTest, SimpleTest) {
+ ActionPipeTestAction a, b;
+ BondActions(&a, &b);
+ a.out_pipe()->set_contents("foo");
+ EXPECT_EQ("foo", b.in_pipe()->contents());
+}
+
+} // namespace chromeos_update_engine
diff --git a/action_processor.cc b/action_processor.cc
new file mode 100644
index 0000000..9a27aac
--- /dev/null
+++ b/action_processor.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/action_processor.h"
+
+#include <string>
+
+#include <base/logging.h>
+
+#include "update_engine/action.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+ActionProcessor::ActionProcessor()
+ : current_action_(nullptr), delegate_(nullptr) {}
+
+ActionProcessor::~ActionProcessor() {
+ if (IsRunning()) {
+ StopProcessing();
+ }
+ for (std::deque<AbstractAction*>::iterator it = actions_.begin();
+ it != actions_.end(); ++it) {
+ (*it)->SetProcessor(nullptr);
+ }
+}
+
+void ActionProcessor::EnqueueAction(AbstractAction* action) {
+ actions_.push_back(action);
+ action->SetProcessor(this);
+}
+
+void ActionProcessor::StartProcessing() {
+ CHECK(!IsRunning());
+ if (!actions_.empty()) {
+ current_action_ = actions_.front();
+ LOG(INFO) << "ActionProcessor::StartProcessing: "
+ << current_action_->Type();
+ actions_.pop_front();
+ current_action_->PerformAction();
+ }
+}
+
+void ActionProcessor::StopProcessing() {
+ CHECK(IsRunning());
+ CHECK(current_action_);
+ current_action_->TerminateProcessing();
+ CHECK(current_action_);
+ current_action_->SetProcessor(nullptr);
+ LOG(INFO) << "ActionProcessor::StopProcessing: aborted "
+ << current_action_->Type();
+ current_action_ = nullptr;
+ if (delegate_)
+ delegate_->ProcessingStopped(this);
+}
+
+void ActionProcessor::ActionComplete(AbstractAction* actionptr,
+ ErrorCode code) {
+ CHECK_EQ(actionptr, current_action_);
+ if (delegate_)
+ delegate_->ActionCompleted(this, actionptr, code);
+ string old_type = current_action_->Type();
+ current_action_->ActionCompleted(code);
+ current_action_->SetProcessor(nullptr);
+ current_action_ = nullptr;
+ if (actions_.empty()) {
+ LOG(INFO) << "ActionProcessor::ActionComplete: finished last action of"
+ " type " << old_type;
+ } else if (code != ErrorCode::kSuccess) {
+ LOG(INFO) << "ActionProcessor::ActionComplete: " << old_type
+ << " action failed. Aborting processing.";
+ actions_.clear();
+ }
+ if (actions_.empty()) {
+ LOG(INFO) << "ActionProcessor::ActionComplete: finished last action of"
+ " type " << old_type;
+ if (delegate_) {
+ delegate_->ProcessingDone(this, code);
+ }
+ return;
+ }
+ current_action_ = actions_.front();
+ actions_.pop_front();
+ LOG(INFO) << "ActionProcessor::ActionComplete: finished " << old_type
+ << ", starting " << current_action_->Type();
+ current_action_->PerformAction();
+}
+
+} // namespace chromeos_update_engine
diff --git a/action_processor.h b/action_processor.h
new file mode 100644
index 0000000..121e0e1
--- /dev/null
+++ b/action_processor.h
@@ -0,0 +1,104 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_ACTION_PROCESSOR_H_
+#define UPDATE_ENGINE_ACTION_PROCESSOR_H_
+
+#include <deque>
+
+#include <base/macros.h>
+
+#include "update_engine/error_code.h"
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+
+// See action.h for an overview of this class and other Action* classes.
+
+// An ActionProcessor keeps a queue of Actions and processes them in order.
+
+namespace chromeos_update_engine {
+
+class AbstractAction;
+class ActionProcessorDelegate;
+
+class ActionProcessor {
+ public:
+ ActionProcessor();
+
+ virtual ~ActionProcessor();
+
+ // Starts processing the first Action in the queue. If there's a delegate,
+ // when all processing is complete, ProcessingDone() will be called on the
+ // delegate.
+ virtual void StartProcessing();
+
+ // Aborts processing. If an Action is running, it will have
+ // TerminateProcessing() called on it. The Action that was running
+ // will be lost and must be re-enqueued if this Processor is to use it.
+ void StopProcessing();
+
+ // Returns true iff an Action is currently processing.
+ bool IsRunning() const { return nullptr != current_action_; }
+
+ // Adds another Action to the end of the queue.
+ virtual void EnqueueAction(AbstractAction* action);
+
+ // Sets/gets the current delegate. Set to null to remove a delegate.
+ ActionProcessorDelegate* delegate() const { return delegate_; }
+ void set_delegate(ActionProcessorDelegate *delegate) {
+ delegate_ = delegate;
+ }
+
+ // Returns a pointer to the current Action that's processing.
+ AbstractAction* current_action() const {
+ return current_action_;
+ }
+
+ // Called by an action to notify processor that it's done. Caller passes self.
+ void ActionComplete(AbstractAction* actionptr, ErrorCode code);
+
+ private:
+ // Actions that have not yet begun processing, in the order in which
+ // they'll be processed.
+ std::deque<AbstractAction*> actions_;
+
+ // A pointer to the currently processing Action, if any.
+ AbstractAction* current_action_;
+
+ // A pointer to the delegate, or null if none.
+ ActionProcessorDelegate *delegate_;
+ DISALLOW_COPY_AND_ASSIGN(ActionProcessor);
+};
+
+// A delegate object can be used to be notified of events that happen
+// in an ActionProcessor. An instance of this class can be passed to an
+// ActionProcessor to register itself.
+class ActionProcessorDelegate {
+ public:
+ virtual ~ActionProcessorDelegate() = default;
+
+ // Called when all processing in an ActionProcessor has completed. A pointer
+ // to the ActionProcessor is passed. |code| is set to the exit code of the
+ // last completed action.
+ virtual void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) {}
+
+ // Called when processing has stopped. Does not mean that all Actions have
+ // completed. If/when all Actions complete, ProcessingDone() will be called.
+ virtual void ProcessingStopped(const ActionProcessor* processor) {}
+
+ // Called whenever an action has finished processing, either successfully
+ // or otherwise.
+ virtual void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {}
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_ACTION_PROCESSOR_H_
diff --git a/action_processor_unittest.cc b/action_processor_unittest.cc
new file mode 100644
index 0000000..64e847d
--- /dev/null
+++ b/action_processor_unittest.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/action_processor.h"
+
+#include <string>
+#include <gtest/gtest.h>
+#include "update_engine/action.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionProcessorTestAction;
+
+template<>
+class ActionTraits<ActionProcessorTestAction> {
+ public:
+ typedef string OutputObjectType;
+ typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionProcessorTestAction : public Action<ActionProcessorTestAction> {
+ public:
+ typedef string InputObjectType;
+ typedef string OutputObjectType;
+ ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+ ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+ ActionProcessor* processor() { return processor_; }
+ void PerformAction() {}
+ void CompleteAction() {
+ ASSERT_TRUE(processor());
+ processor()->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ string Type() const { return "ActionProcessorTestAction"; }
+};
+
+class ActionProcessorTest : public ::testing::Test { };
+
+// This test creates two simple Actions and sends a message via an ActionPipe
+// from one to the other.
+TEST(ActionProcessorTest, SimpleTest) {
+ ActionProcessorTestAction action;
+ ActionProcessor action_processor;
+ EXPECT_FALSE(action_processor.IsRunning());
+ action_processor.EnqueueAction(&action);
+ EXPECT_FALSE(action_processor.IsRunning());
+ EXPECT_FALSE(action.IsRunning());
+ action_processor.StartProcessing();
+ EXPECT_TRUE(action_processor.IsRunning());
+ EXPECT_TRUE(action.IsRunning());
+ EXPECT_EQ(action_processor.current_action(), &action);
+ action.CompleteAction();
+ EXPECT_FALSE(action_processor.IsRunning());
+ EXPECT_FALSE(action.IsRunning());
+}
+
+namespace {
+class MyActionProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ explicit MyActionProcessorDelegate(const ActionProcessor* processor)
+ : processor_(processor),
+ processing_done_called_(false),
+ processing_stopped_called_(false),
+ action_completed_called_(false),
+ action_exit_code_(ErrorCode::kError) {}
+
+ virtual void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) {
+ EXPECT_EQ(processor_, processor);
+ EXPECT_FALSE(processing_done_called_);
+ processing_done_called_ = true;
+ }
+ virtual void ProcessingStopped(const ActionProcessor* processor) {
+ EXPECT_EQ(processor_, processor);
+ EXPECT_FALSE(processing_stopped_called_);
+ processing_stopped_called_ = true;
+ }
+ virtual void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ EXPECT_EQ(processor_, processor);
+ EXPECT_FALSE(action_completed_called_);
+ action_completed_called_ = true;
+ action_exit_code_ = code;
+ }
+
+ const ActionProcessor* processor_;
+ bool processing_done_called_;
+ bool processing_stopped_called_;
+ bool action_completed_called_;
+ ErrorCode action_exit_code_;
+};
+} // namespace
+
+TEST(ActionProcessorTest, DelegateTest) {
+ ActionProcessorTestAction action;
+ ActionProcessor action_processor;
+ MyActionProcessorDelegate delegate(&action_processor);
+ action_processor.set_delegate(&delegate);
+
+ action_processor.EnqueueAction(&action);
+ action_processor.StartProcessing();
+ action.CompleteAction();
+ action_processor.set_delegate(nullptr);
+ EXPECT_TRUE(delegate.processing_done_called_);
+ EXPECT_TRUE(delegate.action_completed_called_);
+}
+
+TEST(ActionProcessorTest, StopProcessingTest) {
+ ActionProcessorTestAction action;
+ ActionProcessor action_processor;
+ MyActionProcessorDelegate delegate(&action_processor);
+ action_processor.set_delegate(&delegate);
+
+ action_processor.EnqueueAction(&action);
+ action_processor.StartProcessing();
+ action_processor.StopProcessing();
+ action_processor.set_delegate(nullptr);
+ EXPECT_TRUE(delegate.processing_stopped_called_);
+ EXPECT_FALSE(delegate.action_completed_called_);
+ EXPECT_FALSE(action_processor.IsRunning());
+ EXPECT_EQ(nullptr, action_processor.current_action());
+}
+
+TEST(ActionProcessorTest, ChainActionsTest) {
+ ActionProcessorTestAction action1, action2;
+ ActionProcessor action_processor;
+ action_processor.EnqueueAction(&action1);
+ action_processor.EnqueueAction(&action2);
+ action_processor.StartProcessing();
+ EXPECT_EQ(&action1, action_processor.current_action());
+ EXPECT_TRUE(action_processor.IsRunning());
+ action1.CompleteAction();
+ EXPECT_EQ(&action2, action_processor.current_action());
+ EXPECT_TRUE(action_processor.IsRunning());
+ action2.CompleteAction();
+ EXPECT_EQ(nullptr, action_processor.current_action());
+ EXPECT_FALSE(action_processor.IsRunning());
+}
+
+TEST(ActionProcessorTest, DtorTest) {
+ ActionProcessorTestAction action1, action2;
+ {
+ ActionProcessor action_processor;
+ action_processor.EnqueueAction(&action1);
+ action_processor.EnqueueAction(&action2);
+ action_processor.StartProcessing();
+ }
+ EXPECT_EQ(nullptr, action1.processor());
+ EXPECT_FALSE(action1.IsRunning());
+ EXPECT_EQ(nullptr, action2.processor());
+ EXPECT_FALSE(action2.IsRunning());
+}
+
+TEST(ActionProcessorTest, DefaultDelegateTest) {
+ // Just make sure it doesn't crash
+ ActionProcessorTestAction action;
+ ActionProcessor action_processor;
+ ActionProcessorDelegate delegate;
+ action_processor.set_delegate(&delegate);
+
+ action_processor.EnqueueAction(&action);
+ action_processor.StartProcessing();
+ action.CompleteAction();
+
+ action_processor.EnqueueAction(&action);
+ action_processor.StartProcessing();
+ action_processor.StopProcessing();
+
+ action_processor.set_delegate(nullptr);
+}
+
+} // namespace chromeos_update_engine
diff --git a/action_unittest.cc b/action_unittest.cc
new file mode 100644
index 0000000..a5f7b11
--- /dev/null
+++ b/action_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/action.h"
+
+#include <string>
+#include <gtest/gtest.h>
+#include "update_engine/action_processor.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionTestAction;
+
+template<>
+class ActionTraits<ActionTestAction> {
+ public:
+ typedef string OutputObjectType;
+ typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionTestAction : public Action<ActionTestAction> {
+ public:
+ typedef string InputObjectType;
+ typedef string OutputObjectType;
+ ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+ ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+ ActionProcessor* processor() { return processor_; }
+ void PerformAction() {}
+ void CompleteAction() {
+ ASSERT_TRUE(processor());
+ processor()->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ string Type() const { return "ActionTestAction"; }
+};
+
+class ActionTest : public ::testing::Test { };
+
+// This test creates two simple Actions and sends a message via an ActionPipe
+// from one to the other.
+TEST(ActionTest, SimpleTest) {
+ ActionTestAction action;
+
+ EXPECT_FALSE(action.in_pipe());
+ EXPECT_FALSE(action.out_pipe());
+ EXPECT_FALSE(action.processor());
+ EXPECT_FALSE(action.IsRunning());
+
+ ActionProcessor action_processor;
+ action_processor.EnqueueAction(&action);
+ EXPECT_EQ(&action_processor, action.processor());
+
+ action_processor.StartProcessing();
+ EXPECT_TRUE(action.IsRunning());
+ action.CompleteAction();
+ EXPECT_FALSE(action.IsRunning());
+}
+
+} // namespace chromeos_update_engine
diff --git a/bzip.cc b/bzip.cc
new file mode 100644
index 0000000..9b15f10
--- /dev/null
+++ b/bzip.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/bzip.h"
+
+#include <stdlib.h>
+#include <algorithm>
+#include <bzlib.h>
+#include <limits>
+
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// BzipData compresses or decompresses the input to the output.
+// Returns true on success.
+// Use one of BzipBuffToBuff*ompress as the template parameter to BzipData().
+int BzipBuffToBuffDecompress(uint8_t* out,
+ uint32_t* out_length,
+ const void* in,
+ uint32_t in_length) {
+ return BZ2_bzBuffToBuffDecompress(
+ reinterpret_cast<char*>(out),
+ out_length,
+ reinterpret_cast<char*>(const_cast<void*>(in)),
+ in_length,
+ 0, // Silent verbosity
+ 0); // Normal algorithm
+}
+
+int BzipBuffToBuffCompress(uint8_t* out,
+ uint32_t* out_length,
+ const void* in,
+ uint32_t in_length) {
+ return BZ2_bzBuffToBuffCompress(
+ reinterpret_cast<char*>(out),
+ out_length,
+ reinterpret_cast<char*>(const_cast<void*>(in)),
+ in_length,
+ 9, // Best compression
+ 0, // Silent verbosity
+ 0); // Default work factor
+}
+
+template<int F(uint8_t* out,
+ uint32_t* out_length,
+ const void* in,
+ uint32_t in_length)>
+bool BzipData(const void* const in,
+ const size_t in_size,
+ chromeos::Blob* const out) {
+ TEST_AND_RETURN_FALSE(out);
+ out->clear();
+ if (in_size == 0) {
+ return true;
+ }
+ // Try increasing buffer size until it works
+ size_t buf_size = in_size;
+ out->resize(buf_size);
+
+ for (;;) {
+ if (buf_size > std::numeric_limits<uint32_t>::max())
+ return false;
+ uint32_t data_size = buf_size;
+ int rc = F(out->data(), &data_size, in, in_size);
+ TEST_AND_RETURN_FALSE(rc == BZ_OUTBUFF_FULL || rc == BZ_OK);
+ if (rc == BZ_OK) {
+ // we're done!
+ out->resize(data_size);
+ return true;
+ }
+
+ // Data didn't fit; double the buffer size.
+ buf_size *= 2;
+ out->resize(buf_size);
+ }
+}
+
+} // namespace
+
+bool BzipDecompress(const chromeos::Blob& in, chromeos::Blob* out) {
+ return BzipData<BzipBuffToBuffDecompress>(in.data(), in.size(), out);
+}
+
+bool BzipCompress(const chromeos::Blob& in, chromeos::Blob* out) {
+ return BzipData<BzipBuffToBuffCompress>(in.data(), in.size(), out);
+}
+
+namespace {
+template<bool F(const void* const in,
+ const size_t in_size,
+ chromeos::Blob* const out)>
+bool BzipString(const string& str,
+ chromeos::Blob* out) {
+ TEST_AND_RETURN_FALSE(out);
+ chromeos::Blob temp;
+ TEST_AND_RETURN_FALSE(F(str.data(), str.size(), &temp));
+ out->clear();
+ out->insert(out->end(), temp.begin(), temp.end());
+ return true;
+}
+} // namespace
+
+bool BzipCompressString(const string& str, chromeos::Blob* out) {
+ return BzipString<BzipData<BzipBuffToBuffCompress>>(str, out);
+}
+
+bool BzipDecompressString(const string& str, chromeos::Blob* out) {
+ return BzipString<BzipData<BzipBuffToBuffDecompress>>(str, out);
+}
+
+} // namespace chromeos_update_engine
diff --git a/bzip.h b/bzip.h
new file mode 100644
index 0000000..af3ada4
--- /dev/null
+++ b/bzip.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_BZIP_H_
+#define UPDATE_ENGINE_BZIP_H_
+
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+
+namespace chromeos_update_engine {
+
+// Bzip2 compresses or decompresses str/in to out.
+bool BzipDecompress(const chromeos::Blob& in, chromeos::Blob* out);
+bool BzipCompress(const chromeos::Blob& in, chromeos::Blob* out);
+bool BzipCompressString(const std::string& str, chromeos::Blob* out);
+bool BzipDecompressString(const std::string& str, chromeos::Blob* out);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_BZIP_H_
diff --git a/bzip_extent_writer.cc b/bzip_extent_writer.cc
new file mode 100644
index 0000000..14a0f63
--- /dev/null
+++ b/bzip_extent_writer.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/bzip_extent_writer.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const chromeos::Blob::size_type kOutputBufferLength = 16 * 1024;
+}
+
+bool BzipExtentWriter::Init(FileDescriptorPtr fd,
+ const vector<Extent>& extents,
+ uint32_t block_size) {
+ // Init bzip2 stream
+ int rc = BZ2_bzDecompressInit(&stream_,
+ 0, // verbosity. (0 == silent)
+ 0); // 0 = faster algo, more memory
+
+ TEST_AND_RETURN_FALSE(rc == BZ_OK);
+
+ return next_->Init(fd, extents, block_size);
+}
+
+bool BzipExtentWriter::Write(const void* bytes, size_t count) {
+ chromeos::Blob output_buffer(kOutputBufferLength);
+
+ // Copy the input data into |input_buffer_| only if |input_buffer_| already
+ // contains unconsumed data. Otherwise, process the data directly from the
+ // source.
+ const uint8_t* input = reinterpret_cast<const uint8_t*>(bytes);
+ const uint8_t* input_end = input + count;
+ if (!input_buffer_.empty()) {
+ input_buffer_.insert(input_buffer_.end(), input, input_end);
+ input = input_buffer_.data();
+ input_end = input + input_buffer_.size();
+ }
+ stream_.next_in = reinterpret_cast<char*>(const_cast<uint8_t*>(input));
+ stream_.avail_in = input_end - input;
+
+ for (;;) {
+ stream_.next_out = reinterpret_cast<char*>(output_buffer.data());
+ stream_.avail_out = output_buffer.size();
+
+ int rc = BZ2_bzDecompress(&stream_);
+ TEST_AND_RETURN_FALSE(rc == BZ_OK || rc == BZ_STREAM_END);
+
+ if (stream_.avail_out == output_buffer.size())
+ break; // got no new bytes
+
+ TEST_AND_RETURN_FALSE(
+ next_->Write(output_buffer.data(),
+ output_buffer.size() - stream_.avail_out));
+
+ if (rc == BZ_STREAM_END)
+ CHECK_EQ(stream_.avail_in, 0u);
+ if (stream_.avail_in == 0)
+ break; // no more input to process
+ }
+
+ // Store unconsumed data (if any) in |input_buffer_|.
+ if (stream_.avail_in || !input_buffer_.empty()) {
+ chromeos::Blob new_input_buffer(input_end - stream_.avail_in, input_end);
+ new_input_buffer.swap(input_buffer_);
+ }
+
+ return true;
+}
+
+bool BzipExtentWriter::EndImpl() {
+ TEST_AND_RETURN_FALSE(input_buffer_.empty());
+ TEST_AND_RETURN_FALSE(BZ2_bzDecompressEnd(&stream_) == BZ_OK);
+ return next_->End();
+}
+
+} // namespace chromeos_update_engine
diff --git a/bzip_extent_writer.h b/bzip_extent_writer.h
new file mode 100644
index 0000000..6a6133a
--- /dev/null
+++ b/bzip_extent_writer.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_BZIP_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_BZIP_EXTENT_WRITER_H_
+
+#include <bzlib.h>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/extent_writer.h"
+#include "update_engine/utils.h"
+
+// BzipExtentWriter is a concrete ExtentWriter subclass that bzip-decompresses
+// what it's given in Write. It passes the decompressed data to an underlying
+// ExtentWriter.
+
+namespace chromeos_update_engine {
+
+class BzipExtentWriter : public ExtentWriter {
+ public:
+ explicit BzipExtentWriter(ExtentWriter* next) : next_(next) {
+ memset(&stream_, 0, sizeof(stream_));
+ }
+ ~BzipExtentWriter() {}
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size);
+ bool Write(const void* bytes, size_t count);
+ bool EndImpl();
+
+ private:
+ ExtentWriter* const next_; // The underlying ExtentWriter.
+ bz_stream stream_; // the libbz2 stream
+ chromeos::Blob input_buffer_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_BZIP_EXTENT_WRITER_H_
diff --git a/bzip_extent_writer_unittest.cc b/bzip_extent_writer_unittest.cc
new file mode 100644
index 0000000..2b98eb1
--- /dev/null
+++ b/bzip_extent_writer_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/bzip_extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const char kPathTemplate[] = "./BzipExtentWriterTest-file.XXXXXX";
+const uint32_t kBlockSize = 4096;
+}
+
+class BzipExtentWriterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ memcpy(path_, kPathTemplate, sizeof(kPathTemplate));
+ fd_.reset(new EintrSafeFileDescriptor);
+ int fd = mkstemp(path_);
+ ASSERT_TRUE(fd_->Open(path_, O_RDWR, 0600));
+ close(fd);
+ }
+ void TearDown() override {
+ fd_->Close();
+ unlink(path_);
+ }
+ void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+ void TestZeroPad(bool aligned_size);
+
+ FileDescriptorPtr fd_;
+ char path_[sizeof(kPathTemplate)];
+};
+
+TEST_F(BzipExtentWriterTest, SimpleTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ // 'echo test | bzip2 | hexdump' yields:
+ static const char test_uncompressed[] = "test\n";
+ static const uint8_t test[] = {
+ 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xcc, 0xc3,
+ 0x71, 0xd4, 0x00, 0x00, 0x02, 0x41, 0x80, 0x00, 0x10, 0x02, 0x00, 0x0c,
+ 0x00, 0x20, 0x00, 0x21, 0x9a, 0x68, 0x33, 0x4d, 0x19, 0x97, 0x8b, 0xb9,
+ 0x22, 0x9c, 0x28, 0x48, 0x66, 0x61, 0xb8, 0xea, 0x00,
+ };
+
+ DirectExtentWriter direct_writer;
+ BzipExtentWriter bzip_writer(&direct_writer);
+ EXPECT_TRUE(bzip_writer.Init(fd_, extents, kBlockSize));
+ EXPECT_TRUE(bzip_writer.Write(test, sizeof(test)));
+ EXPECT_TRUE(bzip_writer.End());
+
+ chromeos::Blob buf;
+ EXPECT_TRUE(utils::ReadFile(path_, &buf));
+ EXPECT_EQ(strlen(test_uncompressed), buf.size());
+ EXPECT_EQ(string(buf.begin(), buf.end()), string(test_uncompressed));
+}
+
+TEST_F(BzipExtentWriterTest, ChunkedTest) {
+ const chromeos::Blob::size_type kDecompressedLength = 2048 * 1024; // 2 MiB
+ string decompressed_path;
+ ASSERT_TRUE(utils::MakeTempFile("BzipExtentWriterTest-decompressed-XXXXXX",
+ &decompressed_path, nullptr));
+ string compressed_path;
+ ASSERT_TRUE(utils::MakeTempFile("BzipExtentWriterTest-compressed-XXXXXX",
+ &compressed_path, nullptr));
+ const size_t kChunkSize = 3;
+
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(0);
+ extent.set_num_blocks(kDecompressedLength / kBlockSize + 1);
+ extents.push_back(extent);
+
+ chromeos::Blob decompressed_data(kDecompressedLength);
+ test_utils::FillWithData(&decompressed_data);
+
+ EXPECT_TRUE(test_utils::WriteFileVector(
+ decompressed_path, decompressed_data));
+
+ EXPECT_EQ(0, test_utils::System(
+ string("cat ") + decompressed_path + "|bzip2>" + compressed_path));
+
+ chromeos::Blob compressed_data;
+ EXPECT_TRUE(utils::ReadFile(compressed_path, &compressed_data));
+
+ DirectExtentWriter direct_writer;
+ BzipExtentWriter bzip_writer(&direct_writer);
+ EXPECT_TRUE(bzip_writer.Init(fd_, extents, kBlockSize));
+
+ chromeos::Blob original_compressed_data = compressed_data;
+ for (chromeos::Blob::size_type i = 0; i < compressed_data.size();
+ i += kChunkSize) {
+ size_t this_chunk_size = min(kChunkSize, compressed_data.size() - i);
+ EXPECT_TRUE(bzip_writer.Write(&compressed_data[i], this_chunk_size));
+ }
+ EXPECT_TRUE(bzip_writer.End());
+
+ // Check that the const input has not been clobbered.
+ test_utils::ExpectVectorsEq(original_compressed_data, compressed_data);
+
+ chromeos::Blob output;
+ EXPECT_TRUE(utils::ReadFile(path_, &output));
+ EXPECT_EQ(kDecompressedLength, output.size());
+ test_utils::ExpectVectorsEq(decompressed_data, output);
+
+ unlink(decompressed_path.c_str());
+ unlink(compressed_path.c_str());
+}
+
+} // namespace chromeos_update_engine
diff --git a/certificate_checker.cc b/certificate_checker.cc
new file mode 100644
index 0000000..4c1d2ed
--- /dev/null
+++ b/certificate_checker.cc
@@ -0,0 +1,190 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/certificate_checker.h"
+
+#include <string>
+
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/logging.h>
+#include <curl/curl.h>
+#include <openssl/evp.h>
+#include <openssl/ssl.h>
+
+#include "metrics/metrics_library.h"
+#include "update_engine/constants.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+// This should be in the same order of CertificateChecker::ServerToCheck, with
+// the exception of kNone.
+static const char* kReportToSendKey[2] =
+ {kPrefsCertificateReportToSendUpdate,
+ kPrefsCertificateReportToSendDownload};
+} // namespace
+
+bool OpenSSLWrapper::GetCertificateDigest(X509_STORE_CTX* x509_ctx,
+ int* out_depth,
+ unsigned int* out_digest_length,
+ uint8_t* out_digest) const {
+ TEST_AND_RETURN_FALSE(out_digest);
+ X509* certificate = X509_STORE_CTX_get_current_cert(x509_ctx);
+ TEST_AND_RETURN_FALSE(certificate);
+ int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
+ if (out_depth)
+ *out_depth = depth;
+
+ unsigned int len;
+ const EVP_MD* digest_function = EVP_sha256();
+ bool success = X509_digest(certificate, digest_function, out_digest, &len);
+
+ if (success && out_digest_length)
+ *out_digest_length = len;
+ return success;
+}
+
+// static
+SystemState* CertificateChecker::system_state_ = nullptr;
+
+// static
+OpenSSLWrapper* CertificateChecker::openssl_wrapper_ = nullptr;
+
+// static
+CURLcode CertificateChecker::ProcessSSLContext(CURL* curl_handle,
+ SSL_CTX* ssl_ctx,
+ void* ptr) {
+ // From here we set the SSL_CTX to another callback, from the openssl library,
+ // which will be called after each server certificate is validated. However,
+ // since openssl does not allow us to pass our own data pointer to the
+ // callback, the certificate check will have to be done statically. Since we
+ // need to know which update server we are using in order to check the
+ // certificate, we hardcode Chrome OS's two known update servers here, and
+ // define a different static callback for each. Since this code should only
+ // run in official builds, this should not be a problem. However, if an update
+ // server different from the ones listed here is used, the check will not
+ // take place.
+ ServerToCheck* server_to_check = reinterpret_cast<ServerToCheck*>(ptr);
+
+ // We check which server to check and set the appropriate static callback.
+ if (*server_to_check == kUpdate)
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackUpdateCheck);
+ if (*server_to_check == kDownload)
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackDownload);
+
+ return CURLE_OK;
+}
+
+// static
+int CertificateChecker::VerifySSLCallbackUpdateCheck(int preverify_ok,
+ X509_STORE_CTX* x509_ctx) {
+ return CertificateChecker::CheckCertificateChange(
+ kUpdate, preverify_ok, x509_ctx) ? 1 : 0;
+}
+
+// static
+int CertificateChecker::VerifySSLCallbackDownload(int preverify_ok,
+ X509_STORE_CTX* x509_ctx) {
+ return CertificateChecker::CheckCertificateChange(
+ kDownload, preverify_ok, x509_ctx) ? 1 : 0;
+}
+
+// static
+bool CertificateChecker::CheckCertificateChange(
+ ServerToCheck server_to_check, int preverify_ok,
+ X509_STORE_CTX* x509_ctx) {
+ static const char kUMAActionCertChanged[] =
+ "Updater.ServerCertificateChanged";
+ static const char kUMAActionCertFailed[] = "Updater.ServerCertificateFailed";
+ TEST_AND_RETURN_FALSE(system_state_ != nullptr);
+ TEST_AND_RETURN_FALSE(system_state_->prefs() != nullptr);
+ TEST_AND_RETURN_FALSE(server_to_check != kNone);
+
+ // If pre-verification failed, we are not interested in the current
+ // certificate. We store a report to UMA and just propagate the fail result.
+ if (!preverify_ok) {
+ LOG_IF(WARNING, !system_state_->prefs()->SetString(
+ kReportToSendKey[server_to_check], kUMAActionCertFailed))
+ << "Failed to store UMA report on a failure to validate "
+ << "certificate from update server.";
+ return false;
+ }
+
+ int depth;
+ unsigned int digest_length;
+ uint8_t digest[EVP_MAX_MD_SIZE];
+
+ if (!openssl_wrapper_->GetCertificateDigest(x509_ctx,
+ &depth,
+ &digest_length,
+ digest)) {
+ LOG(WARNING) << "Failed to generate digest of X509 certificate "
+ << "from update server.";
+ return true;
+ }
+
+ // We convert the raw bytes of the digest to an hex string, for storage in
+ // prefs.
+ string digest_string = base::HexEncode(digest, digest_length);
+
+ string storage_key = base::StringPrintf("%s-%d-%d",
+ kPrefsUpdateServerCertificate,
+ server_to_check,
+ depth);
+ string stored_digest;
+ // If there's no stored certificate, we just store the current one and return.
+ if (!system_state_->prefs()->GetString(storage_key, &stored_digest)) {
+ LOG_IF(WARNING, !system_state_->prefs()->SetString(storage_key,
+ digest_string))
+ << "Failed to store server certificate on storage key " << storage_key;
+ return true;
+ }
+
+ // Certificate changed, we store a report to UMA and store the most recent
+ // certificate.
+ if (stored_digest != digest_string) {
+ LOG_IF(WARNING, !system_state_->prefs()->SetString(
+ kReportToSendKey[server_to_check], kUMAActionCertChanged))
+ << "Failed to store UMA report on a change on the "
+ << "certificate from update server.";
+ LOG_IF(WARNING, !system_state_->prefs()->SetString(storage_key,
+ digest_string))
+ << "Failed to store server certificate on storage key " << storage_key;
+ }
+
+ // Since we don't perform actual SSL verification, we return success.
+ return true;
+}
+
+// static
+void CertificateChecker::FlushReport() {
+ // This check shouldn't be needed, but it is useful for testing.
+ TEST_AND_RETURN(system_state_);
+ TEST_AND_RETURN(system_state_->metrics_lib());
+ TEST_AND_RETURN(system_state_->prefs());
+
+ // We flush reports for both servers.
+ for (size_t i = 0; i < arraysize(kReportToSendKey); i++) {
+ string report_to_send;
+ if (system_state_->prefs()->GetString(kReportToSendKey[i], &report_to_send)
+ && !report_to_send.empty()) {
+ // There is a report to be sent. We send it and erase it.
+ LOG(INFO) << "Found report #" << i << ". Sending it";
+ LOG_IF(WARNING, !system_state_->metrics_lib()->SendUserActionToUMA(
+ report_to_send))
+ << "Failed to send server certificate report to UMA: "
+ << report_to_send;
+ LOG_IF(WARNING, !system_state_->prefs()->Delete(kReportToSendKey[i]))
+ << "Failed to erase server certificate report to be sent to UMA";
+ }
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/certificate_checker.h b/certificate_checker.h
new file mode 100644
index 0000000..81e7dde
--- /dev/null
+++ b/certificate_checker.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_CERTIFICATE_CHECKER_H_
+#define UPDATE_ENGINE_CERTIFICATE_CHECKER_H_
+
+#include <curl/curl.h>
+#include <openssl/ssl.h>
+
+#include <string>
+
+#include <base/macros.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/system_state.h"
+
+namespace chromeos_update_engine {
+
+// Wrapper for openssl operations with the certificates.
+class OpenSSLWrapper {
+ public:
+ OpenSSLWrapper() {}
+ virtual ~OpenSSLWrapper() {}
+
+ // Takes an openssl X509_STORE_CTX, extracts the corresponding certificate
+ // from it and calculates its fingerprint (SHA256 digest). Returns true on
+ // success and false otherwise.
+ //
+ // |x509_ctx| is the pointer to the openssl object that holds the certificate.
+ // |out_depth| is the depth of the current certificate, in the certificate
+ // chain.
+ // |out_digest_length| is the length of the generated digest.
+ // |out_digest| is the byte array where the digest itself will be written.
+ // It should be big enough to hold a SHA1 digest (e.g. EVP_MAX_MD_SIZE).
+ virtual bool GetCertificateDigest(X509_STORE_CTX* x509_ctx,
+ int* out_depth,
+ unsigned int* out_digest_length,
+ uint8_t* out_digest) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(OpenSSLWrapper);
+};
+
+// Responsible for checking whether update server certificates change, and
+// reporting to UMA when this happens. Since all state information is persisted,
+// and openssl forces us to use a static callback with no data pointer, this
+// class is entirely static.
+class CertificateChecker {
+ public:
+ // These values are used to generate the keys of files persisted via prefs.
+ // This means that changing these will cause loss of information on metrics
+ // reporting, during the transition.
+ enum ServerToCheck {
+ kUpdate = 0,
+ kDownload = 1,
+ kNone = 2 // This needs to be the last element. Changing its value is ok.
+ };
+
+ CertificateChecker() {}
+ virtual ~CertificateChecker() {}
+
+ // This callback is called by libcurl just before the initialization of an
+ // SSL connection after having processed all other SSL related options. Used
+ // to check if server certificates change. |ptr| is expected to be a
+ // pointer to a ServerToCheck.
+ static CURLcode ProcessSSLContext(CURL* curl_handle, SSL_CTX* ssl_ctx,
+ void* ptr);
+
+ // Flushes to UMA any certificate-related report that was persisted.
+ static void FlushReport();
+
+ // Setters.
+ static void set_system_state(SystemState* system_state) {
+ system_state_ = system_state;
+ }
+
+ static void set_openssl_wrapper(OpenSSLWrapper* openssl_wrapper) {
+ openssl_wrapper_ = openssl_wrapper;
+ }
+
+ private:
+ FRIEND_TEST(CertificateCheckerTest, NewCertificate);
+ FRIEND_TEST(CertificateCheckerTest, SameCertificate);
+ FRIEND_TEST(CertificateCheckerTest, ChangedCertificate);
+ FRIEND_TEST(CertificateCheckerTest, FailedCertificate);
+ FRIEND_TEST(CertificateCheckerTest, FlushReport);
+ FRIEND_TEST(CertificateCheckerTest, FlushNothingToReport);
+
+ // These callbacks are called by openssl after initial SSL verification. They
+ // are used to perform any additional security verification on the connection,
+ // but we use them here to get hold of the server certificate, in order to
+ // determine if it has changed since the last connection. Since openssl forces
+ // us to do this statically, we define two different callbacks for the two
+ // different official update servers, and only assign the correspondent one.
+ // The assigned callback is then called once per each certificate on the
+ // server and returns 1 for success and 0 for failure.
+ static int VerifySSLCallbackUpdateCheck(int preverify_ok,
+ X509_STORE_CTX* x509_ctx);
+ static int VerifySSLCallbackDownload(int preverify_ok,
+ X509_STORE_CTX* x509_ctx);
+
+ // Checks if server certificate for |server_to_check|, stored in |x509_ctx|,
+ // has changed since last connection to that same server. This is called by
+ // one of the two callbacks defined above. If certificate fails to check or
+ // changes, a report is generated and persisted, to be later sent by
+ // FlushReport. Returns true on success and false otherwise.
+ static bool CheckCertificateChange(ServerToCheck server_to_check,
+ int preverify_ok,
+ X509_STORE_CTX* x509_ctx);
+
+ // Global system context.
+ static SystemState* system_state_;
+
+ // The wrapper for openssl operations.
+ static OpenSSLWrapper* openssl_wrapper_;
+
+ DISALLOW_COPY_AND_ASSIGN(CertificateChecker);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CERTIFICATE_CHECKER_H_
diff --git a/certificate_checker_unittest.cc b/certificate_checker_unittest.cc
new file mode 100644
index 0000000..5d4d8f0
--- /dev/null
+++ b/certificate_checker_unittest.cc
@@ -0,0 +1,172 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/certificate_checker.h"
+
+#include <string>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <metrics/metrics_library_mock.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_certificate_checker.h"
+#include "update_engine/mock_prefs.h"
+
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::SetArrayArgument;
+using ::testing::_;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class CertificateCheckerTest : public testing::Test {
+ public:
+ CertificateCheckerTest() {}
+
+ protected:
+ void SetUp() override {
+ depth_ = 0;
+ length_ = 4;
+ digest_[0] = 0x17;
+ digest_[1] = 0x7D;
+ digest_[2] = 0x07;
+ digest_[3] = 0x5F;
+ digest_hex_ = "177D075F";
+ diff_digest_hex_ = "1234ABCD";
+ cert_key_prefix_ = kPrefsUpdateServerCertificate;
+ server_to_check_ = CertificateChecker::kUpdate;
+ cert_key_ = base::StringPrintf("%s-%d-%d",
+ cert_key_prefix_.c_str(),
+ server_to_check_,
+ depth_);
+ kCertChanged = "Updater.ServerCertificateChanged";
+ kCertFailed = "Updater.ServerCertificateFailed";
+ CertificateChecker::set_system_state(&fake_system_state_);
+ CertificateChecker::set_openssl_wrapper(&openssl_wrapper_);
+ prefs_ = fake_system_state_.mock_prefs();
+ }
+
+ void TearDown() override {}
+
+ FakeSystemState fake_system_state_;
+ MockPrefs* prefs_; // shortcut to fake_system_state_.mock_prefs()
+ MockOpenSSLWrapper openssl_wrapper_;
+ // Parameters of our mock certificate digest.
+ int depth_;
+ unsigned int length_;
+ uint8_t digest_[4];
+ string digest_hex_;
+ string diff_digest_hex_;
+ string cert_key_prefix_;
+ CertificateChecker::ServerToCheck server_to_check_;
+ string cert_key_;
+ string kCertChanged;
+ string kCertFailed;
+};
+
+// check certificate change, new
+TEST_F(CertificateCheckerTest, NewCertificate) {
+ EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(depth_),
+ SetArgumentPointee<2>(length_),
+ SetArrayArgument<3>(digest_, digest_ + 4),
+ Return(true)));
+ EXPECT_CALL(*prefs_, GetString(cert_key_, _))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*prefs_, SetString(cert_key_, digest_hex_))
+ .WillOnce(Return(true));
+ ASSERT_TRUE(CertificateChecker::CheckCertificateChange(
+ server_to_check_, 1, nullptr));
+}
+
+// check certificate change, unchanged
+TEST_F(CertificateCheckerTest, SameCertificate) {
+ EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(depth_),
+ SetArgumentPointee<2>(length_),
+ SetArrayArgument<3>(digest_, digest_ + 4),
+ Return(true)));
+ EXPECT_CALL(*prefs_, GetString(cert_key_, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(digest_hex_),
+ Return(true)));
+ EXPECT_CALL(*prefs_, SetString(_, _)).Times(0);
+ ASSERT_TRUE(CertificateChecker::CheckCertificateChange(
+ server_to_check_, 1, nullptr));
+}
+
+// check certificate change, changed
+TEST_F(CertificateCheckerTest, ChangedCertificate) {
+ EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(depth_),
+ SetArgumentPointee<2>(length_),
+ SetArrayArgument<3>(digest_, digest_ + 4),
+ Return(true)));
+ EXPECT_CALL(*prefs_, GetString(cert_key_, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(diff_digest_hex_),
+ Return(true)));
+ EXPECT_CALL(*prefs_, SetString(kPrefsCertificateReportToSendUpdate,
+ kCertChanged))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*prefs_, SetString(cert_key_, digest_hex_))
+ .WillOnce(Return(true));
+ ASSERT_TRUE(CertificateChecker::CheckCertificateChange(
+ server_to_check_, 1, nullptr));
+}
+
+// check certificate change, failed
+TEST_F(CertificateCheckerTest, FailedCertificate) {
+ EXPECT_CALL(*prefs_, SetString(kPrefsCertificateReportToSendUpdate,
+ kCertFailed))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*prefs_, GetString(_, _)).Times(0);
+ EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(_, _, _, _)).Times(0);
+ ASSERT_FALSE(CertificateChecker::CheckCertificateChange(
+ server_to_check_, 0, nullptr));
+}
+
+// flush send report
+TEST_F(CertificateCheckerTest, FlushReport) {
+ EXPECT_CALL(*prefs_, GetString(kPrefsCertificateReportToSendUpdate, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(kCertChanged),
+ Return(true)));
+ EXPECT_CALL(*prefs_, GetString(kPrefsCertificateReportToSendDownload, _))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+ SendUserActionToUMA(kCertChanged))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*prefs_, Delete(kPrefsCertificateReportToSendUpdate))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*prefs_, SetString(kPrefsCertificateReportToSendDownload, _))
+ .Times(0);
+ CertificateChecker::FlushReport();
+}
+
+// flush nothing to report
+TEST_F(CertificateCheckerTest, FlushNothingToReport) {
+ string empty = "";
+ EXPECT_CALL(*prefs_, GetString(kPrefsCertificateReportToSendUpdate, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(empty),
+ Return(true)));
+ EXPECT_CALL(*prefs_, GetString(kPrefsCertificateReportToSendDownload, _))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+ SendUserActionToUMA(_)).Times(0);
+ EXPECT_CALL(*prefs_, SetString(_, _)).Times(0);
+ CertificateChecker::FlushReport();
+}
+
+} // namespace chromeos_update_engine
diff --git a/chrome_browser_proxy_resolver.cc b/chrome_browser_proxy_resolver.cc
new file mode 100644
index 0000000..116c67a
--- /dev/null
+++ b/chrome_browser_proxy_resolver.cc
@@ -0,0 +1,264 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/chrome_browser_proxy_resolver.h"
+
+#include <deque>
+#include <map>
+#include <string>
+#include <utility>
+
+#include <base/bind.h>
+#include <base/strings/string_tokenizer.h>
+#include <base/strings/string_util.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <dbus/dbus-glib.h>
+
+#include "update_engine/dbus_constants.h"
+#include "update_engine/glib_utils.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+using base::StringTokenizer;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using std::deque;
+using std::make_pair;
+using std::pair;
+using std::string;
+
+#define LIB_CROS_PROXY_RESOLVE_NAME "ProxyResolved"
+#define LIB_CROS_PROXY_RESOLVE_SIGNAL_INTERFACE \
+ "org.chromium.UpdateEngineLibcrosProxyResolvedInterface"
+const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
+const char kLibCrosServicePath[] = "/org/chromium/LibCrosService";
+const char kLibCrosServiceInterface[] = "org.chromium.LibCrosServiceInterface";
+const char kLibCrosServiceResolveNetworkProxyMethodName[] =
+ "ResolveNetworkProxy";
+const char kLibCrosProxyResolveName[] = LIB_CROS_PROXY_RESOLVE_NAME;
+const char kLibCrosProxyResolveSignalInterface[] =
+ LIB_CROS_PROXY_RESOLVE_SIGNAL_INTERFACE;
+const char kLibCrosProxyResolveSignalFilter[] = "type='signal', "
+ "interface='" LIB_CROS_PROXY_RESOLVE_SIGNAL_INTERFACE "', "
+ "member='" LIB_CROS_PROXY_RESOLVE_NAME "'";
+#undef LIB_CROS_PROXY_RESOLVE_SIGNAL_INTERFACE
+#undef LIB_CROS_PROXY_RESOLVE_NAME
+
+namespace {
+
+const int kTimeout = 5; // seconds
+
+} // namespace
+
+ChromeBrowserProxyResolver::ChromeBrowserProxyResolver(
+ DBusWrapperInterface* dbus)
+ : dbus_(dbus), timeout_(kTimeout) {}
+
+bool ChromeBrowserProxyResolver::Init() {
+ if (proxy_)
+ return true; // Already initialized.
+
+ // Set up signal handler. Code lifted from libcros.
+ GError* g_error = nullptr;
+ DBusGConnection* bus = dbus_->BusGet(DBUS_BUS_SYSTEM, &g_error);
+ TEST_AND_RETURN_FALSE(bus);
+ DBusConnection* connection = dbus_->ConnectionGetConnection(bus);
+ TEST_AND_RETURN_FALSE(connection);
+
+ DBusError dbus_error;
+ dbus_error_init(&dbus_error);
+ dbus_->DBusBusAddMatch(connection, kLibCrosProxyResolveSignalFilter,
+ &dbus_error);
+ TEST_AND_RETURN_FALSE(!dbus_error_is_set(&dbus_error));
+ TEST_AND_RETURN_FALSE(dbus_->DBusConnectionAddFilter(
+ connection,
+ &ChromeBrowserProxyResolver::StaticFilterMessage,
+ this,
+ nullptr));
+
+ proxy_ = dbus_->ProxyNewForName(bus, kLibCrosServiceName, kLibCrosServicePath,
+ kLibCrosServiceInterface);
+ if (!proxy_) {
+ dbus_->DBusConnectionRemoveFilter(
+ connection,
+ &ChromeBrowserProxyResolver::StaticFilterMessage,
+ this);
+ }
+ TEST_AND_RETURN_FALSE(proxy_); // For the error log
+ return true;
+}
+
+ChromeBrowserProxyResolver::~ChromeBrowserProxyResolver() {
+ // Remove DBus connection filters and Kill proxy object.
+ if (proxy_) {
+ GError* gerror = nullptr;
+ DBusGConnection* gbus = dbus_->BusGet(DBUS_BUS_SYSTEM, &gerror);
+ if (gbus) {
+ DBusConnection* connection = dbus_->ConnectionGetConnection(gbus);
+ dbus_->DBusConnectionRemoveFilter(
+ connection,
+ &ChromeBrowserProxyResolver::StaticFilterMessage,
+ this);
+ }
+ dbus_->ProxyUnref(proxy_);
+ }
+
+ // Kill outstanding timers
+ for (auto& timer : timers_) {
+ MessageLoop::current()->CancelTask(timer.second);
+ timer.second = MessageLoop::kTaskIdNull;
+ }
+}
+
+bool ChromeBrowserProxyResolver::GetProxiesForUrl(const string& url,
+ ProxiesResolvedFn callback,
+ void* data) {
+ GError* error = nullptr;
+ guint timeout = timeout_;
+ if (proxy_) {
+ if (!dbus_->ProxyCall_3_0(proxy_,
+ kLibCrosServiceResolveNetworkProxyMethodName,
+ &error,
+ url.c_str(),
+ kLibCrosProxyResolveSignalInterface,
+ kLibCrosProxyResolveName)) {
+ if (error) {
+ LOG(WARNING) << "dbus_g_proxy_call failed, continuing with no proxy: "
+ << utils::GetAndFreeGError(&error);
+ } else {
+ LOG(WARNING) << "dbus_g_proxy_call failed with no error string, "
+ "continuing with no proxy.";
+ }
+ timeout = 0;
+ }
+ } else {
+ LOG(WARNING) << "dbus proxy object missing, continuing with no proxy.";
+ timeout = 0;
+ }
+
+ callbacks_.insert(make_pair(url, make_pair(callback, data)));
+ MessageLoop::TaskId timer = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ChromeBrowserProxyResolver::HandleTimeout,
+ base::Unretained(this),
+ url),
+ TimeDelta::FromSeconds(timeout));
+ timers_.insert(make_pair(url, timer));
+ return true;
+}
+
+DBusHandlerResult ChromeBrowserProxyResolver::FilterMessage(
+ DBusConnection* connection,
+ DBusMessage* message) {
+ // Code lifted from libcros.
+ if (!dbus_->DBusMessageIsSignal(message,
+ kLibCrosProxyResolveSignalInterface,
+ kLibCrosProxyResolveName)) {
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ // Get args
+ char* source_url = nullptr;
+ char* proxy_list = nullptr;
+ char* error = nullptr;
+ DBusError arg_error;
+ dbus_error_init(&arg_error);
+ if (!dbus_->DBusMessageGetArgs_3(message, &arg_error,
+ &source_url,
+ &proxy_list,
+ &error)) {
+ LOG(ERROR) << "Error reading dbus signal.";
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ if (!source_url || !proxy_list) {
+ LOG(ERROR) << "Error getting url, proxy list from dbus signal.";
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ HandleReply(source_url, proxy_list);
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+bool ChromeBrowserProxyResolver::DeleteUrlState(
+ const string& source_url,
+ bool delete_timer,
+ pair<ProxiesResolvedFn, void*>* callback) {
+ {
+ CallbacksMap::iterator it = callbacks_.lower_bound(source_url);
+ TEST_AND_RETURN_FALSE(it != callbacks_.end());
+ TEST_AND_RETURN_FALSE(it->first == source_url);
+ if (callback)
+ *callback = it->second;
+ callbacks_.erase(it);
+ }
+ {
+ TimeoutsMap::iterator it = timers_.lower_bound(source_url);
+ TEST_AND_RETURN_FALSE(it != timers_.end());
+ TEST_AND_RETURN_FALSE(it->first == source_url);
+ if (delete_timer)
+ MessageLoop::current()->CancelTask(it->second);
+ timers_.erase(it);
+ }
+ return true;
+}
+
+void ChromeBrowserProxyResolver::HandleReply(const string& source_url,
+ const string& proxy_list) {
+ pair<ProxiesResolvedFn, void*> callback;
+ TEST_AND_RETURN(DeleteUrlState(source_url, true, &callback));
+ (*callback.first)(ParseProxyString(proxy_list), callback.second);
+}
+
+void ChromeBrowserProxyResolver::HandleTimeout(string source_url) {
+ LOG(INFO) << "Timeout handler called. Seems Chrome isn't responding.";
+ pair<ProxiesResolvedFn, void*> callback;
+ TEST_AND_RETURN(DeleteUrlState(source_url, false, &callback));
+ deque<string> proxies;
+ proxies.push_back(kNoProxy);
+ (*callback.first)(proxies, callback.second);
+}
+
+deque<string> ChromeBrowserProxyResolver::ParseProxyString(
+ const string& input) {
+ deque<string> ret;
+ // Some of this code taken from
+ // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and
+ // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc
+ StringTokenizer entry_tok(input, ";");
+ while (entry_tok.GetNext()) {
+ string token = entry_tok.token();
+ base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
+
+ // Start by finding the first space (if any).
+ string::iterator space;
+ for (space = token.begin(); space != token.end(); ++space) {
+ if (IsAsciiWhitespace(*space)) {
+ break;
+ }
+ }
+
+ string scheme = string(token.begin(), space);
+ base::StringToLowerASCII(&scheme);
+ // Chrome uses "socks" to mean socks4 and "proxy" to mean http.
+ if (scheme == "socks")
+ scheme += "4";
+ else if (scheme == "proxy")
+ scheme = "http";
+ else if (scheme != "https" &&
+ scheme != "socks4" &&
+ scheme != "socks5" &&
+ scheme != "direct")
+ continue; // Invalid proxy scheme
+
+ string host_and_port = string(space, token.end());
+ base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
+ if (scheme != "direct" && host_and_port.empty())
+ continue; // Must supply host/port when non-direct proxy used.
+ ret.push_back(scheme + "://" + host_and_port);
+ }
+ if (ret.empty() || *ret.rbegin() != kNoProxy)
+ ret.push_back(kNoProxy);
+ return ret;
+}
+
+} // namespace chromeos_update_engine
diff --git a/chrome_browser_proxy_resolver.h b/chrome_browser_proxy_resolver.h
new file mode 100644
index 0000000..ab0a92c
--- /dev/null
+++ b/chrome_browser_proxy_resolver.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_
+#define UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_
+
+#include <deque>
+#include <map>
+#include <string>
+#include <utility>
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/proxy_resolver.h"
+
+namespace chromeos_update_engine {
+
+extern const char kLibCrosServiceName[];
+extern const char kLibCrosServicePath[];
+extern const char kLibCrosServiceInterface[];
+extern const char kLibCrosServiceResolveNetworkProxyMethodName[];
+extern const char kLibCrosProxyResolveName[];
+extern const char kLibCrosProxyResolveSignalInterface[];
+extern const char kLibCrosProxyResolveSignalFilter[];
+
+class ChromeBrowserProxyResolver : public ProxyResolver {
+ public:
+ explicit ChromeBrowserProxyResolver(DBusWrapperInterface* dbus);
+ ~ChromeBrowserProxyResolver() override;
+ bool Init();
+
+ bool GetProxiesForUrl(const std::string& url,
+ ProxiesResolvedFn callback,
+ void* data) override;
+ void set_timeout(int seconds) { timeout_ = seconds; }
+
+ // Public for test
+ static DBusHandlerResult StaticFilterMessage(
+ DBusConnection* connection,
+ DBusMessage* message,
+ void* data) {
+ return reinterpret_cast<ChromeBrowserProxyResolver*>(data)->FilterMessage(
+ connection, message);
+ }
+
+ private:
+ FRIEND_TEST(ChromeBrowserProxyResolverTest, ParseTest);
+ FRIEND_TEST(ChromeBrowserProxyResolverTest, SuccessTest);
+ typedef std::multimap<std::string, std::pair<ProxiesResolvedFn, void*>>
+ CallbacksMap;
+ typedef std::multimap<std::string, chromeos::MessageLoop::TaskId> TimeoutsMap;
+
+ // Handle a reply from Chrome:
+ void HandleReply(const std::string& source_url,
+ const std::string& proxy_list);
+ DBusHandlerResult FilterMessage(
+ DBusConnection* connection,
+ DBusMessage* message);
+ // Handle no reply:
+ void HandleTimeout(std::string source_url);
+
+ // Parses a string-encoded list of proxies and returns a deque
+ // of individual proxies. The last one will always be kNoProxy.
+ static std::deque<std::string> ParseProxyString(const std::string& input);
+
+ // Deletes internal state for the first instance of url in the state.
+ // If delete_timer is set, calls CancelTask on the timer id.
+ // Returns the callback in an out parameter. Returns true on success.
+ bool DeleteUrlState(const std::string& url,
+ bool delete_timer,
+ std::pair<ProxiesResolvedFn, void*>* callback);
+
+ // Shutdown the dbus proxy object.
+ void Shutdown();
+
+ DBusWrapperInterface* dbus_;
+ DBusGProxy* proxy_{nullptr};
+ DBusGProxy* peer_proxy_{nullptr};
+ int timeout_;
+ TimeoutsMap timers_;
+ CallbacksMap callbacks_;
+ DISALLOW_COPY_AND_ASSIGN(ChromeBrowserProxyResolver);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_
diff --git a/chrome_browser_proxy_resolver_unittest.cc b/chrome_browser_proxy_resolver_unittest.cc
new file mode 100644
index 0000000..3866358
--- /dev/null
+++ b/chrome_browser_proxy_resolver_unittest.cc
@@ -0,0 +1,212 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/chrome_browser_proxy_resolver.h"
+
+#include <deque>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <base/bind.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+
+#include "update_engine/mock_dbus_wrapper.h"
+
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::StrEq;
+using ::testing::_;
+using chromeos::MessageLoop;
+using std::deque;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class ChromeBrowserProxyResolverTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ private:
+ chromeos::FakeMessageLoop loop_{nullptr};
+};
+
+TEST_F(ChromeBrowserProxyResolverTest, ParseTest) {
+ // Test ideas from
+ // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list_unittest.cc
+ const char* inputs[] = {
+ "PROXY foopy:10",
+ " DIRECT", // leading space.
+ "PROXY foopy1 ; proxy foopy2;\t DIRECT",
+ "proxy foopy1 ; SOCKS foopy2",
+ "DIRECT ; proxy foopy1 ; DIRECT ; SOCKS5 foopy2;DIRECT ",
+ "DIRECT ; proxy foopy1:80; DIRECT ; DIRECT",
+ "PROXY-foopy:10",
+ "PROXY",
+ "PROXY foopy1 ; JUNK ; JUNK ; SOCKS5 foopy2 ; ;",
+ "HTTP foopy1; SOCKS5 foopy2"
+ };
+ deque<string> outputs[arraysize(inputs)];
+ outputs[0].push_back("http://foopy:10");
+ outputs[0].push_back(kNoProxy);
+ outputs[1].push_back(kNoProxy);
+ outputs[2].push_back("http://foopy1");
+ outputs[2].push_back("http://foopy2");
+ outputs[2].push_back(kNoProxy);
+ outputs[3].push_back("http://foopy1");
+ outputs[3].push_back("socks4://foopy2");
+ outputs[3].push_back(kNoProxy);
+ outputs[4].push_back(kNoProxy);
+ outputs[4].push_back("http://foopy1");
+ outputs[4].push_back(kNoProxy);
+ outputs[4].push_back("socks5://foopy2");
+ outputs[4].push_back(kNoProxy);
+ outputs[5].push_back(kNoProxy);
+ outputs[5].push_back("http://foopy1:80");
+ outputs[5].push_back(kNoProxy);
+ outputs[5].push_back(kNoProxy);
+ outputs[6].push_back(kNoProxy);
+ outputs[7].push_back(kNoProxy);
+ outputs[8].push_back("http://foopy1");
+ outputs[8].push_back("socks5://foopy2");
+ outputs[8].push_back(kNoProxy);
+ outputs[9].push_back("socks5://foopy2");
+ outputs[9].push_back(kNoProxy);
+
+ for (size_t i = 0; i < arraysize(inputs); i++) {
+ deque<string> results =
+ ChromeBrowserProxyResolver::ParseProxyString(inputs[i]);
+ deque<string>& expected = outputs[i];
+ EXPECT_EQ(results.size(), expected.size()) << "i = " << i;
+ if (expected.size() != results.size())
+ continue;
+ for (size_t j = 0; j < expected.size(); j++) {
+ EXPECT_EQ(expected[j], results[j]) << "i = " << i;
+ }
+ }
+}
+
+namespace {
+void DBusWrapperTestResolved(const deque<string>& proxies,
+ void* /* pirv_data */) {
+ EXPECT_EQ(2, proxies.size());
+ EXPECT_EQ("socks5://192.168.52.83:5555", proxies[0]);
+ EXPECT_EQ(kNoProxy, proxies[1]);
+ MessageLoop::current()->BreakLoop();
+}
+void DBusWrapperTestResolvedNoReply(const deque<string>& proxies,
+ void* /* pirv_data */) {
+ EXPECT_EQ(1, proxies.size());
+ EXPECT_EQ(kNoProxy, proxies[0]);
+ MessageLoop::current()->BreakLoop();
+}
+
+void SendReply(DBusConnection* connection,
+ DBusMessage* message,
+ ChromeBrowserProxyResolver* resolver) {
+ LOG(INFO) << "Calling SendReply";
+ ChromeBrowserProxyResolver::StaticFilterMessage(connection,
+ message,
+ resolver);
+}
+
+// chrome_replies should be set to whether or not we fake a reply from
+// chrome. If there's no reply, the resolver should time out.
+// If chrome_alive is false, assume that sending to chrome fails.
+void RunTest(bool chrome_replies, bool chrome_alive) {
+ intptr_t number = 1;
+ DBusGConnection* kMockSystemGBus =
+ reinterpret_cast<DBusGConnection*>(number++);
+ DBusConnection* kMockSystemBus =
+ reinterpret_cast<DBusConnection*>(number++);
+ DBusGProxy* kMockDbusProxy =
+ reinterpret_cast<DBusGProxy*>(number++);
+ DBusMessage* kMockDbusMessage =
+ reinterpret_cast<DBusMessage*>(number++);
+
+ char kUrl[] = "http://example.com/blah";
+ char kProxyConfig[] = "SOCKS5 192.168.52.83:5555;DIRECT";
+
+ testing::StrictMock<MockDBusWrapper> dbus_iface;
+
+ EXPECT_CALL(dbus_iface, BusGet(_, _))
+ .Times(2)
+ .WillRepeatedly(Return(kMockSystemGBus));
+ EXPECT_CALL(dbus_iface,
+ ConnectionGetConnection(kMockSystemGBus))
+ .Times(2)
+ .WillRepeatedly(Return(kMockSystemBus));
+ EXPECT_CALL(dbus_iface, DBusBusAddMatch(kMockSystemBus, _, _));
+ EXPECT_CALL(dbus_iface,
+ DBusConnectionAddFilter(kMockSystemBus, _, _, _))
+ .WillOnce(Return(1));
+ EXPECT_CALL(dbus_iface,
+ ProxyNewForName(kMockSystemGBus,
+ StrEq(kLibCrosServiceName),
+ StrEq(kLibCrosServicePath),
+ StrEq(kLibCrosServiceInterface)))
+ .WillOnce(Return(kMockDbusProxy));
+ EXPECT_CALL(dbus_iface, ProxyUnref(kMockDbusProxy));
+
+ EXPECT_CALL(dbus_iface, ProxyCall_3_0(
+ kMockDbusProxy,
+ StrEq(kLibCrosServiceResolveNetworkProxyMethodName),
+ _,
+ StrEq(kUrl),
+ StrEq(kLibCrosProxyResolveSignalInterface),
+ StrEq(kLibCrosProxyResolveName)))
+ .WillOnce(Return(chrome_alive ? TRUE : FALSE));
+
+ EXPECT_CALL(dbus_iface,
+ DBusConnectionRemoveFilter(kMockSystemBus, _, _));
+
+ if (chrome_replies) {
+ EXPECT_CALL(dbus_iface,
+ DBusMessageIsSignal(kMockDbusMessage,
+ kLibCrosProxyResolveSignalInterface,
+ kLibCrosProxyResolveName))
+ .WillOnce(Return(1));
+ EXPECT_CALL(dbus_iface,
+ DBusMessageGetArgs_3(kMockDbusMessage, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(static_cast<char*>(kUrl)),
+ SetArgPointee<3>(static_cast<char*>(kProxyConfig)),
+ Return(TRUE)));
+ }
+
+ ChromeBrowserProxyResolver resolver(&dbus_iface);
+ EXPECT_EQ(true, resolver.Init());
+ resolver.set_timeout(1);
+ ProxiesResolvedFn get_proxies_response = &DBusWrapperTestResolvedNoReply;
+
+ if (chrome_replies) {
+ get_proxies_response = &DBusWrapperTestResolved;
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SendReply, kMockSystemBus, kMockDbusMessage, &resolver));
+ }
+
+ EXPECT_TRUE(resolver.GetProxiesForUrl(kUrl, get_proxies_response, nullptr));
+ MessageLoop::current()->Run();
+}
+} // namespace
+
+TEST_F(ChromeBrowserProxyResolverTest, SuccessTest) {
+ RunTest(true, true);
+}
+
+TEST_F(ChromeBrowserProxyResolverTest, NoReplyTest) {
+ RunTest(false, true);
+}
+
+TEST_F(ChromeBrowserProxyResolverTest, NoChromeTest) {
+ RunTest(false, false);
+}
+
+} // namespace chromeos_update_engine
diff --git a/clock.cc b/clock.cc
new file mode 100644
index 0000000..ac8945b
--- /dev/null
+++ b/clock.cc
@@ -0,0 +1,46 @@
+// 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.
+
+#include "update_engine/clock.h"
+
+#include <time.h>
+
+namespace chromeos_update_engine {
+
+base::Time Clock::GetWallclockTime() {
+ return base::Time::Now();
+}
+
+base::Time Clock::GetMonotonicTime() {
+ struct timespec now_ts;
+ if (clock_gettime(CLOCK_MONOTONIC_RAW, &now_ts) != 0) {
+ // Avoid logging this as an error as call-sites may call this very
+ // often and we don't want to fill up the disk. Note that this
+ // only fails if running on ancient kernels (CLOCK_MONOTONIC_RAW
+ // was added in Linux 2.6.28) so it never fails on a ChromeOS
+ // device.
+ return base::Time();
+ }
+ struct timeval now_tv;
+ now_tv.tv_sec = now_ts.tv_sec;
+ now_tv.tv_usec = now_ts.tv_nsec/base::Time::kNanosecondsPerMicrosecond;
+ return base::Time::FromTimeVal(now_tv);
+}
+
+base::Time Clock::GetBootTime() {
+ struct timespec now_ts;
+ if (clock_gettime(CLOCK_BOOTTIME, &now_ts) != 0) {
+ // Avoid logging this as an error as call-sites may call this very
+ // often and we don't want to fill up the disk. Note that this
+ // only fails if running on ancient kernels (CLOCK_BOOTTIME was
+ // added in Linux 2.6.39) so it never fails on a ChromeOS device.
+ return base::Time();
+ }
+ struct timeval now_tv;
+ now_tv.tv_sec = now_ts.tv_sec;
+ now_tv.tv_usec = now_ts.tv_nsec/base::Time::kNanosecondsPerMicrosecond;
+ return base::Time::FromTimeVal(now_tv);
+}
+
+} // namespace chromeos_update_engine
diff --git a/clock.h b/clock.h
new file mode 100644
index 0000000..d53ad34
--- /dev/null
+++ b/clock.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_CLOCK_H_
+#define UPDATE_ENGINE_CLOCK_H_
+
+#include "update_engine/clock_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a clock.
+class Clock : public ClockInterface {
+ public:
+ Clock() {}
+
+ base::Time GetWallclockTime() override;
+
+ base::Time GetMonotonicTime() override;
+
+ base::Time GetBootTime() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Clock);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CLOCK_H_
diff --git a/clock_interface.h b/clock_interface.h
new file mode 100644
index 0000000..8ff122c
--- /dev/null
+++ b/clock_interface.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_CLOCK_INTERFACE_H_
+#define UPDATE_ENGINE_CLOCK_INTERFACE_H_
+
+#include <string>
+
+#include <base/time/time.h>
+
+namespace chromeos_update_engine {
+
+// The clock interface allows access to various system clocks. The
+// sole reason for providing this as an interface is unit testing.
+// Additionally, the sole reason for the methods not being static
+// is also unit testing.
+class ClockInterface {
+ public:
+ virtual ~ClockInterface() = default;
+
+ // Gets the current time e.g. similar to base::Time::Now().
+ virtual base::Time GetWallclockTime() = 0;
+
+ // Returns monotonic time since some unspecified starting point. It
+ // is not increased when the system is sleeping nor is it affected
+ // by NTP or the user changing the time.
+ //
+ // (This is a simple wrapper around clock_gettime(2) / CLOCK_MONOTONIC_RAW.)
+ virtual base::Time GetMonotonicTime() = 0;
+
+ // Returns monotonic time since some unspecified starting point. It
+ // is increased when the system is sleeping but it's not affected
+ // by NTP or the user changing the time.
+ //
+ // (This is a simple wrapper around clock_gettime(2) / CLOCK_BOOTTIME.)
+ virtual base::Time GetBootTime() = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CLOCK_INTERFACE_H_
diff --git a/connection_manager.cc b/connection_manager.cc
new file mode 100644
index 0000000..f6c0217
--- /dev/null
+++ b/connection_manager.cc
@@ -0,0 +1,298 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/connection_manager.h"
+
+#include <set>
+#include <string>
+
+#include <base/stl_util.h>
+#include <base/strings/string_util.h>
+#include <chromeos/dbus/service_constants.h>
+#include <dbus/dbus-glib.h>
+#include <glib.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/prefs.h"
+#include "update_engine/system_state.h"
+#include "update_engine/utils.h"
+
+using std::set;
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Gets the DbusGProxy for FlimFlam. Must be free'd with ProxyUnref()
+bool GetFlimFlamProxy(DBusWrapperInterface* dbus_iface,
+ const char* path,
+ const char* interface,
+ DBusGProxy** out_proxy) {
+ DBusGConnection* bus;
+ DBusGProxy* proxy;
+ GError* error = nullptr;
+
+ bus = dbus_iface->BusGet(DBUS_BUS_SYSTEM, &error);
+ if (!bus) {
+ LOG(ERROR) << "Failed to get system bus";
+ return false;
+ }
+ proxy = dbus_iface->ProxyNewForName(bus, shill::kFlimflamServiceName, path,
+ interface);
+ *out_proxy = proxy;
+ return true;
+}
+
+// On success, caller owns the GHashTable at out_hash_table.
+// Returns true on success.
+bool GetProperties(DBusWrapperInterface* dbus_iface,
+ const char* path,
+ const char* interface,
+ GHashTable** out_hash_table) {
+ DBusGProxy* proxy;
+ GError* error = nullptr;
+
+ TEST_AND_RETURN_FALSE(GetFlimFlamProxy(dbus_iface,
+ path,
+ interface,
+ &proxy));
+
+ gboolean rc = dbus_iface->ProxyCall_0_1(proxy,
+ "GetProperties",
+ &error,
+ out_hash_table);
+ dbus_iface->ProxyUnref(proxy);
+ if (rc == FALSE) {
+ LOG(ERROR) << "dbus_g_proxy_call failed";
+ return false;
+ }
+
+ return true;
+}
+
+// Returns (via out_path) the default network path, or empty string if
+// there's no network up.
+// Returns true on success.
+bool GetDefaultServicePath(DBusWrapperInterface* dbus_iface, string* out_path) {
+ GHashTable* hash_table = nullptr;
+
+ TEST_AND_RETURN_FALSE(GetProperties(dbus_iface,
+ shill::kFlimflamServicePath,
+ shill::kFlimflamManagerInterface,
+ &hash_table));
+
+ GValue* value = reinterpret_cast<GValue*>(g_hash_table_lookup(hash_table,
+ "Services"));
+ GPtrArray* array = nullptr;
+ bool success = false;
+ if (G_VALUE_HOLDS(value, DBUS_TYPE_G_OBJECT_PATH_ARRAY) &&
+ (array = reinterpret_cast<GPtrArray*>(g_value_get_boxed(value))) &&
+ (array->len > 0)) {
+ *out_path = static_cast<const char*>(g_ptr_array_index(array, 0));
+ success = true;
+ }
+
+ g_hash_table_unref(hash_table);
+ return success;
+}
+
+NetworkConnectionType ParseConnectionType(const char* type_str) {
+ if (!strcmp(type_str, shill::kTypeEthernet)) {
+ return kNetEthernet;
+ } else if (!strcmp(type_str, shill::kTypeWifi)) {
+ return kNetWifi;
+ } else if (!strcmp(type_str, shill::kTypeWimax)) {
+ return kNetWimax;
+ } else if (!strcmp(type_str, shill::kTypeBluetooth)) {
+ return kNetBluetooth;
+ } else if (!strcmp(type_str, shill::kTypeCellular)) {
+ return kNetCellular;
+ }
+ return kNetUnknown;
+}
+
+NetworkTethering ParseTethering(const char* tethering_str) {
+ if (!strcmp(tethering_str, shill::kTetheringNotDetectedState)) {
+ return NetworkTethering::kNotDetected;
+ } else if (!strcmp(tethering_str, shill::kTetheringSuspectedState)) {
+ return NetworkTethering::kSuspected;
+ } else if (!strcmp(tethering_str, shill::kTetheringConfirmedState)) {
+ return NetworkTethering::kConfirmed;
+ }
+ LOG(WARNING) << "Unknown Tethering value: " << tethering_str;
+ return NetworkTethering::kUnknown;
+}
+
+bool GetServicePathProperties(DBusWrapperInterface* dbus_iface,
+ const string& path,
+ NetworkConnectionType* out_type,
+ NetworkTethering* out_tethering) {
+ GHashTable* hash_table = nullptr;
+
+ TEST_AND_RETURN_FALSE(GetProperties(dbus_iface,
+ path.c_str(),
+ shill::kFlimflamServiceInterface,
+ &hash_table));
+
+ // Populate the out_tethering.
+ GValue* value =
+ reinterpret_cast<GValue*>(g_hash_table_lookup(hash_table,
+ shill::kTetheringProperty));
+ const char* tethering_str = nullptr;
+
+ if (value != nullptr)
+ tethering_str = g_value_get_string(value);
+ if (tethering_str != nullptr) {
+ *out_tethering = ParseTethering(tethering_str);
+ } else {
+ // Set to Unknown if not present.
+ *out_tethering = NetworkTethering::kUnknown;
+ }
+
+ // Populate the out_type property.
+ value = reinterpret_cast<GValue*>(g_hash_table_lookup(hash_table,
+ shill::kTypeProperty));
+ const char* type_str = nullptr;
+ bool success = false;
+ if (value != nullptr && (type_str = g_value_get_string(value)) != nullptr) {
+ success = true;
+ if (!strcmp(type_str, shill::kTypeVPN)) {
+ value = reinterpret_cast<GValue*>(
+ g_hash_table_lookup(hash_table, shill::kPhysicalTechnologyProperty));
+ if (value != nullptr &&
+ (type_str = g_value_get_string(value)) != nullptr) {
+ *out_type = ParseConnectionType(type_str);
+ } else {
+ LOG(ERROR) << "No PhysicalTechnology property found for a VPN"
+ << " connection (service: " << path << "). Returning default"
+ << " kNetUnknown value.";
+ *out_type = kNetUnknown;
+ }
+ } else {
+ *out_type = ParseConnectionType(type_str);
+ }
+ }
+ g_hash_table_unref(hash_table);
+ return success;
+}
+
+} // namespace
+
+ConnectionManager::ConnectionManager(SystemState *system_state)
+ : system_state_(system_state) {}
+
+bool ConnectionManager::IsUpdateAllowedOver(NetworkConnectionType type,
+ NetworkTethering tethering) const {
+ switch (type) {
+ case kNetBluetooth:
+ return false;
+
+ case kNetCellular: {
+ set<string> allowed_types;
+ const policy::DevicePolicy* device_policy =
+ system_state_->device_policy();
+
+ // A device_policy is loaded in a lazy way right before an update check,
+ // so the device_policy should be already loaded at this point. If it's
+ // not, return a safe value for this setting.
+ if (!device_policy) {
+ LOG(INFO) << "Disabling updates over cellular networks as there's no "
+ "device policy loaded yet.";
+ return false;
+ }
+
+ if (device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+ // The update setting is enforced by the device policy.
+
+ if (!ContainsKey(allowed_types, shill::kTypeCellular)) {
+ LOG(INFO) << "Disabling updates over cellular connection as it's not "
+ "allowed in the device policy.";
+ return false;
+ }
+
+ LOG(INFO) << "Allowing updates over cellular per device policy.";
+ return true;
+ } else {
+ // There's no update setting in the device policy, using the local user
+ // setting.
+ PrefsInterface* prefs = system_state_->prefs();
+
+ if (!prefs || !prefs->Exists(kPrefsUpdateOverCellularPermission)) {
+ LOG(INFO) << "Disabling updates over cellular connection as there's "
+ "no device policy setting nor user preference present.";
+ return false;
+ }
+
+ bool stored_value;
+ if (!prefs->GetBoolean(kPrefsUpdateOverCellularPermission,
+ &stored_value)) {
+ return false;
+ }
+
+ if (!stored_value) {
+ LOG(INFO) << "Disabling updates over cellular connection per user "
+ "setting.";
+ return false;
+ }
+ LOG(INFO) << "Allowing updates over cellular per user setting.";
+ return true;
+ }
+ }
+
+ default:
+ if (tethering == NetworkTethering::kConfirmed) {
+ // Treat this connection as if it is a cellular connection.
+ LOG(INFO) << "Current connection is confirmed tethered, using Cellular "
+ "setting.";
+ return IsUpdateAllowedOver(kNetCellular, NetworkTethering::kUnknown);
+ }
+ return true;
+ }
+}
+
+const char* ConnectionManager::StringForConnectionType(
+ NetworkConnectionType type) const {
+ static const char* const kValues[] = {shill::kTypeEthernet,
+ shill::kTypeWifi,
+ shill::kTypeWimax,
+ shill::kTypeBluetooth,
+ shill::kTypeCellular};
+ if (type < 0 || type >= static_cast<int>(arraysize(kValues))) {
+ return "Unknown";
+ }
+ return kValues[type];
+}
+
+const char* ConnectionManager::StringForTethering(
+ NetworkTethering tethering) const {
+ switch (tethering) {
+ case NetworkTethering::kNotDetected:
+ return shill::kTetheringNotDetectedState;
+ case NetworkTethering::kSuspected:
+ return shill::kTetheringSuspectedState;
+ case NetworkTethering::kConfirmed:
+ return shill::kTetheringConfirmedState;
+ case NetworkTethering::kUnknown:
+ return "Unknown";
+ }
+ // The program shouldn't reach this point, but the compiler isn't smart
+ // enough to infer that.
+ return "Unknown";
+}
+
+bool ConnectionManager::GetConnectionProperties(
+ DBusWrapperInterface* dbus_iface,
+ NetworkConnectionType* out_type,
+ NetworkTethering* out_tethering) const {
+ string default_service_path;
+ TEST_AND_RETURN_FALSE(GetDefaultServicePath(dbus_iface,
+ &default_service_path));
+ TEST_AND_RETURN_FALSE(GetServicePathProperties(dbus_iface,
+ default_service_path,
+ out_type, out_tethering));
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/connection_manager.h b/connection_manager.h
new file mode 100644
index 0000000..f9973a3
--- /dev/null
+++ b/connection_manager.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_CONNECTION_MANAGER_H_
+#define UPDATE_ENGINE_CONNECTION_MANAGER_H_
+
+#include <base/macros.h>
+
+#include "update_engine/dbus_wrapper_interface.h"
+
+namespace chromeos_update_engine {
+
+enum NetworkConnectionType {
+ kNetEthernet = 0,
+ kNetWifi,
+ kNetWimax,
+ kNetBluetooth,
+ kNetCellular,
+ kNetUnknown
+};
+
+enum class NetworkTethering {
+ kNotDetected = 0,
+ kSuspected,
+ kConfirmed,
+ kUnknown
+};
+
+class SystemState;
+
+// This class exposes a generic interface to the connection manager
+// (e.g FlimFlam, Shill, etc.) to consolidate all connection-related
+// logic in update_engine.
+class ConnectionManager {
+ public:
+ // Constructs a new ConnectionManager object initialized with the
+ // given system state.
+ explicit ConnectionManager(SystemState* system_state);
+ virtual ~ConnectionManager() = default;
+
+ // Populates |out_type| with the type of the network connection
+ // that we are currently connected and |out_tethering| with the estimate of
+ // whether that network is being tethered. The dbus_iface is used to query
+ // the real connection manager (e.g shill).
+ virtual bool GetConnectionProperties(DBusWrapperInterface* dbus_iface,
+ NetworkConnectionType* out_type,
+ NetworkTethering* out_tethering) const;
+
+ // Returns true if we're allowed to update the system when we're
+ // connected to the internet through the given network connection type and the
+ // given tethering state.
+ virtual bool IsUpdateAllowedOver(NetworkConnectionType type,
+ NetworkTethering tethering) const;
+
+ // Returns the string representation corresponding to the given
+ // connection type.
+ virtual const char* StringForConnectionType(NetworkConnectionType type) const;
+
+ // Returns the string representation corresponding to the given tethering
+ // state.
+ virtual const char* StringForTethering(NetworkTethering tethering) const;
+
+ private:
+ // The global context for update_engine
+ SystemState* system_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectionManager);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CONNECTION_MANAGER_H_
diff --git a/connection_manager_unittest.cc b/connection_manager_unittest.cc
new file mode 100644
index 0000000..8ca1a00
--- /dev/null
+++ b/connection_manager_unittest.cc
@@ -0,0 +1,431 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/connection_manager.h"
+
+#include <set>
+#include <string>
+
+#include <base/logging.h>
+#include <chromeos/dbus/service_constants.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_dbus_wrapper.h"
+#include "update_engine/test_utils.h"
+
+using std::set;
+using std::string;
+using testing::A;
+using testing::AnyNumber;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+class ConnectionManagerTest : public ::testing::Test {
+ public:
+ ConnectionManagerTest()
+ : kMockFlimFlamManagerProxy_(nullptr),
+ kMockFlimFlamServiceProxy_(nullptr),
+ kServicePath_(nullptr),
+ cmut_(&fake_system_state_) {
+ fake_system_state_.set_connection_manager(&cmut_);
+ }
+
+ protected:
+ void SetupMocks(const char* service_path);
+ void SetManagerReply(const char* reply_value, const GType& reply_type);
+
+ // Sets the |service_type| Type and the |physical_technology|
+ // PhysicalTechnology properties in the mocked service. If a null
+ // |physical_technology| is passed, the property is not set (not present).
+ void SetServiceReply(const char* service_type,
+ const char* physical_technology,
+ const char* service_tethering);
+ void TestWithServiceType(
+ const char* service_type,
+ const char* physical_technology,
+ NetworkConnectionType expected_type);
+ void TestWithServiceTethering(
+ const char* service_tethering,
+ NetworkTethering expected_tethering);
+
+ static const char* kGetPropertiesMethod;
+ DBusGProxy* kMockFlimFlamManagerProxy_;
+ DBusGProxy* kMockFlimFlamServiceProxy_;
+ DBusGConnection* kMockSystemBus_;
+ const char* kServicePath_;
+ testing::StrictMock<MockDBusWrapper> dbus_iface_;
+ ConnectionManager cmut_; // ConnectionManager under test.
+ FakeSystemState fake_system_state_;
+};
+
+// static
+const char* ConnectionManagerTest::kGetPropertiesMethod = "GetProperties";
+
+void ConnectionManagerTest::SetupMocks(const char* service_path) {
+ int number = 1;
+ kMockSystemBus_ = reinterpret_cast<DBusGConnection*>(number++);
+ kMockFlimFlamManagerProxy_ = reinterpret_cast<DBusGProxy*>(number++);
+ kMockFlimFlamServiceProxy_ = reinterpret_cast<DBusGProxy*>(number++);
+ ASSERT_NE(kMockSystemBus_, static_cast<DBusGConnection*>(nullptr));
+
+ kServicePath_ = service_path;
+
+ ON_CALL(dbus_iface_, BusGet(DBUS_BUS_SYSTEM, _))
+ .WillByDefault(Return(kMockSystemBus_));
+ EXPECT_CALL(dbus_iface_, BusGet(DBUS_BUS_SYSTEM, _))
+ .Times(AnyNumber());
+}
+
+void ConnectionManagerTest::SetManagerReply(const char *reply_value,
+ const GType& reply_type) {
+ ASSERT_TRUE(dbus_g_type_is_collection(reply_type));
+
+ // Create the GPtrArray array holding the |reply_value| pointer. The
+ // |reply_value| string is duplicated because it should be mutable on the
+ // interface and is because dbus-glib collections will g_free() each element
+ // of the GPtrArray automatically when the |array_as_value| GValue is unset.
+ // The g_strdup() is not being leaked.
+ GPtrArray* array = g_ptr_array_new();
+ ASSERT_NE(nullptr, array);
+ g_ptr_array_add(array, g_strdup(reply_value));
+
+ GValue* array_as_value = g_new0(GValue, 1);
+ EXPECT_EQ(array_as_value, g_value_init(array_as_value, reply_type));
+ g_value_take_boxed(array_as_value, array);
+
+ // Initialize return value for D-Bus call to Manager object, which is a
+ // hash table of static strings (char*) in GValue* containing a single array.
+ GHashTable* manager_hash_table = g_hash_table_new_full(
+ g_str_hash, g_str_equal,
+ nullptr, // no key_destroy_func because keys are static.
+ test_utils::GValueFree); // value_destroy_func
+ g_hash_table_insert(manager_hash_table,
+ const_cast<char*>("Services"),
+ array_as_value);
+
+ // Plumb return value into mock object.
+ EXPECT_CALL(dbus_iface_, ProxyCall_0_1(kMockFlimFlamManagerProxy_,
+ StrEq(kGetPropertiesMethod),
+ _, A<GHashTable**>()))
+ .WillOnce(DoAll(SetArgumentPointee<3>(manager_hash_table), Return(TRUE)));
+
+ // Set other expectations.
+ EXPECT_CALL(dbus_iface_,
+ ProxyNewForName(kMockSystemBus_,
+ StrEq(shill::kFlimflamServiceName),
+ StrEq(shill::kFlimflamServicePath),
+ StrEq(shill::kFlimflamManagerInterface)))
+ .WillOnce(Return(kMockFlimFlamManagerProxy_));
+ EXPECT_CALL(dbus_iface_, ProxyUnref(kMockFlimFlamManagerProxy_));
+ EXPECT_CALL(dbus_iface_, BusGet(DBUS_BUS_SYSTEM, _))
+ .RetiresOnSaturation();
+}
+
+void ConnectionManagerTest::SetServiceReply(const char* service_type,
+ const char* physical_technology,
+ const char* service_tethering) {
+ // Initialize return value for D-Bus call to Service object, which is a
+ // hash table of static strings (char*) in GValue*.
+ GHashTable* service_hash_table = g_hash_table_new_full(
+ g_str_hash, g_str_equal,
+ nullptr, // no key_destroy_func because keys are static.
+ test_utils::GValueFree); // value_destroy_func
+ GValue* service_type_value = test_utils::GValueNewString(service_type);
+ g_hash_table_insert(service_hash_table,
+ const_cast<char*>("Type"),
+ service_type_value);
+
+ if (physical_technology) {
+ GValue* physical_technology_value =
+ test_utils::GValueNewString(physical_technology);
+ g_hash_table_insert(service_hash_table,
+ const_cast<char*>("PhysicalTechnology"),
+ physical_technology_value);
+ }
+
+ if (service_tethering) {
+ GValue* service_tethering_value =
+ test_utils::GValueNewString(service_tethering);
+ g_hash_table_insert(service_hash_table,
+ const_cast<char*>("Tethering"),
+ service_tethering_value);
+ }
+
+ // Plumb return value into mock object.
+ EXPECT_CALL(dbus_iface_, ProxyCall_0_1(kMockFlimFlamServiceProxy_,
+ StrEq(kGetPropertiesMethod),
+ _, A<GHashTable**>()))
+ .WillOnce(DoAll(SetArgumentPointee<3>(service_hash_table), Return(TRUE)));
+
+ // Set other expectations.
+ EXPECT_CALL(dbus_iface_,
+ ProxyNewForName(kMockSystemBus_,
+ StrEq(shill::kFlimflamServiceName),
+ StrEq(kServicePath_),
+ StrEq(shill::kFlimflamServiceInterface)))
+ .WillOnce(Return(kMockFlimFlamServiceProxy_));
+ EXPECT_CALL(dbus_iface_, ProxyUnref(kMockFlimFlamServiceProxy_));
+ EXPECT_CALL(dbus_iface_, BusGet(DBUS_BUS_SYSTEM, _))
+ .RetiresOnSaturation();
+}
+
+void ConnectionManagerTest::TestWithServiceType(
+ const char* service_type,
+ const char* physical_technology,
+ NetworkConnectionType expected_type) {
+
+ SetupMocks("/service/guest-network");
+ SetManagerReply(kServicePath_, DBUS_TYPE_G_OBJECT_PATH_ARRAY);
+ SetServiceReply(service_type, physical_technology,
+ shill::kTetheringNotDetectedState);
+
+ NetworkConnectionType type;
+ NetworkTethering tethering;
+ EXPECT_TRUE(cmut_.GetConnectionProperties(&dbus_iface_, &type, &tethering));
+ EXPECT_EQ(expected_type, type);
+ testing::Mock::VerifyAndClearExpectations(&dbus_iface_);
+}
+
+void ConnectionManagerTest::TestWithServiceTethering(
+ const char* service_tethering,
+ NetworkTethering expected_tethering) {
+
+ SetupMocks("/service/guest-network");
+ SetManagerReply(kServicePath_, DBUS_TYPE_G_OBJECT_PATH_ARRAY);
+ SetServiceReply(shill::kTypeWifi, nullptr, service_tethering);
+
+ NetworkConnectionType type;
+ NetworkTethering tethering;
+ EXPECT_TRUE(cmut_.GetConnectionProperties(&dbus_iface_, &type, &tethering));
+ EXPECT_EQ(expected_tethering, tethering);
+}
+
+TEST_F(ConnectionManagerTest, SimpleTest) {
+ TestWithServiceType(shill::kTypeEthernet, nullptr, kNetEthernet);
+ TestWithServiceType(shill::kTypeWifi, nullptr, kNetWifi);
+ TestWithServiceType(shill::kTypeWimax, nullptr, kNetWimax);
+ TestWithServiceType(shill::kTypeBluetooth, nullptr, kNetBluetooth);
+ TestWithServiceType(shill::kTypeCellular, nullptr, kNetCellular);
+}
+
+TEST_F(ConnectionManagerTest, PhysicalTechnologyTest) {
+ TestWithServiceType(shill::kTypeVPN, nullptr, kNetUnknown);
+ TestWithServiceType(shill::kTypeVPN, shill::kTypeVPN, kNetUnknown);
+ TestWithServiceType(shill::kTypeVPN, shill::kTypeWifi, kNetWifi);
+ TestWithServiceType(shill::kTypeVPN, shill::kTypeWimax, kNetWimax);
+}
+
+TEST_F(ConnectionManagerTest, TetheringTest) {
+ TestWithServiceTethering(shill::kTetheringConfirmedState,
+ NetworkTethering::kConfirmed);
+ TestWithServiceTethering(shill::kTetheringNotDetectedState,
+ NetworkTethering::kNotDetected);
+ TestWithServiceTethering(shill::kTetheringSuspectedState,
+ NetworkTethering::kSuspected);
+ TestWithServiceTethering("I'm not a valid property value =)",
+ NetworkTethering::kUnknown);
+}
+
+TEST_F(ConnectionManagerTest, UnknownTest) {
+ TestWithServiceType("foo", nullptr, kNetUnknown);
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverEthernetTest) {
+ // Updates over Ethernet are allowed even if there's no policy.
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetEthernet,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverWifiTest) {
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWifi, NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverWimaxTest) {
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWimax,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOverBluetoothTest) {
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetBluetooth,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOnlyOver3GPerPolicyTest) {
+ policy::MockDevicePolicy allow_3g_policy;
+
+ fake_system_state_.set_device_policy(&allow_3g_policy);
+
+ // This test tests cellular (3G) being the only connection type being allowed.
+ set<string> allowed_set;
+ allowed_set.insert(cmut_.StringForConnectionType(kNetCellular));
+
+ EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(1)
+ .WillOnce(DoAll(SetArgumentPointee<0>(allowed_set), Return(true)));
+
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetCellular,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOver3GAndOtherTypesPerPolicyTest) {
+ policy::MockDevicePolicy allow_3g_policy;
+
+ fake_system_state_.set_device_policy(&allow_3g_policy);
+
+ // This test tests multiple connection types being allowed, with
+ // 3G one among them. Only Cellular is currently enforced by the policy
+ // setting, the others are ignored (see Bluetooth for example).
+ set<string> allowed_set;
+ allowed_set.insert(cmut_.StringForConnectionType(kNetCellular));
+ allowed_set.insert(cmut_.StringForConnectionType(kNetBluetooth));
+
+ EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(3)
+ .WillRepeatedly(DoAll(SetArgumentPointee<0>(allowed_set), Return(true)));
+
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetEthernet,
+ NetworkTethering::kUnknown));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetEthernet,
+ NetworkTethering::kNotDetected));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetCellular,
+ NetworkTethering::kUnknown));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWifi, NetworkTethering::kUnknown));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWimax, NetworkTethering::kUnknown));
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetBluetooth,
+ NetworkTethering::kUnknown));
+
+ // Tethered networks are treated in the same way as Cellular networks and
+ // thus allowed.
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetEthernet,
+ NetworkTethering::kConfirmed));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWifi,
+ NetworkTethering::kConfirmed));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOverCellularByDefaultTest) {
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetCellular,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOverTetheredNetworkByDefaultTest) {
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetWifi,
+ NetworkTethering::kConfirmed));
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetEthernet,
+ NetworkTethering::kConfirmed));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWifi,
+ NetworkTethering::kSuspected));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOver3GPerPolicyTest) {
+ policy::MockDevicePolicy block_3g_policy;
+
+ fake_system_state_.set_device_policy(&block_3g_policy);
+
+ // Test that updates for 3G are blocked while updates are allowed
+ // over several other types.
+ set<string> allowed_set;
+ allowed_set.insert(cmut_.StringForConnectionType(kNetEthernet));
+ allowed_set.insert(cmut_.StringForConnectionType(kNetWifi));
+ allowed_set.insert(cmut_.StringForConnectionType(kNetWimax));
+
+ EXPECT_CALL(block_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(1)
+ .WillOnce(DoAll(SetArgumentPointee<0>(allowed_set), Return(true)));
+
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetCellular,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOver3GIfErrorInPolicyFetchTest) {
+ policy::MockDevicePolicy allow_3g_policy;
+
+ fake_system_state_.set_device_policy(&allow_3g_policy);
+
+ set<string> allowed_set;
+ allowed_set.insert(cmut_.StringForConnectionType(kNetCellular));
+
+ // Return false for GetAllowedConnectionTypesForUpdate and see
+ // that updates are still blocked for 3G despite the value being in
+ // the string set above.
+ EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(1)
+ .WillOnce(DoAll(SetArgumentPointee<0>(allowed_set), Return(false)));
+
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetCellular,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, UseUserPrefForUpdatesOverCellularIfNoPolicyTest) {
+ policy::MockDevicePolicy no_policy;
+ testing::NiceMock<MockPrefs>* prefs = fake_system_state_.mock_prefs();
+
+ fake_system_state_.set_device_policy(&no_policy);
+
+ // No setting enforced by the device policy, user prefs should be used.
+ EXPECT_CALL(no_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(3)
+ .WillRepeatedly(Return(false));
+
+ // No user pref: block.
+ EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+ .Times(1)
+ .WillOnce(Return(false));
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetCellular,
+ NetworkTethering::kUnknown));
+
+ // Allow per user pref.
+ EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+ .Times(1)
+ .WillOnce(Return(true));
+ EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
+ .Times(1)
+ .WillOnce(DoAll(SetArgumentPointee<1>(true), Return(true)));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetCellular,
+ NetworkTethering::kUnknown));
+
+ // Block per user pref.
+ EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+ .Times(1)
+ .WillOnce(Return(true));
+ EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
+ .Times(1)
+ .WillOnce(DoAll(SetArgumentPointee<1>(false), Return(true)));
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetCellular,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, StringForConnectionTypeTest) {
+ EXPECT_STREQ(shill::kTypeEthernet,
+ cmut_.StringForConnectionType(kNetEthernet));
+ EXPECT_STREQ(shill::kTypeWifi,
+ cmut_.StringForConnectionType(kNetWifi));
+ EXPECT_STREQ(shill::kTypeWimax,
+ cmut_.StringForConnectionType(kNetWimax));
+ EXPECT_STREQ(shill::kTypeBluetooth,
+ cmut_.StringForConnectionType(kNetBluetooth));
+ EXPECT_STREQ(shill::kTypeCellular,
+ cmut_.StringForConnectionType(kNetCellular));
+ EXPECT_STREQ("Unknown",
+ cmut_.StringForConnectionType(kNetUnknown));
+ EXPECT_STREQ("Unknown",
+ cmut_.StringForConnectionType(
+ static_cast<NetworkConnectionType>(999999)));
+}
+
+TEST_F(ConnectionManagerTest, MalformedServiceList) {
+ SetupMocks("/service/guest-network");
+ SetManagerReply(kServicePath_, DBUS_TYPE_G_STRING_ARRAY);
+
+ NetworkConnectionType type;
+ NetworkTethering tethering;
+ EXPECT_FALSE(cmut_.GetConnectionProperties(&dbus_iface_, &type, &tethering));
+}
+
+} // namespace chromeos_update_engine
diff --git a/constants.cc b/constants.cc
new file mode 100644
index 0000000..0e052c0
--- /dev/null
+++ b/constants.cc
@@ -0,0 +1,81 @@
+// 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.
+
+#include "update_engine/constants.h"
+
+namespace chromeos_update_engine {
+
+const char kPowerwashMarkerFile[] =
+ "/mnt/stateful_partition/factory_install_reset";
+
+const char kPowerwashCommand[] = "safe fast keepimg reason=update_engine\n";
+
+const char kPowerwashSafePrefsDir[] =
+ "/mnt/stateful_partition/unencrypted/preserve/update_engine/prefs";
+
+const char kPrefsDirectory[] = "/var/lib/update_engine/prefs";
+
+const char kStatefulPartition[] = "/mnt/stateful_partition";
+
+const char kSystemRebootedMarkerFile[] = "/tmp/update_engine_update_recorded";
+
+// Constants defining keys for the persisted state of update engine.
+const char kPrefsAttemptInProgress[] = "attempt-in-progress";
+const char kPrefsBackoffExpiryTime[] = "backoff-expiry-time";
+const char kPrefsCertificateReportToSendDownload[] =
+ "certificate-report-to-send-download";
+const char kPrefsCertificateReportToSendUpdate[] =
+ "certificate-report-to-send-update";
+const char kPrefsCurrentBytesDownloaded[] = "current-bytes-downloaded";
+const char kPrefsCurrentResponseSignature[] = "current-response-signature";
+const char kPrefsCurrentUrlFailureCount[] = "current-url-failure-count";
+const char kPrefsCurrentUrlIndex[] = "current-url-index";
+const char kPrefsDailyMetricsLastReportedAt[] =
+ "daily-metrics-last-reported-at";
+const char kPrefsDeltaUpdateFailures[] = "delta-update-failures";
+const char kPrefsFullPayloadAttemptNumber[] = "full-payload-attempt-number";
+const char kPrefsInstallDateDays[] = "install-date-days";
+const char kPrefsLastActivePingDay[] = "last-active-ping-day";
+const char kPrefsLastRollCallPingDay[] = "last-roll-call-ping-day";
+const char kPrefsManifestMetadataSize[] = "manifest-metadata-size";
+const char kPrefsMetricsAttemptLastReportingTime[] =
+ "metrics-attempt-last-reporting-time";
+const char kPrefsMetricsCheckLastReportingTime[] =
+ "metrics-check-last-reporting-time";
+const char kPrefsNumReboots[] = "num-reboots";
+const char kPrefsNumResponsesSeen[] = "num-responses-seen";
+const char kPrefsOmahaCohort[] = "omaha-cohort";
+const char kPrefsOmahaCohortHint[] = "omaha-cohort-hint";
+const char kPrefsOmahaCohortName[] = "omaha-cohort-name";
+const char kPrefsP2PEnabled[] = "p2p-enabled";
+const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
+const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
+const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number";
+const char kPrefsPreviousVersion[] = "previous-version";
+const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
+const char kPrefsRollbackVersion[] = "rollback-version";
+const char kPrefsSystemUpdatedMarker[] = "system-updated-marker";
+const char kPrefsTargetVersionAttempt[] = "target-version-attempt";
+const char kPrefsTargetVersionInstalledFrom[] = "target-version-installed-from";
+const char kPrefsTargetVersionUniqueId[] = "target-version-unique-id";
+const char kPrefsTotalBytesDownloaded[] = "total-bytes-downloaded";
+const char kPrefsUpdateCheckCount[] = "update-check-count";
+const char kPrefsUpdateCheckResponseHash[] = "update-check-response-hash";
+const char kPrefsUpdateDurationUptime[] = "update-duration-uptime";
+const char kPrefsUpdateFirstSeenAt[] = "update-first-seen-at";
+const char kPrefsUpdateOverCellularPermission[] =
+ "update-over-cellular-permission";
+const char kPrefsUpdateServerCertificate[] = "update-server-cert";
+const char kPrefsUpdateStateNextDataLength[] = "update-state-next-data-length";
+const char kPrefsUpdateStateNextDataOffset[] = "update-state-next-data-offset";
+const char kPrefsUpdateStateNextOperation[] = "update-state-next-operation";
+const char kPrefsUpdateStateSHA256Context[] = "update-state-sha-256-context";
+const char kPrefsUpdateStateSignatureBlob[] = "update-state-signature-blob";
+const char kPrefsUpdateStateSignedSHA256Context[] =
+ "update-state-signed-sha-256-context";
+const char kPrefsUpdateTimestampStart[] = "update-timestamp-start";
+const char kPrefsUrlSwitchCount[] = "url-switch-count";
+const char kPrefsWallClockWaitPeriod[] = "wall-clock-wait-period";
+
+} // namespace chromeos_update_engine
diff --git a/constants.h b/constants.h
new file mode 100644
index 0000000..b933f62
--- /dev/null
+++ b/constants.h
@@ -0,0 +1,169 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_CONSTANTS_H_
+#define UPDATE_ENGINE_CONSTANTS_H_
+
+namespace chromeos_update_engine {
+
+// The name of the marker file used to trigger powerwash when post-install
+// completes successfully so that the device is powerwashed on next reboot.
+extern const char kPowerwashMarkerFile[];
+
+// Path to the marker file we use to indicate we've recorded a system reboot.
+extern const char kSystemRebootedMarkerFile[];
+
+// The contents of the powerwash marker file.
+extern const char kPowerwashCommand[];
+
+// Directory for AU prefs that are preserved across powerwash.
+extern const char kPowerwashSafePrefsDir[];
+
+// The location where we store the AU preferences (state etc).
+extern const char kPrefsDirectory[];
+
+// Path to the stateful partition on the root filesystem.
+extern const char kStatefulPartition[];
+
+// Constants related to preferences.
+extern const char kPrefsAttemptInProgress[];
+extern const char kPrefsBackoffExpiryTime[];
+extern const char kPrefsCertificateReportToSendDownload[];
+extern const char kPrefsCertificateReportToSendUpdate[];
+extern const char kPrefsCurrentBytesDownloaded[];
+extern const char kPrefsCurrentResponseSignature[];
+extern const char kPrefsCurrentUrlFailureCount[];
+extern const char kPrefsCurrentUrlIndex[];
+extern const char kPrefsDailyMetricsLastReportedAt[];
+extern const char kPrefsDeltaUpdateFailures[];
+extern const char kPrefsFullPayloadAttemptNumber[];
+extern const char kPrefsInstallDateDays[];
+extern const char kPrefsLastActivePingDay[];
+extern const char kPrefsLastRollCallPingDay[];
+extern const char kPrefsManifestMetadataSize[];
+extern const char kPrefsMetricsAttemptLastReportingTime[];
+extern const char kPrefsMetricsCheckLastReportingTime[];
+extern const char kPrefsNumReboots[];
+extern const char kPrefsNumResponsesSeen[];
+extern const char kPrefsOmahaCohort[];
+extern const char kPrefsOmahaCohortHint[];
+extern const char kPrefsOmahaCohortName[];
+extern const char kPrefsP2PEnabled[];
+extern const char kPrefsP2PFirstAttemptTimestamp[];
+extern const char kPrefsP2PNumAttempts[];
+extern const char kPrefsPayloadAttemptNumber[];
+extern const char kPrefsPreviousVersion[];
+extern const char kPrefsResumedUpdateFailures[];
+extern const char kPrefsRollbackVersion[];
+extern const char kPrefsSystemUpdatedMarker[];
+extern const char kPrefsTargetVersionAttempt[];
+extern const char kPrefsTargetVersionInstalledFrom[];
+extern const char kPrefsTargetVersionUniqueId[];
+extern const char kPrefsTotalBytesDownloaded[];
+extern const char kPrefsUpdateCheckCount[];
+extern const char kPrefsUpdateCheckResponseHash[];
+extern const char kPrefsUpdateDurationUptime[];
+extern const char kPrefsUpdateFirstSeenAt[];
+extern const char kPrefsUpdateOverCellularPermission[];
+extern const char kPrefsUpdateServerCertificate[];
+extern const char kPrefsUpdateStateNextDataLength[];
+extern const char kPrefsUpdateStateNextDataOffset[];
+extern const char kPrefsUpdateStateNextOperation[];
+extern const char kPrefsUpdateStateSHA256Context[];
+extern const char kPrefsUpdateStateSignatureBlob[];
+extern const char kPrefsUpdateStateSignedSHA256Context[];
+extern const char kPrefsUpdateTimestampStart[];
+extern const char kPrefsUrlSwitchCount[];
+extern const char kPrefsWallClockWaitPeriod[];
+
+// A download source is any combination of protocol and server (that's of
+// interest to us when looking at UMA metrics) using which we may download
+// the payload.
+typedef enum {
+ kDownloadSourceHttpsServer, // UMA Binary representation: 0001
+ kDownloadSourceHttpServer, // UMA Binary representation: 0010
+ kDownloadSourceHttpPeer, // UMA Binary representation: 0100
+
+ // Note: Add new sources only above this line.
+ kNumDownloadSources
+} DownloadSource;
+
+// A payload can be a Full or Delta payload. In some cases, a Full payload is
+// used even when a Delta payload was available for the update, called here
+// ForcedFull. The PayloadType enum is only used to send UMA metrics about the
+// successfully applied payload.
+typedef enum {
+ kPayloadTypeFull,
+ kPayloadTypeDelta,
+ kPayloadTypeForcedFull,
+
+ // Note: Add new payload types only above this line.
+ kNumPayloadTypes
+} PayloadType;
+
+// Maximum number of times we'll allow using p2p for the same update payload.
+const int kMaxP2PAttempts = 10;
+
+// Maximum wallclock time we allow attempting to update using p2p for
+// the same update payload - five days.
+const int kMaxP2PAttemptTimeSeconds = 5 * 24 * 60 * 60;
+
+// The maximum amount of time to spend waiting for p2p-client(1) to
+// return while waiting in line to use the LAN - six hours.
+const int kMaxP2PNetworkWaitTimeSeconds = 6 * 60 * 60;
+
+// The maximum number of payload files to keep in /var/cache/p2p.
+const int kMaxP2PFilesToKeep = 3;
+
+// The maximum number of days to keep a p2p file;
+const int kMaxP2PFileAgeDays = 5;
+
+// The default number of UMA buckets for metrics.
+const int kNumDefaultUmaBuckets = 50;
+
+// General constants
+const int kNumBytesInOneMiB = 1024 * 1024;
+
+// Number of redirects allowed when downloading.
+const int kDownloadMaxRedirects = 10;
+
+// The minimum average speed that downloads must sustain...
+//
+// This is set low because some devices may have very poor
+// connectivity and we want to make as much forward progress as
+// possible. For p2p this is high (25 kB/second) since we can assume
+// high bandwidth (same LAN) and we want to fail fast.
+const int kDownloadLowSpeedLimitBps = 1;
+const int kDownloadP2PLowSpeedLimitBps = 25 * 1000;
+
+// ... measured over this period.
+//
+// For non-official builds (e.g. typically built on a developer's
+// workstation and served via devserver) bump this since it takes time
+// for the workstation to generate the payload. For p2p, make this
+// relatively low since we want to fail fast.
+const int kDownloadLowSpeedTimeSeconds = 90;
+const int kDownloadDevModeLowSpeedTimeSeconds = 180;
+const int kDownloadP2PLowSpeedTimeSeconds = 60;
+
+// The maximum amount of HTTP server reconnect attempts.
+//
+// This is set high in order to maximize the attempt's chance of
+// succeeding. When using p2p, this is low in order to fail fast.
+const int kDownloadMaxRetryCount = 20;
+const int kDownloadMaxRetryCountOobeNotComplete = 3;
+const int kDownloadP2PMaxRetryCount = 5;
+
+// The connect timeout, in seconds.
+//
+// This is set high because some devices may have very poor
+// connectivity and we may be using HTTPS which involves complicated
+// multi-roundtrip setup. For p2p, this is set low because we can
+// the server is on the same LAN and we want to fail fast.
+const int kDownloadConnectTimeoutSeconds = 30;
+const int kDownloadP2PConnectTimeoutSeconds = 5;
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CONSTANTS_H_
diff --git a/dbus_bindings/dbus-service-config.json b/dbus_bindings/dbus-service-config.json
new file mode 100644
index 0000000..fdae3ba
--- /dev/null
+++ b/dbus_bindings/dbus-service-config.json
@@ -0,0 +1,3 @@
+{
+ "service_name": "org.chromium.UpdateEngine"
+}
diff --git a/dbus_bindings/org.chromium.UpdateEngineInterface.xml b/dbus_bindings/org.chromium.UpdateEngineInterface.xml
new file mode 100644
index 0000000..dda972d
--- /dev/null
+++ b/dbus_bindings/org.chromium.UpdateEngineInterface.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- COPYRIGHT HERE
+ dbus-binding-tool -mode=glib-server -prefix=update_engine update_engine.xml
+ > glib_server.h
+-->
+<node name="/org/chromium/UpdateEngine">
+ <interface name="org.chromium.UpdateEngineInterface">
+ <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+ value="update_engine_service" />
+ <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+ value="update_engine_client" />
+ <method name="AttemptUpdate">
+ <arg type="s" name="app_version" direction="in" />
+ <arg type="s" name="omaha_url" direction="in" />
+ </method>
+ <!-- TODO(zeuthen,chromium:286399): Rename to AttemptUpdate and
+ update Chrome and other users of the AttemptUpdate() method
+ in lockstep.
+ -->
+ <method name="AttemptUpdateWithFlags">
+ <arg type="s" name="app_version" direction="in" />
+ <arg type="s" name="omaha_url" direction="in" />
+ <!-- See AttemptUpdateFlags enum in dbus_constants.h. -->
+ <arg type="i" name="flags" direction="in" />
+ </method>
+ <method name="AttemptRollback">
+ <arg type="b" name="powerwash" direction="in" />
+ </method>
+ <method name="CanRollback">
+ <arg type="b" name="can_rollback" direction="out" />
+ </method>
+ <method name="ResetStatus">
+ </method>
+ <method name="GetStatus">
+ <arg type="x" name="last_checked_time" direction="out" />
+ <arg type="d" name="progress" direction="out" />
+ <arg type="s" name="current_operation" direction="out" />
+ <arg type="s" name="new_version" direction="out" />
+ <arg type="x" name="new_size" direction="out" />
+ </method>
+ <method name="RebootIfNeeded">
+ </method>
+ <method name="SetChannel">
+ <arg type="s" name="target_channel" direction="in" />
+ <arg type="b" name="is_powerwash_allowed" direction="in" />
+ </method>
+ <method name="GetChannel">
+ <arg type="b" name="get_current_channel" direction="in" />
+ <arg type="s" name="channel" direction="out" />
+ </method>
+ <method name="SetP2PUpdatePermission">
+ <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+ value="update_engine_service_set_p2p_update_permission" />
+ <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+ value="update_engine_client_set_p2p_update_permission" />
+ <arg type="b" name="enabled" direction="in" />
+ </method>
+ <method name="GetP2PUpdatePermission">
+ <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+ value="update_engine_service_get_p2p_update_permission" />
+ <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+ value="update_engine_client_get_p2p_update_permission" />
+ <arg type="b" name="enabled" direction="out" />
+ </method>
+ <method name="SetUpdateOverCellularPermission">
+ <arg type="b" name="allowed" direction="in" />
+ </method>
+ <method name="GetUpdateOverCellularPermission">
+ <arg type="b" name="allowed" direction="out" />
+ </method>
+ <method name="GetDurationSinceUpdate">
+ <arg type="x" name="usec_wallclock" direction="out" />
+ </method>
+ <signal name="StatusUpdate">
+ <arg type="x" name="last_checked_time" />
+ <arg type="d" name="progress" />
+ <arg type="s" name="current_operation" />
+ <arg type="s" name="new_version" />
+ <arg type="x" name="new_size" />
+ </signal>
+ <method name="GetPrevVersion">
+ <arg type="s" name="prev_version" direction="out" />
+ </method>
+ <method name="GetKernelDevices">
+ <arg type="s" name="kernel_devices" direction="out" />
+ </method>
+ <method name="GetRollbackPartition">
+ <arg type="s" name="rollback_partition_name" direction="out" />
+ </method>
+ </interface>
+</node>
diff --git a/dbus_constants.h b/dbus_constants.h
new file mode 100644
index 0000000..d51e6fd
--- /dev/null
+++ b/dbus_constants.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_DBUS_CONSTANTS_H_
+#define UPDATE_ENGINE_DBUS_CONSTANTS_H_
+
+namespace chromeos_update_engine {
+
+static const char* const kUpdateEngineServiceName = "org.chromium.UpdateEngine";
+static const char* const kUpdateEngineServicePath =
+ "/org/chromium/UpdateEngine";
+static const char* const kUpdateEngineServiceInterface =
+ "org.chromium.UpdateEngineInterface";
+
+// Flags used in the AttemptUpdateWithFlags() D-Bus method.
+typedef enum {
+ kAttemptUpdateFlagNonInteractive = (1<<0)
+} AttemptUpdateFlags;
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_DBUS_CONSTANTS_H_
diff --git a/dbus_service.cc b/dbus_service.cc
new file mode 100644
index 0000000..a7ec259
--- /dev/null
+++ b/dbus_service.cc
@@ -0,0 +1,476 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/dbus_service.h"
+
+#include <set>
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/strings/string_utils.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/connection_manager.h"
+#include "update_engine/dbus_constants.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/prefs.h"
+#include "update_engine/update_attempter.h"
+#include "update_engine/utils.h"
+
+using base::StringPrintf;
+using chromeos::string_utils::ToString;
+using chromeos_update_engine::AttemptUpdateFlags;
+using chromeos_update_engine::kAttemptUpdateFlagNonInteractive;
+using std::set;
+using std::string;
+
+#define UPDATE_ENGINE_SERVICE_ERROR update_engine_service_error_quark ()
+#define UPDATE_ENGINE_SERVICE_TYPE_ERROR \
+ (update_engine_service_error_get_type())
+
+enum UpdateEngineServiceError {
+ UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ UPDATE_ENGINE_SERVICE_NUM_ERRORS
+};
+
+static GQuark update_engine_service_error_quark(void) {
+ static GQuark ret = 0;
+
+ if (ret == 0)
+ ret = g_quark_from_static_string("update_engine_service_error");
+
+ return ret;
+}
+
+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
+
+static GType update_engine_service_error_get_type(void) {
+ static GType etype = 0;
+
+ if (etype == 0) {
+ static const GEnumValue values[] = {
+ ENUM_ENTRY(UPDATE_ENGINE_SERVICE_ERROR_FAILED, "Failed"),
+ { 0, 0, 0 }
+ };
+ G_STATIC_ASSERT(UPDATE_ENGINE_SERVICE_NUM_ERRORS ==
+ G_N_ELEMENTS(values) - 1);
+ etype = g_enum_register_static("UpdateEngineServiceError", values);
+ }
+
+ return etype;
+}
+
+G_DEFINE_TYPE(UpdateEngineService, update_engine_service, G_TYPE_OBJECT)
+
+static void update_engine_service_finalize(GObject* object) {
+ G_OBJECT_CLASS(update_engine_service_parent_class)->finalize(object);
+}
+
+static void log_and_set_response_error(GError** error,
+ UpdateEngineServiceError error_code,
+ const string& reason) {
+ LOG(ERROR) << "Sending DBus Failure: " << reason;
+ g_set_error_literal(error, UPDATE_ENGINE_SERVICE_ERROR,
+ error_code, reason.c_str());
+}
+
+static guint status_update_signal = 0;
+
+static void update_engine_service_class_init(UpdateEngineServiceClass* klass) {
+ GObjectClass *object_class;
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->finalize = update_engine_service_finalize;
+
+ status_update_signal = g_signal_new(
+ "status_update",
+ G_OBJECT_CLASS_TYPE(klass),
+ G_SIGNAL_RUN_LAST,
+ 0, // 0 == no class method associated
+ nullptr, // Accumulator
+ nullptr, // Accumulator data
+ nullptr, // Marshaller
+ G_TYPE_NONE, // Return type
+ 5, // param count:
+ G_TYPE_INT64,
+ G_TYPE_DOUBLE,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_INT64);
+}
+
+static void update_engine_service_init(UpdateEngineService* object) {
+ dbus_g_error_domain_register(UPDATE_ENGINE_SERVICE_ERROR,
+ "org.chromium.UpdateEngine.Error",
+ UPDATE_ENGINE_SERVICE_TYPE_ERROR);
+}
+
+UpdateEngineService* update_engine_service_new(void) {
+ return reinterpret_cast<UpdateEngineService*>(
+ g_object_new(UPDATE_ENGINE_TYPE_SERVICE, nullptr));
+}
+
+gboolean update_engine_service_attempt_update(UpdateEngineService* self,
+ gchar* app_version,
+ gchar* omaha_url,
+ GError **error) {
+ return update_engine_service_attempt_update_with_flags(self,
+ app_version,
+ omaha_url,
+ 0, // No flags set.
+ error);
+}
+
+gboolean update_engine_service_attempt_update_with_flags(
+ UpdateEngineService* self,
+ gchar* app_version,
+ gchar* omaha_url,
+ gint flags_as_int,
+ GError **error) {
+ string app_version_string, omaha_url_string;
+ AttemptUpdateFlags flags = static_cast<AttemptUpdateFlags>(flags_as_int);
+ bool interactive = !(flags & kAttemptUpdateFlagNonInteractive);
+
+ if (app_version)
+ app_version_string = app_version;
+ if (omaha_url)
+ omaha_url_string = omaha_url;
+
+ LOG(INFO) << "Attempt update: app_version=\"" << app_version_string << "\" "
+ << "omaha_url=\"" << omaha_url_string << "\" "
+ << "flags=0x" << std::hex << flags << " "
+ << "interactive=" << (interactive? "yes" : "no");
+ self->system_state_->update_attempter()->CheckForUpdate(app_version_string,
+ omaha_url_string,
+ interactive);
+ return TRUE;
+}
+
+gboolean update_engine_service_attempt_rollback(UpdateEngineService* self,
+ gboolean powerwash,
+ GError **error) {
+ LOG(INFO) << "Attempting rollback to non-active partitions.";
+
+ if (!self->system_state_->update_attempter()->Rollback(powerwash)) {
+ // TODO(dgarrett): Give a more specific error code/reason.
+ log_and_set_response_error(error,
+ UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ "Rollback attempt failed.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean update_engine_service_can_rollback(UpdateEngineService* self,
+ gboolean* out_can_rollback,
+ GError **error) {
+ bool can_rollback = self->system_state_->update_attempter()->CanRollback();
+ LOG(INFO) << "Checking to see if we can rollback . Result: " << can_rollback;
+ *out_can_rollback = can_rollback;
+ return TRUE;
+}
+
+gboolean update_engine_service_get_rollback_partition(
+ UpdateEngineService* self,
+ gchar** out_rollback_partition_name,
+ GError **error) {
+ auto name = self->system_state_->update_attempter()->GetRollbackPartition();
+ LOG(INFO) << "Getting rollback partition name. Result: " << name;
+ *out_rollback_partition_name = g_strdup(name.c_str());
+ return TRUE;
+}
+
+gboolean update_engine_service_get_kernel_devices(UpdateEngineService* self,
+ gchar** out_kernel_devices,
+ GError **error) {
+ auto devices = self->system_state_->update_attempter()->GetKernelDevices();
+ string info;
+ for (const auto& device : devices) {
+ base::StringAppendF(&info, "%d:%s\n",
+ device.second ? 1 : 0, device.first.c_str());
+ }
+ LOG(INFO) << "Available kernel devices: " << info;
+ *out_kernel_devices = g_strdup(info.c_str());
+ return TRUE;
+}
+
+
+gboolean update_engine_service_reset_status(UpdateEngineService* self,
+ GError **error) {
+ if (!self->system_state_->update_attempter()->ResetStatus()) {
+ // TODO(dgarrett): Give a more specific error code/reason.
+ log_and_set_response_error(error,
+ UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ "ResetStatus failed.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean update_engine_service_get_status(UpdateEngineService* self,
+ int64_t* last_checked_time,
+ double* progress,
+ gchar** current_operation,
+ gchar** new_version,
+ int64_t* new_size,
+ GError **error) {
+ string current_op;
+ string new_version_str;
+
+ CHECK(self->system_state_->update_attempter()->GetStatus(last_checked_time,
+ progress,
+ ¤t_op,
+ &new_version_str,
+ new_size));
+ *current_operation = g_strdup(current_op.c_str());
+ *new_version = g_strdup(new_version_str.c_str());
+
+ if (!*current_operation) {
+ log_and_set_response_error(error,
+ UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ "Unable to find current_operation.");
+ return FALSE;
+ }
+
+ if (!*new_version) {
+ log_and_set_response_error(error,
+ UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ "Unable to find vew_version.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean update_engine_service_reboot_if_needed(UpdateEngineService* self,
+ GError **error) {
+ if (!self->system_state_->update_attempter()->RebootIfNeeded()) {
+ // TODO(dgarrett): Give a more specific error code/reason.
+ log_and_set_response_error(error,
+ UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ "Reboot not needed, or attempt failed.");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean update_engine_service_set_channel(UpdateEngineService* self,
+ gchar* target_channel,
+ gboolean is_powerwash_allowed,
+ GError **error) {
+ if (!target_channel) {
+ log_and_set_response_error(error,
+ UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ "Target channel to set not specified.");
+ return FALSE;
+ }
+
+ const policy::DevicePolicy* device_policy =
+ self->system_state_->device_policy();
+
+ // The device_policy is loaded in a lazy way before an update check. Load it
+ // now from the libchromeos cache if it wasn't already loaded.
+ if (!device_policy) {
+ chromeos_update_engine::UpdateAttempter* update_attempter =
+ self->system_state_->update_attempter();
+ if (update_attempter) {
+ update_attempter->RefreshDevicePolicy();
+ device_policy = self->system_state_->device_policy();
+ }
+ }
+
+ bool delegated = false;
+ if (device_policy &&
+ device_policy->GetReleaseChannelDelegated(&delegated) && !delegated) {
+ log_and_set_response_error(
+ error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ "Cannot set target channel explicitly when channel "
+ "policy/settings is not delegated");
+ return FALSE;
+ }
+
+ LOG(INFO) << "Setting destination channel to: " << target_channel;
+ if (!self->system_state_->request_params()->SetTargetChannel(
+ target_channel, is_powerwash_allowed)) {
+ // TODO(dgarrett): Give a more specific error code/reason.
+ log_and_set_response_error(error,
+ UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ "Setting channel failed.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean update_engine_service_get_channel(UpdateEngineService* self,
+ gboolean get_current_channel,
+ gchar** channel,
+ GError **error) {
+ chromeos_update_engine::OmahaRequestParams* rp =
+ self->system_state_->request_params();
+
+ string channel_str = get_current_channel ?
+ rp->current_channel() : rp->target_channel();
+
+ *channel = g_strdup(channel_str.c_str());
+ return TRUE;
+}
+
+gboolean update_engine_service_set_p2p_update_permission(
+ UpdateEngineService* self,
+ gboolean enabled,
+ GError **error) {
+ chromeos_update_engine::PrefsInterface* prefs = self->system_state_->prefs();
+
+ if (!prefs->SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, enabled)) {
+ log_and_set_response_error(
+ error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ StringPrintf("Error setting the update via p2p permission to %s.",
+ ToString(enabled).c_str()));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean update_engine_service_get_p2p_update_permission(
+ UpdateEngineService* self,
+ gboolean* enabled,
+ GError **error) {
+ chromeos_update_engine::PrefsInterface* prefs = self->system_state_->prefs();
+
+ bool p2p_pref = false; // Default if no setting is present.
+ if (prefs->Exists(chromeos_update_engine::kPrefsP2PEnabled) &&
+ !prefs->GetBoolean(chromeos_update_engine::kPrefsP2PEnabled, &p2p_pref)) {
+ log_and_set_response_error(error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ "Error getting the P2PEnabled setting.");
+ return FALSE;
+ }
+
+ *enabled = p2p_pref;
+ return TRUE;
+}
+
+gboolean update_engine_service_set_update_over_cellular_permission(
+ UpdateEngineService* self,
+ gboolean allowed,
+ GError **error) {
+ set<string> allowed_types;
+ const policy::DevicePolicy* device_policy =
+ self->system_state_->device_policy();
+
+ // The device_policy is loaded in a lazy way before an update check. Load it
+ // now from the libchromeos cache if it wasn't already loaded.
+ if (!device_policy) {
+ chromeos_update_engine::UpdateAttempter* update_attempter =
+ self->system_state_->update_attempter();
+ if (update_attempter) {
+ update_attempter->RefreshDevicePolicy();
+ device_policy = self->system_state_->device_policy();
+ }
+ }
+
+ // Check if this setting is allowed by the device policy.
+ if (device_policy &&
+ device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+ log_and_set_response_error(
+ error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ "Ignoring the update over cellular setting since there's "
+ "a device policy enforcing this setting.");
+ return FALSE;
+ }
+
+ // If the policy wasn't loaded yet, then it is still OK to change the local
+ // setting because the policy will be checked again during the update check.
+
+ chromeos_update_engine::PrefsInterface* prefs = self->system_state_->prefs();
+
+ if (!prefs->SetBoolean(
+ chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+ allowed)) {
+ log_and_set_response_error(
+ error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ string("Error setting the update over cellular to ") +
+ (allowed ? "true" : "false"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean update_engine_service_get_update_over_cellular_permission(
+ UpdateEngineService* self,
+ gboolean* allowed,
+ GError **error) {
+ chromeos_update_engine::ConnectionManager* cm =
+ self->system_state_->connection_manager();
+
+ // The device_policy is loaded in a lazy way before an update check and is
+ // used to determine if an update is allowed over cellular. Load the device
+ // policy now from the libchromeos cache if it wasn't already loaded.
+ if (!self->system_state_->device_policy()) {
+ chromeos_update_engine::UpdateAttempter* update_attempter =
+ self->system_state_->update_attempter();
+ if (update_attempter)
+ update_attempter->RefreshDevicePolicy();
+ }
+
+ // Return the current setting based on the same logic used while checking for
+ // updates. A log message could be printed as the result of this test.
+ LOG(INFO) << "Checking if updates over cellular networks are allowed:";
+ *allowed = cm->IsUpdateAllowedOver(
+ chromeos_update_engine::kNetCellular,
+ chromeos_update_engine::NetworkTethering::kUnknown);
+
+ return TRUE;
+}
+
+gboolean update_engine_service_get_duration_since_update(
+ UpdateEngineService* self,
+ gint64* out_usec_wallclock,
+ GError **error) {
+
+ base::Time time;
+ if (!self->system_state_->update_attempter()->GetBootTimeAtUpdate(&time)) {
+ log_and_set_response_error(error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+ "No pending update.");
+ return FALSE;
+ }
+
+ chromeos_update_engine::ClockInterface *clock = self->system_state_->clock();
+ *out_usec_wallclock = (clock->GetBootTime() - time).InMicroseconds();
+ return TRUE;
+}
+
+gboolean update_engine_service_emit_status_update(
+ UpdateEngineService* self,
+ gint64 last_checked_time,
+ gdouble progress,
+ const gchar* current_operation,
+ const gchar* new_version,
+ gint64 new_size) {
+ g_signal_emit(self,
+ status_update_signal,
+ 0,
+ last_checked_time,
+ progress,
+ current_operation,
+ new_version,
+ new_size);
+ return TRUE;
+}
+
+gboolean update_engine_service_get_prev_version(
+ UpdateEngineService* self,
+ gchar** prev_version,
+ GError **error) {
+ string ver = self->system_state_->update_attempter()->GetPrevVersion();
+ *prev_version = g_strdup(ver.c_str());
+ return TRUE;
+}
diff --git a/dbus_service.h b/dbus_service.h
new file mode 100644
index 0000000..d61e3b5
--- /dev/null
+++ b/dbus_service.h
@@ -0,0 +1,177 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_DBUS_SERVICE_H_
+#define UPDATE_ENGINE_DBUS_SERVICE_H_
+
+#include <inttypes.h>
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-bindings.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <glib-object.h>
+
+#include "update_engine/update_attempter.h"
+
+// Type macros:
+#define UPDATE_ENGINE_TYPE_SERVICE (update_engine_service_get_type())
+#define UPDATE_ENGINE_SERVICE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), UPDATE_ENGINE_TYPE_SERVICE, \
+ UpdateEngineService))
+#define UPDATE_ENGINE_IS_SERVICE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), UPDATE_ENGINE_TYPE_SERVICE))
+#define UPDATE_ENGINE_SERVICE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), UPDATE_ENGINE_TYPE_SERVICE, \
+ UpdateEngineService))
+#define UPDATE_ENGINE_IS_SERVICE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), UPDATE_ENGINE_TYPE_SERVICE))
+#define UPDATE_ENGINE_SERVICE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), UPDATE_ENGINE_TYPE_SERVICE, \
+ UpdateEngineService))
+
+G_BEGIN_DECLS
+
+struct UpdateEngineService {
+ GObject parent_instance;
+
+ chromeos_update_engine::SystemState* system_state_;
+};
+
+struct UpdateEngineServiceClass {
+ GObjectClass parent_class;
+};
+
+UpdateEngineService* update_engine_service_new(void);
+GType update_engine_service_get_type(void);
+
+// Methods
+
+gboolean update_engine_service_attempt_update(UpdateEngineService* self,
+ gchar* app_version,
+ gchar* omaha_url,
+ GError **error);
+
+gboolean update_engine_service_attempt_update_with_flags(
+ UpdateEngineService* self,
+ gchar* app_version,
+ gchar* omaha_url,
+ gint flags_as_int,
+ GError **error);
+
+gboolean update_engine_service_attempt_rollback(UpdateEngineService* self,
+ gboolean powerwash,
+ GError **error);
+
+// Checks if the system rollback is available by verifying if the secondary
+// system partition is valid and bootable.
+gboolean update_engine_service_can_rollback(
+ UpdateEngineService* self,
+ gboolean* out_can_rollback,
+ GError **error);
+
+// Returns the name of kernel partition that can be rolled back into.
+gboolean update_engine_service_get_rollback_partition(
+ UpdateEngineService* self,
+ gchar** out_rollback_partition_name,
+ GError **error);
+
+// Returns a list of available kernel partitions and whether each of them
+// can be booted from or not.
+gboolean update_engine_service_get_kernel_devices(UpdateEngineService* self,
+ gchar** out_kernel_devices,
+ GError **error);
+
+gboolean update_engine_service_reset_status(UpdateEngineService* self,
+ GError **error);
+
+gboolean update_engine_service_get_status(UpdateEngineService* self,
+ int64_t* last_checked_time,
+ double* progress,
+ gchar** current_operation,
+ gchar** new_version,
+ int64_t* new_size,
+ GError **error);
+
+gboolean update_engine_service_reboot_if_needed(UpdateEngineService* self,
+ GError **error);
+
+// Changes the current channel of the device to the target channel. If the
+// target channel is a less stable channel than the current channel, then the
+// channel change happens immediately (at the next update check). If the
+// target channel is a more stable channel, then if is_powerwash_allowed is set
+// to true, then also the change happens immediately but with a powerwash if
+// required. Otherwise, the change takes effect eventually (when the version on
+// the target channel goes above the version number of what the device
+// currently has).
+gboolean update_engine_service_set_channel(UpdateEngineService* self,
+ gchar* target_channel,
+ gboolean is_powerwash_allowed,
+ GError **error);
+
+// If get_current_channel is set to true, populates |channel| with the name of
+// the channel that the device is currently on. Otherwise, it populates it with
+// the name of the channel the device is supposed to be (in case of a pending
+// channel change).
+gboolean update_engine_service_get_channel(UpdateEngineService* self,
+ gboolean get_current_channel,
+ gchar** channel,
+ GError **error);
+
+// Enables or disables the sharing and consuming updates over P2P feature
+// according to the |enabled| argument passed.
+gboolean update_engine_service_set_p2p_update_permission(
+ UpdateEngineService* self,
+ gboolean enabled,
+ GError **error);
+
+// Returns in |enabled| the current value for the P2P enabled setting. This
+// involves both sharing and consuming updates over P2P.
+gboolean update_engine_service_get_p2p_update_permission(
+ UpdateEngineService* self,
+ gboolean* enabled,
+ GError **error);
+
+// If there's no device policy installed, sets the update over cellular networks
+// permission to the |allowed| value. Otherwise, this method returns with an
+// error since this setting is overridden by the applied policy.
+gboolean update_engine_service_set_update_over_cellular_permission(
+ UpdateEngineService* self,
+ gboolean allowed,
+ GError **error);
+
+// Returns the current value of the update over cellular network setting, either
+// forced by the device policy if the device is enrolled or the current user
+// preference otherwise.
+gboolean update_engine_service_get_update_over_cellular_permission(
+ UpdateEngineService* self,
+ gboolean* allowed,
+ GError **error);
+
+// Returns the duration since the last successful update, as the
+// duration on the wallclock. Returns an error if the device has not
+// updated.
+gboolean update_engine_service_get_duration_since_update(
+ UpdateEngineService* self,
+ gint64* out_usec_wallclock,
+ GError **error);
+
+gboolean update_engine_service_emit_status_update(
+ UpdateEngineService* self,
+ gint64 last_checked_time,
+ gdouble progress,
+ const gchar* current_operation,
+ const gchar* new_version,
+ gint64 new_size);
+
+// Returns the version string of OS that was used before the last reboot
+// into an updated version. This is available only when rebooting into an
+// update from previous version, otherwise an empty string is returned.
+gboolean update_engine_service_get_prev_version(
+ UpdateEngineService* self,
+ gchar** prev_version,
+ GError **error);
+
+G_END_DECLS
+
+#endif // UPDATE_ENGINE_DBUS_SERVICE_H_
diff --git a/dbus_wrapper_interface.h b/dbus_wrapper_interface.h
new file mode 100644
index 0000000..57211c5
--- /dev/null
+++ b/dbus_wrapper_interface.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_DBUS_WRAPPER_INTERFACE_H_
+#define UPDATE_ENGINE_DBUS_WRAPPER_INTERFACE_H_
+
+// A mockable interface for DBus.
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib.h>
+
+#ifndef DBUS_TYPE_G_OBJECT_PATH_ARRAY
+#define DBUS_TYPE_G_OBJECT_PATH_ARRAY \
+ (dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH))
+#endif
+
+#ifndef DBUS_TYPE_G_STRING_ARRAY
+#define DBUS_TYPE_G_STRING_ARRAY \
+ (dbus_g_type_get_collection("GPtrArray", G_TYPE_STRING))
+#endif
+
+namespace chromeos_update_engine {
+
+class DBusWrapperInterface {
+ public:
+ virtual ~DBusWrapperInterface() = default;
+
+ // Wraps dbus_g_proxy_new_for_name().
+ virtual DBusGProxy* ProxyNewForName(DBusGConnection* connection,
+ const char* name,
+ const char* path,
+ const char* interface) = 0;
+
+ // Wraps g_object_unref().
+ virtual void ProxyUnref(DBusGProxy* proxy) = 0;
+
+ // Wraps dbus_g_bus_get().
+ virtual DBusGConnection* BusGet(DBusBusType type, GError** error) = 0;
+
+ // Wraps dbus_g_proxy_call(). Since this is a variadic function without a
+ // va_list equivalent, we have to list specific wrappers depending on the
+ // number of input and output arguments, based on the required usage. Note,
+ // however, that we do rely on automatic signature overriding to facilitate
+ // different types of input/output arguments.
+ virtual gboolean ProxyCall_0_1(DBusGProxy* proxy,
+ const char* method,
+ GError** error,
+ GHashTable** out1) = 0;
+
+ virtual gboolean ProxyCall_0_1(DBusGProxy* proxy,
+ const char* method,
+ GError** error,
+ gint* out1) = 0;
+
+ virtual gboolean ProxyCall_1_0(DBusGProxy* proxy,
+ const char* method,
+ GError** error,
+ gint in1) = 0;
+
+ virtual gboolean ProxyCall_3_0(DBusGProxy* proxy,
+ const char* method,
+ GError** error,
+ const char* in1,
+ const char* in2,
+ const char* in3) = 0;
+
+ // Wrappers for dbus_g_proxy_add_signal() (variadic).
+ virtual void ProxyAddSignal_1(DBusGProxy* proxy,
+ const char* signal_name,
+ GType type1) = 0;
+
+ virtual void ProxyAddSignal_2(DBusGProxy* proxy,
+ const char* signal_name,
+ GType type1,
+ GType type2) = 0;
+
+ // Wrapper for dbus_g_proxy_{connect,disconnect}_signal().
+ virtual void ProxyConnectSignal(DBusGProxy* proxy,
+ const char* signal_name,
+ GCallback handler,
+ void* data,
+ GClosureNotify free_data_func) = 0;
+ virtual void ProxyDisconnectSignal(DBusGProxy* proxy,
+ const char* signal_name,
+ GCallback handler,
+ void* data) = 0;
+
+ // Wraps dbus_g_connection_get_connection().
+ virtual DBusConnection* ConnectionGetConnection(DBusGConnection* gbus) = 0;
+
+ // Wraps dbus_bus_add_match().
+ virtual void DBusBusAddMatch(DBusConnection* connection,
+ const char* rule,
+ DBusError* error) = 0;
+
+ // Wraps dbus_connection_add_filter().
+ virtual dbus_bool_t DBusConnectionAddFilter(
+ DBusConnection* connection,
+ DBusHandleMessageFunction function,
+ void* user_data,
+ DBusFreeFunction free_data_function) = 0;
+
+ // Wraps dbus_connection_remove_filter().
+ virtual void DBusConnectionRemoveFilter(DBusConnection* connection,
+ DBusHandleMessageFunction function,
+ void* user_data) = 0;
+
+ // Wraps dbus_message_is_signal().
+ virtual dbus_bool_t DBusMessageIsSignal(DBusMessage* message,
+ const char* interface,
+ const char* signal_name) = 0;
+
+ // Wraps dbus_message_get_args(). Deploys the same approach for handling
+ // variadic arguments as ProxyCall above.
+ virtual dbus_bool_t DBusMessageGetArgs_3(DBusMessage* message,
+ DBusError* error,
+ char** out1,
+ char** out2,
+ char** out3) = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_DBUS_WRAPPER_INTERFACE_H_
diff --git a/delta_performer.cc b/delta_performer.cc
new file mode 100644
index 0000000..1006f17
--- /dev/null
+++ b/delta_performer.cc
@@ -0,0 +1,1549 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/delta_performer.h"
+
+#include <endian.h>
+#include <errno.h>
+
+#include <algorithm>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/data_encoding.h>
+#include <google/protobuf/repeated_field.h>
+
+#include "update_engine/bzip_extent_writer.h"
+#include "update_engine/constants.h"
+#include "update_engine/extent_writer.h"
+#include "update_engine/hardware_interface.h"
+#if USE_MTD
+#include "update_engine/mtd_file_descriptor.h"
+#endif
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/payload_verifier.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
+#include "update_engine/update_attempter.h"
+
+using google::protobuf::RepeatedPtrField;
+using std::min;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const uint64_t DeltaPerformer::kDeltaVersionSize = 8;
+const uint64_t DeltaPerformer::kDeltaManifestSizeSize = 8;
+const uint64_t DeltaPerformer::kSupportedMajorPayloadVersion = 1;
+const uint64_t DeltaPerformer::kSupportedMinorPayloadVersion = 2;
+const uint64_t DeltaPerformer::kFullPayloadMinorVersion = 0;
+
+const char DeltaPerformer::kUpdatePayloadPublicKeyPath[] =
+ "/usr/share/update_engine/update-payload-key.pub.pem";
+const unsigned DeltaPerformer::kProgressLogMaxChunks = 10;
+const unsigned DeltaPerformer::kProgressLogTimeoutSeconds = 30;
+const unsigned DeltaPerformer::kProgressDownloadWeight = 50;
+const unsigned DeltaPerformer::kProgressOperationsWeight = 50;
+
+const uint32_t kInPlaceMinorPayloadVersion = 1;
+const uint32_t kSourceMinorPayloadVersion = 2;
+const size_t kDefaultChunkSize = 1024 * 1024;
+
+namespace {
+const int kUpdateStateOperationInvalid = -1;
+const int kMaxResumedUpdateFailures = 10;
+#if USE_MTD
+const int kUbiVolumeAttachTimeout = 5 * 60;
+#endif
+
+FileDescriptorPtr CreateFileDescriptor(const char* path) {
+ FileDescriptorPtr ret;
+#if USE_MTD
+ if (strstr(path, "/dev/ubi") == path) {
+ if (!UbiFileDescriptor::IsUbi(path)) {
+ // The volume might not have been attached at boot time.
+ int volume_no;
+ if (utils::SplitPartitionName(path, nullptr, &volume_no)) {
+ utils::TryAttachingUbiVolume(volume_no, kUbiVolumeAttachTimeout);
+ }
+ }
+ if (UbiFileDescriptor::IsUbi(path)) {
+ LOG(INFO) << path << " is a UBI device.";
+ ret.reset(new UbiFileDescriptor);
+ }
+ } else if (MtdFileDescriptor::IsMtd(path)) {
+ LOG(INFO) << path << " is an MTD device.";
+ ret.reset(new MtdFileDescriptor);
+ } else {
+ LOG(INFO) << path << " is not an MTD nor a UBI device.";
+#endif
+ ret.reset(new EintrSafeFileDescriptor);
+#if USE_MTD
+ }
+#endif
+ return ret;
+}
+
+// Opens path for read/write. On success returns an open FileDescriptor
+// and sets *err to 0. On failure, sets *err to errno and returns nullptr.
+FileDescriptorPtr OpenFile(const char* path, int* err) {
+ FileDescriptorPtr fd = CreateFileDescriptor(path);
+ int mode = O_RDWR;
+#if USE_MTD
+ // On NAND devices, we can either read, or write, but not both. So here we
+ // use O_WRONLY.
+ if (UbiFileDescriptor::IsUbi(path) || MtdFileDescriptor::IsMtd(path)) {
+ mode = O_WRONLY;
+ }
+#endif
+ if (!fd->Open(path, mode, 000)) {
+ *err = errno;
+ PLOG(ERROR) << "Unable to open file " << path;
+ return nullptr;
+ }
+ *err = 0;
+ return fd;
+}
+} // namespace
+
+
+// Computes the ratio of |part| and |total|, scaled to |norm|, using integer
+// arithmetic.
+static uint64_t IntRatio(uint64_t part, uint64_t total, uint64_t norm) {
+ return part * norm / total;
+}
+
+void DeltaPerformer::LogProgress(const char* message_prefix) {
+ // Format operations total count and percentage.
+ string total_operations_str("?");
+ string completed_percentage_str("");
+ if (num_total_operations_) {
+ total_operations_str = base::StringPrintf("%zu", num_total_operations_);
+ // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
+ completed_percentage_str =
+ base::StringPrintf(" (%" PRIu64 "%%)",
+ IntRatio(next_operation_num_, num_total_operations_,
+ 100));
+ }
+
+ // Format download total count and percentage.
+ size_t payload_size = install_plan_->payload_size;
+ string payload_size_str("?");
+ string downloaded_percentage_str("");
+ if (payload_size) {
+ payload_size_str = base::StringPrintf("%zu", payload_size);
+ // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
+ downloaded_percentage_str =
+ base::StringPrintf(" (%" PRIu64 "%%)",
+ IntRatio(total_bytes_received_, payload_size, 100));
+ }
+
+ LOG(INFO) << (message_prefix ? message_prefix : "") << next_operation_num_
+ << "/" << total_operations_str << " operations"
+ << completed_percentage_str << ", " << total_bytes_received_
+ << "/" << payload_size_str << " bytes downloaded"
+ << downloaded_percentage_str << ", overall progress "
+ << overall_progress_ << "%";
+}
+
+void DeltaPerformer::UpdateOverallProgress(bool force_log,
+ const char* message_prefix) {
+ // Compute our download and overall progress.
+ unsigned new_overall_progress = 0;
+ COMPILE_ASSERT(kProgressDownloadWeight + kProgressOperationsWeight == 100,
+ progress_weight_dont_add_up);
+ // Only consider download progress if its total size is known; otherwise
+ // adjust the operations weight to compensate for the absence of download
+ // progress. Also, make sure to cap the download portion at
+ // kProgressDownloadWeight, in case we end up downloading more than we
+ // initially expected (this indicates a problem, but could generally happen).
+ // TODO(garnold) the correction of operations weight when we do not have the
+ // total payload size, as well as the conditional guard below, should both be
+ // eliminated once we ensure that the payload_size in the install plan is
+ // always given and is non-zero. This currently isn't the case during unit
+ // tests (see chromium-os:37969).
+ size_t payload_size = install_plan_->payload_size;
+ unsigned actual_operations_weight = kProgressOperationsWeight;
+ if (payload_size)
+ new_overall_progress += min(
+ static_cast<unsigned>(IntRatio(total_bytes_received_, payload_size,
+ kProgressDownloadWeight)),
+ kProgressDownloadWeight);
+ else
+ actual_operations_weight += kProgressDownloadWeight;
+
+ // Only add completed operations if their total number is known; we definitely
+ // expect an update to have at least one operation, so the expectation is that
+ // this will eventually reach |actual_operations_weight|.
+ if (num_total_operations_)
+ new_overall_progress += IntRatio(next_operation_num_, num_total_operations_,
+ actual_operations_weight);
+
+ // Progress ratio cannot recede, unless our assumptions about the total
+ // payload size, total number of operations, or the monotonicity of progress
+ // is breached.
+ if (new_overall_progress < overall_progress_) {
+ LOG(WARNING) << "progress counter receded from " << overall_progress_
+ << "% down to " << new_overall_progress << "%; this is a bug";
+ force_log = true;
+ }
+ overall_progress_ = new_overall_progress;
+
+ // Update chunk index, log as needed: if forced by called, or we completed a
+ // progress chunk, or a timeout has expired.
+ base::Time curr_time = base::Time::Now();
+ unsigned curr_progress_chunk =
+ overall_progress_ * kProgressLogMaxChunks / 100;
+ if (force_log || curr_progress_chunk > last_progress_chunk_ ||
+ curr_time > forced_progress_log_time_) {
+ forced_progress_log_time_ = curr_time + forced_progress_log_wait_;
+ LogProgress(message_prefix);
+ }
+ last_progress_chunk_ = curr_progress_chunk;
+}
+
+
+size_t DeltaPerformer::CopyDataToBuffer(const char** bytes_p, size_t* count_p,
+ size_t max) {
+ const size_t count = *count_p;
+ if (!count)
+ return 0; // Special case shortcut.
+ size_t read_len = min(count, max - buffer_.size());
+ const char* bytes_start = *bytes_p;
+ const char* bytes_end = bytes_start + read_len;
+ buffer_.insert(buffer_.end(), bytes_start, bytes_end);
+ *bytes_p = bytes_end;
+ *count_p = count - read_len;
+ return read_len;
+}
+
+
+bool DeltaPerformer::HandleOpResult(bool op_result, const char* op_type_name,
+ ErrorCode* error) {
+ if (op_result)
+ return true;
+
+ LOG(ERROR) << "Failed to perform " << op_type_name << " operation "
+ << next_operation_num_;
+ *error = ErrorCode::kDownloadOperationExecutionError;
+ return false;
+}
+
+int DeltaPerformer::Open(const char* path, int flags, mode_t mode) {
+ int err;
+ fd_ = OpenFile(path, &err);
+ if (fd_)
+ path_ = path;
+ return -err;
+}
+
+bool DeltaPerformer::OpenKernel(const char* kernel_path) {
+ int err;
+ kernel_fd_ = OpenFile(kernel_path, &err);
+ if (kernel_fd_)
+ kernel_path_ = kernel_path;
+ return static_cast<bool>(kernel_fd_);
+}
+
+bool DeltaPerformer::OpenSourceRootfs(const string& source_path) {
+ int err;
+ source_fd_ = OpenFile(source_path.c_str(), &err);
+ return static_cast<bool>(source_fd_);
+}
+
+bool DeltaPerformer::OpenSourceKernel(const string& source_kernel_path) {
+ int err;
+ source_kernel_fd_ = OpenFile(source_kernel_path.c_str(), &err);
+ return static_cast<bool>(source_kernel_fd_);
+}
+
+int DeltaPerformer::Close() {
+ int err = 0;
+ if (!kernel_fd_->Close()) {
+ err = errno;
+ PLOG(ERROR) << "Unable to close kernel fd:";
+ }
+ if (!fd_->Close()) {
+ err = errno;
+ PLOG(ERROR) << "Unable to close rootfs fd:";
+ }
+ if (source_fd_ && !source_fd_->Close()) {
+ err = errno;
+ PLOG(ERROR) << "Unable to close source rootfs fd:";
+ }
+ if (source_kernel_fd_ && !source_kernel_fd_->Close()) {
+ err = errno;
+ PLOG(ERROR) << "Unable to close source kernel fd:";
+ }
+ LOG_IF(ERROR, !hash_calculator_.Finalize()) << "Unable to finalize the hash.";
+ fd_.reset(); // Set to invalid so that calls to Open() will fail.
+ kernel_fd_.reset();
+ source_fd_.reset();
+ source_kernel_fd_.reset();
+ path_ = "";
+ if (!buffer_.empty()) {
+ LOG(INFO) << "Discarding " << buffer_.size() << " unused downloaded bytes";
+ if (err >= 0)
+ err = 1;
+ }
+ return -err;
+}
+
+namespace {
+
+void LogPartitionInfoHash(const PartitionInfo& info, const string& tag) {
+ string sha256 = chromeos::data_encoding::Base64Encode(info.hash());
+ LOG(INFO) << "PartitionInfo " << tag << " sha256: " << sha256
+ << " size: " << info.size();
+}
+
+void LogPartitionInfo(const DeltaArchiveManifest& manifest) {
+ if (manifest.has_old_kernel_info())
+ LogPartitionInfoHash(manifest.old_kernel_info(), "old_kernel_info");
+ if (manifest.has_old_rootfs_info())
+ LogPartitionInfoHash(manifest.old_rootfs_info(), "old_rootfs_info");
+ if (manifest.has_new_kernel_info())
+ LogPartitionInfoHash(manifest.new_kernel_info(), "new_kernel_info");
+ if (manifest.has_new_rootfs_info())
+ LogPartitionInfoHash(manifest.new_rootfs_info(), "new_rootfs_info");
+}
+
+} // namespace
+
+uint64_t DeltaPerformer::GetVersionOffset() {
+ // Manifest size is stored right after the magic string and the version.
+ return strlen(kDeltaMagic);
+}
+
+uint64_t DeltaPerformer::GetManifestSizeOffset() {
+ // Manifest size is stored right after the magic string and the version.
+ return strlen(kDeltaMagic) + kDeltaVersionSize;
+}
+
+uint64_t DeltaPerformer::GetManifestOffset() {
+ // Actual manifest begins right after the manifest size field.
+ return GetManifestSizeOffset() + kDeltaManifestSizeSize;
+}
+
+uint64_t DeltaPerformer::GetMetadataSize() const {
+ return metadata_size_;
+}
+
+uint32_t DeltaPerformer::GetMinorVersion() const {
+ if (manifest_.has_minor_version()) {
+ return manifest_.minor_version();
+ } else {
+ return (install_plan_->is_full_update ?
+ kFullPayloadMinorVersion :
+ kSupportedMinorPayloadVersion);
+ }
+}
+
+bool DeltaPerformer::GetManifest(DeltaArchiveManifest* out_manifest_p) const {
+ if (!manifest_parsed_)
+ return false;
+ *out_manifest_p = manifest_;
+ return true;
+}
+
+
+DeltaPerformer::MetadataParseResult DeltaPerformer::ParsePayloadMetadata(
+ const chromeos::Blob& payload, ErrorCode* error) {
+ *error = ErrorCode::kSuccess;
+ const uint64_t manifest_offset = GetManifestOffset();
+ uint64_t manifest_size = (metadata_size_ ?
+ metadata_size_ - manifest_offset : 0);
+
+ if (!manifest_size) {
+ // Ensure we have data to cover the payload header.
+ if (payload.size() < manifest_offset)
+ return kMetadataParseInsufficientData;
+
+ // Validate the magic string.
+ if (memcmp(payload.data(), kDeltaMagic, strlen(kDeltaMagic)) != 0) {
+ LOG(ERROR) << "Bad payload format -- invalid delta magic.";
+ *error = ErrorCode::kDownloadInvalidMetadataMagicString;
+ return kMetadataParseError;
+ }
+
+ // Extract the payload version from the metadata.
+ uint64_t major_payload_version;
+ COMPILE_ASSERT(sizeof(major_payload_version) == kDeltaVersionSize,
+ major_payload_version_size_mismatch);
+ memcpy(&major_payload_version,
+ &payload[GetVersionOffset()],
+ kDeltaVersionSize);
+ // switch big endian to host
+ major_payload_version = be64toh(major_payload_version);
+
+ if (major_payload_version != kSupportedMajorPayloadVersion) {
+ LOG(ERROR) << "Bad payload format -- unsupported payload version: "
+ << major_payload_version;
+ *error = ErrorCode::kUnsupportedMajorPayloadVersion;
+ return kMetadataParseError;
+ }
+
+ // Next, parse the manifest size.
+ COMPILE_ASSERT(sizeof(manifest_size) == kDeltaManifestSizeSize,
+ manifest_size_size_mismatch);
+ memcpy(&manifest_size,
+ &payload[GetManifestSizeOffset()],
+ kDeltaManifestSizeSize);
+ manifest_size = be64toh(manifest_size); // switch big endian to host
+
+ // If the metadata size is present in install plan, check for it immediately
+ // even before waiting for that many number of bytes to be downloaded in the
+ // payload. This will prevent any attack which relies on us downloading data
+ // beyond the expected metadata size.
+ metadata_size_ = manifest_offset + manifest_size;
+ if (install_plan_->hash_checks_mandatory) {
+ if (install_plan_->metadata_size != metadata_size_) {
+ LOG(ERROR) << "Mandatory metadata size in Omaha response ("
+ << install_plan_->metadata_size
+ << ") is missing/incorrect, actual = " << metadata_size_;
+ *error = ErrorCode::kDownloadInvalidMetadataSize;
+ return kMetadataParseError;
+ }
+ }
+ }
+
+ // Now that we have validated the metadata size, we should wait for the full
+ // metadata to be read in before we can parse it.
+ if (payload.size() < metadata_size_)
+ return kMetadataParseInsufficientData;
+
+ // Log whether we validated the size or simply trusting what's in the payload
+ // here. This is logged here (after we received the full metadata data) so
+ // that we just log once (instead of logging n times) if it takes n
+ // DeltaPerformer::Write calls to download the full manifest.
+ if (install_plan_->metadata_size == metadata_size_) {
+ LOG(INFO) << "Manifest size in payload matches expected value from Omaha";
+ } else {
+ // For mandatory-cases, we'd have already returned a kMetadataParseError
+ // above. We'll be here only for non-mandatory cases. Just send a UMA stat.
+ LOG(WARNING) << "Ignoring missing/incorrect metadata size ("
+ << install_plan_->metadata_size
+ << ") in Omaha response as validation is not mandatory. "
+ << "Trusting metadata size in payload = " << metadata_size_;
+ SendUmaStat(ErrorCode::kDownloadInvalidMetadataSize);
+ }
+
+ // We have the full metadata in |payload|. Verify its integrity
+ // and authenticity based on the information we have in Omaha response.
+ *error = ValidateMetadataSignature(payload.data(), metadata_size_);
+ if (*error != ErrorCode::kSuccess) {
+ if (install_plan_->hash_checks_mandatory) {
+ // The autoupdate_CatchBadSignatures test checks for this string
+ // in log-files. Keep in sync.
+ LOG(ERROR) << "Mandatory metadata signature validation failed";
+ return kMetadataParseError;
+ }
+
+ // For non-mandatory cases, just send a UMA stat.
+ LOG(WARNING) << "Ignoring metadata signature validation failures";
+ SendUmaStat(*error);
+ *error = ErrorCode::kSuccess;
+ }
+
+ // The payload metadata is deemed valid, it's safe to parse the protobuf.
+ if (!manifest_.ParseFromArray(&payload[manifest_offset], manifest_size)) {
+ LOG(ERROR) << "Unable to parse manifest in update file.";
+ *error = ErrorCode::kDownloadManifestParseError;
+ return kMetadataParseError;
+ }
+
+ manifest_parsed_ = true;
+ return kMetadataParseSuccess;
+}
+
+// Wrapper around write. Returns true if all requested bytes
+// were written, or false on any error, regardless of progress
+// and stores an action exit code in |error|.
+bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode *error) {
+ *error = ErrorCode::kSuccess;
+
+ const char* c_bytes = reinterpret_cast<const char*>(bytes);
+ system_state_->payload_state()->DownloadProgress(count);
+
+ // Update the total byte downloaded count and the progress logs.
+ total_bytes_received_ += count;
+ UpdateOverallProgress(false, "Completed ");
+
+ while (!manifest_valid_) {
+ // Read data up to the needed limit; this is either the payload header size,
+ // or the full metadata size (once it becomes known).
+ const bool do_read_header = !metadata_size_;
+ CopyDataToBuffer(&c_bytes, &count,
+ (do_read_header ? GetManifestOffset() :
+ metadata_size_));
+
+ MetadataParseResult result = ParsePayloadMetadata(buffer_, error);
+ if (result == kMetadataParseError)
+ return false;
+ if (result == kMetadataParseInsufficientData) {
+ // If we just processed the header, make an attempt on the manifest.
+ if (do_read_header && metadata_size_)
+ continue;
+
+ return true;
+ }
+
+ // Checks the integrity of the payload manifest.
+ if ((*error = ValidateManifest()) != ErrorCode::kSuccess)
+ return false;
+ manifest_valid_ = true;
+
+ // Clear the download buffer.
+ DiscardBuffer(false);
+ LOG_IF(WARNING, !prefs_->SetInt64(kPrefsManifestMetadataSize,
+ metadata_size_))
+ << "Unable to save the manifest metadata size.";
+
+ LogPartitionInfo(manifest_);
+ if (!PrimeUpdateState()) {
+ *error = ErrorCode::kDownloadStateInitializationError;
+ LOG(ERROR) << "Unable to prime the update state.";
+ return false;
+ }
+
+ // Open source fds if we have a delta payload with minor version 2.
+ if (!install_plan_->is_full_update &&
+ GetMinorVersion() == kSourceMinorPayloadVersion) {
+ if (!OpenSourceRootfs(install_plan_->source_path)) {
+ LOG(ERROR) << "Unable to open source rootfs partition file "
+ << install_plan_->source_path;
+ Close();
+ return false;
+ }
+ if (!OpenSourceKernel(install_plan_->kernel_source_path)) {
+ LOG(ERROR) << "Unable to open source kernel partition file "
+ << install_plan_->kernel_source_path;
+ Close();
+ return false;
+ }
+ }
+
+ num_rootfs_operations_ = manifest_.install_operations_size();
+ num_total_operations_ =
+ num_rootfs_operations_ + manifest_.kernel_install_operations_size();
+ if (next_operation_num_ > 0)
+ UpdateOverallProgress(true, "Resuming after ");
+ LOG(INFO) << "Starting to apply update payload operations";
+ }
+
+ while (next_operation_num_ < num_total_operations_) {
+ // Check if we should cancel the current attempt for any reason.
+ // In this case, *error will have already been populated with the reason
+ // why we're cancelling.
+ if (system_state_->update_attempter()->ShouldCancel(error))
+ return false;
+
+ const bool is_kernel_partition =
+ (next_operation_num_ >= num_rootfs_operations_);
+ const DeltaArchiveManifest_InstallOperation &op =
+ is_kernel_partition ?
+ manifest_.kernel_install_operations(
+ next_operation_num_ - num_rootfs_operations_) :
+ manifest_.install_operations(next_operation_num_);
+
+ CopyDataToBuffer(&c_bytes, &count, op.data_length());
+
+ // Check whether we received all of the next operation's data payload.
+ if (!CanPerformInstallOperation(op))
+ return true;
+
+ // Validate the operation only if the metadata signature is present.
+ // Otherwise, keep the old behavior. This serves as a knob to disable
+ // the validation logic in case we find some regression after rollout.
+ // NOTE: If hash checks are mandatory and if metadata_signature is empty,
+ // we would have already failed in ParsePayloadMetadata method and thus not
+ // even be here. So no need to handle that case again here.
+ if (!install_plan_->metadata_signature.empty()) {
+ // Note: Validate must be called only if CanPerformInstallOperation is
+ // called. Otherwise, we might be failing operations before even if there
+ // isn't sufficient data to compute the proper hash.
+ *error = ValidateOperationHash(op);
+ if (*error != ErrorCode::kSuccess) {
+ if (install_plan_->hash_checks_mandatory) {
+ LOG(ERROR) << "Mandatory operation hash check failed";
+ return false;
+ }
+
+ // For non-mandatory cases, just send a UMA stat.
+ LOG(WARNING) << "Ignoring operation validation errors";
+ SendUmaStat(*error);
+ *error = ErrorCode::kSuccess;
+ }
+ }
+
+ // Makes sure we unblock exit when this operation completes.
+ ScopedTerminatorExitUnblocker exit_unblocker =
+ ScopedTerminatorExitUnblocker(); // Avoids a compiler unused var bug.
+
+ bool op_result;
+ if (op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+ op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ)
+ op_result = HandleOpResult(
+ PerformReplaceOperation(op, is_kernel_partition), "replace", error);
+ else if (op.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE)
+ op_result = HandleOpResult(
+ PerformMoveOperation(op, is_kernel_partition), "move", error);
+ else if (op.type() == DeltaArchiveManifest_InstallOperation_Type_BSDIFF)
+ op_result = HandleOpResult(
+ PerformBsdiffOperation(op, is_kernel_partition), "bsdiff", error);
+ else if (op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY)
+ op_result =
+ HandleOpResult(PerformSourceCopyOperation(op, is_kernel_partition),
+ "source_copy", error);
+ else if (op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_SOURCE_BSDIFF)
+ op_result =
+ HandleOpResult(PerformSourceBsdiffOperation(op, is_kernel_partition),
+ "source_bsdiff", error);
+ else
+ op_result = HandleOpResult(false, "unknown", error);
+
+ if (!op_result)
+ return false;
+
+ next_operation_num_++;
+ UpdateOverallProgress(false, "Completed ");
+ CheckpointUpdateProgress();
+ }
+ return true;
+}
+
+bool DeltaPerformer::IsManifestValid() {
+ return manifest_valid_;
+}
+
+bool DeltaPerformer::CanPerformInstallOperation(
+ const chromeos_update_engine::DeltaArchiveManifest_InstallOperation&
+ operation) {
+ // Move and source_copy operations don't require any data blob, so they can
+ // always be performed.
+ if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE ||
+ operation.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY)
+ return true;
+
+ // See if we have the entire data blob in the buffer
+ if (operation.data_offset() < buffer_offset_) {
+ LOG(ERROR) << "we threw away data it seems?";
+ return false;
+ }
+
+ return (operation.data_offset() + operation.data_length() <=
+ buffer_offset_ + buffer_.size());
+}
+
+bool DeltaPerformer::PerformReplaceOperation(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ bool is_kernel_partition) {
+ CHECK(operation.type() == \
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE || \
+ operation.type() == \
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+
+ // Since we delete data off the beginning of the buffer as we use it,
+ // the data we need should be exactly at the beginning of the buffer.
+ TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+ TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+
+ // Extract the signature message if it's in this operation.
+ ExtractSignatureMessage(operation);
+
+ DirectExtentWriter direct_writer;
+ ZeroPadExtentWriter zero_pad_writer(&direct_writer);
+ unique_ptr<BzipExtentWriter> bzip_writer;
+
+ // Since bzip decompression is optional, we have a variable writer that will
+ // point to one of the ExtentWriter objects above.
+ ExtentWriter* writer = nullptr;
+ if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+ writer = &zero_pad_writer;
+ } else if (operation.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
+ bzip_writer.reset(new BzipExtentWriter(&zero_pad_writer));
+ writer = bzip_writer.get();
+ } else {
+ NOTREACHED();
+ }
+
+ // Create a vector of extents to pass to the ExtentWriter.
+ vector<Extent> extents;
+ for (int i = 0; i < operation.dst_extents_size(); i++) {
+ extents.push_back(operation.dst_extents(i));
+ }
+
+ FileDescriptorPtr fd = is_kernel_partition ? kernel_fd_ : fd_;
+
+ TEST_AND_RETURN_FALSE(writer->Init(fd, extents, block_size_));
+ TEST_AND_RETURN_FALSE(writer->Write(buffer_.data(), operation.data_length()));
+ TEST_AND_RETURN_FALSE(writer->End());
+
+ // Update buffer
+ DiscardBuffer(true);
+ return true;
+}
+
+bool DeltaPerformer::PerformMoveOperation(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ bool is_kernel_partition) {
+ // Calculate buffer size. Note, this function doesn't do a sliding
+ // window to copy in case the source and destination blocks overlap.
+ // If we wanted to do a sliding window, we could program the server
+ // to generate deltas that effectively did a sliding window.
+
+ uint64_t blocks_to_read = 0;
+ for (int i = 0; i < operation.src_extents_size(); i++)
+ blocks_to_read += operation.src_extents(i).num_blocks();
+
+ uint64_t blocks_to_write = 0;
+ for (int i = 0; i < operation.dst_extents_size(); i++)
+ blocks_to_write += operation.dst_extents(i).num_blocks();
+
+ DCHECK_EQ(blocks_to_write, blocks_to_read);
+ chromeos::Blob buf(blocks_to_write * block_size_);
+
+ FileDescriptorPtr fd = is_kernel_partition ? kernel_fd_ : fd_;
+
+ // Read in bytes.
+ ssize_t bytes_read = 0;
+ for (int i = 0; i < operation.src_extents_size(); i++) {
+ ssize_t bytes_read_this_iteration = 0;
+ const Extent& extent = operation.src_extents(i);
+ const size_t bytes = extent.num_blocks() * block_size_;
+ TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
+ TEST_AND_RETURN_FALSE(utils::PReadAll(fd,
+ &buf[bytes_read],
+ bytes,
+ extent.start_block() * block_size_,
+ &bytes_read_this_iteration));
+ TEST_AND_RETURN_FALSE(
+ bytes_read_this_iteration == static_cast<ssize_t>(bytes));
+ bytes_read += bytes_read_this_iteration;
+ }
+
+ // Write bytes out.
+ ssize_t bytes_written = 0;
+ for (int i = 0; i < operation.dst_extents_size(); i++) {
+ const Extent& extent = operation.dst_extents(i);
+ const size_t bytes = extent.num_blocks() * block_size_;
+ TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
+ TEST_AND_RETURN_FALSE(utils::PWriteAll(fd,
+ &buf[bytes_written],
+ bytes,
+ extent.start_block() * block_size_));
+ bytes_written += bytes;
+ }
+ DCHECK_EQ(bytes_written, bytes_read);
+ DCHECK_EQ(bytes_written, static_cast<ssize_t>(buf.size()));
+ return true;
+}
+
+namespace {
+
+// Takes |extents| and fills an empty vector |blocks| with a block index for
+// each block in |extents|. For example, [(3, 2), (8, 1)] would give [3, 4, 8].
+void ExtentsToBlocks(const RepeatedPtrField<Extent>& extents,
+ vector<uint64_t>* blocks) {
+ for (Extent ext : extents) {
+ for (uint64_t j = 0; j < ext.num_blocks(); j++)
+ blocks->push_back(ext.start_block() + j);
+ }
+}
+
+// Takes |extents| and returns the number of blocks in those extents.
+uint64_t GetBlockCount(const RepeatedPtrField<Extent>& extents) {
+ uint64_t sum = 0;
+ for (Extent ext : extents) {
+ sum += ext.num_blocks();
+ }
+ return sum;
+}
+
+} // namespace
+
+bool DeltaPerformer::PerformSourceCopyOperation(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ bool is_kernel_partition) {
+ if (operation.has_src_length())
+ TEST_AND_RETURN_FALSE(operation.src_length() % block_size_ == 0);
+ if (operation.has_dst_length())
+ TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0);
+
+ uint64_t blocks_to_read = GetBlockCount(operation.src_extents());
+ uint64_t blocks_to_write = GetBlockCount(operation.dst_extents());
+ TEST_AND_RETURN_FALSE(blocks_to_write == blocks_to_read);
+
+ // Create vectors of all the individual src/dst blocks.
+ vector<uint64_t> src_blocks;
+ vector<uint64_t> dst_blocks;
+ ExtentsToBlocks(operation.src_extents(), &src_blocks);
+ ExtentsToBlocks(operation.dst_extents(), &dst_blocks);
+ DCHECK_EQ(src_blocks.size(), blocks_to_read);
+ DCHECK_EQ(src_blocks.size(), dst_blocks.size());
+
+ FileDescriptorPtr src_fd =
+ is_kernel_partition ? source_kernel_fd_ : source_fd_;
+ FileDescriptorPtr dst_fd = is_kernel_partition? kernel_fd_ : fd_;
+
+ chromeos::Blob buf(block_size_);
+ ssize_t bytes_read = 0;
+ // Read/write one block at a time.
+ for (uint64_t i = 0; i < blocks_to_read; i++) {
+ ssize_t bytes_read_this_iteration = 0;
+ uint64_t src_block = src_blocks[i];
+ uint64_t dst_block = dst_blocks[i];
+
+ // Read in bytes.
+ TEST_AND_RETURN_FALSE(
+ utils::PReadAll(src_fd,
+ buf.data(),
+ block_size_,
+ src_block * block_size_,
+ &bytes_read_this_iteration));
+
+ // Write bytes out.
+ TEST_AND_RETURN_FALSE(
+ utils::PWriteAll(dst_fd,
+ buf.data(),
+ block_size_,
+ dst_block * block_size_));
+
+ bytes_read += bytes_read_this_iteration;
+ TEST_AND_RETURN_FALSE(bytes_read_this_iteration ==
+ static_cast<ssize_t>(block_size_));
+ }
+ DCHECK_EQ(bytes_read, static_cast<ssize_t>(blocks_to_read * block_size_));
+ return true;
+}
+
+bool DeltaPerformer::ExtentsToBsdiffPositionsString(
+ const RepeatedPtrField<Extent>& extents,
+ uint64_t block_size,
+ uint64_t full_length,
+ string* positions_string) {
+ string ret;
+ uint64_t length = 0;
+ for (int i = 0; i < extents.size(); i++) {
+ Extent extent = extents.Get(i);
+ int64_t start = extent.start_block() * block_size;
+ uint64_t this_length = min(full_length - length,
+ extent.num_blocks() * block_size);
+ ret += base::StringPrintf("%" PRIi64 ":%" PRIu64 ",", start, this_length);
+ length += this_length;
+ }
+ TEST_AND_RETURN_FALSE(length == full_length);
+ if (!ret.empty())
+ ret.resize(ret.size() - 1); // Strip trailing comma off
+ *positions_string = ret;
+ return true;
+}
+
+bool DeltaPerformer::PerformBsdiffOperation(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ bool is_kernel_partition) {
+ // Since we delete data off the beginning of the buffer as we use it,
+ // the data we need should be exactly at the beginning of the buffer.
+ TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+ TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+
+ string input_positions;
+ TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(),
+ block_size_,
+ operation.src_length(),
+ &input_positions));
+ string output_positions;
+ TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.dst_extents(),
+ block_size_,
+ operation.dst_length(),
+ &output_positions));
+
+ string temp_filename;
+ TEST_AND_RETURN_FALSE(utils::MakeTempFile("/tmp/au_patch.XXXXXX",
+ &temp_filename,
+ nullptr));
+ ScopedPathUnlinker path_unlinker(temp_filename);
+ {
+ int fd = open(temp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ ScopedFdCloser fd_closer(&fd);
+ TEST_AND_RETURN_FALSE(
+ utils::WriteAll(fd, buffer_.data(), operation.data_length()));
+ }
+
+ // Update the buffer to release the patch data memory as soon as the patch
+ // file is written out.
+ DiscardBuffer(true);
+
+ const string& path = is_kernel_partition ? kernel_path_ : path_;
+ vector<string> cmd{kBspatchPath, path, path, temp_filename,
+ input_positions, output_positions};
+
+ int return_code = 0;
+ TEST_AND_RETURN_FALSE(
+ Subprocess::SynchronousExecFlags(cmd,
+ G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
+ &return_code,
+ nullptr));
+ TEST_AND_RETURN_FALSE(return_code == 0);
+
+ if (operation.dst_length() % block_size_) {
+ // Zero out rest of final block.
+ // TODO(adlr): build this into bspatch; it's more efficient that way.
+ const Extent& last_extent =
+ operation.dst_extents(operation.dst_extents_size() - 1);
+ const uint64_t end_byte =
+ (last_extent.start_block() + last_extent.num_blocks()) * block_size_;
+ const uint64_t begin_byte =
+ end_byte - (block_size_ - operation.dst_length() % block_size_);
+ chromeos::Blob zeros(end_byte - begin_byte);
+ FileDescriptorPtr fd = is_kernel_partition ? kernel_fd_ : fd_;
+ TEST_AND_RETURN_FALSE(
+ utils::PWriteAll(fd, zeros.data(), end_byte - begin_byte, begin_byte));
+ }
+ return true;
+}
+
+bool DeltaPerformer::PerformSourceBsdiffOperation(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ bool is_kernel_partition) {
+ // Since we delete data off the beginning of the buffer as we use it,
+ // the data we need should be exactly at the beginning of the buffer.
+ TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+ TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+ if (operation.has_src_length())
+ TEST_AND_RETURN_FALSE(operation.src_length() % block_size_ == 0);
+ if (operation.has_dst_length())
+ TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0);
+
+ string input_positions;
+ TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(),
+ block_size_,
+ operation.src_length(),
+ &input_positions));
+ string output_positions;
+ TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.dst_extents(),
+ block_size_,
+ operation.dst_length(),
+ &output_positions));
+
+ string temp_filename;
+ TEST_AND_RETURN_FALSE(utils::MakeTempFile("/tmp/au_patch.XXXXXX",
+ &temp_filename,
+ nullptr));
+ ScopedPathUnlinker path_unlinker(temp_filename);
+ {
+ int fd = open(temp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ ScopedFdCloser fd_closer(&fd);
+ TEST_AND_RETURN_FALSE(
+ utils::WriteAll(fd, buffer_.data(), operation.data_length()));
+ }
+
+ // Update the buffer to release the patch data memory as soon as the patch
+ // file is written out.
+ DiscardBuffer(true);
+
+ const string& src_path = is_kernel_partition ?
+ install_plan_->kernel_source_path :
+ install_plan_->source_path;
+ const string& dst_path = is_kernel_partition ? kernel_path_ : path_;
+ vector<string> cmd{kBspatchPath, src_path, dst_path, temp_filename,
+ input_positions, output_positions};
+
+ int return_code = 0;
+ TEST_AND_RETURN_FALSE(
+ Subprocess::SynchronousExecFlags(cmd,
+ G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
+ &return_code,
+ nullptr));
+ TEST_AND_RETURN_FALSE(return_code == 0);
+ return true;
+}
+
+bool DeltaPerformer::ExtractSignatureMessage(
+ const DeltaArchiveManifest_InstallOperation& operation) {
+ if (operation.type() != DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+ !manifest_.has_signatures_offset() ||
+ manifest_.signatures_offset() != operation.data_offset()) {
+ return false;
+ }
+ TEST_AND_RETURN_FALSE(manifest_.has_signatures_size() &&
+ manifest_.signatures_size() == operation.data_length());
+ TEST_AND_RETURN_FALSE(signatures_message_data_.empty());
+ TEST_AND_RETURN_FALSE(buffer_offset_ == manifest_.signatures_offset());
+ TEST_AND_RETURN_FALSE(buffer_.size() >= manifest_.signatures_size());
+ signatures_message_data_.assign(
+ buffer_.begin(),
+ buffer_.begin() + manifest_.signatures_size());
+
+ // Save the signature blob because if the update is interrupted after the
+ // download phase we don't go through this path anymore. Some alternatives to
+ // consider:
+ //
+ // 1. On resume, re-download the signature blob from the server and re-verify
+ // it.
+ //
+ // 2. Verify the signature as soon as it's received and don't checkpoint the
+ // blob and the signed sha-256 context.
+ LOG_IF(WARNING, !prefs_->SetString(kPrefsUpdateStateSignatureBlob,
+ string(signatures_message_data_.begin(),
+ signatures_message_data_.end())))
+ << "Unable to store the signature blob.";
+ // The hash of all data consumed so far should be verified against the signed
+ // hash.
+ signed_hash_context_ = hash_calculator_.GetContext();
+ LOG_IF(WARNING, !prefs_->SetString(kPrefsUpdateStateSignedSHA256Context,
+ signed_hash_context_))
+ << "Unable to store the signed hash context.";
+ LOG(INFO) << "Extracted signature data of size "
+ << manifest_.signatures_size() << " at "
+ << manifest_.signatures_offset();
+ return true;
+}
+
+bool DeltaPerformer::GetPublicKeyFromResponse(base::FilePath *out_tmp_key) {
+ if (system_state_->hardware()->IsOfficialBuild() ||
+ utils::FileExists(public_key_path_.c_str()) ||
+ install_plan_->public_key_rsa.empty())
+ return false;
+
+ if (!utils::DecodeAndStoreBase64String(install_plan_->public_key_rsa,
+ out_tmp_key))
+ return false;
+
+ return true;
+}
+
+ErrorCode DeltaPerformer::ValidateMetadataSignature(
+ const void* metadata, uint64_t metadata_size) {
+
+ if (install_plan_->metadata_signature.empty()) {
+ if (install_plan_->hash_checks_mandatory) {
+ LOG(ERROR) << "Missing mandatory metadata signature in Omaha response";
+ return ErrorCode::kDownloadMetadataSignatureMissingError;
+ }
+
+ // For non-mandatory cases, just send a UMA stat.
+ LOG(WARNING) << "Cannot validate metadata as the signature is empty";
+ SendUmaStat(ErrorCode::kDownloadMetadataSignatureMissingError);
+ return ErrorCode::kSuccess;
+ }
+
+ // Convert base64-encoded signature to raw bytes.
+ chromeos::Blob metadata_signature;
+ if (!chromeos::data_encoding::Base64Decode(install_plan_->metadata_signature,
+ &metadata_signature)) {
+ LOG(ERROR) << "Unable to decode base64 metadata signature: "
+ << install_plan_->metadata_signature;
+ return ErrorCode::kDownloadMetadataSignatureError;
+ }
+
+ // See if we should use the public RSA key in the Omaha response.
+ base::FilePath path_to_public_key(public_key_path_);
+ base::FilePath tmp_key;
+ if (GetPublicKeyFromResponse(&tmp_key))
+ path_to_public_key = tmp_key;
+ ScopedPathUnlinker tmp_key_remover(tmp_key.value());
+ if (tmp_key.empty())
+ tmp_key_remover.set_should_remove(false);
+
+ LOG(INFO) << "Verifying metadata hash signature using public key: "
+ << path_to_public_key.value();
+
+ chromeos::Blob expected_metadata_hash;
+ if (!PayloadVerifier::GetRawHashFromSignature(metadata_signature,
+ path_to_public_key.value(),
+ &expected_metadata_hash)) {
+ LOG(ERROR) << "Unable to compute expected hash from metadata signature";
+ return ErrorCode::kDownloadMetadataSignatureError;
+ }
+
+ OmahaHashCalculator metadata_hasher;
+ metadata_hasher.Update(metadata, metadata_size);
+ if (!metadata_hasher.Finalize()) {
+ LOG(ERROR) << "Unable to compute actual hash of manifest";
+ return ErrorCode::kDownloadMetadataSignatureVerificationError;
+ }
+
+ chromeos::Blob calculated_metadata_hash = metadata_hasher.raw_hash();
+ PayloadVerifier::PadRSA2048SHA256Hash(&calculated_metadata_hash);
+ if (calculated_metadata_hash.empty()) {
+ LOG(ERROR) << "Computed actual hash of metadata is empty.";
+ return ErrorCode::kDownloadMetadataSignatureVerificationError;
+ }
+
+ if (calculated_metadata_hash != expected_metadata_hash) {
+ LOG(ERROR) << "Manifest hash verification failed. Expected hash = ";
+ utils::HexDumpVector(expected_metadata_hash);
+ LOG(ERROR) << "Calculated hash = ";
+ utils::HexDumpVector(calculated_metadata_hash);
+ return ErrorCode::kDownloadMetadataSignatureMismatch;
+ }
+
+ // The autoupdate_CatchBadSignatures test checks for this string in
+ // log-files. Keep in sync.
+ LOG(INFO) << "Metadata hash signature matches value in Omaha response.";
+ return ErrorCode::kSuccess;
+}
+
+ErrorCode DeltaPerformer::ValidateManifest() {
+ // Perform assorted checks to sanity check the manifest, make sure it
+ // matches data from other sources, and that it is a supported version.
+ //
+ // TODO(garnold) in general, the presence of an old partition hash should be
+ // the sole indicator for a delta update, as we would generally like update
+ // payloads to be self contained and not assume an Omaha response to tell us
+ // that. However, since this requires some massive reengineering of the update
+ // flow (making filesystem copying happen conditionally only *after*
+ // downloading and parsing of the update manifest) we'll put it off for now.
+ // See chromium-os:7597 for further discussion.
+ if (install_plan_->is_full_update) {
+ if (manifest_.has_old_kernel_info() || manifest_.has_old_rootfs_info()) {
+ LOG(ERROR) << "Purported full payload contains old partition "
+ "hash(es), aborting update";
+ return ErrorCode::kPayloadMismatchedType;
+ }
+
+ if (manifest_.minor_version() != kFullPayloadMinorVersion) {
+ LOG(ERROR) << "Manifest contains minor version "
+ << manifest_.minor_version()
+ << ", but all full payloads should have version "
+ << kFullPayloadMinorVersion << ".";
+ return ErrorCode::kUnsupportedMinorPayloadVersion;
+ }
+ } else {
+ if (manifest_.minor_version() != supported_minor_version_) {
+ LOG(ERROR) << "Manifest contains minor version "
+ << manifest_.minor_version()
+ << " not the supported "
+ << supported_minor_version_;
+ return ErrorCode::kUnsupportedMinorPayloadVersion;
+ }
+ }
+
+ // TODO(garnold) we should be adding more and more manifest checks, such as
+ // partition boundaries etc (see chromium-os:37661).
+
+ return ErrorCode::kSuccess;
+}
+
+ErrorCode DeltaPerformer::ValidateOperationHash(
+ const DeltaArchiveManifest_InstallOperation& operation) {
+
+ if (!operation.data_sha256_hash().size()) {
+ if (!operation.data_length()) {
+ // Operations that do not have any data blob won't have any operation hash
+ // either. So, these operations are always considered validated since the
+ // metadata that contains all the non-data-blob portions of the operation
+ // has already been validated. This is true for both HTTP and HTTPS cases.
+ return ErrorCode::kSuccess;
+ }
+
+ // No hash is present for an operation that has data blobs. This shouldn't
+ // happen normally for any client that has this code, because the
+ // corresponding update should have been produced with the operation
+ // hashes. So if it happens it means either we've turned operation hash
+ // generation off in DeltaDiffGenerator or it's a regression of some sort.
+ // One caveat though: The last operation is a dummy signature operation
+ // that doesn't have a hash at the time the manifest is created. So we
+ // should not complaint about that operation. This operation can be
+ // recognized by the fact that it's offset is mentioned in the manifest.
+ if (manifest_.signatures_offset() &&
+ manifest_.signatures_offset() == operation.data_offset()) {
+ LOG(INFO) << "Skipping hash verification for signature operation "
+ << next_operation_num_ + 1;
+ } else {
+ if (install_plan_->hash_checks_mandatory) {
+ LOG(ERROR) << "Missing mandatory operation hash for operation "
+ << next_operation_num_ + 1;
+ return ErrorCode::kDownloadOperationHashMissingError;
+ }
+
+ // For non-mandatory cases, just send a UMA stat.
+ LOG(WARNING) << "Cannot validate operation " << next_operation_num_ + 1
+ << " as there's no operation hash in manifest";
+ SendUmaStat(ErrorCode::kDownloadOperationHashMissingError);
+ }
+ return ErrorCode::kSuccess;
+ }
+
+ chromeos::Blob expected_op_hash;
+ expected_op_hash.assign(operation.data_sha256_hash().data(),
+ (operation.data_sha256_hash().data() +
+ operation.data_sha256_hash().size()));
+
+ OmahaHashCalculator operation_hasher;
+ operation_hasher.Update(buffer_.data(), operation.data_length());
+ if (!operation_hasher.Finalize()) {
+ LOG(ERROR) << "Unable to compute actual hash of operation "
+ << next_operation_num_;
+ return ErrorCode::kDownloadOperationHashVerificationError;
+ }
+
+ chromeos::Blob calculated_op_hash = operation_hasher.raw_hash();
+ if (calculated_op_hash != expected_op_hash) {
+ LOG(ERROR) << "Hash verification failed for operation "
+ << next_operation_num_ << ". Expected hash = ";
+ utils::HexDumpVector(expected_op_hash);
+ LOG(ERROR) << "Calculated hash over " << operation.data_length()
+ << " bytes at offset: " << operation.data_offset() << " = ";
+ utils::HexDumpVector(calculated_op_hash);
+ return ErrorCode::kDownloadOperationHashMismatch;
+ }
+
+ return ErrorCode::kSuccess;
+}
+
+#define TEST_AND_RETURN_VAL(_retval, _condition) \
+ do { \
+ if (!(_condition)) { \
+ LOG(ERROR) << "VerifyPayload failure: " << #_condition; \
+ return _retval; \
+ } \
+ } while (0);
+
+ErrorCode DeltaPerformer::VerifyPayload(
+ const string& update_check_response_hash,
+ const uint64_t update_check_response_size) {
+
+ // See if we should use the public RSA key in the Omaha response.
+ base::FilePath path_to_public_key(public_key_path_);
+ base::FilePath tmp_key;
+ if (GetPublicKeyFromResponse(&tmp_key))
+ path_to_public_key = tmp_key;
+ ScopedPathUnlinker tmp_key_remover(tmp_key.value());
+ if (tmp_key.empty())
+ tmp_key_remover.set_should_remove(false);
+
+ LOG(INFO) << "Verifying payload using public key: "
+ << path_to_public_key.value();
+
+ // Verifies the download size.
+ TEST_AND_RETURN_VAL(ErrorCode::kPayloadSizeMismatchError,
+ update_check_response_size ==
+ metadata_size_ + buffer_offset_);
+
+ // Verifies the payload hash.
+ const string& payload_hash_data = hash_calculator_.hash();
+ TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadVerificationError,
+ !payload_hash_data.empty());
+ TEST_AND_RETURN_VAL(ErrorCode::kPayloadHashMismatchError,
+ payload_hash_data == update_check_response_hash);
+
+ // Verifies the signed payload hash.
+ if (!utils::FileExists(path_to_public_key.value().c_str())) {
+ LOG(WARNING) << "Not verifying signed delta payload -- missing public key.";
+ return ErrorCode::kSuccess;
+ }
+ TEST_AND_RETURN_VAL(ErrorCode::kSignedDeltaPayloadExpectedError,
+ !signatures_message_data_.empty());
+ chromeos::Blob signed_hash_data;
+ TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+ PayloadVerifier::VerifySignature(
+ signatures_message_data_,
+ path_to_public_key.value(),
+ &signed_hash_data));
+ OmahaHashCalculator signed_hasher;
+ TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+ signed_hasher.SetContext(signed_hash_context_));
+ TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+ signed_hasher.Finalize());
+ chromeos::Blob hash_data = signed_hasher.raw_hash();
+ PayloadVerifier::PadRSA2048SHA256Hash(&hash_data);
+ TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+ !hash_data.empty());
+ if (hash_data != signed_hash_data) {
+ // The autoupdate_CatchBadSignatures test checks for this string
+ // in log-files. Keep in sync.
+ LOG(ERROR) << "Public key verification failed, thus update failed. "
+ "Attached Signature:";
+ utils::HexDumpVector(signed_hash_data);
+ LOG(ERROR) << "Computed Signature:";
+ utils::HexDumpVector(hash_data);
+ return ErrorCode::kDownloadPayloadPubKeyVerificationError;
+ }
+
+ LOG(INFO) << "Payload hash matches value in payload.";
+
+ // At this point, we are guaranteed to have downloaded a full payload, i.e
+ // the one whose size matches the size mentioned in Omaha response. If any
+ // errors happen after this, it's likely a problem with the payload itself or
+ // the state of the system and not a problem with the URL or network. So,
+ // indicate that to the payload state so that AU can backoff appropriately.
+ system_state_->payload_state()->DownloadComplete();
+
+ return ErrorCode::kSuccess;
+}
+
+bool DeltaPerformer::GetNewPartitionInfo(uint64_t* kernel_size,
+ chromeos::Blob* kernel_hash,
+ uint64_t* rootfs_size,
+ chromeos::Blob* rootfs_hash) {
+ TEST_AND_RETURN_FALSE(manifest_valid_ &&
+ manifest_.has_new_kernel_info() &&
+ manifest_.has_new_rootfs_info());
+ *kernel_size = manifest_.new_kernel_info().size();
+ *rootfs_size = manifest_.new_rootfs_info().size();
+ chromeos::Blob new_kernel_hash(manifest_.new_kernel_info().hash().begin(),
+ manifest_.new_kernel_info().hash().end());
+ chromeos::Blob new_rootfs_hash(manifest_.new_rootfs_info().hash().begin(),
+ manifest_.new_rootfs_info().hash().end());
+ kernel_hash->swap(new_kernel_hash);
+ rootfs_hash->swap(new_rootfs_hash);
+ return true;
+}
+
+namespace {
+void LogVerifyError(bool is_kern,
+ const string& local_hash,
+ const string& expected_hash) {
+ const char* type = is_kern ? "kernel" : "rootfs";
+ LOG(ERROR) << "This is a server-side error due to "
+ << "mismatched delta update image!";
+ LOG(ERROR) << "The delta I've been given contains a " << type << " delta "
+ << "update that must be applied over a " << type << " with "
+ << "a specific checksum, but the " << type << " we're starting "
+ << "with doesn't have that checksum! This means that "
+ << "the delta I've been given doesn't match my existing "
+ << "system. The " << type << " partition I have has hash: "
+ << local_hash << " but the update expected me to have "
+ << expected_hash << " .";
+ if (is_kern) {
+ LOG(INFO) << "To get the checksum of a kernel partition on a "
+ << "booted machine, run this command (change /dev/sda2 "
+ << "as needed): dd if=/dev/sda2 bs=1M 2>/dev/null | "
+ << "openssl dgst -sha256 -binary | openssl base64";
+ } else {
+ LOG(INFO) << "To get the checksum of a rootfs partition on a "
+ << "booted machine, run this command (change /dev/sda3 "
+ << "as needed): dd if=/dev/sda3 bs=1M count=$(( "
+ << "$(dumpe2fs /dev/sda3 2>/dev/null | grep 'Block count' "
+ << "| sed 's/[^0-9]*//') / 256 )) | "
+ << "openssl dgst -sha256 -binary | openssl base64";
+ }
+ LOG(INFO) << "To get the checksum of partitions in a bin file, "
+ << "run: .../src/scripts/sha256_partitions.sh .../file.bin";
+}
+
+string StringForHashBytes(const void* bytes, size_t size) {
+ return chromeos::data_encoding::Base64Encode(bytes, size);
+}
+} // namespace
+
+bool DeltaPerformer::VerifySourcePartitions() {
+ LOG(INFO) << "Verifying source partitions.";
+ CHECK(manifest_valid_);
+ CHECK(install_plan_);
+ if (manifest_.has_old_kernel_info()) {
+ const PartitionInfo& info = manifest_.old_kernel_info();
+ bool valid =
+ !install_plan_->source_kernel_hash.empty() &&
+ install_plan_->source_kernel_hash.size() == info.hash().size() &&
+ memcmp(install_plan_->source_kernel_hash.data(),
+ info.hash().data(),
+ install_plan_->source_kernel_hash.size()) == 0;
+ if (!valid) {
+ LogVerifyError(true,
+ StringForHashBytes(
+ install_plan_->source_kernel_hash.data(),
+ install_plan_->source_kernel_hash.size()),
+ StringForHashBytes(info.hash().data(),
+ info.hash().size()));
+ }
+ TEST_AND_RETURN_FALSE(valid);
+ }
+ if (manifest_.has_old_rootfs_info()) {
+ const PartitionInfo& info = manifest_.old_rootfs_info();
+ bool valid =
+ !install_plan_->source_rootfs_hash.empty() &&
+ install_plan_->source_rootfs_hash.size() == info.hash().size() &&
+ memcmp(install_plan_->source_rootfs_hash.data(),
+ info.hash().data(),
+ install_plan_->source_rootfs_hash.size()) == 0;
+ if (!valid) {
+ LogVerifyError(false,
+ StringForHashBytes(
+ install_plan_->source_rootfs_hash.data(),
+ install_plan_->source_rootfs_hash.size()),
+ StringForHashBytes(info.hash().data(),
+ info.hash().size()));
+ }
+ TEST_AND_RETURN_FALSE(valid);
+ }
+ return true;
+}
+
+void DeltaPerformer::DiscardBuffer(bool do_advance_offset) {
+ // Update the buffer offset.
+ if (do_advance_offset)
+ buffer_offset_ += buffer_.size();
+
+ // Hash the content.
+ hash_calculator_.Update(buffer_.data(), buffer_.size());
+
+ // Swap content with an empty vector to ensure that all memory is released.
+ chromeos::Blob().swap(buffer_);
+}
+
+bool DeltaPerformer::CanResumeUpdate(PrefsInterface* prefs,
+ string update_check_response_hash) {
+ int64_t next_operation = kUpdateStateOperationInvalid;
+ if (!(prefs->GetInt64(kPrefsUpdateStateNextOperation, &next_operation) &&
+ next_operation != kUpdateStateOperationInvalid &&
+ next_operation > 0))
+ return false;
+
+ string interrupted_hash;
+ if (!(prefs->GetString(kPrefsUpdateCheckResponseHash, &interrupted_hash) &&
+ !interrupted_hash.empty() &&
+ interrupted_hash == update_check_response_hash))
+ return false;
+
+ int64_t resumed_update_failures;
+ if (!(prefs->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures)
+ && resumed_update_failures > kMaxResumedUpdateFailures))
+ return false;
+
+ // Sanity check the rest.
+ int64_t next_data_offset = -1;
+ if (!(prefs->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset) &&
+ next_data_offset >= 0))
+ return false;
+
+ string sha256_context;
+ if (!(prefs->GetString(kPrefsUpdateStateSHA256Context, &sha256_context) &&
+ !sha256_context.empty()))
+ return false;
+
+ int64_t manifest_metadata_size = 0;
+ if (!(prefs->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size) &&
+ manifest_metadata_size > 0))
+ return false;
+
+ return true;
+}
+
+bool DeltaPerformer::ResetUpdateProgress(PrefsInterface* prefs, bool quick) {
+ TEST_AND_RETURN_FALSE(prefs->SetInt64(kPrefsUpdateStateNextOperation,
+ kUpdateStateOperationInvalid));
+ if (!quick) {
+ prefs->SetString(kPrefsUpdateCheckResponseHash, "");
+ prefs->SetInt64(kPrefsUpdateStateNextDataOffset, -1);
+ prefs->SetInt64(kPrefsUpdateStateNextDataLength, 0);
+ prefs->SetString(kPrefsUpdateStateSHA256Context, "");
+ prefs->SetString(kPrefsUpdateStateSignedSHA256Context, "");
+ prefs->SetString(kPrefsUpdateStateSignatureBlob, "");
+ prefs->SetInt64(kPrefsManifestMetadataSize, -1);
+ prefs->SetInt64(kPrefsResumedUpdateFailures, 0);
+ }
+ return true;
+}
+
+bool DeltaPerformer::CheckpointUpdateProgress() {
+ Terminator::set_exit_blocked(true);
+ if (last_updated_buffer_offset_ != buffer_offset_) {
+ // Resets the progress in case we die in the middle of the state update.
+ ResetUpdateProgress(prefs_, true);
+ TEST_AND_RETURN_FALSE(
+ prefs_->SetString(kPrefsUpdateStateSHA256Context,
+ hash_calculator_.GetContext()));
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataOffset,
+ buffer_offset_));
+ last_updated_buffer_offset_ = buffer_offset_;
+
+ if (next_operation_num_ < num_total_operations_) {
+ const bool is_kernel_partition =
+ next_operation_num_ >= num_rootfs_operations_;
+ const DeltaArchiveManifest_InstallOperation &op =
+ is_kernel_partition ?
+ manifest_.kernel_install_operations(
+ next_operation_num_ - num_rootfs_operations_) :
+ manifest_.install_operations(next_operation_num_);
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+ op.data_length()));
+ } else {
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+ 0));
+ }
+ }
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextOperation,
+ next_operation_num_));
+ return true;
+}
+
+bool DeltaPerformer::PrimeUpdateState() {
+ CHECK(manifest_valid_);
+ block_size_ = manifest_.block_size();
+
+ int64_t next_operation = kUpdateStateOperationInvalid;
+ if (!prefs_->GetInt64(kPrefsUpdateStateNextOperation, &next_operation) ||
+ next_operation == kUpdateStateOperationInvalid ||
+ next_operation <= 0) {
+ // Initiating a new update, no more state needs to be initialized.
+ TEST_AND_RETURN_FALSE(VerifySourcePartitions());
+ return true;
+ }
+ next_operation_num_ = next_operation;
+
+ // Resuming an update -- load the rest of the update state.
+ int64_t next_data_offset = -1;
+ TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsUpdateStateNextDataOffset,
+ &next_data_offset) &&
+ next_data_offset >= 0);
+ buffer_offset_ = next_data_offset;
+
+ // The signed hash context and the signature blob may be empty if the
+ // interrupted update didn't reach the signature.
+ prefs_->GetString(kPrefsUpdateStateSignedSHA256Context,
+ &signed_hash_context_);
+ string signature_blob;
+ if (prefs_->GetString(kPrefsUpdateStateSignatureBlob, &signature_blob)) {
+ signatures_message_data_.assign(signature_blob.begin(),
+ signature_blob.end());
+ }
+
+ string hash_context;
+ TEST_AND_RETURN_FALSE(prefs_->GetString(kPrefsUpdateStateSHA256Context,
+ &hash_context) &&
+ hash_calculator_.SetContext(hash_context));
+
+ int64_t manifest_metadata_size = 0;
+ TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsManifestMetadataSize,
+ &manifest_metadata_size) &&
+ manifest_metadata_size > 0);
+ metadata_size_ = manifest_metadata_size;
+
+ // Advance the download progress to reflect what doesn't need to be
+ // re-downloaded.
+ total_bytes_received_ += buffer_offset_;
+
+ // Speculatively count the resume as a failure.
+ int64_t resumed_update_failures;
+ if (prefs_->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures)) {
+ resumed_update_failures++;
+ } else {
+ resumed_update_failures = 1;
+ }
+ prefs_->SetInt64(kPrefsResumedUpdateFailures, resumed_update_failures);
+ return true;
+}
+
+void DeltaPerformer::SendUmaStat(ErrorCode code) {
+ utils::SendErrorCodeToUma(system_state_, code);
+}
+
+} // namespace chromeos_update_engine
diff --git a/delta_performer.h b/delta_performer.h
new file mode 100644
index 0000000..ba479af
--- /dev/null
+++ b/delta_performer.h
@@ -0,0 +1,401 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_DELTA_PERFORMER_H_
+#define UPDATE_ENGINE_DELTA_PERFORMER_H_
+
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+#include <chromeos/secure_blob.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/file_descriptor.h"
+#include "update_engine/file_writer.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// The minor version used by the in-place delta generator algorithm.
+extern const uint32_t kInPlaceMinorPayloadVersion;
+
+// The minor version used by the A to B delta generator algorithm.
+extern const uint32_t kSourceMinorPayloadVersion;
+
+// Chunk size used for payloads during test.
+extern const size_t kDefaultChunkSize;
+
+class PrefsInterface;
+
+// This class performs the actions in a delta update synchronously. The delta
+// update itself should be passed in in chunks as it is received.
+
+class DeltaPerformer : public FileWriter {
+ public:
+ enum MetadataParseResult {
+ kMetadataParseSuccess,
+ kMetadataParseError,
+ kMetadataParseInsufficientData,
+ };
+
+ static const uint64_t kDeltaVersionSize;
+ static const uint64_t kDeltaManifestSizeSize;
+ static const uint64_t kSupportedMajorPayloadVersion;
+ static const uint64_t kSupportedMinorPayloadVersion;
+ static const uint64_t kFullPayloadMinorVersion;
+ static const char kUpdatePayloadPublicKeyPath[];
+
+ // Defines the granularity of progress logging in terms of how many "completed
+ // chunks" we want to report at the most.
+ static const unsigned kProgressLogMaxChunks;
+ // Defines a timeout since the last progress was logged after which we want to
+ // force another log message (even if the current chunk was not completed).
+ static const unsigned kProgressLogTimeoutSeconds;
+ // These define the relative weights (0-100) we give to the different work
+ // components associated with an update when computing an overall progress.
+ // Currently they include the download progress and the number of completed
+ // operations. They must add up to one hundred (100).
+ static const unsigned kProgressDownloadWeight;
+ static const unsigned kProgressOperationsWeight;
+
+ DeltaPerformer(PrefsInterface* prefs,
+ SystemState* system_state,
+ InstallPlan* install_plan)
+ : prefs_(prefs),
+ system_state_(system_state),
+ install_plan_(install_plan),
+ fd_(nullptr),
+ kernel_fd_(nullptr),
+ source_fd_(nullptr),
+ source_kernel_fd_(nullptr),
+ manifest_parsed_(false),
+ manifest_valid_(false),
+ metadata_size_(0),
+ next_operation_num_(0),
+ buffer_offset_(0),
+ last_updated_buffer_offset_(kuint64max),
+ block_size_(0),
+ public_key_path_(kUpdatePayloadPublicKeyPath),
+ total_bytes_received_(0),
+ num_rootfs_operations_(0),
+ num_total_operations_(0),
+ overall_progress_(0),
+ last_progress_chunk_(0),
+ forced_progress_log_wait_(
+ base::TimeDelta::FromSeconds(kProgressLogTimeoutSeconds)),
+ supported_minor_version_(kSupportedMinorPayloadVersion) {}
+
+ // Opens the kernel. Should be called before or after Open(), but before
+ // Write(). The kernel file will be close()d when Close() is called.
+ bool OpenKernel(const char* kernel_path);
+
+ // Opens the source partition. The file will be closed when Close() is called.
+ bool OpenSourceRootfs(const std::string& kernel_path);
+
+ // Opens the source kernel. The file will be closed when Close() is called.
+ bool OpenSourceKernel(const std::string& source_kernel_path);
+
+ // flags and mode ignored. Once Close()d, a DeltaPerformer can't be
+ // Open()ed again.
+ int Open(const char* path, int flags, mode_t mode) override;
+
+ // FileWriter's Write implementation where caller doesn't care about
+ // error codes.
+ bool Write(const void* bytes, size_t count) override {
+ ErrorCode error;
+ return Write(bytes, count, &error);
+ }
+
+ // FileWriter's Write implementation that returns a more specific |error| code
+ // in case of failures in Write operation.
+ bool Write(const void* bytes, size_t count, ErrorCode *error) override;
+
+ // Wrapper around close. Returns 0 on success or -errno on error.
+ // Closes both 'path' given to Open() and the kernel path.
+ int Close() override;
+
+ // Returns |true| only if the manifest has been processed and it's valid.
+ bool IsManifestValid();
+
+ // Verifies the downloaded payload against the signed hash included in the
+ // payload, against the update check hash (which is in base64 format) and
+ // size using the public key and returns ErrorCode::kSuccess on success, an
+ // error code on failure. This method should be called after closing the
+ // stream. Note this method skips the signed hash check if the public key is
+ // unavailable; it returns ErrorCode::kSignedDeltaPayloadExpectedError if the
+ // public key is available but the delta payload doesn't include a signature.
+ ErrorCode VerifyPayload(const std::string& update_check_response_hash,
+ const uint64_t update_check_response_size);
+
+ // Reads from the update manifest the expected sizes and hashes of the target
+ // kernel and rootfs partitions. These values can be used for applied update
+ // hash verification. This method must be called after the update manifest has
+ // been parsed (e.g., after closing the stream). Returns true on success, and
+ // false on failure (e.g., when the values are not present in the update
+ // manifest).
+ bool GetNewPartitionInfo(uint64_t* kernel_size,
+ chromeos::Blob* kernel_hash,
+ uint64_t* rootfs_size,
+ chromeos::Blob* rootfs_hash);
+
+ // Converts an ordered collection of Extent objects which contain data of
+ // length full_length to a comma-separated string. For each Extent, the
+ // string will have the start offset and then the length in bytes.
+ // The length value of the last extent in the string may be short, since
+ // the full length of all extents in the string is capped to full_length.
+ // Also, an extent starting at kSparseHole, appears as -1 in the string.
+ // For example, if the Extents are {1, 1}, {4, 2}, {kSparseHole, 1},
+ // {0, 1}, block_size is 4096, and full_length is 5 * block_size - 13,
+ // the resulting string will be: "4096:4096,16384:8192,-1:4096,0:4083"
+ static bool ExtentsToBsdiffPositionsString(
+ const google::protobuf::RepeatedPtrField<Extent>& extents,
+ uint64_t block_size,
+ uint64_t full_length,
+ std::string* positions_string);
+
+ // Returns true if a previous update attempt can be continued based on the
+ // persistent preferences and the new update check response hash.
+ static bool CanResumeUpdate(PrefsInterface* prefs,
+ std::string update_check_response_hash);
+
+ // Resets the persistent update progress state to indicate that an update
+ // can't be resumed. Performs a quick update-in-progress reset if |quick| is
+ // true, otherwise resets all progress-related update state. Returns true on
+ // success, false otherwise.
+ static bool ResetUpdateProgress(PrefsInterface* prefs, bool quick);
+
+ // Attempts to parse the update metadata starting from the beginning of
+ // |payload|. On success, returns kMetadataParseSuccess. Returns
+ // kMetadataParseInsufficientData if more data is needed to parse the complete
+ // metadata. Returns kMetadataParseError if the metadata can't be parsed given
+ // the payload.
+ MetadataParseResult ParsePayloadMetadata(const chromeos::Blob& payload,
+ ErrorCode* error);
+
+ void set_public_key_path(const std::string& public_key_path) {
+ public_key_path_ = public_key_path;
+ }
+
+ // Returns the byte offset at which the payload version can be found.
+ static uint64_t GetVersionOffset();
+
+ // Returns the byte offset where the size of the manifest is stored in
+ // a payload. This offset precedes the actual start of the manifest
+ // that's returned by the GetManifestOffset method.
+ static uint64_t GetManifestSizeOffset();
+
+ // Returns the byte offset at which the manifest protobuf begins in a
+ // payload.
+ static uint64_t GetManifestOffset();
+
+ // Returns the size of the payload metadata, which includes the payload header
+ // and the manifest. Is the header was not yet parsed, returns zero.
+ uint64_t GetMetadataSize() const;
+
+ // If the manifest was successfully parsed, copies it to |*out_manifest_p|.
+ // Returns true on success.
+ bool GetManifest(DeltaArchiveManifest* out_manifest_p) const;
+
+ // Returns the delta minor version. If this value is defined in the manifest,
+ // it returns that value, otherwise it returns the default value.
+ uint32_t GetMinorVersion() const;
+
+ private:
+ friend class DeltaPerformerTest;
+ FRIEND_TEST(DeltaPerformerTest, IsIdempotentOperationTest);
+ FRIEND_TEST(DeltaPerformerTest, UsePublicKeyFromResponse);
+
+ // Appends up to |*count_p| bytes from |*bytes_p| to |buffer_|, but only to
+ // the extent that the size of |buffer_| does not exceed |max|. Advances
+ // |*cbytes_p| and decreases |*count_p| by the actual number of bytes copied,
+ // and returns this number.
+ size_t CopyDataToBuffer(const char** bytes_p, size_t* count_p, size_t max);
+
+ // If |op_result| is false, emits an error message using |op_type_name| and
+ // sets |*error| accordingly. Otherwise does nothing. Returns |op_result|.
+ bool HandleOpResult(bool op_result, const char* op_type_name,
+ ErrorCode* error);
+
+ // Logs the progress of downloading/applying an update.
+ void LogProgress(const char* message_prefix);
+
+ // Update overall progress metrics, log as necessary.
+ void UpdateOverallProgress(bool force_log, const char* message_prefix);
+
+ // Verifies that the expected source partition hashes (if present) match the
+ // hashes for the current partitions. Returns true if there are no expected
+ // hashes in the payload (e.g., if it's a new-style full update) or if the
+ // hashes match; returns false otherwise.
+ bool VerifySourcePartitions();
+
+ // Returns true if enough of the delta file has been passed via Write()
+ // to be able to perform a given install operation.
+ bool CanPerformInstallOperation(
+ const DeltaArchiveManifest_InstallOperation& operation);
+
+ // Checks the integrity of the payload manifest. Returns true upon success,
+ // false otherwise.
+ ErrorCode ValidateManifest();
+
+ // Validates that the hash of the blobs corresponding to the given |operation|
+ // matches what's specified in the manifest in the payload.
+ // Returns ErrorCode::kSuccess on match or a suitable error code otherwise.
+ ErrorCode ValidateOperationHash(
+ const DeltaArchiveManifest_InstallOperation& operation);
+
+ // Interprets the given |protobuf| as a DeltaArchiveManifest protocol buffer
+ // of the given protobuf_length and verifies that the signed hash of the
+ // metadata matches what's specified in the install plan from Omaha.
+ // Returns ErrorCode::kSuccess on match or a suitable error code otherwise.
+ // This method must be called before any part of the |protobuf| is parsed
+ // so that a man-in-the-middle attack on the SSL connection to the payload
+ // server doesn't exploit any vulnerability in the code that parses the
+ // protocol buffer.
+ ErrorCode ValidateMetadataSignature(const void* protobuf,
+ uint64_t protobuf_length);
+
+ // Returns true on success.
+ bool PerformInstallOperation(
+ const DeltaArchiveManifest_InstallOperation& operation);
+
+ // These perform a specific type of operation and return true on success.
+ bool PerformReplaceOperation(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ bool is_kernel_partition);
+ bool PerformMoveOperation(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ bool is_kernel_partition);
+ bool PerformBsdiffOperation(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ bool is_kernel_partition);
+ bool PerformSourceCopyOperation(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ bool is_kernel_partition);
+ bool PerformSourceBsdiffOperation(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ bool is_kernel_partition);
+
+ // Returns true if the payload signature message has been extracted from
+ // |operation|, false otherwise.
+ bool ExtractSignatureMessage(
+ const DeltaArchiveManifest_InstallOperation& operation);
+
+ // Updates the hash calculator with the bytes in |buffer_|. Then discard the
+ // content, ensuring that memory is being deallocated. If |do_advance_offset|,
+ // advances the internal offset counter accordingly.
+ void DiscardBuffer(bool do_advance_offset);
+
+ // Checkpoints the update progress into persistent storage to allow this
+ // update attempt to be resumed after reboot.
+ bool CheckpointUpdateProgress();
+
+ // Primes the required update state. Returns true if the update state was
+ // successfully initialized to a saved resume state or if the update is a new
+ // update. Returns false otherwise.
+ bool PrimeUpdateState();
+
+ // Sends UMA statistics for the given error code.
+ void SendUmaStat(ErrorCode code);
+
+ // If the Omaha response contains a public RSA key and we're allowed
+ // to use it (e.g. if we're in developer mode), extract the key from
+ // the response and store it in a temporary file and return true. In
+ // the affirmative the path to the temporary file is stored in
+ // |out_tmp_key| and it is the responsibility of the caller to clean
+ // it up.
+ bool GetPublicKeyFromResponse(base::FilePath *out_tmp_key);
+
+ // Update Engine preference store.
+ PrefsInterface* prefs_;
+
+ // Global context of the system.
+ SystemState* system_state_;
+
+ // Install Plan based on Omaha Response.
+ InstallPlan* install_plan_;
+
+ // File descriptor of open device.
+ FileDescriptorPtr fd_;
+
+ // File descriptor of the kernel device.
+ FileDescriptorPtr kernel_fd_;
+
+ // File descriptor of the source device.
+ FileDescriptorPtr source_fd_;
+
+ // File descriptor of the source kernel device.
+ FileDescriptorPtr source_kernel_fd_;
+
+ std::string path_; // Path that fd_ refers to.
+ std::string kernel_path_; // Path that kernel_fd_ refers to.
+
+ DeltaArchiveManifest manifest_;
+ bool manifest_parsed_;
+ bool manifest_valid_;
+ uint64_t metadata_size_;
+
+ // Index of the next operation to perform in the manifest.
+ size_t next_operation_num_;
+
+ // A buffer used for accumulating downloaded data. Initially, it stores the
+ // payload metadata; once that's downloaded and parsed, it stores data for the
+ // next update operation.
+ chromeos::Blob buffer_;
+ // Offset of buffer_ in the binary blobs section of the update.
+ uint64_t buffer_offset_;
+
+ // Last |buffer_offset_| value updated as part of the progress update.
+ uint64_t last_updated_buffer_offset_;
+
+ // The block size (parsed from the manifest).
+ uint32_t block_size_;
+
+ // Calculates the payload hash.
+ OmahaHashCalculator hash_calculator_;
+
+ // Saves the signed hash context.
+ std::string signed_hash_context_;
+
+ // Signatures message blob extracted directly from the payload.
+ chromeos::Blob signatures_message_data_;
+
+ // The public key to be used. Provided as a member so that tests can
+ // override with test keys.
+ std::string public_key_path_;
+
+ // The number of bytes received so far, used for progress tracking.
+ size_t total_bytes_received_;
+
+ // The number rootfs and total operations in a payload, once we know them.
+ size_t num_rootfs_operations_;
+ size_t num_total_operations_;
+
+ // An overall progress counter, which should reflect both download progress
+ // and the ratio of applied operations. Range is 0-100.
+ unsigned overall_progress_;
+
+ // The last progress chunk recorded.
+ unsigned last_progress_chunk_;
+
+ // The timeout after which we should force emitting a progress log (constant),
+ // and the actual point in time for the next forced log to be emitted.
+ const base::TimeDelta forced_progress_log_wait_;
+ base::Time forced_progress_log_time_;
+
+ // The delta minor payload version supported by DeltaPerformer.
+ uint32_t supported_minor_version_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeltaPerformer);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_DELTA_PERFORMER_H_
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
new file mode 100644
index 0000000..5737422
--- /dev/null
+++ b/delta_performer_unittest.cc
@@ -0,0 +1,1416 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/delta_performer.h"
+
+#include <inttypes.h>
+#include <sys/mount.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_util.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_hardware.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/payload_verifier.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+using testing::Return;
+using testing::_;
+using test_utils::kRandomString;
+using test_utils::ScopedLoopMounter;
+using test_utils::System;
+
+extern const char* kUnittestPrivateKeyPath;
+extern const char* kUnittestPublicKeyPath;
+extern const char* kUnittestPrivateKey2Path;
+extern const char* kUnittestPublicKey2Path;
+
+static const char* kBogusMetadataSignature1 =
+ "awSFIUdUZz2VWFiR+ku0Pj00V7bPQPQFYQSXjEXr3vaw3TE4xHV5CraY3/YrZpBv"
+ "J5z4dSBskoeuaO1TNC/S6E05t+yt36tE4Fh79tMnJ/z9fogBDXWgXLEUyG78IEQr"
+ "YH6/eBsQGT2RJtBgXIXbZ9W+5G9KmGDoPOoiaeNsDuqHiBc/58OFsrxskH8E6vMS"
+ "BmMGGk82mvgzic7ApcoURbCGey1b3Mwne/hPZ/bb9CIyky8Og9IfFMdL2uAweOIR"
+ "fjoTeLYZpt+WN65Vu7jJ0cQN8e1y+2yka5112wpRf/LLtPgiAjEZnsoYpLUd7CoV"
+ "pLRtClp97kN2+tXGNBQqkA==";
+
+static const int kDefaultKernelSize = 4096; // Something small for a test
+static const uint8_t kNewData[] = {'T', 'h', 'i', 's', ' ', 'i', 's', ' ',
+ 'n', 'e', 'w', ' ', 'd', 'a', 't', 'a', '.'};
+
+namespace {
+struct DeltaState {
+ string a_img;
+ string b_img;
+ string result_img;
+ int image_size;
+
+ string delta_path;
+ uint64_t metadata_size;
+
+ string old_kernel;
+ chromeos::Blob old_kernel_data;
+
+ string new_kernel;
+ chromeos::Blob new_kernel_data;
+
+ string result_kernel;
+ chromeos::Blob result_kernel_data;
+
+ // The in-memory copy of delta file.
+ chromeos::Blob delta;
+
+ // The mock system state object with which we initialize the
+ // delta performer.
+ FakeSystemState fake_system_state;
+};
+
+enum SignatureTest {
+ kSignatureNone, // No payload signing.
+ kSignatureGenerator, // Sign the payload at generation time.
+ kSignatureGenerated, // Sign the payload after it's generated.
+ kSignatureGeneratedPlaceholder, // Insert placeholder signatures, then real.
+ kSignatureGeneratedPlaceholderMismatch, // Insert a wrong sized placeholder.
+ kSignatureGeneratedShell, // Sign the generated payload through shell cmds.
+ kSignatureGeneratedShellBadKey, // Sign with a bad key through shell cmds.
+ kSignatureGeneratedShellRotateCl1, // Rotate key, test client v1
+ kSignatureGeneratedShellRotateCl2, // Rotate key, test client v2
+};
+
+// Different options that determine what we should fill into the
+// install_plan.metadata_signature to simulate the contents received in the
+// Omaha response.
+enum MetadataSignatureTest {
+ kEmptyMetadataSignature,
+ kInvalidMetadataSignature,
+ kValidMetadataSignature,
+};
+
+enum OperationHashTest {
+ kInvalidOperationData,
+ kValidOperationData,
+};
+
+} // namespace
+
+class DeltaPerformerTest : public ::testing::Test {
+ public:
+ // Test helper placed where it can easily be friended from DeltaPerformer.
+ static void RunManifestValidation(const DeltaArchiveManifest& manifest,
+ bool full_payload,
+ ErrorCode expected) {
+ MockPrefs prefs;
+ InstallPlan install_plan;
+ FakeSystemState fake_system_state;
+ DeltaPerformer performer(&prefs, &fake_system_state, &install_plan);
+
+ // The install plan is for Full or Delta.
+ install_plan.is_full_update = full_payload;
+
+ // The Manifest we are validating.
+ performer.manifest_.CopyFrom(manifest);
+
+ EXPECT_EQ(expected, performer.ValidateManifest());
+ }
+
+ static void SetSupportedVersion(DeltaPerformer* performer,
+ uint64_t minor_version) {
+ performer->supported_minor_version_ = minor_version;
+ }
+};
+
+static void CompareFilesByBlock(const string& a_file, const string& b_file) {
+ chromeos::Blob a_data, b_data;
+ EXPECT_TRUE(utils::ReadFile(a_file, &a_data)) << "file failed: " << a_file;
+ EXPECT_TRUE(utils::ReadFile(b_file, &b_data)) << "file failed: " << b_file;
+
+ EXPECT_EQ(a_data.size(), b_data.size());
+ EXPECT_EQ(0, a_data.size() % kBlockSize);
+ for (size_t i = 0; i < a_data.size(); i += kBlockSize) {
+ EXPECT_EQ(0, i % kBlockSize);
+ chromeos::Blob a_sub(&a_data[i], &a_data[i + kBlockSize]);
+ chromeos::Blob b_sub(&b_data[i], &b_data[i + kBlockSize]);
+ EXPECT_TRUE(a_sub == b_sub) << "Block " << (i/kBlockSize) << " differs";
+ }
+}
+
+static bool WriteSparseFile(const string& path, off_t size) {
+ int fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644);
+ TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+ ScopedFdCloser fd_closer(&fd);
+ off_t rc = lseek(fd, size + 1, SEEK_SET);
+ TEST_AND_RETURN_FALSE_ERRNO(rc != static_cast<off_t>(-1));
+ int return_code = ftruncate(fd, size);
+ TEST_AND_RETURN_FALSE_ERRNO(return_code == 0);
+ return true;
+}
+
+static size_t GetSignatureSize(const string& private_key_path) {
+ const chromeos::Blob data(1, 'x');
+ chromeos::Blob hash;
+ EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(data, &hash));
+ chromeos::Blob signature;
+ EXPECT_TRUE(PayloadSigner::SignHash(hash,
+ private_key_path,
+ &signature));
+ return signature.size();
+}
+
+static bool InsertSignaturePlaceholder(int signature_size,
+ const string& payload_path,
+ uint64_t* out_metadata_size) {
+ vector<chromeos::Blob> signatures;
+ signatures.push_back(chromeos::Blob(signature_size, 0));
+
+ return PayloadSigner::AddSignatureToPayload(
+ payload_path,
+ signatures,
+ payload_path,
+ out_metadata_size);
+}
+
+static void SignGeneratedPayload(const string& payload_path,
+ uint64_t* out_metadata_size) {
+ int signature_size = GetSignatureSize(kUnittestPrivateKeyPath);
+ chromeos::Blob hash;
+ ASSERT_TRUE(PayloadSigner::HashPayloadForSigning(
+ payload_path,
+ vector<int>(1, signature_size),
+ &hash));
+ chromeos::Blob signature;
+ ASSERT_TRUE(PayloadSigner::SignHash(hash,
+ kUnittestPrivateKeyPath,
+ &signature));
+ ASSERT_TRUE(PayloadSigner::AddSignatureToPayload(
+ payload_path,
+ vector<chromeos::Blob>(1, signature),
+ payload_path,
+ out_metadata_size));
+ EXPECT_TRUE(PayloadVerifier::VerifySignedPayload(
+ payload_path,
+ kUnittestPublicKeyPath,
+ kSignatureMessageOriginalVersion));
+}
+
+static void SignGeneratedShellPayload(SignatureTest signature_test,
+ const string& payload_path) {
+ string private_key_path = kUnittestPrivateKeyPath;
+ if (signature_test == kSignatureGeneratedShellBadKey) {
+ ASSERT_TRUE(utils::MakeTempFile("key.XXXXXX",
+ &private_key_path,
+ nullptr));
+ } else {
+ ASSERT_TRUE(signature_test == kSignatureGeneratedShell ||
+ signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2);
+ }
+ ScopedPathUnlinker key_unlinker(private_key_path);
+ key_unlinker.set_should_remove(signature_test ==
+ kSignatureGeneratedShellBadKey);
+ // Generates a new private key that will not match the public key.
+ if (signature_test == kSignatureGeneratedShellBadKey) {
+ LOG(INFO) << "Generating a mismatched private key.";
+ ASSERT_EQ(0, System(base::StringPrintf(
+ "openssl genrsa -out %s 2048", private_key_path.c_str())));
+ }
+ int signature_size = GetSignatureSize(private_key_path);
+ string hash_file;
+ ASSERT_TRUE(utils::MakeTempFile("hash.XXXXXX", &hash_file, nullptr));
+ ScopedPathUnlinker hash_unlinker(hash_file);
+ string signature_size_string;
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2)
+ signature_size_string = base::StringPrintf("%d:%d",
+ signature_size, signature_size);
+ else
+ signature_size_string = base::StringPrintf("%d", signature_size);
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "./delta_generator -in_file=%s -signature_size=%s "
+ "-out_hash_file=%s",
+ payload_path.c_str(),
+ signature_size_string.c_str(),
+ hash_file.c_str())));
+
+ // Pad the hash
+ chromeos::Blob hash;
+ ASSERT_TRUE(utils::ReadFile(hash_file, &hash));
+ ASSERT_TRUE(PayloadVerifier::PadRSA2048SHA256Hash(&hash));
+ ASSERT_TRUE(test_utils::WriteFileVector(hash_file, hash));
+
+ string sig_file;
+ ASSERT_TRUE(utils::MakeTempFile("signature.XXXXXX", &sig_file, nullptr));
+ ScopedPathUnlinker sig_unlinker(sig_file);
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "openssl rsautl -raw -sign -inkey %s -in %s -out %s",
+ private_key_path.c_str(),
+ hash_file.c_str(),
+ sig_file.c_str())));
+ string sig_file2;
+ ASSERT_TRUE(utils::MakeTempFile("signature.XXXXXX", &sig_file2, nullptr));
+ ScopedPathUnlinker sig2_unlinker(sig_file2);
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2) {
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "openssl rsautl -raw -sign -inkey %s -in %s -out %s",
+ kUnittestPrivateKey2Path,
+ hash_file.c_str(),
+ sig_file2.c_str())));
+ // Append second sig file to first path
+ sig_file += ":" + sig_file2;
+ }
+
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "./delta_generator -in_file=%s -signature_file=%s "
+ "-out_file=%s",
+ payload_path.c_str(),
+ sig_file.c_str(),
+ payload_path.c_str())));
+ int verify_result =
+ System(base::StringPrintf(
+ "./delta_generator -in_file=%s -public_key=%s -public_key_version=%d",
+ payload_path.c_str(),
+ signature_test == kSignatureGeneratedShellRotateCl2 ?
+ kUnittestPublicKey2Path : kUnittestPublicKeyPath,
+ signature_test == kSignatureGeneratedShellRotateCl2 ? 2 : 1));
+ if (signature_test == kSignatureGeneratedShellBadKey) {
+ ASSERT_NE(0, verify_result);
+ } else {
+ ASSERT_EQ(0, verify_result);
+ }
+}
+
+static void GenerateDeltaFile(bool full_kernel,
+ bool full_rootfs,
+ bool noop,
+ off_t chunk_size,
+ SignatureTest signature_test,
+ DeltaState *state,
+ uint32_t minor_version) {
+ EXPECT_TRUE(utils::MakeTempFile("a_img.XXXXXX", &state->a_img, nullptr));
+ EXPECT_TRUE(utils::MakeTempFile("b_img.XXXXXX", &state->b_img, nullptr));
+
+ // result_img is used in minor version 2. Instead of applying the update
+ // in-place on A, we apply it to a new image, result_img.
+ EXPECT_TRUE(
+ utils::MakeTempFile("result_img.XXXXXX", &state->result_img, nullptr));
+ test_utils::CreateExtImageAtPath(state->a_img, nullptr);
+
+ state->image_size = static_cast<int>(utils::FileSize(state->a_img));
+
+ // Extend the "partitions" holding the file system a bit.
+ EXPECT_EQ(0, System(base::StringPrintf(
+ "dd if=/dev/zero of=%s seek=%d bs=1 count=1 status=none",
+ state->a_img.c_str(),
+ state->image_size + 1024 * 1024 - 1)));
+ EXPECT_EQ(state->image_size + 1024 * 1024, utils::FileSize(state->a_img));
+
+ // Create ImageInfo A & B
+ ImageInfo old_image_info;
+ ImageInfo new_image_info;
+
+ if (!full_rootfs) {
+ old_image_info.set_channel("src-channel");
+ old_image_info.set_board("src-board");
+ old_image_info.set_version("src-version");
+ old_image_info.set_key("src-key");
+ old_image_info.set_build_channel("src-build-channel");
+ old_image_info.set_build_version("src-build-version");
+ }
+
+ new_image_info.set_channel("test-channel");
+ new_image_info.set_board("test-board");
+ new_image_info.set_version("test-version");
+ new_image_info.set_key("test-key");
+ new_image_info.set_build_channel("test-build-channel");
+ new_image_info.set_build_version("test-build-version");
+
+ // Make some changes to the A image.
+ {
+ string a_mnt;
+ ScopedLoopMounter b_mounter(state->a_img, &a_mnt, 0);
+
+ chromeos::Blob hardtocompress;
+ while (hardtocompress.size() < 3 * kBlockSize) {
+ hardtocompress.insert(hardtocompress.end(),
+ std::begin(kRandomString), std::end(kRandomString));
+ }
+ EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/hardtocompress",
+ a_mnt.c_str()).c_str(),
+ hardtocompress.data(),
+ hardtocompress.size()));
+
+ chromeos::Blob zeros(16 * 1024, 0);
+ EXPECT_EQ(zeros.size(),
+ base::WriteFile(base::FilePath(base::StringPrintf(
+ "%s/move-to-sparse", a_mnt.c_str())),
+ reinterpret_cast<const char*>(zeros.data()),
+ zeros.size()));
+
+ EXPECT_TRUE(
+ WriteSparseFile(base::StringPrintf("%s/move-from-sparse",
+ a_mnt.c_str()), 16 * 1024));
+
+ EXPECT_EQ(0,
+ System(base::StringPrintf("dd if=/dev/zero of=%s/move-semi-sparse"
+ " bs=1 seek=4096 count=1 status=none",
+ a_mnt.c_str()).c_str()));
+
+ // Write 1 MiB of 0xff to try to catch the case where writing a bsdiff
+ // patch fails to zero out the final block.
+ chromeos::Blob ones(1024 * 1024, 0xff);
+ EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/ones",
+ a_mnt.c_str()).c_str(),
+ ones.data(),
+ ones.size()));
+ }
+
+ if (noop) {
+ EXPECT_TRUE(base::CopyFile(base::FilePath(state->a_img),
+ base::FilePath(state->b_img)));
+ old_image_info = new_image_info;
+ } else {
+ if (minor_version == kSourceMinorPayloadVersion) {
+ // Create a result image with image_size bytes of garbage, followed by
+ // zeroes after the rootfs, like image A and B have.
+ chromeos::Blob ones(state->image_size, 0xff);
+ ones.insert(ones.end(), 1024 * 1024, 0);
+ EXPECT_TRUE(utils::WriteFile(state->result_img.c_str(),
+ ones.data(),
+ ones.size()));
+ EXPECT_EQ(utils::FileSize(state->a_img),
+ utils::FileSize(state->result_img));
+ }
+
+ test_utils::CreateExtImageAtPath(state->b_img, nullptr);
+ EXPECT_EQ(0, System(base::StringPrintf(
+ "dd if=/dev/zero of=%s seek=%d bs=1 count=1 status=none",
+ state->b_img.c_str(),
+ state->image_size + 1024 * 1024 - 1)));
+ EXPECT_EQ(state->image_size + 1024 * 1024, utils::FileSize(state->b_img));
+
+ // Make some changes to the B image.
+ string b_mnt;
+ ScopedLoopMounter b_mounter(state->b_img, &b_mnt, 0);
+
+ EXPECT_EQ(0, System(base::StringPrintf("cp %s/hello %s/hello2",
+ b_mnt.c_str(),
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("rm %s/hello",
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("mv %s/hello2 %s/hello",
+ b_mnt.c_str(),
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("echo foo > %s/foo",
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("touch %s/emptyfile",
+ b_mnt.c_str()).c_str()));
+ EXPECT_TRUE(WriteSparseFile(base::StringPrintf("%s/fullsparse",
+ b_mnt.c_str()),
+ 1024 * 1024));
+
+ EXPECT_TRUE(
+ WriteSparseFile(base::StringPrintf("%s/move-to-sparse", b_mnt.c_str()),
+ 16 * 1024));
+
+ chromeos::Blob zeros(16 * 1024, 0);
+ EXPECT_EQ(zeros.size(),
+ base::WriteFile(base::FilePath(base::StringPrintf(
+ "%s/move-from-sparse", b_mnt.c_str())),
+ reinterpret_cast<const char*>(zeros.data()),
+ zeros.size()));
+
+ EXPECT_EQ(0, System(base::StringPrintf("dd if=/dev/zero "
+ "of=%s/move-semi-sparse "
+ "bs=1 seek=4096 count=1 status=none",
+ b_mnt.c_str()).c_str()));
+
+ EXPECT_EQ(0, System(base::StringPrintf("dd if=/dev/zero "
+ "of=%s/partsparse bs=1 "
+ "seek=4096 count=1 status=none",
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("cp %s/srchardlink0 %s/tmp && "
+ "mv %s/tmp %s/srchardlink1",
+ b_mnt.c_str(),
+ b_mnt.c_str(),
+ b_mnt.c_str(),
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(
+ base::StringPrintf("rm %s/boguslink && echo foobar > %s/boguslink",
+ b_mnt.c_str(), b_mnt.c_str()).c_str()));
+
+ chromeos::Blob hardtocompress;
+ while (hardtocompress.size() < 3 * kBlockSize) {
+ hardtocompress.insert(hardtocompress.end(),
+ std::begin(kRandomString), std::end(kRandomString));
+ }
+ EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/hardtocompress",
+ b_mnt.c_str()).c_str(),
+ hardtocompress.data(),
+ hardtocompress.size()));
+ }
+
+ string old_kernel;
+ EXPECT_TRUE(utils::MakeTempFile("old_kernel.XXXXXX",
+ &state->old_kernel,
+ nullptr));
+
+ string new_kernel;
+ EXPECT_TRUE(utils::MakeTempFile("new_kernel.XXXXXX",
+ &state->new_kernel,
+ nullptr));
+
+ string result_kernel;
+ EXPECT_TRUE(utils::MakeTempFile("result_kernel.XXXXXX",
+ &state->result_kernel,
+ nullptr));
+
+ state->old_kernel_data.resize(kDefaultKernelSize);
+ state->new_kernel_data.resize(state->old_kernel_data.size());
+ state->result_kernel_data.resize(state->old_kernel_data.size());
+ test_utils::FillWithData(&state->old_kernel_data);
+ test_utils::FillWithData(&state->new_kernel_data);
+ test_utils::FillWithData(&state->result_kernel_data);
+
+ // change the new kernel data
+ std::copy(std::begin(kNewData), std::end(kNewData),
+ state->new_kernel_data.begin());
+
+ if (noop) {
+ state->old_kernel_data = state->new_kernel_data;
+ }
+
+ // Write kernels to disk
+ EXPECT_TRUE(utils::WriteFile(state->old_kernel.c_str(),
+ state->old_kernel_data.data(),
+ state->old_kernel_data.size()));
+ EXPECT_TRUE(utils::WriteFile(state->new_kernel.c_str(),
+ state->new_kernel_data.data(),
+ state->new_kernel_data.size()));
+ EXPECT_TRUE(utils::WriteFile(state->result_kernel.c_str(),
+ state->result_kernel_data.data(),
+ state->result_kernel_data.size()));
+
+ EXPECT_TRUE(utils::MakeTempFile("delta.XXXXXX",
+ &state->delta_path,
+ nullptr));
+ LOG(INFO) << "delta path: " << state->delta_path;
+ {
+ const string private_key =
+ signature_test == kSignatureGenerator ? kUnittestPrivateKeyPath : "";
+
+ PayloadGenerationConfig payload_config;
+ payload_config.is_delta = !full_rootfs;
+ payload_config.chunk_size = chunk_size;
+ payload_config.rootfs_partition_size = kRootFSPartitionSize;
+ payload_config.minor_version = minor_version;
+ if (!full_rootfs) {
+ payload_config.source.rootfs.path = state->a_img;
+ if (!full_kernel)
+ payload_config.source.kernel.path = state->old_kernel;
+ payload_config.source.image_info = old_image_info;
+ EXPECT_TRUE(payload_config.source.LoadImageSize());
+ EXPECT_TRUE(payload_config.source.rootfs.OpenFilesystem());
+ EXPECT_TRUE(payload_config.source.kernel.OpenFilesystem());
+ } else {
+ if (payload_config.chunk_size == -1)
+ payload_config.chunk_size = kDefaultChunkSize;
+ }
+ payload_config.target.rootfs.path = state->b_img;
+ payload_config.target.kernel.path = state->new_kernel;
+ payload_config.target.image_info = new_image_info;
+ EXPECT_TRUE(payload_config.target.LoadImageSize());
+ EXPECT_TRUE(payload_config.target.rootfs.OpenFilesystem());
+ EXPECT_TRUE(payload_config.target.kernel.OpenFilesystem());
+
+ EXPECT_TRUE(payload_config.Validate());
+ EXPECT_TRUE(
+ GenerateUpdatePayloadFile(
+ payload_config,
+ state->delta_path,
+ private_key,
+ &state->metadata_size));
+ }
+
+ if (signature_test == kSignatureGeneratedPlaceholder ||
+ signature_test == kSignatureGeneratedPlaceholderMismatch) {
+ int signature_size = GetSignatureSize(kUnittestPrivateKeyPath);
+ LOG(INFO) << "Inserting placeholder signature.";
+ ASSERT_TRUE(InsertSignaturePlaceholder(signature_size, state->delta_path,
+ &state->metadata_size));
+
+ if (signature_test == kSignatureGeneratedPlaceholderMismatch) {
+ signature_size -= 1;
+ LOG(INFO) << "Inserting mismatched placeholder signature.";
+ ASSERT_FALSE(InsertSignaturePlaceholder(signature_size, state->delta_path,
+ &state->metadata_size));
+ return;
+ }
+ }
+
+ if (signature_test == kSignatureGenerated ||
+ signature_test == kSignatureGeneratedPlaceholder ||
+ signature_test == kSignatureGeneratedPlaceholderMismatch) {
+ // Generate the signed payload and update the metadata size in state to
+ // reflect the new size after adding the signature operation to the
+ // manifest.
+ LOG(INFO) << "Signing payload.";
+ SignGeneratedPayload(state->delta_path, &state->metadata_size);
+ } else if (signature_test == kSignatureGeneratedShell ||
+ signature_test == kSignatureGeneratedShellBadKey ||
+ signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2) {
+ SignGeneratedShellPayload(signature_test, state->delta_path);
+ }
+}
+
+static void ApplyDeltaFile(bool full_kernel, bool full_rootfs, bool noop,
+ SignatureTest signature_test, DeltaState* state,
+ bool hash_checks_mandatory,
+ OperationHashTest op_hash_test,
+ DeltaPerformer** performer,
+ uint32_t minor_version) {
+ // Check the metadata.
+ {
+ DeltaArchiveManifest manifest;
+ EXPECT_TRUE(PayloadVerifier::LoadPayload(state->delta_path,
+ &state->delta,
+ &manifest,
+ &state->metadata_size));
+ LOG(INFO) << "Metadata size: " << state->metadata_size;
+
+
+
+ if (signature_test == kSignatureNone) {
+ EXPECT_FALSE(manifest.has_signatures_offset());
+ EXPECT_FALSE(manifest.has_signatures_size());
+ } else {
+ EXPECT_TRUE(manifest.has_signatures_offset());
+ EXPECT_TRUE(manifest.has_signatures_size());
+ Signatures sigs_message;
+ EXPECT_TRUE(sigs_message.ParseFromArray(
+ &state->delta[state->metadata_size + manifest.signatures_offset()],
+ manifest.signatures_size()));
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2)
+ EXPECT_EQ(2, sigs_message.signatures_size());
+ else
+ EXPECT_EQ(1, sigs_message.signatures_size());
+ const Signatures_Signature& signature = sigs_message.signatures(0);
+ EXPECT_EQ(1, signature.version());
+
+ uint64_t expected_sig_data_length = 0;
+ vector<string> key_paths{kUnittestPrivateKeyPath};
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2) {
+ key_paths.push_back(kUnittestPrivateKey2Path);
+ }
+ EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+ key_paths,
+ &expected_sig_data_length));
+ EXPECT_EQ(expected_sig_data_length, manifest.signatures_size());
+ EXPECT_FALSE(signature.data().empty());
+ }
+
+ if (noop) {
+ EXPECT_EQ(0, manifest.install_operations_size());
+ EXPECT_EQ(1, manifest.kernel_install_operations_size());
+ }
+
+ if (full_kernel) {
+ EXPECT_FALSE(manifest.has_old_kernel_info());
+ } else {
+ EXPECT_EQ(state->old_kernel_data.size(),
+ manifest.old_kernel_info().size());
+ EXPECT_FALSE(manifest.old_kernel_info().hash().empty());
+ }
+
+ EXPECT_EQ(manifest.new_image_info().channel(), "test-channel");
+ EXPECT_EQ(manifest.new_image_info().board(), "test-board");
+ EXPECT_EQ(manifest.new_image_info().version(), "test-version");
+ EXPECT_EQ(manifest.new_image_info().key(), "test-key");
+ EXPECT_EQ(manifest.new_image_info().build_channel(), "test-build-channel");
+ EXPECT_EQ(manifest.new_image_info().build_version(), "test-build-version");
+
+ if (!full_rootfs) {
+ if (noop) {
+ EXPECT_EQ(manifest.old_image_info().channel(), "test-channel");
+ EXPECT_EQ(manifest.old_image_info().board(), "test-board");
+ EXPECT_EQ(manifest.old_image_info().version(), "test-version");
+ EXPECT_EQ(manifest.old_image_info().key(), "test-key");
+ EXPECT_EQ(manifest.old_image_info().build_channel(),
+ "test-build-channel");
+ EXPECT_EQ(manifest.old_image_info().build_version(),
+ "test-build-version");
+ } else {
+ EXPECT_EQ(manifest.old_image_info().channel(), "src-channel");
+ EXPECT_EQ(manifest.old_image_info().board(), "src-board");
+ EXPECT_EQ(manifest.old_image_info().version(), "src-version");
+ EXPECT_EQ(manifest.old_image_info().key(), "src-key");
+ EXPECT_EQ(manifest.old_image_info().build_channel(),
+ "src-build-channel");
+ EXPECT_EQ(manifest.old_image_info().build_version(),
+ "src-build-version");
+ }
+ }
+
+
+ if (full_rootfs) {
+ EXPECT_FALSE(manifest.has_old_rootfs_info());
+ EXPECT_FALSE(manifest.has_old_image_info());
+ EXPECT_TRUE(manifest.has_new_image_info());
+ } else {
+ EXPECT_EQ(state->image_size, manifest.old_rootfs_info().size());
+ EXPECT_FALSE(manifest.old_rootfs_info().hash().empty());
+ }
+
+ EXPECT_EQ(state->new_kernel_data.size(), manifest.new_kernel_info().size());
+ EXPECT_EQ(state->image_size, manifest.new_rootfs_info().size());
+
+ EXPECT_FALSE(manifest.new_kernel_info().hash().empty());
+ EXPECT_FALSE(manifest.new_rootfs_info().hash().empty());
+ }
+
+ MockPrefs prefs;
+ EXPECT_CALL(prefs, SetInt64(kPrefsManifestMetadataSize,
+ state->metadata_size)).WillOnce(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextOperation, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, GetInt64(kPrefsUpdateStateNextOperation, _))
+ .WillOnce(Return(false));
+ EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataOffset, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataLength, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSHA256Context, _))
+ .WillRepeatedly(Return(true));
+ if (op_hash_test == kValidOperationData && signature_test != kSignatureNone) {
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignedSHA256Context, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignatureBlob, _))
+ .WillOnce(Return(true));
+ }
+
+ // Update the A image in place.
+ InstallPlan install_plan;
+ install_plan.hash_checks_mandatory = hash_checks_mandatory;
+ install_plan.metadata_size = state->metadata_size;
+ install_plan.is_full_update = full_kernel && full_rootfs;
+ install_plan.source_path = state->a_img.c_str();
+ install_plan.kernel_source_path = state->old_kernel.c_str();
+
+ LOG(INFO) << "Setting payload metadata size in Omaha = "
+ << state->metadata_size;
+ ASSERT_TRUE(PayloadSigner::GetMetadataSignature(
+ state->delta.data(),
+ state->metadata_size,
+ kUnittestPrivateKeyPath,
+ &install_plan.metadata_signature));
+ EXPECT_FALSE(install_plan.metadata_signature.empty());
+
+ *performer = new DeltaPerformer(&prefs,
+ &state->fake_system_state,
+ &install_plan);
+ EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
+ (*performer)->set_public_key_path(kUnittestPublicKeyPath);
+ DeltaPerformerTest::SetSupportedVersion(*performer, minor_version);
+
+ EXPECT_EQ(state->image_size,
+ OmahaHashCalculator::RawHashOfFile(
+ state->a_img,
+ state->image_size,
+ &install_plan.source_rootfs_hash));
+ EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(
+ state->old_kernel_data,
+ &install_plan.source_kernel_hash));
+
+ // With minor version 2, we want the target to be the new image, result_img,
+ // but with version 1, we want to update A in place.
+ if (minor_version == kSourceMinorPayloadVersion) {
+ EXPECT_EQ(0, (*performer)->Open(state->result_img.c_str(), 0, 0));
+ EXPECT_TRUE((*performer)->OpenKernel(state->result_kernel.c_str()));
+ } else {
+ EXPECT_EQ(0, (*performer)->Open(state->a_img.c_str(), 0, 0));
+ EXPECT_TRUE((*performer)->OpenKernel(state->old_kernel.c_str()));
+ }
+
+
+ ErrorCode expected_error, actual_error;
+ bool continue_writing;
+ switch (op_hash_test) {
+ case kInvalidOperationData: {
+ // Muck with some random offset post the metadata size so that
+ // some operation hash will result in a mismatch.
+ int some_offset = state->metadata_size + 300;
+ LOG(INFO) << "Tampered value at offset: " << some_offset;
+ state->delta[some_offset]++;
+ expected_error = ErrorCode::kDownloadOperationHashMismatch;
+ continue_writing = false;
+ break;
+ }
+
+ case kValidOperationData:
+ default:
+ // no change.
+ expected_error = ErrorCode::kSuccess;
+ continue_writing = true;
+ break;
+ }
+
+ // Write at some number of bytes per operation. Arbitrarily chose 5.
+ const size_t kBytesPerWrite = 5;
+ for (size_t i = 0; i < state->delta.size(); i += kBytesPerWrite) {
+ size_t count = std::min(state->delta.size() - i, kBytesPerWrite);
+ bool write_succeeded = ((*performer)->Write(&state->delta[i],
+ count,
+ &actual_error));
+ // Normally write_succeeded should be true every time and
+ // actual_error should be ErrorCode::kSuccess. If so, continue the loop.
+ // But if we seeded an operation hash error above, then write_succeeded
+ // will be false. The failure may happen at any operation n. So, all
+ // Writes until n-1 should succeed and the nth operation will fail with
+ // actual_error. In this case, we should bail out of the loop because
+ // we cannot proceed applying the delta.
+ if (!write_succeeded) {
+ LOG(INFO) << "Write failed. Checking if it failed with expected error";
+ EXPECT_EQ(expected_error, actual_error);
+ if (!continue_writing) {
+ LOG(INFO) << "Cannot continue writing. Bailing out.";
+ break;
+ }
+ }
+
+ EXPECT_EQ(ErrorCode::kSuccess, actual_error);
+ }
+
+ // If we had continued all the way through, Close should succeed.
+ // Otherwise, it should fail. Check appropriately.
+ bool close_result = (*performer)->Close();
+ if (continue_writing)
+ EXPECT_EQ(0, close_result);
+ else
+ EXPECT_LE(0, close_result);
+}
+
+void VerifyPayloadResult(DeltaPerformer* performer,
+ DeltaState* state,
+ ErrorCode expected_result,
+ uint32_t minor_version) {
+ if (!performer) {
+ EXPECT_TRUE(!"Skipping payload verification since performer is null.");
+ return;
+ }
+
+ int expected_times = (expected_result == ErrorCode::kSuccess) ? 1 : 0;
+ EXPECT_CALL(*(state->fake_system_state.mock_payload_state()),
+ DownloadComplete()).Times(expected_times);
+
+ LOG(INFO) << "Verifying payload for expected result "
+ << expected_result;
+ EXPECT_EQ(expected_result, performer->VerifyPayload(
+ OmahaHashCalculator::OmahaHashOfData(state->delta),
+ state->delta.size()));
+ LOG(INFO) << "Verified payload.";
+
+ if (expected_result != ErrorCode::kSuccess) {
+ // no need to verify new partition if VerifyPayload failed.
+ return;
+ }
+
+ chromeos::Blob updated_kernel_partition;
+ if (minor_version == kSourceMinorPayloadVersion) {
+ CompareFilesByBlock(state->result_kernel, state->new_kernel);
+ CompareFilesByBlock(state->result_img, state->b_img);
+ EXPECT_TRUE(utils::ReadFile(state->result_kernel,
+ &updated_kernel_partition));
+ } else {
+ CompareFilesByBlock(state->old_kernel, state->new_kernel);
+ CompareFilesByBlock(state->a_img, state->b_img);
+ EXPECT_TRUE(utils::ReadFile(state->old_kernel, &updated_kernel_partition));
+ }
+
+ ASSERT_GE(updated_kernel_partition.size(), arraysize(kNewData));
+ EXPECT_TRUE(std::equal(std::begin(kNewData), std::end(kNewData),
+ updated_kernel_partition.begin()));
+
+ uint64_t new_kernel_size;
+ chromeos::Blob new_kernel_hash;
+ uint64_t new_rootfs_size;
+ chromeos::Blob new_rootfs_hash;
+ EXPECT_TRUE(performer->GetNewPartitionInfo(&new_kernel_size,
+ &new_kernel_hash,
+ &new_rootfs_size,
+ &new_rootfs_hash));
+ EXPECT_EQ(kDefaultKernelSize, new_kernel_size);
+ chromeos::Blob expected_new_kernel_hash;
+ EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(state->new_kernel_data,
+ &expected_new_kernel_hash));
+ EXPECT_TRUE(expected_new_kernel_hash == new_kernel_hash);
+ EXPECT_EQ(state->image_size, new_rootfs_size);
+ chromeos::Blob expected_new_rootfs_hash;
+ EXPECT_EQ(state->image_size,
+ OmahaHashCalculator::RawHashOfFile(state->b_img,
+ state->image_size,
+ &expected_new_rootfs_hash));
+ EXPECT_TRUE(expected_new_rootfs_hash == new_rootfs_hash);
+}
+
+void VerifyPayload(DeltaPerformer* performer,
+ DeltaState* state,
+ SignatureTest signature_test,
+ uint32_t minor_version) {
+ ErrorCode expected_result = ErrorCode::kSuccess;
+ switch (signature_test) {
+ case kSignatureNone:
+ expected_result = ErrorCode::kSignedDeltaPayloadExpectedError;
+ break;
+ case kSignatureGeneratedShellBadKey:
+ expected_result = ErrorCode::kDownloadPayloadPubKeyVerificationError;
+ break;
+ default: break; // appease gcc
+ }
+
+ VerifyPayloadResult(performer, state, expected_result, minor_version);
+}
+
+void DoSmallImageTest(bool full_kernel, bool full_rootfs, bool noop,
+ off_t chunk_size,
+ SignatureTest signature_test,
+ bool hash_checks_mandatory, uint32_t minor_version) {
+ DeltaState state;
+ DeltaPerformer *performer = nullptr;
+ GenerateDeltaFile(full_kernel, full_rootfs, noop, chunk_size,
+ signature_test, &state, minor_version);
+
+ ScopedPathUnlinker a_img_unlinker(state.a_img);
+ ScopedPathUnlinker b_img_unlinker(state.b_img);
+ ScopedPathUnlinker new_img_unlinker(state.result_img);
+ ScopedPathUnlinker delta_unlinker(state.delta_path);
+ ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
+ ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
+ ScopedPathUnlinker result_kernel_unlinker(state.result_kernel);
+ ApplyDeltaFile(full_kernel, full_rootfs, noop, signature_test,
+ &state, hash_checks_mandatory, kValidOperationData,
+ &performer, minor_version);
+ VerifyPayload(performer, &state, signature_test, minor_version);
+ delete performer;
+}
+
+// Calls delta performer's Write method by pretending to pass in bytes from a
+// delta file whose metadata size is actual_metadata_size and tests if all
+// checks are correctly performed if the install plan contains
+// expected_metadata_size and that the result of the parsing are as per
+// hash_checks_mandatory flag.
+void DoMetadataSizeTest(uint64_t expected_metadata_size,
+ uint64_t actual_metadata_size,
+ bool hash_checks_mandatory) {
+ MockPrefs prefs;
+ InstallPlan install_plan;
+ install_plan.hash_checks_mandatory = hash_checks_mandatory;
+ FakeSystemState fake_system_state;
+ DeltaPerformer performer(&prefs, &fake_system_state, &install_plan);
+ EXPECT_EQ(0, performer.Open("/dev/null", 0, 0));
+ EXPECT_TRUE(performer.OpenKernel("/dev/null"));
+
+ // Set a valid magic string and version number 1.
+ EXPECT_TRUE(performer.Write("CrAU", 4));
+ uint64_t version = htobe64(1);
+ EXPECT_TRUE(performer.Write(&version, 8));
+
+ install_plan.metadata_size = expected_metadata_size;
+ ErrorCode error_code;
+ // When filling in size in manifest, exclude the size of the 20-byte header.
+ uint64_t size_in_manifest = htobe64(actual_metadata_size - 20);
+ bool result = performer.Write(&size_in_manifest, 8, &error_code);
+ if (expected_metadata_size == actual_metadata_size ||
+ !hash_checks_mandatory) {
+ EXPECT_TRUE(result);
+ } else {
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kDownloadInvalidMetadataSize, error_code);
+ }
+
+ EXPECT_LT(performer.Close(), 0);
+}
+
+// Generates a valid delta file but tests the delta performer by suppling
+// different metadata signatures as per omaha_metadata_signature flag and
+// sees if the result of the parsing are as per hash_checks_mandatory flag.
+void DoMetadataSignatureTest(MetadataSignatureTest metadata_signature_test,
+ SignatureTest signature_test,
+ bool hash_checks_mandatory) {
+ DeltaState state;
+
+ // Using kSignatureNone since it doesn't affect the results of our test.
+ // If we've to use other signature options, then we'd have to get the
+ // metadata size again after adding the signing operation to the manifest.
+ GenerateDeltaFile(true, true, false, -1, signature_test, &state,
+ DeltaPerformer::kFullPayloadMinorVersion);
+
+ ScopedPathUnlinker a_img_unlinker(state.a_img);
+ ScopedPathUnlinker b_img_unlinker(state.b_img);
+ ScopedPathUnlinker delta_unlinker(state.delta_path);
+ ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
+ ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
+
+ // Loads the payload and parses the manifest.
+ chromeos::Blob payload;
+ EXPECT_TRUE(utils::ReadFile(state.delta_path, &payload));
+ LOG(INFO) << "Payload size: " << payload.size();
+
+ InstallPlan install_plan;
+ install_plan.hash_checks_mandatory = hash_checks_mandatory;
+ install_plan.metadata_size = state.metadata_size;
+
+ DeltaPerformer::MetadataParseResult expected_result, actual_result;
+ ErrorCode expected_error, actual_error;
+
+ // Fill up the metadata signature in install plan according to the test.
+ switch (metadata_signature_test) {
+ case kEmptyMetadataSignature:
+ install_plan.metadata_signature.clear();
+ expected_result = DeltaPerformer::kMetadataParseError;
+ expected_error = ErrorCode::kDownloadMetadataSignatureMissingError;
+ break;
+
+ case kInvalidMetadataSignature:
+ install_plan.metadata_signature = kBogusMetadataSignature1;
+ expected_result = DeltaPerformer::kMetadataParseError;
+ expected_error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ break;
+
+ case kValidMetadataSignature:
+ default:
+ // Set the install plan's metadata size to be the same as the one
+ // in the manifest so that we pass the metadata size checks. Only
+ // then we can get to manifest signature checks.
+ ASSERT_TRUE(PayloadSigner::GetMetadataSignature(
+ payload.data(),
+ state.metadata_size,
+ kUnittestPrivateKeyPath,
+ &install_plan.metadata_signature));
+ EXPECT_FALSE(install_plan.metadata_signature.empty());
+ expected_result = DeltaPerformer::kMetadataParseSuccess;
+ expected_error = ErrorCode::kSuccess;
+ break;
+ }
+
+ // Ignore the expected result/error if hash checks are not mandatory.
+ if (!hash_checks_mandatory) {
+ expected_result = DeltaPerformer::kMetadataParseSuccess;
+ expected_error = ErrorCode::kSuccess;
+ }
+
+ // Create the delta performer object.
+ MockPrefs prefs;
+ DeltaPerformer delta_performer(&prefs,
+ &state.fake_system_state,
+ &install_plan);
+
+ // Use the public key corresponding to the private key used above to
+ // sign the metadata.
+ EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
+ delta_performer.set_public_key_path(kUnittestPublicKeyPath);
+
+ // Init actual_error with an invalid value so that we make sure
+ // ParsePayloadMetadata properly populates it in all cases.
+ actual_error = ErrorCode::kUmaReportedMax;
+ actual_result = delta_performer.ParsePayloadMetadata(payload, &actual_error);
+
+ EXPECT_EQ(expected_result, actual_result);
+ EXPECT_EQ(expected_error, actual_error);
+
+ // Check that the parsed metadata size is what's expected. This test
+ // implicitly confirms that the metadata signature is valid, if required.
+ EXPECT_EQ(state.metadata_size, delta_performer.GetMetadataSize());
+}
+
+void DoOperationHashMismatchTest(OperationHashTest op_hash_test,
+ bool hash_checks_mandatory) {
+ DeltaState state;
+ uint64_t minor_version = DeltaPerformer::kFullPayloadMinorVersion;
+ GenerateDeltaFile(true, true, false, -1, kSignatureGenerated, &state,
+ minor_version);
+ ScopedPathUnlinker a_img_unlinker(state.a_img);
+ ScopedPathUnlinker b_img_unlinker(state.b_img);
+ ScopedPathUnlinker delta_unlinker(state.delta_path);
+ ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
+ ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
+ DeltaPerformer *performer = nullptr;
+ ApplyDeltaFile(true, true, false, kSignatureGenerated, &state,
+ hash_checks_mandatory, op_hash_test, &performer,
+ minor_version);
+ delete performer;
+}
+
+TEST(DeltaPerformerTest, ExtentsToByteStringTest) {
+ uint64_t test[] = {1, 1, 4, 2, 0, 1};
+ COMPILE_ASSERT(arraysize(test) % 2 == 0, array_size_uneven);
+ const uint64_t block_size = 4096;
+ const uint64_t file_length = 4 * block_size - 13;
+
+ google::protobuf::RepeatedPtrField<Extent> extents;
+ for (size_t i = 0; i < arraysize(test); i += 2) {
+ Extent* extent = extents.Add();
+ extent->set_start_block(test[i]);
+ extent->set_num_blocks(test[i + 1]);
+ }
+
+ string expected_output = "4096:4096,16384:8192,0:4083";
+ string actual_output;
+ EXPECT_TRUE(DeltaPerformer::ExtentsToBsdiffPositionsString(extents,
+ block_size,
+ file_length,
+ &actual_output));
+ EXPECT_EQ(expected_output, actual_output);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestFullGoodTest) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+ manifest.mutable_new_kernel_info();
+ manifest.mutable_new_rootfs_info();
+ manifest.set_minor_version(DeltaPerformer::kFullPayloadMinorVersion);
+
+ DeltaPerformerTest::RunManifestValidation(manifest, true,
+ ErrorCode::kSuccess);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestDeltaGoodTest) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+ manifest.mutable_old_kernel_info();
+ manifest.mutable_old_rootfs_info();
+ manifest.mutable_new_kernel_info();
+ manifest.mutable_new_rootfs_info();
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+ DeltaPerformerTest::RunManifestValidation(manifest, false,
+ ErrorCode::kSuccess);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestFullUnsetMinorVersion) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+
+ DeltaPerformerTest::RunManifestValidation(manifest, true,
+ ErrorCode::kSuccess);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestDeltaUnsetMinorVersion) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+
+ DeltaPerformerTest::RunManifestValidation(
+ manifest, false,
+ ErrorCode::kUnsupportedMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestFullOldKernelTest) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+ manifest.mutable_old_kernel_info();
+ manifest.mutable_new_kernel_info();
+ manifest.mutable_new_rootfs_info();
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+ DeltaPerformerTest::RunManifestValidation(
+ manifest, true,
+ ErrorCode::kPayloadMismatchedType);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestFullOldRootfsTest) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+ manifest.mutable_old_rootfs_info();
+ manifest.mutable_new_kernel_info();
+ manifest.mutable_new_rootfs_info();
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+ DeltaPerformerTest::RunManifestValidation(
+ manifest, true,
+ ErrorCode::kPayloadMismatchedType);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestBadMinorVersion) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+
+ // Generate a bad version number.
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion +
+ 10000);
+
+ DeltaPerformerTest::RunManifestValidation(
+ manifest, false,
+ ErrorCode::kUnsupportedMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignaturePlaceholderTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedPlaceholder,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignaturePlaceholderMismatchTest) {
+ DeltaState state;
+ GenerateDeltaFile(false, false, false, -1,
+ kSignatureGeneratedPlaceholderMismatch, &state,
+ kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageChunksTest) {
+ DoSmallImageTest(false, false, false, kBlockSize, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootFullKernelSmallImageTest) {
+ DoSmallImageTest(true, false, false, -1, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootFullSmallImageTest) {
+ DoSmallImageTest(true, true, false, -1, kSignatureGenerator,
+ true, DeltaPerformer::kFullPayloadMinorVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootNoopSmallImageTest) {
+ DoSmallImageTest(false, false, true, -1, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignNoneTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureNone,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGenerated,
+ true, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedShellTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShell,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedShellBadKeyTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellBadKey,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedShellRotateCl1Test) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellRotateCl1,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedShellRotateCl2Test) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellRotateCl2,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSourceOpsTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGenerator,
+ false, kSourceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, BadDeltaMagicTest) {
+ MockPrefs prefs;
+ InstallPlan install_plan;
+ FakeSystemState fake_system_state;
+ DeltaPerformer performer(&prefs, &fake_system_state, &install_plan);
+ EXPECT_EQ(0, performer.Open("/dev/null", 0, 0));
+ EXPECT_TRUE(performer.OpenKernel("/dev/null"));
+ EXPECT_TRUE(performer.Write("junk", 4));
+ EXPECT_TRUE(performer.Write("morejunk", 8));
+ EXPECT_FALSE(performer.Write("morejunk", 8));
+ EXPECT_LT(performer.Close(), 0);
+}
+
+TEST(DeltaPerformerTest, WriteUpdatesPayloadState) {
+ MockPrefs prefs;
+ InstallPlan install_plan;
+ FakeSystemState fake_system_state;
+ DeltaPerformer performer(&prefs, &fake_system_state, &install_plan);
+ EXPECT_EQ(0, performer.Open("/dev/null", 0, 0));
+ EXPECT_TRUE(performer.OpenKernel("/dev/null"));
+
+ EXPECT_CALL(*(fake_system_state.mock_payload_state()),
+ DownloadProgress(4)).Times(1);
+ EXPECT_CALL(*(fake_system_state.mock_payload_state()),
+ DownloadProgress(8)).Times(2);
+
+ EXPECT_TRUE(performer.Write("junk", 4));
+ EXPECT_TRUE(performer.Write("morejunk", 8));
+ EXPECT_FALSE(performer.Write("morejunk", 8));
+ EXPECT_LT(performer.Close(), 0);
+}
+
+TEST(DeltaPerformerTest, MissingMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(0, 75456, true);
+}
+
+TEST(DeltaPerformerTest, MissingNonMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(0, 123456, false);
+}
+
+TEST(DeltaPerformerTest, InvalidMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(13000, 140000, true);
+}
+
+TEST(DeltaPerformerTest, InvalidNonMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(40000, 50000, false);
+}
+
+TEST(DeltaPerformerTest, ValidMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(85376, 85376, true);
+}
+
+TEST(DeltaPerformerTest, RunAsRootMandatoryEmptyMetadataSignatureTest) {
+ DoMetadataSignatureTest(kEmptyMetadataSignature, kSignatureGenerated, true);
+}
+
+TEST(DeltaPerformerTest, RunAsRootNonMandatoryEmptyMetadataSignatureTest) {
+ DoMetadataSignatureTest(kEmptyMetadataSignature, kSignatureGenerated, false);
+}
+
+TEST(DeltaPerformerTest, RunAsRootMandatoryInvalidMetadataSignatureTest) {
+ DoMetadataSignatureTest(kInvalidMetadataSignature, kSignatureGenerated, true);
+}
+
+TEST(DeltaPerformerTest, RunAsRootNonMandatoryInvalidMetadataSignatureTest) {
+ DoMetadataSignatureTest(kInvalidMetadataSignature, kSignatureGenerated,
+ false);
+}
+
+TEST(DeltaPerformerTest, RunAsRootMandatoryValidMetadataSignature1Test) {
+ DoMetadataSignatureTest(kValidMetadataSignature, kSignatureNone, true);
+}
+
+TEST(DeltaPerformerTest, RunAsRootMandatoryValidMetadataSignature2Test) {
+ DoMetadataSignatureTest(kValidMetadataSignature, kSignatureGenerated, true);
+}
+
+TEST(DeltaPerformerTest, RunAsRootNonMandatoryValidMetadataSignatureTest) {
+ DoMetadataSignatureTest(kValidMetadataSignature, kSignatureGenerated, false);
+}
+
+TEST(DeltaPerformerTest, RunAsRootMandatoryOperationHashMismatchTest) {
+ DoOperationHashMismatchTest(kInvalidOperationData, true);
+}
+
+TEST(DeltaPerformerTest, UsePublicKeyFromResponse) {
+ MockPrefs prefs;
+ FakeSystemState fake_system_state;
+ InstallPlan install_plan;
+ base::FilePath key_path;
+
+ // The result of the GetPublicKeyResponse() method is based on three things
+ //
+ // 1. Whether it's an official build; and
+ // 2. Whether the Public RSA key to be used is in the root filesystem; and
+ // 3. Whether the response has a public key
+ //
+ // We test all eight combinations to ensure that we only use the
+ // public key in the response if
+ //
+ // a. it's not an official build; and
+ // b. there is no key in the root filesystem.
+
+ DeltaPerformer *performer = new DeltaPerformer(&prefs,
+ &fake_system_state,
+ &install_plan);
+ FakeHardware* fake_hardware = fake_system_state.fake_hardware();
+
+ string temp_dir;
+ EXPECT_TRUE(utils::MakeTempDirectory("PublicKeyFromResponseTests.XXXXXX",
+ &temp_dir));
+ string non_existing_file = temp_dir + "/non-existing";
+ string existing_file = temp_dir + "/existing";
+ EXPECT_EQ(0, System(base::StringPrintf("touch %s", existing_file.c_str())));
+
+ // Non-official build, non-existing public-key, key in response -> true
+ fake_hardware->SetIsOfficialBuild(false);
+ performer->public_key_path_ = non_existing_file;
+ install_plan.public_key_rsa = "VGVzdAo="; // result of 'echo "Test" | base64'
+ EXPECT_TRUE(performer->GetPublicKeyFromResponse(&key_path));
+ EXPECT_FALSE(key_path.empty());
+ EXPECT_EQ(unlink(key_path.value().c_str()), 0);
+ // Same with official build -> false
+ fake_hardware->SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, existing public-key, key in response -> false
+ fake_hardware->SetIsOfficialBuild(false);
+ performer->public_key_path_ = existing_file;
+ install_plan.public_key_rsa = "VGVzdAo="; // result of 'echo "Test" | base64'
+ EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+ // Same with official build -> false
+ fake_hardware->SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, non-existing public-key, no key in response -> false
+ fake_hardware->SetIsOfficialBuild(false);
+ performer->public_key_path_ = non_existing_file;
+ install_plan.public_key_rsa = "";
+ EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+ // Same with official build -> false
+ fake_hardware->SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, existing public-key, no key in response -> false
+ fake_hardware->SetIsOfficialBuild(false);
+ performer->public_key_path_ = existing_file;
+ install_plan.public_key_rsa = "";
+ EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+ // Same with official build -> false
+ fake_hardware->SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, non-existing public-key, key in response
+ // but invalid base64 -> false
+ fake_hardware->SetIsOfficialBuild(false);
+ performer->public_key_path_ = non_existing_file;
+ install_plan.public_key_rsa = "not-valid-base64";
+ EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+
+ delete performer;
+ EXPECT_TRUE(test_utils::RecursiveUnlinkDir(temp_dir));
+}
+
+TEST(DeltaPerformerTest, MinorVersionsMatch) {
+ // Test that the minor version in update_engine.conf that is installed to
+ // the image matches the supported delta minor version in the update engine.
+ uint32_t minor_version;
+ chromeos::KeyValueStore store;
+ EXPECT_TRUE(store.Load(base::FilePath("update_engine.conf")));
+ EXPECT_TRUE(utils::GetMinorVersion(store, &minor_version));
+ EXPECT_EQ(DeltaPerformer::kSupportedMinorPayloadVersion, minor_version);
+}
+
+} // namespace chromeos_update_engine
diff --git a/download_action.cc b/download_action.cc
new file mode 100644
index 0000000..8e6061c
--- /dev/null
+++ b/download_action.cc
@@ -0,0 +1,323 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/download_action.h"
+
+#include <errno.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/action_pipe.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/utils.h"
+
+using base::FilePath;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+DownloadAction::DownloadAction(PrefsInterface* prefs,
+ SystemState* system_state,
+ HttpFetcher* http_fetcher)
+ : prefs_(prefs),
+ system_state_(system_state),
+ http_fetcher_(http_fetcher),
+ writer_(nullptr),
+ code_(ErrorCode::kSuccess),
+ delegate_(nullptr),
+ bytes_received_(0),
+ p2p_sharing_fd_(-1),
+ p2p_visible_(true) {}
+
+DownloadAction::~DownloadAction() {}
+
+void DownloadAction::CloseP2PSharingFd(bool delete_p2p_file) {
+ if (p2p_sharing_fd_ != -1) {
+ if (close(p2p_sharing_fd_) != 0) {
+ PLOG(ERROR) << "Error closing p2p sharing fd";
+ }
+ p2p_sharing_fd_ = -1;
+ }
+
+ if (delete_p2p_file) {
+ FilePath path =
+ system_state_->p2p_manager()->FileGetPath(p2p_file_id_);
+ if (unlink(path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting p2p file " << path.value();
+ } else {
+ LOG(INFO) << "Deleted p2p file " << path.value();
+ }
+ }
+
+ // Don't use p2p from this point onwards.
+ p2p_file_id_.clear();
+}
+
+bool DownloadAction::SetupP2PSharingFd() {
+ P2PManager *p2p_manager = system_state_->p2p_manager();
+
+ if (!p2p_manager->FileShare(p2p_file_id_, install_plan_.payload_size)) {
+ LOG(ERROR) << "Unable to share file via p2p";
+ CloseP2PSharingFd(true); // delete p2p file
+ return false;
+ }
+
+ // File has already been created (and allocated, xattrs been
+ // populated etc.) by FileShare() so just open it for writing.
+ FilePath path = p2p_manager->FileGetPath(p2p_file_id_);
+ p2p_sharing_fd_ = open(path.value().c_str(), O_WRONLY);
+ if (p2p_sharing_fd_ == -1) {
+ PLOG(ERROR) << "Error opening file " << path.value();
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return false;
+ }
+
+ // Ensure file to share is world-readable, otherwise
+ // p2p-server and p2p-http-server can't access it.
+ //
+ // (Q: Why doesn't the file have mode 0644 already? A: Because
+ // the process-wide umask is set to 0700 in main.cc.)
+ if (fchmod(p2p_sharing_fd_, 0644) != 0) {
+ PLOG(ERROR) << "Error setting mode 0644 on " << path.value();
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return false;
+ }
+
+ // All good.
+ LOG(INFO) << "Writing payload contents to " << path.value();
+ p2p_manager->FileGetVisible(p2p_file_id_, &p2p_visible_);
+ return true;
+}
+
+void DownloadAction::WriteToP2PFile(const void* data,
+ size_t length,
+ off_t file_offset) {
+ if (p2p_sharing_fd_ == -1) {
+ if (!SetupP2PSharingFd())
+ return;
+ }
+
+ // Check that the file is at least |file_offset| bytes long - if
+ // it's not something is wrong and we must immediately delete the
+ // file to avoid propagating this problem to other peers.
+ //
+ // How can this happen? It could be that we're resuming an update
+ // after a system crash... in this case, it could be that
+ //
+ // 1. the p2p file didn't get properly synced to stable storage; or
+ // 2. the file was deleted at bootup (it's in /var/cache after all); or
+ // 3. other reasons
+ off_t p2p_size = utils::FileSize(p2p_sharing_fd_);
+ if (p2p_size < 0) {
+ PLOG(ERROR) << "Error getting file status for p2p file";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return;
+ }
+ if (p2p_size < file_offset) {
+ LOG(ERROR) << "Wanting to write to file offset " << file_offset
+ << " but existing p2p file is only " << p2p_size
+ << " bytes.";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return;
+ }
+
+ off_t cur_file_offset = lseek(p2p_sharing_fd_, file_offset, SEEK_SET);
+ if (cur_file_offset != static_cast<off_t>(file_offset)) {
+ PLOG(ERROR) << "Error seeking to position "
+ << file_offset << " in p2p file";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ } else {
+ // OK, seeking worked, now write the data
+ ssize_t bytes_written = write(p2p_sharing_fd_, data, length);
+ if (bytes_written != static_cast<ssize_t>(length)) {
+ PLOG(ERROR) << "Error writing "
+ << length << " bytes at file offset "
+ << file_offset << " in p2p file";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ }
+ }
+}
+
+void DownloadAction::PerformAction() {
+ http_fetcher_->set_delegate(this);
+
+ // Get the InstallPlan and read it
+ CHECK(HasInputObject());
+ install_plan_ = GetInputObject();
+ bytes_received_ = 0;
+
+ install_plan_.Dump();
+
+ if (writer_) {
+ LOG(INFO) << "Using writer for test.";
+ } else {
+ delta_performer_.reset(new DeltaPerformer(prefs_,
+ system_state_,
+ &install_plan_));
+ writer_ = delta_performer_.get();
+ }
+ int rc = writer_->Open(install_plan_.install_path.c_str(),
+ O_TRUNC | O_WRONLY | O_CREAT | O_LARGEFILE,
+ 0644);
+ if (rc < 0) {
+ LOG(ERROR) << "Unable to open output file " << install_plan_.install_path;
+ // report error to processor
+ processor_->ActionComplete(this, ErrorCode::kInstallDeviceOpenError);
+ return;
+ }
+ if (delta_performer_.get() &&
+ !delta_performer_->OpenKernel(
+ install_plan_.kernel_install_path.c_str())) {
+ LOG(ERROR) << "Unable to open kernel file "
+ << install_plan_.kernel_install_path.c_str();
+ writer_->Close();
+ processor_->ActionComplete(this, ErrorCode::kKernelDeviceOpenError);
+ return;
+ }
+ if (delegate_) {
+ delegate_->SetDownloadStatus(true); // Set to active.
+ }
+
+ if (system_state_ != nullptr) {
+ const PayloadStateInterface* payload_state = system_state_->payload_state();
+ string file_id = utils::CalculateP2PFileId(install_plan_.payload_hash,
+ install_plan_.payload_size);
+ if (payload_state->GetUsingP2PForSharing()) {
+ // If we're sharing the update, store the file_id to convey
+ // that we should write to the file.
+ p2p_file_id_ = file_id;
+ LOG(INFO) << "p2p file id: " << p2p_file_id_;
+ } else {
+ // Even if we're not sharing the update, it could be that
+ // there's a partial file from a previous attempt with the same
+ // hash. If this is the case, we NEED to clean it up otherwise
+ // we're essentially timing out other peers downloading from us
+ // (since we're never going to complete the file).
+ FilePath path = system_state_->p2p_manager()->FileGetPath(file_id);
+ if (!path.empty()) {
+ if (unlink(path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting p2p file " << path.value();
+ } else {
+ LOG(INFO) << "Deleting partial p2p file " << path.value()
+ << " since we're not using p2p to share.";
+ }
+ }
+ }
+
+ // Tweak timeouts on the HTTP fetcher if we're downloading from a
+ // local peer.
+ if (payload_state->GetUsingP2PForDownloading() &&
+ payload_state->GetP2PUrl() == install_plan_.download_url) {
+ LOG(INFO) << "Tweaking HTTP fetcher since we're downloading via p2p";
+ http_fetcher_->set_low_speed_limit(kDownloadP2PLowSpeedLimitBps,
+ kDownloadP2PLowSpeedTimeSeconds);
+ http_fetcher_->set_max_retry_count(kDownloadP2PMaxRetryCount);
+ http_fetcher_->set_connect_timeout(kDownloadP2PConnectTimeoutSeconds);
+ }
+ }
+
+ http_fetcher_->BeginTransfer(install_plan_.download_url);
+}
+
+void DownloadAction::TerminateProcessing() {
+ if (writer_) {
+ writer_->Close();
+ writer_ = nullptr;
+ }
+ if (delegate_) {
+ delegate_->SetDownloadStatus(false); // Set to inactive.
+ }
+ CloseP2PSharingFd(false); // Keep p2p file.
+ // Terminates the transfer. The action is terminated, if necessary, when the
+ // TransferTerminated callback is received.
+ http_fetcher_->TerminateTransfer();
+}
+
+void DownloadAction::SeekToOffset(off_t offset) {
+ bytes_received_ = offset;
+}
+
+void DownloadAction::ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes,
+ size_t length) {
+ // Note that bytes_received_ is the current offset.
+ if (!p2p_file_id_.empty()) {
+ WriteToP2PFile(bytes, length, bytes_received_);
+ }
+
+ bytes_received_ += length;
+ if (delegate_)
+ delegate_->BytesReceived(bytes_received_, install_plan_.payload_size);
+ if (writer_ && !writer_->Write(bytes, length, &code_)) {
+ LOG(ERROR) << "Error " << code_ << " in DeltaPerformer's Write method when "
+ << "processing the received payload -- Terminating processing";
+ // Delete p2p file, if applicable.
+ if (!p2p_file_id_.empty())
+ CloseP2PSharingFd(true);
+ // Don't tell the action processor that the action is complete until we get
+ // the TransferTerminated callback. Otherwise, this and the HTTP fetcher
+ // objects may get destroyed before all callbacks are complete.
+ TerminateProcessing();
+ return;
+ }
+
+ // Call p2p_manager_->FileMakeVisible() when we've successfully
+ // verified the manifest!
+ if (!p2p_visible_ &&
+ delta_performer_.get() && delta_performer_->IsManifestValid()) {
+ LOG(INFO) << "Manifest has been validated. Making p2p file visible.";
+ system_state_->p2p_manager()->FileMakeVisible(p2p_file_id_);
+ p2p_visible_ = true;
+ }
+}
+
+void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) {
+ if (writer_) {
+ LOG_IF(WARNING, writer_->Close() != 0) << "Error closing the writer.";
+ writer_ = nullptr;
+ }
+ if (delegate_) {
+ delegate_->SetDownloadStatus(false); // Set to inactive.
+ }
+ ErrorCode code =
+ successful ? ErrorCode::kSuccess : ErrorCode::kDownloadTransferError;
+ if (code == ErrorCode::kSuccess && delta_performer_.get()) {
+ code = delta_performer_->VerifyPayload(install_plan_.payload_hash,
+ install_plan_.payload_size);
+ if (code != ErrorCode::kSuccess) {
+ LOG(ERROR) << "Download of " << install_plan_.download_url
+ << " failed due to payload verification error.";
+ // Delete p2p file, if applicable.
+ if (!p2p_file_id_.empty())
+ CloseP2PSharingFd(true);
+ } else if (!delta_performer_->GetNewPartitionInfo(
+ &install_plan_.kernel_size,
+ &install_plan_.kernel_hash,
+ &install_plan_.rootfs_size,
+ &install_plan_.rootfs_hash)) {
+ LOG(ERROR) << "Unable to get new partition hash info.";
+ code = ErrorCode::kDownloadNewPartitionInfoError;
+ }
+ }
+
+ // Write the path to the output pipe if we're successful.
+ if (code == ErrorCode::kSuccess && HasOutputPipe())
+ SetOutputObject(install_plan_);
+ processor_->ActionComplete(this, code);
+}
+
+void DownloadAction::TransferTerminated(HttpFetcher *fetcher) {
+ if (code_ != ErrorCode::kSuccess) {
+ processor_->ActionComplete(this, code_);
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/download_action.h b/download_action.h
new file mode 100644
index 0000000..0d51d9a
--- /dev/null
+++ b/download_action.h
@@ -0,0 +1,154 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_DOWNLOAD_ACTION_H_
+#define UPDATE_ENGINE_DOWNLOAD_ACTION_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <string>
+
+#include <curl/curl.h>
+
+#include "update_engine/action.h"
+#include "update_engine/delta_performer.h"
+#include "update_engine/http_fetcher.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/system_state.h"
+
+// The Download Action downloads a specified url to disk. The url should point
+// to an update in a delta payload format. The payload will be piped into a
+// DeltaPerformer that will apply the delta to the disk.
+
+namespace chromeos_update_engine {
+
+class DownloadActionDelegate {
+ public:
+ virtual ~DownloadActionDelegate() = default;
+
+ // Called right before starting the download with |active| set to
+ // true. Called after completing the download with |active| set to
+ // false.
+ virtual void SetDownloadStatus(bool active) = 0;
+
+ // Called periodically after bytes are received. This method will be
+ // invoked only if the download is active. |bytes_received| is the
+ // number of bytes downloaded thus far. |total| is the number of
+ // bytes expected.
+ virtual void BytesReceived(uint64_t bytes_received, uint64_t total) = 0;
+};
+
+class PrefsInterface;
+
+class DownloadAction : public InstallPlanAction,
+ public HttpFetcherDelegate {
+ public:
+ // Takes ownership of the passed in HttpFetcher. Useful for testing.
+ // A good calling pattern is:
+ // DownloadAction(prefs, system_state, new WhateverHttpFetcher);
+ DownloadAction(PrefsInterface* prefs,
+ SystemState* system_state,
+ HttpFetcher* http_fetcher);
+ ~DownloadAction() override;
+ void PerformAction() override;
+ void TerminateProcessing() override;
+
+ // Testing
+ void SetTestFileWriter(FileWriter* writer) {
+ writer_ = writer;
+ }
+
+ int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
+
+ // Debugging/logging
+ static std::string StaticType() { return "DownloadAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ // HttpFetcherDelegate methods (see http_fetcher.h)
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override;
+ void SeekToOffset(off_t offset) override;
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override;
+ void TransferTerminated(HttpFetcher* fetcher) override;
+
+ DownloadActionDelegate* delegate() const { return delegate_; }
+ void set_delegate(DownloadActionDelegate* delegate) {
+ delegate_ = delegate;
+ }
+
+ HttpFetcher* http_fetcher() { return http_fetcher_.get(); }
+
+ // Returns the p2p file id for the file being written or the empty
+ // string if we're not writing to a p2p file.
+ std::string p2p_file_id() { return p2p_file_id_; }
+
+ private:
+ // Closes the file descriptor for the p2p file being written and
+ // clears |p2p_file_id_| to indicate that we're no longer sharing
+ // the file. If |delete_p2p_file| is True, also deletes the file.
+ // If there is no p2p file descriptor, this method does nothing.
+ void CloseP2PSharingFd(bool delete_p2p_file);
+
+ // Starts sharing the p2p file. Must be called before
+ // WriteToP2PFile(). Returns True if this worked.
+ bool SetupP2PSharingFd();
+
+ // Writes |length| bytes of payload from |data| into |file_offset|
+ // of the p2p file. Also does sanity checks; for example ensures we
+ // don't end up with a file with holes in it.
+ //
+ // This method does nothing if SetupP2PSharingFd() hasn't been
+ // called or if CloseP2PSharingFd() has been called.
+ void WriteToP2PFile(const void* data, size_t length, off_t file_offset);
+
+ // The InstallPlan passed in
+ InstallPlan install_plan_;
+
+ // Update Engine preference store.
+ PrefsInterface* prefs_;
+
+ // Global context for the system.
+ SystemState* system_state_;
+
+ // Pointer to the HttpFetcher that does the http work.
+ std::unique_ptr<HttpFetcher> http_fetcher_;
+
+ // The FileWriter that downloaded data should be written to. It will
+ // either point to *decompressing_file_writer_ or *delta_performer_.
+ FileWriter* writer_;
+
+ std::unique_ptr<DeltaPerformer> delta_performer_;
+
+ // Used by TransferTerminated to figure if this action terminated itself or
+ // was terminated by the action processor.
+ ErrorCode code_;
+
+ // For reporting status to outsiders
+ DownloadActionDelegate* delegate_;
+ uint64_t bytes_received_;
+
+ // The file-id for the file we're sharing or the empty string
+ // if we're not using p2p to share.
+ std::string p2p_file_id_;
+
+ // The file descriptor for the p2p file used for caching the payload or -1
+ // if we're not using p2p to share.
+ int p2p_sharing_fd_;
+
+ // Set to |false| if p2p file is not visible.
+ bool p2p_visible_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadAction);
+};
+
+// We want to be sure that we're compiled with large file support on linux,
+// just in case we find ourselves downloading large images.
+COMPILE_ASSERT(8 == sizeof(off_t), off_t_not_64_bit);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_DOWNLOAD_ACTION_H_
diff --git a/download_action_unittest.cc b/download_action_unittest.cc
new file mode 100644
index 0000000..254dbd7
--- /dev/null
+++ b/download_action_unittest.cc
@@ -0,0 +1,641 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/download_action.h"
+
+#include <glib.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/location.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/action_pipe.h"
+#include "update_engine/fake_p2p_manager_configuration.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+using base::FilePath;
+using base::ReadFileToString;
+using base::WriteFile;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::Return;
+using testing::_;
+using test_utils::ScopedTempFile;
+
+class DownloadActionTest : public ::testing::Test { };
+
+namespace {
+class DownloadActionDelegateMock : public DownloadActionDelegate {
+ public:
+ MOCK_METHOD1(SetDownloadStatus, void(bool active));
+ MOCK_METHOD2(BytesReceived, void(uint64_t bytes_received, uint64_t total));
+};
+
+class DownloadActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ explicit DownloadActionTestProcessorDelegate(ErrorCode expected_code)
+ : processing_done_called_(false),
+ expected_code_(expected_code) {}
+ ~DownloadActionTestProcessorDelegate() override {
+ EXPECT_TRUE(processing_done_called_);
+ }
+ void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) override {
+ chromeos::MessageLoop::current()->BreakLoop();
+ chromeos::Blob found_data;
+ ASSERT_TRUE(utils::ReadFile(path_, &found_data));
+ if (expected_code_ != ErrorCode::kDownloadWriteError) {
+ ASSERT_EQ(expected_data_.size(), found_data.size());
+ for (unsigned i = 0; i < expected_data_.size(); i++) {
+ EXPECT_EQ(expected_data_[i], found_data[i]);
+ }
+ }
+ processing_done_called_ = true;
+ }
+
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) override {
+ const string type = action->Type();
+ if (type == DownloadAction::StaticType()) {
+ EXPECT_EQ(expected_code_, code);
+ } else {
+ EXPECT_EQ(ErrorCode::kSuccess, code);
+ }
+ }
+
+ string path_;
+ chromeos::Blob expected_data_;
+ bool processing_done_called_;
+ ErrorCode expected_code_;
+};
+
+class TestDirectFileWriter : public DirectFileWriter {
+ public:
+ TestDirectFileWriter() : fail_write_(0), current_write_(0) {}
+ void set_fail_write(int fail_write) { fail_write_ = fail_write; }
+
+ virtual bool Write(const void* bytes, size_t count) {
+ if (++current_write_ == fail_write_) {
+ return false;
+ }
+ return DirectFileWriter::Write(bytes, count);
+ }
+
+ private:
+ // If positive, fail on the |fail_write_| call to Write.
+ int fail_write_;
+ int current_write_;
+};
+
+void StartProcessorInRunLoop(ActionProcessor* processor,
+ MockHttpFetcher* http_fetcher) {
+ processor->StartProcessing();
+ http_fetcher->SetOffset(1);
+}
+
+void TestWithData(const chromeos::Blob& data,
+ int fail_write,
+ bool use_download_delegate) {
+ chromeos::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ // TODO(adlr): see if we need a different file for build bots
+ ScopedTempFile output_temp_file;
+ TestDirectFileWriter writer;
+ writer.set_fail_write(fail_write);
+
+ // We pull off the first byte from data and seek past it.
+
+ string hash =
+ OmahaHashCalculator::OmahaHashOfBytes(&data[1], data.size() - 1);
+ uint64_t size = data.size();
+ InstallPlan install_plan(false,
+ false,
+ "",
+ size,
+ hash,
+ 0,
+ "",
+ output_temp_file.GetPath(),
+ "",
+ "",
+ "",
+ "");
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+ MockPrefs prefs;
+ MockHttpFetcher* http_fetcher = new MockHttpFetcher(data.data(),
+ data.size(),
+ nullptr);
+ // takes ownership of passed in HttpFetcher
+ DownloadAction download_action(&prefs, nullptr, http_fetcher);
+ download_action.SetTestFileWriter(&writer);
+ BondActions(&feeder_action, &download_action);
+ DownloadActionDelegateMock download_delegate;
+ if (use_download_delegate) {
+ InSequence s;
+ download_action.set_delegate(&download_delegate);
+ EXPECT_CALL(download_delegate, SetDownloadStatus(true)).Times(1);
+ if (data.size() > kMockHttpFetcherChunkSize)
+ EXPECT_CALL(download_delegate,
+ BytesReceived(1 + kMockHttpFetcherChunkSize, _));
+ EXPECT_CALL(download_delegate, BytesReceived(_, _)).Times(AtLeast(1));
+ EXPECT_CALL(download_delegate, SetDownloadStatus(false)).Times(1);
+ }
+ ErrorCode expected_code = ErrorCode::kSuccess;
+ if (fail_write > 0)
+ expected_code = ErrorCode::kDownloadWriteError;
+ DownloadActionTestProcessorDelegate delegate(expected_code);
+ delegate.expected_data_ = chromeos::Blob(data.begin() + 1, data.end());
+ delegate.path_ = output_temp_file.GetPath();
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&download_action);
+
+ loop.PostTask(FROM_HERE,
+ base::Bind(&StartProcessorInRunLoop, &processor, http_fetcher));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+}
+} // namespace
+
+TEST(DownloadActionTest, SimpleTest) {
+ chromeos::Blob small;
+ const char* foo = "foo";
+ small.insert(small.end(), foo, foo + strlen(foo));
+ TestWithData(small,
+ 0, // fail_write
+ true); // use_download_delegate
+}
+
+TEST(DownloadActionTest, LargeTest) {
+ chromeos::Blob big(5 * kMockHttpFetcherChunkSize);
+ char c = '0';
+ for (unsigned int i = 0; i < big.size(); i++) {
+ big[i] = c;
+ c = ('9' == c) ? '0' : c + 1;
+ }
+ TestWithData(big,
+ 0, // fail_write
+ true); // use_download_delegate
+}
+
+TEST(DownloadActionTest, FailWriteTest) {
+ chromeos::Blob big(5 * kMockHttpFetcherChunkSize);
+ char c = '0';
+ for (unsigned int i = 0; i < big.size(); i++) {
+ big[i] = c;
+ c = ('9' == c) ? '0' : c + 1;
+ }
+ TestWithData(big,
+ 2, // fail_write
+ true); // use_download_delegate
+}
+
+TEST(DownloadActionTest, NoDownloadDelegateTest) {
+ chromeos::Blob small;
+ const char* foo = "foofoo";
+ small.insert(small.end(), foo, foo + strlen(foo));
+ TestWithData(small,
+ 0, // fail_write
+ false); // use_download_delegate
+}
+
+namespace {
+class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ void ProcessingStopped(const ActionProcessor* processor) {
+ chromeos::MessageLoop::current()->BreakLoop();
+ }
+};
+
+void TerminateEarlyTestStarter(ActionProcessor* processor) {
+ processor->StartProcessing();
+ CHECK(processor->IsRunning());
+ processor->StopProcessing();
+}
+
+void TestTerminateEarly(bool use_download_delegate) {
+ chromeos::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ chromeos::Blob data(kMockHttpFetcherChunkSize +
+ kMockHttpFetcherChunkSize / 2);
+ memset(data.data(), 0, data.size());
+
+ ScopedTempFile temp_file;
+ {
+ DirectFileWriter writer;
+
+ // takes ownership of passed in HttpFetcher
+ ObjectFeederAction<InstallPlan> feeder_action;
+ InstallPlan install_plan(false, false, "", 0, "", 0, "",
+ temp_file.GetPath(), "", "", "", "");
+ feeder_action.set_obj(install_plan);
+ MockPrefs prefs;
+ DownloadAction download_action(&prefs, nullptr,
+ new MockHttpFetcher(data.data(),
+ data.size(),
+ nullptr));
+ download_action.SetTestFileWriter(&writer);
+ DownloadActionDelegateMock download_delegate;
+ if (use_download_delegate) {
+ InSequence s;
+ download_action.set_delegate(&download_delegate);
+ EXPECT_CALL(download_delegate, SetDownloadStatus(true)).Times(1);
+ EXPECT_CALL(download_delegate, SetDownloadStatus(false)).Times(1);
+ }
+ TerminateEarlyTestProcessorDelegate delegate;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&download_action);
+ BondActions(&feeder_action, &download_action);
+
+ loop.PostTask(FROM_HERE,
+ base::Bind(&TerminateEarlyTestStarter, &processor));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+ }
+
+ // 1 or 0 chunks should have come through
+ const off_t resulting_file_size(utils::FileSize(temp_file.GetPath()));
+ EXPECT_GE(resulting_file_size, 0);
+ if (resulting_file_size != 0)
+ EXPECT_EQ(kMockHttpFetcherChunkSize, resulting_file_size);
+}
+
+} // namespace
+
+TEST(DownloadActionTest, TerminateEarlyTest) {
+ TestTerminateEarly(true);
+}
+
+TEST(DownloadActionTest, TerminateEarlyNoDownloadDelegateTest) {
+ TestTerminateEarly(false);
+}
+
+class DownloadActionTestAction;
+
+template<>
+class ActionTraits<DownloadActionTestAction> {
+ public:
+ typedef InstallPlan OutputObjectType;
+ typedef InstallPlan InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class DownloadActionTestAction : public Action<DownloadActionTestAction> {
+ public:
+ DownloadActionTestAction() : did_run_(false) {}
+ typedef InstallPlan InputObjectType;
+ typedef InstallPlan OutputObjectType;
+ ActionPipe<InstallPlan>* in_pipe() { return in_pipe_.get(); }
+ ActionPipe<InstallPlan>* out_pipe() { return out_pipe_.get(); }
+ ActionProcessor* processor() { return processor_; }
+ void PerformAction() {
+ did_run_ = true;
+ ASSERT_TRUE(HasInputObject());
+ EXPECT_TRUE(expected_input_object_ == GetInputObject());
+ ASSERT_TRUE(processor());
+ processor()->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ string Type() const { return "DownloadActionTestAction"; }
+ InstallPlan expected_input_object_;
+ bool did_run_;
+};
+
+namespace {
+// This class is an ActionProcessorDelegate that simply terminates the
+// run loop when the ActionProcessor has completed processing. It's used
+// only by the test PassObjectOutTest.
+class PassObjectOutTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+ chromeos::MessageLoop::current()->BreakLoop();
+ }
+};
+
+} // namespace
+
+TEST(DownloadActionTest, PassObjectOutTest) {
+ chromeos::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ DirectFileWriter writer;
+
+ // takes ownership of passed in HttpFetcher
+ InstallPlan install_plan(false,
+ false,
+ "",
+ 1,
+ OmahaHashCalculator::OmahaHashOfString("x"),
+ 0,
+ "",
+ "/dev/null",
+ "/dev/null",
+ "/dev/null",
+ "/dev/null",
+ "");
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+ MockPrefs prefs;
+ DownloadAction download_action(&prefs, nullptr,
+ new MockHttpFetcher("x", 1, nullptr));
+ download_action.SetTestFileWriter(&writer);
+
+ DownloadActionTestAction test_action;
+ test_action.expected_input_object_ = install_plan;
+ BondActions(&feeder_action, &download_action);
+ BondActions(&download_action, &test_action);
+
+ ActionProcessor processor;
+ PassObjectOutTestProcessorDelegate delegate;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&download_action);
+ processor.EnqueueAction(&test_action);
+
+ loop.PostTask(FROM_HERE,
+ base::Bind([&processor] { processor.StartProcessing(); }));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+
+ EXPECT_EQ(true, test_action.did_run_);
+}
+
+TEST(DownloadActionTest, BadOutFileTest) {
+ chromeos::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ const string path("/fake/path/that/cant/be/created/because/of/missing/dirs");
+ DirectFileWriter writer;
+
+ // takes ownership of passed in HttpFetcher
+ InstallPlan install_plan(
+ false, false, "", 0, "", 0, "", path, "", "", "", "");
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+ MockPrefs prefs;
+ DownloadAction download_action(&prefs, nullptr,
+ new MockHttpFetcher("x", 1, nullptr));
+ download_action.SetTestFileWriter(&writer);
+
+ BondActions(&feeder_action, &download_action);
+
+ ActionProcessor processor;
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&download_action);
+ processor.StartProcessing();
+ ASSERT_FALSE(processor.IsRunning());
+
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+}
+
+// Test fixture for P2P tests.
+class P2PDownloadActionTest : public testing::Test {
+ protected:
+ P2PDownloadActionTest()
+ : start_at_offset_(0),
+ fake_um_(fake_system_state_.fake_clock()) {}
+
+ ~P2PDownloadActionTest() override {}
+
+ // Derived from testing::Test.
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ // Derived from testing::Test.
+ void TearDown() override {
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ // To be called by tests to setup the download. The
+ // |starting_offset| parameter is for where to resume.
+ void SetupDownload(off_t starting_offset) {
+ start_at_offset_ = starting_offset;
+ // Prepare data 10 kB of data.
+ data_.clear();
+ for (unsigned int i = 0; i < 10 * 1000; i++)
+ data_ += 'a' + (i % 25);
+
+ // Setup p2p.
+ FakeP2PManagerConfiguration *test_conf = new FakeP2PManagerConfiguration();
+ p2p_manager_.reset(P2PManager::Construct(
+ test_conf, nullptr, &fake_um_, "cros_au", 3,
+ base::TimeDelta::FromDays(5)));
+ fake_system_state_.set_p2p_manager(p2p_manager_.get());
+ }
+
+ // To be called by tests to perform the download. The
+ // |use_p2p_to_share| parameter is used to indicate whether the
+ // payload should be shared via p2p.
+ void StartDownload(bool use_p2p_to_share) {
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ GetUsingP2PForSharing())
+ .WillRepeatedly(Return(use_p2p_to_share));
+
+ ScopedTempFile output_temp_file;
+ TestDirectFileWriter writer;
+ InstallPlan install_plan(false,
+ false,
+ "",
+ data_.length(),
+ "1234hash",
+ 0,
+ "",
+ output_temp_file.GetPath(),
+ "",
+ "",
+ "",
+ "");
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+ MockPrefs prefs;
+ http_fetcher_ = new MockHttpFetcher(data_.c_str(),
+ data_.length(),
+ nullptr);
+ // Note that DownloadAction takes ownership of the passed in HttpFetcher.
+ download_action_.reset(new DownloadAction(&prefs, &fake_system_state_,
+ http_fetcher_));
+ download_action_->SetTestFileWriter(&writer);
+ BondActions(&feeder_action, download_action_.get());
+ DownloadActionTestProcessorDelegate delegate(ErrorCode::kSuccess);
+ delegate.expected_data_ = chromeos::Blob(data_.begin() + start_at_offset_,
+ data_.end());
+ delegate.path_ = output_temp_file.GetPath();
+ processor_.set_delegate(&delegate);
+ processor_.EnqueueAction(&feeder_action);
+ processor_.EnqueueAction(download_action_.get());
+
+ loop_.PostTask(FROM_HERE, base::Bind(
+ &P2PDownloadActionTest::StartProcessorInRunLoopForP2P,
+ base::Unretained(this)));
+ loop_.Run();
+ }
+
+ // Mainloop used to make StartDownload() synchronous.
+ chromeos::FakeMessageLoop loop_{nullptr};
+
+ // The DownloadAction instance under test.
+ unique_ptr<DownloadAction> download_action_;
+
+ // The HttpFetcher used in the test.
+ MockHttpFetcher* http_fetcher_;
+
+ // The P2PManager used in the test.
+ unique_ptr<P2PManager> p2p_manager_;
+
+ // The ActionProcessor used for running the actions.
+ ActionProcessor processor_;
+
+ // A fake system state.
+ FakeSystemState fake_system_state_;
+
+ // The data being downloaded.
+ string data_;
+
+ private:
+ // Callback used in StartDownload() method.
+ void StartProcessorInRunLoopForP2P() {
+ processor_.StartProcessing();
+ http_fetcher_->SetOffset(start_at_offset_);
+ }
+
+ // The requested starting offset passed to SetupDownload().
+ off_t start_at_offset_;
+
+ chromeos_update_manager::FakeUpdateManager fake_um_;
+};
+
+TEST_F(P2PDownloadActionTest, IsWrittenTo) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(0); // starting_offset
+ StartDownload(true); // use_p2p_to_share
+
+ // Check the p2p file and its content matches what was sent.
+ string file_id = download_action_->p2p_file_id();
+ EXPECT_NE("", file_id);
+ EXPECT_EQ(data_.length(), p2p_manager_->FileGetSize(file_id));
+ EXPECT_EQ(data_.length(), p2p_manager_->FileGetExpectedSize(file_id));
+ string p2p_file_contents;
+ EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+ &p2p_file_contents));
+ EXPECT_EQ(data_, p2p_file_contents);
+}
+
+TEST_F(P2PDownloadActionTest, DeleteIfHoleExists) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(1000); // starting_offset
+ StartDownload(true); // use_p2p_to_share
+
+ // DownloadAction should convey that the file is not being shared.
+ // and that we don't have any p2p files.
+ EXPECT_EQ(download_action_->p2p_file_id(), "");
+ EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
+}
+
+TEST_F(P2PDownloadActionTest, CanAppend) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(1000); // starting_offset
+
+ // Prepare the file with existing data before starting to write to
+ // it via DownloadAction.
+ string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+ ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+ string existing_data;
+ for (unsigned int i = 0; i < 1000; i++)
+ existing_data += '0' + (i % 10);
+ ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+ 1000), 1000);
+
+ StartDownload(true); // use_p2p_to_share
+
+ // DownloadAction should convey the same file_id and the file should
+ // have the expected size.
+ EXPECT_EQ(download_action_->p2p_file_id(), file_id);
+ EXPECT_EQ(p2p_manager_->FileGetSize(file_id), data_.length());
+ EXPECT_EQ(p2p_manager_->FileGetExpectedSize(file_id), data_.length());
+ string p2p_file_contents;
+ // Check that the first 1000 bytes wasn't touched and that we
+ // appended the remaining as appropriate.
+ EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+ &p2p_file_contents));
+ EXPECT_EQ(existing_data, p2p_file_contents.substr(0, 1000));
+ EXPECT_EQ(data_.substr(1000), p2p_file_contents.substr(1000));
+}
+
+TEST_F(P2PDownloadActionTest, DeletePartialP2PFileIfResumingWithoutP2P) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(1000); // starting_offset
+
+ // Prepare the file with all existing data before starting to write
+ // to it via DownloadAction.
+ string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+ ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+ string existing_data;
+ for (unsigned int i = 0; i < 1000; i++)
+ existing_data += '0' + (i % 10);
+ ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+ 1000), 1000);
+
+ // Check that the file is there.
+ EXPECT_EQ(p2p_manager_->FileGetSize(file_id), 1000);
+ EXPECT_EQ(p2p_manager_->CountSharedFiles(), 1);
+
+ StartDownload(false); // use_p2p_to_share
+
+ // DownloadAction should have deleted the p2p file. Check that it's gone.
+ EXPECT_EQ(p2p_manager_->FileGetSize(file_id), -1);
+ EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
+}
+
+} // namespace chromeos_update_engine
diff --git a/error_code.h b/error_code.h
new file mode 100644
index 0000000..fde1833
--- /dev/null
+++ b/error_code.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_ERROR_CODE_H_
+#define UPDATE_ENGINE_ERROR_CODE_H_
+
+#include <ostream> // NOLINT(readability/streams)
+
+namespace chromeos_update_engine {
+
+// Action exit codes.
+enum class ErrorCode : int {
+ kSuccess = 0,
+ kError = 1,
+ kOmahaRequestError = 2,
+ kOmahaResponseHandlerError = 3,
+ kFilesystemCopierError = 4,
+ kPostinstallRunnerError = 5,
+ kPayloadMismatchedType = 6,
+ kInstallDeviceOpenError = 7,
+ kKernelDeviceOpenError = 8,
+ kDownloadTransferError = 9,
+ kPayloadHashMismatchError = 10,
+ kPayloadSizeMismatchError = 11,
+ kDownloadPayloadVerificationError = 12,
+ kDownloadNewPartitionInfoError = 13,
+ kDownloadWriteError = 14,
+ kNewRootfsVerificationError = 15,
+ kNewKernelVerificationError = 16,
+ kSignedDeltaPayloadExpectedError = 17,
+ kDownloadPayloadPubKeyVerificationError = 18,
+ kPostinstallBootedFromFirmwareB = 19,
+ kDownloadStateInitializationError = 20,
+ kDownloadInvalidMetadataMagicString = 21,
+ kDownloadSignatureMissingInManifest = 22,
+ kDownloadManifestParseError = 23,
+ kDownloadMetadataSignatureError = 24,
+ kDownloadMetadataSignatureVerificationError = 25,
+ kDownloadMetadataSignatureMismatch = 26,
+ kDownloadOperationHashVerificationError = 27,
+ kDownloadOperationExecutionError = 28,
+ kDownloadOperationHashMismatch = 29,
+ kOmahaRequestEmptyResponseError = 30,
+ kOmahaRequestXMLParseError = 31,
+ kDownloadInvalidMetadataSize = 32,
+ kDownloadInvalidMetadataSignature = 33,
+ kOmahaResponseInvalid = 34,
+ kOmahaUpdateIgnoredPerPolicy = 35,
+ kOmahaUpdateDeferredPerPolicy = 36,
+ kOmahaErrorInHTTPResponse = 37,
+ kDownloadOperationHashMissingError = 38,
+ kDownloadMetadataSignatureMissingError = 39,
+ kOmahaUpdateDeferredForBackoff = 40,
+ kPostinstallPowerwashError = 41,
+ kUpdateCanceledByChannelChange = 42,
+ kPostinstallFirmwareRONotUpdatable = 43,
+ kUnsupportedMajorPayloadVersion = 44,
+ kUnsupportedMinorPayloadVersion = 45,
+ kOmahaRequestXMLHasEntityDecl = 46,
+ kFilesystemVerifierError = 47,
+
+ // VERY IMPORTANT! When adding new error codes:
+ //
+ // 1) Update tools/metrics/histograms/histograms.xml in Chrome.
+ //
+ // 2) Update the assorted switch statements in update_engine which won't
+ // build until this case is added.
+
+ // Any code above this is sent to both Omaha and UMA as-is, except
+ // kOmahaErrorInHTTPResponse (see error code 2000 for more details).
+ // Codes/flags below this line is sent only to Omaha and not to UMA.
+
+ // kUmaReportedMax is not an error code per se, it's just the count
+ // of the number of enums above. Add any new errors above this line if you
+ // want them to show up on UMA. Stuff below this line will not be sent to UMA
+ // but is used for other errors that are sent to Omaha. We don't assign any
+ // particular value for this enum so that it's just one more than the last
+ // one above and thus always represents the correct count of UMA metrics
+ // buckets, even when new enums are added above this line in future. See
+ // utils::SendErrorCodeToUma on how this enum is used.
+ kUmaReportedMax,
+
+ // use the 2xxx range to encode HTTP errors. These errors are available in
+ // Dremel with the individual granularity. But for UMA purposes, all these
+ // errors are aggregated into one: kOmahaErrorInHTTPResponse.
+ kOmahaRequestHTTPResponseBase = 2000, // + HTTP response code
+
+ // TODO(jaysri): Move out all the bit masks into separate constants
+ // outside the enum as part of fixing bug 34369.
+ // Bit flags. Remember to update the mask below for new bits.
+
+ // Set if boot mode not normal.
+ // TODO(garnold) This is very debatable value to use, knowing that the
+ // underlying type is a signed int (often, 32-bit). However, at this point
+ // there are parts of the ecosystem that expect this to be a negative value,
+ // so we preserve this semantics. This should be reconsidered if/when we
+ // modify the implementation of ErrorCode into a properly encapsulated class.
+ kDevModeFlag = 1 << 31,
+
+ // Set if resuming an interruped update.
+ kResumedFlag = 1 << 30,
+
+ // Set if using a dev/test image as opposed to an MP-signed image.
+ kTestImageFlag = 1 << 29,
+
+ // Set if using devserver or Omaha sandbox (using crosh autest).
+ kTestOmahaUrlFlag = 1 << 28,
+
+ // Mask that indicates bit positions that are used to indicate special flags
+ // that are embedded in the error code to provide additional context about
+ // the system in which the error was encountered.
+ kSpecialFlags = (kDevModeFlag | kResumedFlag | kTestImageFlag |
+ kTestOmahaUrlFlag)
+};
+
+inline std::ostream& operator<<(std::ostream& os, ErrorCode val) {
+ return os << static_cast<int>(val);
+}
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_ERROR_CODE_H_
diff --git a/extent_writer.cc b/extent_writer.cc
new file mode 100644
index 0000000..336311d
--- /dev/null
+++ b/extent_writer.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/extent_writer.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/utils.h"
+
+using std::min;
+
+namespace chromeos_update_engine {
+
+bool DirectExtentWriter::Write(const void* bytes, size_t count) {
+ if (count == 0)
+ return true;
+ const char* c_bytes = reinterpret_cast<const char*>(bytes);
+ size_t bytes_written = 0;
+ while (count - bytes_written > 0) {
+ TEST_AND_RETURN_FALSE(next_extent_index_ < extents_.size());
+ uint64_t bytes_remaining_next_extent =
+ extents_[next_extent_index_].num_blocks() * block_size_ -
+ extent_bytes_written_;
+ CHECK_NE(bytes_remaining_next_extent, static_cast<uint64_t>(0));
+ size_t bytes_to_write =
+ static_cast<size_t>(min(static_cast<uint64_t>(count - bytes_written),
+ bytes_remaining_next_extent));
+ TEST_AND_RETURN_FALSE(bytes_to_write > 0);
+
+ if (extents_[next_extent_index_].start_block() != kSparseHole) {
+ const off64_t offset =
+ extents_[next_extent_index_].start_block() * block_size_ +
+ extent_bytes_written_;
+ TEST_AND_RETURN_FALSE_ERRNO(fd_->Seek(offset, SEEK_SET) !=
+ static_cast<off64_t>(-1));
+ TEST_AND_RETURN_FALSE(
+ utils::WriteAll(fd_, c_bytes + bytes_written, bytes_to_write));
+ }
+ bytes_written += bytes_to_write;
+ extent_bytes_written_ += bytes_to_write;
+ if (bytes_remaining_next_extent == bytes_to_write) {
+ // We filled this extent
+ CHECK_EQ(extent_bytes_written_,
+ extents_[next_extent_index_].num_blocks() * block_size_);
+ // move to next extent
+ extent_bytes_written_ = 0;
+ next_extent_index_++;
+ }
+ }
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/extent_writer.h b/extent_writer.h
new file mode 100644
index 0000000..765bff6
--- /dev/null
+++ b/extent_writer.h
@@ -0,0 +1,129 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_EXTENT_WRITER_H_
+
+#include <vector>
+
+#include <base/logging.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/file_descriptor.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+// ExtentWriter is an abstract class which synchronously writes to a given
+// file descriptor at the extents given.
+
+namespace chromeos_update_engine {
+
+class ExtentWriter {
+ public:
+ ExtentWriter() : end_called_(false) {}
+ virtual ~ExtentWriter() {
+ LOG_IF(ERROR, !end_called_) << "End() not called on ExtentWriter.";
+ }
+
+ // Returns true on success.
+ virtual bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) = 0;
+
+ // Returns true on success.
+ virtual bool Write(const void* bytes, size_t count) = 0;
+
+ // Should be called when all writing is complete. Returns true on success.
+ // The fd is not closed. Caller is responsible for closing it.
+ bool End() {
+ end_called_ = true;
+ return EndImpl();
+ }
+ virtual bool EndImpl() = 0;
+ private:
+ bool end_called_;
+};
+
+// DirectExtentWriter is probably the simplest ExtentWriter implementation.
+// It writes the data directly into the extents.
+
+class DirectExtentWriter : public ExtentWriter {
+ public:
+ DirectExtentWriter()
+ : fd_(nullptr),
+ block_size_(0),
+ extent_bytes_written_(0),
+ next_extent_index_(0) {}
+ ~DirectExtentWriter() {}
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) {
+ fd_ = fd;
+ block_size_ = block_size;
+ extents_ = extents;
+ return true;
+ }
+ bool Write(const void* bytes, size_t count);
+ bool EndImpl() {
+ return true;
+ }
+
+ private:
+ FileDescriptorPtr fd_;
+
+ size_t block_size_;
+ // Bytes written into next_extent_index_ thus far
+ uint64_t extent_bytes_written_;
+ std::vector<Extent> extents_;
+ // The next call to write should correspond to extents_[next_extent_index_]
+ std::vector<Extent>::size_type next_extent_index_;
+};
+
+// Takes an underlying ExtentWriter to which all operations are delegated.
+// When End() is called, ZeroPadExtentWriter ensures that the total number
+// of bytes written is a multiple of block_size_. If not, it writes zeros
+// to pad as needed.
+
+class ZeroPadExtentWriter : public ExtentWriter {
+ public:
+ explicit ZeroPadExtentWriter(ExtentWriter* underlying_extent_writer)
+ : underlying_extent_writer_(underlying_extent_writer),
+ block_size_(0),
+ bytes_written_mod_block_size_(0) {}
+ ~ZeroPadExtentWriter() {}
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) {
+ block_size_ = block_size;
+ return underlying_extent_writer_->Init(fd, extents, block_size);
+ }
+ bool Write(const void* bytes, size_t count) {
+ if (underlying_extent_writer_->Write(bytes, count)) {
+ bytes_written_mod_block_size_ += count;
+ bytes_written_mod_block_size_ %= block_size_;
+ return true;
+ }
+ return false;
+ }
+ bool EndImpl() {
+ if (bytes_written_mod_block_size_) {
+ const size_t write_size = block_size_ - bytes_written_mod_block_size_;
+ chromeos::Blob zeros(write_size, 0);
+ TEST_AND_RETURN_FALSE(underlying_extent_writer_->Write(zeros.data(),
+ write_size));
+ }
+ return underlying_extent_writer_->End();
+ }
+
+ private:
+ ExtentWriter* underlying_extent_writer_; // The underlying ExtentWriter.
+ size_t block_size_;
+ size_t bytes_written_mod_block_size_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_EXTENT_WRITER_H_
diff --git a/extent_writer_unittest.cc b/extent_writer_unittest.cc
new file mode 100644
index 0000000..f6519b6
--- /dev/null
+++ b/extent_writer_unittest.cc
@@ -0,0 +1,257 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos_update_engine::test_utils::ExpectVectorsEq;
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+COMPILE_ASSERT(sizeof(off_t) == 8, off_t_not_64_bit);
+
+namespace {
+const char kPathTemplate[] = "./ExtentWriterTest-file.XXXXXX";
+const size_t kBlockSize = 4096;
+}
+
+class ExtentWriterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ memcpy(path_, kPathTemplate, sizeof(kPathTemplate));
+ fd_.reset(new EintrSafeFileDescriptor);
+ int fd = mkstemp(path_);
+ ASSERT_TRUE(fd_->Open(path_, O_RDWR, 0600));
+ close(fd);
+ }
+ void TearDown() override {
+ fd_->Close();
+ unlink(path_);
+ }
+
+ // Writes data to an extent writer in 'chunk_size' chunks with
+ // the first chunk of size first_chunk_size. It calculates what the
+ // resultant file should look like and ensure that the extent writer
+ // wrote the file correctly.
+ void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+ void TestZeroPad(bool aligned_size);
+
+ FileDescriptorPtr fd_;
+ char path_[sizeof(kPathTemplate)];
+};
+
+TEST_F(ExtentWriterTest, SimpleTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ const string bytes = "1234";
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+ EXPECT_TRUE(direct_writer.Write(bytes.data(), bytes.size()));
+ EXPECT_TRUE(direct_writer.End());
+
+ EXPECT_EQ(kBlockSize + bytes.size(), utils::FileSize(path_));
+
+ chromeos::Blob result_file;
+ EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+ chromeos::Blob expected_file(kBlockSize);
+ expected_file.insert(expected_file.end(),
+ bytes.data(), bytes.data() + bytes.size());
+ ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroLengthTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+ EXPECT_TRUE(direct_writer.Write(nullptr, 0));
+ EXPECT_TRUE(direct_writer.End());
+}
+
+TEST_F(ExtentWriterTest, OverflowExtentTest) {
+ WriteAlignedExtents(kBlockSize * 3, kBlockSize * 3);
+}
+
+TEST_F(ExtentWriterTest, UnalignedWriteTest) {
+ WriteAlignedExtents(7, 7);
+}
+
+TEST_F(ExtentWriterTest, LargeUnalignedWriteTest) {
+ WriteAlignedExtents(kBlockSize * 2, kBlockSize / 2);
+}
+
+void ExtentWriterTest::WriteAlignedExtents(size_t chunk_size,
+ size_t first_chunk_size) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(2);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ chromeos::Blob data(kBlockSize * 3);
+ test_utils::FillWithData(&data);
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+
+ size_t bytes_written = 0;
+ while (bytes_written < data.size()) {
+ size_t bytes_to_write = min(data.size() - bytes_written, chunk_size);
+ if (bytes_written == 0) {
+ bytes_to_write = min(data.size() - bytes_written, first_chunk_size);
+ }
+ EXPECT_TRUE(direct_writer.Write(&data[bytes_written], bytes_to_write));
+ bytes_written += bytes_to_write;
+ }
+ EXPECT_TRUE(direct_writer.End());
+
+ EXPECT_EQ(data.size(), utils::FileSize(path_));
+
+ chromeos::Blob result_file;
+ EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+ chromeos::Blob expected_file;
+ expected_file.insert(expected_file.end(),
+ data.begin() + kBlockSize,
+ data.begin() + kBlockSize * 2);
+ expected_file.insert(expected_file.end(),
+ data.begin(), data.begin() + kBlockSize);
+ expected_file.insert(expected_file.end(),
+ data.begin() + kBlockSize * 2, data.end());
+ ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadNullTest) {
+ TestZeroPad(true);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadFillTest) {
+ TestZeroPad(false);
+}
+
+void ExtentWriterTest::TestZeroPad(bool aligned_size) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ chromeos::Blob data(kBlockSize * 2);
+ test_utils::FillWithData(&data);
+
+ DirectExtentWriter direct_writer;
+ ZeroPadExtentWriter zero_pad_writer(&direct_writer);
+
+ EXPECT_TRUE(zero_pad_writer.Init(fd_, extents, kBlockSize));
+ size_t bytes_to_write = data.size();
+ const size_t missing_bytes = (aligned_size ? 0 : 9);
+ bytes_to_write -= missing_bytes;
+ fd_->Seek(kBlockSize - missing_bytes, SEEK_SET);
+ EXPECT_EQ(3, fd_->Write("xxx", 3));
+ ASSERT_TRUE(zero_pad_writer.Write(data.data(), bytes_to_write));
+ EXPECT_TRUE(zero_pad_writer.End());
+
+ EXPECT_EQ(data.size(), utils::FileSize(path_));
+
+ chromeos::Blob result_file;
+ EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+ chromeos::Blob expected_file;
+ expected_file.insert(expected_file.end(),
+ data.begin() + kBlockSize,
+ data.begin() + kBlockSize * 2);
+ expected_file.insert(expected_file.end(),
+ data.begin(), data.begin() + kBlockSize);
+ if (missing_bytes) {
+ memset(&expected_file[kBlockSize - missing_bytes], 0, missing_bytes);
+ }
+
+ ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, SparseFileTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(kSparseHole);
+ extent.set_num_blocks(2);
+ extents.push_back(extent);
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ const int block_count = 4;
+ const int on_disk_count = 2;
+
+ chromeos::Blob data(17);
+ test_utils::FillWithData(&data);
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+
+ size_t bytes_written = 0;
+ while (bytes_written < (block_count * kBlockSize)) {
+ size_t bytes_to_write = min(block_count * kBlockSize - bytes_written,
+ data.size());
+ EXPECT_TRUE(direct_writer.Write(data.data(), bytes_to_write));
+ bytes_written += bytes_to_write;
+ }
+ EXPECT_TRUE(direct_writer.End());
+
+ // check file size, then data inside
+ ASSERT_EQ(2 * kBlockSize, utils::FileSize(path_));
+
+ chromeos::Blob resultant_data;
+ EXPECT_TRUE(utils::ReadFile(path_, &resultant_data));
+
+ // Create expected data
+ chromeos::Blob expected_data(on_disk_count * kBlockSize);
+ chromeos::Blob big(block_count * kBlockSize);
+ for (chromeos::Blob::size_type i = 0; i < big.size(); i++) {
+ big[i] = data[i % data.size()];
+ }
+ memcpy(&expected_data[kBlockSize], &big[0], kBlockSize);
+ memcpy(&expected_data[0], &big[3 * kBlockSize], kBlockSize);
+ ExpectVectorsEq(expected_data, resultant_data);
+}
+
+} // namespace chromeos_update_engine
diff --git a/fake_clock.h b/fake_clock.h
new file mode 100644
index 0000000..c717bdf
--- /dev/null
+++ b/fake_clock.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_FAKE_CLOCK_H_
+#define UPDATE_ENGINE_FAKE_CLOCK_H_
+
+#include "update_engine/clock_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a clock that can be made to tell any time you want.
+class FakeClock : public ClockInterface {
+ public:
+ FakeClock() {}
+
+ base::Time GetWallclockTime() override {
+ return wallclock_time_;
+ }
+
+ base::Time GetMonotonicTime() override {
+ return monotonic_time_;
+ }
+
+ base::Time GetBootTime() override {
+ return boot_time_;
+ }
+
+ void SetWallclockTime(const base::Time &time) {
+ wallclock_time_ = time;
+ }
+
+ void SetMonotonicTime(const base::Time &time) {
+ monotonic_time_ = time;
+ }
+
+ void SetBootTime(const base::Time &time) {
+ boot_time_ = time;
+ }
+
+ private:
+ base::Time wallclock_time_;
+ base::Time monotonic_time_;
+ base::Time boot_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeClock);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_CLOCK_H_
diff --git a/fake_file_writer.h b/fake_file_writer.h
new file mode 100644
index 0000000..751cbcf
--- /dev/null
+++ b/fake_file_writer.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_FAKE_FILE_WRITER_H_
+#define UPDATE_ENGINE_FAKE_FILE_WRITER_H_
+
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/file_writer.h"
+
+// FakeFileWriter is an implementation of FileWriter. It will succeed
+// calls to Open(), Close(), but not do any work. All calls to Write()
+// will append the passed data to an internal vector.
+
+namespace chromeos_update_engine {
+
+class FakeFileWriter : public FileWriter {
+ public:
+ FakeFileWriter() : was_opened_(false), was_closed_(false) {}
+
+ virtual int Open(const char* path, int flags, mode_t mode) {
+ CHECK(!was_opened_);
+ CHECK(!was_closed_);
+ was_opened_ = true;
+ return 0;
+ }
+
+ virtual ssize_t Write(const void* bytes, size_t count) {
+ CHECK(was_opened_);
+ CHECK(!was_closed_);
+ const char* char_bytes = reinterpret_cast<const char*>(bytes);
+ bytes_.insert(bytes_.end(), char_bytes, char_bytes + count);
+ return count;
+ }
+
+ virtual int Close() {
+ CHECK(was_opened_);
+ CHECK(!was_closed_);
+ was_closed_ = true;
+ return 0;
+ }
+
+ const chromeos::Blob& bytes() {
+ return bytes_;
+ }
+
+ private:
+ // The internal store of all bytes that have been written
+ chromeos::Blob bytes_;
+
+ // These are just to ensure FileWriter methods are called properly.
+ bool was_opened_;
+ bool was_closed_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeFileWriter);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_FILE_WRITER_H_
diff --git a/fake_hardware.h b/fake_hardware.h
new file mode 100644
index 0000000..13c3341
--- /dev/null
+++ b/fake_hardware.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_FAKE_HARDWARE_H_
+#define UPDATE_ENGINE_FAKE_HARDWARE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+
+#include "update_engine/hardware_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake hardware interface used for testing.
+class FakeHardware : public HardwareInterface {
+ public:
+ // Value used to signal that the powerwash_count file is not present. When
+ // this value is used in SetPowerwashCount(), GetPowerwashCount() will return
+ // false.
+ static const int kPowerwashCountNotSet = -1;
+
+ FakeHardware()
+ : kernel_device_("/dev/sdz4"),
+ boot_device_("/dev/sdz5"),
+ is_boot_device_removable_(false),
+ kernel_devices_({"/dev/sdz2", "/dev/sdz4"}),
+ is_official_build_(true),
+ is_normal_boot_mode_(true),
+ is_oobe_complete_(false),
+ hardware_class_("Fake HWID BLAH-1234"),
+ firmware_version_("Fake Firmware v1.0.1"),
+ ec_version_("Fake EC v1.0a"),
+ powerwash_count_(kPowerwashCountNotSet) {}
+
+ // HardwareInterface methods.
+ std::string BootKernelDevice() const override { return kernel_device_; }
+
+ std::string BootDevice() const override { return boot_device_; }
+
+ bool IsBootDeviceRemovable() const override {
+ return is_boot_device_removable_;
+ }
+
+ std::vector<std::string> GetKernelDevices() const override {
+ return kernel_devices_;
+ }
+
+ bool IsKernelBootable(const std::string& kernel_device,
+ bool* bootable) const override {
+ auto i = is_bootable_.find(kernel_device);
+ *bootable = (i != is_bootable_.end()) ? i->second : true;
+ return true;
+ }
+
+ bool MarkKernelUnbootable(const std::string& kernel_device) override {
+ is_bootable_[kernel_device] = false;
+ return true;
+ }
+
+ bool IsOfficialBuild() const override { return is_official_build_; }
+
+ bool IsNormalBootMode() const override { return is_normal_boot_mode_; }
+
+ bool IsOOBEComplete(base::Time* out_time_of_oobe) const override {
+ if (out_time_of_oobe != nullptr)
+ *out_time_of_oobe = oobe_timestamp_;
+ return is_oobe_complete_;
+ }
+
+ std::string GetHardwareClass() const override { return hardware_class_; }
+
+ std::string GetFirmwareVersion() const override { return firmware_version_; }
+
+ std::string GetECVersion() const override { return ec_version_; }
+
+ int GetPowerwashCount() const override { return powerwash_count_; }
+
+ // Setters
+ void SetBootDevice(const std::string& boot_device) {
+ boot_device_ = boot_device;
+ }
+
+ void SetIsBootDeviceRemovable(bool is_boot_device_removable) {
+ is_boot_device_removable_ = is_boot_device_removable;
+ }
+
+ void SetIsOfficialBuild(bool is_official_build) {
+ is_official_build_ = is_official_build;
+ }
+
+ void SetIsNormalBootMode(bool is_normal_boot_mode) {
+ is_normal_boot_mode_ = is_normal_boot_mode;
+ }
+
+ // Sets the IsOOBEComplete to True with the given timestamp.
+ void SetIsOOBEComplete(base::Time oobe_timestamp) {
+ is_oobe_complete_ = true;
+ oobe_timestamp_ = oobe_timestamp;
+ }
+
+ // Sets the IsOOBEComplete to False.
+ void UnsetIsOOBEComplete() {
+ is_oobe_complete_ = false;
+ }
+
+ void SetHardwareClass(std::string hardware_class) {
+ hardware_class_ = hardware_class;
+ }
+
+ void SetFirmwareVersion(std::string firmware_version) {
+ firmware_version_ = firmware_version;
+ }
+
+ void SetECVersion(std::string ec_version) {
+ ec_version_ = ec_version;
+ }
+
+ void SetPowerwashCount(int powerwash_count) {
+ powerwash_count_ = powerwash_count;
+ }
+
+ private:
+ std::string kernel_device_;
+ std::string boot_device_;
+ bool is_boot_device_removable_;
+ std::vector<std::string> kernel_devices_;
+ std::map<std::string, bool> is_bootable_;
+ bool is_official_build_;
+ bool is_normal_boot_mode_;
+ bool is_oobe_complete_;
+ base::Time oobe_timestamp_;
+ std::string hardware_class_;
+ std::string firmware_version_;
+ std::string ec_version_;
+ int powerwash_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeHardware);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_HARDWARE_H_
diff --git a/fake_p2p_manager.h b/fake_p2p_manager.h
new file mode 100644
index 0000000..ce214e4
--- /dev/null
+++ b/fake_p2p_manager.h
@@ -0,0 +1,118 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_FAKE_P2P_MANAGER_H_
+#define UPDATE_ENGINE_FAKE_P2P_MANAGER_H_
+
+#include <string>
+
+#include "update_engine/p2p_manager.h"
+
+namespace chromeos_update_engine {
+
+// A fake implementation of P2PManager.
+class FakeP2PManager : public P2PManager {
+ public:
+ FakeP2PManager() :
+ is_p2p_enabled_(false),
+ ensure_p2p_running_result_(false),
+ ensure_p2p_not_running_result_(false),
+ perform_housekeeping_result_(false),
+ count_shared_files_result_(0) {}
+
+ // P2PManager overrides.
+ void SetDevicePolicy(const policy::DevicePolicy* device_policy) override {}
+
+ bool IsP2PEnabled() override {
+ return is_p2p_enabled_;
+ }
+
+ bool EnsureP2PRunning() override {
+ return ensure_p2p_running_result_;
+ }
+
+ bool EnsureP2PNotRunning() override {
+ return ensure_p2p_not_running_result_;
+ }
+
+ bool PerformHousekeeping() override {
+ return perform_housekeeping_result_;
+ }
+
+ void LookupUrlForFile(const std::string& file_id,
+ size_t minimum_size,
+ base::TimeDelta max_time_to_wait,
+ LookupCallback callback) override {
+ callback.Run(lookup_url_for_file_result_);
+ }
+
+ bool FileShare(const std::string& file_id,
+ size_t expected_size) override {
+ return false;
+ }
+
+ base::FilePath FileGetPath(const std::string& file_id) override {
+ return base::FilePath();
+ }
+
+ ssize_t FileGetSize(const std::string& file_id) override {
+ return -1;
+ }
+
+ ssize_t FileGetExpectedSize(const std::string& file_id) override {
+ return -1;
+ }
+
+ bool FileGetVisible(const std::string& file_id,
+ bool *out_result) override {
+ return false;
+ }
+
+ bool FileMakeVisible(const std::string& file_id) override {
+ return false;
+ }
+
+ int CountSharedFiles() override {
+ return count_shared_files_result_;
+ }
+
+ // Methods for controlling what the fake returns and how it acts.
+ void SetP2PEnabled(bool is_p2p_enabled) {
+ is_p2p_enabled_ = is_p2p_enabled;
+ }
+
+ void SetEnsureP2PRunningResult(bool ensure_p2p_running_result) {
+ ensure_p2p_running_result_ = ensure_p2p_running_result;
+ }
+
+ void SetEnsureP2PNotRunningResult(bool ensure_p2p_not_running_result) {
+ ensure_p2p_not_running_result_ = ensure_p2p_not_running_result;
+ }
+
+ void SetPerformHousekeepingResult(bool perform_housekeeping_result) {
+ perform_housekeeping_result_ = perform_housekeeping_result;
+ }
+
+ void SetCountSharedFilesResult(int count_shared_files_result) {
+ count_shared_files_result_ = count_shared_files_result;
+ }
+
+ void SetLookupUrlForFileResult(const std::string& url) {
+ lookup_url_for_file_result_ = url;
+ }
+
+ private:
+ bool is_p2p_enabled_;
+ bool ensure_p2p_running_result_;
+ bool ensure_p2p_not_running_result_;
+ bool perform_housekeeping_result_;
+ int count_shared_files_result_;
+ std::string lookup_url_for_file_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeP2PManager);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_P2P_MANAGER_H_
diff --git a/fake_p2p_manager_configuration.h b/fake_p2p_manager_configuration.h
new file mode 100644
index 0000000..1dc7c6e
--- /dev/null
+++ b/fake_p2p_manager_configuration.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_
+#define UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_
+
+#include "update_engine/p2p_manager.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/string_number_conversions.h>
+
+namespace chromeos_update_engine {
+
+// Configuration for P2PManager for use in unit tests. Instead of
+// /var/cache/p2p, a temporary directory is used.
+class FakeP2PManagerConfiguration : public P2PManager::Configuration {
+ public:
+ FakeP2PManagerConfiguration() {
+ EXPECT_TRUE(utils::MakeTempDirectory("/tmp/p2p-tc.XXXXXX", &p2p_dir_));
+ }
+
+ ~FakeP2PManagerConfiguration() {
+ if (p2p_dir_.size() > 0 && !test_utils::RecursiveUnlinkDir(p2p_dir_)) {
+ PLOG(ERROR) << "Unable to unlink files and directory in " << p2p_dir_;
+ }
+ }
+
+ // P2PManager::Configuration override
+ base::FilePath GetP2PDir() override {
+ return base::FilePath(p2p_dir_);
+ }
+
+ // P2PManager::Configuration override
+ std::vector<std::string> GetInitctlArgs(bool is_start) override {
+ return is_start ? initctl_start_args_ : initctl_stop_args_;
+ }
+
+ // P2PManager::Configuration override
+ std::vector<std::string> GetP2PClientArgs(const std::string &file_id,
+ size_t minimum_size) override {
+ std::vector<std::string> formatted_command = p2p_client_cmd_format_;
+ // Replace {variable} on the passed string.
+ std::string str_minimum_size = base::SizeTToString(minimum_size);
+ for (std::string& arg : formatted_command) {
+ ReplaceSubstringsAfterOffset(&arg, 0, "{file_id}", file_id);
+ ReplaceSubstringsAfterOffset(&arg, 0, "{minsize}", str_minimum_size);
+ }
+ return formatted_command;
+ }
+
+ // Use |command_line| instead of "initctl start p2p" when attempting
+ // to start the p2p service.
+ void SetInitctlStartCommand(const std::vector<std::string>& command) {
+ initctl_start_args_ = command;
+ }
+
+ // Use |command_line| instead of "initctl stop p2p" when attempting
+ // to stop the p2p service.
+ void SetInitctlStopCommand(const std::vector<std::string>& command) {
+ initctl_stop_args_ = command;
+ }
+
+ // Use |command_format| instead of "p2p-client --get-url={file_id}
+ // --minimum-size={minsize}" when attempting to look up a file using
+ // p2p-client(1).
+ //
+ // The passed |command_format| argument can have "{file_id}" and "{minsize}"
+ // as substrings of any of its elements, that will be replaced by the
+ // corresponding values passed to GetP2PClientArgs().
+ void SetP2PClientCommand(const std::vector<std::string>& command_format) {
+ p2p_client_cmd_format_ = command_format;
+ }
+
+ private:
+ // The temporary directory used for p2p.
+ std::string p2p_dir_;
+
+ // Argument vector for starting p2p.
+ std::vector<std::string> initctl_start_args_{"initctl", "start", "p2p"};
+
+ // Argument vector for stopping p2p.
+ std::vector<std::string> initctl_stop_args_{"initctl", "stop", "p2p"};
+
+ // A string for generating the p2p-client command. See the
+ // SetP2PClientCommandLine() for details.
+ std::vector<std::string> p2p_client_cmd_format_{
+ "p2p-client", "--get-url={file_id}", "--minimum-size={minsize}"};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeP2PManagerConfiguration);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_
diff --git a/fake_prefs.cc b/fake_prefs.cc
new file mode 100644
index 0000000..64ff18a
--- /dev/null
+++ b/fake_prefs.cc
@@ -0,0 +1,122 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/fake_prefs.h"
+
+#include <gtest/gtest.h>
+
+using std::string;
+
+using chromeos_update_engine::FakePrefs;
+
+namespace {
+
+void CheckNotNull(const string& key, void* ptr) {
+ EXPECT_NE(nullptr, ptr)
+ << "Called Get*() for key \"" << key << "\" with a null parameter.";
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+// Compile-time type-dependent constants definitions.
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<string>::type =
+ FakePrefs::PrefType::kString;
+template<>
+string FakePrefs::PrefValue::* const // NOLINT(runtime/string), not static str.
+ FakePrefs::PrefConsts<string>::member = &FakePrefs::PrefValue::as_str;
+
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<int64_t>::type =
+ FakePrefs::PrefType::kInt64;
+template<>
+int64_t FakePrefs::PrefValue::* const FakePrefs::PrefConsts<int64_t>::member =
+ &FakePrefs::PrefValue::as_int64;
+
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<bool>::type =
+ FakePrefs::PrefType::kBool;
+template<>
+bool FakePrefs::PrefValue::* const FakePrefs::PrefConsts<bool>::member =
+ &FakePrefs::PrefValue::as_bool;
+
+
+bool FakePrefs::GetString(const string& key, string* value) {
+ return GetValue(key, value);
+}
+
+bool FakePrefs::SetString(const string& key, const string& value) {
+ SetValue(key, value);
+ return true;
+}
+
+bool FakePrefs::GetInt64(const string& key, int64_t* value) {
+ return GetValue(key, value);
+}
+
+bool FakePrefs::SetInt64(const string& key, const int64_t value) {
+ SetValue(key, value);
+ return true;
+}
+
+bool FakePrefs::GetBoolean(const string& key, bool* value) {
+ return GetValue(key, value);
+}
+
+bool FakePrefs::SetBoolean(const string& key, const bool value) {
+ SetValue(key, value);
+ return true;
+}
+
+bool FakePrefs::Exists(const string& key) {
+ return values_.find(key) != values_.end();
+}
+
+bool FakePrefs::Delete(const string& key) {
+ if (values_.find(key) == values_.end())
+ return false;
+ values_.erase(key);
+ return true;
+}
+
+string FakePrefs::GetTypeName(PrefType type) {
+ switch (type) {
+ case PrefType::kString:
+ return "string";
+ case PrefType::kInt64:
+ return "int64_t";
+ case PrefType::kBool:
+ return "bool";
+ }
+ return "Unknown";
+}
+
+void FakePrefs::CheckKeyType(const string& key, PrefType type) const {
+ auto it = values_.find(key);
+ EXPECT_TRUE(it == values_.end() || it->second.type == type)
+ << "Key \"" << key << "\" if defined as " << GetTypeName(it->second.type)
+ << " but is accessed as a " << GetTypeName(type);
+}
+
+template<typename T>
+void FakePrefs::SetValue(const string& key, const T& value) {
+ CheckKeyType(key, PrefConsts<T>::type);
+ values_[key].type = PrefConsts<T>::type;
+ values_[key].value.*(PrefConsts<T>::member) = value;
+}
+
+template<typename T>
+bool FakePrefs::GetValue(const string& key, T* value) const {
+ CheckKeyType(key, PrefConsts<T>::type);
+ auto it = values_.find(key);
+ if (it == values_.end())
+ return false;
+ CheckNotNull(key, value);
+ *value = it->second.value.*(PrefConsts<T>::member);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/fake_prefs.h b/fake_prefs.h
new file mode 100644
index 0000000..f4174bf
--- /dev/null
+++ b/fake_prefs.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_FAKE_PREFS_H_
+#define UPDATE_ENGINE_FAKE_PREFS_H_
+
+#include <map>
+#include <string>
+
+#include <base/macros.h>
+
+#include "update_engine/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake preference store by storing the value associated with
+// a key in a std::map, suitable for testing. It doesn't allow to set a value on
+// a key with a different type than the previously set type. This enforces the
+// type of a given key to be fixed. Also the class checks that the Get*()
+// methods aren't called on a key set with a different type.
+
+class FakePrefs : public PrefsInterface {
+ public:
+ FakePrefs() {}
+
+ // PrefsInterface methods.
+ bool GetString(const std::string& key, std::string* value) override;
+ bool SetString(const std::string& key, const std::string& value) override;
+ bool GetInt64(const std::string& key, int64_t* value) override;
+ bool SetInt64(const std::string& key, const int64_t value) override;
+ bool GetBoolean(const std::string& key, bool* value) override;
+ bool SetBoolean(const std::string& key, const bool value) override;
+
+ bool Exists(const std::string& key) override;
+ bool Delete(const std::string& key) override;
+
+ private:
+ enum class PrefType {
+ kString,
+ kInt64,
+ kBool,
+ };
+ struct PrefValue {
+ std::string as_str;
+ int64_t as_int64;
+ bool as_bool;
+ };
+
+ struct PrefTypeValue {
+ PrefType type;
+ PrefValue value;
+ };
+
+ // Class to store compile-time type-dependent constants.
+ template<typename T>
+ class PrefConsts {
+ public:
+ // The PrefType associated with T.
+ static FakePrefs::PrefType const type;
+
+ // The data member pointer to PrefValue associated with T.
+ static T FakePrefs::PrefValue::* const member;
+ };
+
+ // Returns a string representation of the PrefType useful for logging.
+ static std::string GetTypeName(PrefType type);
+
+ // Checks that the |key| is either not present or has the given |type|.
+ void CheckKeyType(const std::string& key, PrefType type) const;
+
+ // Helper function to set a value of the passed |key|. It sets the type based
+ // on the template parameter T.
+ template<typename T>
+ void SetValue(const std::string& key, const T& value);
+
+ // Helper function to get a value from the map checking for invalid calls.
+ // The function fails the test if you attempt to read a value defined as a
+ // different type. Returns whether the get succeeded.
+ template<typename T>
+ bool GetValue(const std::string& key, T* value) const;
+
+ // Container for all the key/value pairs.
+ std::map<std::string, PrefTypeValue> values_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakePrefs);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_PREFS_H_
diff --git a/fake_system_state.cc b/fake_system_state.cc
new file mode 100644
index 0000000..8747a59
--- /dev/null
+++ b/fake_system_state.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/fake_system_state.h"
+
+namespace chromeos_update_engine {
+
+// Mock the SystemStateInterface so that we could lie that
+// OOBE is completed even when there's no such marker file, etc.
+FakeSystemState::FakeSystemState()
+ : mock_connection_manager_(this),
+ mock_update_attempter_(this, &dbus_),
+ mock_request_params_(this),
+ fake_update_manager_(&fake_clock_),
+ clock_(&fake_clock_),
+ connection_manager_(&mock_connection_manager_),
+ hardware_(&fake_hardware_),
+ metrics_lib_(&mock_metrics_lib_),
+ prefs_(&mock_prefs_),
+ powerwash_safe_prefs_(&mock_powerwash_safe_prefs_),
+ payload_state_(&mock_payload_state_),
+ update_attempter_(&mock_update_attempter_),
+ request_params_(&mock_request_params_),
+ p2p_manager_(&mock_p2p_manager_),
+ update_manager_(&fake_update_manager_),
+ device_policy_(nullptr),
+ fake_system_rebooted_(false) {
+ mock_payload_state_.Initialize(this);
+ mock_update_attempter_.Init();
+}
+
+} // namespace chromeos_update_engine
diff --git a/fake_system_state.h b/fake_system_state.h
new file mode 100644
index 0000000..84c37fc
--- /dev/null
+++ b/fake_system_state.h
@@ -0,0 +1,239 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_
+
+#include <base/logging.h>
+#include <gmock/gmock.h>
+#include <policy/mock_device_policy.h>
+
+#include "metrics/metrics_library_mock.h"
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_hardware.h"
+#include "update_engine/mock_connection_manager.h"
+#include "update_engine/mock_dbus_wrapper.h"
+#include "update_engine/mock_omaha_request_params.h"
+#include "update_engine/mock_p2p_manager.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/mock_update_attempter.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+
+namespace chromeos_update_engine {
+
+// Mock the SystemStateInterface so that we could lie that
+// OOBE is completed even when there's no such marker file, etc.
+class FakeSystemState : public SystemState {
+ public:
+ FakeSystemState();
+
+ // Base class overrides. All getters return the current implementation of
+ // various members, either the default (fake/mock) or the one set to override
+ // it by client code.
+
+ inline ClockInterface* clock() override { return clock_; }
+
+ inline void set_device_policy(
+ const policy::DevicePolicy* device_policy) override {
+ device_policy_ = device_policy;
+ }
+
+ inline const policy::DevicePolicy* device_policy() override {
+ return device_policy_;
+ }
+
+ inline ConnectionManager* connection_manager() override {
+ return connection_manager_;
+ }
+
+ inline HardwareInterface* hardware() override { return hardware_; }
+
+ inline MetricsLibraryInterface* metrics_lib() override {
+ return metrics_lib_;
+ }
+
+ inline PrefsInterface* prefs() override { return prefs_; }
+
+ inline PrefsInterface* powerwash_safe_prefs() override {
+ return powerwash_safe_prefs_;
+ }
+
+ inline PayloadStateInterface* payload_state() override {
+ return payload_state_;
+ }
+
+ inline UpdateAttempter* update_attempter() override {
+ return update_attempter_;
+ }
+
+ inline OmahaRequestParams* request_params() override {
+ return request_params_;
+ }
+
+ inline P2PManager* p2p_manager() override { return p2p_manager_; }
+
+ inline chromeos_update_manager::UpdateManager* update_manager() override {
+ return update_manager_;
+ }
+
+ inline bool system_rebooted() override { return fake_system_rebooted_; }
+
+ // Setters for the various members, can be used for overriding the default
+ // implementations. For convenience, setting to a null pointer will restore
+ // the default implementation.
+
+ inline void set_clock(ClockInterface* clock) {
+ clock_ = clock ? clock : &fake_clock_;
+ }
+
+ inline void set_connection_manager(ConnectionManager* connection_manager) {
+ connection_manager_ = (connection_manager ? connection_manager :
+ &mock_connection_manager_);
+ }
+
+ inline void set_hardware(HardwareInterface* hardware) {
+ hardware_ = hardware ? hardware : &fake_hardware_;
+ }
+
+ inline void set_metrics_lib(MetricsLibraryInterface* metrics_lib) {
+ metrics_lib_ = metrics_lib ? metrics_lib : &mock_metrics_lib_;
+ }
+
+ inline void set_prefs(PrefsInterface* prefs) {
+ prefs_ = prefs ? prefs : &mock_prefs_;
+ }
+
+ inline void set_powerwash_safe_prefs(PrefsInterface* powerwash_safe_prefs) {
+ powerwash_safe_prefs_ = (powerwash_safe_prefs ? powerwash_safe_prefs :
+ &mock_powerwash_safe_prefs_);
+ }
+
+ inline void set_payload_state(PayloadStateInterface *payload_state) {
+ payload_state_ = payload_state ? payload_state : &mock_payload_state_;
+ }
+
+ inline void set_update_attempter(UpdateAttempter* update_attempter) {
+ update_attempter_ = (update_attempter ? update_attempter :
+ &mock_update_attempter_);
+ }
+
+ inline void set_request_params(OmahaRequestParams* request_params) {
+ request_params_ = (request_params ? request_params :
+ &mock_request_params_);
+ }
+
+ inline void set_p2p_manager(P2PManager *p2p_manager) {
+ p2p_manager_ = p2p_manager ? p2p_manager : &mock_p2p_manager_;
+ }
+
+ inline void set_update_manager(
+ chromeos_update_manager::UpdateManager *update_manager) {
+ update_manager_ = update_manager ? update_manager : &fake_update_manager_;
+ }
+
+ inline void set_system_rebooted(bool system_rebooted) {
+ fake_system_rebooted_ = system_rebooted;
+ }
+
+ // Getters for the built-in default implementations. These return the actual
+ // concrete type of each implementation. For additional safety, they will fail
+ // whenever the requested default was overridden by a different
+ // implementation.
+
+ inline FakeClock* fake_clock() {
+ CHECK(clock_ == &fake_clock_);
+ return &fake_clock_;
+ }
+
+ inline testing::NiceMock<MockConnectionManager>* mock_connection_manager() {
+ CHECK(connection_manager_ == &mock_connection_manager_);
+ return &mock_connection_manager_;
+ }
+
+ inline FakeHardware* fake_hardware() {
+ CHECK(hardware_ == &fake_hardware_);
+ return &fake_hardware_;
+ }
+
+ inline testing::NiceMock<MetricsLibraryMock>* mock_metrics_lib() {
+ CHECK(metrics_lib_ == &mock_metrics_lib_);
+ return &mock_metrics_lib_;
+ }
+
+ inline testing::NiceMock<MockPrefs> *mock_prefs() {
+ CHECK(prefs_ == &mock_prefs_);
+ return &mock_prefs_;
+ }
+
+ inline testing::NiceMock<MockPrefs> *mock_powerwash_safe_prefs() {
+ CHECK(powerwash_safe_prefs_ == &mock_powerwash_safe_prefs_);
+ return &mock_powerwash_safe_prefs_;
+ }
+
+ inline testing::NiceMock<MockPayloadState>* mock_payload_state() {
+ CHECK(payload_state_ == &mock_payload_state_);
+ return &mock_payload_state_;
+ }
+
+ inline testing::NiceMock<MockUpdateAttempter>* mock_update_attempter() {
+ CHECK(update_attempter_ == &mock_update_attempter_);
+ return &mock_update_attempter_;
+ }
+
+ inline testing::NiceMock<MockOmahaRequestParams>* mock_request_params() {
+ CHECK(request_params_ == &mock_request_params_);
+ return &mock_request_params_;
+ }
+
+ inline testing::NiceMock<MockP2PManager>* mock_p2p_manager() {
+ CHECK(p2p_manager_ == &mock_p2p_manager_);
+ return &mock_p2p_manager_;
+ }
+
+ inline chromeos_update_manager::FakeUpdateManager* fake_update_manager() {
+ CHECK(update_manager_ == &fake_update_manager_);
+ return &fake_update_manager_;
+ }
+
+ private:
+ // Default mock/fake implementations (owned).
+ FakeClock fake_clock_;
+ testing::NiceMock<MockConnectionManager> mock_connection_manager_;
+ FakeHardware fake_hardware_;
+ testing::NiceMock<MetricsLibraryMock> mock_metrics_lib_;
+ testing::NiceMock<MockPrefs> mock_prefs_;
+ testing::NiceMock<MockPrefs> mock_powerwash_safe_prefs_;
+ testing::NiceMock<MockPayloadState> mock_payload_state_;
+ testing::NiceMock<MockUpdateAttempter> mock_update_attempter_;
+ testing::NiceMock<MockOmahaRequestParams> mock_request_params_;
+ testing::NiceMock<MockP2PManager> mock_p2p_manager_;
+ chromeos_update_manager::FakeUpdateManager fake_update_manager_;
+
+ // Pointers to objects that client code can override. They are initialized to
+ // the default implementations above.
+ ClockInterface* clock_;
+ ConnectionManager* connection_manager_;
+ HardwareInterface* hardware_;
+ MetricsLibraryInterface* metrics_lib_;
+ PrefsInterface* prefs_;
+ PrefsInterface* powerwash_safe_prefs_;
+ PayloadStateInterface* payload_state_;
+ UpdateAttempter* update_attempter_;
+ OmahaRequestParams* request_params_;
+ P2PManager* p2p_manager_;
+ chromeos_update_manager::UpdateManager* update_manager_;
+
+ // Other object pointers (not preinitialized).
+ const policy::DevicePolicy* device_policy_;
+
+ // Other data members.
+ MockDBusWrapper dbus_;
+ bool fake_system_rebooted_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_
diff --git a/file_descriptor.cc b/file_descriptor.cc
new file mode 100644
index 0000000..9907a8c
--- /dev/null
+++ b/file_descriptor.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/file_descriptor.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <base/posix/eintr_wrapper.h>
+
+namespace chromeos_update_engine {
+
+bool EintrSafeFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+ CHECK_EQ(fd_, -1);
+ return ((fd_ = HANDLE_EINTR(open(path, flags, mode))) >= 0);
+}
+
+bool EintrSafeFileDescriptor::Open(const char* path, int flags) {
+ CHECK_EQ(fd_, -1);
+ return ((fd_ = HANDLE_EINTR(open(path, flags))) >= 0);
+}
+
+ssize_t EintrSafeFileDescriptor::Read(void* buf, size_t count) {
+ CHECK_GE(fd_, 0);
+ return HANDLE_EINTR(read(fd_, buf, count));
+}
+
+ssize_t EintrSafeFileDescriptor::Write(const void* buf, size_t count) {
+ CHECK_GE(fd_, 0);
+
+ // Attempt repeated writes, as long as some progress is being made.
+ char* char_buf = const_cast<char*>(reinterpret_cast<const char*>(buf));
+ ssize_t written = 0;
+ while (count > 0) {
+ ssize_t ret = HANDLE_EINTR(write(fd_, char_buf, count));
+
+ // Fail on either an error or no progress.
+ if (ret <= 0)
+ return (written ? written : ret);
+ written += ret;
+ count -= ret;
+ char_buf += ret;
+ }
+ return written;
+}
+
+off64_t EintrSafeFileDescriptor::Seek(off64_t offset, int whence) {
+ CHECK_GE(fd_, 0);
+ return lseek64(fd_, offset, whence);
+}
+
+bool EintrSafeFileDescriptor::Close() {
+ CHECK_GE(fd_, 0);
+ if (IGNORE_EINTR(close(fd_)))
+ return false;
+ Reset();
+ return true;
+}
+
+void EintrSafeFileDescriptor::Reset() {
+ fd_ = -1;
+}
+
+} // namespace chromeos_update_engine
diff --git a/file_descriptor.h b/file_descriptor.h
new file mode 100644
index 0000000..f2e5caf
--- /dev/null
+++ b/file_descriptor.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_FILE_DESCRIPTOR_H_
+#define UPDATE_ENGINE_FILE_DESCRIPTOR_H_
+
+#include <errno.h>
+#include <memory>
+#include <sys/types.h>
+
+#include <base/logging.h>
+
+// Abstraction for managing opening, reading, writing and closing of file
+// descriptors. This includes an abstract class and one standard implementation
+// based on POSIX system calls.
+//
+// TODO(garnold) this class is modeled after (and augments the functionality of)
+// the FileWriter class; ultimately, the latter should be replaced by the former
+// throughout the codebase. A few deviations from the original FileWriter:
+//
+// * Providing two flavors of Open()
+//
+// * A FileDescriptor is reusable and can be used to read/write multiple files
+// as long as open/close preconditions are respected.
+//
+// * Write() returns the number of bytes written: this appears to be more useful
+// for clients, who may wish to retry or otherwise do something useful with
+// the remaining data that was not written.
+//
+// * Provides a Reset() method, which will force to abandon a currently open
+// file descriptor and allow opening another file, without necessarily
+// properly closing the old one. This may be useful in cases where a "closer"
+// class does not care whether Close() was successful, but may need to reuse
+// the same file descriptor again.
+
+namespace chromeos_update_engine {
+
+class FileDescriptor;
+using FileDescriptorPtr = std::shared_ptr<FileDescriptor>;
+
+// An abstract class defining the file descriptor API.
+class FileDescriptor {
+ public:
+ FileDescriptor() {}
+ virtual ~FileDescriptor() {}
+
+ // Opens a file descriptor. The descriptor must be in the closed state prior
+ // to this call. Returns true on success, false otherwise. Specific
+ // implementations may set errno accordingly.
+ virtual bool Open(const char* path, int flags, mode_t mode) = 0;
+ virtual bool Open(const char* path, int flags) = 0;
+
+ // Reads from a file descriptor up to a given count. The descriptor must be
+ // open prior to this call. Returns the number of bytes read, or -1 on error.
+ // Specific implementations may set errno accordingly.
+ virtual ssize_t Read(void* buf, size_t count) = 0;
+
+ // Writes to a file descriptor. The descriptor must be open prior to this
+ // call. Returns the number of bytes written, or -1 if an error occurred and
+ // no bytes were written. Specific implementations may set errno accordingly.
+ virtual ssize_t Write(const void* buf, size_t count) = 0;
+
+ // Seeks to an offset. Returns the resulting offset location as measured in
+ // bytes from the beginning. On error, return -1. Specific implementations
+ // may set errno accordingly.
+ virtual off64_t Seek(off64_t offset, int whence) = 0;
+
+ // Closes a file descriptor. The descriptor must be open prior to this call.
+ // Returns true on success, false otherwise. Specific implementations may set
+ // errno accordingly.
+ virtual bool Close() = 0;
+
+ // Resets the file descriptor, abandoning a currently open file and returning
+ // the descriptor to the closed state.
+ virtual void Reset() = 0;
+
+ // Indicates whether or not an implementation sets meaningful errno.
+ virtual bool IsSettingErrno() = 0;
+
+ // Indicates whether the descriptor is currently open.
+ virtual bool IsOpen() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileDescriptor);
+};
+
+// A simple EINTR-immune wrapper implementation around standard system calls.
+class EintrSafeFileDescriptor : public FileDescriptor {
+ public:
+ EintrSafeFileDescriptor() : fd_(-1) {}
+
+ // Interface methods.
+ bool Open(const char* path, int flags, mode_t mode) override;
+ bool Open(const char* path, int flags) override;
+ ssize_t Read(void* buf, size_t count) override;
+ ssize_t Write(const void* buf, size_t count) override;
+ off64_t Seek(off64_t offset, int whence) override;
+ bool Close() override;
+ void Reset() override;
+ bool IsSettingErrno() override {
+ return true;
+ }
+ bool IsOpen() override {
+ return (fd_ >= 0);
+ }
+
+ protected:
+ int fd_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FILE_DESCRIPTOR_H_
diff --git a/file_writer.cc b/file_writer.cc
new file mode 100644
index 0000000..8f8e989
--- /dev/null
+++ b/file_writer.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/file_writer.h"
+
+#include <errno.h>
+
+namespace chromeos_update_engine {
+
+int DirectFileWriter::Open(const char* path, int flags, mode_t mode) {
+ CHECK_EQ(fd_, -1);
+ fd_ = open(path, flags, mode);
+ if (fd_ < 0)
+ return -errno;
+ return 0;
+}
+
+bool DirectFileWriter::Write(const void* bytes, size_t count) {
+ CHECK_GE(fd_, 0);
+ const char* char_bytes = reinterpret_cast<const char*>(bytes);
+
+ size_t bytes_written = 0;
+ while (bytes_written < count) {
+ ssize_t rc = write(fd_, char_bytes + bytes_written,
+ count - bytes_written);
+ if (rc < 0)
+ return false;
+ bytes_written += rc;
+ }
+ CHECK_EQ(bytes_written, count);
+ return bytes_written == count;
+}
+
+int DirectFileWriter::Close() {
+ CHECK_GE(fd_, 0);
+ int rc = close(fd_);
+
+ // This can be any negative number that's not -1. This way, this FileWriter
+ // won't be used again for another file.
+ fd_ = -2;
+
+ if (rc < 0)
+ return -errno;
+ return rc;
+}
+
+} // namespace chromeos_update_engine
diff --git a/file_writer.h b/file_writer.h
new file mode 100644
index 0000000..f800643
--- /dev/null
+++ b/file_writer.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_FILE_WRITER_H_
+#define UPDATE_ENGINE_FILE_WRITER_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/logging.h>
+
+#include "update_engine/error_code.h"
+#include "update_engine/utils.h"
+
+// FileWriter is a class that is used to (synchronously, for now) write to
+// a file. This file is a thin wrapper around open/write/close system calls,
+// but provides and interface that can be customized by subclasses that wish
+// to filter the data.
+
+namespace chromeos_update_engine {
+
+class FileWriter {
+ public:
+ FileWriter() {}
+ virtual ~FileWriter() {}
+
+ // Wrapper around open. Returns 0 on success or -errno on error.
+ virtual int Open(const char* path, int flags, mode_t mode) = 0;
+
+ // Wrapper around write. Returns true if all requested bytes
+ // were written, or false on any error, regardless of progress.
+ virtual bool Write(const void* bytes, size_t count) = 0;
+
+ // Same as the Write method above but returns a detailed |error| code
+ // in addition if the returned value is false. By default this method
+ // returns kActionExitDownloadWriteError as the error code, but subclasses
+ // can override if they wish to return more specific error codes.
+ virtual bool Write(const void* bytes,
+ size_t count,
+ ErrorCode* error) {
+ *error = ErrorCode::kDownloadWriteError;
+ return Write(bytes, count);
+ }
+
+ // Wrapper around close. Returns 0 on success or -errno on error.
+ virtual int Close() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileWriter);
+};
+
+// Direct file writer is probably the simplest FileWriter implementation.
+// It calls the system calls directly.
+
+class DirectFileWriter : public FileWriter {
+ public:
+ DirectFileWriter() : fd_(-1) {}
+
+ int Open(const char* path, int flags, mode_t mode) override;
+ bool Write(const void* bytes, size_t count) override;
+ int Close() override;
+
+ int fd() const { return fd_; }
+
+ private:
+ int fd_;
+
+ DISALLOW_COPY_AND_ASSIGN(DirectFileWriter);
+};
+
+class ScopedFileWriterCloser {
+ public:
+ explicit ScopedFileWriterCloser(FileWriter* writer) : writer_(writer) {}
+ ~ScopedFileWriterCloser() {
+ int err = writer_->Close();
+ if (err)
+ LOG(ERROR) << "FileWriter::Close failed: "
+ << utils::ErrnoNumberAsString(-err);
+ }
+ private:
+ FileWriter* writer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedFileWriterCloser);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FILE_WRITER_H_
diff --git a/file_writer_unittest.cc b/file_writer_unittest.cc
new file mode 100644
index 0000000..5bdea3c
--- /dev/null
+++ b/file_writer_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/file_writer.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FileWriterTest : public ::testing::Test { };
+
+TEST(FileWriterTest, SimpleTest) {
+ // Create a uniquely named file for testing.
+ string path;
+ ASSERT_TRUE(utils::MakeTempFile("FileWriterTest-XXXXXX", &path, nullptr));
+ ScopedPathUnlinker path_unlinker(path);
+
+ DirectFileWriter file_writer;
+ EXPECT_EQ(0, file_writer.Open(path.c_str(),
+ O_CREAT | O_LARGEFILE | O_TRUNC | O_WRONLY,
+ 0644));
+ EXPECT_TRUE(file_writer.Write("test", 4));
+ chromeos::Blob actual_data;
+ EXPECT_TRUE(utils::ReadFile(path, &actual_data));
+
+ EXPECT_FALSE(memcmp("test", actual_data.data(), actual_data.size()));
+ EXPECT_EQ(0, file_writer.Close());
+}
+
+TEST(FileWriterTest, ErrorTest) {
+ DirectFileWriter file_writer;
+ const string path("/tmp/ENOENT/FileWriterTest");
+ EXPECT_EQ(-ENOENT, file_writer.Open(path.c_str(),
+ O_CREAT | O_LARGEFILE | O_TRUNC, 0644));
+}
+
+TEST(FileWriterTest, WriteErrorTest) {
+ // Create a uniquely named file for testing.
+ string path;
+ ASSERT_TRUE(utils::MakeTempFile("FileWriterTest-XXXXXX", &path, nullptr));
+ ScopedPathUnlinker path_unlinker(path);
+
+ DirectFileWriter file_writer;
+ EXPECT_EQ(0, file_writer.Open(path.c_str(),
+ O_CREAT | O_LARGEFILE | O_TRUNC | O_RDONLY,
+ 0644));
+ EXPECT_FALSE(file_writer.Write("x", 1));
+ EXPECT_EQ(0, file_writer.Close());
+}
+
+
+} // namespace chromeos_update_engine
diff --git a/filesystem_verifier_action.cc b/filesystem_verifier_action.cc
new file mode 100644
index 0000000..70156d9
--- /dev/null
+++ b/filesystem_verifier_action.cc
@@ -0,0 +1,244 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/filesystem_verifier_action.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <string>
+
+#include <base/bind.h>
+#include <base/posix/eintr_wrapper.h>
+
+#include "update_engine/hardware_interface.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/system_state.h"
+#include "update_engine/utils.h"
+
+using chromeos::MessageLoop;
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+const off_t kReadFileBufferSize = 128 * 1024;
+} // namespace
+
+FilesystemVerifierAction::FilesystemVerifierAction(
+ SystemState* system_state,
+ PartitionType partition_type)
+ : partition_type_(partition_type),
+ remaining_size_(kint64max),
+ system_state_(system_state) {}
+
+void FilesystemVerifierAction::PerformAction() {
+ // Will tell the ActionProcessor we've failed if we return.
+ ScopedActionCompleter abort_action_completer(processor_, this);
+
+ if (!HasInputObject()) {
+ LOG(ERROR) << "FilesystemVerifierAction missing input object.";
+ return;
+ }
+ install_plan_ = GetInputObject();
+
+ if (partition_type_ == PartitionType::kKernel) {
+ LOG(INFO) << "verifying kernel, marking as unbootable";
+ if (!system_state_->hardware()->MarkKernelUnbootable(
+ install_plan_.kernel_install_path)) {
+ PLOG(ERROR) << "Unable to clear kernel GPT boot flags: " <<
+ install_plan_.kernel_install_path;
+ }
+ }
+
+ if (install_plan_.is_full_update &&
+ (partition_type_ == PartitionType::kSourceRootfs ||
+ partition_type_ == PartitionType::kSourceKernel)) {
+ // No hash verification needed. Done!
+ LOG(INFO) << "filesystem verifying skipped on full update.";
+ if (HasOutputPipe())
+ SetOutputObject(install_plan_);
+ abort_action_completer.set_code(ErrorCode::kSuccess);
+ return;
+ }
+
+ string target_path;
+ switch (partition_type_) {
+ case PartitionType::kRootfs:
+ target_path = install_plan_.install_path;
+ if (target_path.empty()) {
+ utils::GetInstallDev(system_state_->hardware()->BootDevice(),
+ &target_path);
+ }
+ break;
+ case PartitionType::kKernel:
+ target_path = install_plan_.kernel_install_path;
+ if (target_path.empty()) {
+ string rootfs_path;
+ utils::GetInstallDev(system_state_->hardware()->BootDevice(),
+ &rootfs_path);
+ target_path = utils::KernelDeviceOfBootDevice(rootfs_path);
+ }
+ break;
+ case PartitionType::kSourceRootfs:
+ target_path = install_plan_.source_path.empty() ?
+ system_state_->hardware()->BootDevice() :
+ install_plan_.source_path;
+ break;
+ case PartitionType::kSourceKernel:
+ target_path = install_plan_.kernel_source_path.empty() ?
+ utils::KernelDeviceOfBootDevice(
+ system_state_->hardware()->BootDevice()) :
+ install_plan_.kernel_source_path;
+ break;
+ }
+
+ src_fd_ = HANDLE_EINTR(open(target_path.c_str(), O_RDONLY));
+ if (src_fd_ < 0) {
+ PLOG(ERROR) << "Unable to open " << target_path << " for reading";
+ return;
+ }
+
+ DetermineFilesystemSize(src_fd_);
+ buffer_.resize(kReadFileBufferSize);
+
+ // Start the first read.
+ read_task_ = MessageLoop::current()->WatchFileDescriptor(
+ FROM_HERE,
+ src_fd_,
+ MessageLoop::WatchMode::kWatchRead,
+ true, // persistent
+ base::Bind(&FilesystemVerifierAction::OnReadReadyCallback,
+ base::Unretained(this)));
+
+ abort_action_completer.set_should_complete(false);
+}
+
+void FilesystemVerifierAction::TerminateProcessing() {
+ cancelled_ = true;
+ Cleanup(ErrorCode::kSuccess); // error code is ignored if canceled_ is true.
+}
+
+bool FilesystemVerifierAction::IsCleanupPending() const {
+ return (src_fd_ != -1);
+}
+
+void FilesystemVerifierAction::Cleanup(ErrorCode code) {
+ MessageLoop::current()->CancelTask(read_task_);
+ read_task_ = MessageLoop::kTaskIdNull;
+
+ if (src_fd_ != -1) {
+ if (IGNORE_EINTR(close(src_fd_)) != 0) {
+ PLOG(ERROR) << "Error closing fd " << src_fd_;
+ }
+ src_fd_ = -1;
+ }
+ // This memory is not used anymore.
+ buffer_.clear();
+
+ if (cancelled_)
+ return;
+ if (code == ErrorCode::kSuccess && HasOutputPipe())
+ SetOutputObject(install_plan_);
+ processor_->ActionComplete(this, code);
+}
+
+void FilesystemVerifierAction::OnReadReadyCallback() {
+ size_t bytes_to_read = std::min(static_cast<int64_t>(buffer_.size()),
+ remaining_size_);
+
+ ssize_t bytes_read = 0;
+ if (bytes_to_read) {
+ bytes_read = HANDLE_EINTR(read(src_fd_, buffer_.data(), bytes_to_read));
+ }
+ if (bytes_read < 0) {
+ PLOG(ERROR) << "Read failed";
+ failed_ = true;
+ } else if (bytes_read == 0) {
+ read_done_ = true;
+ } else {
+ remaining_size_ -= bytes_read;
+ }
+
+ if (bytes_read > 0) {
+ CHECK(!read_done_);
+ if (!hasher_.Update(buffer_.data(), bytes_read)) {
+ LOG(ERROR) << "Unable to update the hash.";
+ failed_ = true;
+ }
+ }
+
+ CheckTerminationConditions();
+}
+
+void FilesystemVerifierAction::CheckTerminationConditions() {
+ if (failed_ || cancelled_) {
+ Cleanup(ErrorCode::kError);
+ return;
+ }
+
+ if (read_done_) {
+ // We're done!
+ ErrorCode code = ErrorCode::kSuccess;
+ if (hasher_.Finalize()) {
+ LOG(INFO) << "Hash: " << hasher_.hash();
+ switch (partition_type_) {
+ case PartitionType::kRootfs:
+ if (install_plan_.rootfs_hash != hasher_.raw_hash()) {
+ code = ErrorCode::kNewRootfsVerificationError;
+ LOG(ERROR) << "New rootfs verification failed.";
+ }
+ break;
+ case PartitionType::kKernel:
+ if (install_plan_.kernel_hash != hasher_.raw_hash()) {
+ code = ErrorCode::kNewKernelVerificationError;
+ LOG(ERROR) << "New kernel verification failed.";
+ }
+ break;
+ case PartitionType::kSourceRootfs:
+ install_plan_.source_rootfs_hash = hasher_.raw_hash();
+ break;
+ case PartitionType::kSourceKernel:
+ install_plan_.source_kernel_hash = hasher_.raw_hash();
+ break;
+ }
+ } else {
+ LOG(ERROR) << "Unable to finalize the hash.";
+ code = ErrorCode::kError;
+ }
+ Cleanup(code);
+ }
+}
+
+void FilesystemVerifierAction::DetermineFilesystemSize(int fd) {
+ switch (partition_type_) {
+ case PartitionType::kRootfs:
+ remaining_size_ = install_plan_.rootfs_size;
+ LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes.";
+ break;
+ case PartitionType::kKernel:
+ remaining_size_ = install_plan_.kernel_size;
+ LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes.";
+ break;
+ case PartitionType::kSourceRootfs:
+ {
+ int block_count = 0, block_size = 0;
+ if (utils::GetFilesystemSizeFromFD(fd, &block_count, &block_size)) {
+ remaining_size_ = static_cast<int64_t>(block_count) * block_size;
+ LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes ("
+ << block_count << "x" << block_size << ").";
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return;
+}
+
+} // namespace chromeos_update_engine
diff --git a/filesystem_verifier_action.h b/filesystem_verifier_action.h
new file mode 100644
index 0000000..69e4972
--- /dev/null
+++ b/filesystem_verifier_action.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_FILESYSTEM_VERIFIER_ACTION_H_
+#define UPDATE_ENGINE_FILESYSTEM_VERIFIER_ACTION_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <chromeos/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/action.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/omaha_hash_calculator.h"
+
+// This action will only do real work if it's a delta update. It will
+// copy the root partition to install partition, and then terminate.
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// The type of filesystem that we are verifying.
+enum class PartitionType {
+ kSourceRootfs,
+ kSourceKernel,
+ kRootfs,
+ kKernel,
+};
+
+class FilesystemVerifierAction : public InstallPlanAction {
+ public:
+ FilesystemVerifierAction(SystemState* system_state,
+ PartitionType partition_type);
+
+ void PerformAction() override;
+ void TerminateProcessing() override;
+
+ // Used for testing. Return true if Cleanup() has not yet been called due
+ // to a callback upon the completion or cancellation of the verifier action.
+ // A test should wait until IsCleanupPending() returns false before
+ // terminating the glib main loop.
+ bool IsCleanupPending() const;
+
+ // Debugging/logging
+ static std::string StaticType() { return "FilesystemVerifierAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ private:
+ friend class FilesystemVerifierActionTest;
+ FRIEND_TEST(FilesystemVerifierActionTest,
+ RunAsRootDetermineFilesystemSizeTest);
+
+ // Callback from the main loop when there's data to read from the file
+ // descriptor.
+ void OnReadReadyCallback();
+
+ // Based on the state of the read buffers, terminates read process and the
+ // action.
+ void CheckTerminationConditions();
+
+ // Cleans up all the variables we use for async operations and tells the
+ // ActionProcessor we're done w/ |code| as passed in. |cancelled_| should be
+ // true if TerminateProcessing() was called.
+ void Cleanup(ErrorCode code);
+
+ // Determine, if possible, the source file system size to avoid copying the
+ // whole partition. Currently this supports only the root file system assuming
+ // it's ext3-compatible.
+ void DetermineFilesystemSize(int fd);
+
+ // The type of the partition that we are verifying.
+ PartitionType partition_type_;
+
+ // If non-null, this is the GUnixInputStream object for the opened source
+ // partition.
+ int src_fd_{-1};
+
+ // Buffer for storing data we read.
+ chromeos::Blob buffer_;
+
+ // The task id for the the in-flight async call.
+ chromeos::MessageLoop::TaskId read_task_{chromeos::MessageLoop::kTaskIdNull};
+
+ bool read_done_{false}; // true if reached EOF on the input stream.
+ bool failed_{false}; // true if the action has failed.
+ bool cancelled_{false}; // true if the action has been cancelled.
+
+ // The install plan we're passed in via the input pipe.
+ InstallPlan install_plan_;
+
+ // Calculates the hash of the data.
+ OmahaHashCalculator hasher_;
+
+ // Reads and hashes this many bytes from the head of the input stream. This
+ // field is initialized when the action is started and decremented as more
+ // bytes get read.
+ int64_t remaining_size_;
+
+ // The global context for update_engine.
+ SystemState* system_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FILESYSTEM_VERIFIER_ACTION_H_
diff --git a/filesystem_verifier_action_unittest.cc b/filesystem_verifier_action_unittest.cc
new file mode 100644
index 0000000..da0fd95
--- /dev/null
+++ b/filesystem_verifier_action_unittest.cc
@@ -0,0 +1,381 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/filesystem_verifier_action.h"
+
+#include <fcntl.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_hardware.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos::MessageLoop;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FilesystemVerifierActionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ EXPECT_EQ(0, chromeos::MessageLoopRunMaxIterations(&loop_, 1));
+ }
+
+ // Returns true iff test has completed successfully.
+ bool DoTest(bool terminate_early,
+ bool hash_fail,
+ PartitionType partition_type);
+
+ chromeos::GlibMessageLoop loop_;
+ FakeSystemState fake_system_state_;
+};
+
+class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
+ public:
+ explicit FilesystemVerifierActionTestDelegate(
+ FilesystemVerifierAction* action)
+ : action_(action), ran_(false), code_(ErrorCode::kError) {}
+ void ExitMainLoop() {
+ // We need to wait for the Action to call Cleanup.
+ if (action_->IsCleanupPending()) {
+ LOG(INFO) << "Waiting for Cleanup() to be called.";
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&FilesystemVerifierActionTestDelegate::ExitMainLoop,
+ base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(100));
+ } else {
+ MessageLoop::current()->BreakLoop();
+ }
+ }
+ void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+ ExitMainLoop();
+ }
+ void ProcessingStopped(const ActionProcessor* processor) {
+ ExitMainLoop();
+ }
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == FilesystemVerifierAction::StaticType()) {
+ ran_ = true;
+ code_ = code;
+ }
+ }
+ bool ran() const { return ran_; }
+ ErrorCode code() const { return code_; }
+
+ private:
+ FilesystemVerifierAction* action_;
+ bool ran_;
+ ErrorCode code_;
+};
+
+void StartProcessorInRunLoop(ActionProcessor* processor,
+ FilesystemVerifierAction* filesystem_copier_action,
+ bool terminate_early) {
+ processor->StartProcessing();
+ if (terminate_early) {
+ EXPECT_NE(nullptr, filesystem_copier_action);
+ processor->StopProcessing();
+ }
+}
+
+// TODO(garnold) Temporarily disabling this test, see chromium-os:31082 for
+// details; still trying to track down the root cause for these rare write
+// failures and whether or not they are due to the test setup or an inherent
+// issue with the chroot environment, library versions we use, etc.
+TEST_F(FilesystemVerifierActionTest, DISABLED_RunAsRootSimpleTest) {
+ ASSERT_EQ(0, getuid());
+ bool test = DoTest(false, false, PartitionType::kKernel);
+ EXPECT_TRUE(test);
+ if (!test)
+ return;
+ test = DoTest(false, false, PartitionType::kRootfs);
+ EXPECT_TRUE(test);
+}
+
+bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
+ bool hash_fail,
+ PartitionType partition_type) {
+ // We need MockHardware to verify MarkUnbootable calls, but don't want
+ // warnings about other usages.
+ testing::NiceMock<MockHardware> mock_hardware;
+ fake_system_state_.set_hardware(&mock_hardware);
+
+ string a_loop_file;
+
+ if (!(utils::MakeTempFile("a_loop_file.XXXXXX", &a_loop_file, nullptr))) {
+ ADD_FAILURE();
+ return false;
+ }
+ ScopedPathUnlinker a_loop_file_unlinker(a_loop_file);
+
+ // Make random data for a.
+ const size_t kLoopFileSize = 10 * 1024 * 1024 + 512;
+ chromeos::Blob a_loop_data(kLoopFileSize);
+ test_utils::FillWithData(&a_loop_data);
+
+
+ // Write data to disk
+ if (!(test_utils::WriteFileVector(a_loop_file, a_loop_data))) {
+ ADD_FAILURE();
+ return false;
+ }
+
+ // Attach loop devices to the files
+ string a_dev;
+ test_utils::ScopedLoopbackDeviceBinder a_dev_releaser(a_loop_file, &a_dev);
+ if (!(a_dev_releaser.is_bound())) {
+ ADD_FAILURE();
+ return false;
+ }
+
+ LOG(INFO) << "verifying: " << a_loop_file << " (" << a_dev << ")";
+
+ bool success = true;
+
+ // Set up the action objects
+ InstallPlan install_plan;
+ switch (partition_type) {
+ case PartitionType::kRootfs:
+ install_plan.rootfs_size = kLoopFileSize - (hash_fail ? 1 : 0);
+ install_plan.install_path = a_dev;
+ if (!OmahaHashCalculator::RawHashOfData(
+ a_loop_data, &install_plan.rootfs_hash)) {
+ ADD_FAILURE();
+ success = false;
+ }
+ break;
+ case PartitionType::kKernel:
+ install_plan.kernel_size = kLoopFileSize - (hash_fail ? 1 : 0);
+ install_plan.kernel_install_path = a_dev;
+ if (!OmahaHashCalculator::RawHashOfData(
+ a_loop_data, &install_plan.kernel_hash)) {
+ ADD_FAILURE();
+ success = false;
+ }
+ break;
+ case PartitionType::kSourceRootfs:
+ install_plan.source_path = a_dev;
+ if (!OmahaHashCalculator::RawHashOfData(
+ a_loop_data, &install_plan.source_rootfs_hash)) {
+ ADD_FAILURE();
+ success = false;
+ }
+ break;
+ case PartitionType::kSourceKernel:
+ install_plan.kernel_source_path = a_dev;
+ if (!OmahaHashCalculator::RawHashOfData(
+ a_loop_data, &install_plan.source_kernel_hash)) {
+ ADD_FAILURE();
+ success = false;
+ }
+ break;
+ }
+
+ EXPECT_CALL(mock_hardware,
+ MarkKernelUnbootable(a_dev)).Times(
+ partition_type == PartitionType::kKernel ? 1 : 0);
+
+ ActionProcessor processor;
+
+ ObjectFeederAction<InstallPlan> feeder_action;
+ FilesystemVerifierAction copier_action(&fake_system_state_, partition_type);
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&feeder_action, &copier_action);
+ BondActions(&copier_action, &collector_action);
+
+ FilesystemVerifierActionTestDelegate delegate(&copier_action);
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&copier_action);
+ processor.EnqueueAction(&collector_action);
+
+ feeder_action.set_obj(install_plan);
+
+ loop_.PostTask(FROM_HERE, base::Bind(&StartProcessorInRunLoop,
+ &processor,
+ &copier_action,
+ terminate_early));
+ loop_.Run();
+
+ if (!terminate_early) {
+ bool is_delegate_ran = delegate.ran();
+ EXPECT_TRUE(is_delegate_ran);
+ success = success && is_delegate_ran;
+ } else {
+ EXPECT_EQ(ErrorCode::kError, delegate.code());
+ return (ErrorCode::kError == delegate.code());
+ }
+ if (hash_fail) {
+ ErrorCode expected_exit_code =
+ ((partition_type == PartitionType::kKernel ||
+ partition_type == PartitionType::kSourceKernel) ?
+ ErrorCode::kNewKernelVerificationError :
+ ErrorCode::kNewRootfsVerificationError);
+ EXPECT_EQ(expected_exit_code, delegate.code());
+ return (expected_exit_code == delegate.code());
+ }
+ EXPECT_EQ(ErrorCode::kSuccess, delegate.code());
+
+ // Make sure everything in the out_image is there
+ chromeos::Blob a_out;
+ if (!utils::ReadFile(a_dev, &a_out)) {
+ ADD_FAILURE();
+ return false;
+ }
+ const bool is_a_file_reading_eq =
+ test_utils::ExpectVectorsEq(a_loop_data, a_out);
+ EXPECT_TRUE(is_a_file_reading_eq);
+ success = success && is_a_file_reading_eq;
+
+ bool is_install_plan_eq = (collector_action.object() == install_plan);
+ EXPECT_TRUE(is_install_plan_eq);
+ success = success && is_install_plan_eq;
+
+ LOG(INFO) << "Verifying bootable flag on: " << a_dev;
+ bool bootable;
+ EXPECT_TRUE(mock_hardware.fake().IsKernelBootable(a_dev, &bootable));
+ // We should always mark a partition as unbootable if it's a kernel
+ // partition, but never if it's anything else.
+ EXPECT_EQ(bootable, (partition_type != PartitionType::kKernel));
+
+ return success;
+}
+
+class FilesystemVerifierActionTest2Delegate : public ActionProcessorDelegate {
+ public:
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == FilesystemVerifierAction::StaticType()) {
+ ran_ = true;
+ code_ = code;
+ }
+ }
+ bool ran_;
+ ErrorCode code_;
+};
+
+TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) {
+ ActionProcessor processor;
+ FilesystemVerifierActionTest2Delegate delegate;
+
+ processor.set_delegate(&delegate);
+
+ FilesystemVerifierAction copier_action(&fake_system_state_,
+ PartitionType::kRootfs);
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&copier_action, &collector_action);
+
+ processor.EnqueueAction(&copier_action);
+ processor.EnqueueAction(&collector_action);
+ processor.StartProcessing();
+ EXPECT_FALSE(processor.IsRunning());
+ EXPECT_TRUE(delegate.ran_);
+ EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) {
+ ActionProcessor processor;
+ FilesystemVerifierActionTest2Delegate delegate;
+
+ processor.set_delegate(&delegate);
+
+ ObjectFeederAction<InstallPlan> feeder_action;
+ InstallPlan install_plan(false,
+ false,
+ "",
+ 0,
+ "",
+ 0,
+ "",
+ "/no/such/file",
+ "/no/such/file",
+ "/no/such/file",
+ "/no/such/file",
+ "");
+ feeder_action.set_obj(install_plan);
+ FilesystemVerifierAction verifier_action(&fake_system_state_,
+ PartitionType::kRootfs);
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&verifier_action, &collector_action);
+
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&verifier_action);
+ processor.EnqueueAction(&collector_action);
+ processor.StartProcessing();
+ EXPECT_FALSE(processor.IsRunning());
+ EXPECT_TRUE(delegate.ran_);
+ EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashTest) {
+ ASSERT_EQ(0, getuid());
+ EXPECT_TRUE(DoTest(false, false, PartitionType::kRootfs));
+ EXPECT_TRUE(DoTest(false, false, PartitionType::kKernel));
+ EXPECT_TRUE(DoTest(false, false, PartitionType::kSourceRootfs));
+ EXPECT_TRUE(DoTest(false, false, PartitionType::kSourceKernel));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashFailTest) {
+ ASSERT_EQ(0, getuid());
+ EXPECT_TRUE(DoTest(false, true, PartitionType::kRootfs));
+ EXPECT_TRUE(DoTest(false, true, PartitionType::kKernel));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) {
+ ASSERT_EQ(0, getuid());
+ EXPECT_TRUE(DoTest(true, false, PartitionType::kKernel));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootDetermineFilesystemSizeTest) {
+ string img;
+ EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
+ ScopedPathUnlinker img_unlinker(img);
+ test_utils::CreateExtImageAtPath(img, nullptr);
+ // Extend the "partition" holding the file system from 10MiB to 20MiB.
+ EXPECT_EQ(0, truncate(img.c_str(), 20 * 1024 * 1024));
+
+ for (int i = 0; i < 2; ++i) {
+ PartitionType fs_type =
+ i ? PartitionType::kSourceKernel : PartitionType::kSourceRootfs;
+ FilesystemVerifierAction action(&fake_system_state_, fs_type);
+ EXPECT_EQ(kint64max, action.remaining_size_);
+ {
+ int fd = HANDLE_EINTR(open(img.c_str(), O_RDONLY));
+ EXPECT_GT(fd, 0);
+ ScopedFdCloser fd_closer(&fd);
+ action.DetermineFilesystemSize(fd);
+ }
+ EXPECT_EQ(i ? kint64max : 10 * 1024 * 1024,
+ action.remaining_size_);
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/generate_image.gypi b/generate_image.gypi
new file mode 100644
index 0000000..025e925
--- /dev/null
+++ b/generate_image.gypi
@@ -0,0 +1,27 @@
+{
+ 'variables': {
+ 'out_dir': '<(SHARED_INTERMEDIATE_DIR)/<(image_out_dir)',
+ 'generator': 'sample_images/generate_image.sh',
+ },
+ 'rules': [
+ {
+ 'rule_name': 'generate_image',
+ 'extension': 'txt',
+ 'inputs': [
+ '<(generator)',
+ '<(RULE_INPUT_PATH)',
+ ],
+ 'outputs': [
+ '<(out_dir)/<(RULE_INPUT_ROOT).img',
+ ],
+ 'action': [
+ '<(generator)',
+ '<(RULE_INPUT_PATH)',
+ '<(out_dir)',
+ ],
+ 'msvs_cygwin_shell': 0,
+ 'message': 'Generating image from <(RULE_INPUT_PATH)',
+ 'process_outputs_as_sources': 1,
+ },
+ ],
+}
diff --git a/glib_utils.cc b/glib_utils.cc
new file mode 100644
index 0000000..625d8d1
--- /dev/null
+++ b/glib_utils.cc
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/glib_utils.h"
+
+#include <base/strings/stringprintf.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+namespace utils {
+
+string GetAndFreeGError(GError** error) {
+ if (!*error) {
+ return "Unknown GLib error.";
+ }
+ string message =
+ base::StringPrintf("GError(%d): %s",
+ (*error)->code,
+ (*error)->message ? (*error)->message : "(unknown)");
+ g_error_free(*error);
+ *error = nullptr;
+ return message;
+}
+
+gchar** StringVectorToGStrv(const std::vector<string> &vec_str) {
+ GPtrArray *p = g_ptr_array_new();
+ for (const string& str : vec_str) {
+ g_ptr_array_add(p, g_strdup(str.c_str()));
+ }
+ g_ptr_array_add(p, nullptr);
+ return reinterpret_cast<gchar**>(g_ptr_array_free(p, FALSE));
+}
+
+} // namespace utils
+} // namespace chromeos_update_engine
diff --git a/glib_utils.h b/glib_utils.h
new file mode 100644
index 0000000..97020cb
--- /dev/null
+++ b/glib_utils.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_GLIB_UTILS_H_
+#define UPDATE_ENGINE_GLIB_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include <glib.h>
+
+namespace chromeos_update_engine {
+namespace utils {
+
+// Returns the error message, if any, from a GError pointer. Frees the GError
+// object and resets error to null.
+std::string GetAndFreeGError(GError** error);
+
+// Converts a vector of strings to a NUL-terminated array of
+// strings. The resulting array should be freed with g_strfreev()
+// when are you done with it.
+gchar** StringVectorToGStrv(const std::vector<std::string>& vec_str);
+
+// A base::FreeDeleter that frees memory using g_free(). Useful when
+// integrating with GLib since it can be used with std::unique_ptr to
+// automatically free memory when going out of scope.
+struct GLibFreeDeleter {
+ inline void operator()(void* ptr) const {
+ g_free(static_cast<gpointer>(ptr));
+ }
+};
+
+// A base::FreeDeleter that frees memory using g_strfreev(). Useful
+// when integrating with GLib since it can be used with std::unique_ptr to
+// automatically free memory when going out of scope.
+struct GLibStrvFreeDeleter {
+ inline void operator()(void* ptr) const {
+ g_strfreev(static_cast<gchar**>(ptr));
+ }
+};
+
+} // namespace utils
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_GLIB_UTILS_H_
diff --git a/hardware.cc b/hardware.cc
new file mode 100644
index 0000000..a9f0b25
--- /dev/null
+++ b/hardware.cc
@@ -0,0 +1,225 @@
+// 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.
+
+#include "update_engine/hardware.h"
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <rootdev/rootdev.h>
+#include <vboot/crossystem.h>
+
+extern "C" {
+#include "vboot/vboot_host.h"
+}
+
+#include "update_engine/hwid_override.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+static const char kOOBECompletedMarker[] = "/home/chronos/.oobe_completed";
+
+// The powerwash_count marker file contains the number of times the device was
+// powerwashed. This value is incremented by the clobber-state script when
+// a powerwash is performed.
+static const char kPowerwashCountMarker[] =
+ "/mnt/stateful_partition/unencrypted/preserve/powerwash_count";
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+Hardware::Hardware() {}
+
+Hardware::~Hardware() {}
+
+string Hardware::BootKernelDevice() const {
+ return utils::KernelDeviceOfBootDevice(Hardware::BootDevice());
+}
+
+string Hardware::BootDevice() const {
+ char boot_path[PATH_MAX];
+ // Resolve the boot device path fully, including dereferencing
+ // through dm-verity.
+ int ret = rootdev(boot_path, sizeof(boot_path), true, false);
+
+ if (ret < 0) {
+ LOG(ERROR) << "rootdev failed to find the root device";
+ return "";
+ }
+ LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
+
+ // This local variable is used to construct the return string and is not
+ // passed around after use.
+ return boot_path;
+}
+
+bool Hardware::IsBootDeviceRemovable() const {
+ return utils::IsRemovableDevice(utils::GetDiskName(BootDevice()));
+}
+
+bool Hardware::IsKernelBootable(const string& kernel_device,
+ bool* bootable) const {
+ CgptAddParams params;
+ memset(¶ms, '\0', sizeof(params));
+
+ string disk_name;
+ int partition_num = 0;
+
+ if (!utils::SplitPartitionName(kernel_device, &disk_name, &partition_num))
+ return false;
+
+ params.drive_name = const_cast<char *>(disk_name.c_str());
+ params.partition = partition_num;
+
+ int retval = CgptGetPartitionDetails(¶ms);
+ if (retval != CGPT_OK)
+ return false;
+
+ *bootable = params.successful || (params.tries > 0);
+ return true;
+}
+
+vector<string> Hardware::GetKernelDevices() const {
+ LOG(INFO) << "GetAllKernelDevices";
+
+ string disk_name = utils::GetDiskName(Hardware::BootKernelDevice());
+ if (disk_name.empty()) {
+ LOG(ERROR) << "Failed to get the current kernel boot disk name";
+ return vector<string>();
+ }
+
+ vector<string> devices;
+ for (int partition_num : {2, 4}) { // for now, only #2, #4 for slot A & B
+ string device = utils::MakePartitionName(disk_name, partition_num);
+ if (!device.empty()) {
+ devices.push_back(std::move(device));
+ } else {
+ LOG(ERROR) << "Cannot make a partition name for disk: "
+ << disk_name << ", partition: " << partition_num;
+ }
+ }
+
+ return devices;
+}
+
+
+bool Hardware::MarkKernelUnbootable(const string& kernel_device) {
+ LOG(INFO) << "MarkPartitionUnbootable: " << kernel_device;
+
+ if (kernel_device == BootKernelDevice()) {
+ LOG(ERROR) << "Refusing to mark current kernel as unbootable.";
+ return false;
+ }
+
+ string disk_name;
+ int partition_num = 0;
+
+ if (!utils::SplitPartitionName(kernel_device, &disk_name, &partition_num))
+ return false;
+
+ CgptAddParams params;
+ memset(¶ms, 0, sizeof(params));
+
+ params.drive_name = const_cast<char *>(disk_name.c_str());
+ params.partition = partition_num;
+
+ params.successful = false;
+ params.set_successful = true;
+
+ params.tries = 0;
+ params.set_tries = true;
+
+ int retval = CgptSetAttributes(¶ms);
+ if (retval != CGPT_OK) {
+ LOG(ERROR) << "Marking kernel unbootable failed.";
+ return false;
+ }
+
+ return true;
+}
+
+bool Hardware::IsOfficialBuild() const {
+ return VbGetSystemPropertyInt("debug_build") == 0;
+}
+
+bool Hardware::IsNormalBootMode() const {
+ bool dev_mode = VbGetSystemPropertyInt("devsw_boot") != 0;
+ LOG_IF(INFO, dev_mode) << "Booted in dev mode.";
+ return !dev_mode;
+}
+
+bool Hardware::IsOOBEComplete(base::Time* out_time_of_oobe) const {
+ struct stat statbuf;
+ if (stat(kOOBECompletedMarker, &statbuf) != 0) {
+ if (errno != ENOENT) {
+ PLOG(ERROR) << "Error getting information about "
+ << kOOBECompletedMarker;
+ }
+ return false;
+ }
+
+ if (out_time_of_oobe != nullptr)
+ *out_time_of_oobe = base::Time::FromTimeT(statbuf.st_mtime);
+ return true;
+}
+
+static string ReadValueFromCrosSystem(const string& key) {
+ char value_buffer[VB_MAX_STRING_PROPERTY];
+
+ const char *rv = VbGetSystemPropertyString(key.c_str(), value_buffer,
+ sizeof(value_buffer));
+ if (rv != nullptr) {
+ string return_value(value_buffer);
+ base::TrimWhitespaceASCII(return_value, base::TRIM_ALL, &return_value);
+ return return_value;
+ }
+
+ LOG(ERROR) << "Unable to read crossystem key " << key;
+ return "";
+}
+
+string Hardware::GetHardwareClass() const {
+ if (USE_HWID_OVERRIDE) {
+ return HwidOverride::Read(base::FilePath("/"));
+ }
+ return ReadValueFromCrosSystem("hwid");
+}
+
+string Hardware::GetFirmwareVersion() const {
+ return ReadValueFromCrosSystem("fwid");
+}
+
+string Hardware::GetECVersion() const {
+ string input_line;
+ int exit_code = 0;
+ vector<string> cmd = {"/usr/sbin/mosys", "-k", "ec", "info"};
+
+ bool success = Subprocess::SynchronousExec(cmd, &exit_code, &input_line);
+ if (!success || exit_code) {
+ LOG(ERROR) << "Unable to read ec info from mosys (" << exit_code << ")";
+ return "";
+ }
+
+ return utils::ParseECVersion(input_line);
+}
+
+int Hardware::GetPowerwashCount() const {
+ int powerwash_count;
+ string contents;
+ if (!utils::ReadFile(kPowerwashCountMarker, &contents))
+ return -1;
+ base::TrimWhitespaceASCII(contents, base::TRIM_TRAILING, &contents);
+ if (!base::StringToInt(contents, &powerwash_count))
+ return -1;
+ return powerwash_count;
+}
+
+} // namespace chromeos_update_engine
diff --git a/hardware.h b/hardware.h
new file mode 100644
index 0000000..c573117
--- /dev/null
+++ b/hardware.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_HARDWARE_H_
+#define UPDATE_ENGINE_HARDWARE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/hardware_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements the real interface with the hardware.
+class Hardware : public HardwareInterface {
+ public:
+ Hardware();
+ ~Hardware() override;
+
+ // HardwareInterface methods.
+ std::string BootKernelDevice() const override;
+ std::string BootDevice() const override;
+ bool IsBootDeviceRemovable() const override;
+ std::vector<std::string> GetKernelDevices() const override;
+ bool IsKernelBootable(const std::string& kernel_device,
+ bool* bootable) const override;
+ bool MarkKernelUnbootable(const std::string& kernel_device) override;
+ bool IsOfficialBuild() const override;
+ bool IsNormalBootMode() const override;
+ bool IsOOBEComplete(base::Time* out_time_of_oobe) const override;
+ std::string GetHardwareClass() const override;
+ std::string GetFirmwareVersion() const override;
+ std::string GetECVersion() const override;
+ int GetPowerwashCount() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Hardware);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_HARDWARE_H_
diff --git a/hardware_interface.h b/hardware_interface.h
new file mode 100644
index 0000000..d5a1f1f
--- /dev/null
+++ b/hardware_interface.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_HARDWARE_INTERFACE_H_
+#define UPDATE_ENGINE_HARDWARE_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+
+namespace chromeos_update_engine {
+
+// The hardware interface allows access to the following parts of the system,
+// closely related to the hardware:
+// * crossystem exposed properties: firmware, hwid, etc.
+// * Physical disk: partition booted from and partition name conversions.
+// These stateless functions are tied together in this interface to facilitate
+// unit testing.
+class HardwareInterface {
+ public:
+ virtual ~HardwareInterface() {}
+
+ // Returns the currently booted kernel partition. "/dev/sda2", for example.
+ virtual std::string BootKernelDevice() const = 0;
+
+ // Returns the currently booted rootfs partition. "/dev/sda3", for example.
+ virtual std::string BootDevice() const = 0;
+
+ // Return whether the BootDevice() is a removable device.
+ virtual bool IsBootDeviceRemovable() const = 0;
+
+ // Returns a list of all kernel partitions available (whether bootable or not)
+ virtual std::vector<std::string> GetKernelDevices() const = 0;
+
+ // Is the specified kernel partition currently bootable, based on GPT flags?
+ // Returns success.
+ virtual bool IsKernelBootable(const std::string& kernel_device,
+ bool* bootable) const = 0;
+
+ // Mark the specified kernel partition unbootable in GPT flags. We mark
+ // the other kernel as bootable inside postinst, not inside the UE.
+ // Returns success.
+ virtual bool MarkKernelUnbootable(const std::string& kernel_device) = 0;
+
+ // Returns true if this is an official Chrome OS build, false otherwise.
+ virtual bool IsOfficialBuild() const = 0;
+
+ // Returns true if the boot mode is normal or if it's unable to
+ // determine the boot mode. Returns false if the boot mode is
+ // developer.
+ virtual bool IsNormalBootMode() const = 0;
+
+ // Returns true if the OOBE process has been completed and EULA accepted,
+ // False otherwise. If True is returned, and |out_time_of_oobe| isn't null,
+ // the time-stamp of when OOBE happened is stored at |out_time_of_oobe|.
+ virtual bool IsOOBEComplete(base::Time* out_time_of_oobe) const = 0;
+
+ // Returns the HWID or an empty string on error.
+ virtual std::string GetHardwareClass() const = 0;
+
+ // Returns the firmware version or an empty string if the system is
+ // not running chrome os firmware.
+ virtual std::string GetFirmwareVersion() const = 0;
+
+ // Returns the ec version or an empty string if the system is not
+ // running a custom chrome os ec.
+ virtual std::string GetECVersion() const = 0;
+
+ // Returns the powerwash_count from the stateful. If the file is not found
+ // or is invalid, returns -1. Brand new machines out of the factory or after
+ // recovery don't have this value set.
+ virtual int GetPowerwashCount() const = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_HARDWARE_INTERFACE_H_
diff --git a/http_common.cc b/http_common.cc
new file mode 100644
index 0000000..7737f4c
--- /dev/null
+++ b/http_common.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2009 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.
+
+// Implementation of common HTTP related functions.
+
+#include "update_engine/http_common.h"
+
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+const char *GetHttpResponseDescription(HttpResponseCode code) {
+ static const struct {
+ HttpResponseCode code;
+ const char* description;
+ } http_response_table[] = {
+ { kHttpResponseOk, "OK" },
+ { kHttpResponseCreated, "Created" },
+ { kHttpResponseAccepted, "Accepted" },
+ { kHttpResponseNonAuthInfo, "Non-Authoritative Information" },
+ { kHttpResponseNoContent, "No Content" },
+ { kHttpResponseResetContent, "Reset Content" },
+ { kHttpResponsePartialContent, "Partial Content" },
+ { kHttpResponseMultipleChoices, "Multiple Choices" },
+ { kHttpResponseMovedPermanently, "Moved Permanently" },
+ { kHttpResponseFound, "Found" },
+ { kHttpResponseSeeOther, "See Other" },
+ { kHttpResponseNotModified, "Not Modified" },
+ { kHttpResponseUseProxy, "Use Proxy" },
+ { kHttpResponseTempRedirect, "Temporary Redirect" },
+ { kHttpResponseBadRequest, "Bad Request" },
+ { kHttpResponseUnauth, "Unauthorized" },
+ { kHttpResponseForbidden, "Forbidden" },
+ { kHttpResponseNotFound, "Not Found" },
+ { kHttpResponseRequestTimeout, "Request Timeout" },
+ { kHttpResponseInternalServerError, "Internal Server Error" },
+ { kHttpResponseNotImplemented, "Not Implemented" },
+ { kHttpResponseServiceUnavailable, "Service Unavailable" },
+ { kHttpResponseVersionNotSupported, "HTTP Version Not Supported" },
+ };
+
+ bool is_found = false;
+ size_t i;
+ for (i = 0; i < arraysize(http_response_table); i++)
+ if ((is_found = (http_response_table[i].code == code)))
+ break;
+
+ return (is_found ? http_response_table[i].description : "(unsupported)");
+}
+
+HttpResponseCode StringToHttpResponseCode(const char *s) {
+ return static_cast<HttpResponseCode>(strtoul(s, nullptr, 10));
+}
+
+
+const char *GetHttpContentTypeString(HttpContentType type) {
+ static const struct {
+ HttpContentType type;
+ const char* str;
+ } http_content_type_table[] = {
+ { kHttpContentTypeTextXml, "text/xml" },
+ };
+
+ bool is_found = false;
+ size_t i;
+ for (i = 0; i < arraysize(http_content_type_table); i++)
+ if ((is_found = (http_content_type_table[i].type == type)))
+ break;
+
+ return (is_found ? http_content_type_table[i].str : nullptr);
+}
+
+} // namespace chromeos_update_engine
diff --git a/http_common.h b/http_common.h
new file mode 100644
index 0000000..a4f7bdc
--- /dev/null
+++ b/http_common.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2009 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 file contains general definitions used in implementing, testing and
+// emulating communication over HTTP.
+
+#ifndef UPDATE_ENGINE_HTTP_COMMON_H_
+#define UPDATE_ENGINE_HTTP_COMMON_H_
+
+#include <cstdlib>
+
+namespace chromeos_update_engine {
+
+// Enumeration type for HTTP response codes.
+enum HttpResponseCode {
+ kHttpResponseUndefined = 0,
+ kHttpResponseOk = 200,
+ kHttpResponseCreated = 201,
+ kHttpResponseAccepted = 202,
+ kHttpResponseNonAuthInfo = 203,
+ kHttpResponseNoContent = 204,
+ kHttpResponseResetContent = 205,
+ kHttpResponsePartialContent = 206,
+ kHttpResponseMultipleChoices = 300,
+ kHttpResponseMovedPermanently = 301,
+ kHttpResponseFound = 302,
+ kHttpResponseSeeOther = 303,
+ kHttpResponseNotModified = 304,
+ kHttpResponseUseProxy = 305,
+ kHttpResponseTempRedirect = 307,
+ kHttpResponseBadRequest = 400,
+ kHttpResponseUnauth = 401,
+ kHttpResponseForbidden = 403,
+ kHttpResponseNotFound = 404,
+ kHttpResponseRequestTimeout = 408,
+ kHttpResponseReqRangeNotSat = 416,
+ kHttpResponseInternalServerError = 500,
+ kHttpResponseNotImplemented = 501,
+ kHttpResponseServiceUnavailable = 503,
+ kHttpResponseVersionNotSupported = 505,
+};
+
+// Returns a standard HTTP status line string for a given response code.
+const char *GetHttpResponseDescription(HttpResponseCode code);
+
+// Converts a string beginning with an HTTP error code into numerical value.
+HttpResponseCode StringToHttpResponseCode(const char *s);
+
+
+// Enumeration type for HTTP Content-Type.
+enum HttpContentType {
+ kHttpContentTypeUnspecified = 0,
+ kHttpContentTypeTextXml,
+};
+
+// Returns a standard HTTP Content-Type string.
+const char *GetHttpContentTypeString(HttpContentType type);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_HTTP_COMMON_H_
diff --git a/http_fetcher.cc b/http_fetcher.cc
new file mode 100644
index 0000000..b355fdb
--- /dev/null
+++ b/http_fetcher.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/http_fetcher.h"
+
+#include <base/bind.h>
+
+using base::Closure;
+using chromeos::MessageLoop;
+using std::deque;
+using std::string;
+
+namespace chromeos_update_engine {
+
+HttpFetcher::~HttpFetcher() {
+ if (no_resolver_idle_id_ != MessageLoop::kTaskIdNull) {
+ MessageLoop::current()->CancelTask(no_resolver_idle_id_);
+ no_resolver_idle_id_ = MessageLoop::kTaskIdNull;
+ }
+}
+
+void HttpFetcher::SetPostData(const void* data, size_t size,
+ HttpContentType type) {
+ post_data_set_ = true;
+ post_data_.clear();
+ const char* char_data = reinterpret_cast<const char*>(data);
+ post_data_.insert(post_data_.end(), char_data, char_data + size);
+ post_content_type_ = type;
+}
+
+void HttpFetcher::SetPostData(const void* data, size_t size) {
+ SetPostData(data, size, kHttpContentTypeUnspecified);
+}
+
+// Proxy methods to set the proxies, then to pop them off.
+bool HttpFetcher::ResolveProxiesForUrl(const string& url,
+ const Closure& callback) {
+ CHECK_EQ(static_cast<Closure*>(nullptr), callback_.get());
+ callback_.reset(new Closure(callback));
+
+ if (!proxy_resolver_) {
+ LOG(INFO) << "Not resolving proxies (no proxy resolver).";
+ no_resolver_idle_id_ = MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&HttpFetcher::NoProxyResolverCallback,
+ base::Unretained(this)));
+ return true;
+ }
+ return proxy_resolver_->GetProxiesForUrl(url,
+ &HttpFetcher::StaticProxiesResolved,
+ this);
+}
+
+void HttpFetcher::NoProxyResolverCallback() {
+ ProxiesResolved(deque<string>());
+}
+
+void HttpFetcher::ProxiesResolved(const deque<string>& proxies) {
+ no_resolver_idle_id_ = MessageLoop::kTaskIdNull;
+ if (!proxies.empty())
+ SetProxies(proxies);
+ CHECK_NE(static_cast<Closure*>(nullptr), callback_.get());
+ Closure* callback = callback_.release();
+ // This may indirectly call back into ResolveProxiesForUrl():
+ callback->Run();
+ delete callback;
+}
+
+} // namespace chromeos_update_engine
diff --git a/http_fetcher.h b/http_fetcher.h
new file mode 100644
index 0000000..0d19a9a
--- /dev/null
+++ b/http_fetcher.h
@@ -0,0 +1,195 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_HTTP_FETCHER_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/http_common.h"
+#include "update_engine/proxy_resolver.h"
+#include "update_engine/system_state.h"
+
+// This class is a simple wrapper around an HTTP library (libcurl). We can
+// easily mock out this interface for testing.
+
+// Implementations of this class should use asynchronous i/o. They can access
+// the MessageLoop to request callbacks when timers or file descriptors change.
+
+namespace chromeos_update_engine {
+
+class HttpFetcherDelegate;
+
+class HttpFetcher {
+ public:
+ // |proxy_resolver| is the resolver that will be consulted for proxy
+ // settings. It may be null, in which case direct connections will
+ // be used. Does not take ownership of the resolver.
+ HttpFetcher(ProxyResolver* proxy_resolver, SystemState* system_state)
+ : post_data_set_(false),
+ http_response_code_(0),
+ delegate_(nullptr),
+ proxies_(1, kNoProxy),
+ proxy_resolver_(proxy_resolver),
+ callback_(nullptr),
+ system_state_(system_state) {}
+ virtual ~HttpFetcher();
+
+ void set_delegate(HttpFetcherDelegate* delegate) { delegate_ = delegate; }
+ HttpFetcherDelegate* delegate() const { return delegate_; }
+ int http_response_code() const { return http_response_code_; }
+
+ // Optional: Post data to the server. The HttpFetcher should make a copy
+ // of this data and upload it via HTTP POST during the transfer. The type of
+ // the data is necessary for properly setting the Content-Type HTTP header.
+ void SetPostData(const void* data, size_t size, HttpContentType type);
+
+ // Same without a specified Content-Type.
+ void SetPostData(const void* data, size_t size);
+
+ // Proxy methods to set the proxies, then to pop them off.
+ // Returns true on success.
+ bool ResolveProxiesForUrl(const std::string& url,
+ const base::Closure& callback);
+
+ void SetProxies(const std::deque<std::string>& proxies) {
+ proxies_ = proxies;
+ }
+ const std::string& GetCurrentProxy() const {
+ return proxies_.front();
+ }
+ bool HasProxy() const { return !proxies_.empty(); }
+ void PopProxy() { proxies_.pop_front(); }
+
+ // Downloading should resume from this offset
+ virtual void SetOffset(off_t offset) = 0;
+
+ // Set/unset the length of the range to be downloaded.
+ virtual void SetLength(size_t length) = 0;
+ virtual void UnsetLength() = 0;
+
+ // Begins the transfer to the specified URL. This fetcher instance should not
+ // be destroyed until either TransferComplete, or TransferTerminated is
+ // called.
+ virtual void BeginTransfer(const std::string& url) = 0;
+
+ // Aborts the transfer. The transfer may not abort right away -- delegate's
+ // TransferTerminated() will be called when the transfer is actually done.
+ virtual void TerminateTransfer() = 0;
+
+ // If data is coming in too quickly, you can call Pause() to pause the
+ // transfer. The delegate will not have ReceivedBytes() called while
+ // an HttpFetcher is paused.
+ virtual void Pause() = 0;
+
+ // Used to unpause an HttpFetcher and let the bytes stream in again.
+ // If a delegate is set, ReceivedBytes() may be called on it before
+ // Unpause() returns
+ virtual void Unpause() = 0;
+
+ // These two function are overloaded in LibcurlHttp fetcher to speed
+ // testing.
+ virtual void set_idle_seconds(int seconds) {}
+ virtual void set_retry_seconds(int seconds) {}
+
+ // Sets the values used to time out the connection if the transfer
+ // rate is less than |low_speed_bps| bytes/sec for more than
+ // |low_speed_sec| seconds.
+ virtual void set_low_speed_limit(int low_speed_bps, int low_speed_sec) = 0;
+
+ // Sets the connect timeout, e.g. the maximum amount of time willing
+ // to wait for establishing a connection to the server.
+ virtual void set_connect_timeout(int connect_timeout_seconds) = 0;
+
+ // Sets the number of allowed retries.
+ virtual void set_max_retry_count(int max_retry_count) = 0;
+
+ // Get the total number of bytes downloaded by fetcher.
+ virtual size_t GetBytesDownloaded() = 0;
+
+ ProxyResolver* proxy_resolver() const { return proxy_resolver_; }
+
+ // Returns the global SystemState.
+ SystemState* GetSystemState() {
+ return system_state_;
+ }
+
+ protected:
+ // The URL we're actively fetching from
+ std::string url_;
+
+ // POST data for the transfer, and whether or not it was ever set
+ bool post_data_set_;
+ chromeos::Blob post_data_;
+ HttpContentType post_content_type_;
+
+ // The server's HTTP response code from the last transfer. This
+ // field should be set to 0 when a new transfer is initiated, and
+ // set to the response code when the transfer is complete.
+ int http_response_code_;
+
+ // The delegate; may be null.
+ HttpFetcherDelegate* delegate_;
+
+ // Proxy servers
+ std::deque<std::string> proxies_;
+
+ ProxyResolver* const proxy_resolver_;
+
+ // The ID of the idle callback, used when we have no proxy resolver.
+ chromeos::MessageLoop::TaskId no_resolver_idle_id_{
+ chromeos::MessageLoop::kTaskIdNull};
+
+ // Callback for when we are resolving proxies
+ std::unique_ptr<base::Closure> callback_;
+
+ // Global system context.
+ SystemState* system_state_;
+
+ private:
+ // Callback from the proxy resolver
+ void ProxiesResolved(const std::deque<std::string>& proxies);
+ static void StaticProxiesResolved(const std::deque<std::string>& proxies,
+ void* data) {
+ reinterpret_cast<HttpFetcher*>(data)->ProxiesResolved(proxies);
+ }
+
+ // Callback used to run the proxy resolver callback when there is no
+ // |proxy_resolver_|.
+ void NoProxyResolverCallback();
+
+ DISALLOW_COPY_AND_ASSIGN(HttpFetcher);
+};
+
+// Interface for delegates
+class HttpFetcherDelegate {
+ public:
+ virtual ~HttpFetcherDelegate() = default;
+
+ // Called every time bytes are received.
+ virtual void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes,
+ size_t length) = 0;
+
+ // Called if the fetcher seeks to a particular offset.
+ virtual void SeekToOffset(off_t offset) {}
+
+ // When a transfer has completed, exactly one of these two methods will be
+ // called. TransferTerminated is called when the transfer has been aborted
+ // through TerminateTransfer. TransferComplete is called in all other
+ // situations. It's OK to destroy the |fetcher| object in this callback.
+ virtual void TransferComplete(HttpFetcher* fetcher, bool successful) = 0;
+ virtual void TransferTerminated(HttpFetcher* fetcher) {}
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_HTTP_FETCHER_H_
diff --git a/http_fetcher_unittest.cc b/http_fetcher_unittest.cc
new file mode 100644
index 0000000..fd520e9
--- /dev/null
+++ b/http_fetcher_unittest.cc
@@ -0,0 +1,1099 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_system_state.h"
+#include "update_engine/http_common.h"
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/multi_range_http_fetcher.h"
+#include "update_engine/proxy_resolver.h"
+#include "update_engine/utils.h"
+
+using chromeos::MessageLoop;
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace {
+
+const int kBigLength = 100000;
+const int kMediumLength = 1000;
+const int kFlakyTruncateLength = 29000;
+const int kFlakySleepEvery = 3;
+const int kFlakySleepSecs = 10;
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+static const char *kUnusedUrl = "unused://unused";
+
+static inline string LocalServerUrlForPath(in_port_t port,
+ const string& path) {
+ string port_str = (port ? base::StringPrintf(":%hu", port) : "");
+ return base::StringPrintf("http://127.0.0.1%s%s", port_str.c_str(),
+ path.c_str());
+}
+
+//
+// Class hierarchy for HTTP server implementations.
+//
+
+class HttpServer {
+ public:
+ // This makes it an abstract class (dirty but works).
+ virtual ~HttpServer() = 0;
+
+ virtual in_port_t GetPort() const {
+ return 0;
+ }
+
+ bool started_;
+};
+
+HttpServer::~HttpServer() {}
+
+
+class NullHttpServer : public HttpServer {
+ public:
+ NullHttpServer() {
+ started_ = true;
+ }
+};
+
+
+class PythonHttpServer : public HttpServer {
+ public:
+ PythonHttpServer() : pid_(-1), port_(0) {
+ started_ = false;
+
+ // Spawn the server process.
+ gchar *argv[] = {
+ const_cast<gchar*>("./test_http_server"),
+ nullptr
+ };
+ GError *err;
+ gint server_stdout = -1;
+ if (!g_spawn_async_with_pipes(nullptr, argv, nullptr,
+ G_SPAWN_DO_NOT_REAP_CHILD, nullptr, nullptr,
+ &pid_, nullptr, &server_stdout, nullptr,
+ &err)) {
+ LOG(ERROR) << "failed to spawn http server process";
+ return;
+ }
+ CHECK_GT(pid_, 0);
+ CHECK_GE(server_stdout, 0);
+ LOG(INFO) << "started http server with pid " << pid_;
+
+ // Wait for server to begin accepting connections, obtain its port.
+ char line[80];
+ const size_t listening_msg_prefix_len = strlen(kServerListeningMsgPrefix);
+ CHECK_GT(sizeof(line), listening_msg_prefix_len);
+ int line_len = read(server_stdout, line, sizeof(line) - 1);
+ if (line_len <= static_cast<int>(listening_msg_prefix_len)) {
+ if (line_len < 0) {
+ PLOG(ERROR) << "error reading http server stdout";
+ } else {
+ LOG(ERROR) << "server output too short";
+ }
+ Terminate(true);
+ return;
+ }
+
+ line[line_len] = '\0';
+ CHECK_EQ(strstr(line, kServerListeningMsgPrefix), line);
+ const char* listening_port_str = line + listening_msg_prefix_len;
+ char* end_ptr;
+ long raw_port = strtol(listening_port_str, // NOLINT(runtime/int)
+ &end_ptr, 10);
+ CHECK(!*end_ptr || *end_ptr == '\n');
+ port_ = static_cast<in_port_t>(raw_port);
+ CHECK_GT(port_, 0);
+ started_ = true;
+ LOG(INFO) << "server running, listening on port " << port_;
+ LOG(INFO) << "gdb attach now!";
+ }
+
+ ~PythonHttpServer() {
+ // If there's no process, do nothing.
+ if (pid_ == -1)
+ return;
+
+ // If server is responsive, request that it gracefully terminate.
+ bool do_kill = false;
+ if (started_) {
+ LOG(INFO) << "running wget to exit";
+ if (system((string("wget -t 1 --output-document=/dev/null ") +
+ LocalServerUrlForPath(port_, "/quitquitquit")).c_str())) {
+ LOG(WARNING) << "wget failed, resorting to brute force";
+ do_kill = true;
+ }
+ }
+
+ // Server not responding or wget failed, kill the process.
+ Terminate(do_kill);
+ }
+
+ in_port_t GetPort() const override {
+ return port_;
+ }
+
+ private:
+ void Terminate(bool do_kill) {
+ ASSERT_GT(pid_, 0);
+
+ if (do_kill) {
+ LOG(INFO) << "terminating (SIGKILL) server process with pid " << pid_;
+ kill(pid_, SIGKILL);
+ }
+
+ LOG(INFO) << "waiting for http server with pid " << pid_ << " to terminate";
+ int status;
+ pid_t killed_pid = waitpid(pid_, &status, 0);
+ ASSERT_EQ(killed_pid, pid_);
+ LOG(INFO) << "http server with pid " << pid_
+ << " terminated with status " << status;
+ pid_ = -1;
+ }
+
+ static const char* kServerListeningMsgPrefix;
+
+ GPid pid_;
+ in_port_t port_;
+};
+
+const char* PythonHttpServer::kServerListeningMsgPrefix = "listening on port ";
+
+//
+// Class hierarchy for HTTP fetcher test wrappers.
+//
+
+class AnyHttpFetcherTest {
+ public:
+ AnyHttpFetcherTest() {}
+ virtual ~AnyHttpFetcherTest() {}
+
+ virtual HttpFetcher* NewLargeFetcher(size_t num_proxies) = 0;
+ HttpFetcher* NewLargeFetcher() {
+ return NewLargeFetcher(1);
+ }
+
+ virtual HttpFetcher* NewSmallFetcher(size_t num_proxies) = 0;
+ HttpFetcher* NewSmallFetcher() {
+ return NewSmallFetcher(1);
+ }
+
+ virtual string BigUrl(in_port_t port) const { return kUnusedUrl; }
+ virtual string SmallUrl(in_port_t port) const { return kUnusedUrl; }
+ virtual string ErrorUrl(in_port_t port) const { return kUnusedUrl; }
+
+ virtual bool IsMock() const = 0;
+ virtual bool IsMulti() const = 0;
+
+ virtual void IgnoreServerAborting(HttpServer* server) const {}
+
+ virtual HttpServer* CreateServer() = 0;
+
+ protected:
+ DirectProxyResolver proxy_resolver_;
+ FakeSystemState fake_system_state_;
+};
+
+class MockHttpFetcherTest : public AnyHttpFetcherTest {
+ public:
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewLargeFetcher;
+ HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+ chromeos::Blob big_data(1000000);
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ return new MockHttpFetcher(
+ big_data.data(),
+ big_data.size(),
+ reinterpret_cast<ProxyResolver*>(&proxy_resolver_));
+ }
+
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewSmallFetcher;
+ HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ return new MockHttpFetcher(
+ "x",
+ 1,
+ reinterpret_cast<ProxyResolver*>(&proxy_resolver_));
+ }
+
+ bool IsMock() const override { return true; }
+ bool IsMulti() const override { return false; }
+
+ HttpServer* CreateServer() override {
+ return new NullHttpServer;
+ }
+};
+
+class LibcurlHttpFetcherTest : public AnyHttpFetcherTest {
+ public:
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewLargeFetcher;
+ HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ LibcurlHttpFetcher *ret = new
+ LibcurlHttpFetcher(reinterpret_cast<ProxyResolver*>(&proxy_resolver_),
+ &fake_system_state_);
+ // Speed up test execution.
+ ret->set_idle_seconds(1);
+ ret->set_retry_seconds(1);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ return ret;
+ }
+
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewSmallFetcher;
+ HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+ return NewLargeFetcher(num_proxies);
+ }
+
+ string BigUrl(in_port_t port) const override {
+ return LocalServerUrlForPath(port,
+ base::StringPrintf("/download/%d",
+ kBigLength));
+ }
+ string SmallUrl(in_port_t port) const override {
+ return LocalServerUrlForPath(port, "/foo");
+ }
+ string ErrorUrl(in_port_t port) const override {
+ return LocalServerUrlForPath(port, "/error");
+ }
+
+ bool IsMock() const override { return false; }
+ bool IsMulti() const override { return false; }
+
+ void IgnoreServerAborting(HttpServer* server) const override {
+ // Nothing to do.
+ }
+
+ HttpServer* CreateServer() override {
+ return new PythonHttpServer;
+ }
+};
+
+class MultiRangeHttpFetcherTest : public LibcurlHttpFetcherTest {
+ public:
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewLargeFetcher;
+ HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ ProxyResolver* resolver =
+ reinterpret_cast<ProxyResolver*>(&proxy_resolver_);
+ MultiRangeHttpFetcher *ret =
+ new MultiRangeHttpFetcher(
+ new LibcurlHttpFetcher(resolver, &fake_system_state_));
+ ret->ClearRanges();
+ ret->AddRange(0);
+ // Speed up test execution.
+ ret->set_idle_seconds(1);
+ ret->set_retry_seconds(1);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ return ret;
+ }
+
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewSmallFetcher;
+ HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+ return NewLargeFetcher(num_proxies);
+ }
+
+ bool IsMulti() const override { return true; }
+};
+
+
+//
+// Infrastructure for type tests of HTTP fetcher.
+// See: http://code.google.com/p/googletest/wiki/AdvancedGuide#Typed_Tests
+//
+
+// Fixture class template. We use an explicit constraint to guarantee that it
+// can only be instantiated with an AnyHttpFetcherTest type, see:
+// http://www2.research.att.com/~bs/bs_faq2.html#constraints
+template <typename T>
+class HttpFetcherTest : public ::testing::Test {
+ public:
+ // TODO(deymo): Replace this with a FakeMessageLoop. We can't do that yet
+ // because these tests use g_spawn_async_with_pipes() to launch the
+ // http_test_server.
+ chromeos::GlibMessageLoop loop_;
+
+ T test_;
+
+ protected:
+ HttpFetcherTest() {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ EXPECT_EQ(0, chromeos::MessageLoopRunMaxIterations(&loop_, 1));
+ }
+
+ private:
+ static void TypeConstraint(T* a) {
+ AnyHttpFetcherTest *b = a;
+ if (b == 0) // Silence compiler warning of unused variable.
+ *b = a;
+ }
+};
+
+// Test case types list.
+typedef ::testing::Types<LibcurlHttpFetcherTest,
+ MockHttpFetcherTest,
+ MultiRangeHttpFetcherTest> HttpFetcherTestTypes;
+TYPED_TEST_CASE(HttpFetcherTest, HttpFetcherTestTypes);
+
+
+namespace {
+class HttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ HttpFetcherTestDelegate() :
+ is_expect_error_(false), times_transfer_complete_called_(0),
+ times_transfer_terminated_called_(0), times_received_bytes_called_(0) {}
+
+ void ReceivedBytes(HttpFetcher* /* fetcher */,
+ const void* /* bytes */, size_t /* length */) override {
+ // Update counters
+ times_received_bytes_called_++;
+ }
+
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ if (is_expect_error_)
+ EXPECT_EQ(kHttpResponseNotFound, fetcher->http_response_code());
+ else
+ EXPECT_EQ(kHttpResponseOk, fetcher->http_response_code());
+ MessageLoop::current()->BreakLoop();
+
+ // Update counter
+ times_transfer_complete_called_++;
+ }
+
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ times_transfer_terminated_called_++;
+ }
+
+ // Are we expecting an error response? (default: no)
+ bool is_expect_error_;
+
+ // Counters for callback invocations.
+ int times_transfer_complete_called_;
+ int times_transfer_terminated_called_;
+ int times_received_bytes_called_;
+};
+
+
+void StartTransfer(HttpFetcher* http_fetcher, const string& url) {
+ http_fetcher->BeginTransfer(url);
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, SimpleTest) {
+ HttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ this->test_.SmallUrl(server->GetPort())));
+ this->loop_.Run();
+}
+
+TYPED_TEST(HttpFetcherTest, SimpleBigTest) {
+ HttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ this->test_.BigUrl(server->GetPort())));
+ this->loop_.Run();
+}
+
+// Issue #9648: when server returns an error HTTP response, the fetcher needs to
+// terminate transfer prematurely, rather than try to process the error payload.
+TYPED_TEST(HttpFetcherTest, ErrorTest) {
+ if (this->test_.IsMock() || this->test_.IsMulti())
+ return;
+ HttpFetcherTestDelegate delegate;
+
+ // Delegate should expect an error response.
+ delegate.is_expect_error_ = true;
+
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ this->test_.ErrorUrl(server->GetPort())));
+ this->loop_.Run();
+
+ // Make sure that no bytes were received.
+ CHECK_EQ(delegate.times_received_bytes_called_, 0);
+ CHECK_EQ(fetcher->GetBytesDownloaded(), static_cast<size_t>(0));
+
+ // Make sure that transfer completion was signaled once, and no termination
+ // was signaled.
+ CHECK_EQ(delegate.times_transfer_complete_called_, 1);
+ CHECK_EQ(delegate.times_transfer_terminated_called_, 0);
+}
+
+namespace {
+class PausingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* /* bytes */, size_t /* length */) override {
+ CHECK(!paused_);
+ paused_ = true;
+ fetcher->Pause();
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ void Unpause() {
+ CHECK(paused_);
+ paused_ = false;
+ fetcher_->Unpause();
+ }
+ bool paused_;
+ HttpFetcher* fetcher_;
+};
+
+void UnpausingTimeoutCallback(PausingHttpFetcherTestDelegate* delegate,
+ MessageLoop::TaskId* my_id) {
+ if (delegate->paused_)
+ delegate->Unpause();
+ // Update the task id with the new scheduled callback.
+ *my_id = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UnpausingTimeoutCallback, delegate, my_id),
+ base::TimeDelta::FromMilliseconds(200));
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, PauseTest) {
+ {
+ PausingHttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher());
+ delegate.paused_ = false;
+ delegate.fetcher_ = fetcher.get();
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ MessageLoop::TaskId callback_id;
+ callback_id = this->loop_.PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UnpausingTimeoutCallback, &delegate, &callback_id),
+ base::TimeDelta::FromMilliseconds(200));
+ fetcher->BeginTransfer(this->test_.BigUrl(server->GetPort()));
+
+ this->loop_.Run();
+ EXPECT_TRUE(this->loop_.CancelTask(callback_id));
+ }
+}
+
+namespace {
+class AbortingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {}
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ ADD_FAILURE(); // We should never get here
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ EXPECT_EQ(fetcher, fetcher_.get());
+ EXPECT_FALSE(once_);
+ EXPECT_TRUE(callback_once_);
+ callback_once_ = false;
+ // The fetcher could have a callback scheduled on the ProxyResolver that
+ // can fire after this callback. We wait until the end of the test to
+ // delete the fetcher.
+ }
+ void TerminateTransfer() {
+ CHECK(once_);
+ once_ = false;
+ fetcher_->TerminateTransfer();
+ }
+ void EndLoop() {
+ MessageLoop::current()->BreakLoop();
+ }
+ bool once_;
+ bool callback_once_;
+ unique_ptr<HttpFetcher> fetcher_;
+};
+
+void AbortingTimeoutCallback(AbortingHttpFetcherTestDelegate* delegate,
+ MessageLoop::TaskId* my_id) {
+ if (delegate->once_) {
+ delegate->TerminateTransfer();
+ *my_id = MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(AbortingTimeoutCallback, delegate, my_id));
+ } else {
+ delegate->EndLoop();
+ *my_id = MessageLoop::kTaskIdNull;
+ }
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, AbortTest) {
+ AbortingHttpFetcherTestDelegate delegate;
+ delegate.fetcher_.reset(this->test_.NewLargeFetcher());
+ delegate.once_ = true;
+ delegate.callback_once_ = true;
+ delegate.fetcher_->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ this->test_.IgnoreServerAborting(server.get());
+ ASSERT_TRUE(server->started_);
+
+ MessageLoop::TaskId task_id = MessageLoop::kTaskIdNull;
+
+ task_id = this->loop_.PostTask(
+ FROM_HERE,
+ base::Bind(AbortingTimeoutCallback, &delegate, &task_id));
+ delegate.fetcher_->BeginTransfer(this->test_.BigUrl(server->GetPort()));
+
+ this->loop_.Run();
+ CHECK(!delegate.once_);
+ CHECK(!delegate.callback_once_);
+ this->loop_.CancelTask(task_id);
+}
+
+namespace {
+class FlakyHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ data.append(reinterpret_cast<const char*>(bytes), length);
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_TRUE(successful);
+ EXPECT_EQ(kHttpResponsePartialContent, fetcher->http_response_code());
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ string data;
+};
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, FlakyTest) {
+ if (this->test_.IsMock())
+ return;
+ {
+ FlakyHttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ &StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(server->GetPort(),
+ base::StringPrintf("/flaky/%d/%d/%d/%d",
+ kBigLength,
+ kFlakyTruncateLength,
+ kFlakySleepEvery,
+ kFlakySleepSecs))));
+ this->loop_.Run();
+
+ // verify the data we get back
+ ASSERT_EQ(kBigLength, delegate.data.size());
+ for (int i = 0; i < kBigLength; i += 10) {
+ // Assert so that we don't flood the screen w/ EXPECT errors on failure.
+ ASSERT_EQ(delegate.data.substr(i, 10), "abcdefghij");
+ }
+ }
+}
+
+namespace {
+// This delegate kills the server attached to it after receiving any bytes.
+// This can be used for testing what happens when you try to fetch data and
+// the server dies.
+class FailureHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ explicit FailureHttpFetcherTestDelegate(PythonHttpServer* server)
+ : server_(server) {}
+
+ ~FailureHttpFetcherTestDelegate() override {
+ if (server_) {
+ LOG(INFO) << "Stopping server in destructor";
+ delete server_;
+ LOG(INFO) << "server stopped";
+ }
+ }
+
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ if (server_) {
+ LOG(INFO) << "Stopping server in ReceivedBytes";
+ delete server_;
+ LOG(INFO) << "server stopped";
+ server_ = nullptr;
+ }
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_FALSE(successful);
+ EXPECT_EQ(0, fetcher->http_response_code());
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ PythonHttpServer* server_;
+};
+} // namespace
+
+
+TYPED_TEST(HttpFetcherTest, FailureTest) {
+ // This test ensures that a fetcher responds correctly when a server isn't
+ // available at all.
+ if (this->test_.IsMock())
+ return;
+ {
+ FailureHttpFetcherTestDelegate delegate(nullptr);
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ this->loop_.PostTask(FROM_HERE,
+ base::Bind(StartTransfer,
+ fetcher.get(),
+ "http://host_doesnt_exist99999999"));
+ this->loop_.Run();
+
+ // Exiting and testing happens in the delegate
+ }
+}
+
+TYPED_TEST(HttpFetcherTest, ServerDiesTest) {
+ // This test starts a new http server and kills it after receiving its first
+ // set of bytes. It test whether or not our fetcher eventually gives up on
+ // retries and aborts correctly.
+ if (this->test_.IsMock())
+ return;
+ {
+ PythonHttpServer* server = new PythonHttpServer();
+ int port = server->GetPort();
+ ASSERT_TRUE(server->started_);
+
+ // Handles destruction and claims ownership.
+ FailureHttpFetcherTestDelegate delegate(server);
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(port,
+ base::StringPrintf("/flaky/%d/%d/%d/%d",
+ kBigLength,
+ kFlakyTruncateLength,
+ kFlakySleepEvery,
+ kFlakySleepSecs))));
+ this->loop_.Run();
+
+ // Exiting and testing happens in the delegate
+ }
+}
+
+namespace {
+const HttpResponseCode kRedirectCodes[] = {
+ kHttpResponseMovedPermanently, kHttpResponseFound, kHttpResponseSeeOther,
+ kHttpResponseTempRedirect
+};
+
+class RedirectHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ explicit RedirectHttpFetcherTestDelegate(bool expected_successful)
+ : expected_successful_(expected_successful) {}
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ data.append(reinterpret_cast<const char*>(bytes), length);
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_EQ(expected_successful_, successful);
+ if (expected_successful_) {
+ EXPECT_EQ(kHttpResponseOk, fetcher->http_response_code());
+ } else {
+ EXPECT_GE(fetcher->http_response_code(), kHttpResponseMovedPermanently);
+ EXPECT_LE(fetcher->http_response_code(), kHttpResponseTempRedirect);
+ }
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ bool expected_successful_;
+ string data;
+};
+
+// RedirectTest takes ownership of |http_fetcher|.
+void RedirectTest(const HttpServer* server,
+ bool expected_successful,
+ const string& url,
+ HttpFetcher* http_fetcher) {
+ RedirectHttpFetcherTestDelegate delegate(expected_successful);
+ unique_ptr<HttpFetcher> fetcher(http_fetcher);
+ fetcher->set_delegate(&delegate);
+
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(server->GetPort(), url)));
+ MessageLoop::current()->Run();
+ if (expected_successful) {
+ // verify the data we get back
+ ASSERT_EQ(kMediumLength, delegate.data.size());
+ for (int i = 0; i < kMediumLength; i += 10) {
+ // Assert so that we don't flood the screen w/ EXPECT errors on failure.
+ ASSERT_EQ(delegate.data.substr(i, 10), "abcdefghij");
+ }
+ }
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, SimpleRedirectTest) {
+ if (this->test_.IsMock())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ for (size_t c = 0; c < arraysize(kRedirectCodes); ++c) {
+ const string url = base::StringPrintf("/redirect/%d/download/%d",
+ kRedirectCodes[c],
+ kMediumLength);
+ RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
+ }
+}
+
+TYPED_TEST(HttpFetcherTest, MaxRedirectTest) {
+ if (this->test_.IsMock())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ string url;
+ for (int r = 0; r < kDownloadMaxRedirects; r++) {
+ url += base::StringPrintf("/redirect/%d",
+ kRedirectCodes[r % arraysize(kRedirectCodes)]);
+ }
+ url += base::StringPrintf("/download/%d", kMediumLength);
+ RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
+}
+
+TYPED_TEST(HttpFetcherTest, BeyondMaxRedirectTest) {
+ if (this->test_.IsMock())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ string url;
+ for (int r = 0; r < kDownloadMaxRedirects + 1; r++) {
+ url += base::StringPrintf("/redirect/%d",
+ kRedirectCodes[r % arraysize(kRedirectCodes)]);
+ }
+ url += base::StringPrintf("/download/%d", kMediumLength);
+ RedirectTest(server.get(), false, url, this->test_.NewLargeFetcher());
+}
+
+namespace {
+class MultiHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ explicit MultiHttpFetcherTestDelegate(int expected_response_code)
+ : expected_response_code_(expected_response_code) {}
+
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ EXPECT_EQ(fetcher, fetcher_.get());
+ data.append(reinterpret_cast<const char*>(bytes), length);
+ }
+
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_EQ(fetcher, fetcher_.get());
+ EXPECT_EQ(expected_response_code_ != kHttpResponseUndefined, successful);
+ if (expected_response_code_ != 0)
+ EXPECT_EQ(expected_response_code_, fetcher->http_response_code());
+ // Destroy the fetcher (because we're allowed to).
+ fetcher_.reset(nullptr);
+ MessageLoop::current()->BreakLoop();
+ }
+
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+
+ unique_ptr<HttpFetcher> fetcher_;
+ int expected_response_code_;
+ string data;
+};
+
+void MultiTest(HttpFetcher* fetcher_in,
+ const string& url,
+ const vector<pair<off_t, off_t>>& ranges,
+ const string& expected_prefix,
+ off_t expected_size,
+ HttpResponseCode expected_response_code) {
+ MultiHttpFetcherTestDelegate delegate(expected_response_code);
+ delegate.fetcher_.reset(fetcher_in);
+
+ MultiRangeHttpFetcher* multi_fetcher =
+ dynamic_cast<MultiRangeHttpFetcher*>(fetcher_in);
+ ASSERT_TRUE(multi_fetcher);
+ multi_fetcher->ClearRanges();
+ for (vector<pair<off_t, off_t>>::const_iterator it = ranges.begin(),
+ e = ranges.end(); it != e; ++it) {
+ string tmp_str = base::StringPrintf("%jd+", it->first);
+ if (it->second > 0) {
+ base::StringAppendF(&tmp_str, "%jd", it->second);
+ multi_fetcher->AddRange(it->first, it->second);
+ } else {
+ base::StringAppendF(&tmp_str, "?");
+ multi_fetcher->AddRange(it->first);
+ }
+ LOG(INFO) << "added range: " << tmp_str;
+ }
+ dynamic_cast<FakeSystemState*>(fetcher_in->GetSystemState())
+ ->fake_hardware()->SetIsOfficialBuild(false);
+ multi_fetcher->set_delegate(&delegate);
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(StartTransfer, multi_fetcher, url));
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(expected_size, delegate.data.size());
+ EXPECT_EQ(expected_prefix,
+ string(delegate.data.data(), expected_prefix.size()));
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherSimpleTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 25));
+ ranges.push_back(make_pair(99, 0));
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "abcdefghijabcdefghijabcdejabcdefghijabcdef",
+ kBigLength - (99 - 25),
+ kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherLengthLimitTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 24));
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "abcdefghijabcdefghijabcd",
+ 24,
+ kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherMultiEndTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(kBigLength - 2, 0));
+ ranges.push_back(make_pair(kBigLength - 3, 0));
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "ijhij",
+ 5,
+ kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherInsufficientTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(kBigLength - 2, 4));
+ for (int i = 0; i < 2; ++i) {
+ LOG(INFO) << "i = " << i;
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "ij",
+ 2,
+ kHttpResponseUndefined);
+ ranges.push_back(make_pair(0, 5));
+ }
+}
+
+// Issue #18143: when a fetch of a secondary chunk out of a chain, then it
+// should retry with other proxies listed before giving up.
+//
+// (1) successful recovery: The offset fetch will fail twice but succeed with
+// the third proxy.
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherErrorIfOffsetRecoverableTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 25));
+ ranges.push_back(make_pair(99, 0));
+ MultiTest(this->test_.NewLargeFetcher(3),
+ LocalServerUrlForPath(server->GetPort(),
+ base::StringPrintf("/error-if-offset/%d/2",
+ kBigLength)),
+ ranges,
+ "abcdefghijabcdefghijabcdejabcdefghijabcdef",
+ kBigLength - (99 - 25),
+ kHttpResponsePartialContent);
+}
+
+// (2) unsuccessful recovery: The offset fetch will fail repeatedly. The
+// fetcher will signal a (failed) completed transfer to the delegate.
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherErrorIfOffsetUnrecoverableTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 25));
+ ranges.push_back(make_pair(99, 0));
+ MultiTest(this->test_.NewLargeFetcher(2),
+ LocalServerUrlForPath(server->GetPort(),
+ base::StringPrintf("/error-if-offset/%d/3",
+ kBigLength)),
+ ranges,
+ "abcdefghijabcdefghijabcde", // only received the first chunk
+ 25,
+ kHttpResponseUndefined);
+}
+
+
+
+namespace {
+class BlockedTransferTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ ADD_FAILURE();
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_FALSE(successful);
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+};
+
+void BlockedTransferTestHelper(AnyHttpFetcherTest* fetcher_test,
+ bool is_official_build) {
+ if (fetcher_test->IsMock() || fetcher_test->IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(fetcher_test->CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ BlockedTransferTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(fetcher_test->NewLargeFetcher());
+ LOG(INFO) << "is_official_build: " << is_official_build;
+ // NewLargeFetcher creates the HttpFetcher* with a FakeSystemState.
+ dynamic_cast<FakeSystemState*>(fetcher->GetSystemState())
+ ->fake_hardware()->SetIsOfficialBuild(is_official_build);
+ fetcher->set_delegate(&delegate);
+
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(server->GetPort(),
+ fetcher_test->SmallUrl(server->GetPort()))));
+ MessageLoop::current()->Run();
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, BlockedTransferTest) {
+ BlockedTransferTestHelper(&this->test_, false);
+}
+
+TYPED_TEST(HttpFetcherTest, BlockedTransferOfficialBuildTest) {
+ BlockedTransferTestHelper(&this->test_, true);
+}
+
+} // namespace chromeos_update_engine
diff --git a/hwid_override.cc b/hwid_override.cc
new file mode 100644
index 0000000..258a997
--- /dev/null
+++ b/hwid_override.cc
@@ -0,0 +1,34 @@
+// 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.
+
+#include "update_engine/hwid_override.h"
+
+#include <map>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <chromeos/key_value_store.h>
+
+using std::map;
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char HwidOverride::kHwidOverrideKey[] = "HWID_OVERRIDE";
+
+HwidOverride::HwidOverride() {}
+
+HwidOverride::~HwidOverride() {}
+
+string HwidOverride::Read(const base::FilePath& root) {
+ chromeos::KeyValueStore lsb_release;
+ lsb_release.Load(base::FilePath(root.value() + "/etc/lsb-release"));
+ string result;
+ if (lsb_release.GetString(kHwidOverrideKey, &result))
+ return result;
+ return "";
+}
+
+} // namespace chromeos_update_engine
diff --git a/hwid_override.h b/hwid_override.h
new file mode 100644
index 0000000..dc0873b
--- /dev/null
+++ b/hwid_override.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_HWID_OVERRIDE_H_
+#define UPDATE_ENGINE_HWID_OVERRIDE_H_
+
+#include <map>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+// Class that allows HWID to be read from <root>/etc/lsb-release.
+class HwidOverride {
+ public:
+ HwidOverride();
+ ~HwidOverride();
+
+ // Read HWID from an /etc/lsb-release file under given root.
+ // An empty string is returned if there is any error.
+ static std::string Read(const base::FilePath& root);
+
+ static const char kHwidOverrideKey[];
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HwidOverride);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_HWID_OVERRIDE_H_
diff --git a/hwid_override_unittest.cc b/hwid_override_unittest.cc
new file mode 100644
index 0000000..44a114a
--- /dev/null
+++ b/hwid_override_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/hwid_override.h"
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+class HwidOverrideTest : public ::testing::Test {
+ public:
+ HwidOverrideTest() {}
+ ~HwidOverrideTest() override = default;
+
+ void SetUp() override {
+ ASSERT_TRUE(tempdir_.CreateUniqueTempDir());
+ ASSERT_TRUE(base::CreateDirectory(tempdir_.path().Append("etc")));
+ }
+
+ protected:
+ base::ScopedTempDir tempdir_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HwidOverrideTest);
+};
+
+TEST_F(HwidOverrideTest, ReadGood) {
+ std::string expected_hwid("expected");
+ std::string keyval(HwidOverride::kHwidOverrideKey);
+ keyval += ("=" + expected_hwid);
+ ASSERT_EQ(base::WriteFile(tempdir_.path().Append("etc/lsb-release"),
+ keyval.c_str(), keyval.length()),
+ keyval.length());
+ EXPECT_EQ(expected_hwid, HwidOverride::Read(tempdir_.path()));
+}
+
+TEST_F(HwidOverrideTest, ReadNothing) {
+ std::string keyval("SOMETHING_ELSE=UNINTERESTING");
+ ASSERT_EQ(base::WriteFile(tempdir_.path().Append("etc/lsb-release"),
+ keyval.c_str(), keyval.length()),
+ keyval.length());
+ EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.path()));
+}
+
+TEST_F(HwidOverrideTest, ReadFailure) {
+ EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.path()));
+}
+
+} // namespace chromeos_update_engine
diff --git a/init/update-engine.conf b/init/update-engine.conf
new file mode 100644
index 0000000..35bfc06
--- /dev/null
+++ b/init/update-engine.conf
@@ -0,0 +1,27 @@
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+description "System software update service"
+author "chromium-os-dev@chromium.org"
+
+# N.B. The chromeos-factoryinstall ebuild edits the 'start on' line so as
+# to disable update_engine in factory images. Do not change this without
+# also updating that reference.
+start on starting system-services
+stop on stopping system-services
+respawn
+
+expect fork
+
+# Runs the daemon at low/idle IO priority so that updates don't
+# impact system responsiveness.
+exec ionice -c3 update_engine
+
+# Put update_engine process in its own cgroup.
+# Default cpu.shares is 1024.
+post-start script
+ cgroup_dir="/sys/fs/cgroup/cpu/${UPSTART_JOB}"
+ mkdir -p "${cgroup_dir}"
+ echo $(status | cut -f 4 -d ' ') > "${cgroup_dir}/tasks"
+end script
diff --git a/install_plan.cc b/install_plan.cc
new file mode 100644
index 0000000..7b97876
--- /dev/null
+++ b/install_plan.cc
@@ -0,0 +1,91 @@
+// 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.
+
+#include "update_engine/install_plan.h"
+
+#include <base/logging.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+InstallPlan::InstallPlan(bool is_resume,
+ bool is_full_update,
+ const string& url,
+ uint64_t payload_size,
+ const string& payload_hash,
+ uint64_t metadata_size,
+ const string& metadata_signature,
+ const string& install_path,
+ const string& kernel_install_path,
+ const string& source_path,
+ const string& kernel_source_path,
+ const string& public_key_rsa)
+ : is_resume(is_resume),
+ is_full_update(is_full_update),
+ download_url(url),
+ payload_size(payload_size),
+ payload_hash(payload_hash),
+ metadata_size(metadata_size),
+ metadata_signature(metadata_signature),
+ install_path(install_path),
+ kernel_install_path(kernel_install_path),
+ source_path(source_path),
+ kernel_source_path(kernel_source_path),
+ kernel_size(0),
+ rootfs_size(0),
+ hash_checks_mandatory(false),
+ powerwash_required(false),
+ public_key_rsa(public_key_rsa) {}
+
+InstallPlan::InstallPlan() : is_resume(false),
+ is_full_update(false), // play it safe.
+ payload_size(0),
+ metadata_size(0),
+ kernel_size(0),
+ rootfs_size(0),
+ hash_checks_mandatory(false),
+ powerwash_required(false) {}
+
+
+bool InstallPlan::operator==(const InstallPlan& that) const {
+ return ((is_resume == that.is_resume) &&
+ (is_full_update == that.is_full_update) &&
+ (download_url == that.download_url) &&
+ (payload_size == that.payload_size) &&
+ (payload_hash == that.payload_hash) &&
+ (metadata_size == that.metadata_size) &&
+ (metadata_signature == that.metadata_signature) &&
+ (install_path == that.install_path) &&
+ (kernel_install_path == that.kernel_install_path) &&
+ (source_path == that.source_path) &&
+ (kernel_source_path == that.kernel_source_path));
+}
+
+bool InstallPlan::operator!=(const InstallPlan& that) const {
+ return !((*this) == that);
+}
+
+void InstallPlan::Dump() const {
+ LOG(INFO) << "InstallPlan: "
+ << (is_resume ? "resume" : "new_update")
+ << ", payload type: " << (is_full_update ? "full" : "delta")
+ << ", url: " << download_url
+ << ", payload size: " << payload_size
+ << ", payload hash: " << payload_hash
+ << ", metadata size: " << metadata_size
+ << ", metadata signature: " << metadata_signature
+ << ", install_path: " << install_path
+ << ", kernel_install_path: " << kernel_install_path
+ << ", source_path: " << source_path
+ << ", kernel_source_path: " << kernel_source_path
+ << ", hash_checks_mandatory: " << utils::ToString(
+ hash_checks_mandatory)
+ << ", powerwash_required: " << utils::ToString(
+ powerwash_required);
+}
+
+} // namespace chromeos_update_engine
diff --git a/install_plan.h b/install_plan.h
new file mode 100644
index 0000000..756822a
--- /dev/null
+++ b/install_plan.h
@@ -0,0 +1,131 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_INSTALL_PLAN_H_
+#define UPDATE_ENGINE_INSTALL_PLAN_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/action.h"
+
+// InstallPlan is a simple struct that contains relevant info for many
+// parts of the update system about the install that should happen.
+namespace chromeos_update_engine {
+
+struct InstallPlan {
+ InstallPlan(bool is_resume,
+ bool is_full_update,
+ const std::string& url,
+ uint64_t payload_size,
+ const std::string& payload_hash,
+ uint64_t metadata_size,
+ const std::string& metadata_signature,
+ const std::string& install_path,
+ const std::string& kernel_install_path,
+ const std::string& source_path,
+ const std::string& kernel_source_path,
+ const std::string& public_key_rsa);
+
+ // Default constructor: Initialize all members which don't have a class
+ // initializer.
+ InstallPlan();
+
+ bool operator==(const InstallPlan& that) const;
+ bool operator!=(const InstallPlan& that) const;
+
+ void Dump() const;
+
+ bool is_resume;
+ bool is_full_update;
+ std::string download_url; // url to download from
+ std::string version; // version we are installing.
+
+ uint64_t payload_size; // size of the payload
+ std::string payload_hash; // SHA256 hash of the payload
+ uint64_t metadata_size; // size of the metadata
+ std::string metadata_signature; // signature of the metadata
+ std::string install_path; // path to install device
+ std::string kernel_install_path; // path to kernel install device
+ std::string source_path; // path to source device
+ std::string kernel_source_path; // path to source kernel device
+
+ // The fields below are used for kernel and rootfs verification. The flow is:
+ //
+ // 1. FilesystemVerifierAction computes and fills in the source partition
+ // sizes and hashes.
+ //
+ // 2. DownloadAction verifies the source partition sizes and hashes against
+ // the expected values transmitted in the update manifest. It fills in the
+ // expected applied partition sizes and hashes based on the manifest.
+ //
+ // 3. FilesystemVerifierAction computes and verifies the applied and source
+ // partition sizes and hashes against the expected values.
+ uint64_t kernel_size;
+ uint64_t rootfs_size;
+ chromeos::Blob kernel_hash;
+ chromeos::Blob rootfs_hash;
+ chromeos::Blob source_kernel_hash;
+ chromeos::Blob source_rootfs_hash;
+
+ // True if payload hash checks are mandatory based on the system state and
+ // the Omaha response.
+ bool hash_checks_mandatory;
+
+ // True if Powerwash is required on reboot after applying the payload.
+ // False otherwise.
+ bool powerwash_required;
+
+ // If not blank, a base-64 encoded representation of the PEM-encoded
+ // public key in the response.
+ std::string public_key_rsa;
+};
+
+class InstallPlanAction;
+
+template<>
+class ActionTraits<InstallPlanAction> {
+ public:
+ // Takes the install plan as input
+ typedef InstallPlan InputObjectType;
+ // Passes the install plan as output
+ typedef InstallPlan OutputObjectType;
+};
+
+// Basic action that only receives and sends Install Plans.
+// Can be used to construct an Install Plan to send to any other Action that
+// accept an InstallPlan.
+class InstallPlanAction : public Action<InstallPlanAction> {
+ public:
+ InstallPlanAction() {}
+ explicit InstallPlanAction(const InstallPlan& install_plan):
+ install_plan_(install_plan) {}
+
+ void PerformAction() override {
+ if (HasOutputPipe()) {
+ SetOutputObject(install_plan_);
+ }
+ processor_->ActionComplete(this, ErrorCode::kSuccess);
+ }
+
+ InstallPlan* install_plan() { return &install_plan_; }
+
+ static std::string StaticType() { return "InstallPlanAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType;
+ typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType;
+
+ private:
+ InstallPlan install_plan_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstallPlanAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_INSTALL_PLAN_H_
diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc
new file mode 100644
index 0000000..9d32293
--- /dev/null
+++ b/libcurl_http_fetcher.cc
@@ -0,0 +1,560 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/libcurl_http_fetcher.h"
+
+#include <algorithm>
+#include <string>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/hardware_interface.h"
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using std::max;
+using std::string;
+
+// This is a concrete implementation of HttpFetcher that uses libcurl to do the
+// http work.
+
+namespace chromeos_update_engine {
+
+namespace {
+const int kNoNetworkRetrySeconds = 10;
+const char kCACertificatesPath[] = "/usr/share/chromeos-ca-certificates";
+} // namespace
+
+LibcurlHttpFetcher::~LibcurlHttpFetcher() {
+ LOG_IF(ERROR, transfer_in_progress_)
+ << "Destroying the fetcher while a transfer is in progress.";
+ CleanUp();
+}
+
+bool LibcurlHttpFetcher::GetProxyType(const string& proxy,
+ curl_proxytype* out_type) {
+ if (base::StartsWithASCII(proxy, "socks5://", true) ||
+ base::StartsWithASCII(proxy, "socks://", true)) {
+ *out_type = CURLPROXY_SOCKS5_HOSTNAME;
+ return true;
+ }
+ if (base::StartsWithASCII(proxy, "socks4://", true)) {
+ *out_type = CURLPROXY_SOCKS4A;
+ return true;
+ }
+ if (base::StartsWithASCII(proxy, "http://", true) ||
+ base::StartsWithASCII(proxy, "https://", true)) {
+ *out_type = CURLPROXY_HTTP;
+ return true;
+ }
+ if (base::StartsWithASCII(proxy, kNoProxy, true)) {
+ // known failure case. don't log.
+ return false;
+ }
+ LOG(INFO) << "Unknown proxy type: " << proxy;
+ return false;
+}
+
+void LibcurlHttpFetcher::ResumeTransfer(const string& url) {
+ LOG(INFO) << "Starting/Resuming transfer";
+ CHECK(!transfer_in_progress_);
+ url_ = url;
+ curl_multi_handle_ = curl_multi_init();
+ CHECK(curl_multi_handle_);
+
+ curl_handle_ = curl_easy_init();
+ CHECK(curl_handle_);
+
+ CHECK(HasProxy());
+ bool is_direct = (GetCurrentProxy() == kNoProxy);
+ LOG(INFO) << "Using proxy: " << (is_direct ? "no" : "yes");
+ if (is_direct) {
+ CHECK_EQ(curl_easy_setopt(curl_handle_,
+ CURLOPT_PROXY,
+ ""), CURLE_OK);
+ } else {
+ CHECK_EQ(curl_easy_setopt(curl_handle_,
+ CURLOPT_PROXY,
+ GetCurrentProxy().c_str()), CURLE_OK);
+ // Curl seems to require us to set the protocol
+ curl_proxytype type;
+ if (GetProxyType(GetCurrentProxy(), &type)) {
+ CHECK_EQ(curl_easy_setopt(curl_handle_,
+ CURLOPT_PROXYTYPE,
+ type), CURLE_OK);
+ }
+ }
+
+ if (post_data_set_) {
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS,
+ post_data_.data()),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE,
+ post_data_.size()),
+ CURLE_OK);
+
+ // Set the Content-Type HTTP header, if one was specifically set.
+ CHECK(!curl_http_headers_);
+ if (post_content_type_ != kHttpContentTypeUnspecified) {
+ const string content_type_attr =
+ base::StringPrintf("Content-Type: %s",
+ GetHttpContentTypeString(post_content_type_));
+ curl_http_headers_ = curl_slist_append(nullptr,
+ content_type_attr.c_str());
+ CHECK(curl_http_headers_);
+ CHECK_EQ(
+ curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER,
+ curl_http_headers_),
+ CURLE_OK);
+ } else {
+ LOG(WARNING) << "no content type set, using libcurl default";
+ }
+ }
+
+ if (bytes_downloaded_ > 0 || download_length_) {
+ // Resume from where we left off.
+ resume_offset_ = bytes_downloaded_;
+ CHECK_GE(resume_offset_, 0);
+
+ // Compute end offset, if one is specified. As per HTTP specification, this
+ // is an inclusive boundary. Make sure it doesn't overflow.
+ size_t end_offset = 0;
+ if (download_length_) {
+ end_offset = static_cast<size_t>(resume_offset_) + download_length_ - 1;
+ CHECK_LE((size_t) resume_offset_, end_offset);
+ }
+
+ // Create a string representation of the desired range.
+ string range_str = (end_offset ?
+ base::StringPrintf("%jd-%zu", resume_offset_,
+ end_offset) :
+ base::StringPrintf("%jd-", resume_offset_));
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_RANGE, range_str.c_str()),
+ CURLE_OK);
+ }
+
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
+ StaticLibcurlWrite), CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()),
+ CURLE_OK);
+
+ // If the connection drops under |low_speed_limit_bps_| (10
+ // bytes/sec by default) for |low_speed_time_seconds_| (90 seconds,
+ // 180 on non-official builds), reconnect.
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_LIMIT,
+ low_speed_limit_bps_),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_TIME,
+ low_speed_time_seconds_),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CONNECTTIMEOUT,
+ connect_timeout_seconds_),
+ CURLE_OK);
+
+ // By default, libcurl doesn't follow redirections. Allow up to
+ // |kDownloadMaxRedirects| redirections.
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_FOLLOWLOCATION, 1), CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_MAXREDIRS,
+ kDownloadMaxRedirects),
+ CURLE_OK);
+
+ // Lock down the appropriate curl options for HTTP or HTTPS depending on
+ // the url.
+ if (GetSystemState()->hardware()->IsOfficialBuild()) {
+ if (base::StartsWithASCII(url_, "http://", false))
+ SetCurlOptionsForHttp();
+ else
+ SetCurlOptionsForHttps();
+ } else {
+ LOG(INFO) << "Not setting http(s) curl options because we are "
+ << "running a dev/test image";
+ }
+
+ CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK);
+ transfer_in_progress_ = true;
+}
+
+// Lock down only the protocol in case of HTTP.
+void LibcurlHttpFetcher::SetCurlOptionsForHttp() {
+ LOG(INFO) << "Setting up curl options for HTTP";
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_HTTP),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS,
+ CURLPROTO_HTTP),
+ CURLE_OK);
+}
+
+// Security lock-down in official builds: makes sure that peer certificate
+// verification is enabled, restricts the set of trusted certificates,
+// restricts protocols to HTTPS, restricts ciphers to HIGH.
+void LibcurlHttpFetcher::SetCurlOptionsForHttps() {
+ LOG(INFO) << "Setting up curl options for HTTPS";
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_VERIFYPEER, 1),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CAPATH, kCACertificatesPath),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS,
+ CURLPROTO_HTTPS),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CIPHER_LIST, "HIGH:!ADH"),
+ CURLE_OK);
+ if (check_certificate_ != CertificateChecker::kNone) {
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_DATA,
+ &check_certificate_),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_FUNCTION,
+ CertificateChecker::ProcessSSLContext),
+ CURLE_OK);
+ }
+}
+
+
+// Begins the transfer, which must not have already been started.
+void LibcurlHttpFetcher::BeginTransfer(const string& url) {
+ CHECK(!transfer_in_progress_);
+ url_ = url;
+ auto closure = base::Bind(&LibcurlHttpFetcher::ProxiesResolved,
+ base::Unretained(this));
+ if (!ResolveProxiesForUrl(url_, closure)) {
+ LOG(ERROR) << "Couldn't resolve proxies";
+ if (delegate_)
+ delegate_->TransferComplete(this, false);
+ }
+}
+
+void LibcurlHttpFetcher::ProxiesResolved() {
+ transfer_size_ = -1;
+ resume_offset_ = 0;
+ retry_count_ = 0;
+ no_network_retry_count_ = 0;
+ http_response_code_ = 0;
+ terminate_requested_ = false;
+ sent_byte_ = false;
+ ResumeTransfer(url_);
+ CurlPerformOnce();
+}
+
+void LibcurlHttpFetcher::ForceTransferTermination() {
+ CleanUp();
+ if (delegate_) {
+ // Note that after the callback returns this object may be destroyed.
+ delegate_->TransferTerminated(this);
+ }
+}
+
+void LibcurlHttpFetcher::TerminateTransfer() {
+ if (in_write_callback_) {
+ terminate_requested_ = true;
+ } else {
+ ForceTransferTermination();
+ }
+}
+
+void LibcurlHttpFetcher::CurlPerformOnce() {
+ CHECK(transfer_in_progress_);
+ int running_handles = 0;
+ CURLMcode retcode = CURLM_CALL_MULTI_PERFORM;
+
+ // libcurl may request that we immediately call curl_multi_perform after it
+ // returns, so we do. libcurl promises that curl_multi_perform will not block.
+ while (CURLM_CALL_MULTI_PERFORM == retcode) {
+ retcode = curl_multi_perform(curl_multi_handle_, &running_handles);
+ if (terminate_requested_) {
+ ForceTransferTermination();
+ return;
+ }
+ }
+ if (0 == running_handles) {
+ GetHttpResponseCode();
+ if (http_response_code_) {
+ LOG(INFO) << "HTTP response code: " << http_response_code_;
+ no_network_retry_count_ = 0;
+ } else {
+ LOG(ERROR) << "Unable to get http response code.";
+ }
+
+ // we're done!
+ CleanUp();
+
+ // TODO(petkov): This temporary code tries to deal with the case where the
+ // update engine performs an update check while the network is not ready
+ // (e.g., right after resume). Longer term, we should check if the network
+ // is online/offline and return an appropriate error code.
+ if (!sent_byte_ &&
+ http_response_code_ == 0 &&
+ no_network_retry_count_ < no_network_max_retries_) {
+ no_network_retry_count_++;
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+ base::Unretained(this)),
+ TimeDelta::FromSeconds(kNoNetworkRetrySeconds));
+ LOG(INFO) << "No HTTP response, retry " << no_network_retry_count_;
+ return;
+ }
+
+ if ((!sent_byte_ && !IsHttpResponseSuccess()) || IsHttpResponseError()) {
+ // The transfer completed w/ error and we didn't get any bytes.
+ // If we have another proxy to try, try that.
+ //
+ // TODO(garnold) in fact there are two separate cases here: one case is an
+ // other-than-success return code (including no return code) and no
+ // received bytes, which is necessary due to the way callbacks are
+ // currently processing error conditions; the second is an explicit HTTP
+ // error code, where some data may have been received (as in the case of a
+ // semi-successful multi-chunk fetch). This is a confusing behavior and
+ // should be unified into a complete, coherent interface.
+ LOG(INFO) << "Transfer resulted in an error (" << http_response_code_
+ << "), " << bytes_downloaded_ << " bytes downloaded";
+
+ PopProxy(); // Delete the proxy we just gave up on.
+
+ if (HasProxy()) {
+ // We have another proxy. Retry immediately.
+ LOG(INFO) << "Retrying with next proxy setting";
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+ base::Unretained(this)));
+ } else {
+ // Out of proxies. Give up.
+ LOG(INFO) << "No further proxies, indicating transfer complete";
+ if (delegate_)
+ delegate_->TransferComplete(this, false); // signal fail
+ }
+ } else if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) {
+ retry_count_++;
+ LOG(INFO) << "Transfer interrupted after downloading "
+ << bytes_downloaded_ << " of " << transfer_size_ << " bytes. "
+ << transfer_size_ - bytes_downloaded_ << " bytes remaining "
+ << "after " << retry_count_ << " attempt(s)";
+
+ if (retry_count_ > max_retry_count_) {
+ LOG(INFO) << "Reached max attempts (" << retry_count_ << ")";
+ if (delegate_)
+ delegate_->TransferComplete(this, false); // signal fail
+ } else {
+ // Need to restart transfer
+ LOG(INFO) << "Restarting transfer to download the remaining bytes";
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+ base::Unretained(this)),
+ TimeDelta::FromSeconds(retry_seconds_));
+ }
+ } else {
+ LOG(INFO) << "Transfer completed (" << http_response_code_
+ << "), " << bytes_downloaded_ << " bytes downloaded";
+ if (delegate_) {
+ bool success = IsHttpResponseSuccess();
+ delegate_->TransferComplete(this, success);
+ }
+ }
+ } else {
+ // set up callback
+ SetupMessageLoopSources();
+ }
+}
+
+size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) {
+ // Update HTTP response first.
+ GetHttpResponseCode();
+ const size_t payload_size = size * nmemb;
+
+ // Do nothing if no payload or HTTP response is an error.
+ if (payload_size == 0 || !IsHttpResponseSuccess()) {
+ LOG(INFO) << "HTTP response unsuccessful (" << http_response_code_
+ << ") or no payload (" << payload_size << "), nothing to do";
+ return 0;
+ }
+
+ sent_byte_ = true;
+ {
+ double transfer_size_double;
+ CHECK_EQ(curl_easy_getinfo(curl_handle_,
+ CURLINFO_CONTENT_LENGTH_DOWNLOAD,
+ &transfer_size_double), CURLE_OK);
+ off_t new_transfer_size = static_cast<off_t>(transfer_size_double);
+ if (new_transfer_size > 0) {
+ transfer_size_ = resume_offset_ + new_transfer_size;
+ }
+ }
+ bytes_downloaded_ += payload_size;
+ in_write_callback_ = true;
+ if (delegate_)
+ delegate_->ReceivedBytes(this, ptr, payload_size);
+ in_write_callback_ = false;
+ return payload_size;
+}
+
+void LibcurlHttpFetcher::Pause() {
+ CHECK(curl_handle_);
+ CHECK(transfer_in_progress_);
+ CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_ALL), CURLE_OK);
+}
+
+void LibcurlHttpFetcher::Unpause() {
+ CHECK(curl_handle_);
+ CHECK(transfer_in_progress_);
+ CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_CONT), CURLE_OK);
+}
+
+// This method sets up callbacks with the MessageLoop.
+void LibcurlHttpFetcher::SetupMessageLoopSources() {
+ fd_set fd_read;
+ fd_set fd_write;
+ fd_set fd_exc;
+
+ FD_ZERO(&fd_read);
+ FD_ZERO(&fd_write);
+ FD_ZERO(&fd_exc);
+
+ int fd_max = 0;
+
+ // Ask libcurl for the set of file descriptors we should track on its
+ // behalf.
+ CHECK_EQ(curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write,
+ &fd_exc, &fd_max), CURLM_OK);
+
+ // We should iterate through all file descriptors up to libcurl's fd_max or
+ // the highest one we're tracking, whichever is larger.
+ for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
+ if (!fd_task_maps_[t].empty())
+ fd_max = max(fd_max, fd_task_maps_[t].rbegin()->first);
+ }
+
+ // For each fd, if we're not tracking it, track it. If we are tracking it, but
+ // libcurl doesn't care about it anymore, stop tracking it. After this loop,
+ // there should be exactly as many tasks scheduled in fd_task_maps_[0|1] as
+ // there are read/write fds that we're tracking.
+ for (int fd = 0; fd <= fd_max; ++fd) {
+ // Note that fd_exc is unused in the current version of libcurl so is_exc
+ // should always be false.
+ bool is_exc = FD_ISSET(fd, &fd_exc) != 0;
+ bool must_track[2] = {
+ is_exc || (FD_ISSET(fd, &fd_read) != 0), // track 0 -- read
+ is_exc || (FD_ISSET(fd, &fd_write) != 0) // track 1 -- write
+ };
+ MessageLoop::WatchMode watch_modes[2] = {
+ MessageLoop::WatchMode::kWatchRead,
+ MessageLoop::WatchMode::kWatchWrite,
+ };
+
+ for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
+ auto fd_task_it = fd_task_maps_[t].find(fd);
+ bool tracked = fd_task_it != fd_task_maps_[t].end();
+
+ if (!must_track[t]) {
+ // If we have an outstanding io_channel, remove it.
+ if (tracked) {
+ MessageLoop::current()->CancelTask(fd_task_it->second);
+ fd_task_maps_[t].erase(fd_task_it);
+ }
+ continue;
+ }
+
+ // If we are already tracking this fd, continue -- nothing to do.
+ if (tracked)
+ continue;
+
+ // Track a new fd.
+ fd_task_maps_[t][fd] = MessageLoop::current()->WatchFileDescriptor(
+ FROM_HERE,
+ fd,
+ watch_modes[t],
+ true, // persistent
+ base::Bind(&LibcurlHttpFetcher::CurlPerformOnce,
+ base::Unretained(this)));
+
+ static int io_counter = 0;
+ io_counter++;
+ if (io_counter % 50 == 0) {
+ LOG(INFO) << "io_counter = " << io_counter;
+ }
+ }
+ }
+
+ // Set up a timeout callback for libcurl.
+ if (timeout_id_ == MessageLoop::kTaskIdNull) {
+ LOG(INFO) << "Setting up timeout source: " << idle_seconds_ << " seconds.";
+ timeout_id_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&LibcurlHttpFetcher::TimeoutCallback,
+ base::Unretained(this)),
+ TimeDelta::FromSeconds(idle_seconds_));
+ }
+}
+
+void LibcurlHttpFetcher::RetryTimeoutCallback() {
+ ResumeTransfer(url_);
+ CurlPerformOnce();
+}
+
+void LibcurlHttpFetcher::TimeoutCallback() {
+ if (transfer_in_progress_)
+ CurlPerformOnce();
+
+ // We always re-schedule the callback, even if we don't want to be called
+ // anymore. We will remove the event source separately if we don't want to
+ // be called back.
+ timeout_id_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&LibcurlHttpFetcher::TimeoutCallback, base::Unretained(this)),
+ TimeDelta::FromSeconds(idle_seconds_));
+}
+
+void LibcurlHttpFetcher::CleanUp() {
+ MessageLoop::current()->CancelTask(timeout_id_);
+ timeout_id_ = MessageLoop::kTaskIdNull;
+
+ for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
+ for (const auto& fd_taks_pair : fd_task_maps_[t]) {
+ if (!MessageLoop::current()->CancelTask(fd_taks_pair.second)) {
+ LOG(WARNING) << "Error canceling the watch task "
+ << fd_taks_pair.second << " for "
+ << (t ? "writing" : "reading") << " the fd "
+ << fd_taks_pair.first;
+ }
+ }
+ fd_task_maps_[t].clear();
+ }
+
+ if (curl_http_headers_) {
+ curl_slist_free_all(curl_http_headers_);
+ curl_http_headers_ = nullptr;
+ }
+ if (curl_handle_) {
+ if (curl_multi_handle_) {
+ CHECK_EQ(curl_multi_remove_handle(curl_multi_handle_, curl_handle_),
+ CURLM_OK);
+ }
+ curl_easy_cleanup(curl_handle_);
+ curl_handle_ = nullptr;
+ }
+ if (curl_multi_handle_) {
+ CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK);
+ curl_multi_handle_ = nullptr;
+ }
+ transfer_in_progress_ = false;
+}
+
+void LibcurlHttpFetcher::GetHttpResponseCode() {
+ long http_response_code = 0; // NOLINT(runtime/int) - curl needs long.
+ if (curl_easy_getinfo(curl_handle_,
+ CURLINFO_RESPONSE_CODE,
+ &http_response_code) == CURLE_OK) {
+ http_response_code_ = static_cast<int>(http_response_code);
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/libcurl_http_fetcher.h b/libcurl_http_fetcher.h
new file mode 100644
index 0000000..4f68a9a
--- /dev/null
+++ b/libcurl_http_fetcher.h
@@ -0,0 +1,245 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include <curl/curl.h>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/http_fetcher.h"
+#include "update_engine/system_state.h"
+
+
+// This is a concrete implementation of HttpFetcher that uses libcurl to do the
+// http work.
+
+namespace chromeos_update_engine {
+
+class LibcurlHttpFetcher : public HttpFetcher {
+ public:
+ LibcurlHttpFetcher(ProxyResolver* proxy_resolver,
+ SystemState* system_state)
+ : HttpFetcher(proxy_resolver, system_state) {
+ // Dev users want a longer timeout (180 seconds) because they may
+ // be waiting on the dev server to build an image.
+ if (!system_state->hardware()->IsOfficialBuild())
+ low_speed_time_seconds_ = kDownloadDevModeLowSpeedTimeSeconds;
+ if (!system_state_->hardware()->IsOOBEComplete(nullptr))
+ max_retry_count_ = kDownloadMaxRetryCountOobeNotComplete;
+ }
+
+ // Cleans up all internal state. Does not notify delegate
+ ~LibcurlHttpFetcher() override;
+
+ void SetOffset(off_t offset) override { bytes_downloaded_ = offset; }
+
+ void SetLength(size_t length) override { download_length_ = length; }
+ void UnsetLength() override { SetLength(0); }
+
+ // Begins the transfer if it hasn't already begun.
+ void BeginTransfer(const std::string& url) override;
+
+ // If the transfer is in progress, aborts the transfer early. The transfer
+ // cannot be resumed.
+ void TerminateTransfer() override;
+
+ // Suspend the transfer by calling curl_easy_pause(CURLPAUSE_ALL).
+ void Pause() override;
+
+ // Resume the transfer by calling curl_easy_pause(CURLPAUSE_CONT).
+ void Unpause() override;
+
+ // Libcurl sometimes asks to be called back after some time while
+ // leaving that time unspecified. In that case, we pick a reasonable
+ // default of one second, but it can be overridden here. This is
+ // primarily useful for testing.
+ // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html:
+ // if libcurl returns a -1 timeout here, it just means that libcurl
+ // currently has no stored timeout value. You must not wait too long
+ // (more than a few seconds perhaps) before you call
+ // curl_multi_perform() again.
+ void set_idle_seconds(int seconds) override { idle_seconds_ = seconds; }
+
+ // Sets the retry timeout. Useful for testing.
+ void set_retry_seconds(int seconds) override { retry_seconds_ = seconds; }
+
+ void set_no_network_max_retries(int retries) {
+ no_network_max_retries_ = retries;
+ }
+
+ void set_check_certificate(
+ CertificateChecker::ServerToCheck check_certificate) {
+ check_certificate_ = check_certificate;
+ }
+
+ size_t GetBytesDownloaded() override {
+ return static_cast<size_t>(bytes_downloaded_);
+ }
+
+ void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {
+ low_speed_limit_bps_ = low_speed_bps;
+ low_speed_time_seconds_ = low_speed_sec;
+ }
+
+ void set_connect_timeout(int connect_timeout_seconds) override {
+ connect_timeout_seconds_ = connect_timeout_seconds;
+ }
+
+ void set_max_retry_count(int max_retry_count) override {
+ max_retry_count_ = max_retry_count;
+ }
+
+ private:
+ // Callback for when proxy resolution has completed. This begins the
+ // transfer.
+ void ProxiesResolved();
+
+ // Asks libcurl for the http response code and stores it in the object.
+ void GetHttpResponseCode();
+
+ // Checks whether stored HTTP response is within the success range.
+ inline bool IsHttpResponseSuccess() {
+ return (http_response_code_ >= 200 && http_response_code_ < 300);
+ }
+
+ // Checks whether stored HTTP response is within the error range. This
+ // includes both errors with the request (4xx) and server errors (5xx).
+ inline bool IsHttpResponseError() {
+ return (http_response_code_ >= 400 && http_response_code_ < 600);
+ }
+
+ // Resumes a transfer where it left off. This will use the
+ // HTTP Range: header to make a new connection from where the last
+ // left off.
+ virtual void ResumeTransfer(const std::string& url);
+
+ void TimeoutCallback();
+ void RetryTimeoutCallback();
+
+ // Calls into curl_multi_perform to let libcurl do its work. Returns after
+ // curl_multi_perform is finished, which may actually be after more than
+ // one call to curl_multi_perform. This method will set up the glib run
+ // loop with sources for future work that libcurl will do.
+ // This method will not block.
+ // Returns true if we should resume immediately after this call.
+ void CurlPerformOnce();
+
+ // Sets up message loop sources as needed by libcurl. This is generally
+ // the file descriptor of the socket and a timer in case nothing happens
+ // on the fds.
+ void SetupMessageLoopSources();
+
+ // Callback called by libcurl when new data has arrived on the transfer
+ size_t LibcurlWrite(void *ptr, size_t size, size_t nmemb);
+ static size_t StaticLibcurlWrite(void *ptr, size_t size,
+ size_t nmemb, void *stream) {
+ return reinterpret_cast<LibcurlHttpFetcher*>(stream)->
+ LibcurlWrite(ptr, size, nmemb);
+ }
+
+ // Cleans up the following if they are non-null:
+ // curl(m) handles, fd_task_maps_, timeout_id_.
+ void CleanUp();
+
+ // Force terminate the transfer. This will invoke the delegate's (if any)
+ // TransferTerminated callback so, after returning, this fetcher instance may
+ // be destroyed.
+ void ForceTransferTermination();
+
+ // Sets the curl options for HTTP URL.
+ void SetCurlOptionsForHttp();
+
+ // Sets the curl options for HTTPS URL.
+ void SetCurlOptionsForHttps();
+
+ // Convert a proxy URL into a curl proxy type, if applicable. Returns true iff
+ // conversion was successful, false otherwise (in which case nothing is
+ // written to |out_type|).
+ bool GetProxyType(const std::string& proxy, curl_proxytype* out_type);
+
+ // Handles for the libcurl library
+ CURLM* curl_multi_handle_{nullptr};
+ CURL* curl_handle_{nullptr};
+ struct curl_slist* curl_http_headers_{nullptr};
+
+ // Lists of all read(0)/write(1) file descriptors that we're waiting on from
+ // the message loop. libcurl may open/close descriptors and switch their
+ // directions so maintain two separate lists so that watch conditions can be
+ // set appropriately.
+ std::map<int, chromeos::MessageLoop::TaskId> fd_task_maps_[2];
+
+ // The TaskId of the timer we're waiting on. kTaskIdNull if we are not waiting
+ // on it.
+ chromeos::MessageLoop::TaskId timeout_id_{chromeos::MessageLoop::kTaskIdNull};
+
+ bool transfer_in_progress_{false};
+
+ // The transfer size. -1 if not known.
+ off_t transfer_size_{0};
+
+ // How many bytes have been downloaded and sent to the delegate.
+ off_t bytes_downloaded_{0};
+
+ // The remaining maximum number of bytes to download. Zero represents an
+ // unspecified length.
+ size_t download_length_{0};
+
+ // If we resumed an earlier transfer, data offset that we used for the
+ // new connection. 0 otherwise.
+ // In this class, resume refers to resuming a dropped HTTP connection,
+ // not to resuming an interrupted download.
+ off_t resume_offset_{0};
+
+ // Number of resumes performed so far and the max allowed.
+ int retry_count_{0};
+ int max_retry_count_{kDownloadMaxRetryCount};
+
+ // Seconds to wait before retrying a resume.
+ int retry_seconds_{20};
+
+ // Number of resumes due to no network (e.g., HTTP response code 0).
+ int no_network_retry_count_{0};
+ int no_network_max_retries_{0};
+
+ // Seconds to wait before asking libcurl to "perform".
+ int idle_seconds_{1};
+
+ // If true, we are currently performing a write callback on the delegate.
+ bool in_write_callback_{false};
+
+ // If true, we have returned at least one byte in the write callback
+ // to the delegate.
+ bool sent_byte_{false};
+
+ // We can't clean everything up while we're in a write callback, so
+ // if we get a terminate request, queue it until we can handle it.
+ bool terminate_requested_{false};
+
+ // Represents which server certificate to be checked against this
+ // connection's certificate. If no certificate check needs to be performed,
+ // this should be kNone.
+ CertificateChecker::ServerToCheck check_certificate_{
+ CertificateChecker::kNone};
+
+ int low_speed_limit_bps_{kDownloadLowSpeedLimitBps};
+ int low_speed_time_seconds_{kDownloadLowSpeedTimeSeconds};
+ int connect_timeout_seconds_{kDownloadConnectTimeoutSeconds};
+ int num_max_retries_;
+
+ DISALLOW_COPY_AND_ASSIGN(LibcurlHttpFetcher);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
diff --git a/local_coverage_rate b/local_coverage_rate
new file mode 100755
index 0000000..8a44f73
--- /dev/null
+++ b/local_coverage_rate
@@ -0,0 +1,89 @@
+#!/bin/bash
+
+# Copyright (c) 2009 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.
+
+# Calculates the test-coverage percentage for non-test files in the
+# update_engine directory. Requires a file 'app.info' to contain the
+# results of running the unittests while collecting coverage data.
+
+cat app.info | awk -F '[,:]' '
+
+BEGIN { OFS = ":"; }
+
+/^SF:/{ FILEN = $2; }
+
+/^end_of_record$/{ FILEN = ""; }
+
+/^DA:/{ print FILEN, $2, $3; }
+
+' | sort | awk -F : '
+BEGIN {
+ OFS = ":";
+ FILEN = "";
+ LINE = "";
+ HITS = 0;
+}
+{
+ NEWFILEN = $1;
+ NEWLINE = $2;
+ if ((NEWFILEN == FILEN) && (NEWLINE == LINE)) {
+ HITS += $3
+ } else {
+ if (FILEN != "") {
+ print FILEN, LINE, HITS;
+ }
+ FILEN = NEWFILEN;
+ LINE = NEWLINE;
+ HITS = $3;
+ }
+}
+' | grep '^.*\/trunk\/src\/platform\/update_engine\/' | \
+fgrep -v '_unittest.cc:' | \
+fgrep -v '/test_utils.' | \
+fgrep -v '/test_http_server.cc' | \
+fgrep -v '/testrunner.cc' | \
+fgrep -v '/mock' | \
+fgrep -v '.pb.cc' | \
+awk -F : '
+
+function printfile() {
+ if (FNAME != "")
+ printf "%-40s %4d / %4d: %5.1f%%\n", FNAME, FILE_GOOD_LINES,
+ (FILE_BAD_LINES + FILE_GOOD_LINES),
+ (FILE_GOOD_LINES * 100) / (FILE_BAD_LINES + FILE_GOOD_LINES);
+}
+
+BEGIN {
+ FNAME = "";
+ FILE_BAD_LINES = 0;
+ FILE_GOOD_LINES = 0;
+}
+{
+ // calc filename
+ ARR_SIZE = split($1, PARTS, "/");
+ NEWFNAME = PARTS[ARR_SIZE];
+ if (NEWFNAME != FNAME) {
+ printfile();
+ FILE_BAD_LINES = 0;
+ FILE_GOOD_LINES = 0;
+ FNAME = NEWFNAME;
+ }
+ if ($3 == "0") {
+ BAD_LINES += 1;
+ FILE_BAD_LINES += 1;
+ } else {
+ GOOD_LINES += 1;
+ FILE_GOOD_LINES += 1;
+ }
+}
+
+END {
+ printfile();
+ print "---\nSummary: tested " GOOD_LINES " / " (BAD_LINES + GOOD_LINES);
+ printf(
+ "Test coverage: %.1f%%\n",
+ ((GOOD_LINES * 100) / (BAD_LINES + GOOD_LINES)));
+}
+'
diff --git a/main.cc b/main.cc
new file mode 100644
index 0000000..51a04ec
--- /dev/null
+++ b/main.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/files/file_util.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <chromeos/flag_helper.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <glib.h>
+#include <metrics/metrics_library.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/dbus_constants.h"
+#include "update_engine/dbus_service.h"
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/glib_utils.h"
+#include "update_engine/real_system_state.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
+#include "update_engine/update_attempter.h"
+extern "C" {
+#include "update_engine/org.chromium.UpdateEngineInterface.dbusserver.h"
+}
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+const int kDBusSystemMaxWaitSeconds = 2 * 60;
+} // namespace
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Wait for DBus to be ready by attempting to get the system bus up to
+// |timeout| time. Returns whether it succeeded to get the bus.
+bool WaitForDBusSystem(base::TimeDelta timeout) {
+ GError *error = nullptr;
+ DBusGConnection *bus = nullptr;
+ Clock clock;
+ base::Time deadline = clock.GetMonotonicTime() + timeout;
+
+ while (clock.GetMonotonicTime() < deadline) {
+ bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
+ if (bus)
+ return true;
+ LOG(WARNING) << "Failed to get system bus, waiting: "
+ << utils::GetAndFreeGError(&error);
+ // Wait 1 second.
+ sleep(1);
+ }
+ LOG(ERROR) << "Failed to get system bus after " << timeout.InSeconds()
+ << " seconds.";
+ return false;
+}
+
+void SetupDBusService(UpdateEngineService* service) {
+ DBusGConnection *bus;
+ DBusGProxy *proxy;
+ GError *error = nullptr;
+
+ bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
+ LOG_IF(FATAL, !bus) << "Failed to get bus: "
+ << utils::GetAndFreeGError(&error);
+ proxy = dbus_g_proxy_new_for_name(bus,
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS);
+ guint32 request_name_ret;
+ if (!org_freedesktop_DBus_request_name(proxy,
+ kUpdateEngineServiceName,
+ 0,
+ &request_name_ret,
+ &error)) {
+ LOG(FATAL) << "Failed to get name: " << utils::GetAndFreeGError(&error);
+ }
+ if (request_name_ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+ g_warning("Got result code %u from requesting name", request_name_ret);
+ LOG(FATAL) << "Got result code " << request_name_ret
+ << " from requesting name, but expected "
+ << DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER;
+ }
+ dbus_g_connection_register_g_object(bus,
+ "/org/chromium/UpdateEngine",
+ G_OBJECT(service));
+}
+
+void SetupLogSymlink(const string& symlink_path, const string& log_path) {
+ // TODO(petkov): To ensure a smooth transition between non-timestamped and
+ // timestamped logs, move an existing log to start the first timestamped
+ // one. This code can go away once all clients are switched to this version or
+ // we stop caring about the old-style logs.
+ if (utils::FileExists(symlink_path.c_str()) &&
+ !utils::IsSymlink(symlink_path.c_str())) {
+ base::ReplaceFile(base::FilePath(symlink_path),
+ base::FilePath(log_path),
+ nullptr);
+ }
+ base::DeleteFile(base::FilePath(symlink_path), true);
+ if (symlink(log_path.c_str(), symlink_path.c_str()) == -1) {
+ PLOG(ERROR) << "Unable to create symlink " << symlink_path
+ << " pointing at " << log_path;
+ }
+}
+
+string GetTimeAsString(time_t utime) {
+ struct tm tm;
+ CHECK_EQ(localtime_r(&utime, &tm), &tm);
+ char str[16];
+ CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15u);
+ return str;
+}
+
+string SetupLogFile(const string& kLogsRoot) {
+ const string kLogSymlink = kLogsRoot + "/update_engine.log";
+ const string kLogsDir = kLogsRoot + "/update_engine";
+ const string kLogPath =
+ base::StringPrintf("%s/update_engine.%s",
+ kLogsDir.c_str(),
+ GetTimeAsString(::time(nullptr)).c_str());
+ mkdir(kLogsDir.c_str(), 0755);
+ SetupLogSymlink(kLogSymlink, kLogPath);
+ return kLogSymlink;
+}
+
+void SetupLogging(bool log_to_std_err) {
+ string log_file;
+ logging::LoggingSettings log_settings;
+ log_settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+ log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+
+ if (log_to_std_err) {
+ // Log to stderr initially.
+ log_settings.log_file = nullptr;
+ log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ } else {
+ log_file = SetupLogFile("/var/log");
+ log_settings.log_file = log_file.c_str();
+ log_settings.logging_dest = logging::LOG_TO_FILE;
+ }
+
+ logging::InitLogging(log_settings);
+}
+
+} // namespace
+} // namespace chromeos_update_engine
+
+int main(int argc, char** argv) {
+ DEFINE_bool(logtostderr, false,
+ "Write logs to stderr instead of to a file in log_dir.");
+ DEFINE_bool(foreground, false,
+ "Don't daemon()ize; run in foreground.");
+
+ ::g_type_init();
+ dbus_threads_init_default();
+ base::AtExitManager exit_manager; // Required for base/rand_util.h.
+ chromeos_update_engine::Terminator::Init();
+ chromeos_update_engine::Subprocess::Init();
+ chromeos::FlagHelper::Init(argc, argv, "Chromium OS Update Engine");
+ chromeos_update_engine::SetupLogging(FLAGS_logtostderr);
+ if (!FLAGS_foreground)
+ PLOG_IF(FATAL, daemon(0, 0) == 1) << "daemon() failed";
+
+ LOG(INFO) << "Chrome OS Update Engine starting";
+
+ // Ensure that all written files have safe permissions.
+ // This is a mask, so we _block_ execute for the owner, and ALL
+ // permissions for other users.
+ // Done _after_ log file creation.
+ umask(S_IXUSR | S_IRWXG | S_IRWXO);
+
+ // Create the single Glib main loop. Code accessing directly the glib main
+ // loop (such as calling g_timeout_add() or similar functions) will still work
+ // since the backend for the message loop is still the Glib main loop.
+ // TODO(deymo): Replace this |loop| with one based on libevent once no other
+ // code here uses glib directly.
+ chromeos::GlibMessageLoop loop;
+ loop.SetAsCurrent();
+
+ // Wait up to 2 minutes for DBus to be ready.
+ LOG_IF(FATAL, !chromeos_update_engine::WaitForDBusSystem(
+ base::TimeDelta::FromSeconds(kDBusSystemMaxWaitSeconds)))
+ << "Failed to initialize DBus, aborting.";
+
+ chromeos_update_engine::RealSystemState real_system_state;
+ LOG_IF(ERROR, !real_system_state.Initialize())
+ << "Failed to initialize system state.";
+ chromeos_update_engine::UpdateAttempter *update_attempter =
+ real_system_state.update_attempter();
+ CHECK(update_attempter);
+
+ // Sets static members for the certificate checker.
+ chromeos_update_engine::CertificateChecker::set_system_state(
+ &real_system_state);
+ chromeos_update_engine::OpenSSLWrapper openssl_wrapper;
+ chromeos_update_engine::CertificateChecker::set_openssl_wrapper(
+ &openssl_wrapper);
+
+ // Create the dbus service object:
+ dbus_g_object_type_install_info(UPDATE_ENGINE_TYPE_SERVICE,
+ &dbus_glib_update_engine_service_object_info);
+ UpdateEngineService* service = update_engine_service_new();
+ service->system_state_ = &real_system_state;
+ update_attempter->set_dbus_service(service);
+ chromeos_update_engine::SetupDBusService(service);
+
+ // Initiate update checks.
+ update_attempter->ScheduleUpdates();
+
+ // Update boot flags after 45 seconds.
+ loop.PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&chromeos_update_engine::UpdateAttempter::UpdateBootFlags,
+ base::Unretained(update_attempter)),
+ base::TimeDelta::FromSeconds(45));
+
+ // Broadcast the update engine status on startup to ensure consistent system
+ // state on crashes.
+ loop.PostTask(FROM_HERE, base::Bind(
+ &chromeos_update_engine::UpdateAttempter::BroadcastStatus,
+ base::Unretained(update_attempter)));
+
+ // Run the UpdateEngineStarted() method on |update_attempter|.
+ loop.PostTask(FROM_HERE, base::Bind(
+ &chromeos_update_engine::UpdateAttempter::UpdateEngineStarted,
+ base::Unretained(update_attempter)));
+
+ // Run the main loop until exit time:
+ loop.Run();
+
+ // Cleanup:
+ update_attempter->set_dbus_service(nullptr);
+ g_object_unref(G_OBJECT(service));
+
+ loop.ReleaseFromCurrent();
+ LOG(INFO) << "Chrome OS Update Engine terminating";
+ return 0;
+}
diff --git a/metrics.cc b/metrics.cc
new file mode 100644
index 0000000..deb445b
--- /dev/null
+++ b/metrics.cc
@@ -0,0 +1,483 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/metrics.h"
+
+#include <string>
+
+#include <base/logging.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/constants.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/system_state.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace metrics {
+
+// UpdateEngine.Daily.* metrics.
+const char kMetricDailyOSAgeDays[] = "UpdateEngine.Daily.OSAgeDays";
+
+// UpdateEngine.Check.* metrics.
+const char kMetricCheckDownloadErrorCode[] =
+ "UpdateEngine.Check.DownloadErrorCode";
+const char kMetricCheckReaction[] = "UpdateEngine.Check.Reaction";
+const char kMetricCheckResult[] = "UpdateEngine.Check.Result";
+const char kMetricCheckTimeSinceLastCheckMinutes[] =
+ "UpdateEngine.Check.TimeSinceLastCheckMinutes";
+const char kMetricCheckTimeSinceLastCheckUptimeMinutes[] =
+ "UpdateEngine.Check.TimeSinceLastCheckUptimeMinutes";
+
+// UpdateEngine.Attempt.* metrics.
+const char kMetricAttemptNumber[] = "UpdateEngine.Attempt.Number";
+const char kMetricAttemptPayloadType[] =
+ "UpdateEngine.Attempt.PayloadType";
+const char kMetricAttemptPayloadSizeMiB[] =
+ "UpdateEngine.Attempt.PayloadSizeMiB";
+const char kMetricAttemptConnectionType[] =
+ "UpdateEngine.Attempt.ConnectionType";
+const char kMetricAttemptDurationMinutes[] =
+ "UpdateEngine.Attempt.DurationMinutes";
+const char kMetricAttemptDurationUptimeMinutes[] =
+ "UpdateEngine.Attempt.DurationUptimeMinutes";
+const char kMetricAttemptTimeSinceLastAttemptMinutes[] =
+ "UpdateEngine.Attempt.TimeSinceLastAttemptMinutes";
+const char kMetricAttemptTimeSinceLastAttemptUptimeMinutes[] =
+ "UpdateEngine.Attempt.TimeSinceLastAttemptUptimeMinutes";
+const char kMetricAttemptPayloadBytesDownloadedMiB[] =
+ "UpdateEngine.Attempt.PayloadBytesDownloadedMiB";
+const char kMetricAttemptPayloadDownloadSpeedKBps[] =
+ "UpdateEngine.Attempt.PayloadDownloadSpeedKBps";
+const char kMetricAttemptDownloadSource[] =
+ "UpdateEngine.Attempt.DownloadSource";
+const char kMetricAttemptResult[] =
+ "UpdateEngine.Attempt.Result";
+const char kMetricAttemptInternalErrorCode[] =
+ "UpdateEngine.Attempt.InternalErrorCode";
+const char kMetricAttemptDownloadErrorCode[] =
+ "UpdateEngine.Attempt.DownloadErrorCode";
+
+// UpdateEngine.SuccessfulUpdate.* metrics.
+const char kMetricSuccessfulUpdateAttemptCount[] =
+ "UpdateEngine.SuccessfulUpdate.AttemptCount";
+const char kMetricSuccessfulUpdateBytesDownloadedMiB[] =
+ "UpdateEngine.SuccessfulUpdate.BytesDownloadedMiB";
+const char kMetricSuccessfulUpdateDownloadOverheadPercentage[] =
+ "UpdateEngine.SuccessfulUpdate.DownloadOverheadPercentage";
+const char kMetricSuccessfulUpdateDownloadSourcesUsed[] =
+ "UpdateEngine.SuccessfulUpdate.DownloadSourcesUsed";
+const char kMetricSuccessfulUpdatePayloadType[] =
+ "UpdateEngine.SuccessfulUpdate.PayloadType";
+const char kMetricSuccessfulUpdatePayloadSizeMiB[] =
+ "UpdateEngine.SuccessfulUpdate.PayloadSizeMiB";
+const char kMetricSuccessfulUpdateRebootCount[] =
+ "UpdateEngine.SuccessfulUpdate.RebootCount";
+const char kMetricSuccessfulUpdateTotalDurationMinutes[] =
+ "UpdateEngine.SuccessfulUpdate.TotalDurationMinutes";
+const char kMetricSuccessfulUpdateUpdatesAbandonedCount[] =
+ "UpdateEngine.SuccessfulUpdate.UpdatesAbandonedCount";
+const char kMetricSuccessfulUpdateUrlSwitchCount[] =
+ "UpdateEngine.SuccessfulUpdate.UrlSwitchCount";
+
+// UpdateEngine.Rollback.* metric.
+const char kMetricRollbackResult[] = "UpdateEngine.Rollback.Result";
+
+// UpdateEngine.* metrics.
+const char kMetricFailedUpdateCount[] = "UpdateEngine.FailedUpdateCount";
+const char kMetricInstallDateProvisioningSource[] =
+ "UpdateEngine.InstallDateProvisioningSource";
+const char kMetricTimeToRebootMinutes[] =
+ "UpdateEngine.TimeToRebootMinutes";
+
+void ReportDailyMetrics(SystemState *system_state,
+ base::TimeDelta os_age) {
+ string metric = metrics::kMetricDailyOSAgeDays;
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(os_age)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ static_cast<int>(os_age.InDays()),
+ 0, // min: 0 days
+ 6*30, // max: 6 months (approx)
+ 50); // num_buckets
+}
+
+void ReportUpdateCheckMetrics(SystemState *system_state,
+ CheckResult result,
+ CheckReaction reaction,
+ DownloadErrorCode download_error_code) {
+ string metric;
+ int value;
+ int max_value;
+
+ if (result != metrics::CheckResult::kUnset) {
+ metric = metrics::kMetricCheckResult;
+ value = static_cast<int>(result);
+ max_value = static_cast<int>(metrics::CheckResult::kNumConstants) - 1;
+ LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+ system_state->metrics_lib()->SendEnumToUMA(metric, value, max_value);
+ }
+ if (reaction != metrics::CheckReaction::kUnset) {
+ metric = metrics::kMetricCheckReaction;
+ value = static_cast<int>(reaction);
+ max_value = static_cast<int>(metrics::CheckReaction::kNumConstants) - 1;
+ LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+ system_state->metrics_lib()->SendEnumToUMA(metric, value, max_value);
+ }
+ if (download_error_code != metrics::DownloadErrorCode::kUnset) {
+ metric = metrics::kMetricCheckDownloadErrorCode;
+ value = static_cast<int>(download_error_code);
+ LOG(INFO) << "Sending " << value << " for metric " << metric << " (sparse)";
+ system_state->metrics_lib()->SendSparseToUMA(metric, value);
+ }
+
+ base::TimeDelta time_since_last;
+ if (utils::WallclockDurationHelper(system_state,
+ kPrefsMetricsCheckLastReportingTime,
+ &time_since_last)) {
+ metric = kMetricCheckTimeSinceLastCheckMinutes;
+ LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ time_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30*24*60, // max: 30 days
+ 50); // num_buckets
+ }
+
+ base::TimeDelta uptime_since_last;
+ static int64_t uptime_since_last_storage = 0;
+ if (utils::MonotonicDurationHelper(system_state,
+ &uptime_since_last_storage,
+ &uptime_since_last)) {
+ metric = kMetricCheckTimeSinceLastCheckUptimeMinutes;
+ LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ uptime_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30*24*60, // max: 30 days
+ 50); // num_buckets
+ }
+}
+
+void ReportAbnormallyTerminatedUpdateAttemptMetrics(
+ SystemState *system_state) {
+
+ string metric = metrics::kMetricAttemptResult;
+ AttemptResult attempt_result = AttemptResult::kAbnormalTermination;
+
+ LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric,
+ static_cast<int>(attempt_result),
+ static_cast<int>(AttemptResult::kNumConstants));
+}
+
+void ReportUpdateAttemptMetrics(
+ SystemState *system_state,
+ int attempt_number,
+ PayloadType payload_type,
+ base::TimeDelta duration,
+ base::TimeDelta duration_uptime,
+ int64_t payload_size,
+ int64_t payload_bytes_downloaded,
+ int64_t payload_download_speed_bps,
+ DownloadSource download_source,
+ AttemptResult attempt_result,
+ ErrorCode internal_error_code,
+ DownloadErrorCode payload_download_error_code,
+ ConnectionType connection_type) {
+ string metric;
+
+ metric = metrics::kMetricAttemptNumber;
+ LOG(INFO) << "Uploading " << attempt_number << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ attempt_number,
+ 0, // min: 0 attempts
+ 49, // max: 49 attempts
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptPayloadType;
+ LOG(INFO) << "Uploading " << utils::ToString(payload_type)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(metric,
+ payload_type,
+ kNumPayloadTypes);
+
+ metric = metrics::kMetricAttemptDurationMinutes;
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ duration.InMinutes(),
+ 0, // min: 0 min
+ 10*24*60, // max: 10 days
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptDurationUptimeMinutes;
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration_uptime)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ duration_uptime.InMinutes(),
+ 0, // min: 0 min
+ 10*24*60, // max: 10 days
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptPayloadSizeMiB;
+ int64_t payload_size_mib = payload_size / kNumBytesInOneMiB;
+ LOG(INFO) << "Uploading " << payload_size_mib << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ payload_size_mib,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptPayloadBytesDownloadedMiB;
+ int64_t payload_bytes_downloaded_mib =
+ payload_bytes_downloaded / kNumBytesInOneMiB;
+ LOG(INFO) << "Uploading " << payload_bytes_downloaded_mib
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ payload_bytes_downloaded_mib,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptPayloadDownloadSpeedKBps;
+ int64_t payload_download_speed_kbps = payload_download_speed_bps / 1000;
+ LOG(INFO) << "Uploading " << payload_download_speed_kbps
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ payload_download_speed_kbps,
+ 0, // min: 0 kB/s
+ 10*1000, // max: 10000 kB/s = 10 MB/s
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptDownloadSource;
+ LOG(INFO) << "Uploading " << download_source
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(metric,
+ download_source,
+ kNumDownloadSources);
+
+ metric = metrics::kMetricAttemptResult;
+ LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric,
+ static_cast<int>(attempt_result),
+ static_cast<int>(AttemptResult::kNumConstants));
+
+ if (internal_error_code != ErrorCode::kSuccess) {
+ metric = metrics::kMetricAttemptInternalErrorCode;
+ LOG(INFO) << "Uploading " << internal_error_code
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric,
+ static_cast<int>(internal_error_code),
+ static_cast<int>(ErrorCode::kUmaReportedMax));
+ }
+
+ if (payload_download_error_code != DownloadErrorCode::kUnset) {
+ metric = metrics::kMetricAttemptDownloadErrorCode;
+ LOG(INFO) << "Uploading " << static_cast<int>(payload_download_error_code)
+ << " for metric " << metric << " (sparse)";
+ system_state->metrics_lib()->SendSparseToUMA(
+ metric,
+ static_cast<int>(payload_download_error_code));
+ }
+
+ base::TimeDelta time_since_last;
+ if (utils::WallclockDurationHelper(system_state,
+ kPrefsMetricsAttemptLastReportingTime,
+ &time_since_last)) {
+ metric = kMetricAttemptTimeSinceLastAttemptMinutes;
+ LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ time_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30*24*60, // max: 30 days
+ 50); // num_buckets
+ }
+
+ static int64_t uptime_since_last_storage = 0;
+ base::TimeDelta uptime_since_last;
+ if (utils::MonotonicDurationHelper(system_state,
+ &uptime_since_last_storage,
+ &uptime_since_last)) {
+ metric = kMetricAttemptTimeSinceLastAttemptUptimeMinutes;
+ LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ uptime_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30*24*60, // max: 30 days
+ 50); // num_buckets
+ }
+
+ metric = metrics::kMetricAttemptConnectionType;
+ LOG(INFO) << "Uploading " << static_cast<int>(connection_type)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric,
+ static_cast<int>(connection_type),
+ static_cast<int>(ConnectionType::kNumConstants));
+}
+
+
+void ReportSuccessfulUpdateMetrics(
+ SystemState *system_state,
+ int attempt_count,
+ int updates_abandoned_count,
+ PayloadType payload_type,
+ int64_t payload_size,
+ int64_t num_bytes_downloaded[kNumDownloadSources],
+ int download_overhead_percentage,
+ base::TimeDelta total_duration,
+ int reboot_count,
+ int url_switch_count) {
+ string metric;
+ int64_t mbs;
+
+ metric = kMetricSuccessfulUpdatePayloadSizeMiB;
+ mbs = payload_size / kNumBytesInOneMiB;
+ LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ mbs,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+
+ int64_t total_bytes = 0;
+ int download_sources_used = 0;
+ for (int i = 0; i < kNumDownloadSources + 1; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+
+ // Only consider this download source (and send byte counts) as
+ // having been used if we downloaded a non-trivial amount of bytes
+ // (e.g. at least 1 MiB) that contributed to the
+ // update. Otherwise we're going to end up with a lot of zero-byte
+ // events in the histogram.
+
+ metric = metrics::kMetricSuccessfulUpdateBytesDownloadedMiB;
+ if (i < kNumDownloadSources) {
+ metric += utils::ToString(source);
+ mbs = num_bytes_downloaded[i] / kNumBytesInOneMiB;
+ total_bytes += num_bytes_downloaded[i];
+ if (mbs > 0)
+ download_sources_used |= (1 << i);
+ } else {
+ mbs = total_bytes / kNumBytesInOneMiB;
+ }
+
+ if (mbs > 0) {
+ LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ mbs,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+ }
+ }
+
+ metric = metrics::kMetricSuccessfulUpdateDownloadSourcesUsed;
+ LOG(INFO) << "Uploading 0x" << std::hex << download_sources_used
+ << " (bit flags) for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ download_sources_used,
+ 0, // min
+ (1 << kNumDownloadSources) - 1, // max
+ 1 << kNumDownloadSources); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage;
+ LOG(INFO) << "Uploading " << download_overhead_percentage
+ << "% for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ download_overhead_percentage,
+ 0, // min: 0% overhead
+ 1000, // max: 1000% overhead
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateUrlSwitchCount;
+ LOG(INFO) << "Uploading " << url_switch_count
+ << " (count) for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ url_switch_count,
+ 0, // min: 0 URL switches
+ 49, // max: 49 URL switches
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateTotalDurationMinutes;
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(total_duration)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ static_cast<int>(total_duration.InMinutes()),
+ 0, // min: 0 min
+ 365*24*60, // max: 365 days ~= 1 year
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateRebootCount;
+ LOG(INFO) << "Uploading reboot count of " << reboot_count << " for metric "
+ << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ reboot_count,
+ 0, // min: 0 reboots
+ 49, // max: 49 reboots
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdatePayloadType;
+ system_state->metrics_lib()->SendEnumToUMA(metric,
+ payload_type,
+ kNumPayloadTypes);
+ LOG(INFO) << "Uploading " << utils::ToString(payload_type)
+ << " for metric " << metric;
+
+ metric = metrics::kMetricSuccessfulUpdateAttemptCount;
+ system_state->metrics_lib()->SendToUMA(metric,
+ attempt_count,
+ 1, // min: 1 attempt
+ 50, // max: 50 attempts
+ 50); // num_buckets
+ LOG(INFO) << "Uploading " << attempt_count
+ << " for metric " << metric;
+
+ metric = metrics::kMetricSuccessfulUpdateUpdatesAbandonedCount;
+ LOG(INFO) << "Uploading " << updates_abandoned_count
+ << " (count) for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ updates_abandoned_count,
+ 0, // min: 0 counts
+ 49, // max: 49 counts
+ 50); // num_buckets
+}
+
+void ReportRollbackMetrics(SystemState *system_state,
+ RollbackResult result) {
+ string metric;
+ int value;
+
+ metric = metrics::kMetricRollbackResult;
+ value = static_cast<int>(result);
+ LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric,
+ value,
+ static_cast<int>(metrics::RollbackResult::kNumConstants));
+}
+
+} // namespace metrics
+
+} // namespace chromeos_update_engine
diff --git a/metrics.h b/metrics.h
new file mode 100644
index 0000000..7dfd918
--- /dev/null
+++ b/metrics.h
@@ -0,0 +1,298 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_METRICS_H_
+#define UPDATE_ENGINE_METRICS_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/error_code.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+namespace metrics {
+
+// UpdateEngine.Daily.* metrics.
+extern const char kMetricDailyOSAgeDays[];
+
+// UpdateEngine.Check.* metrics.
+extern const char kMetricCheckDownloadErrorCode[];
+extern const char kMetricCheckReaction[];
+extern const char kMetricCheckResult[];
+extern const char kMetricCheckTimeSinceLastCheckMinutes[];
+extern const char kMetricCheckTimeSinceLastCheckUptimeMinutes[];
+
+// UpdateEngine.Attempt.* metrics.
+extern const char kMetricAttemptNumber[];
+extern const char kMetricAttemptPayloadType[];
+extern const char kMetricAttemptPayloadSizeMiB[];
+extern const char kMetricAttemptConnectionType[];
+extern const char kMetricAttemptDurationMinutes[];
+extern const char kMetricAttemptDurationUptimeMinutes[];
+extern const char kMetricAttemptTimeSinceLastAttemptSeconds[];
+extern const char kMetricAttemptTimeSinceLastAttemptUptimeSeconds[];
+extern const char kMetricAttemptPayloadBytesDownloaded[];
+extern const char kMetricAttemptPayloadDownloadSpeedKBps[];
+extern const char kMetricAttemptDownloadSource[];
+extern const char kMetricAttemptResult[];
+extern const char kMetricAttemptInternalErrorCode[];
+extern const char kMetricAttemptDownloadErrorCode[];
+
+// UpdateEngine.SuccessfulUpdate.* metrics.
+extern const char kMetricSuccessfulUpdateAttemptCount[];
+extern const char kMetricSuccessfulUpdateBytesDownloadedMiB[];
+extern const char kMetricSuccessfulUpdateDownloadOverheadPercentage[];
+extern const char kMetricSuccessfulUpdateDownloadSourcesUsed[];
+extern const char kMetricSuccessfulUpdatePayloadType[];
+extern const char kMetricSuccessfulUpdatePayloadSizeMiB[];
+extern const char kMetricSuccessfulUpdateTotalDurationMinutes[];
+extern const char kMetricSuccessfulUpdateRebootCount[];
+extern const char kMetricSuccessfulUpdateUpdatesAbandonedCount[];
+extern const char kMetricSuccessfulUpdateUrlSwitchCount[];
+
+// UpdateEngine.Rollback.* metric.
+extern const char kMetricRollbackResult[];
+
+// UpdateEngine.* metrics.
+extern const char kMetricFailedUpdateCount[];
+extern const char kMetricInstallDateProvisioningSource[];
+extern const char kMetricTimeToRebootMinutes[];
+
+// The possible outcomes when checking for updates.
+//
+// This is used in the UpdateEngine.Check.Result histogram.
+enum class CheckResult {
+ kUpdateAvailable, // Response indicates an update is available.
+ kNoUpdateAvailable, // Response indicates no updates are available.
+ kDownloadError, // Error downloading response from Omaha.
+ kParsingError, // Error parsing response.
+ kRebootPending, // No update check was performed a reboot is pending.
+
+ kNumConstants,
+ kUnset = -1
+};
+
+// Possible ways a device can react to a new update being available.
+//
+// This is used in the UpdateEngine.Check.Reaction histogram.
+enum class CheckReaction {
+ kUpdating, // Device proceeds to download and apply update.
+ kIgnored , // Device-policy dictates ignoring the update.
+ kDeferring, // Device-policy dictates waiting.
+ kBackingOff, // Previous errors dictates waiting.
+
+ kNumConstants,
+ kUnset = -1
+};
+
+// The possible ways that downloading from a HTTP or HTTPS server can fail.
+//
+// This is used in the UpdateEngine.Check.DownloadErrorCode and
+// UpdateEngine.Attempt.DownloadErrorCode histograms.
+enum class DownloadErrorCode {
+ // Errors that can happen in the field. See http://crbug.com/355745
+ // for how we plan to add more detail in the future.
+ kDownloadError = 0, // Error downloading data from server.
+
+ // IMPORTANT: When adding a new error code, add at the bottom of the
+ // above block and before the kInputMalformed field. This
+ // is to ensure that error codes are not reordered.
+
+ // This error code is used to convey that malformed input was given
+ // to the utils::GetDownloadErrorCode() function. This should never
+ // happen but if it does it's because of an internal update_engine
+ // error and we're interested in knowing this.
+ kInputMalformed = 100,
+
+ // Bucket for capturing HTTP status codes not in the 200-599
+ // range. This should never happen in practice but if it does we
+ // want to know.
+ kHttpStatusOther = 101,
+
+ // Above 200 and below 600, the value is the HTTP status code.
+ kHttpStatus200 = 200,
+
+ kNumConstants = 600,
+
+ kUnset = -1
+};
+
+// Possible ways an update attempt can end.
+//
+// This is used in the UpdateEngine.Attempt.Result histogram.
+enum class AttemptResult {
+ kUpdateSucceeded, // The update succeeded.
+ kInternalError, // An internal error occurred.
+ kPayloadDownloadError, // Failure while downloading payload.
+ kMetadataMalformed, // Metadata was malformed.
+ kOperationMalformed, // An operation was malformed.
+ kOperationExecutionError, // An operation failed to execute.
+ kMetadataVerificationFailed, // Metadata verification failed.
+ kPayloadVerificationFailed, // Payload verification failed.
+ kVerificationFailed, // Root or Kernel partition verification failed.
+ kPostInstallFailed, // The postinstall step failed.
+ kAbnormalTermination, // The attempt ended abnormally.
+
+ kNumConstants,
+
+ kUnset = -1
+};
+
+// Possible ways the device is connected to the Internet.
+//
+// This is used in the UpdateEngine.Attempt.ConnectionType histogram.
+enum class ConnectionType {
+ kUnknown, // Unknown.
+ kEthernet, // Ethernet.
+ kWifi, // Wireless.
+ kWimax, // WiMax.
+ kBluetooth, // Bluetooth.
+ kCellular, // Cellular.
+ kTetheredEthernet, // Tethered (Ethernet).
+ kTetheredWifi, // Tethered (Wifi).
+
+ kNumConstants,
+ kUnset = -1
+};
+
+// Possible ways a rollback can end.
+//
+// This is used in the UpdateEngine.Rollback histogram.
+enum class RollbackResult {
+ kFailed,
+ kSuccess,
+
+ kNumConstants
+};
+
+// Helper function to report metrics related to rollback. The
+// following metrics are reported:
+//
+// |kMetricRollbackResult|
+void ReportRollbackMetrics(SystemState *system_state,
+ RollbackResult result);
+
+// Helper function to report metrics reported once a day. The
+// following metrics are reported:
+//
+// |kMetricDailyOSAgeDays|
+void ReportDailyMetrics(SystemState *system_state,
+ base::TimeDelta os_age);
+
+// Helper function to report metrics after completing an update check
+// with the ChromeOS update server ("Omaha"). The following metrics
+// are reported:
+//
+// |kMetricCheckResult|
+// |kMetricCheckReaction|
+// |kMetricCheckDownloadErrorCode|
+// |kMetricCheckTimeSinceLastCheckMinutes|
+// |kMetricCheckTimeSinceLastCheckUptimeMinutes|
+//
+// The |kMetricCheckResult| metric will only be reported if |result|
+// is not |kUnset|.
+//
+// The |kMetricCheckReaction| metric will only be reported if
+// |reaction| is not |kUnset|.
+//
+// The |kMetricCheckDownloadErrorCode| will only be reported if
+// |download_error_code| is not |kUnset|.
+//
+// The values for the |kMetricCheckTimeSinceLastCheckMinutes| and
+// |kMetricCheckTimeSinceLastCheckUptimeMinutes| metrics are
+// automatically reported and calculated by maintaining persistent
+// and process-local state variables.
+void ReportUpdateCheckMetrics(SystemState *system_state,
+ CheckResult result,
+ CheckReaction reaction,
+ DownloadErrorCode download_error_code);
+
+
+// Helper function to report metrics after the completion of each
+// update attempt. The following metrics are reported:
+//
+// |kMetricAttemptNumber|
+// |kMetricAttemptPayloadType|
+// |kMetricAttemptPayloadSizeMiB|
+// |kMetricAttemptDurationSeconds|
+// |kMetricAttemptDurationUptimeSeconds|
+// |kMetricAttemptTimeSinceLastAttemptMinutes|
+// |kMetricAttemptTimeSinceLastAttemptUptimeMinutes|
+// |kMetricAttemptPayloadBytesDownloadedMiB|
+// |kMetricAttemptPayloadDownloadSpeedKBps|
+// |kMetricAttemptDownloadSource|
+// |kMetricAttemptResult|
+// |kMetricAttemptInternalErrorCode|
+// |kMetricAttemptDownloadErrorCode|
+//
+// The |kMetricAttemptInternalErrorCode| metric will only be reported
+// if |internal_error_code| is not |kErrorSuccess|.
+//
+// The |kMetricAttemptDownloadErrorCode| metric will only be
+// reported if |payload_download_error_code| is not |kUnset|.
+//
+// The values for the |kMetricAttemptTimeSinceLastAttemptMinutes| and
+// |kMetricAttemptTimeSinceLastAttemptUptimeMinutes| metrics are
+// automatically calculated and reported by maintaining persistent and
+// process-local state variables.
+void ReportUpdateAttemptMetrics(
+ SystemState *system_state,
+ int attempt_number,
+ PayloadType payload_type,
+ base::TimeDelta duration,
+ base::TimeDelta duration_uptime,
+ int64_t payload_size,
+ int64_t payload_bytes_downloaded,
+ int64_t payload_download_speed_bps,
+ DownloadSource download_source,
+ AttemptResult attempt_result,
+ ErrorCode internal_error_code,
+ DownloadErrorCode payload_download_error_code,
+ ConnectionType connection_type);
+
+// Reports the |kAbnormalTermination| for the |kMetricAttemptResult|
+// metric. No other metrics in the UpdateEngine.Attempt.* namespace
+// will be reported.
+void ReportAbnormallyTerminatedUpdateAttemptMetrics(SystemState *system_state);
+
+// Helper function to report the after the completion of a successful
+// update attempt. The following metrics are reported:
+//
+// |kMetricSuccessfulUpdateAttemptCount|
+// |kMetricSuccessfulUpdateUpdatesAbandonedCount|
+// |kMetricSuccessfulUpdatePayloadType|
+// |kMetricSuccessfulUpdatePayloadSizeMiB|
+// |kMetricSuccessfulUpdateBytesDownloadedMiBHttpsServer|
+// |kMetricSuccessfulUpdateBytesDownloadedMiBHttpServer|
+// |kMetricSuccessfulUpdateBytesDownloadedMiBHttpPeer|
+// |kMetricSuccessfulUpdateBytesDownloadedMiB|
+// |kMetricSuccessfulUpdateDownloadSourcesUsed|
+// |kMetricSuccessfulUpdateDownloadOverheadPercentage|
+// |kMetricSuccessfulUpdateTotalDurationMinutes|
+// |kMetricSuccessfulUpdateRebootCount|
+// |kMetricSuccessfulUpdateUrlSwitchCount|
+//
+// The values for the |kMetricSuccessfulUpdateDownloadSourcesUsed| are
+// |kMetricSuccessfulUpdateBytesDownloadedMiB| metrics automatically
+// calculated from examining the |num_bytes_downloaded| array.
+void ReportSuccessfulUpdateMetrics(
+ SystemState *system_state,
+ int attempt_count,
+ int updates_abandoned_count,
+ PayloadType payload_type,
+ int64_t payload_size,
+ int64_t num_bytes_downloaded[kNumDownloadSources],
+ int download_overhead_percentage,
+ base::TimeDelta total_duration,
+ int reboot_count,
+ int url_switch_count);
+
+} // namespace metrics
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_METRICS_H_
diff --git a/mock_action.h b/mock_action.h
new file mode 100644
index 0000000..f7bfedd
--- /dev/null
+++ b/mock_action.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_ACTION_H_
+#define UPDATE_ENGINE_MOCK_ACTION_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/action.h"
+
+namespace chromeos_update_engine {
+
+class MockAction;
+
+template<>
+class ActionTraits<MockAction> {
+ public:
+ typedef NoneType OutputObjectType;
+ typedef NoneType InputObjectType;
+};
+
+class MockAction : public Action<MockAction> {
+ public:
+ MOCK_METHOD0(PerformAction, void());
+ MOCK_CONST_METHOD0(Type, std::string());
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_ACTION_H_
diff --git a/mock_action_processor.h b/mock_action_processor.h
new file mode 100644
index 0000000..24297ea
--- /dev/null
+++ b/mock_action_processor.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_ACTION_PROCESSOR_H_
+#define UPDATE_ENGINE_MOCK_ACTION_PROCESSOR_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/action.h"
+
+namespace chromeos_update_engine {
+
+class MockActionProcessor : public ActionProcessor {
+ public:
+ MOCK_METHOD0(StartProcessing, void());
+ MOCK_METHOD1(EnqueueAction, void(AbstractAction* action));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_ACTION_PROCESSOR_H_
diff --git a/mock_certificate_checker.h b/mock_certificate_checker.h
new file mode 100644
index 0000000..8c26b42
--- /dev/null
+++ b/mock_certificate_checker.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_CERTIFICATE_CHECKER_H_
+#define UPDATE_ENGINE_MOCK_CERTIFICATE_CHECKER_H_
+
+#include <gmock/gmock.h>
+#include <openssl/ssl.h>
+
+#include "update_engine/certificate_checker.h"
+
+namespace chromeos_update_engine {
+
+class MockOpenSSLWrapper : public OpenSSLWrapper {
+ public:
+ MOCK_CONST_METHOD4(GetCertificateDigest,
+ bool(X509_STORE_CTX* x509_ctx,
+ int* out_depth,
+ unsigned int* out_digest_length,
+ uint8_t* out_digest));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_CERTIFICATE_CHECKER_H_
diff --git a/mock_connection_manager.h b/mock_connection_manager.h
new file mode 100644
index 0000000..9530865
--- /dev/null
+++ b/mock_connection_manager.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
+#define UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/connection_manager.h"
+
+namespace chromeos_update_engine {
+
+// This class mocks the generic interface to the connection manager
+// (e.g FlimFlam, Shill, etc.) to consolidate all connection-related
+// logic in update_engine.
+class MockConnectionManager : public ConnectionManager {
+ public:
+ explicit MockConnectionManager(SystemState* system_state)
+ : ConnectionManager(system_state) {}
+
+ MOCK_CONST_METHOD3(GetConnectionProperties,
+ bool(DBusWrapperInterface* dbus_iface,
+ NetworkConnectionType* out_type,
+ NetworkTethering* out_tethering));
+
+ MOCK_CONST_METHOD2(IsUpdateAllowedOver, bool(NetworkConnectionType type,
+ NetworkTethering tethering));
+
+ MOCK_CONST_METHOD1(StringForConnectionType,
+ const char*(NetworkConnectionType type));
+
+ MOCK_CONST_METHOD1(StringForTethering,
+ const char*(NetworkTethering tethering));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
diff --git a/mock_dbus_wrapper.h b/mock_dbus_wrapper.h
new file mode 100644
index 0000000..5d630a6
--- /dev/null
+++ b/mock_dbus_wrapper.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_DBUS_WRAPPER_H_
+#define UPDATE_ENGINE_MOCK_DBUS_WRAPPER_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/dbus_wrapper_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockDBusWrapper : public DBusWrapperInterface {
+ public:
+ MOCK_METHOD4(ProxyNewForName, DBusGProxy*(DBusGConnection *connection,
+ const char *name,
+ const char *path,
+ const char *interface));
+
+ MOCK_METHOD1(ProxyUnref, void(DBusGProxy* proxy));
+
+ MOCK_METHOD2(BusGet, DBusGConnection*(DBusBusType type, GError **error));
+
+ MOCK_METHOD4(ProxyCall_0_1, gboolean(DBusGProxy *proxy,
+ const char *method,
+ GError **error,
+ GHashTable** out1));
+ MOCK_METHOD4(ProxyCall_0_1, gboolean(DBusGProxy *proxy,
+ const char *method,
+ GError **error,
+ gint* out1));
+ MOCK_METHOD4(ProxyCall_1_0, gboolean(DBusGProxy *proxy,
+ const char *method,
+ GError **error,
+ gint in1));
+ MOCK_METHOD6(ProxyCall_3_0, gboolean(DBusGProxy* proxy,
+ const char* method,
+ GError** error,
+ const char* in1,
+ const char* in2,
+ const char* in3));
+
+ MOCK_METHOD3(ProxyAddSignal_1, void(DBusGProxy* proxy,
+ const char* signal_name,
+ GType type1));
+
+ MOCK_METHOD4(ProxyAddSignal_2, void(DBusGProxy* proxy,
+ const char* signal_name,
+ GType type1,
+ GType type2));
+
+ MOCK_METHOD5(ProxyConnectSignal, void(DBusGProxy* proxy,
+ const char* signal_name,
+ GCallback handler,
+ void* data,
+ GClosureNotify free_data_func));
+
+ MOCK_METHOD4(ProxyDisconnectSignal, void(DBusGProxy* proxy,
+ const char* signal_name,
+ GCallback handler,
+ void* data));
+
+ MOCK_METHOD1(ConnectionGetConnection, DBusConnection*(DBusGConnection* gbus));
+
+ MOCK_METHOD3(DBusBusAddMatch, void(DBusConnection* connection,
+ const char* rule,
+ DBusError* error));
+
+ MOCK_METHOD4(DBusConnectionAddFilter, dbus_bool_t(
+ DBusConnection* connection,
+ DBusHandleMessageFunction function,
+ void* user_data,
+ DBusFreeFunction free_data_function));
+
+ MOCK_METHOD3(DBusConnectionRemoveFilter, void(
+ DBusConnection* connection,
+ DBusHandleMessageFunction function,
+ void* user_data));
+
+ MOCK_METHOD3(DBusMessageIsSignal, dbus_bool_t(DBusMessage* message,
+ const char* interface,
+ const char* signal_name));
+
+ MOCK_METHOD5(DBusMessageGetArgs_3, dbus_bool_t(DBusMessage* message,
+ DBusError* error,
+ char** out1,
+ char** out2,
+ char** out3));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_DBUS_WRAPPER_H_
diff --git a/mock_file_writer.h b/mock_file_writer.h
new file mode 100644
index 0000000..65a5531
--- /dev/null
+++ b/mock_file_writer.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_FILE_WRITER_H_
+#define UPDATE_ENGINE_MOCK_FILE_WRITER_H_
+
+#include <gmock/gmock.h>
+#include "update_engine/file_writer.h"
+
+namespace chromeos_update_engine {
+
+class MockFileWriter : public FileWriter {
+ public:
+ MOCK_METHOD3(Open, int(const char* path, int flags, mode_t mode));
+ MOCK_METHOD2(Write, ssize_t(const void* bytes, size_t count));
+ MOCK_METHOD0(Close, int());
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_FILE_WRITER_H_
diff --git a/mock_hardware.h b/mock_hardware.h
new file mode 100644
index 0000000..bf26821
--- /dev/null
+++ b/mock_hardware.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_HARDWARE_H_
+#define UPDATE_ENGINE_MOCK_HARDWARE_H_
+
+#include <string>
+#include <vector>
+
+#include "update_engine/fake_hardware.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+// A mocked, fake implementation of HardwareInterface.
+class MockHardware : public HardwareInterface {
+ public:
+ MockHardware() {
+ // Delegate all calls to the fake instance
+ ON_CALL(*this, BootKernelDevice())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::BootKernelDevice));
+ ON_CALL(*this, BootDevice())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::BootDevice));
+ ON_CALL(*this, IsBootDeviceRemovable())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::IsBootDeviceRemovable));
+ ON_CALL(*this, GetKernelDevices())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::GetKernelDevices));
+ ON_CALL(*this, IsKernelBootable(testing::_, testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::IsKernelBootable));
+ ON_CALL(*this, MarkKernelUnbootable(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::MarkKernelUnbootable));
+ ON_CALL(*this, IsOfficialBuild())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::IsOfficialBuild));
+ ON_CALL(*this, IsNormalBootMode())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::IsNormalBootMode));
+ ON_CALL(*this, IsOOBEComplete(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::IsOOBEComplete));
+ ON_CALL(*this, GetHardwareClass())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::GetHardwareClass));
+ ON_CALL(*this, GetFirmwareVersion())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::GetFirmwareVersion));
+ ON_CALL(*this, GetECVersion())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::GetECVersion));
+ ON_CALL(*this, GetPowerwashCount())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::GetPowerwashCount));
+ }
+
+ ~MockHardware() override {}
+
+ // Hardware overrides.
+ MOCK_CONST_METHOD0(BootKernelDevice, std::string());
+ MOCK_CONST_METHOD0(BootDevice, std::string());
+ MOCK_CONST_METHOD0(IsBootDeviceRemovable, bool());
+ MOCK_CONST_METHOD0(GetKernelDevices, std::vector<std::string>());
+ MOCK_CONST_METHOD2(IsKernelBootable,
+ bool(const std::string& kernel_device, bool* bootable));
+ MOCK_METHOD1(MarkKernelUnbootable,
+ bool(const std::string& kernel_device));
+ MOCK_CONST_METHOD0(IsOfficialBuild, bool());
+ MOCK_CONST_METHOD0(IsNormalBootMode, bool());
+ MOCK_CONST_METHOD1(IsOOBEComplete, bool(base::Time* out_time_of_oobe));
+ MOCK_CONST_METHOD0(GetHardwareClass, std::string());
+ MOCK_CONST_METHOD0(GetFirmwareVersion, std::string());
+ MOCK_CONST_METHOD0(GetECVersion, std::string());
+ MOCK_CONST_METHOD0(GetPowerwashCount, int());
+
+ // Returns a reference to the underlying FakeHardware.
+ FakeHardware& fake() {
+ return fake_;
+ }
+
+ private:
+ // The underlying FakeHardware.
+ FakeHardware fake_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockHardware);
+};
+
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_HARDWARE_H_
diff --git a/mock_http_fetcher.cc b/mock_http_fetcher.cc
new file mode 100644
index 0000000..a8dd1ae
--- /dev/null
+++ b/mock_http_fetcher.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/mock_http_fetcher.h"
+
+#include <algorithm>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+// This is a mock implementation of HttpFetcher which is useful for testing.
+
+using chromeos::MessageLoop;
+using std::min;
+
+namespace chromeos_update_engine {
+
+MockHttpFetcher::~MockHttpFetcher() {
+ CHECK(timeout_id_ == MessageLoop::kTaskIdNull) <<
+ "Call TerminateTransfer() before dtor.";
+}
+
+void MockHttpFetcher::BeginTransfer(const std::string& url) {
+ EXPECT_FALSE(never_use_);
+ if (fail_transfer_ || data_.empty()) {
+ // No data to send, just notify of completion..
+ SignalTransferComplete();
+ return;
+ }
+ if (sent_size_ < data_.size())
+ SendData(true);
+}
+
+// Returns false on one condition: If timeout_id_ was already set
+// and it needs to be deleted by the caller. If timeout_id_ is null
+// when this function is called, this function will always return true.
+bool MockHttpFetcher::SendData(bool skip_delivery) {
+ if (fail_transfer_) {
+ SignalTransferComplete();
+ return timeout_id_ != MessageLoop::kTaskIdNull;
+ }
+
+ CHECK_LT(sent_size_, data_.size());
+ if (!skip_delivery) {
+ const size_t chunk_size = min(kMockHttpFetcherChunkSize,
+ data_.size() - sent_size_);
+ CHECK(delegate_);
+ delegate_->ReceivedBytes(this, &data_[sent_size_], chunk_size);
+ // We may get terminated in the callback.
+ if (sent_size_ == data_.size()) {
+ LOG(INFO) << "Terminated in the ReceivedBytes callback.";
+ return timeout_id_ != MessageLoop::kTaskIdNull;
+ }
+ sent_size_ += chunk_size;
+ CHECK_LE(sent_size_, data_.size());
+ if (sent_size_ == data_.size()) {
+ // We've sent all the data. Notify of success.
+ SignalTransferComplete();
+ }
+ }
+
+ if (paused_) {
+ // If we're paused, we should return true if timeout_id_ is set,
+ // since we need the caller to delete it.
+ return timeout_id_ != MessageLoop::kTaskIdNull;
+ }
+
+ if (timeout_id_ != MessageLoop::kTaskIdNull) {
+ // we still need a timeout if there's more data to send
+ return sent_size_ < data_.size();
+ } else if (sent_size_ < data_.size()) {
+ // we don't have a timeout source and we need one
+ timeout_id_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(10));
+ }
+ return true;
+}
+
+void MockHttpFetcher::TimeoutCallback() {
+ CHECK(!paused_);
+ if (SendData(false)) {
+ // We need to re-schedule the timeout.
+ timeout_id_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(10));
+ } else {
+ timeout_id_ = MessageLoop::kTaskIdNull;
+ }
+}
+
+// If the transfer is in progress, aborts the transfer early.
+// The transfer cannot be resumed.
+void MockHttpFetcher::TerminateTransfer() {
+ LOG(INFO) << "Terminating transfer.";
+ sent_size_ = data_.size();
+ // Kill any timeout, it is ok to call with kTaskIdNull.
+ MessageLoop::current()->CancelTask(timeout_id_);
+ timeout_id_ = MessageLoop::kTaskIdNull;
+ delegate_->TransferTerminated(this);
+}
+
+void MockHttpFetcher::Pause() {
+ CHECK(!paused_);
+ paused_ = true;
+ MessageLoop::current()->CancelTask(timeout_id_);
+ timeout_id_ = MessageLoop::kTaskIdNull;
+}
+
+void MockHttpFetcher::Unpause() {
+ CHECK(paused_) << "You must pause before unpause.";
+ paused_ = false;
+ if (sent_size_ < data_.size()) {
+ SendData(false);
+ }
+}
+
+void MockHttpFetcher::FailTransfer(int http_response_code) {
+ fail_transfer_ = true;
+ http_response_code_ = http_response_code;
+}
+
+void MockHttpFetcher::SignalTransferComplete() {
+ // If the transfer has been failed, the HTTP response code should be set
+ // already.
+ if (!fail_transfer_) {
+ http_response_code_ = 200;
+ }
+ delegate_->TransferComplete(this, !fail_transfer_);
+}
+
+} // namespace chromeos_update_engine
diff --git a/mock_http_fetcher.h b/mock_http_fetcher.h
new file mode 100644
index 0000000..89f5d21
--- /dev/null
+++ b/mock_http_fetcher.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_MOCK_HTTP_FETCHER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <glib.h>
+
+#include "update_engine/fake_system_state.h"
+#include "update_engine/http_fetcher.h"
+#include "update_engine/mock_connection_manager.h"
+
+// This is a mock implementation of HttpFetcher which is useful for testing.
+// All data must be passed into the ctor. When started, MockHttpFetcher will
+// deliver the data in chunks of size kMockHttpFetcherChunkSize. To simulate
+// a network failure, you can call FailTransfer().
+
+namespace chromeos_update_engine {
+
+// MockHttpFetcher will send a chunk of data down in each call to BeginTransfer
+// and Unpause. For the other chunks of data, a callback is put on the run
+// loop and when that's called, another chunk is sent down.
+const size_t kMockHttpFetcherChunkSize(65536);
+
+class MockHttpFetcher : public HttpFetcher {
+ public:
+ // The data passed in here is copied and then passed to the delegate after
+ // the transfer begins.
+ MockHttpFetcher(const uint8_t* data,
+ size_t size,
+ ProxyResolver* proxy_resolver)
+ : HttpFetcher(proxy_resolver, &fake_system_state_),
+ sent_size_(0),
+ timeout_id_(chromeos::MessageLoop::kTaskIdNull),
+ paused_(false),
+ fail_transfer_(false),
+ never_use_(false),
+ mock_connection_manager_(&fake_system_state_) {
+ fake_system_state_.set_connection_manager(&mock_connection_manager_);
+ data_.insert(data_.end(), data, data + size);
+ }
+
+ // Constructor overload for string data.
+ MockHttpFetcher(const char* data, size_t size, ProxyResolver* proxy_resolver)
+ : MockHttpFetcher(reinterpret_cast<const uint8_t*>(data), size,
+ proxy_resolver) {}
+
+ // Cleans up all internal state. Does not notify delegate
+ ~MockHttpFetcher() override;
+
+ // Ignores this.
+ void SetOffset(off_t offset) override {
+ sent_size_ = offset;
+ if (delegate_)
+ delegate_->SeekToOffset(offset);
+ }
+
+ // Do nothing.
+ void SetLength(size_t length) override {}
+ void UnsetLength() override {}
+ void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {}
+ void set_connect_timeout(int connect_timeout_seconds) override {}
+ void set_max_retry_count(int max_retry_count) override {}
+
+ // Dummy: no bytes were downloaded.
+ size_t GetBytesDownloaded() override {
+ return sent_size_;
+ }
+
+ // Begins the transfer if it hasn't already begun.
+ void BeginTransfer(const std::string& url) override;
+
+ // If the transfer is in progress, aborts the transfer early.
+ // The transfer cannot be resumed.
+ void TerminateTransfer() override;
+
+ // Suspend the mock transfer.
+ void Pause() override;
+
+ // Resume the mock transfer.
+ void Unpause() override;
+
+ // Fail the transfer. This simulates a network failure.
+ void FailTransfer(int http_response_code);
+
+ // If set to true, this will EXPECT fail on BeginTransfer
+ void set_never_use(bool never_use) { never_use_ = never_use; }
+
+ const chromeos::Blob& post_data() const {
+ return post_data_;
+ }
+
+ private:
+ // Sends data to the delegate and sets up a timeout callback if needed.
+ // There must be a delegate and there must be data to send. If there is
+ // already a timeout callback, and it should be deleted by the caller,
+ // this will return false; otherwise true is returned.
+ // If skip_delivery is true, no bytes will be delivered, but the callbacks
+ // still be set if needed.
+ bool SendData(bool skip_delivery);
+
+ // Callback for when our message loop timeout expires.
+ void TimeoutCallback();
+
+ // Sets the HTTP response code and signals to the delegate that the transfer
+ // is complete.
+ void SignalTransferComplete();
+
+ // A full copy of the data we'll return to the delegate
+ chromeos::Blob data_;
+
+ // The number of bytes we've sent so far
+ size_t sent_size_;
+
+ // The TaskId of the timeout callback. After each chunk of data sent, we
+ // time out for 0s just to make sure that run loop services other clients.
+ chromeos::MessageLoop::TaskId timeout_id_;
+
+ // True iff the fetcher is paused.
+ bool paused_;
+
+ // Set to true if the transfer should fail.
+ bool fail_transfer_;
+
+ // Set to true if BeginTransfer should EXPECT fail.
+ bool never_use_;
+
+ FakeSystemState fake_system_state_;
+ MockConnectionManager mock_connection_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockHttpFetcher);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_HTTP_FETCHER_H_
diff --git a/mock_omaha_request_params.h b/mock_omaha_request_params.h
new file mode 100644
index 0000000..f04d361
--- /dev/null
+++ b/mock_omaha_request_params.h
@@ -0,0 +1,75 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_
+#define UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/omaha_request_params.h"
+
+namespace chromeos_update_engine {
+
+class MockOmahaRequestParams : public OmahaRequestParams {
+ public:
+ explicit MockOmahaRequestParams(SystemState* system_state)
+ : OmahaRequestParams(system_state) {
+ // Delegate all calls to the parent instance by default. This helps the
+ // migration from tests using the real RequestParams when they should have
+ // use a fake or mock.
+ ON_CALL(*this, to_more_stable_channel())
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::fake_to_more_stable_channel));
+ ON_CALL(*this, GetAppId())
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::FakeGetAppId));
+ ON_CALL(*this, SetTargetChannel(testing::_, testing::_))
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::FakeSetTargetChannel));
+ ON_CALL(*this, UpdateDownloadChannel())
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::FakeUpdateDownloadChannel));
+ ON_CALL(*this, is_powerwash_allowed())
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::fake_is_powerwash_allowed));
+ }
+
+ MOCK_CONST_METHOD0(to_more_stable_channel, bool(void));
+ MOCK_CONST_METHOD0(GetAppId, std::string(void));
+ MOCK_METHOD2(SetTargetChannel, bool(const std::string& channel,
+ bool is_powerwash_allowed));
+ MOCK_METHOD0(UpdateDownloadChannel, void(void));
+ MOCK_CONST_METHOD0(is_powerwash_allowed, bool(void));
+ MOCK_CONST_METHOD0(IsUpdateUrlOfficial, bool(void));
+
+ private:
+ // Wrappers to call the parent class and behave like the real object by
+ // default. See "Delegating Calls to a Parent Class" in gmock's documentation.
+ bool fake_to_more_stable_channel() const {
+ return OmahaRequestParams::to_more_stable_channel();
+ }
+
+ std::string FakeGetAppId() const {
+ return OmahaRequestParams::GetAppId();
+ }
+
+ bool FakeSetTargetChannel(const std::string& channel,
+ bool is_powerwash_allowed) {
+ return OmahaRequestParams::SetTargetChannel(channel, is_powerwash_allowed);
+ }
+
+ void FakeUpdateDownloadChannel() {
+ return OmahaRequestParams::UpdateDownloadChannel();
+ }
+
+ bool fake_is_powerwash_allowed() const {
+ return OmahaRequestParams::is_powerwash_allowed();
+ }
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_
diff --git a/mock_p2p_manager.h b/mock_p2p_manager.h
new file mode 100644
index 0000000..6b5a75e
--- /dev/null
+++ b/mock_p2p_manager.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_P2P_MANAGER_H_
+#define UPDATE_ENGINE_MOCK_P2P_MANAGER_H_
+
+#include <string>
+
+#include "update_engine/fake_p2p_manager.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+// A mocked, fake implementation of P2PManager.
+class MockP2PManager : public P2PManager {
+ public:
+ MockP2PManager() {
+ // Delegate all calls to the fake instance
+ ON_CALL(*this, SetDevicePolicy(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::SetDevicePolicy));
+ ON_CALL(*this, IsP2PEnabled())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::IsP2PEnabled));
+ ON_CALL(*this, EnsureP2PRunning())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::EnsureP2PRunning));
+ ON_CALL(*this, EnsureP2PNotRunning())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::EnsureP2PNotRunning));
+ ON_CALL(*this, PerformHousekeeping())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::PerformHousekeeping));
+ ON_CALL(*this, LookupUrlForFile(testing::_, testing::_, testing::_,
+ testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::LookupUrlForFile));
+ ON_CALL(*this, FileShare(testing::_, testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileShare));
+ ON_CALL(*this, FileGetPath(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileGetPath));
+ ON_CALL(*this, FileGetSize(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileGetSize));
+ ON_CALL(*this, FileGetExpectedSize(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileGetExpectedSize));
+ ON_CALL(*this, FileGetVisible(testing::_, testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileGetVisible));
+ ON_CALL(*this, FileMakeVisible(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileMakeVisible));
+ ON_CALL(*this, CountSharedFiles())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::CountSharedFiles));
+ }
+
+ ~MockP2PManager() override {}
+
+ // P2PManager overrides.
+ MOCK_METHOD1(SetDevicePolicy, void(const policy::DevicePolicy*));
+ MOCK_METHOD0(IsP2PEnabled, bool());
+ MOCK_METHOD0(EnsureP2PRunning, bool());
+ MOCK_METHOD0(EnsureP2PNotRunning, bool());
+ MOCK_METHOD0(PerformHousekeeping, bool());
+ MOCK_METHOD4(LookupUrlForFile, void(const std::string&,
+ size_t,
+ base::TimeDelta,
+ LookupCallback));
+ MOCK_METHOD2(FileShare, bool(const std::string&, size_t));
+ MOCK_METHOD1(FileGetPath, base::FilePath(const std::string&));
+ MOCK_METHOD1(FileGetSize, ssize_t(const std::string&));
+ MOCK_METHOD1(FileGetExpectedSize, ssize_t(const std::string&));
+ MOCK_METHOD2(FileGetVisible, bool(const std::string&, bool*));
+ MOCK_METHOD1(FileMakeVisible, bool(const std::string&));
+ MOCK_METHOD0(CountSharedFiles, int());
+
+ // Returns a reference to the underlying FakeP2PManager.
+ FakeP2PManager& fake() {
+ return fake_;
+ }
+
+ private:
+ // The underlying FakeP2PManager.
+ FakeP2PManager fake_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockP2PManager);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_P2P_MANAGER_H_
diff --git a/mock_payload_state.h b/mock_payload_state.h
new file mode 100644
index 0000000..2ac7e97
--- /dev/null
+++ b/mock_payload_state.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_
+#define UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/payload_state_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockPayloadState: public PayloadStateInterface {
+ public:
+ bool Initialize(SystemState* system_state) {
+ return true;
+ }
+
+ // Significant methods.
+ MOCK_METHOD1(SetResponse, void(const OmahaResponse& response));
+ MOCK_METHOD0(DownloadComplete, void());
+ MOCK_METHOD1(DownloadProgress, void(size_t count));
+ MOCK_METHOD0(UpdateResumed, void());
+ MOCK_METHOD0(UpdateRestarted, void());
+ MOCK_METHOD0(UpdateSucceeded, void());
+ MOCK_METHOD1(UpdateFailed, void(ErrorCode error));
+ MOCK_METHOD0(ResetUpdateStatus, void());
+ MOCK_METHOD0(ShouldBackoffDownload, bool());
+ MOCK_METHOD0(UpdateEngineStarted, void());
+ MOCK_METHOD0(Rollback, void());
+ MOCK_METHOD1(ExpectRebootInNewVersion,
+ void(const std::string& target_version_uid));
+ MOCK_METHOD0(P2PNewAttempt, void());
+ MOCK_METHOD0(P2PAttemptAllowed, bool());
+ MOCK_METHOD1(SetUsingP2PForDownloading, void(bool value));
+ MOCK_METHOD1(SetUsingP2PForSharing, void(bool value));
+ MOCK_METHOD1(SetScatteringWaitPeriod, void(base::TimeDelta));
+ MOCK_METHOD1(SetP2PUrl, void(const std::string&));
+
+ // Getters.
+ MOCK_METHOD0(GetResponseSignature, std::string());
+ MOCK_METHOD0(GetPayloadAttemptNumber, int());
+ MOCK_METHOD0(GetFullPayloadAttemptNumber, int());
+ MOCK_METHOD0(GetCurrentUrl, std::string());
+ MOCK_METHOD0(GetUrlFailureCount, uint32_t());
+ MOCK_METHOD0(GetUrlSwitchCount, uint32_t());
+ MOCK_METHOD0(GetNumResponsesSeen, int());
+ MOCK_METHOD0(GetBackoffExpiryTime, base::Time());
+ MOCK_METHOD0(GetUpdateDuration, base::TimeDelta());
+ MOCK_METHOD0(GetUpdateDurationUptime, base::TimeDelta());
+ MOCK_METHOD1(GetCurrentBytesDownloaded, uint64_t(DownloadSource source));
+ MOCK_METHOD1(GetTotalBytesDownloaded, uint64_t(DownloadSource source));
+ MOCK_METHOD0(GetNumReboots, uint32_t());
+ MOCK_METHOD0(GetRollbackVersion, std::string());
+ MOCK_METHOD0(GetP2PNumAttempts, int());
+ MOCK_METHOD0(GetP2PFirstAttemptTimestamp, base::Time());
+ MOCK_CONST_METHOD0(GetUsingP2PForDownloading, bool());
+ MOCK_CONST_METHOD0(GetUsingP2PForSharing, bool());
+ MOCK_METHOD0(GetScatteringWaitPeriod, base::TimeDelta());
+ MOCK_CONST_METHOD0(GetP2PUrl, std::string());
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_
diff --git a/mock_prefs.h b/mock_prefs.h
new file mode 100644
index 0000000..176176b
--- /dev/null
+++ b/mock_prefs.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_PREFS_H_
+#define UPDATE_ENGINE_MOCK_PREFS_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockPrefs : public PrefsInterface {
+ public:
+ MOCK_METHOD2(GetString, bool(const std::string& key, std::string* value));
+ MOCK_METHOD2(SetString, bool(const std::string& key,
+ const std::string& value));
+ MOCK_METHOD2(GetInt64, bool(const std::string& key, int64_t* value));
+ MOCK_METHOD2(SetInt64, bool(const std::string& key, const int64_t value));
+
+ MOCK_METHOD2(GetBoolean, bool(const std::string& key, bool* value));
+ MOCK_METHOD2(SetBoolean, bool(const std::string& key, const bool value));
+
+ MOCK_METHOD1(Exists, bool(const std::string& key));
+ MOCK_METHOD1(Delete, bool(const std::string& key));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_PREFS_H_
diff --git a/mock_update_attempter.h b/mock_update_attempter.h
new file mode 100644
index 0000000..0c20311
--- /dev/null
+++ b/mock_update_attempter.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_
+#define UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_
+
+#include <string>
+
+#include "update_engine/update_attempter.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+class MockUpdateAttempter : public UpdateAttempter {
+ public:
+ using UpdateAttempter::UpdateAttempter;
+
+ MOCK_METHOD6(Update, void(const std::string& app_version,
+ const std::string& omaha_url,
+ const std::string& target_channel,
+ const std::string& target_version_prefix,
+ bool obey_proxies,
+ bool interactive));
+
+ MOCK_METHOD5(GetStatus, bool(int64_t* last_checked_time,
+ double* progress,
+ std::string* current_operation,
+ std::string* new_version,
+ int64_t* new_size));
+
+ MOCK_METHOD1(GetBootTimeAtUpdate, bool(base::Time* out_boot_time));
+
+ MOCK_METHOD0(ResetStatus, bool(void));
+
+ MOCK_METHOD3(CheckForUpdate, void(const std::string& app_version,
+ const std::string& omaha_url,
+ bool is_interactive));
+
+ MOCK_METHOD0(RefreshDevicePolicy, void(void));
+
+ MOCK_CONST_METHOD0(consecutive_failed_update_checks, unsigned int(void));
+
+ MOCK_CONST_METHOD0(server_dictated_poll_interval, unsigned int(void));
+
+ MOCK_METHOD0(IsAnyUpdateSourceAllowed, bool(void));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_
diff --git a/mtd_file_descriptor.cc b/mtd_file_descriptor.cc
new file mode 100644
index 0000000..7ab2afa
--- /dev/null
+++ b/mtd_file_descriptor.cc
@@ -0,0 +1,254 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/mtd_file_descriptor.h"
+
+#include <fcntl.h>
+#include <mtd/ubi-user.h>
+#include <string>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <update_engine/subprocess.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+static const char kSysfsClassUbi[] = "/sys/class/ubi/";
+static const char kUsableEbSize[] = "/usable_eb_size";
+static const char kReservedEbs[] = "/reserved_ebs";
+
+using chromeos_update_engine::Subprocess;
+using chromeos_update_engine::UbiVolumeInfo;
+using chromeos_update_engine::utils::ReadFile;
+
+// Return a UbiVolumeInfo pointer if |path| is a UBI volume. Otherwise, return
+// a null unique pointer.
+std::unique_ptr<UbiVolumeInfo> GetUbiVolumeInfo(const string& path) {
+ base::FilePath device_node(path);
+ base::FilePath ubi_name(device_node.BaseName());
+
+ std::string sysfs_node(kSysfsClassUbi);
+ sysfs_node.append(ubi_name.MaybeAsASCII());
+
+ std::unique_ptr<UbiVolumeInfo> ret;
+
+ // Obtain volume info from sysfs.
+ std::string s_reserved_ebs;
+ if (!ReadFile(sysfs_node + kReservedEbs, &s_reserved_ebs)) {
+ LOG(ERROR) << "Cannot read " << sysfs_node + kReservedEbs;
+ return ret;
+ }
+ std::string s_eb_size;
+ if (!ReadFile(sysfs_node + kUsableEbSize, &s_eb_size)) {
+ LOG(ERROR) << "Cannot read " << sysfs_node + kUsableEbSize;
+ return ret;
+ }
+
+ base::TrimWhitespaceASCII(s_reserved_ebs,
+ base::TRIM_TRAILING,
+ &s_reserved_ebs);
+ base::TrimWhitespaceASCII(s_eb_size, base::TRIM_TRAILING, &s_eb_size);
+
+ uint64_t reserved_ebs, eb_size;
+ if (!base::StringToUint64(s_reserved_ebs, &reserved_ebs)) {
+ LOG(ERROR) << "Cannot parse reserved_ebs: " << s_reserved_ebs;
+ return ret;
+ }
+ if (!base::StringToUint64(s_eb_size, &eb_size)) {
+ LOG(ERROR) << "Cannot parse usable_eb_size: " << s_eb_size;
+ return ret;
+ }
+
+ ret.reset(new UbiVolumeInfo);
+ ret->reserved_ebs = reserved_ebs;
+ ret->eraseblock_size = eb_size;
+ return ret;
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+MtdFileDescriptor::MtdFileDescriptor()
+ : read_ctx_(nullptr, &mtd_read_close),
+ write_ctx_(nullptr, &mtd_write_close) {}
+
+bool MtdFileDescriptor::IsMtd(const char* path) {
+ uint64_t size;
+ return mtd_node_info(path, &size, nullptr, nullptr) == 0;
+}
+
+bool MtdFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+ // This File Descriptor does not support read and write.
+ TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR);
+ // But we need to open the underlying file descriptor in O_RDWR mode because
+ // during write, we need to read back to verify the write actually sticks or
+ // we have to skip the block. That job is done by mtdutils library.
+ if ((flags & O_ACCMODE) == O_WRONLY) {
+ flags &= ~O_ACCMODE;
+ flags |= O_RDWR;
+ }
+ TEST_AND_RETURN_FALSE(
+ EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode));
+
+ if ((flags & O_ACCMODE) == O_RDWR) {
+ write_ctx_.reset(mtd_write_descriptor(fd_, path));
+ nr_written_ = 0;
+ } else {
+ read_ctx_.reset(mtd_read_descriptor(fd_, path));
+ }
+
+ if (!read_ctx_ && !write_ctx_) {
+ Close();
+ return false;
+ }
+
+ return true;
+}
+
+bool MtdFileDescriptor::Open(const char* path, int flags) {
+ mode_t cur = umask(022);
+ umask(cur);
+ return Open(path, flags, 0777 & ~cur);
+}
+
+ssize_t MtdFileDescriptor::Read(void* buf, size_t count) {
+ CHECK(read_ctx_);
+ return mtd_read_data(read_ctx_.get(), static_cast<char*>(buf), count);
+}
+
+ssize_t MtdFileDescriptor::Write(const void* buf, size_t count) {
+ CHECK(write_ctx_);
+ ssize_t result = mtd_write_data(write_ctx_.get(),
+ static_cast<const char*>(buf),
+ count);
+ if (result > 0) {
+ nr_written_ += result;
+ }
+ return result;
+}
+
+off64_t MtdFileDescriptor::Seek(off64_t offset, int whence) {
+ if (write_ctx_) {
+ // Ignore seek in write mode.
+ return nr_written_;
+ }
+ return EintrSafeFileDescriptor::Seek(offset, whence);
+}
+
+bool MtdFileDescriptor::Close() {
+ read_ctx_.reset();
+ write_ctx_.reset();
+ return EintrSafeFileDescriptor::Close();
+}
+
+bool UbiFileDescriptor::IsUbi(const char* path) {
+ base::FilePath device_node(path);
+ base::FilePath ubi_name(device_node.BaseName());
+ TEST_AND_RETURN_FALSE(
+ base::StartsWithASCII(ubi_name.MaybeAsASCII(), "ubi", true));
+
+ return static_cast<bool>(GetUbiVolumeInfo(path));
+}
+
+bool UbiFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+ std::unique_ptr<UbiVolumeInfo> info = GetUbiVolumeInfo(path);
+ if (!info) {
+ return false;
+ }
+
+ // This File Descriptor does not support read and write.
+ TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR);
+ TEST_AND_RETURN_FALSE(
+ EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode));
+
+ usable_eb_blocks_ = info->reserved_ebs;
+ eraseblock_size_ = info->eraseblock_size;
+ volume_size_ = usable_eb_blocks_ * eraseblock_size_;
+
+ if ((flags & O_ACCMODE) == O_WRONLY) {
+ // It's best to use volume update ioctl so that UBI layer will mark the
+ // volume as being updated, and only clear that mark if the update is
+ // successful. We will need to pad to the whole volume size at close.
+ uint64_t vsize = volume_size_;
+ if (ioctl(fd_, UBI_IOCVOLUP, &vsize) != 0) {
+ PLOG(ERROR) << "Cannot issue volume update ioctl";
+ EintrSafeFileDescriptor::Close();
+ return false;
+ }
+ mode_ = kWriteOnly;
+ nr_written_ = 0;
+ } else {
+ mode_ = kReadOnly;
+ }
+
+ return true;
+}
+
+bool UbiFileDescriptor::Open(const char* path, int flags) {
+ mode_t cur = umask(022);
+ umask(cur);
+ return Open(path, flags, 0777 & ~cur);
+}
+
+ssize_t UbiFileDescriptor::Read(void* buf, size_t count) {
+ CHECK(mode_ == kReadOnly);
+ return EintrSafeFileDescriptor::Read(buf, count);
+}
+
+ssize_t UbiFileDescriptor::Write(const void* buf, size_t count) {
+ CHECK(mode_ == kWriteOnly);
+ ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, count);
+ if (nr_chunk >= 0) {
+ nr_written_ += nr_chunk;
+ }
+ return nr_chunk;
+}
+
+off64_t UbiFileDescriptor::Seek(off64_t offset, int whence) {
+ if (mode_ == kWriteOnly) {
+ // Ignore seek in write mode.
+ return nr_written_;
+ }
+ return EintrSafeFileDescriptor::Seek(offset, whence);
+}
+
+bool UbiFileDescriptor::Close() {
+ bool pad_ok = true;
+ if (IsOpen() && mode_ == kWriteOnly) {
+ char buf[1024];
+ memset(buf, 0xFF, sizeof(buf));
+ while (nr_written_ < volume_size_) {
+ // We have written less than the whole volume. In order for us to clear
+ // the update marker, we need to fill the rest. It is recommended to fill
+ // UBI writes with 0xFF.
+ uint64_t to_write = volume_size_ - nr_written_;
+ if (to_write > sizeof(buf)) {
+ to_write = sizeof(buf);
+ }
+ ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, to_write);
+ if (nr_chunk < 0) {
+ LOG(ERROR) << "Cannot 0xFF-pad before closing.";
+ // There is an error, but we can't really do any meaningful thing here.
+ pad_ok = false;
+ break;
+ }
+ nr_written_ += nr_chunk;
+ }
+ }
+ return EintrSafeFileDescriptor::Close() && pad_ok;
+}
+
+} // namespace chromeos_update_engine
diff --git a/mtd_file_descriptor.h b/mtd_file_descriptor.h
new file mode 100644
index 0000000..ad545b6
--- /dev/null
+++ b/mtd_file_descriptor.h
@@ -0,0 +1,78 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MTD_FILE_DESCRIPTOR_H_
+#define UPDATE_ENGINE_MTD_FILE_DESCRIPTOR_H_
+
+// This module defines file descriptors that deal with NAND media. We are
+// concerned with raw NAND access (as MTD device), and through UBI layer.
+
+#include <mtdutils.h>
+
+#include "update_engine/file_descriptor.h"
+
+namespace chromeos_update_engine {
+
+// A class defining the file descriptor API for raw MTD device. This file
+// descriptor supports either random read, or sequential write but not both at
+// once.
+class MtdFileDescriptor : public EintrSafeFileDescriptor {
+ public:
+ MtdFileDescriptor();
+
+ static bool IsMtd(const char* path);
+
+ bool Open(const char* path, int flags, mode_t mode) override;
+ bool Open(const char* path, int flags) override;
+ ssize_t Read(void* buf, size_t count) override;
+ ssize_t Write(const void* buf, size_t count) override;
+ off64_t Seek(off64_t offset, int whence) override;
+ bool Close() override;
+
+ private:
+ std::unique_ptr<MtdReadContext, decltype(&mtd_read_close)> read_ctx_;
+ std::unique_ptr<MtdWriteContext, decltype(&mtd_write_close)> write_ctx_;
+ uint64_t nr_written_;
+};
+
+struct UbiVolumeInfo {
+ // Number of eraseblocks.
+ uint64_t reserved_ebs;
+ // Size of each eraseblock.
+ uint64_t eraseblock_size;
+};
+
+// A file descriptor to update a UBI volume, similar to MtdFileDescriptor.
+// Once the file descriptor is opened for write, the volume is marked as being
+// updated. The volume will not be usable until an update is completed. See
+// UBI_IOCVOLUP ioctl operation.
+class UbiFileDescriptor : public EintrSafeFileDescriptor {
+ public:
+ // Perform some queries about |path| to see if it is a UBI volume.
+ static bool IsUbi(const char* path);
+
+ bool Open(const char* path, int flags, mode_t mode) override;
+ bool Open(const char* path, int flags) override;
+ ssize_t Read(void* buf, size_t count) override;
+ ssize_t Write(const void* buf, size_t count) override;
+ off64_t Seek(off64_t offset, int whence) override;
+ bool Close() override;
+
+ private:
+ enum Mode {
+ kReadOnly,
+ kWriteOnly
+ };
+
+ uint64_t usable_eb_blocks_;
+ uint64_t eraseblock_size_;
+ uint64_t volume_size_;
+ uint64_t nr_written_;
+
+ Mode mode_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MTD_FILE_DESCRIPTOR_H_
diff --git a/multi_range_http_fetcher.cc b/multi_range_http_fetcher.cc
new file mode 100644
index 0000000..07118a3
--- /dev/null
+++ b/multi_range_http_fetcher.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/multi_range_http_fetcher.h"
+
+#include <base/strings/stringprintf.h>
+
+#include <algorithm>
+#include <string>
+
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+// Begins the transfer to the specified URL.
+// State change: Stopped -> Downloading
+// (corner case: Stopped -> Stopped for an empty request)
+void MultiRangeHttpFetcher::BeginTransfer(const std::string& url) {
+ CHECK(!base_fetcher_active_) << "BeginTransfer but already active.";
+ CHECK(!pending_transfer_ended_) << "BeginTransfer but pending.";
+ CHECK(!terminating_) << "BeginTransfer but terminating.";
+
+ if (ranges_.empty()) {
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferComplete(this, true);
+ return;
+ }
+ url_ = url;
+ current_index_ = 0;
+ bytes_received_this_range_ = 0;
+ LOG(INFO) << "starting first transfer";
+ base_fetcher_->set_delegate(this);
+ StartTransfer();
+}
+
+// State change: Downloading -> Pending transfer ended
+void MultiRangeHttpFetcher::TerminateTransfer() {
+ if (!base_fetcher_active_) {
+ LOG(INFO) << "Called TerminateTransfer but not active.";
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferTerminated(this);
+ return;
+ }
+ terminating_ = true;
+
+ if (!pending_transfer_ended_) {
+ base_fetcher_->TerminateTransfer();
+ }
+}
+
+// State change: Stopped or Downloading -> Downloading
+void MultiRangeHttpFetcher::StartTransfer() {
+ if (current_index_ >= ranges_.size()) {
+ return;
+ }
+
+ Range range = ranges_[current_index_];
+ LOG(INFO) << "starting transfer of range " << range.ToString();
+
+ bytes_received_this_range_ = 0;
+ base_fetcher_->SetOffset(range.offset());
+ if (range.HasLength())
+ base_fetcher_->SetLength(range.length());
+ else
+ base_fetcher_->UnsetLength();
+ if (delegate_)
+ delegate_->SeekToOffset(range.offset());
+ base_fetcher_active_ = true;
+ base_fetcher_->BeginTransfer(url_);
+}
+
+// State change: Downloading -> Downloading or Pending transfer ended
+void MultiRangeHttpFetcher::ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes,
+ size_t length) {
+ CHECK_LT(current_index_, ranges_.size());
+ CHECK_EQ(fetcher, base_fetcher_.get());
+ CHECK(!pending_transfer_ended_);
+ size_t next_size = length;
+ Range range = ranges_[current_index_];
+ if (range.HasLength()) {
+ next_size = std::min(next_size,
+ range.length() - bytes_received_this_range_);
+ }
+ LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0";
+ if (delegate_) {
+ delegate_->ReceivedBytes(this, bytes, next_size);
+ }
+ bytes_received_this_range_ += length;
+ if (range.HasLength() && bytes_received_this_range_ >= range.length()) {
+ // Terminates the current fetcher. Waits for its TransferTerminated
+ // callback before starting the next range so that we don't end up
+ // signalling the delegate that the whole multi-transfer is complete
+ // before all fetchers are really done and cleaned up.
+ pending_transfer_ended_ = true;
+ LOG(INFO) << "terminating transfer";
+ fetcher->TerminateTransfer();
+ }
+}
+
+// State change: Downloading or Pending transfer ended -> Stopped
+void MultiRangeHttpFetcher::TransferEnded(HttpFetcher* fetcher,
+ bool successful) {
+ CHECK(base_fetcher_active_) << "Transfer ended unexpectedly.";
+ CHECK_EQ(fetcher, base_fetcher_.get());
+ pending_transfer_ended_ = false;
+ http_response_code_ = fetcher->http_response_code();
+ LOG(INFO) << "TransferEnded w/ code " << http_response_code_;
+ if (terminating_) {
+ LOG(INFO) << "Terminating.";
+ Reset();
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferTerminated(this);
+ return;
+ }
+
+ // If we didn't get enough bytes, it's failure
+ Range range = ranges_[current_index_];
+ if (range.HasLength()) {
+ if (bytes_received_this_range_ < range.length()) {
+ // Failure
+ LOG(INFO) << "Didn't get enough bytes. Ending w/ failure.";
+ Reset();
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferComplete(this, false);
+ return;
+ }
+ // We got enough bytes and there were bytes specified, so this is success.
+ successful = true;
+ }
+
+ // If we have another transfer, do that.
+ if (current_index_ + 1 < ranges_.size()) {
+ current_index_++;
+ LOG(INFO) << "Starting next transfer (" << current_index_ << ").";
+ StartTransfer();
+ return;
+ }
+
+ LOG(INFO) << "Done w/ all transfers";
+ Reset();
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferComplete(this, successful);
+}
+
+void MultiRangeHttpFetcher::TransferComplete(HttpFetcher* fetcher,
+ bool successful) {
+ LOG(INFO) << "Received transfer complete.";
+ TransferEnded(fetcher, successful);
+}
+
+void MultiRangeHttpFetcher::TransferTerminated(HttpFetcher* fetcher) {
+ LOG(INFO) << "Received transfer terminated.";
+ TransferEnded(fetcher, false);
+}
+
+void MultiRangeHttpFetcher::Reset() {
+ base_fetcher_active_ = pending_transfer_ended_ = terminating_ = false;
+ current_index_ = 0;
+ bytes_received_this_range_ = 0;
+}
+
+std::string MultiRangeHttpFetcher::Range::ToString() const {
+ std::string range_str = base::StringPrintf("%jd+", offset());
+ if (HasLength())
+ base::StringAppendF(&range_str, "%zu", length());
+ else
+ base::StringAppendF(&range_str, "?");
+ return range_str;
+}
+
+} // namespace chromeos_update_engine
diff --git a/multi_range_http_fetcher.h b/multi_range_http_fetcher.h
new file mode 100644
index 0000000..0b6edfa
--- /dev/null
+++ b/multi_range_http_fetcher.h
@@ -0,0 +1,168 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_MULTI_RANGE_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_MULTI_RANGE_HTTP_FETCHER_H_
+
+#include <deque>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "update_engine/http_fetcher.h"
+
+// This class is a simple wrapper around an HttpFetcher. The client
+// specifies a vector of byte ranges. MultiRangeHttpFetcher will fetch bytes
+// from those offsets, using the same bash fetcher for all ranges. Thus, the
+// fetcher must support beginning a transfer after one has stopped. Pass -1
+// as a length to specify unlimited length. It really only would make sense
+// for the last range specified to have unlimited length, tho it is legal for
+// other entries to have unlimited length.
+
+// There are three states a MultiRangeHttpFetcher object will be in:
+// - Stopped (start state)
+// - Downloading
+// - Pending transfer ended
+// Various functions below that might change state indicate possible
+// state changes.
+
+namespace chromeos_update_engine {
+
+class MultiRangeHttpFetcher : public HttpFetcher, public HttpFetcherDelegate {
+ public:
+ // Takes ownership of the passed in fetcher.
+ explicit MultiRangeHttpFetcher(HttpFetcher* base_fetcher)
+ : HttpFetcher(base_fetcher->proxy_resolver(),
+ base_fetcher->GetSystemState()),
+ base_fetcher_(base_fetcher),
+ base_fetcher_active_(false),
+ pending_transfer_ended_(false),
+ terminating_(false),
+ current_index_(0),
+ bytes_received_this_range_(0) {}
+ ~MultiRangeHttpFetcher() override {}
+
+ void ClearRanges() { ranges_.clear(); }
+
+ void AddRange(off_t offset, size_t size) {
+ CHECK_GT(size, static_cast<size_t>(0));
+ ranges_.push_back(Range(offset, size));
+ }
+
+ void AddRange(off_t offset) {
+ ranges_.push_back(Range(offset));
+ }
+
+ // HttpFetcher overrides.
+ void SetOffset(off_t offset) override {} // for now, doesn't support this
+
+ void SetLength(size_t length) override {} // unsupported
+ void UnsetLength() override {}
+
+ // Begins the transfer to the specified URL.
+ // State change: Stopped -> Downloading
+ // (corner case: Stopped -> Stopped for an empty request)
+ void BeginTransfer(const std::string& url) override;
+
+ // State change: Downloading -> Pending transfer ended
+ void TerminateTransfer() override;
+
+ void Pause() override { base_fetcher_->Pause(); }
+
+ void Unpause() override { base_fetcher_->Unpause(); }
+
+ // These functions are overloaded in LibcurlHttp fetcher for testing purposes.
+ void set_idle_seconds(int seconds) override {
+ base_fetcher_->set_idle_seconds(seconds);
+ }
+ void set_retry_seconds(int seconds) override {
+ base_fetcher_->set_retry_seconds(seconds);
+ }
+ // TODO(deymo): Determine if this method should be virtual in HttpFetcher so
+ // this call is sent to the base_fetcher_.
+ virtual void SetProxies(const std::deque<std::string>& proxies) {
+ base_fetcher_->SetProxies(proxies);
+ }
+
+ inline size_t GetBytesDownloaded() override {
+ return base_fetcher_->GetBytesDownloaded();
+ }
+
+ void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {
+ base_fetcher_->set_low_speed_limit(low_speed_bps, low_speed_sec);
+ }
+
+ void set_connect_timeout(int connect_timeout_seconds) override {
+ base_fetcher_->set_connect_timeout(connect_timeout_seconds);
+ }
+
+ void set_max_retry_count(int max_retry_count) override {
+ base_fetcher_->set_max_retry_count(max_retry_count);
+ }
+
+ private:
+ // A range object defining the offset and length of a download chunk. Zero
+ // length indicates an unspecified end offset (note that it is impossible to
+ // request a zero-length range in HTTP).
+ class Range {
+ public:
+ Range(off_t offset, size_t length) : offset_(offset), length_(length) {}
+ explicit Range(off_t offset) : offset_(offset), length_(0) {}
+
+ inline off_t offset() const { return offset_; }
+ inline size_t length() const { return length_; }
+
+ inline bool HasLength() const { return (length_ > 0); }
+
+ std::string ToString() const;
+
+ private:
+ off_t offset_;
+ size_t length_;
+ };
+
+ typedef std::vector<Range> RangesVect;
+
+ // State change: Stopped or Downloading -> Downloading
+ void StartTransfer();
+
+ // HttpFetcherDelegate overrides.
+ // State change: Downloading -> Downloading or Pending transfer ended
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes,
+ size_t length) override;
+
+ // State change: Pending transfer ended -> Stopped
+ void TransferEnded(HttpFetcher* fetcher, bool successful);
+ // These two call TransferEnded():
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override;
+ void TransferTerminated(HttpFetcher* fetcher) override;
+
+ void Reset();
+
+ std::unique_ptr<HttpFetcher> base_fetcher_;
+
+ // If true, do not send any more data or TransferComplete to the delegate.
+ bool base_fetcher_active_;
+
+ // If true, the next fetcher needs to be started when TransferTerminated is
+ // received from the current fetcher.
+ bool pending_transfer_ended_;
+
+ // True if we are waiting for base fetcher to terminate b/c we are
+ // ourselves terminating.
+ bool terminating_;
+
+ RangesVect ranges_;
+
+ RangesVect::size_type current_index_; // index into ranges_
+ size_t bytes_received_this_range_;
+
+ DISALLOW_COPY_AND_ASSIGN(MultiRangeHttpFetcher);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MULTI_RANGE_HTTP_FETCHER_H_
diff --git a/omaha_hash_calculator.cc b/omaha_hash_calculator.cc
new file mode 100644
index 0000000..4a919b8
--- /dev/null
+++ b/omaha_hash_calculator.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/omaha_hash_calculator.h"
+
+#include <fcntl.h>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <chromeos/data_encoding.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+OmahaHashCalculator::OmahaHashCalculator() : valid_(false) {
+ valid_ = (SHA256_Init(&ctx_) == 1);
+ LOG_IF(ERROR, !valid_) << "SHA256_Init failed";
+}
+
+// Update is called with all of the data that should be hashed in order.
+// Mostly just passes the data through to OpenSSL's SHA256_Update()
+bool OmahaHashCalculator::Update(const void* data, size_t length) {
+ TEST_AND_RETURN_FALSE(valid_);
+ TEST_AND_RETURN_FALSE(hash_.empty());
+ static_assert(sizeof(size_t) <= sizeof(unsigned long), // NOLINT(runtime/int)
+ "length param may be truncated in SHA256_Update");
+ TEST_AND_RETURN_FALSE(SHA256_Update(&ctx_, data, length) == 1);
+ return true;
+}
+
+off_t OmahaHashCalculator::UpdateFile(const string& name, off_t length) {
+ int fd = HANDLE_EINTR(open(name.c_str(), O_RDONLY));
+ if (fd < 0) {
+ return -1;
+ }
+
+ const int kBufferSize = 128 * 1024; // 128 KiB
+ chromeos::Blob buffer(kBufferSize);
+ off_t bytes_processed = 0;
+ while (length < 0 || bytes_processed < length) {
+ off_t bytes_to_read = buffer.size();
+ if (length >= 0 && bytes_to_read > length - bytes_processed) {
+ bytes_to_read = length - bytes_processed;
+ }
+ ssize_t rc = HANDLE_EINTR(read(fd, buffer.data(), bytes_to_read));
+ if (rc == 0) { // EOF
+ break;
+ }
+ if (rc < 0 || !Update(buffer.data(), rc)) {
+ bytes_processed = -1;
+ break;
+ }
+ bytes_processed += rc;
+ }
+ IGNORE_EINTR(close(fd));
+ return bytes_processed;
+}
+
+// Call Finalize() when all data has been passed in. This mostly just
+// calls OpenSSL's SHA256_Final() and then base64 encodes the hash.
+bool OmahaHashCalculator::Finalize() {
+ TEST_AND_RETURN_FALSE(hash_.empty());
+ TEST_AND_RETURN_FALSE(raw_hash_.empty());
+ raw_hash_.resize(SHA256_DIGEST_LENGTH);
+ TEST_AND_RETURN_FALSE(SHA256_Final(raw_hash_.data(), &ctx_) == 1);
+
+ // Convert raw_hash_ to base64 encoding and store it in hash_.
+ hash_ = chromeos::data_encoding::Base64Encode(raw_hash_.data(),
+ raw_hash_.size());
+ return true;
+}
+
+bool OmahaHashCalculator::RawHashOfBytes(const void* data,
+ size_t length,
+ chromeos::Blob* out_hash) {
+ OmahaHashCalculator calc;
+ TEST_AND_RETURN_FALSE(calc.Update(data, length));
+ TEST_AND_RETURN_FALSE(calc.Finalize());
+ *out_hash = calc.raw_hash();
+ return true;
+}
+
+bool OmahaHashCalculator::RawHashOfData(const chromeos::Blob& data,
+ chromeos::Blob* out_hash) {
+ return RawHashOfBytes(data.data(), data.size(), out_hash);
+}
+
+off_t OmahaHashCalculator::RawHashOfFile(const string& name, off_t length,
+ chromeos::Blob* out_hash) {
+ OmahaHashCalculator calc;
+ off_t res = calc.UpdateFile(name, length);
+ if (res < 0) {
+ return res;
+ }
+ if (!calc.Finalize()) {
+ return -1;
+ }
+ *out_hash = calc.raw_hash();
+ return res;
+}
+
+string OmahaHashCalculator::OmahaHashOfBytes(const void* data, size_t length) {
+ OmahaHashCalculator calc;
+ calc.Update(data, length);
+ calc.Finalize();
+ return calc.hash();
+}
+
+string OmahaHashCalculator::OmahaHashOfString(const string& str) {
+ return OmahaHashOfBytes(str.data(), str.size());
+}
+
+string OmahaHashCalculator::OmahaHashOfData(const chromeos::Blob& data) {
+ return OmahaHashOfBytes(data.data(), data.size());
+}
+
+string OmahaHashCalculator::GetContext() const {
+ return string(reinterpret_cast<const char*>(&ctx_), sizeof(ctx_));
+}
+
+bool OmahaHashCalculator::SetContext(const string& context) {
+ TEST_AND_RETURN_FALSE(context.size() == sizeof(ctx_));
+ memcpy(&ctx_, context.data(), sizeof(ctx_));
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/omaha_hash_calculator.h b/omaha_hash_calculator.h
new file mode 100644
index 0000000..a32ac18
--- /dev/null
+++ b/omaha_hash_calculator.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H_
+#define UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H_
+
+#include <openssl/sha.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+// Omaha uses base64 encoded SHA-256 as the hash. This class provides a simple
+// wrapper around OpenSSL providing such a formatted hash of data passed in.
+// The methods of this class must be called in a very specific order: First the
+// ctor (of course), then 0 or more calls to Update(), then Finalize(), then 0
+// or more calls to hash().
+
+namespace chromeos_update_engine {
+
+class OmahaHashCalculator {
+ public:
+ OmahaHashCalculator();
+
+ // Update is called with all of the data that should be hashed in order.
+ // Update will read |length| bytes of |data|.
+ // Returns true on success.
+ bool Update(const void* data, size_t length);
+
+ // Updates the hash with up to |length| bytes of data from |file|. If |length|
+ // is negative, reads in and updates with the whole file. Returns the number
+ // of bytes that the hash was updated with, or -1 on error.
+ off_t UpdateFile(const std::string& name, off_t length);
+
+ // Call Finalize() when all data has been passed in. This method tells
+ // OpenSSl that no more data will come in and base64 encodes the resulting
+ // hash.
+ // Returns true on success.
+ bool Finalize();
+
+ // Gets the hash. Finalize() must have been called.
+ const std::string& hash() const {
+ DCHECK(!hash_.empty()) << "Call Finalize() first";
+ return hash_;
+ }
+
+ const chromeos::Blob& raw_hash() const {
+ DCHECK(!raw_hash_.empty()) << "Call Finalize() first";
+ return raw_hash_;
+ }
+
+ // Gets the current hash context. Note that the string will contain binary
+ // data (including \0 characters).
+ std::string GetContext() const;
+
+ // Sets the current hash context. |context| must the string returned by a
+ // previous OmahaHashCalculator::GetContext method call. Returns true on
+ // success, and false otherwise.
+ bool SetContext(const std::string& context);
+
+ static bool RawHashOfBytes(const void* data,
+ size_t length,
+ chromeos::Blob* out_hash);
+ static bool RawHashOfData(const chromeos::Blob& data,
+ chromeos::Blob* out_hash);
+ static off_t RawHashOfFile(const std::string& name, off_t length,
+ chromeos::Blob* out_hash);
+
+ // Used by tests
+ static std::string OmahaHashOfBytes(const void* data, size_t length);
+ static std::string OmahaHashOfString(const std::string& str);
+ static std::string OmahaHashOfData(const chromeos::Blob& data);
+
+ private:
+ // If non-empty, the final base64 encoded hash and the raw hash. Will only be
+ // set to non-empty when Finalize is called.
+ std::string hash_;
+ chromeos::Blob raw_hash_;
+
+ // Init success
+ bool valid_;
+
+ // The hash state used by OpenSSL
+ SHA256_CTX ctx_;
+ DISALLOW_COPY_AND_ASSIGN(OmahaHashCalculator);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H_
diff --git a/omaha_hash_calculator_unittest.cc b/omaha_hash_calculator_unittest.cc
new file mode 100644
index 0000000..29a0e45
--- /dev/null
+++ b/omaha_hash_calculator_unittest.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/omaha_hash_calculator.h"
+
+#include <math.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// Generated by running this on a linux shell:
+// $ echo -n hi | openssl dgst -sha256 -binary | openssl base64
+static const char kExpectedHash[] =
+ "j0NDRmSPa5bfid2pAcUXaxCm2Dlh3TwayItZstwyeqQ=";
+static const uint8_t kExpectedRawHash[] = {
+ 0x8f, 0x43, 0x43, 0x46, 0x64, 0x8f, 0x6b, 0x96,
+ 0xdf, 0x89, 0xdd, 0xa9, 0x01, 0xc5, 0x17, 0x6b,
+ 0x10, 0xa6, 0xd8, 0x39, 0x61, 0xdd, 0x3c, 0x1a,
+ 0xc8, 0x8b, 0x59, 0xb2, 0xdc, 0x32, 0x7a, 0xa4
+};
+
+class OmahaHashCalculatorTest : public ::testing::Test {
+ public:
+ OmahaHashCalculatorTest() {}
+};
+
+TEST_F(OmahaHashCalculatorTest, SimpleTest) {
+ OmahaHashCalculator calc;
+ calc.Update("hi", 2);
+ calc.Finalize();
+ EXPECT_EQ(kExpectedHash, calc.hash());
+ chromeos::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc.raw_hash());
+}
+
+TEST_F(OmahaHashCalculatorTest, MultiUpdateTest) {
+ OmahaHashCalculator calc;
+ calc.Update("h", 1);
+ calc.Update("i", 1);
+ calc.Finalize();
+ EXPECT_EQ(kExpectedHash, calc.hash());
+ chromeos::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc.raw_hash());
+}
+
+TEST_F(OmahaHashCalculatorTest, ContextTest) {
+ OmahaHashCalculator calc;
+ calc.Update("h", 1);
+ string calc_context = calc.GetContext();
+ calc.Finalize();
+ OmahaHashCalculator calc_next;
+ calc_next.SetContext(calc_context);
+ calc_next.Update("i", 1);
+ calc_next.Finalize();
+ EXPECT_EQ(kExpectedHash, calc_next.hash());
+ chromeos::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc_next.raw_hash());
+}
+
+TEST_F(OmahaHashCalculatorTest, BigTest) {
+ OmahaHashCalculator calc;
+
+ int digit_count = 1;
+ int next_overflow = 10;
+ for (int i = 0; i < 1000000; i++) {
+ char buf[8];
+ if (i == next_overflow) {
+ next_overflow *= 10;
+ digit_count++;
+ }
+ ASSERT_EQ(digit_count, snprintf(buf, sizeof(buf), "%d", i)) << " i = " << i;
+ calc.Update(buf, strlen(buf));
+ }
+ calc.Finalize();
+
+ // Hash constant generated by running this on a linux shell:
+ // $ C=0
+ // $ while [ $C -lt 1000000 ]; do
+ // echo -n $C
+ // let C=C+1
+ // done | openssl dgst -sha256 -binary | openssl base64
+ EXPECT_EQ("NZf8k6SPBkYMvhaX8YgzuMgbkLP1XZ+neM8K5wcSsf8=", calc.hash());
+}
+
+TEST_F(OmahaHashCalculatorTest, UpdateFileSimpleTest) {
+ string data_path;
+ ASSERT_TRUE(
+ utils::MakeTempFile("data.XXXXXX", &data_path, nullptr));
+ ScopedPathUnlinker data_path_unlinker(data_path);
+ ASSERT_TRUE(utils::WriteFile(data_path.c_str(), "hi", 2));
+
+ static const int kLengths[] = { -1, 2, 10 };
+ for (size_t i = 0; i < arraysize(kLengths); i++) {
+ OmahaHashCalculator calc;
+ EXPECT_EQ(2, calc.UpdateFile(data_path, kLengths[i]));
+ EXPECT_TRUE(calc.Finalize());
+ EXPECT_EQ(kExpectedHash, calc.hash());
+ chromeos::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc.raw_hash());
+ }
+
+ OmahaHashCalculator calc;
+ EXPECT_EQ(0, calc.UpdateFile(data_path, 0));
+ EXPECT_EQ(1, calc.UpdateFile(data_path, 1));
+ EXPECT_TRUE(calc.Finalize());
+ // echo -n h | openssl dgst -sha256 -binary | openssl base64
+ EXPECT_EQ("qqlAJmTxpB9A67xSyZk+tmrrNmYClY/fqig7ceZNsSM=", calc.hash());
+}
+
+TEST_F(OmahaHashCalculatorTest, RawHashOfFileSimpleTest) {
+ string data_path;
+ ASSERT_TRUE(
+ utils::MakeTempFile("data.XXXXXX", &data_path, nullptr));
+ ScopedPathUnlinker data_path_unlinker(data_path);
+ ASSERT_TRUE(utils::WriteFile(data_path.c_str(), "hi", 2));
+
+ static const int kLengths[] = { -1, 2, 10 };
+ for (size_t i = 0; i < arraysize(kLengths); i++) {
+ chromeos::Blob exp_raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ chromeos::Blob raw_hash;
+ EXPECT_EQ(2, OmahaHashCalculator::RawHashOfFile(data_path,
+ kLengths[i],
+ &raw_hash));
+ EXPECT_TRUE(exp_raw_hash == raw_hash);
+ }
+}
+
+TEST_F(OmahaHashCalculatorTest, UpdateFileNonexistentTest) {
+ OmahaHashCalculator calc;
+ EXPECT_EQ(-1, calc.UpdateFile("/some/non-existent/file", -1));
+}
+
+TEST_F(OmahaHashCalculatorTest, AbortTest) {
+ // Just make sure we don't crash and valgrind doesn't detect memory leaks
+ {
+ OmahaHashCalculator calc;
+ }
+ {
+ OmahaHashCalculator calc;
+ calc.Update("h", 1);
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
new file mode 100644
index 0000000..dff8ad3
--- /dev/null
+++ b/omaha_request_action.cc
@@ -0,0 +1,1465 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/omaha_request_action.h"
+
+#include <inttypes.h>
+
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <expat.h>
+
+#include "update_engine/action_pipe.h"
+#include "update_engine/constants.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/real_dbus_wrapper.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// List of custom pair tags that we interpret in the Omaha Response:
+static const char* kTagDeadline = "deadline";
+static const char* kTagDisablePayloadBackoff = "DisablePayloadBackoff";
+static const char* kTagVersion = "version";
+// Deprecated: "IsDelta"
+static const char* kTagIsDeltaPayload = "IsDeltaPayload";
+static const char* kTagMaxFailureCountPerUrl = "MaxFailureCountPerUrl";
+static const char* kTagMaxDaysToScatter = "MaxDaysToScatter";
+// Deprecated: "ManifestSignatureRsa"
+// Deprecated: "ManifestSize"
+static const char* kTagMetadataSignatureRsa = "MetadataSignatureRsa";
+static const char* kTagMetadataSize = "MetadataSize";
+static const char* kTagMoreInfo = "MoreInfo";
+// Deprecated: "NeedsAdmin"
+static const char* kTagPrompt = "Prompt";
+static const char* kTagSha256 = "sha256";
+static const char* kTagDisableP2PForDownloading = "DisableP2PForDownloading";
+static const char* kTagDisableP2PForSharing = "DisableP2PForSharing";
+static const char* kTagPublicKeyRsa = "PublicKeyRsa";
+
+namespace {
+
+static const char* const kGupdateVersion = "ChromeOSUpdateEngine-0.1.0.0";
+
+// Returns an XML ping element attribute assignment with attribute
+// |name| and value |ping_days| if |ping_days| has a value that needs
+// to be sent, or an empty string otherwise.
+string GetPingAttribute(const string& name, int ping_days) {
+ if (ping_days > 0 || ping_days == OmahaRequestAction::kNeverPinged)
+ return base::StringPrintf(" %s=\"%d\"", name.c_str(), ping_days);
+ return "";
+}
+
+// Returns an XML ping element if any of the elapsed days need to be
+// sent, or an empty string otherwise.
+string GetPingXml(int ping_active_days, int ping_roll_call_days) {
+ string ping_active = GetPingAttribute("a", ping_active_days);
+ string ping_roll_call = GetPingAttribute("r", ping_roll_call_days);
+ if (!ping_active.empty() || !ping_roll_call.empty()) {
+ return base::StringPrintf(" <ping active=\"1\"%s%s></ping>\n",
+ ping_active.c_str(),
+ ping_roll_call.c_str());
+ }
+ return "";
+}
+
+// Returns an XML that goes into the body of the <app> element of the Omaha
+// request based on the given parameters.
+string GetAppBody(const OmahaEvent* event,
+ OmahaRequestParams* params,
+ bool ping_only,
+ bool include_ping,
+ int ping_active_days,
+ int ping_roll_call_days,
+ PrefsInterface* prefs) {
+ string app_body;
+ if (event == nullptr) {
+ if (include_ping)
+ app_body = GetPingXml(ping_active_days, ping_roll_call_days);
+ if (!ping_only) {
+ app_body += base::StringPrintf(
+ " <updatecheck targetversionprefix=\"%s\""
+ "></updatecheck>\n",
+ XmlEncodeWithDefault(params->target_version_prefix(), "").c_str());
+
+ // If this is the first update check after a reboot following a previous
+ // update, generate an event containing the previous version number. If
+ // the previous version preference file doesn't exist the event is still
+ // generated with a previous version of 0.0.0.0 -- this is relevant for
+ // older clients or new installs. The previous version event is not sent
+ // for ping-only requests because they come before the client has
+ // rebooted.
+ string prev_version;
+ if (!prefs->GetString(kPrefsPreviousVersion, &prev_version)) {
+ prev_version = "0.0.0.0";
+ }
+
+ app_body += base::StringPrintf(
+ " <event eventtype=\"%d\" eventresult=\"%d\" "
+ "previousversion=\"%s\"></event>\n",
+ OmahaEvent::kTypeUpdateComplete,
+ OmahaEvent::kResultSuccessReboot,
+ XmlEncodeWithDefault(prev_version, "0.0.0.0").c_str());
+ LOG_IF(WARNING, !prefs->SetString(kPrefsPreviousVersion, ""))
+ << "Unable to reset the previous version.";
+ }
+ } else {
+ // The error code is an optional attribute so append it only if the result
+ // is not success.
+ string error_code;
+ if (event->result != OmahaEvent::kResultSuccess) {
+ error_code = base::StringPrintf(" errorcode=\"%d\"",
+ static_cast<int>(event->error_code));
+ }
+ app_body = base::StringPrintf(
+ " <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n",
+ event->type, event->result, error_code.c_str());
+ }
+
+ return app_body;
+}
+
+// Returns the cohort* argument to include in the <app> tag for the passed
+// |arg_name| and |prefs_key|, if any. The return value is suitable to
+// concatenate to the list of arguments and includes a space at the end.
+string GetCohortArgXml(PrefsInterface* prefs,
+ const string arg_name,
+ const string prefs_key) {
+ // There's nothing wrong with not having a given cohort setting, so we check
+ // existance first to avoid the warning log message.
+ if (!prefs->Exists(prefs_key))
+ return "";
+ string cohort_value;
+ if (!prefs->GetString(prefs_key, &cohort_value) || cohort_value.empty())
+ return "";
+ // This is a sanity check to avoid sending a huge XML file back to Ohama due
+ // to a compromised stateful partition making the update check fail in low
+ // network environments envent after a reboot.
+ if (cohort_value.size() > 1024) {
+ LOG(WARNING) << "The omaha cohort setting " << arg_name
+ << " has a too big value, which must be an error or an "
+ "attacker trying to inhibit updates.";
+ return "";
+ }
+
+ string escaped_xml_value;
+ if (!XmlEncode(cohort_value, &escaped_xml_value)) {
+ LOG(WARNING) << "The omaha cohort setting " << arg_name
+ << " is ASCII-7 invalid, ignoring it.";
+ return "";
+ }
+
+ return base::StringPrintf("%s=\"%s\" ",
+ arg_name.c_str(), escaped_xml_value.c_str());
+}
+
+// Returns an XML that corresponds to the entire <app> node of the Omaha
+// request based on the given parameters.
+string GetAppXml(const OmahaEvent* event,
+ OmahaRequestParams* params,
+ bool ping_only,
+ bool include_ping,
+ int ping_active_days,
+ int ping_roll_call_days,
+ int install_date_in_days,
+ SystemState* system_state) {
+ string app_body = GetAppBody(event, params, ping_only, include_ping,
+ ping_active_days, ping_roll_call_days,
+ system_state->prefs());
+ string app_versions;
+
+ // If we are upgrading to a more stable channel and we are allowed to do
+ // powerwash, then pass 0.0.0.0 as the version. This is needed to get the
+ // highest-versioned payload on the destination channel.
+ if (params->to_more_stable_channel() && params->is_powerwash_allowed()) {
+ LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash "
+ << "on downgrading to the version in the more stable channel";
+ app_versions = "version=\"0.0.0.0\" from_version=\"" +
+ XmlEncodeWithDefault(params->app_version(), "0.0.0.0") + "\" ";
+ } else {
+ app_versions = "version=\"" +
+ XmlEncodeWithDefault(params->app_version(), "0.0.0.0") + "\" ";
+ }
+
+ string download_channel = params->download_channel();
+ string app_channels =
+ "track=\"" + XmlEncodeWithDefault(download_channel, "") + "\" ";
+ if (params->current_channel() != download_channel) {
+ app_channels += "from_track=\"" + XmlEncodeWithDefault(
+ params->current_channel(), "") + "\" ";
+ }
+
+ string delta_okay_str = params->delta_okay() ? "true" : "false";
+
+ // If install_date_days is not set (e.g. its value is -1 ), don't
+ // include the attribute.
+ string install_date_in_days_str = "";
+ if (install_date_in_days >= 0) {
+ install_date_in_days_str = base::StringPrintf("installdate=\"%d\" ",
+ install_date_in_days);
+ }
+
+ string app_cohort_args;
+ app_cohort_args += GetCohortArgXml(system_state->prefs(),
+ "cohort", kPrefsOmahaCohort);
+ app_cohort_args += GetCohortArgXml(system_state->prefs(),
+ "cohorthint", kPrefsOmahaCohortHint);
+ app_cohort_args += GetCohortArgXml(system_state->prefs(),
+ "cohortname", kPrefsOmahaCohortName);
+
+ string app_xml = " <app "
+ "appid=\"" + XmlEncodeWithDefault(params->GetAppId(), "") + "\" " +
+ app_cohort_args +
+ app_versions +
+ app_channels +
+ "lang=\"" + XmlEncodeWithDefault(params->app_lang(), "en-US") + "\" " +
+ "board=\"" + XmlEncodeWithDefault(params->os_board(), "") + "\" " +
+ "hardware_class=\"" + XmlEncodeWithDefault(params->hwid(), "") + "\" " +
+ "delta_okay=\"" + delta_okay_str + "\" "
+ "fw_version=\"" + XmlEncodeWithDefault(params->fw_version(), "") + "\" " +
+ "ec_version=\"" + XmlEncodeWithDefault(params->ec_version(), "") + "\" " +
+ install_date_in_days_str +
+ ">\n" +
+ app_body +
+ " </app>\n";
+
+ return app_xml;
+}
+
+// Returns an XML that corresponds to the entire <os> node of the Omaha
+// request based on the given parameters.
+string GetOsXml(OmahaRequestParams* params) {
+ string os_xml =" <os "
+ "version=\"" + XmlEncodeWithDefault(params->os_version(), "") + "\" " +
+ "platform=\"" + XmlEncodeWithDefault(params->os_platform(), "") + "\" " +
+ "sp=\"" + XmlEncodeWithDefault(params->os_sp(), "") + "\">"
+ "</os>\n";
+ return os_xml;
+}
+
+// Returns an XML that corresponds to the entire Omaha request based on the
+// given parameters.
+string GetRequestXml(const OmahaEvent* event,
+ OmahaRequestParams* params,
+ bool ping_only,
+ bool include_ping,
+ int ping_active_days,
+ int ping_roll_call_days,
+ int install_date_in_days,
+ SystemState* system_state) {
+ string os_xml = GetOsXml(params);
+ string app_xml = GetAppXml(event, params, ping_only, include_ping,
+ ping_active_days, ping_roll_call_days,
+ install_date_in_days, system_state);
+
+ string install_source = base::StringPrintf("installsource=\"%s\" ",
+ (params->interactive() ? "ondemandupdate" : "scheduler"));
+
+ string request_xml =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<request protocol=\"3.0\" " + (
+ "version=\"" + XmlEncodeWithDefault(kGupdateVersion, "") + "\" "
+ "updaterversion=\"" + XmlEncodeWithDefault(kGupdateVersion,
+ "") + "\" " +
+ install_source +
+ "ismachine=\"1\">\n") +
+ os_xml +
+ app_xml +
+ "</request>\n";
+
+ return request_xml;
+}
+
+} // namespace
+
+// Struct used for holding data obtained when parsing the XML.
+struct OmahaParserData {
+ explicit OmahaParserData(XML_Parser _xml_parser) : xml_parser(_xml_parser) {}
+
+ // Pointer to the expat XML_Parser object.
+ XML_Parser xml_parser;
+
+ // This is the state of the parser as it's processing the XML.
+ bool failed = false;
+ bool entity_decl = false;
+ string current_path;
+
+ // These are the values extracted from the XML.
+ string app_cohort;
+ string app_cohorthint;
+ string app_cohortname;
+ bool app_cohort_set = false;
+ bool app_cohorthint_set = false;
+ bool app_cohortname_set = false;
+ string updatecheck_status;
+ string updatecheck_poll_interval;
+ string daystart_elapsed_days;
+ string daystart_elapsed_seconds;
+ vector<string> url_codebase;
+ string package_name;
+ string package_size;
+ string manifest_version;
+ map<string, string> action_postinstall_attrs;
+};
+
+namespace {
+
+// Callback function invoked by expat.
+void ParserHandlerStart(void* user_data, const XML_Char* element,
+ const XML_Char** attr) {
+ OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+
+ if (data->failed)
+ return;
+
+ data->current_path += string("/") + element;
+
+ map<string, string> attrs;
+ if (attr != nullptr) {
+ for (int n = 0; attr[n] != nullptr && attr[n+1] != nullptr; n += 2) {
+ string key = attr[n];
+ string value = attr[n + 1];
+ attrs[key] = value;
+ }
+ }
+
+ if (data->current_path == "/response/app") {
+ if (attrs.find("cohort") != attrs.end()) {
+ data->app_cohort_set = true;
+ data->app_cohort = attrs["cohort"];
+ }
+ if (attrs.find("cohorthint") != attrs.end()) {
+ data->app_cohorthint_set = true;
+ data->app_cohorthint = attrs["cohorthint"];
+ }
+ if (attrs.find("cohortname") != attrs.end()) {
+ data->app_cohortname_set = true;
+ data->app_cohortname = attrs["cohortname"];
+ }
+ } else if (data->current_path == "/response/app/updatecheck") {
+ // There is only supposed to be a single <updatecheck> element.
+ data->updatecheck_status = attrs["status"];
+ data->updatecheck_poll_interval = attrs["PollInterval"];
+ } else if (data->current_path == "/response/daystart") {
+ // Get the install-date.
+ data->daystart_elapsed_days = attrs["elapsed_days"];
+ data->daystart_elapsed_seconds = attrs["elapsed_seconds"];
+ } else if (data->current_path == "/response/app/updatecheck/urls/url") {
+ // Look at all <url> elements.
+ data->url_codebase.push_back(attrs["codebase"]);
+ } else if (data->package_name.empty() && data->current_path ==
+ "/response/app/updatecheck/manifest/packages/package") {
+ // Only look at the first <package>.
+ data->package_name = attrs["name"];
+ data->package_size = attrs["size"];
+ } else if (data->current_path == "/response/app/updatecheck/manifest") {
+ // Get the version.
+ data->manifest_version = attrs[kTagVersion];
+ } else if (data->current_path ==
+ "/response/app/updatecheck/manifest/actions/action") {
+ // We only care about the postinstall action.
+ if (attrs["event"] == "postinstall") {
+ data->action_postinstall_attrs = attrs;
+ }
+ }
+}
+
+// Callback function invoked by expat.
+void ParserHandlerEnd(void* user_data, const XML_Char* element) {
+ OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+ if (data->failed)
+ return;
+
+ const string path_suffix = string("/") + element;
+
+ if (!base::EndsWith(data->current_path, path_suffix, true)) {
+ LOG(ERROR) << "Unexpected end element '" << element
+ << "' with current_path='" << data->current_path << "'";
+ data->failed = true;
+ return;
+ }
+ data->current_path.resize(data->current_path.size() - path_suffix.size());
+}
+
+// Callback function invoked by expat.
+//
+// This is called for entity declarations. Since Omaha is guaranteed
+// to never return any XML with entities our course of action is to
+// just stop parsing. This avoids potential resource exhaustion
+// problems AKA the "billion laughs". CVE-2013-0340.
+void ParserHandlerEntityDecl(void *user_data,
+ const XML_Char *entity_name,
+ int is_parameter_entity,
+ const XML_Char *value,
+ int value_length,
+ const XML_Char *base,
+ const XML_Char *system_id,
+ const XML_Char *public_id,
+ const XML_Char *notation_name) {
+ OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+
+ LOG(ERROR) << "XML entities are not supported. Aborting parsing.";
+ data->failed = true;
+ data->entity_decl = true;
+ XML_StopParser(data->xml_parser, false);
+}
+
+} // namespace
+
+bool XmlEncode(const string& input, string* output) {
+ if (std::find_if(input.begin(), input.end(),
+ [](const char c){return c & 0x80;}) != input.end()) {
+ LOG(WARNING) << "Invalid ASCII-7 string passed to the XML encoder:";
+ utils::HexDumpString(input);
+ return false;
+ }
+ output->clear();
+ // We need at least input.size() space in the output, but the code below will
+ // handle it if we need more.
+ output->reserve(input.size());
+ for (char c : input) {
+ switch (c) {
+ case '\"':
+ output->append(""");
+ break;
+ case '\'':
+ output->append("'");
+ break;
+ case '&':
+ output->append("&");
+ break;
+ case '<':
+ output->append("<");
+ break;
+ case '>':
+ output->append(">");
+ break;
+ default:
+ output->push_back(c);
+ }
+ }
+ return true;
+}
+
+string XmlEncodeWithDefault(const string& input, const string& default_value) {
+ string output;
+ if (XmlEncode(input, &output))
+ return output;
+ return default_value;
+}
+
+OmahaRequestAction::OmahaRequestAction(SystemState* system_state,
+ OmahaEvent* event,
+ HttpFetcher* http_fetcher,
+ bool ping_only)
+ : system_state_(system_state),
+ event_(event),
+ http_fetcher_(http_fetcher),
+ ping_only_(ping_only),
+ ping_active_days_(0),
+ ping_roll_call_days_(0) {
+ params_ = system_state->request_params();
+}
+
+OmahaRequestAction::~OmahaRequestAction() {}
+
+// Calculates the value to use for the ping days parameter.
+int OmahaRequestAction::CalculatePingDays(const string& key) {
+ int days = kNeverPinged;
+ int64_t last_ping = 0;
+ if (system_state_->prefs()->GetInt64(key, &last_ping) && last_ping >= 0) {
+ days = (Time::Now() - Time::FromInternalValue(last_ping)).InDays();
+ if (days < 0) {
+ // If |days| is negative, then the system clock must have jumped
+ // back in time since the ping was sent. Mark the value so that
+ // it doesn't get sent to the server but we still update the
+ // last ping daystart preference. This way the next ping time
+ // will be correct, hopefully.
+ days = kPingTimeJump;
+ LOG(WARNING) <<
+ "System clock jumped back in time. Resetting ping daystarts.";
+ }
+ }
+ return days;
+}
+
+void OmahaRequestAction::InitPingDays() {
+ // We send pings only along with update checks, not with events.
+ if (IsEvent()) {
+ return;
+ }
+ // TODO(petkov): Figure a way to distinguish active use pings
+ // vs. roll call pings. Currently, the two pings are identical. A
+ // fix needs to change this code as well as UpdateLastPingDays and ShouldPing.
+ ping_active_days_ = CalculatePingDays(kPrefsLastActivePingDay);
+ ping_roll_call_days_ = CalculatePingDays(kPrefsLastRollCallPingDay);
+}
+
+bool OmahaRequestAction::ShouldPing() const {
+ if (ping_active_days_ == OmahaRequestAction::kNeverPinged &&
+ ping_roll_call_days_ == OmahaRequestAction::kNeverPinged) {
+ int powerwash_count = system_state_->hardware()->GetPowerwashCount();
+ if (powerwash_count > 0) {
+ LOG(INFO) << "Not sending ping with a=-1 r=-1 to omaha because "
+ << "powerwash_count is " << powerwash_count;
+ return false;
+ }
+ return true;
+ }
+ return ping_active_days_ > 0 || ping_roll_call_days_ > 0;
+}
+
+// static
+int OmahaRequestAction::GetInstallDate(SystemState* system_state) {
+ PrefsInterface* prefs = system_state->prefs();
+ if (prefs == nullptr)
+ return -1;
+
+ // If we have the value stored on disk, just return it.
+ int64_t stored_value;
+ if (prefs->GetInt64(kPrefsInstallDateDays, &stored_value)) {
+ // Convert and sanity-check.
+ int install_date_days = static_cast<int>(stored_value);
+ if (install_date_days >= 0)
+ return install_date_days;
+ LOG(ERROR) << "Dropping stored Omaha InstallData since its value num_days="
+ << install_date_days << " looks suspicious.";
+ prefs->Delete(kPrefsInstallDateDays);
+ }
+
+ // Otherwise, if OOBE is not complete then do nothing and wait for
+ // ParseResponse() to call ParseInstallDate() and then
+ // PersistInstallDate() to set the kPrefsInstallDateDays state
+ // variable. Once that is done, we'll then report back in future
+ // Omaha requests. This works exactly because OOBE triggers an
+ // update check.
+ //
+ // However, if OOBE is complete and the kPrefsInstallDateDays state
+ // variable is not set, there are two possibilities
+ //
+ // 1. The update check in OOBE failed so we never got a response
+ // from Omaha (no network etc.); or
+ //
+ // 2. OOBE was done on an older version that didn't write to the
+ // kPrefsInstallDateDays state variable.
+ //
+ // In both cases, we approximate the install date by simply
+ // inspecting the timestamp of when OOBE happened.
+
+ Time time_of_oobe;
+ if (!system_state->hardware()->IsOOBEComplete(&time_of_oobe)) {
+ LOG(INFO) << "Not generating Omaha InstallData as we have "
+ << "no prefs file and OOBE is not complete.";
+ return -1;
+ }
+
+ int num_days;
+ if (!utils::ConvertToOmahaInstallDate(time_of_oobe, &num_days)) {
+ LOG(ERROR) << "Not generating Omaha InstallData from time of OOBE "
+ << "as its value '" << utils::ToString(time_of_oobe)
+ << "' looks suspicious.";
+ return -1;
+ }
+
+ // Persist this to disk, for future use.
+ if (!OmahaRequestAction::PersistInstallDate(system_state,
+ num_days,
+ kProvisionedFromOOBEMarker))
+ return -1;
+
+ LOG(INFO) << "Set the Omaha InstallDate from OOBE time-stamp to "
+ << num_days << " days";
+
+ return num_days;
+}
+
+void OmahaRequestAction::PerformAction() {
+ http_fetcher_->set_delegate(this);
+ InitPingDays();
+ if (ping_only_ && !ShouldPing()) {
+ processor_->ActionComplete(this, ErrorCode::kSuccess);
+ return;
+ }
+
+ string request_post(GetRequestXml(event_.get(),
+ params_,
+ ping_only_,
+ ShouldPing(), // include_ping
+ ping_active_days_,
+ ping_roll_call_days_,
+ GetInstallDate(system_state_),
+ system_state_));
+
+ http_fetcher_->SetPostData(request_post.data(), request_post.size(),
+ kHttpContentTypeTextXml);
+ LOG(INFO) << "Posting an Omaha request to " << params_->update_url();
+ LOG(INFO) << "Request: " << request_post;
+ http_fetcher_->BeginTransfer(params_->update_url());
+}
+
+void OmahaRequestAction::TerminateProcessing() {
+ http_fetcher_->TerminateTransfer();
+}
+
+// We just store the response in the buffer. Once we've received all bytes,
+// we'll look in the buffer and decide what to do.
+void OmahaRequestAction::ReceivedBytes(HttpFetcher *fetcher,
+ const void* bytes,
+ size_t length) {
+ const uint8_t* byte_ptr = reinterpret_cast<const uint8_t*>(bytes);
+ response_buffer_.insert(response_buffer_.end(), byte_ptr, byte_ptr + length);
+}
+
+namespace {
+
+// Parses a 64 bit base-10 int from a string and returns it. Returns 0
+// on error. If the string contains "0", that's indistinguishable from
+// error.
+off_t ParseInt(const string& str) {
+ off_t ret = 0;
+ int rc = sscanf(str.c_str(), "%" PRIi64, &ret); // NOLINT(runtime/printf)
+ if (rc < 1) {
+ // failure
+ return 0;
+ }
+ return ret;
+}
+
+// Parses |str| and returns |true| if, and only if, its value is "true".
+bool ParseBool(const string& str) {
+ return str == "true";
+}
+
+// Update the last ping day preferences based on the server daystart
+// response. Returns true on success, false otherwise.
+bool UpdateLastPingDays(OmahaParserData *parser_data, PrefsInterface* prefs) {
+ int64_t elapsed_seconds = 0;
+ TEST_AND_RETURN_FALSE(
+ base::StringToInt64(parser_data->daystart_elapsed_seconds,
+ &elapsed_seconds));
+ TEST_AND_RETURN_FALSE(elapsed_seconds >= 0);
+
+ // Remember the local time that matches the server's last midnight
+ // time.
+ Time daystart = Time::Now() - TimeDelta::FromSeconds(elapsed_seconds);
+ prefs->SetInt64(kPrefsLastActivePingDay, daystart.ToInternalValue());
+ prefs->SetInt64(kPrefsLastRollCallPingDay, daystart.ToInternalValue());
+ return true;
+}
+} // namespace
+
+bool OmahaRequestAction::ParseResponse(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ if (parser_data->updatecheck_status.empty()) {
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ // chromium-os:37289: The PollInterval is not supported by Omaha server
+ // currently. But still keeping this existing code in case we ever decide to
+ // slow down the request rate from the server-side. Note that the PollInterval
+ // is not persisted, so it has to be sent by the server on every response to
+ // guarantee that the scheduler uses this value (otherwise, if the device got
+ // rebooted after the last server-indicated value, it'll revert to the default
+ // value). Also kDefaultMaxUpdateChecks value for the scattering logic is
+ // based on the assumption that we perform an update check every hour so that
+ // the max value of 8 will roughly be equivalent to one work day. If we decide
+ // to use PollInterval permanently, we should update the
+ // max_update_checks_allowed to take PollInterval into account. Note: The
+ // parsing for PollInterval happens even before parsing of the status because
+ // we may want to specify the PollInterval even when there's no update.
+ base::StringToInt(parser_data->updatecheck_poll_interval,
+ &output_object->poll_interval);
+
+ // Check for the "elapsed_days" attribute in the "daystart"
+ // element. This is the number of days since Jan 1 2007, 0:00
+ // PST. If we don't have a persisted value of the Omaha InstallDate,
+ // we'll use it to calculate it and then persist it.
+ if (ParseInstallDate(parser_data, output_object) &&
+ !HasInstallDate(system_state_)) {
+ // Since output_object->install_date_days is never negative, the
+ // elapsed_days -> install-date calculation is reduced to simply
+ // rounding down to the nearest number divisible by 7.
+ int remainder = output_object->install_date_days % 7;
+ int install_date_days_rounded =
+ output_object->install_date_days - remainder;
+ if (PersistInstallDate(system_state_,
+ install_date_days_rounded,
+ kProvisionedFromOmahaResponse)) {
+ LOG(INFO) << "Set the Omaha InstallDate from Omaha Response to "
+ << install_date_days_rounded << " days";
+ }
+ }
+
+ // We persist the cohorts sent by omaha even if the status is "noupdate".
+ if (parser_data->app_cohort_set)
+ PersistCohortData(kPrefsOmahaCohort, parser_data->app_cohort);
+ if (parser_data->app_cohorthint_set)
+ PersistCohortData(kPrefsOmahaCohortHint, parser_data->app_cohorthint);
+ if (parser_data->app_cohortname_set)
+ PersistCohortData(kPrefsOmahaCohortName, parser_data->app_cohortname);
+
+ if (!ParseStatus(parser_data, output_object, completer))
+ return false;
+
+ // Note: ParseUrls MUST be called before ParsePackage as ParsePackage
+ // appends the package name to the URLs populated in this method.
+ if (!ParseUrls(parser_data, output_object, completer))
+ return false;
+
+ if (!ParsePackage(parser_data, output_object, completer))
+ return false;
+
+ if (!ParseParams(parser_data, output_object, completer))
+ return false;
+
+ return true;
+}
+
+bool OmahaRequestAction::ParseStatus(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ const string& status = parser_data->updatecheck_status;
+ if (status == "noupdate") {
+ LOG(INFO) << "No update.";
+ output_object->update_exists = false;
+ SetOutputObject(*output_object);
+ completer->set_code(ErrorCode::kSuccess);
+ return false;
+ }
+
+ if (status != "ok") {
+ LOG(ERROR) << "Unknown Omaha response status: " << status;
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ return true;
+}
+
+bool OmahaRequestAction::ParseUrls(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ if (parser_data->url_codebase.empty()) {
+ LOG(ERROR) << "No Omaha Response URLs";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ LOG(INFO) << "Found " << parser_data->url_codebase.size() << " url(s)";
+ output_object->payload_urls.clear();
+ for (const auto& codebase : parser_data->url_codebase) {
+ if (codebase.empty()) {
+ LOG(ERROR) << "Omaha Response URL has empty codebase";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ output_object->payload_urls.push_back(codebase);
+ }
+
+ return true;
+}
+
+bool OmahaRequestAction::ParsePackage(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ if (parser_data->package_name.empty()) {
+ LOG(ERROR) << "Omaha Response has empty package name";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ // Append the package name to each URL in our list so that we don't
+ // propagate the urlBase vs packageName distinctions beyond this point.
+ // From now on, we only need to use payload_urls.
+ for (auto& payload_url : output_object->payload_urls)
+ payload_url += parser_data->package_name;
+
+ // Parse the payload size.
+ off_t size = ParseInt(parser_data->package_size);
+ if (size <= 0) {
+ LOG(ERROR) << "Omaha Response has invalid payload size: " << size;
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ output_object->size = size;
+
+ LOG(INFO) << "Payload size = " << output_object->size << " bytes";
+
+ return true;
+}
+
+bool OmahaRequestAction::ParseParams(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ output_object->version = parser_data->manifest_version;
+ if (output_object->version.empty()) {
+ LOG(ERROR) << "Omaha Response does not have version in manifest!";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ LOG(INFO) << "Received omaha response to update to version "
+ << output_object->version;
+
+ map<string, string> attrs = parser_data->action_postinstall_attrs;
+ if (attrs.empty()) {
+ LOG(ERROR) << "Omaha Response has no postinstall event action";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ output_object->hash = attrs[kTagSha256];
+ if (output_object->hash.empty()) {
+ LOG(ERROR) << "Omaha Response has empty sha256 value";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ // Get the optional properties one by one.
+ output_object->more_info_url = attrs[kTagMoreInfo];
+ output_object->metadata_size = ParseInt(attrs[kTagMetadataSize]);
+ output_object->metadata_signature = attrs[kTagMetadataSignatureRsa];
+ output_object->prompt = ParseBool(attrs[kTagPrompt]);
+ output_object->deadline = attrs[kTagDeadline];
+ output_object->max_days_to_scatter = ParseInt(attrs[kTagMaxDaysToScatter]);
+ output_object->disable_p2p_for_downloading =
+ ParseBool(attrs[kTagDisableP2PForDownloading]);
+ output_object->disable_p2p_for_sharing =
+ ParseBool(attrs[kTagDisableP2PForSharing]);
+ output_object->public_key_rsa = attrs[kTagPublicKeyRsa];
+
+ string max = attrs[kTagMaxFailureCountPerUrl];
+ if (!base::StringToUint(max, &output_object->max_failure_count_per_url))
+ output_object->max_failure_count_per_url = kDefaultMaxFailureCountPerUrl;
+
+ output_object->is_delta_payload = ParseBool(attrs[kTagIsDeltaPayload]);
+
+ output_object->disable_payload_backoff =
+ ParseBool(attrs[kTagDisablePayloadBackoff]);
+
+ return true;
+}
+
+// If the transfer was successful, this uses expat to parse the response
+// and fill in the appropriate fields of the output object. Also, notifies
+// the processor that we're done.
+void OmahaRequestAction::TransferComplete(HttpFetcher *fetcher,
+ bool successful) {
+ ScopedActionCompleter completer(processor_, this);
+ string current_response(response_buffer_.begin(), response_buffer_.end());
+ LOG(INFO) << "Omaha request response: " << current_response;
+
+ PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+ // Events are best effort transactions -- assume they always succeed.
+ if (IsEvent()) {
+ CHECK(!HasOutputPipe()) << "No output pipe allowed for event requests.";
+ if (event_->result == OmahaEvent::kResultError && successful &&
+ system_state_->hardware()->IsOfficialBuild()) {
+ LOG(INFO) << "Signalling Crash Reporter.";
+ utils::ScheduleCrashReporterUpload();
+ }
+ completer.set_code(ErrorCode::kSuccess);
+ return;
+ }
+
+ if (!successful) {
+ LOG(ERROR) << "Omaha request network transfer failed.";
+ int code = GetHTTPResponseCode();
+ // Makes sure we send sane error values.
+ if (code < 0 || code >= 1000) {
+ code = 999;
+ }
+ completer.set_code(static_cast<ErrorCode>(
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + code));
+ return;
+ }
+
+ XML_Parser parser = XML_ParserCreate(nullptr);
+ OmahaParserData parser_data(parser);
+ XML_SetUserData(parser, &parser_data);
+ XML_SetElementHandler(parser, ParserHandlerStart, ParserHandlerEnd);
+ XML_SetEntityDeclHandler(parser, ParserHandlerEntityDecl);
+ XML_Status res = XML_Parse(
+ parser,
+ reinterpret_cast<const char*>(response_buffer_.data()),
+ response_buffer_.size(),
+ XML_TRUE);
+ XML_ParserFree(parser);
+
+ if (res != XML_STATUS_OK || parser_data.failed) {
+ LOG(ERROR) << "Omaha response not valid XML: "
+ << XML_ErrorString(XML_GetErrorCode(parser))
+ << " at line " << XML_GetCurrentLineNumber(parser)
+ << " col " << XML_GetCurrentColumnNumber(parser);
+ ErrorCode error_code = ErrorCode::kOmahaRequestXMLParseError;
+ if (response_buffer_.empty()) {
+ error_code = ErrorCode::kOmahaRequestEmptyResponseError;
+ } else if (parser_data.entity_decl) {
+ error_code = ErrorCode::kOmahaRequestXMLHasEntityDecl;
+ }
+ completer.set_code(error_code);
+ return;
+ }
+
+ // Update the last ping day preferences based on the server daystart response
+ // even if we didn't send a ping. Omaha always includes the daystart in the
+ // response, but log the error if it didn't.
+ LOG_IF(ERROR, !UpdateLastPingDays(&parser_data, system_state_->prefs()))
+ << "Failed to update the last ping day preferences!";
+
+ if (!HasOutputPipe()) {
+ // Just set success to whether or not the http transfer succeeded,
+ // which must be true at this point in the code.
+ completer.set_code(ErrorCode::kSuccess);
+ return;
+ }
+
+ OmahaResponse output_object;
+ if (!ParseResponse(&parser_data, &output_object, &completer))
+ return;
+ output_object.update_exists = true;
+ SetOutputObject(output_object);
+
+ if (ShouldIgnoreUpdate(output_object)) {
+ output_object.update_exists = false;
+ completer.set_code(ErrorCode::kOmahaUpdateIgnoredPerPolicy);
+ return;
+ }
+
+ // If Omaha says to disable p2p, respect that
+ if (output_object.disable_p2p_for_downloading) {
+ LOG(INFO) << "Forcibly disabling use of p2p for downloading as "
+ << "requested by Omaha.";
+ payload_state->SetUsingP2PForDownloading(false);
+ }
+ if (output_object.disable_p2p_for_sharing) {
+ LOG(INFO) << "Forcibly disabling use of p2p for sharing as "
+ << "requested by Omaha.";
+ payload_state->SetUsingP2PForSharing(false);
+ }
+
+ // Update the payload state with the current response. The payload state
+ // will automatically reset all stale state if this response is different
+ // from what's stored already. We are updating the payload state as late
+ // as possible in this method so that if a new release gets pushed and then
+ // got pulled back due to some issues, we don't want to clear our internal
+ // state unnecessarily.
+ payload_state->SetResponse(output_object);
+
+ // It could be we've already exceeded the deadline for when p2p is
+ // allowed or that we've tried too many times with p2p. Check that.
+ if (payload_state->GetUsingP2PForDownloading()) {
+ payload_state->P2PNewAttempt();
+ if (!payload_state->P2PAttemptAllowed()) {
+ LOG(INFO) << "Forcibly disabling use of p2p for downloading because "
+ << "of previous failures when using p2p.";
+ payload_state->SetUsingP2PForDownloading(false);
+ }
+ }
+
+ // From here on, we'll complete stuff in CompleteProcessing() so
+ // disable |completer| since we'll create a new one in that
+ // function.
+ completer.set_should_complete(false);
+
+ // If we're allowed to use p2p for downloading we do not pay
+ // attention to wall-clock-based waiting if the URL is indeed
+ // available via p2p. Therefore, check if the file is available via
+ // p2p before deferring...
+ if (payload_state->GetUsingP2PForDownloading()) {
+ LookupPayloadViaP2P(output_object);
+ } else {
+ CompleteProcessing();
+ }
+}
+
+void OmahaRequestAction::CompleteProcessing() {
+ ScopedActionCompleter completer(processor_, this);
+ OmahaResponse& output_object = const_cast<OmahaResponse&>(GetOutputObject());
+ PayloadStateInterface* payload_state = system_state_->payload_state();
+
+ if (ShouldDeferDownload(&output_object)) {
+ output_object.update_exists = false;
+ LOG(INFO) << "Ignoring Omaha updates as updates are deferred by policy.";
+ completer.set_code(ErrorCode::kOmahaUpdateDeferredPerPolicy);
+ return;
+ }
+
+ if (payload_state->ShouldBackoffDownload()) {
+ output_object.update_exists = false;
+ LOG(INFO) << "Ignoring Omaha updates in order to backoff our retry "
+ << "attempts";
+ completer.set_code(ErrorCode::kOmahaUpdateDeferredForBackoff);
+ return;
+ }
+ completer.set_code(ErrorCode::kSuccess);
+}
+
+void OmahaRequestAction::OnLookupPayloadViaP2PCompleted(const string& url) {
+ LOG(INFO) << "Lookup complete, p2p-client returned URL '" << url << "'";
+ if (!url.empty()) {
+ system_state_->payload_state()->SetP2PUrl(url);
+ } else {
+ LOG(INFO) << "Forcibly disabling use of p2p for downloading "
+ << "because no suitable peer could be found.";
+ system_state_->payload_state()->SetUsingP2PForDownloading(false);
+ }
+ CompleteProcessing();
+}
+
+void OmahaRequestAction::LookupPayloadViaP2P(const OmahaResponse& response) {
+ // If the device is in the middle of an update, the state variables
+ // kPrefsUpdateStateNextDataOffset, kPrefsUpdateStateNextDataLength
+ // tracks the offset and length of the operation currently in
+ // progress. The offset is based from the end of the manifest which
+ // is kPrefsManifestMetadataSize bytes long.
+ //
+ // To make forward progress and avoid deadlocks, we need to find a
+ // peer that has at least the entire operation we're currently
+ // working on. Otherwise we may end up in a situation where two
+ // devices bounce back and forth downloading from each other,
+ // neither making any forward progress until one of them decides to
+ // stop using p2p (via kMaxP2PAttempts and kMaxP2PAttemptTimeSeconds
+ // safe-guards). See http://crbug.com/297170 for an example)
+ size_t minimum_size = 0;
+ int64_t manifest_metadata_size = 0;
+ int64_t next_data_offset = 0;
+ int64_t next_data_length = 0;
+ if (system_state_ &&
+ system_state_->prefs()->GetInt64(kPrefsManifestMetadataSize,
+ &manifest_metadata_size) &&
+ manifest_metadata_size != -1 &&
+ system_state_->prefs()->GetInt64(kPrefsUpdateStateNextDataOffset,
+ &next_data_offset) &&
+ next_data_offset != -1 &&
+ system_state_->prefs()->GetInt64(kPrefsUpdateStateNextDataLength,
+ &next_data_length)) {
+ minimum_size = manifest_metadata_size + next_data_offset + next_data_length;
+ }
+
+ string file_id = utils::CalculateP2PFileId(response.hash, response.size);
+ if (system_state_->p2p_manager()) {
+ LOG(INFO) << "Checking if payload is available via p2p, file_id="
+ << file_id << " minimum_size=" << minimum_size;
+ system_state_->p2p_manager()->LookupUrlForFile(
+ file_id,
+ minimum_size,
+ TimeDelta::FromSeconds(kMaxP2PNetworkWaitTimeSeconds),
+ base::Bind(&OmahaRequestAction::OnLookupPayloadViaP2PCompleted,
+ base::Unretained(this)));
+ }
+}
+
+bool OmahaRequestAction::ShouldDeferDownload(OmahaResponse* output_object) {
+ if (params_->interactive()) {
+ LOG(INFO) << "Not deferring download because update is interactive.";
+ return false;
+ }
+
+ // If we're using p2p to download _and_ we have a p2p URL, we never
+ // defer the download. This is because the download will always
+ // happen from a peer on the LAN and we've been waiting in line for
+ // our turn.
+ const PayloadStateInterface* payload_state = system_state_->payload_state();
+ if (payload_state->GetUsingP2PForDownloading() &&
+ !payload_state->GetP2PUrl().empty()) {
+ LOG(INFO) << "Download not deferred because download "
+ << "will happen from a local peer (via p2p).";
+ return false;
+ }
+
+ // We should defer the downloads only if we've first satisfied the
+ // wall-clock-based-waiting period and then the update-check-based waiting
+ // period, if required.
+ if (!params_->wall_clock_based_wait_enabled()) {
+ LOG(INFO) << "Wall-clock-based waiting period is not enabled,"
+ << " so no deferring needed.";
+ return false;
+ }
+
+ switch (IsWallClockBasedWaitingSatisfied(output_object)) {
+ case kWallClockWaitNotSatisfied:
+ // We haven't even satisfied the first condition, passing the
+ // wall-clock-based waiting period, so we should defer the downloads
+ // until that happens.
+ LOG(INFO) << "wall-clock-based-wait not satisfied.";
+ return true;
+
+ case kWallClockWaitDoneButUpdateCheckWaitRequired:
+ LOG(INFO) << "wall-clock-based-wait satisfied and "
+ << "update-check-based-wait required.";
+ return !IsUpdateCheckCountBasedWaitingSatisfied();
+
+ case kWallClockWaitDoneAndUpdateCheckWaitNotRequired:
+ // Wall-clock-based waiting period is satisfied, and it's determined
+ // that we do not need the update-check-based wait. so no need to
+ // defer downloads.
+ LOG(INFO) << "wall-clock-based-wait satisfied and "
+ << "update-check-based-wait is not required.";
+ return false;
+
+ default:
+ // Returning false for this default case so we err on the
+ // side of downloading updates than deferring in case of any bugs.
+ NOTREACHED();
+ return false;
+ }
+}
+
+OmahaRequestAction::WallClockWaitResult
+OmahaRequestAction::IsWallClockBasedWaitingSatisfied(
+ OmahaResponse* output_object) {
+ Time update_first_seen_at;
+ int64_t update_first_seen_at_int;
+
+ if (system_state_->prefs()->Exists(kPrefsUpdateFirstSeenAt)) {
+ if (system_state_->prefs()->GetInt64(kPrefsUpdateFirstSeenAt,
+ &update_first_seen_at_int)) {
+ // Note: This timestamp could be that of ANY update we saw in the past
+ // (not necessarily this particular update we're considering to apply)
+ // but never got to apply because of some reason (e.g. stop AU policy,
+ // updates being pulled out from Omaha, changes in target version prefix,
+ // new update being rolled out, etc.). But for the purposes of scattering
+ // it doesn't matter which update the timestamp corresponds to. i.e.
+ // the clock starts ticking the first time we see an update and we're
+ // ready to apply when the random wait period is satisfied relative to
+ // that first seen timestamp.
+ update_first_seen_at = Time::FromInternalValue(update_first_seen_at_int);
+ LOG(INFO) << "Using persisted value of UpdateFirstSeenAt: "
+ << utils::ToString(update_first_seen_at);
+ } else {
+ // This seems like an unexpected error where the persisted value exists
+ // but it's not readable for some reason. Just skip scattering in this
+ // case to be safe.
+ LOG(INFO) << "Not scattering as UpdateFirstSeenAt value cannot be read";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+ } else {
+ update_first_seen_at = Time::Now();
+ update_first_seen_at_int = update_first_seen_at.ToInternalValue();
+ if (system_state_->prefs()->SetInt64(kPrefsUpdateFirstSeenAt,
+ update_first_seen_at_int)) {
+ LOG(INFO) << "Persisted the new value for UpdateFirstSeenAt: "
+ << utils::ToString(update_first_seen_at);
+ } else {
+ // This seems like an unexpected error where the value cannot be
+ // persisted for some reason. Just skip scattering in this
+ // case to be safe.
+ LOG(INFO) << "Not scattering as UpdateFirstSeenAt value "
+ << utils::ToString(update_first_seen_at)
+ << " cannot be persisted";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+ }
+
+ TimeDelta elapsed_time = Time::Now() - update_first_seen_at;
+ TimeDelta max_scatter_period = TimeDelta::FromDays(
+ output_object->max_days_to_scatter);
+
+ LOG(INFO) << "Waiting Period = "
+ << utils::FormatSecs(params_->waiting_period().InSeconds())
+ << ", Time Elapsed = "
+ << utils::FormatSecs(elapsed_time.InSeconds())
+ << ", MaxDaysToScatter = "
+ << max_scatter_period.InDays();
+
+ if (!output_object->deadline.empty()) {
+ // The deadline is set for all rules which serve a delta update from a
+ // previous FSI, which means this update will be applied mostly in OOBE
+ // cases. For these cases, we shouldn't scatter so as to finish the OOBE
+ // quickly.
+ LOG(INFO) << "Not scattering as deadline flag is set";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ if (max_scatter_period.InDays() == 0) {
+ // This means the Omaha rule creator decides that this rule
+ // should not be scattered irrespective of the policy.
+ LOG(INFO) << "Not scattering as MaxDaysToScatter in rule is 0.";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ if (elapsed_time > max_scatter_period) {
+ // This means we've waited more than the upperbound wait in the rule
+ // from the time we first saw a valid update available to us.
+ // This will prevent update starvation.
+ LOG(INFO) << "Not scattering as we're past the MaxDaysToScatter limit.";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ // This means we are required to participate in scattering.
+ // See if our turn has arrived now.
+ TimeDelta remaining_wait_time = params_->waiting_period() - elapsed_time;
+ if (remaining_wait_time.InSeconds() <= 0) {
+ // Yes, it's our turn now.
+ LOG(INFO) << "Successfully passed the wall-clock-based-wait.";
+
+ // But we can't download until the update-check-count-based wait is also
+ // satisfied, so mark it as required now if update checks are enabled.
+ return params_->update_check_count_wait_enabled() ?
+ kWallClockWaitDoneButUpdateCheckWaitRequired :
+ kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ // Not our turn yet, so we have to wait until our turn to
+ // help scatter the downloads across all clients of the enterprise.
+ LOG(INFO) << "Update deferred for another "
+ << utils::FormatSecs(remaining_wait_time.InSeconds())
+ << " per policy.";
+ return kWallClockWaitNotSatisfied;
+}
+
+bool OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied() {
+ int64_t update_check_count_value;
+
+ if (system_state_->prefs()->Exists(kPrefsUpdateCheckCount)) {
+ if (!system_state_->prefs()->GetInt64(kPrefsUpdateCheckCount,
+ &update_check_count_value)) {
+ // We are unable to read the update check count from file for some reason.
+ // So let's proceed anyway so as to not stall the update.
+ LOG(ERROR) << "Unable to read update check count. "
+ << "Skipping update-check-count-based-wait.";
+ return true;
+ }
+ } else {
+ // This file does not exist. This means we haven't started our update
+ // check count down yet, so this is the right time to start the count down.
+ update_check_count_value = base::RandInt(
+ params_->min_update_checks_needed(),
+ params_->max_update_checks_allowed());
+
+ LOG(INFO) << "Randomly picked update check count value = "
+ << update_check_count_value;
+
+ // Write out the initial value of update_check_count_value.
+ if (!system_state_->prefs()->SetInt64(kPrefsUpdateCheckCount,
+ update_check_count_value)) {
+ // We weren't able to write the update check count file for some reason.
+ // So let's proceed anyway so as to not stall the update.
+ LOG(ERROR) << "Unable to write update check count. "
+ << "Skipping update-check-count-based-wait.";
+ return true;
+ }
+ }
+
+ if (update_check_count_value == 0) {
+ LOG(INFO) << "Successfully passed the update-check-based-wait.";
+ return true;
+ }
+
+ if (update_check_count_value < 0 ||
+ update_check_count_value > params_->max_update_checks_allowed()) {
+ // We err on the side of skipping scattering logic instead of stalling
+ // a machine from receiving any updates in case of any unexpected state.
+ LOG(ERROR) << "Invalid value for update check count detected. "
+ << "Skipping update-check-count-based-wait.";
+ return true;
+ }
+
+ // Legal value, we need to wait for more update checks to happen
+ // until this becomes 0.
+ LOG(INFO) << "Deferring Omaha updates for another "
+ << update_check_count_value
+ << " update checks per policy";
+ return false;
+}
+
+// static
+bool OmahaRequestAction::ParseInstallDate(OmahaParserData* parser_data,
+ OmahaResponse* output_object) {
+ int64_t elapsed_days = 0;
+ if (!base::StringToInt64(parser_data->daystart_elapsed_days,
+ &elapsed_days))
+ return false;
+
+ if (elapsed_days < 0)
+ return false;
+
+ output_object->install_date_days = elapsed_days;
+ return true;
+}
+
+// static
+bool OmahaRequestAction::HasInstallDate(SystemState *system_state) {
+ PrefsInterface* prefs = system_state->prefs();
+ if (prefs == nullptr)
+ return false;
+
+ return prefs->Exists(kPrefsInstallDateDays);
+}
+
+// static
+bool OmahaRequestAction::PersistInstallDate(
+ SystemState *system_state,
+ int install_date_days,
+ InstallDateProvisioningSource source) {
+ TEST_AND_RETURN_FALSE(install_date_days >= 0);
+
+ PrefsInterface* prefs = system_state->prefs();
+ if (prefs == nullptr)
+ return false;
+
+ if (!prefs->SetInt64(kPrefsInstallDateDays, install_date_days))
+ return false;
+
+ string metric_name = "Installer.InstallDateProvisioningSource";
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric_name,
+ static_cast<int>(source), // Sample.
+ kProvisionedMax); // Maximum.
+
+ metric_name = metrics::kMetricInstallDateProvisioningSource;
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric_name,
+ static_cast<int>(source), // Sample.
+ kProvisionedMax); // Maximum.
+
+ return true;
+}
+
+bool OmahaRequestAction::PersistCohortData(
+ const string& prefs_key,
+ const string& new_value) {
+ if (new_value.empty() && system_state_->prefs()->Exists(prefs_key)) {
+ LOG(INFO) << "Removing stored " << prefs_key << " value.";
+ return system_state_->prefs()->Delete(prefs_key);
+ } else if (!new_value.empty()) {
+ LOG(INFO) << "Storing new setting " << prefs_key << " as " << new_value;
+ return system_state_->prefs()->SetString(prefs_key, new_value);
+ }
+ return true;
+}
+
+void OmahaRequestAction::ActionCompleted(ErrorCode code) {
+ // We only want to report this on "update check".
+ if (ping_only_ || event_ != nullptr)
+ return;
+
+ metrics::CheckResult result = metrics::CheckResult::kUnset;
+ metrics::CheckReaction reaction = metrics::CheckReaction::kUnset;
+ metrics::DownloadErrorCode download_error_code =
+ metrics::DownloadErrorCode::kUnset;
+
+ // Regular update attempt.
+ switch (code) {
+ case ErrorCode::kSuccess:
+ // OK, we parsed the response successfully but that does
+ // necessarily mean that an update is available.
+ if (HasOutputPipe()) {
+ const OmahaResponse& response = GetOutputObject();
+ if (response.update_exists) {
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kUpdating;
+ } else {
+ result = metrics::CheckResult::kNoUpdateAvailable;
+ }
+ } else {
+ result = metrics::CheckResult::kNoUpdateAvailable;
+ }
+ break;
+
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kIgnored;
+ break;
+
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kDeferring;
+ break;
+
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kBackingOff;
+ break;
+
+ default:
+ // We report two flavors of errors, "Download errors" and "Parsing
+ // error". Try to convert to the former and if that doesn't work
+ // we know it's the latter.
+ metrics::DownloadErrorCode tmp_error = utils::GetDownloadErrorCode(code);
+ if (tmp_error != metrics::DownloadErrorCode::kInputMalformed) {
+ result = metrics::CheckResult::kDownloadError;
+ download_error_code = tmp_error;
+ } else {
+ result = metrics::CheckResult::kParsingError;
+ }
+ break;
+ }
+
+ metrics::ReportUpdateCheckMetrics(system_state_,
+ result, reaction, download_error_code);
+}
+
+bool OmahaRequestAction::ShouldIgnoreUpdate(
+ const OmahaResponse& response) const {
+ // Note: policy decision to not update to a version we rolled back from.
+ string rollback_version =
+ system_state_->payload_state()->GetRollbackVersion();
+ if (!rollback_version.empty()) {
+ LOG(INFO) << "Detected previous rollback from version " << rollback_version;
+ if (rollback_version == response.version) {
+ LOG(INFO) << "Received version that we rolled back from. Ignoring.";
+ return true;
+ }
+ }
+
+ if (!IsUpdateAllowedOverCurrentConnection()) {
+ LOG(INFO) << "Update is not allowed over current connection.";
+ return true;
+ }
+
+ // Note: We could technically delete the UpdateFirstSeenAt state when we
+ // return true. If we do, it'll mean a device has to restart the
+ // UpdateFirstSeenAt and thus help scattering take effect when the AU is
+ // turned on again. On the other hand, it also increases the chance of update
+ // starvation if an admin turns AU on/off more frequently. We choose to err on
+ // the side of preventing starvation at the cost of not applying scattering in
+ // those cases.
+ return false;
+}
+
+bool OmahaRequestAction::IsUpdateAllowedOverCurrentConnection() const {
+ NetworkConnectionType type;
+ NetworkTethering tethering;
+ RealDBusWrapper dbus_iface;
+ ConnectionManager* connection_manager = system_state_->connection_manager();
+ if (!connection_manager->GetConnectionProperties(&dbus_iface,
+ &type, &tethering)) {
+ LOG(INFO) << "We could not determine our connection type. "
+ << "Defaulting to allow updates.";
+ return true;
+ }
+ bool is_allowed = connection_manager->IsUpdateAllowedOver(type, tethering);
+ LOG(INFO) << "We are connected via "
+ << connection_manager->StringForConnectionType(type)
+ << ", Updates allowed: " << (is_allowed ? "Yes" : "No");
+ return is_allowed;
+}
+
+} // namespace chromeos_update_engine
diff --git a/omaha_request_action.h b/omaha_request_action.h
new file mode 100644
index 0000000..b56af1e
--- /dev/null
+++ b/omaha_request_action.h
@@ -0,0 +1,315 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_
+#define UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+#include <curl/curl.h>
+
+#include "update_engine/action.h"
+#include "update_engine/http_fetcher.h"
+#include "update_engine/omaha_response.h"
+
+// The Omaha Request action makes a request to Omaha and can output
+// the response on the output ActionPipe.
+
+namespace chromeos_update_engine {
+
+// Encodes XML entities in a given string. Input must be ASCII-7 valid. If
+// the input is invalid, the default value is used instead.
+std::string XmlEncodeWithDefault(const std::string& input,
+ const std::string& default_value);
+
+// Escapes text so it can be included as character data and attribute
+// values. The |input| string must be valid ASCII-7, no UTF-8 supported.
+// Returns whether the |input| was valid and escaped properly in |output|.
+bool XmlEncode(const std::string& input, std::string* output);
+
+// This struct encapsulates the Omaha event information. For a
+// complete list of defined event types and results, see
+// http://code.google.com/p/omaha/wiki/ServerProtocol#event
+struct OmahaEvent {
+ // The Type values correspond to EVENT_TYPE values of Omaha.
+ enum Type {
+ kTypeUnknown = 0,
+ kTypeDownloadComplete = 1,
+ kTypeInstallComplete = 2,
+ kTypeUpdateComplete = 3,
+ kTypeUpdateDownloadStarted = 13,
+ kTypeUpdateDownloadFinished = 14,
+ };
+
+ // The Result values correspond to EVENT_RESULT values of Omaha.
+ enum Result {
+ kResultError = 0,
+ kResultSuccess = 1,
+ kResultSuccessReboot = 2,
+ kResultUpdateDeferred = 9, // When we ignore/defer updates due to policy.
+ };
+
+ OmahaEvent()
+ : type(kTypeUnknown),
+ result(kResultError),
+ error_code(ErrorCode::kError) {}
+ explicit OmahaEvent(Type in_type)
+ : type(in_type),
+ result(kResultSuccess),
+ error_code(ErrorCode::kSuccess) {}
+ OmahaEvent(Type in_type, Result in_result, ErrorCode in_error_code)
+ : type(in_type),
+ result(in_result),
+ error_code(in_error_code) {}
+
+ Type type;
+ Result result;
+ ErrorCode error_code;
+};
+
+class NoneType;
+class OmahaRequestAction;
+class OmahaRequestParams;
+class PrefsInterface;
+
+// This struct is declared in the .cc file.
+struct OmahaParserData;
+
+template<>
+class ActionTraits<OmahaRequestAction> {
+ public:
+ // Takes parameters on the input pipe.
+ typedef NoneType InputObjectType;
+ // On UpdateCheck success, puts the Omaha response on output. Event
+ // requests do not have an output pipe.
+ typedef OmahaResponse OutputObjectType;
+};
+
+class OmahaRequestAction : public Action<OmahaRequestAction>,
+ public HttpFetcherDelegate {
+ public:
+ static const int kNeverPinged = -1;
+ static const int kPingTimeJump = -2;
+ // We choose this value of 10 as a heuristic for a work day in trying
+ // each URL, assuming we check roughly every 45 mins. This is a good time to
+ // wait - neither too long nor too little - so we don't give up the preferred
+ // URLs that appear earlier in list too quickly before moving on to the
+ // fallback ones.
+ static const int kDefaultMaxFailureCountPerUrl = 10;
+
+ // These are the possible outcome upon checking whether we satisfied
+ // the wall-clock-based-wait.
+ enum WallClockWaitResult {
+ kWallClockWaitNotSatisfied,
+ kWallClockWaitDoneButUpdateCheckWaitRequired,
+ kWallClockWaitDoneAndUpdateCheckWaitNotRequired,
+ };
+
+ // The ctor takes in all the parameters that will be used for making
+ // the request to Omaha. For some of them we have constants that
+ // should be used.
+ //
+ // Takes ownership of the passed in HttpFetcher. Useful for testing.
+ //
+ // Takes ownership of the passed in OmahaEvent. If |event| is null,
+ // this is an UpdateCheck request, otherwise it's an Event request.
+ // Event requests always succeed.
+ //
+ // A good calling pattern is:
+ // OmahaRequestAction(..., new OmahaEvent(...), new WhateverHttpFetcher);
+ // or
+ // OmahaRequestAction(..., nullptr, new WhateverHttpFetcher);
+ OmahaRequestAction(SystemState* system_state,
+ OmahaEvent* event,
+ HttpFetcher* http_fetcher,
+ bool ping_only);
+ ~OmahaRequestAction() override;
+ typedef ActionTraits<OmahaRequestAction>::InputObjectType InputObjectType;
+ typedef ActionTraits<OmahaRequestAction>::OutputObjectType OutputObjectType;
+ void PerformAction() override;
+ void TerminateProcessing() override;
+ void ActionCompleted(ErrorCode code) override;
+
+ int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
+
+ // Debugging/logging
+ static std::string StaticType() { return "OmahaRequestAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ // Delegate methods (see http_fetcher.h)
+ void ReceivedBytes(HttpFetcher *fetcher,
+ const void* bytes, size_t length) override;
+
+ void TransferComplete(HttpFetcher *fetcher, bool successful) override;
+
+ // Returns true if this is an Event request, false if it's an UpdateCheck.
+ bool IsEvent() const { return event_.get() != nullptr; }
+
+ private:
+ FRIEND_TEST(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE);
+ FRIEND_TEST(OmahaRequestActionTest,
+ GetInstallDateWhenOOBECompletedWithInvalidDate);
+ FRIEND_TEST(OmahaRequestActionTest,
+ GetInstallDateWhenOOBECompletedWithValidDate);
+ FRIEND_TEST(OmahaRequestActionTest,
+ GetInstallDateWhenOOBECompletedDateChanges);
+
+ // Enumeration used in PersistInstallDate().
+ enum InstallDateProvisioningSource {
+ kProvisionedFromOmahaResponse,
+ kProvisionedFromOOBEMarker,
+
+ // kProvisionedMax is the count of the number of enums above. Add
+ // any new enums above this line only.
+ kProvisionedMax
+ };
+
+ // Gets the install date, expressed as the number of PST8PDT
+ // calendar weeks since January 1st 2007, times seven. Returns -1 if
+ // unknown. See http://crbug.com/336838 for details about this value.
+ static int GetInstallDate(SystemState* system_state);
+
+ // Parses the Omaha Response in |doc| and sets the
+ // |install_date_days| field of |output_object| to the value of the
+ // elapsed_days attribute of the daystart element. Returns True if
+ // the value was set, False if it wasn't found.
+ static bool ParseInstallDate(OmahaParserData* parser_data,
+ OmahaResponse* output_object);
+
+ // Returns True if the kPrefsInstallDateDays state variable is set,
+ // False otherwise.
+ static bool HasInstallDate(SystemState *system_state);
+
+ // Writes |install_date_days| into the kPrefsInstallDateDays state
+ // variable and emits an UMA stat for the |source| used. Returns
+ // True if the value was written, False if an error occurred.
+ static bool PersistInstallDate(SystemState *system_state,
+ int install_date_days,
+ InstallDateProvisioningSource source);
+
+ // Persist the new cohort* value received in the XML file in the |prefs_key|
+ // preference file. If the |new_value| is empty, the currently stored value
+ // will be deleted. Don't call this function with an empty |new_value| if the
+ // value was not set in the XML, since that would delete the stored value.
+ bool PersistCohortData(const std::string& prefs_key,
+ const std::string& new_value);
+
+ // If this is an update check request, initializes
+ // |ping_active_days_| and |ping_roll_call_days_| to values that may
+ // be sent as pings to Omaha.
+ void InitPingDays();
+
+ // Based on the persistent preference store values, calculates the
+ // number of days since the last ping sent for |key|.
+ int CalculatePingDays(const std::string& key);
+
+ // Returns whether we have "active_days" or "roll_call_days" ping values to
+ // send to Omaha and thus we should include them in the response.
+ bool ShouldPing() const;
+
+ // Returns true if the download of a new update should be deferred.
+ // False if the update can be downloaded.
+ bool ShouldDeferDownload(OmahaResponse* output_object);
+
+ // Returns true if the basic wall-clock-based waiting period has been
+ // satisfied based on the scattering policy setting. False otherwise.
+ // If true, it also indicates whether the additional update-check-count-based
+ // waiting period also needs to be satisfied before the download can begin.
+ WallClockWaitResult IsWallClockBasedWaitingSatisfied(
+ OmahaResponse* output_object);
+
+ // Returns true if the update-check-count-based waiting period has been
+ // satisfied. False otherwise.
+ bool IsUpdateCheckCountBasedWaitingSatisfied();
+
+ // Parses the response from Omaha that's available in |doc| using the other
+ // helper methods below and populates the |output_object| with the relevant
+ // values. Returns true if we should continue the parsing. False otherwise,
+ // in which case it sets any error code using |completer|.
+ bool ParseResponse(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Parses the status property in the given update_check_node and populates
+ // |output_object| if valid. Returns true if we should continue the parsing.
+ // False otherwise, in which case it sets any error code using |completer|.
+ bool ParseStatus(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Parses the URL nodes in the given XML document and populates
+ // |output_object| if valid. Returns true if we should continue the parsing.
+ // False otherwise, in which case it sets any error code using |completer|.
+ bool ParseUrls(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Parses the package node in the given XML document and populates
+ // |output_object| if valid. Returns true if we should continue the parsing.
+ // False otherwise, in which case it sets any error code using |completer|.
+ bool ParsePackage(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Parses the other parameters in the given XML document and populates
+ // |output_object| if valid. Returns true if we should continue the parsing.
+ // False otherwise, in which case it sets any error code using |completer|.
+ bool ParseParams(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Called by TransferComplete() to complete processing, either
+ // asynchronously after looking up resources via p2p or directly.
+ void CompleteProcessing();
+
+ // Helper to asynchronously look up payload on the LAN.
+ void LookupPayloadViaP2P(const OmahaResponse& response);
+
+ // Callback used by LookupPayloadViaP2P().
+ void OnLookupPayloadViaP2PCompleted(const std::string& url);
+
+ // Returns true if the current update should be ignored.
+ bool ShouldIgnoreUpdate(const OmahaResponse& response) const;
+
+ // Returns true if updates are allowed over the current type of connection.
+ // False otherwise.
+ bool IsUpdateAllowedOverCurrentConnection() const;
+
+ // Global system context.
+ SystemState* system_state_;
+
+ // Contains state that is relevant in the processing of the Omaha request.
+ OmahaRequestParams* params_;
+
+ // Pointer to the OmahaEvent info. This is an UpdateCheck request if null.
+ std::unique_ptr<OmahaEvent> event_;
+
+ // pointer to the HttpFetcher that does the http work
+ std::unique_ptr<HttpFetcher> http_fetcher_;
+
+ // If true, only include the <ping> element in the request.
+ bool ping_only_;
+
+ // Stores the response from the omaha server
+ chromeos::Blob response_buffer_;
+
+ // Initialized by InitPingDays to values that may be sent to Omaha
+ // as part of a ping message. Note that only positive values and -1
+ // are sent to Omaha.
+ int ping_active_days_;
+ int ping_roll_call_days_;
+
+ DISALLOW_COPY_AND_ASSIGN(OmahaRequestAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
new file mode 100644
index 0000000..1131c9c
--- /dev/null
+++ b/omaha_request_action_unittest.cc
@@ -0,0 +1,2117 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/omaha_request_action.h"
+
+#include <glib.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/action_pipe.h"
+#include "update_engine/constants.h"
+#include "update_engine/fake_prefs.h"
+#include "update_engine/mock_connection_manager.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/prefs.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using std::vector;
+using testing::AllOf;
+using testing::AnyNumber;
+using testing::DoAll;
+using testing::Ge;
+using testing::Le;
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::SaveArg;
+using testing::SetArgumentPointee;
+using testing::_;
+
+namespace {
+
+// This is a helper struct to allow unit tests build an update response with the
+// values they care about.
+struct FakeUpdateResponse {
+ string GetNoUpdateResponse() const {
+ string entity_str;
+ if (include_entity)
+ entity_str = "<!DOCTYPE response [<!ENTITY CrOS \"ChromeOS\">]>";
+ return
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+ entity_str + "<response protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"/>"
+ "<app appid=\"" + app_id + "\" " +
+ (include_cohorts ? "cohort=\"" + cohort + "\" cohorthint=\"" +
+ cohorthint + "\" cohortname=\"" + cohortname + "\" " : "") +
+ " status=\"ok\">"
+ "<ping status=\"ok\"/>"
+ "<updatecheck status=\"noupdate\"/></app></response>";
+ }
+
+ string GetUpdateResponse() const {
+ return
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+ "protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"" +
+ (elapsed_days.empty() ? "" : (" elapsed_days=\"" + elapsed_days + "\""))
+ + "/>"
+ "<app appid=\"" + app_id + "\" " +
+ (include_cohorts ? "cohort=\"" + cohort + "\" cohorthint=\"" +
+ cohorthint + "\" cohortname=\"" + cohortname + "\" " : "") +
+ " status=\"ok\">"
+ "<ping status=\"ok\"/><updatecheck status=\"ok\">"
+ "<urls><url codebase=\"" + codebase + "\"/></urls>"
+ "<manifest version=\"" + version + "\">"
+ "<packages><package hash=\"not-used\" name=\"" + filename + "\" "
+ "size=\"" + base::Int64ToString(size) + "\"/></packages>"
+ "<actions><action event=\"postinstall\" "
+ "ChromeOSVersion=\"" + version + "\" "
+ "MoreInfo=\"" + more_info_url + "\" Prompt=\"" + prompt + "\" "
+ "IsDelta=\"true\" "
+ "IsDeltaPayload=\"true\" "
+ "MaxDaysToScatter=\"" + max_days_to_scatter + "\" "
+ "sha256=\"" + hash + "\" "
+ "needsadmin=\"" + needsadmin + "\" " +
+ (deadline.empty() ? "" : ("deadline=\"" + deadline + "\" ")) +
+ (disable_p2p_for_downloading ?
+ "DisableP2PForDownloading=\"true\" " : "") +
+ (disable_p2p_for_sharing ? "DisableP2PForSharing=\"true\" " : "") +
+ "/></actions></manifest></updatecheck></app></response>";
+ }
+
+ // Return the payload URL, which is split in two fields in the XML response.
+ string GetPayloadUrl() {
+ return codebase + filename;
+ }
+
+ string app_id = chromeos_update_engine::OmahaRequestParams::kAppId;
+ string version = "1.2.3.4";
+ string more_info_url = "http://more/info";
+ string prompt = "true";
+ string codebase = "http://code/base/";
+ string filename = "file.signed";
+ string hash = "HASH1234=";
+ string needsadmin = "false";
+ int64_t size = 123;
+ string deadline = "";
+ string max_days_to_scatter = "7";
+ string elapsed_days = "42";
+
+ // P2P setting defaults to allowed.
+ bool disable_p2p_for_downloading = false;
+ bool disable_p2p_for_sharing = false;
+
+ // Omaha cohorts settings.
+ bool include_cohorts = false;
+ string cohort = "";
+ string cohorthint = "";
+ string cohortname = "";
+
+ // Whether to include the CrOS <!ENTITY> in the XML response.
+ bool include_entity = false;
+};
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+class OmahaRequestActionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ fake_system_state_.set_request_params(&request_params_);
+ fake_system_state_.set_prefs(&fake_prefs_);
+ }
+
+ // Returns true iff an output response was obtained from the
+ // OmahaRequestAction. |prefs| may be null, in which case a local MockPrefs
+ // is used. |payload_state| may be null, in which case a local mock is used.
+ // |p2p_manager| may be null, in which case a local mock is used.
+ // |connection_manager| may be null, in which case a local mock is used.
+ // out_response may be null. If |fail_http_response_code| is non-negative,
+ // the transfer will fail with that code. |ping_only| is passed through to the
+ // OmahaRequestAction constructor. out_post_data may be null; if non-null, the
+ // post-data received by the mock HttpFetcher is returned.
+ //
+ // The |expected_check_result|, |expected_check_reaction| and
+ // |expected_error_code| parameters are for checking expectations
+ // about reporting UpdateEngine.Check.{Result,Reaction,DownloadError}
+ // UMA statistics. Use the appropriate ::kUnset value to specify that
+ // the given metric should not be reported.
+ bool TestUpdateCheck(OmahaRequestParams* request_params,
+ const string& http_response,
+ int fail_http_response_code,
+ bool ping_only,
+ ErrorCode expected_code,
+ metrics::CheckResult expected_check_result,
+ metrics::CheckReaction expected_check_reaction,
+ metrics::DownloadErrorCode expected_download_error_code,
+ OmahaResponse* out_response,
+ chromeos::Blob* out_post_data);
+
+ // Runs and checks a ping test. |ping_only| indicates whether it should send
+ // only a ping or also an updatecheck.
+ void PingTest(bool ping_only);
+
+ // InstallDate test helper function.
+ bool InstallDateParseHelper(const string &elapsed_days,
+ OmahaResponse *response);
+
+ // P2P test helper function.
+ void P2PTest(
+ bool initial_allow_p2p_for_downloading,
+ bool initial_allow_p2p_for_sharing,
+ bool omaha_disable_p2p_for_downloading,
+ bool omaha_disable_p2p_for_sharing,
+ bool payload_state_allow_p2p_attempt,
+ bool expect_p2p_client_lookup,
+ const string& p2p_client_result_url,
+ bool expected_allow_p2p_for_downloading,
+ bool expected_allow_p2p_for_sharing,
+ const string& expected_p2p_url);
+
+ FakeSystemState fake_system_state_;
+ FakeUpdateResponse fake_update_response_;
+
+ // By default, all tests use these objects unless they replace them in the
+ // fake_system_state_.
+ OmahaRequestParams request_params_ = OmahaRequestParams{
+ &fake_system_state_,
+ OmahaRequestParams::kOsPlatform,
+ OmahaRequestParams::kOsVersion,
+ "service_pack",
+ "x86-generic",
+ OmahaRequestParams::kAppId,
+ "0.1.0.0",
+ "en-US",
+ "unittest",
+ "OEM MODEL 09235 7471",
+ "ChromeOSFirmware.1.0",
+ "0X0A1",
+ false, // delta okay
+ false, // interactive
+ "http://url",
+ ""}; // target_version_prefix
+
+ FakePrefs fake_prefs_;
+};
+
+namespace {
+class OmahaRequestActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ OmahaRequestActionTestProcessorDelegate()
+ : expected_code_(ErrorCode::kSuccess) {}
+ ~OmahaRequestActionTestProcessorDelegate() override {
+ }
+ void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) override {
+ chromeos::MessageLoop::current()->BreakLoop();
+ }
+
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) override {
+ // make sure actions always succeed
+ if (action->Type() == OmahaRequestAction::StaticType())
+ EXPECT_EQ(expected_code_, code);
+ else
+ EXPECT_EQ(ErrorCode::kSuccess, code);
+ }
+ ErrorCode expected_code_;
+};
+} // namespace
+
+class OutputObjectCollectorAction;
+
+template<>
+class ActionTraits<OutputObjectCollectorAction> {
+ public:
+ // Does not take an object for input
+ typedef OmahaResponse InputObjectType;
+ // On success, puts the output path on output
+ typedef NoneType OutputObjectType;
+};
+
+class OutputObjectCollectorAction : public Action<OutputObjectCollectorAction> {
+ public:
+ OutputObjectCollectorAction() : has_input_object_(false) {}
+ void PerformAction() {
+ // copy input object
+ has_input_object_ = HasInputObject();
+ if (has_input_object_)
+ omaha_response_ = GetInputObject();
+ processor_->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ // Should never be called
+ void TerminateProcessing() {
+ CHECK(false);
+ }
+ // Debugging/logging
+ static string StaticType() {
+ return "OutputObjectCollectorAction";
+ }
+ string Type() const { return StaticType(); }
+ bool has_input_object_;
+ OmahaResponse omaha_response_;
+};
+
+bool OmahaRequestActionTest::TestUpdateCheck(
+ OmahaRequestParams* request_params,
+ const string& http_response,
+ int fail_http_response_code,
+ bool ping_only,
+ ErrorCode expected_code,
+ metrics::CheckResult expected_check_result,
+ metrics::CheckReaction expected_check_reaction,
+ metrics::DownloadErrorCode expected_download_error_code,
+ OmahaResponse* out_response,
+ chromeos::Blob* out_post_data) {
+ chromeos::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+ MockHttpFetcher* fetcher = new MockHttpFetcher(http_response.data(),
+ http_response.size(),
+ nullptr);
+ if (fail_http_response_code >= 0) {
+ fetcher->FailTransfer(fail_http_response_code);
+ }
+ if (request_params)
+ fake_system_state_.set_request_params(request_params);
+ OmahaRequestAction action(&fake_system_state_,
+ nullptr,
+ fetcher,
+ ping_only);
+ OmahaRequestActionTestProcessorDelegate delegate;
+ delegate.expected_code_ = expected_code;
+
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&action);
+
+ OutputObjectCollectorAction collector_action;
+ BondActions(&action, &collector_action);
+ processor.EnqueueAction(&collector_action);
+
+ EXPECT_CALL(*fake_system_state_.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+ SendEnumToUMA(metrics::kMetricCheckResult,
+ static_cast<int>(expected_check_result),
+ static_cast<int>(metrics::CheckResult::kNumConstants) - 1))
+ .Times(expected_check_result == metrics::CheckResult::kUnset ? 0 : 1);
+ EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+ SendEnumToUMA(metrics::kMetricCheckReaction,
+ static_cast<int>(expected_check_reaction),
+ static_cast<int>(metrics::CheckReaction::kNumConstants) - 1))
+ .Times(expected_check_reaction == metrics::CheckReaction::kUnset ? 0 : 1);
+ EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+ SendSparseToUMA(metrics::kMetricCheckDownloadErrorCode,
+ static_cast<int>(expected_download_error_code)))
+ .Times(expected_download_error_code == metrics::DownloadErrorCode::kUnset
+ ? 0 : 1);
+
+ loop.PostTask(base::Bind([&processor] { processor.StartProcessing(); }));
+ LOG(INFO) << "loop.PendingTasks() = " << loop.PendingTasks();
+ loop.Run();
+ LOG(INFO) << "loop.PendingTasks() = " << loop.PendingTasks();
+ EXPECT_FALSE(loop.PendingTasks());
+ if (collector_action.has_input_object_ && out_response)
+ *out_response = collector_action.omaha_response_;
+ if (out_post_data)
+ *out_post_data = fetcher->post_data();
+ return collector_action.has_input_object_;
+}
+
+// Tests Event requests -- they should always succeed. |out_post_data|
+// may be null; if non-null, the post-data received by the mock
+// HttpFetcher is returned.
+void TestEvent(OmahaRequestParams params,
+ OmahaEvent* event,
+ const string& http_response,
+ chromeos::Blob* out_post_data) {
+ chromeos::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+ MockHttpFetcher* fetcher = new MockHttpFetcher(http_response.data(),
+ http_response.size(),
+ nullptr);
+ FakeSystemState fake_system_state;
+ fake_system_state.set_request_params(¶ms);
+ OmahaRequestAction action(&fake_system_state, event, fetcher, false);
+ OmahaRequestActionTestProcessorDelegate delegate;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&action);
+
+ loop.PostTask(base::Bind([&processor] { processor.StartProcessing(); }));
+ loop.Run();
+
+ // This test should schedule a callback to notify the crash reporter if
+ // the passed event is an error.
+ EXPECT_EQ(event->result == OmahaEvent::kResultError, loop.PendingTasks());
+
+ if (out_post_data)
+ *out_post_data = fetcher->post_data();
+}
+
+TEST_F(OmahaRequestActionTest, RejectEntities) {
+ OmahaResponse response;
+ fake_update_response_.include_entity = true;
+ ASSERT_FALSE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetNoUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaRequestXMLHasEntityDecl,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NoUpdateTest) {
+ OmahaResponse response;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetNoUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kNoUpdateAvailable,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+}
+
+// Test that all the values in the response are parsed in a normal update
+// response.
+TEST_F(OmahaRequestActionTest, ValidUpdateTest) {
+ OmahaResponse response;
+ fake_update_response_.deadline = "20101020";
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(fake_update_response_.version, response.version);
+ EXPECT_EQ(fake_update_response_.GetPayloadUrl(), response.payload_urls[0]);
+ EXPECT_EQ(fake_update_response_.more_info_url, response.more_info_url);
+ EXPECT_EQ(fake_update_response_.hash, response.hash);
+ EXPECT_EQ(fake_update_response_.size, response.size);
+ EXPECT_EQ(fake_update_response_.prompt == "true", response.prompt);
+ EXPECT_EQ(fake_update_response_.deadline, response.deadline);
+ // Omaha cohort attribets are not set in the response, so they should not be
+ // persisted.
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohort));
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohortHint));
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohortName));
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByConnection) {
+ OmahaResponse response;
+ // Set up a connection manager that doesn't allow a valid update over
+ // the current ethernet connection.
+ MockConnectionManager mock_cm(nullptr);
+ fake_system_state_.set_connection_manager(&mock_cm);
+
+ EXPECT_CALL(mock_cm, GetConnectionProperties(_, _, _))
+ .WillRepeatedly(DoAll(SetArgumentPointee<1>(kNetEthernet),
+ SetArgumentPointee<2>(NetworkTethering::kUnknown),
+ Return(true)));
+ EXPECT_CALL(mock_cm, IsUpdateAllowedOver(kNetEthernet, _))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(mock_cm, StringForConnectionType(kNetEthernet))
+ .WillRepeatedly(Return(shill::kTypeEthernet));
+
+ ASSERT_FALSE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaUpdateIgnoredPerPolicy,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kIgnored,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByRollback) {
+ string rollback_version = "1234.0.0";
+ OmahaResponse response;
+
+ MockPayloadState mock_payload_state;
+ fake_system_state_.set_payload_state(&mock_payload_state);
+
+ EXPECT_CALL(mock_payload_state, GetRollbackVersion())
+ .WillRepeatedly(Return(rollback_version));
+
+ fake_update_response_.version = rollback_version;
+ ASSERT_FALSE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaUpdateIgnoredPerPolicy,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kIgnored,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, WallClockBasedWaitAloneCausesScattering) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_update_check_count_wait_enabled(false);
+ params.set_waiting_period(TimeDelta::FromDays(2));
+
+ ASSERT_FALSE(
+ TestUpdateCheck(¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaUpdateDeferredPerPolicy,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kDeferring,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+
+ // Verify if we are interactive check we don't defer.
+ params.set_interactive(true);
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NoWallClockBasedWaitCausesNoScattering) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(false);
+ params.set_waiting_period(TimeDelta::FromDays(2));
+
+ params.set_update_check_count_wait_enabled(true);
+ params.set_min_update_checks_needed(1);
+ params.set_max_update_checks_allowed(8);
+
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ZeroMaxDaysToScatterCausesNoScattering) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta::FromDays(2));
+
+ params.set_update_check_count_wait_enabled(true);
+ params.set_min_update_checks_needed(1);
+ params.set_max_update_checks_allowed(8);
+
+ fake_update_response_.max_days_to_scatter = "0";
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_TRUE(response.update_exists);
+}
+
+
+TEST_F(OmahaRequestActionTest, ZeroUpdateCheckCountCausesNoScattering) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta());
+
+ params.set_update_check_count_wait_enabled(true);
+ params.set_min_update_checks_needed(0);
+ params.set_max_update_checks_allowed(0);
+
+ ASSERT_TRUE(TestUpdateCheck(
+ ¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ int64_t count;
+ ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count));
+ ASSERT_EQ(count, 0);
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NonZeroUpdateCheckCountCausesScattering) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta());
+
+ params.set_update_check_count_wait_enabled(true);
+ params.set_min_update_checks_needed(1);
+ params.set_max_update_checks_allowed(8);
+
+ ASSERT_FALSE(TestUpdateCheck(
+ ¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaUpdateDeferredPerPolicy,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kDeferring,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ int64_t count;
+ ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count));
+ ASSERT_GT(count, 0);
+ EXPECT_FALSE(response.update_exists);
+
+ // Verify if we are interactive check we don't defer.
+ params.set_interactive(true);
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ExistingUpdateCheckCountCausesScattering) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta());
+
+ params.set_update_check_count_wait_enabled(true);
+ params.set_min_update_checks_needed(1);
+ params.set_max_update_checks_allowed(8);
+
+ ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsUpdateCheckCount, 5));
+
+ ASSERT_FALSE(TestUpdateCheck(
+ ¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaUpdateDeferredPerPolicy,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kDeferring,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ int64_t count;
+ ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count));
+ // count remains the same, as the decrementing happens in update_attempter
+ // which this test doesn't exercise.
+ ASSERT_EQ(count, 5);
+ EXPECT_FALSE(response.update_exists);
+
+ // Verify if we are interactive check we don't defer.
+ params.set_interactive(true);
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, CohortsArePersisted) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ fake_update_response_.include_cohorts = true;
+ fake_update_response_.cohort = "s/154454/8479665";
+ fake_update_response_.cohorthint = "please-put-me-on-beta";
+ fake_update_response_.cohortname = "stable";
+
+ ASSERT_TRUE(TestUpdateCheck(¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ string value;
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+ EXPECT_EQ(fake_update_response_.cohort, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+ EXPECT_EQ(fake_update_response_.cohorthint, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+ EXPECT_EQ(fake_update_response_.cohortname, value);
+}
+
+TEST_F(OmahaRequestActionTest, CohortsAreUpdated) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value"));
+ EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohortHint, "old_hint"));
+ EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohortName, "old_name"));
+ fake_update_response_.include_cohorts = true;
+ fake_update_response_.cohort = "s/154454/8479665";
+ fake_update_response_.cohorthint = "please-put-me-on-beta";
+ fake_update_response_.cohortname = "";
+
+ ASSERT_TRUE(TestUpdateCheck(¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ string value;
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+ EXPECT_EQ(fake_update_response_.cohort, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+ EXPECT_EQ(fake_update_response_.cohorthint, value);
+
+ EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+}
+
+TEST_F(OmahaRequestActionTest, CohortsAreNotModifiedWhenMissing) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value"));
+
+ ASSERT_TRUE(TestUpdateCheck(¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ string value;
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+ EXPECT_EQ("old_value", value);
+
+ EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+ EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+}
+
+TEST_F(OmahaRequestActionTest, CohortsArePersistedWhenNoUpdate) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ fake_update_response_.include_cohorts = true;
+ fake_update_response_.cohort = "s/154454/8479665";
+ fake_update_response_.cohorthint = "please-put-me-on-beta";
+ fake_update_response_.cohortname = "stable";
+
+ ASSERT_TRUE(TestUpdateCheck(¶ms,
+ fake_update_response_.GetNoUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kNoUpdateAvailable,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ string value;
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+ EXPECT_EQ(fake_update_response_.cohort, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+ EXPECT_EQ(fake_update_response_.cohorthint, value);
+
+ EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+ EXPECT_EQ(fake_update_response_.cohortname, value);
+}
+
+TEST_F(OmahaRequestActionTest, NoOutputPipeTest) {
+ const string http_response(fake_update_response_.GetNoUpdateResponse());
+
+ chromeos::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ OmahaRequestParams params = request_params_;
+ fake_system_state_.set_request_params(¶ms);
+ OmahaRequestAction action(&fake_system_state_, nullptr,
+ new MockHttpFetcher(http_response.data(),
+ http_response.size(),
+ nullptr),
+ false);
+ OmahaRequestActionTestProcessorDelegate delegate;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&action);
+
+ loop.PostTask(base::Bind([&processor] { processor.StartProcessing(); }));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+ EXPECT_FALSE(processor.IsRunning());
+}
+
+TEST_F(OmahaRequestActionTest, InvalidXmlTest) {
+ OmahaResponse response;
+ ASSERT_FALSE(
+ TestUpdateCheck(nullptr, // request_params
+ "invalid xml>",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaRequestXMLParseError,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, EmptyResponseTest) {
+ OmahaResponse response;
+ ASSERT_FALSE(
+ TestUpdateCheck(nullptr, // request_params
+ "",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaRequestEmptyResponseError,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingStatusTest) {
+ OmahaResponse response;
+ ASSERT_FALSE(TestUpdateCheck(
+ nullptr, // request_params
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"/>"
+ "<app appid=\"foo\" status=\"ok\">"
+ "<ping status=\"ok\"/>"
+ "<updatecheck/></app></response>",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaResponseInvalid,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, InvalidStatusTest) {
+ OmahaResponse response;
+ ASSERT_FALSE(TestUpdateCheck(
+ nullptr, // request_params
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"/>"
+ "<app appid=\"foo\" status=\"ok\">"
+ "<ping status=\"ok\"/>"
+ "<updatecheck status=\"InvalidStatusTest\"/></app></response>",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaResponseInvalid,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingNodesetTest) {
+ OmahaResponse response;
+ ASSERT_FALSE(TestUpdateCheck(
+ nullptr, // request_params
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"/>"
+ "<app appid=\"foo\" status=\"ok\">"
+ "<ping status=\"ok\"/>"
+ "</app></response>",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaResponseInvalid,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingFieldTest) {
+ string input_response =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+ "<daystart elapsed_seconds=\"100\"/>"
+ "<app appid=\"xyz\" status=\"ok\">"
+ "<updatecheck status=\"ok\">"
+ "<urls><url codebase=\"http://missing/field/test/\"/></urls>"
+ "<manifest version=\"10.2.3.4\">"
+ "<packages><package hash=\"not-used\" name=\"f\" "
+ "size=\"587\"/></packages>"
+ "<actions><action event=\"postinstall\" "
+ "ChromeOSVersion=\"10.2.3.4\" "
+ "Prompt=\"false\" "
+ "IsDelta=\"true\" "
+ "IsDeltaPayload=\"false\" "
+ "sha256=\"lkq34j5345\" "
+ "needsadmin=\"true\" "
+ "/></actions></manifest></updatecheck></app></response>";
+ LOG(INFO) << "Input Response = " << input_response;
+
+ OmahaResponse response;
+ ASSERT_TRUE(TestUpdateCheck(nullptr, // request_params
+ input_response,
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ("10.2.3.4", response.version);
+ EXPECT_EQ("http://missing/field/test/f", response.payload_urls[0]);
+ EXPECT_EQ("", response.more_info_url);
+ EXPECT_EQ("lkq34j5345", response.hash);
+ EXPECT_EQ(587, response.size);
+ EXPECT_FALSE(response.prompt);
+ EXPECT_TRUE(response.deadline.empty());
+}
+
+namespace {
+class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ void ProcessingStopped(const ActionProcessor* processor) {
+ chromeos::MessageLoop::current()->BreakLoop();
+ }
+};
+
+void TerminateTransferTestStarter(ActionProcessor* processor) {
+ processor->StartProcessing();
+ CHECK(processor->IsRunning());
+ processor->StopProcessing();
+}
+} // namespace
+
+TEST_F(OmahaRequestActionTest, TerminateTransferTest) {
+ chromeos::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ string http_response("doesn't matter");
+ OmahaRequestAction action(&fake_system_state_, nullptr,
+ new MockHttpFetcher(http_response.data(),
+ http_response.size(),
+ nullptr),
+ false);
+ TerminateEarlyTestProcessorDelegate delegate;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&action);
+
+ loop.PostTask(base::Bind(&TerminateTransferTestStarter, &processor));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+}
+
+TEST_F(OmahaRequestActionTest, XmlEncodeTest) {
+ string output;
+ EXPECT_TRUE(XmlEncode("ab", &output));
+ EXPECT_EQ("ab", output);
+ EXPECT_TRUE(XmlEncode("a<b", &output));
+ EXPECT_EQ("a<b", output);
+ EXPECT_TRUE(XmlEncode("<&>\"\'\\", &output));
+ EXPECT_EQ("<&>"'\\", output);
+ EXPECT_TRUE(XmlEncode("<&>", &output));
+ EXPECT_EQ("&lt;&amp;&gt;", output);
+ // Check that unterminated UTF-8 strings are handled properly.
+ EXPECT_FALSE(XmlEncode("\xc2", &output));
+ // Fail with invalid ASCII-7 chars.
+ EXPECT_FALSE(XmlEncode("This is an 'n' with a tilde: \xc3\xb1", &output));
+}
+
+TEST_F(OmahaRequestActionTest, XmlEncodeWithDefaultTest) {
+ EXPECT_EQ("<&>", XmlEncodeWithDefault("<&>", "something else"));
+ EXPECT_EQ("<not escaped>", XmlEncodeWithDefault("\xc2", "<not escaped>"));
+}
+
+TEST_F(OmahaRequestActionTest, XmlEncodeIsUsedForParams) {
+ chromeos::Blob post_data;
+
+ // Make sure XML Encode is being called on the params
+ OmahaRequestParams params(&fake_system_state_,
+ OmahaRequestParams::kOsPlatform,
+ OmahaRequestParams::kOsVersion,
+ "testtheservice_pack>",
+ "x86 generic<id",
+ OmahaRequestParams::kAppId,
+ "0.1.0.0",
+ "en-US",
+ "unittest_track<",
+ "<OEM MODEL>",
+ "ChromeOSFirmware.1.0",
+ "EC100",
+ false, // delta okay
+ false, // interactive
+ "http://url",
+ ""); // target_version_prefix
+ fake_prefs_.SetString(kPrefsOmahaCohort, "evil\nstring");
+ fake_prefs_.SetString(kPrefsOmahaCohortHint, "evil&string\\");
+ fake_prefs_.SetString(kPrefsOmahaCohortName,
+ JoinString(vector<string>(100, "My spoon is too big."),
+ ' '));
+ OmahaResponse response;
+ ASSERT_FALSE(
+ TestUpdateCheck(¶ms,
+ "invalid xml>",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaRequestXMLParseError,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ &post_data));
+ // convert post_data to string
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_NE(string::npos, post_str.find("testtheservice_pack>"));
+ EXPECT_EQ(string::npos, post_str.find("testtheservice_pack>"));
+ EXPECT_NE(string::npos, post_str.find("x86 generic<id"));
+ EXPECT_EQ(string::npos, post_str.find("x86 generic<id"));
+ EXPECT_NE(string::npos, post_str.find("unittest_track&lt;"));
+ EXPECT_EQ(string::npos, post_str.find("unittest_track<"));
+ EXPECT_NE(string::npos, post_str.find("<OEM MODEL>"));
+ EXPECT_EQ(string::npos, post_str.find("<OEM MODEL>"));
+ EXPECT_NE(string::npos, post_str.find("cohort=\"evil\nstring\""));
+ EXPECT_EQ(string::npos, post_str.find("cohorthint=\"evil&string\\\""));
+ EXPECT_NE(string::npos, post_str.find("cohorthint=\"evil&string\\\""));
+ // Values from Prefs that are too big are removed from the XML instead of
+ // encoded.
+ EXPECT_EQ(string::npos, post_str.find("cohortname="));
+}
+
+TEST_F(OmahaRequestActionTest, XmlDecodeTest) {
+ OmahaResponse response;
+ fake_update_response_.deadline = "<20110101";
+ fake_update_response_.more_info_url = "testthe<url";
+ fake_update_response_.codebase = "testthe&codebase/";
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ EXPECT_EQ(response.more_info_url, "testthe<url");
+ EXPECT_EQ(response.payload_urls[0], "testthe&codebase/file.signed");
+ EXPECT_EQ(response.deadline, "<20110101");
+}
+
+TEST_F(OmahaRequestActionTest, ParseIntTest) {
+ OmahaResponse response;
+ // overflows int32_t:
+ fake_update_response_.size = 123123123123123ll;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ EXPECT_EQ(response.size, 123123123123123ll);
+}
+
+TEST_F(OmahaRequestActionTest, FormatUpdateCheckOutputTest) {
+ chromeos::Blob post_data;
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+
+ EXPECT_CALL(prefs, GetString(kPrefsPreviousVersion, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(string("")), Return(true)));
+ EXPECT_CALL(prefs, SetString(kPrefsPreviousVersion, _)).Times(1);
+ ASSERT_FALSE(TestUpdateCheck(nullptr, // request_params
+ "invalid xml>",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaRequestXMLParseError,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr, // response
+ &post_data));
+ // convert post_data to string
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_NE(post_str.find(
+ " <ping active=\"1\" a=\"-1\" r=\"-1\"></ping>\n"
+ " <updatecheck targetversionprefix=\"\"></updatecheck>\n"),
+ string::npos);
+ EXPECT_NE(post_str.find("hardware_class=\"OEM MODEL 09235 7471\""),
+ string::npos);
+ EXPECT_NE(post_str.find("fw_version=\"ChromeOSFirmware.1.0\""),
+ string::npos);
+ EXPECT_NE(post_str.find("ec_version=\"0X0A1\""),
+ string::npos);
+}
+
+
+TEST_F(OmahaRequestActionTest, FormatSuccessEventOutputTest) {
+ chromeos::Blob post_data;
+ TestEvent(request_params_,
+ new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted),
+ "invalid xml>",
+ &post_data);
+ // convert post_data to string
+ string post_str(post_data.begin(), post_data.end());
+ string expected_event = base::StringPrintf(
+ " <event eventtype=\"%d\" eventresult=\"%d\"></event>\n",
+ OmahaEvent::kTypeUpdateDownloadStarted,
+ OmahaEvent::kResultSuccess);
+ EXPECT_NE(post_str.find(expected_event), string::npos);
+ EXPECT_EQ(post_str.find("ping"), string::npos);
+ EXPECT_EQ(post_str.find("updatecheck"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, FormatErrorEventOutputTest) {
+ chromeos::Blob post_data;
+ TestEvent(request_params_,
+ new OmahaEvent(OmahaEvent::kTypeDownloadComplete,
+ OmahaEvent::kResultError,
+ ErrorCode::kError),
+ "invalid xml>",
+ &post_data);
+ // convert post_data to string
+ string post_str(post_data.begin(), post_data.end());
+ string expected_event = base::StringPrintf(
+ " <event eventtype=\"%d\" eventresult=\"%d\" "
+ "errorcode=\"%d\"></event>\n",
+ OmahaEvent::kTypeDownloadComplete,
+ OmahaEvent::kResultError,
+ static_cast<int>(ErrorCode::kError));
+ EXPECT_NE(post_str.find(expected_event), string::npos);
+ EXPECT_EQ(post_str.find("updatecheck"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, IsEventTest) {
+ string http_response("doesn't matter");
+ // Create a copy of the OmahaRequestParams to reuse it later.
+ OmahaRequestParams params = request_params_;
+ fake_system_state_.set_request_params(¶ms);
+ OmahaRequestAction update_check_action(
+ &fake_system_state_,
+ nullptr,
+ new MockHttpFetcher(http_response.data(),
+ http_response.size(),
+ nullptr),
+ false);
+ EXPECT_FALSE(update_check_action.IsEvent());
+
+ params = request_params_;
+ fake_system_state_.set_request_params(¶ms);
+ OmahaRequestAction event_action(
+ &fake_system_state_,
+ new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+ new MockHttpFetcher(http_response.data(),
+ http_response.size(),
+ nullptr),
+ false);
+ EXPECT_TRUE(event_action.IsEvent());
+}
+
+TEST_F(OmahaRequestActionTest, FormatDeltaOkayOutputTest) {
+ for (int i = 0; i < 2; i++) {
+ bool delta_okay = i == 1;
+ const char* delta_okay_str = delta_okay ? "true" : "false";
+ chromeos::Blob post_data;
+ OmahaRequestParams params(&fake_system_state_,
+ OmahaRequestParams::kOsPlatform,
+ OmahaRequestParams::kOsVersion,
+ "service_pack",
+ "x86-generic",
+ OmahaRequestParams::kAppId,
+ "0.1.0.0",
+ "en-US",
+ "unittest_track",
+ "OEM MODEL REV 1234",
+ "ChromeOSFirmware.1.0",
+ "EC100",
+ delta_okay,
+ false, // interactive
+ "http://url",
+ ""); // target_version_prefix
+ ASSERT_FALSE(TestUpdateCheck(¶ms,
+ "invalid xml>",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaRequestXMLParseError,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ &post_data));
+ // convert post_data to string
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_NE(post_str.find(base::StringPrintf(" delta_okay=\"%s\"",
+ delta_okay_str)),
+ string::npos)
+ << "i = " << i;
+ }
+}
+
+TEST_F(OmahaRequestActionTest, FormatInteractiveOutputTest) {
+ for (int i = 0; i < 2; i++) {
+ bool interactive = i == 1;
+ const char* interactive_str = interactive ? "ondemandupdate" : "scheduler";
+ chromeos::Blob post_data;
+ FakeSystemState fake_system_state;
+ OmahaRequestParams params(&fake_system_state_,
+ OmahaRequestParams::kOsPlatform,
+ OmahaRequestParams::kOsVersion,
+ "service_pack",
+ "x86-generic",
+ OmahaRequestParams::kAppId,
+ "0.1.0.0",
+ "en-US",
+ "unittest_track",
+ "OEM MODEL REV 1234",
+ "ChromeOSFirmware.1.0",
+ "EC100",
+ true, // delta_okay
+ interactive,
+ "http://url",
+ ""); // target_version_prefix
+ ASSERT_FALSE(TestUpdateCheck(¶ms,
+ "invalid xml>",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaRequestXMLParseError,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ &post_data));
+ // convert post_data to string
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_NE(post_str.find(base::StringPrintf("installsource=\"%s\"",
+ interactive_str)),
+ string::npos)
+ << "i = " << i;
+ }
+}
+
+TEST_F(OmahaRequestActionTest, OmahaEventTest) {
+ OmahaEvent default_event;
+ EXPECT_EQ(OmahaEvent::kTypeUnknown, default_event.type);
+ EXPECT_EQ(OmahaEvent::kResultError, default_event.result);
+ EXPECT_EQ(ErrorCode::kError, default_event.error_code);
+
+ OmahaEvent success_event(OmahaEvent::kTypeUpdateDownloadStarted);
+ EXPECT_EQ(OmahaEvent::kTypeUpdateDownloadStarted, success_event.type);
+ EXPECT_EQ(OmahaEvent::kResultSuccess, success_event.result);
+ EXPECT_EQ(ErrorCode::kSuccess, success_event.error_code);
+
+ OmahaEvent error_event(OmahaEvent::kTypeUpdateDownloadFinished,
+ OmahaEvent::kResultError,
+ ErrorCode::kError);
+ EXPECT_EQ(OmahaEvent::kTypeUpdateDownloadFinished, error_event.type);
+ EXPECT_EQ(OmahaEvent::kResultError, error_event.result);
+ EXPECT_EQ(ErrorCode::kError, error_event.error_code);
+}
+
+void OmahaRequestActionTest::PingTest(bool ping_only) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ // Add a few hours to the day difference to test no rounding, etc.
+ int64_t five_days_ago =
+ (Time::Now() - TimeDelta::FromHours(5 * 24 + 13)).ToInternalValue();
+ int64_t six_days_ago =
+ (Time::Now() - TimeDelta::FromHours(6 * 24 + 11)).ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(six_days_ago), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(five_days_ago), Return(true)));
+ chromeos::Blob post_data;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetNoUpdateResponse(),
+ -1,
+ ping_only,
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUnset,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ &post_data));
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_NE(post_str.find("<ping active=\"1\" a=\"6\" r=\"5\"></ping>"),
+ string::npos);
+ if (ping_only) {
+ EXPECT_EQ(post_str.find("updatecheck"), string::npos);
+ EXPECT_EQ(post_str.find("previousversion"), string::npos);
+ } else {
+ EXPECT_NE(post_str.find("updatecheck"), string::npos);
+ EXPECT_NE(post_str.find("previousversion"), string::npos);
+ }
+}
+
+TEST_F(OmahaRequestActionTest, PingTestSendOnlyAPing) {
+ PingTest(true /* ping_only */);
+}
+
+TEST_F(OmahaRequestActionTest, PingTestSendAlsoAnUpdateCheck) {
+ PingTest(false /* ping_only */);
+}
+
+TEST_F(OmahaRequestActionTest, ActivePingTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ int64_t three_days_ago =
+ (Time::Now() - TimeDelta::FromHours(3 * 24 + 12)).ToInternalValue();
+ int64_t now = Time::Now().ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(three_days_ago), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+ chromeos::Blob post_data;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetNoUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kNoUpdateAvailable,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ &post_data));
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_NE(post_str.find("<ping active=\"1\" a=\"3\"></ping>"),
+ string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, RollCallPingTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ int64_t four_days_ago =
+ (Time::Now() - TimeDelta::FromHours(4 * 24)).ToInternalValue();
+ int64_t now = Time::Now().ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(four_days_ago), Return(true)));
+ chromeos::Blob post_data;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetNoUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kNoUpdateAvailable,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ &post_data));
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_NE(post_str.find("<ping active=\"1\" r=\"4\"></ping>\n"),
+ string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, NoPingTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ int64_t one_hour_ago =
+ (Time::Now() - TimeDelta::FromHours(1)).ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(one_hour_ago), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(one_hour_ago), Return(true)));
+ // LastActivePingDay and PrefsLastRollCallPingDay are set even if we didn't
+ // send a ping.
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(Return(true));
+ chromeos::Blob post_data;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetNoUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kNoUpdateAvailable,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ &post_data));
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_EQ(post_str.find("ping"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, IgnoreEmptyPingTest) {
+ // This test ensures that we ignore empty ping only requests.
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ int64_t now = Time::Now().ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
+ chromeos::Blob post_data;
+ EXPECT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetNoUpdateResponse(),
+ -1,
+ true, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUnset,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ &post_data));
+ EXPECT_EQ(post_data.size(), 0);
+}
+
+TEST_F(OmahaRequestActionTest, BackInTimePingTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ int64_t future =
+ (Time::Now() + TimeDelta::FromHours(3 * 24 + 4)).ToInternalValue();
+ EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(future), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(future), Return(true)));
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(Return(true));
+ chromeos::Blob post_data;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+ "protocol=\"3.0\"><daystart elapsed_seconds=\"100\"/>"
+ "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+ "<updatecheck status=\"noupdate\"/></app></response>",
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kNoUpdateAvailable,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ &post_data));
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_EQ(post_str.find("ping"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, LastPingDayUpdateTest) {
+ // This test checks that the action updates the last ping day to now
+ // minus 200 seconds with a slack of 5 seconds. Therefore, the test
+ // may fail if it runs for longer than 5 seconds. It shouldn't run
+ // that long though.
+ int64_t midnight =
+ (Time::Now() - TimeDelta::FromSeconds(200)).ToInternalValue();
+ int64_t midnight_slack =
+ (Time::Now() - TimeDelta::FromSeconds(195)).ToInternalValue();
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay,
+ AllOf(Ge(midnight), Le(midnight_slack))))
+ .WillOnce(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay,
+ AllOf(Ge(midnight), Le(midnight_slack))))
+ .WillOnce(Return(true));
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+ "protocol=\"3.0\"><daystart elapsed_seconds=\"200\"/>"
+ "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+ "<updatecheck status=\"noupdate\"/></app></response>",
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kNoUpdateAvailable,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ nullptr));
+}
+
+TEST_F(OmahaRequestActionTest, NoElapsedSecondsTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+ "protocol=\"3.0\"><daystart blah=\"200\"/>"
+ "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+ "<updatecheck status=\"noupdate\"/></app></response>",
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kNoUpdateAvailable,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ nullptr));
+}
+
+TEST_F(OmahaRequestActionTest, BadElapsedSecondsTest) {
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
+ EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+ "protocol=\"3.0\"><daystart elapsed_seconds=\"x\"/>"
+ "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+ "<updatecheck status=\"noupdate\"/></app></response>",
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kNoUpdateAvailable,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ nullptr));
+}
+
+TEST_F(OmahaRequestActionTest, NoUniqueIDTest) {
+ chromeos::Blob post_data;
+ ASSERT_FALSE(TestUpdateCheck(nullptr, // request_params
+ "invalid xml>",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaRequestXMLParseError,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr, // response
+ &post_data));
+ // convert post_data to string
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_EQ(post_str.find("machineid="), string::npos);
+ EXPECT_EQ(post_str.find("userid="), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, NetworkFailureTest) {
+ OmahaResponse response;
+ const int http_error_code =
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 501;
+ ASSERT_FALSE(
+ TestUpdateCheck(nullptr, // request_params
+ "",
+ 501,
+ false, // ping_only
+ static_cast<ErrorCode>(http_error_code),
+ metrics::CheckResult::kDownloadError,
+ metrics::CheckReaction::kUnset,
+ static_cast<metrics::DownloadErrorCode>(501),
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NetworkFailureBadHTTPCodeTest) {
+ OmahaResponse response;
+ const int http_error_code =
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 999;
+ ASSERT_FALSE(
+ TestUpdateCheck(nullptr, // request_params
+ "",
+ 1500,
+ false, // ping_only
+ static_cast<ErrorCode>(http_error_code),
+ metrics::CheckResult::kDownloadError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kHttpStatusOther,
+ &response,
+ nullptr));
+ EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsPersistedFirstTime) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta().FromDays(1));
+ params.set_update_check_count_wait_enabled(false);
+
+ ASSERT_FALSE(TestUpdateCheck(
+ ¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaUpdateDeferredPerPolicy,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kDeferring,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ int64_t timestamp = 0;
+ ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt, ×tamp));
+ ASSERT_GT(timestamp, 0);
+ EXPECT_FALSE(response.update_exists);
+
+ // Verify if we are interactive check we don't defer.
+ params.set_interactive(true);
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsUsedIfAlreadyPresent) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta().FromDays(1));
+ params.set_update_check_count_wait_enabled(false);
+
+ // Set the timestamp to a very old value such that it exceeds the
+ // waiting period set above.
+ Time t1;
+ Time::FromString("1/1/2012", &t1);
+ ASSERT_TRUE(fake_prefs_.SetInt64(
+ kPrefsUpdateFirstSeenAt, t1.ToInternalValue()));
+ ASSERT_TRUE(TestUpdateCheck(
+ ¶ms,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ EXPECT_TRUE(response.update_exists);
+
+ // Make sure the timestamp t1 is unchanged showing that it was reused.
+ int64_t timestamp = 0;
+ ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt, ×tamp));
+ ASSERT_TRUE(timestamp == t1.ToInternalValue());
+}
+
+TEST_F(OmahaRequestActionTest, TestChangingToMoreStableChannel) {
+ // Create a uniquely named test directory.
+ string test_dir;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ "omaha_request_action-test-XXXXXX", &test_dir));
+
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+ kStatefulPartition + "/etc"));
+ chromeos::Blob post_data;
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ ASSERT_TRUE(WriteFileString(
+ test_dir + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID={11111111-1111-1111-1111-111111111111}\n"
+ "CHROMEOS_BOARD_APPID={22222222-2222-2222-2222-222222222222}\n"
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_IS_POWERWASH_ALLOWED=true\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ OmahaRequestParams params = request_params_;
+ params.set_root(test_dir);
+ params.SetLockDown(false);
+ params.Init("1.2.3.4", "", 0);
+ EXPECT_EQ("canary-channel", params.current_channel());
+ EXPECT_EQ("stable-channel", params.target_channel());
+ EXPECT_TRUE(params.to_more_stable_channel());
+ EXPECT_TRUE(params.is_powerwash_allowed());
+ ASSERT_FALSE(TestUpdateCheck(¶ms,
+ "invalid xml>",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaRequestXMLParseError,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr, // response
+ &post_data));
+ // convert post_data to string
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_NE(string::npos, post_str.find(
+ "appid=\"{22222222-2222-2222-2222-222222222222}\" "
+ "version=\"0.0.0.0\" from_version=\"1.2.3.4\" "
+ "track=\"stable-channel\" from_track=\"canary-channel\" "));
+
+ ASSERT_TRUE(test_utils::RecursiveUnlinkDir(test_dir));
+}
+
+TEST_F(OmahaRequestActionTest, TestChangingToLessStableChannel) {
+ // Create a uniquely named test directory.
+ string test_dir;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ "omaha_request_action-test-XXXXXX", &test_dir));
+
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+ kStatefulPartition + "/etc"));
+ chromeos::Blob post_data;
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ ASSERT_TRUE(WriteFileString(
+ test_dir + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID={11111111-1111-1111-1111-111111111111}\n"
+ "CHROMEOS_BOARD_APPID={22222222-2222-2222-2222-222222222222}\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+ OmahaRequestParams params = request_params_;
+ params.set_root(test_dir);
+ params.SetLockDown(false);
+ params.Init("5.6.7.8", "", 0);
+ EXPECT_EQ("stable-channel", params.current_channel());
+ EXPECT_EQ("canary-channel", params.target_channel());
+ EXPECT_FALSE(params.to_more_stable_channel());
+ EXPECT_FALSE(params.is_powerwash_allowed());
+ ASSERT_FALSE(TestUpdateCheck(¶ms,
+ "invalid xml>",
+ -1,
+ false, // ping_only
+ ErrorCode::kOmahaRequestXMLParseError,
+ metrics::CheckResult::kParsingError,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr, // response
+ &post_data));
+ // convert post_data to string
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_NE(string::npos, post_str.find(
+ "appid=\"{11111111-1111-1111-1111-111111111111}\" "
+ "version=\"5.6.7.8\" "
+ "track=\"canary-channel\" from_track=\"stable-channel\""));
+ EXPECT_EQ(string::npos, post_str.find("from_version"));
+}
+
+// Checks that the initial ping with a=-1 r=-1 is not send when the device
+// was powerwashed.
+TEST_F(OmahaRequestActionTest, PingWhenPowerwashed) {
+ fake_prefs_.SetString(kPrefsPreviousVersion, "");
+
+ // Flag that the device was powerwashed in the past.
+ fake_system_state_.fake_hardware()->SetPowerwashCount(1);
+
+ chromeos::Blob post_data;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetNoUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kNoUpdateAvailable,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ &post_data));
+ // We shouldn't send a ping in this case since powerwash > 0.
+ string post_str(post_data.begin(), post_data.end());
+ EXPECT_EQ(string::npos, post_str.find("<ping"));
+}
+
+void OmahaRequestActionTest::P2PTest(
+ bool initial_allow_p2p_for_downloading,
+ bool initial_allow_p2p_for_sharing,
+ bool omaha_disable_p2p_for_downloading,
+ bool omaha_disable_p2p_for_sharing,
+ bool payload_state_allow_p2p_attempt,
+ bool expect_p2p_client_lookup,
+ const string& p2p_client_result_url,
+ bool expected_allow_p2p_for_downloading,
+ bool expected_allow_p2p_for_sharing,
+ const string& expected_p2p_url) {
+ OmahaResponse response;
+ OmahaRequestParams request_params = request_params_;
+ bool actual_allow_p2p_for_downloading = initial_allow_p2p_for_downloading;
+ bool actual_allow_p2p_for_sharing = initial_allow_p2p_for_sharing;
+ string actual_p2p_url;
+
+ MockPayloadState mock_payload_state;
+ fake_system_state_.set_payload_state(&mock_payload_state);
+ EXPECT_CALL(mock_payload_state, P2PAttemptAllowed())
+ .WillRepeatedly(Return(payload_state_allow_p2p_attempt));
+ EXPECT_CALL(mock_payload_state, GetUsingP2PForDownloading())
+ .WillRepeatedly(ReturnPointee(&actual_allow_p2p_for_downloading));
+ EXPECT_CALL(mock_payload_state, GetUsingP2PForSharing())
+ .WillRepeatedly(ReturnPointee(&actual_allow_p2p_for_sharing));
+ EXPECT_CALL(mock_payload_state, SetUsingP2PForDownloading(_))
+ .WillRepeatedly(SaveArg<0>(&actual_allow_p2p_for_downloading));
+ EXPECT_CALL(mock_payload_state, SetUsingP2PForSharing(_))
+ .WillRepeatedly(SaveArg<0>(&actual_allow_p2p_for_sharing));
+ EXPECT_CALL(mock_payload_state, SetP2PUrl(_))
+ .WillRepeatedly(SaveArg<0>(&actual_p2p_url));
+
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetLookupUrlForFileResult(p2p_client_result_url);
+
+ TimeDelta timeout = TimeDelta::FromSeconds(kMaxP2PNetworkWaitTimeSeconds);
+ EXPECT_CALL(mock_p2p_manager, LookupUrlForFile(_, _, timeout, _))
+ .Times(expect_p2p_client_lookup ? 1 : 0);
+
+ fake_update_response_.disable_p2p_for_downloading =
+ omaha_disable_p2p_for_downloading;
+ fake_update_response_.disable_p2p_for_sharing = omaha_disable_p2p_for_sharing;
+ ASSERT_TRUE(
+ TestUpdateCheck(&request_params,
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+ EXPECT_TRUE(response.update_exists);
+
+ EXPECT_EQ(omaha_disable_p2p_for_downloading,
+ response.disable_p2p_for_downloading);
+ EXPECT_EQ(omaha_disable_p2p_for_sharing,
+ response.disable_p2p_for_sharing);
+
+ EXPECT_EQ(expected_allow_p2p_for_downloading,
+ actual_allow_p2p_for_downloading);
+ EXPECT_EQ(expected_allow_p2p_for_sharing, actual_allow_p2p_for_sharing);
+ EXPECT_EQ(expected_p2p_url, actual_p2p_url);
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeer) {
+ P2PTest(true, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ false, // omaha_disable_p2p_for_downloading
+ false, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ true, // expect_p2p_client_lookup
+ "http://1.3.5.7/p2p", // p2p_client_result_url
+ true, // expected_allow_p2p_for_downloading
+ true, // expected_allow_p2p_for_sharing
+ "http://1.3.5.7/p2p"); // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithoutPeer) {
+ P2PTest(true, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ false, // omaha_disable_p2p_for_downloading
+ false, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ true, // expect_p2p_client_lookup
+ "", // p2p_client_result_url
+ false, // expected_allow_p2p_for_downloading
+ true, // expected_allow_p2p_for_sharing
+ ""); // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PDownloadNotAllowed) {
+ P2PTest(false, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ false, // omaha_disable_p2p_for_downloading
+ false, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ false, // expect_p2p_client_lookup
+ "unset", // p2p_client_result_url
+ false, // expected_allow_p2p_for_downloading
+ true, // expected_allow_p2p_for_sharing
+ ""); // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeerDownloadDisabledByOmaha) {
+ P2PTest(true, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ true, // omaha_disable_p2p_for_downloading
+ false, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ false, // expect_p2p_client_lookup
+ "unset", // p2p_client_result_url
+ false, // expected_allow_p2p_for_downloading
+ true, // expected_allow_p2p_for_sharing
+ ""); // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeerSharingDisabledByOmaha) {
+ P2PTest(true, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ false, // omaha_disable_p2p_for_downloading
+ true, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ true, // expect_p2p_client_lookup
+ "http://1.3.5.7/p2p", // p2p_client_result_url
+ true, // expected_allow_p2p_for_downloading
+ false, // expected_allow_p2p_for_sharing
+ "http://1.3.5.7/p2p"); // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeerBothDisabledByOmaha) {
+ P2PTest(true, // initial_allow_p2p_for_downloading
+ true, // initial_allow_p2p_for_sharing
+ true, // omaha_disable_p2p_for_downloading
+ true, // omaha_disable_p2p_for_sharing
+ true, // payload_state_allow_p2p_attempt
+ false, // expect_p2p_client_lookup
+ "unset", // p2p_client_result_url
+ false, // expected_allow_p2p_for_downloading
+ false, // expected_allow_p2p_for_sharing
+ ""); // expected_p2p_url
+}
+
+bool OmahaRequestActionTest::InstallDateParseHelper(const string &elapsed_days,
+ OmahaResponse *response) {
+ fake_update_response_.elapsed_days = elapsed_days;
+ return
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ response,
+ nullptr);
+}
+
+TEST_F(OmahaRequestActionTest, ParseInstallDateFromResponse) {
+ OmahaResponse response;
+
+ // Check that we parse elapsed_days in the Omaha Response correctly.
+ // and that the kPrefsInstallDateDays value is written to.
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays));
+ EXPECT_TRUE(InstallDateParseHelper("42", &response));
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(42, response.install_date_days);
+ EXPECT_TRUE(fake_prefs_.Exists(kPrefsInstallDateDays));
+ int64_t prefs_days;
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 42);
+
+ // If there already is a value set, we shouldn't do anything.
+ EXPECT_TRUE(InstallDateParseHelper("7", &response));
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(7, response.install_date_days);
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 42);
+
+ // Note that elapsed_days is not necessarily divisible by 7 so check
+ // that we round down correctly when populating kPrefsInstallDateDays.
+ EXPECT_TRUE(fake_prefs_.Delete(kPrefsInstallDateDays));
+ EXPECT_TRUE(InstallDateParseHelper("23", &response));
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(23, response.install_date_days);
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 21);
+
+ // Check that we correctly handle elapsed_days not being included in
+ // the Omaha Response.
+ EXPECT_TRUE(InstallDateParseHelper("", &response));
+ EXPECT_TRUE(response.update_exists);
+ EXPECT_EQ(-1, response.install_date_days);
+}
+
+// If there is no prefs and OOBE is not complete, we should not
+// report anything to Omaha.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE) {
+ EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), -1);
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays));
+}
+
+// If OOBE is complete and happened on a valid date (e.g. after Jan
+// 1 2007 0:00 PST), that date should be used and written to
+// prefs. However, first try with an invalid date and check we do
+// nothing.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithInvalidDate) {
+ Time oobe_date = Time::FromTimeT(42); // Dec 31, 1969 16:00:42 PST.
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date);
+ EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), -1);
+ EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays));
+}
+
+// Then check with a valid date. The date Jan 20, 2007 0:00 PST
+// should yield an InstallDate of 14.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithValidDate) {
+ Time oobe_date = Time::FromTimeT(1169280000); // Jan 20, 2007 0:00 PST.
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date);
+ EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 14);
+ EXPECT_TRUE(fake_prefs_.Exists(kPrefsInstallDateDays));
+
+ int64_t prefs_days;
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 14);
+}
+
+// Now that we have a valid date in prefs, check that we keep using
+// that even if OOBE date reports something else. The date Jan 30,
+// 2007 0:00 PST should yield an InstallDate of 28... but since
+// there's a prefs file, we should still get 14.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedDateChanges) {
+ // Set a valid date in the prefs first.
+ EXPECT_TRUE(fake_prefs_.SetInt64(kPrefsInstallDateDays, 14));
+
+ Time oobe_date = Time::FromTimeT(1170144000); // Jan 30, 2007 0:00 PST.
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date);
+ EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 14);
+
+ int64_t prefs_days;
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 14);
+
+ // If we delete the prefs file, we should get 28 days.
+ EXPECT_TRUE(fake_prefs_.Delete(kPrefsInstallDateDays));
+ EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 28);
+ EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+ EXPECT_EQ(prefs_days, 28);
+}
+
+} // namespace chromeos_update_engine
diff --git a/omaha_request_params.cc b/omaha_request_params.cc
new file mode 100644
index 0000000..abbd012
--- /dev/null
+++ b/omaha_request_params.cc
@@ -0,0 +1,291 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/omaha_request_params.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/utsname.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <chromeos/key_value_store.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/system_state.h"
+#include "update_engine/utils.h"
+
+#define CALL_MEMBER_FN(object, member) ((object).*(member))
+
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const char kProductionOmahaUrl[] =
+ "https://tools.google.com/service/update2";
+const char kAUTestOmahaUrl[] =
+ "https://omaha.sandbox.google.com/service/update2";
+
+const char OmahaRequestParams::kAppId[] =
+ "{87efface-864d-49a5-9bb3-4b050a7c227a}";
+const char OmahaRequestParams::kOsPlatform[] = "Chrome OS";
+const char OmahaRequestParams::kOsVersion[] = "Indy";
+const char OmahaRequestParams::kUpdateChannelKey[] = "CHROMEOS_RELEASE_TRACK";
+const char OmahaRequestParams::kIsPowerwashAllowedKey[] =
+ "CHROMEOS_IS_POWERWASH_ALLOWED";
+const char OmahaRequestParams::kAutoUpdateServerKey[] = "CHROMEOS_AUSERVER";
+
+const char* kChannelsByStability[] = {
+ // This list has to be sorted from least stable to most stable channel.
+ "canary-channel",
+ "dev-channel",
+ "beta-channel",
+ "stable-channel",
+};
+
+bool OmahaRequestParams::Init(const string& in_app_version,
+ const string& in_update_url,
+ bool in_interactive) {
+ LOG(INFO) << "Initializing parameters for this update attempt";
+ InitFromLsbValue();
+ bool stateful_override = !ShouldLockDown();
+ os_platform_ = OmahaRequestParams::kOsPlatform;
+ os_version_ = OmahaRequestParams::kOsVersion;
+ app_version_ = in_app_version.empty() ?
+ GetLsbValue("CHROMEOS_RELEASE_VERSION", "", nullptr, stateful_override) :
+ in_app_version;
+ os_sp_ = app_version_ + "_" + GetMachineType();
+ os_board_ = GetLsbValue("CHROMEOS_RELEASE_BOARD",
+ "",
+ nullptr,
+ stateful_override);
+ string release_app_id = GetLsbValue("CHROMEOS_RELEASE_APPID",
+ OmahaRequestParams::kAppId,
+ nullptr,
+ stateful_override);
+ board_app_id_ = GetLsbValue("CHROMEOS_BOARD_APPID",
+ release_app_id,
+ nullptr,
+ stateful_override);
+ canary_app_id_ = GetLsbValue("CHROMEOS_CANARY_APPID",
+ release_app_id,
+ nullptr,
+ stateful_override);
+ app_lang_ = "en-US";
+ hwid_ = system_state_->hardware()->GetHardwareClass();
+ if (CollectECFWVersions()) {
+ fw_version_ = system_state_->hardware()->GetFirmwareVersion();
+ ec_version_ = system_state_->hardware()->GetECVersion();
+ }
+
+ if (current_channel_ == target_channel_) {
+ // deltas are only okay if the /.nodelta file does not exist. if we don't
+ // know (i.e. stat() returns some unexpected error), then err on the side of
+ // caution and say deltas are not okay.
+ struct stat stbuf;
+ delta_okay_ = (stat((root_ + "/.nodelta").c_str(), &stbuf) < 0) &&
+ (errno == ENOENT);
+
+ } else {
+ LOG(INFO) << "Disabling deltas as a channel change is pending";
+ // For now, disable delta updates if the current channel is different from
+ // the channel that we're sending to the update server because such updates
+ // are destined to fail -- the current rootfs hash will be different than
+ // the expected hash due to the different channel in /etc/lsb-release.
+ delta_okay_ = false;
+ }
+
+ if (in_update_url.empty())
+ update_url_ = GetLsbValue(kAutoUpdateServerKey, kProductionOmahaUrl,
+ nullptr, stateful_override);
+ else
+ update_url_ = in_update_url;
+
+ // Set the interactive flag accordingly.
+ interactive_ = in_interactive;
+ return true;
+}
+
+bool OmahaRequestParams::IsUpdateUrlOfficial() const {
+ return (update_url_ == kAUTestOmahaUrl ||
+ update_url_ == GetLsbValue(kAutoUpdateServerKey, kProductionOmahaUrl,
+ nullptr, !ShouldLockDown()));
+}
+
+bool OmahaRequestParams::CollectECFWVersions() const {
+ return base::StartsWithASCII(hwid_, string("SAMS ALEX"), true) ||
+ base::StartsWithASCII(hwid_, string("BUTTERFLY"), true) ||
+ base::StartsWithASCII(hwid_, string("LUMPY"), true) ||
+ base::StartsWithASCII(hwid_, string("PARROT"), true) ||
+ base::StartsWithASCII(hwid_, string("SPRING"), true) ||
+ base::StartsWithASCII(hwid_, string("SNOW"), true);
+}
+
+bool OmahaRequestParams::SetTargetChannel(const string& new_target_channel,
+ bool is_powerwash_allowed) {
+ LOG(INFO) << "SetTargetChannel called with " << new_target_channel
+ << ", Is Powerwash Allowed = "
+ << utils::ToString(is_powerwash_allowed)
+ << ". Current channel = " << current_channel_
+ << ", existing target channel = " << target_channel_
+ << ", download channel = " << download_channel_;
+ TEST_AND_RETURN_FALSE(IsValidChannel(new_target_channel));
+ chromeos::KeyValueStore lsb_release;
+ base::FilePath kFile(root_ + kStatefulPartition + "/etc/lsb-release");
+
+ lsb_release.Load(kFile);
+ lsb_release.SetString(kUpdateChannelKey, new_target_channel);
+ lsb_release.SetBoolean(kIsPowerwashAllowedKey, is_powerwash_allowed);
+
+ TEST_AND_RETURN_FALSE(base::CreateDirectory(kFile.DirName()));
+ TEST_AND_RETURN_FALSE(lsb_release.Save(kFile));
+ target_channel_ = new_target_channel;
+ is_powerwash_allowed_ = is_powerwash_allowed;
+ return true;
+}
+
+void OmahaRequestParams::SetTargetChannelFromLsbValue() {
+ string target_channel_new_value = GetLsbValue(
+ kUpdateChannelKey,
+ current_channel_,
+ &chromeos_update_engine::OmahaRequestParams::IsValidChannel,
+ true); // stateful_override
+
+ if (target_channel_ != target_channel_new_value) {
+ target_channel_ = target_channel_new_value;
+ LOG(INFO) << "Target Channel set to " << target_channel_
+ << " from LSB file";
+ }
+}
+
+void OmahaRequestParams::SetCurrentChannelFromLsbValue() {
+ string current_channel_new_value = GetLsbValue(
+ kUpdateChannelKey,
+ current_channel_,
+ nullptr, // No need to validate the read-only rootfs channel.
+ false); // stateful_override is false so we get the current channel.
+
+ if (current_channel_ != current_channel_new_value) {
+ current_channel_ = current_channel_new_value;
+ LOG(INFO) << "Current Channel set to " << current_channel_
+ << " from LSB file in rootfs";
+ }
+}
+
+void OmahaRequestParams::SetIsPowerwashAllowedFromLsbValue() {
+ string is_powerwash_allowed_str = GetLsbValue(
+ kIsPowerwashAllowedKey,
+ "false",
+ nullptr, // no need to validate
+ true); // always get it from stateful, as that's the only place it'll be
+ bool is_powerwash_allowed_new_value = (is_powerwash_allowed_str == "true");
+ if (is_powerwash_allowed_ != is_powerwash_allowed_new_value) {
+ is_powerwash_allowed_ = is_powerwash_allowed_new_value;
+ LOG(INFO) << "Powerwash Allowed set to "
+ << utils::ToString(is_powerwash_allowed_)
+ << " from LSB file in stateful";
+ }
+}
+
+void OmahaRequestParams::UpdateDownloadChannel() {
+ if (download_channel_ != target_channel_) {
+ download_channel_ = target_channel_;
+ LOG(INFO) << "Download channel for this attempt = " << download_channel_;
+ }
+}
+
+void OmahaRequestParams::InitFromLsbValue() {
+ SetCurrentChannelFromLsbValue();
+ SetTargetChannelFromLsbValue();
+ SetIsPowerwashAllowedFromLsbValue();
+ UpdateDownloadChannel();
+}
+
+string OmahaRequestParams::GetLsbValue(const string& key,
+ const string& default_value,
+ ValueValidator validator,
+ bool stateful_override) const {
+ vector<string> files;
+ if (stateful_override) {
+ files.push_back(string(kStatefulPartition) + "/etc/lsb-release");
+ }
+ files.push_back("/etc/lsb-release");
+ for (vector<string>::const_iterator it = files.begin();
+ it != files.end(); ++it) {
+ // TODO(adlr): make sure files checked are owned as root (and all their
+ // parents are recursively, too).
+ chromeos::KeyValueStore data;
+ if (!data.Load(base::FilePath(root_ + *it)))
+ continue;
+
+ string value;
+ if (data.GetString(key, &value)) {
+ if (validator && !CALL_MEMBER_FN(*this, validator)(value)) {
+ continue;
+ }
+ return value;
+ }
+ }
+ // not found
+ return default_value;
+}
+
+string OmahaRequestParams::GetMachineType() const {
+ struct utsname buf;
+ string ret;
+ if (uname(&buf) == 0)
+ ret = buf.machine;
+ return ret;
+}
+
+bool OmahaRequestParams::ShouldLockDown() const {
+ if (force_lock_down_) {
+ return forced_lock_down_;
+ }
+ return system_state_->hardware()->IsOfficialBuild() &&
+ system_state_->hardware()->IsNormalBootMode();
+}
+
+bool OmahaRequestParams::IsValidChannel(const string& channel) const {
+ return GetChannelIndex(channel) >= 0;
+}
+
+void OmahaRequestParams::set_root(const string& root) {
+ root_ = root;
+ InitFromLsbValue();
+}
+
+void OmahaRequestParams::SetLockDown(bool lock) {
+ force_lock_down_ = true;
+ forced_lock_down_ = lock;
+}
+
+int OmahaRequestParams::GetChannelIndex(const string& channel) const {
+ for (size_t t = 0; t < arraysize(kChannelsByStability); ++t)
+ if (channel == kChannelsByStability[t])
+ return t;
+
+ return -1;
+}
+
+bool OmahaRequestParams::to_more_stable_channel() const {
+ int current_channel_index = GetChannelIndex(current_channel_);
+ int download_channel_index = GetChannelIndex(download_channel_);
+
+ return download_channel_index > current_channel_index;
+}
+
+string OmahaRequestParams::GetAppId() const {
+ return download_channel_ == "canary-channel" ? canary_app_id_ : board_app_id_;
+}
+
+} // namespace chromeos_update_engine
diff --git a/omaha_request_params.h b/omaha_request_params.h
new file mode 100644
index 0000000..345f5c2
--- /dev/null
+++ b/omaha_request_params.h
@@ -0,0 +1,370 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_
+#define UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+// This gathers local system information and prepares info used by the
+// Omaha request action.
+
+namespace chromeos_update_engine {
+
+// The default "official" Omaha update URL.
+extern const char kProductionOmahaUrl[];
+
+// The autoupdate test Omaha update URL.
+extern const char kAUTestOmahaUrl[];
+
+class SystemState;
+
+// This class encapsulates the data Omaha gets for the request, along with
+// essential state needed for the processing of the request/response. The
+// strings in this struct should not be XML escaped.
+//
+// TODO(jaysri): chromium-os:39752 tracks the need to rename this class to
+// reflect its lifetime more appropriately.
+class OmahaRequestParams {
+ public:
+ explicit OmahaRequestParams(SystemState* system_state)
+ : system_state_(system_state),
+ os_platform_(kOsPlatform),
+ os_version_(kOsVersion),
+ board_app_id_(kAppId),
+ canary_app_id_(kAppId),
+ delta_okay_(true),
+ interactive_(false),
+ wall_clock_based_wait_enabled_(false),
+ update_check_count_wait_enabled_(false),
+ min_update_checks_needed_(kDefaultMinUpdateChecks),
+ max_update_checks_allowed_(kDefaultMaxUpdateChecks),
+ is_powerwash_allowed_(false),
+ force_lock_down_(false),
+ forced_lock_down_(false) {
+ InitFromLsbValue();
+ }
+
+ OmahaRequestParams(SystemState* system_state,
+ const std::string& in_os_platform,
+ const std::string& in_os_version,
+ const std::string& in_os_sp,
+ const std::string& in_os_board,
+ const std::string& in_app_id,
+ const std::string& in_app_version,
+ const std::string& in_app_lang,
+ const std::string& in_target_channel,
+ const std::string& in_hwid,
+ const std::string& in_fw_version,
+ const std::string& in_ec_version,
+ bool in_delta_okay,
+ bool in_interactive,
+ const std::string& in_update_url,
+ const std::string& in_target_version_prefix)
+ : system_state_(system_state),
+ os_platform_(in_os_platform),
+ os_version_(in_os_version),
+ os_sp_(in_os_sp),
+ os_board_(in_os_board),
+ board_app_id_(in_app_id),
+ canary_app_id_(in_app_id),
+ app_version_(in_app_version),
+ app_lang_(in_app_lang),
+ current_channel_(in_target_channel),
+ target_channel_(in_target_channel),
+ hwid_(in_hwid),
+ fw_version_(in_fw_version),
+ ec_version_(in_ec_version),
+ delta_okay_(in_delta_okay),
+ interactive_(in_interactive),
+ update_url_(in_update_url),
+ target_version_prefix_(in_target_version_prefix),
+ wall_clock_based_wait_enabled_(false),
+ update_check_count_wait_enabled_(false),
+ min_update_checks_needed_(kDefaultMinUpdateChecks),
+ max_update_checks_allowed_(kDefaultMaxUpdateChecks),
+ is_powerwash_allowed_(false),
+ force_lock_down_(false),
+ forced_lock_down_(false) {}
+
+ virtual ~OmahaRequestParams() = default;
+
+ // Setters and getters for the various properties.
+ inline std::string os_platform() const { return os_platform_; }
+ inline std::string os_version() const { return os_version_; }
+ inline std::string os_sp() const { return os_sp_; }
+ inline std::string os_board() const { return os_board_; }
+ inline std::string board_app_id() const { return board_app_id_; }
+ inline std::string canary_app_id() const { return canary_app_id_; }
+ inline std::string app_lang() const { return app_lang_; }
+ inline std::string hwid() const { return hwid_; }
+ inline std::string fw_version() const { return fw_version_; }
+ inline std::string ec_version() const { return ec_version_; }
+
+ inline void set_app_version(const std::string& version) {
+ app_version_ = version;
+ }
+ inline std::string app_version() const { return app_version_; }
+
+ inline std::string current_channel() const { return current_channel_; }
+ inline std::string target_channel() const { return target_channel_; }
+ inline std::string download_channel() const { return download_channel_; }
+
+ // Can client accept a delta ?
+ inline void set_delta_okay(bool ok) { delta_okay_ = ok; }
+ inline bool delta_okay() const { return delta_okay_; }
+
+ // True if this is a user-initiated update check.
+ inline void set_interactive(bool interactive) { interactive_ = interactive; }
+ inline bool interactive() const { return interactive_; }
+
+ inline void set_update_url(const std::string& url) { update_url_ = url; }
+ inline std::string update_url() const { return update_url_; }
+
+ inline void set_target_version_prefix(const std::string& prefix) {
+ target_version_prefix_ = prefix;
+ }
+
+ inline std::string target_version_prefix() const {
+ return target_version_prefix_;
+ }
+
+ inline void set_wall_clock_based_wait_enabled(bool enabled) {
+ wall_clock_based_wait_enabled_ = enabled;
+ }
+ inline bool wall_clock_based_wait_enabled() const {
+ return wall_clock_based_wait_enabled_;
+ }
+
+ inline void set_waiting_period(base::TimeDelta period) {
+ waiting_period_ = period;
+ }
+ base::TimeDelta waiting_period() const { return waiting_period_; }
+
+ inline void set_update_check_count_wait_enabled(bool enabled) {
+ update_check_count_wait_enabled_ = enabled;
+ }
+
+ inline bool update_check_count_wait_enabled() const {
+ return update_check_count_wait_enabled_;
+ }
+
+ inline void set_min_update_checks_needed(int64_t min) {
+ min_update_checks_needed_ = min;
+ }
+ inline int64_t min_update_checks_needed() const {
+ return min_update_checks_needed_;
+ }
+
+ inline void set_max_update_checks_allowed(int64_t max) {
+ max_update_checks_allowed_ = max;
+ }
+ inline int64_t max_update_checks_allowed() const {
+ return max_update_checks_allowed_;
+ }
+
+ // True if we're trying to update to a more stable channel.
+ // i.e. index(target_channel) > index(current_channel).
+ virtual bool to_more_stable_channel() const;
+
+ // Returns the app id corresponding to the current value of the
+ // download channel.
+ virtual std::string GetAppId() const;
+
+ // Suggested defaults
+ static const char kAppId[];
+ static const char kOsPlatform[];
+ static const char kOsVersion[];
+ static const char kUpdateUrl[];
+ static const char kUpdateChannelKey[];
+ static const char kIsPowerwashAllowedKey[];
+ static const char kAutoUpdateServerKey[];
+ static const int64_t kDefaultMinUpdateChecks = 0;
+ static const int64_t kDefaultMaxUpdateChecks = 8;
+
+ // Initializes all the data in the object. Non-empty
+ // |in_app_version| or |in_update_url| prevents automatic detection
+ // of the parameter. Returns true on success, false otherwise.
+ bool Init(const std::string& in_app_version,
+ const std::string& in_update_url,
+ bool in_interactive);
+
+ // Permanently changes the release channel to |channel|. Performs a
+ // powerwash, if required and allowed.
+ // Returns true on success, false otherwise. Note: This call will fail if
+ // there's a channel change pending already. This is to serialize all the
+ // channel changes done by the user in order to avoid having to solve
+ // numerous edge cases around ensuring the powerwash happens as intended in
+ // all such cases.
+ virtual bool SetTargetChannel(const std::string& channel,
+ bool is_powerwash_allowed);
+
+ // Updates the download channel for this particular attempt from the current
+ // value of target channel. This method takes a "snapshot" of the current
+ // value of target channel and uses it for all subsequent Omaha requests for
+ // this attempt (i.e. initial request as well as download progress/error
+ // event requests). The snapshot will be updated only when either this method
+ // or Init is called again.
+ virtual void UpdateDownloadChannel();
+
+ virtual bool is_powerwash_allowed() const { return is_powerwash_allowed_; }
+
+ // Check if the provided update URL is official, meaning either the default
+ // autoupdate server or the autoupdate autotest server.
+ virtual bool IsUpdateUrlOfficial() const;
+
+ // For unit-tests.
+ void set_root(const std::string& root);
+ void set_current_channel(const std::string& channel) {
+ current_channel_ = channel;
+ }
+ void set_target_channel(const std::string& channel) {
+ target_channel_ = channel;
+ }
+
+ // Enforce security mode for testing purposes.
+ void SetLockDown(bool lock);
+
+ private:
+ FRIEND_TEST(OmahaRequestParamsTest, IsValidChannelTest);
+ FRIEND_TEST(OmahaRequestParamsTest, ShouldLockDownTest);
+ FRIEND_TEST(OmahaRequestParamsTest, ChannelIndexTest);
+ FRIEND_TEST(OmahaRequestParamsTest, LsbPreserveTest);
+ FRIEND_TEST(OmahaRequestParamsTest, CollectECFWVersionsTest);
+
+ // Use a validator that is a non-static member of this class so that its
+ // inputs can be mocked in unit tests (e.g., build type for IsValidChannel).
+ typedef bool(
+ OmahaRequestParams::*ValueValidator)( // NOLINT(readability/casting)
+ const std::string&) const;
+
+ // Returns true if parameter values should be locked down for security
+ // reasons. If this is an official build running in normal boot mode, all
+ // values except the release channel are parsed only from the read-only rootfs
+ // partition and the channel values are restricted to a pre-approved set.
+ bool ShouldLockDown() const;
+
+ // Returns true if |channel| is a valid channel, false otherwise. This method
+ // restricts the channel value only if the image is official (see
+ // IsOfficialBuild).
+ bool IsValidChannel(const std::string& channel) const;
+
+ // Returns the index of the given channel.
+ int GetChannelIndex(const std::string& channel) const;
+
+ // Returns True if we should store the fw/ec versions based on our hwid_.
+ // Compares hwid to a set of whitelisted prefixes.
+ bool CollectECFWVersions() const;
+
+ // These are individual helper methods to initialize the said properties from
+ // the LSB value.
+ void SetTargetChannelFromLsbValue();
+ void SetCurrentChannelFromLsbValue();
+ void SetIsPowerwashAllowedFromLsbValue();
+
+ // Initializes the required properties from the LSB value.
+ void InitFromLsbValue();
+
+ // Fetches the value for a given key from
+ // /mnt/stateful_partition/etc/lsb-release if possible and |stateful_override|
+ // is true. Failing that, it looks for the key in /etc/lsb-release. If
+ // |validator| is non-null, uses it to validate and ignore invalid values.
+ std::string GetLsbValue(const std::string& key,
+ const std::string& default_value,
+ ValueValidator validator,
+ bool stateful_override) const;
+
+ // Gets the machine type (e.g. "i686").
+ std::string GetMachineType() const;
+
+ // Global system context.
+ SystemState* system_state_;
+
+ // Basic properties of the OS and Application that go into the Omaha request.
+ std::string os_platform_;
+ std::string os_version_;
+ std::string os_sp_;
+ std::string os_board_;
+
+ // The board app id identifies the app id for the board irrespective of the
+ // channel that we're on. The canary app id identifies the app id to be used
+ // iff we're in the canary-channel. These values could be different depending
+ // on how the release tools are implemented.
+ std::string board_app_id_;
+ std::string canary_app_id_;
+
+ std::string app_version_;
+ std::string app_lang_;
+
+ // The three channel values we deal with.
+ // Current channel: is always the channel from /etc/lsb-release. It never
+ // changes. It's just read in during initialization.
+ std::string current_channel_;
+
+ // Target channel: It starts off with the value of current channel. But if
+ // the user changes the channel, then it'll have a different value. If the
+ // user changes multiple times, target channel will always contain the most
+ // recent change and is updated immediately to the user-selected value even
+ // if we're in the middle of a download (as opposed to download channel
+ // which gets updated only at the start of next download)
+ std::string target_channel_;
+
+ // The channel from which we're downloading the payload. This should normally
+ // be the same as target channel. But if the user made another channel change
+ // we started the download, then they'd be different, in which case, we'd
+ // detect elsewhere that the target channel has been changed and cancel the
+ // current download attempt.
+ std::string download_channel_;
+
+ std::string hwid_; // Hardware Qualification ID of the client
+ std::string fw_version_; // Chrome OS Firmware Version.
+ std::string ec_version_; // Chrome OS EC Version.
+ bool delta_okay_; // If this client can accept a delta
+ bool interactive_; // Whether this is a user-initiated update check
+
+ // The URL to send the Omaha request to.
+ std::string update_url_;
+
+ // Prefix of the target OS version that the enterprise wants this device
+ // to be pinned to. It's empty otherwise.
+ std::string target_version_prefix_;
+
+ // True if scattering is enabled, in which case waiting_period_ specifies the
+ // amount of absolute time that we've to wait for before sending a request to
+ // Omaha.
+ bool wall_clock_based_wait_enabled_;
+ base::TimeDelta waiting_period_;
+
+ // True if scattering is enabled to denote the number of update checks
+ // we've to skip before we can send a request to Omaha. The min and max
+ // values establish the bounds for a random number to be chosen within that
+ // range to enable such a wait.
+ bool update_check_count_wait_enabled_;
+ int64_t min_update_checks_needed_;
+ int64_t max_update_checks_allowed_;
+
+ // True if we are allowed to do powerwash, if required, on a channel change.
+ bool is_powerwash_allowed_;
+
+ // When reading files, prepend root_ to the paths. Useful for testing.
+ std::string root_;
+
+ // Force security lock down for testing purposes.
+ bool force_lock_down_;
+ bool forced_lock_down_;
+
+ // TODO(jaysri): Uncomment this after fixing unit tests, as part of
+ // chromium-os:39752
+ // DISALLOW_COPY_AND_ASSIGN(OmahaRequestParams);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_
diff --git a/omaha_request_params_unittest.cc b/omaha_request_params_unittest.cc
new file mode 100644
index 0000000..82d74f1
--- /dev/null
+++ b/omaha_request_params_unittest.cc
@@ -0,0 +1,584 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/omaha_request_params.h"
+
+#include <stdio.h>
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class OmahaRequestParamsTest : public ::testing::Test {
+ public:
+ OmahaRequestParamsTest() : params_(&fake_system_state_) {}
+
+ protected:
+ // Return true iff the OmahaRequestParams::Init succeeded. If
+ // out is non-null, it's set w/ the generated data.
+ bool DoTest(OmahaRequestParams* out, const string& app_version,
+ const string& omaha_url);
+
+ void SetUp() override {
+ // Create a uniquely named test directory.
+ ASSERT_TRUE(utils::MakeTempDirectory(kTestDirTemplate,
+ &test_dir_));
+
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir_ + "/etc"));
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir_ +
+ kStatefulPartition + "/etc"));
+ // Create a fresh copy of the params for each test, so there's no
+ // unintended reuse of state across tests.
+ OmahaRequestParams new_params(&fake_system_state_);
+ params_ = new_params;
+ params_.set_root(test_dir_);
+ params_.SetLockDown(false);
+ }
+
+ void TearDown() override {
+ EXPECT_EQ(0, System(string("rm -rf ") + test_dir_));
+ }
+
+ OmahaRequestParams params_;
+ FakeSystemState fake_system_state_;
+
+ static const char* kTestDirTemplate;
+ string test_dir_;
+};
+
+const char* OmahaRequestParamsTest::kTestDirTemplate =
+ "omaha_request_params-test-XXXXXX";
+
+bool OmahaRequestParamsTest::DoTest(OmahaRequestParams* out,
+ const string& app_version,
+ const string& omaha_url) {
+ bool success = params_.Init(app_version, omaha_url, false);
+ if (out)
+ *out = params_;
+ return success;
+}
+
+namespace {
+string GetMachineType() {
+ string machine_type;
+ if (!utils::ReadPipe("uname -m", &machine_type))
+ return "";
+ // Strip anything from the first newline char.
+ size_t newline_pos = machine_type.find('\n');
+ if (newline_pos != string::npos)
+ machine_type.erase(newline_pos);
+ return machine_type;
+}
+} // namespace
+
+TEST_F(OmahaRequestParamsTest, SimpleTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, AppIDTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_RELEASE_APPID={58c35cef-9d30-476e-9098-ce20377d535d}\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{58c35cef-9d30-476e-9098-ce20377d535d}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, MissingChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRXCK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_EQ("", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ConfusingReleaseTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRXCK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_EQ("", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, MissingVersionTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ForceVersionTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "ForcedVersion", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("ForcedVersion_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("ForcedVersion", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ForcedURLTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", "http://forced.google.com"));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ("http://forced.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, MissingURLTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ(kProductionOmahaUrl, out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, NoDeltasTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRXCK=dev-channel"));
+ ASSERT_TRUE(WriteFileString(test_dir_ + "/.nodelta", ""));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_FALSE(out.delta_okay());
+}
+
+TEST_F(OmahaRequestParamsTest, OverrideTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+ "CHROMEOS_RELEASE_TRACK=beta-channel\n"
+ "CHROMEOS_AUSERVER=https://www.google.com"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("x86-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_FALSE(out.delta_okay());
+ EXPECT_EQ("beta-channel", out.target_channel());
+ EXPECT_EQ("https://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, OverrideLockDownTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=https://www.google.com"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ params_.SetLockDown(true);
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_FALSE(out.delta_okay());
+ EXPECT_EQ("stable-channel", out.target_channel());
+ EXPECT_EQ("https://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, OverrideSameChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("x86-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ {
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_root(test_dir_);
+ params.SetLockDown(false);
+ EXPECT_TRUE(params.Init("", "", false));
+ params.SetTargetChannel("canary-channel", false);
+ EXPECT_FALSE(params.is_powerwash_allowed());
+ }
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("canary-channel", out.target_channel());
+ EXPECT_FALSE(out.is_powerwash_allowed());
+}
+
+TEST_F(OmahaRequestParamsTest, SetIsPowerwashAllowedTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ {
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_root(test_dir_);
+ params.SetLockDown(false);
+ EXPECT_TRUE(params.Init("", "", false));
+ params.SetTargetChannel("canary-channel", true);
+ EXPECT_TRUE(params.is_powerwash_allowed());
+ }
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("canary-channel", out.target_channel());
+ EXPECT_TRUE(out.is_powerwash_allowed());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelInvalidTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ {
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_root(string("./") + test_dir_);
+ params.SetLockDown(true);
+ EXPECT_TRUE(params.Init("", "", false));
+ params.SetTargetChannel("dogfood-channel", true);
+ EXPECT_FALSE(params.is_powerwash_allowed());
+ }
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_FALSE(out.is_powerwash_allowed());
+}
+
+TEST_F(OmahaRequestParamsTest, IsValidChannelTest) {
+ params_.SetLockDown(false);
+ EXPECT_TRUE(params_.IsValidChannel("canary-channel"));
+ EXPECT_TRUE(params_.IsValidChannel("stable-channel"));
+ EXPECT_TRUE(params_.IsValidChannel("beta-channel"));
+ EXPECT_TRUE(params_.IsValidChannel("dev-channel"));
+ EXPECT_FALSE(params_.IsValidChannel("testimage-channel"));
+ EXPECT_FALSE(params_.IsValidChannel("dogfood-channel"));
+ EXPECT_FALSE(params_.IsValidChannel("some-channel"));
+ EXPECT_FALSE(params_.IsValidChannel(""));
+}
+
+TEST_F(OmahaRequestParamsTest, ValidChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ params_.SetLockDown(true);
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelWorks) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ params_.SetLockDown(false);
+
+ // Check LSB value is used by default when SetTargetChannel is not called.
+ params_.Init("", "", false);
+ EXPECT_EQ("dev-channel", params_.target_channel());
+
+ // When an invalid value is set, it should be ignored and the
+ // value from lsb-release should be used instead.
+ params_.Init("", "", false);
+ EXPECT_FALSE(params_.SetTargetChannel("invalid-channel", false));
+ EXPECT_EQ("dev-channel", params_.target_channel());
+
+ // When set to a valid value, it should take effect.
+ params_.Init("", "", false);
+ EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true));
+ EXPECT_EQ("beta-channel", params_.target_channel());
+
+ // When set to the same value, it should be idempotent.
+ params_.Init("", "", false);
+ EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true));
+ EXPECT_EQ("beta-channel", params_.target_channel());
+
+ // When set to a valid value while a change is already pending, it should
+ // succeed.
+ params_.Init("", "", false);
+ EXPECT_TRUE(params_.SetTargetChannel("stable-channel", true));
+ EXPECT_EQ("stable-channel", params_.target_channel());
+
+ // Set a different channel in stateful LSB release.
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"
+ "CHROMEOS_IS_POWERWASH_ALLOWED=true\n"));
+
+ // When set to a valid value while a change is already pending, it should
+ // succeed.
+ params_.Init("", "", false);
+ EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true));
+ // The target channel should reflect the change, but the download channel
+ // should continue to retain the old value ...
+ EXPECT_EQ("beta-channel", params_.target_channel());
+ EXPECT_EQ("stable-channel", params_.download_channel());
+
+ // ... until we update the download channel explicitly.
+ params_.UpdateDownloadChannel();
+ EXPECT_EQ("beta-channel", params_.download_channel());
+ EXPECT_EQ("beta-channel", params_.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ChannelIndexTest) {
+ int canary = params_.GetChannelIndex("canary-channel");
+ int dev = params_.GetChannelIndex("dev-channel");
+ int beta = params_.GetChannelIndex("beta-channel");
+ int stable = params_.GetChannelIndex("stable-channel");
+ EXPECT_LE(canary, dev);
+ EXPECT_LE(dev, beta);
+ EXPECT_LE(beta, stable);
+
+ // testimage-channel or other names are not recognized, so index will be -1.
+ int testimage = params_.GetChannelIndex("testimage-channel");
+ int bogus = params_.GetChannelIndex("bogus-channel");
+ EXPECT_EQ(-1, testimage);
+ EXPECT_EQ(-1, bogus);
+}
+
+TEST_F(OmahaRequestParamsTest, ToMoreStableChannelFlagTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"
+ "CHROMEOS_AUSERVER=https://www.google.com"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("https://www.google.com", out.update_url());
+ EXPECT_FALSE(out.delta_okay());
+ EXPECT_EQ("stable-channel", out.target_channel());
+ EXPECT_TRUE(out.to_more_stable_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ShouldLockDownTest) {
+ EXPECT_FALSE(params_.ShouldLockDown());
+}
+
+TEST_F(OmahaRequestParamsTest, BoardAppIdUsedForNonCanaryChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID=r\n"
+ "CHROMEOS_BOARD_APPID=b\n"
+ "CHROMEOS_CANARY_APPID=c\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("stable-channel", out.download_channel());
+ EXPECT_EQ("b", out.GetAppId());
+}
+
+TEST_F(OmahaRequestParamsTest, CanaryAppIdUsedForCanaryChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID=r\n"
+ "CHROMEOS_BOARD_APPID=b\n"
+ "CHROMEOS_CANARY_APPID=c\n"
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("canary-channel", out.download_channel());
+ EXPECT_EQ("c", out.GetAppId());
+}
+
+TEST_F(OmahaRequestParamsTest, ReleaseAppIdUsedAsDefaultTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID=r\n"
+ "CHROMEOS_CANARY_APPID=c\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("stable-channel", out.download_channel());
+ EXPECT_EQ("r", out.GetAppId());
+}
+
+TEST_F(OmahaRequestParamsTest, CollectECFWVersionsTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID=r\n"
+ "CHROMEOS_CANARY_APPID=c\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ OmahaRequestParams out(&fake_system_state_);
+ out.hwid_ = string("STUMPY ALEX 12345");
+ EXPECT_FALSE(out.CollectECFWVersions());
+
+ out.hwid_ = string("SNOW 12345");
+ EXPECT_TRUE(out.CollectECFWVersions());
+
+ out.hwid_ = string("SAMS ALEX 12345");
+ EXPECT_TRUE(out.CollectECFWVersions());
+}
+
+
+} // namespace chromeos_update_engine
diff --git a/omaha_response.h b/omaha_response.h
new file mode 100644
index 0000000..d58bf46
--- /dev/null
+++ b/omaha_response.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_OMAHA_RESPONSE_H_
+#define UPDATE_ENGINE_OMAHA_RESPONSE_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+// This struct encapsulates the data Omaha's response for the request.
+// The strings in this struct are not XML escaped.
+struct OmahaResponse {
+ // True iff there is an update to be downloaded.
+ bool update_exists = false;
+
+ // If non-zero, server-dictated poll interval in seconds.
+ int poll_interval = 0;
+
+ // These are only valid if update_exists is true:
+ std::string version;
+
+ // The ordered list of URLs in the Omaha response. Each item is a complete
+ // URL (i.e. in terms of Omaha XML, each value is a urlBase + packageName)
+ std::vector<std::string> payload_urls;
+
+ std::string more_info_url;
+ std::string hash;
+ std::string metadata_signature;
+ std::string deadline;
+ off_t size = 0;
+ off_t metadata_size = 0;
+ int max_days_to_scatter = 0;
+ // The number of URL-related failures to tolerate before moving on to the
+ // next URL in the current pass. This is a configurable value from the
+ // Omaha Response attribute, if ever we need to fine tune the behavior.
+ uint32_t max_failure_count_per_url = 0;
+ bool prompt = false;
+
+ // True if the payload described in this response is a delta payload.
+ // False if it's a full payload.
+ bool is_delta_payload = false;
+
+ // True if the Omaha rule instructs us to disable the back-off logic
+ // on the client altogether. False otherwise.
+ bool disable_payload_backoff = false;
+
+ // True if the Omaha rule instructs us to disable p2p for downloading.
+ bool disable_p2p_for_downloading = false;
+
+ // True if the Omaha rule instructs us to disable p2p for sharing.
+ bool disable_p2p_for_sharing = false;
+
+ // If not blank, a base-64 encoded representation of the PEM-encoded
+ // public key in the response.
+ std::string public_key_rsa;
+
+ // If not -1, the number of days since the epoch Jan 1, 2007 0:00
+ // PST, according to the Omaha Server's clock and timezone (PST8PDT,
+ // aka "Pacific Time".)
+ int install_date_days = -1;
+};
+COMPILE_ASSERT(sizeof(off_t) == 8, off_t_not_64bit);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_OMAHA_RESPONSE_H_
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
new file mode 100644
index 0000000..67639cc
--- /dev/null
+++ b/omaha_response_handler_action.cc
@@ -0,0 +1,196 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/omaha_response_handler_action.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/connection_manager.h"
+#include "update_engine/constants.h"
+#include "update_engine/delta_performer.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char OmahaResponseHandlerAction::kDeadlineFile[] =
+ "/tmp/update-check-response-deadline";
+
+OmahaResponseHandlerAction::OmahaResponseHandlerAction(
+ SystemState* system_state)
+ : system_state_(system_state),
+ got_no_update_response_(false),
+ key_path_(DeltaPerformer::kUpdatePayloadPublicKeyPath),
+ deadline_file_(kDeadlineFile) {}
+
+OmahaResponseHandlerAction::OmahaResponseHandlerAction(
+ SystemState* system_state, const string& deadline_file)
+ : system_state_(system_state),
+ got_no_update_response_(false),
+ key_path_(DeltaPerformer::kUpdatePayloadPublicKeyPath),
+ deadline_file_(deadline_file) {}
+
+void OmahaResponseHandlerAction::PerformAction() {
+ CHECK(HasInputObject());
+ ScopedActionCompleter completer(processor_, this);
+ const OmahaResponse& response = GetInputObject();
+ if (!response.update_exists) {
+ got_no_update_response_ = true;
+ LOG(INFO) << "There are no updates. Aborting.";
+ return;
+ }
+
+ // All decisions as to which URL should be used have already been done. So,
+ // make the current URL as the download URL.
+ string current_url = system_state_->payload_state()->GetCurrentUrl();
+ if (current_url.empty()) {
+ // This shouldn't happen as we should always supply the HTTPS backup URL.
+ // Handling this anyway, just in case.
+ LOG(ERROR) << "There are no suitable URLs in the response to use.";
+ completer.set_code(ErrorCode::kOmahaResponseInvalid);
+ return;
+ }
+
+ install_plan_.download_url = current_url;
+ install_plan_.version = response.version;
+
+ OmahaRequestParams* const params = system_state_->request_params();
+ PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+ // If we're using p2p to download and there is a local peer, use it.
+ if (payload_state->GetUsingP2PForDownloading() &&
+ !payload_state->GetP2PUrl().empty()) {
+ LOG(INFO) << "Replacing URL " << install_plan_.download_url
+ << " with local URL " << payload_state->GetP2PUrl()
+ << " since p2p is enabled.";
+ install_plan_.download_url = payload_state->GetP2PUrl();
+ payload_state->SetUsingP2PForDownloading(true);
+ }
+
+ // Fill up the other properties based on the response.
+ install_plan_.payload_size = response.size;
+ install_plan_.payload_hash = response.hash;
+ install_plan_.metadata_size = response.metadata_size;
+ install_plan_.metadata_signature = response.metadata_signature;
+ install_plan_.public_key_rsa = response.public_key_rsa;
+ install_plan_.hash_checks_mandatory = AreHashChecksMandatory(response);
+ install_plan_.is_resume =
+ DeltaPerformer::CanResumeUpdate(system_state_->prefs(), response.hash);
+ if (install_plan_.is_resume) {
+ payload_state->UpdateResumed();
+ } else {
+ payload_state->UpdateRestarted();
+ LOG_IF(WARNING, !DeltaPerformer::ResetUpdateProgress(
+ system_state_->prefs(), false))
+ << "Unable to reset the update progress.";
+ LOG_IF(WARNING, !system_state_->prefs()->SetString(
+ kPrefsUpdateCheckResponseHash, response.hash))
+ << "Unable to save the update check response hash.";
+ }
+ install_plan_.is_full_update = !response.is_delta_payload;
+
+ TEST_AND_RETURN(utils::GetInstallDev(
+ (!boot_device_.empty() ? boot_device_ :
+ system_state_->hardware()->BootDevice()),
+ &install_plan_.install_path));
+ install_plan_.kernel_install_path =
+ utils::KernelDeviceOfBootDevice(install_plan_.install_path);
+ install_plan_.source_path = system_state_->hardware()->BootDevice();
+ install_plan_.kernel_source_path =
+ utils::KernelDeviceOfBootDevice(install_plan_.source_path);
+
+ if (params->to_more_stable_channel() && params->is_powerwash_allowed())
+ install_plan_.powerwash_required = true;
+
+
+ TEST_AND_RETURN(HasOutputPipe());
+ if (HasOutputPipe())
+ SetOutputObject(install_plan_);
+ LOG(INFO) << "Using this install plan:";
+ install_plan_.Dump();
+
+ // Send the deadline data (if any) to Chrome through a file. This is a pretty
+ // hacky solution but should be OK for now.
+ //
+ // TODO(petkov): Re-architect this to avoid communication through a
+ // file. Ideally, we would include this information in D-Bus's GetStatus
+ // method and UpdateStatus signal. A potential issue is that update_engine may
+ // be unresponsive during an update download.
+ utils::WriteFile(deadline_file_.c_str(),
+ response.deadline.data(),
+ response.deadline.size());
+ chmod(deadline_file_.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ completer.set_code(ErrorCode::kSuccess);
+}
+
+bool OmahaResponseHandlerAction::AreHashChecksMandatory(
+ const OmahaResponse& response) {
+ // We sometimes need to waive the hash checks in order to download from
+ // sources that don't provide hashes, such as dev server.
+ // At this point UpdateAttempter::IsAnyUpdateSourceAllowed() has already been
+ // checked, so an unofficial update URL won't get this far unless it's OK to
+ // use without a hash. Additionally, we want to always waive hash checks on
+ // unofficial builds (i.e. dev/test images).
+ // The end result is this:
+ // * Base image:
+ // - Official URLs require a hash.
+ // - Unofficial URLs only get this far if the IsAnyUpdateSourceAllowed()
+ // devmode/debugd checks pass, in which case the hash is waived.
+ // * Dev/test image:
+ // - Any URL is allowed through with no hash checking.
+ if (!system_state_->request_params()->IsUpdateUrlOfficial() ||
+ !system_state_->hardware()->IsOfficialBuild()) {
+ // Still do a hash check if a public key is included.
+ if (!response.public_key_rsa.empty()) {
+ // The autoupdate_CatchBadSignatures test checks for this string
+ // in log-files. Keep in sync.
+ LOG(INFO) << "Mandating payload hash checks since Omaha Response "
+ << "for unofficial build includes public RSA key.";
+ return true;
+ } else {
+ LOG(INFO) << "Waiving payload hash checks for unofficial update URL.";
+ return false;
+ }
+ }
+
+ // If we're using p2p, |install_plan_.download_url| may contain a
+ // HTTP URL even if |response.payload_urls| contain only HTTPS URLs.
+ if (!base::StartsWithASCII(install_plan_.download_url, "https://", false)) {
+ LOG(INFO) << "Mandating hash checks since download_url is not HTTPS.";
+ return true;
+ }
+
+ // TODO(jaysri): VALIDATION: For official builds, we currently waive hash
+ // checks for HTTPS until we have rolled out at least once and are confident
+ // nothing breaks. chromium-os:37082 tracks turning this on for HTTPS
+ // eventually.
+
+ // Even if there's a single non-HTTPS URL, make the hash checks as
+ // mandatory because we could be downloading the payload from any URL later
+ // on. It's really hard to do book-keeping based on each byte being
+ // downloaded to see whether we only used HTTPS throughout.
+ for (size_t i = 0; i < response.payload_urls.size(); i++) {
+ if (!base::StartsWithASCII(response.payload_urls[i], "https://", false)) {
+ LOG(INFO) << "Mandating payload hash checks since Omaha response "
+ << "contains non-HTTPS URL(s)";
+ return true;
+ }
+ }
+
+ LOG(INFO) << "Waiving payload hash checks since Omaha response "
+ << "only has HTTPS URL(s)";
+ return false;
+}
+
+} // namespace chromeos_update_engine
diff --git a/omaha_response_handler_action.h b/omaha_response_handler_action.h
new file mode 100644
index 0000000..c3e4a68
--- /dev/null
+++ b/omaha_response_handler_action.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_
+#define UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_
+
+#include <string>
+
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/action.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/system_state.h"
+
+// This class reads in an Omaha response and converts what it sees into
+// an install plan which is passed out.
+
+namespace chromeos_update_engine {
+
+class OmahaResponseHandlerAction;
+
+template<>
+class ActionTraits<OmahaResponseHandlerAction> {
+ public:
+ typedef OmahaResponse InputObjectType;
+ typedef InstallPlan OutputObjectType;
+};
+
+class OmahaResponseHandlerAction : public Action<OmahaResponseHandlerAction> {
+ public:
+ static const char kDeadlineFile[];
+
+ explicit OmahaResponseHandlerAction(SystemState* system_state);
+
+ typedef ActionTraits<OmahaResponseHandlerAction>::InputObjectType
+ InputObjectType;
+ typedef ActionTraits<OmahaResponseHandlerAction>::OutputObjectType
+ OutputObjectType;
+ void PerformAction() override;
+
+ // This is a synchronous action, and thus TerminateProcessing() should
+ // never be called
+ void TerminateProcessing() override { CHECK(false); }
+
+ // For unit-testing
+ void set_boot_device(const std::string& boot_device) {
+ boot_device_ = boot_device;
+ }
+
+ bool GotNoUpdateResponse() const { return got_no_update_response_; }
+ const InstallPlan& install_plan() const { return install_plan_; }
+
+ // Debugging/logging
+ static std::string StaticType() { return "OmahaResponseHandlerAction"; }
+ std::string Type() const override { return StaticType(); }
+ void set_key_path(const std::string& path) { key_path_ = path; }
+
+ private:
+ // Returns true if payload hash checks are mandatory based on the state
+ // of the system and the contents of the Omaha response. False otherwise.
+ bool AreHashChecksMandatory(const OmahaResponse& response);
+
+ // Global system context.
+ SystemState* system_state_;
+
+ // set to non-empty in unit tests
+ std::string boot_device_;
+
+ // The install plan, if we have an update.
+ InstallPlan install_plan_;
+
+ // True only if we got a response and the response said no updates
+ bool got_no_update_response_;
+
+ // Public key path to use for payload verification.
+ std::string key_path_;
+
+ // File used for communication deadline to Chrome.
+ const std::string deadline_file_;
+
+ // Special ctor + friend declarations for testing purposes.
+ OmahaResponseHandlerAction(SystemState* system_state,
+ const std::string& deadline_file);
+
+ friend class OmahaResponseHandlerActionTest;
+
+ FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+
+ DISALLOW_COPY_AND_ASSIGN(OmahaResponseHandlerAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
new file mode 100644
index 0000000..bdbf3a7
--- /dev/null
+++ b/omaha_response_handler_action_unittest.cc
@@ -0,0 +1,434 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/omaha_response_handler_action.h"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using testing::Return;
+
+namespace chromeos_update_engine {
+
+class OmahaResponseHandlerActionTest : public ::testing::Test {
+ public:
+ // Return true iff the OmahaResponseHandlerAction succeeded.
+ // If out is non-null, it's set w/ the response from the action.
+ bool DoTestCommon(FakeSystemState* fake_system_state,
+ const OmahaResponse& in,
+ const string& boot_dev,
+ const string& deadline_file,
+ InstallPlan* out);
+ bool DoTest(const OmahaResponse& in,
+ const string& boot_dev,
+ const string& deadline_file,
+ InstallPlan* out);
+};
+
+class OmahaResponseHandlerActionProcessorDelegate
+ : public ActionProcessorDelegate {
+ public:
+ OmahaResponseHandlerActionProcessorDelegate()
+ : code_(ErrorCode::kError),
+ code_set_(false) {}
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == OmahaResponseHandlerAction::StaticType()) {
+ code_ = code;
+ code_set_ = true;
+ }
+ }
+ ErrorCode code_;
+ bool code_set_;
+};
+
+namespace {
+const char* const kLongName =
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "-the_update_a.b.c.d_DELTA_.tgz";
+const char* const kBadVersion = "don't update me";
+} // namespace
+
+bool OmahaResponseHandlerActionTest::DoTestCommon(
+ FakeSystemState* fake_system_state,
+ const OmahaResponse& in,
+ const string& boot_dev,
+ const string& test_deadline_file,
+ InstallPlan* out) {
+ ActionProcessor processor;
+ OmahaResponseHandlerActionProcessorDelegate delegate;
+ processor.set_delegate(&delegate);
+
+ ObjectFeederAction<OmahaResponse> feeder_action;
+ feeder_action.set_obj(in);
+ if (in.update_exists && in.version != kBadVersion) {
+ EXPECT_CALL(*(fake_system_state->mock_prefs()),
+ SetString(kPrefsUpdateCheckResponseHash, in.hash))
+ .WillOnce(Return(true));
+ }
+
+ string current_url = in.payload_urls.size() ? in.payload_urls[0] : "";
+ EXPECT_CALL(*(fake_system_state->mock_payload_state()), GetCurrentUrl())
+ .WillRepeatedly(Return(current_url));
+
+ OmahaResponseHandlerAction response_handler_action(
+ fake_system_state,
+ (test_deadline_file.empty() ?
+ OmahaResponseHandlerAction::kDeadlineFile : test_deadline_file));
+ response_handler_action.set_boot_device(boot_dev);
+ BondActions(&feeder_action, &response_handler_action);
+ ObjectCollectorAction<InstallPlan> collector_action;
+ BondActions(&response_handler_action, &collector_action);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&response_handler_action);
+ processor.EnqueueAction(&collector_action);
+ processor.StartProcessing();
+ EXPECT_TRUE(!processor.IsRunning())
+ << "Update test to handle non-async actions";
+ if (out)
+ *out = collector_action.object();
+ EXPECT_TRUE(delegate.code_set_);
+ return delegate.code_ == ErrorCode::kSuccess;
+}
+
+bool OmahaResponseHandlerActionTest::DoTest(const OmahaResponse& in,
+ const string& boot_dev,
+ const string& deadline_file,
+ InstallPlan* out) {
+ FakeSystemState fake_system_state;
+ return DoTestCommon(&fake_system_state, in, boot_dev, deadline_file, out);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, SimpleTest) {
+ string test_deadline_file;
+ CHECK(utils::MakeTempFile(
+ "omaha_response_handler_action_unittest-XXXXXX",
+ &test_deadline_file, nullptr));
+ ScopedPathUnlinker deadline_unlinker(test_deadline_file);
+ {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://foo/the_update_a.b.c.d.tgz");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASH+";
+ in.size = 12;
+ in.prompt = false;
+ in.deadline = "20101020";
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "/dev/sda3", test_deadline_file, &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_EQ("/dev/sda5", install_plan.install_path);
+ string deadline;
+ EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline));
+ EXPECT_EQ("20101020", deadline);
+ struct stat deadline_stat;
+ EXPECT_EQ(0, stat(test_deadline_file.c_str(), &deadline_stat));
+ EXPECT_EQ(S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
+ deadline_stat.st_mode);
+ EXPECT_EQ(in.version, install_plan.version);
+ }
+ {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://foo/the_update_a.b.c.d.tgz");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ in.prompt = true;
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "/dev/sda5", test_deadline_file, &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_EQ("/dev/sda3", install_plan.install_path);
+ string deadline;
+ EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline) &&
+ deadline.empty());
+ EXPECT_EQ(in.version, install_plan.version);
+ }
+ {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back(kLongName);
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ in.prompt = true;
+ in.deadline = "some-deadline";
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "/dev/sda3", test_deadline_file, &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_EQ("/dev/sda5", install_plan.install_path);
+ string deadline;
+ EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline));
+ EXPECT_EQ("some-deadline", deadline);
+ EXPECT_EQ(in.version, install_plan.version);
+ }
+}
+
+TEST_F(OmahaResponseHandlerActionTest, NoUpdatesTest) {
+ OmahaResponse in;
+ in.update_exists = false;
+ InstallPlan install_plan;
+ EXPECT_FALSE(DoTest(in, "/dev/sda1", "", &install_plan));
+ EXPECT_EQ("", install_plan.download_url);
+ EXPECT_EQ("", install_plan.payload_hash);
+ EXPECT_EQ("", install_plan.install_path);
+ EXPECT_EQ("", install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://test.should/need/hash.checks.signed");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ FakeSystemState fake_system_state;
+ // Hash checks are always skipped for non-official update URLs.
+ EXPECT_CALL(*(fake_system_state.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+ &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_TRUE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForUnofficialUpdateUrl) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://url.normally/needs/hash.checks.signed");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ FakeSystemState fake_system_state;
+ EXPECT_CALL(*(fake_system_state.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(false));
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+ &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_FALSE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
+ HashChecksForOfficialUrlUnofficialBuildTest) {
+ // Official URLs for unofficial builds (dev/test images) don't require hash.
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://url.normally/needs/hash.checks.signed");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ FakeSystemState fake_system_state;
+ EXPECT_CALL(*(fake_system_state.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ fake_system_state.fake_hardware()->SetIsOfficialBuild(false);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+ &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_FALSE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpsTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("https://test.should.not/need/hash.checks.signed");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ FakeSystemState fake_system_state;
+ EXPECT_CALL(*(fake_system_state.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+ &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_FALSE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForBothHttpAndHttpsTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://test.should.still/need/hash.checks");
+ in.payload_urls.push_back("https://test.should.still/need/hash.checks");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ FakeSystemState fake_system_state;
+ EXPECT_CALL(*(fake_system_state.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+ &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_TRUE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, ChangeToMoreStableChannelTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("https://MoreStableChannelTest");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHjk";
+ in.size = 15;
+
+ // Create a uniquely named test directory.
+ string test_dir;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ "omaha_response_handler_action-test-XXXXXX", &test_dir));
+
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+ kStatefulPartition + "/etc"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_IS_POWERWASH_ALLOWED=true\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+
+ FakeSystemState fake_system_state;
+ OmahaRequestParams params(&fake_system_state);
+ params.set_root(test_dir);
+ params.SetLockDown(false);
+ params.Init("1.2.3.4", "", 0);
+ EXPECT_EQ("canary-channel", params.current_channel());
+ EXPECT_EQ("stable-channel", params.target_channel());
+ EXPECT_TRUE(params.to_more_stable_channel());
+ EXPECT_TRUE(params.is_powerwash_allowed());
+
+ fake_system_state.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+ &install_plan));
+ EXPECT_TRUE(install_plan.powerwash_required);
+
+ ASSERT_TRUE(test_utils::RecursiveUnlinkDir(test_dir));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, ChangeToLessStableChannelTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("https://LessStableChannelTest");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHjk";
+ in.size = 15;
+
+ // Create a uniquely named test directory.
+ string test_dir;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ "omaha_response_handler_action-test-XXXXXX", &test_dir));
+
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+ kStatefulPartition + "/etc"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+
+ FakeSystemState fake_system_state;
+ OmahaRequestParams params(&fake_system_state);
+ params.set_root(test_dir);
+ params.SetLockDown(false);
+ params.Init("5.6.7.8", "", 0);
+ EXPECT_EQ("stable-channel", params.current_channel());
+ params.SetTargetChannel("canary-channel", false);
+ EXPECT_EQ("canary-channel", params.target_channel());
+ EXPECT_FALSE(params.to_more_stable_channel());
+ EXPECT_FALSE(params.is_powerwash_allowed());
+
+ fake_system_state.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+ &install_plan));
+ EXPECT_FALSE(install_plan.powerwash_required);
+
+ ASSERT_TRUE(test_utils::RecursiveUnlinkDir(test_dir));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, P2PUrlIsUsedAndHashChecksMandatory) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("https://would.not/cause/hash/checks");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+
+ FakeSystemState fake_system_state;
+ OmahaRequestParams params(&fake_system_state);
+ // We're using a real OmahaRequestParams object here so we can't mock
+ // IsUpdateUrlOfficial(), but setting the update URL to the AutoUpdate test
+ // server will cause IsUpdateUrlOfficial() to return true.
+ params.set_update_url(kAUTestOmahaUrl);
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_CALL(*fake_system_state.mock_payload_state(),
+ SetUsingP2PForDownloading(true));
+
+ string p2p_url = "http://9.8.7.6/p2p";
+ EXPECT_CALL(*fake_system_state.mock_payload_state(), GetP2PUrl())
+ .WillRepeatedly(Return(p2p_url));
+ EXPECT_CALL(*fake_system_state.mock_payload_state(),
+ GetUsingP2PForDownloading()).WillRepeatedly(Return(true));
+
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+ &install_plan));
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_EQ(install_plan.download_url, p2p_url);
+ EXPECT_TRUE(install_plan.hash_checks_mandatory);
+}
+
+} // namespace chromeos_update_engine
diff --git a/p2p_manager.cc b/p2p_manager.cc
new file mode 100644
index 0000000..c72a023
--- /dev/null
+++ b/p2p_manager.cc
@@ -0,0 +1,727 @@
+// 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 nanosecond 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 <errno.h>
+#include <fcntl.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 <algorithm>
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_enumerator.h>
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/glib_utils.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+#include "update_engine/utils.h"
+
+using base::Bind;
+using base::Callback;
+using base::FilePath;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::Policy;
+using chromeos_update_manager::UpdateManager;
+using std::map;
+using std::pair;
+using std::string;
+using std::unique_ptr;
+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() {}
+
+ FilePath GetP2PDir() override {
+ return FilePath(kDefaultP2PDir);
+ }
+
+ vector<string> GetInitctlArgs(bool is_start) override {
+ vector<string> args;
+ args.push_back("initctl");
+ args.push_back(is_start ? "start" : "stop");
+ args.push_back("p2p");
+ return args;
+ }
+
+ vector<string> GetP2PClientArgs(const string &file_id,
+ size_t minimum_size) override {
+ 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,
+ ClockInterface *clock,
+ UpdateManager* update_manager,
+ const string& file_extension,
+ const int num_files_to_keep,
+ const TimeDelta& max_file_age);
+
+ // P2PManager methods.
+ void SetDevicePolicy(const policy::DevicePolicy* device_policy) override;
+ bool IsP2PEnabled() override;
+ bool EnsureP2PRunning() override;
+ bool EnsureP2PNotRunning() override;
+ bool PerformHousekeeping() override;
+ void LookupUrlForFile(const string& file_id,
+ size_t minimum_size,
+ TimeDelta max_time_to_wait,
+ LookupCallback callback) override;
+ bool FileShare(const string& file_id,
+ size_t expected_size) override;
+ FilePath FileGetPath(const string& file_id) override;
+ ssize_t FileGetSize(const string& file_id) override;
+ ssize_t FileGetExpectedSize(const string& file_id) override;
+ bool FileGetVisible(const string& file_id,
+ bool *out_result) override;
+ bool FileMakeVisible(const string& file_id) override;
+ int CountSharedFiles() override;
+
+ 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);
+
+ // Utility function to delete a file given by |path| and log the
+ // path as well as |reason|. Returns false on failure.
+ bool DeleteP2PFile(const FilePath& path, const string& reason);
+
+ // Schedules an async request for tracking changes in P2P enabled status.
+ void ScheduleEnabledStatusChange();
+
+ // An async callback used by the above.
+ void OnEnabledStatusChange(EvalStatus status, const bool& result);
+
+ // The device policy being used or null if no policy is being used.
+ const policy::DevicePolicy* device_policy_ = nullptr;
+
+ // Configuration object.
+ unique_ptr<Configuration> configuration_;
+
+ // Object for telling the time.
+ ClockInterface* clock_;
+
+ // A pointer to the global Update Manager.
+ UpdateManager* update_manager_;
+
+ // 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_;
+
+ // If non-zero, files older than this will not be kept after
+ // performing housekeeping.
+ const TimeDelta max_file_age_;
+
+ // The string ".p2p".
+ static const char kP2PExtension[];
+
+ // The string ".tmp".
+ static const char kTmpExtension[];
+
+ // Whether P2P service may be running; initially, we assume it may be.
+ bool may_be_running_ = true;
+
+ // The current known enabled status of the P2P feature (initialized lazily),
+ // and whether an async status check has been scheduled.
+ bool is_enabled_;
+ bool waiting_for_enabled_status_change_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(P2PManagerImpl);
+};
+
+const char P2PManagerImpl::kP2PExtension[] = ".p2p";
+
+const char P2PManagerImpl::kTmpExtension[] = ".tmp";
+
+P2PManagerImpl::P2PManagerImpl(Configuration *configuration,
+ ClockInterface *clock,
+ UpdateManager* update_manager,
+ const string& file_extension,
+ const int num_files_to_keep,
+ const TimeDelta& max_file_age)
+ : clock_(clock),
+ update_manager_(update_manager),
+ file_extension_(file_extension),
+ num_files_to_keep_(num_files_to_keep),
+ max_file_age_(max_file_age) {
+ configuration_.reset(configuration != nullptr ? configuration :
+ new ConfigurationImpl());
+}
+
+void P2PManagerImpl::SetDevicePolicy(
+ const policy::DevicePolicy* device_policy) {
+ device_policy_ = device_policy;
+}
+
+bool P2PManagerImpl::IsP2PEnabled() {
+ if (!waiting_for_enabled_status_change_) {
+ // Get and store an initial value.
+ if (update_manager_->PolicyRequest(&Policy::P2PEnabled, &is_enabled_) ==
+ EvalStatus::kFailed) {
+ is_enabled_ = false;
+ LOG(ERROR) << "Querying P2P enabled status failed, disabling.";
+ }
+
+ // Track future changes (async).
+ ScheduleEnabledStatusChange();
+ }
+
+ return is_enabled_;
+}
+
+bool P2PManagerImpl::EnsureP2P(bool should_be_running) {
+ int return_code = 0;
+ string output;
+
+ may_be_running_ = true; // Unless successful, we must be conservative.
+
+ vector<string> args = configuration_->GetInitctlArgs(should_be_running);
+ if (!Subprocess::SynchronousExec(args, &return_code, &output)) {
+ LOG(ERROR) << "Error spawning " << utils::StringVectorToString(args);
+ return false;
+ }
+
+ // If initctl(8) does not exit normally (exit status other than zero), ensure
+ // that the error message is not benign by scanning stderr; this is a
+ // necessity because initctl does not offer actions such as "start if not
+ // running" or "stop if running".
+ // TODO(zeuthen,chromium:277051): Avoid doing this.
+ if (return_code != 0) {
+ const char *expected_error_message = should_be_running ?
+ "initctl: Job is already running: p2p\n" :
+ "initctl: Unknown instance \n";
+ if (output != expected_error_message)
+ return false;
+ }
+
+ may_be_running_ = should_be_running; // Successful after all.
+ return true;
+}
+
+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::DeleteP2PFile(const FilePath& path,
+ const string& reason) {
+ LOG(INFO) << "Deleting p2p file " << path.value()
+ << " (reason: " << reason << ")";
+ if (unlink(path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting p2p file " << path.value();
+ return false;
+ }
+ return true;
+}
+
+
+bool P2PManagerImpl::PerformHousekeeping() {
+ // Open p2p dir.
+ FilePath p2p_dir = configuration_->GetP2PDir();
+ const string ext_visible = GetExt(kVisible);
+ const string ext_non_visible = GetExt(kNonVisible);
+
+ bool deletion_failed = false;
+ vector<pair<FilePath, Time>> matches;
+
+ base::FileEnumerator dir(p2p_dir, false, base::FileEnumerator::FILES);
+ // Go through all files and collect their mtime.
+ for (FilePath name = dir.Next(); !name.empty(); name = dir.Next()) {
+ if (!(base::EndsWith(name.value(), ext_visible, true) ||
+ base::EndsWith(name.value(), ext_non_visible, true)))
+ continue;
+
+ Time time = dir.GetInfo().GetLastModifiedTime();
+
+ // If instructed to keep only files younger than a given age
+ // (|max_file_age_| != 0), delete files satisfying this criteria
+ // right now. Otherwise add it to a list we'll consider for later.
+ if (clock_ != nullptr && max_file_age_ != TimeDelta() &&
+ clock_->GetWallclockTime() - time > max_file_age_) {
+ if (!DeleteP2PFile(name, "file too old"))
+ deletion_failed = true;
+ } else {
+ matches.push_back(std::make_pair(name, time));
+ }
+ }
+
+ // If instructed to only keep N files (|max_files_to_keep_ != 0),
+ // sort list of matches, newest (biggest time) to oldest (lowest
+ // time). Then delete starting at element |num_files_to_keep_|.
+ if (num_files_to_keep_ > 0) {
+ std::sort(matches.begin(), matches.end(), MatchCompareFunc);
+ vector<pair<FilePath, Time>>::const_iterator i;
+ for (i = matches.begin() + num_files_to_keep_; i < matches.end(); ++i) {
+ if (!DeleteP2PFile(i->first, "too many files"))
+ deletion_failed = true;
+ }
+ }
+
+ return !deletion_failed;
+}
+
+// Helper class for implementing LookupUrlForFile().
+class LookupData {
+ public:
+ explicit LookupData(P2PManager::LookupCallback callback)
+ : callback_(callback) {}
+
+ ~LookupData() {
+ if (timeout_task_ != MessageLoop::kTaskIdNull)
+ MessageLoop::current()->CancelTask(timeout_task_);
+ if (child_tag_)
+ Subprocess::Get().KillExec(child_tag_);
+ }
+
+ void InitiateLookup(const vector<string>& cmd, 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 the message loop (this
+ // guarantee is useful for testing).
+
+ // We expect to run just "p2p-client" and find it in the path.
+ child_tag_ = Subprocess::Get().ExecFlags(
+ cmd, G_SPAWN_SEARCH_PATH, false /* redirect stderr */, OnLookupDone,
+ this);
+
+ if (!child_tag_) {
+ LOG(ERROR) << "Error spawning " << utils::StringVectorToString(cmd);
+ ReportErrorAndDeleteInIdle();
+ return;
+ }
+
+ if (timeout > TimeDelta()) {
+ timeout_task_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ Bind(&LookupData::OnTimeout, base::Unretained(this)),
+ timeout);
+ }
+ }
+
+ private:
+ void ReportErrorAndDeleteInIdle() {
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &LookupData::OnIdleForReportErrorAndDelete,
+ base::Unretained(this)));
+ }
+
+ void OnIdleForReportErrorAndDelete() {
+ ReportError();
+ delete this;
+ }
+
+ void IssueCallback(const string& url) {
+ if (!callback_.is_null())
+ callback_.Run(url);
+ }
+
+ void ReportError() {
+ if (reported_)
+ return;
+ IssueCallback("");
+ reported_ = true;
+ }
+
+ void ReportSuccess(const string& output) {
+ if (reported_)
+ return;
+ string url = output;
+ 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 void OnLookupDone(int return_code,
+ const string& output,
+ void *user_data) {
+ LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
+ lookup_data->child_tag_ = 0;
+ if (return_code != 0) {
+ LOG(INFO) << "Child exited with non-zero exit code "
+ << return_code;
+ lookup_data->ReportError();
+ } else {
+ lookup_data->ReportSuccess(output);
+ }
+ delete lookup_data;
+ }
+
+ void OnTimeout() {
+ timeout_task_ = MessageLoop::kTaskIdNull;
+ ReportError();
+ delete this;
+ }
+
+ P2PManager::LookupCallback callback_;
+
+ // The Subprocess tag of the running process. A value of 0 means that the
+ // process is not running.
+ uint32_t child_tag_{0};
+
+ // The timeout task_id we are waiting on, if any.
+ MessageLoop::TaskId timeout_task_{MessageLoop::kTaskIdNull};
+
+ bool reported_{false};
+};
+
+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);
+ lookup_data->InitiateLookup(args, max_time_to_wait);
+}
+
+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) {
+ if (errno == ENOSYS || errno == EOPNOTSUPP) {
+ // If the filesystem doesn't support the fallocate, keep
+ // going. This is helpful when running unit tests on build
+ // machines with ancient filesystems and/or OSes.
+ PLOG(WARNING) << "Ignoring fallocate(2) failure";
+ } else {
+ // ENOSPC can happen (funky race though, cf. the statvfs() check
+ // above), handle it gracefully, e.g. use logging level INFO.
+ 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 != nullptr)
+ *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;
+
+ return utils::FileSize(path.value());
+}
+
+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 = nullptr;
+ long long int val = strtoll(ea_value, &endp, 0); // NOLINT(runtime/int)
+ 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() {
+ int num_files = 0;
+
+ FilePath p2p_dir = configuration_->GetP2PDir();
+ const string ext_visible = GetExt(kVisible);
+ const string ext_non_visible = GetExt(kNonVisible);
+
+ base::FileEnumerator dir(p2p_dir, false, base::FileEnumerator::FILES);
+ for (FilePath name = dir.Next(); !name.empty(); name = dir.Next()) {
+ if (base::EndsWith(name.value(), ext_visible, true) ||
+ base::EndsWith(name.value(), ext_non_visible, true))
+ num_files += 1;
+ }
+
+ return num_files;
+}
+
+void P2PManagerImpl::ScheduleEnabledStatusChange() {
+ if (waiting_for_enabled_status_change_)
+ return;
+
+ Callback<void(EvalStatus, const bool&)> callback = Bind(
+ &P2PManagerImpl::OnEnabledStatusChange, base::Unretained(this));
+ update_manager_->AsyncPolicyRequest(callback, &Policy::P2PEnabledChanged,
+ is_enabled_);
+ waiting_for_enabled_status_change_ = true;
+}
+
+void P2PManagerImpl::OnEnabledStatusChange(EvalStatus status,
+ const bool& result) {
+ waiting_for_enabled_status_change_ = false;
+
+ if (status == EvalStatus::kSucceeded) {
+ if (result == is_enabled_) {
+ LOG(WARNING) << "P2P enabled status did not change, which means that it "
+ "is permanent; not scheduling further checks.";
+ waiting_for_enabled_status_change_ = true;
+ return;
+ }
+
+ is_enabled_ = result;
+
+ // If P2P is running but shouldn't be, make sure it isn't.
+ if (may_be_running_ && !is_enabled_ && !EnsureP2PNotRunning()) {
+ LOG(WARNING) << "Failed to stop P2P service.";
+ }
+ } else {
+ LOG(WARNING)
+ << "P2P enabled tracking failed (possibly timed out); retrying.";
+ }
+
+ ScheduleEnabledStatusChange();
+}
+
+P2PManager* P2PManager::Construct(
+ Configuration *configuration,
+ ClockInterface *clock,
+ UpdateManager* update_manager,
+ const string& file_extension,
+ const int num_files_to_keep,
+ const TimeDelta& max_file_age) {
+ return new P2PManagerImpl(configuration,
+ clock,
+ update_manager,
+ file_extension,
+ num_files_to_keep,
+ max_file_age);
+}
+
+} // namespace chromeos_update_engine
diff --git a/p2p_manager.h b/p2p_manager.h
new file mode 100644
index 0000000..fb07c2e
--- /dev/null
+++ b/p2p_manager.h
@@ -0,0 +1,176 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_P2P_MANAGER_H_
+#define UPDATE_ENGINE_P2P_MANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/memory/ref_counted.h>
+#include <base/time/time.h>
+#include <policy/device_policy.h>
+#include <policy/libpolicy.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/update_manager/update_manager.h"
+
+namespace chromeos_update_engine {
+
+// Interface for sharing and discovering files via p2p.
+class P2PManager {
+ public:
+ // Interface used for P2PManager implementations. The sole reason
+ // for this interface is unit testing.
+ class Configuration {
+ public:
+ virtual ~Configuration() {}
+
+ // Gets the path to the p2p dir being used, e.g. /var/cache/p2p.
+ virtual base::FilePath GetP2PDir() = 0;
+
+ // Gets the argument vector for starting (if |is_start| is True)
+ // resp. stopping (if |is_start| is False) the p2p service
+ // e.g. ["initctl", "start", "p2p"] or ["initctl", "stop", "p2p"].
+ virtual std::vector<std::string> GetInitctlArgs(bool is_start) = 0;
+
+ // Gets the argument vector for invoking p2p-client, e.g.
+ // "p2p-client --get-url=file_id_we_want --minimum-size=42123".
+ virtual std::vector<std::string> GetP2PClientArgs(
+ const std::string& file_id, size_t minimum_size) = 0;
+ };
+
+ virtual ~P2PManager() {}
+
+ // The type for the callback used in LookupUrlForFile().
+ // If the lookup failed, |url| is empty.
+ typedef base::Callback<void(const std::string& url)> LookupCallback;
+
+ // Use the device policy specified by |device_policy|. If this is
+ // null, then no device policy is used.
+ virtual void SetDevicePolicy(const policy::DevicePolicy* device_policy) = 0;
+
+ // Returns true iff P2P is currently allowed for use on this device. This
+ // value is determined and maintained by the Update Manager.
+ virtual bool IsP2PEnabled() = 0;
+
+ // Ensures that the p2p subsystem is running (e.g. starts it if it's
+ // not already running) and blocks until this is so. Returns false
+ // if an error occurred.
+ virtual bool EnsureP2PRunning() = 0;
+
+ // Ensures that the p2p subsystem is not running (e.g. stops it if
+ // it's running) and blocks until this is so. Returns false if an
+ // error occurred.
+ virtual bool EnsureP2PNotRunning() = 0;
+
+ // Cleans up files in /var/cache/p2p owned by this application as
+ // per the |file_extension| and |num_files_to_keep| values passed
+ // when the object was constructed. This may be called even if
+ // the p2p subsystem is not running.
+ virtual bool PerformHousekeeping() = 0;
+
+ // Asynchronously finds a peer that serves the file identified by
+ // |file_id|. If |minimum_size| is non-zero, will find a peer that
+ // has at least that many bytes. When the result is ready |callback|
+ // is called from the default GLib mainloop.
+ //
+ // This operation may take a very long time to complete because part
+ // of the p2p protocol involves waiting for the LAN-wide sum of all
+ // num-connections to drop below a given threshold. However, if
+ // |max_time_to_wait| is non-zero, the operation is guaranteed to
+ // not exceed this duration.
+ //
+ // If the file is not available on the LAN (or if mDNS/DNS-SD is
+ // filtered), this is guaranteed to not take longer than 5 seconds.
+ virtual void LookupUrlForFile(const std::string& file_id,
+ size_t minimum_size,
+ base::TimeDelta max_time_to_wait,
+ LookupCallback callback) = 0;
+
+ // Shares a file identified by |file_id| in the directory
+ // /var/cache/p2p. Initially the file will not be visible, that is,
+ // it will have a .tmp extension and not be shared via p2p. Use the
+ // FileMakeVisible() method to change this.
+ //
+ // If you know the final size of the file, pass it in the
+ // |expected_size| parameter. Otherwise pass zero. If non-zero, the
+ // amount of free space in /var/cache/p2p is checked and if there is
+ // less than twice the amount of space available, this method
+ // fails. Additionally, disk space will be reserved via fallocate(2)
+ // and |expected_size| is written to the user.cros-p2p-filesize
+ // xattr of the created file.
+ //
+ // If the file already exists, true is returned. Any on-disk xattr
+ // is not updated.
+ virtual bool FileShare(const std::string& file_id,
+ size_t expected_size) = 0;
+
+ // Gets a fully qualified path for the file identified by |file_id|.
+ // If the file has not been shared already using the FileShare()
+ // method, an empty base::FilePath is returned - use FilePath::empty() to
+ // find out.
+ virtual base::FilePath FileGetPath(const std::string& file_id) = 0;
+
+ // Gets the actual size of the file identified by |file_id|. This is
+ // equivalent to reading the value of the st_size field of the
+ // struct stat on the file given by FileGetPath(). Returns -1 if an
+ // error occurs.
+ //
+ // For a file just created with FileShare() this will return 0.
+ virtual ssize_t FileGetSize(const std::string& file_id) = 0;
+
+ // Gets the expected size of the file identified by |file_id|. This
+ // is equivalent to reading the value of the user.cros-p2p-filesize
+ // xattr on the file given by FileGetPath(). Returns -1 if an error
+ // occurs.
+ //
+ // For a file just created with FileShare() this will return the
+ // value of the |expected_size| parameter passed to that method.
+ virtual ssize_t FileGetExpectedSize(const std::string& file_id) = 0;
+
+ // Gets whether the file identified by |file_id| is publicly
+ // visible. If |out_result| is not null, the result is returned
+ // there. Returns false if an error occurs.
+ virtual bool FileGetVisible(const std::string& file_id,
+ bool *out_result) = 0;
+
+ // Makes the file identified by |file_id| publicly visible
+ // (e.g. removes the .tmp extension). If the file is already
+ // visible, this method does nothing. Returns False if
+ // the method fails or there is no file for |file_id|.
+ virtual bool FileMakeVisible(const std::string& file_id) = 0;
+
+ // Counts the number of shared files used by this application
+ // (cf. the |file_extension parameter|. Returns -1 if an error
+ // occurred.
+ virtual int CountSharedFiles() = 0;
+
+ // Creates a suitable P2PManager instance and initializes the object
+ // so it's ready for use. The |file_extension| parameter is used to
+ // identify your application, use e.g. "cros_au". If
+ // |configuration| is non-null, the P2PManager will take ownership
+ // of the Configuration object and use it (hence, it must be
+ // heap-allocated).
+ //
+ // The |num_files_to_keep| parameter specifies how many files to
+ // keep after performing housekeeping (cf. the PerformHousekeeping()
+ // method) - pass zero to allow infinitely many files. The
+ // |max_file_age| parameter specifies the maximum file age after
+ // performing housekeeping (pass zero to allow files of any age).
+ static P2PManager* Construct(
+ Configuration *configuration,
+ ClockInterface *clock,
+ chromeos_update_manager::UpdateManager* update_manager,
+ const std::string& file_extension,
+ const int num_files_to_keep,
+ const base::TimeDelta& max_file_age);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_P2P_MANAGER_H_
diff --git a/p2p_manager_unittest.cc b/p2p_manager_unittest.cc
new file mode 100644
index 0000000..8667578
--- /dev/null
+++ b/p2p_manager_unittest.cc
@@ -0,0 +1,502 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/p2p_manager.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <attr/xattr.h> // NOLINT - requires typed defined in unistd.h
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/files/file_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_device_policy.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_p2p_manager_configuration.h"
+#include "update_engine/prefs.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+#include "update_engine/update_manager/mock_policy.h"
+#include "update_engine/utils.h"
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+// Test fixture that sets up a testing configuration (with e.g. a
+// temporary p2p dir) for P2PManager and cleans up when the test is
+// done.
+class P2PManagerTest : public testing::Test {
+ protected:
+ P2PManagerTest() : fake_um_(&fake_clock_) {}
+ ~P2PManagerTest() override {}
+
+ // Derived from testing::Test.
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ test_conf_ = new FakeP2PManagerConfiguration();
+
+ // Allocate and install a mock policy implementation in the fake Update
+ // Manager. Note that the FakeUpdateManager takes ownership of the policy
+ // object.
+ mock_policy_ = new chromeos_update_manager::MockPolicy(&fake_clock_);
+ fake_um_.set_policy(mock_policy_);
+
+ // Construct the P2P manager under test.
+ manager_.reset(P2PManager::Construct(test_conf_, &fake_clock_, &fake_um_,
+ "cros_au", 3,
+ TimeDelta::FromDays(5)));
+ }
+
+ void TearDown() override {
+ EXPECT_EQ(0, chromeos::MessageLoopRunMaxIterations(&loop_, 1));
+ }
+
+ // TODO(deymo): Replace this with a FakeMessageLoop. P2PManager uses glib to
+ // interact with the p2p-client tool, so we need to run a GlibMessageLoop
+ // here.
+ chromeos::GlibMessageLoop loop_;
+
+ // The P2PManager::Configuration instance used for testing.
+ FakeP2PManagerConfiguration *test_conf_;
+
+ FakeClock fake_clock_;
+ chromeos_update_manager::MockPolicy *mock_policy_ = nullptr;
+ chromeos_update_manager::FakeUpdateManager fake_um_;
+
+ unique_ptr<P2PManager> manager_;
+};
+
+
+// Check that IsP2PEnabled() polls the policy correctly, with the value not
+// changing between calls.
+TEST_F(P2PManagerTest, P2PEnabledInitAndNotChanged) {
+ EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
+
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+ chromeos::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+}
+
+// Check that IsP2PEnabled() polls the policy correctly, with the value changing
+// between calls.
+TEST_F(P2PManagerTest, P2PEnabledInitAndChanged) {
+ EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<3>(true),
+ Return(chromeos_update_manager::EvalStatus::kSucceeded)));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, true));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
+
+ EXPECT_TRUE(manager_->IsP2PEnabled());
+ chromeos::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+}
+
+// Check that we keep the $N newest files with the .$EXT.p2p extension.
+TEST_F(P2PManagerTest, HousekeepingCountLimit) {
+ // Specifically pass 0 for |max_file_age| to allow files of any age. Note that
+ // we need to reallocate the test_conf_ member, whose currently aliased object
+ // will be freed.
+ test_conf_ = new FakeP2PManagerConfiguration();
+ manager_.reset(P2PManager::Construct(
+ test_conf_, &fake_clock_, &fake_um_, "cros_au", 3,
+ TimeDelta() /* max_file_age */));
+ EXPECT_EQ(manager_->CountSharedFiles(), 0);
+
+ base::Time start_time = base::Time::FromDoubleT(1246996800.);
+ // Generate files with different timestamps matching our pattern and generate
+ // other files not matching the pattern.
+ for (int n = 0; n < 5; n++) {
+ base::FilePath path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+ "file_%d.cros_au.p2p", n));
+ base::Time file_time = start_time + TimeDelta::FromMinutes(n);
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
+
+ path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+ "file_%d.OTHER.p2p", n));
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 5);
+
+ EXPECT_TRUE(manager_->PerformHousekeeping());
+
+ // At this point - after HouseKeeping - we should only have
+ // eight files left.
+ for (int n = 0; n < 5; n++) {
+ string file_name;
+ bool expect;
+
+ expect = (n >= 2);
+ file_name = base::StringPrintf(
+ "%s/file_%d.cros_au.p2p",
+ test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
+
+ file_name = base::StringPrintf(
+ "%s/file_%d.OTHER.p2p",
+ test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_TRUE(utils::FileExists(file_name.c_str()));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 3);
+}
+
+// Check that we keep files with the .$EXT.p2p extension not older
+// than some specificed age (5 days, in this test).
+TEST_F(P2PManagerTest, HousekeepingAgeLimit) {
+ // We set the cutoff time to be 1 billion seconds (01:46:40 UTC on 9
+ // September 2001 - arbitrary number, but constant to avoid test
+ // flakiness) since the epoch and then we put two files before that
+ // date and three files after.
+ base::Time cutoff_time = base::Time::FromTimeT(1000000000);
+ TimeDelta age_limit = TimeDelta::FromDays(5);
+
+ // Set the clock just so files with a timestamp before |cutoff_time|
+ // will be deleted at housekeeping.
+ fake_clock_.SetWallclockTime(cutoff_time + age_limit);
+
+ // Specifically pass 0 for |num_files_to_keep| to allow any number of files.
+ // Note that we need to reallocate the test_conf_ member, whose currently
+ // aliased object will be freed.
+ test_conf_ = new FakeP2PManagerConfiguration();
+ manager_.reset(P2PManager::Construct(
+ test_conf_, &fake_clock_, &fake_um_, "cros_au",
+ 0 /* num_files_to_keep */, age_limit));
+ EXPECT_EQ(manager_->CountSharedFiles(), 0);
+
+ // Generate files with different timestamps matching our pattern and generate
+ // other files not matching the pattern.
+ for (int n = 0; n < 5; n++) {
+ base::FilePath path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+ "file_%d.cros_au.p2p", n));
+
+ // With five files and aiming for two of them to be before
+ // |cutoff_time|, we distribute it like this:
+ //
+ // -------- 0 -------- 1 -------- 2 -------- 3 -------- 4 --------
+ // |
+ // cutoff_time
+ //
+ base::Time file_date = cutoff_time + (n - 2) * TimeDelta::FromDays(1)
+ + TimeDelta::FromHours(12);
+
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
+
+ path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+ "file_%d.OTHER.p2p", n));
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 5);
+
+ EXPECT_TRUE(manager_->PerformHousekeeping());
+
+ // At this point - after HouseKeeping - we should only have
+ // eight files left.
+ for (int n = 0; n < 5; n++) {
+ string file_name;
+ bool expect;
+
+ expect = (n >= 2);
+ file_name = base::StringPrintf(
+ "%s/file_%d.cros_au.p2p",
+ test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
+
+ file_name = base::StringPrintf(
+ "%s/file_%d.OTHER.p2p",
+ test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_TRUE(utils::FileExists(file_name.c_str()));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 3);
+}
+
+static bool CheckP2PFile(const string& p2p_dir, const string& file_name,
+ ssize_t expected_size, ssize_t expected_size_xattr) {
+ string path = p2p_dir + "/" + file_name;
+ char ea_value[64] = { 0 };
+ ssize_t ea_size;
+
+ off_t p2p_size = utils::FileSize(path);
+ if (p2p_size < 0) {
+ LOG(ERROR) << "File " << path << " does not exist";
+ return false;
+ }
+
+ if (expected_size != 0) {
+ if (p2p_size != expected_size) {
+ LOG(ERROR) << "Expected size " << expected_size
+ << " but size was " << p2p_size;
+ return false;
+ }
+ }
+
+ if (expected_size_xattr == 0) {
+ ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize",
+ &ea_value, sizeof ea_value - 1);
+ if (ea_size == -1 && errno == ENOATTR) {
+ // This is valid behavior as we support files without the xattr set.
+ } else {
+ PLOG(ERROR) << "getxattr() didn't fail with ENOATTR as expected, "
+ << "ea_size=" << ea_size << ", errno=" << errno;
+ return false;
+ }
+ } else {
+ ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize",
+ &ea_value, sizeof ea_value - 1);
+ if (ea_size < 0) {
+ LOG(ERROR) << "Error getting xattr attribute";
+ return false;
+ }
+ char* endp = nullptr;
+ long long int val = strtoll(ea_value, &endp, 0); // NOLINT(runtime/int)
+ if (endp == nullptr || *endp != '\0') {
+ LOG(ERROR) << "Error parsing xattr '" << ea_value
+ << "' as an integer";
+ return false;
+ }
+ if (val != expected_size_xattr) {
+ LOG(ERROR) << "Expected xattr size " << expected_size_xattr
+ << " but size was " << val;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool CreateP2PFile(string p2p_dir, string file_name,
+ size_t size, size_t size_xattr) {
+ string path = p2p_dir + "/" + file_name;
+
+ int fd = open(path.c_str(), O_CREAT|O_RDWR, 0644);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error creating file with path " << path;
+ return false;
+ }
+ if (ftruncate(fd, size) != 0) {
+ PLOG(ERROR) << "Error truncating " << path << " to size " << size;
+ close(fd);
+ return false;
+ }
+
+ if (size_xattr != 0) {
+ string decimal_size = base::StringPrintf("%zu", size_xattr);
+ if (fsetxattr(fd, "user.cros-p2p-filesize",
+ decimal_size.c_str(), decimal_size.size(), 0) != 0) {
+ PLOG(ERROR) << "Error setting xattr on " << path;
+ close(fd);
+ return false;
+ }
+ }
+
+ close(fd);
+ return true;
+}
+
+// Check that sharing a *new* file works.
+TEST_F(P2PManagerTest, ShareFile) {
+ if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+ const int kP2PTestFileSize = 1000 * 1000; // 1 MB
+
+ EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p.tmp", 0, kP2PTestFileSize));
+
+ // Sharing it again - with the same expected size - should return true
+ EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
+
+ // ... but if we use the wrong size, it should fail
+ EXPECT_FALSE(manager_->FileShare("foo", kP2PTestFileSize + 1));
+}
+
+// Check that making a shared file visible, does what is expected.
+TEST_F(P2PManagerTest, MakeFileVisible) {
+ if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+ const int kP2PTestFileSize = 1000 * 1000; // 1 MB
+
+ // First, check that it's not visible.
+ manager_->FileShare("foo", kP2PTestFileSize);
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p.tmp", 0, kP2PTestFileSize));
+ // Make the file visible and check that it changed its name. Do it
+ // twice to check that FileMakeVisible() is idempotent.
+ for (int n = 0; n < 2; n++) {
+ manager_->FileMakeVisible("foo");
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p", 0, kP2PTestFileSize));
+ }
+}
+
+// Check that we return the right values for existing files in P2P_DIR.
+TEST_F(P2PManagerTest, ExistingFiles) {
+ if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ bool visible;
+
+ // Check that errors are returned if the file does not exist
+ EXPECT_EQ(manager_->FileGetPath("foo"), base::FilePath());
+ EXPECT_EQ(manager_->FileGetSize("foo"), -1);
+ EXPECT_EQ(manager_->FileGetExpectedSize("foo"), -1);
+ EXPECT_FALSE(manager_->FileGetVisible("foo", nullptr));
+ // ... then create the file ...
+ EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p", 42, 43));
+ // ... and then check that the expected values are returned
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+ EXPECT_EQ(manager_->FileGetSize("foo"), 42);
+ EXPECT_EQ(manager_->FileGetExpectedSize("foo"), 43);
+ EXPECT_TRUE(manager_->FileGetVisible("foo", &visible));
+ EXPECT_TRUE(visible);
+
+ // One more time, this time with a .tmp variant. First ensure it errors out..
+ EXPECT_EQ(manager_->FileGetPath("bar"), base::FilePath());
+ EXPECT_EQ(manager_->FileGetSize("bar"), -1);
+ EXPECT_EQ(manager_->FileGetExpectedSize("bar"), -1);
+ EXPECT_FALSE(manager_->FileGetVisible("bar", nullptr));
+ // ... then create the file ...
+ EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(),
+ "bar.cros_au.p2p.tmp", 44, 45));
+ // ... and then check that the expected values are returned
+ EXPECT_EQ(manager_->FileGetPath("bar"),
+ test_conf_->GetP2PDir().Append("bar.cros_au.p2p.tmp"));
+ EXPECT_EQ(manager_->FileGetSize("bar"), 44);
+ EXPECT_EQ(manager_->FileGetExpectedSize("bar"), 45);
+ EXPECT_TRUE(manager_->FileGetVisible("bar", &visible));
+ EXPECT_FALSE(visible);
+}
+
+// This is a little bit ugly but short of mocking a 'p2p' service this
+// will have to do. E.g. we essentially simulate the various
+// behaviours of initctl(8) that we rely on.
+TEST_F(P2PManagerTest, StartP2P) {
+ // Check that we can start the service
+ test_conf_->SetInitctlStartCommand({"true"});
+ EXPECT_TRUE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand({"false"});
+ EXPECT_FALSE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand({
+ "sh", "-c", "echo \"initctl: Job is already running: p2p\" >&2; false"});
+ EXPECT_TRUE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand({
+ "sh", "-c", "echo something else >&2; false"});
+ EXPECT_FALSE(manager_->EnsureP2PRunning());
+}
+
+// Same comment as for StartP2P
+TEST_F(P2PManagerTest, StopP2P) {
+ // Check that we can start the service
+ test_conf_->SetInitctlStopCommand({"true"});
+ EXPECT_TRUE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand({"false"});
+ EXPECT_FALSE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand({
+ "sh", "-c", "echo \"initctl: Unknown instance \" >&2; false"});
+ EXPECT_TRUE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand({
+ "sh", "-c", "echo something else >&2; false"});
+ EXPECT_FALSE(manager_->EnsureP2PNotRunning());
+}
+
+static void ExpectUrl(const string& expected_url,
+ const string& url) {
+ EXPECT_EQ(url, expected_url);
+ MessageLoop::current()->BreakLoop();
+}
+
+// Like StartP2P, we're mocking the different results that p2p-client
+// can return. It's not pretty but it works.
+TEST_F(P2PManagerTest, LookupURL) {
+ // Emulate p2p-client returning valid URL with "fooX", 42 and "cros_au"
+ // being propagated in the right places.
+ test_conf_->SetP2PClientCommand({
+ "echo", "http://1.2.3.4/{file_id}_{minsize}"});
+ manager_->LookupUrlForFile("fooX", 42, TimeDelta(),
+ base::Bind(ExpectUrl,
+ "http://1.2.3.4/fooX.cros_au_42"));
+ loop_.Run();
+
+ // Emulate p2p-client returning invalid URL.
+ test_conf_->SetP2PClientCommand({"echo", "not_a_valid_url"});
+ manager_->LookupUrlForFile("foobar", 42, TimeDelta(),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client conveying failure.
+ test_conf_->SetP2PClientCommand({"false"});
+ manager_->LookupUrlForFile("foobar", 42, TimeDelta(),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client not existing.
+ test_conf_->SetP2PClientCommand({"/path/to/non/existent/helper/program"});
+ manager_->LookupUrlForFile("foobar", 42,
+ TimeDelta(),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client crashing.
+ test_conf_->SetP2PClientCommand({"sh", "-c", "kill -SEGV $$"});
+ manager_->LookupUrlForFile("foobar", 42, TimeDelta(),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client exceeding its timeout.
+ test_conf_->SetP2PClientCommand({
+ "sh", "-c", "echo http://1.2.3.4/; sleep 2"});
+ manager_->LookupUrlForFile("foobar", 42, TimeDelta::FromMilliseconds(500),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_constants.cc b/payload_constants.cc
new file mode 100644
index 0000000..2fc31e3
--- /dev/null
+++ b/payload_constants.cc
@@ -0,0 +1,12 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_constants.h"
+
+namespace chromeos_update_engine {
+
+const char kDeltaMagic[] = "CrAU";
+const char kBspatchPath[] = "bspatch";
+
+}; // namespace chromeos_update_engine
diff --git a/payload_constants.h b/payload_constants.h
new file mode 100644
index 0000000..30dde5e
--- /dev/null
+++ b/payload_constants.h
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSTANTS_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSTANTS_H_
+
+#include <stdint.h>
+
+#include <limits>
+
+namespace chromeos_update_engine {
+
+extern const char kBspatchPath[];
+extern const char kDeltaMagic[];
+
+// A block number denoting a hole on a sparse file. Used on Extents to refer to
+// section of blocks not present on disk on a sparse file.
+const uint64_t kSparseHole = std::numeric_limits<uint64_t>::max();
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSTANTS_H_
diff --git a/payload_generator/ab_generator.cc b/payload_generator/ab_generator.cc
new file mode 100644
index 0000000..67163eb
--- /dev/null
+++ b/payload_generator/ab_generator.cc
@@ -0,0 +1,365 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/ab_generator.h"
+
+#include <algorithm>
+
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/bzip.h"
+#include "update_engine/delta_performer.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Compare two AnnotatedOperations by the start block of the first Extent in
+// their destination extents.
+bool CompareAopsByDestination(AnnotatedOperation first_aop,
+ AnnotatedOperation second_aop) {
+ // We want empty operations to be at the end of the payload.
+ if (!first_aop.op.dst_extents().size() || !second_aop.op.dst_extents().size())
+ return ((!first_aop.op.dst_extents().size()) <
+ (!second_aop.op.dst_extents().size()));
+ uint32_t first_dst_start = first_aop.op.dst_extents(0).start_block();
+ uint32_t second_dst_start = second_aop.op.dst_extents(0).start_block();
+ return first_dst_start < second_dst_start;
+}
+
+} // namespace
+
+bool ABGenerator::GenerateOperations(
+ const PayloadGenerationConfig& config,
+ int data_file_fd,
+ off_t* data_file_size,
+ vector<AnnotatedOperation>* rootfs_ops,
+ vector<AnnotatedOperation>* kernel_ops) {
+
+ off_t chunk_blocks = (config.chunk_size == -1 ? -1 :
+ config.chunk_size / config.block_size);
+
+ rootfs_ops->clear();
+ TEST_AND_RETURN_FALSE(diff_utils::DeltaReadPartition(
+ rootfs_ops,
+ config.source.rootfs,
+ config.target.rootfs,
+ chunk_blocks,
+ data_file_fd,
+ data_file_size,
+ false, // skip_block_0
+ true)); // src_ops_allowed
+ LOG(INFO) << "done reading normal files";
+
+ // Read kernel partition
+ TEST_AND_RETURN_FALSE(diff_utils::DeltaReadPartition(
+ kernel_ops,
+ config.source.kernel,
+ config.target.kernel,
+ chunk_blocks,
+ data_file_fd,
+ data_file_size,
+ false, // skip_block_0
+ true)); // src_ops_allowed
+ LOG(INFO) << "done reading kernel";
+
+ TEST_AND_RETURN_FALSE(FragmentOperations(rootfs_ops,
+ config.target.rootfs.path,
+ data_file_fd,
+ data_file_size));
+ TEST_AND_RETURN_FALSE(FragmentOperations(kernel_ops,
+ config.target.kernel.path,
+ data_file_fd,
+ data_file_size));
+ SortOperationsByDestination(rootfs_ops);
+ SortOperationsByDestination(kernel_ops);
+ // TODO(alliewood): Change merge operations to use config.chunk_size once
+ // specifying chunk_size on the command line works. crbug/485397.
+ TEST_AND_RETURN_FALSE(MergeOperations(rootfs_ops,
+ kDefaultChunkSize,
+ config.target.rootfs.path,
+ data_file_fd,
+ data_file_size));
+ TEST_AND_RETURN_FALSE(MergeOperations(kernel_ops,
+ kDefaultChunkSize,
+ config.target.kernel.path,
+ data_file_fd,
+ data_file_size));
+ return true;
+}
+
+void ABGenerator::SortOperationsByDestination(
+ vector<AnnotatedOperation>* aops) {
+ sort(aops->begin(), aops->end(), CompareAopsByDestination);
+}
+
+bool ABGenerator::FragmentOperations(
+ vector<AnnotatedOperation>* aops,
+ const string& target_part_path,
+ int data_fd,
+ off_t* data_file_size) {
+ vector<AnnotatedOperation> fragmented_aops;
+ for (const AnnotatedOperation& aop : *aops) {
+ if (aop.op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY) {
+ TEST_AND_RETURN_FALSE(SplitSourceCopy(aop, &fragmented_aops));
+ } else if ((aop.op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE) ||
+ (aop.op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ)) {
+ TEST_AND_RETURN_FALSE(SplitReplaceOrReplaceBz(aop, &fragmented_aops,
+ target_part_path, data_fd,
+ data_file_size));
+ } else {
+ fragmented_aops.push_back(aop);
+ }
+ }
+ *aops = fragmented_aops;
+ return true;
+}
+
+bool ABGenerator::SplitSourceCopy(
+ const AnnotatedOperation& original_aop,
+ vector<AnnotatedOperation>* result_aops) {
+ DeltaArchiveManifest_InstallOperation original_op = original_aop.op;
+ TEST_AND_RETURN_FALSE(original_op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+ // Keeps track of the index of curr_src_ext.
+ int curr_src_ext_index = 0;
+ Extent curr_src_ext = original_op.src_extents(curr_src_ext_index);
+ for (int i = 0; i < original_op.dst_extents_size(); i++) {
+ Extent dst_ext = original_op.dst_extents(i);
+ // The new operation which will have only one dst extent.
+ DeltaArchiveManifest_InstallOperation new_op;
+ uint64_t blocks_left = dst_ext.num_blocks();
+ while (blocks_left > 0) {
+ if (curr_src_ext.num_blocks() <= blocks_left) {
+ // If the curr_src_ext is smaller than dst_ext, add it.
+ blocks_left -= curr_src_ext.num_blocks();
+ *(new_op.add_src_extents()) = curr_src_ext;
+ if (curr_src_ext_index + 1 < original_op.src_extents().size()) {
+ curr_src_ext = original_op.src_extents(++curr_src_ext_index);
+ } else {
+ break;
+ }
+ } else {
+ // Split src_exts that are bigger than the dst_ext we're dealing with.
+ Extent first_ext;
+ first_ext.set_num_blocks(blocks_left);
+ first_ext.set_start_block(curr_src_ext.start_block());
+ *(new_op.add_src_extents()) = first_ext;
+ // Keep the second half of the split op.
+ curr_src_ext.set_num_blocks(curr_src_ext.num_blocks() - blocks_left);
+ curr_src_ext.set_start_block(curr_src_ext.start_block() + blocks_left);
+ blocks_left -= first_ext.num_blocks();
+ }
+ }
+ // Fix up our new operation and add it to the results.
+ new_op.set_type(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+ *(new_op.add_dst_extents()) = dst_ext;
+ new_op.set_src_length(dst_ext.num_blocks() * kBlockSize);
+ new_op.set_dst_length(dst_ext.num_blocks() * kBlockSize);
+
+ AnnotatedOperation new_aop;
+ new_aop.op = new_op;
+ new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i);
+ result_aops->push_back(new_aop);
+ }
+ if (curr_src_ext_index != original_op.src_extents().size() - 1) {
+ LOG(FATAL) << "Incorrectly split SOURCE_COPY operation. Did not use all "
+ << "source extents.";
+ }
+ return true;
+}
+
+bool ABGenerator::SplitReplaceOrReplaceBz(
+ const AnnotatedOperation& original_aop,
+ vector<AnnotatedOperation>* result_aops,
+ const string& target_part_path,
+ int data_fd,
+ off_t* data_file_size) {
+ DeltaArchiveManifest_InstallOperation original_op = original_aop.op;
+ const bool is_replace =
+ original_op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE;
+ TEST_AND_RETURN_FALSE(
+ is_replace ||
+ (original_op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ));
+
+ uint32_t data_offset = original_op.data_offset();
+ for (int i = 0; i < original_op.dst_extents_size(); i++) {
+ Extent dst_ext = original_op.dst_extents(i);
+ // Make a new operation with only one dst extent.
+ DeltaArchiveManifest_InstallOperation new_op;
+ *(new_op.add_dst_extents()) = dst_ext;
+ uint32_t data_size = dst_ext.num_blocks() * kBlockSize;
+ new_op.set_dst_length(data_size);
+ // If this is a REPLACE, attempt to reuse portions of the existing blob.
+ if (is_replace) {
+ new_op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+ new_op.set_data_length(data_size);
+ new_op.set_data_offset(data_offset);
+ data_offset += data_size;
+ }
+
+ AnnotatedOperation new_aop;
+ new_aop.op = new_op;
+ new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i);
+ TEST_AND_RETURN_FALSE(AddDataAndSetType(&new_aop, target_part_path, data_fd,
+ data_file_size));
+
+ result_aops->push_back(new_aop);
+ }
+ return true;
+}
+
+bool ABGenerator::MergeOperations(vector<AnnotatedOperation>* aops,
+ off_t chunk_size,
+ const string& target_part_path,
+ int data_fd,
+ off_t* data_file_size) {
+ vector<AnnotatedOperation> new_aops;
+ for (const AnnotatedOperation& curr_aop : *aops) {
+ if (new_aops.empty()) {
+ new_aops.push_back(curr_aop);
+ continue;
+ }
+ AnnotatedOperation& last_aop = new_aops.back();
+
+ if (last_aop.op.dst_extents_size() <= 0 ||
+ curr_aop.op.dst_extents_size() <= 0) {
+ new_aops.push_back(curr_aop);
+ continue;
+ }
+ uint32_t last_dst_idx = last_aop.op.dst_extents_size() - 1;
+ uint32_t last_end_block =
+ last_aop.op.dst_extents(last_dst_idx).start_block() +
+ last_aop.op.dst_extents(last_dst_idx).num_blocks();
+ uint32_t curr_start_block = curr_aop.op.dst_extents(0).start_block();
+ uint32_t combined_block_count =
+ last_aop.op.dst_extents(last_dst_idx).num_blocks() +
+ curr_aop.op.dst_extents(0).num_blocks();
+ bool good_op_type =
+ curr_aop.op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY ||
+ curr_aop.op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+ curr_aop.op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ;
+ if (good_op_type &&
+ last_aop.op.type() == curr_aop.op.type() &&
+ last_end_block == curr_start_block &&
+ static_cast<off_t>(combined_block_count * kBlockSize) <= chunk_size) {
+ // If the operations have the same type (which is a type that we can
+ // merge), are contiguous, are fragmented to have one destination extent,
+ // and their combined block count would be less than chunk size, merge
+ // them.
+ last_aop.name = base::StringPrintf("%s,%s",
+ last_aop.name.c_str(),
+ curr_aop.name.c_str());
+
+ ExtendExtents(last_aop.op.mutable_src_extents(),
+ curr_aop.op.src_extents());
+ if (curr_aop.op.src_length() > 0)
+ last_aop.op.set_src_length(last_aop.op.src_length() +
+ curr_aop.op.src_length());
+ ExtendExtents(last_aop.op.mutable_dst_extents(),
+ curr_aop.op.dst_extents());
+ if (curr_aop.op.dst_length() > 0)
+ last_aop.op.set_dst_length(last_aop.op.dst_length() +
+ curr_aop.op.dst_length());
+ // Set the data length to zero so we know to add the blob later.
+ if (curr_aop.op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+ curr_aop.op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
+ last_aop.op.set_data_length(0);
+ }
+ } else {
+ // Otherwise just include the extent as is.
+ new_aops.push_back(curr_aop);
+ }
+ }
+
+ // Set the blobs for REPLACE/REPLACE_BZ operations that have been merged.
+ for (AnnotatedOperation& curr_aop : new_aops) {
+ if (curr_aop.op.data_length() == 0 &&
+ (curr_aop.op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+ curr_aop.op.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ)) {
+ TEST_AND_RETURN_FALSE(AddDataAndSetType(&curr_aop, target_part_path,
+ data_fd, data_file_size));
+ }
+ }
+
+ *aops = new_aops;
+ return true;
+}
+
+bool ABGenerator::AddDataAndSetType(AnnotatedOperation* aop,
+ const string& target_part_path,
+ int data_fd,
+ off_t* data_file_size) {
+ TEST_AND_RETURN_FALSE(
+ aop->op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+ aop->op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+
+ chromeos::Blob data(aop->op.dst_length());
+ vector<Extent> dst_extents;
+ ExtentsToVector(aop->op.dst_extents(), &dst_extents);
+ TEST_AND_RETURN_FALSE(utils::ReadExtents(target_part_path,
+ dst_extents,
+ &data,
+ data.size(),
+ kBlockSize));
+
+ chromeos::Blob data_bz;
+ TEST_AND_RETURN_FALSE(BzipCompress(data, &data_bz));
+ CHECK(!data_bz.empty());
+
+ chromeos::Blob* data_p = nullptr;
+ DeltaArchiveManifest_InstallOperation_Type new_op_type;
+ if (data_bz.size() < data.size()) {
+ new_op_type = DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ;
+ data_p = &data_bz;
+ } else {
+ new_op_type = DeltaArchiveManifest_InstallOperation_Type_REPLACE;
+ data_p = &data;
+ }
+
+ // If the operation already points to a data blob, check whether it's
+ // identical to the new one, in which case don't add it.
+ if (aop->op.type() == new_op_type &&
+ aop->op.data_length() == data_p->size()) {
+ chromeos::Blob current_data(data_p->size());
+ ssize_t bytes_read;
+ TEST_AND_RETURN_FALSE(utils::PReadAll(data_fd,
+ current_data.data(),
+ aop->op.data_length(),
+ aop->op.data_offset(),
+ &bytes_read));
+ TEST_AND_RETURN_FALSE(bytes_read ==
+ static_cast<ssize_t>(aop->op.data_length()));
+ if (current_data == *data_p)
+ data_p = nullptr;
+ }
+
+ if (data_p) {
+ aop->op.set_type(new_op_type);
+ aop->SetOperationBlob(data_p, data_fd, data_file_size);
+ }
+
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/ab_generator.h b/payload_generator/ab_generator.h
new file mode 100644
index 0000000..d027f22
--- /dev/null
+++ b/payload_generator/ab_generator.h
@@ -0,0 +1,120 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_AB_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_AB_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/payload_generator/filesystem_interface.h"
+#include "update_engine/payload_generator/operations_generator.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// The ABGenerator is an operations generator that generates payloads using the
+// A-to-B operations SOURCE_COPY and SOURCE_BSDIFF introduced in the payload
+// minor version 2 format.
+class ABGenerator : public OperationsGenerator {
+ public:
+ ABGenerator() = default;
+
+ // Generate the update payload operations for the kernel and rootfs using
+ // SOURCE_* operations, used for generating deltas for the minor version
+ // kSourceMinorPayloadVersion. This function will generate operations in the
+ // rootfs that will read blocks from the source partition in random order and
+ // write the new image on the target partition, also possibly in random order.
+ // The rootfs operations are stored in |rootfs_ops| and should be executed in
+ // that order. The kernel operations are stored in |kernel_ops|. All
+ // the offsets in the operations reference the data written to |data_file_fd|.
+ // The total amount of data written to that file is stored in
+ // |data_file_size|.
+ bool GenerateOperations(
+ const PayloadGenerationConfig& config,
+ int data_file_fd,
+ off_t* data_file_size,
+ std::vector<AnnotatedOperation>* rootfs_ops,
+ std::vector<AnnotatedOperation>* kernel_ops) override;
+
+ // Split the operations in the vector of AnnotatedOperations |aops|
+ // such that for every operation there is only one dst extent and updates
+ // |aops| with the new list of operations. All kinds of operations are
+ // fragmented except BSDIFF and SOURCE_BSDIFF operations.
+ // The |target_rootfs_part| is the filename of the new image, where the
+ // destination extents refer to. The blobs of the operations in |aops| should
+ // reference the file |data_fd| whose initial size is |*data_file_size|. The
+ // file contents and the value pointed by |data_file_size| are updated if
+ // needed.
+ static bool FragmentOperations(std::vector<AnnotatedOperation>* aops,
+ const std::string& target_rootfs_part,
+ int data_fd,
+ off_t* data_file_size);
+
+ // Takes a vector of AnnotatedOperations |aops| and sorts them by the first
+ // start block in their destination extents. Sets |aops| to a vector of the
+ // sorted operations.
+ static void SortOperationsByDestination(
+ std::vector<AnnotatedOperation>* aops);
+
+ // Takes an SOURCE_COPY install operation, |aop|, and adds one operation for
+ // each dst extent in |aop| to |ops|. The new operations added to |ops| will
+ // have only one dst extent. The src extents are split so the number of blocks
+ // in the src and dst extents are equal.
+ // E.g. we have a SOURCE_COPY operation:
+ // src extents: [(1, 3), (5, 1), (7, 1)], dst extents: [(2, 2), (6, 3)]
+ // Then we will get 2 new operations:
+ // 1. src extents: [(1, 2)], dst extents: [(2, 2)]
+ // 2. src extents: [(3, 1),(5, 1),(7, 1)], dst extents: [(6, 3)]
+ static bool SplitSourceCopy(const AnnotatedOperation& original_aop,
+ std::vector<AnnotatedOperation>* result_aops);
+
+ // Takes a REPLACE/REPLACE_BZ operation |aop|, and adds one operation for each
+ // dst extent in |aop| to |ops|. The new operations added to |ops| will have
+ // only one dst extent each, and may be either a REPLACE or REPLACE_BZ
+ // depending on whether compression is advantageous.
+ static bool SplitReplaceOrReplaceBz(
+ const AnnotatedOperation& original_aop,
+ std::vector<AnnotatedOperation>* result_aops,
+ const std::string& target_part,
+ int data_fd,
+ off_t* data_file_size);
+
+ // Takes a sorted (by first destination extent) vector of operations |aops|
+ // and merges SOURCE_COPY, REPLACE, and REPLACE_BZ operations in that vector.
+ // It will merge two operations if:
+ // - They are of the same type.
+ // - They are contiguous.
+ // - Their combined blocks do not exceed |chunk_size|.
+ static bool MergeOperations(std::vector<AnnotatedOperation>* aops,
+ off_t chunk_size,
+ const std::string& target_part,
+ int data_fd,
+ off_t* data_file_size);
+
+ private:
+ // Adds the data payload for a REPLACE/REPLACE_BZ operation |aop| by reading
+ // its output extents from |target_part_path| and appending a corresponding
+ // data blob to |data_fd|. The blob will be compressed if this is smaller than
+ // the uncompressed form, and the operation type will be set accordingly.
+ // |*data_file_size| will be updated as well. If the operation happens to have
+ // the right type and already points to a data blob, we check whether its
+ // content is identical to the new one, in which case nothing is written.
+ static bool AddDataAndSetType(AnnotatedOperation* aop,
+ const std::string& target_part_path,
+ int data_fd,
+ off_t* data_file_size);
+
+ DISALLOW_COPY_AND_ASSIGN(ABGenerator);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_AB_GENERATOR_H_
diff --git a/payload_generator/ab_generator_unittest.cc b/payload_generator/ab_generator_unittest.cc
new file mode 100644
index 0000000..9241701
--- /dev/null
+++ b/payload_generator/ab_generator_unittest.cc
@@ -0,0 +1,558 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/ab_generator.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/bzip.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+bool ExtentEquals(Extent ext, uint64_t start_block, uint64_t num_blocks) {
+ return ext.start_block() == start_block && ext.num_blocks() == num_blocks;
+}
+
+// Tests splitting of a REPLACE/REPLACE_BZ operation.
+void TestSplitReplaceOrReplaceBzOperation(
+ DeltaArchiveManifest_InstallOperation_Type orig_type,
+ bool compressible) {
+ const size_t op_ex1_start_block = 2;
+ const size_t op_ex1_num_blocks = 2;
+ const size_t op_ex2_start_block = 6;
+ const size_t op_ex2_num_blocks = 1;
+ const size_t part_num_blocks = 7;
+
+ // Create the target partition data.
+ string part_path;
+ EXPECT_TRUE(utils::MakeTempFile(
+ "SplitReplaceOrReplaceBzTest_part.XXXXXX", &part_path, nullptr));
+ ScopedPathUnlinker part_path_unlinker(part_path);
+ const size_t part_size = part_num_blocks * kBlockSize;
+ chromeos::Blob part_data;
+ if (compressible) {
+ part_data.resize(part_size);
+ test_utils::FillWithData(&part_data);
+ } else {
+ std::mt19937 gen(12345);
+ std::uniform_int_distribution<uint8_t> dis(0, 255);
+ for (uint32_t i = 0; i < part_size; i++)
+ part_data.push_back(dis(gen));
+ }
+ ASSERT_EQ(part_size, part_data.size());
+ ASSERT_TRUE(utils::WriteFile(part_path.c_str(), part_data.data(), part_size));
+
+ // Create original operation and blob data.
+ const size_t op_ex1_offset = op_ex1_start_block * kBlockSize;
+ const size_t op_ex1_size = op_ex1_num_blocks * kBlockSize;
+ const size_t op_ex2_offset = op_ex2_start_block * kBlockSize;
+ const size_t op_ex2_size = op_ex2_num_blocks * kBlockSize;
+ DeltaArchiveManifest_InstallOperation op;
+ op.set_type(orig_type);
+ *(op.add_dst_extents()) = ExtentForRange(op_ex1_start_block,
+ op_ex1_num_blocks);
+ *(op.add_dst_extents()) = ExtentForRange(op_ex2_start_block,
+ op_ex2_num_blocks);
+ op.set_dst_length(op_ex1_num_blocks + op_ex2_num_blocks);
+
+ chromeos::Blob op_data;
+ op_data.insert(op_data.end(),
+ part_data.begin() + op_ex1_offset,
+ part_data.begin() + op_ex1_offset + op_ex1_size);
+ op_data.insert(op_data.end(),
+ part_data.begin() + op_ex2_offset,
+ part_data.begin() + op_ex2_offset + op_ex2_size);
+ chromeos::Blob op_blob;
+ if (orig_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+ op_blob = op_data;
+ } else {
+ ASSERT_TRUE(BzipCompress(op_data, &op_blob));
+ }
+ op.set_data_offset(0);
+ op.set_data_length(op_blob.size());
+
+ AnnotatedOperation aop;
+ aop.op = op;
+ aop.name = "SplitTestOp";
+
+ // Create the data file.
+ string data_path;
+ EXPECT_TRUE(utils::MakeTempFile(
+ "SplitReplaceOrReplaceBzTest_data.XXXXXX", &data_path, nullptr));
+ ScopedPathUnlinker data_path_unlinker(data_path);
+ int data_fd = open(data_path.c_str(), O_RDWR, 000);
+ EXPECT_GE(data_fd, 0);
+ ScopedFdCloser data_fd_closer(&data_fd);
+ EXPECT_TRUE(utils::WriteFile(data_path.c_str(), op_blob.data(),
+ op_blob.size()));
+ off_t data_file_size = op_blob.size();
+
+ // Split the operation.
+ vector<AnnotatedOperation> result_ops;
+ ASSERT_TRUE(ABGenerator::SplitReplaceOrReplaceBz(
+ aop, &result_ops, part_path, data_fd, &data_file_size));
+
+ // Check the result.
+ DeltaArchiveManifest_InstallOperation_Type expected_type =
+ compressible ?
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ :
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE;
+
+ ASSERT_EQ(2, result_ops.size());
+
+ EXPECT_EQ("SplitTestOp:0", result_ops[0].name);
+ DeltaArchiveManifest_InstallOperation first_op = result_ops[0].op;
+ EXPECT_EQ(expected_type, first_op.type());
+ EXPECT_EQ(op_ex1_size, first_op.dst_length());
+ EXPECT_EQ(1, first_op.dst_extents().size());
+ EXPECT_TRUE(ExtentEquals(first_op.dst_extents(0), op_ex1_start_block,
+ op_ex1_num_blocks));
+ // Obtain the expected blob.
+ chromeos::Blob first_expected_data(
+ part_data.begin() + op_ex1_offset,
+ part_data.begin() + op_ex1_offset + op_ex1_size);
+ chromeos::Blob first_expected_blob;
+ if (compressible) {
+ ASSERT_TRUE(BzipCompress(first_expected_data, &first_expected_blob));
+ } else {
+ first_expected_blob = first_expected_data;
+ }
+ EXPECT_EQ(first_expected_blob.size(), first_op.data_length());
+ // Check that the actual blob matches what's expected.
+ chromeos::Blob first_data_blob(first_op.data_length());
+ ssize_t bytes_read;
+ ASSERT_TRUE(utils::PReadAll(data_fd,
+ first_data_blob.data(),
+ first_op.data_length(),
+ first_op.data_offset(),
+ &bytes_read));
+ ASSERT_EQ(bytes_read, first_op.data_length());
+ EXPECT_EQ(first_expected_blob, first_data_blob);
+
+ EXPECT_EQ("SplitTestOp:1", result_ops[1].name);
+ DeltaArchiveManifest_InstallOperation second_op = result_ops[1].op;
+ EXPECT_EQ(expected_type, second_op.type());
+ EXPECT_EQ(op_ex2_size, second_op.dst_length());
+ EXPECT_EQ(1, second_op.dst_extents().size());
+ EXPECT_TRUE(ExtentEquals(second_op.dst_extents(0), op_ex2_start_block,
+ op_ex2_num_blocks));
+ // Obtain the expected blob.
+ chromeos::Blob second_expected_data(
+ part_data.begin() + op_ex2_offset,
+ part_data.begin() + op_ex2_offset + op_ex2_size);
+ chromeos::Blob second_expected_blob;
+ if (compressible) {
+ ASSERT_TRUE(BzipCompress(second_expected_data, &second_expected_blob));
+ } else {
+ second_expected_blob = second_expected_data;
+ }
+ EXPECT_EQ(second_expected_blob.size(), second_op.data_length());
+ // Check that the actual blob matches what's expected.
+ chromeos::Blob second_data_blob(second_op.data_length());
+ ASSERT_TRUE(utils::PReadAll(data_fd,
+ second_data_blob.data(),
+ second_op.data_length(),
+ second_op.data_offset(),
+ &bytes_read));
+ ASSERT_EQ(bytes_read, second_op.data_length());
+ EXPECT_EQ(second_expected_blob, second_data_blob);
+
+ // Check relative layout of data blobs.
+ EXPECT_EQ(first_op.data_offset() + first_op.data_length(),
+ second_op.data_offset());
+ EXPECT_EQ(second_op.data_offset() + second_op.data_length(), data_file_size);
+ // If we split a REPLACE into multiple ones, ensure reuse of preexisting blob.
+ if (!compressible &&
+ orig_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+ EXPECT_EQ(0, first_op.data_offset());
+ }
+}
+
+// Tests merging of REPLACE/REPLACE_BZ operations.
+void TestMergeReplaceOrReplaceBzOperations(
+ DeltaArchiveManifest_InstallOperation_Type orig_type,
+ bool compressible) {
+ const size_t first_op_num_blocks = 1;
+ const size_t second_op_num_blocks = 2;
+ const size_t total_op_num_blocks = first_op_num_blocks + second_op_num_blocks;
+ const size_t part_num_blocks = total_op_num_blocks + 2;
+
+ // Create the target partition data.
+ string part_path;
+ EXPECT_TRUE(utils::MakeTempFile(
+ "MergeReplaceOrReplaceBzTest_part.XXXXXX", &part_path, nullptr));
+ ScopedPathUnlinker part_path_unlinker(part_path);
+ const size_t part_size = part_num_blocks * kBlockSize;
+ chromeos::Blob part_data;
+ if (compressible) {
+ part_data.resize(part_size);
+ test_utils::FillWithData(&part_data);
+ } else {
+ std::mt19937 gen(12345);
+ std::uniform_int_distribution<uint8_t> dis(0, 255);
+ for (uint32_t i = 0; i < part_size; i++)
+ part_data.push_back(dis(gen));
+ }
+ ASSERT_EQ(part_size, part_data.size());
+ ASSERT_TRUE(utils::WriteFile(part_path.c_str(), part_data.data(), part_size));
+
+ // Create original operations and blob data.
+ vector<AnnotatedOperation> aops;
+ chromeos::Blob blob_data;
+ const size_t total_op_size = total_op_num_blocks * kBlockSize;
+
+ DeltaArchiveManifest_InstallOperation first_op;
+ first_op.set_type(orig_type);
+ const size_t first_op_size = first_op_num_blocks * kBlockSize;
+ first_op.set_dst_length(first_op_size);
+ *(first_op.add_dst_extents()) = ExtentForRange(0, first_op_num_blocks);
+ chromeos::Blob first_op_data(part_data.begin(),
+ part_data.begin() + first_op_size);
+ chromeos::Blob first_op_blob;
+ if (orig_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+ first_op_blob = first_op_data;
+ } else {
+ ASSERT_TRUE(BzipCompress(first_op_data, &first_op_blob));
+ }
+ first_op.set_data_offset(0);
+ first_op.set_data_length(first_op_blob.size());
+ blob_data.insert(blob_data.end(), first_op_blob.begin(), first_op_blob.end());
+ AnnotatedOperation first_aop;
+ first_aop.op = first_op;
+ first_aop.name = "first";
+ aops.push_back(first_aop);
+
+ DeltaArchiveManifest_InstallOperation second_op;
+ second_op.set_type(orig_type);
+ const size_t second_op_size = second_op_num_blocks * kBlockSize;
+ second_op.set_dst_length(second_op_size);
+ *(second_op.add_dst_extents()) = ExtentForRange(first_op_num_blocks,
+ second_op_num_blocks);
+ chromeos::Blob second_op_data(part_data.begin() + first_op_size,
+ part_data.begin() + total_op_size);
+ chromeos::Blob second_op_blob;
+ if (orig_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+ second_op_blob = second_op_data;
+ } else {
+ ASSERT_TRUE(BzipCompress(second_op_data, &second_op_blob));
+ }
+ second_op.set_data_offset(first_op_blob.size());
+ second_op.set_data_length(second_op_blob.size());
+ blob_data.insert(blob_data.end(), second_op_blob.begin(),
+ second_op_blob.end());
+ AnnotatedOperation second_aop;
+ second_aop.op = second_op;
+ second_aop.name = "second";
+ aops.push_back(second_aop);
+
+ // Create the data file.
+ string data_path;
+ EXPECT_TRUE(utils::MakeTempFile(
+ "MergeReplaceOrReplaceBzTest_data.XXXXXX", &data_path, nullptr));
+ ScopedPathUnlinker data_path_unlinker(data_path);
+ int data_fd = open(data_path.c_str(), O_RDWR, 000);
+ EXPECT_GE(data_fd, 0);
+ ScopedFdCloser data_fd_closer(&data_fd);
+ EXPECT_TRUE(utils::WriteFile(data_path.c_str(), blob_data.data(),
+ blob_data.size()));
+ off_t data_file_size = blob_data.size();
+
+ // Merge the operations.
+ EXPECT_TRUE(ABGenerator::MergeOperations(
+ &aops, 5 * kBlockSize, part_path, data_fd, &data_file_size));
+
+ // Check the result.
+ DeltaArchiveManifest_InstallOperation_Type expected_op_type =
+ compressible ?
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ :
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE;
+ EXPECT_EQ(1, aops.size());
+ DeltaArchiveManifest_InstallOperation new_op = aops[0].op;
+ EXPECT_EQ(expected_op_type, new_op.type());
+ EXPECT_FALSE(new_op.has_src_length());
+ EXPECT_EQ(total_op_num_blocks * kBlockSize, new_op.dst_length());
+ EXPECT_EQ(1, new_op.dst_extents().size());
+ EXPECT_TRUE(ExtentEquals(new_op.dst_extents(0), 0, total_op_num_blocks));
+ EXPECT_EQ("first,second", aops[0].name);
+
+ // Check to see if the blob pointed to in the new extent has what we expect.
+ chromeos::Blob expected_data(part_data.begin(),
+ part_data.begin() + total_op_size);
+ chromeos::Blob expected_blob;
+ if (compressible) {
+ ASSERT_TRUE(BzipCompress(expected_data, &expected_blob));
+ } else {
+ expected_blob = expected_data;
+ }
+ ASSERT_EQ(expected_blob.size(), new_op.data_length());
+ ASSERT_EQ(blob_data.size() + expected_blob.size(), data_file_size);
+ chromeos::Blob new_op_blob(new_op.data_length());
+ ssize_t bytes_read;
+ ASSERT_TRUE(utils::PReadAll(data_fd,
+ new_op_blob.data(),
+ new_op.data_length(),
+ new_op.data_offset(),
+ &bytes_read));
+ ASSERT_EQ(new_op.data_length(), bytes_read);
+ EXPECT_EQ(expected_blob, new_op_blob);
+}
+
+} // namespace
+
+class ABGeneratorTest : public ::testing::Test {};
+
+TEST_F(ABGeneratorTest, SplitSourceCopyTest) {
+ DeltaArchiveManifest_InstallOperation op;
+ op.set_type(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+ *(op.add_src_extents()) = ExtentForRange(2, 3);
+ *(op.add_src_extents()) = ExtentForRange(6, 1);
+ *(op.add_src_extents()) = ExtentForRange(8, 4);
+ *(op.add_dst_extents()) = ExtentForRange(10, 2);
+ *(op.add_dst_extents()) = ExtentForRange(14, 3);
+ *(op.add_dst_extents()) = ExtentForRange(18, 3);
+
+ AnnotatedOperation aop;
+ aop.op = op;
+ aop.name = "SplitSourceCopyTestOp";
+ vector<AnnotatedOperation> result_ops;
+ EXPECT_TRUE(ABGenerator::SplitSourceCopy(aop, &result_ops));
+ EXPECT_EQ(result_ops.size(), 3);
+
+ EXPECT_EQ("SplitSourceCopyTestOp:0", result_ops[0].name);
+ DeltaArchiveManifest_InstallOperation first_op = result_ops[0].op;
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY,
+ first_op.type());
+ EXPECT_EQ(kBlockSize * 2, first_op.src_length());
+ EXPECT_EQ(1, first_op.src_extents().size());
+ EXPECT_EQ(2, first_op.src_extents(0).start_block());
+ EXPECT_EQ(2, first_op.src_extents(0).num_blocks());
+ EXPECT_EQ(kBlockSize * 2, first_op.dst_length());
+ EXPECT_EQ(1, first_op.dst_extents().size());
+ EXPECT_EQ(10, first_op.dst_extents(0).start_block());
+ EXPECT_EQ(2, first_op.dst_extents(0).num_blocks());
+
+ EXPECT_EQ("SplitSourceCopyTestOp:1", result_ops[1].name);
+ DeltaArchiveManifest_InstallOperation second_op = result_ops[1].op;
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY,
+ second_op.type());
+ EXPECT_EQ(kBlockSize * 3, second_op.src_length());
+ EXPECT_EQ(3, second_op.src_extents().size());
+ EXPECT_EQ(4, second_op.src_extents(0).start_block());
+ EXPECT_EQ(1, second_op.src_extents(0).num_blocks());
+ EXPECT_EQ(6, second_op.src_extents(1).start_block());
+ EXPECT_EQ(1, second_op.src_extents(1).num_blocks());
+ EXPECT_EQ(8, second_op.src_extents(2).start_block());
+ EXPECT_EQ(1, second_op.src_extents(2).num_blocks());
+ EXPECT_EQ(kBlockSize * 3, second_op.dst_length());
+ EXPECT_EQ(1, second_op.dst_extents().size());
+ EXPECT_EQ(14, second_op.dst_extents(0).start_block());
+ EXPECT_EQ(3, second_op.dst_extents(0).num_blocks());
+
+ EXPECT_EQ("SplitSourceCopyTestOp:2", result_ops[2].name);
+ DeltaArchiveManifest_InstallOperation third_op = result_ops[2].op;
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY,
+ third_op.type());
+ EXPECT_EQ(kBlockSize * 3, third_op.src_length());
+ EXPECT_EQ(1, third_op.src_extents().size());
+ EXPECT_EQ(9, third_op.src_extents(0).start_block());
+ EXPECT_EQ(3, third_op.src_extents(0).num_blocks());
+ EXPECT_EQ(kBlockSize * 3, third_op.dst_length());
+ EXPECT_EQ(1, third_op.dst_extents().size());
+ EXPECT_EQ(18, third_op.dst_extents(0).start_block());
+ EXPECT_EQ(3, third_op.dst_extents(0).num_blocks());
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceTest) {
+ TestSplitReplaceOrReplaceBzOperation(
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE, false);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceIntoReplaceBzTest) {
+ TestSplitReplaceOrReplaceBzOperation(
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE, true);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceBzTest) {
+ TestSplitReplaceOrReplaceBzOperation(
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ, true);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceBzIntoReplaceTest) {
+ TestSplitReplaceOrReplaceBzOperation(
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ, false);
+}
+
+TEST_F(ABGeneratorTest, SortOperationsByDestinationTest) {
+ vector<AnnotatedOperation> aops;
+ // One operation with multiple destination extents.
+ DeltaArchiveManifest_InstallOperation first_op;
+ *(first_op.add_dst_extents()) = ExtentForRange(6, 1);
+ *(first_op.add_dst_extents()) = ExtentForRange(10, 2);
+ AnnotatedOperation first_aop;
+ first_aop.op = first_op;
+ first_aop.name = "first";
+ aops.push_back(first_aop);
+
+ // One with no destination extent. Should end up at the end of the vector.
+ DeltaArchiveManifest_InstallOperation second_op;
+ AnnotatedOperation second_aop;
+ second_aop.op = second_op;
+ second_aop.name = "second";
+ aops.push_back(second_aop);
+
+ // One with one destination extent.
+ DeltaArchiveManifest_InstallOperation third_op;
+ *(third_op.add_dst_extents()) = ExtentForRange(3, 2);
+ AnnotatedOperation third_aop;
+ third_aop.op = third_op;
+ third_aop.name = "third";
+ aops.push_back(third_aop);
+
+ ABGenerator::SortOperationsByDestination(&aops);
+ EXPECT_EQ(aops.size(), 3);
+ EXPECT_EQ(third_aop.name, aops[0].name);
+ EXPECT_EQ(first_aop.name, aops[1].name);
+ EXPECT_EQ(second_aop.name, aops[2].name);
+}
+
+TEST_F(ABGeneratorTest, MergeSourceCopyOperationsTest) {
+ vector<AnnotatedOperation> aops;
+ DeltaArchiveManifest_InstallOperation first_op;
+ first_op.set_type(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+ first_op.set_src_length(kBlockSize);
+ first_op.set_dst_length(kBlockSize);
+ *(first_op.add_src_extents()) = ExtentForRange(1, 1);
+ *(first_op.add_dst_extents()) = ExtentForRange(6, 1);
+ AnnotatedOperation first_aop;
+ first_aop.op = first_op;
+ first_aop.name = "1";
+ aops.push_back(first_aop);
+
+ DeltaArchiveManifest_InstallOperation second_op;
+ second_op.set_type(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+ second_op.set_src_length(3 * kBlockSize);
+ second_op.set_dst_length(3 * kBlockSize);
+ *(second_op.add_src_extents()) = ExtentForRange(2, 2);
+ *(second_op.add_src_extents()) = ExtentForRange(8, 2);
+ *(second_op.add_dst_extents()) = ExtentForRange(7, 3);
+ *(second_op.add_dst_extents()) = ExtentForRange(11, 1);
+ AnnotatedOperation second_aop;
+ second_aop.op = second_op;
+ second_aop.name = "2";
+ aops.push_back(second_aop);
+
+ DeltaArchiveManifest_InstallOperation third_op;
+ third_op.set_type(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+ third_op.set_src_length(kBlockSize);
+ third_op.set_dst_length(kBlockSize);
+ *(third_op.add_src_extents()) = ExtentForRange(11, 1);
+ *(third_op.add_dst_extents()) = ExtentForRange(12, 1);
+ AnnotatedOperation third_aop;
+ third_aop.op = third_op;
+ third_aop.name = "3";
+ aops.push_back(third_aop);
+
+ EXPECT_TRUE(ABGenerator::MergeOperations(&aops, 5 * kBlockSize,
+ "", 0, nullptr));
+
+ EXPECT_EQ(aops.size(), 1);
+ DeltaArchiveManifest_InstallOperation first_result_op = aops[0].op;
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY,
+ first_result_op.type());
+ EXPECT_EQ(kBlockSize * 5, first_result_op.src_length());
+ EXPECT_EQ(3, first_result_op.src_extents().size());
+ EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(0), 1, 3));
+ EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(1), 8, 2));
+ EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(2), 11, 1));
+ EXPECT_EQ(kBlockSize * 5, first_result_op.dst_length());
+ EXPECT_EQ(2, first_result_op.dst_extents().size());
+ EXPECT_TRUE(ExtentEquals(first_result_op.dst_extents(0), 6, 4));
+ EXPECT_TRUE(ExtentEquals(first_result_op.dst_extents(1), 11, 2));
+ EXPECT_EQ(aops[0].name, "1,2,3");
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceOperationsTest) {
+ TestMergeReplaceOrReplaceBzOperations(
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE, false);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceOperationsToReplaceBzTest) {
+ TestMergeReplaceOrReplaceBzOperations(
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE, true);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceBzOperationsTest) {
+ TestMergeReplaceOrReplaceBzOperations(
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ, true);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceBzOperationsToReplaceTest) {
+ TestMergeReplaceOrReplaceBzOperations(
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ, false);
+}
+
+TEST_F(ABGeneratorTest, NoMergeOperationsTest) {
+ // Test to make sure we don't merge operations that shouldn't be merged.
+ vector<AnnotatedOperation> aops;
+ DeltaArchiveManifest_InstallOperation first_op;
+ first_op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+ *(first_op.add_dst_extents()) = ExtentForRange(0, 1);
+ first_op.set_data_length(kBlockSize);
+ AnnotatedOperation first_aop;
+ first_aop.op = first_op;
+ aops.push_back(first_aop);
+
+ // Should merge with first, except op types don't match...
+ DeltaArchiveManifest_InstallOperation second_op;
+ second_op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+ *(second_op.add_dst_extents()) = ExtentForRange(1, 2);
+ second_op.set_data_length(2 * kBlockSize);
+ AnnotatedOperation second_aop;
+ second_aop.op = second_op;
+ aops.push_back(second_aop);
+
+ // Should merge with second, except it would exceed chunk size...
+ DeltaArchiveManifest_InstallOperation third_op;
+ third_op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+ *(third_op.add_dst_extents()) = ExtentForRange(3, 3);
+ third_op.set_data_length(3 * kBlockSize);
+ AnnotatedOperation third_aop;
+ third_aop.op = third_op;
+ aops.push_back(third_aop);
+
+ // Should merge with third, except they aren't contiguous...
+ DeltaArchiveManifest_InstallOperation fourth_op;
+ fourth_op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+ *(fourth_op.add_dst_extents()) = ExtentForRange(7, 2);
+ fourth_op.set_data_length(2 * kBlockSize);
+ AnnotatedOperation fourth_aop;
+ fourth_aop.op = fourth_op;
+ aops.push_back(fourth_aop);
+
+ EXPECT_TRUE(ABGenerator::MergeOperations(
+ &aops, 4 * kBlockSize, "", 0, nullptr));
+
+ // No operations were merged, the number of ops is the same.
+ EXPECT_EQ(aops.size(), 4);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/annotated_operation.cc b/payload_generator/annotated_operation.cc
new file mode 100644
index 0000000..2aa79fc
--- /dev/null
+++ b/payload_generator/annotated_operation.cc
@@ -0,0 +1,81 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/annotated_operation.h"
+
+#include <base/format_macros.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+// Output the list of extents as (start_block, num_blocks) in the passed output
+// stream.
+void OutputExtents(std::ostream* os,
+ const google::protobuf::RepeatedPtrField<Extent>& extents) {
+ for (const auto& extent : extents) {
+ *os << " (" << extent.start_block() << ", " << extent.num_blocks() << ")";
+ }
+}
+} // namespace
+
+bool AnnotatedOperation::SetOperationBlob(chromeos::Blob* blob, int data_fd,
+ off_t* data_file_size) {
+ TEST_AND_RETURN_FALSE(utils::PWriteAll(data_fd,
+ blob->data(),
+ blob->size(),
+ *data_file_size));
+ op.set_data_length(blob->size());
+ op.set_data_offset(*data_file_size);
+ *data_file_size += blob->size();
+ return true;
+}
+
+string InstallOperationTypeName(
+ DeltaArchiveManifest_InstallOperation_Type op_type) {
+ switch (op_type) {
+ case DeltaArchiveManifest_InstallOperation_Type_BSDIFF:
+ return "BSDIFF";
+ case DeltaArchiveManifest_InstallOperation_Type_MOVE:
+ return "MOVE";
+ case DeltaArchiveManifest_InstallOperation_Type_REPLACE:
+ return "REPLACE";
+ case DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ:
+ return "REPLACE_BZ";
+ case DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY:
+ return "SOURCE_COPY";
+ case DeltaArchiveManifest_InstallOperation_Type_SOURCE_BSDIFF:
+ return "SOURCE_BSDIFF";
+ }
+ return "UNK";
+}
+
+std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop) {
+ // For example, this prints:
+ // REPLACE_BZ 500 @3000
+ // name: /foo/bar
+ // dst: (123, 3) (127, 2)
+ os << InstallOperationTypeName(aop.op.type()) << " " << aop.op.data_length();
+ if (aop.op.data_length() > 0)
+ os << " @" << aop.op.data_offset();
+ if (!aop.name.empty()) {
+ os << std::endl << " name: " << aop.name;
+ }
+ if (aop.op.src_extents_size() != 0) {
+ os << std::endl << " src:";
+ OutputExtents(&os, aop.op.src_extents());
+ }
+ if (aop.op.dst_extents_size() != 0) {
+ os << std::endl << " dst:";
+ OutputExtents(&os, aop.op.dst_extents());
+ }
+ return os;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/annotated_operation.h b/payload_generator/annotated_operation.h
new file mode 100644
index 0000000..bacc0ab
--- /dev/null
+++ b/payload_generator/annotated_operation.h
@@ -0,0 +1,39 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
+
+#include <ostream> // NOLINT(readability/streams)
+#include <string>
+
+#include <chromeos/secure_blob.h>
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+struct AnnotatedOperation {
+ // The name given to the operation, for logging and debugging purposes only.
+ // This normally includes the path to the file and the chunk used, if any.
+ std::string name;
+
+ // The InstallOperation, as defined by the protobuf.
+ DeltaArchiveManifest_InstallOperation op;
+
+ // Writes |blob| to the end of |data_fd|, and updates |data_file_size| to
+ // match the new size of |data_fd|. It sets the data_offset and data_length
+ // in AnnotatedOperation to match the offset and size of |blob| in |data_fd|.
+ bool SetOperationBlob(chromeos::Blob* blob, int data_fd,
+ off_t* data_file_size);
+};
+
+// For logging purposes.
+std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop);
+
+std::string InstallOperationTypeName(
+ DeltaArchiveManifest_InstallOperation_Type op_type);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
diff --git a/payload_generator/cycle_breaker.cc b/payload_generator/cycle_breaker.cc
new file mode 100644
index 0000000..906502d
--- /dev/null
+++ b/payload_generator/cycle_breaker.cc
@@ -0,0 +1,199 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/cycle_breaker.h"
+
+#include <inttypes.h>
+
+#include <set>
+#include <string>
+#include <utility>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/payload_generator/graph_utils.h"
+#include "update_engine/payload_generator/tarjan.h"
+#include "update_engine/utils.h"
+
+using std::make_pair;
+using std::set;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// This is the outer function from the original paper.
+void CycleBreaker::BreakCycles(const Graph& graph, set<Edge>* out_cut_edges) {
+ cut_edges_.clear();
+
+ // Make a copy, which we will modify by removing edges. Thus, in each
+ // iteration subgraph_ is the current subgraph or the original with
+ // vertices we desire. This variable was "A_K" in the original paper.
+ subgraph_ = graph;
+
+ // The paper calls for the "adjacency structure (i.e., graph) of
+ // strong (-ly connected) component K with least vertex in subgraph
+ // induced by {s, s + 1, ..., n}".
+ // We arbitrarily order each vertex by its index in the graph. Thus,
+ // each iteration, we are looking at the subgraph {s, s + 1, ..., n}
+ // and looking for the strongly connected component with vertex s.
+
+ TarjanAlgorithm tarjan;
+ skipped_ops_ = 0;
+
+ for (Graph::size_type i = 0; i < subgraph_.size(); i++) {
+ DeltaArchiveManifest_InstallOperation_Type op_type = graph[i].op.type();
+ if (op_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+ op_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
+ skipped_ops_++;
+ continue;
+ }
+
+ if (i > 0) {
+ // Erase node (i - 1) from subgraph_. First, erase what it points to
+ subgraph_[i - 1].out_edges.clear();
+ // Now, erase any pointers to node (i - 1)
+ for (Graph::size_type j = i; j < subgraph_.size(); j++) {
+ subgraph_[j].out_edges.erase(i - 1);
+ }
+ }
+
+ // Calculate SCC (strongly connected component) with vertex i.
+ vector<Vertex::Index> component_indexes;
+ tarjan.Execute(i, &subgraph_, &component_indexes);
+
+ // Set subgraph edges for the components in the SCC.
+ for (vector<Vertex::Index>::iterator it = component_indexes.begin();
+ it != component_indexes.end(); ++it) {
+ subgraph_[*it].subgraph_edges.clear();
+ for (vector<Vertex::Index>::iterator jt = component_indexes.begin();
+ jt != component_indexes.end(); ++jt) {
+ // If there's a link from *it -> *jt in the graph,
+ // add a subgraph_ edge
+ if (utils::MapContainsKey(subgraph_[*it].out_edges, *jt))
+ subgraph_[*it].subgraph_edges.insert(*jt);
+ }
+ }
+
+ current_vertex_ = i;
+ blocked_.clear();
+ blocked_.resize(subgraph_.size());
+ blocked_graph_.clear();
+ blocked_graph_.resize(subgraph_.size());
+ Circuit(current_vertex_, 0);
+ }
+
+ out_cut_edges->swap(cut_edges_);
+ LOG(INFO) << "Cycle breaker skipped " << skipped_ops_ << " ops.";
+ DCHECK(stack_.empty());
+}
+
+static const size_t kMaxEdgesToConsider = 2;
+
+void CycleBreaker::HandleCircuit() {
+ stack_.push_back(current_vertex_);
+ CHECK_GE(stack_.size(),
+ static_cast<vector<Vertex::Index>::size_type>(2));
+ Edge min_edge = make_pair(stack_[0], stack_[1]);
+ uint64_t min_edge_weight = kuint64max;
+ size_t edges_considered = 0;
+ for (vector<Vertex::Index>::const_iterator it = stack_.begin();
+ it != (stack_.end() - 1); ++it) {
+ Edge edge = make_pair(*it, *(it + 1));
+ if (cut_edges_.find(edge) != cut_edges_.end()) {
+ stack_.pop_back();
+ return;
+ }
+ uint64_t edge_weight = graph_utils::EdgeWeight(subgraph_, edge);
+ if (edge_weight < min_edge_weight) {
+ min_edge_weight = edge_weight;
+ min_edge = edge;
+ }
+ edges_considered++;
+ if (edges_considered == kMaxEdgesToConsider)
+ break;
+ }
+ cut_edges_.insert(min_edge);
+ stack_.pop_back();
+}
+
+void CycleBreaker::Unblock(Vertex::Index u) {
+ blocked_[u] = false;
+
+ for (Vertex::EdgeMap::iterator it = blocked_graph_[u].out_edges.begin();
+ it != blocked_graph_[u].out_edges.end(); ) {
+ Vertex::Index w = it->first;
+ blocked_graph_[u].out_edges.erase(it++);
+ if (blocked_[w])
+ Unblock(w);
+ }
+}
+
+bool CycleBreaker::StackContainsCutEdge() const {
+ for (vector<Vertex::Index>::const_iterator it = ++stack_.begin(),
+ e = stack_.end(); it != e; ++it) {
+ Edge edge = make_pair(*(it - 1), *it);
+ if (utils::SetContainsKey(cut_edges_, edge)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CycleBreaker::Circuit(Vertex::Index vertex, Vertex::Index depth) {
+ // "vertex" was "v" in the original paper.
+ bool found = false; // Was "f" in the original paper.
+ stack_.push_back(vertex);
+ blocked_[vertex] = true;
+ {
+ static int counter = 0;
+ counter++;
+ if (counter == 10000) {
+ counter = 0;
+ std::string stack_str;
+ for (Vertex::Index index : stack_) {
+ stack_str += std::to_string(index);
+ stack_str += " -> ";
+ }
+ LOG(INFO) << "stack: " << stack_str;
+ }
+ }
+
+ for (Vertex::SubgraphEdgeMap::iterator w =
+ subgraph_[vertex].subgraph_edges.begin();
+ w != subgraph_[vertex].subgraph_edges.end(); ++w) {
+ if (*w == current_vertex_) {
+ // The original paper called for printing stack_ followed by
+ // current_vertex_ here, which is a cycle. Instead, we call
+ // HandleCircuit() to break it.
+ HandleCircuit();
+ found = true;
+ } else if (!blocked_[*w]) {
+ if (Circuit(*w, depth + 1)) {
+ found = true;
+ if ((depth > kMaxEdgesToConsider) || StackContainsCutEdge())
+ break;
+ }
+ }
+ }
+
+ if (found) {
+ Unblock(vertex);
+ } else {
+ for (Vertex::SubgraphEdgeMap::iterator w =
+ subgraph_[vertex].subgraph_edges.begin();
+ w != subgraph_[vertex].subgraph_edges.end(); ++w) {
+ if (blocked_graph_[*w].out_edges.find(vertex) ==
+ blocked_graph_[*w].out_edges.end()) {
+ blocked_graph_[*w].out_edges.insert(make_pair(vertex,
+ EdgeProperties()));
+ }
+ }
+ }
+ CHECK_EQ(vertex, stack_.back());
+ stack_.pop_back();
+ return found;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/cycle_breaker.h b/payload_generator/cycle_breaker.h
new file mode 100644
index 0000000..420f1e2
--- /dev/null
+++ b/payload_generator/cycle_breaker.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_CYCLE_BREAKER_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_CYCLE_BREAKER_H_
+
+// This is a modified implementation of Donald B. Johnson's algorithm for
+// finding all elementary cycles (a.k.a. circuits) in a directed graph.
+// See the paper "Finding All the Elementary Circuits of a Directed Graph"
+// at http://dutta.csc.ncsu.edu/csc791_spring07/wrap/circuits_johnson.pdf
+// for reference.
+
+// Note: this version of the algorithm not only finds cycles, but breaks them.
+// It uses a simple greedy algorithm for cutting: when a cycle is discovered,
+// the edge with the least weight is cut. Longer term we may wish to do
+// something more intelligent, since the goal is (ideally) to minimize the
+// sum of the weights of all cut cycles. In practice, it's intractable
+// to consider all cycles before cutting any; there are simply too many.
+// In a sample graph representative of a typical workload, I found over
+// 5 * 10^15 cycles.
+
+#include <set>
+#include <vector>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+namespace chromeos_update_engine {
+
+class CycleBreaker {
+ public:
+ CycleBreaker() : skipped_ops_(0) {}
+ // out_cut_edges is replaced with the cut edges.
+ void BreakCycles(const Graph& graph, std::set<Edge>* out_cut_edges);
+
+ size_t skipped_ops() const { return skipped_ops_; }
+
+ private:
+ void HandleCircuit();
+ void Unblock(Vertex::Index u);
+ bool Circuit(Vertex::Index vertex, Vertex::Index depth);
+ bool StackContainsCutEdge() const;
+
+ std::vector<bool> blocked_; // "blocked" in the paper
+ Vertex::Index current_vertex_; // "s" in the paper
+ std::vector<Vertex::Index> stack_; // the stack variable in the paper
+ Graph subgraph_; // "A_K" in the paper
+ Graph blocked_graph_; // "B" in the paper
+
+ std::set<Edge> cut_edges_;
+
+ // Number of operations skipped b/c we know they don't have any
+ // incoming edges.
+ size_t skipped_ops_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_CYCLE_BREAKER_H_
diff --git a/payload_generator/cycle_breaker_unittest.cc b/payload_generator/cycle_breaker_unittest.cc
new file mode 100644
index 0000000..b58bb08
--- /dev/null
+++ b/payload_generator/cycle_breaker_unittest.cc
@@ -0,0 +1,266 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/cycle_breaker.h"
+
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/utils.h"
+
+using std::make_pair;
+using std::pair;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+void SetOpForNodes(Graph* graph) {
+ for (Graph::iterator it = graph->begin(), e = graph->end(); it != e; ++it) {
+ it->op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+ }
+}
+} // namespace
+
+class CycleBreakerTest : public ::testing::Test {};
+
+TEST(CycleBreakerTest, SimpleTest) {
+ int counter = 0;
+ const Vertex::Index n_a = counter++;
+ const Vertex::Index n_b = counter++;
+ const Vertex::Index n_c = counter++;
+ const Vertex::Index n_d = counter++;
+ const Vertex::Index n_e = counter++;
+ const Vertex::Index n_f = counter++;
+ const Vertex::Index n_g = counter++;
+ const Vertex::Index n_h = counter++;
+ const Graph::size_type kNodeCount = counter++;
+
+ Graph graph(kNodeCount);
+ SetOpForNodes(&graph);
+
+ graph[n_a].out_edges.insert(make_pair(n_e, EdgeProperties()));
+ graph[n_a].out_edges.insert(make_pair(n_f, EdgeProperties()));
+ graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties()));
+ graph[n_c].out_edges.insert(make_pair(n_d, EdgeProperties()));
+ graph[n_d].out_edges.insert(make_pair(n_e, EdgeProperties()));
+ graph[n_d].out_edges.insert(make_pair(n_f, EdgeProperties()));
+ graph[n_e].out_edges.insert(make_pair(n_b, EdgeProperties()));
+ graph[n_e].out_edges.insert(make_pair(n_c, EdgeProperties()));
+ graph[n_e].out_edges.insert(make_pair(n_f, EdgeProperties()));
+ graph[n_f].out_edges.insert(make_pair(n_g, EdgeProperties()));
+ graph[n_g].out_edges.insert(make_pair(n_h, EdgeProperties()));
+ graph[n_h].out_edges.insert(make_pair(n_g, EdgeProperties()));
+
+ CycleBreaker breaker;
+
+ set<Edge> broken_edges;
+ breaker.BreakCycles(graph, &broken_edges);
+
+ // The following cycles must be cut:
+ // A->E->B
+ // C->D->E
+ // G->H
+
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_a, n_e)) ||
+ utils::SetContainsKey(broken_edges, make_pair(n_e, n_b)) ||
+ utils::SetContainsKey(broken_edges, make_pair(n_b, n_a)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_c, n_d)) ||
+ utils::SetContainsKey(broken_edges, make_pair(n_d, n_e)) ||
+ utils::SetContainsKey(broken_edges, make_pair(n_e, n_c)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_g, n_h)) ||
+ utils::SetContainsKey(broken_edges, make_pair(n_h, n_g)));
+ EXPECT_EQ(3, broken_edges.size());
+}
+
+namespace {
+pair<Vertex::Index, EdgeProperties> EdgeWithWeight(Vertex::Index dest,
+uint64_t weight) {
+ EdgeProperties props;
+ props.extents.resize(1);
+ props.extents[0].set_num_blocks(weight);
+ return make_pair(dest, props);
+}
+} // namespace
+
+
+// This creates a bunch of cycles like this:
+//
+// root <------.
+// (t)-> / | \ |
+// V V V |
+// N N N |
+// \ | / |
+// VVV |
+// N |
+// / | \ |
+// V V V |
+// N N N |
+// ... |
+// (s)-> \ | / |
+// VVV |
+// N |
+// \_________/
+//
+// such that the original cutting algo would cut edges (s). We changed
+// the algorithm to cut cycles (t) instead, since they are closer to the
+// root, and that can massively speed up cycle cutting.
+TEST(CycleBreakerTest, AggressiveCutTest) {
+ int counter = 0;
+
+ const int kNodesPerGroup = 4;
+ const int kGroups = 33;
+
+ Graph graph(kGroups * kNodesPerGroup + 1); // + 1 for the root node
+ SetOpForNodes(&graph);
+
+ const Vertex::Index n_root = counter++;
+
+ Vertex::Index last_hub = n_root;
+ for (int i = 0; i < kGroups; i++) {
+ uint64_t weight = 5;
+ if (i == 0)
+ weight = 2;
+ else if (i == (kGroups - 1))
+ weight = 1;
+
+ const Vertex::Index next_hub = counter++;
+
+ for (int j = 0; j < (kNodesPerGroup - 1); j++) {
+ const Vertex::Index node = counter++;
+ graph[last_hub].out_edges.insert(EdgeWithWeight(node, weight));
+ graph[node].out_edges.insert(EdgeWithWeight(next_hub, weight));
+ }
+ last_hub = next_hub;
+ }
+
+ graph[last_hub].out_edges.insert(EdgeWithWeight(n_root, 5));
+
+ EXPECT_EQ(counter, graph.size());
+
+ CycleBreaker breaker;
+
+ set<Edge> broken_edges;
+ LOG(INFO) << "If this hangs for more than 1 second, the test has failed.";
+ breaker.BreakCycles(graph, &broken_edges);
+
+ set<Edge> expected_cuts;
+
+ for (Vertex::EdgeMap::const_iterator it = graph[n_root].out_edges.begin(),
+ e = graph[n_root].out_edges.end(); it != e; ++it) {
+ expected_cuts.insert(make_pair(n_root, it->first));
+ }
+
+ EXPECT_TRUE(broken_edges == expected_cuts);
+}
+
+TEST(CycleBreakerTest, WeightTest) {
+ int counter = 0;
+ const Vertex::Index n_a = counter++;
+ const Vertex::Index n_b = counter++;
+ const Vertex::Index n_c = counter++;
+ const Vertex::Index n_d = counter++;
+ const Vertex::Index n_e = counter++;
+ const Vertex::Index n_f = counter++;
+ const Vertex::Index n_g = counter++;
+ const Vertex::Index n_h = counter++;
+ const Vertex::Index n_i = counter++;
+ const Vertex::Index n_j = counter++;
+ const Graph::size_type kNodeCount = counter++;
+
+ Graph graph(kNodeCount);
+ SetOpForNodes(&graph);
+
+ graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 4));
+ graph[n_a].out_edges.insert(EdgeWithWeight(n_f, 3));
+ graph[n_a].out_edges.insert(EdgeWithWeight(n_h, 2));
+ graph[n_b].out_edges.insert(EdgeWithWeight(n_a, 3));
+ graph[n_b].out_edges.insert(EdgeWithWeight(n_c, 4));
+ graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 5));
+ graph[n_c].out_edges.insert(EdgeWithWeight(n_d, 3));
+ graph[n_d].out_edges.insert(EdgeWithWeight(n_a, 6));
+ graph[n_d].out_edges.insert(EdgeWithWeight(n_e, 3));
+ graph[n_e].out_edges.insert(EdgeWithWeight(n_d, 4));
+ graph[n_e].out_edges.insert(EdgeWithWeight(n_g, 5));
+ graph[n_f].out_edges.insert(EdgeWithWeight(n_g, 2));
+ graph[n_g].out_edges.insert(EdgeWithWeight(n_f, 3));
+ graph[n_g].out_edges.insert(EdgeWithWeight(n_d, 5));
+ graph[n_h].out_edges.insert(EdgeWithWeight(n_i, 8));
+ graph[n_i].out_edges.insert(EdgeWithWeight(n_e, 4));
+ graph[n_i].out_edges.insert(EdgeWithWeight(n_h, 9));
+ graph[n_i].out_edges.insert(EdgeWithWeight(n_j, 6));
+
+ CycleBreaker breaker;
+
+ set<Edge> broken_edges;
+ breaker.BreakCycles(graph, &broken_edges);
+
+ // These are required to be broken:
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_b, n_a)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_b, n_c)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_d, n_e)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_f, n_g)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_h, n_i)));
+}
+
+TEST(CycleBreakerTest, UnblockGraphTest) {
+ int counter = 0;
+ const Vertex::Index n_a = counter++;
+ const Vertex::Index n_b = counter++;
+ const Vertex::Index n_c = counter++;
+ const Vertex::Index n_d = counter++;
+ const Graph::size_type kNodeCount = counter++;
+
+ Graph graph(kNodeCount);
+ SetOpForNodes(&graph);
+
+ graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 1));
+ graph[n_a].out_edges.insert(EdgeWithWeight(n_c, 1));
+ graph[n_b].out_edges.insert(EdgeWithWeight(n_c, 2));
+ graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 2));
+ graph[n_b].out_edges.insert(EdgeWithWeight(n_d, 2));
+ graph[n_d].out_edges.insert(EdgeWithWeight(n_a, 2));
+
+ CycleBreaker breaker;
+
+ set<Edge> broken_edges;
+ breaker.BreakCycles(graph, &broken_edges);
+
+ // These are required to be broken:
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_a, n_b)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_a, n_c)));
+}
+
+TEST(CycleBreakerTest, SkipOpsTest) {
+ int counter = 0;
+ const Vertex::Index n_a = counter++;
+ const Vertex::Index n_b = counter++;
+ const Vertex::Index n_c = counter++;
+ const Graph::size_type kNodeCount = counter++;
+
+ Graph graph(kNodeCount);
+ SetOpForNodes(&graph);
+ graph[n_a].op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+ graph[n_c].op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+
+ graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 1));
+ graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 1));
+
+ CycleBreaker breaker;
+
+ set<Edge> broken_edges;
+ breaker.BreakCycles(graph, &broken_edges);
+
+ EXPECT_EQ(2, breaker.skipped_ops());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/delta_diff_generator.cc b/payload_generator/delta_diff_generator.cc
new file mode 100644
index 0000000..dbb63a8
--- /dev/null
+++ b/payload_generator/delta_diff_generator.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/delta_diff_generator.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "update_engine/delta_performer.h"
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/ab_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/full_update_generator.h"
+#include "update_engine/payload_generator/inplace_generator.h"
+#include "update_engine/payload_generator/payload_file.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// bytes
+const size_t kRootFSPartitionSize = static_cast<size_t>(2) * 1024 * 1024 * 1024;
+const size_t kBlockSize = 4096; // bytes
+
+bool GenerateUpdatePayloadFile(
+ const PayloadGenerationConfig& config,
+ const string& output_path,
+ const string& private_key_path,
+ uint64_t* metadata_size) {
+ if (config.is_delta) {
+ LOG_IF(WARNING, config.source.rootfs.size != config.target.rootfs.size)
+ << "Old and new images have different block counts.";
+ // TODO(deymo): Our tools only support growing the filesystem size during
+ // an update. Remove this check when that's fixed. crbug.com/192136
+ LOG_IF(FATAL, config.source.rootfs.size > config.target.rootfs.size)
+ << "Shirking the rootfs size is not supported at the moment.";
+ }
+
+ // Sanity checks for the partition size.
+ LOG(INFO) << "Rootfs partition size: " << config.rootfs_partition_size;
+ LOG(INFO) << "Actual filesystem size: " << config.target.rootfs.size;
+
+ LOG(INFO) << "Block count: "
+ << config.target.rootfs.size / config.block_size;
+
+ LOG_IF(INFO, config.source.kernel.path.empty())
+ << "Will generate full kernel update.";
+
+ const string kTempFileTemplate("CrAU_temp_data.XXXXXX");
+ string temp_file_path;
+ off_t data_file_size = 0;
+
+ LOG(INFO) << "Reading files...";
+
+ // Create empty payload file object.
+ PayloadFile payload;
+ TEST_AND_RETURN_FALSE(payload.Init(config));
+
+ vector<AnnotatedOperation> rootfs_ops;
+ vector<AnnotatedOperation> kernel_ops;
+
+ // Select payload generation strategy based on the config.
+ unique_ptr<OperationsGenerator> strategy;
+ if (config.is_delta) {
+ // We don't efficiently support deltas on squashfs. For now, we will
+ // produce full operations in that case.
+ if (utils::IsSquashfsFilesystem(config.target.rootfs.path)) {
+ LOG(INFO) << "Using generator FullUpdateGenerator() for squashfs deltas";
+ strategy.reset(new FullUpdateGenerator());
+ } else if (utils::IsExtFilesystem(config.target.rootfs.path)) {
+ // Delta update (with possibly a full kernel update).
+ if (config.minor_version == kInPlaceMinorPayloadVersion) {
+ LOG(INFO) << "Using generator InplaceGenerator()";
+ strategy.reset(new InplaceGenerator());
+ } else if (config.minor_version == kSourceMinorPayloadVersion) {
+ LOG(INFO) << "Using generator ABGenerator()";
+ strategy.reset(new ABGenerator());
+ } else {
+ LOG(ERROR) << "Unsupported minor version given for delta payload: "
+ << config.minor_version;
+ return false;
+ }
+ } else {
+ LOG(ERROR) << "Unsupported filesystem for delta payload in "
+ << config.target.rootfs.path;
+ return false;
+ }
+ } else {
+ // Full update.
+ LOG(INFO) << "Using generator FullUpdateGenerator()";
+ strategy.reset(new FullUpdateGenerator());
+ }
+
+ int data_file_fd;
+ TEST_AND_RETURN_FALSE(
+ utils::MakeTempFile(kTempFileTemplate, &temp_file_path, &data_file_fd));
+ unique_ptr<ScopedPathUnlinker> temp_file_unlinker(
+ new ScopedPathUnlinker(temp_file_path));
+ TEST_AND_RETURN_FALSE(data_file_fd >= 0);
+
+ {
+ ScopedFdCloser data_file_fd_closer(&data_file_fd);
+ // Generate the operations using the strategy we selected above.
+ TEST_AND_RETURN_FALSE(strategy->GenerateOperations(config,
+ data_file_fd,
+ &data_file_size,
+ &rootfs_ops,
+ &kernel_ops));
+ }
+
+ // Filter the no-operations. OperationsGenerators should not output this kind
+ // of operations normally, but this is an extra step to fix that if
+ // happened.
+ diff_utils::FilterNoopOperations(&rootfs_ops);
+ diff_utils::FilterNoopOperations(&kernel_ops);
+
+ // Write payload file to disk.
+ payload.AddPartitionOperations(PartitionName::kRootfs, rootfs_ops);
+ payload.AddPartitionOperations(PartitionName::kKernel, kernel_ops);
+ payload.WritePayload(output_path, temp_file_path, private_key_path,
+ metadata_size);
+ temp_file_unlinker.reset();
+
+ LOG(INFO) << "All done. Successfully created delta file with "
+ << "metadata size = " << *metadata_size;
+ return true;
+}
+
+}; // namespace chromeos_update_engine
diff --git a/payload_generator/delta_diff_generator.h b/payload_generator/delta_diff_generator.h
new file mode 100644
index 0000000..405f451
--- /dev/null
+++ b/payload_generator/delta_diff_generator.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
+
+#include <string>
+
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+extern const size_t kBlockSize;
+extern const size_t kRootFSPartitionSize;
+
+// The |config| describes the payload generation request, describing both
+// old and new images for delta payloads and only the new image for full
+// payloads.
+// For delta payloads, the images should be already mounted read-only at
+// the respective rootfs_mountpt.
+// |private_key_path| points to a private key used to sign the update.
+// Pass empty string to not sign the update.
+// |output_path| is the filename where the delta update should be written.
+// Returns true on success. Also writes the size of the metadata into
+// |metadata_size|.
+bool GenerateUpdatePayloadFile(const PayloadGenerationConfig& config,
+ const std::string& output_path,
+ const std::string& private_key_path,
+ uint64_t* metadata_size);
+
+
+}; // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
diff --git a/payload_generator/delta_diff_utils.cc b/payload_generator/delta_diff_utils.cc
new file mode 100644
index 0000000..6c15919
--- /dev/null
+++ b/payload_generator/delta_diff_utils.cc
@@ -0,0 +1,517 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/delta_diff_utils.h"
+
+#include <algorithm>
+#include <map>
+
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/bzip.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/utils.h"
+
+using std::map;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+namespace {
+
+const char* const kBsdiffPath = "bsdiff";
+
+// The maximum destination size allowed for bsdiff. In general, bsdiff should
+// work for arbitrary big files, but the payload generation and payload
+// application requires a significant amount of RAM. We put a hard-limit of
+// 200 MiB that should not affect any released board, but will limit the
+// Chrome binary in ASan builders.
+const uint64_t kMaxBsdiffDestinationSize = 200 * 1024 * 1024; // bytes
+
+// Process a range of blocks from |range_start| to |range_end| in the extent at
+// position |*idx_p| of |extents|. If |do_remove| is true, this range will be
+// removed, which may cause the extent to be trimmed, split or removed entirely.
+// The value of |*idx_p| is updated to point to the next extent to be processed.
+// Returns true iff the next extent to process is a new or updated one.
+bool ProcessExtentBlockRange(vector<Extent>* extents, size_t* idx_p,
+ const bool do_remove, uint64_t range_start,
+ uint64_t range_end) {
+ size_t idx = *idx_p;
+ uint64_t start_block = (*extents)[idx].start_block();
+ uint64_t num_blocks = (*extents)[idx].num_blocks();
+ uint64_t range_size = range_end - range_start;
+
+ if (do_remove) {
+ if (range_size == num_blocks) {
+ // Remove the entire extent.
+ extents->erase(extents->begin() + idx);
+ } else if (range_end == num_blocks) {
+ // Trim the end of the extent.
+ (*extents)[idx].set_num_blocks(num_blocks - range_size);
+ idx++;
+ } else if (range_start == 0) {
+ // Trim the head of the extent.
+ (*extents)[idx].set_start_block(start_block + range_size);
+ (*extents)[idx].set_num_blocks(num_blocks - range_size);
+ } else {
+ // Trim the middle, splitting the remainder into two parts.
+ (*extents)[idx].set_num_blocks(range_start);
+ Extent e;
+ e.set_start_block(start_block + range_end);
+ e.set_num_blocks(num_blocks - range_end);
+ idx++;
+ extents->insert(extents->begin() + idx, e);
+ }
+ } else if (range_end == num_blocks) {
+ // Done with this extent.
+ idx++;
+ } else {
+ return false;
+ }
+
+ *idx_p = idx;
+ return true;
+}
+
+// Remove identical corresponding block ranges in |src_extents| and
+// |dst_extents|. Used for preventing moving of blocks onto themselves during
+// MOVE operations. The value of |total_bytes| indicates the actual length of
+// content; this may be slightly less than the total size of blocks, in which
+// case the last block is only partly occupied with data. Returns the total
+// number of bytes removed.
+size_t RemoveIdenticalBlockRanges(vector<Extent>* src_extents,
+ vector<Extent>* dst_extents,
+ const size_t total_bytes) {
+ size_t src_idx = 0;
+ size_t dst_idx = 0;
+ uint64_t src_offset = 0, dst_offset = 0;
+ bool new_src = true, new_dst = true;
+ size_t removed_bytes = 0, nonfull_block_bytes;
+ bool do_remove = false;
+ while (src_idx < src_extents->size() && dst_idx < dst_extents->size()) {
+ if (new_src) {
+ src_offset = 0;
+ new_src = false;
+ }
+ if (new_dst) {
+ dst_offset = 0;
+ new_dst = false;
+ }
+
+ do_remove = ((*src_extents)[src_idx].start_block() + src_offset ==
+ (*dst_extents)[dst_idx].start_block() + dst_offset);
+
+ uint64_t src_num_blocks = (*src_extents)[src_idx].num_blocks();
+ uint64_t dst_num_blocks = (*dst_extents)[dst_idx].num_blocks();
+ uint64_t min_num_blocks = std::min(src_num_blocks - src_offset,
+ dst_num_blocks - dst_offset);
+ uint64_t prev_src_offset = src_offset;
+ uint64_t prev_dst_offset = dst_offset;
+ src_offset += min_num_blocks;
+ dst_offset += min_num_blocks;
+
+ new_src = ProcessExtentBlockRange(src_extents, &src_idx, do_remove,
+ prev_src_offset, src_offset);
+ new_dst = ProcessExtentBlockRange(dst_extents, &dst_idx, do_remove,
+ prev_dst_offset, dst_offset);
+ if (do_remove)
+ removed_bytes += min_num_blocks * kBlockSize;
+ }
+
+ // If we removed the last block and this block is only partly used by file
+ // content, deduct the unused portion from the total removed byte count.
+ if (do_remove && (nonfull_block_bytes = total_bytes % kBlockSize))
+ removed_bytes -= kBlockSize - nonfull_block_bytes;
+
+ return removed_bytes;
+}
+
+} // namespace
+
+namespace diff_utils {
+
+bool DeltaReadPartition(
+ vector<AnnotatedOperation>* aops,
+ const PartitionConfig& old_part,
+ const PartitionConfig& new_part,
+ off_t chunk_blocks,
+ int data_fd,
+ off_t* data_file_size,
+ bool skip_block_0,
+ bool src_ops_allowed) {
+ ExtentRanges old_visited_blocks;
+ ExtentRanges new_visited_blocks;
+
+ // We can't produce a MOVE operation with a 0 block as neither source nor
+ // destination, so we avoid generating an operation for the block 0 here, and
+ // we will add an operation for it in the InplaceGenerator. Excluding both
+ // old and new blocks ensures that identical images would still produce empty
+ // deltas.
+ if (skip_block_0) {
+ old_visited_blocks.AddBlock(0);
+ new_visited_blocks.AddBlock(0);
+ }
+
+ map<string, vector<Extent>> old_files_map;
+ if (old_part.fs_interface) {
+ vector<FilesystemInterface::File> old_files;
+ old_part.fs_interface->GetFiles(&old_files);
+ for (const FilesystemInterface::File& file : old_files)
+ old_files_map[file.name] = file.extents;
+ }
+
+ TEST_AND_RETURN_FALSE(new_part.fs_interface);
+ vector<FilesystemInterface::File> new_files;
+ new_part.fs_interface->GetFiles(&new_files);
+
+ // The processing is very straightforward here, we generate operations for
+ // every file (and pseudo-file such as the metadata) in the new filesystem
+ // based on the file with the same name in the old filesystem, if any.
+ // Files with overlapping data blocks (like hardlinks or filesystems with tail
+ // packing or compression where the blocks store more than one file) are only
+ // generated once in the new image, but are also used only once from the old
+ // image due to some simplifications (see below).
+ for (const FilesystemInterface::File& new_file : new_files) {
+ // Ignore the files in the new filesystem without blocks. Symlinks with
+ // data blocks (for example, symlinks bigger than 60 bytes in ext2) are
+ // handled as normal files. We also ignore blocks that were already
+ // processed by a previous file.
+ vector<Extent> new_file_extents = FilterExtentRanges(
+ new_file.extents, new_visited_blocks);
+ new_visited_blocks.AddExtents(new_file_extents);
+
+ if (new_file_extents.empty())
+ continue;
+
+ LOG(INFO) << "Encoding file " << new_file.name << " ("
+ << BlocksInExtents(new_file_extents) << " blocks)";
+
+ // We can't visit each dst image inode more than once, as that would
+ // duplicate work. Here, we avoid visiting each source image inode
+ // more than once. Technically, we could have multiple operations
+ // that read the same blocks from the source image for diffing, but
+ // we choose not to avoid complexity. Eventually we will move away
+ // from using a graph/cycle detection/etc to generate diffs, and at that
+ // time, it will be easy (non-complex) to have many operations read
+ // from the same source blocks. At that time, this code can die. -adlr
+ vector<Extent> old_file_extents = FilterExtentRanges(
+ old_files_map[new_file.name], old_visited_blocks);
+ old_visited_blocks.AddExtents(old_file_extents);
+
+ TEST_AND_RETURN_FALSE(DeltaReadFile(
+ aops,
+ old_part.path,
+ new_part.path,
+ old_file_extents,
+ new_file_extents,
+ new_file.name, // operation name
+ chunk_blocks,
+ data_fd,
+ data_file_size,
+ src_ops_allowed));
+ }
+ // Process all the blocks not included in any file. We provided all the unused
+ // blocks in the old partition as available data.
+ vector<Extent> new_unvisited = {
+ ExtentForRange(0, new_part.size / kBlockSize)};
+ new_unvisited = FilterExtentRanges(new_unvisited, new_visited_blocks);
+ if (new_unvisited.empty())
+ return true;
+
+ vector<Extent> old_unvisited;
+ if (old_part.fs_interface) {
+ old_unvisited.push_back(ExtentForRange(0, old_part.size / kBlockSize));
+ old_unvisited = FilterExtentRanges(old_unvisited, old_visited_blocks);
+ }
+
+ LOG(INFO) << "Scanning " << BlocksInExtents(new_unvisited)
+ << " unwritten blocks";
+ TEST_AND_RETURN_FALSE(DeltaReadFile(
+ aops,
+ old_part.path,
+ new_part.path,
+ old_unvisited,
+ new_unvisited,
+ "<non-file-data>", // operation name
+ chunk_blocks,
+ data_fd,
+ data_file_size,
+ src_ops_allowed));
+
+ return true;
+}
+
+bool DeltaReadFile(
+ vector<AnnotatedOperation>* aops,
+ const string& old_part,
+ const string& new_part,
+ const vector<Extent>& old_extents,
+ const vector<Extent>& new_extents,
+ const string& name,
+ off_t chunk_blocks,
+ int data_fd,
+ off_t* data_file_size,
+ bool src_ops_allowed) {
+ chromeos::Blob data;
+ DeltaArchiveManifest_InstallOperation operation;
+
+ uint64_t total_blocks = BlocksInExtents(new_extents);
+ if (chunk_blocks == -1)
+ chunk_blocks = total_blocks;
+
+ // If bsdiff breaks again, blacklist the problem file by using:
+ // bsdiff_allowed = (name != "/foo/bar")
+ //
+ // TODO(dgarrett): chromium-os:15274 connect this test to the command line.
+ bool bsdiff_allowed = true;
+ if (static_cast<uint64_t>(chunk_blocks) * kBlockSize >
+ kMaxBsdiffDestinationSize) {
+ bsdiff_allowed = false;
+ }
+
+ if (!bsdiff_allowed) {
+ LOG(INFO) << "bsdiff blacklisting: " << name;
+ }
+
+ for (uint64_t block_offset = 0; block_offset < total_blocks;
+ block_offset += chunk_blocks) {
+ // Split the old/new file in the same chunks. Note that this could drop
+ // some information from the old file used for the new chunk. If the old
+ // file is smaller (or even empty when there's no old file) the chunk will
+ // also be empty.
+ vector<Extent> old_extents_chunk = ExtentsSublist(
+ old_extents, block_offset, chunk_blocks);
+ vector<Extent> new_extents_chunk = ExtentsSublist(
+ new_extents, block_offset, chunk_blocks);
+ NormalizeExtents(&old_extents_chunk);
+ NormalizeExtents(&new_extents_chunk);
+
+ TEST_AND_RETURN_FALSE(ReadExtentsToDiff(old_part,
+ new_part,
+ old_extents_chunk,
+ new_extents_chunk,
+ bsdiff_allowed,
+ &data,
+ &operation,
+ src_ops_allowed));
+
+ // Check if the operation writes nothing.
+ if (operation.dst_extents_size() == 0) {
+ if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE) {
+ LOG(INFO) << "Empty MOVE operation ("
+ << name << "), skipping";
+ continue;
+ } else {
+ LOG(ERROR) << "Empty non-MOVE operation";
+ return false;
+ }
+ }
+
+ // Write the data
+ if (operation.type() != DeltaArchiveManifest_InstallOperation_Type_MOVE &&
+ operation.type() !=
+ DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY) {
+ operation.set_data_offset(*data_file_size);
+ operation.set_data_length(data.size());
+ }
+
+ TEST_AND_RETURN_FALSE(utils::WriteAll(data_fd, data.data(), data.size()));
+ *data_file_size += data.size();
+
+ // Now, insert into the list of operations.
+ AnnotatedOperation aop;
+ aop.name = name;
+ if (static_cast<uint64_t>(chunk_blocks) < total_blocks) {
+ aop.name = base::StringPrintf("%s:%" PRIu64,
+ name.c_str(), block_offset / chunk_blocks);
+ }
+ aop.op = operation;
+ aops->emplace_back(aop);
+ }
+ return true;
+}
+
+bool ReadExtentsToDiff(const string& old_part,
+ const string& new_part,
+ const vector<Extent>& old_extents,
+ const vector<Extent>& new_extents,
+ bool bsdiff_allowed,
+ chromeos::Blob* out_data,
+ DeltaArchiveManifest_InstallOperation* out_op,
+ bool src_ops_allowed) {
+ DeltaArchiveManifest_InstallOperation operation;
+ // Data blob that will be written to delta file.
+ const chromeos::Blob* data_blob = nullptr;
+
+ // We read blocks from old_extents and write blocks to new_extents.
+ uint64_t blocks_to_read = BlocksInExtents(old_extents);
+ uint64_t blocks_to_write = BlocksInExtents(new_extents);
+
+ // Make copies of the extents so we can modify them.
+ vector<Extent> src_extents = old_extents;
+ vector<Extent> dst_extents = new_extents;
+
+ // Read in bytes from new data.
+ chromeos::Blob new_data;
+ TEST_AND_RETURN_FALSE(utils::ReadExtents(new_part,
+ new_extents,
+ &new_data,
+ kBlockSize * blocks_to_write,
+ kBlockSize));
+ TEST_AND_RETURN_FALSE(!new_data.empty());
+
+
+ // Using a REPLACE is always an option.
+ operation.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+ data_blob = &new_data;
+
+ // Try compressing it with bzip2.
+ chromeos::Blob new_data_bz;
+ TEST_AND_RETURN_FALSE(BzipCompress(new_data, &new_data_bz));
+ CHECK(!new_data_bz.empty());
+ if (new_data_bz.size() < data_blob->size()) {
+ // A REPLACE_BZ is better.
+ operation.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+ data_blob = &new_data_bz;
+ }
+
+ chromeos::Blob old_data;
+ chromeos::Blob empty_blob;
+ chromeos::Blob bsdiff_delta;
+ if (blocks_to_read > 0) {
+ // Read old data.
+ TEST_AND_RETURN_FALSE(
+ utils::ReadExtents(old_part, src_extents, &old_data,
+ kBlockSize * blocks_to_read, kBlockSize));
+ if (old_data == new_data) {
+ // No change in data.
+ if (src_ops_allowed) {
+ operation.set_type(
+ DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+ } else {
+ operation.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+ }
+ data_blob = &empty_blob;
+ } else if (bsdiff_allowed) {
+ // If the source file is considered bsdiff safe (no bsdiff bugs
+ // triggered), see if BSDIFF encoding is smaller.
+ base::FilePath old_chunk;
+ TEST_AND_RETURN_FALSE(base::CreateTemporaryFile(&old_chunk));
+ ScopedPathUnlinker old_unlinker(old_chunk.value());
+ TEST_AND_RETURN_FALSE(
+ utils::WriteFile(old_chunk.value().c_str(),
+ old_data.data(), old_data.size()));
+ base::FilePath new_chunk;
+ TEST_AND_RETURN_FALSE(base::CreateTemporaryFile(&new_chunk));
+ ScopedPathUnlinker new_unlinker(new_chunk.value());
+ TEST_AND_RETURN_FALSE(
+ utils::WriteFile(new_chunk.value().c_str(),
+ new_data.data(), new_data.size()));
+
+ TEST_AND_RETURN_FALSE(
+ BsdiffFiles(old_chunk.value(), new_chunk.value(), &bsdiff_delta));
+ CHECK_GT(bsdiff_delta.size(), static_cast<chromeos::Blob::size_type>(0));
+ if (bsdiff_delta.size() < data_blob->size()) {
+ if (src_ops_allowed) {
+ operation.set_type(
+ DeltaArchiveManifest_InstallOperation_Type_SOURCE_BSDIFF);
+ } else {
+ operation.set_type(DeltaArchiveManifest_InstallOperation_Type_BSDIFF);
+ }
+ data_blob = &bsdiff_delta;
+ }
+ }
+ }
+
+ size_t removed_bytes = 0;
+ // Remove identical src/dst block ranges in MOVE operations.
+ if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE) {
+ removed_bytes = RemoveIdenticalBlockRanges(
+ &src_extents, &dst_extents, new_data.size());
+ }
+ // Set legacy src_length and dst_length fields.
+ operation.set_src_length(old_data.size() - removed_bytes);
+ operation.set_dst_length(new_data.size() - removed_bytes);
+
+ // Embed extents in the operation.
+ StoreExtents(src_extents, operation.mutable_src_extents());
+ StoreExtents(dst_extents, operation.mutable_dst_extents());
+
+ // Replace operations should not have source extents.
+ if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+ operation.type() ==
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
+ operation.clear_src_extents();
+ operation.clear_src_length();
+ }
+
+ *out_data = std::move(*data_blob);
+ *out_op = operation;
+
+ return true;
+}
+
+// Runs the bsdiff tool on two files and returns the resulting delta in
+// 'out'. Returns true on success.
+bool BsdiffFiles(const string& old_file,
+ const string& new_file,
+ chromeos::Blob* out) {
+ const string kPatchFile = "delta.patchXXXXXX";
+ string patch_file_path;
+
+ TEST_AND_RETURN_FALSE(
+ utils::MakeTempFile(kPatchFile, &patch_file_path, nullptr));
+
+ vector<string> cmd;
+ cmd.push_back(kBsdiffPath);
+ cmd.push_back(old_file);
+ cmd.push_back(new_file);
+ cmd.push_back(patch_file_path);
+
+ int rc = 1;
+ chromeos::Blob patch_file;
+ TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &rc, nullptr));
+ TEST_AND_RETURN_FALSE(rc == 0);
+ TEST_AND_RETURN_FALSE(utils::ReadFile(patch_file_path, out));
+ unlink(patch_file_path.c_str());
+ return true;
+}
+
+// Returns true if |op| is a no-op operation that doesn't do any useful work
+// (e.g., a move operation that copies blocks onto themselves).
+bool IsNoopOperation(const DeltaArchiveManifest_InstallOperation& op) {
+ return (op.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE &&
+ ExpandExtents(op.src_extents()) == ExpandExtents(op.dst_extents()));
+}
+
+void FilterNoopOperations(vector<AnnotatedOperation>* ops) {
+ ops->erase(
+ std::remove_if(
+ ops->begin(), ops->end(),
+ [](const AnnotatedOperation& aop){return IsNoopOperation(aop.op);}),
+ ops->end());
+}
+
+bool InitializePartitionInfo(const PartitionConfig& part, PartitionInfo* info) {
+ info->set_size(part.size);
+ OmahaHashCalculator hasher;
+ TEST_AND_RETURN_FALSE(hasher.UpdateFile(part.path, part.size) ==
+ static_cast<off_t>(part.size));
+ TEST_AND_RETURN_FALSE(hasher.Finalize());
+ const chromeos::Blob& hash = hasher.raw_hash();
+ info->set_hash(hash.data(), hash.size());
+ LOG(INFO) << part.path << ": size=" << part.size << " hash=" << hasher.hash();
+ return true;
+}
+
+} // namespace diff_utils
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/delta_diff_utils.h b/payload_generator/delta_diff_utils.h
new file mode 100644
index 0000000..0be8666
--- /dev/null
+++ b/payload_generator/delta_diff_utils.h
@@ -0,0 +1,94 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+namespace diff_utils {
+
+// Create operations in |aops| to produce all the blocks in the |new_part|
+// partition using the filesystem opened in that PartitionConfig.
+// It uses the files reported by the filesystem in |old_part| and the data
+// blocks in that partition (if available) to determine the best way to compress
+// the new files (REPLACE, REPLACE_BZ, COPY, BSDIFF) and writes any necessary
+// data to the end of |data_fd| updating |data_file_size| accordingly.
+bool DeltaReadPartition(std::vector<AnnotatedOperation>* aops,
+ const PartitionConfig& old_part,
+ const PartitionConfig& new_part,
+ off_t chunk_blocks,
+ int data_fd,
+ off_t* data_file_size,
+ bool skip_block_0,
+ bool src_ops_allowed);
+
+// For a given file |name| append operations to |aops| to produce it in the
+// |new_part|. The file will be split in chunks of |chunk_blocks| blocks each
+// or treated as a single chunk if |chunk_blocks| is -1. The file data is
+// stored in |new_part| in the blocks described by |new_extents| and, if it
+// exists, the old version exists in |old_part| in the blocks described by
+// |old_extents|. The operations added to |aops| reference the data blob
+// in the file |data_fd|, which has length *data_file_size. *data_file_size is
+// updated appropriately. Returns true on success.
+bool DeltaReadFile(std::vector<AnnotatedOperation>* aops,
+ const std::string& old_part,
+ const std::string& new_part,
+ const std::vector<Extent>& old_extents,
+ const std::vector<Extent>& new_extents,
+ const std::string& name,
+ off_t chunk_blocks,
+ int data_fd,
+ off_t* data_file_size,
+ bool src_ops_allowed);
+
+// Reads the blocks |old_extents| from |old_part| (if it exists) and the
+// |new_extents| from |new_part| and determines the smallest way to encode
+// this |new_extents| for the diff. It stores necessary data in |out_data| and
+// fills in |out_op|. If there's no change in old and new files, it creates a
+// MOVE operation. If there is a change, the smallest of REPLACE, REPLACE_BZ,
+// or BSDIFF wins. |new_extents| must not be empty.
+// If |src_ops_allowed| is true, it will emit SOURCE_COPY and SOURCE_BSDIFF
+// operations instead of MOVE and BSDIFF, respectively.
+// Returns true on success.
+bool ReadExtentsToDiff(const std::string& old_part,
+ const std::string& new_part,
+ const std::vector<Extent>& old_extents,
+ const std::vector<Extent>& new_extents,
+ bool bsdiff_allowed,
+ chromeos::Blob* out_data,
+ DeltaArchiveManifest_InstallOperation* out_op,
+ bool src_ops_allowed);
+
+// Runs the bsdiff tool on two files and returns the resulting delta in
+// |out|. Returns true on success.
+bool BsdiffFiles(const std::string& old_file,
+ const std::string& new_file,
+ chromeos::Blob* out);
+
+// Returns true if |op| is a no-op operation that doesn't do any useful work
+// (e.g., a move operation that copies blocks onto themselves).
+bool IsNoopOperation(const DeltaArchiveManifest_InstallOperation& op);
+
+// Filters all the operations that are no-op, maintaining the relative order
+// of the rest of the operations.
+void FilterNoopOperations(std::vector<AnnotatedOperation>* ops);
+
+bool InitializePartitionInfo(const PartitionConfig& partition,
+ PartitionInfo* info);
+
+} // namespace diff_utils
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_UTILS_H_
diff --git a/payload_generator/delta_diff_utils_unittest.cc b/payload_generator/delta_diff_utils_unittest.cc
new file mode 100644
index 0000000..2157aa1
--- /dev/null
+++ b/payload_generator/delta_diff_utils_unittest.cc
@@ -0,0 +1,479 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/delta_diff_utils.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/scoped_file.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/payload_generator/fake_filesystem.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Writes the |data| in the blocks specified by |extents| on the partition
+// |part_path|. The |data| size could be smaller than the size of the blocks
+// passed.
+bool WriteExtents(const string& part_path,
+ const vector<Extent>& extents,
+ off_t block_size,
+ const chromeos::Blob& data) {
+ uint64_t offset = 0;
+ base::ScopedFILE fp(fopen(part_path.c_str(), "r+"));
+ TEST_AND_RETURN_FALSE(fp.get());
+
+ for (const Extent& extent : extents) {
+ if (offset >= data.size())
+ break;
+ TEST_AND_RETURN_FALSE(
+ fseek(fp.get(), extent.start_block() * block_size, SEEK_SET) == 0);
+ uint64_t to_write = std::min(extent.num_blocks() * block_size,
+ data.size() - offset);
+ TEST_AND_RETURN_FALSE(
+ fwrite(data.data() + offset, 1, to_write, fp.get()) == to_write);
+ offset += extent.num_blocks() * block_size;
+ }
+ return true;
+}
+
+} // namespace
+
+class DeltaDiffUtilsTest : public ::testing::Test {
+ protected:
+ const uint64_t kFilesystemSize = kBlockSize * 1024;
+
+ void SetUp() override {
+ old_part_path_ = "DeltaDiffUtilsTest-old_part_path-XXXXXX";
+ CreateFilesystem(&old_fs_, &old_part_path_, kFilesystemSize);
+
+ new_part_path_ = "DeltaDiffUtilsTest-new_part_path-XXXXXX";
+ CreateFilesystem(&old_fs_, &new_part_path_, kFilesystemSize);
+ }
+
+ void TearDown() override {
+ unlink(old_part_path_.c_str());
+ unlink(new_part_path_.c_str());
+ }
+
+ // Create a fake filesystem of the given size and initialize the partition
+ // holding it.
+ void CreateFilesystem(unique_ptr<FakeFilesystem>* fs, string* filename,
+ uint64_t size) {
+ string pattern = *filename;
+ ASSERT_TRUE(utils::MakeTempFile(pattern.c_str(), filename, nullptr));
+ ASSERT_EQ(0, truncate(filename->c_str(), size));
+ fs->reset(new FakeFilesystem(kBlockSize, size / kBlockSize));
+ }
+
+ // Paths to old and new temporary filesystems used in the tests.
+ string old_part_path_;
+ string new_part_path_;
+
+ // FilesystemInterface fake implementations used to mock out the file/block
+ // distribution.
+ unique_ptr<FakeFilesystem> old_fs_;
+ unique_ptr<FakeFilesystem> new_fs_;
+};
+
+TEST_F(DeltaDiffUtilsTest, MoveSmallTest) {
+ chromeos::Blob data_blob(kBlockSize);
+ test_utils::FillWithData(&data_blob);
+
+ // The old file is on a different block than the new one.
+ vector<Extent> old_extents = { ExtentForRange(11, 1) };
+ vector<Extent> new_extents = { ExtentForRange(1, 1) };
+
+ EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+ EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+ chromeos::Blob data;
+ DeltaArchiveManifest_InstallOperation op;
+ EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+ old_part_path_,
+ new_part_path_,
+ old_extents,
+ new_extents,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+ EXPECT_TRUE(data.empty());
+
+ EXPECT_TRUE(op.has_type());
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type());
+ EXPECT_FALSE(op.has_data_offset());
+ EXPECT_FALSE(op.has_data_length());
+ EXPECT_EQ(1, op.src_extents_size());
+ EXPECT_EQ(kBlockSize, op.src_length());
+ EXPECT_EQ(1, op.dst_extents_size());
+ EXPECT_EQ(kBlockSize, op.dst_length());
+ EXPECT_EQ(BlocksInExtents(op.src_extents()),
+ BlocksInExtents(op.dst_extents()));
+ EXPECT_EQ(1, BlocksInExtents(op.dst_extents()));
+}
+
+TEST_F(DeltaDiffUtilsTest, MoveWithSameBlock) {
+ // Setup the old/new files so that it has immobile chunks; we make sure to
+ // utilize all sub-cases of such chunks: blocks 21--22 induce a split (src)
+ // and complete removal (dst), whereas blocks 24--25 induce trimming of the
+ // tail (src) and head (dst) of extents. The final block (29) is used for
+ // ensuring we properly account for the number of bytes removed in cases where
+ // the last block is partly filled. The detailed configuration:
+ //
+ // Old: [ 20 21 22 23 24 25 ] [ 28 29 ]
+ // New: [ 18 ] [ 21 22 ] [ 20 ] [ 24 25 26 ] [ 29 ]
+ // Same: ^^ ^^ ^^ ^^ ^^
+ vector<Extent> old_extents = {
+ ExtentForRange(20, 6),
+ ExtentForRange(28, 2) };
+ vector<Extent> new_extents = {
+ ExtentForRange(18, 1),
+ ExtentForRange(21, 2),
+ ExtentForRange(20, 1),
+ ExtentForRange(24, 3),
+ ExtentForRange(29, 1) };
+
+ uint64_t num_blocks = BlocksInExtents(old_extents);
+ EXPECT_EQ(num_blocks, BlocksInExtents(new_extents));
+
+ // The size of the data should match the total number of blocks. Each block
+ // has a different content.
+ chromeos::Blob file_data;
+ for (uint64_t i = 0; i < num_blocks; ++i) {
+ file_data.resize(file_data.size() + kBlockSize, 'a' + i);
+ }
+
+ EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, file_data));
+ EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, file_data));
+
+ chromeos::Blob data;
+ DeltaArchiveManifest_InstallOperation op;
+ EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+ old_part_path_,
+ new_part_path_,
+ old_extents,
+ new_extents,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+
+ EXPECT_TRUE(data.empty());
+
+ EXPECT_TRUE(op.has_type());
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type());
+ EXPECT_FALSE(op.has_data_offset());
+ EXPECT_FALSE(op.has_data_length());
+
+ // The expected old and new extents that actually moved. See comment above.
+ old_extents = {
+ ExtentForRange(20, 1),
+ ExtentForRange(23, 1),
+ ExtentForRange(28, 1) };
+ new_extents = {
+ ExtentForRange(18, 1),
+ ExtentForRange(20, 1),
+ ExtentForRange(26, 1) };
+ num_blocks = BlocksInExtents(old_extents);
+
+ EXPECT_EQ(num_blocks * kBlockSize, op.src_length());
+ EXPECT_EQ(num_blocks * kBlockSize, op.dst_length());
+
+ EXPECT_EQ(old_extents.size(), op.src_extents_size());
+ for (int i = 0; i < op.src_extents_size(); i++) {
+ EXPECT_EQ(old_extents[i].start_block(), op.src_extents(i).start_block())
+ << "i == " << i;
+ EXPECT_EQ(old_extents[i].num_blocks(), op.src_extents(i).num_blocks())
+ << "i == " << i;
+ }
+
+ EXPECT_EQ(new_extents.size(), op.dst_extents_size());
+ for (int i = 0; i < op.dst_extents_size(); i++) {
+ EXPECT_EQ(new_extents[i].start_block(), op.dst_extents(i).start_block())
+ << "i == " << i;
+ EXPECT_EQ(new_extents[i].num_blocks(), op.dst_extents(i).num_blocks())
+ << "i == " << i;
+ }
+}
+
+TEST_F(DeltaDiffUtilsTest, BsdiffSmallTest) {
+ // Test a BSDIFF operation from block 1 to block 2.
+ chromeos::Blob data_blob(kBlockSize);
+ test_utils::FillWithData(&data_blob);
+
+ // The old file is on a different block than the new one.
+ vector<Extent> old_extents = { ExtentForRange(1, 1) };
+ vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+ EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+ // Modify one byte in the new file.
+ data_blob[0]++;
+ EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+ chromeos::Blob data;
+ DeltaArchiveManifest_InstallOperation op;
+ EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+ old_part_path_,
+ new_part_path_,
+ old_extents,
+ new_extents,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+
+ EXPECT_FALSE(data.empty());
+
+ EXPECT_TRUE(op.has_type());
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_BSDIFF, op.type());
+ EXPECT_FALSE(op.has_data_offset());
+ EXPECT_FALSE(op.has_data_length());
+ EXPECT_EQ(1, op.src_extents_size());
+ EXPECT_EQ(kBlockSize, op.src_length());
+ EXPECT_EQ(1, op.dst_extents_size());
+ EXPECT_EQ(kBlockSize, op.dst_length());
+ EXPECT_EQ(BlocksInExtents(op.src_extents()),
+ BlocksInExtents(op.dst_extents()));
+ EXPECT_EQ(1, BlocksInExtents(op.dst_extents()));
+}
+
+TEST_F(DeltaDiffUtilsTest, BsdiffNotAllowedTest) {
+ // Same setup as the previous test, but this time BSDIFF operations are not
+ // allowed.
+ chromeos::Blob data_blob(kBlockSize);
+ test_utils::FillWithData(&data_blob);
+
+ // The old file is on a different block than the new one.
+ vector<Extent> old_extents = { ExtentForRange(1, 1) };
+ vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+ EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+ // Modify one byte in the new file.
+ data_blob[0]++;
+ EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+ chromeos::Blob data;
+ DeltaArchiveManifest_InstallOperation op;
+ EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+ old_part_path_,
+ new_part_path_,
+ old_extents,
+ new_extents,
+ false, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+
+ EXPECT_FALSE(data.empty());
+
+ // The point of this test is that we don't use BSDIFF the way the above
+ // did. The rest of the details are to be caught in other tests.
+ EXPECT_TRUE(op.has_type());
+ EXPECT_NE(DeltaArchiveManifest_InstallOperation_Type_BSDIFF, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, BsdiffNotAllowedMoveTest) {
+ chromeos::Blob data_blob(kBlockSize);
+ test_utils::FillWithData(&data_blob);
+
+ // The old file is on a different block than the new one.
+ vector<Extent> old_extents = { ExtentForRange(1, 1) };
+ vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+ EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+ EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+ chromeos::Blob data;
+ DeltaArchiveManifest_InstallOperation op;
+ EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+ old_part_path_,
+ new_part_path_,
+ old_extents,
+ new_extents,
+ false, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+
+ EXPECT_TRUE(data.empty());
+
+ // The point of this test is that we can still use a MOVE for a file
+ // that is blacklisted.
+ EXPECT_TRUE(op.has_type());
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, ReplaceSmallTest) {
+ // The old file is on a different block than the new one.
+ vector<Extent> old_extents = { ExtentForRange(1, 1) };
+ vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+ // Make a blob that's just 1's that will compress well.
+ chromeos::Blob ones(kBlockSize, 1);
+
+ // Make a blob with random data that won't compress well.
+ chromeos::Blob random_data;
+ std::mt19937 gen(12345);
+ std::uniform_int_distribution<uint8_t> dis(0, 255);
+ for (uint32_t i = 0; i < kBlockSize; i++) {
+ random_data.push_back(dis(gen));
+ }
+
+ for (int i = 0; i < 2; i++) {
+ chromeos::Blob data_to_test = i == 0 ? random_data : ones;
+ // The old_extents will be initialized with 0.
+ EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize,
+ data_to_test));
+
+ chromeos::Blob data;
+ DeltaArchiveManifest_InstallOperation op;
+ EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+ old_part_path_,
+ new_part_path_,
+ old_extents,
+ new_extents,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+ EXPECT_FALSE(data.empty());
+
+ EXPECT_TRUE(op.has_type());
+ const DeltaArchiveManifest_InstallOperation_Type expected_type =
+ (i == 0 ? DeltaArchiveManifest_InstallOperation_Type_REPLACE :
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+ EXPECT_EQ(expected_type, op.type());
+ EXPECT_FALSE(op.has_data_offset());
+ EXPECT_FALSE(op.has_data_length());
+ EXPECT_EQ(0, op.src_extents_size());
+ EXPECT_FALSE(op.has_src_length());
+ EXPECT_EQ(1, op.dst_extents_size());
+ EXPECT_EQ(data_to_test.size(), op.dst_length());
+ EXPECT_EQ(1, BlocksInExtents(op.dst_extents()));
+ }
+}
+
+TEST_F(DeltaDiffUtilsTest, SourceCopyTest) {
+ // Makes sure SOURCE_COPY operations are emitted whenever src_ops_allowed
+ // is true. It is the same setup as MoveSmallTest, which checks that
+ // the operation is well-formed.
+ chromeos::Blob data_blob(kBlockSize);
+ test_utils::FillWithData(&data_blob);
+
+ // The old file is on a different block than the new one.
+ vector<Extent> old_extents = { ExtentForRange(11, 1) };
+ vector<Extent> new_extents = { ExtentForRange(1, 1) };
+
+ EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+ EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+ chromeos::Blob data;
+ DeltaArchiveManifest_InstallOperation op;
+ EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+ old_part_path_,
+ new_part_path_,
+ old_extents,
+ new_extents,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ true)); // src_ops_allowed
+ EXPECT_TRUE(data.empty());
+
+ EXPECT_TRUE(op.has_type());
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, SourceBsdiffTest) {
+ // Makes sure SOURCE_BSDIFF operations are emitted whenever src_ops_allowed
+ // is true. It is the same setup as BsdiffSmallTest, which checks
+ // that the operation is well-formed.
+ chromeos::Blob data_blob(kBlockSize);
+ test_utils::FillWithData(&data_blob);
+
+ // The old file is on a different block than the new one.
+ vector<Extent> old_extents = { ExtentForRange(1, 1) };
+ vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+ EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+ // Modify one byte in the new file.
+ data_blob[0]++;
+ EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+ chromeos::Blob data;
+ DeltaArchiveManifest_InstallOperation op;
+ EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+ old_part_path_,
+ new_part_path_,
+ old_extents,
+ new_extents,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ true)); // src_ops_allowed
+
+ EXPECT_FALSE(data.empty());
+ EXPECT_TRUE(op.has_type());
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_BSDIFF,
+ op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, IsNoopOperationTest) {
+ DeltaArchiveManifest_InstallOperation op;
+ op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+ EXPECT_FALSE(diff_utils::IsNoopOperation(op));
+ op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+ EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+ *(op.add_src_extents()) = ExtentForRange(3, 2);
+ *(op.add_dst_extents()) = ExtentForRange(3, 2);
+ EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+ *(op.add_src_extents()) = ExtentForRange(7, 5);
+ *(op.add_dst_extents()) = ExtentForRange(7, 5);
+ EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+ *(op.add_src_extents()) = ExtentForRange(20, 2);
+ *(op.add_dst_extents()) = ExtentForRange(20, 1);
+ *(op.add_dst_extents()) = ExtentForRange(21, 1);
+ EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+ *(op.add_src_extents()) = ExtentForRange(24, 1);
+ *(op.add_dst_extents()) = ExtentForRange(25, 1);
+ EXPECT_FALSE(diff_utils::IsNoopOperation(op));
+}
+
+TEST_F(DeltaDiffUtilsTest, FilterNoopOperations) {
+ AnnotatedOperation aop1;
+ aop1.op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+ *(aop1.op.add_dst_extents()) = ExtentForRange(3, 2);
+ aop1.name = "aop1";
+
+ AnnotatedOperation aop2 = aop1;
+ aop2.name = "aop2";
+
+ AnnotatedOperation noop;
+ noop.op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+ *(noop.op.add_src_extents()) = ExtentForRange(3, 2);
+ *(noop.op.add_dst_extents()) = ExtentForRange(3, 2);
+ noop.name = "noop";
+
+ vector<AnnotatedOperation> ops = {noop, aop1, noop, noop, aop2, noop};
+ diff_utils::FilterNoopOperations(&ops);
+ EXPECT_EQ(2u, ops.size());
+ EXPECT_EQ("aop1", ops[0].name);
+ EXPECT_EQ("aop2", ops[1].name);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/ext2_filesystem.cc b/payload_generator/ext2_filesystem.cc
new file mode 100644
index 0000000..b7276d5
--- /dev/null
+++ b/payload_generator/ext2_filesystem.cc
@@ -0,0 +1,343 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/ext2_filesystem.h"
+
+#include <et/com_err.h>
+#include <ext2fs/ext2_io.h>
+#include <ext2fs/ext2fs.h>
+
+#include <map>
+#include <set>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+// Processes all blocks belonging to an inode and adds them to the extent list.
+// This function should match the prototype expected by ext2fs_block_iterate2().
+int ProcessInodeAllBlocks(ext2_filsys fs,
+ blk_t* blocknr,
+ e2_blkcnt_t blockcnt,
+ blk_t ref_blk,
+ int ref_offset,
+ void* priv) {
+ vector<Extent>* extents = static_cast<vector<Extent>*>(priv);
+ AppendBlockToExtents(extents, *blocknr);
+ return 0;
+}
+
+// Processes only indirect, double indirect or triple indirect metadata
+// blocks belonging to an inode. This function should match the prototype of
+// ext2fs_block_iterate2().
+int AddMetadataBlocks(ext2_filsys fs,
+ blk_t* blocknr,
+ e2_blkcnt_t blockcnt,
+ blk_t ref_blk,
+ int ref_offset,
+ void* priv) {
+ set<uint64_t>* blocks = static_cast<set<uint64_t>*>(priv);
+ // If |blockcnt| is non-negative, |blocknr| points to the physical block
+ // number.
+ // If |blockcnt| is negative, it is one of the values: BLOCK_COUNT_IND,
+ // BLOCK_COUNT_DIND, BLOCK_COUNT_TIND or BLOCK_COUNT_TRANSLATOR and
+ // |blocknr| points to a block in the first three cases. The last case is
+ // only used by GNU Hurd, so we shouldn't see those cases here.
+ if (blockcnt == BLOCK_COUNT_IND || blockcnt == BLOCK_COUNT_DIND ||
+ blockcnt == BLOCK_COUNT_TIND) {
+ blocks->insert(*blocknr);
+ }
+ return 0;
+}
+
+struct UpdateFileAndAppendState {
+ std::map<ext2_ino_t, FilesystemInterface::File>* inodes = nullptr;
+ set<ext2_ino_t>* used_inodes = nullptr;
+ vector<FilesystemInterface::File>* files = nullptr;
+ ext2_filsys filsys;
+};
+
+int UpdateFileAndAppend(ext2_ino_t dir,
+ int entry,
+ struct ext2_dir_entry *dirent,
+ int offset,
+ int blocksize,
+ char *buf,
+ void *priv_data) {
+ UpdateFileAndAppendState* state =
+ static_cast<UpdateFileAndAppendState*>(priv_data);
+ uint32_t file_type = dirent->name_len >> 8;
+ // Directories can't have hard links, and they are added from the outer loop.
+ if (file_type == EXT2_FT_DIR)
+ return 0;
+
+ auto ino_file = state->inodes->find(dirent->inode);
+ if (ino_file == state->inodes->end())
+ return 0;
+ auto dir_file = state->inodes->find(dir);
+ if (dir_file == state->inodes->end())
+ return 0;
+ string basename(dirent->name, dirent->name_len & 0xff);
+ ino_file->second.name = dir_file->second.name;
+ if (dir_file->second.name != "/")
+ ino_file->second.name += "/";
+ ino_file->second.name += basename;
+
+ // Append this file to the output. If the file has a hard link, it will be
+ // added twice to the output, but with different names, which is ok. That will
+ // help identify all the versions of the same file.
+ state->files->push_back(ino_file->second);
+ state->used_inodes->insert(dirent->inode);
+ return 0;
+}
+
+} // namespace
+
+unique_ptr<Ext2Filesystem> Ext2Filesystem::CreateFromFile(
+ const string& filename) {
+ if (filename.empty())
+ return nullptr;
+ unique_ptr<Ext2Filesystem> result(new Ext2Filesystem());
+ result->filename_ = filename;
+
+ errcode_t err = ext2fs_open(filename.c_str(),
+ 0, // flags (read only)
+ 0, // superblock block number
+ 0, // block_size (autodetect)
+ unix_io_manager,
+ &result->filsys_);
+ if (err) {
+ LOG(ERROR) << "Opening ext2fs " << filename;
+ return nullptr;
+ }
+ return result;
+}
+
+Ext2Filesystem::~Ext2Filesystem() {
+ ext2fs_free(filsys_);
+}
+
+size_t Ext2Filesystem::GetBlockSize() const {
+ return filsys_->blocksize;
+}
+
+size_t Ext2Filesystem::GetBlockCount() const {
+ return ext2fs_blocks_count(filsys_->super);
+}
+
+bool Ext2Filesystem::GetFiles(vector<File>* files) const {
+ TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_read_inode_bitmap(filsys_));
+
+ ext2_inode_scan iscan;
+ TEST_AND_RETURN_FALSE_ERRCODE(
+ ext2fs_open_inode_scan(filsys_, 0 /* buffer_blocks */, &iscan));
+
+ std::map<ext2_ino_t, File> inodes;
+
+ // List of directories. We need to first parse all the files in a directory
+ // to later fix the absolute paths.
+ vector<ext2_ino_t> directories;
+
+ set<uint64_t> inode_blocks;
+
+ // Iterator
+ ext2_ino_t it_ino;
+ ext2_inode it_inode;
+
+ bool ok = true;
+ while (true) {
+ errcode_t error = ext2fs_get_next_inode(iscan, &it_ino, &it_inode);
+ if (error) {
+ LOG(ERROR) << "Failed to retrieve next inode (" << error << ")";
+ ok = false;
+ break;
+ }
+ if (it_ino == 0)
+ break;
+
+ // Skip inodes that are not in use.
+ if (!ext2fs_test_inode_bitmap(filsys_->inode_map, it_ino))
+ continue;
+
+ File& file = inodes[it_ino];
+ if (it_ino == EXT2_RESIZE_INO) {
+ file.name = "<group-descriptors>";
+ } else {
+ file.name = base::StringPrintf("<inode-%u>", it_ino);
+ }
+
+ memset(&file.file_stat, 0, sizeof(file.file_stat));
+ file.file_stat.st_ino = it_ino;
+ file.file_stat.st_mode = it_inode.i_mode;
+ file.file_stat.st_nlink = it_inode.i_links_count;
+ file.file_stat.st_uid = it_inode.i_uid;
+ file.file_stat.st_gid = it_inode.i_gid;
+ file.file_stat.st_size = it_inode.i_size;
+ file.file_stat.st_blksize = filsys_->blocksize;
+ file.file_stat.st_blocks = it_inode.i_blocks;
+ file.file_stat.st_atime = it_inode.i_atime;
+ file.file_stat.st_mtime = it_inode.i_mtime;
+ file.file_stat.st_ctime = it_inode.i_ctime;
+
+ bool is_dir = (ext2fs_check_directory(filsys_, it_ino) == 0);
+ if (is_dir)
+ directories.push_back(it_ino);
+
+ if (!ext2fs_inode_has_valid_blocks(&it_inode))
+ continue;
+
+ // Process the inode data and metadata blocks.
+ // For normal files, inode blocks are indirect, double indirect
+ // and triple indirect blocks (no data blocks). For directories and
+ // the journal, all blocks are considered metadata blocks.
+ int flags = it_ino < EXT2_GOOD_OLD_FIRST_INO ? 0 : BLOCK_FLAG_DATA_ONLY;
+ error = ext2fs_block_iterate2(filsys_, it_ino, flags,
+ nullptr, // block_buf
+ ProcessInodeAllBlocks,
+ &file.extents);
+
+ if (error) {
+ LOG(ERROR) << "Failed to enumerate inode " << it_ino
+ << " blocks (" << error << ")";
+ continue;
+ }
+ if (it_ino >= EXT2_GOOD_OLD_FIRST_INO) {
+ ext2fs_block_iterate2(filsys_, it_ino, 0, nullptr,
+ AddMetadataBlocks,
+ &inode_blocks);
+ }
+ }
+ ext2fs_close_inode_scan(iscan);
+ if (!ok)
+ return false;
+
+ // The set of inodes already added to the output. There can be less elements
+ // here than in files since the later can contain repeated inodes due to
+ // hardlink files.
+ set<ext2_ino_t> used_inodes;
+
+ UpdateFileAndAppendState priv_data;
+ priv_data.inodes = &inodes;
+ priv_data.used_inodes = &used_inodes;
+ priv_data.files = files;
+ priv_data.filsys = filsys_;
+
+ files->clear();
+ // Iterate over all the files of each directory to update the name and add it.
+ for (ext2_ino_t dir_ino : directories) {
+ char* dir_name = nullptr;
+ errcode_t error = ext2fs_get_pathname(filsys_, dir_ino, 0, &dir_name);
+ if (error) {
+ // Not being able to read a directory name is not a fatal error, it is
+ // just skiped.
+ LOG(WARNING) << "Reading directory name on inode " << dir_ino
+ << " (error " << error << ")";
+ inodes[dir_ino].name = base::StringPrintf("<dir-%u>", dir_ino);
+ } else {
+ inodes[dir_ino].name = dir_name;
+ files->push_back(inodes[dir_ino]);
+ used_inodes.insert(dir_ino);
+ }
+ ext2fs_free_mem(&dir_name);
+
+ error = ext2fs_dir_iterate2(
+ filsys_, dir_ino, 0, nullptr /* block_buf */,
+ UpdateFileAndAppend, &priv_data);
+ if (error) {
+ LOG(WARNING) << "Failed to enumerate files in directory "
+ << inodes[dir_ino].name << " (error " << error << ")";
+ }
+ }
+
+ // Add <inode-blocks> file with the blocks that hold inodes.
+ File inode_file;
+ inode_file.name = "<inode-blocks>";
+ for (uint64_t block : inode_blocks) {
+ AppendBlockToExtents(&inode_file.extents, block);
+ }
+ files->push_back(inode_file);
+
+ // Add <free-spacce> blocs.
+ errcode_t error = ext2fs_read_block_bitmap(filsys_);
+ if (error) {
+ LOG(ERROR) << "Reading the blocks bitmap (error " << error << ")";
+ } else {
+ File free_space;
+ free_space.name = "<free-space>";
+ blk64_t blk_start = ext2fs_get_block_bitmap_start2(filsys_->block_map);
+ blk64_t blk_end = ext2fs_get_block_bitmap_end2(filsys_->block_map);
+ for (blk64_t block = blk_start; block < blk_end; block++) {
+ if (!ext2fs_test_block_bitmap2(filsys_->block_map, block))
+ AppendBlockToExtents(&free_space.extents, block);
+ }
+ files->push_back(free_space);
+ }
+
+ // Add all the unreachable files plus the pseudo-files with an inode. Since
+ // these inodes aren't files in the filesystem, ignore the empty ones.
+ for (const auto& ino_file : inodes) {
+ if (used_inodes.find(ino_file.first) != used_inodes.end())
+ continue;
+ if (ino_file.second.extents.empty())
+ continue;
+
+ File file = ino_file.second;
+ ExtentRanges ranges;
+ ranges.AddExtents(file.extents);
+ file.extents = ranges.GetExtentsForBlockCount(ranges.blocks());
+
+ files->push_back(file);
+ }
+
+ return true;
+}
+
+bool Ext2Filesystem::LoadSettings(chromeos::KeyValueStore* store) const {
+ // First search for the settings inode following symlinks if we find some.
+ ext2_ino_t ino_num = 0;
+ errcode_t err = ext2fs_namei_follow(
+ filsys_, EXT2_ROOT_INO /* root */, EXT2_ROOT_INO /* cwd */,
+ "/etc/update_engine.conf", &ino_num);
+ if (err != 0)
+ return false;
+
+ ext2_inode ino_data;
+ if (ext2fs_read_inode(filsys_, ino_num, &ino_data) != 0)
+ return false;
+
+ // Load the list of blocks and then the contents of the inodes.
+ vector<Extent> extents;
+ err = ext2fs_block_iterate2(filsys_, ino_num, BLOCK_FLAG_DATA_ONLY,
+ nullptr, // block_buf
+ ProcessInodeAllBlocks,
+ &extents);
+ if (err != 0)
+ return false;
+
+ chromeos::Blob blob;
+ uint64_t physical_size = BlocksInExtents(extents) * filsys_->blocksize;
+ // Sparse holes in the settings file are not supported.
+ if (EXT2_I_SIZE(&ino_data) > physical_size)
+ return false;
+ if (!utils::ReadExtents(filename_, extents, &blob, physical_size,
+ filsys_->blocksize))
+ return false;
+
+ string text(blob.begin(), blob.begin() + EXT2_I_SIZE(&ino_data));
+ return store->LoadFromString(text);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/ext2_filesystem.h b/payload_generator/ext2_filesystem.h
new file mode 100644
index 0000000..66cb65f
--- /dev/null
+++ b/payload_generator/ext2_filesystem.h
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_EXT2_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXT2_FILESYSTEM_H_
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <ext2fs/ext2fs.h>
+
+namespace chromeos_update_engine {
+
+class Ext2Filesystem : public FilesystemInterface {
+ public:
+ // Creates an Ext2Filesystem from a ext2 formatted filesystem stored in a
+ // file. The file doesn't need to be loop-back mounted.
+ static std::unique_ptr<Ext2Filesystem> CreateFromFile(
+ const std::string& filename);
+ virtual ~Ext2Filesystem();
+
+ // FilesystemInterface overrides.
+ size_t GetBlockSize() const override;
+ size_t GetBlockCount() const override;
+
+ // GetFiles will return one FilesystemInterface::File for every file and every
+ // directory in the filesystem. Hard-linked files will appear in the list
+ // several times with the same list of blocks.
+ // On addition to actual files, it also returns these pseudo-files:
+ // <free-space>: With all the unallocated data-blocks.
+ // <inode-blocks>: Will all the data-blocks for second and third level inodes
+ // of all the files.
+ // <group-descriptors>: With the block group descriptor and their reserved
+ // space.
+ // <metadata>: With the rest of ext2 metadata blocks, such as superblocks
+ // and bitmap tables.
+ bool GetFiles(std::vector<File>* files) const override;
+
+ bool LoadSettings(chromeos::KeyValueStore* store) const override;
+
+ private:
+ Ext2Filesystem() = default;
+
+ // The ext2 main data structure holding the filesystem.
+ ext2_filsys filsys_ = nullptr;
+
+ // The file where the filesystem is stored.
+ std::string filename_;
+
+ DISALLOW_COPY_AND_ASSIGN(Ext2Filesystem);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXT2_FILESYSTEM_H_
diff --git a/payload_generator/ext2_filesystem_unittest.cc b/payload_generator/ext2_filesystem_unittest.cc
new file mode 100644
index 0000000..d2ea2b3
--- /dev/null
+++ b/payload_generator/ext2_filesystem_unittest.cc
@@ -0,0 +1,202 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/ext2_filesystem.h"
+
+#include <unistd.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos_update_engine::test_utils::System;
+using std::map;
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+uint64_t kDefaultFilesystemSize = 4 * 1024 * 1024;
+size_t kDefaultFilesystemBlockCount = 1024;
+size_t kDefaultFilesystemBlockSize = 4096;
+
+// Checks that all the blocks in |extents| are in the range [0, total_blocks).
+void ExpectBlocksInRange(const vector<Extent>& extents, uint64_t total_blocks) {
+ for (const Extent& extent : extents) {
+ EXPECT_LE(0, extent.start_block());
+ EXPECT_LE(extent.start_block() + extent.num_blocks(), total_blocks);
+ }
+}
+
+} // namespace
+
+
+class Ext2FilesystemTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(utils::MakeTempFile("Ext2FilesystemTest-XXXXXX",
+ &fs_filename_, nullptr));
+ ASSERT_EQ(0, truncate(fs_filename_.c_str(), kDefaultFilesystemSize));
+ }
+
+ void TearDown() override {
+ unlink(fs_filename_.c_str());
+ }
+
+ string fs_filename_;
+};
+
+TEST_F(Ext2FilesystemTest, InvalidFilesystem) {
+ unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(fs_filename_);
+ ASSERT_EQ(nullptr, fs.get());
+
+ fs = Ext2Filesystem::CreateFromFile("/path/to/invalid/file");
+ ASSERT_EQ(nullptr, fs.get());
+}
+
+TEST_F(Ext2FilesystemTest, EmptyFilesystem) {
+ EXPECT_EQ(0, System(base::StringPrintf(
+ "/sbin/mkfs.ext2 -q -b %" PRIuS " -F %s",
+ kDefaultFilesystemBlockSize, fs_filename_.c_str())));
+ unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(fs_filename_);
+
+ ASSERT_NE(nullptr, fs.get());
+ EXPECT_EQ(kDefaultFilesystemBlockCount, fs->GetBlockCount());
+ EXPECT_EQ(kDefaultFilesystemBlockSize, fs->GetBlockSize());
+
+ vector<FilesystemInterface::File> files;
+ EXPECT_TRUE(fs->GetFiles(&files));
+
+ map<string, FilesystemInterface::File> map_files;
+ for (const auto& file : files) {
+ EXPECT_EQ(map_files.end(), map_files.find(file.name))
+ << "File " << file.name << " repeated in the list.";
+ map_files[file.name] = file;
+ ExpectBlocksInRange(file.extents, fs->GetBlockCount());
+ }
+ EXPECT_EQ(2, map_files["/"].file_stat.st_ino);
+ EXPECT_FALSE(map_files["<free-space>"].extents.empty());
+}
+
+// This test parses the sample images generated during build time with the
+// "generate_image.sh" script. The expected conditions of each file in these
+// images is encoded in the file name, as defined in the mentioned script.
+TEST_F(Ext2FilesystemTest, ParseGeneratedImages) {
+ const vector<string> kGeneratedImages = {
+ "disk_ext2_1k.img",
+ "disk_ext2_4k.img" };
+ base::FilePath build_path = test_utils::GetBuildArtifactsPath().Append("gen");
+ for (const string& fs_name : kGeneratedImages) {
+ LOG(INFO) << "Testing " << fs_name;
+ unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(
+ build_path.Append(fs_name).value());
+ ASSERT_NE(nullptr, fs.get());
+
+ vector<FilesystemInterface::File> files;
+ map<string, FilesystemInterface::File> map_files;
+ set<string> filenames;
+ EXPECT_TRUE(fs->GetFiles(&files));
+ for (const auto& file : files) {
+ // Check no repeated files. We should parse hard-links with two different
+ // names.
+ EXPECT_EQ(map_files.end(), map_files.find(file.name))
+ << "File " << file.name << " repeated in the list.";
+ map_files[file.name] = file;
+ filenames.insert(file.name);
+ ExpectBlocksInRange(file.extents, fs->GetBlockCount());
+ }
+
+ // Check that all the files are parsed, and the /removed file should not
+ // be included in the list.
+ set<string> kExpectedFiles = {
+ "/",
+ "/dir1",
+ "/dir1/file",
+ "/dir1/dir2",
+ "/dir1/dir2/file",
+ "/dir1/dir2/dir1",
+ "/empty-file",
+ "/link-hard-regular-16k",
+ "/link-long_symlink",
+ "/link-short_symlink",
+ "/lost+found",
+ "/regular-small",
+ "/regular-16k",
+ "/regular-32k-zeros",
+ "/regular-with_net_cap",
+ "/sparse_empty-10k",
+ "/sparse_empty-2blocks",
+ "/sparse-10000blocks",
+ "/sparse-16k-last_block",
+ "/sparse-16k-first_block",
+ "/sparse-16k-holes",
+ "<inode-blocks>",
+ "<free-space>",
+ "<group-descriptors>",
+ };
+ EXPECT_EQ(kExpectedFiles, filenames);
+
+ FilesystemInterface::File file;
+
+ // Small symlinks don't actually have data blocks.
+ EXPECT_TRUE(map_files["/link-short_symlink"].extents.empty());
+ EXPECT_EQ(1, BlocksInExtents(map_files["/link-long_symlink"].extents));
+
+ // Hard-links report the same list of blocks.
+ EXPECT_EQ(map_files["/link-hard-regular-16k"].extents,
+ map_files["/regular-16k"].extents);
+ EXPECT_FALSE(map_files["/regular-16k"].extents.empty());
+
+ // The number of blocks in these files doesn't depend on the
+ // block size.
+ EXPECT_TRUE(map_files["/empty-file"].extents.empty());
+ EXPECT_EQ(1, BlocksInExtents(map_files["/regular-small"].extents));
+ EXPECT_EQ(1, BlocksInExtents(map_files["/regular-with_net_cap"].extents));
+ EXPECT_TRUE(map_files["/sparse_empty-10k"].extents.empty());
+ EXPECT_TRUE(map_files["/sparse_empty-2blocks"].extents.empty());
+ EXPECT_EQ(1, BlocksInExtents(map_files["/sparse-16k-last_block"].extents));
+ EXPECT_EQ(1, BlocksInExtents(map_files["/sparse-16k-first_block"].extents));
+ EXPECT_EQ(2, BlocksInExtents(map_files["/sparse-16k-holes"].extents));
+ }
+}
+
+TEST_F(Ext2FilesystemTest, LoadSettingsFailsTest) {
+ base::FilePath path = test_utils::GetBuildArtifactsPath().Append(
+ "gen/disk_ext2_1k.img");
+ unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(path.value());
+
+ chromeos::KeyValueStore store;
+ // disk_ext2_1k.img doesn't have the /etc/update_engine.conf file.
+ EXPECT_FALSE(fs->LoadSettings(&store));
+}
+
+TEST_F(Ext2FilesystemTest, LoadSettingsWorksTest) {
+ base::FilePath path = test_utils::GetBuildArtifactsPath().Append(
+ "gen/disk_ext2_ue_settings.img");
+ unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(path.value());
+
+ chromeos::KeyValueStore store;
+ EXPECT_TRUE(fs->LoadSettings(&store));
+ string minor_version;
+ EXPECT_TRUE(store.GetString("PAYLOAD_MINOR_VERSION", &minor_version));
+ EXPECT_EQ("1234", minor_version);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/extent_ranges.cc b/payload_generator/extent_ranges.cc
new file mode 100644
index 0000000..d86bcfc
--- /dev/null
+++ b/payload_generator/extent_ranges.cc
@@ -0,0 +1,277 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/extent_ranges.h"
+
+#include <algorithm>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "update_engine/payload_constants.h"
+
+using std::set;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+bool ExtentRanges::ExtentsOverlapOrTouch(const Extent& a, const Extent& b) {
+ if (a.start_block() == b.start_block())
+ return true;
+ if (a.start_block() == kSparseHole || b.start_block() == kSparseHole)
+ return false;
+ if (a.start_block() < b.start_block()) {
+ return a.start_block() + a.num_blocks() >= b.start_block();
+ } else {
+ return b.start_block() + b.num_blocks() >= a.start_block();
+ }
+}
+
+bool ExtentRanges::ExtentsOverlap(const Extent& a, const Extent& b) {
+ if (a.start_block() == b.start_block())
+ return true;
+ if (a.start_block() == kSparseHole || b.start_block() == kSparseHole)
+ return false;
+ if (a.start_block() < b.start_block()) {
+ return a.start_block() + a.num_blocks() > b.start_block();
+ } else {
+ return b.start_block() + b.num_blocks() > a.start_block();
+ }
+}
+
+void ExtentRanges::AddBlock(uint64_t block) {
+ AddExtent(ExtentForRange(block, 1));
+}
+
+void ExtentRanges::SubtractBlock(uint64_t block) {
+ SubtractExtent(ExtentForRange(block, 1));
+}
+
+namespace {
+
+Extent UnionOverlappingExtents(const Extent& first, const Extent& second) {
+ CHECK_NE(kSparseHole, first.start_block());
+ CHECK_NE(kSparseHole, second.start_block());
+ uint64_t start = std::min(first.start_block(), second.start_block());
+ uint64_t end = std::max(first.start_block() + first.num_blocks(),
+ second.start_block() + second.num_blocks());
+ return ExtentForRange(start, end - start);
+}
+
+} // namespace
+
+void ExtentRanges::AddExtent(Extent extent) {
+ if (extent.start_block() == kSparseHole || extent.num_blocks() == 0)
+ return;
+
+ ExtentSet::iterator begin_del = extent_set_.end();
+ ExtentSet::iterator end_del = extent_set_.end();
+ uint64_t del_blocks = 0;
+ for (ExtentSet::iterator it = extent_set_.begin(), e = extent_set_.end();
+ it != e; ++it) {
+ if (ExtentsOverlapOrTouch(*it, extent)) {
+ end_del = it;
+ ++end_del;
+ del_blocks += it->num_blocks();
+ if (begin_del == extent_set_.end())
+ begin_del = it;
+
+ extent = UnionOverlappingExtents(extent, *it);
+ }
+ }
+ extent_set_.erase(begin_del, end_del);
+ extent_set_.insert(extent);
+ blocks_ -= del_blocks;
+ blocks_ += extent.num_blocks();
+}
+
+namespace {
+// Returns base - subtractee (set subtraction).
+ExtentRanges::ExtentSet SubtractOverlappingExtents(const Extent& base,
+ const Extent& subtractee) {
+ ExtentRanges::ExtentSet ret;
+ if (subtractee.start_block() > base.start_block()) {
+ ret.insert(ExtentForRange(base.start_block(),
+ subtractee.start_block() - base.start_block()));
+ }
+ uint64_t base_end = base.start_block() + base.num_blocks();
+ uint64_t subtractee_end = subtractee.start_block() + subtractee.num_blocks();
+ if (base_end > subtractee_end) {
+ ret.insert(ExtentForRange(subtractee_end, base_end - subtractee_end));
+ }
+ return ret;
+}
+} // namespace
+
+void ExtentRanges::SubtractExtent(const Extent& extent) {
+ if (extent.start_block() == kSparseHole || extent.num_blocks() == 0)
+ return;
+
+ ExtentSet::iterator begin_del = extent_set_.end();
+ ExtentSet::iterator end_del = extent_set_.end();
+ uint64_t del_blocks = 0;
+ ExtentSet new_extents;
+ for (ExtentSet::iterator it = extent_set_.begin(), e = extent_set_.end();
+ it != e; ++it) {
+ if (!ExtentsOverlap(*it, extent))
+ continue;
+
+ if (begin_del == extent_set_.end())
+ begin_del = it;
+ end_del = it;
+ ++end_del;
+
+ del_blocks += it->num_blocks();
+
+ ExtentSet subtraction = SubtractOverlappingExtents(*it, extent);
+ for (ExtentSet::iterator jt = subtraction.begin(), je = subtraction.end();
+ jt != je; ++jt) {
+ new_extents.insert(*jt);
+ del_blocks -= jt->num_blocks();
+ }
+ }
+ extent_set_.erase(begin_del, end_del);
+ extent_set_.insert(new_extents.begin(), new_extents.end());
+ blocks_ -= del_blocks;
+}
+
+void ExtentRanges::AddRanges(const ExtentRanges& ranges) {
+ for (ExtentSet::const_iterator it = ranges.extent_set_.begin(),
+ e = ranges.extent_set_.end(); it != e; ++it) {
+ AddExtent(*it);
+ }
+}
+
+void ExtentRanges::SubtractRanges(const ExtentRanges& ranges) {
+ for (ExtentSet::const_iterator it = ranges.extent_set_.begin(),
+ e = ranges.extent_set_.end(); it != e; ++it) {
+ SubtractExtent(*it);
+ }
+}
+
+void ExtentRanges::AddExtents(const vector<Extent>& extents) {
+ for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end();
+ it != e; ++it) {
+ AddExtent(*it);
+ }
+}
+
+void ExtentRanges::SubtractExtents(const vector<Extent>& extents) {
+ for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end();
+ it != e; ++it) {
+ SubtractExtent(*it);
+ }
+}
+
+void ExtentRanges::AddRepeatedExtents(
+ const ::google::protobuf::RepeatedPtrField<Extent> &exts) {
+ for (int i = 0, e = exts.size(); i != e; ++i) {
+ AddExtent(exts.Get(i));
+ }
+}
+
+void ExtentRanges::SubtractRepeatedExtents(
+ const ::google::protobuf::RepeatedPtrField<Extent> &exts) {
+ for (int i = 0, e = exts.size(); i != e; ++i) {
+ SubtractExtent(exts.Get(i));
+ }
+}
+
+void ExtentRanges::Dump() const {
+ LOG(INFO) << "ExtentRanges Dump. blocks: " << blocks_;
+ for (ExtentSet::const_iterator it = extent_set_.begin(),
+ e = extent_set_.end();
+ it != e; ++it) {
+ LOG(INFO) << "{" << it->start_block() << ", " << it->num_blocks() << "}";
+ }
+}
+
+Extent ExtentForRange(uint64_t start_block, uint64_t num_blocks) {
+ Extent ret;
+ ret.set_start_block(start_block);
+ ret.set_num_blocks(num_blocks);
+ return ret;
+}
+
+vector<Extent> ExtentRanges::GetExtentsForBlockCount(
+ uint64_t count) const {
+ vector<Extent> out;
+ if (count == 0)
+ return out;
+ uint64_t out_blocks = 0;
+ CHECK(count <= blocks_);
+ for (ExtentSet::const_iterator it = extent_set_.begin(),
+ e = extent_set_.end();
+ it != e; ++it) {
+ const uint64_t blocks_needed = count - out_blocks;
+ const Extent& extent = *it;
+ out.push_back(extent);
+ out_blocks += extent.num_blocks();
+ if (extent.num_blocks() < blocks_needed)
+ continue;
+ if (extent.num_blocks() == blocks_needed)
+ break;
+ // If we get here, we just added the last extent needed, but it's too big
+ out_blocks -= extent.num_blocks();
+ out_blocks += blocks_needed;
+ out.back().set_num_blocks(blocks_needed);
+ break;
+ }
+ return out;
+}
+
+vector<Extent> FilterExtentRanges(const std::vector<Extent>& extents,
+ const ExtentRanges& ranges) {
+ vector<Extent> result;
+ const ExtentRanges::ExtentSet& extent_set = ranges.extent_set();
+ for (Extent extent : extents) {
+ // The extents are sorted by the start_block. We want to iterate all the
+ // Extents in the ExtentSet possibly overlapping the current |extent|. This
+ // is achieved by looking from the extent whose start_block is *lower* than
+ // the extent.start_block() up to the greatest extent whose start_block is
+ // lower than extent.start_block() + extent.num_blocks().
+ auto lower = extent_set.lower_bound(extent);
+ // We need to decrement the lower_bound to look at the extent that could
+ // overlap the beginning of the current |extent|.
+ if (lower != extent_set.begin())
+ lower--;
+ auto upper = extent_set.lower_bound(
+ ExtentForRange(extent.start_block() + extent.num_blocks(), 0));
+ for (auto iter = lower; iter != upper; ++iter) {
+ if (!ExtentRanges::ExtentsOverlap(extent, *iter))
+ continue;
+ if (iter->start_block() <= extent.start_block()) {
+ // We need to cut blocks from the beginning of the |extent|.
+ uint64_t cut_blocks = iter->start_block() + iter->num_blocks() -
+ extent.start_block();
+ if (cut_blocks >= extent.num_blocks()) {
+ extent.set_num_blocks(0);
+ break;
+ }
+ extent = ExtentForRange(extent.start_block() + cut_blocks,
+ extent.num_blocks() - cut_blocks);
+ } else {
+ // We need to cut blocks on the middle of the extent, possible up to the
+ // end of it.
+ result.push_back(
+ ExtentForRange(extent.start_block(),
+ iter->start_block() - extent.start_block()));
+ uint64_t new_start = iter->start_block() + iter->num_blocks();
+ uint64_t old_end = extent.start_block() + extent.num_blocks();
+ if (new_start >= old_end) {
+ extent.set_num_blocks(0);
+ break;
+ }
+ extent = ExtentForRange(new_start, old_end - new_start);
+ }
+ }
+ if (extent.num_blocks() > 0)
+ result.push_back(extent);
+ }
+ return result;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/extent_ranges.h b/payload_generator/extent_ranges.h
new file mode 100644
index 0000000..fc15149
--- /dev/null
+++ b/payload_generator/extent_ranges.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_RANGES_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_RANGES_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// An ExtentRanges object represents an unordered collection of extents (and
+// therefore blocks). Such an object may be modified by adding or subtracting
+// blocks (think: set addition or set subtraction). Note that ExtentRanges
+// ignores sparse hole extents mostly to avoid confusion between extending a
+// sparse hole range vs. set addition but also to ensure that the delta
+// generator doesn't use sparse holes as scratch space.
+
+namespace chromeos_update_engine {
+
+struct ExtentLess {
+ bool operator()(const Extent& x, const Extent& y) const {
+ return x.start_block() < y.start_block();
+ }
+};
+
+Extent ExtentForRange(uint64_t start_block, uint64_t num_blocks);
+
+class ExtentRanges {
+ public:
+ typedef std::set<Extent, ExtentLess> ExtentSet;
+
+ ExtentRanges() : blocks_(0) {}
+ void AddBlock(uint64_t block);
+ void SubtractBlock(uint64_t block);
+ void AddExtent(Extent extent);
+ void SubtractExtent(const Extent& extent);
+ void AddExtents(const std::vector<Extent>& extents);
+ void SubtractExtents(const std::vector<Extent>& extents);
+ void AddRepeatedExtents(
+ const ::google::protobuf::RepeatedPtrField<Extent> &exts);
+ void SubtractRepeatedExtents(
+ const ::google::protobuf::RepeatedPtrField<Extent> &exts);
+ void AddRanges(const ExtentRanges& ranges);
+ void SubtractRanges(const ExtentRanges& ranges);
+
+ static bool ExtentsOverlapOrTouch(const Extent& a, const Extent& b);
+ static bool ExtentsOverlap(const Extent& a, const Extent& b);
+
+ // Dumps contents to the log file. Useful for debugging.
+ void Dump() const;
+
+ uint64_t blocks() const { return blocks_; }
+ const ExtentSet& extent_set() const { return extent_set_; }
+
+ // Returns an ordered vector of extents for |count| blocks,
+ // using extents in extent_set_. The returned extents are not
+ // removed from extent_set_. |count| must be less than or equal to
+ // the number of blocks in this extent set.
+ std::vector<Extent> GetExtentsForBlockCount(uint64_t count) const;
+
+ private:
+ ExtentSet extent_set_;
+ uint64_t blocks_;
+};
+
+// Filters out from the passed list of extents |extents| all the blocks in the
+// ExtentRanges set. Note that the order of the blocks in |extents| is preserved
+// omitting blocks present in the ExtentRanges |ranges|.
+std::vector<Extent> FilterExtentRanges(const std::vector<Extent>& extents,
+ const ExtentRanges& ranges);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_RANGES_H_
diff --git a/payload_generator/extent_ranges_unittest.cc b/payload_generator/extent_ranges_unittest.cc
new file mode 100644
index 0000000..b7036a5
--- /dev/null
+++ b/payload_generator/extent_ranges_unittest.cc
@@ -0,0 +1,314 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/extent_ranges.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/test_utils.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class ExtentRangesTest : public ::testing::Test {};
+
+namespace {
+void ExpectRangeEq(const ExtentRanges& ranges,
+ const uint64_t* expected,
+ size_t sz,
+ int line) {
+ uint64_t blocks = 0;
+ for (size_t i = 1; i < sz; i += 2) {
+ blocks += expected[i];
+ }
+ EXPECT_EQ(blocks, ranges.blocks()) << "line: " << line;
+
+ const ExtentRanges::ExtentSet& result = ranges.extent_set();
+ ExtentRanges::ExtentSet::const_iterator it = result.begin();
+ for (size_t i = 0; i < sz; i += 2) {
+ EXPECT_FALSE(it == result.end()) << "line: " << line;
+ EXPECT_EQ(expected[i], it->start_block()) << "line: " << line;
+ EXPECT_EQ(expected[i + 1], it->num_blocks()) << "line: " << line;
+ ++it;
+ }
+}
+
+#define EXPECT_RANGE_EQ(ranges, var) \
+ do { \
+ ExpectRangeEq(ranges, var, arraysize(var), __LINE__); \
+ } while (0)
+
+void ExpectRangesOverlapOrTouch(uint64_t a_start, uint64_t a_num,
+ uint64_t b_start, uint64_t b_num) {
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+}
+
+void ExpectFalseRangesOverlapOrTouch(uint64_t a_start, uint64_t a_num,
+ uint64_t b_start, uint64_t b_num) {
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+}
+
+void ExpectRangesOverlap(uint64_t a_start, uint64_t a_num,
+ uint64_t b_start, uint64_t b_num) {
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+}
+
+void ExpectFalseRangesOverlap(uint64_t a_start, uint64_t a_num,
+ uint64_t b_start, uint64_t b_num) {
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+}
+
+} // namespace
+
+TEST(ExtentRangesTest, ExtentsOverlapTest) {
+ ExpectRangesOverlapOrTouch(10, 20, 30, 10);
+ ExpectRangesOverlap(10, 20, 25, 10);
+ ExpectFalseRangesOverlapOrTouch(10, 20, 35, 10);
+ ExpectFalseRangesOverlap(10, 20, 30, 10);
+ ExpectRangesOverlap(12, 4, 12, 3);
+
+ ExpectRangesOverlapOrTouch(kSparseHole, 2, kSparseHole, 3);
+ ExpectRangesOverlap(kSparseHole, 2, kSparseHole, 3);
+ ExpectFalseRangesOverlapOrTouch(kSparseHole, 2, 10, 3);
+ ExpectFalseRangesOverlapOrTouch(10, 2, kSparseHole, 3);
+ ExpectFalseRangesOverlap(kSparseHole, 2, 10, 3);
+ ExpectFalseRangesOverlap(10, 2, kSparseHole, 3);
+}
+
+TEST(ExtentRangesTest, SimpleTest) {
+ ExtentRanges ranges;
+ {
+ static const uint64_t expected[] = {};
+ // Can't use arraysize() on 0-length arrays:
+ ExpectRangeEq(ranges, expected, 0, __LINE__);
+ }
+ ranges.SubtractBlock(2);
+ {
+ static const uint64_t expected[] = {};
+ // Can't use arraysize() on 0-length arrays:
+ ExpectRangeEq(ranges, expected, 0, __LINE__);
+ }
+
+ ranges.AddBlock(0);
+ ranges.Dump();
+ ranges.AddBlock(1);
+ ranges.AddBlock(3);
+
+ {
+ static const uint64_t expected[] = {0, 2, 3, 1};
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+ ranges.AddBlock(2);
+ {
+ static const uint64_t expected[] = {0, 4};
+ EXPECT_RANGE_EQ(ranges, expected);
+ ranges.AddBlock(kSparseHole);
+ EXPECT_RANGE_EQ(ranges, expected);
+ ranges.SubtractBlock(kSparseHole);
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+ ranges.SubtractBlock(2);
+ {
+ static const uint64_t expected[] = {0, 2, 3, 1};
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+
+ for (uint64_t i = 100; i < 1000; i += 100) {
+ ranges.AddExtent(ExtentForRange(i, 50));
+ }
+ {
+ static const uint64_t expected[] = {
+ 0, 2, 3, 1, 100, 50, 200, 50, 300, 50, 400, 50,
+ 500, 50, 600, 50, 700, 50, 800, 50, 900, 50
+ };
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+
+ ranges.SubtractExtent(ExtentForRange(210, 410 - 210));
+ {
+ static const uint64_t expected[] = {
+ 0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+ 600, 50, 700, 50, 800, 50, 900, 50
+ };
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+ ranges.AddExtent(ExtentForRange(100000, 0));
+ {
+ static const uint64_t expected[] = {
+ 0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+ 600, 50, 700, 50, 800, 50, 900, 50
+ };
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+ ranges.SubtractExtent(ExtentForRange(3, 0));
+ {
+ static const uint64_t expected[] = {
+ 0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+ 600, 50, 700, 50, 800, 50, 900, 50
+ };
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+}
+
+TEST(ExtentRangesTest, MultipleRanges) {
+ ExtentRanges ranges_a, ranges_b;
+ ranges_a.AddBlock(0);
+ ranges_b.AddBlock(4);
+ ranges_b.AddBlock(3);
+ {
+ uint64_t expected[] = {3, 2};
+ EXPECT_RANGE_EQ(ranges_b, expected);
+ }
+ ranges_a.AddRanges(ranges_b);
+ {
+ uint64_t expected[] = {0, 1, 3, 2};
+ EXPECT_RANGE_EQ(ranges_a, expected);
+ }
+ ranges_a.SubtractRanges(ranges_b);
+ {
+ uint64_t expected[] = {0, 1};
+ EXPECT_RANGE_EQ(ranges_a, expected);
+ }
+ {
+ uint64_t expected[] = {3, 2};
+ EXPECT_RANGE_EQ(ranges_b, expected);
+ }
+}
+
+TEST(ExtentRangesTest, GetExtentsForBlockCountTest) {
+ ExtentRanges ranges;
+ ranges.AddExtents(vector<Extent>(1, ExtentForRange(10, 30)));
+ {
+ vector<Extent> zero_extents = ranges.GetExtentsForBlockCount(0);
+ EXPECT_TRUE(zero_extents.empty());
+ }
+ ::google::protobuf::RepeatedPtrField<Extent> rep_field;
+ *rep_field.Add() = ExtentForRange(30, 40);
+ ranges.AddRepeatedExtents(rep_field);
+ ranges.SubtractExtents(vector<Extent>(1, ExtentForRange(20, 10)));
+ *rep_field.Mutable(0) = ExtentForRange(50, 10);
+ ranges.SubtractRepeatedExtents(rep_field);
+ EXPECT_EQ(40, ranges.blocks());
+
+ for (int i = 0; i < 2; i++) {
+ vector<Extent> expected(2);
+ expected[0] = ExtentForRange(10, 10);
+ expected[1] = ExtentForRange(30, i == 0 ? 10 : 20);
+ vector<Extent> actual =
+ ranges.GetExtentsForBlockCount(10 + expected[1].num_blocks());
+ EXPECT_EQ(expected.size(), actual.size());
+ for (vector<Extent>::size_type j = 0, e = expected.size(); j != e; ++j) {
+ EXPECT_EQ(expected[j].start_block(), actual[j].start_block())
+ << "j = " << j;
+ EXPECT_EQ(expected[j].num_blocks(), actual[j].num_blocks())
+ << "j = " << j;
+ }
+ }
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesEmptyRanges) {
+ ExtentRanges ranges;
+ EXPECT_EQ(vector<Extent>(),
+ FilterExtentRanges(vector<Extent>(), ranges));
+ EXPECT_EQ(
+ vector<Extent>{ ExtentForRange(50, 10) },
+ FilterExtentRanges(vector<Extent>{ ExtentForRange(50, 10) }, ranges));
+ // Check that the empty Extents are ignored.
+ EXPECT_EQ(
+ (vector<Extent>{ ExtentForRange(10, 10), ExtentForRange(20, 10) }),
+ FilterExtentRanges(vector<Extent>{
+ ExtentForRange(10, 10),
+ ExtentForRange(3, 0),
+ ExtentForRange(20, 10) }, ranges));
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesMultipleRanges) {
+ // Two overlaping extents, with three ranges to remove.
+ vector<Extent> extents {
+ ExtentForRange(10, 100),
+ ExtentForRange(30, 100) };
+ ExtentRanges ranges;
+ // This overlaps the beginning of the second extent.
+ ranges.AddExtent(ExtentForRange(28, 3));
+ ranges.AddExtent(ExtentForRange(50, 10));
+ ranges.AddExtent(ExtentForRange(70, 10));
+ // This overlaps the end of the second extent.
+ ranges.AddExtent(ExtentForRange(108, 6));
+ EXPECT_EQ(
+ (vector<Extent>{
+ // For the first extent:
+ ExtentForRange(10, 18),
+ ExtentForRange(31, 19),
+ ExtentForRange(60, 10),
+ ExtentForRange(80, 28),
+ // For the second extent:
+ ExtentForRange(31, 19),
+ ExtentForRange(60, 10),
+ ExtentForRange(80, 28),
+ ExtentForRange(114, 16)}),
+ FilterExtentRanges(extents, ranges));
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesOvelapping) {
+ ExtentRanges ranges;
+ ranges.AddExtent(ExtentForRange(10, 3));
+ ranges.AddExtent(ExtentForRange(20, 5));
+ // Requested extent overlaps with one of the ranges.
+ EXPECT_EQ(vector<Extent>(),
+ FilterExtentRanges(vector<Extent>{
+ ExtentForRange(10, 1),
+ ExtentForRange(22, 1) },
+ ranges));
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/extent_utils.cc b/payload_generator/extent_utils.cc
new file mode 100644
index 0000000..caacb38
--- /dev/null
+++ b/payload_generator/extent_utils.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/extent_utils.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+void AppendBlockToExtents(vector<Extent>* extents, uint64_t block) {
+ // First try to extend the last extent in |extents|, if any.
+ if (!extents->empty()) {
+ Extent& extent = extents->back();
+ uint64_t next_block = extent.start_block() == kSparseHole ?
+ kSparseHole : extent.start_block() + extent.num_blocks();
+ if (next_block == block) {
+ extent.set_num_blocks(extent.num_blocks() + 1);
+ return;
+ }
+ }
+ // If unable to extend the last extent, append a new single-block extent.
+ Extent new_extent;
+ new_extent.set_start_block(block);
+ new_extent.set_num_blocks(1);
+ extents->push_back(new_extent);
+}
+
+Extent GetElement(const vector<Extent>& collection, size_t index) {
+ return collection[index];
+}
+
+Extent GetElement(
+ const google::protobuf::RepeatedPtrField<Extent>& collection,
+ size_t index) {
+ return collection.Get(index);
+}
+
+void ExtendExtents(
+ google::protobuf::RepeatedPtrField<Extent>* extents,
+ const google::protobuf::RepeatedPtrField<Extent>& extents_to_add) {
+ vector<Extent> extents_vector;
+ vector<Extent> extents_to_add_vector;
+ ExtentsToVector(*extents, &extents_vector);
+ ExtentsToVector(extents_to_add, &extents_to_add_vector);
+ extents_vector.insert(extents_vector.end(),
+ extents_to_add_vector.begin(),
+ extents_to_add_vector.end());
+ NormalizeExtents(&extents_vector);
+ extents->Clear();
+ StoreExtents(extents_vector, extents);
+}
+
+// Stores all Extents in 'extents' into 'out'.
+void StoreExtents(const vector<Extent>& extents,
+ google::protobuf::RepeatedPtrField<Extent>* out) {
+ for (const Extent& extent : extents) {
+ Extent* new_extent = out->Add();
+ *new_extent = extent;
+ }
+}
+
+// Stores all extents in |extents| into |out_vector|.
+void ExtentsToVector(const google::protobuf::RepeatedPtrField<Extent>& extents,
+ vector<Extent>* out_vector) {
+ out_vector->clear();
+ for (int i = 0; i < extents.size(); i++) {
+ out_vector->push_back(extents.Get(i));
+ }
+}
+
+void NormalizeExtents(vector<Extent>* extents) {
+ vector<Extent> new_extents;
+ for (const Extent& curr_ext : *extents) {
+ if (new_extents.empty()) {
+ new_extents.push_back(curr_ext);
+ continue;
+ }
+ Extent& last_ext = new_extents.back();
+ if (last_ext.start_block() + last_ext.num_blocks() ==
+ curr_ext.start_block()) {
+ // If the extents are touching, we want to combine them.
+ last_ext.set_num_blocks(last_ext.num_blocks() + curr_ext.num_blocks());
+ } else {
+ // Otherwise just include the extent as is.
+ new_extents.push_back(curr_ext);
+ }
+ }
+ *extents = new_extents;
+}
+
+vector<Extent> ExtentsSublist(const vector<Extent>& extents,
+ uint64_t block_offset, uint64_t block_count) {
+ vector<Extent> result;
+ uint64_t scanned_blocks = 0;
+ if (block_count == 0)
+ return result;
+ uint64_t end_block_offset = block_offset + block_count;
+ for (const Extent& extent : extents) {
+ // The loop invariant is that if |extents| has enough blocks, there's
+ // still some extent to add to |result|. This implies that at the beginning
+ // of the loop scanned_blocks < block_offset + block_count.
+ if (scanned_blocks + extent.num_blocks() > block_offset) {
+ // This case implies that |extent| has some overlapping with the requested
+ // subsequence.
+ uint64_t new_start = extent.start_block();
+ uint64_t new_num_blocks = extent.num_blocks();
+ if (scanned_blocks + new_num_blocks > end_block_offset) {
+ // Cut the end part of the extent.
+ new_num_blocks = end_block_offset - scanned_blocks;
+ }
+ if (block_offset > scanned_blocks) {
+ // Cut the begin part of the extent.
+ new_num_blocks -= block_offset - scanned_blocks;
+ new_start += block_offset - scanned_blocks;
+ }
+ result.push_back(ExtentForRange(new_start, new_num_blocks));
+ }
+ scanned_blocks += extent.num_blocks();
+ if (scanned_blocks >= end_block_offset)
+ break;
+ }
+ return result;
+}
+
+bool operator==(const Extent& a, const Extent& b) {
+ return a.start_block() == b.start_block() && a.num_blocks() == b.num_blocks();
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/extent_utils.h b/payload_generator/extent_utils.h
new file mode 100644
index 0000000..eeeed40
--- /dev/null
+++ b/payload_generator/extent_utils.h
@@ -0,0 +1,89 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_
+
+#include <vector>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/update_metadata.pb.h"
+
+// Utility functions for manipulating Extents and lists of blocks.
+
+namespace chromeos_update_engine {
+
+// |block| must either be the next block in the last extent or a block
+// in the next extent. This function will not handle inserting block
+// into an arbitrary place in the extents.
+void AppendBlockToExtents(std::vector<Extent>* extents, uint64_t block);
+
+// Get/SetElement are intentionally overloaded so that templated functions
+// can accept either type of collection of Extents.
+Extent GetElement(const std::vector<Extent>& collection, size_t index);
+Extent GetElement(
+ const google::protobuf::RepeatedPtrField<Extent>& collection,
+ size_t index);
+
+// Return the total number of blocks in a collection (vector or
+// RepeatedPtrField) of Extents.
+template<typename T>
+uint64_t BlocksInExtents(const T& collection) {
+ uint64_t ret = 0;
+ for (size_t i = 0; i < static_cast<size_t>(collection.size()); ++i) {
+ ret += GetElement(collection, i).num_blocks();
+ }
+ return ret;
+}
+
+// Takes a collection (vector or RepeatedPtrField) of Extent and
+// returns a vector of the blocks referenced, in order.
+template<typename T>
+std::vector<uint64_t> ExpandExtents(const T& extents) {
+ std::vector<uint64_t> ret;
+ for (size_t i = 0, e = static_cast<size_t>(extents.size()); i != e; ++i) {
+ const Extent extent = GetElement(extents, i);
+ if (extent.start_block() == kSparseHole) {
+ ret.resize(ret.size() + extent.num_blocks(), kSparseHole);
+ } else {
+ for (uint64_t block = extent.start_block();
+ block < (extent.start_block() + extent.num_blocks()); block++) {
+ ret.push_back(block);
+ }
+ }
+ }
+ return ret;
+}
+
+// Stores all Extents in 'extents' into 'out'.
+void StoreExtents(const std::vector<Extent>& extents,
+ google::protobuf::RepeatedPtrField<Extent>* out);
+
+// Stores all extents in |extents| into |out_vector|.
+void ExtentsToVector(const google::protobuf::RepeatedPtrField<Extent>& extents,
+ std::vector<Extent>* out_vector);
+
+// Takes a pointer to extents |extents| and extents |extents_to_add|, and
+// merges them by adding |extents_to_add| to |extents| and normalizing.
+void ExtendExtents(
+ google::protobuf::RepeatedPtrField<Extent>* extents,
+ const google::protobuf::RepeatedPtrField<Extent>& extents_to_add);
+
+// Takes a vector of extents and normalizes those extents. Expects the extents
+// to be sorted by start block. E.g. if |extents| is [(1, 2), (3, 5), (10, 2)]
+// then |extents| will be changed to [(1, 7), (10, 2)].
+void NormalizeExtents(std::vector<Extent>* extents);
+
+// Return a subsequence of the list of blocks passed. Both the passed list of
+// blocks |extents| and the return value are expressed as a list of Extent, not
+// blocks. The returned list skips the first |block_offset| blocks from the
+// |extents| and cotains |block_count| blocks (or less if |extents| is shorter).
+std::vector<Extent> ExtentsSublist(const std::vector<Extent>& extents,
+ uint64_t block_offset, uint64_t block_count);
+
+bool operator==(const Extent& a, const Extent& b);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_
diff --git a/payload_generator/extent_utils_unittest.cc b/payload_generator/extent_utils_unittest.cc
new file mode 100644
index 0000000..188164d
--- /dev/null
+++ b/payload_generator/extent_utils_unittest.cc
@@ -0,0 +1,152 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/extent_utils.h"
+
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/test_utils.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class ExtentUtilsTest : public ::testing::Test {};
+
+TEST(ExtentUtilsTest, AppendSparseToExtentsTest) {
+ vector<Extent> extents;
+
+ EXPECT_EQ(0, extents.size());
+ AppendBlockToExtents(&extents, kSparseHole);
+ EXPECT_EQ(1, extents.size());
+ AppendBlockToExtents(&extents, 0);
+ EXPECT_EQ(2, extents.size());
+ AppendBlockToExtents(&extents, kSparseHole);
+ AppendBlockToExtents(&extents, kSparseHole);
+
+ ASSERT_EQ(3, extents.size());
+ EXPECT_EQ(kSparseHole, extents[0].start_block());
+ EXPECT_EQ(1, extents[0].num_blocks());
+ EXPECT_EQ(0, extents[1].start_block());
+ EXPECT_EQ(1, extents[1].num_blocks());
+ EXPECT_EQ(kSparseHole, extents[2].start_block());
+ EXPECT_EQ(2, extents[2].num_blocks());
+}
+
+TEST(ExtentUtilsTest, BlocksInExtentsTest) {
+ {
+ vector<Extent> extents;
+ EXPECT_EQ(0, BlocksInExtents(extents));
+ extents.push_back(ExtentForRange(0, 1));
+ EXPECT_EQ(1, BlocksInExtents(extents));
+ extents.push_back(ExtentForRange(23, 55));
+ EXPECT_EQ(56, BlocksInExtents(extents));
+ extents.push_back(ExtentForRange(1, 2));
+ EXPECT_EQ(58, BlocksInExtents(extents));
+ }
+ {
+ google::protobuf::RepeatedPtrField<Extent> extents;
+ EXPECT_EQ(0, BlocksInExtents(extents));
+ *extents.Add() = ExtentForRange(0, 1);
+ EXPECT_EQ(1, BlocksInExtents(extents));
+ *extents.Add() = ExtentForRange(23, 55);
+ EXPECT_EQ(56, BlocksInExtents(extents));
+ *extents.Add() = ExtentForRange(1, 2);
+ EXPECT_EQ(58, BlocksInExtents(extents));
+ }
+}
+
+TEST(ExtentUtilsTest, ExtendExtentsTest) {
+ DeltaArchiveManifest_InstallOperation first_op;
+ *(first_op.add_src_extents()) = ExtentForRange(1, 1);
+ *(first_op.add_src_extents()) = ExtentForRange(3, 1);
+
+ DeltaArchiveManifest_InstallOperation second_op;
+ *(second_op.add_src_extents()) = ExtentForRange(4, 2);
+ *(second_op.add_src_extents()) = ExtentForRange(8, 2);
+
+ ExtendExtents(first_op.mutable_src_extents(), second_op.src_extents());
+ vector<Extent> first_op_vec;
+ ExtentsToVector(first_op.src_extents(), &first_op_vec);
+ EXPECT_EQ((vector<Extent>{
+ ExtentForRange(1, 1),
+ ExtentForRange(3, 3),
+ ExtentForRange(8, 2)}), first_op_vec);
+}
+
+TEST(ExtentUtilsTest, NormalizeExtentsSimpleList) {
+ // Make sure it works when there's just one extent.
+ vector<Extent> extents;
+ NormalizeExtents(&extents);
+ EXPECT_EQ(0, extents.size());
+
+ extents = { ExtentForRange(0, 3) };
+ NormalizeExtents(&extents);
+ EXPECT_EQ(1, extents.size());
+ EXPECT_EQ(ExtentForRange(0, 3), extents[0]);
+}
+
+TEST(ExtentUtilsTest, NormalizeExtentsTest) {
+ vector<Extent> extents = {
+ ExtentForRange(0, 3),
+ ExtentForRange(3, 2),
+ ExtentForRange(5, 1),
+ ExtentForRange(8, 4),
+ ExtentForRange(13, 1),
+ ExtentForRange(14, 2)
+ };
+ NormalizeExtents(&extents);
+ EXPECT_EQ(3, extents.size());
+ EXPECT_EQ(ExtentForRange(0, 6), extents[0]);
+ EXPECT_EQ(ExtentForRange(8, 4), extents[1]);
+ EXPECT_EQ(ExtentForRange(13, 3), extents[2]);
+}
+
+TEST(ExtentUtilsTest, ExtentsSublistTest) {
+ vector<Extent> extents = {
+ ExtentForRange(10, 10),
+ ExtentForRange(30, 10),
+ ExtentForRange(50, 10)
+ };
+
+ // Simple empty result cases.
+ EXPECT_EQ(vector<Extent>(),
+ ExtentsSublist(extents, 1000, 20));
+ EXPECT_EQ(vector<Extent>(),
+ ExtentsSublist(extents, 5, 0));
+ EXPECT_EQ(vector<Extent>(),
+ ExtentsSublist(extents, 30, 1));
+
+ // Normal test cases.
+ EXPECT_EQ(vector<Extent>{ ExtentForRange(13, 2) },
+ ExtentsSublist(extents, 3, 2));
+ EXPECT_EQ(vector<Extent>{ ExtentForRange(15, 5) },
+ ExtentsSublist(extents, 5, 5));
+ EXPECT_EQ((vector<Extent>{ ExtentForRange(15, 5), ExtentForRange(30, 5) }),
+ ExtentsSublist(extents, 5, 10));
+ EXPECT_EQ((vector<Extent>{
+ ExtentForRange(13, 7),
+ ExtentForRange(30, 10),
+ ExtentForRange(50, 3), }),
+ ExtentsSublist(extents, 3, 20));
+
+ // Extact match case.
+ EXPECT_EQ(vector<Extent>{ ExtentForRange(30, 10) },
+ ExtentsSublist(extents, 10, 10));
+ EXPECT_EQ(vector<Extent>{ ExtentForRange(50, 10) },
+ ExtentsSublist(extents, 20, 10));
+
+ // Cases where the requested num_blocks is too big.
+ EXPECT_EQ(vector<Extent>{ ExtentForRange(53, 7) },
+ ExtentsSublist(extents, 23, 100));
+ EXPECT_EQ((vector<Extent>{ ExtentForRange(34, 6), ExtentForRange(50, 10) }),
+ ExtentsSublist(extents, 14, 100));
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/fake_filesystem.cc b/payload_generator/fake_filesystem.cc
new file mode 100644
index 0000000..00911e1
--- /dev/null
+++ b/payload_generator/fake_filesystem.cc
@@ -0,0 +1,48 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/fake_filesystem.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+FakeFilesystem::FakeFilesystem(uint64_t block_size, uint64_t block_count) :
+ block_size_(block_size),
+ block_count_(block_count) {
+}
+
+size_t FakeFilesystem::GetBlockSize() const {
+ return block_size_;
+}
+
+size_t FakeFilesystem::GetBlockCount() const {
+ return block_count_;
+}
+
+bool FakeFilesystem::GetFiles(std::vector<File>* files) const {
+ *files = files_;
+ return true;
+}
+
+void FakeFilesystem::AddFile(const std::string& filename,
+ const std::vector<Extent> extents) {
+ File file;
+ file.name = filename;
+ file.extents = extents;
+ for (const Extent& extent : extents) {
+ EXPECT_LE(0, extent.start_block());
+ EXPECT_LE(extent.start_block() + extent.num_blocks(), block_count_);
+ }
+ files_.push_back(file);
+}
+
+bool FakeFilesystem::LoadSettings(chromeos::KeyValueStore* store) const {
+ if (minor_version_ < 0)
+ return false;
+ store->SetString("PAYLOAD_MINOR_VERSION", std::to_string(minor_version_));
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/fake_filesystem.h b/payload_generator/fake_filesystem.h
new file mode 100644
index 0000000..35b7756
--- /dev/null
+++ b/payload_generator/fake_filesystem.h
@@ -0,0 +1,56 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_FAKE_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FAKE_FILESYSTEM_H_
+
+// A fake filesystem interface implementation allowing the user to add arbitrary
+// files/metadata.
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <string>
+#include <vector>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class FakeFilesystem : public FilesystemInterface {
+ public:
+ FakeFilesystem(uint64_t block_size, uint64_t block_count);
+ virtual ~FakeFilesystem() = default;
+
+ // FilesystemInterface overrides.
+ size_t GetBlockSize() const override;
+ size_t GetBlockCount() const override;
+ bool GetFiles(std::vector<File>* files) const override;
+ bool LoadSettings(chromeos::KeyValueStore* store) const override;
+
+ // Fake methods.
+
+ // Add a file to the list of fake files.
+ void AddFile(const std::string& filename, const std::vector<Extent> extents);
+
+ // Sets the PAYLOAD_MINOR_VERSION key stored by LoadSettings(). Use a negative
+ // value to produce an error in LoadSettings().
+ void SetMinorVersion(int minor_version) {
+ minor_version_ = minor_version;
+ }
+
+ private:
+ FakeFilesystem() = default;
+
+ uint64_t block_size_;
+ uint64_t block_count_;
+ int minor_version_{-1};
+
+ std::vector<File> files_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeFilesystem);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_FAKE_FILESYSTEM_H_
diff --git a/payload_generator/filesystem_interface.h b/payload_generator/filesystem_interface.h
new file mode 100644
index 0000000..186eac4
--- /dev/null
+++ b/payload_generator/filesystem_interface.h
@@ -0,0 +1,83 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_FILESYSTEM_INTERFACE_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FILESYSTEM_INTERFACE_H_
+
+// This class is used to abstract a filesystem and iterate the blocks
+// associated with the files and filesystem structures.
+// For the purposes of the update payload generation, a filesystem is a formated
+// partition composed by fixed-size blocks, since that's the interface used in
+// the update payload.
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/key_value_store.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class FilesystemInterface {
+ public:
+ // This represents a file or pseudo-file in the filesystem. It can include
+ // all sort of files, like symlinks, hardlinks, directories and even a file
+ // entry representing the metadata, free space, journaling data, etc.
+ struct File {
+ File() {
+ memset(&file_stat, 0, sizeof(file_stat));
+ }
+
+ // The stat struct for the file. This is invalid (inode 0) for some
+ // pseudo-files.
+ struct stat file_stat;
+
+ // The absolute path to the file inside the filesystem, for example,
+ // "/usr/bin/bash". For pseudo-files, like blocks associated to internal
+ // filesystem tables or free space, the path doesn't start with a /.
+ std::string name;
+
+ // The list of all physical blocks holding the data of this file in
+ // the same order as the logical data. All the block numbers shall be
+ // between 0 and GetBlockCount() - 1. The blocks are encoded in extents,
+ // indicating the starting block, and the number of consecutive blocks.
+ std::vector<Extent> extents;
+ };
+
+ virtual ~FilesystemInterface() = default;
+
+ // Returns the size of a block in the filesystem.
+ virtual size_t GetBlockSize() const = 0;
+
+ // Returns the number of blocks in the filesystem.
+ virtual size_t GetBlockCount() const = 0;
+
+ // Stores in |files| the list of files and pseudo-files in the filesystem. See
+ // FileInterface for details. The paths returned by this method shall not
+ // be repeated; but the same block could be present in more than one file as
+ // happens for example with hard-linked files, but not limited to those cases.
+ // Returns whether the function succeeded.
+ virtual bool GetFiles(std::vector<File>* files) const = 0;
+
+ // Load the image settings stored in the filesystem in the
+ // /etc/update_engine.conf file. Returns whether the settings were found.
+ virtual bool LoadSettings(chromeos::KeyValueStore* store) const = 0;
+
+ protected:
+ FilesystemInterface() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FilesystemInterface);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_FILESYSTEM_INTERFACE_H_
diff --git a/payload_generator/full_update_generator.cc b/payload_generator/full_update_generator.cc
new file mode 100644
index 0000000..c28f7e8
--- /dev/null
+++ b/payload_generator/full_update_generator.cc
@@ -0,0 +1,213 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/full_update_generator.h"
+
+#include <fcntl.h>
+#include <inttypes.h>
+
+#include <algorithm>
+#include <deque>
+#include <memory>
+
+#include <base/format_macros.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/bzip.h"
+#include "update_engine/utils.h"
+
+using std::deque;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+const size_t kDefaultFullChunkSize = 1024 * 1024; // 1 MiB
+
+// This class encapsulates a full update chunk processing thread. The processor
+// reads a chunk of data from the input file descriptor and compresses it. The
+// processor needs to be started through Start() then waited on through Wait().
+class ChunkProcessor {
+ public:
+ // Read a chunk of |size| bytes from |fd| starting at offset |offset|.
+ ChunkProcessor(int fd, off_t offset, size_t size)
+ : thread_(nullptr),
+ fd_(fd),
+ offset_(offset),
+ buffer_in_(size) {}
+ ~ChunkProcessor() { Wait(); }
+
+ off_t offset() const { return offset_; }
+ const chromeos::Blob& buffer_in() const { return buffer_in_; }
+ const chromeos::Blob& buffer_compressed() const { return buffer_compressed_; }
+
+ // Starts the processor. Returns true on success, false on failure.
+ bool Start();
+
+ // Waits for the processor to complete. Returns true on success, false on
+ // failure.
+ bool Wait();
+
+ bool ShouldCompress() const {
+ return buffer_compressed_.size() < buffer_in_.size();
+ }
+
+ private:
+ // Reads the input data into |buffer_in_| and compresses it into
+ // |buffer_compressed_|. Returns true on success, false otherwise.
+ bool ReadAndCompress();
+ static gpointer ReadAndCompressThread(gpointer data);
+
+ GThread* thread_;
+ int fd_;
+ off_t offset_;
+ chromeos::Blob buffer_in_;
+ chromeos::Blob buffer_compressed_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChunkProcessor);
+};
+
+bool ChunkProcessor::Start() {
+ // g_thread_create is deprecated since glib 2.32. Use
+ // g_thread_new instead.
+ thread_ = g_thread_try_new("chunk_proc", ReadAndCompressThread, this,
+ nullptr);
+ TEST_AND_RETURN_FALSE(thread_ != nullptr);
+ return true;
+}
+
+bool ChunkProcessor::Wait() {
+ if (!thread_) {
+ return false;
+ }
+ gpointer result = g_thread_join(thread_);
+ thread_ = nullptr;
+ TEST_AND_RETURN_FALSE(result == this);
+ return true;
+}
+
+gpointer ChunkProcessor::ReadAndCompressThread(gpointer data) {
+ return reinterpret_cast<ChunkProcessor*>(data)->ReadAndCompress() ?
+ data : nullptr;
+}
+
+bool ChunkProcessor::ReadAndCompress() {
+ ssize_t bytes_read = -1;
+ TEST_AND_RETURN_FALSE(utils::PReadAll(fd_,
+ buffer_in_.data(),
+ buffer_in_.size(),
+ offset_,
+ &bytes_read));
+ TEST_AND_RETURN_FALSE(bytes_read == static_cast<ssize_t>(buffer_in_.size()));
+ TEST_AND_RETURN_FALSE(BzipCompress(buffer_in_, &buffer_compressed_));
+ return true;
+}
+
+} // namespace
+
+bool FullUpdateGenerator::GenerateOperations(
+ const PayloadGenerationConfig& config,
+ int fd,
+ off_t* data_file_size,
+ vector<AnnotatedOperation>* rootfs_ops,
+ vector<AnnotatedOperation>* kernel_ops) {
+ TEST_AND_RETURN_FALSE(config.Validate());
+ rootfs_ops->clear();
+ kernel_ops->clear();
+
+ // FullUpdateGenerator requires a positive chunk_size, otherwise there will
+ // be only one operation with the whole partition which should not be allowed.
+ size_t full_chunk_size = kDefaultFullChunkSize;
+ if (config.chunk_size >= 0) {
+ full_chunk_size = config.chunk_size;
+ } else {
+ LOG(INFO) << "No chunk_size provided, using the default chunk_size for the "
+ << "full operations: " << full_chunk_size << " bytes.";
+ }
+ TEST_AND_RETURN_FALSE(full_chunk_size > 0);
+ TEST_AND_RETURN_FALSE(full_chunk_size % config.block_size == 0);
+
+ size_t max_threads = std::max(sysconf(_SC_NPROCESSORS_ONLN), 4L);
+ LOG(INFO) << "Max threads: " << max_threads;
+
+ const PartitionConfig* partitions[] = {
+ &config.target.rootfs,
+ &config.target.kernel};
+
+ for (int part_id = 0; part_id < 2; ++part_id) {
+ const PartitionConfig* partition = partitions[part_id];
+ LOG(INFO) << "compressing " << partition->path;
+ int in_fd = open(partition->path.c_str(), O_RDONLY, 0);
+ TEST_AND_RETURN_FALSE(in_fd >= 0);
+ ScopedFdCloser in_fd_closer(&in_fd);
+ deque<shared_ptr<ChunkProcessor>> threads;
+ int last_progress_update = INT_MIN;
+ size_t bytes_left = partition->size, counter = 0, offset = 0;
+ while (bytes_left > 0 || !threads.empty()) {
+ // Check and start new chunk processors if possible.
+ while (threads.size() < max_threads && bytes_left > 0) {
+ size_t this_chunk_bytes = std::min(bytes_left, full_chunk_size);
+ shared_ptr<ChunkProcessor> processor(
+ new ChunkProcessor(in_fd, offset, this_chunk_bytes));
+ threads.push_back(processor);
+ TEST_AND_RETURN_FALSE(processor->Start());
+ bytes_left -= this_chunk_bytes;
+ offset += this_chunk_bytes;
+ }
+
+ // Need to wait for a chunk processor to complete and process its output
+ // before spawning new processors.
+ shared_ptr<ChunkProcessor> processor = threads.front();
+ threads.pop_front();
+ TEST_AND_RETURN_FALSE(processor->Wait());
+
+ DeltaArchiveManifest_InstallOperation* op = nullptr;
+ if (part_id == 0) {
+ rootfs_ops->emplace_back();
+ rootfs_ops->back().name =
+ base::StringPrintf("<rootfs-operation-%" PRIuS ">", counter++);
+ op = &rootfs_ops->back().op;
+ } else {
+ kernel_ops->emplace_back();
+ kernel_ops->back().name =
+ base::StringPrintf("<kernel-operation-%" PRIuS ">", counter++);
+ op = &kernel_ops->back().op;
+ }
+
+ const bool compress = processor->ShouldCompress();
+ const chromeos::Blob& use_buf =
+ compress ? processor->buffer_compressed() : processor->buffer_in();
+ op->set_type(compress ?
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ :
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+ op->set_data_offset(*data_file_size);
+ TEST_AND_RETURN_FALSE(utils::WriteAll(fd, use_buf.data(),
+ use_buf.size()));
+ *data_file_size += use_buf.size();
+ op->set_data_length(use_buf.size());
+ Extent* dst_extent = op->add_dst_extents();
+ dst_extent->set_start_block(processor->offset() / config.block_size);
+ dst_extent->set_num_blocks(
+ processor->buffer_in().size() / config.block_size);
+
+ int progress = static_cast<int>(
+ (processor->offset() + processor->buffer_in().size()) * 100.0 /
+ partition->size);
+ if (last_progress_update < progress &&
+ (last_progress_update + 10 <= progress || progress == 100)) {
+ LOG(INFO) << progress << "% complete (output size: "
+ << *data_file_size << ")";
+ last_progress_update = progress;
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/full_update_generator.h b/payload_generator/full_update_generator.h
new file mode 100644
index 0000000..b4d1135
--- /dev/null
+++ b/payload_generator/full_update_generator.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_FULL_UPDATE_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FULL_UPDATE_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/operations_generator.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+class FullUpdateGenerator : public OperationsGenerator {
+ public:
+ FullUpdateGenerator() = default;
+
+ // Creates a full update for the target image defined in |config|. |config|
+ // must be a valid payload generation configuration for a full payload.
+ // Populates |rootfs_ops| and |kernel_ops|, with data about the update
+ // operations, and writes relevant data to |data_file_fd|, updating
+ // |data_file_size| as it does.
+ bool GenerateOperations(
+ const PayloadGenerationConfig& config,
+ int data_file_fd,
+ off_t* data_file_size,
+ std::vector<AnnotatedOperation>* rootfs_ops,
+ std::vector<AnnotatedOperation>* kernel_ops) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FullUpdateGenerator);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_FULL_UPDATE_GENERATOR_H_
diff --git a/payload_generator/full_update_generator_unittest.cc b/payload_generator/full_update_generator_unittest.cc
new file mode 100644
index 0000000..0a04e4e
--- /dev/null
+++ b/payload_generator/full_update_generator_unittest.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/full_update_generator.h"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/delta_performer.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/test_utils.h"
+
+using chromeos_update_engine::test_utils::FillWithData;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FullUpdateGeneratorTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ config_.is_delta = false;
+ config_.minor_version = DeltaPerformer::kFullPayloadMinorVersion;
+ config_.chunk_size = 128 * 1024;
+ config_.block_size = 4096;
+
+ EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_rootfs.XXXXXX",
+ &config_.target.rootfs.path,
+ nullptr));
+ EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_kernel.XXXXXX",
+ &config_.target.kernel.path,
+ nullptr));
+ EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_blobs.XXXXXX",
+ &out_blobs_path_,
+ &out_blobs_fd_));
+
+ rootfs_part_unlinker_.reset(
+ new ScopedPathUnlinker(config_.target.rootfs.path));
+ kernel_part_unlinker_.reset(
+ new ScopedPathUnlinker(config_.target.kernel.path));
+ out_blobs_unlinker_.reset(new ScopedPathUnlinker(out_blobs_path_));
+ }
+
+ PayloadGenerationConfig config_;
+
+ // Output file holding the payload blobs.
+ string out_blobs_path_;
+ int out_blobs_fd_{-1};
+ ScopedFdCloser out_blobs_fd_closer_{&out_blobs_fd_};
+
+ std::unique_ptr<ScopedPathUnlinker> rootfs_part_unlinker_;
+ std::unique_ptr<ScopedPathUnlinker> kernel_part_unlinker_;
+ std::unique_ptr<ScopedPathUnlinker> out_blobs_unlinker_;
+
+ // FullUpdateGenerator under test.
+ FullUpdateGenerator generator_;
+};
+
+TEST_F(FullUpdateGeneratorTest, RunTest) {
+ chromeos::Blob new_root(9 * 1024 * 1024);
+ chromeos::Blob new_kern(3 * 1024 * 1024);
+ FillWithData(&new_root);
+ FillWithData(&new_kern);
+
+ // Assume hashes take 2 MiB beyond the rootfs.
+ config_.rootfs_partition_size = new_root.size();
+ config_.target.rootfs.size = new_root.size() - 2 * 1024 * 1024;
+ config_.target.kernel.size = new_kern.size();
+
+ EXPECT_TRUE(test_utils::WriteFileVector(config_.target.rootfs.path,
+ new_root));
+ EXPECT_TRUE(test_utils::WriteFileVector(config_.target.kernel.path,
+ new_kern));
+
+ off_t out_blobs_length = 0;
+ vector<AnnotatedOperation> rootfs_ops;
+ vector<AnnotatedOperation> kernel_ops;
+
+ EXPECT_TRUE(generator_.GenerateOperations(config_,
+ out_blobs_fd_,
+ &out_blobs_length,
+ &rootfs_ops,
+ &kernel_ops));
+ int64_t target_rootfs_chunks =
+ config_.target.rootfs.size / config_.chunk_size;
+ EXPECT_EQ(target_rootfs_chunks, rootfs_ops.size());
+ EXPECT_EQ(new_kern.size() / config_.chunk_size, kernel_ops.size());
+ for (off_t i = 0; i < target_rootfs_chunks; ++i) {
+ EXPECT_EQ(1, rootfs_ops[i].op.dst_extents_size());
+ EXPECT_EQ(i * config_.chunk_size / config_.block_size,
+ rootfs_ops[i].op.dst_extents(0).start_block()) << "i = " << i;
+ EXPECT_EQ(config_.chunk_size / config_.block_size,
+ rootfs_ops[i].op.dst_extents(0).num_blocks());
+ if (rootfs_ops[i].op.type() !=
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ,
+ rootfs_ops[i].op.type());
+ }
+ }
+}
+
+// Test that if the chunk size is not a divisor of the image size, it handles
+// correctly the last chunk of each partition.
+TEST_F(FullUpdateGeneratorTest, ChunkSizeTooBig) {
+ config_.chunk_size = 1024 * 1024;
+ chromeos::Blob new_root(1536 * 1024); // 1.5 MiB
+ chromeos::Blob new_kern(128 * 1024);
+ config_.rootfs_partition_size = new_root.size();
+ config_.target.rootfs.size = new_root.size();
+ config_.target.kernel.size = new_kern.size();
+
+ EXPECT_TRUE(test_utils::WriteFileVector(config_.target.rootfs.path,
+ new_root));
+ EXPECT_TRUE(test_utils::WriteFileVector(config_.target.kernel.path,
+ new_kern));
+
+ off_t out_blobs_length = 0;
+ vector<AnnotatedOperation> rootfs_ops;
+ vector<AnnotatedOperation> kernel_ops;
+
+ EXPECT_TRUE(generator_.GenerateOperations(config_,
+ out_blobs_fd_,
+ &out_blobs_length,
+ &rootfs_ops,
+ &kernel_ops));
+ // rootfs has one chunk and a half.
+ EXPECT_EQ(2, rootfs_ops.size());
+ EXPECT_EQ(config_.chunk_size / config_.block_size,
+ BlocksInExtents(rootfs_ops[0].op.dst_extents()));
+ EXPECT_EQ((new_root.size() - config_.chunk_size) / config_.block_size,
+ BlocksInExtents(rootfs_ops[1].op.dst_extents()));
+
+ // kernel has less than one chunk.
+ EXPECT_EQ(1, kernel_ops.size());
+ EXPECT_EQ(new_kern.size() / config_.block_size,
+ BlocksInExtents(kernel_ops[0].op.dst_extents()));
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
new file mode 100644
index 0000000..170a612
--- /dev/null
+++ b/payload_generator/generate_delta_main.cc
@@ -0,0 +1,462 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <chromeos/flag_helper.h>
+#include <glib.h>
+
+#include "update_engine/delta_performer.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/payload_verifier.h"
+#include "update_engine/prefs.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+// This file contains a simple program that takes an old path, a new path,
+// and an output file as arguments and the path to an output file and
+// generates a delta that can be sent to Chrome OS clients.
+
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+void ParseSignatureSizes(const string& signature_sizes_flag,
+ vector<int>* signature_sizes) {
+ signature_sizes->clear();
+ vector<string> split_strings;
+
+ base::SplitString(signature_sizes_flag, ':', &split_strings);
+ for (const string& str : split_strings) {
+ int size = 0;
+ bool parsing_successful = base::StringToInt(str, &size);
+ LOG_IF(FATAL, !parsing_successful)
+ << "Invalid signature size: " << str;
+
+ LOG_IF(FATAL, size != (2048 / 8)) <<
+ "Only signature sizes of 256 bytes are supported.";
+
+ signature_sizes->push_back(size);
+ }
+}
+
+bool ParseImageInfo(const string& channel,
+ const string& board,
+ const string& version,
+ const string& key,
+ const string& build_channel,
+ const string& build_version,
+ ImageInfo* image_info) {
+ // All of these arguments should be present or missing.
+ bool empty = channel.empty();
+
+ CHECK_EQ(channel.empty(), empty);
+ CHECK_EQ(board.empty(), empty);
+ CHECK_EQ(version.empty(), empty);
+ CHECK_EQ(key.empty(), empty);
+
+ if (empty)
+ return false;
+
+ image_info->set_channel(channel);
+ image_info->set_board(board);
+ image_info->set_version(version);
+ image_info->set_key(key);
+
+ image_info->set_build_channel(
+ build_channel.empty() ? channel : build_channel);
+
+ image_info->set_build_version(
+ build_version.empty() ? version : build_version);
+
+ return true;
+}
+
+void CalculatePayloadHashForSigning(const vector<int> &sizes,
+ const string& out_hash_file,
+ const string& in_file) {
+ LOG(INFO) << "Calculating payload hash for signing.";
+ LOG_IF(FATAL, in_file.empty())
+ << "Must pass --in_file to calculate hash for signing.";
+ LOG_IF(FATAL, out_hash_file.empty())
+ << "Must pass --out_hash_file to calculate hash for signing.";
+
+ chromeos::Blob hash;
+ bool result = PayloadSigner::HashPayloadForSigning(in_file, sizes,
+ &hash);
+ CHECK(result);
+
+ result = utils::WriteFile(out_hash_file.c_str(), hash.data(), hash.size());
+ CHECK(result);
+ LOG(INFO) << "Done calculating payload hash for signing.";
+}
+
+
+void CalculateMetadataHashForSigning(const vector<int> &sizes,
+ const string& out_metadata_hash_file,
+ const string& in_file) {
+ LOG(INFO) << "Calculating metadata hash for signing.";
+ LOG_IF(FATAL, in_file.empty())
+ << "Must pass --in_file to calculate metadata hash for signing.";
+ LOG_IF(FATAL, out_metadata_hash_file.empty())
+ << "Must pass --out_metadata_hash_file to calculate metadata hash.";
+
+ chromeos::Blob hash;
+ bool result = PayloadSigner::HashMetadataForSigning(in_file, sizes,
+ &hash);
+ CHECK(result);
+
+ result = utils::WriteFile(out_metadata_hash_file.c_str(), hash.data(),
+ hash.size());
+ CHECK(result);
+
+ LOG(INFO) << "Done calculating metadata hash for signing.";
+}
+
+void SignPayload(const string& in_file,
+ const string& out_file,
+ const string& signature_file) {
+ LOG(INFO) << "Signing payload.";
+ LOG_IF(FATAL, in_file.empty())
+ << "Must pass --in_file to sign payload.";
+ LOG_IF(FATAL, out_file.empty())
+ << "Must pass --out_file to sign payload.";
+ LOG_IF(FATAL, signature_file.empty())
+ << "Must pass --signature_file to sign payload.";
+ vector<chromeos::Blob> signatures;
+ vector<string> signature_files;
+ base::SplitString(signature_file, ':', &signature_files);
+ for (const string& signature_file : signature_files) {
+ chromeos::Blob signature;
+ CHECK(utils::ReadFile(signature_file, &signature));
+ signatures.push_back(signature);
+ }
+ uint64_t final_metadata_size;
+ CHECK(PayloadSigner::AddSignatureToPayload(
+ in_file, signatures, out_file, &final_metadata_size));
+ LOG(INFO) << "Done signing payload. Final metadata size = "
+ << final_metadata_size;
+}
+
+void VerifySignedPayload(const string& in_file,
+ const string& public_key,
+ int public_key_version) {
+ LOG(INFO) << "Verifying signed payload.";
+ LOG_IF(FATAL, in_file.empty())
+ << "Must pass --in_file to verify signed payload.";
+ LOG_IF(FATAL, public_key.empty())
+ << "Must pass --public_key to verify signed payload.";
+ CHECK(PayloadVerifier::VerifySignedPayload(in_file, public_key,
+ public_key_version));
+ LOG(INFO) << "Done verifying signed payload.";
+}
+
+void ApplyDelta(const string& in_file,
+ const string& old_kernel,
+ const string& old_rootfs,
+ const string& prefs_dir) {
+ LOG(INFO) << "Applying delta.";
+ LOG_IF(FATAL, old_rootfs.empty())
+ << "Must pass --old_image to apply delta.";
+ Prefs prefs;
+ InstallPlan install_plan;
+ LOG(INFO) << "Setting up preferences under: " << prefs_dir;
+ LOG_IF(ERROR, !prefs.Init(base::FilePath(prefs_dir)))
+ << "Failed to initialize preferences.";
+ // Get original checksums
+ LOG(INFO) << "Calculating original checksums";
+ PartitionInfo kern_info, root_info;
+ ImageConfig old_image;
+ old_image.kernel.path = old_kernel;
+ old_image.rootfs.path = old_rootfs;
+ CHECK(old_image.LoadImageSize());
+ CHECK(diff_utils::InitializePartitionInfo(old_image.kernel, &kern_info));
+ CHECK(diff_utils::InitializePartitionInfo(old_image.rootfs, &root_info));
+ install_plan.kernel_hash.assign(kern_info.hash().begin(),
+ kern_info.hash().end());
+ install_plan.rootfs_hash.assign(root_info.hash().begin(),
+ root_info.hash().end());
+ DeltaPerformer performer(&prefs, nullptr, &install_plan);
+ CHECK_EQ(performer.Open(old_rootfs.c_str(), 0, 0), 0);
+ CHECK(performer.OpenKernel(old_kernel.c_str()));
+ chromeos::Blob buf(1024 * 1024);
+ int fd = open(in_file.c_str(), O_RDONLY, 0);
+ CHECK_GE(fd, 0);
+ ScopedFdCloser fd_closer(&fd);
+ for (off_t offset = 0;; offset += buf.size()) {
+ ssize_t bytes_read;
+ CHECK(utils::PReadAll(fd, buf.data(), buf.size(), offset, &bytes_read));
+ if (bytes_read == 0)
+ break;
+ CHECK_EQ(performer.Write(buf.data(), bytes_read), bytes_read);
+ }
+ CHECK_EQ(performer.Close(), 0);
+ DeltaPerformer::ResetUpdateProgress(&prefs, false);
+ LOG(INFO) << "Done applying delta.";
+}
+
+int Main(int argc, char** argv) {
+ DEFINE_string(old_dir, "",
+ "[DEPRECATED] Directory where the old rootfs is loop mounted "
+ "read-only. Not required anymore.");
+ DEFINE_string(new_dir, "",
+ "[DEPRECATED] Directory where the new rootfs is loop mounted "
+ "read-only. Not required anymore.");
+ DEFINE_string(old_image, "", "Path to the old rootfs");
+ DEFINE_string(new_image, "", "Path to the new rootfs");
+ DEFINE_string(old_kernel, "", "Path to the old kernel partition image");
+ DEFINE_string(new_kernel, "", "Path to the new kernel partition image");
+ DEFINE_string(in_file, "",
+ "Path to input delta payload file used to hash/sign payloads "
+ "and apply delta over old_image (for debugging)");
+ DEFINE_string(out_file, "", "Path to output delta payload file");
+ DEFINE_string(out_hash_file, "", "Path to output hash file");
+ DEFINE_string(out_metadata_hash_file, "",
+ "Path to output metadata hash file");
+ DEFINE_string(private_key, "", "Path to private key in .pem format");
+ DEFINE_string(public_key, "", "Path to public key in .pem format");
+ DEFINE_int32(public_key_version,
+ chromeos_update_engine::kSignatureMessageCurrentVersion,
+ "Key-check version # of client");
+ DEFINE_string(prefs_dir, "/tmp/update_engine_prefs",
+ "Preferences directory, used with apply_delta");
+ DEFINE_string(signature_size, "",
+ "Raw signature size used for hash calculation. "
+ "You may pass in multiple sizes by colon separating them. E.g. "
+ "2048:2048:4096 will assume 3 signatures, the first two with "
+ "2048 size and the last 4096.");
+ DEFINE_string(signature_file, "",
+ "Raw signature file to sign payload with. To pass multiple "
+ "signatures, use a single argument with a colon between paths, "
+ "e.g. /path/to/sig:/path/to/next:/path/to/last_sig . Each "
+ "signature will be assigned a client version, starting from "
+ "kSignatureOriginalVersion.");
+ DEFINE_int32(chunk_size, -1, "Payload chunk size (-1 -- no limit/default)");
+ DEFINE_uint64(rootfs_partition_size,
+ chromeos_update_engine::kRootFSPartitionSize,
+ "RootFS partition size for the image once installed");
+ DEFINE_int32(minor_version, -1,
+ "The minor version of the payload being generated "
+ "(-1 means autodetect).");
+
+ DEFINE_string(old_channel, "",
+ "The channel for the old image. 'dev-channel', 'npo-channel', "
+ "etc. Ignored, except during delta generation.");
+ DEFINE_string(old_board, "",
+ "The board for the old image. 'x86-mario', 'lumpy', "
+ "etc. Ignored, except during delta generation.");
+ DEFINE_string(old_version, "",
+ "The build version of the old image. 1.2.3, etc.");
+ DEFINE_string(old_key, "",
+ "The key used to sign the old image. 'premp', 'mp', 'mp-v3',"
+ " etc");
+ DEFINE_string(old_build_channel, "",
+ "The channel for the build of the old image. 'dev-channel', "
+ "etc, but will never contain special channels such as "
+ "'npo-channel'. Ignored, except during delta generation.");
+ DEFINE_string(old_build_version, "",
+ "The version of the build containing the old image.");
+
+ DEFINE_string(new_channel, "",
+ "The channel for the new image. 'dev-channel', 'npo-channel', "
+ "etc. Ignored, except during delta generation.");
+ DEFINE_string(new_board, "",
+ "The board for the new image. 'x86-mario', 'lumpy', "
+ "etc. Ignored, except during delta generation.");
+ DEFINE_string(new_version, "",
+ "The build version of the new image. 1.2.3, etc.");
+ DEFINE_string(new_key, "",
+ "The key used to sign the new image. 'premp', 'mp', 'mp-v3',"
+ " etc");
+ DEFINE_string(new_build_channel, "",
+ "The channel for the build of the new image. 'dev-channel', "
+ "etc, but will never contain special channels such as "
+ "'npo-channel'. Ignored, except during delta generation.");
+ DEFINE_string(new_build_version, "",
+ "The version of the build containing the new image.");
+
+ chromeos::FlagHelper::Init(argc, argv,
+ "Generates a payload to provide to ChromeOS' update_engine.\n\n"
+ "This tool can create full payloads and also delta payloads if the src\n"
+ "image is provided. It also provides debugging options to apply, sign\n"
+ "and verify payloads.");
+ Terminator::Init();
+ Subprocess::Init();
+
+ logging::LoggingSettings log_settings;
+ log_settings.log_file = "delta_generator.log";
+ log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ log_settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+ log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+
+ logging::InitLogging(log_settings);
+
+ // Check flags.
+ if (!FLAGS_old_dir.empty()) {
+ LOG(INFO) << "--old_dir flag is deprecated and ignored.";
+ }
+ if (!FLAGS_new_dir.empty()) {
+ LOG(INFO) << "--new_dir flag is deprecated and ignored.";
+ }
+
+ vector<int> signature_sizes;
+ ParseSignatureSizes(FLAGS_signature_size, &signature_sizes);
+
+ if (!FLAGS_out_hash_file.empty() || !FLAGS_out_metadata_hash_file.empty()) {
+ if (!FLAGS_out_hash_file.empty()) {
+ CalculatePayloadHashForSigning(signature_sizes, FLAGS_out_hash_file,
+ FLAGS_in_file);
+ }
+ if (!FLAGS_out_metadata_hash_file.empty()) {
+ CalculateMetadataHashForSigning(signature_sizes,
+ FLAGS_out_metadata_hash_file,
+ FLAGS_in_file);
+ }
+ return 0;
+ }
+ if (!FLAGS_signature_file.empty()) {
+ SignPayload(FLAGS_in_file, FLAGS_out_file, FLAGS_signature_file);
+ return 0;
+ }
+ if (!FLAGS_public_key.empty()) {
+ VerifySignedPayload(FLAGS_in_file, FLAGS_public_key,
+ FLAGS_public_key_version);
+ return 0;
+ }
+ if (!FLAGS_in_file.empty()) {
+ ApplyDelta(FLAGS_in_file, FLAGS_old_kernel, FLAGS_old_image,
+ FLAGS_prefs_dir);
+ return 0;
+ }
+
+ // A payload generation was requested. Convert the flags to a
+ // PayloadGenerationConfig.
+ PayloadGenerationConfig payload_config;
+ payload_config.source.rootfs.path = FLAGS_old_image;
+ payload_config.source.kernel.path = FLAGS_old_kernel;
+
+ payload_config.target.rootfs.path = FLAGS_new_image;
+ payload_config.target.kernel.path = FLAGS_new_kernel;
+
+ payload_config.chunk_size = FLAGS_chunk_size;
+ payload_config.block_size = kBlockSize;
+
+ // The kernel and rootfs size is never passed to the delta_generator, so we
+ // need to detect those from the provided files.
+ if (!FLAGS_old_image.empty()) {
+ CHECK(payload_config.source.LoadImageSize());
+ }
+ if (!FLAGS_new_image.empty()) {
+ CHECK(payload_config.target.LoadImageSize());
+ }
+
+ payload_config.is_delta = !FLAGS_old_image.empty();
+
+ CHECK(!FLAGS_out_file.empty());
+
+ // Ignore failures. These are optional arguments.
+ ParseImageInfo(FLAGS_new_channel,
+ FLAGS_new_board,
+ FLAGS_new_version,
+ FLAGS_new_key,
+ FLAGS_new_build_channel,
+ FLAGS_new_build_version,
+ &payload_config.target.image_info);
+
+ // Ignore failures. These are optional arguments.
+ ParseImageInfo(FLAGS_old_channel,
+ FLAGS_old_board,
+ FLAGS_old_version,
+ FLAGS_old_key,
+ FLAGS_old_build_channel,
+ FLAGS_old_build_version,
+ &payload_config.source.image_info);
+
+ payload_config.rootfs_partition_size = FLAGS_rootfs_partition_size;
+
+ // Load the rootfs size from verity's kernel command line if rootfs
+ // verification is enabled.
+ payload_config.source.LoadVerityRootfsSize();
+ payload_config.target.LoadVerityRootfsSize();
+
+ if (payload_config.is_delta) {
+ // Avoid opening the filesystem interface for full payloads.
+ CHECK(payload_config.target.rootfs.OpenFilesystem());
+ CHECK(payload_config.target.kernel.OpenFilesystem());
+ CHECK(payload_config.source.rootfs.OpenFilesystem());
+ CHECK(payload_config.source.kernel.OpenFilesystem());
+ }
+
+ if (FLAGS_minor_version == -1) {
+ // Autodetect minor_version by looking at the update_engine.conf in the old
+ // image.
+ if (payload_config.is_delta) {
+ CHECK(payload_config.source.rootfs.fs_interface);
+ chromeos::KeyValueStore store;
+ uint32_t minor_version;
+ if (payload_config.source.rootfs.fs_interface->LoadSettings(&store) &&
+ utils::GetMinorVersion(store, &minor_version)) {
+ payload_config.minor_version = minor_version;
+ } else {
+ payload_config.minor_version = kInPlaceMinorPayloadVersion;
+ }
+ } else {
+ payload_config.minor_version = DeltaPerformer::kFullPayloadMinorVersion;
+ }
+ LOG(INFO) << "Auto-detected minor_version=" << payload_config.minor_version;
+ } else {
+ payload_config.minor_version = FLAGS_minor_version;
+ LOG(INFO) << "Using provided minor_version=" << FLAGS_minor_version;
+ }
+
+ if (payload_config.is_delta) {
+ LOG(INFO) << "Generating delta update";
+ } else {
+ LOG(INFO) << "Generating full update";
+ }
+
+ // From this point, all the options have been parsed.
+ if (!payload_config.Validate()) {
+ LOG(ERROR) << "Invalid options passed. See errors above.";
+ return 1;
+ }
+
+ uint64_t metadata_size;
+ if (!GenerateUpdatePayloadFile(payload_config,
+ FLAGS_out_file,
+ FLAGS_private_key,
+ &metadata_size)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+} // namespace
+
+} // namespace chromeos_update_engine
+
+int main(int argc, char** argv) {
+ return chromeos_update_engine::Main(argc, argv);
+}
diff --git a/payload_generator/graph_types.cc b/payload_generator/graph_types.cc
new file mode 100644
index 0000000..3e5adc6
--- /dev/null
+++ b/payload_generator/graph_types.cc
@@ -0,0 +1,11 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/graph_types.h"
+
+namespace chromeos_update_engine {
+
+const Vertex::Index Vertex::kInvalidIndex = static_cast<Vertex::Index>(-1);
+
+} // chromeos_update_engine
diff --git a/payload_generator/graph_types.h b/payload_generator/graph_types.h
new file mode 100644
index 0000000..7b20bb5
--- /dev/null
+++ b/payload_generator/graph_types.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_TYPES_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_TYPES_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/update_metadata.pb.h"
+
+// A few classes that help in generating delta images use these types
+// for the graph work.
+
+namespace chromeos_update_engine {
+
+struct EdgeProperties {
+ // Read-before extents. I.e., blocks in |extents| must be read by the
+ // node pointed to before the pointing node runs (presumably b/c it
+ // overwrites these blocks).
+ std::vector<Extent> extents;
+
+ // Write before extents. I.e., blocks in |write_extents| must be written
+ // by the node pointed to before the pointing node runs (presumably
+ // b/c it reads the data written by the other node).
+ std::vector<Extent> write_extents;
+
+ bool operator==(const EdgeProperties& that) const {
+ return extents == that.extents && write_extents == that.write_extents;
+ }
+};
+
+struct Vertex {
+ Vertex() :
+ valid(true),
+ index(-1),
+ lowlink(-1) {}
+ bool valid;
+
+ typedef std::map<std::vector<Vertex>::size_type, EdgeProperties> EdgeMap;
+ EdgeMap out_edges;
+
+ // We sometimes wish to consider a subgraph of a graph. A subgraph would have
+ // a subset of the vertices from the graph and a subset of the edges.
+ // When considering this vertex within a subgraph, subgraph_edges stores
+ // the out-edges.
+ typedef std::set<std::vector<Vertex>::size_type> SubgraphEdgeMap;
+ SubgraphEdgeMap subgraph_edges;
+
+ // For Tarjan's algorithm:
+ std::vector<Vertex>::size_type index;
+ std::vector<Vertex>::size_type lowlink;
+
+ // Other Vertex properties:
+ DeltaArchiveManifest_InstallOperation op;
+ std::string file_name;
+
+ typedef std::vector<Vertex>::size_type Index;
+ static const Vertex::Index kInvalidIndex;
+};
+
+typedef std::vector<Vertex> Graph;
+
+typedef std::pair<Vertex::Index, Vertex::Index> Edge;
+
+const uint64_t kTempBlockStart = 1ULL << 60;
+COMPILE_ASSERT(kTempBlockStart != 0, kTempBlockStart_invalid);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_TYPES_H_
diff --git a/payload_generator/graph_utils.cc b/payload_generator/graph_utils.cc
new file mode 100644
index 0000000..9166560
--- /dev/null
+++ b/payload_generator/graph_utils.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/graph_utils.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+namespace graph_utils {
+
+uint64_t EdgeWeight(const Graph& graph, const Edge& edge) {
+ uint64_t weight = 0;
+ const vector<Extent>& extents =
+ graph[edge.first].out_edges.find(edge.second)->second.extents;
+ for (vector<Extent>::const_iterator it = extents.begin();
+ it != extents.end(); ++it) {
+ if (it->start_block() != kSparseHole)
+ weight += it->num_blocks();
+ }
+ return weight;
+}
+
+void AddReadBeforeDep(Vertex* src,
+ Vertex::Index dst,
+ uint64_t block) {
+ Vertex::EdgeMap::iterator edge_it = src->out_edges.find(dst);
+ if (edge_it == src->out_edges.end()) {
+ // Must create new edge
+ pair<Vertex::EdgeMap::iterator, bool> result =
+ src->out_edges.insert(make_pair(dst, EdgeProperties()));
+ CHECK(result.second);
+ edge_it = result.first;
+ }
+ AppendBlockToExtents(&edge_it->second.extents, block);
+}
+
+void AddReadBeforeDepExtents(Vertex* src,
+ Vertex::Index dst,
+ const vector<Extent>& extents) {
+ // TODO(adlr): Be more efficient than adding each block individually.
+ for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end();
+ it != e; ++it) {
+ const Extent& extent = *it;
+ for (uint64_t block = extent.start_block(),
+ block_end = extent.start_block() + extent.num_blocks();
+ block != block_end; ++block) {
+ AddReadBeforeDep(src, dst, block);
+ }
+ }
+}
+
+void DropWriteBeforeDeps(Vertex::EdgeMap* edge_map) {
+ // Specially crafted for-loop for the map-iterate-delete dance.
+ for (Vertex::EdgeMap::iterator it = edge_map->begin();
+ it != edge_map->end(); ) {
+ if (!it->second.write_extents.empty())
+ it->second.write_extents.clear();
+ if (it->second.extents.empty()) {
+ // Erase *it, as it contains no blocks
+ edge_map->erase(it++);
+ } else {
+ ++it;
+ }
+ }
+}
+
+// For each node N in graph, drop all edges N->|index|.
+void DropIncomingEdgesTo(Graph* graph, Vertex::Index index) {
+ // This would be much more efficient if we had doubly-linked
+ // edges in the graph.
+ for (Graph::iterator it = graph->begin(), e = graph->end(); it != e; ++it) {
+ it->out_edges.erase(index);
+ }
+}
+
+namespace {
+template<typename T>
+void DumpExtents(const T& field, int prepend_space_count) {
+ string header(prepend_space_count, ' ');
+ for (int i = 0, e = field.size(); i != e; ++i) {
+ LOG(INFO) << header << "(" << GetElement(field, i).start_block() << ", "
+ << GetElement(field, i).num_blocks() << ")";
+ }
+}
+
+void DumpOutEdges(const Vertex::EdgeMap& out_edges) {
+ for (Vertex::EdgeMap::const_iterator it = out_edges.begin(),
+ e = out_edges.end(); it != e; ++it) {
+ LOG(INFO) << " " << it->first << " read-before:";
+ DumpExtents(it->second.extents, 6);
+ LOG(INFO) << " write-before:";
+ DumpExtents(it->second.write_extents, 6);
+ }
+}
+} // namespace
+
+void DumpGraph(const Graph& graph) {
+ LOG(INFO) << "Graph length: " << graph.size();
+ for (Graph::size_type i = 0, e = graph.size(); i != e; ++i) {
+ LOG(INFO) << i
+ << (graph[i].valid ? "" : "-INV")
+ << ": " << graph[i].file_name
+ << ": " << InstallOperationTypeName(graph[i].op.type());
+ LOG(INFO) << " src_extents:";
+ DumpExtents(graph[i].op.src_extents(), 4);
+ LOG(INFO) << " dst_extents:";
+ DumpExtents(graph[i].op.dst_extents(), 4);
+ LOG(INFO) << " out edges:";
+ DumpOutEdges(graph[i].out_edges);
+ }
+}
+
+} // namespace graph_utils
+} // namespace chromeos_update_engine
diff --git a/payload_generator/graph_utils.h b/payload_generator/graph_utils.h
new file mode 100644
index 0000000..6595e57
--- /dev/null
+++ b/payload_generator/graph_utils.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_UTILS_H_
+
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/update_metadata.pb.h"
+
+// A few utility functions for graphs
+
+namespace chromeos_update_engine {
+
+namespace graph_utils {
+
+// Returns the number of blocks represented by all extents in the edge.
+uint64_t EdgeWeight(const Graph& graph, const Edge& edge);
+
+// These add a read-before dependency from graph[src] -> graph[dst]. If the dep
+// already exists, the block/s is/are added to the existing edge.
+void AddReadBeforeDep(Vertex* src,
+ Vertex::Index dst,
+ uint64_t block);
+void AddReadBeforeDepExtents(Vertex* src,
+ Vertex::Index dst,
+ const std::vector<Extent>& extents);
+
+void DropWriteBeforeDeps(Vertex::EdgeMap* edge_map);
+
+// For each node N in graph, drop all edges N->|index|.
+void DropIncomingEdgesTo(Graph* graph, Vertex::Index index);
+
+void DumpGraph(const Graph& graph);
+
+} // namespace graph_utils
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_UTILS_H_
diff --git a/payload_generator/graph_utils_unittest.cc b/payload_generator/graph_utils_unittest.cc
new file mode 100644
index 0000000..bd6c979
--- /dev/null
+++ b/payload_generator/graph_utils_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/graph_utils.h"
+
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using std::make_pair;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class GraphUtilsTest : public ::testing::Test {};
+
+TEST(GraphUtilsTest, SimpleTest) {
+ Graph graph(2);
+
+ graph[0].out_edges.insert(make_pair(1, EdgeProperties()));
+
+ vector<Extent>& extents = graph[0].out_edges[1].extents;
+
+ EXPECT_EQ(0, extents.size());
+ AppendBlockToExtents(&extents, 0);
+ EXPECT_EQ(1, extents.size());
+ AppendBlockToExtents(&extents, 1);
+ AppendBlockToExtents(&extents, 2);
+ EXPECT_EQ(1, extents.size());
+ AppendBlockToExtents(&extents, 4);
+
+ EXPECT_EQ(2, extents.size());
+ EXPECT_EQ(0, extents[0].start_block());
+ EXPECT_EQ(3, extents[0].num_blocks());
+ EXPECT_EQ(4, extents[1].start_block());
+ EXPECT_EQ(1, extents[1].num_blocks());
+
+ EXPECT_EQ(4, graph_utils::EdgeWeight(graph, make_pair(0, 1)));
+}
+
+
+TEST(GraphUtilsTest, DepsTest) {
+ Graph graph(3);
+
+ graph_utils::AddReadBeforeDep(&graph[0], 1, 3);
+ EXPECT_EQ(1, graph[0].out_edges.size());
+ {
+ Extent& extent = graph[0].out_edges[1].extents[0];
+ EXPECT_EQ(3, extent.start_block());
+ EXPECT_EQ(1, extent.num_blocks());
+ }
+ graph_utils::AddReadBeforeDep(&graph[0], 1, 4);
+ EXPECT_EQ(1, graph[0].out_edges.size());
+ {
+ Extent& extent = graph[0].out_edges[1].extents[0];
+ EXPECT_EQ(3, extent.start_block());
+ EXPECT_EQ(2, extent.num_blocks());
+ }
+ graph_utils::AddReadBeforeDepExtents(&graph[2], 1,
+ vector<Extent>(1, ExtentForRange(5, 2)));
+ EXPECT_EQ(1, graph[2].out_edges.size());
+ {
+ Extent& extent = graph[2].out_edges[1].extents[0];
+ EXPECT_EQ(5, extent.start_block());
+ EXPECT_EQ(2, extent.num_blocks());
+ }
+ // Change most recent edge from read-before to write-before
+ graph[2].out_edges[1].write_extents.swap(graph[2].out_edges[1].extents);
+ graph_utils::DropWriteBeforeDeps(&graph[2].out_edges);
+ EXPECT_EQ(0, graph[2].out_edges.size());
+
+ EXPECT_EQ(1, graph[0].out_edges.size());
+ graph_utils::DropIncomingEdgesTo(&graph, 1);
+ EXPECT_EQ(0, graph[0].out_edges.size());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/inplace_generator.cc b/payload_generator/inplace_generator.cc
new file mode 100644
index 0000000..2e0dbaf
--- /dev/null
+++ b/payload_generator/inplace_generator.cc
@@ -0,0 +1,806 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/inplace_generator.h"
+
+#include <algorithm>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/cycle_breaker.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/payload_generator/graph_utils.h"
+#include "update_engine/payload_generator/topological_sort.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::make_pair;
+using std::map;
+using std::pair;
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+using Block = InplaceGenerator::Block;
+
+// This class allocates non-existent temp blocks, starting from
+// kTempBlockStart. Other code is responsible for converting these
+// temp blocks into real blocks, as the client can't read or write to
+// these blocks.
+class DummyExtentAllocator {
+ public:
+ vector<Extent> Allocate(const uint64_t block_count) {
+ vector<Extent> ret(1);
+ ret[0].set_start_block(next_block_);
+ ret[0].set_num_blocks(block_count);
+ next_block_ += block_count;
+ return ret;
+ }
+
+ private:
+ uint64_t next_block_{kTempBlockStart};
+};
+
+// Takes a vector of blocks and returns an equivalent vector of Extent
+// objects.
+vector<Extent> CompressExtents(const vector<uint64_t>& blocks) {
+ vector<Extent> new_extents;
+ for (uint64_t block : blocks) {
+ AppendBlockToExtents(&new_extents, block);
+ }
+ return new_extents;
+}
+
+void InplaceGenerator::CheckGraph(const Graph& graph) {
+ for (const Vertex& v : graph) {
+ CHECK(v.op.has_type());
+ }
+}
+
+void InplaceGenerator::SubstituteBlocks(
+ Vertex* vertex,
+ const vector<Extent>& remove_extents,
+ const vector<Extent>& replace_extents) {
+ // First, expand out the blocks that op reads from
+ vector<uint64_t> read_blocks =
+ ExpandExtents(vertex->op.src_extents());
+ {
+ // Expand remove_extents and replace_extents
+ vector<uint64_t> remove_extents_expanded = ExpandExtents(remove_extents);
+ vector<uint64_t> replace_extents_expanded = ExpandExtents(replace_extents);
+ CHECK_EQ(remove_extents_expanded.size(), replace_extents_expanded.size());
+ map<uint64_t, uint64_t> conversion;
+ for (vector<uint64_t>::size_type i = 0;
+ i < replace_extents_expanded.size(); i++) {
+ conversion[remove_extents_expanded[i]] = replace_extents_expanded[i];
+ }
+ ApplyMap(&read_blocks, conversion);
+ for (auto& edge_prop_pair : vertex->out_edges) {
+ vector<uint64_t> write_before_deps_expanded = ExpandExtents(
+ edge_prop_pair.second.write_extents);
+ ApplyMap(&write_before_deps_expanded, conversion);
+ edge_prop_pair.second.write_extents =
+ CompressExtents(write_before_deps_expanded);
+ }
+ }
+ // Convert read_blocks back to extents
+ vertex->op.clear_src_extents();
+ vector<Extent> new_extents = CompressExtents(read_blocks);
+ StoreExtents(new_extents, vertex->op.mutable_src_extents());
+}
+
+bool InplaceGenerator::CutEdges(Graph* graph,
+ const set<Edge>& edges,
+ vector<CutEdgeVertexes>* out_cuts) {
+ DummyExtentAllocator scratch_allocator;
+ vector<CutEdgeVertexes> cuts;
+ cuts.reserve(edges.size());
+
+ uint64_t scratch_blocks_used = 0;
+ for (const Edge& edge : edges) {
+ cuts.resize(cuts.size() + 1);
+ vector<Extent> old_extents =
+ (*graph)[edge.first].out_edges[edge.second].extents;
+ // Choose some scratch space
+ scratch_blocks_used += graph_utils::EdgeWeight(*graph, edge);
+ cuts.back().tmp_extents =
+ scratch_allocator.Allocate(graph_utils::EdgeWeight(*graph, edge));
+ // create vertex to copy original->scratch
+ cuts.back().new_vertex = graph->size();
+ graph->emplace_back();
+ cuts.back().old_src = edge.first;
+ cuts.back().old_dst = edge.second;
+
+ EdgeProperties& cut_edge_properties =
+ (*graph)[edge.first].out_edges.find(edge.second)->second;
+
+ // This should never happen, as we should only be cutting edges between
+ // real file nodes, and write-before relationships are created from
+ // a real file node to a temp copy node:
+ CHECK(cut_edge_properties.write_extents.empty())
+ << "Can't cut edge that has write-before relationship.";
+
+ // make node depend on the copy operation
+ (*graph)[edge.first].out_edges.insert(make_pair(graph->size() - 1,
+ cut_edge_properties));
+
+ // Set src/dst extents and other proto variables for copy operation
+ graph->back().op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+ StoreExtents(cut_edge_properties.extents,
+ graph->back().op.mutable_src_extents());
+ StoreExtents(cuts.back().tmp_extents,
+ graph->back().op.mutable_dst_extents());
+ graph->back().op.set_src_length(
+ graph_utils::EdgeWeight(*graph, edge) * kBlockSize);
+ graph->back().op.set_dst_length(graph->back().op.src_length());
+
+ // make the dest node read from the scratch space
+ SubstituteBlocks(
+ &((*graph)[edge.second]),
+ (*graph)[edge.first].out_edges[edge.second].extents,
+ cuts.back().tmp_extents);
+
+ // delete the old edge
+ CHECK_EQ(static_cast<Graph::size_type>(1),
+ (*graph)[edge.first].out_edges.erase(edge.second));
+
+ // Add an edge from dst to copy operation
+ EdgeProperties write_before_edge_properties;
+ write_before_edge_properties.write_extents = cuts.back().tmp_extents;
+ (*graph)[edge.second].out_edges.insert(
+ make_pair(graph->size() - 1, write_before_edge_properties));
+ }
+ out_cuts->swap(cuts);
+ return true;
+}
+
+// Creates all the edges for the graph. Writers of a block point to
+// readers of the same block. This is because for an edge A->B, B
+// must complete before A executes.
+void InplaceGenerator::CreateEdges(
+ Graph* graph,
+ const vector<Block>& blocks) {
+ for (vector<Block>::size_type i = 0;
+ i < blocks.size(); i++) {
+ // Blocks with both a reader and writer get an edge
+ if (blocks[i].reader == Vertex::kInvalidIndex ||
+ blocks[i].writer == Vertex::kInvalidIndex)
+ continue;
+ // Don't have a node depend on itself
+ if (blocks[i].reader == blocks[i].writer)
+ continue;
+ // See if there's already an edge we can add onto
+ Vertex::EdgeMap::iterator edge_it =
+ (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader);
+ if (edge_it == (*graph)[blocks[i].writer].out_edges.end()) {
+ // No existing edge. Create one
+ (*graph)[blocks[i].writer].out_edges.insert(
+ make_pair(blocks[i].reader, EdgeProperties()));
+ edge_it = (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader);
+ CHECK(edge_it != (*graph)[blocks[i].writer].out_edges.end());
+ }
+ AppendBlockToExtents(&edge_it->second.extents, i);
+ }
+}
+
+namespace {
+
+class SortCutsByTopoOrderLess {
+ public:
+ explicit SortCutsByTopoOrderLess(
+ const vector<vector<Vertex::Index>::size_type>& table)
+ : table_(table) {}
+ bool operator()(const CutEdgeVertexes& a, const CutEdgeVertexes& b) {
+ return table_[a.old_dst] < table_[b.old_dst];
+ }
+ private:
+ const vector<vector<Vertex::Index>::size_type>& table_;
+};
+
+} // namespace
+
+void InplaceGenerator::GenerateReverseTopoOrderMap(
+ const vector<Vertex::Index>& op_indexes,
+ vector<vector<Vertex::Index>::size_type>* reverse_op_indexes) {
+ vector<vector<Vertex::Index>::size_type> table(op_indexes.size());
+ for (vector<Vertex::Index>::size_type i = 0, e = op_indexes.size();
+ i != e; ++i) {
+ Vertex::Index node = op_indexes[i];
+ if (table.size() < (node + 1)) {
+ table.resize(node + 1);
+ }
+ table[node] = i;
+ }
+ reverse_op_indexes->swap(table);
+}
+
+void InplaceGenerator::SortCutsByTopoOrder(
+ const vector<Vertex::Index>& op_indexes,
+ vector<CutEdgeVertexes>* cuts) {
+ // first, make a reverse lookup table.
+ vector<vector<Vertex::Index>::size_type> table;
+ GenerateReverseTopoOrderMap(op_indexes, &table);
+ SortCutsByTopoOrderLess less(table);
+ sort(cuts->begin(), cuts->end(), less);
+}
+
+void InplaceGenerator::MoveFullOpsToBack(Graph* graph,
+ vector<Vertex::Index>* op_indexes) {
+ vector<Vertex::Index> ret;
+ vector<Vertex::Index> full_ops;
+ ret.reserve(op_indexes->size());
+ for (vector<Vertex::Index>::size_type i = 0, e = op_indexes->size(); i != e;
+ ++i) {
+ DeltaArchiveManifest_InstallOperation_Type type =
+ (*graph)[(*op_indexes)[i]].op.type();
+ if (type == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+ type == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
+ full_ops.push_back((*op_indexes)[i]);
+ } else {
+ ret.push_back((*op_indexes)[i]);
+ }
+ }
+ LOG(INFO) << "Stats: " << full_ops.size() << " full ops out of "
+ << (full_ops.size() + ret.size()) << " total ops.";
+ ret.insert(ret.end(), full_ops.begin(), full_ops.end());
+ op_indexes->swap(ret);
+}
+
+namespace {
+
+template<typename T>
+bool TempBlocksExistInExtents(const T& extents) {
+ for (int i = 0, e = extents.size(); i < e; ++i) {
+ Extent extent = GetElement(extents, i);
+ uint64_t start = extent.start_block();
+ uint64_t num = extent.num_blocks();
+ if (start >= kTempBlockStart || (start + num) >= kTempBlockStart) {
+ LOG(ERROR) << "temp block!";
+ LOG(ERROR) << "start: " << start << ", num: " << num;
+ LOG(ERROR) << "kTempBlockStart: " << kTempBlockStart;
+ LOG(ERROR) << "returning true";
+ return true;
+ }
+ // check for wrap-around, which would be a bug:
+ CHECK(start <= (start + num));
+ }
+ return false;
+}
+
+// Converts the cuts, which must all have the same |old_dst| member,
+// to full. It does this by converting the |old_dst| to REPLACE or
+// REPLACE_BZ, dropping all incoming edges to |old_dst|, and marking
+// all temp nodes invalid.
+bool ConvertCutsToFull(
+ Graph* graph,
+ const string& new_part,
+ int data_fd,
+ off_t* data_file_size,
+ vector<Vertex::Index>* op_indexes,
+ vector<vector<Vertex::Index>::size_type>* reverse_op_indexes,
+ const vector<CutEdgeVertexes>& cuts) {
+ CHECK(!cuts.empty());
+ set<Vertex::Index> deleted_nodes;
+ for (const CutEdgeVertexes& cut : cuts) {
+ TEST_AND_RETURN_FALSE(InplaceGenerator::ConvertCutToFullOp(
+ graph,
+ cut,
+ new_part,
+ data_fd,
+ data_file_size));
+ deleted_nodes.insert(cut.new_vertex);
+ }
+ deleted_nodes.insert(cuts[0].old_dst);
+
+ vector<Vertex::Index> new_op_indexes;
+ new_op_indexes.reserve(op_indexes->size());
+ for (Vertex::Index vertex_index : *op_indexes) {
+ if (utils::SetContainsKey(deleted_nodes, vertex_index))
+ continue;
+ new_op_indexes.push_back(vertex_index);
+ }
+ new_op_indexes.push_back(cuts[0].old_dst);
+ op_indexes->swap(new_op_indexes);
+ InplaceGenerator::GenerateReverseTopoOrderMap(*op_indexes,
+ reverse_op_indexes);
+ return true;
+}
+
+// Tries to assign temp blocks for a collection of cuts, all of which share
+// the same old_dst member. If temp blocks can't be found, old_dst will be
+// converted to a REPLACE or REPLACE_BZ operation. Returns true on success,
+// which can happen even if blocks are converted to full. Returns false
+// on exceptional error cases.
+bool AssignBlockForAdjoiningCuts(
+ Graph* graph,
+ const string& new_part,
+ int data_fd,
+ off_t* data_file_size,
+ vector<Vertex::Index>* op_indexes,
+ vector<vector<Vertex::Index>::size_type>* reverse_op_indexes,
+ const vector<CutEdgeVertexes>& cuts) {
+ CHECK(!cuts.empty());
+ const Vertex::Index old_dst = cuts[0].old_dst;
+ // Calculate # of blocks needed
+ uint64_t blocks_needed = 0;
+ vector<uint64_t> cuts_blocks_needed(cuts.size());
+ for (vector<CutEdgeVertexes>::size_type i = 0; i < cuts.size(); ++i) {
+ uint64_t cut_blocks_needed = 0;
+ for (const Extent& extent : cuts[i].tmp_extents) {
+ cut_blocks_needed += extent.num_blocks();
+ }
+ blocks_needed += cut_blocks_needed;
+ cuts_blocks_needed[i] = cut_blocks_needed;
+ }
+
+ // Find enough blocks
+ ExtentRanges scratch_ranges;
+ // Each block that's supplying temp blocks and the corresponding blocks:
+ typedef vector<pair<Vertex::Index, ExtentRanges>> SupplierVector;
+ SupplierVector block_suppliers;
+ uint64_t scratch_blocks_found = 0;
+ for (vector<Vertex::Index>::size_type i = (*reverse_op_indexes)[old_dst] + 1,
+ e = op_indexes->size(); i < e; ++i) {
+ Vertex::Index test_node = (*op_indexes)[i];
+ if (!(*graph)[test_node].valid)
+ continue;
+ // See if this node has sufficient blocks
+ ExtentRanges ranges;
+ ranges.AddRepeatedExtents((*graph)[test_node].op.dst_extents());
+ ranges.SubtractExtent(ExtentForRange(
+ kTempBlockStart, kSparseHole - kTempBlockStart));
+ ranges.SubtractRepeatedExtents((*graph)[test_node].op.src_extents());
+ // For now, for simplicity, subtract out all blocks in read-before
+ // dependencies.
+ for (Vertex::EdgeMap::const_iterator edge_i =
+ (*graph)[test_node].out_edges.begin(),
+ edge_e = (*graph)[test_node].out_edges.end();
+ edge_i != edge_e; ++edge_i) {
+ ranges.SubtractExtents(edge_i->second.extents);
+ }
+ if (ranges.blocks() == 0)
+ continue;
+
+ if (ranges.blocks() + scratch_blocks_found > blocks_needed) {
+ // trim down ranges
+ vector<Extent> new_ranges = ranges.GetExtentsForBlockCount(
+ blocks_needed - scratch_blocks_found);
+ ranges = ExtentRanges();
+ ranges.AddExtents(new_ranges);
+ }
+ scratch_ranges.AddRanges(ranges);
+ block_suppliers.push_back(make_pair(test_node, ranges));
+ scratch_blocks_found += ranges.blocks();
+ if (scratch_ranges.blocks() >= blocks_needed)
+ break;
+ }
+ if (scratch_ranges.blocks() < blocks_needed) {
+ LOG(INFO) << "Unable to find sufficient scratch";
+ TEST_AND_RETURN_FALSE(ConvertCutsToFull(graph,
+ new_part,
+ data_fd,
+ data_file_size,
+ op_indexes,
+ reverse_op_indexes,
+ cuts));
+ return true;
+ }
+ // Use the scratch we found
+ TEST_AND_RETURN_FALSE(scratch_ranges.blocks() == scratch_blocks_found);
+
+ // Make all the suppliers depend on this node
+ for (const auto& index_range_pair : block_suppliers) {
+ graph_utils::AddReadBeforeDepExtents(
+ &(*graph)[index_range_pair.first],
+ old_dst,
+ index_range_pair.second.GetExtentsForBlockCount(
+ index_range_pair.second.blocks()));
+ }
+
+ // Replace temp blocks in each cut
+ for (vector<CutEdgeVertexes>::size_type i = 0; i < cuts.size(); ++i) {
+ const CutEdgeVertexes& cut = cuts[i];
+ vector<Extent> real_extents =
+ scratch_ranges.GetExtentsForBlockCount(cuts_blocks_needed[i]);
+ scratch_ranges.SubtractExtents(real_extents);
+
+ // Fix the old dest node w/ the real blocks
+ InplaceGenerator::SubstituteBlocks(&(*graph)[old_dst],
+ cut.tmp_extents,
+ real_extents);
+
+ // Fix the new node w/ the real blocks. Since the new node is just a
+ // copy operation, we can replace all the dest extents w/ the real
+ // blocks.
+ DeltaArchiveManifest_InstallOperation *op = &(*graph)[cut.new_vertex].op;
+ op->clear_dst_extents();
+ StoreExtents(real_extents, op->mutable_dst_extents());
+ }
+ return true;
+}
+
+} // namespace
+
+bool InplaceGenerator::AssignTempBlocks(
+ Graph* graph,
+ const string& new_part,
+ int data_fd,
+ off_t* data_file_size,
+ vector<Vertex::Index>* op_indexes,
+ vector<vector<Vertex::Index>::size_type>* reverse_op_indexes,
+ const vector<CutEdgeVertexes>& cuts) {
+ CHECK(!cuts.empty());
+
+ // group of cuts w/ the same old_dst:
+ vector<CutEdgeVertexes> cuts_group;
+
+ for (vector<CutEdgeVertexes>::size_type i = cuts.size() - 1, e = 0;
+ true ; --i) {
+ LOG(INFO) << "Fixing temp blocks in cut " << i
+ << ": old dst: " << cuts[i].old_dst << " new vertex: "
+ << cuts[i].new_vertex << " path: "
+ << (*graph)[cuts[i].old_dst].file_name;
+
+ if (cuts_group.empty() || (cuts_group[0].old_dst == cuts[i].old_dst)) {
+ cuts_group.push_back(cuts[i]);
+ } else {
+ CHECK(!cuts_group.empty());
+ TEST_AND_RETURN_FALSE(AssignBlockForAdjoiningCuts(graph,
+ new_part,
+ data_fd,
+ data_file_size,
+ op_indexes,
+ reverse_op_indexes,
+ cuts_group));
+ cuts_group.clear();
+ cuts_group.push_back(cuts[i]);
+ }
+
+ if (i == e) {
+ // break out of for() loop
+ break;
+ }
+ }
+ CHECK(!cuts_group.empty());
+ TEST_AND_RETURN_FALSE(AssignBlockForAdjoiningCuts(graph,
+ new_part,
+ data_fd,
+ data_file_size,
+ op_indexes,
+ reverse_op_indexes,
+ cuts_group));
+ return true;
+}
+
+bool InplaceGenerator::NoTempBlocksRemain(const Graph& graph) {
+ size_t idx = 0;
+ for (Graph::const_iterator it = graph.begin(), e = graph.end(); it != e;
+ ++it, ++idx) {
+ if (!it->valid)
+ continue;
+ const DeltaArchiveManifest_InstallOperation& op = it->op;
+ if (TempBlocksExistInExtents(op.dst_extents()) ||
+ TempBlocksExistInExtents(op.src_extents())) {
+ LOG(INFO) << "bad extents in node " << idx;
+ LOG(INFO) << "so yeah";
+ return false;
+ }
+
+ // Check out-edges:
+ for (const auto& edge_prop_pair : it->out_edges) {
+ if (TempBlocksExistInExtents(edge_prop_pair.second.extents) ||
+ TempBlocksExistInExtents(edge_prop_pair.second.write_extents)) {
+ LOG(INFO) << "bad out edge in node " << idx;
+ LOG(INFO) << "so yeah";
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool InplaceGenerator::ConvertCutToFullOp(Graph* graph,
+ const CutEdgeVertexes& cut,
+ const string& new_part,
+ int data_fd,
+ off_t* data_file_size) {
+ // Drop all incoming edges, keep all outgoing edges
+
+ // Keep all outgoing edges
+ if ((*graph)[cut.old_dst].op.type() !=
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ &&
+ (*graph)[cut.old_dst].op.type() !=
+ DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+ Vertex::EdgeMap out_edges = (*graph)[cut.old_dst].out_edges;
+ graph_utils::DropWriteBeforeDeps(&out_edges);
+
+ // Replace the operation with a REPLACE or REPLACE_BZ to generate the same
+ // |new_extents| list of blocks and update the graph.
+ vector<AnnotatedOperation> new_aop;
+ vector<Extent> new_extents;
+ ExtentsToVector((*graph)[cut.old_dst].op.dst_extents(),
+ &new_extents);
+ TEST_AND_RETURN_FALSE(diff_utils::DeltaReadFile(
+ &new_aop,
+ "", // old_part
+ new_part,
+ vector<Extent>(), // old_extents
+ new_extents,
+ (*graph)[cut.old_dst].file_name,
+ -1, // chunk_blocks, forces to have a single operation.
+ data_fd,
+ data_file_size,
+ false)); // src_ops_allowed
+ TEST_AND_RETURN_FALSE(new_aop.size() == 1);
+ TEST_AND_RETURN_FALSE(AddInstallOpToGraph(
+ graph, cut.old_dst, nullptr, new_aop.front().op, new_aop.front().name));
+
+ (*graph)[cut.old_dst].out_edges = out_edges;
+
+ // Right now we don't have doubly-linked edges, so we have to scan
+ // the whole graph.
+ graph_utils::DropIncomingEdgesTo(graph, cut.old_dst);
+ }
+
+ // Delete temp node
+ (*graph)[cut.old_src].out_edges.erase(cut.new_vertex);
+ CHECK((*graph)[cut.old_dst].out_edges.find(cut.new_vertex) ==
+ (*graph)[cut.old_dst].out_edges.end());
+ (*graph)[cut.new_vertex].valid = false;
+ LOG(INFO) << "marked node invalid: " << cut.new_vertex;
+ return true;
+}
+
+bool InplaceGenerator::ConvertGraphToDag(Graph* graph,
+ const string& new_part,
+ int fd,
+ off_t* data_file_size,
+ vector<Vertex::Index>* final_order,
+ Vertex::Index scratch_vertex) {
+ CycleBreaker cycle_breaker;
+ LOG(INFO) << "Finding cycles...";
+ set<Edge> cut_edges;
+ cycle_breaker.BreakCycles(*graph, &cut_edges);
+ LOG(INFO) << "done finding cycles";
+ CheckGraph(*graph);
+
+ // Calculate number of scratch blocks needed
+
+ LOG(INFO) << "Cutting cycles...";
+ vector<CutEdgeVertexes> cuts;
+ TEST_AND_RETURN_FALSE(CutEdges(graph, cut_edges, &cuts));
+ LOG(INFO) << "done cutting cycles";
+ LOG(INFO) << "There are " << cuts.size() << " cuts.";
+ CheckGraph(*graph);
+
+ LOG(INFO) << "Creating initial topological order...";
+ TopologicalSort(*graph, final_order);
+ LOG(INFO) << "done with initial topo order";
+ CheckGraph(*graph);
+
+ LOG(INFO) << "Moving full ops to the back";
+ MoveFullOpsToBack(graph, final_order);
+ LOG(INFO) << "done moving full ops to back";
+
+ vector<vector<Vertex::Index>::size_type> inverse_final_order;
+ GenerateReverseTopoOrderMap(*final_order, &inverse_final_order);
+
+ SortCutsByTopoOrder(*final_order, &cuts);
+
+ if (!cuts.empty())
+ TEST_AND_RETURN_FALSE(AssignTempBlocks(graph,
+ new_part,
+ fd,
+ data_file_size,
+ final_order,
+ &inverse_final_order,
+ cuts));
+ LOG(INFO) << "Making sure all temp blocks have been allocated";
+
+ // Remove the scratch node, if any
+ if (scratch_vertex != Vertex::kInvalidIndex) {
+ final_order->erase(final_order->begin() +
+ inverse_final_order[scratch_vertex]);
+ (*graph)[scratch_vertex].valid = false;
+ GenerateReverseTopoOrderMap(*final_order, &inverse_final_order);
+ }
+
+ graph_utils::DumpGraph(*graph);
+ CHECK(NoTempBlocksRemain(*graph));
+ LOG(INFO) << "done making sure all temp blocks are allocated";
+ return true;
+}
+
+void InplaceGenerator::CreateScratchNode(uint64_t start_block,
+ uint64_t num_blocks,
+ Vertex* vertex) {
+ vertex->file_name = "<scratch>";
+ vertex->op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+ vertex->op.set_data_offset(0);
+ vertex->op.set_data_length(0);
+ Extent* extent = vertex->op.add_dst_extents();
+ extent->set_start_block(start_block);
+ extent->set_num_blocks(num_blocks);
+}
+
+bool InplaceGenerator::AddInstallOpToBlocksVector(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ const Graph& graph,
+ Vertex::Index vertex,
+ vector<Block>* blocks) {
+ // See if this is already present.
+ TEST_AND_RETURN_FALSE(operation.dst_extents_size() > 0);
+
+ enum BlockField { READER = 0, WRITER, BLOCK_FIELD_COUNT };
+ for (int field = READER; field < BLOCK_FIELD_COUNT; field++) {
+ const int extents_size =
+ (field == READER) ? operation.src_extents_size() :
+ operation.dst_extents_size();
+ const char* past_participle = (field == READER) ? "read" : "written";
+ const google::protobuf::RepeatedPtrField<Extent>& extents =
+ (field == READER) ? operation.src_extents() : operation.dst_extents();
+ Vertex::Index Block::*access_type = (field == READER) ?
+ &Block::reader : &Block::writer;
+
+ for (int i = 0; i < extents_size; i++) {
+ const Extent& extent = extents.Get(i);
+ for (uint64_t block = extent.start_block();
+ block < (extent.start_block() + extent.num_blocks()); block++) {
+ if ((*blocks)[block].*access_type != Vertex::kInvalidIndex) {
+ LOG(FATAL) << "Block " << block << " is already "
+ << past_participle << " by "
+ << (*blocks)[block].*access_type << "("
+ << graph[(*blocks)[block].*access_type].file_name
+ << ") and also " << vertex << "("
+ << graph[vertex].file_name << ")";
+ }
+ (*blocks)[block].*access_type = vertex;
+ }
+ }
+ }
+ return true;
+}
+
+bool InplaceGenerator::AddInstallOpToGraph(
+ Graph* graph,
+ Vertex::Index existing_vertex,
+ vector<Block>* blocks,
+ const DeltaArchiveManifest_InstallOperation operation,
+ const string& op_name) {
+ Vertex::Index vertex = existing_vertex;
+ if (vertex == Vertex::kInvalidIndex) {
+ graph->emplace_back();
+ vertex = graph->size() - 1;
+ }
+ (*graph)[vertex].op = operation;
+ CHECK((*graph)[vertex].op.has_type());
+ (*graph)[vertex].file_name = op_name;
+
+ if (blocks)
+ TEST_AND_RETURN_FALSE(InplaceGenerator::AddInstallOpToBlocksVector(
+ (*graph)[vertex].op,
+ *graph,
+ vertex,
+ blocks));
+ return true;
+}
+
+void InplaceGenerator::ApplyMap(vector<uint64_t>* collection,
+ const map<uint64_t, uint64_t>& the_map) {
+ for (uint64_t& elem : *collection) {
+ const auto& map_it = the_map.find(elem);
+ if (map_it != the_map.end())
+ elem = map_it->second;
+ }
+}
+
+bool InplaceGenerator::GenerateOperations(
+ const PayloadGenerationConfig& config,
+ int data_file_fd,
+ off_t* data_file_size,
+ vector<AnnotatedOperation>* rootfs_ops,
+ vector<AnnotatedOperation>* kernel_ops) {
+ off_t chunk_blocks = (config.chunk_size == -1 ? -1 :
+ config.chunk_size / config.block_size);
+
+ // Temporary list of operations used to construct the dependency graph.
+ vector<AnnotatedOperation> aops;
+ TEST_AND_RETURN_FALSE(
+ diff_utils::DeltaReadPartition(&aops,
+ config.source.rootfs,
+ config.target.rootfs,
+ chunk_blocks,
+ data_file_fd,
+ data_file_size,
+ true, // skip_block_0
+ false)); // src_ops_allowed
+ // Convert the rootfs operations to the graph.
+ Graph graph;
+ CheckGraph(graph);
+ vector<Block> blocks(config.target.rootfs.size / config.block_size);
+ for (const auto& aop : aops) {
+ AddInstallOpToGraph(
+ &graph, Vertex::kInvalidIndex, &blocks, aop.op, aop.name);
+ }
+ LOG(INFO) << "done reading normal files";
+ CheckGraph(graph);
+
+ // Final scratch block (if there's space)
+ Vertex::Index scratch_vertex = Vertex::kInvalidIndex;
+ if (blocks.size() < (config.rootfs_partition_size / kBlockSize)) {
+ scratch_vertex = graph.size();
+ graph.emplace_back();
+ CreateScratchNode(
+ blocks.size(),
+ (config.rootfs_partition_size / kBlockSize) - blocks.size(),
+ &graph.back());
+ }
+
+ // Read kernel partition
+ LOG(INFO) << "Delta compressing kernel partition...";
+ // It is safe to not skip the block 0 since we will not be using the cycle
+ // breaking algorithm on this list of operations as we expect no cycles here.
+ TEST_AND_RETURN_FALSE(
+ diff_utils::DeltaReadPartition(kernel_ops,
+ config.source.kernel,
+ config.target.kernel,
+ chunk_blocks,
+ data_file_fd,
+ data_file_size,
+ false, // skip_block_0
+ false)); // src_ops_allowed
+ LOG(INFO) << "done reading kernel";
+ CheckGraph(graph);
+
+ LOG(INFO) << "Creating edges...";
+ CreateEdges(&graph, blocks);
+ LOG(INFO) << "Done creating edges";
+ CheckGraph(graph);
+
+ vector<Vertex::Index> final_order;
+ TEST_AND_RETURN_FALSE(ConvertGraphToDag(
+ &graph,
+ config.target.rootfs.path,
+ data_file_fd,
+ data_file_size,
+ &final_order,
+ scratch_vertex));
+
+ // Copy operations over to the rootfs_ops in the final_order generated by the
+ // topological sort.
+ rootfs_ops->clear();
+ for (const Vertex::Index vertex_index : final_order) {
+ const Vertex& vertex = graph[vertex_index];
+ rootfs_ops->emplace_back();
+ rootfs_ops->back().op = vertex.op;
+ rootfs_ops->back().name = vertex.file_name;
+ }
+
+ // Re-add the operation for the block 0.
+ TEST_AND_RETURN_FALSE(diff_utils::DeltaReadFile(
+ rootfs_ops,
+ config.source.rootfs.path,
+ config.target.rootfs.path,
+ vector<Extent>{ExtentForRange(0, 1)},
+ vector<Extent>{ExtentForRange(0, 1)},
+ "<block-0>", // operation name
+ -1, // chunk_blocks
+ data_file_fd,
+ data_file_size,
+ false)); // src_ops_allowed
+
+ return true;
+}
+
+}; // namespace chromeos_update_engine
diff --git a/payload_generator/inplace_generator.h b/payload_generator/inplace_generator.h
new file mode 100644
index 0000000..158e364
--- /dev/null
+++ b/payload_generator/inplace_generator.h
@@ -0,0 +1,221 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/payload_generator/operations_generator.h"
+
+// InplaceGenerator contains all functionality related to the inplace algorithm
+// for generating update payloads. These are the functions used when delta minor
+// version is 1.
+
+namespace chromeos_update_engine {
+
+// This struct stores all relevant info for an edge that is cut between
+// nodes old_src -> old_dst by creating new vertex new_vertex. The new
+// relationship is:
+// old_src -(read before)-> new_vertex <-(write before)- old_dst
+// new_vertex is a MOVE operation that moves some existing blocks into
+// temp space. The temp extents are, by necessity, stored in new_vertex
+// (as dst extents) and old_dst (as src extents), but they are also broken
+// out into tmp_extents, as the nodes themselves may contain many more
+// extents.
+struct CutEdgeVertexes {
+ Vertex::Index new_vertex;
+ Vertex::Index old_src;
+ Vertex::Index old_dst;
+ std::vector<Extent> tmp_extents;
+};
+
+class InplaceGenerator : public OperationsGenerator {
+ public:
+ // Represents a disk block on the install partition.
+ struct Block {
+ // During install, each block on the install partition will be written
+ // and some may be read (in all likelihood, many will be read).
+ // The reading and writing will be performed by InstallOperations,
+ // each of which has a corresponding vertex in a graph.
+ // A Block object tells which vertex will read or write this block
+ // at install time.
+ // Generally, there will be a vector of Block objects whose length
+ // is the number of blocks on the install partition.
+ Block() : reader(Vertex::kInvalidIndex), writer(Vertex::kInvalidIndex) {}
+ Vertex::Index reader;
+ Vertex::Index writer;
+ };
+
+ InplaceGenerator() = default;
+
+ // Checks all the operations in the graph have a type assigned.
+ static void CheckGraph(const Graph& graph);
+
+ // Modifies blocks read by 'op' so that any blocks referred to by
+ // 'remove_extents' are replaced with blocks from 'replace_extents'.
+ // 'remove_extents' and 'replace_extents' must be the same number of blocks.
+ // Blocks will be substituted in the order listed in the vectors.
+ // E.g. if 'op' reads blocks 1, 2, 3, 4, 5, 6, 7, 8, remove_extents
+ // contains blocks 6, 2, 3, 5, and replace blocks contains
+ // 12, 13, 14, 15, then op will be changed to read from:
+ // 1, 13, 14, 4, 15, 12, 7, 8
+ static void SubstituteBlocks(Vertex* vertex,
+ const std::vector<Extent>& remove_extents,
+ const std::vector<Extent>& replace_extents);
+
+ // Cuts 'edges' from 'graph' according to the AU algorithm. This means
+ // for each edge A->B, remove the dependency that B occur before A.
+ // Do this by creating a new operation X that copies from the blocks
+ // specified by the edge's properties to temp space T. Modify B to read
+ // from T rather than the blocks in the edge. Modify A to depend on X,
+ // but not on B. Free space is found by looking in 'blocks'.
+ // Returns true on success.
+ static bool CutEdges(Graph* graph,
+ const std::set<Edge>& edges,
+ std::vector<CutEdgeVertexes>* out_cuts);
+
+ // Creates all the edges for the graph. Writers of a block point to
+ // readers of the same block. This is because for an edge A->B, B
+ // must complete before A executes.
+ static void CreateEdges(Graph* graph,
+ const std::vector<Block>& blocks);
+
+ // Takes |op_indexes|, which is effectively a mapping from order in
+ // which the op is performed -> graph vertex index, and produces the
+ // reverse: a mapping from graph vertex index -> op_indexes index.
+ static void GenerateReverseTopoOrderMap(
+ const std::vector<Vertex::Index>& op_indexes,
+ std::vector<std::vector<Vertex::Index>::size_type>* reverse_op_indexes);
+
+ // Sorts the vector |cuts| by its |cuts[].old_dest| member. Order is
+ // determined by the order of elements in op_indexes.
+ static void SortCutsByTopoOrder(
+ const std::vector<Vertex::Index>& op_indexes,
+ std::vector<CutEdgeVertexes>* cuts);
+
+ // Given a topologically sorted graph |op_indexes| and |graph|, alters
+ // |op_indexes| to move all the full operations to the end of the vector.
+ // Full operations should not be depended on, so this is safe.
+ static void MoveFullOpsToBack(Graph* graph,
+ std::vector<Vertex::Index>* op_indexes);
+
+ // Returns true iff there are no extents in the graph that refer to temp
+ // blocks. Temp blocks are in the range [kTempBlockStart, kSparseHole).
+ static bool NoTempBlocksRemain(const Graph& graph);
+
+ // Takes a |graph|, which has edges that must be cut, as listed in
+ // |cuts|. Cuts the edges. Maintains a list in which the operations
+ // will be performed (in |op_indexes|) and the reverse (in
+ // |reverse_op_indexes|). Cutting edges requires scratch space, and
+ // if insufficient scratch is found, the file is reread and will be
+ // send down (either as REPLACE or REPLACE_BZ). Returns true on
+ // success.
+ static bool AssignTempBlocks(
+ Graph* graph,
+ const std::string& new_part,
+ int data_fd,
+ off_t* data_file_size,
+ std::vector<Vertex::Index>* op_indexes,
+ std::vector<std::vector<Vertex::Index>::size_type>* reverse_op_indexes,
+ const std::vector<CutEdgeVertexes>& cuts);
+
+ // Handles allocation of temp blocks to a cut edge by converting the
+ // dest node to a full op. This removes the need for temp blocks, but
+ // comes at the cost of a worse compression ratio.
+ // For example, say we have A->B->A. It would first be cut to form:
+ // A->B->N<-A, where N copies blocks to temp space. If there are no
+ // temp blocks, this function can be called to convert it to the form:
+ // A->B. Now, A is a full operation.
+ static bool ConvertCutToFullOp(Graph* graph,
+ const CutEdgeVertexes& cut,
+ const std::string& new_part,
+ int data_fd,
+ off_t* data_file_size);
+
+ // Takes a graph, which is not a DAG, which represents the files just
+ // read from disk, and converts it into a DAG by breaking all cycles
+ // and finding temp space to resolve broken edges.
+ // The final order of the nodes is given in |final_order|
+ // Some files may need to be reread from disk, thus |fd| and
+ // |data_file_size| are be passed.
+ // If |scratch_vertex| is not kInvalidIndex, removes it from
+ // |final_order| before returning.
+ // Returns true on success.
+ static bool ConvertGraphToDag(Graph* graph,
+ const std::string& new_part,
+ int fd,
+ off_t* data_file_size,
+ std::vector<Vertex::Index>* final_order,
+ Vertex::Index scratch_vertex);
+
+ // Creates a dummy REPLACE_BZ node in the given |vertex|. This can be used
+ // to provide scratch space. The node writes |num_blocks| blocks starting at
+ // |start_block|The node should be marked invalid before writing all nodes to
+ // the output file.
+ static void CreateScratchNode(uint64_t start_block,
+ uint64_t num_blocks,
+ Vertex* vertex);
+
+ // The |blocks| vector contains a reader and writer for each block on the
+ // filesystem that's being in-place updated. We populate the reader/writer
+ // fields of |blocks| by calling this function.
+ // For each block in |operation| that is read or written, find that block
+ // in |blocks| and set the reader/writer field to the vertex passed.
+ // |graph| is not strictly necessary, but useful for printing out
+ // error messages.
+ static bool AddInstallOpToBlocksVector(
+ const DeltaArchiveManifest_InstallOperation& operation,
+ const Graph& graph,
+ Vertex::Index vertex,
+ std::vector<Block>* blocks);
+
+ // Add a vertex (if |existing_vertex| is kInvalidVertex) or update an
+ // |existing_vertex| with the passed |operation|.
+ // This method will also register the vertex as the reader or writer of the
+ // blocks involved in the operation updating the |blocks| vector. The
+ // |op_name| associated with the Vertex is used for logging purposes.
+ static bool AddInstallOpToGraph(
+ Graph* graph,
+ Vertex::Index existing_vertex,
+ std::vector<Block>* blocks,
+ const DeltaArchiveManifest_InstallOperation operation,
+ const std::string& op_name);
+
+ // Apply the transformation stored in |the_map| to the |collection| vector
+ // replacing the map keys found in |collection| with its associated value in
+ // |the_map|.
+ static void ApplyMap(std::vector<uint64_t>* collection,
+ const std::map<uint64_t, uint64_t>& the_map);
+
+ // Generate the update payload operations for the kernel and rootfs using
+ // only operations that read from the target and/or write to the target,
+ // hence, applying the payload "in-place" in the target partition. This method
+ // assumes that the contents of the source image are pre-copied to the target
+ // partition, up to the size of the source image. Use this method to generate
+ // a delta update with the minor version kInPlaceMinorPayloadVersion.
+ // The rootfs operations are stored in |graph| and should be executed in the
+ // |final_order| order. The kernel operations are stored in |kernel_ops|. All
+ // the offsets in the operations reference the data written to |data_file_fd|.
+ // The total amount of data written to that file is stored in
+ // |data_file_size|.
+ bool GenerateOperations(
+ const PayloadGenerationConfig& config,
+ int data_file_fd,
+ off_t* data_file_size,
+ std::vector<AnnotatedOperation>* rootfs_ops,
+ std::vector<AnnotatedOperation>* kernel_ops) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InplaceGenerator);
+};
+
+}; // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_
diff --git a/payload_generator/inplace_generator_unittest.cc b/payload_generator/inplace_generator_unittest.cc
new file mode 100644
index 0000000..5de0df9
--- /dev/null
+++ b/payload_generator/inplace_generator_unittest.cc
@@ -0,0 +1,518 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/inplace_generator.h"
+
+#include <map>
+#include <set>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/cycle_breaker.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/payload_generator/graph_utils.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::map;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+using Block = InplaceGenerator::Block;
+
+namespace {
+
+#define OP_BSDIFF DeltaArchiveManifest_InstallOperation_Type_BSDIFF
+#define OP_MOVE DeltaArchiveManifest_InstallOperation_Type_MOVE
+#define OP_REPLACE DeltaArchiveManifest_InstallOperation_Type_REPLACE
+#define OP_REPLACE_BZ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ
+
+void GenVertex(Vertex* out,
+ const vector<Extent>& src_extents,
+ const vector<Extent>& dst_extents,
+ const string& path,
+ DeltaArchiveManifest_InstallOperation_Type type) {
+ out->op.set_type(type);
+ out->file_name = path;
+ StoreExtents(src_extents, out->op.mutable_src_extents());
+ StoreExtents(dst_extents, out->op.mutable_dst_extents());
+}
+
+vector<Extent> VectOfExt(uint64_t start_block, uint64_t num_blocks) {
+ return vector<Extent>(1, ExtentForRange(start_block, num_blocks));
+}
+
+EdgeProperties EdgeWithReadDep(const vector<Extent>& extents) {
+ EdgeProperties ret;
+ ret.extents = extents;
+ return ret;
+}
+
+EdgeProperties EdgeWithWriteDep(const vector<Extent>& extents) {
+ EdgeProperties ret;
+ ret.write_extents = extents;
+ return ret;
+}
+
+template<typename T>
+void DumpVect(const vector<T>& vect) {
+ stringstream ss(stringstream::out);
+ for (typename vector<T>::const_iterator it = vect.begin(), e = vect.end();
+ it != e; ++it) {
+ ss << *it << ", ";
+ }
+ LOG(INFO) << "{" << ss.str() << "}";
+}
+
+void AppendExtent(vector<Extent>* vect, uint64_t start, uint64_t length) {
+ vect->resize(vect->size() + 1);
+ vect->back().set_start_block(start);
+ vect->back().set_num_blocks(length);
+}
+
+void OpAppendExtent(DeltaArchiveManifest_InstallOperation* op,
+ uint64_t start,
+ uint64_t length) {
+ Extent* extent = op->add_src_extents();
+ extent->set_start_block(start);
+ extent->set_num_blocks(length);
+}
+
+} // namespace
+
+class InplaceGeneratorTest : public ::testing::Test {
+};
+
+TEST_F(InplaceGeneratorTest, BlockDefaultValues) {
+ // Tests that a Block is initialized with the default values as a
+ // Vertex::kInvalidIndex. This is required by the delta generators.
+ Block block;
+ EXPECT_EQ(Vertex::kInvalidIndex, block.reader);
+ EXPECT_EQ(Vertex::kInvalidIndex, block.writer);
+}
+
+TEST_F(InplaceGeneratorTest, SubstituteBlocksTest) {
+ vector<Extent> remove_blocks;
+ AppendExtent(&remove_blocks, 3, 3);
+ AppendExtent(&remove_blocks, 7, 1);
+ vector<Extent> replace_blocks;
+ AppendExtent(&replace_blocks, 10, 2);
+ AppendExtent(&replace_blocks, 13, 2);
+ Vertex vertex;
+ DeltaArchiveManifest_InstallOperation& op = vertex.op;
+ OpAppendExtent(&op, 4, 3);
+ OpAppendExtent(&op, kSparseHole, 4); // Sparse hole in file
+ OpAppendExtent(&op, 3, 1);
+ OpAppendExtent(&op, 7, 3);
+
+ InplaceGenerator::SubstituteBlocks(&vertex, remove_blocks, replace_blocks);
+
+ EXPECT_EQ(7, op.src_extents_size());
+ EXPECT_EQ(11, op.src_extents(0).start_block());
+ EXPECT_EQ(1, op.src_extents(0).num_blocks());
+ EXPECT_EQ(13, op.src_extents(1).start_block());
+ EXPECT_EQ(1, op.src_extents(1).num_blocks());
+ EXPECT_EQ(6, op.src_extents(2).start_block());
+ EXPECT_EQ(1, op.src_extents(2).num_blocks());
+ EXPECT_EQ(kSparseHole, op.src_extents(3).start_block());
+ EXPECT_EQ(4, op.src_extents(3).num_blocks());
+ EXPECT_EQ(10, op.src_extents(4).start_block());
+ EXPECT_EQ(1, op.src_extents(4).num_blocks());
+ EXPECT_EQ(14, op.src_extents(5).start_block());
+ EXPECT_EQ(1, op.src_extents(5).num_blocks());
+ EXPECT_EQ(8, op.src_extents(6).start_block());
+ EXPECT_EQ(2, op.src_extents(6).num_blocks());
+}
+
+TEST_F(InplaceGeneratorTest, CutEdgesTest) {
+ Graph graph;
+ vector<Block> blocks(9);
+
+ // Create nodes in graph
+ {
+ graph.resize(graph.size() + 1);
+ graph.back().op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+ // Reads from blocks 3, 5, 7
+ vector<Extent> extents;
+ AppendBlockToExtents(&extents, 3);
+ AppendBlockToExtents(&extents, 5);
+ AppendBlockToExtents(&extents, 7);
+ StoreExtents(extents,
+ graph.back().op.mutable_src_extents());
+ blocks[3].reader = graph.size() - 1;
+ blocks[5].reader = graph.size() - 1;
+ blocks[7].reader = graph.size() - 1;
+
+ // Writes to blocks 1, 2, 4
+ extents.clear();
+ AppendBlockToExtents(&extents, 1);
+ AppendBlockToExtents(&extents, 2);
+ AppendBlockToExtents(&extents, 4);
+ StoreExtents(extents,
+ graph.back().op.mutable_dst_extents());
+ blocks[1].writer = graph.size() - 1;
+ blocks[2].writer = graph.size() - 1;
+ blocks[4].writer = graph.size() - 1;
+ }
+ {
+ graph.resize(graph.size() + 1);
+ graph.back().op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+ // Reads from blocks 1, 2, 4
+ vector<Extent> extents;
+ AppendBlockToExtents(&extents, 1);
+ AppendBlockToExtents(&extents, 2);
+ AppendBlockToExtents(&extents, 4);
+ StoreExtents(extents,
+ graph.back().op.mutable_src_extents());
+ blocks[1].reader = graph.size() - 1;
+ blocks[2].reader = graph.size() - 1;
+ blocks[4].reader = graph.size() - 1;
+
+ // Writes to blocks 3, 5, 6
+ extents.clear();
+ AppendBlockToExtents(&extents, 3);
+ AppendBlockToExtents(&extents, 5);
+ AppendBlockToExtents(&extents, 6);
+ StoreExtents(extents,
+ graph.back().op.mutable_dst_extents());
+ blocks[3].writer = graph.size() - 1;
+ blocks[5].writer = graph.size() - 1;
+ blocks[6].writer = graph.size() - 1;
+ }
+
+ // Create edges
+ InplaceGenerator::CreateEdges(&graph, blocks);
+
+ // Find cycles
+ CycleBreaker cycle_breaker;
+ set<Edge> cut_edges;
+ cycle_breaker.BreakCycles(graph, &cut_edges);
+
+ EXPECT_EQ(1, cut_edges.size());
+ EXPECT_TRUE(cut_edges.end() != cut_edges.find(
+ std::pair<Vertex::Index, Vertex::Index>(1, 0)));
+
+ vector<CutEdgeVertexes> cuts;
+ EXPECT_TRUE(InplaceGenerator::CutEdges(&graph, cut_edges, &cuts));
+
+ EXPECT_EQ(3, graph.size());
+
+ // Check new node in graph:
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE,
+ graph.back().op.type());
+ EXPECT_EQ(2, graph.back().op.src_extents_size());
+ EXPECT_EQ(1, graph.back().op.dst_extents_size());
+ EXPECT_EQ(kTempBlockStart, graph.back().op.dst_extents(0).start_block());
+ EXPECT_EQ(2, graph.back().op.dst_extents(0).num_blocks());
+ EXPECT_TRUE(graph.back().out_edges.empty());
+
+ // Check that old node reads from new blocks
+ EXPECT_EQ(2, graph[0].op.src_extents_size());
+ EXPECT_EQ(kTempBlockStart, graph[0].op.src_extents(0).start_block());
+ EXPECT_EQ(2, graph[0].op.src_extents(0).num_blocks());
+ EXPECT_EQ(7, graph[0].op.src_extents(1).start_block());
+ EXPECT_EQ(1, graph[0].op.src_extents(1).num_blocks());
+
+ // And that the old dst extents haven't changed
+ EXPECT_EQ(2, graph[0].op.dst_extents_size());
+ EXPECT_EQ(1, graph[0].op.dst_extents(0).start_block());
+ EXPECT_EQ(2, graph[0].op.dst_extents(0).num_blocks());
+ EXPECT_EQ(4, graph[0].op.dst_extents(1).start_block());
+ EXPECT_EQ(1, graph[0].op.dst_extents(1).num_blocks());
+
+ // Ensure it only depends on the next node and the new temp node
+ EXPECT_EQ(2, graph[0].out_edges.size());
+ EXPECT_TRUE(graph[0].out_edges.end() != graph[0].out_edges.find(1));
+ EXPECT_TRUE(graph[0].out_edges.end() != graph[0].out_edges.find(graph.size() -
+ 1));
+
+ // Check second node has unchanged extents
+ EXPECT_EQ(2, graph[1].op.src_extents_size());
+ EXPECT_EQ(1, graph[1].op.src_extents(0).start_block());
+ EXPECT_EQ(2, graph[1].op.src_extents(0).num_blocks());
+ EXPECT_EQ(4, graph[1].op.src_extents(1).start_block());
+ EXPECT_EQ(1, graph[1].op.src_extents(1).num_blocks());
+
+ EXPECT_EQ(2, graph[1].op.dst_extents_size());
+ EXPECT_EQ(3, graph[1].op.dst_extents(0).start_block());
+ EXPECT_EQ(1, graph[1].op.dst_extents(0).num_blocks());
+ EXPECT_EQ(5, graph[1].op.dst_extents(1).start_block());
+ EXPECT_EQ(2, graph[1].op.dst_extents(1).num_blocks());
+
+ // Ensure it only depends on the next node
+ EXPECT_EQ(1, graph[1].out_edges.size());
+ EXPECT_TRUE(graph[1].out_edges.end() != graph[1].out_edges.find(2));
+}
+
+TEST_F(InplaceGeneratorTest, AssignTempBlocksReuseTest) {
+ Graph graph(9);
+
+ const vector<Extent> empt;
+ uint64_t tmp = kTempBlockStart;
+ const string kFilename = "/foo";
+
+ vector<CutEdgeVertexes> cuts;
+ cuts.resize(3);
+
+ // Simple broken loop:
+ GenVertex(&graph[0], VectOfExt(0, 1), VectOfExt(1, 1), "", OP_MOVE);
+ GenVertex(&graph[1], VectOfExt(tmp, 1), VectOfExt(0, 1), "", OP_MOVE);
+ GenVertex(&graph[2], VectOfExt(1, 1), VectOfExt(tmp, 1), "", OP_MOVE);
+ // Corresponding edges:
+ graph[0].out_edges[2] = EdgeWithReadDep(VectOfExt(1, 1));
+ graph[1].out_edges[2] = EdgeWithWriteDep(VectOfExt(tmp, 1));
+ graph[1].out_edges[0] = EdgeWithReadDep(VectOfExt(0, 1));
+ // Store the cut:
+ cuts[0].old_dst = 1;
+ cuts[0].old_src = 0;
+ cuts[0].new_vertex = 2;
+ cuts[0].tmp_extents = VectOfExt(tmp, 1);
+ tmp++;
+
+ // Slightly more complex pair of loops:
+ GenVertex(&graph[3], VectOfExt(4, 2), VectOfExt(2, 2), "", OP_MOVE);
+ GenVertex(&graph[4], VectOfExt(6, 1), VectOfExt(7, 1), "", OP_MOVE);
+ GenVertex(&graph[5], VectOfExt(tmp, 3), VectOfExt(4, 3), kFilename, OP_MOVE);
+ GenVertex(&graph[6], VectOfExt(2, 2), VectOfExt(tmp, 2), "", OP_MOVE);
+ GenVertex(&graph[7], VectOfExt(7, 1), VectOfExt(tmp + 2, 1), "", OP_MOVE);
+ // Corresponding edges:
+ graph[3].out_edges[6] = EdgeWithReadDep(VectOfExt(2, 2));
+ graph[4].out_edges[7] = EdgeWithReadDep(VectOfExt(7, 1));
+ graph[5].out_edges[6] = EdgeWithWriteDep(VectOfExt(tmp, 2));
+ graph[5].out_edges[7] = EdgeWithWriteDep(VectOfExt(tmp + 2, 1));
+ graph[5].out_edges[3] = EdgeWithReadDep(VectOfExt(4, 2));
+ graph[5].out_edges[4] = EdgeWithReadDep(VectOfExt(6, 1));
+ // Store the cuts:
+ cuts[1].old_dst = 5;
+ cuts[1].old_src = 3;
+ cuts[1].new_vertex = 6;
+ cuts[1].tmp_extents = VectOfExt(tmp, 2);
+ cuts[2].old_dst = 5;
+ cuts[2].old_src = 4;
+ cuts[2].new_vertex = 7;
+ cuts[2].tmp_extents = VectOfExt(tmp + 2, 1);
+
+ // Supplier of temp block:
+ GenVertex(&graph[8], empt, VectOfExt(8, 1), "", OP_REPLACE);
+
+ // Specify the final order:
+ vector<Vertex::Index> op_indexes;
+ op_indexes.push_back(2);
+ op_indexes.push_back(0);
+ op_indexes.push_back(1);
+ op_indexes.push_back(6);
+ op_indexes.push_back(3);
+ op_indexes.push_back(7);
+ op_indexes.push_back(4);
+ op_indexes.push_back(5);
+ op_indexes.push_back(8);
+
+ vector<vector<Vertex::Index>::size_type> reverse_op_indexes;
+ InplaceGenerator::GenerateReverseTopoOrderMap(op_indexes,
+ &reverse_op_indexes);
+
+ int fd;
+ EXPECT_TRUE(utils::MakeTempFile("AssignTempBlocksReuseTest.XXXXXX",
+ nullptr,
+ &fd));
+ ScopedFdCloser fd_closer(&fd);
+ off_t data_file_size = 0;
+
+ EXPECT_TRUE(InplaceGenerator::AssignTempBlocks(&graph,
+ "/dev/zero",
+ fd,
+ &data_file_size,
+ &op_indexes,
+ &reverse_op_indexes,
+ cuts));
+ EXPECT_FALSE(graph[6].valid);
+ EXPECT_FALSE(graph[7].valid);
+ EXPECT_EQ(1, graph[1].op.src_extents_size());
+ EXPECT_EQ(2, graph[1].op.src_extents(0).start_block());
+ EXPECT_EQ(1, graph[1].op.src_extents(0).num_blocks());
+ EXPECT_EQ(OP_REPLACE_BZ, graph[5].op.type());
+}
+
+TEST_F(InplaceGeneratorTest, MoveFullOpsToBackTest) {
+ Graph graph(4);
+ graph[0].file_name = "A";
+ graph[0].op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+ graph[1].file_name = "B";
+ graph[1].op.set_type(DeltaArchiveManifest_InstallOperation_Type_BSDIFF);
+ graph[2].file_name = "C";
+ graph[2].op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+ graph[3].file_name = "D";
+ graph[3].op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+
+ vector<Vertex::Index> vect(graph.size());
+
+ for (vector<Vertex::Index>::size_type i = 0; i < vect.size(); ++i) {
+ vect[i] = i;
+ }
+ InplaceGenerator::MoveFullOpsToBack(&graph, &vect);
+ EXPECT_EQ(vect.size(), graph.size());
+ EXPECT_EQ(graph[vect[0]].file_name, "B");
+ EXPECT_EQ(graph[vect[1]].file_name, "D");
+ EXPECT_EQ(graph[vect[2]].file_name, "A");
+ EXPECT_EQ(graph[vect[3]].file_name, "C");
+}
+
+TEST_F(InplaceGeneratorTest, AssignTempBlocksTest) {
+ Graph graph(9);
+ const vector<Extent> empt; // empty
+ const string kFilename = "/foo";
+
+ // Some scratch space:
+ GenVertex(&graph[0], empt, VectOfExt(200, 1), "", OP_REPLACE);
+ GenVertex(&graph[1], empt, VectOfExt(210, 10), "", OP_REPLACE);
+ GenVertex(&graph[2], empt, VectOfExt(220, 1), "", OP_REPLACE);
+
+ // A cycle that requires 10 blocks to break:
+ GenVertex(&graph[3], VectOfExt(10, 11), VectOfExt(0, 9), "", OP_BSDIFF);
+ graph[3].out_edges[4] = EdgeWithReadDep(VectOfExt(0, 9));
+ GenVertex(&graph[4], VectOfExt(0, 9), VectOfExt(10, 11), "", OP_BSDIFF);
+ graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11));
+
+ // A cycle that requires 9 blocks to break:
+ GenVertex(&graph[5], VectOfExt(40, 11), VectOfExt(30, 10), "", OP_BSDIFF);
+ graph[5].out_edges[6] = EdgeWithReadDep(VectOfExt(30, 10));
+ GenVertex(&graph[6], VectOfExt(30, 10), VectOfExt(40, 11), "", OP_BSDIFF);
+ graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11));
+
+ // A cycle that requires 40 blocks to break (which is too many):
+ GenVertex(&graph[7],
+ VectOfExt(120, 50),
+ VectOfExt(60, 40),
+ "",
+ OP_BSDIFF);
+ graph[7].out_edges[8] = EdgeWithReadDep(VectOfExt(60, 40));
+ GenVertex(&graph[8],
+ VectOfExt(60, 40),
+ VectOfExt(120, 50),
+ kFilename,
+ OP_BSDIFF);
+ graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50));
+
+ graph_utils::DumpGraph(graph);
+
+ vector<Vertex::Index> final_order;
+
+ int fd;
+ EXPECT_TRUE(utils::MakeTempFile("AssignTempBlocksTestData.XXXXXX",
+ nullptr,
+ &fd));
+ ScopedFdCloser fd_closer(&fd);
+ off_t data_file_size = 0;
+
+ EXPECT_TRUE(InplaceGenerator::ConvertGraphToDag(&graph,
+ "/dev/zero",
+ fd,
+ &data_file_size,
+ &final_order,
+ Vertex::kInvalidIndex));
+
+ Graph expected_graph(12);
+ GenVertex(&expected_graph[0], empt, VectOfExt(200, 1), "", OP_REPLACE);
+ GenVertex(&expected_graph[1], empt, VectOfExt(210, 10), "", OP_REPLACE);
+ GenVertex(&expected_graph[2], empt, VectOfExt(220, 1), "", OP_REPLACE);
+ GenVertex(&expected_graph[3],
+ VectOfExt(10, 11),
+ VectOfExt(0, 9),
+ "",
+ OP_BSDIFF);
+ expected_graph[3].out_edges[9] = EdgeWithReadDep(VectOfExt(0, 9));
+ GenVertex(&expected_graph[4],
+ VectOfExt(60, 9),
+ VectOfExt(10, 11),
+ "",
+ OP_BSDIFF);
+ expected_graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11));
+ expected_graph[4].out_edges[9] = EdgeWithWriteDep(VectOfExt(60, 9));
+ GenVertex(&expected_graph[5],
+ VectOfExt(40, 11),
+ VectOfExt(30, 10),
+ "",
+ OP_BSDIFF);
+ expected_graph[5].out_edges[10] = EdgeWithReadDep(VectOfExt(30, 10));
+
+ GenVertex(&expected_graph[6],
+ VectOfExt(60, 10),
+ VectOfExt(40, 11),
+ "",
+ OP_BSDIFF);
+ expected_graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11));
+ expected_graph[6].out_edges[10] = EdgeWithWriteDep(VectOfExt(60, 10));
+
+ GenVertex(&expected_graph[7],
+ VectOfExt(120, 50),
+ VectOfExt(60, 40),
+ "",
+ OP_BSDIFF);
+ expected_graph[7].out_edges[6] = EdgeWithReadDep(VectOfExt(60, 10));
+
+ GenVertex(&expected_graph[8], empt, VectOfExt(0, 50), "/foo", OP_REPLACE_BZ);
+ expected_graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50));
+
+ GenVertex(&expected_graph[9],
+ VectOfExt(0, 9),
+ VectOfExt(60, 9),
+ "",
+ OP_MOVE);
+
+ GenVertex(&expected_graph[10],
+ VectOfExt(30, 10),
+ VectOfExt(60, 10),
+ "",
+ OP_MOVE);
+ expected_graph[10].out_edges[4] = EdgeWithReadDep(VectOfExt(60, 9));
+
+ EXPECT_EQ(12, graph.size());
+ EXPECT_FALSE(graph.back().valid);
+ for (Graph::size_type i = 0; i < graph.size() - 1; i++) {
+ EXPECT_TRUE(graph[i].out_edges == expected_graph[i].out_edges);
+ if (i == 8) {
+ // special case
+ } else {
+ // EXPECT_TRUE(graph[i] == expected_graph[i]) << "i = " << i;
+ }
+ }
+}
+
+TEST_F(InplaceGeneratorTest, CreateScratchNodeTest) {
+ Vertex vertex;
+ InplaceGenerator::CreateScratchNode(12, 34, &vertex);
+ EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ,
+ vertex.op.type());
+ EXPECT_EQ(0, vertex.op.data_offset());
+ EXPECT_EQ(0, vertex.op.data_length());
+ EXPECT_EQ(1, vertex.op.dst_extents_size());
+ EXPECT_EQ(12, vertex.op.dst_extents(0).start_block());
+ EXPECT_EQ(34, vertex.op.dst_extents(0).num_blocks());
+}
+
+TEST_F(InplaceGeneratorTest, ApplyMapTest) {
+ vector<uint64_t> collection = {1, 2, 3, 4, 6};
+ vector<uint64_t> expected_values = {1, 2, 5, 4, 8};
+ map<uint64_t, uint64_t> value_map;
+ value_map[3] = 5;
+ value_map[6] = 8;
+ value_map[5] = 10;
+
+ InplaceGenerator::ApplyMap(&collection, value_map);
+ EXPECT_EQ(expected_values, collection);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/operations_generator.h b/payload_generator/operations_generator.h
new file mode 100644
index 0000000..31d57b3
--- /dev/null
+++ b/payload_generator/operations_generator.h
@@ -0,0 +1,48 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_OPERATIONS_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_OPERATIONS_GENERATOR_H_
+
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+class OperationsGenerator {
+ public:
+ virtual ~OperationsGenerator() = default;
+
+ // This method generates two lists of operations, one for the rootfs and one
+ // for the kernel and stores the generated operations in |rootfs_ops| and
+ // |kernel_ops| respectivelly. These operations are generated based on the
+ // given |config|. The operations should be applied in the order specified in
+ // the list, and they respect the payload version and type (delta or full)
+ // specified in |config|.
+ // The operations generated will refer to offsets in the file |data_file_fd|,
+ // where this function stores the output, but not necessarily in the same
+ // order as they appear in the |rootfs_ops| and |kernel_ops|.
+ // This function stores the amount of data written to |data_file_fd| in
+ // |data_file_size|.
+ virtual bool GenerateOperations(
+ const PayloadGenerationConfig& config,
+ int data_file_fd,
+ off_t* data_file_size,
+ std::vector<AnnotatedOperation>* rootfs_ops,
+ std::vector<AnnotatedOperation>* kernel_ops) = 0;
+
+ protected:
+ OperationsGenerator() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(OperationsGenerator);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_OPERATIONS_GENERATOR_H_
diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc
new file mode 100644
index 0000000..fe453a2
--- /dev/null
+++ b/payload_generator/payload_file.cc
@@ -0,0 +1,319 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/payload_file.h"
+
+#include <algorithm>
+
+#include "update_engine/file_writer.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/payload_signer.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+const uint64_t kMajorVersionNumber = 1;
+
+static const char* kInstallOperationTypes[] = {
+ "REPLACE",
+ "REPLACE_BZ",
+ "MOVE",
+ "BSDIFF",
+ "SOURCE_COPY",
+ "SOURCE_BSDIFF"
+};
+
+struct DeltaObject {
+ DeltaObject(const string& in_name, const int in_type, const off_t in_size)
+ : name(in_name),
+ type(in_type),
+ size(in_size) {}
+ bool operator <(const DeltaObject& object) const {
+ return (size != object.size) ? (size < object.size) : (name < object.name);
+ }
+ string name;
+ int type;
+ off_t size;
+};
+
+// Writes the uint64_t passed in in host-endian to the file as big-endian.
+// Returns true on success.
+bool WriteUint64AsBigEndian(FileWriter* writer, const uint64_t value) {
+ uint64_t value_be = htobe64(value);
+ TEST_AND_RETURN_FALSE(writer->Write(&value_be, sizeof(value_be)));
+ return true;
+}
+
+} // namespace
+
+const vector<PartitionName> PayloadFile::partition_disk_order_ = {
+ PartitionName::kRootfs,
+ PartitionName::kKernel,
+};
+
+bool PayloadFile::Init(const PayloadGenerationConfig& config) {
+ manifest_.set_minor_version(config.minor_version);
+
+ if (!config.source.ImageInfoIsEmpty())
+ *(manifest_.mutable_old_image_info()) = config.source.image_info;
+
+ if (!config.target.ImageInfoIsEmpty())
+ *(manifest_.mutable_new_image_info()) = config.target.image_info;
+
+ manifest_.set_block_size(config.block_size);
+
+ // Initialize the PartitionInfo objects if present.
+ if (!config.source.kernel.path.empty()) {
+ TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(
+ config.source.kernel,
+ manifest_.mutable_old_kernel_info()));
+ }
+ TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(
+ config.target.kernel,
+ manifest_.mutable_new_kernel_info()));
+ if (!config.source.rootfs.path.empty()) {
+ TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(
+ config.source.rootfs,
+ manifest_.mutable_old_rootfs_info()));
+ }
+ TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(
+ config.target.rootfs,
+ manifest_.mutable_new_rootfs_info()));
+ return true;
+}
+
+void PayloadFile::AddPartitionOperations(
+ PartitionName name,
+ const vector<AnnotatedOperation>& aops) {
+ aops_map_[name].insert(aops_map_[name].end(), aops.begin(), aops.end());
+}
+
+bool PayloadFile::WritePayload(const string& payload_file,
+ const string& data_blobs_path,
+ const string& private_key_path,
+ uint64_t* medatata_size_out) {
+ // Reorder the data blobs with the manifest_.
+ string ordered_blobs_path;
+ TEST_AND_RETURN_FALSE(utils::MakeTempFile(
+ "CrAU_temp_data.ordered.XXXXXX",
+ &ordered_blobs_path,
+ nullptr));
+ ScopedPathUnlinker ordered_blobs_unlinker(ordered_blobs_path);
+ TEST_AND_RETURN_FALSE(ReorderDataBlobs(data_blobs_path, ordered_blobs_path));
+
+ // Copy the operations from the aops_map_ to the manifest.
+ manifest_.clear_install_operations();
+ manifest_.clear_kernel_install_operations();
+ for (PartitionName name : partition_disk_order_) {
+ for (const AnnotatedOperation& aop : aops_map_[name]) {
+ if (name == PartitionName::kKernel) {
+ *manifest_.add_kernel_install_operations() = aop.op;
+ } else {
+ *manifest_.add_install_operations() = aop.op;
+ }
+ }
+ }
+
+ // Check that install op blobs are in order.
+ uint64_t next_blob_offset = 0;
+ {
+ for (int i = 0; i < (manifest_.install_operations_size() +
+ manifest_.kernel_install_operations_size()); i++) {
+ DeltaArchiveManifest_InstallOperation* op =
+ i < manifest_.install_operations_size() ?
+ manifest_.mutable_install_operations(i) :
+ manifest_.mutable_kernel_install_operations(
+ i - manifest_.install_operations_size());
+ if (op->has_data_offset()) {
+ if (op->data_offset() != next_blob_offset) {
+ LOG(FATAL) << "bad blob offset! " << op->data_offset() << " != "
+ << next_blob_offset;
+ }
+ next_blob_offset += op->data_length();
+ }
+ }
+ }
+
+ // Signatures appear at the end of the blobs. Note the offset in the
+ // manifest_.
+ if (!private_key_path.empty()) {
+ uint64_t signature_blob_length = 0;
+ TEST_AND_RETURN_FALSE(
+ PayloadSigner::SignatureBlobLength(vector<string>(1, private_key_path),
+ &signature_blob_length));
+ AddSignatureOp(next_blob_offset, signature_blob_length, &manifest_);
+ }
+
+ // Serialize protobuf
+ string serialized_manifest;
+ TEST_AND_RETURN_FALSE(manifest_.AppendToString(&serialized_manifest));
+
+ LOG(INFO) << "Writing final delta file header...";
+ DirectFileWriter writer;
+ TEST_AND_RETURN_FALSE_ERRNO(writer.Open(payload_file.c_str(),
+ O_WRONLY | O_CREAT | O_TRUNC,
+ 0644) == 0);
+ ScopedFileWriterCloser writer_closer(&writer);
+
+ // Write header
+ TEST_AND_RETURN_FALSE(writer.Write(kDeltaMagic, strlen(kDeltaMagic)));
+
+ // Write major version number
+ TEST_AND_RETURN_FALSE(WriteUint64AsBigEndian(&writer, kMajorVersionNumber));
+
+ // Write protobuf length
+ TEST_AND_RETURN_FALSE(WriteUint64AsBigEndian(&writer,
+ serialized_manifest.size()));
+
+ // Write protobuf
+ LOG(INFO) << "Writing final delta file protobuf... "
+ << serialized_manifest.size();
+ TEST_AND_RETURN_FALSE(writer.Write(serialized_manifest.data(),
+ serialized_manifest.size()));
+
+ // Append the data blobs
+ LOG(INFO) << "Writing final delta file data blobs...";
+ int blobs_fd = open(ordered_blobs_path.c_str(), O_RDONLY, 0);
+ ScopedFdCloser blobs_fd_closer(&blobs_fd);
+ TEST_AND_RETURN_FALSE(blobs_fd >= 0);
+ for (;;) {
+ vector<char> buf(1024 * 1024);
+ ssize_t rc = read(blobs_fd, buf.data(), buf.size());
+ if (0 == rc) {
+ // EOF
+ break;
+ }
+ TEST_AND_RETURN_FALSE_ERRNO(rc > 0);
+ TEST_AND_RETURN_FALSE(writer.Write(buf.data(), rc));
+ }
+
+ // Write signature blob.
+ if (!private_key_path.empty()) {
+ LOG(INFO) << "Signing the update...";
+ chromeos::Blob signature_blob;
+ TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(
+ payload_file,
+ vector<string>(1, private_key_path),
+ &signature_blob));
+ TEST_AND_RETURN_FALSE(writer.Write(signature_blob.data(),
+ signature_blob.size()));
+ }
+
+ *medatata_size_out =
+ strlen(kDeltaMagic) + 2 * sizeof(uint64_t) + serialized_manifest.size();
+ ReportPayloadUsage(*medatata_size_out);
+ return true;
+}
+
+bool PayloadFile::ReorderDataBlobs(
+ const string& data_blobs_path,
+ const string& new_data_blobs_path) {
+ int in_fd = open(data_blobs_path.c_str(), O_RDONLY, 0);
+ TEST_AND_RETURN_FALSE_ERRNO(in_fd >= 0);
+ ScopedFdCloser in_fd_closer(&in_fd);
+
+ DirectFileWriter writer;
+ TEST_AND_RETURN_FALSE(
+ writer.Open(new_data_blobs_path.c_str(),
+ O_WRONLY | O_TRUNC | O_CREAT,
+ 0644) == 0);
+ ScopedFileWriterCloser writer_closer(&writer);
+ uint64_t out_file_size = 0;
+
+ for (PartitionName name : partition_disk_order_) {
+ for (AnnotatedOperation& aop : aops_map_[name]) {
+ if (!aop.op.has_data_offset())
+ continue;
+ CHECK(aop.op.has_data_length());
+ chromeos::Blob buf(aop.op.data_length());
+ ssize_t rc = pread(in_fd, buf.data(), buf.size(), aop.op.data_offset());
+ TEST_AND_RETURN_FALSE(rc == static_cast<ssize_t>(buf.size()));
+
+ // Add the hash of the data blobs for this operation
+ TEST_AND_RETURN_FALSE(AddOperationHash(&aop.op, buf));
+
+ aop.op.set_data_offset(out_file_size);
+ TEST_AND_RETURN_FALSE(writer.Write(buf.data(), buf.size()));
+ out_file_size += buf.size();
+ }
+ }
+ return true;
+}
+
+bool PayloadFile::AddOperationHash(
+ DeltaArchiveManifest_InstallOperation* op,
+ const chromeos::Blob& buf) {
+ OmahaHashCalculator hasher;
+ TEST_AND_RETURN_FALSE(hasher.Update(buf.data(), buf.size()));
+ TEST_AND_RETURN_FALSE(hasher.Finalize());
+ const chromeos::Blob& hash = hasher.raw_hash();
+ op->set_data_sha256_hash(hash.data(), hash.size());
+ return true;
+}
+
+void PayloadFile::ReportPayloadUsage(uint64_t metadata_size) const {
+ vector<DeltaObject> objects;
+ off_t total_size = 0;
+
+ for (PartitionName name : partition_disk_order_) {
+ const auto& partition_aops = aops_map_.find(name);
+ if (partition_aops == aops_map_.end())
+ continue;
+ for (const AnnotatedOperation& aop : partition_aops->second) {
+ objects.push_back(DeltaObject(aop.name,
+ aop.op.type(),
+ aop.op.data_length()));
+ total_size += aop.op.data_length();
+ }
+ }
+
+ objects.push_back(DeltaObject("<manifest-metadata>",
+ -1,
+ metadata_size));
+ total_size += metadata_size;
+
+ std::sort(objects.begin(), objects.end());
+
+ static const char kFormatString[] = "%6.2f%% %10jd %-10s %s\n";
+ for (const DeltaObject& object : objects) {
+ fprintf(stderr, kFormatString,
+ object.size * 100.0 / total_size,
+ static_cast<intmax_t>(object.size),
+ object.type >= 0 ? kInstallOperationTypes[object.type] : "-",
+ object.name.c_str());
+ }
+ fprintf(stderr, kFormatString,
+ 100.0, static_cast<intmax_t>(total_size), "", "<total>");
+}
+
+void AddSignatureOp(uint64_t signature_blob_offset,
+ uint64_t signature_blob_length,
+ DeltaArchiveManifest* manifest) {
+ LOG(INFO) << "Making room for signature in file";
+ manifest->set_signatures_offset(signature_blob_offset);
+ LOG(INFO) << "set? " << manifest->has_signatures_offset();
+ // Add a dummy op at the end to appease older clients
+ DeltaArchiveManifest_InstallOperation* dummy_op =
+ manifest->add_kernel_install_operations();
+ dummy_op->set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+ dummy_op->set_data_offset(signature_blob_offset);
+ manifest->set_signatures_offset(signature_blob_offset);
+ dummy_op->set_data_length(signature_blob_length);
+ manifest->set_signatures_size(signature_blob_length);
+ Extent* dummy_extent = dummy_op->add_dst_extents();
+ // Tell the dummy op to write this data to a big sparse hole
+ dummy_extent->set_start_block(kSparseHole);
+ dummy_extent->set_num_blocks((signature_blob_length + kBlockSize - 1) /
+ kBlockSize);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/payload_file.h b/payload_generator/payload_file.h
new file mode 100644
index 0000000..150b0b0
--- /dev/null
+++ b/payload_generator/payload_file.h
@@ -0,0 +1,86 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_FILE_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_FILE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// Class to handle the creation of a payload file. This class is the only one
+// dealing with writing the payload and its format, but has no logic about what
+// should be on it.
+class PayloadFile {
+ public:
+ // Initialize the payload file with the payload generation config. It computes
+ // required hashes of the requested partitions.
+ bool Init(const PayloadGenerationConfig& config);
+
+ // Sets the list of operations to the payload manifest. The operations
+ // reference a blob stored in the file provided to WritePayload().
+ void AddPartitionOperations(PartitionName name,
+ const std::vector<AnnotatedOperation>& aops);
+
+ // Write the payload to the |payload_file| file. The operations reference
+ // blobs in the |data_blobs_path| file and the blobs will be reordered in the
+ // payload file to match the order of the operations. The size of the metadata
+ // section of the payload is stored in |metadata_size_out|.
+ bool WritePayload(const std::string& payload_file,
+ const std::string& data_blobs_path,
+ const std::string& private_key_path,
+ uint64_t* medatata_size_out);
+
+ private:
+ FRIEND_TEST(PayloadFileTest, ReorderBlobsTest);
+
+ // Computes a SHA256 hash of the given buf and sets the hash value in the
+ // operation so that update_engine could verify. This hash should be set
+ // for all operations that have a non-zero data blob. One exception is the
+ // dummy operation for signature blob because the contents of the signature
+ // blob will not be available at payload creation time. So, update_engine will
+ // gracefully ignore the dummy signature operation.
+ static bool AddOperationHash(DeltaArchiveManifest_InstallOperation* op,
+ const chromeos::Blob& buf);
+
+ // Install operations in the manifest may reference data blobs, which
+ // are in data_blobs_path. This function creates a new data blobs file
+ // with the data blobs in the same order as the referencing install
+ // operations in the manifest. E.g. if manifest[0] has a data blob
+ // "X" at offset 1, manifest[1] has a data blob "Y" at offset 0,
+ // and data_blobs_path's file contains "YX", new_data_blobs_path
+ // will set to be a file that contains "XY".
+ bool ReorderDataBlobs(const std::string& data_blobs_path,
+ const std::string& new_data_blobs_path);
+
+ // Print in stderr the Payload usage report.
+ void ReportPayloadUsage(uint64_t metadata_size) const;
+
+ // The list of partitions in the order their blobs should appear in the
+ // payload file.
+ static const std::vector<PartitionName> partition_disk_order_;
+
+ DeltaArchiveManifest manifest_;
+
+ std::map<PartitionName, std::vector<AnnotatedOperation>> aops_map_;
+};
+
+// Adds a dummy operation that points to a signature blob located at the
+// specified offset/length.
+void AddSignatureOp(uint64_t signature_blob_offset,
+ uint64_t signature_blob_length,
+ DeltaArchiveManifest* manifest);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_FILE_H_
diff --git a/payload_generator/payload_file_unittest.cc b/payload_generator/payload_file_unittest.cc
new file mode 100644
index 0000000..1d9661f
--- /dev/null
+++ b/payload_generator/payload_file_unittest.cc
@@ -0,0 +1,84 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/payload_file.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/test_utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class PayloadFileTest : public ::testing::Test {
+ protected:
+ PayloadFile payload_;
+};
+
+TEST_F(PayloadFileTest, ReorderBlobsTest) {
+ string orig_blobs;
+ EXPECT_TRUE(utils::MakeTempFile("ReorderBlobsTest.orig.XXXXXX", &orig_blobs,
+ nullptr));
+ ScopedPathUnlinker orig_blobs_unlinker(orig_blobs);
+
+ // The operations have three blob and one gap (the whitespace):
+ // Rootfs operation 1: [8, 3] bcd
+ // Rootfs operation 2: [7, 1] a
+ // Kernel operation 1: [0, 6] kernel
+ string orig_data = "kernel abcd";
+ EXPECT_TRUE(
+ utils::WriteFile(orig_blobs.c_str(), orig_data.data(), orig_data.size()));
+
+ string new_blobs;
+ EXPECT_TRUE(
+ utils::MakeTempFile("ReorderBlobsTest.new.XXXXXX", &new_blobs, nullptr));
+ ScopedPathUnlinker new_blobs_unlinker(new_blobs);
+
+ vector<AnnotatedOperation> aops;
+ AnnotatedOperation aop;
+ aop.op.set_data_offset(8);
+ aop.op.set_data_length(3);
+ aops.push_back(aop);
+
+ aop.op.set_data_offset(7);
+ aop.op.set_data_length(1);
+ aops.push_back(aop);
+ payload_.AddPartitionOperations(PartitionName::kRootfs, aops);
+
+ aop.op.set_data_offset(0);
+ aop.op.set_data_length(6);
+ aops = {aop};
+ payload_.AddPartitionOperations(PartitionName::kKernel, aops);
+
+ EXPECT_TRUE(payload_.ReorderDataBlobs(orig_blobs, new_blobs));
+
+ const vector<AnnotatedOperation>& rootfs_aops =
+ payload_.aops_map_[PartitionName::kRootfs];
+ const vector<AnnotatedOperation>& kernel_aops =
+ payload_.aops_map_[PartitionName::kKernel];
+ string new_data;
+ EXPECT_TRUE(utils::ReadFile(new_blobs, &new_data));
+ // Kernel blobs should appear at the end.
+ EXPECT_EQ("bcdakernel", new_data);
+
+ EXPECT_EQ(2, rootfs_aops.size());
+ EXPECT_EQ(0, rootfs_aops[0].op.data_offset());
+ EXPECT_EQ(3, rootfs_aops[0].op.data_length());
+ EXPECT_EQ(3, rootfs_aops[1].op.data_offset());
+ EXPECT_EQ(1, rootfs_aops[1].op.data_length());
+
+ EXPECT_EQ(1, kernel_aops.size());
+ EXPECT_EQ(4, kernel_aops[0].op.data_offset());
+ EXPECT_EQ(6, kernel_aops[0].op.data_length());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc
new file mode 100644
index 0000000..d58c9a1
--- /dev/null
+++ b/payload_generator/payload_generation_config.cc
@@ -0,0 +1,152 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+#include <base/logging.h>
+
+#include "update_engine/delta_performer.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/ext2_filesystem.h"
+#include "update_engine/payload_generator/raw_filesystem.h"
+#include "update_engine/payload_generator/verity_utils.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+bool PartitionConfig::ValidateExists() const {
+ TEST_AND_RETURN_FALSE(!path.empty());
+ TEST_AND_RETURN_FALSE(utils::FileExists(path.c_str()));
+ TEST_AND_RETURN_FALSE(size > 0);
+ // The requested size is within the limits of the file.
+ TEST_AND_RETURN_FALSE(static_cast<off_t>(size) <=
+ utils::FileSize(path.c_str()));
+ return true;
+}
+
+bool PartitionConfig::OpenFilesystem() {
+ if (path.empty())
+ return true;
+ fs_interface.reset();
+ if (name == PartitionName::kRootfs) {
+ fs_interface = Ext2Filesystem::CreateFromFile(path);
+ }
+
+ if (!fs_interface) {
+ // Fall back to a RAW filesystem.
+ TEST_AND_RETURN_FALSE(size % kBlockSize == 0);
+ std::string str_name = "other";
+ switch (name) {
+ case PartitionName::kKernel:
+ str_name = "kernel";
+ break;
+ case PartitionName::kRootfs:
+ str_name = "rootfs";
+ break;
+ }
+ fs_interface = RawFilesystem::Create(
+ "<" + str_name + "-partition>",
+ kBlockSize,
+ size / kBlockSize);
+ }
+ return true;
+}
+
+bool ImageConfig::ValidateIsEmpty() const {
+ TEST_AND_RETURN_FALSE(ImageInfoIsEmpty());
+
+ TEST_AND_RETURN_FALSE(rootfs.path.empty());
+ TEST_AND_RETURN_FALSE(rootfs.size == 0);
+ TEST_AND_RETURN_FALSE(kernel.path.empty());
+ TEST_AND_RETURN_FALSE(kernel.size == 0);
+ return true;
+}
+
+bool ImageConfig::LoadImageSize() {
+ TEST_AND_RETURN_FALSE(!rootfs.path.empty());
+ int rootfs_block_count, rootfs_block_size;
+ TEST_AND_RETURN_FALSE(utils::GetFilesystemSize(rootfs.path,
+ &rootfs_block_count,
+ &rootfs_block_size));
+ rootfs.size = static_cast<uint64_t>(rootfs_block_count) * rootfs_block_size;
+ if (!kernel.path.empty())
+ kernel.size = utils::FileSize(kernel.path);
+
+ // TODO(deymo): The delta generator algorithm doesn't support a block size
+ // different than 4 KiB. Remove this check once that's fixed. crbug.com/455045
+ if (rootfs_block_size != 4096) {
+ LOG(ERROR) << "The filesystem provided in " << rootfs.path
+ << " has a block size of " << rootfs_block_size
+ << " but delta_generator only supports 4096.";
+ return false;
+ }
+ return true;
+}
+
+bool ImageConfig::LoadVerityRootfsSize() {
+ if (kernel.path.empty())
+ return false;
+ uint64_t verity_rootfs_size = 0;
+ if (!GetVerityRootfsSize(kernel.path, &verity_rootfs_size)) {
+ LOG(INFO) << "Couldn't find verity options in source kernel config, will "
+ << "use the rootfs filesystem size instead: " << rootfs.size;
+ return false;
+ }
+ if (rootfs.size != verity_rootfs_size) {
+ LOG(WARNING) << "Using the rootfs size found in the kernel config ("
+ << verity_rootfs_size << ") instead of the rootfs filesystem "
+ << " size (" << rootfs.size << ").";
+ rootfs.size = verity_rootfs_size;
+ }
+ return true;
+}
+
+bool ImageConfig::ImageInfoIsEmpty() const {
+ return image_info.board().empty()
+ && image_info.key().empty()
+ && image_info.channel().empty()
+ && image_info.version().empty()
+ && image_info.build_channel().empty()
+ && image_info.build_version().empty();
+}
+
+bool PayloadGenerationConfig::Validate() const {
+ if (is_delta) {
+ TEST_AND_RETURN_FALSE(source.rootfs.ValidateExists());
+ TEST_AND_RETURN_FALSE(source.rootfs.size % block_size == 0);
+
+ if (!source.kernel.path.empty()) {
+ TEST_AND_RETURN_FALSE(source.kernel.ValidateExists());
+ TEST_AND_RETURN_FALSE(source.kernel.size % block_size == 0);
+ }
+
+ // Check for the supported minor_version values.
+ TEST_AND_RETURN_FALSE(minor_version == kInPlaceMinorPayloadVersion ||
+ minor_version == kSourceMinorPayloadVersion);
+
+ // If new_image_info is present, old_image_info must be present.
+ TEST_AND_RETURN_FALSE(source.ImageInfoIsEmpty() ==
+ target.ImageInfoIsEmpty());
+ } else {
+ // All the "source" image fields must be empty for full payloads.
+ TEST_AND_RETURN_FALSE(source.ValidateIsEmpty());
+ TEST_AND_RETURN_FALSE(minor_version ==
+ DeltaPerformer::kFullPayloadMinorVersion);
+ }
+
+ // In all cases, the target image must exists.
+ TEST_AND_RETURN_FALSE(target.rootfs.ValidateExists());
+ TEST_AND_RETURN_FALSE(target.kernel.ValidateExists());
+ TEST_AND_RETURN_FALSE(target.rootfs.size % block_size == 0);
+ TEST_AND_RETURN_FALSE(target.kernel.size % block_size == 0);
+
+ TEST_AND_RETURN_FALSE(chunk_size == -1 || chunk_size % block_size == 0);
+
+ TEST_AND_RETURN_FALSE(rootfs_partition_size % block_size == 0);
+ TEST_AND_RETURN_FALSE(rootfs_partition_size >= target.rootfs.size);
+
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h
new file mode 100644
index 0000000..f18055f
--- /dev/null
+++ b/payload_generator/payload_generation_config.h
@@ -0,0 +1,128 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
+
+#include <cstddef>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// The list different kind of partitions supported by the updater.
+enum class PartitionName {
+ kKernel,
+ kRootfs,
+};
+
+struct PartitionConfig {
+ explicit PartitionConfig(PartitionName name) : name(name) {}
+
+ // Returns whether the PartitionConfig is not an empty image and all the
+ // fields are set correctly to a valid image file.
+ bool ValidateExists() const;
+
+ // Open then filesystem stored in this partition and stores it in
+ // |fs_interface|. Returns whether opening the filesystem worked.
+ bool OpenFilesystem();
+
+ // The path to the partition file. This can be a regular file or a block
+ // device such as a loop device.
+ std::string path;
+
+ // The size of the data in |path|. If rootfs verification is used (verity)
+ // this value should match the size of the verity device for the rootfs, and
+ // the size of the whole kernel. This value could be smaller than the
+ // partition and is the size of the data update_engine assumes verified for
+ // the source image, and the size of that data it should generate for the
+ // target image.
+ uint64_t size = 0;
+
+ // The FilesystemInterface implementation used to access this partition's
+ // files.
+ std::unique_ptr<FilesystemInterface> fs_interface;
+
+ PartitionName name;
+};
+
+// The ImageConfig struct describes a pair of binaries kernel and rootfs and the
+// metadata associated with the image they are part of, like build number, size,
+// etc.
+struct ImageConfig {
+ // Returns whether the ImageConfig is an empty image.
+ bool ValidateIsEmpty() const;
+
+ // Load |rootfs_size| and |kernel.size| from the respective image files. For
+ // the kernel, the whole |kernel.path| file is assumed. For the rootfs, the
+ // size is detected from the filesystem.
+ // Returns whether the image size was properly detected.
+ bool LoadImageSize();
+
+ // Load the |rootfs_size| stored in the kernel command line in the
+ // |kernel.path| when the kernel is using rootfs verification (dm-verity).
+ // Returns whether it loaded the size from the kernel command line. For
+ // example, it would return false if no |kernel.path| was provided or the
+ // kernel doesn't have verity enabled.
+ bool LoadVerityRootfsSize();
+
+ // Returns whether the |image_info| field is empty.
+ bool ImageInfoIsEmpty() const;
+
+ // The ImageInfo message defined in the update_metadata.proto file describes
+ // the metadata of the image.
+ ImageInfo image_info;
+
+ // The updated partitions.
+ PartitionConfig rootfs = PartitionConfig{PartitionName::kRootfs};
+ PartitionConfig kernel = PartitionConfig{PartitionName::kKernel};
+};
+
+// The PayloadGenerationConfig struct encapsulates all the configuration to
+// build the requested payload. This includes information about the old and new
+// image as well as the restrictions applied to the payload (like minor-version
+// and full/delta payload).
+struct PayloadGenerationConfig {
+ // Returns whether the PayloadGenerationConfig is valid.
+ bool Validate() const;
+
+ // Image information about the new image that's the target of this payload.
+ ImageConfig target;
+
+ // Image information pertaining the old image, if any. This is only valid
+ // if is_full is false, so we are requested a delta payload.
+ ImageConfig source;
+
+ // Wheter the requested payload is a delta payload.
+ bool is_delta = false;
+
+ // The minor_version of the requested payload.
+ uint32_t minor_version;
+
+ // The size of the rootfs partition, that not necessarily is the same as the
+ // filesystem in either source or target version, since there is some space
+ // after the partition used to store the verity hashes and or the bootcache.
+ uint64_t rootfs_partition_size = 0;
+
+ // The chunk size is the maximum size that a single operation should write in
+ // the destination. Operations bigger than chunk_size should be split. A value
+ // of -1 means no chunk_size limit.
+ off_t chunk_size = -1;
+
+ // TODO(deymo): Remove the block_size member and maybe replace it with a
+ // minimum alignment size for blocks (if needed). Algorithms should be able to
+ // pick the block_size they want, but for now only 4 KiB is supported.
+
+ // The block size used for all the operations in the manifest.
+ size_t block_size = 4096;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
diff --git a/payload_generator/payload_signer.cc b/payload_generator/payload_signer.cc
new file mode 100644
index 0000000..8c3bc28
--- /dev/null
+++ b/payload_generator/payload_signer.cc
@@ -0,0 +1,322 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/payload_signer.h"
+
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <chromeos/data_encoding.h>
+#include <openssl/pem.h>
+
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/payload_generator/payload_file.h"
+#include "update_engine/payload_verifier.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Given raw |signatures|, packs them into a protobuf and serializes it into a
+// binary blob. Returns true on success, false otherwise.
+bool ConvertSignatureToProtobufBlob(const vector<chromeos::Blob>& signatures,
+ chromeos::Blob* out_signature_blob) {
+ // Pack it into a protobuf
+ Signatures out_message;
+ uint32_t version = kSignatureMessageOriginalVersion;
+ LOG_IF(WARNING, kSignatureMessageCurrentVersion -
+ kSignatureMessageOriginalVersion + 1 < signatures.size())
+ << "You may want to support clients in the range ["
+ << kSignatureMessageOriginalVersion << ", "
+ << kSignatureMessageCurrentVersion << "] inclusive, but you only "
+ << "provided " << signatures.size() << " signatures.";
+ for (const chromeos::Blob& signature : signatures) {
+ Signatures_Signature* sig_message = out_message.add_signatures();
+ sig_message->set_version(version++);
+ sig_message->set_data(signature.data(), signature.size());
+ }
+
+ // Serialize protobuf
+ string serialized;
+ TEST_AND_RETURN_FALSE(out_message.AppendToString(&serialized));
+ out_signature_blob->insert(out_signature_blob->end(),
+ serialized.begin(),
+ serialized.end());
+ LOG(INFO) << "Signature blob size: " << out_signature_blob->size();
+ return true;
+}
+
+// Given an unsigned payload under |payload_path| and the |signature_blob_size|
+// generates an updated payload that includes a dummy signature op in its
+// manifest. It populates |out_metadata_size| with the size of the final
+// manifest after adding the dummy signature operation, and
+// |out_signatures_offset| with the expected offset for the new blob. Returns
+// true on success, false otherwise.
+bool AddSignatureOpToPayload(const string& payload_path,
+ uint64_t signature_blob_size,
+ chromeos::Blob* out_payload,
+ uint64_t* out_metadata_size,
+ uint64_t* out_signatures_offset) {
+ const int kProtobufOffset = 20;
+ const int kProtobufSizeOffset = 12;
+
+ // Loads the payload.
+ chromeos::Blob payload;
+ DeltaArchiveManifest manifest;
+ uint64_t metadata_size;
+ TEST_AND_RETURN_FALSE(PayloadVerifier::LoadPayload(
+ payload_path, &payload, &manifest, &metadata_size));
+
+ // Is there already a signature op in place?
+ if (manifest.has_signatures_size()) {
+ // The signature op is tied to the size of the signature blob, but not it's
+ // contents. We don't allow the manifest to change if there is already an op
+ // present, because that might invalidate previously generated
+ // hashes/signatures.
+ if (manifest.signatures_size() != signature_blob_size) {
+ LOG(ERROR) << "Attempt to insert different signature sized blob. "
+ << "(current:" << manifest.signatures_size()
+ << "new:" << signature_blob_size << ")";
+ return false;
+ }
+
+ LOG(INFO) << "Matching signature sizes already present.";
+ } else {
+ // Updates the manifest to include the signature operation.
+ AddSignatureOp(payload.size() - metadata_size,
+ signature_blob_size,
+ &manifest);
+
+ // Updates the payload to include the new manifest.
+ string serialized_manifest;
+ TEST_AND_RETURN_FALSE(manifest.AppendToString(&serialized_manifest));
+ LOG(INFO) << "Updated protobuf size: " << serialized_manifest.size();
+ payload.erase(payload.begin() + kProtobufOffset,
+ payload.begin() + metadata_size);
+ payload.insert(payload.begin() + kProtobufOffset,
+ serialized_manifest.begin(),
+ serialized_manifest.end());
+
+ // Updates the protobuf size.
+ uint64_t size_be = htobe64(serialized_manifest.size());
+ memcpy(&payload[kProtobufSizeOffset], &size_be, sizeof(size_be));
+ metadata_size = serialized_manifest.size() + kProtobufOffset;
+
+ LOG(INFO) << "Updated payload size: " << payload.size();
+ LOG(INFO) << "Updated metadata size: " << metadata_size;
+ }
+
+ out_payload->swap(payload);
+ *out_metadata_size = metadata_size;
+ *out_signatures_offset = metadata_size + manifest.signatures_offset();
+ LOG(INFO) << "Signature Blob Offset: " << *out_signatures_offset;
+ return true;
+}
+} // namespace
+
+bool PayloadSigner::SignHash(const chromeos::Blob& hash,
+ const string& private_key_path,
+ chromeos::Blob* out_signature) {
+ LOG(INFO) << "Signing hash with private key: " << private_key_path;
+ string sig_path;
+ TEST_AND_RETURN_FALSE(
+ utils::MakeTempFile("signature.XXXXXX", &sig_path, nullptr));
+ ScopedPathUnlinker sig_path_unlinker(sig_path);
+
+ string hash_path;
+ TEST_AND_RETURN_FALSE(
+ utils::MakeTempFile("hash.XXXXXX", &hash_path, nullptr));
+ ScopedPathUnlinker hash_path_unlinker(hash_path);
+ // We expect unpadded SHA256 hash coming in
+ TEST_AND_RETURN_FALSE(hash.size() == 32);
+ chromeos::Blob padded_hash(hash);
+ PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash);
+ TEST_AND_RETURN_FALSE(utils::WriteFile(hash_path.c_str(),
+ padded_hash.data(),
+ padded_hash.size()));
+
+ // This runs on the server, so it's okay to cop out and call openssl
+ // executable rather than properly use the library
+ vector<string> cmd;
+ base::SplitString("openssl rsautl -raw -sign -inkey x -in x -out x",
+ ' ',
+ &cmd);
+ cmd[cmd.size() - 5] = private_key_path;
+ cmd[cmd.size() - 3] = hash_path;
+ cmd[cmd.size() - 1] = sig_path;
+
+ int return_code = 0;
+ TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &return_code,
+ nullptr));
+ TEST_AND_RETURN_FALSE(return_code == 0);
+
+ chromeos::Blob signature;
+ TEST_AND_RETURN_FALSE(utils::ReadFile(sig_path, &signature));
+ out_signature->swap(signature);
+ return true;
+}
+
+bool PayloadSigner::SignPayload(const string& unsigned_payload_path,
+ const vector<string>& private_key_paths,
+ chromeos::Blob* out_signature_blob) {
+ chromeos::Blob hash_data;
+ TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfFile(
+ unsigned_payload_path, -1, &hash_data) ==
+ utils::FileSize(unsigned_payload_path));
+
+ vector<chromeos::Blob> signatures;
+ for (const string& path : private_key_paths) {
+ chromeos::Blob signature;
+ TEST_AND_RETURN_FALSE(SignHash(hash_data, path, &signature));
+ signatures.push_back(signature);
+ }
+ TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
+ out_signature_blob));
+ return true;
+}
+
+bool PayloadSigner::SignatureBlobLength(const vector<string>& private_key_paths,
+ uint64_t* out_length) {
+ DCHECK(out_length);
+
+ string x_path;
+ TEST_AND_RETURN_FALSE(
+ utils::MakeTempFile("signed_data.XXXXXX", &x_path, nullptr));
+ ScopedPathUnlinker x_path_unlinker(x_path);
+ TEST_AND_RETURN_FALSE(utils::WriteFile(x_path.c_str(), "x", 1));
+
+ chromeos::Blob sig_blob;
+ TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(x_path,
+ private_key_paths,
+ &sig_blob));
+ *out_length = sig_blob.size();
+ return true;
+}
+
+bool PayloadSigner::PrepPayloadForHashing(
+ const string& payload_path,
+ const vector<int>& signature_sizes,
+ chromeos::Blob* payload_out,
+ uint64_t* metadata_size_out,
+ uint64_t* signatures_offset_out) {
+ // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory.
+
+ // Loads the payload and adds the signature op to it.
+ vector<chromeos::Blob> signatures;
+ for (int signature_size : signature_sizes) {
+ signatures.emplace_back(signature_size, 0);
+ }
+ chromeos::Blob signature_blob;
+ TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
+ &signature_blob));
+ TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path,
+ signature_blob.size(),
+ payload_out,
+ metadata_size_out,
+ signatures_offset_out));
+
+ return true;
+}
+
+bool PayloadSigner::HashPayloadForSigning(const string& payload_path,
+ const vector<int>& signature_sizes,
+ chromeos::Blob* out_hash_data) {
+ chromeos::Blob payload;
+ uint64_t metadata_size;
+ uint64_t signatures_offset;
+
+ TEST_AND_RETURN_FALSE(PrepPayloadForHashing(payload_path,
+ signature_sizes,
+ &payload,
+ &metadata_size,
+ &signatures_offset));
+
+ // Calculates the hash on the updated payload. Note that we stop calculating
+ // before we reach the signature information.
+ TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(payload.data(),
+ signatures_offset,
+ out_hash_data));
+ return true;
+}
+
+bool PayloadSigner::HashMetadataForSigning(const string& payload_path,
+ const vector<int>& signature_sizes,
+ chromeos::Blob* out_metadata_hash) {
+ chromeos::Blob payload;
+ uint64_t metadata_size;
+ uint64_t signatures_offset;
+
+ TEST_AND_RETURN_FALSE(PrepPayloadForHashing(payload_path,
+ signature_sizes,
+ &payload,
+ &metadata_size,
+ &signatures_offset));
+
+ // Calculates the hash on the manifest.
+ TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(payload.data(),
+ metadata_size,
+ out_metadata_hash));
+ return true;
+}
+
+bool PayloadSigner::AddSignatureToPayload(
+ const string& payload_path,
+ const vector<chromeos::Blob>& signatures,
+ const string& signed_payload_path,
+ uint64_t *out_metadata_size) {
+ // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory.
+
+ // Loads the payload and adds the signature op to it.
+ chromeos::Blob signature_blob;
+ TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
+ &signature_blob));
+ chromeos::Blob payload;
+ uint64_t signatures_offset;
+ TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path,
+ signature_blob.size(),
+ &payload,
+ out_metadata_size,
+ &signatures_offset));
+ // Appends the signature blob to the end of the payload and writes the new
+ // payload.
+ LOG(INFO) << "Payload size before signatures: " << payload.size();
+ payload.resize(signatures_offset);
+ payload.insert(payload.begin() + signatures_offset,
+ signature_blob.begin(),
+ signature_blob.end());
+ LOG(INFO) << "Signed payload size: " << payload.size();
+ TEST_AND_RETURN_FALSE(utils::WriteFile(signed_payload_path.c_str(),
+ payload.data(),
+ payload.size()));
+ return true;
+}
+
+bool PayloadSigner::GetMetadataSignature(const void* const metadata,
+ size_t metadata_size,
+ const string& private_key_path,
+ string* out_signature) {
+ // Calculates the hash on the updated payload. Note that the payload includes
+ // the signature op but doesn't include the signature blob at the end.
+ chromeos::Blob metadata_hash;
+ TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(metadata,
+ metadata_size,
+ &metadata_hash));
+
+ chromeos::Blob signature;
+ TEST_AND_RETURN_FALSE(SignHash(metadata_hash,
+ private_key_path,
+ &signature));
+
+ *out_signature = chromeos::data_encoding::Base64Encode(signature);
+ return true;
+}
+
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/payload_signer.h b/payload_generator/payload_signer.h
new file mode 100644
index 0000000..a17a984
--- /dev/null
+++ b/payload_generator/payload_signer.h
@@ -0,0 +1,110 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// This class encapsulates methods used for payload signing.
+// See update_metadata.proto for more info.
+
+namespace chromeos_update_engine {
+
+class PayloadSigner {
+ public:
+ // Given a raw |hash| and a private key in |private_key_path| calculates the
+ // raw signature in |out_signature|. Returns true on success, false otherwise.
+ static bool SignHash(const chromeos::Blob& hash,
+ const std::string& private_key_path,
+ chromeos::Blob* out_signature);
+
+ // Given an unsigned payload in |unsigned_payload_path| and private keys in
+ // |private_key_path|, calculates the signature blob into
+ // |out_signature_blob|. Note that the payload must already have an updated
+ // manifest that includes the dummy signature op. Returns true on success,
+ // false otherwise.
+ static bool SignPayload(const std::string& unsigned_payload_path,
+ const std::vector<std::string>& private_key_paths,
+ chromeos::Blob* out_signature_blob);
+
+ // Returns the length of out_signature_blob that will result in a call
+ // to SignPayload with the given private keys. Returns true on success.
+ static bool SignatureBlobLength(
+ const std::vector<std::string>& private_key_paths,
+ uint64_t* out_length);
+
+ // This is a helper method for HashPayloadforSigning and
+ // HashMetadataForSigning. It loads the payload into memory, and inserts
+ // signature placeholders if Signatures aren't already present.
+ static bool PrepPayloadForHashing(
+ const std::string& payload_path,
+ const std::vector<int>& signature_sizes,
+ chromeos::Blob* payload_out,
+ uint64_t* metadata_size_out,
+ uint64_t* signatures_offset_out);
+
+ // Given an unsigned payload in |payload_path|,
+ // this method does two things:
+ // 1. Uses PrepPayloadForHashing to inserts placeholder signature operations
+ // to make the manifest match what the final signed payload will look
+ // like based on |signatures_sizes|, if needed.
+ // 2. It calculates the raw SHA256 hash of the payload in |payload_path|
+ // (except signatures) and returns the result in |out_hash_data|.
+ //
+ // The dummy signatures are not preserved or written to disk.
+ static bool HashPayloadForSigning(const std::string& payload_path,
+ const std::vector<int>& signature_sizes,
+ chromeos::Blob* out_hash_data);
+
+ // Given an unsigned payload in |payload_path|,
+ // this method does two things:
+ // 1. Uses PrepPayloadForHashing to inserts placeholder signature operations
+ // to make the manifest match what the final signed payload will look
+ // like based on |signatures_sizes|, if needed.
+ // 2. It calculates the raw SHA256 hash of the metadata from the payload in
+ // |payload_path| (except signatures) and returns the result in
+ // |out_metadata_hash|.
+ //
+ // The dummy signatures are not preserved or written to disk.
+ static bool HashMetadataForSigning(const std::string& payload_path,
+ const std::vector<int>& signature_sizes,
+ chromeos::Blob* out_metadata_hash);
+
+ // Given an unsigned payload in |payload_path| (with no dummy signature op)
+ // and the raw |signatures| updates the payload to include the signature thus
+ // turning it into a signed payload. The new payload is stored in
+ // |signed_payload_path|. |payload_path| and |signed_payload_path| can point
+ // to the same file. Populates |out_metadata_size| with the size of the
+ // metadata after adding the signature operation in the manifest.Returns true
+ // on success, false otherwise.
+ static bool AddSignatureToPayload(
+ const std::string& payload_path,
+ const std::vector<chromeos::Blob>& signatures,
+ const std::string& signed_payload_path,
+ uint64_t* out_metadata_size);
+
+ // Computes the SHA256 hash of the first metadata_size bytes of |metadata|
+ // and signs the hash with the given private_key_path and writes the signed
+ // hash in |out_signature|. Returns true if successful or false if there was
+ // any error in the computations.
+ static bool GetMetadataSignature(const void* const metadata,
+ size_t metadata_size,
+ const std::string& private_key_path,
+ std::string* out_signature);
+
+ private:
+ // This should never be constructed
+ DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadSigner);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
diff --git a/payload_generator/payload_signer_unittest.cc b/payload_generator/payload_signer_unittest.cc
new file mode 100644
index 0000000..d74cdfb
--- /dev/null
+++ b/payload_generator/payload_signer_unittest.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/payload_signer.h"
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_verifier.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+// Note: the test key was generated with the following command:
+// openssl genrsa -out unittest_key.pem 2048
+// The public-key version is created by the build system.
+
+namespace chromeos_update_engine {
+
+const char* kUnittestPrivateKeyPath = "unittest_key.pem";
+const char* kUnittestPublicKeyPath = "unittest_key.pub.pem";
+const char* kUnittestPrivateKey2Path = "unittest_key2.pem";
+const char* kUnittestPublicKey2Path = "unittest_key2.pub.pem";
+
+// Some data and its corresponding hash and signature:
+const char kDataToSign[] = "This is some data to sign.";
+
+// Generated by:
+// echo -n 'This is some data to sign.' | openssl dgst -sha256 -binary |
+// hexdump -v -e '" " 8/1 "0x%02x, " "\n"'
+const uint8_t kDataHash[] = {
+ 0x7a, 0x07, 0xa6, 0x44, 0x08, 0x86, 0x20, 0xa6,
+ 0xc1, 0xf8, 0xd9, 0x02, 0x05, 0x63, 0x0d, 0xb7,
+ 0xfc, 0x2b, 0xa0, 0xa9, 0x7c, 0x9d, 0x1d, 0x8c,
+ 0x01, 0xf5, 0x78, 0x6d, 0xc5, 0x11, 0xb4, 0x06
+};
+
+// Generated with openssl 1.0, which at the time of this writing, you need
+// to download and install yourself. Here's my command:
+// echo -n 'This is some data to sign.' | openssl dgst -sha256 -binary |
+// ~/local/bin/openssl pkeyutl -sign -inkey unittest_key.pem -pkeyopt
+// digest:sha256 | hexdump -v -e '" " 8/1 "0x%02x, " "\n"'
+const uint8_t kDataSignature[] = {
+ 0x9f, 0x86, 0x25, 0x8b, 0xf3, 0xcc, 0xe3, 0x95,
+ 0x5f, 0x45, 0x83, 0xb2, 0x66, 0xf0, 0x2a, 0xcf,
+ 0xb7, 0xaa, 0x52, 0x25, 0x7a, 0xdd, 0x9d, 0x65,
+ 0xe5, 0xd6, 0x02, 0x4b, 0x37, 0x99, 0x53, 0x06,
+ 0xc2, 0xc9, 0x37, 0x36, 0x25, 0x62, 0x09, 0x4f,
+ 0x6b, 0x22, 0xf8, 0xb3, 0x89, 0x14, 0x98, 0x1a,
+ 0xbc, 0x30, 0x90, 0x4a, 0x43, 0xf5, 0xea, 0x2e,
+ 0xf0, 0xa4, 0xba, 0xc3, 0xa7, 0xa3, 0x44, 0x70,
+ 0xd6, 0xc4, 0x89, 0xd8, 0x45, 0x71, 0xbb, 0xee,
+ 0x59, 0x87, 0x3d, 0xd5, 0xe5, 0x40, 0x22, 0x3d,
+ 0x73, 0x7e, 0x2a, 0x58, 0x93, 0x8e, 0xcb, 0x9c,
+ 0xf2, 0xbb, 0x4a, 0xc9, 0xd2, 0x2c, 0x52, 0x42,
+ 0xb0, 0xd1, 0x13, 0x22, 0xa4, 0x78, 0xc7, 0xc6,
+ 0x3e, 0xf1, 0xdc, 0x4c, 0x7b, 0x2d, 0x40, 0xda,
+ 0x58, 0xac, 0x4a, 0x11, 0x96, 0x3d, 0xa0, 0x01,
+ 0xf6, 0x96, 0x74, 0xf6, 0x6c, 0x0c, 0x49, 0x69,
+ 0x4e, 0xc1, 0x7e, 0x9f, 0x2a, 0x42, 0xdd, 0x15,
+ 0x6b, 0x37, 0x2e, 0x3a, 0xa7, 0xa7, 0x6d, 0x91,
+ 0x13, 0xe8, 0x59, 0xde, 0xfe, 0x99, 0x07, 0xd9,
+ 0x34, 0x0f, 0x17, 0xb3, 0x05, 0x4c, 0xd2, 0xc6,
+ 0x82, 0xb7, 0x38, 0x36, 0x63, 0x1d, 0x9e, 0x21,
+ 0xa6, 0x32, 0xef, 0xf1, 0x65, 0xe6, 0xed, 0x95,
+ 0x25, 0x9b, 0x61, 0xe0, 0xba, 0x86, 0xa1, 0x7f,
+ 0xf8, 0xa5, 0x4a, 0x32, 0x1f, 0x15, 0x20, 0x8a,
+ 0x41, 0xc5, 0xb0, 0xd9, 0x4a, 0xda, 0x85, 0xf3,
+ 0xdc, 0xa0, 0x98, 0x5d, 0x1d, 0x18, 0x9d, 0x2e,
+ 0x42, 0xea, 0x69, 0x13, 0x74, 0x3c, 0x74, 0xf7,
+ 0x6d, 0x43, 0xb0, 0x63, 0x90, 0xdb, 0x04, 0xd5,
+ 0x05, 0xc9, 0x73, 0x1f, 0x6c, 0xd6, 0xfa, 0x46,
+ 0x4e, 0x0f, 0x33, 0x58, 0x5b, 0x0d, 0x1b, 0x55,
+ 0x39, 0xb9, 0x0f, 0x43, 0x37, 0xc0, 0x06, 0x0c,
+ 0x29, 0x93, 0x43, 0xc7, 0x43, 0xb9, 0xab, 0x7d
+};
+
+namespace {
+void SignSampleData(chromeos::Blob* out_signature_blob) {
+ string data_path;
+ ASSERT_TRUE(
+ utils::MakeTempFile("data.XXXXXX", &data_path, nullptr));
+ ScopedPathUnlinker data_path_unlinker(data_path);
+ ASSERT_TRUE(utils::WriteFile(data_path.c_str(),
+ kDataToSign,
+ strlen(kDataToSign)));
+ uint64_t length = 0;
+ EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+ vector<string> (1, kUnittestPrivateKeyPath),
+ &length));
+ EXPECT_GT(length, 0);
+ EXPECT_TRUE(PayloadSigner::SignPayload(
+ data_path,
+ vector<string>(1, kUnittestPrivateKeyPath),
+ out_signature_blob));
+ EXPECT_EQ(length, out_signature_blob->size());
+}
+} // namespace
+
+TEST(PayloadSignerTest, SimpleTest) {
+ chromeos::Blob signature_blob;
+ SignSampleData(&signature_blob);
+
+ // Check the signature itself
+ Signatures signatures;
+ EXPECT_TRUE(signatures.ParseFromArray(signature_blob.data(),
+ signature_blob.size()));
+ EXPECT_EQ(1, signatures.signatures_size());
+ const Signatures_Signature& signature = signatures.signatures(0);
+ EXPECT_EQ(kSignatureMessageOriginalVersion, signature.version());
+ const string sig_data = signature.data();
+ ASSERT_EQ(arraysize(kDataSignature), sig_data.size());
+ for (size_t i = 0; i < arraysize(kDataSignature); i++) {
+ EXPECT_EQ(kDataSignature[i], static_cast<uint8_t>(sig_data[i]));
+ }
+}
+
+TEST(PayloadSignerTest, VerifySignatureTest) {
+ chromeos::Blob signature_blob;
+ SignSampleData(&signature_blob);
+
+ chromeos::Blob hash_data;
+ EXPECT_TRUE(PayloadVerifier::VerifySignature(signature_blob,
+ kUnittestPublicKeyPath,
+ &hash_data));
+ chromeos::Blob padded_hash_data(std::begin(kDataHash), std::end(kDataHash));
+ PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash_data);
+ ASSERT_EQ(padded_hash_data.size(), hash_data.size());
+ for (size_t i = 0; i < padded_hash_data.size(); i++) {
+ EXPECT_EQ(padded_hash_data[i], hash_data[i]);
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/raw_filesystem.cc b/payload_generator/raw_filesystem.cc
new file mode 100644
index 0000000..f24096e
--- /dev/null
+++ b/payload_generator/raw_filesystem.cc
@@ -0,0 +1,41 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/raw_filesystem.h"
+
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_engine {
+
+unique_ptr<RawFilesystem> RawFilesystem::Create(
+ const std::string& filename, uint64_t block_size, uint64_t block_count) {
+ unique_ptr<RawFilesystem> result(new RawFilesystem());
+ result->filename_ = filename;
+ result->block_size_ = block_size;
+ result->block_count_ = block_count;
+ return result;
+}
+
+size_t RawFilesystem::GetBlockSize() const {
+ return block_size_;
+}
+
+size_t RawFilesystem::GetBlockCount() const {
+ return block_count_;
+}
+
+bool RawFilesystem::GetFiles(std::vector<File>* files) const {
+ files->clear();
+ File file;
+ file.name = filename_;
+ file.extents = { ExtentForRange(0, block_count_) };
+ files->push_back(file);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/raw_filesystem.h b/payload_generator/raw_filesystem.h
new file mode 100644
index 0000000..bd66487
--- /dev/null
+++ b/payload_generator/raw_filesystem.h
@@ -0,0 +1,48 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_RAW_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_RAW_FILESYSTEM_H_
+
+// A simple filesystem interface implementation used for unknown filesystem
+// format such as the kernel.
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <string>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+class RawFilesystem : public FilesystemInterface {
+ public:
+ static std::unique_ptr<RawFilesystem> Create(
+ const std::string& filename, uint64_t block_size, uint64_t block_count);
+ virtual ~RawFilesystem() = default;
+
+ // FilesystemInterface overrides.
+ size_t GetBlockSize() const override;
+ size_t GetBlockCount() const override;
+
+ // GetFiles will return only one file with all the blocks of the filesystem
+ // with the name passed during construction.
+ bool GetFiles(std::vector<File>* files) const override;
+
+ bool LoadSettings(chromeos::KeyValueStore* store) const override {
+ return false;
+ }
+
+ private:
+ RawFilesystem() = default;
+
+ std::string filename_;
+ uint64_t block_count_;
+ uint64_t block_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(RawFilesystem);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_RAW_FILESYSTEM_H_
diff --git a/payload_generator/tarjan.cc b/payload_generator/tarjan.cc
new file mode 100644
index 0000000..f3029c1
--- /dev/null
+++ b/payload_generator/tarjan.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "update_engine/payload_generator/tarjan.h"
+
+#include <algorithm>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "update_engine/utils.h"
+
+using std::min;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const vector<Vertex>::size_type kInvalidIndex = -1;
+}
+
+void TarjanAlgorithm::Execute(Vertex::Index vertex,
+ Graph* graph,
+ vector<Vertex::Index>* out) {
+ stack_.clear();
+ components_.clear();
+ index_ = 0;
+ for (Graph::iterator it = graph->begin(); it != graph->end(); ++it)
+ it->index = it->lowlink = kInvalidIndex;
+ required_vertex_ = vertex;
+
+ Tarjan(vertex, graph);
+ if (!components_.empty())
+ out->swap(components_[0]);
+}
+
+void TarjanAlgorithm::Tarjan(Vertex::Index vertex, Graph* graph) {
+ CHECK_EQ((*graph)[vertex].index, kInvalidIndex);
+ (*graph)[vertex].index = index_;
+ (*graph)[vertex].lowlink = index_;
+ index_++;
+ stack_.push_back(vertex);
+ for (Vertex::EdgeMap::iterator it = (*graph)[vertex].out_edges.begin();
+ it != (*graph)[vertex].out_edges.end(); ++it) {
+ Vertex::Index vertex_next = it->first;
+ if ((*graph)[vertex_next].index == kInvalidIndex) {
+ Tarjan(vertex_next, graph);
+ (*graph)[vertex].lowlink = min((*graph)[vertex].lowlink,
+ (*graph)[vertex_next].lowlink);
+ } else if (utils::VectorContainsValue(stack_, vertex_next)) {
+ (*graph)[vertex].lowlink = min((*graph)[vertex].lowlink,
+ (*graph)[vertex_next].index);
+ }
+ }
+ if ((*graph)[vertex].lowlink == (*graph)[vertex].index) {
+ vector<Vertex::Index> component;
+ Vertex::Index other_vertex;
+ do {
+ other_vertex = stack_.back();
+ stack_.pop_back();
+ component.push_back(other_vertex);
+ } while (other_vertex != vertex && !stack_.empty());
+
+ if (utils::VectorContainsValue(component, required_vertex_)) {
+ components_.resize(components_.size() + 1);
+ component.swap(components_.back());
+ }
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/tarjan.h b/payload_generator/tarjan.h
new file mode 100644
index 0000000..e3dc97f
--- /dev/null
+++ b/payload_generator/tarjan.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_TARJAN_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_TARJAN_H_
+
+// This is an implementation of Tarjan's algorithm which finds all
+// Strongly Connected Components in a graph.
+
+// Note: a true Tarjan algorithm would find all strongly connected components
+// in the graph. This implementation will only find the strongly connected
+// component containing the vertex passed in.
+
+#include <vector>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+namespace chromeos_update_engine {
+
+class TarjanAlgorithm {
+ public:
+ TarjanAlgorithm() : index_(0), required_vertex_(0) {}
+
+ // 'out' is set to the result if there is one, otherwise it's untouched.
+ void Execute(Vertex::Index vertex,
+ Graph* graph,
+ std::vector<Vertex::Index>* out);
+ private:
+ void Tarjan(Vertex::Index vertex, Graph* graph);
+
+ Vertex::Index index_;
+ Vertex::Index required_vertex_;
+ std::vector<Vertex::Index> stack_;
+ std::vector<std::vector<Vertex::Index>> components_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_TARJAN_H_
diff --git a/payload_generator/tarjan_unittest.cc b/payload_generator/tarjan_unittest.cc
new file mode 100644
index 0000000..76f5f2c
--- /dev/null
+++ b/payload_generator/tarjan_unittest.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/tarjan.h"
+
+#include <string>
+#include <utility>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/utils.h"
+
+using std::make_pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class TarjanAlgorithmTest : public ::testing::Test {};
+
+TEST(TarjanAlgorithmTest, SimpleTest) {
+ const Vertex::Index n_a = 0;
+ const Vertex::Index n_b = 1;
+ const Vertex::Index n_c = 2;
+ const Vertex::Index n_d = 3;
+ const Vertex::Index n_e = 4;
+ const Vertex::Index n_f = 5;
+ const Vertex::Index n_g = 6;
+ const Vertex::Index n_h = 7;
+ const Graph::size_type kNodeCount = 8;
+
+ Graph graph(kNodeCount);
+
+ graph[n_a].out_edges.insert(make_pair(n_e, EdgeProperties()));
+ graph[n_a].out_edges.insert(make_pair(n_f, EdgeProperties()));
+ graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties()));
+ graph[n_c].out_edges.insert(make_pair(n_d, EdgeProperties()));
+ graph[n_d].out_edges.insert(make_pair(n_e, EdgeProperties()));
+ graph[n_d].out_edges.insert(make_pair(n_f, EdgeProperties()));
+ graph[n_e].out_edges.insert(make_pair(n_b, EdgeProperties()));
+ graph[n_e].out_edges.insert(make_pair(n_c, EdgeProperties()));
+ graph[n_e].out_edges.insert(make_pair(n_f, EdgeProperties()));
+ graph[n_f].out_edges.insert(make_pair(n_g, EdgeProperties()));
+ graph[n_g].out_edges.insert(make_pair(n_h, EdgeProperties()));
+ graph[n_h].out_edges.insert(make_pair(n_g, EdgeProperties()));
+
+ TarjanAlgorithm tarjan;
+
+ for (Vertex::Index i = n_a; i <= n_e; i++) {
+ vector<Vertex::Index> vertex_indexes;
+ tarjan.Execute(i, &graph, &vertex_indexes);
+
+ EXPECT_EQ(5, vertex_indexes.size());
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_a));
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_b));
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_c));
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_d));
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_e));
+ }
+
+ {
+ vector<Vertex::Index> vertex_indexes;
+ tarjan.Execute(n_f, &graph, &vertex_indexes);
+
+ EXPECT_EQ(1, vertex_indexes.size());
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_f));
+ }
+
+ for (Vertex::Index i = n_g; i <= n_h; i++) {
+ vector<Vertex::Index> vertex_indexes;
+ tarjan.Execute(i, &graph, &vertex_indexes);
+
+ EXPECT_EQ(2, vertex_indexes.size());
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_g));
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_h));
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/topological_sort.cc b/payload_generator/topological_sort.cc
new file mode 100644
index 0000000..2a6e586
--- /dev/null
+++ b/payload_generator/topological_sort.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/topological_sort.h"
+
+#include <set>
+#include <vector>
+
+#include <base/logging.h>
+
+using std::set;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+void TopologicalSortVisit(const Graph& graph,
+ set<Vertex::Index>* visited_nodes,
+ vector<Vertex::Index>* nodes,
+ Vertex::Index node) {
+ if (visited_nodes->find(node) != visited_nodes->end())
+ return;
+
+ visited_nodes->insert(node);
+ // Visit all children.
+ for (Vertex::EdgeMap::const_iterator it = graph[node].out_edges.begin();
+ it != graph[node].out_edges.end(); ++it) {
+ TopologicalSortVisit(graph, visited_nodes, nodes, it->first);
+ }
+ // Visit this node.
+ nodes->push_back(node);
+}
+} // namespace
+
+void TopologicalSort(const Graph& graph, vector<Vertex::Index>* out) {
+ set<Vertex::Index> visited_nodes;
+
+ for (Vertex::Index i = 0; i < graph.size(); i++) {
+ TopologicalSortVisit(graph, &visited_nodes, out, i);
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/topological_sort.h b/payload_generator/topological_sort.h
new file mode 100644
index 0000000..6e81d91
--- /dev/null
+++ b/payload_generator/topological_sort.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_TOPOLOGICAL_SORT_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_TOPOLOGICAL_SORT_H_
+
+#include <vector>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+namespace chromeos_update_engine {
+
+// Performs a topological sort on the directed graph 'graph' and stores
+// the nodes, in order visited, in 'out'.
+// For example, this graph:
+// A ---> C ----.
+// \ v
+// `--> B --> D
+// Might result in this in 'out':
+// out[0] = D
+// out[1] = B
+// out[2] = C
+// out[3] = A
+// Note: results are undefined if there is a cycle in the graph.
+void TopologicalSort(const Graph& graph, std::vector<Vertex::Index>* out);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_TOPOLOGICAL_SORT_H_
diff --git a/payload_generator/topological_sort_unittest.cc b/payload_generator/topological_sort_unittest.cc
new file mode 100644
index 0000000..e5e7477
--- /dev/null
+++ b/payload_generator/topological_sort_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/topological_sort.h"
+
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+using std::make_pair;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class TopologicalSortTest : public ::testing::Test {};
+
+namespace {
+// Returns true if the value is found in vect. If found, the index is stored
+// in out_index if out_index is not null.
+template<typename T>
+bool IndexOf(const vector<T>& vect,
+ const T& value,
+ typename vector<T>::size_type* out_index) {
+ for (typename vector<T>::size_type i = 0; i < vect.size(); i++) {
+ if (vect[i] == value) {
+ if (out_index) {
+ *out_index = i;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+} // namespace
+
+TEST(TopologicalSortTest, SimpleTest) {
+ int counter = 0;
+ const Vertex::Index n_a = counter++;
+ const Vertex::Index n_b = counter++;
+ const Vertex::Index n_c = counter++;
+ const Vertex::Index n_d = counter++;
+ const Vertex::Index n_e = counter++;
+ const Vertex::Index n_f = counter++;
+ const Vertex::Index n_g = counter++;
+ const Vertex::Index n_h = counter++;
+ const Vertex::Index n_i = counter++;
+ const Vertex::Index n_j = counter++;
+ const Graph::size_type kNodeCount = counter++;
+
+ Graph graph(kNodeCount);
+
+ graph[n_i].out_edges.insert(make_pair(n_j, EdgeProperties()));
+ graph[n_i].out_edges.insert(make_pair(n_c, EdgeProperties()));
+ graph[n_i].out_edges.insert(make_pair(n_e, EdgeProperties()));
+ graph[n_i].out_edges.insert(make_pair(n_h, EdgeProperties()));
+ graph[n_c].out_edges.insert(make_pair(n_b, EdgeProperties()));
+ graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties()));
+ graph[n_e].out_edges.insert(make_pair(n_d, EdgeProperties()));
+ graph[n_e].out_edges.insert(make_pair(n_g, EdgeProperties()));
+ graph[n_g].out_edges.insert(make_pair(n_d, EdgeProperties()));
+ graph[n_g].out_edges.insert(make_pair(n_f, EdgeProperties()));
+ graph[n_d].out_edges.insert(make_pair(n_a, EdgeProperties()));
+
+ vector<Vertex::Index> sorted;
+ TopologicalSort(graph, &sorted);
+
+ for (Vertex::Index i = 0; i < graph.size(); i++) {
+ vector<Vertex::Index>::size_type src_index = 0;
+ EXPECT_TRUE(IndexOf(sorted, i, &src_index));
+ for (Vertex::EdgeMap::const_iterator it = graph[i].out_edges.begin();
+ it != graph[i].out_edges.end(); ++it) {
+ vector<Vertex::Index>::size_type dst_index = 0;
+ EXPECT_TRUE(IndexOf(sorted, it->first, &dst_index));
+ EXPECT_LT(dst_index, src_index);
+ }
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/verity_utils.cc b/payload_generator/verity_utils.cc
new file mode 100644
index 0000000..c9198e0
--- /dev/null
+++ b/payload_generator/verity_utils.cc
@@ -0,0 +1,127 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/verity_utils.h"
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <chromeos/strings/string_utils.h>
+extern "C" {
+#include <vboot/vboot_host.h>
+}
+
+using std::string;
+using std::vector;
+
+extern "C" {
+
+// vboot_host.h has a default VbExError() that will call exit() when a function
+// fails. We redefine that function here so it doesn't exit.
+void VbExError(const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ fprintf(stderr, "ERROR: ");
+ va_end(ap);
+}
+
+}
+
+namespace {
+
+// Splits a string with zero or more arguments separated by spaces into a list
+// of strings, but respecting the double quotes. For example, the string:
+// a="foo" b=foo c="bar baz" "my dir"/"my file"
+// has only four arguments, since some parts are grouped together due to the
+// double quotes.
+vector<string> SplitQuotedArgs(const string arglist) {
+ vector<string> terms = chromeos::string_utils::Split(
+ arglist, " ", false, false);
+ vector<string> result;
+ string last_term;
+ size_t quotes = 0;
+ for (const string& term : terms) {
+ if (quotes % 2 == 0 && term.empty())
+ continue;
+
+ quotes += std::count(term.begin(), term.end(), '"');
+ if (last_term.empty()) {
+ last_term = term;
+ } else {
+ last_term += " " + term;
+ }
+ if (quotes % 2 == 0) {
+ result.push_back(last_term);
+ last_term.clear();
+ quotes = 0;
+ }
+ }
+ // Unterminated quoted string found.
+ if (!last_term.empty())
+ result.push_back(last_term);
+ return result;
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+bool ParseVerityRootfsSize(const string& kernel_cmdline,
+ uint64_t* rootfs_size) {
+ vector<string> kernel_args = SplitQuotedArgs(kernel_cmdline);
+
+ for (const string& arg : kernel_args) {
+ std::pair<string, string> key_value =
+ chromeos::string_utils::SplitAtFirst(arg, "=", true);
+ if (key_value.first != "dm")
+ continue;
+ string value = key_value.second;
+ if (value.size() > 1 && value.front() == '"' && value.back() == '"')
+ value = value.substr(1, value.size() - 1);
+
+ vector<string> dm_parts = SplitQuotedArgs(value);
+ // Check if this is a dm-verity device.
+ if (std::find(dm_parts.begin(), dm_parts.end(), "verity") == dm_parts.end())
+ continue;
+ for (const string& dm_part : dm_parts) {
+ key_value = chromeos::string_utils::SplitAtFirst(dm_part, "=", true);
+ if (key_value.first != "hashstart")
+ continue;
+ if (!base::StringToUint64(key_value.second, rootfs_size))
+ continue;
+ // The hashstart= value is specified in 512-byte blocks, so we need to
+ // convert that to bytes.
+ *rootfs_size *= 512;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GetVerityRootfsSize(const string& kernel_dev, uint64_t* rootfs_size) {
+ string kernel_cmdline;
+ char *config = FindKernelConfig(kernel_dev.c_str(), USE_PREAMBLE_LOAD_ADDR);
+ if (!config) {
+ LOG(WARNING) << "Error retrieving kernel command line from '"
+ << kernel_dev << "', ignoring.";
+ return false;
+ }
+ kernel_cmdline = string(config, MAX_KERNEL_CONFIG_SIZE);
+
+ // FindKernelConfig() expects the caller to free the char*.
+ free(config);
+
+ if (!ParseVerityRootfsSize(kernel_cmdline, rootfs_size)) {
+ LOG(INFO) << "Didn't find the rootfs size in the kernel command line: "
+ << kernel_cmdline;
+ return false;
+ }
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/verity_utils.h b/payload_generator/verity_utils.h
new file mode 100644
index 0000000..3f7b297
--- /dev/null
+++ b/payload_generator/verity_utils.h
@@ -0,0 +1,19 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_VERITY_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_VERITY_UTILS_H_
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+bool GetVerityRootfsSize(const std::string& kernel_dev, uint64_t* rootfs_size);
+
+bool ParseVerityRootfsSize(const std::string& kernel_cmdline,
+ uint64_t* rootfs_size);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_VERITY_UTILS_H_
diff --git a/payload_generator/verity_utils_unittest.cc b/payload_generator/verity_utils_unittest.cc
new file mode 100644
index 0000000..d1e54e6
--- /dev/null
+++ b/payload_generator/verity_utils_unittest.cc
@@ -0,0 +1,61 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/verity_utils.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+// A real kernel command line found on a device.
+static const char* kVerityKernelCommandLine =
+ "console= loglevel=7 init=/sbin/init cros_secure oops=panic panic=-1 "
+ "root=/dev/dm-0 rootwait ro dm_verity.error_behavior=3 "
+ "dm_verity.max_bios=-1 dm_verity.dev_wait=1 "
+ "dm=\"1 vroot none ro 1,0 1536000 verity payload=PARTUUID=%U/PARTNROFF=1 "
+ "hashtree=PARTUUID=%U/PARTNROFF=1 hashstart=1536000 alg=sha1 "
+ "root_hexdigest=16b55bbea634fc3abf4c339da207cf050b1809d6 "
+ "salt=18a095c4e473b68558afefdf83438d482cf37894d312afce6991c8267ea233f6\" "
+ "noinitrd vt.global_cursor_default=0 kern_guid=%U ";
+
+// A real kernel command line from a parrot device, including the bootcache.
+static const char* kVerityAndBootcacheKernelCommandLine =
+ "console= loglevel=7 init=/sbin/init cros_secure oops=panic panic=-1 "
+ "root=/dev/dm-1 rootwait ro dm_verity.error_behavior=3 "
+ "dm_verity.max_bios=-1 dm_verity.dev_wait=1 "
+ "dm=\"2 vboot none ro 1,0 2545920 bootcache PARTUUID=%U/PARTNROFF=1 "
+ "2545920 d5d03fb5459b6a75f069378c1799ba313d8ea89a 512 20000 100000, vroot "
+ "none ro 1,0 2506752 verity payload=254:0 hashtree=254:0 hashstart=2506752 "
+ "alg=sha1 root_hexdigest=3deebbc697a30cc585cf85a3b4351dc772861321 "
+ "salt=6a13027cdf234c58a0b1f43e6a7428f41672cca89d5574c1f405649df65fb071\" "
+ "noinitrd vt.global_cursor_default=0 kern_guid=%U add_efi_memmap "
+ "boot=local noresume noswap i915.modeset=1 tpm_tis.force=1 "
+ "tpm_tis.interrupts=0 nmi_watchdog=panic,lapic "
+ "iTCO_vendor_support.vendorsupport=3";
+
+TEST(VerityUtilsTest, ParseVerityRootfsSizeWithInvalidValues) {
+ uint64_t rootfs_size = 0;
+ EXPECT_FALSE(ParseVerityRootfsSize("", &rootfs_size));
+
+ // Not a verity dm device.
+ EXPECT_FALSE(ParseVerityRootfsSize(
+ "dm=\"1 vroot none ro 1,0 1234 something\"", &rootfs_size));
+ EXPECT_FALSE(ParseVerityRootfsSize(
+ "ro verity hashattr=1234", &rootfs_size));
+
+ // The verity doesn't have the hashstart= attribute.
+ EXPECT_FALSE(ParseVerityRootfsSize(
+ "dm=\"1 vroot none ro 1,0 1234 verity payload=fake\"", &rootfs_size));
+}
+
+TEST(VerityUtilsTest, ParseVerityRootfsSizeWithValidValues) {
+ uint64_t rootfs_size = 0;
+ EXPECT_TRUE(ParseVerityRootfsSize(kVerityKernelCommandLine, &rootfs_size));
+ EXPECT_EQ(1536000 * 512, rootfs_size);
+ EXPECT_TRUE(ParseVerityRootfsSize(kVerityAndBootcacheKernelCommandLine,
+ &rootfs_size));
+ EXPECT_EQ(2506752 * 512, rootfs_size);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_state.cc b/payload_state.cc
new file mode 100644
index 0000000..ed7b775
--- /dev/null
+++ b/payload_state.cc
@@ -0,0 +1,1568 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_state.h"
+
+#include <algorithm>
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/format_macros.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/clock.h"
+#include "update_engine/constants.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/prefs.h"
+#include "update_engine/real_dbus_wrapper.h"
+#include "update_engine/system_state.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::min;
+using std::string;
+
+namespace chromeos_update_engine {
+
+const TimeDelta PayloadState::kDurationSlack = TimeDelta::FromSeconds(600);
+
+// We want to upperbound backoffs to 16 days
+static const int kMaxBackoffDays = 16;
+
+// We want to randomize retry attempts after the backoff by +/- 6 hours.
+static const uint32_t kMaxBackoffFuzzMinutes = 12 * 60;
+
+PayloadState::PayloadState()
+ : prefs_(nullptr),
+ using_p2p_for_downloading_(false),
+ p2p_num_attempts_(0),
+ payload_attempt_number_(0),
+ full_payload_attempt_number_(0),
+ url_index_(0),
+ url_failure_count_(0),
+ url_switch_count_(0),
+ attempt_num_bytes_downloaded_(0),
+ attempt_connection_type_(metrics::ConnectionType::kUnknown),
+ attempt_type_(AttemptType::kUpdate) {
+ for (int i = 0; i <= kNumDownloadSources; i++)
+ total_bytes_downloaded_[i] = current_bytes_downloaded_[i] = 0;
+}
+
+bool PayloadState::Initialize(SystemState* system_state) {
+ system_state_ = system_state;
+ prefs_ = system_state_->prefs();
+ powerwash_safe_prefs_ = system_state_->powerwash_safe_prefs();
+ LoadResponseSignature();
+ LoadPayloadAttemptNumber();
+ LoadFullPayloadAttemptNumber();
+ LoadUrlIndex();
+ LoadUrlFailureCount();
+ LoadUrlSwitchCount();
+ LoadBackoffExpiryTime();
+ LoadUpdateTimestampStart();
+ // The LoadUpdateDurationUptime() method relies on LoadUpdateTimestampStart()
+ // being called before it. Don't reorder.
+ LoadUpdateDurationUptime();
+ for (int i = 0; i < kNumDownloadSources; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+ LoadCurrentBytesDownloaded(source);
+ LoadTotalBytesDownloaded(source);
+ }
+ LoadNumReboots();
+ LoadNumResponsesSeen();
+ LoadRollbackVersion();
+ LoadP2PFirstAttemptTimestamp();
+ LoadP2PNumAttempts();
+ return true;
+}
+
+void PayloadState::SetResponse(const OmahaResponse& omaha_response) {
+ // Always store the latest response.
+ response_ = omaha_response;
+
+ // Compute the candidate URLs first as they are used to calculate the
+ // response signature so that a change in enterprise policy for
+ // HTTP downloads being enabled or not could be honored as soon as the
+ // next update check happens.
+ ComputeCandidateUrls();
+
+ // Check if the "signature" of this response (i.e. the fields we care about)
+ // has changed.
+ string new_response_signature = CalculateResponseSignature();
+ bool has_response_changed = (response_signature_ != new_response_signature);
+
+ // If the response has changed, we should persist the new signature and
+ // clear away all the existing state.
+ if (has_response_changed) {
+ LOG(INFO) << "Resetting all persisted state as this is a new response";
+ SetNumResponsesSeen(num_responses_seen_ + 1);
+ SetResponseSignature(new_response_signature);
+ ResetPersistedState();
+ ReportUpdatesAbandonedEventCountMetric();
+ return;
+ }
+
+ // This is the earliest point at which we can validate whether the URL index
+ // we loaded from the persisted state is a valid value. If the response
+ // hasn't changed but the URL index is invalid, it's indicative of some
+ // tampering of the persisted state.
+ if (static_cast<uint32_t>(url_index_) >= candidate_urls_.size()) {
+ LOG(INFO) << "Resetting all payload state as the url index seems to have "
+ "been tampered with";
+ ResetPersistedState();
+ return;
+ }
+
+ // Update the current download source which depends on the latest value of
+ // the response.
+ UpdateCurrentDownloadSource();
+}
+
+void PayloadState::SetUsingP2PForDownloading(bool value) {
+ using_p2p_for_downloading_ = value;
+ // Update the current download source which depends on whether we are
+ // using p2p or not.
+ UpdateCurrentDownloadSource();
+}
+
+void PayloadState::DownloadComplete() {
+ LOG(INFO) << "Payload downloaded successfully";
+ IncrementPayloadAttemptNumber();
+ IncrementFullPayloadAttemptNumber();
+}
+
+void PayloadState::DownloadProgress(size_t count) {
+ if (count == 0)
+ return;
+
+ CalculateUpdateDurationUptime();
+ UpdateBytesDownloaded(count);
+
+ // We've received non-zero bytes from a recent download operation. Since our
+ // URL failure count is meant to penalize a URL only for consecutive
+ // failures, downloading bytes successfully means we should reset the failure
+ // count (as we know at least that the URL is working). In future, we can
+ // design this to be more sophisticated to check for more intelligent failure
+ // patterns, but right now, even 1 byte downloaded will mark the URL to be
+ // good unless it hits 10 (or configured number of) consecutive failures
+ // again.
+
+ if (GetUrlFailureCount() == 0)
+ return;
+
+ LOG(INFO) << "Resetting failure count of Url" << GetUrlIndex()
+ << " to 0 as we received " << count << " bytes successfully";
+ SetUrlFailureCount(0);
+}
+
+void PayloadState::AttemptStarted(AttemptType attempt_type) {
+ // Flush previous state from abnormal attempt failure, if any.
+ ReportAndClearPersistedAttemptMetrics();
+
+ attempt_type_ = attempt_type;
+
+ ClockInterface *clock = system_state_->clock();
+ attempt_start_time_boot_ = clock->GetBootTime();
+ attempt_start_time_monotonic_ = clock->GetMonotonicTime();
+ attempt_num_bytes_downloaded_ = 0;
+
+ metrics::ConnectionType type;
+ NetworkConnectionType network_connection_type;
+ NetworkTethering tethering;
+ RealDBusWrapper dbus_iface;
+ ConnectionManager* connection_manager = system_state_->connection_manager();
+ if (!connection_manager->GetConnectionProperties(&dbus_iface,
+ &network_connection_type,
+ &tethering)) {
+ LOG(ERROR) << "Failed to determine connection type.";
+ type = metrics::ConnectionType::kUnknown;
+ } else {
+ type = utils::GetConnectionType(network_connection_type, tethering);
+ }
+ attempt_connection_type_ = type;
+
+ if (attempt_type == AttemptType::kUpdate)
+ PersistAttemptMetrics();
+}
+
+void PayloadState::UpdateResumed() {
+ LOG(INFO) << "Resuming an update that was previously started.";
+ UpdateNumReboots();
+ AttemptStarted(AttemptType::kUpdate);
+}
+
+void PayloadState::UpdateRestarted() {
+ LOG(INFO) << "Starting a new update";
+ ResetDownloadSourcesOnNewUpdate();
+ SetNumReboots(0);
+ AttemptStarted(AttemptType::kUpdate);
+}
+
+void PayloadState::UpdateSucceeded() {
+ // Send the relevant metrics that are tracked in this class to UMA.
+ CalculateUpdateDurationUptime();
+ SetUpdateTimestampEnd(system_state_->clock()->GetWallclockTime());
+
+ switch (attempt_type_) {
+ case AttemptType::kUpdate:
+ CollectAndReportAttemptMetrics(ErrorCode::kSuccess);
+ CollectAndReportSuccessfulUpdateMetrics();
+ ClearPersistedAttemptMetrics();
+ break;
+
+ case AttemptType::kRollback:
+ metrics::ReportRollbackMetrics(system_state_,
+ metrics::RollbackResult::kSuccess);
+ break;
+ }
+
+ // Reset the number of responses seen since it counts from the last
+ // successful update, e.g. now.
+ SetNumResponsesSeen(0);
+
+ CreateSystemUpdatedMarkerFile();
+}
+
+void PayloadState::UpdateFailed(ErrorCode error) {
+ ErrorCode base_error = utils::GetBaseErrorCode(error);
+ LOG(INFO) << "Updating payload state for error code: " << base_error
+ << " (" << utils::CodeToString(base_error) << ")";
+
+ if (candidate_urls_.size() == 0) {
+ // This means we got this error even before we got a valid Omaha response
+ // or don't have any valid candidates in the Omaha response.
+ // So we should not advance the url_index_ in such cases.
+ LOG(INFO) << "Ignoring failures until we get a valid Omaha response.";
+ return;
+ }
+
+ switch (attempt_type_) {
+ case AttemptType::kUpdate:
+ CollectAndReportAttemptMetrics(base_error);
+ ClearPersistedAttemptMetrics();
+ break;
+
+ case AttemptType::kRollback:
+ metrics::ReportRollbackMetrics(system_state_,
+ metrics::RollbackResult::kFailed);
+ break;
+ }
+
+ switch (base_error) {
+ // Errors which are good indicators of a problem with a particular URL or
+ // the protocol used in the URL or entities in the communication channel
+ // (e.g. proxies). We should try the next available URL in the next update
+ // check to quickly recover from these errors.
+ case ErrorCode::kPayloadHashMismatchError:
+ case ErrorCode::kPayloadSizeMismatchError:
+ case ErrorCode::kDownloadPayloadVerificationError:
+ case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+ case ErrorCode::kSignedDeltaPayloadExpectedError:
+ case ErrorCode::kDownloadInvalidMetadataMagicString:
+ case ErrorCode::kDownloadSignatureMissingInManifest:
+ case ErrorCode::kDownloadManifestParseError:
+ case ErrorCode::kDownloadMetadataSignatureError:
+ case ErrorCode::kDownloadMetadataSignatureVerificationError:
+ case ErrorCode::kDownloadMetadataSignatureMismatch:
+ case ErrorCode::kDownloadOperationHashVerificationError:
+ case ErrorCode::kDownloadOperationExecutionError:
+ case ErrorCode::kDownloadOperationHashMismatch:
+ case ErrorCode::kDownloadInvalidMetadataSize:
+ case ErrorCode::kDownloadInvalidMetadataSignature:
+ case ErrorCode::kDownloadOperationHashMissingError:
+ case ErrorCode::kDownloadMetadataSignatureMissingError:
+ case ErrorCode::kPayloadMismatchedType:
+ case ErrorCode::kUnsupportedMajorPayloadVersion:
+ case ErrorCode::kUnsupportedMinorPayloadVersion:
+ IncrementUrlIndex();
+ break;
+
+ // Errors which seem to be just transient network/communication related
+ // failures and do not indicate any inherent problem with the URL itself.
+ // So, we should keep the current URL but just increment the
+ // failure count to give it more chances. This way, while we maximize our
+ // chances of downloading from the URLs that appear earlier in the response
+ // (because download from a local server URL that appears earlier in a
+ // response is preferable than downloading from the next URL which could be
+ // a internet URL and thus could be more expensive).
+
+ case ErrorCode::kError:
+ case ErrorCode::kDownloadTransferError:
+ case ErrorCode::kDownloadWriteError:
+ case ErrorCode::kDownloadStateInitializationError:
+ case ErrorCode::kOmahaErrorInHTTPResponse: // Aggregate for HTTP errors.
+ IncrementFailureCount();
+ break;
+
+ // Errors which are not specific to a URL and hence shouldn't result in
+ // the URL being penalized. This can happen in two cases:
+ // 1. We haven't started downloading anything: These errors don't cost us
+ // anything in terms of actual payload bytes, so we should just do the
+ // regular retries at the next update check.
+ // 2. We have successfully downloaded the payload: In this case, the
+ // payload attempt number would have been incremented and would take care
+ // of the backoff at the next update check.
+ // In either case, there's no need to update URL index or failure count.
+ case ErrorCode::kOmahaRequestError:
+ case ErrorCode::kOmahaResponseHandlerError:
+ case ErrorCode::kPostinstallRunnerError:
+ case ErrorCode::kFilesystemCopierError:
+ case ErrorCode::kInstallDeviceOpenError:
+ case ErrorCode::kKernelDeviceOpenError:
+ case ErrorCode::kDownloadNewPartitionInfoError:
+ case ErrorCode::kNewRootfsVerificationError:
+ case ErrorCode::kNewKernelVerificationError:
+ case ErrorCode::kPostinstallBootedFromFirmwareB:
+ case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+ case ErrorCode::kOmahaRequestEmptyResponseError:
+ case ErrorCode::kOmahaRequestXMLParseError:
+ case ErrorCode::kOmahaResponseInvalid:
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ case ErrorCode::kPostinstallPowerwashError:
+ case ErrorCode::kUpdateCanceledByChannelChange:
+ case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+ case ErrorCode::kFilesystemVerifierError:
+ LOG(INFO) << "Not incrementing URL index or failure count for this error";
+ break;
+
+ case ErrorCode::kSuccess: // success code
+ case ErrorCode::kUmaReportedMax: // not an error code
+ case ErrorCode::kOmahaRequestHTTPResponseBase: // aggregated already
+ case ErrorCode::kDevModeFlag: // not an error code
+ case ErrorCode::kResumedFlag: // not an error code
+ case ErrorCode::kTestImageFlag: // not an error code
+ case ErrorCode::kTestOmahaUrlFlag: // not an error code
+ case ErrorCode::kSpecialFlags: // not an error code
+ // These shouldn't happen. Enumerating these explicitly here so that we
+ // can let the compiler warn about new error codes that are added to
+ // action_processor.h but not added here.
+ LOG(WARNING) << "Unexpected error code for UpdateFailed";
+ break;
+
+ // Note: Not adding a default here so as to let the compiler warn us of
+ // any new enums that were added in the .h but not listed in this switch.
+ }
+}
+
+bool PayloadState::ShouldBackoffDownload() {
+ if (response_.disable_payload_backoff) {
+ LOG(INFO) << "Payload backoff logic is disabled. "
+ "Can proceed with the download";
+ return false;
+ }
+ if (GetUsingP2PForDownloading() && !GetP2PUrl().empty()) {
+ LOG(INFO) << "Payload backoff logic is disabled because download "
+ << "will happen from local peer (via p2p).";
+ return false;
+ }
+ if (system_state_->request_params()->interactive()) {
+ LOG(INFO) << "Payload backoff disabled for interactive update checks.";
+ return false;
+ }
+ if (response_.is_delta_payload) {
+ // If delta payloads fail, we want to fallback quickly to full payloads as
+ // they are more likely to succeed. Exponential backoffs would greatly
+ // slow down the fallback to full payloads. So we don't backoff for delta
+ // payloads.
+ LOG(INFO) << "No backoffs for delta payloads. "
+ << "Can proceed with the download";
+ return false;
+ }
+
+ if (!system_state_->hardware()->IsOfficialBuild()) {
+ // Backoffs are needed only for official builds. We do not want any delays
+ // or update failures due to backoffs during testing or development.
+ LOG(INFO) << "No backoffs for test/dev images. "
+ << "Can proceed with the download";
+ return false;
+ }
+
+ if (backoff_expiry_time_.is_null()) {
+ LOG(INFO) << "No backoff expiry time has been set. "
+ << "Can proceed with the download";
+ return false;
+ }
+
+ if (backoff_expiry_time_ < Time::Now()) {
+ LOG(INFO) << "The backoff expiry time ("
+ << utils::ToString(backoff_expiry_time_)
+ << ") has elapsed. Can proceed with the download";
+ return false;
+ }
+
+ LOG(INFO) << "Cannot proceed with downloads as we need to backoff until "
+ << utils::ToString(backoff_expiry_time_);
+ return true;
+}
+
+void PayloadState::Rollback() {
+ SetRollbackVersion(system_state_->request_params()->app_version());
+ AttemptStarted(AttemptType::kRollback);
+}
+
+void PayloadState::IncrementPayloadAttemptNumber() {
+ // Update the payload attempt number for both payload types: full and delta.
+ SetPayloadAttemptNumber(GetPayloadAttemptNumber() + 1);
+
+ // Report the metric every time the value is incremented.
+ string metric = "Installer.PayloadAttemptNumber";
+ int value = GetPayloadAttemptNumber();
+
+ LOG(INFO) << "Uploading " << value << " (count) for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ value,
+ 1, // min value
+ 50, // max value
+ kNumDefaultUmaBuckets);
+}
+
+void PayloadState::IncrementFullPayloadAttemptNumber() {
+ // Update the payload attempt number for full payloads and the backoff time.
+ if (response_.is_delta_payload) {
+ LOG(INFO) << "Not incrementing payload attempt number for delta payloads";
+ return;
+ }
+
+ LOG(INFO) << "Incrementing the full payload attempt number";
+ SetFullPayloadAttemptNumber(GetFullPayloadAttemptNumber() + 1);
+ UpdateBackoffExpiryTime();
+
+ // Report the metric every time the value is incremented.
+ string metric = "Installer.FullPayloadAttemptNumber";
+ int value = GetFullPayloadAttemptNumber();
+
+ LOG(INFO) << "Uploading " << value << " (count) for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ value,
+ 1, // min value
+ 50, // max value
+ kNumDefaultUmaBuckets);
+}
+
+void PayloadState::IncrementUrlIndex() {
+ uint32_t next_url_index = GetUrlIndex() + 1;
+ if (next_url_index < candidate_urls_.size()) {
+ LOG(INFO) << "Incrementing the URL index for next attempt";
+ SetUrlIndex(next_url_index);
+ } else {
+ LOG(INFO) << "Resetting the current URL index (" << GetUrlIndex() << ") to "
+ << "0 as we only have " << candidate_urls_.size()
+ << " candidate URL(s)";
+ SetUrlIndex(0);
+ IncrementPayloadAttemptNumber();
+ IncrementFullPayloadAttemptNumber();
+ }
+
+ // If we have multiple URLs, record that we just switched to another one
+ if (candidate_urls_.size() > 1)
+ SetUrlSwitchCount(url_switch_count_ + 1);
+
+ // Whenever we update the URL index, we should also clear the URL failure
+ // count so we can start over fresh for the new URL.
+ SetUrlFailureCount(0);
+}
+
+void PayloadState::IncrementFailureCount() {
+ uint32_t next_url_failure_count = GetUrlFailureCount() + 1;
+ if (next_url_failure_count < response_.max_failure_count_per_url) {
+ LOG(INFO) << "Incrementing the URL failure count";
+ SetUrlFailureCount(next_url_failure_count);
+ } else {
+ LOG(INFO) << "Reached max number of failures for Url" << GetUrlIndex()
+ << ". Trying next available URL";
+ IncrementUrlIndex();
+ }
+}
+
+void PayloadState::UpdateBackoffExpiryTime() {
+ if (response_.disable_payload_backoff) {
+ LOG(INFO) << "Resetting backoff expiry time as payload backoff is disabled";
+ SetBackoffExpiryTime(Time());
+ return;
+ }
+
+ if (GetFullPayloadAttemptNumber() == 0) {
+ SetBackoffExpiryTime(Time());
+ return;
+ }
+
+ // Since we're doing left-shift below, make sure we don't shift more
+ // than this. E.g. if int is 4-bytes, don't left-shift more than 30 bits,
+ // since we don't expect value of kMaxBackoffDays to be more than 100 anyway.
+ int num_days = 1; // the value to be shifted.
+ const int kMaxShifts = (sizeof(num_days) * 8) - 2;
+
+ // Normal backoff days is 2 raised to (payload_attempt_number - 1).
+ // E.g. if payload_attempt_number is over 30, limit power to 30.
+ int power = min(GetFullPayloadAttemptNumber() - 1, kMaxShifts);
+
+ // The number of days is the minimum of 2 raised to (payload_attempt_number
+ // - 1) or kMaxBackoffDays.
+ num_days = min(num_days << power, kMaxBackoffDays);
+
+ // We don't want all retries to happen exactly at the same time when
+ // retrying after backoff. So add some random minutes to fuzz.
+ int fuzz_minutes = utils::FuzzInt(0, kMaxBackoffFuzzMinutes);
+ TimeDelta next_backoff_interval = TimeDelta::FromDays(num_days) +
+ TimeDelta::FromMinutes(fuzz_minutes);
+ LOG(INFO) << "Incrementing the backoff expiry time by "
+ << utils::FormatTimeDelta(next_backoff_interval);
+ SetBackoffExpiryTime(Time::Now() + next_backoff_interval);
+}
+
+void PayloadState::UpdateCurrentDownloadSource() {
+ current_download_source_ = kNumDownloadSources;
+
+ if (using_p2p_for_downloading_) {
+ current_download_source_ = kDownloadSourceHttpPeer;
+ } else if (GetUrlIndex() < candidate_urls_.size()) {
+ string current_url = candidate_urls_[GetUrlIndex()];
+ if (base::StartsWithASCII(current_url, "https://", false))
+ current_download_source_ = kDownloadSourceHttpsServer;
+ else if (base::StartsWithASCII(current_url, "http://", false))
+ current_download_source_ = kDownloadSourceHttpServer;
+ }
+
+ LOG(INFO) << "Current download source: "
+ << utils::ToString(current_download_source_);
+}
+
+void PayloadState::UpdateBytesDownloaded(size_t count) {
+ SetCurrentBytesDownloaded(
+ current_download_source_,
+ GetCurrentBytesDownloaded(current_download_source_) + count,
+ false);
+ SetTotalBytesDownloaded(
+ current_download_source_,
+ GetTotalBytesDownloaded(current_download_source_) + count,
+ false);
+
+ attempt_num_bytes_downloaded_ += count;
+}
+
+PayloadType PayloadState::CalculatePayloadType() {
+ PayloadType payload_type;
+ OmahaRequestParams* params = system_state_->request_params();
+ if (response_.is_delta_payload) {
+ payload_type = kPayloadTypeDelta;
+ } else if (params->delta_okay()) {
+ payload_type = kPayloadTypeFull;
+ } else { // Full payload, delta was not allowed by request.
+ payload_type = kPayloadTypeForcedFull;
+ }
+ return payload_type;
+}
+
+// TODO(zeuthen): Currently we don't report the UpdateEngine.Attempt.*
+// metrics if the attempt ends abnormally, e.g. if the update_engine
+// process crashes or the device is rebooted. See
+// http://crbug.com/357676
+void PayloadState::CollectAndReportAttemptMetrics(ErrorCode code) {
+ int attempt_number = GetPayloadAttemptNumber();
+
+ PayloadType payload_type = CalculatePayloadType();
+
+ int64_t payload_size = response_.size;
+
+ int64_t payload_bytes_downloaded = attempt_num_bytes_downloaded_;
+
+ ClockInterface *clock = system_state_->clock();
+ TimeDelta duration = clock->GetBootTime() - attempt_start_time_boot_;
+ TimeDelta duration_uptime = clock->GetMonotonicTime() -
+ attempt_start_time_monotonic_;
+
+ int64_t payload_download_speed_bps = 0;
+ int64_t usec = duration_uptime.InMicroseconds();
+ if (usec > 0) {
+ double sec = static_cast<double>(usec) / Time::kMicrosecondsPerSecond;
+ double bps = static_cast<double>(payload_bytes_downloaded) / sec;
+ payload_download_speed_bps = static_cast<int64_t>(bps);
+ }
+
+ DownloadSource download_source = current_download_source_;
+
+ metrics::DownloadErrorCode payload_download_error_code =
+ metrics::DownloadErrorCode::kUnset;
+ ErrorCode internal_error_code = ErrorCode::kSuccess;
+ metrics::AttemptResult attempt_result = utils::GetAttemptResult(code);
+
+ // Add additional detail to AttemptResult
+ switch (attempt_result) {
+ case metrics::AttemptResult::kPayloadDownloadError:
+ payload_download_error_code = utils::GetDownloadErrorCode(code);
+ break;
+
+ case metrics::AttemptResult::kInternalError:
+ internal_error_code = code;
+ break;
+
+ // Explicit fall-through for cases where we do not have additional
+ // detail. We avoid the default keyword to force people adding new
+ // AttemptResult values to visit this code and examine whether
+ // additional detail is needed.
+ case metrics::AttemptResult::kUpdateSucceeded:
+ case metrics::AttemptResult::kMetadataMalformed:
+ case metrics::AttemptResult::kOperationMalformed:
+ case metrics::AttemptResult::kOperationExecutionError:
+ case metrics::AttemptResult::kMetadataVerificationFailed:
+ case metrics::AttemptResult::kPayloadVerificationFailed:
+ case metrics::AttemptResult::kVerificationFailed:
+ case metrics::AttemptResult::kPostInstallFailed:
+ case metrics::AttemptResult::kAbnormalTermination:
+ case metrics::AttemptResult::kNumConstants:
+ case metrics::AttemptResult::kUnset:
+ break;
+ }
+
+ metrics::ReportUpdateAttemptMetrics(system_state_,
+ attempt_number,
+ payload_type,
+ duration,
+ duration_uptime,
+ payload_size,
+ payload_bytes_downloaded,
+ payload_download_speed_bps,
+ download_source,
+ attempt_result,
+ internal_error_code,
+ payload_download_error_code,
+ attempt_connection_type_);
+}
+
+void PayloadState::PersistAttemptMetrics() {
+ // TODO(zeuthen): For now we only persist whether an attempt was in
+ // progress and not values/metrics related to the attempt. This
+ // means that when this happens, of all the UpdateEngine.Attempt.*
+ // metrics, only UpdateEngine.Attempt.Result is reported (with the
+ // value |kAbnormalTermination|). In the future we might want to
+ // persist more data so we can report other metrics in the
+ // UpdateEngine.Attempt.* namespace when this happens.
+ prefs_->SetBoolean(kPrefsAttemptInProgress, true);
+}
+
+void PayloadState::ClearPersistedAttemptMetrics() {
+ prefs_->Delete(kPrefsAttemptInProgress);
+}
+
+void PayloadState::ReportAndClearPersistedAttemptMetrics() {
+ bool attempt_in_progress = false;
+ if (!prefs_->GetBoolean(kPrefsAttemptInProgress, &attempt_in_progress))
+ return;
+ if (!attempt_in_progress)
+ return;
+
+ metrics::ReportAbnormallyTerminatedUpdateAttemptMetrics(system_state_);
+
+ ClearPersistedAttemptMetrics();
+}
+
+void PayloadState::CollectAndReportSuccessfulUpdateMetrics() {
+ string metric;
+
+ // Report metrics collected from all known download sources to UMA.
+ int64_t successful_bytes_by_source[kNumDownloadSources];
+ int64_t total_bytes_by_source[kNumDownloadSources];
+ int64_t successful_bytes = 0;
+ int64_t total_bytes = 0;
+ int64_t successful_mbs = 0;
+ int64_t total_mbs = 0;
+
+ for (int i = 0; i < kNumDownloadSources; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+ int64_t bytes;
+
+ // Only consider this download source (and send byte counts) as
+ // having been used if we downloaded a non-trivial amount of bytes
+ // (e.g. at least 1 MiB) that contributed to the final success of
+ // the update. Otherwise we're going to end up with a lot of
+ // zero-byte events in the histogram.
+
+ bytes = GetCurrentBytesDownloaded(source);
+ successful_bytes_by_source[i] = bytes;
+ successful_bytes += bytes;
+ successful_mbs += bytes / kNumBytesInOneMiB;
+ SetCurrentBytesDownloaded(source, 0, true);
+
+ bytes = GetTotalBytesDownloaded(source);
+ total_bytes_by_source[i] = bytes;
+ total_bytes += bytes;
+ total_mbs += bytes / kNumBytesInOneMiB;
+ SetTotalBytesDownloaded(source, 0, true);
+ }
+
+ int download_overhead_percentage = 0;
+ if (successful_bytes > 0) {
+ download_overhead_percentage = (total_bytes - successful_bytes) * 100ULL /
+ successful_bytes;
+ }
+
+ int url_switch_count = static_cast<int>(url_switch_count_);
+
+ int reboot_count = GetNumReboots();
+
+ SetNumReboots(0);
+
+ TimeDelta duration = GetUpdateDuration();
+ TimeDelta duration_uptime = GetUpdateDurationUptime();
+
+ prefs_->Delete(kPrefsUpdateTimestampStart);
+ prefs_->Delete(kPrefsUpdateDurationUptime);
+
+ PayloadType payload_type = CalculatePayloadType();
+
+ int64_t payload_size = response_.size;
+
+ int attempt_count = GetPayloadAttemptNumber();
+
+ int updates_abandoned_count = num_responses_seen_ - 1;
+
+ metrics::ReportSuccessfulUpdateMetrics(system_state_,
+ attempt_count,
+ updates_abandoned_count,
+ payload_type,
+ payload_size,
+ total_bytes_by_source,
+ download_overhead_percentage,
+ duration,
+ reboot_count,
+ url_switch_count);
+
+ // TODO(zeuthen): This is the old metric reporting code which is
+ // slated for removal soon. See http://crbug.com/355745 for details.
+
+ // The old metrics code is using MiB's instead of bytes to calculate
+ // the overhead which due to rounding makes the numbers slightly
+ // different.
+ download_overhead_percentage = 0;
+ if (successful_mbs > 0) {
+ download_overhead_percentage = (total_mbs - successful_mbs) * 100ULL /
+ successful_mbs;
+ }
+
+ int download_sources_used = 0;
+ for (int i = 0; i < kNumDownloadSources; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+ const int kMaxMiBs = 10240; // Anything above 10GB goes in the last bucket.
+ int64_t mbs;
+
+ // Only consider this download source (and send byte counts) as
+ // having been used if we downloaded a non-trivial amount of bytes
+ // (e.g. at least 1 MiB) that contributed to the final success of
+ // the update. Otherwise we're going to end up with a lot of
+ // zero-byte events in the histogram.
+
+ mbs = successful_bytes_by_source[i] / kNumBytesInOneMiB;
+ if (mbs > 0) {
+ metric = "Installer.SuccessfulMBsDownloadedFrom" +
+ utils::ToString(source);
+ LOG(INFO) << "Uploading " << mbs << " (MBs) for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(metric,
+ mbs,
+ 0, // min
+ kMaxMiBs,
+ kNumDefaultUmaBuckets);
+ }
+
+ mbs = total_bytes_by_source[i] / kNumBytesInOneMiB;
+ if (mbs > 0) {
+ metric = "Installer.TotalMBsDownloadedFrom" + utils::ToString(source);
+ LOG(INFO) << "Uploading " << mbs << " (MBs) for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(metric,
+ mbs,
+ 0, // min
+ kMaxMiBs,
+ kNumDefaultUmaBuckets);
+ download_sources_used |= (1 << i);
+ }
+ }
+
+ metric = "Installer.DownloadSourcesUsed";
+ LOG(INFO) << "Uploading 0x" << std::hex << download_sources_used
+ << " (bit flags) for metric " << metric;
+ int num_buckets = min(1 << kNumDownloadSources, kNumDefaultUmaBuckets);
+ system_state_->metrics_lib()->SendToUMA(metric,
+ download_sources_used,
+ 0, // min
+ 1 << kNumDownloadSources,
+ num_buckets);
+
+ metric = "Installer.DownloadOverheadPercentage";
+ LOG(INFO) << "Uploading " << download_overhead_percentage
+ << "% for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(metric,
+ download_overhead_percentage,
+ 0, // min: 0% overhead
+ 1000, // max: 1000% overhead
+ kNumDefaultUmaBuckets);
+
+ metric = "Installer.UpdateURLSwitches";
+ LOG(INFO) << "Uploading " << url_switch_count
+ << " (count) for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ url_switch_count,
+ 0, // min value
+ 100, // max value
+ kNumDefaultUmaBuckets);
+
+ metric = "Installer.UpdateNumReboots";
+ LOG(INFO) << "Uploading reboot count of " << reboot_count << " for metric "
+ << metric;
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ reboot_count, // sample
+ 0, // min = 0.
+ 50, // max
+ 25); // buckets
+
+ metric = "Installer.UpdateDurationMinutes";
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ static_cast<int>(duration.InMinutes()),
+ 1, // min: 1 minute
+ 365*24*60, // max: 1 year (approx)
+ kNumDefaultUmaBuckets);
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration)
+ << " for metric " << metric;
+
+ metric = "Installer.UpdateDurationUptimeMinutes";
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ static_cast<int>(duration_uptime.InMinutes()),
+ 1, // min: 1 minute
+ 30*24*60, // max: 1 month (approx)
+ kNumDefaultUmaBuckets);
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration_uptime)
+ << " for metric " << metric;
+
+ metric = "Installer.PayloadFormat";
+ system_state_->metrics_lib()->SendEnumToUMA(
+ metric,
+ payload_type,
+ kNumPayloadTypes);
+ LOG(INFO) << "Uploading " << utils::ToString(payload_type)
+ << " for metric " << metric;
+
+ metric = "Installer.AttemptsCount.Total";
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ attempt_count,
+ 1, // min
+ 50, // max
+ kNumDefaultUmaBuckets);
+ LOG(INFO) << "Uploading " << attempt_count
+ << " for metric " << metric;
+
+ metric = "Installer.UpdatesAbandonedCount";
+ LOG(INFO) << "Uploading " << updates_abandoned_count
+ << " (count) for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ updates_abandoned_count,
+ 0, // min value
+ 100, // max value
+ kNumDefaultUmaBuckets);
+}
+
+void PayloadState::UpdateNumReboots() {
+ // We only update the reboot count when the system has been detected to have
+ // been rebooted.
+ if (!system_state_->system_rebooted()) {
+ return;
+ }
+
+ SetNumReboots(GetNumReboots() + 1);
+}
+
+void PayloadState::SetNumReboots(uint32_t num_reboots) {
+ CHECK(prefs_);
+ num_reboots_ = num_reboots;
+ prefs_->SetInt64(kPrefsNumReboots, num_reboots);
+ LOG(INFO) << "Number of Reboots during current update attempt = "
+ << num_reboots_;
+}
+
+void PayloadState::ResetPersistedState() {
+ SetPayloadAttemptNumber(0);
+ SetFullPayloadAttemptNumber(0);
+ SetUrlIndex(0);
+ SetUrlFailureCount(0);
+ SetUrlSwitchCount(0);
+ UpdateBackoffExpiryTime(); // This will reset the backoff expiry time.
+ SetUpdateTimestampStart(system_state_->clock()->GetWallclockTime());
+ SetUpdateTimestampEnd(Time()); // Set to null time
+ SetUpdateDurationUptime(TimeDelta::FromSeconds(0));
+ ResetDownloadSourcesOnNewUpdate();
+ ResetRollbackVersion();
+ SetP2PNumAttempts(0);
+ SetP2PFirstAttemptTimestamp(Time()); // Set to null time
+ SetScatteringWaitPeriod(TimeDelta());
+}
+
+void PayloadState::ResetRollbackVersion() {
+ CHECK(powerwash_safe_prefs_);
+ rollback_version_ = "";
+ powerwash_safe_prefs_->Delete(kPrefsRollbackVersion);
+}
+
+void PayloadState::ResetDownloadSourcesOnNewUpdate() {
+ for (int i = 0; i < kNumDownloadSources; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+ SetCurrentBytesDownloaded(source, 0, true);
+ // Note: Not resetting the TotalBytesDownloaded as we want that metric
+ // to count the bytes downloaded across various update attempts until
+ // we have successfully applied the update.
+ }
+}
+
+int64_t PayloadState::GetPersistedValue(const string& key) {
+ CHECK(prefs_);
+ if (!prefs_->Exists(key))
+ return 0;
+
+ int64_t stored_value;
+ if (!prefs_->GetInt64(key, &stored_value))
+ return 0;
+
+ if (stored_value < 0) {
+ LOG(ERROR) << key << ": Invalid value (" << stored_value
+ << ") in persisted state. Defaulting to 0";
+ return 0;
+ }
+
+ return stored_value;
+}
+
+string PayloadState::CalculateResponseSignature() {
+ string response_sign = base::StringPrintf(
+ "NumURLs = %d\n", static_cast<int>(candidate_urls_.size()));
+
+ for (size_t i = 0; i < candidate_urls_.size(); i++)
+ response_sign += base::StringPrintf("Candidate Url%d = %s\n",
+ static_cast<int>(i),
+ candidate_urls_[i].c_str());
+
+ response_sign += base::StringPrintf(
+ "Payload Size = %ju\n"
+ "Payload Sha256 Hash = %s\n"
+ "Metadata Size = %ju\n"
+ "Metadata Signature = %s\n"
+ "Is Delta Payload = %d\n"
+ "Max Failure Count Per Url = %d\n"
+ "Disable Payload Backoff = %d\n",
+ static_cast<uintmax_t>(response_.size),
+ response_.hash.c_str(),
+ static_cast<uintmax_t>(response_.metadata_size),
+ response_.metadata_signature.c_str(),
+ response_.is_delta_payload,
+ response_.max_failure_count_per_url,
+ response_.disable_payload_backoff);
+ return response_sign;
+}
+
+void PayloadState::LoadResponseSignature() {
+ CHECK(prefs_);
+ string stored_value;
+ if (prefs_->Exists(kPrefsCurrentResponseSignature) &&
+ prefs_->GetString(kPrefsCurrentResponseSignature, &stored_value)) {
+ SetResponseSignature(stored_value);
+ }
+}
+
+void PayloadState::SetResponseSignature(const string& response_signature) {
+ CHECK(prefs_);
+ response_signature_ = response_signature;
+ LOG(INFO) << "Current Response Signature = \n" << response_signature_;
+ prefs_->SetString(kPrefsCurrentResponseSignature, response_signature_);
+}
+
+void PayloadState::LoadPayloadAttemptNumber() {
+ SetPayloadAttemptNumber(GetPersistedValue(kPrefsPayloadAttemptNumber));
+}
+
+void PayloadState::LoadFullPayloadAttemptNumber() {
+ SetFullPayloadAttemptNumber(GetPersistedValue(
+ kPrefsFullPayloadAttemptNumber));
+}
+
+void PayloadState::SetPayloadAttemptNumber(int payload_attempt_number) {
+ CHECK(prefs_);
+ payload_attempt_number_ = payload_attempt_number;
+ LOG(INFO) << "Payload Attempt Number = " << payload_attempt_number_;
+ prefs_->SetInt64(kPrefsPayloadAttemptNumber, payload_attempt_number_);
+}
+
+void PayloadState::SetFullPayloadAttemptNumber(
+ int full_payload_attempt_number) {
+ CHECK(prefs_);
+ full_payload_attempt_number_ = full_payload_attempt_number;
+ LOG(INFO) << "Full Payload Attempt Number = " << full_payload_attempt_number_;
+ prefs_->SetInt64(kPrefsFullPayloadAttemptNumber,
+ full_payload_attempt_number_);
+}
+
+void PayloadState::LoadUrlIndex() {
+ SetUrlIndex(GetPersistedValue(kPrefsCurrentUrlIndex));
+}
+
+void PayloadState::SetUrlIndex(uint32_t url_index) {
+ CHECK(prefs_);
+ url_index_ = url_index;
+ LOG(INFO) << "Current URL Index = " << url_index_;
+ prefs_->SetInt64(kPrefsCurrentUrlIndex, url_index_);
+
+ // Also update the download source, which is purely dependent on the
+ // current URL index alone.
+ UpdateCurrentDownloadSource();
+}
+
+void PayloadState::LoadScatteringWaitPeriod() {
+ SetScatteringWaitPeriod(
+ TimeDelta::FromSeconds(GetPersistedValue(kPrefsWallClockWaitPeriod)));
+}
+
+void PayloadState::SetScatteringWaitPeriod(TimeDelta wait_period) {
+ CHECK(prefs_);
+ scattering_wait_period_ = wait_period;
+ LOG(INFO) << "Scattering Wait Period (seconds) = "
+ << scattering_wait_period_.InSeconds();
+ if (scattering_wait_period_.InSeconds() > 0) {
+ prefs_->SetInt64(kPrefsWallClockWaitPeriod,
+ scattering_wait_period_.InSeconds());
+ } else {
+ prefs_->Delete(kPrefsWallClockWaitPeriod);
+ }
+}
+
+void PayloadState::LoadUrlSwitchCount() {
+ SetUrlSwitchCount(GetPersistedValue(kPrefsUrlSwitchCount));
+}
+
+void PayloadState::SetUrlSwitchCount(uint32_t url_switch_count) {
+ CHECK(prefs_);
+ url_switch_count_ = url_switch_count;
+ LOG(INFO) << "URL Switch Count = " << url_switch_count_;
+ prefs_->SetInt64(kPrefsUrlSwitchCount, url_switch_count_);
+}
+
+void PayloadState::LoadUrlFailureCount() {
+ SetUrlFailureCount(GetPersistedValue(kPrefsCurrentUrlFailureCount));
+}
+
+void PayloadState::SetUrlFailureCount(uint32_t url_failure_count) {
+ CHECK(prefs_);
+ url_failure_count_ = url_failure_count;
+ LOG(INFO) << "Current URL (Url" << GetUrlIndex()
+ << ")'s Failure Count = " << url_failure_count_;
+ prefs_->SetInt64(kPrefsCurrentUrlFailureCount, url_failure_count_);
+}
+
+void PayloadState::LoadBackoffExpiryTime() {
+ CHECK(prefs_);
+ int64_t stored_value;
+ if (!prefs_->Exists(kPrefsBackoffExpiryTime))
+ return;
+
+ if (!prefs_->GetInt64(kPrefsBackoffExpiryTime, &stored_value))
+ return;
+
+ Time stored_time = Time::FromInternalValue(stored_value);
+ if (stored_time > Time::Now() + TimeDelta::FromDays(kMaxBackoffDays)) {
+ LOG(ERROR) << "Invalid backoff expiry time ("
+ << utils::ToString(stored_time)
+ << ") in persisted state. Resetting.";
+ stored_time = Time();
+ }
+ SetBackoffExpiryTime(stored_time);
+}
+
+void PayloadState::SetBackoffExpiryTime(const Time& new_time) {
+ CHECK(prefs_);
+ backoff_expiry_time_ = new_time;
+ LOG(INFO) << "Backoff Expiry Time = "
+ << utils::ToString(backoff_expiry_time_);
+ prefs_->SetInt64(kPrefsBackoffExpiryTime,
+ backoff_expiry_time_.ToInternalValue());
+}
+
+TimeDelta PayloadState::GetUpdateDuration() {
+ Time end_time = update_timestamp_end_.is_null()
+ ? system_state_->clock()->GetWallclockTime() :
+ update_timestamp_end_;
+ return end_time - update_timestamp_start_;
+}
+
+void PayloadState::LoadUpdateTimestampStart() {
+ int64_t stored_value;
+ Time stored_time;
+
+ CHECK(prefs_);
+
+ Time now = system_state_->clock()->GetWallclockTime();
+
+ if (!prefs_->Exists(kPrefsUpdateTimestampStart)) {
+ // The preference missing is not unexpected - in that case, just
+ // use the current time as start time
+ stored_time = now;
+ } else if (!prefs_->GetInt64(kPrefsUpdateTimestampStart, &stored_value)) {
+ LOG(ERROR) << "Invalid UpdateTimestampStart value. Resetting.";
+ stored_time = now;
+ } else {
+ stored_time = Time::FromInternalValue(stored_value);
+ }
+
+ // Sanity check: If the time read from disk is in the future
+ // (modulo some slack to account for possible NTP drift
+ // adjustments), something is fishy and we should report and
+ // reset.
+ TimeDelta duration_according_to_stored_time = now - stored_time;
+ if (duration_according_to_stored_time < -kDurationSlack) {
+ LOG(ERROR) << "The UpdateTimestampStart value ("
+ << utils::ToString(stored_time)
+ << ") in persisted state is "
+ << utils::FormatTimeDelta(duration_according_to_stored_time)
+ << " in the future. Resetting.";
+ stored_time = now;
+ }
+
+ SetUpdateTimestampStart(stored_time);
+}
+
+void PayloadState::SetUpdateTimestampStart(const Time& value) {
+ CHECK(prefs_);
+ update_timestamp_start_ = value;
+ prefs_->SetInt64(kPrefsUpdateTimestampStart,
+ update_timestamp_start_.ToInternalValue());
+ LOG(INFO) << "Update Timestamp Start = "
+ << utils::ToString(update_timestamp_start_);
+}
+
+void PayloadState::SetUpdateTimestampEnd(const Time& value) {
+ update_timestamp_end_ = value;
+ LOG(INFO) << "Update Timestamp End = "
+ << utils::ToString(update_timestamp_end_);
+}
+
+TimeDelta PayloadState::GetUpdateDurationUptime() {
+ return update_duration_uptime_;
+}
+
+void PayloadState::LoadUpdateDurationUptime() {
+ int64_t stored_value;
+ TimeDelta stored_delta;
+
+ CHECK(prefs_);
+
+ if (!prefs_->Exists(kPrefsUpdateDurationUptime)) {
+ // The preference missing is not unexpected - in that case, just
+ // we'll use zero as the delta
+ } else if (!prefs_->GetInt64(kPrefsUpdateDurationUptime, &stored_value)) {
+ LOG(ERROR) << "Invalid UpdateDurationUptime value. Resetting.";
+ stored_delta = TimeDelta::FromSeconds(0);
+ } else {
+ stored_delta = TimeDelta::FromInternalValue(stored_value);
+ }
+
+ // Sanity-check: Uptime can never be greater than the wall-clock
+ // difference (modulo some slack). If it is, report and reset
+ // to the wall-clock difference.
+ TimeDelta diff = GetUpdateDuration() - stored_delta;
+ if (diff < -kDurationSlack) {
+ LOG(ERROR) << "The UpdateDurationUptime value ("
+ << utils::FormatTimeDelta(stored_delta)
+ << ") in persisted state is "
+ << utils::FormatTimeDelta(diff)
+ << " larger than the wall-clock delta. Resetting.";
+ stored_delta = update_duration_current_;
+ }
+
+ SetUpdateDurationUptime(stored_delta);
+}
+
+void PayloadState::LoadNumReboots() {
+ SetNumReboots(GetPersistedValue(kPrefsNumReboots));
+}
+
+void PayloadState::LoadRollbackVersion() {
+ CHECK(powerwash_safe_prefs_);
+ string rollback_version;
+ if (powerwash_safe_prefs_->GetString(kPrefsRollbackVersion,
+ &rollback_version)) {
+ SetRollbackVersion(rollback_version);
+ }
+}
+
+void PayloadState::SetRollbackVersion(const string& rollback_version) {
+ CHECK(powerwash_safe_prefs_);
+ LOG(INFO) << "Blacklisting version "<< rollback_version;
+ rollback_version_ = rollback_version;
+ powerwash_safe_prefs_->SetString(kPrefsRollbackVersion, rollback_version);
+}
+
+void PayloadState::SetUpdateDurationUptimeExtended(const TimeDelta& value,
+ const Time& timestamp,
+ bool use_logging) {
+ CHECK(prefs_);
+ update_duration_uptime_ = value;
+ update_duration_uptime_timestamp_ = timestamp;
+ prefs_->SetInt64(kPrefsUpdateDurationUptime,
+ update_duration_uptime_.ToInternalValue());
+ if (use_logging) {
+ LOG(INFO) << "Update Duration Uptime = "
+ << utils::FormatTimeDelta(update_duration_uptime_);
+ }
+}
+
+void PayloadState::SetUpdateDurationUptime(const TimeDelta& value) {
+ Time now = system_state_->clock()->GetMonotonicTime();
+ SetUpdateDurationUptimeExtended(value, now, true);
+}
+
+void PayloadState::CalculateUpdateDurationUptime() {
+ Time now = system_state_->clock()->GetMonotonicTime();
+ TimeDelta uptime_since_last_update = now - update_duration_uptime_timestamp_;
+ TimeDelta new_uptime = update_duration_uptime_ + uptime_since_last_update;
+ // We're frequently called so avoid logging this write
+ SetUpdateDurationUptimeExtended(new_uptime, now, false);
+}
+
+string PayloadState::GetPrefsKey(const string& prefix, DownloadSource source) {
+ return prefix + "-from-" + utils::ToString(source);
+}
+
+void PayloadState::LoadCurrentBytesDownloaded(DownloadSource source) {
+ string key = GetPrefsKey(kPrefsCurrentBytesDownloaded, source);
+ SetCurrentBytesDownloaded(source, GetPersistedValue(key), true);
+}
+
+void PayloadState::SetCurrentBytesDownloaded(
+ DownloadSource source,
+ uint64_t current_bytes_downloaded,
+ bool log) {
+ CHECK(prefs_);
+
+ if (source >= kNumDownloadSources)
+ return;
+
+ // Update the in-memory value.
+ current_bytes_downloaded_[source] = current_bytes_downloaded;
+
+ string prefs_key = GetPrefsKey(kPrefsCurrentBytesDownloaded, source);
+ prefs_->SetInt64(prefs_key, current_bytes_downloaded);
+ LOG_IF(INFO, log) << "Current bytes downloaded for "
+ << utils::ToString(source) << " = "
+ << GetCurrentBytesDownloaded(source);
+}
+
+void PayloadState::LoadTotalBytesDownloaded(DownloadSource source) {
+ string key = GetPrefsKey(kPrefsTotalBytesDownloaded, source);
+ SetTotalBytesDownloaded(source, GetPersistedValue(key), true);
+}
+
+void PayloadState::SetTotalBytesDownloaded(
+ DownloadSource source,
+ uint64_t total_bytes_downloaded,
+ bool log) {
+ CHECK(prefs_);
+
+ if (source >= kNumDownloadSources)
+ return;
+
+ // Update the in-memory value.
+ total_bytes_downloaded_[source] = total_bytes_downloaded;
+
+ // Persist.
+ string prefs_key = GetPrefsKey(kPrefsTotalBytesDownloaded, source);
+ prefs_->SetInt64(prefs_key, total_bytes_downloaded);
+ LOG_IF(INFO, log) << "Total bytes downloaded for "
+ << utils::ToString(source) << " = "
+ << GetTotalBytesDownloaded(source);
+}
+
+void PayloadState::LoadNumResponsesSeen() {
+ SetNumResponsesSeen(GetPersistedValue(kPrefsNumResponsesSeen));
+}
+
+void PayloadState::SetNumResponsesSeen(int num_responses_seen) {
+ CHECK(prefs_);
+ num_responses_seen_ = num_responses_seen;
+ LOG(INFO) << "Num Responses Seen = " << num_responses_seen_;
+ prefs_->SetInt64(kPrefsNumResponsesSeen, num_responses_seen_);
+}
+
+void PayloadState::ReportUpdatesAbandonedEventCountMetric() {
+ string metric = "Installer.UpdatesAbandonedEventCount";
+ int value = num_responses_seen_ - 1;
+
+ // Do not send an "abandoned" event when 0 payloads were abandoned since the
+ // last successful update.
+ if (value == 0)
+ return;
+
+ LOG(INFO) << "Uploading " << value << " (count) for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ value,
+ 0, // min value
+ 100, // max value
+ kNumDefaultUmaBuckets);
+}
+
+void PayloadState::ComputeCandidateUrls() {
+ bool http_url_ok = true;
+
+ if (system_state_->hardware()->IsOfficialBuild()) {
+ const policy::DevicePolicy* policy = system_state_->device_policy();
+ if (policy && policy->GetHttpDownloadsEnabled(&http_url_ok) && !http_url_ok)
+ LOG(INFO) << "Downloads via HTTP Url are not enabled by device policy";
+ } else {
+ LOG(INFO) << "Allowing HTTP downloads for unofficial builds";
+ http_url_ok = true;
+ }
+
+ candidate_urls_.clear();
+ for (size_t i = 0; i < response_.payload_urls.size(); i++) {
+ string candidate_url = response_.payload_urls[i];
+ if (base::StartsWithASCII(candidate_url, "http://", false) && !http_url_ok)
+ continue;
+ candidate_urls_.push_back(candidate_url);
+ LOG(INFO) << "Candidate Url" << (candidate_urls_.size() - 1)
+ << ": " << candidate_url;
+ }
+
+ LOG(INFO) << "Found " << candidate_urls_.size() << " candidate URLs "
+ << "out of " << response_.payload_urls.size() << " URLs supplied";
+}
+
+void PayloadState::CreateSystemUpdatedMarkerFile() {
+ CHECK(prefs_);
+ int64_t value = system_state_->clock()->GetWallclockTime().ToInternalValue();
+ prefs_->SetInt64(kPrefsSystemUpdatedMarker, value);
+}
+
+void PayloadState::BootedIntoUpdate(TimeDelta time_to_reboot) {
+ // Send |time_to_reboot| as a UMA stat.
+ string metric = "Installer.TimeToRebootMinutes";
+ system_state_->metrics_lib()->SendToUMA(metric,
+ time_to_reboot.InMinutes(),
+ 0, // min: 0 minute
+ 30*24*60, // max: 1 month (approx)
+ kNumDefaultUmaBuckets);
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(time_to_reboot)
+ << " for metric " << metric;
+
+ metric = metrics::kMetricTimeToRebootMinutes;
+ system_state_->metrics_lib()->SendToUMA(metric,
+ time_to_reboot.InMinutes(),
+ 0, // min: 0 minute
+ 30*24*60, // max: 1 month (approx)
+ kNumDefaultUmaBuckets);
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(time_to_reboot)
+ << " for metric " << metric;
+}
+
+void PayloadState::UpdateEngineStarted() {
+ // Flush previous state from abnormal attempt failure, if any.
+ ReportAndClearPersistedAttemptMetrics();
+
+ // Avoid the UpdateEngineStarted actions if this is not the first time we
+ // run the update engine since reboot.
+ if (!system_state_->system_rebooted())
+ return;
+
+ // Figure out if we just booted into a new update
+ if (prefs_->Exists(kPrefsSystemUpdatedMarker)) {
+ int64_t stored_value;
+ if (prefs_->GetInt64(kPrefsSystemUpdatedMarker, &stored_value)) {
+ Time system_updated_at = Time::FromInternalValue(stored_value);
+ if (!system_updated_at.is_null()) {
+ TimeDelta time_to_reboot =
+ system_state_->clock()->GetWallclockTime() - system_updated_at;
+ if (time_to_reboot.ToInternalValue() < 0) {
+ LOG(ERROR) << "time_to_reboot is negative - system_updated_at: "
+ << utils::ToString(system_updated_at);
+ } else {
+ BootedIntoUpdate(time_to_reboot);
+ }
+ }
+ }
+ prefs_->Delete(kPrefsSystemUpdatedMarker);
+ }
+ // Check if it is needed to send metrics about a failed reboot into a new
+ // version.
+ ReportFailedBootIfNeeded();
+}
+
+void PayloadState::ReportFailedBootIfNeeded() {
+ // If the kPrefsTargetVersionInstalledFrom is present, a successfully applied
+ // payload was marked as ready immediately before the last reboot, and we
+ // need to check if such payload successfully rebooted or not.
+ if (prefs_->Exists(kPrefsTargetVersionInstalledFrom)) {
+ int64_t installed_from = 0;
+ if (!prefs_->GetInt64(kPrefsTargetVersionInstalledFrom, &installed_from)) {
+ LOG(ERROR) << "Error reading TargetVersionInstalledFrom on reboot.";
+ return;
+ }
+ if (static_cast<int>(installed_from) ==
+ utils::GetPartitionNumber(system_state_->hardware()->BootDevice())) {
+ // A reboot was pending, but the chromebook is again in the same
+ // BootDevice where the update was installed from.
+ int64_t target_attempt;
+ if (!prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt)) {
+ LOG(ERROR) << "Error reading TargetVersionAttempt when "
+ "TargetVersionInstalledFrom was present.";
+ target_attempt = 1;
+ }
+
+ // Report the UMA metric of the current boot failure.
+ string metric = "Installer.RebootToNewPartitionAttempt";
+
+ LOG(INFO) << "Uploading " << target_attempt
+ << " (count) for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ target_attempt,
+ 1, // min value
+ 50, // max value
+ kNumDefaultUmaBuckets);
+
+ metric = metrics::kMetricFailedUpdateCount;
+ LOG(INFO) << "Uploading " << target_attempt
+ << " (count) for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ target_attempt,
+ 1, // min value
+ 50, // max value
+ kNumDefaultUmaBuckets);
+ } else {
+ prefs_->Delete(kPrefsTargetVersionAttempt);
+ prefs_->Delete(kPrefsTargetVersionUniqueId);
+ }
+ prefs_->Delete(kPrefsTargetVersionInstalledFrom);
+ }
+}
+
+void PayloadState::ExpectRebootInNewVersion(const string& target_version_uid) {
+ // Expect to boot into the new partition in the next reboot setting the
+ // TargetVersion* flags in the Prefs.
+ string stored_target_version_uid;
+ string target_version_id;
+ string target_partition;
+ int64_t target_attempt;
+
+ if (prefs_->Exists(kPrefsTargetVersionUniqueId) &&
+ prefs_->GetString(kPrefsTargetVersionUniqueId,
+ &stored_target_version_uid) &&
+ stored_target_version_uid == target_version_uid) {
+ if (!prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt))
+ target_attempt = 0;
+ } else {
+ prefs_->SetString(kPrefsTargetVersionUniqueId, target_version_uid);
+ target_attempt = 0;
+ }
+ prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt + 1);
+
+ prefs_->SetInt64(kPrefsTargetVersionInstalledFrom,
+ utils::GetPartitionNumber(
+ system_state_->hardware()->BootDevice()));
+}
+
+void PayloadState::ResetUpdateStatus() {
+ // Remove the TargetVersionInstalledFrom pref so that if the machine is
+ // rebooted the next boot is not flagged as failed to rebooted into the
+ // new applied payload.
+ prefs_->Delete(kPrefsTargetVersionInstalledFrom);
+
+ // Also decrement the attempt number if it exists.
+ int64_t target_attempt;
+ if (prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt))
+ prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt-1);
+}
+
+int PayloadState::GetP2PNumAttempts() {
+ return p2p_num_attempts_;
+}
+
+void PayloadState::SetP2PNumAttempts(int value) {
+ p2p_num_attempts_ = value;
+ LOG(INFO) << "p2p Num Attempts = " << p2p_num_attempts_;
+ CHECK(prefs_);
+ prefs_->SetInt64(kPrefsP2PNumAttempts, value);
+}
+
+void PayloadState::LoadP2PNumAttempts() {
+ SetP2PNumAttempts(GetPersistedValue(kPrefsP2PNumAttempts));
+}
+
+Time PayloadState::GetP2PFirstAttemptTimestamp() {
+ return p2p_first_attempt_timestamp_;
+}
+
+void PayloadState::SetP2PFirstAttemptTimestamp(const Time& time) {
+ p2p_first_attempt_timestamp_ = time;
+ LOG(INFO) << "p2p First Attempt Timestamp = "
+ << utils::ToString(p2p_first_attempt_timestamp_);
+ CHECK(prefs_);
+ int64_t stored_value = time.ToInternalValue();
+ prefs_->SetInt64(kPrefsP2PFirstAttemptTimestamp, stored_value);
+}
+
+void PayloadState::LoadP2PFirstAttemptTimestamp() {
+ int64_t stored_value = GetPersistedValue(kPrefsP2PFirstAttemptTimestamp);
+ Time stored_time = Time::FromInternalValue(stored_value);
+ SetP2PFirstAttemptTimestamp(stored_time);
+}
+
+void PayloadState::P2PNewAttempt() {
+ CHECK(prefs_);
+ // Set timestamp, if it hasn't been set already
+ if (p2p_first_attempt_timestamp_.is_null()) {
+ SetP2PFirstAttemptTimestamp(system_state_->clock()->GetWallclockTime());
+ }
+ // Increase number of attempts
+ SetP2PNumAttempts(GetP2PNumAttempts() + 1);
+}
+
+bool PayloadState::P2PAttemptAllowed() {
+ if (p2p_num_attempts_ > kMaxP2PAttempts) {
+ LOG(INFO) << "Number of p2p attempts is " << p2p_num_attempts_
+ << " which is greater than "
+ << kMaxP2PAttempts
+ << " - disallowing p2p.";
+ return false;
+ }
+
+ if (!p2p_first_attempt_timestamp_.is_null()) {
+ Time now = system_state_->clock()->GetWallclockTime();
+ TimeDelta time_spent_attempting_p2p = now - p2p_first_attempt_timestamp_;
+ if (time_spent_attempting_p2p.InSeconds() < 0) {
+ LOG(ERROR) << "Time spent attempting p2p is negative"
+ << " - disallowing p2p.";
+ return false;
+ }
+ if (time_spent_attempting_p2p.InSeconds() > kMaxP2PAttemptTimeSeconds) {
+ LOG(INFO) << "Time spent attempting p2p is "
+ << utils::FormatTimeDelta(time_spent_attempting_p2p)
+ << " which is greater than "
+ << utils::FormatTimeDelta(TimeDelta::FromSeconds(
+ kMaxP2PAttemptTimeSeconds))
+ << " - disallowing p2p.";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_state.h b/payload_state.h
new file mode 100644
index 0000000..7def799
--- /dev/null
+++ b/payload_state.h
@@ -0,0 +1,568 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_STATE_H_
+#define UPDATE_ENGINE_PAYLOAD_STATE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/metrics.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// Encapsulates all the payload state required for download. This includes the
+// state necessary for handling multiple URLs in Omaha response, the backoff
+// state, etc. All state is persisted so that we use the most recently saved
+// value when resuming the update_engine process. All state is also cached in
+// memory so that we ensure we always make progress based on last known good
+// state even when there's any issue in reading/writing from the file system.
+class PayloadState : public PayloadStateInterface {
+ public:
+ PayloadState();
+ ~PayloadState() override {}
+
+ // Initializes a payload state object using the given global system state.
+ // It performs the initial loading of all persisted state into memory and
+ // dumps the initial state for debugging purposes. Note: the other methods
+ // should be called only after calling Initialize on this object.
+ bool Initialize(SystemState* system_state);
+
+ // Implementation of PayloadStateInterface methods.
+ void SetResponse(const OmahaResponse& response) override;
+ void DownloadComplete() override;
+ void DownloadProgress(size_t count) override;
+ void UpdateResumed() override;
+ void UpdateRestarted() override;
+ void UpdateSucceeded() override;
+ void UpdateFailed(ErrorCode error) override;
+ void ResetUpdateStatus() override;
+ bool ShouldBackoffDownload() override;
+ void Rollback() override;
+ void ExpectRebootInNewVersion(const std::string& target_version_uid) override;
+ void SetUsingP2PForDownloading(bool value) override;
+
+ void SetUsingP2PForSharing(bool value) override {
+ using_p2p_for_sharing_ = value;
+ }
+
+ inline std::string GetResponseSignature() override {
+ return response_signature_;
+ }
+
+ inline int GetFullPayloadAttemptNumber() override {
+ return full_payload_attempt_number_;
+ }
+
+ inline int GetPayloadAttemptNumber() override {
+ return payload_attempt_number_;
+ }
+
+ inline std::string GetCurrentUrl() override {
+ return candidate_urls_.size() ? candidate_urls_[url_index_] : "";
+ }
+
+ inline uint32_t GetUrlFailureCount() override {
+ return url_failure_count_;
+ }
+
+ inline uint32_t GetUrlSwitchCount() override {
+ return url_switch_count_;
+ }
+
+ inline int GetNumResponsesSeen() override {
+ return num_responses_seen_;
+ }
+
+ inline base::Time GetBackoffExpiryTime() override {
+ return backoff_expiry_time_;
+ }
+
+ base::TimeDelta GetUpdateDuration() override;
+
+ base::TimeDelta GetUpdateDurationUptime() override;
+
+ inline uint64_t GetCurrentBytesDownloaded(DownloadSource source) override {
+ return source < kNumDownloadSources ? current_bytes_downloaded_[source] : 0;
+ }
+
+ inline uint64_t GetTotalBytesDownloaded(DownloadSource source) override {
+ return source < kNumDownloadSources ? total_bytes_downloaded_[source] : 0;
+ }
+
+ inline uint32_t GetNumReboots() override {
+ return num_reboots_;
+ }
+
+ void UpdateEngineStarted() override;
+
+ inline std::string GetRollbackVersion() override {
+ return rollback_version_;
+ }
+
+ int GetP2PNumAttempts() override;
+ base::Time GetP2PFirstAttemptTimestamp() override;
+ void P2PNewAttempt() override;
+ bool P2PAttemptAllowed() override;
+
+ bool GetUsingP2PForDownloading() const override {
+ return using_p2p_for_downloading_;
+ }
+
+ bool GetUsingP2PForSharing() const override {
+ return using_p2p_for_sharing_;
+ }
+
+ base::TimeDelta GetScatteringWaitPeriod() override {
+ return scattering_wait_period_;
+ }
+
+ void SetScatteringWaitPeriod(base::TimeDelta wait_period) override;
+
+ void SetP2PUrl(const std::string& url) override {
+ p2p_url_ = url;
+ }
+
+ std::string GetP2PUrl() const override {
+ return p2p_url_;
+ }
+
+ private:
+ enum class AttemptType {
+ kUpdate,
+ kRollback,
+ };
+
+ friend class PayloadStateTest;
+ FRIEND_TEST(PayloadStateTest, RebootAfterUpdateFailedMetric);
+ FRIEND_TEST(PayloadStateTest, RebootAfterUpdateSucceed);
+ FRIEND_TEST(PayloadStateTest, RebootAfterCanceledUpdate);
+ FRIEND_TEST(PayloadStateTest, RollbackVersion);
+ FRIEND_TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs);
+
+ // Helper called when an attempt has begun, is called by
+ // UpdateResumed(), UpdateRestarted() and Rollback().
+ void AttemptStarted(AttemptType attempt_type);
+
+ // Increments the payload attempt number used for metrics.
+ void IncrementPayloadAttemptNumber();
+
+ // Increments the payload attempt number which governs the backoff behavior
+ // at the time of the next update check.
+ void IncrementFullPayloadAttemptNumber();
+
+ // Advances the current URL index to the next available one. If all URLs have
+ // been exhausted during the current payload download attempt (as indicated
+ // by the payload attempt number), then it will increment the payload attempt
+ // number and wrap around again with the first URL in the list. This also
+ // updates the URL switch count, if needed.
+ void IncrementUrlIndex();
+
+ // Increments the failure count of the current URL. If the configured max
+ // failure count is reached for this URL, it advances the current URL index
+ // to the next URL and resets the failure count for that URL.
+ void IncrementFailureCount();
+
+ // Updates the backoff expiry time exponentially based on the current
+ // payload attempt number.
+ void UpdateBackoffExpiryTime();
+
+ // Updates the value of current download source based on the current URL
+ // index. If the download source is not one of the known sources, it's set
+ // to kNumDownloadSources.
+ void UpdateCurrentDownloadSource();
+
+ // Updates the various metrics corresponding with the given number of bytes
+ // that were downloaded recently.
+ void UpdateBytesDownloaded(size_t count);
+
+ // Calculates the PayloadType we're using.
+ PayloadType CalculatePayloadType();
+
+ // Collects and reports the various metrics related to an update attempt.
+ void CollectAndReportAttemptMetrics(ErrorCode code);
+
+ // Persists values related to the UpdateEngine.Attempt.* metrics so
+ // we can identify later if an update attempt ends abnormally.
+ void PersistAttemptMetrics();
+
+ // Clears persistent state previously set using AttemptMetricsPersist().
+ void ClearPersistedAttemptMetrics();
+
+ // Checks if persistent state previously set using AttemptMetricsPersist()
+ // exists and, if so, emits it with |attempt_result| set to
+ // metrics::AttemptResult::kAbnormalTermination.
+ void ReportAndClearPersistedAttemptMetrics();
+
+ // Collects and reports the various metrics related to a successful update.
+ void CollectAndReportSuccessfulUpdateMetrics();
+
+ // Checks if we were expecting to be running in the new version but the
+ // boot into the new version failed for some reason. If that's the case, an
+ // UMA metric is sent reporting the number of attempts the same applied
+ // payload was attempted to reboot. This function is called by UpdateAttempter
+ // every time the update engine starts and there's no reboot pending.
+ void ReportFailedBootIfNeeded();
+
+ // Resets all the persisted state values which are maintained relative to the
+ // current response signature. The response signature itself is not reset.
+ void ResetPersistedState();
+
+ // Resets the appropriate state related to download sources that need to be
+ // reset on a new update.
+ void ResetDownloadSourcesOnNewUpdate();
+
+ // Returns the persisted value from prefs_ for the given key. It also
+ // validates that the value returned is non-negative.
+ int64_t GetPersistedValue(const std::string& key);
+
+ // Calculates the response "signature", which is basically a string composed
+ // of the subset of the fields in the current response that affect the
+ // behavior of the PayloadState.
+ std::string CalculateResponseSignature();
+
+ // Initializes the current response signature from the persisted state.
+ void LoadResponseSignature();
+
+ // Sets the response signature to the given value. Also persists the value
+ // being set so that we resume from the save value in case of a process
+ // restart.
+ void SetResponseSignature(const std::string& response_signature);
+
+ // Initializes the payload attempt number from the persisted state.
+ void LoadPayloadAttemptNumber();
+
+ // Initializes the payload attempt number for full payloads from the persisted
+ // state.
+ void LoadFullPayloadAttemptNumber();
+
+ // Sets the payload attempt number to the given value. Also persists the
+ // value being set so that we resume from the same value in case of a process
+ // restart.
+ void SetPayloadAttemptNumber(int payload_attempt_number);
+
+ // Sets the payload attempt number for full updates to the given value. Also
+ // persists the value being set so that we resume from the same value in case
+ // of a process restart.
+ void SetFullPayloadAttemptNumber(int payload_attempt_number);
+
+ // Initializes the current URL index from the persisted state.
+ void LoadUrlIndex();
+
+ // Sets the current URL index to the given value. Also persists the value
+ // being set so that we resume from the same value in case of a process
+ // restart.
+ void SetUrlIndex(uint32_t url_index);
+
+ // Initializes the current URL's failure count from the persisted stae.
+ void LoadUrlFailureCount();
+
+ // Sets the current URL's failure count to the given value. Also persists the
+ // value being set so that we resume from the same value in case of a process
+ // restart.
+ void SetUrlFailureCount(uint32_t url_failure_count);
+
+ // Sets |url_switch_count_| to the given value and persists the value.
+ void SetUrlSwitchCount(uint32_t url_switch_count);
+
+ // Initializes |url_switch_count_| from the persisted stae.
+ void LoadUrlSwitchCount();
+
+ // Initializes the backoff expiry time from the persisted state.
+ void LoadBackoffExpiryTime();
+
+ // Sets the backoff expiry time to the given value. Also persists the value
+ // being set so that we resume from the same value in case of a process
+ // restart.
+ void SetBackoffExpiryTime(const base::Time& new_time);
+
+ // Initializes |update_timestamp_start_| from the persisted state.
+ void LoadUpdateTimestampStart();
+
+ // Sets |update_timestamp_start_| to the given value and persists the value.
+ void SetUpdateTimestampStart(const base::Time& value);
+
+ // Sets |update_timestamp_end_| to the given value. This is not persisted
+ // as it happens at the end of the update process where state is deleted
+ // anyway.
+ void SetUpdateTimestampEnd(const base::Time& value);
+
+ // Initializes |update_duration_uptime_| from the persisted state.
+ void LoadUpdateDurationUptime();
+
+ // Helper method used in SetUpdateDurationUptime() and
+ // CalculateUpdateDurationUptime().
+ void SetUpdateDurationUptimeExtended(const base::TimeDelta& value,
+ const base::Time& timestamp,
+ bool use_logging);
+
+ // Sets |update_duration_uptime_| to the given value and persists
+ // the value and sets |update_duration_uptime_timestamp_| to the
+ // current monotonic time.
+ void SetUpdateDurationUptime(const base::TimeDelta& value);
+
+ // Adds the difference between current monotonic time and
+ // |update_duration_uptime_timestamp_| to |update_duration_uptime_| and
+ // sets |update_duration_uptime_timestamp_| to current monotonic time.
+ void CalculateUpdateDurationUptime();
+
+ // Returns the full key for a download source given the prefix.
+ std::string GetPrefsKey(const std::string& prefix, DownloadSource source);
+
+ // Loads the number of bytes that have been currently downloaded through the
+ // previous attempts from the persisted state for the given source. It's
+ // reset to 0 everytime we begin a full update and is continued from previous
+ // attempt if we're resuming the update.
+ void LoadCurrentBytesDownloaded(DownloadSource source);
+
+ // Sets the number of bytes that have been currently downloaded for the
+ // given source. This value is also persisted.
+ void SetCurrentBytesDownloaded(DownloadSource source,
+ uint64_t current_bytes_downloaded,
+ bool log);
+
+ // Loads the total number of bytes that have been downloaded (since the last
+ // successful update) from the persisted state for the given source. It's
+ // reset to 0 everytime we successfully apply an update and counts the bytes
+ // downloaded for both successful and failed attempts since then.
+ void LoadTotalBytesDownloaded(DownloadSource source);
+
+ // Sets the total number of bytes that have been downloaded so far for the
+ // given source. This value is also persisted.
+ void SetTotalBytesDownloaded(DownloadSource source,
+ uint64_t total_bytes_downloaded,
+ bool log);
+
+ // Loads the blacklisted version from our prefs file.
+ void LoadRollbackVersion();
+
+ // Blacklists this version from getting AU'd to until we receive a new update
+ // response.
+ void SetRollbackVersion(const std::string& rollback_version);
+
+ // Clears any blacklisted version.
+ void ResetRollbackVersion();
+
+ inline uint32_t GetUrlIndex() {
+ return url_index_;
+ }
+
+ // Computes the list of candidate URLs from the total list of payload URLs in
+ // the Omaha response.
+ void ComputeCandidateUrls();
+
+ // Sets |num_responses_seen_| and persist it to disk.
+ void SetNumResponsesSeen(int num_responses_seen);
+
+ // Initializes |num_responses_seen_| from persisted state.
+ void LoadNumResponsesSeen();
+
+ // Reports metric conveying how many times updates were abandoned since
+ // the last update was applied. The difference between this metric and the
+ // previous ReportUpdatesAbandonedCountMetric() one is that this metric is
+ // reported every time an update is abandoned, as oposed to the mentioned
+ // metric that is reported once the new update was applied.
+ void ReportUpdatesAbandonedEventCountMetric();
+
+ // Initializes |num_reboots_| from the persisted state.
+ void LoadNumReboots();
+
+ // Sets |num_reboots| for the update attempt. Also persists the
+ // value being set so that we resume from the same value in case of a process
+ // restart.
+ void SetNumReboots(uint32_t num_reboots);
+
+ // Checks to see if the device rebooted since the last call and if so
+ // increments num_reboots.
+ void UpdateNumReboots();
+
+ // Writes the current wall-clock time to the kPrefsSystemUpdatedMarker
+ // state variable.
+ void CreateSystemUpdatedMarkerFile();
+
+ // Called at program startup if the device booted into a new update.
+ // The |time_to_reboot| parameter contains the (wall-clock) duration
+ // from when the update successfully completed (the value written
+ // into the kPrefsSystemUpdatedMarker state variable) until the device
+ // was booted into the update (current wall-clock time).
+ void BootedIntoUpdate(base::TimeDelta time_to_reboot);
+
+ // Loads the |kPrefsP2PFirstAttemptTimestamp| state variable from disk
+ // into |p2p_first_attempt_timestamp_|.
+ void LoadP2PFirstAttemptTimestamp();
+
+ // Loads the |kPrefsP2PNumAttempts| state variable into |p2p_num_attempts_|.
+ void LoadP2PNumAttempts();
+
+ // Sets the |kPrefsP2PNumAttempts| state variable to |value|.
+ void SetP2PNumAttempts(int value);
+
+ // Sets the |kPrefsP2PFirstAttemptTimestamp| state variable to |time|.
+ void SetP2PFirstAttemptTimestamp(const base::Time& time);
+
+ // Loads the persisted scattering wallclock-based wait period.
+ void LoadScatteringWaitPeriod();
+
+ // The global state of the system.
+ SystemState* system_state_;
+
+ // Interface object with which we read/write persisted state. This must
+ // be set by calling the Initialize method before calling any other method.
+ PrefsInterface* prefs_;
+
+ // Interface object with which we read/write persisted state. This must
+ // be set by calling the Initialize method before calling any other method.
+ // This object persists across powerwashes.
+ PrefsInterface* powerwash_safe_prefs_;
+
+ // This is the current response object from Omaha.
+ OmahaResponse response_;
+
+ // Whether P2P is being used for downloading and sharing.
+ bool using_p2p_for_downloading_;
+ bool using_p2p_for_sharing_;
+
+ // Stores the P2P download URL, if one is used.
+ std::string p2p_url_;
+
+ // The cached value of |kPrefsP2PFirstAttemptTimestamp|.
+ base::Time p2p_first_attempt_timestamp_;
+
+ // The cached value of |kPrefsP2PNumAttempts|.
+ int p2p_num_attempts_;
+
+ // This stores a "signature" of the current response. The signature here
+ // refers to a subset of the current response from Omaha. Each update to
+ // this value is persisted so we resume from the same value in case of a
+ // process restart.
+ std::string response_signature_;
+
+ // The number of times we've tried to download the payload. This is
+ // incremented each time we download the payload successsfully or when we
+ // exhaust all failure limits for all URLs and are about to wrap around back
+ // to the first URL. Each update to this value is persisted so we resume from
+ // the same value in case of a process restart.
+ int payload_attempt_number_;
+
+ // The number of times we've tried to download the payload in full. This is
+ // incremented each time we download the payload in full successsfully or
+ // when we exhaust all failure limits for all URLs and are about to wrap
+ // around back to the first URL. Each update to this value is persisted so
+ // we resume from the same value in case of a process restart.
+ int full_payload_attempt_number_;
+
+ // The index of the current URL. This type is different from the one in the
+ // accessor methods because PrefsInterface supports only int64_t but we want
+ // to provide a stronger abstraction of uint32_t. Each update to this value
+ // is persisted so we resume from the same value in case of a process
+ // restart.
+ int64_t url_index_;
+
+ // The count of failures encountered in the current attempt to download using
+ // the current URL (specified by url_index_). Each update to this value is
+ // persisted so we resume from the same value in case of a process restart.
+ int64_t url_failure_count_;
+
+ // The number of times we've switched URLs.
+ int32_t url_switch_count_;
+
+ // The current download source based on the current URL. This value is
+ // not persisted as it can be recomputed everytime we update the URL.
+ // We're storing this so as not to recompute this on every few bytes of
+ // data we read from the socket.
+ DownloadSource current_download_source_;
+
+ // The number of different Omaha responses seen. Increases every time
+ // a new response is seen. Resets to 0 only when the system has been
+ // successfully updated.
+ int num_responses_seen_;
+
+ // The number of system reboots during an update attempt. Technically since
+ // we don't go out of our way to not update it when not attempting an update,
+ // also records the number of reboots before the next update attempt starts.
+ uint32_t num_reboots_;
+
+ // The timestamp until which we've to wait before attempting to download the
+ // payload again, so as to backoff repeated downloads.
+ base::Time backoff_expiry_time_;
+
+ // The most recently calculated value of the update duration.
+ base::TimeDelta update_duration_current_;
+
+ // The point in time (wall-clock) that the update was started.
+ base::Time update_timestamp_start_;
+
+ // The point in time (wall-clock) that the update ended. If the update
+ // is still in progress, this is set to the Epoch (e.g. 0).
+ base::Time update_timestamp_end_;
+
+ // The update duration uptime
+ base::TimeDelta update_duration_uptime_;
+
+ // The monotonic time when |update_duration_uptime_| was last set
+ base::Time update_duration_uptime_timestamp_;
+
+ // The number of bytes that have been downloaded for each source for each new
+ // update attempt. If we resume an update, we'll continue from the previous
+ // value, but if we get a new response or if the previous attempt failed,
+ // we'll reset this to 0 to start afresh. Each update to this value is
+ // persisted so we resume from the same value in case of a process restart.
+ // The extra index in the array is to no-op accidental access in case the
+ // return value from GetCurrentDownloadSource is used without validation.
+ uint64_t current_bytes_downloaded_[kNumDownloadSources + 1];
+
+ // The number of bytes that have been downloaded for each source since the
+ // the last successful update. This is used to compute the overhead we incur.
+ // Each update to this value is persisted so we resume from the same value in
+ // case of a process restart.
+ // The extra index in the array is to no-op accidental access in case the
+ // return value from GetCurrentDownloadSource is used without validation.
+ uint64_t total_bytes_downloaded_[kNumDownloadSources + 1];
+
+ // A small timespan used when comparing wall-clock times for coping
+ // with the fact that clocks drift and consequently are adjusted
+ // (either forwards or backwards) via NTP.
+ static const base::TimeDelta kDurationSlack;
+
+ // The ordered list of the subset of payload URL candidates which are
+ // allowed as per device policy.
+ std::vector<std::string> candidate_urls_;
+
+ // This stores a blacklisted version set as part of rollback. When we rollback
+ // we store the version of the os from which we are rolling back from in order
+ // to guarantee that we do not re-update to it on the next au attempt after
+ // reboot.
+ std::string rollback_version_;
+
+ // The number of bytes downloaded per attempt.
+ int64_t attempt_num_bytes_downloaded_;
+
+ // The boot time when the attempt was started.
+ base::Time attempt_start_time_boot_;
+
+ // The monotonic time when the attempt was started.
+ base::Time attempt_start_time_monotonic_;
+
+ // The connection type when the attempt started.
+ metrics::ConnectionType attempt_connection_type_;
+
+ // Whether we're currently rolling back.
+ AttemptType attempt_type_;
+
+ // The current scattering wallclock-based wait period.
+ base::TimeDelta scattering_wait_period_;
+
+ DISALLOW_COPY_AND_ASSIGN(PayloadState);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_STATE_H_
diff --git a/payload_state_interface.h b/payload_state_interface.h
new file mode 100644
index 0000000..95344c6
--- /dev/null
+++ b/payload_state_interface.h
@@ -0,0 +1,187 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_
+#define UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_
+
+#include <string>
+
+#include "update_engine/action_processor.h"
+#include "update_engine/constants.h"
+#include "update_engine/omaha_response.h"
+
+namespace chromeos_update_engine {
+
+// Describes the methods that need to be implemented by the PayloadState class.
+// This interface has been carved out to support mocking of the PayloadState
+// object.
+class PayloadStateInterface {
+ public:
+ virtual ~PayloadStateInterface() = default;
+
+ // Sets the internal payload state based on the given Omaha response. This
+ // response could be the same or different from the one for which we've stored
+ // the internal state. If it's different, then this method resets all the
+ // internal state corresponding to the old response. Since the Omaha response
+ // has a lot of fields that are not related to payload state, it uses only
+ // a subset of the fields in the Omaha response to compare equality.
+ virtual void SetResponse(const OmahaResponse& response) = 0;
+
+ // This method should be called whenever we have completed downloading all
+ // the bytes of a payload and have verified that its size and hash match the
+ // expected values. We use this notificaiton to increment the payload attempt
+ // number so that the throttle the next attempt to download the same payload
+ // (in case there's an error in subsequent steps such as post-install)
+ // appropriately.
+ virtual void DownloadComplete() = 0;
+
+ // This method should be called whenever we receive new bytes from the
+ // network for the current payload. We use this notification to reset the
+ // failure count for a given URL since receipt of some bytes means we are
+ // able to make forward progress with the current URL.
+ virtual void DownloadProgress(size_t count) = 0;
+
+ // This method should be called every time we resume an update attempt.
+ virtual void UpdateResumed() = 0;
+
+ // This method should be called every time we begin a new update. This method
+ // should not be called when we resume an update from the previously
+ // downloaded point. This is used to reset the metrics for each new update.
+ virtual void UpdateRestarted() = 0;
+
+ // This method should be called once after an update attempt succeeds. This
+ // is when the relevant UMA metrics that are tracked on a per-update-basis
+ // are uploaded to the UMA server.
+ virtual void UpdateSucceeded() = 0;
+
+ // This method should be called whenever an update attempt fails with the
+ // given error code. We use this notification to update the payload state
+ // depending on the type of the error that happened.
+ virtual void UpdateFailed(ErrorCode error) = 0;
+
+ // This method should be called whenever a succeeded update is canceled, and
+ // thus can only be called after UpdateSucceeded(). This is currently used
+ // only for manual testing using the update_engine_client.
+ virtual void ResetUpdateStatus() = 0;
+
+ // This method should be called every time we initiate a Rollback.
+ virtual void Rollback() = 0;
+
+ // Sets the expectations to boot into the new version in the next reboot.
+ // This function is called every time a new update is marked as ready by
+ // UpdateSuccess(). |target_version_uid| is an unique identifier of the
+ // applied payload. It can be any string, as long as the same string is used
+ // for the same payload.
+ virtual void ExpectRebootInNewVersion(
+ const std::string& target_version_uid) = 0;
+
+ // Sets whether P2P is being used to download the update payload. This
+ // is used to keep track of download sources being used and should be called
+ // before the transfer begins.
+ virtual void SetUsingP2PForDownloading(bool value) = 0;
+
+ // Sets whether P2P is being used for sharing the update payloads.
+ virtual void SetUsingP2PForSharing(bool value) = 0;
+
+ // Returns true if we should backoff the current download attempt.
+ // False otherwise.
+ virtual bool ShouldBackoffDownload() = 0;
+
+ // Returns the currently stored response "signature". The signature is a
+ // subset of fields that are of interest to the PayloadState behavior.
+ virtual std::string GetResponseSignature() = 0;
+
+ // Returns the payload attempt number.
+ virtual int GetPayloadAttemptNumber() = 0;
+
+ // Returns the payload attempt number of the attempted full payload. Returns
+ // 0 for delta payloads.
+ virtual int GetFullPayloadAttemptNumber() = 0;
+
+ // Returns the current URL. Returns an empty string if there's no valid URL.
+ virtual std::string GetCurrentUrl() = 0;
+
+ // Returns the current URL's failure count.
+ virtual uint32_t GetUrlFailureCount() = 0;
+
+ // Returns the total number of times a new URL has been switched to
+ // for the current response.
+ virtual uint32_t GetUrlSwitchCount() = 0;
+
+ // Returns the total number of different responses seen since the
+ // last successful update.
+ virtual int GetNumResponsesSeen() = 0;
+
+ // Returns the expiry time for the current backoff period.
+ virtual base::Time GetBackoffExpiryTime() = 0;
+
+ // Returns the elapsed time used for this update, including time
+ // where the device is powered off and sleeping. If the
+ // update has not completed, returns the time spent so far.
+ virtual base::TimeDelta GetUpdateDuration() = 0;
+
+ // Returns the time used for this update not including time when
+ // the device is powered off or sleeping. If the update has not
+ // completed, returns the time spent so far.
+ virtual base::TimeDelta GetUpdateDurationUptime() = 0;
+
+ // Returns the number of bytes that have been downloaded for each source for
+ // each new update attempt. If we resume an update, we'll continue from the
+ // previous value, but if we get a new response or if the previous attempt
+ // failed, we'll reset this to 0 to start afresh.
+ virtual uint64_t GetCurrentBytesDownloaded(DownloadSource source) = 0;
+
+ // Returns the total number of bytes that have been downloaded for each
+ // source since the the last successful update. This is used to compute the
+ // overhead we incur.
+ virtual uint64_t GetTotalBytesDownloaded(DownloadSource source) = 0;
+
+ // Returns the reboot count for this update attempt.
+ virtual uint32_t GetNumReboots() = 0;
+
+ // Called at update_engine startup to do various house-keeping.
+ virtual void UpdateEngineStarted() = 0;
+
+ // Returns the version from before a rollback if our last update was a
+ // rollback.
+ virtual std::string GetRollbackVersion() = 0;
+
+ // Returns the value of number of attempts we've attempted to
+ // download the payload via p2p.
+ virtual int GetP2PNumAttempts() = 0;
+
+ // Returns the value of timestamp of the first time we've attempted
+ // to download the payload via p2p.
+ virtual base::Time GetP2PFirstAttemptTimestamp() = 0;
+
+ // Should be called every time we decide to use p2p for an update
+ // attempt. This is used to increase the p2p attempt counter and
+ // set the timestamp for first attempt.
+ virtual void P2PNewAttempt() = 0;
+
+ // Returns |true| if we are allowed to continue using p2p for
+ // downloading and |false| otherwise. This is done by recording
+ // and examining how many attempts have been done already as well
+ // as when the first attempt was.
+ virtual bool P2PAttemptAllowed() = 0;
+
+ // Gets the values previously set with SetUsingP2PForDownloading() and
+ // SetUsingP2PForSharing().
+ virtual bool GetUsingP2PForDownloading() const = 0;
+ virtual bool GetUsingP2PForSharing() const = 0;
+
+ // Returns the current (persisted) scattering wallclock-based wait period.
+ virtual base::TimeDelta GetScatteringWaitPeriod() = 0;
+
+ // Sets and persists the scattering wallclock-based wait period.
+ virtual void SetScatteringWaitPeriod(base::TimeDelta wait_period) = 0;
+
+ // Sets/gets the P2P download URL, if one is to be used.
+ virtual void SetP2PUrl(const std::string& url) = 0;
+ virtual std::string GetP2PUrl() const = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_
diff --git a/payload_state_unittest.cc b/payload_state_unittest.cc
new file mode 100644
index 0000000..08ed398
--- /dev/null
+++ b/payload_state_unittest.cc
@@ -0,0 +1,1747 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_state.h"
+
+#include <glib.h>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_hardware.h"
+#include "update_engine/fake_prefs.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/prefs.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+const char* kCurrentBytesDownloadedFromHttps =
+ "current-bytes-downloaded-from-HttpsServer";
+const char* kTotalBytesDownloadedFromHttps =
+ "total-bytes-downloaded-from-HttpsServer";
+const char* kCurrentBytesDownloadedFromHttp =
+ "current-bytes-downloaded-from-HttpServer";
+const char* kTotalBytesDownloadedFromHttp =
+ "total-bytes-downloaded-from-HttpServer";
+const char* kCurrentBytesDownloadedFromHttpPeer =
+ "current-bytes-downloaded-from-HttpPeer";
+const char* kTotalBytesDownloadedFromHttpPeer =
+ "total-bytes-downloaded-from-HttpPeer";
+
+static void SetupPayloadStateWith2Urls(string hash,
+ bool http_enabled,
+ PayloadState* payload_state,
+ OmahaResponse* response) {
+ response->payload_urls.clear();
+ response->payload_urls.push_back("http://test");
+ response->payload_urls.push_back("https://test");
+ response->size = 523456789;
+ response->hash = hash;
+ response->metadata_size = 558123;
+ response->metadata_signature = "metasign";
+ response->max_failure_count_per_url = 3;
+ payload_state->SetResponse(*response);
+ string stored_response_sign = payload_state->GetResponseSignature();
+
+ string expected_url_https_only =
+ "NumURLs = 1\n"
+ "Candidate Url0 = https://test\n";
+
+ string expected_urls_both =
+ "NumURLs = 2\n"
+ "Candidate Url0 = http://test\n"
+ "Candidate Url1 = https://test\n";
+
+ string expected_response_sign =
+ (http_enabled ? expected_urls_both : expected_url_https_only) +
+ base::StringPrintf("Payload Size = 523456789\n"
+ "Payload Sha256 Hash = %s\n"
+ "Metadata Size = 558123\n"
+ "Metadata Signature = metasign\n"
+ "Is Delta Payload = %d\n"
+ "Max Failure Count Per Url = %d\n"
+ "Disable Payload Backoff = %d\n",
+ hash.c_str(),
+ response->is_delta_payload,
+ response->max_failure_count_per_url,
+ response->disable_payload_backoff);
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+}
+
+class PayloadStateTest : public ::testing::Test { };
+
+TEST(PayloadStateTest, DidYouAddANewErrorCode) {
+ if (static_cast<int>(ErrorCode::kUmaReportedMax) != 48) {
+ LOG(ERROR) << "The following failure is intentional. If you added a new "
+ << "ErrorCode enum value, make sure to add it to the "
+ << "PayloadState::UpdateFailed method and then update this test "
+ << "to the new value of ErrorCode::kUmaReportedMax, which is "
+ << ErrorCode::kUmaReportedMax;
+ EXPECT_FALSE("Please see the log line above");
+ }
+}
+
+TEST(PayloadStateTest, SetResponseWorksWithEmptyResponse) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0)).Times(AtLeast(1));
+ PayloadState payload_state;
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ payload_state.SetResponse(response);
+ string stored_response_sign = payload_state.GetResponseSignature();
+ string expected_response_sign = "NumURLs = 0\n"
+ "Payload Size = 0\n"
+ "Payload Sha256 Hash = \n"
+ "Metadata Size = 0\n"
+ "Metadata Signature = \n"
+ "Is Delta Payload = 0\n"
+ "Max Failure Count Per Url = 0\n"
+ "Disable Payload Backoff = 0\n";
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+ EXPECT_EQ("", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, SetResponseWorksWithSingleUrl) {
+ OmahaResponse response;
+ response.payload_urls.push_back("https://single.url.test");
+ response.size = 123456789;
+ response.hash = "hash";
+ response.metadata_size = 58123;
+ response.metadata_signature = "msign";
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0))
+ .Times(AtLeast(1));
+ PayloadState payload_state;
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ payload_state.SetResponse(response);
+ string stored_response_sign = payload_state.GetResponseSignature();
+ string expected_response_sign = "NumURLs = 1\n"
+ "Candidate Url0 = https://single.url.test\n"
+ "Payload Size = 123456789\n"
+ "Payload Sha256 Hash = hash\n"
+ "Metadata Size = 58123\n"
+ "Metadata Signature = msign\n"
+ "Is Delta Payload = 0\n"
+ "Max Failure Count Per Url = 0\n"
+ "Disable Payload Backoff = 0\n";
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+ EXPECT_EQ("https://single.url.test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, SetResponseWorksWithMultipleUrls) {
+ OmahaResponse response;
+ response.payload_urls.push_back("http://multiple.url.test");
+ response.payload_urls.push_back("https://multiple.url.test");
+ response.size = 523456789;
+ response.hash = "rhash";
+ response.metadata_size = 558123;
+ response.metadata_signature = "metasign";
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0))
+ .Times(AtLeast(1));
+
+ PayloadState payload_state;
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ payload_state.SetResponse(response);
+ string stored_response_sign = payload_state.GetResponseSignature();
+ string expected_response_sign = "NumURLs = 2\n"
+ "Candidate Url0 = http://multiple.url.test\n"
+ "Candidate Url1 = https://multiple.url.test\n"
+ "Payload Size = 523456789\n"
+ "Payload Sha256 Hash = rhash\n"
+ "Metadata Size = 558123\n"
+ "Metadata Signature = metasign\n"
+ "Is Delta Payload = 0\n"
+ "Max Failure Count Per Url = 0\n"
+ "Disable Payload Backoff = 0\n";
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+ EXPECT_EQ("http://multiple.url.test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, CanAdvanceUrlIndexCorrectly) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ PayloadState payload_state;
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ // Payload attempt should start with 0 and then advance to 1.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _)).Times(AtLeast(2));
+
+ // Reboots will be set
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, _)).Times(AtLeast(1));
+
+ // Url index should go from 0 to 1 twice.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 1)).Times(AtLeast(1));
+
+ // Failure count should be called each times url index is set, so that's
+ // 4 times for this test.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(4));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // This does a SetResponse which causes all the states to be set to 0 for
+ // the first time.
+ SetupPayloadStateWith2Urls("Hash1235", true, &payload_state, &response);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+ // Verify that on the first error, the URL index advances to 1.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // Verify that on the next error, the URL index wraps around to 0.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+ // Verify that on the next error, it again advances to 1.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // Verify that we switched URLs three times
+ EXPECT_EQ(3, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, NewResponseResetsPayloadState) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+
+ // The first response doesn't send an abandoned event.
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.UpdatesAbandonedEventCount", 0, _, _, _)).Times(0);
+
+ // Set the first response.
+ SetupPayloadStateWith2Urls("Hash5823", true, &payload_state, &response);
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+ // Advance the URL index to 1 by faking an error.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.UpdatesAbandonedEventCount", 1, _, _, _));
+
+ // Now, slightly change the response and set it again.
+ SetupPayloadStateWith2Urls("Hash8225", true, &payload_state, &response);
+ EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+ // Fake an error again.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.UpdatesAbandonedEventCount", 2, _, _, _));
+
+ // Return a third different response.
+ SetupPayloadStateWith2Urls("Hash9999", true, &payload_state, &response);
+ EXPECT_EQ(3, payload_state.GetNumResponsesSeen());
+
+ // Make sure the url index was reset to 0 because of the new response.
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(0,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(0,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+}
+
+TEST(PayloadStateTest, AllCountersGetUpdatedProperlyOnErrorCodesAndEvents) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ int progress_bytes = 100;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(2));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 2))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(2));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 2))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _)).Times(AtLeast(4));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(4));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 1)).Times(AtLeast(2));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(7));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 1))
+ .Times(AtLeast(2));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 2))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, progress_bytes))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kTotalBytesDownloadedFromHttp, progress_bytes))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0))
+ .Times(AtLeast(1));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ SetupPayloadStateWith2Urls("Hash5873", true, &payload_state, &response);
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+ // This should advance the URL index.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+ // This should advance the failure count only.
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+ // This should advance the failure count only.
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(2, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+ // This should advance the URL index as we've reached the
+ // max failure count and reset the failure count for the new URL index.
+ // This should also wrap around the URL index and thus cause the payload
+ // attempt number to be incremented.
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(2, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // This should advance the URL index.
+ payload_state.UpdateFailed(ErrorCode::kPayloadHashMismatchError);
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(3, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // This should advance the URL index and payload attempt number due to
+ // wrap-around of URL index.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMissingError);
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(4, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // This HTTP error code should only increase the failure count.
+ payload_state.UpdateFailed(static_cast<ErrorCode>(
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 404));
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(4, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // And that failure count should be reset when we download some bytes
+ // afterwards.
+ payload_state.DownloadProgress(progress_bytes);
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(4, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // Now, slightly change the response and set it again.
+ SetupPayloadStateWith2Urls("Hash8532", true, &payload_state, &response);
+ EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+ // Make sure the url index was reset to 0 because of the new response.
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulFullDownload) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _))
+ .Times(AtLeast(2));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.PayloadAttemptNumber", 1, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.FullPayloadAttemptNumber", 1, _, _, _));
+
+ // This should just advance the payload attempt number;
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulDeltaDownload) {
+ OmahaResponse response;
+ response.is_delta_payload = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+
+ // kPrefsFullPayloadAttemptNumber is not incremented for delta payloads.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _))
+ .Times(1);
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ // Metrics for Full payload attempt number is not sent with Delta payloads.
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.PayloadAttemptNumber", 1, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.FullPayloadAttemptNumber", _, _, _, _))
+ .Times(0);
+
+ // This should just advance the payload attempt number;
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, SetResponseResetsInvalidUrlIndex) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash4427", true, &payload_state, &response);
+
+ // Generate enough events to advance URL index, failure count and
+ // payload attempt number all to 1.
+ payload_state.DownloadComplete();
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+ // Now, simulate a corrupted url index on persisted store which gets
+ // loaded when update_engine restarts. Using a different prefs object
+ // so as to not bother accounting for the uninteresting calls above.
+ FakeSystemState fake_system_state2;
+ NiceMock<MockPrefs>* prefs2 = fake_system_state2.mock_prefs();
+ EXPECT_CALL(*prefs2, Exists(_)).WillRepeatedly(Return(true));
+ EXPECT_CALL(*prefs2, GetInt64(_, _)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsPayloadAttemptNumber, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsFullPayloadAttemptNumber, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsCurrentUrlIndex, _))
+ .WillRepeatedly(DoAll(SetArgumentPointee<1>(2), Return(true)));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsCurrentUrlFailureCount, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsUrlSwitchCount, _))
+ .Times(AtLeast(1));
+
+ // Note: This will be a different payload object, but the response should
+ // have the same hash as before so as to not trivially reset because the
+ // response was different. We want to specifically test that even if the
+ // response is same, we should reset the state if we find it corrupted.
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state2));
+ SetupPayloadStateWith2Urls("Hash4427", true, &payload_state, &response);
+
+ // Make sure all counters get reset to 0 because of the corrupted URL index
+ // we supplied above.
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, NoBackoffInteractiveChecks) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ OmahaRequestParams params(&fake_system_state);
+ params.Init("", "", true); // is_interactive = True.
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Simulate two failures (enough to cause payload backoff) and check
+ // again that we're ready to re-download without any backoff as this is
+ // an interactive check.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, NoBackoffForP2PUpdates) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ OmahaRequestParams params(&fake_system_state);
+ params.Init("", "", false); // is_interactive = False.
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Simulate two failures (enough to cause payload backoff) and check
+ // again that we're ready to re-download without any backoff as this is
+ // an interactive check.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ // Set p2p url.
+ payload_state.SetUsingP2PForDownloading(true);
+ payload_state.SetP2PUrl("http://mypeer:52909/path/to/file");
+ // Should not backoff for p2p updates.
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+ payload_state.SetP2PUrl("");
+ // No actual p2p update if no url is provided.
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, NoBackoffForDeltaPayloads) {
+ OmahaResponse response;
+ response.is_delta_payload = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Simulate a successful download and see that we're ready to download
+ // again without any backoff as this is a delta payload.
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+ // Simulate two failures (enough to cause payload backoff) and check
+ // again that we're ready to re-download without any backoff as this is
+ // a delta payload.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+static void CheckPayloadBackoffState(PayloadState* payload_state,
+ int expected_attempt_number,
+ TimeDelta expected_days) {
+ payload_state->DownloadComplete();
+ EXPECT_EQ(expected_attempt_number,
+ payload_state->GetFullPayloadAttemptNumber());
+ EXPECT_TRUE(payload_state->ShouldBackoffDownload());
+ Time backoff_expiry_time = payload_state->GetBackoffExpiryTime();
+ // Add 1 hour extra to the 6 hour fuzz check to tolerate edge cases.
+ TimeDelta max_fuzz_delta = TimeDelta::FromHours(7);
+ Time expected_min_time = Time::Now() + expected_days - max_fuzz_delta;
+ Time expected_max_time = Time::Now() + expected_days + max_fuzz_delta;
+ EXPECT_LT(expected_min_time.ToInternalValue(),
+ backoff_expiry_time.ToInternalValue());
+ EXPECT_GT(expected_max_time.ToInternalValue(),
+ backoff_expiry_time.ToInternalValue());
+}
+
+TEST(PayloadStateTest, BackoffPeriodsAreInCorrectRange) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash8939", true, &payload_state, &response);
+
+ CheckPayloadBackoffState(&payload_state, 1, TimeDelta::FromDays(1));
+ CheckPayloadBackoffState(&payload_state, 2, TimeDelta::FromDays(2));
+ CheckPayloadBackoffState(&payload_state, 3, TimeDelta::FromDays(4));
+ CheckPayloadBackoffState(&payload_state, 4, TimeDelta::FromDays(8));
+ CheckPayloadBackoffState(&payload_state, 5, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 6, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 7, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 8, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 9, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 10, TimeDelta::FromDays(16));
+}
+
+TEST(PayloadStateTest, BackoffLogicCanBeDisabled) {
+ OmahaResponse response;
+ response.disable_payload_backoff = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash8939", true, &payload_state, &response);
+
+ // Simulate a successful download and see that we are ready to download
+ // again without any backoff.
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+ // Test again, this time by simulating two errors that would cause
+ // the payload attempt number to increment due to wrap around. And
+ // check that we are still ready to re-download without any backoff.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, BytesDownloadedMetricsGetAddedToCorrectSources) {
+ OmahaResponse response;
+ response.disable_payload_backoff = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ int https_total = 0;
+ int http_total = 0;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash3286", true, &payload_state, &response);
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+ // Simulate a previous attempt with in order to set an initial non-zero value
+ // for the total bytes downloaded for HTTP.
+ int prev_chunk = 323456789;
+ http_total += prev_chunk;
+ payload_state.DownloadProgress(prev_chunk);
+
+ // Ensure that the initial values for HTTP reflect this attempt.
+ EXPECT_EQ(prev_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+
+ // Change the response hash so as to simulate a new response which will
+ // reset the current bytes downloaded, but not the total bytes downloaded.
+ SetupPayloadStateWith2Urls("Hash9904", true, &payload_state, &response);
+ EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+ // First, simulate successful download of a few bytes over HTTP.
+ int first_chunk = 5000000;
+ http_total += first_chunk;
+ payload_state.DownloadProgress(first_chunk);
+ // Test that first all progress is made on HTTP and none on HTTPS.
+ EXPECT_EQ(first_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(https_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ // Simulate an error that'll cause the url index to point to https.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+
+ // Test that no new progress is made on HTTP and new progress is on HTTPS.
+ int second_chunk = 23456789;
+ https_total += second_chunk;
+ payload_state.DownloadProgress(second_chunk);
+ EXPECT_EQ(first_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(second_chunk, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(https_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ // Simulate error to go back to http.
+ payload_state.UpdateFailed(error);
+ int third_chunk = 32345678;
+ int http_chunk = first_chunk + third_chunk;
+ http_total += third_chunk;
+ int https_chunk = second_chunk;
+ payload_state.DownloadProgress(third_chunk);
+
+ // Test that third chunk is again back on HTTP. HTTPS remains on second chunk.
+ EXPECT_EQ(http_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(second_chunk, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(https_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ // Simulate error (will cause URL switch), set p2p is to be used and
+ // then do 42MB worth of progress
+ payload_state.UpdateFailed(error);
+ payload_state.SetUsingP2PForDownloading(true);
+ int p2p_total = 42 * 1000 * 1000;
+ payload_state.DownloadProgress(p2p_total);
+
+ EXPECT_EQ(p2p_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpPeer));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.SuccessfulMBsDownloadedFromHttpServer",
+ http_chunk / kNumBytesInOneMiB, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.TotalMBsDownloadedFromHttpServer",
+ http_total / kNumBytesInOneMiB, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.SuccessfulMBsDownloadedFromHttpsServer",
+ https_chunk / kNumBytesInOneMiB, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.TotalMBsDownloadedFromHttpsServer",
+ https_total / kNumBytesInOneMiB, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.SuccessfulMBsDownloadedFromHttpPeer",
+ p2p_total / kNumBytesInOneMiB, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.TotalMBsDownloadedFromHttpPeer",
+ p2p_total / kNumBytesInOneMiB, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.UpdateURLSwitches",
+ 3, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricSuccessfulUpdateUrlSwitchCount,
+ 3, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.UpdateDurationMinutes",
+ _, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricSuccessfulUpdateTotalDurationMinutes,
+ _, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.UpdateDurationUptimeMinutes",
+ _, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.DownloadSourcesUsed",
+ (1 << kDownloadSourceHttpsServer) | (1 << kDownloadSourceHttpServer) |
+ (1 << kDownloadSourceHttpPeer),
+ _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.DownloadOverheadPercentage", 318, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage,
+ 314, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ "Installer.PayloadFormat", kPayloadTypeFull, kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricAttemptPayloadType, kPayloadTypeFull, kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeFull,
+ kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.AttemptsCount.Total", 1, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricSuccessfulUpdateAttemptCount, 1, _, _, _));
+
+ payload_state.UpdateSucceeded();
+
+ // Make sure the metrics are reset after a successful update.
+ EXPECT_EQ(0,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(0,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+ EXPECT_EQ(0, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, DownloadSourcesUsedIsCorrect) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash3286", true, &payload_state, &response);
+
+ // Simulate progress in order to mark HTTP as one of the sources used.
+ int num_bytes = 42 * 1000 * 1000;
+ payload_state.DownloadProgress(num_bytes);
+
+ // Check that this was done via HTTP.
+ EXPECT_EQ(num_bytes,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(num_bytes,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+
+ // Check that only HTTP is reported as a download source.
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.DownloadSourcesUsed",
+ (1 << kDownloadSourceHttpServer),
+ _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricSuccessfulUpdateDownloadSourcesUsed,
+ (1 << kDownloadSourceHttpServer),
+ _, _, _));
+
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, RestartingUpdateResetsMetrics) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Set the first response.
+ SetupPayloadStateWith2Urls("Hash5823", true, &payload_state, &response);
+
+ int num_bytes = 10000;
+ payload_state.DownloadProgress(num_bytes);
+ EXPECT_EQ(num_bytes,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(num_bytes,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(0,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ payload_state.UpdateRestarted();
+ // Make sure the current bytes downloaded is reset, but not the total bytes.
+ EXPECT_EQ(0,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(num_bytes,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+}
+
+TEST(PayloadStateTest, NumRebootsIncrementsCorrectly) {
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AtLeast(0));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 1)).Times(AtLeast(1));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ payload_state.UpdateRestarted();
+ EXPECT_EQ(0, payload_state.GetNumReboots());
+
+ fake_system_state.set_system_rebooted(true);
+ payload_state.UpdateResumed();
+ // Num reboots should be incremented because system rebooted detected.
+ EXPECT_EQ(1, payload_state.GetNumReboots());
+
+ fake_system_state.set_system_rebooted(false);
+ payload_state.UpdateResumed();
+ // Num reboots should now be 1 as reboot was not detected.
+ EXPECT_EQ(1, payload_state.GetNumReboots());
+
+ // Restart the update again to verify we set the num of reboots back to 0.
+ payload_state.UpdateRestarted();
+ EXPECT_EQ(0, payload_state.GetNumReboots());
+}
+
+TEST(PayloadStateTest, RollbackVersion) {
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ NiceMock<MockPrefs>* mock_powerwash_safe_prefs =
+ fake_system_state.mock_powerwash_safe_prefs();
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Verify pre-conditions are good.
+ EXPECT_TRUE(payload_state.GetRollbackVersion().empty());
+
+ // Mock out the os version and make sure it's blacklisted correctly.
+ string rollback_version = "2345.0.0";
+ OmahaRequestParams params(&fake_system_state);
+ params.Init(rollback_version, "", false);
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_CALL(*mock_powerwash_safe_prefs, SetString(kPrefsRollbackVersion,
+ rollback_version));
+ payload_state.Rollback();
+
+ EXPECT_EQ(rollback_version, payload_state.GetRollbackVersion());
+
+ // Change it up a little and verify we load it correctly.
+ rollback_version = "2345.0.1";
+ // Let's verify we can reload it correctly.
+ EXPECT_CALL(*mock_powerwash_safe_prefs, GetString(
+ kPrefsRollbackVersion, _)).WillOnce(DoAll(
+ SetArgumentPointee<1>(rollback_version), Return(true)));
+ EXPECT_CALL(*mock_powerwash_safe_prefs, SetString(kPrefsRollbackVersion,
+ rollback_version));
+ payload_state.LoadRollbackVersion();
+ EXPECT_EQ(rollback_version, payload_state.GetRollbackVersion());
+
+ // Check that we report only UpdateEngine.Rollback.* metrics in
+ // UpdateSucceeded().
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+ .Times(0);
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(0);
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+ SendEnumToUMA(
+ metrics::kMetricRollbackResult,
+ static_cast<int>(metrics::RollbackResult::kSuccess),
+ static_cast<int>(metrics::RollbackResult::kNumConstants)));
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, DurationsAreCorrect) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+
+ // Set the clock to a well-known time - 1 second on the wall-clock
+ // and 2 seconds on the monotonic clock
+ fake_clock.SetWallclockTime(Time::FromInternalValue(1000000));
+ fake_clock.SetMonotonicTime(Time::FromInternalValue(2000000));
+
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Check that durations are correct for a successful update where
+ // time has advanced 7 seconds on the wall clock and 4 seconds on
+ // the monotonic clock.
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+ fake_clock.SetWallclockTime(Time::FromInternalValue(8000000));
+ fake_clock.SetMonotonicTime(Time::FromInternalValue(6000000));
+ payload_state.UpdateSucceeded();
+ EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 7000000);
+ EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 4000000);
+
+ // Check that durations are reset when a new response comes in.
+ SetupPayloadStateWith2Urls("Hash8594", true, &payload_state, &response);
+ EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 0);
+ EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 0);
+
+ // Advance time a bit (10 secs), simulate download progress and
+ // check that durations are updated.
+ fake_clock.SetWallclockTime(Time::FromInternalValue(18000000));
+ fake_clock.SetMonotonicTime(Time::FromInternalValue(16000000));
+ payload_state.DownloadProgress(10);
+ EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 10000000);
+ EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 10000000);
+
+ // Now simulate a reboot by resetting monotonic time (to 5000) and
+ // creating a new PayloadState object and check that we load the
+ // durations correctly (e.g. they are the same as before).
+ fake_clock.SetMonotonicTime(Time::FromInternalValue(5000));
+ PayloadState payload_state2;
+ EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+ EXPECT_EQ(payload_state2.GetUpdateDuration().InMicroseconds(), 10000000);
+ EXPECT_EQ(payload_state2.GetUpdateDurationUptime().InMicroseconds(),
+ 10000000);
+
+ // Advance wall-clock by 7 seconds and monotonic clock by 6 seconds
+ // and check that the durations are increased accordingly.
+ fake_clock.SetWallclockTime(Time::FromInternalValue(25000000));
+ fake_clock.SetMonotonicTime(Time::FromInternalValue(6005000));
+ payload_state2.UpdateSucceeded();
+ EXPECT_EQ(payload_state2.GetUpdateDuration().InMicroseconds(), 17000000);
+ EXPECT_EQ(payload_state2.GetUpdateDurationUptime().InMicroseconds(),
+ 16000000);
+}
+
+TEST(PayloadStateTest, RebootAfterSuccessfulUpdateTest) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+
+ // Set the clock to a well-known time (t = 30 seconds).
+ fake_clock.SetWallclockTime(Time::FromInternalValue(
+ 30 * Time::kMicrosecondsPerSecond));
+
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Make the update succeed.
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+ payload_state.UpdateSucceeded();
+
+ // Check that the marker was written.
+ EXPECT_TRUE(fake_prefs.Exists(kPrefsSystemUpdatedMarker));
+
+ // Now simulate a reboot and set the wallclock time to a later point
+ // (t = 500 seconds). We do this by using a new PayloadState object
+ // and checking that it emits the right UMA metric with the right
+ // value.
+ fake_clock.SetWallclockTime(Time::FromInternalValue(
+ 500 * Time::kMicrosecondsPerSecond));
+ PayloadState payload_state2;
+ EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+
+ // Expect 500 - 30 seconds = 470 seconds ~= 7 min 50 sec
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.TimeToRebootMinutes",
+ 7, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricTimeToRebootMinutes,
+ 7, _, _, _));
+ fake_system_state.set_system_rebooted(true);
+
+ payload_state2.UpdateEngineStarted();
+
+ // Check that the marker was nuked.
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsSystemUpdatedMarker));
+}
+
+TEST(PayloadStateTest, RestartAfterCrash) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Only the |kPrefsAttemptInProgress| state variable should be read.
+ EXPECT_CALL(*prefs, Exists(_)).Times(0);
+ EXPECT_CALL(*prefs, SetString(_, _)).Times(0);
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(0);
+ EXPECT_CALL(*prefs, SetBoolean(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetString(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetInt64(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetBoolean(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetBoolean(kPrefsAttemptInProgress, _));
+
+ // No metrics are reported after a crash.
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+ SendToUMA(_, _, _, _, _)).Times(0);
+
+ // Simulate an update_engine restart without a reboot.
+ fake_system_state.set_system_rebooted(false);
+
+ payload_state.UpdateEngineStarted();
+}
+
+TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsNoReporting) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ // If there's no marker at startup, ensure we don't report a metric.
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+ SendEnumToUMA(
+ metrics::kMetricAttemptResult,
+ static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+ _)).Times(0);
+ payload_state.UpdateEngineStarted();
+}
+
+TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsReported) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakePrefs fake_prefs;
+
+ // If we have a marker at startup, ensure it's reported and the
+ // marker is then cleared.
+ fake_system_state.set_prefs(&fake_prefs);
+ fake_prefs.SetBoolean(kPrefsAttemptInProgress, true);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+ SendEnumToUMA(
+ metrics::kMetricAttemptResult,
+ static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+ _)).Times(1);
+ payload_state.UpdateEngineStarted();
+
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+}
+
+TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsClearedOnSucceess) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakePrefs fake_prefs;
+
+ // Make sure the marker is written and cleared during an attempt and
+ // also that we DO NOT emit the metric (since the attempt didn't end
+ // abnormally).
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+ SendEnumToUMA(
+ metrics::kMetricAttemptResult,
+ static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+ _)).Times(0);
+
+ // Attempt not in progress, should be clear.
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+
+ payload_state.UpdateRestarted();
+
+ // Attempt not in progress, should be set.
+ EXPECT_TRUE(fake_prefs.Exists(kPrefsAttemptInProgress));
+
+ payload_state.UpdateSucceeded();
+
+ // Attempt not in progress, should be clear.
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+}
+
+TEST(PayloadStateTest, CandidateUrlsComputedCorrectly) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ policy::MockDevicePolicy disable_http_policy;
+ fake_system_state.set_device_policy(&disable_http_policy);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Test with no device policy. Should default to allowing http.
+ EXPECT_CALL(disable_http_policy, GetHttpDownloadsEnabled(_))
+ .WillRepeatedly(Return(false));
+
+ // Set the first response.
+ SetupPayloadStateWith2Urls("Hash8433", true, &payload_state, &response);
+
+ // Check that we use the HTTP URL since there is no value set for allowing
+ // http.
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+ // Test with device policy not allowing http updates.
+ EXPECT_CALL(disable_http_policy, GetHttpDownloadsEnabled(_))
+ .WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(true)));
+
+ // Reset state and set again.
+ SetupPayloadStateWith2Urls("Hash8433", false, &payload_state, &response);
+
+ // Check that we skip the HTTP URL and use only the HTTPS url.
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // Advance the URL index to 1 by faking an error.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+
+ // Check that we still skip the HTTP URL and use only the HTTPS url.
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+
+ // Now, slightly change the response and set it again.
+ SetupPayloadStateWith2Urls("Hash2399", false, &payload_state, &response);
+
+ // Check that we still skip the HTTP URL and use only the HTTPS url.
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // Now, pretend that the HTTP policy is turned on. We want to make sure
+ // the new policy is honored.
+ policy::MockDevicePolicy enable_http_policy;
+ fake_system_state.set_device_policy(&enable_http_policy);
+ EXPECT_CALL(enable_http_policy, GetHttpDownloadsEnabled(_))
+ .WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true)));
+
+ // Now, set the same response using the same hash
+ // so that we can test that the state is reset not because of the
+ // hash but because of the policy change which results in candidate url
+ // list change.
+ SetupPayloadStateWith2Urls("Hash2399", true, &payload_state, &response);
+
+ // Check that we use the HTTP URL now and the failure count is reset.
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+
+ // Fake a failure and see if we're moving over to the HTTPS url and update
+ // the URL switch count properly.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsDelta) {
+ OmahaResponse response;
+ response.is_delta_payload = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ "Installer.PayloadFormat", kPayloadTypeDelta, kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricAttemptPayloadType, kPayloadTypeDelta, kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeDelta,
+ kNumPayloadTypes));
+ payload_state.UpdateSucceeded();
+
+ // Mock the request to a request where the delta was disabled but Omaha sends
+ // a delta anyway and test again.
+ OmahaRequestParams params(&fake_system_state);
+ params.set_delta_okay(false);
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ payload_state.DownloadComplete();
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ "Installer.PayloadFormat", kPayloadTypeDelta, kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricAttemptPayloadType, kPayloadTypeDelta,
+ kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeDelta,
+ kNumPayloadTypes));
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsForcedFull) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Mock the request to a request where the delta was disabled.
+ OmahaRequestParams params(&fake_system_state);
+ params.set_delta_okay(false);
+ fake_system_state.set_request_params(¶ms);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ "Installer.PayloadFormat", kPayloadTypeForcedFull, kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricAttemptPayloadType, kPayloadTypeForcedFull,
+ kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeForcedFull,
+ kNumPayloadTypes));
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsFull) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Mock the request to a request where the delta is enabled, although the
+ // result is full.
+ OmahaRequestParams params(&fake_system_state);
+ params.set_delta_okay(true);
+ fake_system_state.set_request_params(¶ms);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ "Installer.PayloadFormat", kPayloadTypeFull, kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricAttemptPayloadType, kPayloadTypeFull,
+ kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeFull,
+ kNumPayloadTypes));
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, RebootAfterUpdateFailedMetric) {
+ FakeSystemState fake_system_state;
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakePrefs fake_prefs;
+ fake_system_state.set_prefs(&fake_prefs);
+
+ FakeHardware* fake_hardware = fake_system_state.fake_hardware();
+ fake_hardware->SetBootDevice("/dev/sda3");
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+ payload_state.UpdateSucceeded();
+ payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+ // Reboot into the same environment to get an UMA metric with a value of 1.
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.RebootToNewPartitionAttempt", 1, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, 1, _, _, _));
+ payload_state.ReportFailedBootIfNeeded();
+ Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+
+ // Simulate a second update and reboot into the same environment, this should
+ // send a value of 2.
+ payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.RebootToNewPartitionAttempt", 2, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, 2, _, _, _));
+ payload_state.ReportFailedBootIfNeeded();
+ Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+
+ // Simulate a third failed reboot to new version, but this time for a
+ // different payload. This should send a value of 1 this time.
+ payload_state.ExpectRebootInNewVersion("Version:3141592");
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.RebootToNewPartitionAttempt", 1, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, 1, _, _, _));
+ payload_state.ReportFailedBootIfNeeded();
+ Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+}
+
+TEST(PayloadStateTest, RebootAfterUpdateSucceed) {
+ FakeSystemState fake_system_state;
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakePrefs fake_prefs;
+ fake_system_state.set_prefs(&fake_prefs);
+
+ FakeHardware* fake_hardware = fake_system_state.fake_hardware();
+ fake_hardware->SetBootDevice("/dev/sda3");
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+ payload_state.UpdateSucceeded();
+ payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+ // Change the BootDevice to a different one, no metric should be sent.
+ fake_hardware->SetBootDevice("/dev/sda5");
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.RebootToNewPartitionAttempt", _, _, _, _))
+ .Times(0);
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, _, _, _, _))
+ .Times(0);
+ payload_state.ReportFailedBootIfNeeded();
+
+ // A second reboot in eiher partition should not send a metric.
+ payload_state.ReportFailedBootIfNeeded();
+ fake_hardware->SetBootDevice("/dev/sda3");
+ payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(PayloadStateTest, RebootAfterCanceledUpdate) {
+ FakeSystemState fake_system_state;
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+ payload_state.UpdateSucceeded();
+ payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.RebootToNewPartitionAttempt", _, _, _, _))
+ .Times(0);
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, _, _, _, _))
+ .Times(0);
+
+ // Cancel the applied update.
+ payload_state.ResetUpdateStatus();
+
+ // Simulate a reboot.
+ payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs) {
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ "Installer.RebootToNewPartitionAttempt", _, _, _, _))
+ .Times(0);
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, _, _, _, _))
+ .Times(0);
+
+ // Simulate a reboot in this environment.
+ payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(PayloadStateTest, DisallowP2PAfterTooManyAttempts) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakePrefs fake_prefs;
+ fake_system_state.set_prefs(&fake_prefs);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ // Should allow exactly kMaxP2PAttempts...
+ for (int n = 0; n < kMaxP2PAttempts; n++) {
+ payload_state.P2PNewAttempt();
+ EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+ }
+ // ... but not more than that.
+ payload_state.P2PNewAttempt();
+ EXPECT_FALSE(payload_state.P2PAttemptAllowed());
+}
+
+TEST(PayloadStateTest, DisallowP2PAfterDeadline) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ // Set the clock to 1 second.
+ Time epoch = Time::FromInternalValue(1000000);
+ fake_clock.SetWallclockTime(epoch);
+
+ // Do an attempt - this will set the timestamp.
+ payload_state.P2PNewAttempt();
+
+ // Check that the timestamp equals what we just set.
+ EXPECT_EQ(epoch, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // Time hasn't advanced - this should work.
+ EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+
+ // Set clock to half the deadline - this should work.
+ fake_clock.SetWallclockTime(epoch +
+ TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds) / 2);
+ EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+
+ // Check that the first attempt timestamp hasn't changed just
+ // because the wall-clock time changed.
+ EXPECT_EQ(epoch, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // Set clock to _just_ before the deadline - this should work.
+ fake_clock.SetWallclockTime(epoch +
+ TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds - 1));
+ EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+
+ // Set clock to _just_ after the deadline - this should not work.
+ fake_clock.SetWallclockTime(epoch +
+ TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds + 1));
+ EXPECT_FALSE(payload_state.P2PAttemptAllowed());
+}
+
+TEST(PayloadStateTest, P2PStateVarsInitialValue) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ Time null_time = Time();
+ EXPECT_EQ(null_time, payload_state.GetP2PFirstAttemptTimestamp());
+ EXPECT_EQ(0, payload_state.GetP2PNumAttempts());
+}
+
+TEST(PayloadStateTest, P2PStateVarsArePersisted) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ // Set the clock to something known.
+ Time time = Time::FromInternalValue(12345);
+ fake_clock.SetWallclockTime(time);
+
+ // New p2p attempt - as a side-effect this will update the p2p state vars.
+ payload_state.P2PNewAttempt();
+ EXPECT_EQ(1, payload_state.GetP2PNumAttempts());
+ EXPECT_EQ(time, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // Now create a new PayloadState and check that it loads the state
+ // vars correctly.
+ PayloadState payload_state2;
+ EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+ EXPECT_EQ(1, payload_state2.GetP2PNumAttempts());
+ EXPECT_EQ(time, payload_state2.GetP2PFirstAttemptTimestamp());
+}
+
+TEST(PayloadStateTest, P2PStateVarsAreClearedOnNewResponse) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ // Set the clock to something known.
+ Time time = Time::FromInternalValue(12345);
+ fake_clock.SetWallclockTime(time);
+
+ // New p2p attempt - as a side-effect this will update the p2p state vars.
+ payload_state.P2PNewAttempt();
+ EXPECT_EQ(1, payload_state.GetP2PNumAttempts());
+ EXPECT_EQ(time, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // Set a new response...
+ SetupPayloadStateWith2Urls("Hash9904", true, &payload_state, &response);
+
+ // ... and check that it clears the P2P state vars.
+ Time null_time = Time();
+ EXPECT_EQ(0, payload_state.GetP2PNumAttempts());
+ EXPECT_EQ(null_time, payload_state.GetP2PFirstAttemptTimestamp());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_verifier.cc b/payload_verifier.cc
new file mode 100644
index 0000000..2b96186
--- /dev/null
+++ b/payload_verifier.cc
@@ -0,0 +1,219 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_verifier.h"
+
+#include <base/logging.h>
+#include <openssl/pem.h>
+
+#include "update_engine/delta_performer.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+const uint32_t kSignatureMessageOriginalVersion = 1;
+const uint32_t kSignatureMessageCurrentVersion = 1;
+
+namespace {
+
+// The following is a standard PKCS1-v1_5 padding for SHA256 signatures, as
+// defined in RFC3447. It is prepended to the actual signature (32 bytes) to
+// form a sequence of 256 bytes (2048 bits) that is amenable to RSA signing. The
+// padded hash will look as follows:
+//
+// 0x00 0x01 0xff ... 0xff 0x00 ASN1HEADER SHA256HASH
+// |--------------205-----------||----19----||----32----|
+//
+// where ASN1HEADER is the ASN.1 description of the signed data. The complete 51
+// bytes of actual data (i.e. the ASN.1 header complete with the hash) are
+// packed as follows:
+//
+// SEQUENCE(2+49) {
+// SEQUENCE(2+13) {
+// OBJECT(2+9) id-sha256
+// NULL(2+0)
+// }
+// OCTET STRING(2+32) <actual signature bytes...>
+// }
+const uint8_t kRSA2048SHA256Padding[] = {
+ // PKCS1-v1_5 padding
+ 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x00,
+ // ASN.1 header
+ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+ 0x00, 0x04, 0x20,
+};
+
+} // namespace
+
+bool PayloadVerifier::LoadPayload(const string& payload_path,
+ chromeos::Blob* out_payload,
+ DeltaArchiveManifest* out_manifest,
+ uint64_t* out_metadata_size) {
+ chromeos::Blob payload;
+ // Loads the payload and parses the manifest.
+ TEST_AND_RETURN_FALSE(utils::ReadFile(payload_path, &payload));
+ LOG(INFO) << "Payload size: " << payload.size();
+ ErrorCode error = ErrorCode::kSuccess;
+ InstallPlan install_plan;
+ DeltaPerformer delta_performer(nullptr, nullptr, &install_plan);
+ TEST_AND_RETURN_FALSE(
+ delta_performer.ParsePayloadMetadata(payload, &error) ==
+ DeltaPerformer::kMetadataParseSuccess);
+ TEST_AND_RETURN_FALSE(delta_performer.GetManifest(out_manifest));
+ *out_metadata_size = delta_performer.GetMetadataSize();
+ LOG(INFO) << "Metadata size: " << *out_metadata_size;
+ out_payload->swap(payload);
+ return true;
+}
+
+bool PayloadVerifier::VerifySignature(const chromeos::Blob& signature_blob,
+ const string& public_key_path,
+ chromeos::Blob* out_hash_data) {
+ return VerifySignatureBlob(signature_blob, public_key_path,
+ kSignatureMessageCurrentVersion, out_hash_data);
+}
+
+bool PayloadVerifier::VerifySignatureBlob(
+ const chromeos::Blob& signature_blob,
+ const string& public_key_path,
+ uint32_t client_version,
+ chromeos::Blob* out_hash_data) {
+ TEST_AND_RETURN_FALSE(!public_key_path.empty());
+
+ Signatures signatures;
+ LOG(INFO) << "signature size = " << signature_blob.size();
+ TEST_AND_RETURN_FALSE(signatures.ParseFromArray(signature_blob.data(),
+ signature_blob.size()));
+
+ // Finds a signature that matches the current version.
+ int sig_index = 0;
+ for (; sig_index < signatures.signatures_size(); sig_index++) {
+ const Signatures_Signature& signature = signatures.signatures(sig_index);
+ if (signature.has_version() &&
+ signature.version() == client_version) {
+ break;
+ }
+ }
+ TEST_AND_RETURN_FALSE(sig_index < signatures.signatures_size());
+
+ const Signatures_Signature& signature = signatures.signatures(sig_index);
+ chromeos::Blob sig_data(signature.data().begin(), signature.data().end());
+
+ return GetRawHashFromSignature(sig_data, public_key_path, out_hash_data);
+}
+
+
+bool PayloadVerifier::GetRawHashFromSignature(
+ const chromeos::Blob& sig_data,
+ const string& public_key_path,
+ chromeos::Blob* out_hash_data) {
+ TEST_AND_RETURN_FALSE(!public_key_path.empty());
+
+ // The code below executes the equivalent of:
+ //
+ // openssl rsautl -verify -pubin -inkey |public_key_path|
+ // -in |sig_data| -out |out_hash_data|
+
+ // Loads the public key.
+ FILE* fpubkey = fopen(public_key_path.c_str(), "rb");
+ if (!fpubkey) {
+ LOG(ERROR) << "Unable to open public key file: " << public_key_path;
+ return false;
+ }
+
+ char dummy_password[] = { ' ', 0 }; // Ensure no password is read from stdin.
+ RSA* rsa = PEM_read_RSA_PUBKEY(fpubkey, nullptr, nullptr, dummy_password);
+ fclose(fpubkey);
+ TEST_AND_RETURN_FALSE(rsa != nullptr);
+ unsigned int keysize = RSA_size(rsa);
+ if (sig_data.size() > 2 * keysize) {
+ LOG(ERROR) << "Signature size is too big for public key size.";
+ RSA_free(rsa);
+ return false;
+ }
+
+ // Decrypts the signature.
+ chromeos::Blob hash_data(keysize);
+ int decrypt_size = RSA_public_decrypt(sig_data.size(),
+ sig_data.data(),
+ hash_data.data(),
+ rsa,
+ RSA_NO_PADDING);
+ RSA_free(rsa);
+ TEST_AND_RETURN_FALSE(decrypt_size > 0 &&
+ decrypt_size <= static_cast<int>(hash_data.size()));
+ hash_data.resize(decrypt_size);
+ out_hash_data->swap(hash_data);
+ return true;
+}
+
+bool PayloadVerifier::VerifySignedPayload(const string& payload_path,
+ const string& public_key_path,
+ uint32_t client_key_check_version) {
+ chromeos::Blob payload;
+ DeltaArchiveManifest manifest;
+ uint64_t metadata_size;
+ TEST_AND_RETURN_FALSE(LoadPayload(
+ payload_path, &payload, &manifest, &metadata_size));
+ TEST_AND_RETURN_FALSE(manifest.has_signatures_offset() &&
+ manifest.has_signatures_size());
+ CHECK_EQ(payload.size(),
+ metadata_size + manifest.signatures_offset() +
+ manifest.signatures_size());
+ chromeos::Blob signature_blob(
+ payload.begin() + metadata_size + manifest.signatures_offset(),
+ payload.end());
+ chromeos::Blob signed_hash;
+ TEST_AND_RETURN_FALSE(VerifySignatureBlob(
+ signature_blob, public_key_path, client_key_check_version, &signed_hash));
+ TEST_AND_RETURN_FALSE(!signed_hash.empty());
+ chromeos::Blob hash;
+ TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(
+ payload.data(), metadata_size + manifest.signatures_offset(), &hash));
+ PadRSA2048SHA256Hash(&hash);
+ TEST_AND_RETURN_FALSE(hash == signed_hash);
+ return true;
+}
+
+bool PayloadVerifier::PadRSA2048SHA256Hash(chromeos::Blob* hash) {
+ TEST_AND_RETURN_FALSE(hash->size() == 32);
+ hash->insert(hash->begin(),
+ reinterpret_cast<const char*>(kRSA2048SHA256Padding),
+ reinterpret_cast<const char*>(kRSA2048SHA256Padding +
+ sizeof(kRSA2048SHA256Padding)));
+ TEST_AND_RETURN_FALSE(hash->size() == 256);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_verifier.h b/payload_verifier.h
new file mode 100644
index 0000000..3f4f165
--- /dev/null
+++ b/payload_verifier.h
@@ -0,0 +1,79 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_VERIFIER_H_
+#define UPDATE_ENGINE_PAYLOAD_VERIFIER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// This class encapsulates methods used for payload signature verification.
+// See payload_generator/payload_signer.h for payload signing.
+
+namespace chromeos_update_engine {
+
+extern const uint32_t kSignatureMessageOriginalVersion;
+extern const uint32_t kSignatureMessageCurrentVersion;
+
+class PayloadVerifier {
+ public:
+ // Returns false if the payload signature can't be verified. Returns true
+ // otherwise and sets |out_hash| to the signed payload hash.
+ static bool VerifySignature(const chromeos::Blob& signature_blob,
+ const std::string& public_key_path,
+ chromeos::Blob* out_hash_data);
+
+ // Interprets signature_blob as a protocol buffer containing the Signatures
+ // message and decrypts the signature data using the public_key_path and
+ // stores the resultant raw hash data in out_hash_data. Returns true if
+ // everything is successful. False otherwise. It also takes the client_version
+ // and interprets the signature blob according to that version.
+ static bool VerifySignatureBlob(const chromeos::Blob& signature_blob,
+ const std::string& public_key_path,
+ uint32_t client_version,
+ chromeos::Blob* out_hash_data);
+
+ // Decrypts sig_data with the given public_key_path and populates
+ // out_hash_data with the decoded raw hash. Returns true if successful,
+ // false otherwise.
+ static bool GetRawHashFromSignature(const chromeos::Blob& sig_data,
+ const std::string& public_key_path,
+ chromeos::Blob* out_hash_data);
+
+ // Returns true if the payload in |payload_path| is signed and its hash can be
+ // verified using the public key in |public_key_path| with the signature
+ // of a given version in the signature blob. Returns false otherwise.
+ static bool VerifySignedPayload(const std::string& payload_path,
+ const std::string& public_key_path,
+ uint32_t client_key_check_version);
+
+ // Pads a SHA256 hash so that it may be encrypted/signed with RSA2048
+ // using the PKCS#1 v1.5 scheme.
+ // hash should be a pointer to vector of exactly 256 bits. The vector
+ // will be modified in place and will result in having a length of
+ // 2048 bits. Returns true on success, false otherwise.
+ static bool PadRSA2048SHA256Hash(chromeos::Blob* hash);
+
+ // Reads the payload from the given |payload_path| into the |out_payload|
+ // vector. It also parses the manifest protobuf in the payload and returns it
+ // in |out_manifest| along with the size of the entire metadata in
+ // |out_metadata_size|.
+ static bool LoadPayload(const std::string& payload_path,
+ chromeos::Blob* out_payload,
+ DeltaArchiveManifest* out_manifest,
+ uint64_t* out_metadata_size);
+
+ private:
+ // This should never be constructed
+ DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadVerifier);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_VERIFIER_H_
diff --git a/postinstall_runner_action.cc b/postinstall_runner_action.cc
new file mode 100644
index 0000000..f27aace
--- /dev/null
+++ b/postinstall_runner_action.cc
@@ -0,0 +1,133 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/postinstall_runner_action.h"
+
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <vector>
+
+#include "update_engine/action_processor.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+
+namespace {
+// The absolute path to the post install command.
+const char kPostinstallScript[] = "/postinst";
+
+// Path to the binary file used by kPostinstallScript. Used to get and log the
+// file format of the binary to debug issues when the ELF format on the update
+// doesn't match the one on the current system. This path is not executed.
+const char kDebugPostinstallBinaryPath[] = "/usr/bin/cros_installer";
+}
+
+void PostinstallRunnerAction::PerformAction() {
+ CHECK(HasInputObject());
+ install_plan_ = GetInputObject();
+ const string install_device = install_plan_.install_path;
+ ScopedActionCompleter completer(processor_, this);
+
+ // Make mountpoint.
+ TEST_AND_RETURN(utils::MakeTempDirectory("/tmp/au_postint_mount.XXXXXX",
+ &temp_rootfs_dir_));
+ ScopedDirRemover temp_dir_remover(temp_rootfs_dir_);
+
+ const string mountable_device =
+ utils::MakePartitionNameForMount(install_device);
+ if (mountable_device.empty()) {
+ LOG(ERROR) << "Cannot make mountable device from " << install_device;
+ return;
+ }
+
+ if (!utils::MountFilesystem(mountable_device, temp_rootfs_dir_, MS_RDONLY))
+ return;
+
+ LOG(INFO) << "Performing postinst with install device " << install_device
+ << " and mountable device " << mountable_device;
+
+ temp_dir_remover.set_should_remove(false);
+ completer.set_should_complete(false);
+
+ if (install_plan_.powerwash_required) {
+ if (utils::CreatePowerwashMarkerFile(powerwash_marker_file_)) {
+ powerwash_marker_created_ = true;
+ } else {
+ completer.set_code(ErrorCode::kPostinstallPowerwashError);
+ return;
+ }
+ }
+
+ // Logs the file format of the postinstall script we are about to run. This
+ // will help debug when the postinstall script doesn't match the architecture
+ // of our build.
+ LOG(INFO) << "Format file for new " << kPostinstallScript << " is: "
+ << utils::GetFileFormat(temp_rootfs_dir_ + kPostinstallScript);
+ LOG(INFO) << "Format file for new " << kDebugPostinstallBinaryPath << " is: "
+ << utils::GetFileFormat(
+ temp_rootfs_dir_ + kDebugPostinstallBinaryPath);
+
+ // Runs the postinstall script asynchronously to free up the main loop while
+ // it's running.
+ vector<string> command;
+ if (!install_plan_.download_url.empty()) {
+ command.push_back(temp_rootfs_dir_ + kPostinstallScript);
+ } else {
+ // TODO(sosa): crbug.com/366207.
+ // If we're doing a rollback, just run our own postinstall.
+ command.push_back(kPostinstallScript);
+ }
+ command.push_back(install_device);
+ if (!Subprocess::Get().Exec(command, StaticCompletePostinstall, this)) {
+ CompletePostinstall(1);
+ }
+}
+
+void PostinstallRunnerAction::CompletePostinstall(int return_code) {
+ ScopedActionCompleter completer(processor_, this);
+ ScopedTempUnmounter temp_unmounter(temp_rootfs_dir_);
+ if (return_code != 0) {
+ LOG(ERROR) << "Postinst command failed with code: " << return_code;
+
+ // Undo any changes done to trigger Powerwash using clobber-state.
+ if (powerwash_marker_created_)
+ utils::DeletePowerwashMarkerFile(powerwash_marker_file_);
+
+ if (return_code == 3) {
+ // This special return code means that we tried to update firmware,
+ // but couldn't because we booted from FW B, and we need to reboot
+ // to get back to FW A.
+ completer.set_code(ErrorCode::kPostinstallBootedFromFirmwareB);
+ }
+
+ if (return_code == 4) {
+ // This special return code means that we tried to update firmware,
+ // but couldn't because we booted from FW B, and we need to reboot
+ // to get back to FW A.
+ completer.set_code(ErrorCode::kPostinstallFirmwareRONotUpdatable);
+ }
+
+ return;
+ }
+
+ LOG(INFO) << "Postinst command succeeded";
+ if (HasOutputPipe()) {
+ SetOutputObject(install_plan_);
+ }
+
+ completer.set_code(ErrorCode::kSuccess);
+}
+
+void PostinstallRunnerAction::StaticCompletePostinstall(int return_code,
+ const string& output,
+ void* p) {
+ reinterpret_cast<PostinstallRunnerAction*>(p)->CompletePostinstall(
+ return_code);
+}
+
+} // namespace chromeos_update_engine
diff --git a/postinstall_runner_action.h b/postinstall_runner_action.h
new file mode 100644
index 0000000..e7ac1c5
--- /dev/null
+++ b/postinstall_runner_action.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_POSTINSTALL_RUNNER_ACTION_H_
+#define UPDATE_ENGINE_POSTINSTALL_RUNNER_ACTION_H_
+
+#include <string>
+
+#include "update_engine/action.h"
+#include "update_engine/install_plan.h"
+
+// The Postinstall Runner Action is responsible for running the postinstall
+// script of a successfully downloaded update.
+
+namespace chromeos_update_engine {
+
+class PostinstallRunnerAction : public InstallPlanAction {
+ public:
+ PostinstallRunnerAction()
+ : powerwash_marker_created_(false),
+ powerwash_marker_file_(nullptr) {}
+
+ void PerformAction();
+
+ // Note that there's no support for terminating this action currently.
+ void TerminateProcessing() { CHECK(false); }
+
+ // Debugging/logging
+ static std::string StaticType() { return "PostinstallRunnerAction"; }
+ std::string Type() const { return StaticType(); }
+
+ private:
+ // Subprocess::Exec callback.
+ void CompletePostinstall(int return_code);
+ static void StaticCompletePostinstall(int return_code,
+ const std::string& output,
+ void* p);
+
+ InstallPlan install_plan_;
+ std::string temp_rootfs_dir_;
+
+ // True if Powerwash Marker was created before invoking post-install script.
+ // False otherwise. Used for cleaning up if post-install fails.
+ bool powerwash_marker_created_;
+
+ // Non-null value will cause post-install to override the default marker
+ // file name; used for testing.
+ const char* powerwash_marker_file_;
+
+ // Special ctor + friend declaration for testing purposes.
+ explicit PostinstallRunnerAction(const char* powerwash_marker_file)
+ : powerwash_marker_created_(false),
+ powerwash_marker_file_(powerwash_marker_file) {}
+
+ friend class PostinstallRunnerActionTest;
+
+ DISALLOW_COPY_AND_ASSIGN(PostinstallRunnerAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_POSTINSTALL_RUNNER_ACTION_H_
diff --git a/postinstall_runner_action_unittest.cc b/postinstall_runner_action_unittest.cc
new file mode 100644
index 0000000..4549272
--- /dev/null
+++ b/postinstall_runner_action_unittest.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/postinstall_runner_action.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos::MessageLoop;
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class PostinstallRunnerActionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ EXPECT_EQ(0, chromeos::MessageLoopRunMaxIterations(&loop_, 1));
+ }
+
+ // DoTest with various combinations of do_losetup, err_code and
+ // powerwash_required.
+ void DoTest(bool do_losetup, int err_code, bool powerwash_required);
+
+ private:
+ static const char* kImageMountPointTemplate;
+
+ chromeos::GlibMessageLoop loop_;
+};
+
+class PostinstActionProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ PostinstActionProcessorDelegate()
+ : code_(ErrorCode::kError),
+ code_set_(false) {}
+ void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) {
+ MessageLoop::current()->BreakLoop();
+ }
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == PostinstallRunnerAction::StaticType()) {
+ code_ = code;
+ code_set_ = true;
+ }
+ }
+ ErrorCode code_;
+ bool code_set_;
+};
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
+ DoTest(true, 0, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) {
+ DoTest(true, 0, true);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
+ DoTest(false, 0, true);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
+ DoTest(true, 1, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) {
+ DoTest(true, 3, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareROErrScriptTest) {
+ DoTest(true, 4, false);
+}
+
+const char* PostinstallRunnerActionTest::kImageMountPointTemplate =
+ "au_destination-XXXXXX";
+
+void PostinstallRunnerActionTest::DoTest(
+ bool do_losetup,
+ int err_code,
+ bool powerwash_required) {
+ ASSERT_EQ(0, getuid()) << "Run me as root. Ideally don't run other tests "
+ << "as root, tho.";
+ // True if the post-install action is expected to succeed.
+ bool should_succeed = do_losetup && !err_code;
+
+ string orig_cwd;
+ {
+ vector<char> buf(1000);
+ ASSERT_EQ(buf.data(), getcwd(buf.data(), buf.size()));
+ orig_cwd = string(buf.data(), strlen(buf.data()));
+ }
+
+ // Create a unique named working directory and chdir into it.
+ string cwd;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ "postinstall_runner_action_unittest-XXXXXX",
+ &cwd));
+ ASSERT_EQ(0, test_utils::Chdir(cwd));
+
+ // Create a 10MiB sparse file to be used as image; format it as ext2.
+ ASSERT_EQ(0, System(
+ "dd if=/dev/zero of=image.dat seek=10485759 bs=1 count=1 "
+ "status=none"));
+ ASSERT_EQ(0, System("mkfs.ext2 -F image.dat"));
+
+ // Create a uniquely named image mount point, mount the image.
+ ASSERT_EQ(0, System(string("mkdir -p ") + kStatefulPartition));
+ string mountpoint;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ string(kStatefulPartition) + "/" + kImageMountPointTemplate,
+ &mountpoint));
+ ASSERT_EQ(0, System(string("mount -o loop image.dat ") + mountpoint));
+
+ // Generate a fake postinst script inside the image.
+ string script = (err_code ?
+ base::StringPrintf("#!/bin/bash\nexit %d", err_code) :
+ base::StringPrintf(
+ "#!/bin/bash\n"
+ "mount | grep au_postint_mount | grep ext2\n"
+ "if [ $? -eq 0 ]; then\n"
+ " touch %s/postinst_called\n"
+ "fi\n",
+ cwd.c_str()));
+ const string script_file_name = mountpoint + "/postinst";
+ ASSERT_TRUE(WriteFileString(script_file_name, script));
+ ASSERT_EQ(0, System(string("chmod a+x ") + script_file_name));
+
+ // Unmount image; do not remove the uniquely named directory as it will be
+ // reused during the test.
+ ASSERT_TRUE(utils::UnmountFilesystem(mountpoint));
+
+ // get a loop device we can use for the install device
+ string dev = "/dev/null";
+
+ unique_ptr<test_utils::ScopedLoopbackDeviceBinder> loop_releaser;
+ if (do_losetup) {
+ loop_releaser.reset(new test_utils::ScopedLoopbackDeviceBinder(
+ cwd + "/image.dat", &dev));
+ }
+
+ // We use a test-specific powerwash marker file, to avoid race conditions.
+ string powerwash_marker_file = mountpoint + "/factory_install_reset";
+ LOG(INFO) << ">>> powerwash_marker_file=" << powerwash_marker_file;
+
+ ActionProcessor processor;
+ ObjectFeederAction<InstallPlan> feeder_action;
+ InstallPlan install_plan;
+ install_plan.install_path = dev;
+ install_plan.download_url = "http://devserver:8080/update";
+ install_plan.powerwash_required = powerwash_required;
+ feeder_action.set_obj(install_plan);
+ PostinstallRunnerAction runner_action(powerwash_marker_file.c_str());
+ BondActions(&feeder_action, &runner_action);
+ ObjectCollectorAction<InstallPlan> collector_action;
+ BondActions(&runner_action, &collector_action);
+ PostinstActionProcessorDelegate delegate;
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&runner_action);
+ processor.EnqueueAction(&collector_action);
+ processor.set_delegate(&delegate);
+
+ loop_.PostTask(FROM_HERE,
+ base::Bind([&processor] { processor.StartProcessing(); }));
+ loop_.Run();
+ ASSERT_FALSE(processor.IsRunning());
+
+ EXPECT_TRUE(delegate.code_set_);
+ EXPECT_EQ(should_succeed, delegate.code_ == ErrorCode::kSuccess);
+ EXPECT_EQ(should_succeed, !collector_action.object().install_path.empty());
+ if (should_succeed)
+ EXPECT_TRUE(install_plan == collector_action.object());
+
+ const base::FilePath kPowerwashMarkerPath(powerwash_marker_file);
+ string actual_cmd;
+ if (should_succeed && powerwash_required) {
+ EXPECT_TRUE(base::ReadFileToString(kPowerwashMarkerPath, &actual_cmd));
+ EXPECT_EQ(kPowerwashCommand, actual_cmd);
+ } else {
+ EXPECT_FALSE(
+ base::ReadFileToString(kPowerwashMarkerPath, &actual_cmd));
+ }
+
+ if (err_code == 2)
+ EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB, delegate.code_);
+
+ struct stat stbuf;
+ int rc = lstat((string(cwd) + "/postinst_called").c_str(), &stbuf);
+ if (should_succeed)
+ ASSERT_EQ(0, rc);
+ else
+ ASSERT_LT(rc, 0);
+
+ if (do_losetup) {
+ loop_releaser.reset(nullptr);
+ }
+
+ // Remove unique stateful directory.
+ ASSERT_EQ(0, System(string("rm -fr ") + mountpoint));
+
+ // Remove the temporary work directory.
+ ASSERT_EQ(0, test_utils::Chdir(orig_cwd));
+ ASSERT_EQ(0, System(string("rm -fr ") + cwd));
+}
+
+// Death tests don't seem to be working on Hardy
+TEST_F(PostinstallRunnerActionTest, DISABLED_RunAsRootDeathTest) {
+ ASSERT_EQ(0, getuid());
+ PostinstallRunnerAction runner_action;
+ ASSERT_DEATH({ runner_action.TerminateProcessing(); },
+ "postinstall_runner_action.h:.*] Check failed");
+}
+
+} // namespace chromeos_update_engine
diff --git a/prefs.cc b/prefs.cc
new file mode 100644
index 0000000..5b91939
--- /dev/null
+++ b/prefs.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/prefs.h"
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+bool Prefs::Init(const base::FilePath& prefs_dir) {
+ prefs_dir_ = prefs_dir;
+ return true;
+}
+
+bool Prefs::GetString(const string& key, string* value) {
+ base::FilePath filename;
+ TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+ if (!base::ReadFileToString(filename, value)) {
+ LOG(INFO) << key << " not present in " << prefs_dir_.value();
+ return false;
+ }
+ return true;
+}
+
+bool Prefs::SetString(const string& key, const string& value) {
+ base::FilePath filename;
+ TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+ TEST_AND_RETURN_FALSE(base::CreateDirectory(filename.DirName()));
+ TEST_AND_RETURN_FALSE(base::WriteFile(filename, value.data(), value.size()) ==
+ static_cast<int>(value.size()));
+ return true;
+}
+
+bool Prefs::GetInt64(const string& key, int64_t* value) {
+ string str_value;
+ if (!GetString(key, &str_value))
+ return false;
+ base::TrimWhitespaceASCII(str_value, base::TRIM_ALL, &str_value);
+ TEST_AND_RETURN_FALSE(base::StringToInt64(str_value, value));
+ return true;
+}
+
+bool Prefs::SetInt64(const string& key, const int64_t value) {
+ return SetString(key, base::Int64ToString(value));
+}
+
+bool Prefs::GetBoolean(const string& key, bool* value) {
+ string str_value;
+ if (!GetString(key, &str_value))
+ return false;
+ base::TrimWhitespaceASCII(str_value, base::TRIM_ALL, &str_value);
+ if (str_value == "false") {
+ *value = false;
+ return true;
+ }
+ if (str_value == "true") {
+ *value = true;
+ return true;
+ }
+ return false;
+}
+
+bool Prefs::SetBoolean(const string& key, const bool value) {
+ return SetString(key, value ? "true" : "false");
+}
+
+bool Prefs::Exists(const string& key) {
+ base::FilePath filename;
+ TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+ return base::PathExists(filename);
+}
+
+bool Prefs::Delete(const string& key) {
+ base::FilePath filename;
+ TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+ return base::DeleteFile(filename, false);
+}
+
+bool Prefs::GetFileNameForKey(const string& key,
+ base::FilePath* filename) {
+ // Allows only non-empty keys containing [A-Za-z0-9_-].
+ TEST_AND_RETURN_FALSE(!key.empty());
+ for (size_t i = 0; i < key.size(); ++i) {
+ char c = key.at(i);
+ TEST_AND_RETURN_FALSE(IsAsciiAlpha(c) || IsAsciiDigit(c) ||
+ c == '_' || c == '-');
+ }
+ *filename = prefs_dir_.Append(key);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/prefs.h b/prefs.h
new file mode 100644
index 0000000..1c4ec4f
--- /dev/null
+++ b/prefs.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PREFS_H_
+#define UPDATE_ENGINE_PREFS_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include "gtest/gtest_prod.h" // for FRIEND_TEST
+#include "update_engine/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a preference store by storing the value associated with
+// a key in a separate file named after the key under a preference
+// store directory.
+
+class Prefs : public PrefsInterface {
+ public:
+ Prefs() {}
+
+ // Initializes the store by associating this object with |prefs_dir|
+ // as the preference store directory. Returns true on success, false
+ // otherwise.
+ bool Init(const base::FilePath& prefs_dir);
+
+ // PrefsInterface methods.
+ bool GetString(const std::string& key, std::string* value) override;
+ bool SetString(const std::string& key, const std::string& value) override;
+ bool GetInt64(const std::string& key, int64_t* value) override;
+ bool SetInt64(const std::string& key, const int64_t value) override;
+ bool GetBoolean(const std::string& key, bool* value) override;
+ bool SetBoolean(const std::string& key, const bool value) override;
+
+ bool Exists(const std::string& key) override;
+ bool Delete(const std::string& key) override;
+
+ private:
+ FRIEND_TEST(PrefsTest, GetFileNameForKey);
+ FRIEND_TEST(PrefsTest, GetFileNameForKeyBadCharacter);
+ FRIEND_TEST(PrefsTest, GetFileNameForKeyEmpty);
+
+ // Sets |filename| to the full path to the file containing the data
+ // associated with |key|. Returns true on success, false otherwise.
+ bool GetFileNameForKey(const std::string& key, base::FilePath* filename);
+
+ // Preference store directory.
+ base::FilePath prefs_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(Prefs);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PREFS_H_
diff --git a/prefs_interface.h b/prefs_interface.h
new file mode 100644
index 0000000..cdc6efe
--- /dev/null
+++ b/prefs_interface.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PREFS_INTERFACE_H_
+#define UPDATE_ENGINE_PREFS_INTERFACE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+// The prefs interface allows access to a persistent preferences
+// store. The two reasons for providing this as an interface are
+// testing as well as easier switching to a new implementation in the
+// future, if necessary.
+
+class PrefsInterface {
+ public:
+ virtual ~PrefsInterface() {}
+
+ // Gets a string |value| associated with |key|. Returns true on
+ // success, false on failure (including when the |key| is not
+ // present in the store).
+ virtual bool GetString(const std::string& key, std::string* value) = 0;
+
+ // Associates |key| with a string |value|. Returns true on success,
+ // false otherwise.
+ virtual bool SetString(const std::string& key, const std::string& value) = 0;
+
+ // Gets an int64_t |value| associated with |key|. Returns true on
+ // success, false on failure (including when the |key| is not
+ // present in the store).
+ virtual bool GetInt64(const std::string& key, int64_t* value) = 0;
+
+ // Associates |key| with an int64_t |value|. Returns true on success,
+ // false otherwise.
+ virtual bool SetInt64(const std::string& key, const int64_t value) = 0;
+
+ // Gets a boolean |value| associated with |key|. Returns true on
+ // success, false on failure (including when the |key| is not
+ // present in the store).
+ virtual bool GetBoolean(const std::string& key, bool* value) = 0;
+
+ // Associates |key| with a boolean |value|. Returns true on success,
+ // false otherwise.
+ virtual bool SetBoolean(const std::string& key, const bool value) = 0;
+
+ // Returns true if the setting exists (i.e. a file with the given key
+ // exists in the prefs directory)
+ virtual bool Exists(const std::string& key) = 0;
+
+ // Returns true if successfully deleted the file corresponding to
+ // this key. Calling with non-existent keys does nothing.
+ virtual bool Delete(const std::string& key) = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PREFS_INTERFACE_H_
diff --git a/prefs_unittest.cc b/prefs_unittest.cc
new file mode 100644
index 0000000..fd36acc
--- /dev/null
+++ b/prefs_unittest.cc
@@ -0,0 +1,272 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/prefs.h"
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <base/macros.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+class PrefsTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(base::CreateNewTempDirectory("auprefs", &prefs_dir_));
+ ASSERT_TRUE(prefs_.Init(prefs_dir_));
+ }
+
+ void TearDown() override {
+ base::DeleteFile(prefs_dir_, true); // recursive
+ }
+
+ bool SetValue(const string& key, const string& value) {
+ return base::WriteFile(prefs_dir_.Append(key), value.data(),
+ value.length()) == static_cast<int>(value.length());
+ }
+
+ base::FilePath prefs_dir_;
+ Prefs prefs_;
+};
+
+TEST_F(PrefsTest, GetFileNameForKey) {
+ const char kKey[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-";
+ base::FilePath path;
+ EXPECT_TRUE(prefs_.GetFileNameForKey(kKey, &path));
+ EXPECT_EQ(prefs_dir_.Append(kKey).value(), path.value());
+}
+
+TEST_F(PrefsTest, GetFileNameForKeyBadCharacter) {
+ base::FilePath path;
+ EXPECT_FALSE(prefs_.GetFileNameForKey("ABC abc", &path));
+}
+
+TEST_F(PrefsTest, GetFileNameForKeyEmpty) {
+ base::FilePath path;
+ EXPECT_FALSE(prefs_.GetFileNameForKey("", &path));
+}
+
+TEST_F(PrefsTest, GetString) {
+ const char kKey[] = "test-key";
+ const string test_data = "test data";
+ ASSERT_TRUE(SetValue(kKey, test_data));
+ string value;
+ EXPECT_TRUE(prefs_.GetString(kKey, &value));
+ EXPECT_EQ(test_data, value);
+}
+
+TEST_F(PrefsTest, GetStringBadKey) {
+ string value;
+ EXPECT_FALSE(prefs_.GetString(",bad", &value));
+}
+
+TEST_F(PrefsTest, GetStringNonExistentKey) {
+ string value;
+ EXPECT_FALSE(prefs_.GetString("non-existent-key", &value));
+}
+
+TEST_F(PrefsTest, SetString) {
+ const char kKey[] = "my_test_key";
+ const char kValue[] = "some test value\non 2 lines";
+ EXPECT_TRUE(prefs_.SetString(kKey, kValue));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ(kValue, value);
+}
+
+TEST_F(PrefsTest, SetStringBadKey) {
+ const char kKey[] = ".no-dots";
+ EXPECT_FALSE(prefs_.SetString(kKey, "some value"));
+ EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, SetStringCreateDir) {
+ const char kKey[] = "a-test-key";
+ const char kValue[] = "test value";
+ base::FilePath subdir = prefs_dir_.Append("subdir1").Append("subdir2");
+ EXPECT_TRUE(prefs_.Init(subdir));
+ EXPECT_TRUE(prefs_.SetString(kKey, kValue));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(subdir.Append(kKey), &value));
+ EXPECT_EQ(kValue, value);
+}
+
+TEST_F(PrefsTest, SetStringDirCreationFailure) {
+ EXPECT_TRUE(prefs_.Init(base::FilePath("/dev/null")));
+ const char kKey[] = "test-key";
+ EXPECT_FALSE(prefs_.SetString(kKey, "test value"));
+}
+
+TEST_F(PrefsTest, SetStringFileCreationFailure) {
+ const char kKey[] = "a-test-key";
+ base::CreateDirectory(prefs_dir_.Append(kKey));
+ EXPECT_FALSE(prefs_.SetString(kKey, "test value"));
+ EXPECT_TRUE(base::DirectoryExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, GetInt64) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, " \n 25 \t "));
+ int64_t value;
+ EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+ EXPECT_EQ(25, value);
+}
+
+TEST_F(PrefsTest, GetInt64BadValue) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, "30a"));
+ int64_t value;
+ EXPECT_FALSE(prefs_.GetInt64(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetInt64Max) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, base::StringPrintf("%" PRIi64, kint64max)));
+ int64_t value;
+ EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+ EXPECT_EQ(kint64max, value);
+}
+
+TEST_F(PrefsTest, GetInt64Min) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, base::StringPrintf("%" PRIi64, kint64min)));
+ int64_t value;
+ EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+ EXPECT_EQ(kint64min, value);
+}
+
+TEST_F(PrefsTest, GetInt64Negative) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, " \t -100 \n "));
+ int64_t value;
+ EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+ EXPECT_EQ(-100, value);
+}
+
+TEST_F(PrefsTest, GetInt64NonExistentKey) {
+ int64_t value;
+ EXPECT_FALSE(prefs_.GetInt64("random-key", &value));
+}
+
+TEST_F(PrefsTest, SetInt64) {
+ const char kKey[] = "test_int";
+ EXPECT_TRUE(prefs_.SetInt64(kKey, -123));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ("-123", value);
+}
+
+TEST_F(PrefsTest, SetInt64BadKey) {
+ const char kKey[] = "s p a c e s";
+ EXPECT_FALSE(prefs_.SetInt64(kKey, 20));
+ EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, SetInt64Max) {
+ const char kKey[] = "test-max-int";
+ EXPECT_TRUE(prefs_.SetInt64(kKey, kint64max));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ(base::StringPrintf("%" PRIi64, kint64max), value);
+}
+
+TEST_F(PrefsTest, SetInt64Min) {
+ const char kKey[] = "test-min-int";
+ EXPECT_TRUE(prefs_.SetInt64(kKey, kint64min));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ(base::StringPrintf("%" PRIi64, kint64min), value);
+}
+
+TEST_F(PrefsTest, GetBooleanFalse) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, " \n false \t "));
+ bool value;
+ EXPECT_TRUE(prefs_.GetBoolean(kKey, &value));
+ EXPECT_FALSE(value);
+}
+
+TEST_F(PrefsTest, GetBooleanTrue) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, " \t true \n "));
+ bool value;
+ EXPECT_TRUE(prefs_.GetBoolean(kKey, &value));
+ EXPECT_TRUE(value);
+}
+
+TEST_F(PrefsTest, GetBooleanBadValue) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, "1"));
+ bool value;
+ EXPECT_FALSE(prefs_.GetBoolean(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetBooleanBadEmptyValue) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, ""));
+ bool value;
+ EXPECT_FALSE(prefs_.GetBoolean(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetBooleanNonExistentKey) {
+ bool value;
+ EXPECT_FALSE(prefs_.GetBoolean("random-key", &value));
+}
+
+TEST_F(PrefsTest, SetBooleanTrue) {
+ const char kKey[] = "test-bool";
+ EXPECT_TRUE(prefs_.SetBoolean(kKey, true));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ("true", value);
+}
+
+TEST_F(PrefsTest, SetBooleanFalse) {
+ const char kKey[] = "test-bool";
+ EXPECT_TRUE(prefs_.SetBoolean(kKey, false));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ("false", value);
+}
+
+TEST_F(PrefsTest, SetBooleanBadKey) {
+ const char kKey[] = "s p a c e s";
+ EXPECT_FALSE(prefs_.SetBoolean(kKey, true));
+ EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, ExistsWorks) {
+ const char kKey[] = "exists-key";
+
+ // test that the key doesn't exist before we set it.
+ EXPECT_FALSE(prefs_.Exists(kKey));
+
+ // test that the key exists after we set it.
+ ASSERT_TRUE(prefs_.SetInt64(kKey, 8));
+ EXPECT_TRUE(prefs_.Exists(kKey));
+}
+
+TEST_F(PrefsTest, DeleteWorks) {
+ const char kKey[] = "delete-key";
+
+ // test that it's alright to delete a non-existent key.
+ EXPECT_TRUE(prefs_.Delete(kKey));
+
+ // delete the key after we set it.
+ ASSERT_TRUE(prefs_.SetInt64(kKey, 0));
+ EXPECT_TRUE(prefs_.Delete(kKey));
+
+ // make sure it doesn't exist anymore.
+ EXPECT_FALSE(prefs_.Exists(kKey));
+}
+
+} // namespace chromeos_update_engine
diff --git a/proxy_resolver.cc b/proxy_resolver.cc
new file mode 100644
index 0000000..8b8a9c8
--- /dev/null
+++ b/proxy_resolver.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/proxy_resolver.h"
+
+#include <base/bind.h>
+#include <base/location.h>
+
+using chromeos::MessageLoop;
+using std::deque;
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char kNoProxy[] = "direct://";
+
+DirectProxyResolver::~DirectProxyResolver() {
+ if (idle_callback_id_ != MessageLoop::kTaskIdNull) {
+ // The DirectProxyResolver is instantiated as part of the UpdateAttempter
+ // which is also instantiated by default by the FakeSystemState, even when
+ // it is not used. We check the manage_shares_id_ before calling the
+ // MessageLoop::current() since the unit test using a FakeSystemState may
+ // have not define a MessageLoop for the current thread.
+ MessageLoop::current()->CancelTask(idle_callback_id_);
+ idle_callback_id_ = MessageLoop::kTaskIdNull;
+ }
+}
+
+bool DirectProxyResolver::GetProxiesForUrl(const string& url,
+ ProxiesResolvedFn callback,
+ void* data) {
+ idle_callback_id_ = MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &DirectProxyResolver::ReturnCallback,
+ base::Unretained(this),
+ callback,
+ data));
+ return true;
+}
+
+void DirectProxyResolver::ReturnCallback(ProxiesResolvedFn callback,
+ void* data) {
+ idle_callback_id_ = MessageLoop::kTaskIdNull;
+
+ // Initialize proxy pool with as many proxies as indicated (all identical).
+ deque<string> proxies(num_proxies_, kNoProxy);
+
+ (*callback)(proxies, data);
+}
+
+
+} // namespace chromeos_update_engine
diff --git a/proxy_resolver.h b/proxy_resolver.h
new file mode 100644
index 0000000..9d6b73a
--- /dev/null
+++ b/proxy_resolver.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PROXY_RESOLVER_H_
+#define UPDATE_ENGINE_PROXY_RESOLVER_H_
+
+#include <deque>
+#include <string>
+
+#include <base/logging.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+extern const char kNoProxy[];
+
+// Callback for a call to GetProxiesForUrl().
+// Resultant proxies are in |out_proxy|. Each will be in one of the
+// following forms:
+// http://<host>[:<port>] - HTTP proxy
+// socks{4,5}://<host>[:<port>] - SOCKS4/5 proxy
+// kNoProxy - no proxy
+typedef void (*ProxiesResolvedFn)(const std::deque<std::string>& proxies,
+ void* data);
+
+class ProxyResolver {
+ public:
+ ProxyResolver() {}
+ virtual ~ProxyResolver() {}
+
+ // Finds proxies for the given URL and returns them via the callback.
+ // |data| will be passed to the callback.
+ // Returns true on success.
+ virtual bool GetProxiesForUrl(const std::string& url,
+ ProxiesResolvedFn callback,
+ void* data) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolver);
+};
+
+// Always says to not use a proxy
+class DirectProxyResolver : public ProxyResolver {
+ public:
+ DirectProxyResolver() = default;
+ ~DirectProxyResolver() override;
+ bool GetProxiesForUrl(const std::string& url,
+ ProxiesResolvedFn callback,
+ void* data) override;
+
+ // Set the number of direct (non-) proxies to be returned by resolver.
+ // The default value is 1; higher numbers are currently used in testing.
+ inline void set_num_proxies(size_t num_proxies) {
+ num_proxies_ = num_proxies;
+ }
+
+ private:
+ // The ID of the main loop callback.
+ chromeos::MessageLoop::TaskId idle_callback_id_{
+ chromeos::MessageLoop::kTaskIdNull};
+
+ // Number of direct proxies to return on resolved list; currently used for
+ // testing.
+ size_t num_proxies_{1};
+
+ // The MainLoop callback, from here we return to the client.
+ void ReturnCallback(ProxiesResolvedFn callback, void* data);
+ DISALLOW_COPY_AND_ASSIGN(DirectProxyResolver);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PROXY_RESOLVER_H_
diff --git a/real_dbus_wrapper.h b/real_dbus_wrapper.h
new file mode 100644
index 0000000..0c6847b
--- /dev/null
+++ b/real_dbus_wrapper.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_REAL_DBUS_WRAPPER_H_
+#define UPDATE_ENGINE_REAL_DBUS_WRAPPER_H_
+
+// A mockable interface for DBus.
+
+#include <base/macros.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "update_engine/dbus_wrapper_interface.h"
+
+namespace chromeos_update_engine {
+
+class RealDBusWrapper : public DBusWrapperInterface {
+ DBusGProxy* ProxyNewForName(DBusGConnection* connection,
+ const char* name,
+ const char* path,
+ const char* interface) override {
+ return dbus_g_proxy_new_for_name(connection,
+ name,
+ path,
+ interface);
+ }
+
+ void ProxyUnref(DBusGProxy* proxy) override {
+ g_object_unref(proxy);
+ }
+
+ DBusGConnection* BusGet(DBusBusType type, GError** error) override {
+ return dbus_g_bus_get(type, error);
+ }
+
+ gboolean ProxyCall_0_1(DBusGProxy* proxy,
+ const char* method,
+ GError** error,
+ GHashTable** out1) override {
+ return dbus_g_proxy_call(proxy, method, error, G_TYPE_INVALID,
+ dbus_g_type_get_map("GHashTable",
+ G_TYPE_STRING,
+ G_TYPE_VALUE),
+ out1, G_TYPE_INVALID);
+ }
+
+ gboolean ProxyCall_0_1(DBusGProxy* proxy,
+ const char* method,
+ GError** error,
+ gint* out1) override {
+ return dbus_g_proxy_call(proxy, method, error, G_TYPE_INVALID,
+ G_TYPE_INT, out1, G_TYPE_INVALID);
+ }
+
+ gboolean ProxyCall_1_0(DBusGProxy* proxy,
+ const char* method,
+ GError** error,
+ gint in1) override {
+ return dbus_g_proxy_call(proxy, method, error,
+ G_TYPE_INT, in1,
+ G_TYPE_INVALID, G_TYPE_INVALID);
+ }
+
+ gboolean ProxyCall_3_0(DBusGProxy* proxy,
+ const char* method,
+ GError** error,
+ const char* in1,
+ const char* in2,
+ const char* in3) override {
+ return dbus_g_proxy_call(
+ proxy, method, error,
+ G_TYPE_STRING, in1, G_TYPE_STRING, in2, G_TYPE_STRING, in3,
+ G_TYPE_INVALID, G_TYPE_INVALID);
+ }
+
+ void ProxyAddSignal_1(DBusGProxy* proxy,
+ const char* signal_name,
+ GType type1) override {
+ dbus_g_object_register_marshaller(
+ g_cclosure_marshal_generic, G_TYPE_NONE, type1, G_TYPE_INVALID);
+ dbus_g_proxy_add_signal(proxy, signal_name, type1, G_TYPE_INVALID);
+ }
+
+ void ProxyAddSignal_2(DBusGProxy* proxy,
+ const char* signal_name,
+ GType type1,
+ GType type2) override {
+ dbus_g_object_register_marshaller(
+ g_cclosure_marshal_generic, G_TYPE_NONE, type1, type2, G_TYPE_INVALID);
+ dbus_g_proxy_add_signal(proxy, signal_name, type1, type2, G_TYPE_INVALID);
+ }
+
+ void ProxyConnectSignal(DBusGProxy* proxy,
+ const char* signal_name,
+ GCallback handler,
+ void* data,
+ GClosureNotify free_data_func) override {
+ dbus_g_proxy_connect_signal(proxy, signal_name, handler, data,
+ free_data_func);
+ }
+
+ void ProxyDisconnectSignal(DBusGProxy* proxy,
+ const char* signal_name,
+ GCallback handler,
+ void* data) override {
+ dbus_g_proxy_disconnect_signal(proxy, signal_name, handler, data);
+ }
+
+ DBusConnection* ConnectionGetConnection(DBusGConnection* gbus) override {
+ return dbus_g_connection_get_connection(gbus);
+ }
+
+ void DBusBusAddMatch(DBusConnection* connection,
+ const char* rule,
+ DBusError* error) override {
+ dbus_bus_add_match(connection, rule, error);
+ }
+
+ dbus_bool_t DBusConnectionAddFilter(
+ DBusConnection* connection,
+ DBusHandleMessageFunction function,
+ void* user_data,
+ DBusFreeFunction free_data_function) override {
+ return dbus_connection_add_filter(connection,
+ function,
+ user_data,
+ free_data_function);
+ }
+
+ void DBusConnectionRemoveFilter(DBusConnection* connection,
+ DBusHandleMessageFunction function,
+ void* user_data) override {
+ dbus_connection_remove_filter(connection, function, user_data);
+ }
+
+ dbus_bool_t DBusMessageIsSignal(DBusMessage* message,
+ const char* interface,
+ const char* signal_name) override {
+ return dbus_message_is_signal(message, interface, signal_name);
+ }
+
+ dbus_bool_t DBusMessageGetArgs_3(DBusMessage* message,
+ DBusError* error,
+ char** out1,
+ char** out2,
+ char** out3) override {
+ return dbus_message_get_args(message, error,
+ DBUS_TYPE_STRING, out1,
+ DBUS_TYPE_STRING, out2,
+ DBUS_TYPE_STRING, out3,
+ G_TYPE_INVALID);
+ }
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_REAL_DBUS_WRAPPER_H_
diff --git a/real_system_state.cc b/real_system_state.cc
new file mode 100644
index 0000000..f16f2f8
--- /dev/null
+++ b/real_system_state.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/real_system_state.h"
+
+#include <base/files/file_util.h>
+#include <base/time/time.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/update_manager/state_factory.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+RealSystemState::RealSystemState()
+ : device_policy_(nullptr),
+ connection_manager_(this),
+ update_attempter_(this, &dbus_),
+ request_params_(this),
+ system_rebooted_(false) {}
+
+bool RealSystemState::Initialize() {
+ metrics_lib_.Init();
+
+ if (!prefs_.Init(base::FilePath(kPrefsDirectory))) {
+ LOG(ERROR) << "Failed to initialize preferences.";
+ return false;
+ }
+
+ if (!powerwash_safe_prefs_.Init(base::FilePath(kPowerwashSafePrefsDir))) {
+ LOG(ERROR) << "Failed to initialize powerwash preferences.";
+ return false;
+ }
+
+ if (!utils::FileExists(kSystemRebootedMarkerFile)) {
+ if (!utils::WriteFile(kSystemRebootedMarkerFile, "", 0)) {
+ LOG(ERROR) << "Could not create reboot marker file";
+ return false;
+ }
+ system_rebooted_ = true;
+ }
+
+ // Initialize the Update Manager using the default state factory.
+ chromeos_update_manager::State* um_state =
+ chromeos_update_manager::DefaultStateFactory(
+ &policy_provider_, &dbus_, this);
+ if (!um_state) {
+ LOG(ERROR) << "Failed to initialize the Update Manager.";
+ return false;
+ }
+ update_manager_.reset(
+ new chromeos_update_manager::UpdateManager(
+ &clock_, base::TimeDelta::FromSeconds(5),
+ base::TimeDelta::FromHours(12), um_state));
+
+ // The P2P Manager depends on the Update Manager for its initialization.
+ p2p_manager_.reset(P2PManager::Construct(
+ nullptr, &clock_, update_manager_.get(), "cros_au",
+ kMaxP2PFilesToKeep, base::TimeDelta::FromDays(kMaxP2PFileAgeDays)));
+
+ if (!payload_state_.Initialize(this)) {
+ LOG(ERROR) << "Failed to initialize the payload state object.";
+ return false;
+ }
+
+ // Initialize the update attempter.
+ update_attempter_.Init();
+
+ // All is well. Initialization successful.
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/real_system_state.h b/real_system_state.h
new file mode 100644
index 0000000..7e0c16d
--- /dev/null
+++ b/real_system_state.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_REAL_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_REAL_SYSTEM_STATE_H_
+
+#include "update_engine/system_state.h"
+
+#include <memory>
+
+#include <metrics/metrics_library.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/clock.h"
+#include "update_engine/connection_manager.h"
+#include "update_engine/hardware.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state.h"
+#include "update_engine/prefs.h"
+#include "update_engine/real_dbus_wrapper.h"
+#include "update_engine/update_attempter.h"
+#include "update_engine/update_manager/update_manager.h"
+
+namespace chromeos_update_engine {
+
+// A real implementation of the SystemStateInterface which is
+// used by the actual product code.
+class RealSystemState : public SystemState {
+ public:
+ // Constructs all system objects that do not require separate initialization;
+ // see Initialize() below for the remaining ones.
+ RealSystemState();
+
+ // Initializes and sets systems objects that require an initialization
+ // separately from construction. Returns |true| on success.
+ bool Initialize();
+
+ inline void set_device_policy(
+ const policy::DevicePolicy* device_policy) override {
+ device_policy_ = device_policy;
+ }
+
+ inline const policy::DevicePolicy* device_policy() override {
+ return device_policy_;
+ }
+
+ inline ClockInterface* clock() override { return &clock_; }
+
+ inline ConnectionManager* connection_manager() override {
+ return &connection_manager_;
+ }
+
+ inline HardwareInterface* hardware() override { return &hardware_; }
+
+ inline MetricsLibraryInterface* metrics_lib() override {
+ return &metrics_lib_;
+ }
+
+ inline PrefsInterface* prefs() override { return &prefs_; }
+
+ inline PrefsInterface* powerwash_safe_prefs() override {
+ return &powerwash_safe_prefs_;
+ }
+
+ inline PayloadStateInterface* payload_state() override {
+ return &payload_state_;
+ }
+
+ inline UpdateAttempter* update_attempter() override {
+ return &update_attempter_;
+ }
+
+ inline OmahaRequestParams* request_params() override {
+ return &request_params_;
+ }
+
+ inline P2PManager* p2p_manager() override { return p2p_manager_.get(); }
+
+ inline chromeos_update_manager::UpdateManager* update_manager() override {
+ return update_manager_.get();
+ }
+
+ inline bool system_rebooted() override { return system_rebooted_; }
+
+ private:
+ // Interface for the clock.
+ Clock clock_;
+
+ // The latest device policy object from the policy provider.
+ const policy::DevicePolicy* device_policy_;
+
+ // The connection manager object that makes download
+ // decisions depending on the current type of connection.
+ ConnectionManager connection_manager_;
+
+ // Interface for the hardware functions.
+ Hardware hardware_;
+
+ // The Metrics Library interface for reporting UMA stats.
+ MetricsLibrary metrics_lib_;
+
+ // Interface for persisted store.
+ Prefs prefs_;
+
+ // Interface for persisted store that persists across powerwashes.
+ Prefs powerwash_safe_prefs_;
+
+ // All state pertaining to payload state such as
+ // response, URL, backoff states.
+ PayloadState payload_state_;
+
+ // The dbus object used to initialize the update attempter.
+ RealDBusWrapper dbus_;
+
+ // Pointer to the update attempter object.
+ UpdateAttempter update_attempter_;
+
+ // Common parameters for all Omaha requests.
+ OmahaRequestParams request_params_;
+
+ std::unique_ptr<P2PManager> p2p_manager_;
+
+ std::unique_ptr<chromeos_update_manager::UpdateManager> update_manager_;
+
+ policy::PolicyProvider policy_provider_;
+
+ // If true, this is the first instance of the update engine since the system
+ // rebooted. Important for tracking whether you are running instance of the
+ // update engine on first boot or due to a crash/restart.
+ bool system_rebooted_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_REAL_SYSTEM_STATE_H_
diff --git a/run_unittests b/run_unittests
new file mode 100755
index 0000000..7a7c85a
--- /dev/null
+++ b/run_unittests
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Runs the update engine unit tests, including both userland and run-as-root
+# tests.
+
+if [ ! -e ./update_engine_unittests ]; then
+ echo 'Error: unit test binary missing' >&2
+ exit 1
+fi
+
+user_pass=0
+./update_engine_unittests --gtest_filter='-*.RunAsRoot*' && user_pass=1
+root_pass=0
+sudo ./update_engine_unittests --gtest_filter='*.RunAsRoot*' && root_pass=1
+
+echo -n "User tests: " && [ $user_pass == 1 ] && echo "PASSED" || echo "FAILED"
+echo -n "Root tests: " && [ $root_pass == 1 ] && echo "PASSED" || echo "FAILED"
+
+exit $((2 - user_pass - root_pass))
diff --git a/sample_images/disk_ext2_1k.txt b/sample_images/disk_ext2_1k.txt
new file mode 100644
index 0000000..1494535
--- /dev/null
+++ b/sample_images/disk_ext2_1k.txt
@@ -0,0 +1 @@
+default 16777216 1024
diff --git a/sample_images/disk_ext2_4k.txt b/sample_images/disk_ext2_4k.txt
new file mode 100644
index 0000000..cac718f
--- /dev/null
+++ b/sample_images/disk_ext2_4k.txt
@@ -0,0 +1 @@
+default 16777216 4096
diff --git a/sample_images/disk_ext2_ue_settings.txt b/sample_images/disk_ext2_ue_settings.txt
new file mode 100644
index 0000000..554f7da
--- /dev/null
+++ b/sample_images/disk_ext2_ue_settings.txt
@@ -0,0 +1 @@
+ue_settings 16777216 4096
diff --git a/sample_images/generate_image.sh b/sample_images/generate_image.sh
new file mode 100755
index 0000000..c4f132a
--- /dev/null
+++ b/sample_images/generate_image.sh
@@ -0,0 +1,158 @@
+#!/bin/bash
+
+# Copyright 2015 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.
+
+set -e
+
+# cleanup <path>
+# Unmount and remove the mountpoint <path>
+cleanup() {
+ if ! sudo umount "$1" 2>/dev/null; then
+ if mountpoint -q "$1"; then
+ sync && sudo umount "$1"
+ fi
+ fi
+ rmdir "$1"
+}
+
+# add_files_default <mntdir> <block_size>
+# Add several test files to the image mounted in <mntdir>.
+add_files_default() {
+ local mntdir="$1"
+ local block_size="$2"
+
+ ### Generate the files used in unittest with descriptive names.
+ sudo touch "${mntdir}"/empty-file
+
+ # regular: Regular files.
+ echo "small file" | sudo dd of="${mntdir}"/regular-small status=none
+ dd if=/dev/zero bs=1024 count=16 status=none | tr '\0' '\141' |
+ sudo dd of="${mntdir}"/regular-16k status=none
+ sudo dd if=/dev/zero of="${mntdir}"/regular-32k-zeros bs=1024 count=16 \
+ status=none
+
+ echo "with net_cap" | sudo dd of="${mntdir}"/regular-with_net_cap status=none
+ sudo setcap cap_net_raw=ep "${mntdir}"/regular-with_net_cap
+
+ # sparse_empty: Files with no data blocks at all (only sparse holes).
+ sudo truncate --size=10240 "${mntdir}"/sparse_empty-10k
+ sudo truncate --size=$(( block_size * 2 )) "${mntdir}"/sparse_empty-2blocks
+
+ # sparse: Files with some data blocks but also sparse holes.
+ echo -n "foo" |
+ sudo dd of="${mntdir}"/sparse-16k-last_block bs=1 \
+ seek=$(( 16 * 1024 - 3)) status=none
+
+ # ext2 inodes have 12 direct blocks, one indirect, one double indirect and
+ # one triple indirect. 10000 should be enough to have an indirect and double
+ # indirect block.
+ echo -n "foo" |
+ sudo dd of="${mntdir}"/sparse-10000blocks bs=1 \
+ seek=$(( block_size * 10000 )) status=none
+
+ sudo truncate --size=16384 "${mntdir}"/sparse-16k-first_block
+ echo "first block" | sudo dd of="${mntdir}"/sparse-16k-first_block status=none
+
+ sudo truncate --size=16384 "${mntdir}"/sparse-16k-holes
+ echo "a" | sudo dd of="${mntdir}"/sparse-16k-holes bs=1 seek=100 status=none
+ echo "b" | sudo dd of="${mntdir}"/sparse-16k-holes bs=1 seek=10000 status=none
+
+ # link: symlinks and hardlinks.
+ sudo ln -s "broken-link" "${mntdir}"/link-short_symlink
+ sudo ln -s $(dd if=/dev/zero bs=256 count=1 status=none | tr '\0' '\141') \
+ "${mntdir}"/link-long_symlink
+ sudo ln "${mntdir}"/regular-16k "${mntdir}"/link-hard-regular-16k
+
+ # Directories.
+ sudo mkdir -p "${mntdir}"/dir1/dir2/dir1
+ echo "foo" | sudo tee "${mntdir}"/dir1/dir2/file >/dev/null
+ echo "bar" | sudo tee "${mntdir}"/dir1/file >/dev/null
+
+ # removed: removed files that should not be listed.
+ echo "We will remove this file so it's contents will be somewhere in the " \
+ "empty space data but it won't be all zeros." |
+ sudo dd of="${mntdir}"/removed conv=fsync status=none
+ sudo rm "${mntdir}"/removed
+}
+
+# add_files_ue_settings <mntdir> <block_size>
+# Add the update_engine.conf settings file. This file contains the
+add_files_ue_settings() {
+ local mntdir="$1"
+
+ sudo mkdir -p "${mntdir}"/etc >/dev/null
+ sudo tee "${mntdir}"/etc/update_engine.conf >/dev/null <<EOF
+PAYLOAD_MINOR_VERSION=1234
+EOF
+ # Example of a real lsb-release file released on link stable.
+ sudo tee "${mntdir}"/etc/lsb-release >/dev/null <<EOF
+CHROMEOS_AUSERVER=https://tools.google.com/service/update2
+CHROMEOS_BOARD_APPID={F26D159B-52A3-491A-AE25-B23670A66B32}
+CHROMEOS_CANARY_APPID={90F229CE-83E2-4FAF-8479-E368A34938B1}
+CHROMEOS_DEVSERVER=
+CHROMEOS_RELEASE_APPID={F26D159B-52A3-491A-AE25-B23670A66B32}
+CHROMEOS_RELEASE_BOARD=link-signed-mp-v4keys
+CHROMEOS_RELEASE_BRANCH_NUMBER=63
+CHROMEOS_RELEASE_BUILD_NUMBER=6946
+CHROMEOS_RELEASE_BUILD_TYPE=Official Build
+CHROMEOS_RELEASE_CHROME_MILESTONE=43
+CHROMEOS_RELEASE_DESCRIPTION=6946.63.0 (Official Build) stable-channel link
+CHROMEOS_RELEASE_NAME=Chrome OS
+CHROMEOS_RELEASE_PATCH_NUMBER=0
+CHROMEOS_RELEASE_TRACK=stable-channel
+CHROMEOS_RELEASE_VERSION=6946.63.0
+GOOGLE_RELEASE=6946.63.0
+EOF
+}
+
+# generate_fs <filename> <kind> <size> [block_size] [block_groups]
+generate_fs() {
+ local filename="$1"
+ local kind="$2"
+ local size="$3"
+ local block_size="${4:-4096}"
+ local block_groups="${5:-}"
+
+ local mkfs_opts=( -q -F -b "${block_size}" -L "ROOT-TEST" -t ext2 )
+ if [[ -n "${block_groups}" ]]; then
+ mkfs_opts+=( -G "${block_groups}" )
+ fi
+
+ local mntdir=$(mktemp --tmpdir -d generate_ext2.XXXXXX)
+ trap 'cleanup "${mntdir}"; rm -f "${filename}"' INT TERM EXIT
+
+ # Cleanup old image.
+ if [[ -e "${filename}" ]]; then
+ rm -f "${filename}"
+ fi
+ truncate --size="${size}" "${filename}"
+
+ mkfs.ext2 "${mkfs_opts[@]}" "${filename}"
+ sudo mount "${filename}" "${mntdir}" -o loop
+
+ case "${kind}" in
+ ue_settings)
+ add_files_ue_settings "${mntdir}" "${block_size}"
+ ;;
+ default)
+ add_files_default "${mntdir}" "${block_size}"
+ ;;
+ esac
+
+ cleanup "${mntdir}"
+ trap - INT TERM EXIT
+}
+
+image_desc="${1:-}"
+output_dir="${2:-}"
+
+if [[ ! -e "${image_desc}" || ! -d "${output_dir}" ]]; then
+ echo "Use: $0 <image_description.txt> <output_dir>" >&2
+ exit 1
+fi
+
+args=( $(cat ${image_desc}) )
+dest_image="${output_dir}/$(basename ${image_desc} .txt).img"
+generate_fs "${dest_image}" "${args[@]}"
diff --git a/sample_omaha_v3_response.xml b/sample_omaha_v3_response.xml
new file mode 100644
index 0000000..abba523
--- /dev/null
+++ b/sample_omaha_v3_response.xml
@@ -0,0 +1,19 @@
+<response protocol="3.0" server="prod">
+ <daystart elapsed_seconds="56652"/>
+ <app appid="{90f229ce-83e2-4faf-8479-e368a34938b1}" status="ok">
+ <updatecheck status="ok">
+ <urls>
+ <url codebase="https://storage.googleapis.com/chromeos-releases-public/canary-channel/canary-channel/3095.0.0/"/>
+ </urls>
+ <manifest version="3095.0.0">
+ <packages>
+ <package hash="HVOmp67vBjPdvpWmOC2Uw4UDwsc=" name="chromeos_3095.0.0_x86-zgb_canary-channel_full_mp-v2.bin-df37843370ddf1e3819a2afeaa934faa.signed" required="true" size="400752559"/>
+ </packages>
+ <actions>
+ <action event="update" run="chromeos_3095.0.0_x86-zgb_canary-channel_full_mp-v2.bin-df37843370ddf1e3819a2afeaa934faa.signed"/>
+ <action ChromeOSVersion="3095.0.0" ChromeVersion="24.0.1307.0" IsDelta="true" IsDeltaPayload="false" MaxDaysToScatter="14" MetadataSignatureRsa="xXrO/LahHlKk3YmqEf1qE0PN587Sc2IJV+FN7J7x1h49waNQIy/QwYO4LaOySgETe5JZXtkAEzzqakfJwxQ2pVfzj1GkExwjd5LTn1He2GvA73B8fKbS4bfP7dbUFwD5039xCwf1U2gezFViOiOPiVURx/pEsdhv+Cqx/3HbjIuj5au2dooSyDxLC5AnODzAKyYfAcjMuiLON+9SqmctJW+VjzdY9SbJAnkH2qqVjFyBKAXsYT+hOTIJ3MJpg8OSVxMMtGB99PxbOJ52F37d2Y5Fws/AUkNnNEsan/WRJA1kuWoS6rpeR8JQYuVhLiK2u/KpOcvMVRw3Q2VUxtcAGw==" MetadataSize="58315" event="postinstall" sha256="DIAVxoI+8NpsudUawOA5U92VHlaxQBS3ejN4EPM6T2A="/>
+ </actions>
+ </manifest>
+ </updatecheck>
+ </app>
+</response>
diff --git a/subprocess.cc b/subprocess.cc
new file mode 100644
index 0000000..6a34641
--- /dev/null
+++ b/subprocess.cc
@@ -0,0 +1,275 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/subprocess.h"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/glib_utils.h"
+
+using chromeos::MessageLoop;
+using std::shared_ptr;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+void Subprocess::GChildExitedCallback(GPid pid, gint status, gpointer data) {
+ SubprocessRecord* record = reinterpret_cast<SubprocessRecord*>(data);
+
+ // Make sure we read any remaining process output and then close the pipe.
+ OnStdoutReady(record);
+
+ MessageLoop::current()->CancelTask(record->task_id);
+ record->task_id = MessageLoop::kTaskIdNull;
+ if (IGNORE_EINTR(close(record->stdout_fd)) != 0) {
+ PLOG(ERROR) << "Error closing fd " << record->stdout_fd;
+ }
+ g_spawn_close_pid(pid);
+ gint use_status = status;
+ if (WIFEXITED(status))
+ use_status = WEXITSTATUS(status);
+
+ if (status) {
+ LOG(INFO) << "Subprocess status: " << use_status;
+ }
+ if (!record->stdout.empty()) {
+ LOG(INFO) << "Subprocess output:\n" << record->stdout;
+ }
+ if (record->callback) {
+ record->callback(use_status, record->stdout, record->callback_data);
+ }
+ Get().subprocess_records_.erase(record->tag);
+}
+
+void Subprocess::GRedirectStderrToStdout(gpointer user_data) {
+ dup2(1, 2);
+}
+
+void Subprocess::OnStdoutReady(SubprocessRecord* record) {
+ char buf[1024];
+ ssize_t rc = 0;
+ do {
+ rc = HANDLE_EINTR(read(record->stdout_fd, buf, arraysize(buf)));
+ if (rc < 0) {
+ // EAGAIN and EWOULDBLOCK are normal return values when there's no more
+ // input as we are in non-blocking mode.
+ if (errno != EWOULDBLOCK && errno != EAGAIN) {
+ PLOG(ERROR) << "Error reading fd " << record->stdout_fd;
+ }
+ } else {
+ record->stdout.append(buf, rc);
+ }
+ } while (rc > 0);
+}
+
+namespace {
+void FreeArgv(char** argv) {
+ for (int i = 0; argv[i]; i++) {
+ free(argv[i]);
+ argv[i] = nullptr;
+ }
+}
+
+void FreeArgvInError(char** argv) {
+ FreeArgv(argv);
+ LOG(ERROR) << "Ran out of memory copying args.";
+}
+
+// Note: Caller responsible for free()ing the returned value!
+// Will return null on failure and free any allocated memory.
+char** ArgPointer() {
+ const char* keys[] = {"LD_LIBRARY_PATH", "PATH"};
+ char** ret = new char*[arraysize(keys) + 1];
+ int pointer = 0;
+ for (size_t i = 0; i < arraysize(keys); i++) {
+ if (getenv(keys[i])) {
+ ret[pointer] = strdup(base::StringPrintf("%s=%s", keys[i],
+ getenv(keys[i])).c_str());
+ if (!ret[pointer]) {
+ FreeArgv(ret);
+ delete [] ret;
+ return nullptr;
+ }
+ ++pointer;
+ }
+ }
+ ret[pointer] = nullptr;
+ return ret;
+}
+
+class ScopedFreeArgPointer {
+ public:
+ explicit ScopedFreeArgPointer(char** arr) : arr_(arr) {}
+ ~ScopedFreeArgPointer() {
+ if (!arr_)
+ return;
+ for (int i = 0; arr_[i]; i++)
+ free(arr_[i]);
+ delete[] arr_;
+ }
+ private:
+ char** arr_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedFreeArgPointer);
+};
+} // namespace
+
+uint32_t Subprocess::Exec(const vector<string>& cmd,
+ ExecCallback callback,
+ void* p) {
+ return ExecFlags(cmd, static_cast<GSpawnFlags>(0), true, callback, p);
+}
+
+uint32_t Subprocess::ExecFlags(const vector<string>& cmd,
+ GSpawnFlags flags,
+ bool redirect_stderr_to_stdout,
+ ExecCallback callback,
+ void* p) {
+ unique_ptr<gchar*, utils::GLibStrvFreeDeleter> argv(
+ utils::StringVectorToGStrv(cmd));
+
+ char** argp = ArgPointer();
+ if (!argp) {
+ FreeArgvInError(argv.get()); // null in argv[i] terminates argv.
+ return 0;
+ }
+ ScopedFreeArgPointer argp_free(argp);
+
+ shared_ptr<SubprocessRecord> record(new SubprocessRecord);
+ record->callback = callback;
+ record->callback_data = p;
+ gint stdout_fd = -1;
+ GError* error = nullptr;
+ bool success = g_spawn_async_with_pipes(
+ nullptr, // working directory
+ argv.get(),
+ argp,
+ static_cast<GSpawnFlags>(flags | G_SPAWN_DO_NOT_REAP_CHILD), // flags
+ // child setup function:
+ redirect_stderr_to_stdout ? GRedirectStderrToStdout : nullptr,
+ nullptr, // child setup data pointer
+ &record->pid,
+ nullptr,
+ &stdout_fd,
+ nullptr,
+ &error);
+ if (!success) {
+ LOG(ERROR) << "g_spawn_async failed: " << utils::GetAndFreeGError(&error);
+ return 0;
+ }
+ record->tag =
+ g_child_watch_add(record->pid, GChildExitedCallback, record.get());
+ record->stdout_fd = stdout_fd;
+ subprocess_records_[record->tag] = record;
+
+ // Capture the subprocess output. Make our end of the pipe non-blocking.
+ int fd_flags = fcntl(stdout_fd, F_GETFL, 0) | O_NONBLOCK;
+ if (HANDLE_EINTR(fcntl(record->stdout_fd, F_SETFL, fd_flags)) < 0) {
+ LOG(ERROR) << "Unable to set non-blocking I/O mode on fd "
+ << record->stdout_fd << ".";
+ }
+
+ record->task_id = MessageLoop::current()->WatchFileDescriptor(
+ FROM_HERE,
+ record->stdout_fd,
+ MessageLoop::WatchMode::kWatchRead,
+ true,
+ base::Bind(&Subprocess::OnStdoutReady, record.get()));
+
+ return record->tag;
+}
+
+void Subprocess::KillExec(uint32_t tag) {
+ const auto& record = subprocess_records_.find(tag);
+ if (record == subprocess_records_.end())
+ return;
+ record->second->callback = nullptr;
+ kill(record->second->pid, SIGTERM);
+}
+
+bool Subprocess::SynchronousExecFlags(const vector<string>& cmd,
+ GSpawnFlags flags,
+ int* return_code,
+ string* stdout) {
+ if (stdout) {
+ *stdout = "";
+ }
+ GError* err = nullptr;
+ unique_ptr<char*[]> argv(new char*[cmd.size() + 1]);
+ for (unsigned int i = 0; i < cmd.size(); i++) {
+ argv[i] = strdup(cmd[i].c_str());
+ if (!argv[i]) {
+ FreeArgvInError(argv.get()); // null in argv[i] terminates argv.
+ return false;
+ }
+ }
+ argv[cmd.size()] = nullptr;
+
+ char** argp = ArgPointer();
+ if (!argp) {
+ FreeArgvInError(argv.get()); // null in argv[i] terminates argv.
+ return false;
+ }
+ ScopedFreeArgPointer argp_free(argp);
+
+ char* child_stdout;
+ bool success = g_spawn_sync(
+ nullptr, // working directory
+ argv.get(),
+ argp,
+ static_cast<GSpawnFlags>(G_SPAWN_STDERR_TO_DEV_NULL |
+ G_SPAWN_SEARCH_PATH | flags), // flags
+ GRedirectStderrToStdout, // child setup function
+ nullptr, // data for child setup function
+ &child_stdout,
+ nullptr,
+ return_code,
+ &err);
+ FreeArgv(argv.get());
+ LOG_IF(INFO, err) << utils::GetAndFreeGError(&err);
+ if (child_stdout) {
+ if (stdout) {
+ *stdout = child_stdout;
+ } else if (*child_stdout) {
+ LOG(INFO) << "Subprocess output:\n" << child_stdout;
+ }
+ g_free(child_stdout);
+ }
+ return success;
+}
+
+bool Subprocess::SynchronousExec(const vector<string>& cmd,
+ int* return_code,
+ string* stdout) {
+ return SynchronousExecFlags(cmd,
+ static_cast<GSpawnFlags>(0),
+ return_code,
+ stdout);
+}
+
+bool Subprocess::SubprocessInFlight() {
+ for (const auto& tag_record_pair : subprocess_records_) {
+ if (tag_record_pair.second->callback)
+ return true;
+ }
+ return false;
+}
+
+Subprocess* Subprocess::subprocess_singleton_ = nullptr;
+
+} // namespace chromeos_update_engine
diff --git a/subprocess.h b/subprocess.h
new file mode 100644
index 0000000..13f7eb7
--- /dev/null
+++ b/subprocess.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_SUBPROCESS_H_
+#define UPDATE_ENGINE_SUBPROCESS_H_
+
+#include <glib.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+// The Subprocess class is a singleton. It's used to spawn off a subprocess
+// and get notified when the subprocess exits. The result of Exec() can
+// be saved and used to cancel the callback request and kill your process. If
+// you know you won't call KillExec(), you may safely lose the return value
+// from Exec().
+
+namespace chromeos_update_engine {
+
+class Subprocess {
+ public:
+ typedef void(*ExecCallback)(int return_code,
+ const std::string& output,
+ void *p);
+
+ static void Init() {
+ CHECK(!subprocess_singleton_);
+ subprocess_singleton_ = new Subprocess;
+ }
+
+ // Returns a tag > 0 on success.
+ uint32_t Exec(const std::vector<std::string>& cmd,
+ ExecCallback callback,
+ void* p);
+ uint32_t ExecFlags(const std::vector<std::string>& cmd,
+ GSpawnFlags flags,
+ bool redirect_stderr_to_stdout,
+ ExecCallback callback,
+ void* p);
+
+ // Kills the running process with SIGTERM and ignores the callback.
+ void KillExec(uint32_t tag);
+
+ // Executes a command synchronously. Returns true on success. If |stdout| is
+ // non-null, the process output is stored in it, otherwise the output is
+ // logged. Note that stderr is redirected to stdout.
+ static bool SynchronousExecFlags(const std::vector<std::string>& cmd,
+ GSpawnFlags flags,
+ int* return_code,
+ std::string* stdout);
+ static bool SynchronousExec(const std::vector<std::string>& cmd,
+ int* return_code,
+ std::string* stdout);
+
+ // Gets the one instance.
+ static Subprocess& Get() {
+ return *subprocess_singleton_;
+ }
+
+ // Returns true iff there is at least one subprocess we're waiting on.
+ bool SubprocessInFlight();
+
+ private:
+ FRIEND_TEST(SubprocessTest, CancelTest);
+
+ struct SubprocessRecord {
+ SubprocessRecord() = default;
+
+ uint32_t tag{0};
+ chromeos::MessageLoop::TaskId task_id{chromeos::MessageLoop::kTaskIdNull};
+
+ ExecCallback callback{nullptr};
+ void* callback_data{nullptr};
+
+ GPid pid;
+
+ int stdout_fd{-1};
+ std::string stdout;
+ };
+
+ Subprocess() {}
+
+ // Callback for when any subprocess terminates. This calls the user
+ // requested callback.
+ static void GChildExitedCallback(GPid pid, gint status, gpointer data);
+
+ // Callback which runs in the child before exec to redirect stderr onto
+ // stdout.
+ static void GRedirectStderrToStdout(gpointer user_data);
+
+ // Callback which runs whenever there is input available on the subprocess
+ // stdout pipe.
+ static void OnStdoutReady(SubprocessRecord* record);
+
+ // The global instance.
+ static Subprocess* subprocess_singleton_;
+
+ // A map from the asynchronous subprocess tag (see Exec) to the subprocess
+ // record structure for all active asynchronous subprocesses.
+ std::map<uint32_t, std::shared_ptr<SubprocessRecord>> subprocess_records_;
+
+ DISALLOW_COPY_AND_ASSIGN(Subprocess);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_SUBPROCESS_H_
diff --git a/subprocess_unittest.cc b/subprocess_unittest.cc
new file mode 100644
index 0000000..dba9e6c
--- /dev/null
+++ b/subprocess_unittest.cc
@@ -0,0 +1,233 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/subprocess.h"
+
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class SubprocessTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ EXPECT_EQ(0, chromeos::MessageLoopRunMaxIterations(&loop_, 1));
+ }
+
+ // TODO(deymo): Replace this with a FakeMessageLoop. Subprocess uses glib to
+ // asynchronously spawn a process, so we need to run a GlibMessageLoop here.
+ chromeos::GlibMessageLoop loop_;
+};
+
+namespace {
+int local_server_port = 0;
+
+void Callback(int return_code, const string& output, void* /* unused */) {
+ EXPECT_EQ(1, return_code);
+ MessageLoop::current()->BreakLoop();
+}
+
+void CallbackEcho(int return_code, const string& output, void* /* unused */) {
+ EXPECT_EQ(0, return_code);
+ EXPECT_NE(string::npos, output.find("this is stdout"));
+ EXPECT_NE(string::npos, output.find("this is stderr"));
+ MessageLoop::current()->BreakLoop();
+}
+
+void CallbackStdoutOnlyEcho(int return_code,
+ const string& output,
+ void* /* unused */) {
+ EXPECT_EQ(0, return_code);
+ EXPECT_NE(string::npos, output.find("on stdout"));
+ EXPECT_EQ(string::npos, output.find("on stderr"));
+ MessageLoop::current()->BreakLoop();
+}
+
+} // namespace
+
+TEST_F(SubprocessTest, SimpleTest) {
+ Subprocess::Get().Exec(vector<string>{"/bin/false"}, Callback, nullptr);
+ loop_.Run();
+}
+
+TEST_F(SubprocessTest, EchoTest) {
+ Subprocess::Get().Exec(
+ vector<string>{
+ "/bin/sh",
+ "-c",
+ "echo this is stdout; echo this is stderr > /dev/stderr"},
+ CallbackEcho,
+ nullptr);
+ loop_.Run();
+}
+
+TEST_F(SubprocessTest, StderrNotIncludedInOutputTest) {
+ Subprocess::Get().ExecFlags(
+ vector<string>{"/bin/sh", "-c", "echo on stdout; echo on stderr >&2"},
+ static_cast<GSpawnFlags>(0),
+ false, // don't redirect stderr
+ CallbackStdoutOnlyEcho,
+ nullptr);
+ loop_.Run();
+}
+
+TEST_F(SubprocessTest, SynchronousEchoTest) {
+ vector<string> cmd = {
+ "/bin/sh",
+ "-c",
+ "echo -n stdout-here; echo -n stderr-there > /dev/stderr"};
+ int rc = -1;
+ string stdout;
+ ASSERT_TRUE(Subprocess::SynchronousExec(cmd, &rc, &stdout));
+ EXPECT_EQ(0, rc);
+ EXPECT_EQ("stdout-herestderr-there", stdout);
+}
+
+TEST_F(SubprocessTest, SynchronousEchoNoOutputTest) {
+ vector<string> cmd = {"/bin/sh", "-c", "echo test"};
+ int rc = -1;
+ ASSERT_TRUE(Subprocess::SynchronousExec(cmd, &rc, nullptr));
+ EXPECT_EQ(0, rc);
+}
+
+namespace {
+void CallbackBad(int return_code, const string& output, void* p) {
+ CHECK(false) << "should never be called.";
+}
+
+// TODO(garnold) this test method uses test_http_server as a representative for
+// interactive processes that can be spawned/terminated at will. This causes us
+// to go through hoops when spawning this process (e.g. obtaining the port
+// number it uses so we can control it with wget). It would have been much
+// preferred to use something else and thus simplify both test_http_server
+// (doesn't have to be able to communicate through a temp file) and the test
+// code below; for example, it sounds like a brain dead sleep loop with proper
+// signal handlers could be used instead.
+void StartAndCancelInRunLoop(bool* spawned) {
+ // Create a temp file for test_http_server to communicate its port number.
+ char temp_file_name[] = "/tmp/subprocess_unittest-test_http_server-XXXXXX";
+ int temp_fd = mkstemp(temp_file_name);
+ CHECK_GE(temp_fd, 0);
+ int temp_flags = fcntl(temp_fd, F_GETFL, 0) | O_NONBLOCK;
+ CHECK_EQ(fcntl(temp_fd, F_SETFL, temp_flags), 0);
+
+ vector<string> cmd;
+ cmd.push_back("./test_http_server");
+ cmd.push_back(temp_file_name);
+ uint32_t tag = Subprocess::Get().Exec(cmd, CallbackBad, nullptr);
+ EXPECT_NE(0, tag);
+ *spawned = true;
+ printf("test http server spawned\n");
+ // Wait for server to be up and running
+ TimeDelta total_wait_time;
+ const TimeDelta kSleepTime = TimeDelta::FromMilliseconds(100);
+ const TimeDelta kMaxWaitTime = TimeDelta::FromSeconds(3);
+ local_server_port = 0;
+ static const char* kServerListeningMsgPrefix = "listening on port ";
+ while (total_wait_time.InMicroseconds() < kMaxWaitTime.InMicroseconds()) {
+ char line[80];
+ int line_len = read(temp_fd, line, sizeof(line) - 1);
+ if (line_len > 0) {
+ line[line_len] = '\0';
+ CHECK_EQ(strstr(line, kServerListeningMsgPrefix), line);
+ const char* listening_port_str =
+ line + strlen(kServerListeningMsgPrefix);
+ char* end_ptr;
+ long raw_port = strtol(listening_port_str, // NOLINT(runtime/int)
+ &end_ptr, 10);
+ CHECK(!*end_ptr || *end_ptr == '\n');
+ local_server_port = static_cast<in_port_t>(raw_port);
+ break;
+ } else if (line_len < 0 && errno != EAGAIN) {
+ LOG(INFO) << "error reading from " << temp_file_name << ": "
+ << strerror(errno);
+ break;
+ }
+ g_usleep(kSleepTime.InMicroseconds());
+ total_wait_time += kSleepTime;
+ }
+ close(temp_fd);
+ remove(temp_file_name);
+ CHECK_GT(local_server_port, 0);
+ LOG(INFO) << "server listening on port " << local_server_port;
+ Subprocess::Get().KillExec(tag);
+}
+
+void ExitWhenDone(bool* spawned) {
+ if (*spawned && !Subprocess::Get().SubprocessInFlight()) {
+ // tear down the sub process
+ printf("tear down time\n");
+ int status = test_utils::System(
+ base::StringPrintf("wget -O /dev/null http://127.0.0.1:%d/quitquitquit",
+ local_server_port));
+ EXPECT_NE(-1, status) << "system() failed";
+ EXPECT_TRUE(WIFEXITED(status))
+ << "command failed to run or died abnormally";
+ MessageLoop::current()->BreakLoop();
+ } else {
+ // Re-run this callback again in 10 ms.
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ExitWhenDone, spawned),
+ TimeDelta::FromMilliseconds(10));
+ }
+}
+
+} // namespace
+
+TEST_F(SubprocessTest, CancelTest) {
+ bool spawned = false;
+ loop_.PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&StartAndCancelInRunLoop, &spawned),
+ TimeDelta::FromMilliseconds(100));
+ loop_.PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ExitWhenDone, &spawned),
+ TimeDelta::FromMilliseconds(10));
+ loop_.Run();
+ // This test would leak a callback that runs when the child process exits
+ // unless we wait for it to run.
+ chromeos::MessageLoopRunUntil(
+ &loop_,
+ TimeDelta::FromSeconds(10),
+ base::Bind([] {
+ return Subprocess::Get().subprocess_records_.empty();
+ }));
+}
+
+} // namespace chromeos_update_engine
diff --git a/system_state.h b/system_state.h
new file mode 100644
index 0000000..21aeabd
--- /dev/null
+++ b/system_state.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_SYSTEM_STATE_H_
+
+class MetricsLibraryInterface;
+
+namespace chromeos_update_manager {
+
+class UpdateManager;
+
+} // namespace chromeos_update_manager
+
+namespace policy {
+
+class DevicePolicy;
+
+} // namespace policy
+
+namespace chromeos_update_engine {
+
+// SystemState is the root class within the update engine. So we should avoid
+// any circular references in header file inclusion. Hence forward-declaring
+// the required classes.
+class ClockInterface;
+class ConnectionManager;
+class GpioHandler;
+class HardwareInterface;
+class OmahaRequestParams;
+class P2PManager;
+class PayloadStateInterface;
+class PrefsInterface;
+class UpdateAttempter;
+
+// An interface to global system context, including platform resources,
+// the current state of the system, high-level objects whose lifetime is same
+// as main, system interfaces, etc.
+// Carved out separately so it can be mocked for unit tests.
+// Currently it has only one method, but we should start migrating other
+// methods to use this as and when needed to unit test them.
+// TODO(jaysri): Consider renaming this to something like GlobalContext.
+class SystemState {
+ public:
+ // Destructs this object.
+ virtual ~SystemState() {}
+
+ // Sets or gets the latest device policy.
+ virtual void set_device_policy(const policy::DevicePolicy* device_policy) = 0;
+ virtual const policy::DevicePolicy* device_policy() = 0;
+
+ // Gets the interface object for the clock.
+ virtual ClockInterface* clock() = 0;
+
+ // Gets the connection manager object.
+ virtual ConnectionManager* connection_manager() = 0;
+
+ // Gets the hardware interface object.
+ virtual HardwareInterface* hardware() = 0;
+
+ // Gets the Metrics Library interface for reporting UMA stats.
+ virtual MetricsLibraryInterface* metrics_lib() = 0;
+
+ // Gets the interface object for persisted store.
+ virtual PrefsInterface* prefs() = 0;
+
+ // Gets the interface object for the persisted store that persists across
+ // powerwashes. Please note that this should be used very seldomly and must
+ // be forwards and backwards compatible as powerwash is used to go back and
+ // forth in system versions.
+ virtual PrefsInterface* powerwash_safe_prefs() = 0;
+
+ // Gets the interface for the payload state object.
+ virtual PayloadStateInterface* payload_state() = 0;
+
+ // Returns a pointer to the update attempter object.
+ virtual UpdateAttempter* update_attempter() = 0;
+
+ // Returns a pointer to the object that stores the parameters that are
+ // common to all Omaha requests.
+ virtual OmahaRequestParams* request_params() = 0;
+
+ // Returns a pointer to the P2PManager singleton.
+ virtual P2PManager* p2p_manager() = 0;
+
+ // Returns a pointer to the UpdateManager singleton.
+ virtual chromeos_update_manager::UpdateManager* update_manager() = 0;
+
+ // If true, this is the first instance of the update engine since the system
+ // restarted. Important for tracking whether you are running instance of the
+ // update engine on first boot or due to a crash/restart.
+ virtual bool system_rebooted() = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_SYSTEM_STATE_H_
diff --git a/terminator.cc b/terminator.cc
new file mode 100644
index 0000000..6d00ddb
--- /dev/null
+++ b/terminator.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/terminator.h"
+
+#include <cstdlib>
+
+namespace chromeos_update_engine {
+
+volatile sig_atomic_t Terminator::exit_status_ = 1; // default exit status
+volatile sig_atomic_t Terminator::exit_blocked_ = 0;
+volatile sig_atomic_t Terminator::exit_requested_ = 0;
+
+void Terminator::Init() {
+ exit_blocked_ = 0;
+ exit_requested_ = 0;
+ signal(SIGTERM, HandleSignal);
+}
+
+void Terminator::Init(int exit_status) {
+ exit_status_ = exit_status;
+ Init();
+}
+
+void Terminator::Exit() {
+ exit(exit_status_);
+}
+
+void Terminator::HandleSignal(int signum) {
+ if (exit_blocked_ == 0) {
+ Exit();
+ }
+ exit_requested_ = 1;
+}
+
+ScopedTerminatorExitUnblocker::~ScopedTerminatorExitUnblocker() {
+ Terminator::set_exit_blocked(false);
+ if (Terminator::exit_requested()) {
+ Terminator::Exit();
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/terminator.h b/terminator.h
new file mode 100644
index 0000000..f8a261e
--- /dev/null
+++ b/terminator.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_TERMINATOR_H_
+#define UPDATE_ENGINE_TERMINATOR_H_
+
+#include <signal.h>
+
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+namespace chromeos_update_engine {
+
+// A class allowing graceful delayed exit.
+class Terminator {
+ public:
+ // Initializes the terminator and sets up signal handlers.
+ static void Init();
+ static void Init(int exit_status);
+
+ // Terminates the current process.
+ static void Exit();
+
+ // Set to true if the terminator should block termination requests in an
+ // attempt to block exiting.
+ static void set_exit_blocked(bool block) { exit_blocked_ = block ? 1 : 0; }
+ static bool exit_blocked() { return exit_blocked_ != 0; }
+
+ // Returns true if the system is trying to terminate the process, false
+ // otherwise. Returns true only if exit was blocked when the termination
+ // request arrived.
+ static bool exit_requested() { return exit_requested_ != 0; }
+
+ private:
+ FRIEND_TEST(TerminatorTest, HandleSignalTest);
+ FRIEND_TEST(TerminatorDeathTest, ScopedTerminatorExitUnblockerExitTest);
+
+ // The signal handler.
+ static void HandleSignal(int signum);
+
+ static volatile sig_atomic_t exit_status_;
+ static volatile sig_atomic_t exit_blocked_;
+ static volatile sig_atomic_t exit_requested_;
+};
+
+class ScopedTerminatorExitUnblocker {
+ public:
+ ~ScopedTerminatorExitUnblocker();
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_TERMINATOR_H_
diff --git a/terminator_unittest.cc b/terminator_unittest.cc
new file mode 100644
index 0000000..5a6f4e2
--- /dev/null
+++ b/terminator_unittest.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/terminator.h"
+
+#include <gtest/gtest.h>
+#include <gtest/gtest-spi.h>
+
+using testing::ExitedWithCode;
+
+namespace chromeos_update_engine {
+
+class TerminatorTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ Terminator::Init();
+ ASSERT_FALSE(Terminator::exit_blocked());
+ ASSERT_FALSE(Terminator::exit_requested());
+ }
+ void TearDown() override {
+ // Makes sure subsequent non-Terminator tests don't get accidentally
+ // terminated.
+ Terminator::Init();
+ }
+};
+
+typedef TerminatorTest TerminatorDeathTest;
+
+namespace {
+void UnblockExitThroughUnblocker() {
+ ScopedTerminatorExitUnblocker unblocker = ScopedTerminatorExitUnblocker();
+}
+
+void RaiseSIGTERM() {
+ ASSERT_EXIT(raise(SIGTERM), ExitedWithCode(2), "");
+}
+} // namespace
+
+TEST_F(TerminatorTest, HandleSignalTest) {
+ Terminator::set_exit_blocked(true);
+ Terminator::HandleSignal(SIGTERM);
+ ASSERT_TRUE(Terminator::exit_requested());
+}
+
+TEST_F(TerminatorTest, ScopedTerminatorExitUnblockerTest) {
+ Terminator::set_exit_blocked(true);
+ ASSERT_TRUE(Terminator::exit_blocked());
+ ASSERT_FALSE(Terminator::exit_requested());
+ UnblockExitThroughUnblocker();
+ ASSERT_FALSE(Terminator::exit_blocked());
+ ASSERT_FALSE(Terminator::exit_requested());
+}
+
+TEST_F(TerminatorDeathTest, ExitTest) {
+ ASSERT_EXIT(Terminator::Exit(), ExitedWithCode(2), "");
+ Terminator::set_exit_blocked(true);
+ ASSERT_EXIT(Terminator::Exit(), ExitedWithCode(2), "");
+}
+
+TEST_F(TerminatorDeathTest, RaiseSignalTest) {
+ RaiseSIGTERM();
+ Terminator::set_exit_blocked(true);
+ EXPECT_FATAL_FAILURE(RaiseSIGTERM(), "");
+}
+
+TEST_F(TerminatorDeathTest, ScopedTerminatorExitUnblockerExitTest) {
+ Terminator::set_exit_blocked(true);
+ Terminator::exit_requested_ = 1;
+ ASSERT_EXIT(UnblockExitThroughUnblocker(), ExitedWithCode(2), "");
+}
+
+} // namespace chromeos_update_engine
diff --git a/test_http_server.cc b/test_http_server.cc
new file mode 100644
index 0000000..988599e
--- /dev/null
+++ b/test_http_server.cc
@@ -0,0 +1,613 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file implements a simple HTTP server. It can exhibit odd behavior
+// that's useful for testing. For example, it's useful to test that
+// the updater can continue a connection if it's dropped, or that it
+// handles very slow data transfers.
+
+// To use this, simply make an HTTP connection to localhost:port and
+// GET a url.
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/http_common.h"
+
+
+// HTTP end-of-line delimiter; sorry, this needs to be a macro.
+#define EOL "\r\n"
+
+using std::string;
+using std::vector;
+
+
+namespace chromeos_update_engine {
+
+static const char* kListeningMsgPrefix = "listening on port ";
+
+enum {
+ RC_OK = 0,
+ RC_BAD_ARGS,
+ RC_ERR_READ,
+ RC_ERR_SETSOCKOPT,
+ RC_ERR_BIND,
+ RC_ERR_LISTEN,
+ RC_ERR_GETSOCKNAME,
+ RC_ERR_REPORT,
+};
+
+struct HttpRequest {
+ HttpRequest()
+ : start_offset(0), end_offset(0), return_code(kHttpResponseOk) {}
+ string host;
+ string url;
+ off_t start_offset;
+ off_t end_offset; // non-inclusive, zero indicates unspecified.
+ HttpResponseCode return_code;
+};
+
+bool ParseRequest(int fd, HttpRequest* request) {
+ string headers;
+ do {
+ char buf[1024];
+ ssize_t r = read(fd, buf, sizeof(buf));
+ if (r < 0) {
+ perror("read");
+ exit(RC_ERR_READ);
+ }
+ headers.append(buf, r);
+ } while (!base::EndsWith(headers, EOL EOL, true));
+
+ LOG(INFO) << "got headers:\n--8<------8<------8<------8<----\n"
+ << headers
+ << "\n--8<------8<------8<------8<----";
+
+ // Break header into lines.
+ vector<string> lines;
+ base::SplitStringUsingSubstr(
+ headers.substr(0, headers.length() - strlen(EOL EOL)), EOL, &lines);
+
+ // Decode URL line.
+ vector<string> terms;
+ base::SplitStringAlongWhitespace(lines[0], &terms);
+ CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(3));
+ CHECK_EQ(terms[0], "GET");
+ request->url = terms[1];
+ LOG(INFO) << "URL: " << request->url;
+
+ // Decode remaining lines.
+ size_t i;
+ for (i = 1; i < lines.size(); i++) {
+ vector<string> terms;
+ base::SplitStringAlongWhitespace(lines[i], &terms);
+
+ if (terms[0] == "Range:") {
+ CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2));
+ string &range = terms[1];
+ LOG(INFO) << "range attribute: " << range;
+ CHECK(base::StartsWithASCII(range, "bytes=", true) &&
+ range.find('-') != string::npos);
+ request->start_offset = atoll(range.c_str() + strlen("bytes="));
+ // Decode end offset and increment it by one (so it is non-inclusive).
+ if (range.find('-') < range.length() - 1)
+ request->end_offset = atoll(range.c_str() + range.find('-') + 1) + 1;
+ request->return_code = kHttpResponsePartialContent;
+ string tmp_str = base::StringPrintf("decoded range offsets: "
+ "start=%jd end=",
+ (intmax_t)request->start_offset);
+ if (request->end_offset > 0)
+ base::StringAppendF(&tmp_str, "%jd (non-inclusive)",
+ (intmax_t)request->end_offset);
+ else
+ base::StringAppendF(&tmp_str, "unspecified");
+ LOG(INFO) << tmp_str;
+ } else if (terms[0] == "Host:") {
+ CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2));
+ request->host = terms[1];
+ LOG(INFO) << "host attribute: " << request->host;
+ } else {
+ LOG(WARNING) << "ignoring HTTP attribute: `" << lines[i] << "'";
+ }
+ }
+
+ return true;
+}
+
+string Itoa(off_t num) {
+ char buf[100] = {0};
+ snprintf(buf, sizeof(buf), "%" PRIi64, num);
+ return buf;
+}
+
+// Writes a string into a file. Returns total number of bytes written or -1 if a
+// write error occurred.
+ssize_t WriteString(int fd, const string& str) {
+ const size_t total_size = str.size();
+ size_t remaining_size = total_size;
+ char const *data = str.data();
+
+ while (remaining_size) {
+ ssize_t written = write(fd, data, remaining_size);
+ if (written < 0) {
+ perror("write");
+ LOG(INFO) << "write failed";
+ return -1;
+ }
+ data += written;
+ remaining_size -= written;
+ }
+
+ return total_size;
+}
+
+// Writes the headers of an HTTP response into a file.
+ssize_t WriteHeaders(int fd, const off_t start_offset, const off_t end_offset,
+ HttpResponseCode return_code) {
+ ssize_t written = 0, ret;
+
+ ret = WriteString(fd,
+ string("HTTP/1.1 ") + Itoa(return_code) + " " +
+ GetHttpResponseDescription(return_code) +
+ EOL
+ "Content-Type: application/octet-stream" EOL);
+ if (ret < 0)
+ return -1;
+ written += ret;
+
+ // Compute content legnth.
+ const off_t content_length = end_offset - start_offset;;
+
+ // A start offset that equals the end offset indicates that the response
+ // should contain the full range of bytes in the requested resource.
+ if (start_offset || start_offset == end_offset) {
+ ret = WriteString(fd,
+ string("Accept-Ranges: bytes" EOL
+ "Content-Range: bytes ") +
+ Itoa(start_offset == end_offset ? 0 : start_offset) +
+ "-" + Itoa(end_offset - 1) + "/" + Itoa(end_offset) +
+ EOL);
+ if (ret < 0)
+ return -1;
+ written += ret;
+ }
+
+ ret = WriteString(fd, string("Content-Length: ") + Itoa(content_length) +
+ EOL EOL);
+ if (ret < 0)
+ return -1;
+ written += ret;
+
+ return written;
+}
+
+// Writes a predetermined payload of lines of ascending bytes to a file. The
+// first byte of output is appropriately offset with respect to the request line
+// length. Returns the number of successfully written bytes.
+size_t WritePayload(int fd, const off_t start_offset, const off_t end_offset,
+ const char first_byte, const size_t line_len) {
+ CHECK_LE(start_offset, end_offset);
+ CHECK_GT(line_len, static_cast<size_t>(0));
+
+ LOG(INFO) << "writing payload: " << line_len << "-byte lines starting with `"
+ << first_byte << "', offset range " << start_offset << " -> "
+ << end_offset;
+
+ // Populate line of ascending characters.
+ string line;
+ line.reserve(line_len);
+ char byte = first_byte;
+ size_t i;
+ for (i = 0; i < line_len; i++)
+ line += byte++;
+
+ const size_t total_len = end_offset - start_offset;
+ size_t remaining_len = total_len;
+ bool success = true;
+
+ // If start offset is not aligned with line boundary, output partial line up
+ // to the first line boundary.
+ size_t start_modulo = start_offset % line_len;
+ if (start_modulo) {
+ string partial = line.substr(start_modulo, remaining_len);
+ ssize_t ret = WriteString(fd, partial);
+ if ((success = (ret >= 0 && (size_t) ret == partial.length())))
+ remaining_len -= partial.length();
+ }
+
+ // Output full lines up to the maximal line boundary below the end offset.
+ while (success && remaining_len >= line_len) {
+ ssize_t ret = WriteString(fd, line);
+ if ((success = (ret >= 0 && (size_t) ret == line_len)))
+ remaining_len -= line_len;
+ }
+
+ // Output a partial line up to the end offset.
+ if (success && remaining_len) {
+ string partial = line.substr(0, remaining_len);
+ ssize_t ret = WriteString(fd, partial);
+ if ((success = (ret >= 0 && (size_t) ret == partial.length())))
+ remaining_len -= partial.length();
+ }
+
+ return (total_len - remaining_len);
+}
+
+// Write default payload lines of the form 'abcdefghij'.
+inline size_t WritePayload(int fd, const off_t start_offset,
+ const off_t end_offset) {
+ return WritePayload(fd, start_offset, end_offset, 'a', 10);
+}
+
+// Send an empty response, then kill the server.
+void HandleQuit(int fd) {
+ WriteHeaders(fd, 0, 0, kHttpResponseOk);
+ LOG(INFO) << "pid(" << getpid() << "): HTTP server exiting ...";
+ exit(RC_OK);
+}
+
+
+// Generates an HTTP response with payload corresponding to requested offsets
+// and length. Optionally, truncate the payload at a given length and add a
+// pause midway through the transfer. Returns the total number of bytes
+// delivered or -1 for error.
+ssize_t HandleGet(int fd, const HttpRequest& request, const size_t total_length,
+ const size_t truncate_length, const int sleep_every,
+ const int sleep_secs) {
+ ssize_t ret;
+ size_t written = 0;
+
+ // Obtain start offset, make sure it is within total payload length.
+ const size_t start_offset = request.start_offset;
+ if (start_offset >= total_length) {
+ LOG(WARNING) << "start offset (" << start_offset
+ << ") exceeds total length (" << total_length
+ << "), generating error response ("
+ << kHttpResponseReqRangeNotSat << ")";
+ return WriteHeaders(fd, total_length, total_length,
+ kHttpResponseReqRangeNotSat);
+ }
+
+ // Obtain end offset, adjust to fit in total payload length and ensure it does
+ // not preceded the start offset.
+ size_t end_offset = (request.end_offset > 0 ?
+ request.end_offset : total_length);
+ if (end_offset < start_offset) {
+ LOG(WARNING) << "end offset (" << end_offset << ") precedes start offset ("
+ << start_offset << "), generating error response";
+ return WriteHeaders(fd, 0, 0, kHttpResponseBadRequest);
+ }
+ if (end_offset > total_length) {
+ LOG(INFO) << "requested end offset (" << end_offset
+ << ") exceeds total length (" << total_length << "), adjusting";
+ end_offset = total_length;
+ }
+
+ // Generate headers
+ LOG(INFO) << "generating response header: range=" << start_offset << "-"
+ << (end_offset - 1) << "/" << (end_offset - start_offset)
+ << ", return code=" << request.return_code;
+ if ((ret = WriteHeaders(fd, start_offset, end_offset,
+ request.return_code)) < 0)
+ return -1;
+ LOG(INFO) << ret << " header bytes written";
+ written += ret;
+
+ // Compute payload length, truncate as necessary.
+ size_t payload_length = end_offset - start_offset;
+ if (truncate_length > 0 && truncate_length < payload_length) {
+ LOG(INFO) << "truncating request payload length (" << payload_length
+ << ") at " << truncate_length;
+ payload_length = truncate_length;
+ end_offset = start_offset + payload_length;
+ }
+
+ LOG(INFO) << "generating response payload: range=" << start_offset << "-"
+ << (end_offset - 1) << "/" << (end_offset - start_offset);
+
+ // Decide about optional midway delay.
+ if (truncate_length > 0 && sleep_every > 0 && sleep_secs >= 0 &&
+ start_offset % (truncate_length * sleep_every) == 0) {
+ const off_t midway_offset = start_offset + payload_length / 2;
+
+ if ((ret = WritePayload(fd, start_offset, midway_offset)) < 0)
+ return -1;
+ LOG(INFO) << ret << " payload bytes written (first chunk)";
+ written += ret;
+
+ LOG(INFO) << "sleeping for " << sleep_secs << " seconds...";
+ sleep(sleep_secs);
+
+ if ((ret = WritePayload(fd, midway_offset, end_offset)) < 0)
+ return -1;
+ LOG(INFO) << ret << " payload bytes written (second chunk)";
+ written += ret;
+ } else {
+ if ((ret = WritePayload(fd, start_offset, end_offset)) < 0)
+ return -1;
+ LOG(INFO) << ret << " payload bytes written";
+ written += ret;
+ }
+
+ LOG(INFO) << "response generation complete, " << written
+ << " total bytes written";
+ return written;
+}
+
+ssize_t HandleGet(int fd, const HttpRequest& request,
+ const size_t total_length) {
+ return HandleGet(fd, request, total_length, 0, 0, 0);
+}
+
+// Handles /redirect/<code>/<url> requests by returning the specified
+// redirect <code> with a location pointing to /<url>.
+void HandleRedirect(int fd, const HttpRequest& request) {
+ LOG(INFO) << "Redirecting...";
+ string url = request.url;
+ CHECK_EQ(static_cast<size_t>(0), url.find("/redirect/"));
+ url.erase(0, strlen("/redirect/"));
+ string::size_type url_start = url.find('/');
+ CHECK_NE(url_start, string::npos);
+ HttpResponseCode code = StringToHttpResponseCode(url.c_str());
+ url.erase(0, url_start);
+ url = "http://" + request.host + url;
+ const char *status = GetHttpResponseDescription(code);
+ if (!status)
+ CHECK(false) << "Unrecognized redirection code: " << code;
+ LOG(INFO) << "Code: " << code << " " << status;
+ LOG(INFO) << "New URL: " << url;
+
+ ssize_t ret;
+ if ((ret = WriteString(fd, "HTTP/1.1 " + Itoa(code) + " " +
+ status + EOL)) < 0)
+ return;
+ WriteString(fd, "Location: " + url + EOL);
+}
+
+// Generate a page not found error response with actual text payload. Return
+// number of bytes written or -1 for error.
+ssize_t HandleError(int fd, const HttpRequest& request) {
+ LOG(INFO) << "Generating error HTTP response";
+
+ ssize_t ret;
+ size_t written = 0;
+
+ const string data("This is an error page.");
+
+ if ((ret = WriteHeaders(fd, 0, data.size(), kHttpResponseNotFound)) < 0)
+ return -1;
+ written += ret;
+
+ if ((ret = WriteString(fd, data)) < 0)
+ return -1;
+ written += ret;
+
+ return written;
+}
+
+// Generate an error response if the requested offset is nonzero, up to a given
+// maximal number of successive failures. The error generated is an "Internal
+// Server Error" (500).
+ssize_t HandleErrorIfOffset(int fd, const HttpRequest& request,
+ size_t end_offset, int max_fails) {
+ static int num_fails = 0;
+
+ if (request.start_offset > 0 && num_fails < max_fails) {
+ LOG(INFO) << "Generating error HTTP response";
+
+ ssize_t ret;
+ size_t written = 0;
+
+ const string data("This is an error page.");
+
+ if ((ret = WriteHeaders(fd, 0, data.size(),
+ kHttpResponseInternalServerError)) < 0)
+ return -1;
+ written += ret;
+
+ if ((ret = WriteString(fd, data)) < 0)
+ return -1;
+ written += ret;
+
+ num_fails++;
+ return written;
+ } else {
+ num_fails = 0;
+ return HandleGet(fd, request, end_offset);
+ }
+}
+
+void HandleDefault(int fd, const HttpRequest& request) {
+ const off_t start_offset = request.start_offset;
+ const string data("unhandled path");
+ const size_t size = data.size();
+ ssize_t ret;
+
+ if ((ret = WriteHeaders(fd, start_offset, size, request.return_code)) < 0)
+ return;
+ WriteString(fd, (start_offset < static_cast<off_t>(size) ?
+ data.substr(start_offset) : ""));
+}
+
+
+// Break a URL into terms delimited by slashes.
+class UrlTerms {
+ public:
+ UrlTerms(const string &url, size_t num_terms) {
+ // URL must be non-empty and start with a slash.
+ CHECK_GT(url.size(), static_cast<size_t>(0));
+ CHECK_EQ(url[0], '/');
+
+ // Split it into terms delimited by slashes, omitting the preceeding slash.
+ base::SplitStringDontTrim(url.substr(1), '/', &terms);
+
+ // Ensure expected length.
+ CHECK_EQ(terms.size(), num_terms);
+ }
+
+ inline string Get(const off_t index) const {
+ return terms[index];
+ }
+ inline const char *GetCStr(const off_t index) const {
+ return Get(index).c_str();
+ }
+ inline int GetInt(const off_t index) const {
+ return atoi(GetCStr(index));
+ }
+ inline size_t GetSizeT(const off_t index) const {
+ return static_cast<size_t>(atol(GetCStr(index)));
+ }
+
+ private:
+ vector<string> terms;
+};
+
+void HandleConnection(int fd) {
+ HttpRequest request;
+ ParseRequest(fd, &request);
+
+ string &url = request.url;
+ LOG(INFO) << "pid(" << getpid() << "): handling url " << url;
+ if (url == "/quitquitquit") {
+ HandleQuit(fd);
+ } else if (base::StartsWithASCII(url, "/download/", true)) {
+ const UrlTerms terms(url, 2);
+ HandleGet(fd, request, terms.GetSizeT(1));
+ } else if (base::StartsWithASCII(url, "/flaky/", true)) {
+ const UrlTerms terms(url, 5);
+ HandleGet(fd, request, terms.GetSizeT(1), terms.GetSizeT(2),
+ terms.GetInt(3), terms.GetInt(4));
+ } else if (url.find("/redirect/") == 0) {
+ HandleRedirect(fd, request);
+ } else if (url == "/error") {
+ HandleError(fd, request);
+ } else if (base::StartsWithASCII(url, "/error-if-offset/", true)) {
+ const UrlTerms terms(url, 3);
+ HandleErrorIfOffset(fd, request, terms.GetSizeT(1), terms.GetInt(2));
+ } else {
+ HandleDefault(fd, request);
+ }
+
+ close(fd);
+}
+
+} // namespace chromeos_update_engine
+
+using namespace chromeos_update_engine; // NOLINT(build/namespaces)
+
+
+void usage(const char *prog_arg) {
+ fprintf(
+ stderr,
+ "Usage: %s [ FILE ]\n"
+ "Once accepting connections, the following is written to FILE (or "
+ "stdout):\n"
+ "\"%sN\" (where N is an integer port number)\n",
+ basename(prog_arg), kListeningMsgPrefix);
+}
+
+int main(int argc, char** argv) {
+ // Check invocation.
+ if (argc > 2)
+ errx(RC_BAD_ARGS, "unexpected number of arguments (use -h for usage)");
+
+ // Parse (optional) argument.
+ int report_fd = STDOUT_FILENO;
+ if (argc == 2) {
+ if (!strcmp(argv[1], "-h")) {
+ usage(argv[0]);
+ exit(RC_OK);
+ }
+
+ report_fd = open(argv[1], O_WRONLY | O_CREAT, 00644);
+ }
+
+ // Ignore SIGPIPE on write() to sockets.
+ signal(SIGPIPE, SIG_IGN);
+
+ int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (listen_fd < 0)
+ LOG(FATAL) << "socket() failed";
+
+ struct sockaddr_in server_addr = sockaddr_in();
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_addr.s_addr = INADDR_ANY;
+ server_addr.sin_port = 0;
+
+ {
+ // Get rid of "Address in use" error
+ int tr = 1;
+ if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tr,
+ sizeof(int)) == -1) {
+ perror("setsockopt");
+ exit(RC_ERR_SETSOCKOPT);
+ }
+ }
+
+ // Bind the socket and set for listening.
+ if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr),
+ sizeof(server_addr)) < 0) {
+ perror("bind");
+ exit(RC_ERR_BIND);
+ }
+ if (listen(listen_fd, 5) < 0) {
+ perror("listen");
+ exit(RC_ERR_LISTEN);
+ }
+
+ // Check the actual port.
+ struct sockaddr_in bound_addr = sockaddr_in();
+ socklen_t bound_addr_len = sizeof(bound_addr);
+ if (getsockname(listen_fd, reinterpret_cast<struct sockaddr*>(&bound_addr),
+ &bound_addr_len) < 0) {
+ perror("getsockname");
+ exit(RC_ERR_GETSOCKNAME);
+ }
+ in_port_t port = ntohs(bound_addr.sin_port);
+
+ // Output the listening port, indicating that the server is processing
+ // requests. IMPORTANT! (a) the format of this message is as expected by some
+ // unit tests, avoid unilateral changes; (b) it is necessary to flush/sync the
+ // file to prevent the spawning process from waiting indefinitely for this
+ // message.
+ string listening_msg = base::StringPrintf("%s%hu", kListeningMsgPrefix, port);
+ LOG(INFO) << listening_msg;
+ CHECK_EQ(write(report_fd, listening_msg.c_str(), listening_msg.length()),
+ static_cast<int>(listening_msg.length()));
+ CHECK_EQ(write(report_fd, "\n", 1), 1);
+ if (report_fd == STDOUT_FILENO)
+ fsync(report_fd);
+ else
+ close(report_fd);
+
+ while (1) {
+ LOG(INFO) << "pid(" << getpid() << "): waiting to accept new connection";
+ int client_fd = accept(listen_fd, nullptr, nullptr);
+ LOG(INFO) << "got past accept";
+ if (client_fd < 0)
+ LOG(FATAL) << "ERROR on accept";
+ HandleConnection(client_fd);
+ }
+ return 0;
+}
diff --git a/test_utils.cc b/test_utils.cc
new file mode 100644
index 0000000..0c02279
--- /dev/null
+++ b/test_utils.cc
@@ -0,0 +1,325 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/test_utils.h"
+
+#include <attr/xattr.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/file_writer.h"
+#include "update_engine/utils.h"
+
+using base::StringPrintf;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+void PrintTo(const Extent& extent, ::std::ostream* os) {
+ *os << "(" << extent.start_block() << ", " << extent.num_blocks() << ")";
+}
+
+namespace test_utils {
+
+const char* const kMountPathTemplate = "UpdateEngineTests_mnt-XXXXXX";
+
+const uint8_t kRandomString[] = {
+ 0xf2, 0xb7, 0x55, 0x92, 0xea, 0xa6, 0xc9, 0x57,
+ 0xe0, 0xf8, 0xeb, 0x34, 0x93, 0xd9, 0xc4, 0x8f,
+ 0xcb, 0x20, 0xfa, 0x37, 0x4b, 0x40, 0xcf, 0xdc,
+ 0xa5, 0x08, 0x70, 0x89, 0x79, 0x35, 0xe2, 0x3d,
+ 0x56, 0xa4, 0x75, 0x73, 0xa3, 0x6d, 0xd1, 0xd5,
+ 0x26, 0xbb, 0x9c, 0x60, 0xbd, 0x2f, 0x5a, 0xfa,
+ 0xb7, 0xd4, 0x3a, 0x50, 0xa7, 0x6b, 0x3e, 0xfd,
+ 0x61, 0x2b, 0x3a, 0x31, 0x30, 0x13, 0x33, 0x53,
+ 0xdb, 0xd0, 0x32, 0x71, 0x5c, 0x39, 0xed, 0xda,
+ 0xb4, 0x84, 0xca, 0xbc, 0xbd, 0x78, 0x1c, 0x0c,
+ 0xd8, 0x0b, 0x41, 0xe8, 0xe1, 0xe0, 0x41, 0xad,
+ 0x03, 0x12, 0xd3, 0x3d, 0xb8, 0x75, 0x9b, 0xe6,
+ 0xd9, 0x01, 0xd0, 0x87, 0xf4, 0x36, 0xfa, 0xa7,
+ 0x0a, 0xfa, 0xc5, 0x87, 0x65, 0xab, 0x9a, 0x7b,
+ 0xeb, 0x58, 0x23, 0xf0, 0xa8, 0x0a, 0xf2, 0x33,
+ 0x3a, 0xe2, 0xe3, 0x35, 0x74, 0x95, 0xdd, 0x3c,
+ 0x59, 0x5a, 0xd9, 0x52, 0x3a, 0x3c, 0xac, 0xe5,
+ 0x15, 0x87, 0x6d, 0x82, 0xbc, 0xf8, 0x7d, 0xbe,
+ 0xca, 0xd3, 0x2c, 0xd6, 0xec, 0x38, 0xeb, 0xe4,
+ 0x53, 0xb0, 0x4c, 0x3f, 0x39, 0x29, 0xf7, 0xa4,
+ 0x73, 0xa8, 0xcb, 0x32, 0x50, 0x05, 0x8c, 0x1c,
+ 0x1c, 0xca, 0xc9, 0x76, 0x0b, 0x8f, 0x6b, 0x57,
+ 0x1f, 0x24, 0x2b, 0xba, 0x82, 0xba, 0xed, 0x58,
+ 0xd8, 0xbf, 0xec, 0x06, 0x64, 0x52, 0x6a, 0x3f,
+ 0xe4, 0xad, 0xce, 0x84, 0xb4, 0x27, 0x55, 0x14,
+ 0xe3, 0x75, 0x59, 0x73, 0x71, 0x51, 0xea, 0xe8,
+ 0xcc, 0xda, 0x4f, 0x09, 0xaf, 0xa4, 0xbc, 0x0e,
+ 0xa6, 0x1f, 0xe2, 0x3a, 0xf8, 0x96, 0x7d, 0x30,
+ 0x23, 0xc5, 0x12, 0xb5, 0xd8, 0x73, 0x6b, 0x71,
+ 0xab, 0xf1, 0xd7, 0x43, 0x58, 0xa7, 0xc9, 0xf0,
+ 0xe4, 0x85, 0x1c, 0xd6, 0x92, 0x50, 0x2c, 0x98,
+ 0x36, 0xfe, 0x87, 0xaf, 0x43, 0x8f, 0x8f, 0xf5,
+ 0x88, 0x48, 0x18, 0x42, 0xcf, 0x42, 0xc1, 0xa8,
+ 0xe8, 0x05, 0x08, 0xa1, 0x45, 0x70, 0x5b, 0x8c,
+ 0x39, 0x28, 0xab, 0xe9, 0x6b, 0x51, 0xd2, 0xcb,
+ 0x30, 0x04, 0xea, 0x7d, 0x2f, 0x6e, 0x6c, 0x3b,
+ 0x5f, 0x82, 0xd9, 0x5b, 0x89, 0x37, 0x65, 0x65,
+ 0xbe, 0x9f, 0xa3, 0x5d,
+};
+
+bool IsXAttrSupported(const base::FilePath& dir_path) {
+ char *path = strdup(dir_path.Append("xattr_test_XXXXXX").value().c_str());
+
+ int fd = mkstemp(path);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error creating temporary file in " << dir_path.value();
+ free(path);
+ return false;
+ }
+
+ if (unlink(path) != 0) {
+ PLOG(ERROR) << "Error unlinking temporary file " << path;
+ close(fd);
+ free(path);
+ return false;
+ }
+
+ int xattr_res = fsetxattr(fd, "user.xattr-test", "value", strlen("value"), 0);
+ if (xattr_res != 0) {
+ if (errno == ENOTSUP) {
+ // Leave it to call-sites to warn about non-support.
+ } else {
+ PLOG(ERROR) << "Error setting xattr on " << path;
+ }
+ }
+ close(fd);
+ free(path);
+ return xattr_res == 0;
+}
+
+bool WriteFileVector(const string& path, const chromeos::Blob& data) {
+ return utils::WriteFile(path.c_str(), data.data(), data.size());
+}
+
+bool WriteFileString(const string& path, const string& data) {
+ return utils::WriteFile(path.c_str(), data.data(), data.size());
+}
+
+// Binds provided |filename| to an unused loopback device, whose name is written
+// to the string pointed to by |lo_dev_name_p|. Returns true on success, false
+// otherwise (along with corresponding test failures), in which case the content
+// of |lo_dev_name_p| is unknown.
+bool BindToUnusedLoopDevice(const string& filename, string* lo_dev_name_p) {
+ CHECK(lo_dev_name_p);
+
+ // Bind to an unused loopback device, sanity check the device name.
+ lo_dev_name_p->clear();
+ if (!(utils::ReadPipe("losetup --show -f " + filename, lo_dev_name_p) &&
+ base::StartsWithASCII(*lo_dev_name_p, "/dev/loop", true))) {
+ ADD_FAILURE();
+ return false;
+ }
+
+ // Strip anything from the first newline char.
+ size_t newline_pos = lo_dev_name_p->find('\n');
+ if (newline_pos != string::npos)
+ lo_dev_name_p->erase(newline_pos);
+
+ return true;
+}
+
+bool ExpectVectorsEq(const chromeos::Blob& expected,
+ const chromeos::Blob& actual) {
+ EXPECT_EQ(expected.size(), actual.size());
+ if (expected.size() != actual.size())
+ return false;
+ bool is_all_eq = true;
+ for (unsigned int i = 0; i < expected.size(); i++) {
+ EXPECT_EQ(expected[i], actual[i]) << "offset: " << i;
+ is_all_eq = is_all_eq && (expected[i] == actual[i]);
+ }
+ return is_all_eq;
+}
+
+void FillWithData(chromeos::Blob* buffer) {
+ size_t input_counter = 0;
+ for (uint8_t& b : *buffer) {
+ b = kRandomString[input_counter];
+ input_counter++;
+ input_counter %= sizeof(kRandomString);
+ }
+}
+
+void CreateEmptyExtImageAtPath(const string& path,
+ size_t size,
+ int block_size) {
+ EXPECT_EQ(0, System(StringPrintf("dd if=/dev/zero of=%s"
+ " seek=%zu bs=1 count=1 status=none",
+ path.c_str(), size)));
+ EXPECT_EQ(0, System(StringPrintf("mkfs.ext3 -q -b %d -F %s",
+ block_size, path.c_str())));
+}
+
+void CreateExtImageAtPath(const string& path, vector<string>* out_paths) {
+ // create 10MiB sparse file, mounted at a unique location.
+ string mount_path;
+ CHECK(utils::MakeTempDirectory(kMountPathTemplate, &mount_path));
+ ScopedDirRemover mount_path_unlinker(mount_path);
+
+ EXPECT_EQ(0, System(StringPrintf("dd if=/dev/zero of=%s"
+ " seek=10485759 bs=1 count=1 status=none",
+ path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mkfs.ext3 -q -b 4096 -F %s",
+ path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mount -o loop %s %s", path.c_str(),
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("echo hi > %s/hi", mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("echo hello > %s/hello",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir", mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir/empty_dir",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir/mnt",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("echo T > %s/some_dir/test",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mkfifo %s/some_dir/fifo",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mknod %s/cdev c 2 3", mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("ln -s /some/target %s/sym",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("ln %s/some_dir/test %s/testlink",
+ mount_path.c_str(), mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("echo T > %s/srchardlink0",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("ln %s/srchardlink0 %s/srchardlink1",
+ mount_path.c_str(), mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("ln -s bogus %s/boguslink",
+ mount_path.c_str())));
+ EXPECT_TRUE(utils::UnmountFilesystem(mount_path.c_str()));
+
+ if (out_paths) {
+ out_paths->clear();
+ out_paths->push_back("");
+ out_paths->push_back("/hi");
+ out_paths->push_back("/boguslink");
+ out_paths->push_back("/hello");
+ out_paths->push_back("/some_dir");
+ out_paths->push_back("/some_dir/empty_dir");
+ out_paths->push_back("/some_dir/mnt");
+ out_paths->push_back("/some_dir/test");
+ out_paths->push_back("/some_dir/fifo");
+ out_paths->push_back("/cdev");
+ out_paths->push_back("/testlink");
+ out_paths->push_back("/sym");
+ out_paths->push_back("/srchardlink0");
+ out_paths->push_back("/srchardlink1");
+ out_paths->push_back("/lost+found");
+ }
+}
+
+ScopedLoopMounter::ScopedLoopMounter(const string& file_path,
+ string* mnt_path,
+ unsigned long flags) { // NOLINT - long
+ EXPECT_TRUE(utils::MakeTempDirectory("mnt.XXXXXX", mnt_path));
+ dir_remover_.reset(new ScopedDirRemover(*mnt_path));
+
+ string loop_dev;
+ loop_binder_.reset(new ScopedLoopbackDeviceBinder(file_path, &loop_dev));
+
+ EXPECT_TRUE(utils::MountFilesystem(loop_dev, *mnt_path, flags));
+ unmounter_.reset(new ScopedFilesystemUnmounter(*mnt_path));
+}
+
+namespace {
+class ScopedDirCloser {
+ public:
+ explicit ScopedDirCloser(DIR** dir) : dir_(dir) {}
+ ~ScopedDirCloser() {
+ if (dir_ && *dir_) {
+ int r = closedir(*dir_);
+ TEST_AND_RETURN_ERRNO(r == 0);
+ *dir_ = nullptr;
+ dir_ = nullptr;
+ }
+ }
+ private:
+ DIR** dir_;
+};
+} // namespace
+
+bool RecursiveUnlinkDir(const string& path) {
+ struct stat stbuf;
+ int r = lstat(path.c_str(), &stbuf);
+ TEST_AND_RETURN_FALSE_ERRNO((r == 0) || (errno == ENOENT));
+ if ((r < 0) && (errno == ENOENT))
+ // path request is missing. that's fine.
+ return true;
+ if (!S_ISDIR(stbuf.st_mode)) {
+ TEST_AND_RETURN_FALSE_ERRNO((unlink(path.c_str()) == 0) ||
+ (errno == ENOENT));
+ // success or path disappeared before we could unlink.
+ return true;
+ }
+ {
+ // We have a dir, unlink all children, then delete dir
+ DIR *dir = opendir(path.c_str());
+ TEST_AND_RETURN_FALSE_ERRNO(dir);
+ ScopedDirCloser dir_closer(&dir);
+ struct dirent dir_entry;
+ struct dirent *dir_entry_p;
+ int err = 0;
+ while ((err = readdir_r(dir, &dir_entry, &dir_entry_p)) == 0) {
+ if (dir_entry_p == nullptr) {
+ // end of stream reached
+ break;
+ }
+ // Skip . and ..
+ if (!strcmp(dir_entry_p->d_name, ".") ||
+ !strcmp(dir_entry_p->d_name, ".."))
+ continue;
+ TEST_AND_RETURN_FALSE(RecursiveUnlinkDir(path + "/" +
+ dir_entry_p->d_name));
+ }
+ TEST_AND_RETURN_FALSE(err == 0);
+ }
+ // unlink dir
+ TEST_AND_RETURN_FALSE_ERRNO((rmdir(path.c_str()) == 0) || (errno == ENOENT));
+ return true;
+}
+
+GValue* GValueNewString(const char* str) {
+ GValue* gval = g_new0(GValue, 1);
+ g_value_init(gval, G_TYPE_STRING);
+ g_value_set_string(gval, str);
+ return gval;
+}
+
+void GValueFree(gpointer arg) {
+ auto gval = reinterpret_cast<GValue*>(arg);
+ g_value_unset(gval);
+ g_free(gval);
+}
+
+base::FilePath GetBuildArtifactsPath() {
+ base::FilePath exe_path;
+ base::ReadSymbolicLink(base::FilePath("/proc/self/exe"), &exe_path);
+ return exe_path.DirName();
+}
+
+} // namespace test_utils
+} // namespace chromeos_update_engine
diff --git a/test_utils.h b/test_utils.h
new file mode 100644
index 0000000..13bbd41
--- /dev/null
+++ b/test_utils.h
@@ -0,0 +1,271 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_TEST_UTILS_H_
+#define UPDATE_ENGINE_TEST_UTILS_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+// Streams used for gtest's PrintTo() functions.
+#include <iostream> // NOLINT(readability/streams)
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <glib-object.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/action.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+// These are some handy functions for unittests.
+
+namespace chromeos_update_engine {
+
+// PrintTo() functions are used by gtest to log these objects. These PrintTo()
+// functions must be defined in the same namespace as the first argument.
+void PrintTo(const Extent& extent, ::std::ostream* os);
+
+namespace test_utils {
+
+// 300 byte pseudo-random string. Not null terminated.
+// This does not gzip compress well.
+extern const uint8_t kRandomString[300];
+
+// Writes the data passed to path. The file at path will be overwritten if it
+// exists. Returns true on success, false otherwise.
+bool WriteFileVector(const std::string& path, const chromeos::Blob& data);
+bool WriteFileString(const std::string& path, const std::string& data);
+
+bool BindToUnusedLoopDevice(const std::string &filename,
+ std::string* lo_dev_name_ptr);
+
+// Returns true iff a == b
+bool ExpectVectorsEq(const chromeos::Blob& a, const chromeos::Blob& b);
+
+inline int System(const std::string& cmd) {
+ return system(cmd.c_str());
+}
+
+inline int Symlink(const std::string& oldpath, const std::string& newpath) {
+ return symlink(oldpath.c_str(), newpath.c_str());
+}
+
+inline int Chmod(const std::string& path, mode_t mode) {
+ return chmod(path.c_str(), mode);
+}
+
+inline int Mkdir(const std::string& path, mode_t mode) {
+ return mkdir(path.c_str(), mode);
+}
+
+inline int Chdir(const std::string& path) {
+ return chdir(path.c_str());
+}
+
+// Checks if xattr is supported in the directory specified by
+// |dir_path| which must be writable. Returns true if the feature is
+// supported, false if not or if an error occurred.
+bool IsXAttrSupported(const base::FilePath& dir_path);
+
+void FillWithData(chromeos::Blob* buffer);
+
+// Creates an empty ext image.
+void CreateEmptyExtImageAtPath(const std::string& path,
+ size_t size,
+ int block_size);
+
+// Creates an ext image with some files in it. The paths creates are
+// returned in out_paths.
+void CreateExtImageAtPath(const std::string& path,
+ std::vector<std::string>* out_paths);
+
+// Class to unmount FS when object goes out of scope
+class ScopedFilesystemUnmounter {
+ public:
+ explicit ScopedFilesystemUnmounter(const std::string& mountpoint)
+ : mountpoint_(mountpoint),
+ should_unmount_(true) {}
+ ~ScopedFilesystemUnmounter() {
+ if (should_unmount_) {
+ utils::UnmountFilesystem(mountpoint_);
+ }
+ }
+ void set_should_unmount(bool unmount) { should_unmount_ = unmount; }
+ private:
+ const std::string mountpoint_;
+ bool should_unmount_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedFilesystemUnmounter);
+};
+
+class ScopedLoopbackDeviceBinder {
+ public:
+ ScopedLoopbackDeviceBinder(const std::string& file, std::string* dev) {
+ is_bound_ = BindToUnusedLoopDevice(file, &dev_);
+ EXPECT_TRUE(is_bound_);
+
+ if (is_bound_ && dev)
+ *dev = dev_;
+ }
+
+ ~ScopedLoopbackDeviceBinder() {
+ if (!is_bound_)
+ return;
+
+ for (int retry = 0; retry < 5; retry++) {
+ std::vector<std::string> args;
+ args.push_back("/sbin/losetup");
+ args.push_back("-d");
+ args.push_back(dev_);
+ int return_code = 0;
+ EXPECT_TRUE(Subprocess::SynchronousExec(args, &return_code, nullptr));
+ if (return_code == 0) {
+ return;
+ }
+ sleep(1);
+ }
+ ADD_FAILURE();
+ }
+
+ const std::string &dev() {
+ EXPECT_TRUE(is_bound_);
+ return dev_;
+ }
+
+ bool is_bound() const { return is_bound_; }
+
+ private:
+ std::string dev_;
+ bool is_bound_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedLoopbackDeviceBinder);
+};
+
+class ScopedTempFile {
+ public:
+ ScopedTempFile() {
+ EXPECT_TRUE(utils::MakeTempFile("/tmp/update_engine_test_temp_file.XXXXXX",
+ &path_,
+ nullptr));
+ unlinker_.reset(new ScopedPathUnlinker(path_));
+ }
+ const std::string& GetPath() { return path_; }
+ private:
+ std::string path_;
+ std::unique_ptr<ScopedPathUnlinker> unlinker_;
+};
+
+class ScopedLoopMounter {
+ public:
+ explicit ScopedLoopMounter(const std::string& file_path,
+ std::string* mnt_path,
+ unsigned long flags); // NOLINT(runtime/int)
+
+ private:
+ // These objects must be destructed in the following order:
+ // ScopedFilesystemUnmounter (the file system must be unmounted first)
+ // ScopedLoopbackDeviceBinder (then the loop device can be deleted)
+ // ScopedDirRemover (then the mount point can be deleted)
+ std::unique_ptr<ScopedDirRemover> dir_remover_;
+ std::unique_ptr<ScopedLoopbackDeviceBinder> loop_binder_;
+ std::unique_ptr<ScopedFilesystemUnmounter> unmounter_;
+};
+
+// Deletes a directory and all its contents synchronously. Returns true
+// on success. This may be called with a regular file--it will just unlink it.
+// This WILL cross filesystem boundaries.
+bool RecursiveUnlinkDir(const std::string& path);
+
+// Allocates, initializes and returns a string GValue object.
+GValue* GValueNewString(const char* str);
+
+// Frees a GValue object and its allocated resources.
+void GValueFree(gpointer arg);
+
+// Returns the path where the build artifacts are stored. This is the directory
+// where the unittest executable is being run from.
+base::FilePath GetBuildArtifactsPath();
+
+} // namespace test_utils
+
+// Useful actions for test. These need to be defined in the
+// chromeos_update_engine namespace.
+
+class NoneType;
+
+template<typename T>
+class ObjectFeederAction;
+
+template<typename T>
+class ActionTraits<ObjectFeederAction<T>> {
+ public:
+ typedef T OutputObjectType;
+ typedef NoneType InputObjectType;
+};
+
+// This is a simple Action class for testing. It feeds an object into
+// another action.
+template<typename T>
+class ObjectFeederAction : public Action<ObjectFeederAction<T>> {
+ public:
+ typedef NoneType InputObjectType;
+ typedef T OutputObjectType;
+ void PerformAction() {
+ LOG(INFO) << "feeder running!";
+ CHECK(this->processor_);
+ if (this->HasOutputPipe()) {
+ this->SetOutputObject(out_obj_);
+ }
+ this->processor_->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ static std::string StaticType() { return "ObjectFeederAction"; }
+ std::string Type() const { return StaticType(); }
+ void set_obj(const T& out_obj) {
+ out_obj_ = out_obj;
+ }
+ private:
+ T out_obj_;
+};
+
+template<typename T>
+class ObjectCollectorAction;
+
+template<typename T>
+class ActionTraits<ObjectCollectorAction<T>> {
+ public:
+ typedef NoneType OutputObjectType;
+ typedef T InputObjectType;
+};
+
+// This is a simple Action class for testing. It receives an object from
+// another action.
+template<typename T>
+class ObjectCollectorAction : public Action<ObjectCollectorAction<T>> {
+ public:
+ typedef T InputObjectType;
+ typedef NoneType OutputObjectType;
+ void PerformAction() {
+ LOG(INFO) << "collector running!";
+ ASSERT_TRUE(this->processor_);
+ if (this->HasInputObject()) {
+ object_ = this->GetInputObject();
+ }
+ this->processor_->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ static std::string StaticType() { return "ObjectCollectorAction"; }
+ std::string Type() const { return StaticType(); }
+ const T& object() const { return object_; }
+ private:
+ T object_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_TEST_UTILS_H_
diff --git a/test_utils_unittest.cc b/test_utils_unittest.cc
new file mode 100644
index 0000000..ad41814
--- /dev/null
+++ b/test_utils_unittest.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/test_utils.h"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+namespace test_utils {
+
+class TestUtilsTest : public ::testing::Test { };
+
+TEST(UtilsTest, RecursiveUnlinkDirTest) {
+ string first_dir_name;
+ ASSERT_TRUE(utils::MakeTempDirectory("RecursiveUnlinkDirTest-a-XXXXXX",
+ &first_dir_name));
+ ASSERT_EQ(0, Chmod(first_dir_name, 0755));
+ string second_dir_name;
+ ASSERT_TRUE(utils::MakeTempDirectory("RecursiveUnlinkDirTest-b-XXXXXX",
+ &second_dir_name));
+ ASSERT_EQ(0, Chmod(second_dir_name, 0755));
+
+ EXPECT_EQ(0, Symlink(string("../") + first_dir_name,
+ second_dir_name + "/link"));
+ EXPECT_EQ(0, System(string("echo hi > ") + second_dir_name + "/file"));
+ EXPECT_EQ(0, Mkdir(second_dir_name + "/dir", 0755));
+ EXPECT_EQ(0, System(string("echo ok > ") + second_dir_name + "/dir/subfile"));
+ EXPECT_TRUE(test_utils::RecursiveUnlinkDir(second_dir_name));
+ EXPECT_TRUE(utils::FileExists(first_dir_name.c_str()));
+ EXPECT_EQ(0, System(string("rm -rf ") + first_dir_name));
+ EXPECT_FALSE(utils::FileExists(second_dir_name.c_str()));
+ EXPECT_TRUE(test_utils::RecursiveUnlinkDir("/something/that/doesnt/exist"));
+}
+
+} // namespace test_utils
+} // namespace chromeos_update_engine
diff --git a/testrunner.cc b/testrunner.cc
new file mode 100644
index 0000000..4786c74
--- /dev/null
+++ b/testrunner.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// based on pam_google_testrunner.cc
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <chromeos/test_helpers.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-bindings.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
+
+int main(int argc, char **argv) {
+ LOG(INFO) << "started";
+ ::g_type_init();
+ dbus_threads_init_default();
+ base::AtExitManager exit_manager;
+ // TODO(garnold) temporarily cause the unittest binary to exit with status
+ // code 2 upon catching a SIGTERM. This will help diagnose why the unittest
+ // binary is perceived as failing by the buildbot. We should revert it to use
+ // the default exit status of 1. Corresponding reverts are necessary in
+ // terminator_unittest.cc.
+ chromeos_update_engine::Terminator::Init(2);
+ chromeos_update_engine::Subprocess::Init();
+ LOG(INFO) << "parsing command line arguments";
+ base::CommandLine::Init(argc, argv);
+ LOG(INFO) << "initializing gtest";
+ SetUpTests(&argc, argv, true);
+ LOG(INFO) << "running unit tests";
+ int test_result = RUN_ALL_TESTS();
+ LOG(INFO) << "unittest return value: " << test_result;
+ return test_result;
+}
diff --git a/unittest_key.pem b/unittest_key.pem
new file mode 100644
index 0000000..224d3c3
--- /dev/null
+++ b/unittest_key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAx6hqS+Hbc7jN82ek+OBkHJNvlWXktyS2XCuQtGtnHjEV7Ou5
+hHNF96s5wEn8RDtrdLocm0a+SqXXf7KycFRTZxLdDbqWAMUlPXOaPI+PZLWkB7/K
+V7Cdj6vPGbeq7elu+aT/budhzTvq2stYrrAlcr/21tOVDPiWtfCdykh7FCXi6FaY
+HoNy5A6EKQLfLBuJoU/QoCufldmwlFaFDKl+Koos6R1QYJfCNZfgocrW1PIh+8t1
+JIvw6Izo8+fTme7nev7Ol2YZiMWJpRYKx8MgxW2UgTXlRpmQN56pc9LUsnVA8FND
+BN57+gMJj+XmdDmbtMpOsyXfSNEgnuwMPcEcmwIDAQABAoIBABPkwgKhlH4pUcwI
+7bUmlpMKVbnrFyjwbYMtjBOOCA5IEckzi56Y5cXRt8VjGdGqogBVcvg9ykQh1iER
+KxpqLI0+oev2RW/6NMW0uQ+DtmPwfVGQWJb4MBraoZ4MYOmnsrkJKbJhN6t9Zt86
+F7IANxsB6ZRqLJXIRywFt5MqOak+GAnQJ8C8eSQg70NhbEhSOrD8wrD6tfvgIqta
+XxhtlQWUAILIWetnWrJsalMqnreGn7vhc7+iihhMtXh1xNBMTA+gzpov/Cx21iH5
+DM9ppSA6HHDXrMhauryypIRrhjOUWRyDws/kIHgIW4TCbULOlxqsputQeTmdf0ti
+7lpwqAECgYEA7nNKkct3Vs51Ugk4GUPC4NOyYRPNc9UQAfHViB9gSDRacCo9Ax9J
+83hJGqDXlNGzELOnhzMn8jQMyF13eWzOsMozK6Fj3uW7DBvelg5bfgsZDUUO5WUF
+6BYbOheVqf12rIHR9BKBmCfLEKyxbKmw5bnB0uNo7IuBPBNuhPbvkgECgYEA1lo5
+XHWJpQnVl+JzXLHpXBK2nfnFAOtvzlTW+7gteeU12X2HcFASrzp7C1ULVV+i1Kcz
+tDFIA5yiFjEqmSJ/TsO8aqAhL5BXJjylCepQK7XkEOGCR8eQjlt7E4DulAsQbfpt
+k30HVVlIOFqLCWKSW8M3dy/Plodq/Gyq26rntpsCgYAzsNyGdIQfVkxKh2MY3v6c
+/Gdb8g4EwThiI4m1o4+ct3SvggiN57eBRx8Z3ao+QaM+yKNVhLpxH+VxfgmLUhIQ
+cxTarXbX+BcvTc9X2i7tSPyaStEq21aHdFtcoYY5Po/+X3ojHevoDyBPMhCYTMTj
+V/xzegbh2HAglNnNizZuAQKBgQCyOxEpBP5vgS7d/MgJklFEYrb/wjgBnMI5oSek
+5C7EBUdyUUM1qw7uLsUy1gL3eO7uvRxrvvJvNmU76KPP8vRCLNTVH9KYNv+P5qsg
+BHmm7rX1J11pi9Fx3TUIMZOu+0gs+ib0lOhtGjDH0tl680BZFohfDR0hv/XAcCbd
+Qk0q8wKBgQCGbURFFW/5RZUA77MmpY6jsEMw2gJmVtjO5IWZg9VPvLQQPgCr4Ho/
+bS2LIsT6pS+hrLOoz5KI0YueRS0jQYI+WkRqNf5wYNjxPql9632FiDLHO+Xv8PBe
+kHrPHy0GGT1igXScY4eemw4ZC1OSdZfkVn6Ui/JvBHrydJ2LrutMWQ==
+-----END RSA PRIVATE KEY-----
diff --git a/unittest_key2.pem b/unittest_key2.pem
new file mode 100644
index 0000000..d1f9a78
--- /dev/null
+++ b/unittest_key2.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAqdM7gnd43cWfQzyrt16QDHE+T0yxg18OZO+9sc8jWpjC1zFt
+oMFokESiu9TLUp+KUC274f+DxIgAfSCO6U5DJFRPXUzve1vaXYfqljUBxc+JYcGd
+d6PCUl4Ap9f02DhkrGnf/rkHOCEhFNpm53dovZ9A6Q5KBfcg1HeQ088opVceQGtU
+mL+bONq5tLvNFLUU0Ru0Am4ADJ8Xmw4rpvqvmXJaFmVuf/Gx7+TOnaJvZTeOO9AJ
+K6q6V8E7+ZZiMIc5F4EOs5AX/lZfNO3RVgG2O7Dhzza+nOzJ3ZJGK5R4WwZduhn9
+QRYogYPB0c62878qXpf2Bn3NpUPi8Cf/RLMMmQIDAQABAoIBACyLUWKpP7S770hN
+k6TnUtVQps1aCn2w4y+qipEnCdjrlL+pIV43HNwqhJzL9gDYBAl/1XYz9TYJjkdD
+0Ph1JLtUufR5B5/NufsqeWeow6xFAX34sPr+oyvDqFxeEsTcFdv7cVt44OHiHrE/
+kBpKgdiq+vWmX9gsuBnCuuQzxC+Juo6nupwZXcpa/ow9lC4QsgKqcjaUGrXXy2t9
+Er+9aSl8NdTjK76BXQsDgNkDyJZwNN14xrdS8eFsS4twskaOEYI4hEM0g62NOjgd
+Po8Ap/MnPpGSGcAd3d3Fq8KgT1lpyMKedLFU+k0H+/Y4RBl7grz1XXvSTzGi3Qy6
+38F4eVkCgYEA4mo4iiXSfrov9QffsVM17O0t9hUsOJHnUsEScxWLDm4IzaObyTtv
+tWW33iQSeFu4Wsol0nzjqWo3BaqiRidRUd42yZ07LJvfUDxUX9xPaUPFRs25iwhZ
+6tKAVqGk7/CFrN+R44sIwbsSvbExMAyW6gnj93EWUmMWWYp02hLbN0sCgYEAwAQI
+awVoc56OCtRpfYtlAPD/VOP1mbNzRmVl/UyZ4XYmz6f/hEz63Bk5PhYSZftlmK/r
+nj4qnl7HZ8jrJgZn2e97rPNpk7KDVU1+csCgLWZBTOXl/o9tOTyjh9LoRAjKtBB7
+x6CkWyiyd94xIq5VbnXhvL3a4d4o6OxMWdG5aSsCgYEAo44z1afIzP7WkdzkPIZt
+l/8linR1A1BymBccqsHPN9dIyLP9X3puEc2u6uuH5CXtoLgSZmENXF577L38h0zz
+s34gebf4/RqEUMOj97OAMfxgz+rgs4yO19DEINCYAzPufJjsHEFdTAVFXn5Xl+wg
+QGRwp1ir1Uv64yffjYC9ls0CgYEAjvIxpiKniPNvwUYypmDgt5uyKetvCpaaabzQ
++YpOQJep+wuRYFfCpZotkDf0SHGoR8wnd23GYpIilvPvgyZfp9HuW2n2nhrWROnl
+Cd63IDUwxeOcni7+XA71mwb7HLMC3Jws2geQc8DPZAdIww3P0eT2QYGBcobmI8jO
+akuEYXMCgYAm79Kb/r+3Hew5oAS1Whw70DskVlOutSgNsDPfW9MtDcnETBcGep7A
+1jCL5jjdUYRonimVMFjh1K+UFzV/DQHkgNzjxz9Inbh02y67vL2X836dS9esOcbx
+uZhf+8rL+GnSNqYDqCEuP7qCIloDhguJq9NKyTB4yc59qIkY2zPAzQ==
+-----END RSA PRIVATE KEY-----
diff --git a/update_attempter.cc b/update_attempter.cc
new file mode 100644
index 0000000..a1b2f11
--- /dev/null
+++ b/update_attempter.cc
@@ -0,0 +1,1684 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_attempter.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/rand_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include <glib.h>
+#include <metrics/metrics_library.h>
+#include <policy/device_policy.h>
+#include <policy/libpolicy.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/clock_interface.h"
+#include "update_engine/constants.h"
+#include "update_engine/dbus_service.h"
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/download_action.h"
+#include "update_engine/filesystem_verifier_action.h"
+#include "update_engine/glib_utils.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/metrics.h"
+#include "update_engine/multi_range_http_fetcher.h"
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/omaha_response_handler_action.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/postinstall_runner_action.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+#include "update_engine/utils.h"
+
+using base::Bind;
+using base::Callback;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using chromeos::MessageLoop;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::Policy;
+using chromeos_update_manager::UpdateCheckParams;
+using std::set;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const int UpdateAttempter::kMaxDeltaUpdateFailures = 3;
+
+namespace {
+const int kMaxConsecutiveObeyProxyRequests = 20;
+
+const char kUpdateCompletedMarker[] =
+ "/var/run/update_engine_autoupdate_completed";
+
+// By default autest bypasses scattering. If we want to test scattering,
+// use kScheduledAUTestURLRequest. The URL used is same in both cases, but
+// different params are passed to CheckForUpdate().
+const char kAUTestURLRequest[] = "autest";
+const char kScheduledAUTestURLRequest[] = "autest-scheduled";
+} // namespace
+
+const char* UpdateStatusToString(UpdateStatus status) {
+ switch (status) {
+ case UPDATE_STATUS_IDLE:
+ return update_engine::kUpdateStatusIdle;
+ case UPDATE_STATUS_CHECKING_FOR_UPDATE:
+ return update_engine::kUpdateStatusCheckingForUpdate;
+ case UPDATE_STATUS_UPDATE_AVAILABLE:
+ return update_engine::kUpdateStatusUpdateAvailable;
+ case UPDATE_STATUS_DOWNLOADING:
+ return update_engine::kUpdateStatusDownloading;
+ case UPDATE_STATUS_VERIFYING:
+ return update_engine::kUpdateStatusVerifying;
+ case UPDATE_STATUS_FINALIZING:
+ return update_engine::kUpdateStatusFinalizing;
+ case UPDATE_STATUS_UPDATED_NEED_REBOOT:
+ return update_engine::kUpdateStatusUpdatedNeedReboot;
+ case UPDATE_STATUS_REPORTING_ERROR_EVENT:
+ return update_engine::kUpdateStatusReportingErrorEvent;
+ case UPDATE_STATUS_ATTEMPTING_ROLLBACK:
+ return update_engine::kUpdateStatusAttemptingRollback;
+ case UPDATE_STATUS_DISABLED:
+ return update_engine::kUpdateStatusDisabled;
+ default:
+ return "unknown status";
+ }
+}
+
+// Turns a generic ErrorCode::kError to a generic error code specific
+// to |action| (e.g., ErrorCode::kFilesystemVerifierError). If |code| is
+// not ErrorCode::kError, or the action is not matched, returns |code|
+// unchanged.
+ErrorCode GetErrorCodeForAction(AbstractAction* action,
+ ErrorCode code) {
+ if (code != ErrorCode::kError)
+ return code;
+
+ const string type = action->Type();
+ if (type == OmahaRequestAction::StaticType())
+ return ErrorCode::kOmahaRequestError;
+ if (type == OmahaResponseHandlerAction::StaticType())
+ return ErrorCode::kOmahaResponseHandlerError;
+ if (type == FilesystemVerifierAction::StaticType())
+ return ErrorCode::kFilesystemVerifierError;
+ if (type == PostinstallRunnerAction::StaticType())
+ return ErrorCode::kPostinstallRunnerError;
+
+ return code;
+}
+
+UpdateAttempter::UpdateAttempter(SystemState* system_state,
+ DBusWrapperInterface* dbus_iface)
+ : UpdateAttempter(system_state, dbus_iface, kUpdateCompletedMarker) {}
+
+UpdateAttempter::UpdateAttempter(SystemState* system_state,
+ DBusWrapperInterface* dbus_iface,
+ const string& update_completed_marker)
+ : processor_(new ActionProcessor()),
+ system_state_(system_state),
+ dbus_iface_(dbus_iface),
+ chrome_proxy_resolver_(dbus_iface),
+ update_completed_marker_(update_completed_marker) {
+ if (!update_completed_marker_.empty() &&
+ utils::FileExists(update_completed_marker_.c_str())) {
+ status_ = UPDATE_STATUS_UPDATED_NEED_REBOOT;
+ } else {
+ status_ = UPDATE_STATUS_IDLE;
+ }
+}
+
+UpdateAttempter::~UpdateAttempter() {
+ CleanupCpuSharesManagement();
+}
+
+void UpdateAttempter::Init() {
+ // Pulling from the SystemState can only be done after construction, since
+ // this is an aggregate of various objects (such as the UpdateAttempter),
+ // which requires them all to be constructed prior to it being used.
+ prefs_ = system_state_->prefs();
+ omaha_request_params_ = system_state_->request_params();
+}
+
+void UpdateAttempter::ScheduleUpdates() {
+ if (IsUpdateRunningOrScheduled())
+ return;
+
+ chromeos_update_manager::UpdateManager* const update_manager =
+ system_state_->update_manager();
+ CHECK(update_manager);
+ Callback<void(EvalStatus, const UpdateCheckParams&)> callback = Bind(
+ &UpdateAttempter::OnUpdateScheduled, base::Unretained(this));
+ // We limit the async policy request to a reasonably short time, to avoid a
+ // starvation due to a transient bug.
+ update_manager->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+ waiting_for_scheduled_check_ = true;
+}
+
+bool UpdateAttempter::CheckAndReportDailyMetrics() {
+ int64_t stored_value;
+ Time now = system_state_->clock()->GetWallclockTime();
+ if (system_state_->prefs()->Exists(kPrefsDailyMetricsLastReportedAt) &&
+ system_state_->prefs()->GetInt64(kPrefsDailyMetricsLastReportedAt,
+ &stored_value)) {
+ Time last_reported_at = Time::FromInternalValue(stored_value);
+ TimeDelta time_reported_since = now - last_reported_at;
+ if (time_reported_since.InSeconds() < 0) {
+ LOG(WARNING) << "Last reported daily metrics "
+ << utils::FormatTimeDelta(time_reported_since) << " ago "
+ << "which is negative. Either the system clock is wrong or "
+ << "the kPrefsDailyMetricsLastReportedAt state variable "
+ << "is wrong.";
+ // In this case, report daily metrics to reset.
+ } else {
+ if (time_reported_since.InSeconds() < 24*60*60) {
+ LOG(INFO) << "Last reported daily metrics "
+ << utils::FormatTimeDelta(time_reported_since) << " ago.";
+ return false;
+ }
+ LOG(INFO) << "Last reported daily metrics "
+ << utils::FormatTimeDelta(time_reported_since) << " ago, "
+ << "which is more than 24 hours ago.";
+ }
+ }
+
+ LOG(INFO) << "Reporting daily metrics.";
+ system_state_->prefs()->SetInt64(kPrefsDailyMetricsLastReportedAt,
+ now.ToInternalValue());
+
+ ReportOSAge();
+
+ return true;
+}
+
+void UpdateAttempter::ReportOSAge() {
+ struct stat sb;
+
+ if (system_state_ == nullptr)
+ return;
+
+ if (stat("/etc/lsb-release", &sb) != 0) {
+ PLOG(ERROR) << "Error getting file status for /etc/lsb-release "
+ << "(Note: this may happen in some unit tests)";
+ return;
+ }
+
+ Time lsb_release_timestamp = utils::TimeFromStructTimespec(&sb.st_ctim);
+ Time now = system_state_->clock()->GetWallclockTime();
+ TimeDelta age = now - lsb_release_timestamp;
+ if (age.InSeconds() < 0) {
+ LOG(ERROR) << "The OS age (" << utils::FormatTimeDelta(age)
+ << ") is negative. Maybe the clock is wrong? "
+ << "(Note: this may happen in some unit tests.)";
+ return;
+ }
+
+ string metric = "Installer.OSAgeDays";
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(age)
+ << " for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ static_cast<int>(age.InDays()),
+ 0, // min: 0 days
+ 6*30, // max: 6 months (approx)
+ kNumDefaultUmaBuckets);
+
+ metrics::ReportDailyMetrics(system_state_, age);
+}
+
+void UpdateAttempter::Update(const string& app_version,
+ const string& omaha_url,
+ const string& target_channel,
+ const string& target_version_prefix,
+ bool obey_proxies,
+ bool interactive) {
+ // This is normally called frequently enough so it's appropriate to use as a
+ // hook for reporting daily metrics.
+ // TODO(garnold) This should be hooked to a separate (reliable and consistent)
+ // timeout event.
+ CheckAndReportDailyMetrics();
+
+ // Notify of the new update attempt, clearing prior interactive requests.
+ if (forced_update_pending_callback_.get())
+ forced_update_pending_callback_->Run(false, false);
+
+ chrome_proxy_resolver_.Init();
+ fake_update_success_ = false;
+ if (status_ == UPDATE_STATUS_UPDATED_NEED_REBOOT) {
+ // Although we have applied an update, we still want to ping Omaha
+ // to ensure the number of active statistics is accurate.
+ //
+ // Also convey to the UpdateEngine.Check.Result metric that we're
+ // not performing an update check because of this.
+ LOG(INFO) << "Not updating b/c we already updated and we're waiting for "
+ << "reboot, we'll ping Omaha instead";
+ metrics::ReportUpdateCheckMetrics(system_state_,
+ metrics::CheckResult::kRebootPending,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset);
+ PingOmaha();
+ return;
+ }
+ if (status_ != UPDATE_STATUS_IDLE) {
+ // Update in progress. Do nothing
+ return;
+ }
+
+ if (!CalculateUpdateParams(app_version,
+ omaha_url,
+ target_channel,
+ target_version_prefix,
+ obey_proxies,
+ interactive)) {
+ return;
+ }
+
+ BuildUpdateActions(interactive);
+
+ SetStatusAndNotify(UPDATE_STATUS_CHECKING_FOR_UPDATE);
+
+ // Update the last check time here; it may be re-updated when an Omaha
+ // response is received, but this will prevent us from repeatedly scheduling
+ // checks in the case where a response is not received.
+ UpdateLastCheckedTime();
+
+ // Just in case we didn't update boot flags yet, make sure they're updated
+ // before any update processing starts.
+ start_action_processor_ = true;
+ UpdateBootFlags();
+}
+
+void UpdateAttempter::RefreshDevicePolicy() {
+ // Lazy initialize the policy provider, or reload the latest policy data.
+ if (!policy_provider_.get())
+ policy_provider_.reset(new policy::PolicyProvider());
+ policy_provider_->Reload();
+
+ const policy::DevicePolicy* device_policy = nullptr;
+ if (policy_provider_->device_policy_is_loaded())
+ device_policy = &policy_provider_->GetDevicePolicy();
+
+ if (device_policy)
+ LOG(INFO) << "Device policies/settings present";
+ else
+ LOG(INFO) << "No device policies/settings present.";
+
+ system_state_->set_device_policy(device_policy);
+ system_state_->p2p_manager()->SetDevicePolicy(device_policy);
+}
+
+void UpdateAttempter::CalculateP2PParams(bool interactive) {
+ bool use_p2p_for_downloading = false;
+ bool use_p2p_for_sharing = false;
+
+ // Never use p2p for downloading in interactive checks unless the
+ // developer has opted in for it via a marker file.
+ //
+ // (Why would a developer want to opt in? If he's working on the
+ // update_engine or p2p codebases so he can actually test his
+ // code.).
+
+ if (system_state_ != nullptr) {
+ if (!system_state_->p2p_manager()->IsP2PEnabled()) {
+ LOG(INFO) << "p2p is not enabled - disallowing p2p for both"
+ << " downloading and sharing.";
+ } else {
+ // Allow p2p for sharing, even in interactive checks.
+ use_p2p_for_sharing = true;
+ if (!interactive) {
+ LOG(INFO) << "Non-interactive check - allowing p2p for downloading";
+ use_p2p_for_downloading = true;
+ } else {
+ LOG(INFO) << "Forcibly disabling use of p2p for downloading "
+ << "since this update attempt is interactive.";
+ }
+ }
+ }
+
+ PayloadStateInterface* const payload_state = system_state_->payload_state();
+ payload_state->SetUsingP2PForDownloading(use_p2p_for_downloading);
+ payload_state->SetUsingP2PForSharing(use_p2p_for_sharing);
+}
+
+bool UpdateAttempter::CalculateUpdateParams(const string& app_version,
+ const string& omaha_url,
+ const string& target_channel,
+ const string& target_version_prefix,
+ bool obey_proxies,
+ bool interactive) {
+ http_response_code_ = 0;
+ PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+ // Refresh the policy before computing all the update parameters.
+ RefreshDevicePolicy();
+
+ // Set the target version prefix, if provided.
+ if (!target_version_prefix.empty())
+ omaha_request_params_->set_target_version_prefix(target_version_prefix);
+
+ CalculateScatteringParams(interactive);
+
+ CalculateP2PParams(interactive);
+ if (payload_state->GetUsingP2PForDownloading() ||
+ payload_state->GetUsingP2PForSharing()) {
+ // OK, p2p is to be used - start it and perform housekeeping.
+ if (!StartP2PAndPerformHousekeeping()) {
+ // If this fails, disable p2p for this attempt
+ LOG(INFO) << "Forcibly disabling use of p2p since starting p2p or "
+ << "performing housekeeping failed.";
+ payload_state->SetUsingP2PForDownloading(false);
+ payload_state->SetUsingP2PForSharing(false);
+ }
+ }
+
+ if (!omaha_request_params_->Init(app_version,
+ omaha_url,
+ interactive)) {
+ LOG(ERROR) << "Unable to initialize Omaha request params.";
+ return false;
+ }
+
+ // Set the target channel, if one was provided.
+ if (target_channel.empty()) {
+ LOG(INFO) << "No target channel mandated by policy.";
+ } else {
+ LOG(INFO) << "Setting target channel as mandated: " << target_channel;
+ // Pass in false for powerwash_allowed until we add it to the policy
+ // protobuf.
+ omaha_request_params_->SetTargetChannel(target_channel, false);
+
+ // Since this is the beginning of a new attempt, update the download
+ // channel. The download channel won't be updated until the next attempt,
+ // even if target channel changes meanwhile, so that how we'll know if we
+ // should cancel the current download attempt if there's such a change in
+ // target channel.
+ omaha_request_params_->UpdateDownloadChannel();
+ }
+
+ LOG(INFO) << "target_version_prefix = "
+ << omaha_request_params_->target_version_prefix()
+ << ", scatter_factor_in_seconds = "
+ << utils::FormatSecs(scatter_factor_.InSeconds());
+
+ LOG(INFO) << "Wall Clock Based Wait Enabled = "
+ << omaha_request_params_->wall_clock_based_wait_enabled()
+ << ", Update Check Count Wait Enabled = "
+ << omaha_request_params_->update_check_count_wait_enabled()
+ << ", Waiting Period = " << utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+
+ LOG(INFO) << "Use p2p For Downloading = "
+ << payload_state->GetUsingP2PForDownloading()
+ << ", Use p2p For Sharing = "
+ << payload_state->GetUsingP2PForSharing();
+
+ obeying_proxies_ = true;
+ if (obey_proxies || proxy_manual_checks_ == 0) {
+ LOG(INFO) << "forced to obey proxies";
+ // If forced to obey proxies, every 20th request will not use proxies
+ proxy_manual_checks_++;
+ LOG(INFO) << "proxy manual checks: " << proxy_manual_checks_;
+ if (proxy_manual_checks_ >= kMaxConsecutiveObeyProxyRequests) {
+ proxy_manual_checks_ = 0;
+ obeying_proxies_ = false;
+ }
+ } else if (base::RandInt(0, 4) == 0) {
+ obeying_proxies_ = false;
+ }
+ LOG_IF(INFO, !obeying_proxies_) << "To help ensure updates work, this update "
+ "check we are ignoring the proxy settings and using "
+ "direct connections.";
+
+ DisableDeltaUpdateIfNeeded();
+ return true;
+}
+
+void UpdateAttempter::CalculateScatteringParams(bool interactive) {
+ // Take a copy of the old scatter value before we update it, as
+ // we need to update the waiting period if this value changes.
+ TimeDelta old_scatter_factor = scatter_factor_;
+ const policy::DevicePolicy* device_policy = system_state_->device_policy();
+ if (device_policy) {
+ int64_t new_scatter_factor_in_secs = 0;
+ device_policy->GetScatterFactorInSeconds(&new_scatter_factor_in_secs);
+ if (new_scatter_factor_in_secs < 0) // sanitize input, just in case.
+ new_scatter_factor_in_secs = 0;
+ scatter_factor_ = TimeDelta::FromSeconds(new_scatter_factor_in_secs);
+ }
+
+ bool is_scatter_enabled = false;
+ if (scatter_factor_.InSeconds() == 0) {
+ LOG(INFO) << "Scattering disabled since scatter factor is set to 0";
+ } else if (interactive) {
+ LOG(INFO) << "Scattering disabled as this is an interactive update check";
+ } else if (!system_state_->hardware()->IsOOBEComplete(nullptr)) {
+ LOG(INFO) << "Scattering disabled since OOBE is not complete yet";
+ } else {
+ is_scatter_enabled = true;
+ LOG(INFO) << "Scattering is enabled";
+ }
+
+ if (is_scatter_enabled) {
+ // This means the scattering policy is turned on.
+ // Now check if we need to update the waiting period. The two cases
+ // in which we'd need to update the waiting period are:
+ // 1. First time in process or a scheduled check after a user-initiated one.
+ // (omaha_request_params_->waiting_period will be zero in this case).
+ // 2. Admin has changed the scattering policy value.
+ // (new scattering value will be different from old one in this case).
+ int64_t wait_period_in_secs = 0;
+ if (omaha_request_params_->waiting_period().InSeconds() == 0) {
+ // First case. Check if we have a suitable value to set for
+ // the waiting period.
+ if (prefs_->GetInt64(kPrefsWallClockWaitPeriod, &wait_period_in_secs) &&
+ wait_period_in_secs > 0 &&
+ wait_period_in_secs <= scatter_factor_.InSeconds()) {
+ // This means:
+ // 1. There's a persisted value for the waiting period available.
+ // 2. And that persisted value is still valid.
+ // So, in this case, we should reuse the persisted value instead of
+ // generating a new random value to improve the chances of a good
+ // distribution for scattering.
+ omaha_request_params_->set_waiting_period(
+ TimeDelta::FromSeconds(wait_period_in_secs));
+ LOG(INFO) << "Using persisted wall-clock waiting period: " <<
+ utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+ } else {
+ // This means there's no persisted value for the waiting period
+ // available or its value is invalid given the new scatter_factor value.
+ // So, we should go ahead and regenerate a new value for the
+ // waiting period.
+ LOG(INFO) << "Persisted value not present or not valid ("
+ << utils::FormatSecs(wait_period_in_secs)
+ << ") for wall-clock waiting period.";
+ GenerateNewWaitingPeriod();
+ }
+ } else if (scatter_factor_ != old_scatter_factor) {
+ // This means there's already a waiting period value, but we detected
+ // a change in the scattering policy value. So, we should regenerate the
+ // waiting period to make sure it's within the bounds of the new scatter
+ // factor value.
+ GenerateNewWaitingPeriod();
+ } else {
+ // Neither the first time scattering is enabled nor the scattering value
+ // changed. Nothing to do.
+ LOG(INFO) << "Keeping current wall-clock waiting period: " <<
+ utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+ }
+
+ // The invariant at this point is that omaha_request_params_->waiting_period
+ // is non-zero no matter which path we took above.
+ LOG_IF(ERROR, omaha_request_params_->waiting_period().InSeconds() == 0)
+ << "Waiting Period should NOT be zero at this point!!!";
+
+ // Since scattering is enabled, wall clock based wait will always be
+ // enabled.
+ omaha_request_params_->set_wall_clock_based_wait_enabled(true);
+
+ // If we don't have any issues in accessing the file system to update
+ // the update check count value, we'll turn that on as well.
+ bool decrement_succeeded = DecrementUpdateCheckCount();
+ omaha_request_params_->set_update_check_count_wait_enabled(
+ decrement_succeeded);
+ } else {
+ // This means the scattering feature is turned off or disabled for
+ // this particular update check. Make sure to disable
+ // all the knobs and artifacts so that we don't invoke any scattering
+ // related code.
+ omaha_request_params_->set_wall_clock_based_wait_enabled(false);
+ omaha_request_params_->set_update_check_count_wait_enabled(false);
+ omaha_request_params_->set_waiting_period(TimeDelta::FromSeconds(0));
+ prefs_->Delete(kPrefsWallClockWaitPeriod);
+ prefs_->Delete(kPrefsUpdateCheckCount);
+ // Don't delete the UpdateFirstSeenAt file as we don't want manual checks
+ // that result in no-updates (e.g. due to server side throttling) to
+ // cause update starvation by having the client generate a new
+ // UpdateFirstSeenAt for each scheduled check that follows a manual check.
+ }
+}
+
+void UpdateAttempter::GenerateNewWaitingPeriod() {
+ omaha_request_params_->set_waiting_period(TimeDelta::FromSeconds(
+ base::RandInt(1, scatter_factor_.InSeconds())));
+
+ LOG(INFO) << "Generated new wall-clock waiting period: " << utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+
+ // Do a best-effort to persist this in all cases. Even if the persistence
+ // fails, we'll still be able to scatter based on our in-memory value.
+ // The persistence only helps in ensuring a good overall distribution
+ // across multiple devices if they tend to reboot too often.
+ system_state_->payload_state()->SetScatteringWaitPeriod(
+ omaha_request_params_->waiting_period());
+}
+
+void UpdateAttempter::BuildPostInstallActions(
+ InstallPlanAction* previous_action) {
+ shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
+ new PostinstallRunnerAction());
+ actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
+ BondActions(previous_action,
+ postinstall_runner_action.get());
+}
+
+void UpdateAttempter::BuildUpdateActions(bool interactive) {
+ CHECK(!processor_->IsRunning());
+ processor_->set_delegate(this);
+
+ // Actions:
+ LibcurlHttpFetcher* update_check_fetcher =
+ new LibcurlHttpFetcher(GetProxyResolver(), system_state_);
+ // Try harder to connect to the network, esp when not interactive.
+ // See comment in libcurl_http_fetcher.cc.
+ update_check_fetcher->set_no_network_max_retries(interactive ? 1 : 3);
+ update_check_fetcher->set_check_certificate(CertificateChecker::kUpdate);
+ shared_ptr<OmahaRequestAction> update_check_action(
+ new OmahaRequestAction(system_state_,
+ nullptr,
+ update_check_fetcher, // passes ownership
+ false));
+ shared_ptr<OmahaResponseHandlerAction> response_handler_action(
+ new OmahaResponseHandlerAction(system_state_));
+ shared_ptr<FilesystemVerifierAction> src_filesystem_verifier_action(
+ new FilesystemVerifierAction(system_state_,
+ PartitionType::kSourceRootfs));
+ shared_ptr<FilesystemVerifierAction> src_kernel_filesystem_verifier_action(
+ new FilesystemVerifierAction(system_state_,
+ PartitionType::kSourceKernel));
+
+ shared_ptr<OmahaRequestAction> download_started_action(
+ new OmahaRequestAction(system_state_,
+ new OmahaEvent(
+ OmahaEvent::kTypeUpdateDownloadStarted),
+ new LibcurlHttpFetcher(GetProxyResolver(),
+ system_state_),
+ false));
+ LibcurlHttpFetcher* download_fetcher =
+ new LibcurlHttpFetcher(GetProxyResolver(), system_state_);
+ download_fetcher->set_check_certificate(CertificateChecker::kDownload);
+ shared_ptr<DownloadAction> download_action(
+ new DownloadAction(prefs_,
+ system_state_,
+ new MultiRangeHttpFetcher(
+ download_fetcher))); // passes ownership
+ shared_ptr<OmahaRequestAction> download_finished_action(
+ new OmahaRequestAction(system_state_,
+ new OmahaEvent(
+ OmahaEvent::kTypeUpdateDownloadFinished),
+ new LibcurlHttpFetcher(GetProxyResolver(),
+ system_state_),
+ false));
+ shared_ptr<FilesystemVerifierAction> dst_filesystem_verifier_action(
+ new FilesystemVerifierAction(system_state_, PartitionType::kRootfs));
+ shared_ptr<FilesystemVerifierAction> dst_kernel_filesystem_verifier_action(
+ new FilesystemVerifierAction(system_state_, PartitionType::kKernel));
+ shared_ptr<OmahaRequestAction> update_complete_action(
+ new OmahaRequestAction(system_state_,
+ new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+ new LibcurlHttpFetcher(GetProxyResolver(),
+ system_state_),
+ false));
+
+ download_action->set_delegate(this);
+ response_handler_action_ = response_handler_action;
+ download_action_ = download_action;
+
+ actions_.push_back(shared_ptr<AbstractAction>(update_check_action));
+ actions_.push_back(shared_ptr<AbstractAction>(response_handler_action));
+ actions_.push_back(shared_ptr<AbstractAction>(
+ src_filesystem_verifier_action));
+ actions_.push_back(shared_ptr<AbstractAction>(
+ src_kernel_filesystem_verifier_action));
+ actions_.push_back(shared_ptr<AbstractAction>(download_started_action));
+ actions_.push_back(shared_ptr<AbstractAction>(download_action));
+ actions_.push_back(shared_ptr<AbstractAction>(download_finished_action));
+ actions_.push_back(shared_ptr<AbstractAction>(
+ dst_filesystem_verifier_action));
+ actions_.push_back(shared_ptr<AbstractAction>(
+ dst_kernel_filesystem_verifier_action));
+
+ // Bond them together. We have to use the leaf-types when calling
+ // BondActions().
+ BondActions(update_check_action.get(),
+ response_handler_action.get());
+ BondActions(response_handler_action.get(),
+ src_filesystem_verifier_action.get());
+ BondActions(src_filesystem_verifier_action.get(),
+ src_kernel_filesystem_verifier_action.get());
+ BondActions(src_kernel_filesystem_verifier_action.get(),
+ download_action.get());
+ BondActions(download_action.get(),
+ dst_filesystem_verifier_action.get());
+ BondActions(dst_filesystem_verifier_action.get(),
+ dst_kernel_filesystem_verifier_action.get());
+
+ BuildPostInstallActions(dst_kernel_filesystem_verifier_action.get());
+
+ actions_.push_back(shared_ptr<AbstractAction>(update_complete_action));
+
+ // Enqueue the actions
+ for (const shared_ptr<AbstractAction>& action : actions_) {
+ processor_->EnqueueAction(action.get());
+ }
+}
+
+bool UpdateAttempter::Rollback(bool powerwash) {
+ if (!CanRollback()) {
+ return false;
+ }
+
+ // Extra check for enterprise-enrolled devices since they don't support
+ // powerwash.
+ if (powerwash) {
+ // Enterprise-enrolled devices have an empty owner in their device policy.
+ string owner;
+ RefreshDevicePolicy();
+ const policy::DevicePolicy* device_policy = system_state_->device_policy();
+ if (device_policy && (!device_policy->GetOwner(&owner) || owner.empty())) {
+ LOG(ERROR) << "Enterprise device detected. "
+ << "Cannot perform a powerwash for enterprise devices.";
+ return false;
+ }
+ }
+
+ processor_->set_delegate(this);
+
+ // Initialize the default request params.
+ if (!omaha_request_params_->Init("", "", true)) {
+ LOG(ERROR) << "Unable to initialize Omaha request params.";
+ return false;
+ }
+
+ LOG(INFO) << "Setting rollback options.";
+ InstallPlan install_plan;
+
+ TEST_AND_RETURN_FALSE(utils::GetInstallDev(
+ system_state_->hardware()->BootDevice(),
+ &install_plan.install_path));
+
+ install_plan.kernel_install_path =
+ utils::KernelDeviceOfBootDevice(install_plan.install_path);
+ install_plan.source_path = system_state_->hardware()->BootDevice();
+ install_plan.kernel_source_path =
+ utils::KernelDeviceOfBootDevice(install_plan.source_path);
+ install_plan.powerwash_required = powerwash;
+
+ LOG(INFO) << "Using this install plan:";
+ install_plan.Dump();
+
+ shared_ptr<InstallPlanAction> install_plan_action(
+ new InstallPlanAction(install_plan));
+ actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
+
+ BuildPostInstallActions(install_plan_action.get());
+
+ // Enqueue the actions
+ for (const shared_ptr<AbstractAction>& action : actions_) {
+ processor_->EnqueueAction(action.get());
+ }
+
+ // Update the payload state for Rollback.
+ system_state_->payload_state()->Rollback();
+
+ SetStatusAndNotify(UPDATE_STATUS_ATTEMPTING_ROLLBACK);
+
+ // Just in case we didn't update boot flags yet, make sure they're updated
+ // before any update processing starts. This also schedules the start of the
+ // actions we just posted.
+ start_action_processor_ = true;
+ UpdateBootFlags();
+ return true;
+}
+
+bool UpdateAttempter::CanRollback() const {
+ // We can only rollback if the update_engine isn't busy and we have a valid
+ // rollback partition.
+ return (status_ == UPDATE_STATUS_IDLE && !GetRollbackPartition().empty());
+}
+
+string UpdateAttempter::GetRollbackPartition() const {
+ vector<string> kernel_devices =
+ system_state_->hardware()->GetKernelDevices();
+
+ string boot_kernel_device =
+ system_state_->hardware()->BootKernelDevice();
+
+ LOG(INFO) << "UpdateAttempter::GetRollbackPartition";
+ for (const auto& name : kernel_devices)
+ LOG(INFO) << " Available kernel device = " << name;
+ LOG(INFO) << " Boot kernel device = " << boot_kernel_device;
+
+ auto current = std::find(kernel_devices.begin(), kernel_devices.end(),
+ boot_kernel_device);
+
+ if (current == kernel_devices.end()) {
+ LOG(ERROR) << "Unable to find the boot kernel device in the list of "
+ << "available devices";
+ return string();
+ }
+
+ for (string const& device_name : kernel_devices) {
+ if (device_name != *current) {
+ bool bootable = false;
+ if (system_state_->hardware()->IsKernelBootable(device_name, &bootable) &&
+ bootable) {
+ return device_name;
+ }
+ }
+ }
+
+ return string();
+}
+
+vector<std::pair<string, bool>>
+ UpdateAttempter::GetKernelDevices() const {
+ vector<string> kernel_devices =
+ system_state_->hardware()->GetKernelDevices();
+
+ string boot_kernel_device =
+ system_state_->hardware()->BootKernelDevice();
+
+ vector<std::pair<string, bool>> info_list;
+ info_list.reserve(kernel_devices.size());
+
+ for (string device_name : kernel_devices) {
+ bool bootable = false;
+ system_state_->hardware()->IsKernelBootable(device_name, &bootable);
+ // Add '*' to the name of the partition we booted from.
+ if (device_name == boot_kernel_device)
+ device_name += '*';
+ info_list.emplace_back(device_name, bootable);
+ }
+
+ return info_list;
+}
+
+void UpdateAttempter::CheckForUpdate(const string& app_version,
+ const string& omaha_url,
+ bool interactive) {
+ LOG(INFO) << "Forced update check requested.";
+ forced_app_version_.clear();
+ forced_omaha_url_.clear();
+
+ // Certain conditions must be met to allow setting custom version and update
+ // server URLs. However, kScheduledAUTestURLRequest and kAUTestURLRequest are
+ // always allowed regardless of device state.
+ if (IsAnyUpdateSourceAllowed()) {
+ forced_app_version_ = app_version;
+ forced_omaha_url_ = omaha_url;
+ }
+ if (omaha_url == kScheduledAUTestURLRequest) {
+ forced_omaha_url_ = chromeos_update_engine::kAUTestOmahaUrl;
+ // Pretend that it's not user-initiated even though it is,
+ // so as to test scattering logic, etc. which get kicked off
+ // only in scheduled update checks.
+ interactive = false;
+ } else if (omaha_url == kAUTestURLRequest) {
+ forced_omaha_url_ = chromeos_update_engine::kAUTestOmahaUrl;
+ }
+
+ if (forced_update_pending_callback_.get()) {
+ // Make sure that a scheduling request is made prior to calling the forced
+ // update pending callback.
+ ScheduleUpdates();
+ forced_update_pending_callback_->Run(true, interactive);
+ }
+}
+
+bool UpdateAttempter::RebootIfNeeded() {
+ if (status_ != UPDATE_STATUS_UPDATED_NEED_REBOOT) {
+ LOG(INFO) << "Reboot requested, but status is "
+ << UpdateStatusToString(status_) << ", so not rebooting.";
+ return false;
+ }
+
+ if (USE_POWER_MANAGEMENT && RequestPowerManagerReboot())
+ return true;
+
+ return RebootDirectly();
+}
+
+void UpdateAttempter::WriteUpdateCompletedMarker() {
+ if (update_completed_marker_.empty())
+ return;
+
+ int64_t value = system_state_->clock()->GetBootTime().ToInternalValue();
+ string contents = StringPrintf("%" PRIi64, value);
+
+ utils::WriteFile(update_completed_marker_.c_str(),
+ contents.c_str(),
+ contents.length());
+}
+
+bool UpdateAttempter::RequestPowerManagerReboot() {
+ GError* error = nullptr;
+ DBusGConnection* bus = dbus_iface_->BusGet(DBUS_BUS_SYSTEM, &error);
+ if (!bus) {
+ LOG(ERROR) << "Failed to get system bus: "
+ << utils::GetAndFreeGError(&error);
+ return false;
+ }
+
+ LOG(INFO) << "Calling " << power_manager::kPowerManagerInterface << "."
+ << power_manager::kRequestRestartMethod;
+ DBusGProxy* proxy = dbus_iface_->ProxyNewForName(
+ bus,
+ power_manager::kPowerManagerServiceName,
+ power_manager::kPowerManagerServicePath,
+ power_manager::kPowerManagerInterface);
+ const gboolean success = dbus_iface_->ProxyCall_1_0(
+ proxy,
+ power_manager::kRequestRestartMethod,
+ &error,
+ power_manager::REQUEST_RESTART_FOR_UPDATE);
+ dbus_iface_->ProxyUnref(proxy);
+
+ if (!success) {
+ LOG(ERROR) << "Failed to call " << power_manager::kRequestRestartMethod
+ << ": " << utils::GetAndFreeGError(&error);
+ return false;
+ }
+
+ return true;
+}
+
+bool UpdateAttempter::RebootDirectly() {
+ vector<string> command;
+ command.push_back("/sbin/shutdown");
+ command.push_back("-r");
+ command.push_back("now");
+ LOG(INFO) << "Running \"" << JoinString(command, ' ') << "\"";
+ int rc = 0;
+ Subprocess::SynchronousExec(command, &rc, nullptr);
+ return rc == 0;
+}
+
+void UpdateAttempter::OnUpdateScheduled(EvalStatus status,
+ const UpdateCheckParams& params) {
+ waiting_for_scheduled_check_ = false;
+
+ if (status == EvalStatus::kSucceeded) {
+ if (!params.updates_enabled) {
+ LOG(WARNING) << "Updates permanently disabled.";
+ // Signal disabled status, then switch right back to idle. This is
+ // necessary for ensuring that observers waiting for a signal change will
+ // actually notice one on subsequent calls. Note that we don't need to
+ // re-schedule a check in this case as updates are permanently disabled;
+ // further (forced) checks may still initiate a scheduling call.
+ SetStatusAndNotify(UPDATE_STATUS_DISABLED);
+ SetStatusAndNotify(UPDATE_STATUS_IDLE);
+ return;
+ }
+
+ LOG(INFO) << "Running "
+ << (params.is_interactive ? "interactive" : "periodic")
+ << " update.";
+
+ string app_version, omaha_url;
+ if (params.is_interactive) {
+ app_version = forced_app_version_;
+ omaha_url = forced_omaha_url_;
+ } else {
+ // Flush previously generated UMA reports before periodic updates.
+ CertificateChecker::FlushReport();
+ }
+
+ Update(app_version, omaha_url, params.target_channel,
+ params.target_version_prefix, false, params.is_interactive);
+ } else {
+ LOG(WARNING)
+ << "Update check scheduling failed (possibly timed out); retrying.";
+ ScheduleUpdates();
+ }
+
+ // This check ensures that future update checks will be or are already
+ // scheduled. The check should never fail. A check failure means that there's
+ // a bug that will most likely prevent further automatic update checks. It
+ // seems better to crash in such cases and restart the update_engine daemon
+ // into, hopefully, a known good state.
+ CHECK(IsUpdateRunningOrScheduled());
+}
+
+void UpdateAttempter::UpdateLastCheckedTime() {
+ last_checked_time_ = system_state_->clock()->GetWallclockTime().ToTimeT();
+}
+
+// Delegate methods:
+void UpdateAttempter::ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) {
+ LOG(INFO) << "Processing Done.";
+ actions_.clear();
+
+ // Reset cpu shares back to normal.
+ CleanupCpuSharesManagement();
+
+ if (status_ == UPDATE_STATUS_REPORTING_ERROR_EVENT) {
+ LOG(INFO) << "Error event sent.";
+
+ // Inform scheduler of new status;
+ SetStatusAndNotify(UPDATE_STATUS_IDLE);
+ ScheduleUpdates();
+
+ if (!fake_update_success_) {
+ return;
+ }
+ LOG(INFO) << "Booted from FW B and tried to install new firmware, "
+ "so requesting reboot from user.";
+ }
+
+ if (code == ErrorCode::kSuccess) {
+ WriteUpdateCompletedMarker();
+ prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
+ prefs_->SetString(kPrefsPreviousVersion,
+ omaha_request_params_->app_version());
+ DeltaPerformer::ResetUpdateProgress(prefs_, false);
+
+ system_state_->payload_state()->UpdateSucceeded();
+
+ // Since we're done with scattering fully at this point, this is the
+ // safest point delete the state files, as we're sure that the status is
+ // set to reboot (which means no more updates will be applied until reboot)
+ // This deletion is required for correctness as we want the next update
+ // check to re-create a new random number for the update check count.
+ // Similarly, we also delete the wall-clock-wait period that was persisted
+ // so that we start with a new random value for the next update check
+ // after reboot so that the same device is not favored or punished in any
+ // way.
+ prefs_->Delete(kPrefsUpdateCheckCount);
+ system_state_->payload_state()->SetScatteringWaitPeriod(TimeDelta());
+ prefs_->Delete(kPrefsUpdateFirstSeenAt);
+
+ SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT);
+ ScheduleUpdates();
+ LOG(INFO) << "Update successfully applied, waiting to reboot.";
+
+ // This pointer is null during rollback operations, and the stats
+ // don't make much sense then anyway.
+ if (response_handler_action_) {
+ const InstallPlan& install_plan =
+ response_handler_action_->install_plan();
+
+ // Generate an unique payload identifier.
+ const string target_version_uid =
+ install_plan.payload_hash + ":" + install_plan.metadata_signature;
+
+ // Expect to reboot into the new version to send the proper metric during
+ // next boot.
+ system_state_->payload_state()->ExpectRebootInNewVersion(
+ target_version_uid);
+
+ // Also report the success code so that the percentiles can be
+ // interpreted properly for the remaining error codes in UMA.
+ utils::SendErrorCodeToUma(system_state_, code);
+ } else {
+ // If we just finished a rollback, then we expect to have no Omaha
+ // response. Otherwise, it's an error.
+ if (system_state_->payload_state()->GetRollbackVersion().empty()) {
+ LOG(ERROR) << "Can't send metrics because expected "
+ "response_handler_action_ missing.";
+ }
+ }
+ return;
+ }
+
+ if (ScheduleErrorEventAction()) {
+ return;
+ }
+ LOG(INFO) << "No update.";
+ SetStatusAndNotify(UPDATE_STATUS_IDLE);
+ ScheduleUpdates();
+}
+
+void UpdateAttempter::ProcessingStopped(const ActionProcessor* processor) {
+ // Reset cpu shares back to normal.
+ CleanupCpuSharesManagement();
+ download_progress_ = 0.0;
+ SetStatusAndNotify(UPDATE_STATUS_IDLE);
+ ScheduleUpdates();
+ actions_.clear();
+ error_event_.reset(nullptr);
+}
+
+// Called whenever an action has finished processing, either successfully
+// or otherwise.
+void UpdateAttempter::ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ // Reset download progress regardless of whether or not the download
+ // action succeeded. Also, get the response code from HTTP request
+ // actions (update download as well as the initial update check
+ // actions).
+ const string type = action->Type();
+ if (type == DownloadAction::StaticType()) {
+ download_progress_ = 0.0;
+ DownloadAction* download_action = static_cast<DownloadAction*>(action);
+ http_response_code_ = download_action->GetHTTPResponseCode();
+ } else if (type == OmahaRequestAction::StaticType()) {
+ OmahaRequestAction* omaha_request_action =
+ static_cast<OmahaRequestAction*>(action);
+ // If the request is not an event, then it's the update-check.
+ if (!omaha_request_action->IsEvent()) {
+ http_response_code_ = omaha_request_action->GetHTTPResponseCode();
+
+ // Record the number of consecutive failed update checks.
+ if (http_response_code_ == kHttpResponseInternalServerError ||
+ http_response_code_ == kHttpResponseServiceUnavailable) {
+ consecutive_failed_update_checks_++;
+ } else {
+ consecutive_failed_update_checks_ = 0;
+ }
+
+ // Store the server-dictated poll interval, if any.
+ server_dictated_poll_interval_ =
+ std::max(0, omaha_request_action->GetOutputObject().poll_interval);
+ }
+ }
+ if (code != ErrorCode::kSuccess) {
+ // If the current state is at or past the download phase, count the failure
+ // in case a switch to full update becomes necessary. Ignore network
+ // transfer timeouts and failures.
+ if (status_ >= UPDATE_STATUS_DOWNLOADING &&
+ code != ErrorCode::kDownloadTransferError) {
+ MarkDeltaUpdateFailure();
+ }
+ // On failure, schedule an error event to be sent to Omaha.
+ CreatePendingErrorEvent(action, code);
+ return;
+ }
+ // Find out which action completed.
+ if (type == OmahaResponseHandlerAction::StaticType()) {
+ // Note that the status will be updated to DOWNLOADING when some bytes get
+ // actually downloaded from the server and the BytesReceived callback is
+ // invoked. This avoids notifying the user that a download has started in
+ // cases when the server and the client are unable to initiate the download.
+ CHECK(action == response_handler_action_.get());
+ const InstallPlan& plan = response_handler_action_->install_plan();
+ UpdateLastCheckedTime();
+ new_version_ = plan.version;
+ new_payload_size_ = plan.payload_size;
+ SetupDownload();
+ SetupCpuSharesManagement();
+ SetStatusAndNotify(UPDATE_STATUS_UPDATE_AVAILABLE);
+ } else if (type == DownloadAction::StaticType()) {
+ SetStatusAndNotify(UPDATE_STATUS_FINALIZING);
+ }
+}
+
+void UpdateAttempter::SetDownloadStatus(bool active) {
+ download_active_ = active;
+ LOG(INFO) << "Download status: " << (active ? "active" : "inactive");
+}
+
+void UpdateAttempter::BytesReceived(uint64_t bytes_received, uint64_t total) {
+ if (!download_active_) {
+ LOG(ERROR) << "BytesReceived called while not downloading.";
+ return;
+ }
+ double progress = static_cast<double>(bytes_received) /
+ static_cast<double>(total);
+ // Self throttle based on progress. Also send notifications if
+ // progress is too slow.
+ const double kDeltaPercent = 0.01; // 1%
+ if (status_ != UPDATE_STATUS_DOWNLOADING ||
+ bytes_received == total ||
+ progress - download_progress_ >= kDeltaPercent ||
+ TimeTicks::Now() - last_notify_time_ >= TimeDelta::FromSeconds(10)) {
+ download_progress_ = progress;
+ SetStatusAndNotify(UPDATE_STATUS_DOWNLOADING);
+ }
+}
+
+bool UpdateAttempter::ResetStatus() {
+ LOG(INFO) << "Attempting to reset state from "
+ << UpdateStatusToString(status_) << " to UPDATE_STATUS_IDLE";
+
+ switch (status_) {
+ case UPDATE_STATUS_IDLE:
+ // no-op.
+ return true;
+
+ case UPDATE_STATUS_UPDATED_NEED_REBOOT: {
+ bool ret_value = true;
+ status_ = UPDATE_STATUS_IDLE;
+ LOG(INFO) << "Reset Successful";
+
+ // Remove the reboot marker so that if the machine is rebooted
+ // after resetting to idle state, it doesn't go back to
+ // UPDATE_STATUS_UPDATED_NEED_REBOOT state.
+ if (!update_completed_marker_.empty()) {
+ if (!base::DeleteFile(base::FilePath(update_completed_marker_), false))
+ ret_value = false;
+ }
+
+ // Notify the PayloadState that the successful payload was canceled.
+ system_state_->payload_state()->ResetUpdateStatus();
+
+ return ret_value;
+ }
+
+ default:
+ LOG(ERROR) << "Reset not allowed in this state.";
+ return false;
+ }
+}
+
+bool UpdateAttempter::GetStatus(int64_t* last_checked_time,
+ double* progress,
+ string* current_operation,
+ string* new_version,
+ int64_t* new_payload_size) {
+ *last_checked_time = last_checked_time_;
+ *progress = download_progress_;
+ *current_operation = UpdateStatusToString(status_);
+ *new_version = new_version_;
+ *new_payload_size = new_payload_size_;
+ return true;
+}
+
+void UpdateAttempter::UpdateBootFlags() {
+ if (update_boot_flags_running_) {
+ LOG(INFO) << "Update boot flags running, nothing to do.";
+ return;
+ }
+ if (updated_boot_flags_) {
+ LOG(INFO) << "Already updated boot flags. Skipping.";
+ if (start_action_processor_) {
+ ScheduleProcessingStart();
+ }
+ return;
+ }
+ // This is purely best effort. Failures should be logged by Subprocess. Run
+ // the script asynchronously to avoid blocking the event loop regardless of
+ // the script runtime.
+ update_boot_flags_running_ = true;
+ LOG(INFO) << "Updating boot flags...";
+ vector<string> cmd{set_good_kernel_cmd_};
+ if (!Subprocess::Get().Exec(cmd, StaticCompleteUpdateBootFlags, this)) {
+ CompleteUpdateBootFlags(1);
+ }
+}
+
+void UpdateAttempter::CompleteUpdateBootFlags(int return_code) {
+ update_boot_flags_running_ = false;
+ updated_boot_flags_ = true;
+ if (start_action_processor_) {
+ ScheduleProcessingStart();
+ }
+}
+
+void UpdateAttempter::StaticCompleteUpdateBootFlags(
+ int return_code,
+ const string& output,
+ void* p) {
+ reinterpret_cast<UpdateAttempter*>(p)->CompleteUpdateBootFlags(return_code);
+}
+
+void UpdateAttempter::BroadcastStatus() {
+ if (!dbus_service_) {
+ return;
+ }
+ last_notify_time_ = TimeTicks::Now();
+ update_engine_service_emit_status_update(
+ dbus_service_,
+ last_checked_time_,
+ download_progress_,
+ UpdateStatusToString(status_),
+ new_version_.c_str(),
+ new_payload_size_);
+}
+
+uint32_t UpdateAttempter::GetErrorCodeFlags() {
+ uint32_t flags = 0;
+
+ if (!system_state_->hardware()->IsNormalBootMode())
+ flags |= static_cast<uint32_t>(ErrorCode::kDevModeFlag);
+
+ if (response_handler_action_.get() &&
+ response_handler_action_->install_plan().is_resume)
+ flags |= static_cast<uint32_t>(ErrorCode::kResumedFlag);
+
+ if (!system_state_->hardware()->IsOfficialBuild())
+ flags |= static_cast<uint32_t>(ErrorCode::kTestImageFlag);
+
+ if (omaha_request_params_->update_url() != kProductionOmahaUrl)
+ flags |= static_cast<uint32_t>(ErrorCode::kTestOmahaUrlFlag);
+
+ return flags;
+}
+
+bool UpdateAttempter::ShouldCancel(ErrorCode* cancel_reason) {
+ // Check if the channel we're attempting to update to is the same as the
+ // target channel currently chosen by the user.
+ OmahaRequestParams* params = system_state_->request_params();
+ if (params->download_channel() != params->target_channel()) {
+ LOG(ERROR) << "Aborting download as target channel: "
+ << params->target_channel()
+ << " is different from the download channel: "
+ << params->download_channel();
+ *cancel_reason = ErrorCode::kUpdateCanceledByChannelChange;
+ return true;
+ }
+
+ return false;
+}
+
+void UpdateAttempter::SetStatusAndNotify(UpdateStatus status) {
+ status_ = status;
+ BroadcastStatus();
+}
+
+void UpdateAttempter::CreatePendingErrorEvent(AbstractAction* action,
+ ErrorCode code) {
+ if (error_event_.get()) {
+ // This shouldn't really happen.
+ LOG(WARNING) << "There's already an existing pending error event.";
+ return;
+ }
+
+ // For now assume that a generic Omaha response action failure means that
+ // there's no update so don't send an event. Also, double check that the
+ // failure has not occurred while sending an error event -- in which case
+ // don't schedule another. This shouldn't really happen but just in case...
+ if ((action->Type() == OmahaResponseHandlerAction::StaticType() &&
+ code == ErrorCode::kError) ||
+ status_ == UPDATE_STATUS_REPORTING_ERROR_EVENT) {
+ return;
+ }
+
+ // Classify the code to generate the appropriate result so that
+ // the Borgmon charts show up the results correctly.
+ // Do this before calling GetErrorCodeForAction which could potentially
+ // augment the bit representation of code and thus cause no matches for
+ // the switch cases below.
+ OmahaEvent::Result event_result;
+ switch (code) {
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ event_result = OmahaEvent::kResultUpdateDeferred;
+ break;
+ default:
+ event_result = OmahaEvent::kResultError;
+ break;
+ }
+
+ code = GetErrorCodeForAction(action, code);
+ fake_update_success_ = code == ErrorCode::kPostinstallBootedFromFirmwareB;
+
+ // Compute the final error code with all the bit flags to be sent to Omaha.
+ code = static_cast<ErrorCode>(
+ static_cast<uint32_t>(code) | GetErrorCodeFlags());
+ error_event_.reset(new OmahaEvent(OmahaEvent::kTypeUpdateComplete,
+ event_result,
+ code));
+}
+
+bool UpdateAttempter::ScheduleErrorEventAction() {
+ if (error_event_.get() == nullptr)
+ return false;
+
+ LOG(ERROR) << "Update failed.";
+ system_state_->payload_state()->UpdateFailed(error_event_->error_code);
+
+ // Send it to Uma.
+ LOG(INFO) << "Reporting the error event";
+ utils::SendErrorCodeToUma(system_state_, error_event_->error_code);
+
+ // Send it to Omaha.
+ shared_ptr<OmahaRequestAction> error_event_action(
+ new OmahaRequestAction(system_state_,
+ error_event_.release(), // Pass ownership.
+ new LibcurlHttpFetcher(GetProxyResolver(),
+ system_state_),
+ false));
+ actions_.push_back(shared_ptr<AbstractAction>(error_event_action));
+ processor_->EnqueueAction(error_event_action.get());
+ SetStatusAndNotify(UPDATE_STATUS_REPORTING_ERROR_EVENT);
+ processor_->StartProcessing();
+ return true;
+}
+
+void UpdateAttempter::SetCpuShares(utils::CpuShares shares) {
+ if (shares_ == shares) {
+ return;
+ }
+ if (utils::SetCpuShares(shares)) {
+ shares_ = shares;
+ LOG(INFO) << "CPU shares = " << shares_;
+ }
+}
+
+void UpdateAttempter::SetupCpuSharesManagement() {
+ if (manage_shares_id_ != MessageLoop::kTaskIdNull) {
+ LOG(ERROR) << "Cpu shares timeout source hasn't been destroyed.";
+ CleanupCpuSharesManagement();
+ }
+ const int kCpuSharesTimeout = 2 * 60 * 60; // 2 hours
+ manage_shares_id_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ Bind(&UpdateAttempter::ManageCpuSharesCallback, base::Unretained(this)),
+ TimeDelta::FromSeconds(kCpuSharesTimeout));
+ SetCpuShares(utils::kCpuSharesLow);
+}
+
+void UpdateAttempter::CleanupCpuSharesManagement() {
+ if (manage_shares_id_ != MessageLoop::kTaskIdNull) {
+ // The UpdateAttempter is instantiated by default by the FakeSystemState,
+ // even when it is not used. We check the manage_shares_id_ before calling
+ // the MessageLoop::current() since the unit test using a FakeSystemState
+ // may have not define a MessageLoop for the current thread.
+ MessageLoop::current()->CancelTask(manage_shares_id_);
+ manage_shares_id_ = MessageLoop::kTaskIdNull;
+ }
+ SetCpuShares(utils::kCpuSharesNormal);
+}
+
+void UpdateAttempter::ScheduleProcessingStart() {
+ LOG(INFO) << "Scheduling an action processor start.";
+ start_action_processor_ = false;
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind([this] { this->processor_->StartProcessing(); }));
+}
+
+void UpdateAttempter::ManageCpuSharesCallback() {
+ SetCpuShares(utils::kCpuSharesNormal);
+ manage_shares_id_ = MessageLoop::kTaskIdNull;
+}
+
+void UpdateAttempter::DisableDeltaUpdateIfNeeded() {
+ int64_t delta_failures;
+ if (omaha_request_params_->delta_okay() &&
+ prefs_->GetInt64(kPrefsDeltaUpdateFailures, &delta_failures) &&
+ delta_failures >= kMaxDeltaUpdateFailures) {
+ LOG(WARNING) << "Too many delta update failures, forcing full update.";
+ omaha_request_params_->set_delta_okay(false);
+ }
+}
+
+void UpdateAttempter::MarkDeltaUpdateFailure() {
+ // Don't try to resume a failed delta update.
+ DeltaPerformer::ResetUpdateProgress(prefs_, false);
+ int64_t delta_failures;
+ if (!prefs_->GetInt64(kPrefsDeltaUpdateFailures, &delta_failures) ||
+ delta_failures < 0) {
+ delta_failures = 0;
+ }
+ prefs_->SetInt64(kPrefsDeltaUpdateFailures, ++delta_failures);
+}
+
+void UpdateAttempter::SetupDownload() {
+ MultiRangeHttpFetcher* fetcher =
+ static_cast<MultiRangeHttpFetcher*>(download_action_->http_fetcher());
+ fetcher->ClearRanges();
+ if (response_handler_action_->install_plan().is_resume) {
+ // Resuming an update so fetch the update manifest metadata first.
+ int64_t manifest_metadata_size = 0;
+ prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
+ fetcher->AddRange(0, manifest_metadata_size);
+ // If there're remaining unprocessed data blobs, fetch them. Be careful not
+ // to request data beyond the end of the payload to avoid 416 HTTP response
+ // error codes.
+ int64_t next_data_offset = 0;
+ prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
+ uint64_t resume_offset = manifest_metadata_size + next_data_offset;
+ if (resume_offset < response_handler_action_->install_plan().payload_size) {
+ fetcher->AddRange(resume_offset);
+ }
+ } else {
+ fetcher->AddRange(0);
+ }
+}
+
+void UpdateAttempter::PingOmaha() {
+ if (!processor_->IsRunning()) {
+ shared_ptr<OmahaRequestAction> ping_action(
+ new OmahaRequestAction(system_state_,
+ nullptr,
+ new LibcurlHttpFetcher(GetProxyResolver(),
+ system_state_),
+ true));
+ actions_.push_back(shared_ptr<OmahaRequestAction>(ping_action));
+ processor_->set_delegate(nullptr);
+ processor_->EnqueueAction(ping_action.get());
+ // Call StartProcessing() synchronously here to avoid any race conditions
+ // caused by multiple outstanding ping Omaha requests. If we call
+ // StartProcessing() asynchronously, the device can be suspended before we
+ // get a chance to callback to StartProcessing(). When the device resumes
+ // (assuming the device sleeps longer than the next update check period),
+ // StartProcessing() is called back and at the same time, the next update
+ // check is fired which eventually invokes StartProcessing(). A crash
+ // can occur because StartProcessing() checks to make sure that the
+ // processor is idle which it isn't due to the two concurrent ping Omaha
+ // requests.
+ processor_->StartProcessing();
+ } else {
+ LOG(WARNING) << "Action processor running, Omaha ping suppressed.";
+ }
+
+ // Update the last check time here; it may be re-updated when an Omaha
+ // response is received, but this will prevent us from repeatedly scheduling
+ // checks in the case where a response is not received.
+ UpdateLastCheckedTime();
+
+ // Update the status which will schedule the next update check
+ SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT);
+ ScheduleUpdates();
+}
+
+
+bool UpdateAttempter::DecrementUpdateCheckCount() {
+ int64_t update_check_count_value;
+
+ if (!prefs_->Exists(kPrefsUpdateCheckCount)) {
+ // This file does not exist. This means we haven't started our update
+ // check count down yet, so nothing more to do. This file will be created
+ // later when we first satisfy the wall-clock-based-wait period.
+ LOG(INFO) << "No existing update check count. That's normal.";
+ return true;
+ }
+
+ if (prefs_->GetInt64(kPrefsUpdateCheckCount, &update_check_count_value)) {
+ // Only if we're able to read a proper integer value, then go ahead
+ // and decrement and write back the result in the same file, if needed.
+ LOG(INFO) << "Update check count = " << update_check_count_value;
+
+ if (update_check_count_value == 0) {
+ // It could be 0, if, for some reason, the file didn't get deleted
+ // when we set our status to waiting for reboot. so we just leave it
+ // as is so that we can prevent another update_check wait for this client.
+ LOG(INFO) << "Not decrementing update check count as it's already 0.";
+ return true;
+ }
+
+ if (update_check_count_value > 0)
+ update_check_count_value--;
+ else
+ update_check_count_value = 0;
+
+ // Write out the new value of update_check_count_value.
+ if (prefs_->SetInt64(kPrefsUpdateCheckCount, update_check_count_value)) {
+ // We successfully wrote out te new value, so enable the
+ // update check based wait.
+ LOG(INFO) << "New update check count = " << update_check_count_value;
+ return true;
+ }
+ }
+
+ LOG(INFO) << "Deleting update check count state due to read/write errors.";
+
+ // We cannot read/write to the file, so disable the update check based wait
+ // so that we don't get stuck in this OS version by any chance (which could
+ // happen if there's some bug that causes to read/write incorrectly).
+ // Also attempt to delete the file to do our best effort to cleanup.
+ prefs_->Delete(kPrefsUpdateCheckCount);
+ return false;
+}
+
+
+void UpdateAttempter::UpdateEngineStarted() {
+ // If we just booted into a new update, keep the previous OS version
+ // in case we rebooted because of a crash of the old version, so we
+ // can do a proper crash report with correct information.
+ // This must be done before calling
+ // system_state_->payload_state()->UpdateEngineStarted() since it will
+ // delete SystemUpdated marker file.
+ if (system_state_->system_rebooted() &&
+ prefs_->Exists(kPrefsSystemUpdatedMarker)) {
+ if (!prefs_->GetString(kPrefsPreviousVersion, &prev_version_)) {
+ // If we fail to get the version string, make sure it stays empty.
+ prev_version_.clear();
+ }
+ }
+
+ system_state_->payload_state()->UpdateEngineStarted();
+ StartP2PAtStartup();
+}
+
+bool UpdateAttempter::StartP2PAtStartup() {
+ if (system_state_ == nullptr ||
+ !system_state_->p2p_manager()->IsP2PEnabled()) {
+ LOG(INFO) << "Not starting p2p at startup since it's not enabled.";
+ return false;
+ }
+
+ if (system_state_->p2p_manager()->CountSharedFiles() < 1) {
+ LOG(INFO) << "Not starting p2p at startup since our application "
+ << "is not sharing any files.";
+ return false;
+ }
+
+ return StartP2PAndPerformHousekeeping();
+}
+
+bool UpdateAttempter::StartP2PAndPerformHousekeeping() {
+ if (system_state_ == nullptr)
+ return false;
+
+ if (!system_state_->p2p_manager()->IsP2PEnabled()) {
+ LOG(INFO) << "Not starting p2p since it's not enabled.";
+ return false;
+ }
+
+ LOG(INFO) << "Ensuring that p2p is running.";
+ if (!system_state_->p2p_manager()->EnsureP2PRunning()) {
+ LOG(ERROR) << "Error starting p2p.";
+ return false;
+ }
+
+ LOG(INFO) << "Performing p2p housekeeping.";
+ if (!system_state_->p2p_manager()->PerformHousekeeping()) {
+ LOG(ERROR) << "Error performing housekeeping for p2p.";
+ return false;
+ }
+
+ LOG(INFO) << "Done performing p2p housekeeping.";
+ return true;
+}
+
+bool UpdateAttempter::GetBootTimeAtUpdate(Time *out_boot_time) {
+ if (update_completed_marker_.empty())
+ return false;
+
+ string contents;
+ if (!utils::ReadFile(update_completed_marker_, &contents))
+ return false;
+
+ char *endp;
+ int64_t stored_value = strtoll(contents.c_str(), &endp, 10);
+ if (*endp != '\0') {
+ LOG(ERROR) << "Error parsing file " << update_completed_marker_ << " "
+ << "with content '" << contents << "'";
+ return false;
+ }
+
+ *out_boot_time = Time::FromInternalValue(stored_value);
+ return true;
+}
+
+bool UpdateAttempter::IsUpdateRunningOrScheduled() {
+ return ((status_ != UPDATE_STATUS_IDLE &&
+ status_ != UPDATE_STATUS_UPDATED_NEED_REBOOT) ||
+ waiting_for_scheduled_check_);
+}
+
+bool UpdateAttempter::IsAnyUpdateSourceAllowed() {
+ // We allow updates from any source if either of these are true:
+ // * The device is running an unofficial (dev/test) image.
+ // * The debugd dev features are accessible (i.e. in devmode with no owner).
+ // This protects users running a base image, while still allowing a specific
+ // window (gated by the debug dev features) where `cros flash` is usable.
+ if (!system_state_->hardware()->IsOfficialBuild()) {
+ LOG(INFO) << "Non-official build; allowing any update source.";
+ return true;
+ }
+
+ // Even though the debugd tools are also gated on devmode, checking here can
+ // save us a D-Bus call so it's worth doing explicitly.
+ if (system_state_->hardware()->IsNormalBootMode()) {
+ LOG(INFO) << "Not in devmode; disallowing custom update sources.";
+ return false;
+ }
+
+ // Official images in devmode are allowed a custom update source iff the
+ // debugd dev tools are enabled.
+ GError* error = nullptr;
+ DBusGConnection* bus = dbus_iface_->BusGet(DBUS_BUS_SYSTEM, &error);
+ if (!bus) {
+ LOG(ERROR) << "Failed to get system bus: "
+ << utils::GetAndFreeGError(&error);
+ return false;
+ }
+
+ gint dev_features = debugd::DEV_FEATURES_DISABLED;
+ DBusGProxy* proxy = dbus_iface_->ProxyNewForName(
+ bus,
+ debugd::kDebugdServiceName,
+ debugd::kDebugdServicePath,
+ debugd::kDebugdInterface);
+ const gboolean success = dbus_iface_->ProxyCall_0_1(
+ proxy,
+ debugd::kQueryDevFeatures,
+ &error,
+ &dev_features);
+ dbus_iface_->ProxyUnref(proxy);
+
+ // Some boards may not include debugd so it's expected that this may fail,
+ // in which case we default to disallowing custom update sources.
+ if (success && !(dev_features & debugd::DEV_FEATURES_DISABLED)) {
+ LOG(INFO) << "Debugd dev tools enabled; allowing any update source.";
+ return true;
+ }
+ LOG(INFO) << "Debugd dev tools disabled; disallowing custom update sources.";
+ return false;
+}
+
+} // namespace chromeos_update_engine
diff --git a/update_attempter.h b/update_attempter.h
new file mode 100644
index 0000000..1326365
--- /dev/null
+++ b/update_attempter.h
@@ -0,0 +1,517 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_ATTEMPTER_H_
+#define UPDATE_ENGINE_UPDATE_ATTEMPTER_H_
+
+#include <time.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/time/time.h>
+#include <glib.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/action_processor.h"
+#include "update_engine/chrome_browser_proxy_resolver.h"
+#include "update_engine/download_action.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/omaha_response_handler_action.h"
+#include "update_engine/proxy_resolver.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+
+class MetricsLibraryInterface;
+struct UpdateEngineService;
+
+namespace policy {
+class PolicyProvider;
+}
+
+namespace chromeos_update_engine {
+
+class DBusWrapperInterface;
+
+enum UpdateStatus {
+ UPDATE_STATUS_IDLE = 0,
+ UPDATE_STATUS_CHECKING_FOR_UPDATE,
+ UPDATE_STATUS_UPDATE_AVAILABLE,
+ UPDATE_STATUS_DOWNLOADING,
+ UPDATE_STATUS_VERIFYING,
+ UPDATE_STATUS_FINALIZING,
+ UPDATE_STATUS_UPDATED_NEED_REBOOT,
+ UPDATE_STATUS_REPORTING_ERROR_EVENT,
+ UPDATE_STATUS_ATTEMPTING_ROLLBACK,
+ UPDATE_STATUS_DISABLED,
+};
+
+const char* UpdateStatusToString(UpdateStatus status);
+
+class UpdateAttempter : public ActionProcessorDelegate,
+ public DownloadActionDelegate {
+ public:
+ static const int kMaxDeltaUpdateFailures;
+
+ UpdateAttempter(SystemState* system_state,
+ DBusWrapperInterface* dbus_iface);
+ ~UpdateAttempter() override;
+
+ // Further initialization to be done post construction.
+ void Init();
+
+ // Initiates scheduling of update checks.
+ virtual void ScheduleUpdates();
+
+ // Checks for update and, if a newer version is available, attempts to update
+ // the system. Non-empty |in_app_version| or |in_update_url| prevents
+ // automatic detection of the parameter. |target_channel| denotes a
+ // policy-mandated channel we are updating to, if not empty. If |obey_proxies|
+ // is true, the update will likely respect Chrome's proxy setting. For
+ // security reasons, we may still not honor them. |interactive| should be true
+ // if this was called from the user (ie dbus).
+ virtual void Update(const std::string& app_version,
+ const std::string& omaha_url,
+ const std::string& target_channel,
+ const std::string& target_version_prefix,
+ bool obey_proxies,
+ bool interactive);
+
+ // ActionProcessorDelegate methods:
+ void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) override;
+ void ProcessingStopped(const ActionProcessor* processor) override;
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) override;
+
+ // Resets the current state to UPDATE_STATUS_IDLE.
+ // Used by update_engine_client for restarting a new update without
+ // having to reboot once the previous update has reached
+ // UPDATE_STATUS_UPDATED_NEED_REBOOT state. This is used only
+ // for testing purposes.
+ virtual bool ResetStatus();
+
+ // Returns the current status in the out params. Returns true on success.
+ virtual bool GetStatus(int64_t* last_checked_time,
+ double* progress,
+ std::string* current_operation,
+ std::string* new_version,
+ int64_t* new_size);
+
+ // Runs chromeos-setgoodkernel, whose responsibility it is to mark the
+ // currently booted partition has high priority/permanent/etc. The execution
+ // is asynchronous. On completion, the action processor may be started
+ // depending on the |start_action_processor_| field. Note that every update
+ // attempt goes through this method.
+ void UpdateBootFlags();
+
+ // Subprocess::Exec callback.
+ void CompleteUpdateBootFlags(int return_code);
+ static void StaticCompleteUpdateBootFlags(int return_code,
+ const std::string& output,
+ void* p);
+
+ UpdateStatus status() const { return status_; }
+
+ int http_response_code() const { return http_response_code_; }
+ void set_http_response_code(int code) { http_response_code_ = code; }
+
+ void set_dbus_service(struct UpdateEngineService* dbus_service) {
+ dbus_service_ = dbus_service;
+ }
+
+ // This is the internal entry point for going through an
+ // update. If the current status is idle invokes Update.
+ // This is called by the DBus implementation.
+ virtual void CheckForUpdate(const std::string& app_version,
+ const std::string& omaha_url,
+ bool is_interactive);
+
+ // This is the internal entry point for going through a rollback. This will
+ // attempt to run the postinstall on the non-active partition and set it as
+ // the partition to boot from. If |powerwash| is True, perform a powerwash
+ // as part of rollback. Returns True on success.
+ bool Rollback(bool powerwash);
+
+ // This is the internal entry point for checking if we can rollback.
+ bool CanRollback() const;
+
+ // This is the internal entry point for getting a rollback partition name,
+ // if one exists. It returns the bootable rollback kernel device partition
+ // name or empty string if none is available.
+ std::string GetRollbackPartition() const;
+
+ // Returns a list of available kernel partitions along with information
+ // whether it is possible to boot from it.
+ std::vector<std::pair<std::string, bool>> GetKernelDevices() const;
+
+ // Initiates a reboot if the current state is
+ // UPDATED_NEED_REBOOT. Returns true on sucess, false otherwise.
+ bool RebootIfNeeded();
+
+ // DownloadActionDelegate methods
+ void SetDownloadStatus(bool active) override;
+ void BytesReceived(uint64_t bytes_received, uint64_t total) override;
+
+ // Broadcasts the current status over D-Bus.
+ void BroadcastStatus();
+
+ // Returns the special flags to be added to ErrorCode values based on the
+ // parameters used in the current update attempt.
+ uint32_t GetErrorCodeFlags();
+
+ // Returns true if we should cancel the current download attempt based on the
+ // current state of the system, in which case |cancel_reason| indicates the
+ // reason for the cancellation. False otherwise, in which case
+ // |cancel_reason| is untouched.
+ bool ShouldCancel(ErrorCode* cancel_reason);
+
+ // Called at update_engine startup to do various house-keeping.
+ void UpdateEngineStarted();
+
+ // Reloads the device policy from libchromeos. Note: This method doesn't
+ // cause a real-time policy fetch from the policy server. It just reloads the
+ // latest value that libchromeos has cached. libchromeos fetches the policies
+ // from the server asynchronously at its own frequency.
+ virtual void RefreshDevicePolicy();
+
+ // Returns the boottime (CLOCK_BOOTTIME) recorded at the last
+ // successful update. Returns false if the device has not updated.
+ virtual bool GetBootTimeAtUpdate(base::Time *out_boot_time);
+
+ // Returns a version OS version that was being used before the last reboot,
+ // and if that reboot happended to be into an update (current version).
+ // This will return an empty string otherwise.
+ std::string const& GetPrevVersion() const { return prev_version_; }
+
+ // Returns the number of consecutive failed update checks.
+ virtual unsigned int consecutive_failed_update_checks() const {
+ return consecutive_failed_update_checks_;
+ }
+
+ // Returns the poll interval dictated by Omaha, if provided; zero otherwise.
+ virtual unsigned int server_dictated_poll_interval() const {
+ return server_dictated_poll_interval_;
+ }
+
+ // Sets a callback to be used when either a forced update request is received
+ // (first argument set to true) or cleared by an update attempt (first
+ // argument set to false). The callback further encodes whether the forced
+ // check is an interactive one (second argument set to true). Takes ownership
+ // of the callback object. A null value disables callback on these events.
+ // Note that only one callback can be set, so effectively at most one client
+ // can be notified.
+ virtual void set_forced_update_pending_callback(
+ base::Callback<void(bool, bool)>* // NOLINT(readability/function)
+ callback) {
+ forced_update_pending_callback_.reset(callback);
+ }
+
+ // Returns true if we should allow updates from any source. In official builds
+ // we want to restrict updates to known safe sources, but under certain
+ // conditions it's useful to allow updating from anywhere (e.g. to allow
+ // 'cros flash' to function properly).
+ virtual bool IsAnyUpdateSourceAllowed();
+
+ private:
+ // Update server URL for automated lab test.
+ static const char* const kTestUpdateUrl;
+
+ // Special ctor + friend declarations for testing purposes.
+ UpdateAttempter(SystemState* system_state,
+ DBusWrapperInterface* dbus_iface,
+ const std::string& update_completed_marker);
+
+ friend class UpdateAttempterUnderTest;
+ friend class UpdateAttempterTest;
+ FRIEND_TEST(UpdateAttempterTest, ActionCompletedDownloadTest);
+ FRIEND_TEST(UpdateAttempterTest, ActionCompletedErrorTest);
+ FRIEND_TEST(UpdateAttempterTest, ActionCompletedOmahaRequestTest);
+ FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventTest);
+ FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+ FRIEND_TEST(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest);
+ FRIEND_TEST(UpdateAttempterTest, MarkDeltaUpdateFailureTest);
+ FRIEND_TEST(UpdateAttempterTest, ReadTrackFromPolicy);
+ FRIEND_TEST(UpdateAttempterTest, PingOmahaTest);
+ FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest);
+ FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest);
+ FRIEND_TEST(UpdateAttempterTest, UpdateTest);
+ FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics);
+ FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile);
+
+ // Checks if it's more than 24 hours since daily metrics were last
+ // reported and, if so, reports daily metrics. Returns |true| if
+ // metrics were reported, |false| otherwise.
+ bool CheckAndReportDailyMetrics();
+
+ // Calculates and reports the age of the currently running OS. This
+ // is defined as the age of the /etc/lsb-release file.
+ void ReportOSAge();
+
+ // Sets the status to the given status and notifies a status update over dbus.
+ void SetStatusAndNotify(UpdateStatus status);
+
+ // Sets up the download parameters after receiving the update check response.
+ void SetupDownload();
+
+ // Creates an error event object in |error_event_| to be included in an
+ // OmahaRequestAction once the current action processor is done.
+ void CreatePendingErrorEvent(AbstractAction* action, ErrorCode code);
+
+ // If there's a pending error event allocated in |error_event_|, schedules an
+ // OmahaRequestAction with that event in the current processor, clears the
+ // pending event, updates the status and returns true. Returns false
+ // otherwise.
+ bool ScheduleErrorEventAction();
+
+ // Sets the cpu shares to |shares| and updates |shares_| if the new
+ // |shares| is different than the current |shares_|, otherwise simply
+ // returns.
+ void SetCpuShares(utils::CpuShares shares);
+
+ // Sets the cpu shares to low and sets up timeout events to increase it.
+ void SetupCpuSharesManagement();
+
+ // Resets the cpu shares to normal and destroys any scheduled timeout
+ // sources.
+ void CleanupCpuSharesManagement();
+
+ // The cpu shares timeout source callback sets the current cpu shares to
+ // normal.
+ void ManageCpuSharesCallback();
+
+ // Schedules an event loop callback to start the action processor. This is
+ // scheduled asynchronously to unblock the event loop.
+ void ScheduleProcessingStart();
+
+ // Checks if a full update is needed and forces it by updating the Omaha
+ // request params.
+ void DisableDeltaUpdateIfNeeded();
+
+ // If this was a delta update attempt that failed, count it so that a full
+ // update can be tried when needed.
+ void MarkDeltaUpdateFailure();
+
+ ProxyResolver* GetProxyResolver() {
+ return obeying_proxies_ ?
+ reinterpret_cast<ProxyResolver*>(&chrome_proxy_resolver_) :
+ reinterpret_cast<ProxyResolver*>(&direct_proxy_resolver_);
+ }
+
+ // Sends a ping to Omaha.
+ // This is used after an update has been applied and we're waiting for the
+ // user to reboot. This ping helps keep the number of actives count
+ // accurate in case a user takes a long time to reboot the device after an
+ // update has been applied.
+ void PingOmaha();
+
+ // Helper method of Update() to calculate the update-related parameters
+ // from various sources and set the appropriate state. Please refer to
+ // Update() method for the meaning of the parametes.
+ bool CalculateUpdateParams(const std::string& app_version,
+ const std::string& omaha_url,
+ const std::string& target_channel,
+ const std::string& target_version_prefix,
+ bool obey_proxies,
+ bool interactive);
+
+ // Calculates all the scattering related parameters (such as waiting period,
+ // which type of scattering is enabled, etc.) and also updates/deletes
+ // the corresponding prefs file used in scattering. Should be called
+ // only after the device policy has been loaded and set in the system_state_.
+ void CalculateScatteringParams(bool is_interactive);
+
+ // Sets a random value for the waiting period to wait for before downloading
+ // an update, if one available. This value will be upperbounded by the
+ // scatter factor value specified from policy.
+ void GenerateNewWaitingPeriod();
+
+ // Helper method of Update() and Rollback() to construct the sequence of
+ // actions to be performed for the postinstall.
+ // |previous_action| is the previous action to get
+ // bonded with the install_plan that gets passed to postinstall.
+ void BuildPostInstallActions(InstallPlanAction* previous_action);
+
+ // Helper method of Update() to construct the sequence of actions to
+ // be performed for an update check. Please refer to
+ // Update() method for the meaning of the parameters.
+ void BuildUpdateActions(bool interactive);
+
+ // Decrements the count in the kUpdateCheckCountFilePath.
+ // Returns True if successfully decremented, false otherwise.
+ bool DecrementUpdateCheckCount();
+
+ // Starts p2p and performs housekeeping. Returns true only if p2p is
+ // running and housekeeping was done.
+ bool StartP2PAndPerformHousekeeping();
+
+ // Calculates whether peer-to-peer should be used. Sets the
+ // |use_p2p_to_download_| and |use_p2p_to_share_| parameters
+ // on the |omaha_request_params_| object.
+ void CalculateP2PParams(bool interactive);
+
+ // Starts P2P if it's enabled and there are files to actually share.
+ // Called only at program startup. Returns true only if p2p was
+ // started and housekeeping was performed.
+ bool StartP2PAtStartup();
+
+ // Writes to the processing completed marker. Does nothing if
+ // |update_completed_marker_| is empty.
+ void WriteUpdateCompletedMarker();
+
+ // Sends a D-Bus message to the Chrome OS power manager asking it to reboot
+ // the system. Returns true on success.
+ bool RequestPowerManagerReboot();
+
+ // Reboots the system directly by calling /sbin/shutdown. Returns true on
+ // success.
+ bool RebootDirectly();
+
+ // Callback for the async UpdateCheckAllowed policy request. If |status| is
+ // |EvalStatus::kSucceeded|, either runs or suppresses periodic update checks,
+ // based on the content of |params|. Otherwise, retries the policy request.
+ void OnUpdateScheduled(
+ chromeos_update_manager::EvalStatus status,
+ const chromeos_update_manager::UpdateCheckParams& params);
+
+ // Updates the time an update was last attempted to the current time.
+ void UpdateLastCheckedTime();
+
+ // Returns whether an update is currently running or scheduled.
+ bool IsUpdateRunningOrScheduled();
+
+ // Last status notification timestamp used for throttling. Use monotonic
+ // TimeTicks to ensure that notifications are sent even if the system clock is
+ // set back in the middle of an update.
+ base::TimeTicks last_notify_time_;
+
+ std::vector<std::shared_ptr<AbstractAction>> actions_;
+ std::unique_ptr<ActionProcessor> processor_;
+
+ // External state of the system outside the update_engine process
+ // carved out separately to mock out easily in unit tests.
+ SystemState* system_state_;
+
+ // Interface for getting D-Bus connections.
+ DBusWrapperInterface* dbus_iface_ = nullptr;
+
+ // If non-null, this UpdateAttempter will send status updates over this
+ // dbus service.
+ UpdateEngineService* dbus_service_ = nullptr;
+
+ // Pointer to the OmahaResponseHandlerAction in the actions_ vector.
+ std::shared_ptr<OmahaResponseHandlerAction> response_handler_action_;
+
+ // Pointer to the DownloadAction in the actions_ vector.
+ std::shared_ptr<DownloadAction> download_action_;
+
+ // Pointer to the preferences store interface. This is just a cached
+ // copy of system_state->prefs() because it's used in many methods and
+ // is convenient this way.
+ PrefsInterface* prefs_ = nullptr;
+
+ // Pending error event, if any.
+ std::unique_ptr<OmahaEvent> error_event_;
+
+ // If we should request a reboot even tho we failed the update
+ bool fake_update_success_ = false;
+
+ // HTTP server response code from the last HTTP request action.
+ int http_response_code_ = 0;
+
+ // Current cpu shares.
+ utils::CpuShares shares_ = utils::kCpuSharesNormal;
+
+ // The cpu shares management timeout task id.
+ chromeos::MessageLoop::TaskId manage_shares_id_{
+ chromeos::MessageLoop::kTaskIdNull};
+
+ // Set to true if an update download is active (and BytesReceived
+ // will be called), set to false otherwise.
+ bool download_active_ = false;
+
+ // For status:
+ UpdateStatus status_;
+ double download_progress_ = 0.0;
+ int64_t last_checked_time_ = 0;
+ std::string prev_version_;
+ std::string new_version_ = "0.0.0.0";
+ int64_t new_payload_size_ = 0;
+
+ // Common parameters for all Omaha requests.
+ OmahaRequestParams* omaha_request_params_ = nullptr;
+
+ // Number of consecutive manual update checks we've had where we obeyed
+ // Chrome's proxy settings.
+ int proxy_manual_checks_ = 0;
+
+ // If true, this update cycle we are obeying proxies
+ bool obeying_proxies_ = true;
+
+ // Our two proxy resolvers
+ DirectProxyResolver direct_proxy_resolver_;
+ ChromeBrowserProxyResolver chrome_proxy_resolver_;
+
+ // Originally, both of these flags are false. Once UpdateBootFlags is called,
+ // |update_boot_flags_running_| is set to true. As soon as UpdateBootFlags
+ // completes its asynchronous run, |update_boot_flags_running_| is reset to
+ // false and |updated_boot_flags_| is set to true. From that point on there
+ // will be no more changes to these flags.
+ //
+ // True if UpdateBootFlags has completed.
+ bool updated_boot_flags_ = false;
+ // True if UpdateBootFlags is running.
+ bool update_boot_flags_running_ = false;
+
+ // The command to run to set the current kernel as good.
+ std::string set_good_kernel_cmd_ = "/usr/sbin/chromeos-setgoodkernel";
+
+ // True if the action processor needs to be started by the boot flag updater.
+ bool start_action_processor_ = false;
+
+ // Used for fetching information about the device policy.
+ std::unique_ptr<policy::PolicyProvider> policy_provider_;
+
+ // The current scatter factor as found in the policy setting.
+ base::TimeDelta scatter_factor_;
+
+ // Update completed marker file. An empty string means this marker is being
+ // ignored (nor is it being written), which is useful for testing situations.
+ std::string update_completed_marker_;
+
+ // The number of consecutive failed update checks. Needed for calculating the
+ // next update check interval.
+ unsigned int consecutive_failed_update_checks_ = 0;
+
+ // The poll interval (in seconds) that was dictated by Omaha, if any; zero
+ // otherwise. This is needed for calculating the update check interval.
+ unsigned int server_dictated_poll_interval_ = 0;
+
+ // Tracks whether we have scheduled update checks.
+ bool waiting_for_scheduled_check_ = false;
+
+ // A callback to use when a forced update request is either received (true) or
+ // cleared by an update attempt (false). The second argument indicates whether
+ // this is an interactive update, and its value is significant iff the first
+ // argument is true.
+ std::unique_ptr<base::Callback<void(bool, bool)>>
+ forced_update_pending_callback_;
+
+ // The |app_version| and |omaha_url| parameters received during the latest
+ // forced update request. They are retrieved for use once the update is
+ // actually scheduled.
+ std::string forced_app_version_;
+ std::string forced_omaha_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateAttempter);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_UPDATE_ATTEMPTER_H_
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
new file mode 100644
index 0000000..41a8232
--- /dev/null
+++ b/update_attempter_unittest.cc
@@ -0,0 +1,1022 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_attempter.h"
+
+#include <stdint.h>
+
+#include <memory>
+
+#include <base/files/file_util.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_device_policy.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_prefs.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/filesystem_verifier_action.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/mock_action.h"
+#include "update_engine/mock_action_processor.h"
+#include "update_engine/mock_dbus_wrapper.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/mock_p2p_manager.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/postinstall_runner_action.h"
+#include "update_engine/prefs.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using std::string;
+using std::unique_ptr;
+using testing::A;
+using testing::DoAll;
+using testing::InSequence;
+using testing::Ne;
+using testing::NiceMock;
+using testing::Property;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::SaveArg;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+// Test a subclass rather than the main class directly so that we can mock out
+// methods within the class. There're explicit unit tests for the mocked out
+// methods.
+class UpdateAttempterUnderTest : public UpdateAttempter {
+ public:
+ // We always feed an explicit update completed marker name; however, unless
+ // explicitly specified, we feed an empty string, which causes the
+ // UpdateAttempter class to ignore / not write the marker file.
+ UpdateAttempterUnderTest(SystemState* system_state,
+ DBusWrapperInterface* dbus_iface)
+ : UpdateAttempterUnderTest(system_state, dbus_iface, "") {}
+
+ UpdateAttempterUnderTest(SystemState* system_state,
+ DBusWrapperInterface* dbus_iface,
+ const string& update_completed_marker)
+ : UpdateAttempter(system_state, dbus_iface, update_completed_marker) {}
+
+ // Wrap the update scheduling method, allowing us to opt out of scheduled
+ // updates for testing purposes.
+ void ScheduleUpdates() override {
+ schedule_updates_called_ = true;
+ if (do_schedule_updates_) {
+ UpdateAttempter::ScheduleUpdates();
+ } else {
+ LOG(INFO) << "[TEST] Update scheduling disabled.";
+ }
+ }
+ void EnableScheduleUpdates() { do_schedule_updates_ = true; }
+ void DisableScheduleUpdates() { do_schedule_updates_ = false; }
+
+ // Indicates whether ScheduleUpdates() was called.
+ bool schedule_updates_called() const { return schedule_updates_called_; }
+
+ // Need to expose forced_omaha_url_ so we can test it.
+ const string& forced_omaha_url() const { return forced_omaha_url_; }
+
+ private:
+ bool schedule_updates_called_ = false;
+ bool do_schedule_updates_ = true;
+};
+
+class UpdateAttempterTest : public ::testing::Test {
+ protected:
+ UpdateAttempterTest()
+ : attempter_(&fake_system_state_, &dbus_),
+ mock_connection_manager(&fake_system_state_),
+ fake_dbus_system_bus_(reinterpret_cast<DBusGConnection*>(1)),
+ fake_dbus_debugd_proxy_(reinterpret_cast<DBusGProxy*>(2)) {
+ // Override system state members.
+ fake_system_state_.set_connection_manager(&mock_connection_manager);
+ fake_system_state_.set_update_attempter(&attempter_);
+ loop_.SetAsCurrent();
+
+ // Finish initializing the attempter.
+ attempter_.Init();
+
+ // We set the set_good_kernel command to a non-existent path so it fails to
+ // run it. This avoids the async call to the command and continues the
+ // update process right away. Tests testing that behavior can override the
+ // default set_good_kernel command if needed.
+ attempter_.set_good_kernel_cmd_ = "/path/to/non-existent/command";
+ }
+
+ void SetUp() override {
+ CHECK(utils::MakeTempDirectory("UpdateAttempterTest-XXXXXX", &test_dir_));
+
+ EXPECT_EQ(nullptr, attempter_.dbus_service_);
+ EXPECT_NE(nullptr, attempter_.system_state_);
+ EXPECT_EQ(0, attempter_.http_response_code_);
+ EXPECT_EQ(utils::kCpuSharesNormal, attempter_.shares_);
+ EXPECT_EQ(MessageLoop::kTaskIdNull, attempter_.manage_shares_id_);
+ EXPECT_FALSE(attempter_.download_active_);
+ EXPECT_EQ(UPDATE_STATUS_IDLE, attempter_.status_);
+ EXPECT_EQ(0.0, attempter_.download_progress_);
+ EXPECT_EQ(0, attempter_.last_checked_time_);
+ EXPECT_EQ("0.0.0.0", attempter_.new_version_);
+ EXPECT_EQ(0, attempter_.new_payload_size_);
+ processor_ = new NiceMock<MockActionProcessor>();
+ attempter_.processor_.reset(processor_); // Transfers ownership.
+ prefs_ = fake_system_state_.mock_prefs();
+
+ // Set up store/load semantics of P2P properties via the mock PayloadState.
+ actual_using_p2p_for_downloading_ = false;
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ SetUsingP2PForDownloading(_))
+ .WillRepeatedly(SaveArg<0>(&actual_using_p2p_for_downloading_));
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ GetUsingP2PForDownloading())
+ .WillRepeatedly(ReturnPointee(&actual_using_p2p_for_downloading_));
+ actual_using_p2p_for_sharing_ = false;
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ SetUsingP2PForSharing(_))
+ .WillRepeatedly(SaveArg<0>(&actual_using_p2p_for_sharing_));
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ GetUsingP2PForDownloading())
+ .WillRepeatedly(ReturnPointee(&actual_using_p2p_for_sharing_));
+
+ // Set up mock debugd access over the system D-Bus. ProxyCall_0_1() also
+ // needs to be mocked in any test using debugd to provide the desired value.
+ ON_CALL(dbus_, BusGet(DBUS_BUS_SYSTEM, _))
+ .WillByDefault(Return(fake_dbus_system_bus_));
+ ON_CALL(dbus_, ProxyNewForName(fake_dbus_system_bus_,
+ StrEq(debugd::kDebugdServiceName),
+ StrEq(debugd::kDebugdServicePath),
+ StrEq(debugd::kDebugdInterface)))
+ .WillByDefault(Return(fake_dbus_debugd_proxy_));
+ }
+
+ void TearDown() override {
+ test_utils::RecursiveUnlinkDir(test_dir_);
+ EXPECT_EQ(0, MessageLoopRunMaxIterations(&loop_, 1));
+ }
+
+ public:
+ void ScheduleQuitMainLoop();
+
+ // Callbacks to run the different tests from the main loop.
+ void UpdateTestStart();
+ void UpdateTestVerify();
+ void RollbackTestStart(bool enterprise_rollback, bool valid_slot);
+ void RollbackTestVerify();
+ void PingOmahaTestStart();
+ void ReadScatterFactorFromPolicyTestStart();
+ void DecrementUpdateCheckCountTestStart();
+ void NoScatteringDoneDuringManualUpdateTestStart();
+ void P2PNotEnabledStart();
+ void P2PEnabledStart();
+ void P2PEnabledInteractiveStart();
+ void P2PEnabledStartingFailsStart();
+ void P2PEnabledHousekeepingFailsStart();
+
+ bool actual_using_p2p_for_downloading() {
+ return actual_using_p2p_for_downloading_;
+ }
+ bool actual_using_p2p_for_sharing() {
+ return actual_using_p2p_for_sharing_;
+ }
+
+ // TODO(deymo): Replace this with a FakeMessageLoop. Some of these tests use a
+ // real LibcurlHttpFetcher, which still requires a GlibMessageLoop.
+ chromeos::GlibMessageLoop loop_;
+
+ FakeSystemState fake_system_state_;
+ NiceMock<MockDBusWrapper> dbus_;
+ UpdateAttempterUnderTest attempter_;
+ NiceMock<MockActionProcessor>* processor_;
+ NiceMock<MockPrefs>* prefs_; // Shortcut to fake_system_state_->mock_prefs().
+ NiceMock<MockConnectionManager> mock_connection_manager;
+ // fake_dbus_xxx pointers will be non-null for comparison purposes, but won't
+ // be valid objects so don't try to use them.
+ DBusGConnection* fake_dbus_system_bus_;
+ DBusGProxy* fake_dbus_debugd_proxy_;
+
+ string test_dir_;
+
+ bool actual_using_p2p_for_downloading_;
+ bool actual_using_p2p_for_sharing_;
+};
+
+void UpdateAttempterTest::ScheduleQuitMainLoop() {
+ loop_.PostTask(FROM_HERE, base::Bind([this] { this->loop_.BreakLoop(); }));
+}
+
+TEST_F(UpdateAttempterTest, ActionCompletedDownloadTest) {
+ unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr));
+ fetcher->FailTransfer(503); // Sets the HTTP response code.
+ DownloadAction action(prefs_, nullptr, fetcher.release());
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0);
+ attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess);
+ EXPECT_EQ(503, attempter_.http_response_code());
+ EXPECT_EQ(UPDATE_STATUS_FINALIZING, attempter_.status());
+ ASSERT_EQ(nullptr, attempter_.error_event_.get());
+}
+
+TEST_F(UpdateAttempterTest, ActionCompletedErrorTest) {
+ MockAction action;
+ EXPECT_CALL(action, Type()).WillRepeatedly(Return("MockAction"));
+ attempter_.status_ = UPDATE_STATUS_DOWNLOADING;
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+ .WillOnce(Return(false));
+ attempter_.ActionCompleted(nullptr, &action, ErrorCode::kError);
+ ASSERT_NE(nullptr, attempter_.error_event_.get());
+}
+
+TEST_F(UpdateAttempterTest, ActionCompletedOmahaRequestTest) {
+ unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr));
+ fetcher->FailTransfer(500); // Sets the HTTP response code.
+ OmahaRequestAction action(&fake_system_state_, nullptr,
+ fetcher.release(), false);
+ ObjectCollectorAction<OmahaResponse> collector_action;
+ BondActions(&action, &collector_action);
+ OmahaResponse response;
+ response.poll_interval = 234;
+ action.SetOutputObject(response);
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0);
+ attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess);
+ EXPECT_EQ(500, attempter_.http_response_code());
+ EXPECT_EQ(UPDATE_STATUS_IDLE, attempter_.status());
+ EXPECT_EQ(234, attempter_.server_dictated_poll_interval_);
+ ASSERT_TRUE(attempter_.error_event_.get() == nullptr);
+}
+
+TEST_F(UpdateAttempterTest, RunAsRootConstructWithUpdatedMarkerTest) {
+ string test_update_completed_marker;
+ CHECK(utils::MakeTempFile(
+ "update_attempter_unittest-update_completed_marker-XXXXXX",
+ &test_update_completed_marker, nullptr));
+ ScopedPathUnlinker completed_marker_unlinker(test_update_completed_marker);
+ const base::FilePath marker(test_update_completed_marker);
+ EXPECT_EQ(0, base::WriteFile(marker, "", 0));
+ UpdateAttempterUnderTest attempter(&fake_system_state_, &dbus_,
+ test_update_completed_marker);
+ EXPECT_EQ(UPDATE_STATUS_UPDATED_NEED_REBOOT, attempter.status());
+}
+
+TEST_F(UpdateAttempterTest, GetErrorCodeForActionTest) {
+ extern ErrorCode GetErrorCodeForAction(AbstractAction* action,
+ ErrorCode code);
+ EXPECT_EQ(ErrorCode::kSuccess,
+ GetErrorCodeForAction(nullptr, ErrorCode::kSuccess));
+
+ FakeSystemState fake_system_state;
+ OmahaRequestAction omaha_request_action(&fake_system_state, nullptr,
+ nullptr, false);
+ EXPECT_EQ(ErrorCode::kOmahaRequestError,
+ GetErrorCodeForAction(&omaha_request_action, ErrorCode::kError));
+ OmahaResponseHandlerAction omaha_response_handler_action(&fake_system_state_);
+ EXPECT_EQ(ErrorCode::kOmahaResponseHandlerError,
+ GetErrorCodeForAction(&omaha_response_handler_action,
+ ErrorCode::kError));
+ FilesystemVerifierAction filesystem_verifier_action(
+ &fake_system_state_, PartitionType::kRootfs);
+ EXPECT_EQ(ErrorCode::kFilesystemVerifierError,
+ GetErrorCodeForAction(&filesystem_verifier_action,
+ ErrorCode::kError));
+ PostinstallRunnerAction postinstall_runner_action;
+ EXPECT_EQ(ErrorCode::kPostinstallRunnerError,
+ GetErrorCodeForAction(&postinstall_runner_action,
+ ErrorCode::kError));
+ MockAction action_mock;
+ EXPECT_CALL(action_mock, Type()).WillOnce(Return("MockAction"));
+ EXPECT_EQ(ErrorCode::kError,
+ GetErrorCodeForAction(&action_mock, ErrorCode::kError));
+}
+
+TEST_F(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest) {
+ attempter_.omaha_request_params_->set_delta_okay(true);
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+ .WillOnce(Return(false));
+ attempter_.DisableDeltaUpdateIfNeeded();
+ EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay());
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures - 1),
+ Return(true)));
+ attempter_.DisableDeltaUpdateIfNeeded();
+ EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay());
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures),
+ Return(true)));
+ attempter_.DisableDeltaUpdateIfNeeded();
+ EXPECT_FALSE(attempter_.omaha_request_params_->delta_okay());
+ EXPECT_CALL(*prefs_, GetInt64(_, _)).Times(0);
+ attempter_.DisableDeltaUpdateIfNeeded();
+ EXPECT_FALSE(attempter_.omaha_request_params_->delta_okay());
+}
+
+TEST_F(UpdateAttempterTest, MarkDeltaUpdateFailureTest) {
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+ .WillOnce(Return(false))
+ .WillOnce(DoAll(SetArgumentPointee<1>(-1), Return(true)))
+ .WillOnce(DoAll(SetArgumentPointee<1>(1), Return(true)))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures),
+ Return(true)));
+ EXPECT_CALL(*prefs_, SetInt64(Ne(kPrefsDeltaUpdateFailures), _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*prefs_, SetInt64(kPrefsDeltaUpdateFailures, 1)).Times(2);
+ EXPECT_CALL(*prefs_, SetInt64(kPrefsDeltaUpdateFailures, 2));
+ EXPECT_CALL(*prefs_, SetInt64(kPrefsDeltaUpdateFailures,
+ UpdateAttempter::kMaxDeltaUpdateFailures + 1));
+ for (int i = 0; i < 4; i ++)
+ attempter_.MarkDeltaUpdateFailure();
+}
+
+TEST_F(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest) {
+ EXPECT_CALL(*processor_, EnqueueAction(_)).Times(0);
+ EXPECT_CALL(*processor_, StartProcessing()).Times(0);
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(), UpdateFailed(_))
+ .Times(0);
+ OmahaResponse response;
+ string url1 = "http://url1";
+ response.payload_urls.push_back(url1);
+ response.payload_urls.push_back("https://url");
+ EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl())
+ .WillRepeatedly(Return(url1));
+ fake_system_state_.mock_payload_state()->SetResponse(response);
+ attempter_.ScheduleErrorEventAction();
+ EXPECT_EQ(url1, fake_system_state_.mock_payload_state()->GetCurrentUrl());
+}
+
+TEST_F(UpdateAttempterTest, ScheduleErrorEventActionTest) {
+ EXPECT_CALL(*processor_,
+ EnqueueAction(Property(&AbstractAction::Type,
+ OmahaRequestAction::StaticType())));
+ EXPECT_CALL(*processor_, StartProcessing());
+ ErrorCode err = ErrorCode::kError;
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(), UpdateFailed(err));
+ attempter_.error_event_.reset(new OmahaEvent(OmahaEvent::kTypeUpdateComplete,
+ OmahaEvent::kResultError,
+ err));
+ attempter_.ScheduleErrorEventAction();
+ EXPECT_EQ(UPDATE_STATUS_REPORTING_ERROR_EVENT, attempter_.status());
+}
+
+namespace {
+// Actions that will be built as part of an update check.
+const string kUpdateActionTypes[] = { // NOLINT(runtime/string)
+ OmahaRequestAction::StaticType(),
+ OmahaResponseHandlerAction::StaticType(),
+ FilesystemVerifierAction::StaticType(),
+ FilesystemVerifierAction::StaticType(),
+ OmahaRequestAction::StaticType(),
+ DownloadAction::StaticType(),
+ OmahaRequestAction::StaticType(),
+ FilesystemVerifierAction::StaticType(),
+ FilesystemVerifierAction::StaticType(),
+ PostinstallRunnerAction::StaticType(),
+ OmahaRequestAction::StaticType()
+};
+
+// Actions that will be built as part of a user-initiated rollback.
+const string kRollbackActionTypes[] = { // NOLINT(runtime/string)
+ InstallPlanAction::StaticType(),
+ PostinstallRunnerAction::StaticType(),
+};
+
+} // namespace
+
+void UpdateAttempterTest::UpdateTestStart() {
+ attempter_.set_http_response_code(200);
+
+ // Expect that the device policy is loaded by the UpdateAttempter at some
+ // point by calling RefreshDevicePolicy.
+ policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+ attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+ EXPECT_CALL(*device_policy, LoadPolicy())
+ .Times(testing::AtLeast(1)).WillRepeatedly(Return(true));
+
+ {
+ InSequence s;
+ for (size_t i = 0; i < arraysize(kUpdateActionTypes); ++i) {
+ EXPECT_CALL(*processor_,
+ EnqueueAction(Property(&AbstractAction::Type,
+ kUpdateActionTypes[i])));
+ }
+ EXPECT_CALL(*processor_, StartProcessing());
+ }
+
+ attempter_.Update("", "", "", "", false, false);
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::UpdateTestVerify,
+ base::Unretained(this)));
+}
+
+void UpdateAttempterTest::UpdateTestVerify() {
+ EXPECT_EQ(0, attempter_.http_response_code());
+ EXPECT_EQ(&attempter_, processor_->delegate());
+ EXPECT_EQ(arraysize(kUpdateActionTypes), attempter_.actions_.size());
+ for (size_t i = 0; i < arraysize(kUpdateActionTypes); ++i) {
+ EXPECT_EQ(kUpdateActionTypes[i], attempter_.actions_[i]->Type());
+ }
+ EXPECT_EQ(attempter_.response_handler_action_.get(),
+ attempter_.actions_[1].get());
+ DownloadAction* download_action =
+ dynamic_cast<DownloadAction*>(attempter_.actions_[5].get());
+ ASSERT_NE(nullptr, download_action);
+ EXPECT_EQ(&attempter_, download_action->delegate());
+ EXPECT_EQ(UPDATE_STATUS_CHECKING_FOR_UPDATE, attempter_.status());
+ loop_.BreakLoop();
+}
+
+void UpdateAttempterTest::RollbackTestStart(
+ bool enterprise_rollback, bool valid_slot) {
+ // Create a device policy so that we can change settings.
+ policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+ attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy);
+
+ if (!valid_slot) {
+ // References bootable kernels in fake_hardware.h
+ string rollback_kernel = "/dev/sdz2";
+ LOG(INFO) << "Test Mark Unbootable: " << rollback_kernel;
+ fake_system_state_.fake_hardware()->MarkKernelUnbootable(
+ rollback_kernel);
+ }
+
+ bool is_rollback_allowed = false;
+
+ // We only allow rollback on devices that are not enterprise enrolled and
+ // which have a valid slot to rollback to.
+ if (!enterprise_rollback && valid_slot) {
+ is_rollback_allowed = true;
+ }
+
+ if (enterprise_rollback) {
+ // We return an empty owner as this is an enterprise.
+ EXPECT_CALL(*device_policy, GetOwner(_)).WillRepeatedly(
+ DoAll(SetArgumentPointee<0>(string("")),
+ Return(true)));
+ } else {
+ // We return a fake owner as this is an owned consumer device.
+ EXPECT_CALL(*device_policy, GetOwner(_)).WillRepeatedly(
+ DoAll(SetArgumentPointee<0>(string("fake.mail@fake.com")),
+ Return(true)));
+ }
+
+ if (is_rollback_allowed) {
+ InSequence s;
+ for (size_t i = 0; i < arraysize(kRollbackActionTypes); ++i) {
+ EXPECT_CALL(*processor_,
+ EnqueueAction(Property(&AbstractAction::Type,
+ kRollbackActionTypes[i])));
+ }
+ EXPECT_CALL(*processor_, StartProcessing());
+
+ EXPECT_TRUE(attempter_.Rollback(true));
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::RollbackTestVerify,
+ base::Unretained(this)));
+ } else {
+ EXPECT_FALSE(attempter_.Rollback(true));
+ loop_.BreakLoop();
+ }
+}
+
+void UpdateAttempterTest::RollbackTestVerify() {
+ // Verifies the actions that were enqueued.
+ EXPECT_EQ(&attempter_, processor_->delegate());
+ EXPECT_EQ(arraysize(kRollbackActionTypes), attempter_.actions_.size());
+ for (size_t i = 0; i < arraysize(kRollbackActionTypes); ++i) {
+ EXPECT_EQ(kRollbackActionTypes[i], attempter_.actions_[i]->Type());
+ }
+ EXPECT_EQ(UPDATE_STATUS_ATTEMPTING_ROLLBACK, attempter_.status());
+ InstallPlanAction* install_plan_action =
+ dynamic_cast<InstallPlanAction*>(attempter_.actions_[0].get());
+ InstallPlan* install_plan = install_plan_action->install_plan();
+ // Matches fake_hardware.h -> rollback should move from kernel/boot device
+ // pair to other pair.
+ EXPECT_EQ(install_plan->install_path, string("/dev/sdz3"));
+ EXPECT_EQ(install_plan->kernel_install_path, string("/dev/sdz2"));
+ EXPECT_EQ(install_plan->powerwash_required, true);
+ loop_.BreakLoop();
+}
+
+TEST_F(UpdateAttempterTest, UpdateTest) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::UpdateTestStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, RollbackTest) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::RollbackTestStart,
+ base::Unretained(this),
+ false, true));
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, InvalidSlotRollbackTest) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::RollbackTestStart,
+ base::Unretained(this),
+ false, false));
+ loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, EnterpriseRollbackTest) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::RollbackTestStart,
+ base::Unretained(this),
+ true, true));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::PingOmahaTestStart() {
+ EXPECT_CALL(*processor_,
+ EnqueueAction(Property(&AbstractAction::Type,
+ OmahaRequestAction::StaticType())));
+ EXPECT_CALL(*processor_, StartProcessing());
+ attempter_.PingOmaha();
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, PingOmahaTest) {
+ EXPECT_FALSE(attempter_.waiting_for_scheduled_check_);
+ EXPECT_FALSE(attempter_.schedule_updates_called());
+ // Disable scheduling of subsequnet checks; we're using the DefaultPolicy in
+ // testing, which is more permissive than we want to handle here.
+ attempter_.DisableScheduleUpdates();
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::PingOmahaTestStart,
+ base::Unretained(this)));
+ chromeos::MessageLoopRunMaxIterations(&loop_, 100);
+ EXPECT_EQ(UPDATE_STATUS_UPDATED_NEED_REBOOT, attempter_.status());
+ EXPECT_TRUE(attempter_.schedule_updates_called());
+}
+
+TEST_F(UpdateAttempterTest, CreatePendingErrorEventTest) {
+ MockAction action;
+ const ErrorCode kCode = ErrorCode::kDownloadTransferError;
+ attempter_.CreatePendingErrorEvent(&action, kCode);
+ ASSERT_NE(nullptr, attempter_.error_event_.get());
+ EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type);
+ EXPECT_EQ(OmahaEvent::kResultError, attempter_.error_event_->result);
+ EXPECT_EQ(
+ static_cast<ErrorCode>(static_cast<int>(kCode) |
+ static_cast<int>(ErrorCode::kTestOmahaUrlFlag)),
+ attempter_.error_event_->error_code);
+}
+
+TEST_F(UpdateAttempterTest, CreatePendingErrorEventResumedTest) {
+ OmahaResponseHandlerAction *response_action =
+ new OmahaResponseHandlerAction(&fake_system_state_);
+ response_action->install_plan_.is_resume = true;
+ attempter_.response_handler_action_.reset(response_action);
+ MockAction action;
+ const ErrorCode kCode = ErrorCode::kInstallDeviceOpenError;
+ attempter_.CreatePendingErrorEvent(&action, kCode);
+ ASSERT_NE(nullptr, attempter_.error_event_.get());
+ EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type);
+ EXPECT_EQ(OmahaEvent::kResultError, attempter_.error_event_->result);
+ EXPECT_EQ(
+ static_cast<ErrorCode>(
+ static_cast<int>(kCode) |
+ static_cast<int>(ErrorCode::kResumedFlag) |
+ static_cast<int>(ErrorCode::kTestOmahaUrlFlag)),
+ attempter_.error_event_->error_code);
+}
+
+TEST_F(UpdateAttempterTest, P2PNotStartedAtStartupWhenNotEnabled) {
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(false);
+ EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()).Times(0);
+ attempter_.UpdateEngineStarted();
+}
+
+TEST_F(UpdateAttempterTest, P2PNotStartedAtStartupWhenEnabledButNotSharing) {
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()).Times(0);
+ attempter_.UpdateEngineStarted();
+}
+
+TEST_F(UpdateAttempterTest, P2PStartedAtStartupWhenEnabledAndSharing) {
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ mock_p2p_manager.fake().SetCountSharedFilesResult(1);
+ EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning());
+ attempter_.UpdateEngineStarted();
+}
+
+TEST_F(UpdateAttempterTest, P2PNotEnabled) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::P2PNotEnabledStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::P2PNotEnabledStart() {
+ // If P2P is not enabled, check that we do not attempt housekeeping
+ // and do not convey that p2p is to be used.
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(false);
+ EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0);
+ attempter_.Update("", "", "", "", false, false);
+ EXPECT_FALSE(actual_using_p2p_for_downloading_);
+ EXPECT_FALSE(actual_using_p2p_for_sharing());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabledStartingFails) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::P2PEnabledStartingFailsStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledStartingFailsStart() {
+ // If p2p is enabled, but starting it fails ensure we don't do
+ // any housekeeping and do not convey that p2p should be used.
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ mock_p2p_manager.fake().SetEnsureP2PRunningResult(false);
+ mock_p2p_manager.fake().SetPerformHousekeepingResult(false);
+ EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0);
+ attempter_.Update("", "", "", "", false, false);
+ EXPECT_FALSE(actual_using_p2p_for_downloading());
+ EXPECT_FALSE(actual_using_p2p_for_sharing());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabledHousekeepingFails) {
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind(&UpdateAttempterTest::P2PEnabledHousekeepingFailsStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledHousekeepingFailsStart() {
+ // If p2p is enabled, starting it works but housekeeping fails, ensure
+ // we do not convey p2p is to be used.
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
+ mock_p2p_manager.fake().SetPerformHousekeepingResult(false);
+ EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
+ attempter_.Update("", "", "", "", false, false);
+ EXPECT_FALSE(actual_using_p2p_for_downloading());
+ EXPECT_FALSE(actual_using_p2p_for_sharing());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabled) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::P2PEnabledStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledStart() {
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ // If P2P is enabled and starting it works, check that we performed
+ // housekeeping and that we convey p2p should be used.
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
+ mock_p2p_manager.fake().SetPerformHousekeepingResult(true);
+ EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
+ attempter_.Update("", "", "", "", false, false);
+ EXPECT_TRUE(actual_using_p2p_for_downloading());
+ EXPECT_TRUE(actual_using_p2p_for_sharing());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabledInteractive) {
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&UpdateAttempterTest::P2PEnabledInteractiveStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledInteractiveStart() {
+ MockP2PManager mock_p2p_manager;
+ fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+ // For an interactive check, if P2P is enabled and starting it
+ // works, check that we performed housekeeping and that we convey
+ // p2p should be used for sharing but NOT for downloading.
+ mock_p2p_manager.fake().SetP2PEnabled(true);
+ mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
+ mock_p2p_manager.fake().SetPerformHousekeepingResult(true);
+ EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
+ attempter_.Update("", "", "", "", false, true /* interactive */);
+ EXPECT_FALSE(actual_using_p2p_for_downloading());
+ EXPECT_TRUE(actual_using_p2p_for_sharing());
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, ReadScatterFactorFromPolicy) {
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind(&UpdateAttempterTest::ReadScatterFactorFromPolicyTestStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+// Tests that the scatter_factor_in_seconds value is properly fetched
+// from the device policy.
+void UpdateAttempterTest::ReadScatterFactorFromPolicyTestStart() {
+ int64_t scatter_factor_in_seconds = 36000;
+
+ policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+ attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy);
+
+ EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+ .WillRepeatedly(DoAll(
+ SetArgumentPointee<0>(scatter_factor_in_seconds),
+ Return(true)));
+
+ attempter_.Update("", "", "", "", false, false);
+ EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
+
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, DecrementUpdateCheckCountTest) {
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind(&UpdateAttempterTest::DecrementUpdateCheckCountTestStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::DecrementUpdateCheckCountTestStart() {
+ // Tests that the scatter_factor_in_seconds value is properly fetched
+ // from the device policy and is decremented if value > 0.
+ int64_t initial_value = 5;
+ FakePrefs fake_prefs;
+ attempter_.prefs_ = &fake_prefs;
+
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+
+ EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value));
+
+ int64_t scatter_factor_in_seconds = 10;
+
+ policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+ attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy);
+
+ EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+ .WillRepeatedly(DoAll(
+ SetArgumentPointee<0>(scatter_factor_in_seconds),
+ Return(true)));
+
+ attempter_.Update("", "", "", "", false, false);
+ EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
+
+ // Make sure the file still exists.
+ EXPECT_TRUE(fake_prefs.Exists(kPrefsUpdateCheckCount));
+
+ int64_t new_value;
+ EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &new_value));
+ EXPECT_EQ(initial_value - 1, new_value);
+
+ EXPECT_TRUE(
+ attempter_.omaha_request_params_->update_check_count_wait_enabled());
+
+ // However, if the count is already 0, it's not decremented. Test that.
+ initial_value = 0;
+ EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value));
+ attempter_.Update("", "", "", "", false, false);
+ EXPECT_TRUE(fake_prefs.Exists(kPrefsUpdateCheckCount));
+ EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &new_value));
+ EXPECT_EQ(initial_value, new_value);
+
+ ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, NoScatteringDoneDuringManualUpdateTestStart) {
+ loop_.PostTask(FROM_HERE, base::Bind(
+ &UpdateAttempterTest::NoScatteringDoneDuringManualUpdateTestStart,
+ base::Unretained(this)));
+ loop_.Run();
+}
+
+void UpdateAttempterTest::NoScatteringDoneDuringManualUpdateTestStart() {
+ // Tests that no scattering logic is enabled if the update check
+ // is manually done (as opposed to a scheduled update check)
+ int64_t initial_value = 8;
+ FakePrefs fake_prefs;
+ attempter_.prefs_ = &fake_prefs;
+
+ fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+ fake_system_state_.set_prefs(&fake_prefs);
+
+ EXPECT_TRUE(fake_prefs.SetInt64(kPrefsWallClockWaitPeriod, initial_value));
+ EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value));
+
+ // make sure scatter_factor is non-zero as scattering is disabled
+ // otherwise.
+ int64_t scatter_factor_in_seconds = 50;
+
+ policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+ attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy);
+
+ EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+ .WillRepeatedly(DoAll(
+ SetArgumentPointee<0>(scatter_factor_in_seconds),
+ Return(true)));
+
+ // Trigger an interactive check so we can test that scattering is disabled.
+ attempter_.Update("", "", "", "", false, true);
+ EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
+
+ // Make sure scattering is disabled for manual (i.e. user initiated) update
+ // checks and all artifacts are removed.
+ EXPECT_FALSE(
+ attempter_.omaha_request_params_->wall_clock_based_wait_enabled());
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsWallClockWaitPeriod));
+ EXPECT_EQ(0, attempter_.omaha_request_params_->waiting_period().InSeconds());
+ EXPECT_FALSE(
+ attempter_.omaha_request_params_->update_check_count_wait_enabled());
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsUpdateCheckCount));
+
+ ScheduleQuitMainLoop();
+}
+
+// Checks that we only report daily metrics at most every 24 hours.
+TEST_F(UpdateAttempterTest, ReportDailyMetrics) {
+ FakeClock fake_clock;
+ FakePrefs fake_prefs;
+
+ fake_system_state_.set_clock(&fake_clock);
+ fake_system_state_.set_prefs(&fake_prefs);
+
+ Time epoch = Time::FromInternalValue(0);
+ fake_clock.SetWallclockTime(epoch);
+
+ // If there is no kPrefsDailyMetricsLastReportedAt state variable,
+ // we should report.
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+ // We should not report again if no time has passed.
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // We should not report if only 10 hours has passed.
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(10));
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // We should not report if only 24 hours - 1 sec has passed.
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(24) -
+ TimeDelta::FromSeconds(1));
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // We should report if 24 hours has passed.
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(24));
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+
+ // But then we should not report again..
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // .. until another 24 hours has passed
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(47));
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(48));
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // .. and another 24 hours
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(71));
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(72));
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // If the span between time of reporting and present time is
+ // negative, we report. This is in order to reset the timestamp and
+ // avoid an edge condition whereby a distant point in the future is
+ // in the state variable resulting in us never ever reporting again.
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(71));
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+ // In this case we should not update until the clock reads 71 + 24 = 95.
+ // Check that.
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(94));
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+ fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(95));
+ EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+ EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+}
+
+TEST_F(UpdateAttempterTest, BootTimeInUpdateMarkerFile) {
+ const string update_completed_marker = test_dir_ + "/update-completed-marker";
+ UpdateAttempterUnderTest attempter(&fake_system_state_, &dbus_,
+ update_completed_marker);
+
+ FakeClock fake_clock;
+ fake_clock.SetBootTime(Time::FromTimeT(42));
+ fake_system_state_.set_clock(&fake_clock);
+
+ Time boot_time;
+ EXPECT_FALSE(attempter.GetBootTimeAtUpdate(&boot_time));
+
+ attempter.WriteUpdateCompletedMarker();
+
+ EXPECT_TRUE(attempter.GetBootTimeAtUpdate(&boot_time));
+ EXPECT_EQ(boot_time.ToTimeT(), 42);
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedUnofficial) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedOfficialDevmode) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(false);
+ EXPECT_CALL(dbus_, ProxyCall_0_1(fake_dbus_debugd_proxy_,
+ StrEq(debugd::kQueryDevFeatures),
+ _, A<gint*>()))
+ .WillRepeatedly(DoAll(SetArgumentPointee<3>(0),
+ Return(true)));
+ EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedOfficialNormal) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(true);
+ // debugd should not be queried in this case.
+ EXPECT_CALL(dbus_, ProxyCall_0_1(fake_dbus_debugd_proxy_,
+ StrEq(debugd::kQueryDevFeatures),
+ _, A<gint*>()))
+ .Times(0);
+ EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedDebugdDisabled) {
+ using debugd::DEV_FEATURES_DISABLED;
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(false);
+ EXPECT_CALL(dbus_, ProxyCall_0_1(fake_dbus_debugd_proxy_,
+ StrEq(debugd::kQueryDevFeatures),
+ _, A<gint*>()))
+ .WillRepeatedly(DoAll(SetArgumentPointee<3>(DEV_FEATURES_DISABLED),
+ Return(true)));
+ EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedDebugdFailure) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(false);
+ EXPECT_CALL(dbus_, ProxyCall_0_1(fake_dbus_debugd_proxy_,
+ StrEq(debugd::kQueryDevFeatures),
+ _, A<gint*>()))
+ .WillRepeatedly(Return(false));
+ EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateAUTest) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(true);
+ attempter_.CheckForUpdate("", "autest", true);
+ EXPECT_EQ(chromeos_update_engine::kAUTestOmahaUrl,
+ attempter_.forced_omaha_url());
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateScheduledAUTest) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(true);
+ attempter_.CheckForUpdate("", "autest-scheduled", true);
+ EXPECT_EQ(chromeos_update_engine::kAUTestOmahaUrl,
+ attempter_.forced_omaha_url());
+}
+
+} // namespace chromeos_update_engine
diff --git a/update_engine.conf b/update_engine.conf
new file mode 100644
index 0000000..dc65ebe
--- /dev/null
+++ b/update_engine.conf
@@ -0,0 +1 @@
+PAYLOAD_MINOR_VERSION=2
diff --git a/update_engine.gyp b/update_engine.gyp
new file mode 100644
index 0000000..d3232a3
--- /dev/null
+++ b/update_engine.gyp
@@ -0,0 +1,432 @@
+{
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'libchrome-<(libbase_ver)',
+ 'libchromeos-<(libbase_ver)',
+ ],
+ # Defaults if the -DUSE_* flags are not passed to gyp is 0. You can set
+ # the default value for the USE flag in the ebuild.
+ 'USE_hwid_override%': '0',
+ 'USE_power_management%': '0',
+ },
+ 'cflags': [
+ '-g',
+ '-ffunction-sections',
+ '-Wall',
+ '-Wextra',
+ '-Werror',
+ '-Wno-unused-parameter',
+ '-Wno-deprecated-register',
+ ],
+ 'cflags_cc': [
+ '-fno-strict-aliasing',
+ '-Wnon-virtual-dtor',
+ ],
+ 'ldflags': [
+ '-Wl,--gc-sections',
+ ],
+ 'defines': [
+ '_POSIX_C_SOURCE=199309L',
+ 'USE_HWID_OVERRIDE=<(USE_hwid_override)',
+ 'USE_MTD=<(USE_mtd)',
+ 'USE_POWER_MANAGEMENT=<(USE_power_management)',
+ ],
+ },
+ 'targets': [
+ # Protobufs.
+ {
+ 'target_name': 'update_metadata-protos',
+ 'type': 'static_library',
+ 'variables': {
+ 'proto_in_dir': '.',
+ 'proto_out_dir': 'include/update_engine',
+ 'exported_deps': [
+ 'protobuf-lite',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'update_metadata.proto'
+ ],
+ 'includes': ['../common-mk/protoc.gypi'],
+ },
+ # D-Bus glib bindings.
+ {
+ 'target_name': 'update_engine-dbus-server',
+ 'type': 'none',
+ 'variables': {
+ 'dbus_glib_type': 'server',
+ 'dbus_glib_out_dir': 'include/update_engine',
+ 'dbus_glib_prefix': 'update_engine_service',
+ },
+ 'sources': [
+ 'dbus_bindings/org.chromium.UpdateEngineInterface.xml',
+ ],
+ 'includes': ['../common-mk/dbus_glib.gypi'],
+ },
+ # The main static_library with all the code.
+ {
+ 'target_name': 'libupdate_engine',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'update_metadata-protos',
+ ],
+ 'variables': {
+ 'exported_deps': [
+ 'dbus-1',
+ 'dbus-glib-1',
+ 'glib-2.0',
+ 'gthread-2.0',
+ 'libchrome-<(libbase_ver)',
+ 'libchromeos-<(libbase_ver)',
+ 'libcrypto',
+ 'libcurl',
+ 'libmetrics-<(libbase_ver)',
+ 'libssl',
+ 'expat'
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'link_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ 'libraries': [
+ '-lbz2',
+ '-lpolicy-<(libbase_ver)',
+ '-lrootdev',
+ '-lrt',
+ '-lvboot_host',
+ ],
+ },
+ 'sources': [
+ 'action_processor.cc',
+ 'bzip.cc',
+ 'bzip_extent_writer.cc',
+ 'certificate_checker.cc',
+ 'chrome_browser_proxy_resolver.cc',
+ 'clock.cc',
+ 'connection_manager.cc',
+ 'constants.cc',
+ 'dbus_service.cc',
+ 'delta_performer.cc',
+ 'download_action.cc',
+ 'extent_writer.cc',
+ 'file_descriptor.cc',
+ 'file_writer.cc',
+ 'filesystem_verifier_action.cc',
+ 'glib_utils.cc',
+ 'hardware.cc',
+ 'http_common.cc',
+ 'http_fetcher.cc',
+ 'hwid_override.cc',
+ 'install_plan.cc',
+ 'libcurl_http_fetcher.cc',
+ 'metrics.cc',
+ 'multi_range_http_fetcher.cc',
+ 'omaha_hash_calculator.cc',
+ 'omaha_request_action.cc',
+ 'omaha_request_params.cc',
+ 'omaha_response_handler_action.cc',
+ 'p2p_manager.cc',
+ 'payload_constants.cc',
+ 'payload_state.cc',
+ 'payload_verifier.cc',
+ 'postinstall_runner_action.cc',
+ 'prefs.cc',
+ 'proxy_resolver.cc',
+ 'real_system_state.cc',
+ 'subprocess.cc',
+ 'terminator.cc',
+ 'update_attempter.cc',
+ 'update_manager/boxed_value.cc',
+ 'update_manager/chromeos_policy.cc',
+ 'update_manager/default_policy.cc',
+ 'update_manager/evaluation_context.cc',
+ 'update_manager/policy.cc',
+ 'update_manager/real_config_provider.cc',
+ 'update_manager/real_device_policy_provider.cc',
+ 'update_manager/real_random_provider.cc',
+ 'update_manager/real_shill_provider.cc',
+ 'update_manager/real_system_provider.cc',
+ 'update_manager/real_time_provider.cc',
+ 'update_manager/real_updater_provider.cc',
+ 'update_manager/state_factory.cc',
+ 'update_manager/update_manager.cc',
+ 'utils.cc',
+ ],
+ 'conditions': [
+ ['USE_mtd == 1', {
+ 'sources': [
+ 'mtd_file_descriptor.cc',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lmtdutils',
+ ],
+ },
+ }],
+ ],
+ },
+ # update_engine daemon.
+ {
+ 'target_name': 'update_engine',
+ 'type': 'executable',
+ 'dependencies': [
+ 'libupdate_engine',
+ 'update_engine-dbus-server',
+ ],
+ 'sources': [
+ 'main.cc',
+ ]
+ },
+ # update_engine console client.
+ {
+ 'target_name': 'update_engine_client',
+ 'type': 'executable',
+ 'variables': {
+ 'exported_deps': [
+ 'libchrome-<(libbase_ver)',
+ 'libchromeos-<(libbase_ver)',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'link_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'update_engine_client.cc',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'update_engine-dbus-proxies',
+ 'variables': {
+ 'dbus_service_config': 'dbus_bindings/dbus-service-config.json',
+ 'proxy_output_file': 'include/update_engine/dbus_proxies.h'
+ },
+ 'sources': [
+ 'dbus_bindings/org.chromium.UpdateEngineInterface.xml',
+ ],
+ 'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+ },
+ ]
+ },
+ # server-side code. This is used for delta_generator and unittests but not
+ # for any client code.
+ {
+ 'target_name': 'libpayload_generator',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'update_metadata-protos',
+ ],
+ 'variables': {
+ 'exported_deps': [
+ 'ext2fs',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'link_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ 'libraries': [
+ '-lvboot_host',
+ ],
+ },
+ 'sources': [
+ 'payload_generator/ab_generator.cc',
+ 'payload_generator/annotated_operation.cc',
+ 'payload_generator/cycle_breaker.cc',
+ 'payload_generator/delta_diff_generator.cc',
+ 'payload_generator/delta_diff_utils.cc',
+ 'payload_generator/ext2_filesystem.cc',
+ 'payload_generator/extent_ranges.cc',
+ 'payload_generator/extent_utils.cc',
+ 'payload_generator/full_update_generator.cc',
+ 'payload_generator/graph_types.cc',
+ 'payload_generator/graph_utils.cc',
+ 'payload_generator/inplace_generator.cc',
+ 'payload_generator/payload_file.cc',
+ 'payload_generator/payload_generation_config.cc',
+ 'payload_generator/payload_signer.cc',
+ 'payload_generator/raw_filesystem.cc',
+ 'payload_generator/tarjan.cc',
+ 'payload_generator/topological_sort.cc',
+ 'payload_generator/verity_utils.cc',
+ ],
+ },
+ # server-side delta generator.
+ {
+ 'target_name': 'delta_generator',
+ 'type': 'executable',
+ 'dependencies': [
+ 'libupdate_engine',
+ 'libpayload_generator',
+ ],
+ 'link_settings': {
+ 'ldflags!': [
+ '-pie',
+ ],
+ },
+ 'sources': [
+ 'payload_generator/generate_delta_main.cc',
+ ]
+ },
+ ],
+ 'conditions': [
+ ['USE_test == 1', {
+ 'targets': [
+ # Public keys used for unit testing.
+ {
+ 'target_name': 'update_engine-testkeys',
+ 'type': 'none',
+ 'variables': {
+ 'openssl_pem_in_dir': '.',
+ 'openssl_pem_out_dir': 'include/update_engine',
+ },
+ 'sources': [
+ 'unittest_key.pem',
+ 'unittest_key2.pem',
+ ],
+ 'includes': ['../common-mk/openssl_pem.gypi'],
+ },
+ # Sample images used for testing.
+ {
+ 'target_name': 'update_engine-test_images',
+ 'type': 'none',
+ 'variables': {
+ 'image_out_dir': '.',
+ },
+ 'sources': [
+ 'sample_images/disk_ext2_1k.txt',
+ 'sample_images/disk_ext2_4k.txt',
+ 'sample_images/disk_ext2_ue_settings.txt',
+ ],
+ 'includes': ['generate_image.gypi'],
+ },
+ # Test HTTP Server.
+ {
+ 'target_name': 'test_http_server',
+ 'type': 'executable',
+ 'dependencies': ['libupdate_engine'],
+ 'sources': [
+ 'test_http_server.cc',
+ ]
+ },
+ # Main unittest file.
+ {
+ 'target_name': 'update_engine_unittests',
+ 'type': 'executable',
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'variables': {
+ 'deps': [
+ 'libchromeos-test-<(libbase_ver)',
+ 'libchrome-test-<(libbase_ver)',
+ ],
+ },
+ 'dependencies': [
+ 'libupdate_engine',
+ 'libpayload_generator',
+ ],
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'action_pipe_unittest.cc',
+ 'action_processor_unittest.cc',
+ 'action_unittest.cc',
+ 'bzip_extent_writer_unittest.cc',
+ 'certificate_checker_unittest.cc',
+ 'chrome_browser_proxy_resolver_unittest.cc',
+ 'connection_manager_unittest.cc',
+ 'delta_performer_unittest.cc',
+ 'download_action_unittest.cc',
+ 'extent_writer_unittest.cc',
+ 'fake_prefs.cc',
+ 'fake_system_state.cc',
+ 'file_writer_unittest.cc',
+ 'filesystem_verifier_action_unittest.cc',
+ 'http_fetcher_unittest.cc',
+ 'hwid_override_unittest.cc',
+ 'mock_http_fetcher.cc',
+ 'omaha_hash_calculator_unittest.cc',
+ 'omaha_request_action_unittest.cc',
+ 'omaha_request_params_unittest.cc',
+ 'omaha_response_handler_action_unittest.cc',
+ 'p2p_manager_unittest.cc',
+ 'payload_generator/ab_generator_unittest.cc',
+ 'payload_generator/cycle_breaker_unittest.cc',
+ 'payload_generator/delta_diff_utils_unittest.cc',
+ 'payload_generator/ext2_filesystem_unittest.cc',
+ 'payload_generator/extent_ranges_unittest.cc',
+ 'payload_generator/extent_utils_unittest.cc',
+ 'payload_generator/fake_filesystem.cc',
+ 'payload_generator/full_update_generator_unittest.cc',
+ 'payload_generator/graph_utils_unittest.cc',
+ 'payload_generator/inplace_generator_unittest.cc',
+ 'payload_generator/payload_signer_unittest.cc',
+ 'payload_generator/payload_file_unittest.cc',
+ 'payload_generator/tarjan_unittest.cc',
+ 'payload_generator/topological_sort_unittest.cc',
+ 'payload_generator/verity_utils_unittest.cc',
+ 'payload_state_unittest.cc',
+ 'postinstall_runner_action_unittest.cc',
+ 'prefs_unittest.cc',
+ 'subprocess_unittest.cc',
+ 'terminator_unittest.cc',
+ 'test_utils.cc',
+ 'test_utils_unittest.cc',
+ 'update_attempter_unittest.cc',
+ 'update_manager/boxed_value_unittest.cc',
+ 'update_manager/chromeos_policy_unittest.cc',
+ 'update_manager/evaluation_context_unittest.cc',
+ 'update_manager/generic_variables_unittest.cc',
+ 'update_manager/prng_unittest.cc',
+ 'update_manager/real_config_provider_unittest.cc',
+ 'update_manager/real_device_policy_provider_unittest.cc',
+ 'update_manager/real_random_provider_unittest.cc',
+ 'update_manager/real_shill_provider_unittest.cc',
+ 'update_manager/real_system_provider_unittest.cc',
+ 'update_manager/real_time_provider_unittest.cc',
+ 'update_manager/real_updater_provider_unittest.cc',
+ 'update_manager/umtest_utils.cc',
+ 'update_manager/update_manager_unittest.cc',
+ 'update_manager/variable_unittest.cc',
+ 'utils_unittest.cc',
+ 'zip_unittest.cc',
+ # Main entry point for runnning tests.
+ 'testrunner.cc',
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/update_engine_client.cc b/update_engine_client.cc
new file mode 100644
index 0000000..d0fee7a
--- /dev/null
+++ b/update_engine_client.cc
@@ -0,0 +1,658 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <inttypes.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <chromeos/daemons/dbus_daemon.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/flag_helper.h>
+#include <dbus/bus.h>
+
+#include "update_engine/dbus_constants.h"
+#include "update_engine/dbus_proxies.h"
+
+using chromeos_update_engine::kAttemptUpdateFlagNonInteractive;
+using chromeos_update_engine::kUpdateEngineServiceName;
+using std::string;
+
+namespace {
+
+// Constant to signal that we need to continue running the daemon after
+// initialization.
+const int kContinueRunning = -1;
+
+class UpdateEngineClient : public chromeos::DBusDaemon {
+ public:
+ UpdateEngineClient(int argc, char** argv) : argc_(argc), argv_(argv) {}
+ ~UpdateEngineClient() override = default;
+
+ protected:
+ int OnInit() override {
+ int ret = DBusDaemon::OnInit();
+ if (ret != EX_OK)
+ return ret;
+ if (!InitProxy())
+ return 1;
+ // Wait for the UpdateEngine to be available or timeout.
+ proxy_->GetObjectProxy()->WaitForServiceToBeAvailable(
+ base::Bind(&UpdateEngineClient::OnServiceAvailable,
+ base::Unretained(this)));
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UpdateEngineClient::OnServiceAvailableTimeout,
+ base::Unretained(this)),
+ base::TimeDelta::FromSeconds(10));
+ return EX_OK;
+ }
+
+ private:
+ bool InitProxy();
+
+ // Callback called when the UpdateEngine service becomes available.
+ void OnServiceAvailable(bool service_is_available);
+
+ // Callback called when the UpdateEngine service doesn't become available
+ // after a timeout.
+ void OnServiceAvailableTimeout();
+
+
+ // Callback called when a StatusUpdate signal is received.
+ void OnStatusUpdateSignal(int64_t last_checked_time,
+ double progress,
+ const string& current_operation,
+ const string& new_version,
+ int64_t new_size);
+ // Callback called when the OnStatusUpdateSignal() handler is registered.
+ void OnStatusUpdateSignalRegistration(const string& interface,
+ const string& signal_name,
+ bool success);
+
+ // Registers a callback that prints on stderr the received StatusUpdate
+ // signals.
+ // The daemon should continue running for this to work.
+ void WatchForUpdates();
+
+ void ResetStatus();
+
+ // Show the status of the update engine in stdout.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ void ShowStatus();
+
+ // Return the current operation status, such as UPDATE_STATUS_IDLE.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ string GetCurrentOperation();
+
+ void Rollback(bool rollback);
+ string GetRollbackPartition();
+ string GetKernelDevices();
+ void CheckForUpdates(const string& app_version,
+ const string& omaha_url,
+ bool interactive);
+
+ // Reboot the device if a reboot is needed.
+ // Blocking call. Ignores failures.
+ void RebootIfNeeded();
+
+ // Getter and setter for the target channel. If |get_current_channel| is true,
+ // the current channel instead of the target channel will be returned.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ void SetTargetChannel(const string& target_channel, bool allow_powerwash);
+ string GetChannel(bool get_current_channel);
+
+ // Getter and setter for the updates over cellular connections.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ void SetUpdateOverCellularPermission(bool allowed);
+ bool GetUpdateOverCellularPermission();
+
+ // Getter and setter for the updates from P2P permission.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ void SetP2PUpdatePermission(bool enabled);
+ bool GetP2PUpdatePermission();
+
+ // This is similar to watching for updates but rather than registering
+ // a signal watch, actively poll the daemon just in case it stops
+ // sending notifications.
+ void WaitForUpdateComplete();
+ void OnUpdateCompleteCheck(int64_t last_checked_time,
+ double progress,
+ const string& current_operation,
+ const string& new_version,
+ int64_t new_size);
+
+ // Blocking call. Exits the program with error 1 in case of an error.
+ void ShowPrevVersion();
+
+ // Returns whether the current status is such that a reboot is needed.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ bool GetIsRebootNeeded();
+
+ // Blocks until a reboot is needed. If the reboot is needed, exits the program
+ // with 0. Otherwise it exits the program with 1 if an error occurs before
+ // the reboot is needed.
+ void WaitForRebootNeeded();
+ void OnRebootNeededCheck(int64_t last_checked_time,
+ double progress,
+ const string& current_operation,
+ const string& new_version,
+ int64_t new_size);
+ // Callback called when the OnRebootNeededCheck() handler is registered. This
+ // is useful to check at this point if the reboot is needed, without loosing
+ // any StatusUpdate signals and avoiding the race condition.
+ void OnRebootNeededCheckRegistration(const string& interface,
+ const string& signal_name,
+ bool success);
+
+ // Main method that parses and triggers all the actions based on the passed
+ // flags.
+ int ProcessFlags();
+
+ // DBus Proxy to the update_engine daemon object used for all the calls.
+ std::unique_ptr<org::chromium::UpdateEngineInterfaceProxy> proxy_;
+
+ // Copy of argc and argv passed to main().
+ int argc_;
+ char** argv_;
+
+ // Tell whether the UpdateEngine service is available after startup.
+ bool service_is_available_{false};
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateEngineClient);
+};
+
+bool UpdateEngineClient::InitProxy() {
+ proxy_.reset(new org::chromium::UpdateEngineInterfaceProxy(bus_));
+
+ if (!proxy_->GetObjectProxy()) {
+ LOG(ERROR) << "Error getting dbus proxy for " << kUpdateEngineServiceName;
+ return false;
+ }
+ return true;
+}
+
+void UpdateEngineClient::OnServiceAvailable(bool service_is_available) {
+ service_is_available_ = service_is_available;
+ if (!service_is_available) {
+ LOG(ERROR) << "UpdateEngineService not available.";
+ QuitWithExitCode(-1);
+ }
+ int ret = ProcessFlags();
+ if (ret != kContinueRunning)
+ QuitWithExitCode(ret);
+}
+
+void UpdateEngineClient::OnServiceAvailableTimeout() {
+ if (!service_is_available_) {
+ LOG(ERROR) << "Waiting for UpdateEngineService timeout. Is update_engine "
+ "daemon running?";
+ QuitWithExitCode(-1);
+ }
+}
+
+void UpdateEngineClient::OnStatusUpdateSignal(
+ int64_t last_checked_time,
+ double progress,
+ const string& current_operation,
+ const string& new_version,
+ int64_t new_size) {
+ LOG(INFO) << "Got status update:";
+ LOG(INFO) << " last_checked_time: " << last_checked_time;
+ LOG(INFO) << " progress: " << progress;
+ LOG(INFO) << " current_operation: " << current_operation;
+ LOG(INFO) << " new_version: " << new_version;
+ LOG(INFO) << " new_size: " << new_size;
+}
+
+void UpdateEngineClient::OnStatusUpdateSignalRegistration(
+ const string& interface,
+ const string& signal_name,
+ bool success) {
+ VLOG(1) << "OnStatusUpdateSignalRegistration(" << interface << ", "
+ << signal_name << ", " << success << ");";
+ if (!success) {
+ LOG(ERROR) << "Couldn't connect to the " << signal_name << " signal.";
+ exit(1);
+ }
+}
+
+void UpdateEngineClient::WatchForUpdates() {
+ proxy_->RegisterStatusUpdateSignalHandler(
+ base::Bind(&UpdateEngineClient::OnStatusUpdateSignal,
+ base::Unretained(this)),
+ base::Bind(&UpdateEngineClient::OnStatusUpdateSignalRegistration,
+ base::Unretained(this)));
+}
+
+void UpdateEngineClient::ResetStatus() {
+ bool ret = proxy_->ResetStatus(nullptr);
+ CHECK(ret) << "ResetStatus() failed.";
+}
+
+void UpdateEngineClient::ShowStatus() {
+ int64_t last_checked_time = 0;
+ double progress = 0.0;
+ string current_op;
+ string new_version;
+ int64_t new_size = 0;
+
+ bool ret = proxy_->GetStatus(
+ &last_checked_time, &progress, ¤t_op, &new_version, &new_size,
+ nullptr);
+ CHECK(ret) << "GetStatus() failed";
+ printf("LAST_CHECKED_TIME=%" PRIi64 "\nPROGRESS=%f\nCURRENT_OP=%s\n"
+ "NEW_VERSION=%s\nNEW_SIZE=%" PRIi64 "\n",
+ last_checked_time,
+ progress,
+ current_op.c_str(),
+ new_version.c_str(),
+ new_size);
+}
+
+string UpdateEngineClient::GetCurrentOperation() {
+ int64_t last_checked_time = 0;
+ double progress = 0.0;
+ string current_op;
+ string new_version;
+ int64_t new_size = 0;
+
+ bool ret = proxy_->GetStatus(
+ &last_checked_time, &progress, ¤t_op, &new_version, &new_size,
+ nullptr);
+ CHECK(ret) << "GetStatus() failed";
+ return current_op;
+}
+
+void UpdateEngineClient::Rollback(bool rollback) {
+ bool ret = proxy_->AttemptRollback(rollback, nullptr);
+ CHECK(ret) << "Rollback request failed.";
+}
+
+string UpdateEngineClient::GetRollbackPartition() {
+ string rollback_partition;
+ bool ret = proxy_->GetRollbackPartition(&rollback_partition, nullptr);
+ CHECK(ret) << "Error while querying rollback partition availabilty.";
+ return rollback_partition;
+}
+
+string UpdateEngineClient::GetKernelDevices() {
+ string kernel_devices;
+ bool ret = proxy_->GetKernelDevices(&kernel_devices, nullptr);
+ CHECK(ret) << "Error while getting a list of kernel devices";
+ return kernel_devices;
+}
+
+void UpdateEngineClient::CheckForUpdates(const string& app_version,
+ const string& omaha_url,
+ bool interactive) {
+ int32_t flags = interactive ? 0 : kAttemptUpdateFlagNonInteractive;
+ bool ret = proxy_->AttemptUpdateWithFlags(app_version, omaha_url, flags,
+ nullptr);
+ CHECK(ret) << "Error checking for update.";
+}
+
+void UpdateEngineClient::RebootIfNeeded() {
+ bool ret = proxy_->RebootIfNeeded(nullptr);
+ if (!ret) {
+ // Reboot error code doesn't necessarily mean that a reboot
+ // failed. For example, D-Bus may be shutdown before we receive the
+ // result.
+ LOG(INFO) << "RebootIfNeeded() failure ignored.";
+ }
+}
+
+void UpdateEngineClient::SetTargetChannel(const string& target_channel,
+ bool allow_powerwash) {
+ bool ret = proxy_->SetChannel(target_channel, allow_powerwash, nullptr);
+ CHECK(ret) << "Error setting the channel.";
+ LOG(INFO) << "Channel permanently set to: " << target_channel;
+}
+
+string UpdateEngineClient::GetChannel(bool get_current_channel) {
+ string channel;
+ bool ret = proxy_->GetChannel(get_current_channel, &channel, nullptr);
+ CHECK(ret) << "Error getting the channel.";
+ return channel;
+}
+
+void UpdateEngineClient::SetUpdateOverCellularPermission(bool allowed) {
+ bool ret = proxy_->SetUpdateOverCellularPermission(allowed, nullptr);
+ CHECK(ret) << "Error setting the update over cellular setting.";
+}
+
+bool UpdateEngineClient::GetUpdateOverCellularPermission() {
+ bool allowed;
+ bool ret = proxy_->GetUpdateOverCellularPermission(&allowed, nullptr);
+ CHECK(ret) << "Error getting the update over cellular setting.";
+ return allowed;
+}
+
+void UpdateEngineClient::SetP2PUpdatePermission(bool enabled) {
+ bool ret = proxy_->SetP2PUpdatePermission(enabled, nullptr);
+ CHECK(ret) << "Error setting the peer-to-peer update setting.";
+}
+
+bool UpdateEngineClient::GetP2PUpdatePermission() {
+ bool enabled;
+ bool ret = proxy_->GetP2PUpdatePermission(&enabled, nullptr);
+ CHECK(ret) << "Error getting the peer-to-peer update setting.";
+ return enabled;
+}
+
+void UpdateEngineClient::OnUpdateCompleteCheck(
+ int64_t /* last_checked_time */,
+ double /* progress */,
+ const string& current_operation,
+ const string& /* new_version */,
+ int64_t /* new_size */) {
+ if (current_operation == update_engine::kUpdateStatusIdle) {
+ LOG(ERROR) << "Update failed, current operations is " << current_operation;
+ exit(1);
+ }
+ if (current_operation == update_engine::kUpdateStatusUpdatedNeedReboot) {
+ LOG(INFO) << "Update succeeded -- reboot needed.";
+ exit(0);
+ }
+}
+
+void UpdateEngineClient::WaitForUpdateComplete() {
+ proxy_->RegisterStatusUpdateSignalHandler(
+ base::Bind(&UpdateEngineClient::OnUpdateCompleteCheck,
+ base::Unretained(this)),
+ base::Bind(&UpdateEngineClient::OnStatusUpdateSignalRegistration,
+ base::Unretained(this)));
+}
+
+void UpdateEngineClient::ShowPrevVersion() {
+ string prev_version = nullptr;
+
+ bool ret = proxy_->GetPrevVersion(&prev_version, nullptr);;
+ if (!ret) {
+ LOG(ERROR) << "Error getting previous version.";
+ } else {
+ LOG(INFO) << "Previous version = " << prev_version;
+ }
+}
+
+bool UpdateEngineClient::GetIsRebootNeeded() {
+ return GetCurrentOperation() == update_engine::kUpdateStatusUpdatedNeedReboot;
+}
+
+void UpdateEngineClient::OnRebootNeededCheck(
+ int64_t /* last_checked_time */,
+ double /* progress */,
+ const string& current_operation,
+ const string& /* new_version */,
+ int64_t /* new_size */) {
+ if (current_operation == update_engine::kUpdateStatusUpdatedNeedReboot) {
+ LOG(INFO) << "Reboot needed.";
+ exit(0);
+ }
+}
+
+void UpdateEngineClient::OnRebootNeededCheckRegistration(
+ const string& interface,
+ const string& signal_name,
+ bool success) {
+ if (GetIsRebootNeeded())
+ exit(0);
+ if (!success) {
+ LOG(ERROR) << "Couldn't connect to the " << signal_name << " signal.";
+ exit(1);
+ }
+}
+
+// Blocks until a reboot is needed. Returns true if waiting succeeded,
+// false if an error occurred.
+void UpdateEngineClient::WaitForRebootNeeded() {
+ proxy_->RegisterStatusUpdateSignalHandler(
+ base::Bind(&UpdateEngineClient::OnUpdateCompleteCheck,
+ base::Unretained(this)),
+ base::Bind(&UpdateEngineClient::OnStatusUpdateSignalRegistration,
+ base::Unretained(this)));
+ if (GetIsRebootNeeded())
+ exit(0);
+}
+
+int UpdateEngineClient::ProcessFlags() {
+ DEFINE_string(app_version, "", "Force the current app version.");
+ DEFINE_string(channel, "",
+ "Set the target channel. The device will be powerwashed if the "
+ "target channel is more stable than the current channel unless "
+ "--nopowerwash is specified.");
+ DEFINE_bool(check_for_update, false, "Initiate check for updates.");
+ DEFINE_bool(follow, false, "Wait for any update operations to complete."
+ "Exit status is 0 if the update succeeded, and 1 otherwise.");
+ DEFINE_bool(interactive, true, "Mark the update request as interactive.");
+ DEFINE_string(omaha_url, "", "The URL of the Omaha update server.");
+ DEFINE_string(p2p_update, "",
+ "Enables (\"yes\") or disables (\"no\") the peer-to-peer update"
+ " sharing.");
+ DEFINE_bool(powerwash, true, "When performing rollback or channel change, "
+ "do a powerwash or allow it respectively.");
+ DEFINE_bool(reboot, false, "Initiate a reboot if needed.");
+ DEFINE_bool(is_reboot_needed, false, "Exit status 0 if reboot is needed, "
+ "2 if reboot is not needed or 1 if an error occurred.");
+ DEFINE_bool(block_until_reboot_is_needed, false, "Blocks until reboot is "
+ "needed. Returns non-zero exit status if an error occurred.");
+ DEFINE_bool(reset_status, false, "Sets the status in update_engine to idle.");
+ DEFINE_bool(rollback, false,
+ "Perform a rollback to the previous partition. The device will "
+ "be powerwashed unless --nopowerwash is specified.");
+ DEFINE_bool(can_rollback, false, "Shows whether rollback partition "
+ "is available.");
+ DEFINE_bool(show_channel, false, "Show the current and target channels.");
+ DEFINE_bool(show_p2p_update, false,
+ "Show the current setting for peer-to-peer update sharing.");
+ DEFINE_bool(show_update_over_cellular, false,
+ "Show the current setting for updates over cellular networks.");
+ DEFINE_bool(status, false, "Print the status to stdout.");
+ DEFINE_bool(update, false, "Forces an update and waits for it to complete. "
+ "Implies --follow.");
+ DEFINE_string(update_over_cellular, "",
+ "Enables (\"yes\") or disables (\"no\") the updates over "
+ "cellular networks.");
+ DEFINE_bool(watch_for_updates, false,
+ "Listen for status updates and print them to the screen.");
+ DEFINE_bool(prev_version, false,
+ "Show the previous OS version used before the update reboot.");
+ DEFINE_bool(show_kernels, false, "Show the list of kernel patritions and "
+ "whether each of them is bootable or not");
+
+ // Boilerplate init commands.
+ base::CommandLine::Init(argc_, argv_);
+ chromeos::FlagHelper::Init(argc_, argv_, "Chromium OS Update Engine Client");
+
+ // Ensure there are no positional arguments.
+ const std::vector<string> positional_args =
+ base::CommandLine::ForCurrentProcess()->GetArgs();
+ if (!positional_args.empty()) {
+ LOG(ERROR) << "Found a positional argument '" << positional_args.front()
+ << "'. If you want to pass a value to a flag, pass it as "
+ "--flag=value.";
+ return 1;
+ }
+
+ // Update the status if requested.
+ if (FLAGS_reset_status) {
+ LOG(INFO) << "Setting Update Engine status to idle ...";
+ ResetStatus();
+ LOG(INFO) << "ResetStatus succeeded; to undo partition table changes run:\n"
+ "(D=$(rootdev -d) P=$(rootdev -s); cgpt p -i$(($(echo ${P#$D} "
+ "| sed 's/^[^0-9]*//')-1)) $D;)";
+ }
+
+ // Changes the current update over cellular network setting.
+ if (!FLAGS_update_over_cellular.empty()) {
+ bool allowed = FLAGS_update_over_cellular == "yes";
+ if (!allowed && FLAGS_update_over_cellular != "no") {
+ LOG(ERROR) << "Unknown option: \"" << FLAGS_update_over_cellular
+ << "\". Please specify \"yes\" or \"no\".";
+ } else {
+ SetUpdateOverCellularPermission(allowed);
+ }
+ }
+
+ // Show the current update over cellular network setting.
+ if (FLAGS_show_update_over_cellular) {
+ bool allowed = GetUpdateOverCellularPermission();
+ LOG(INFO) << "Current update over cellular network setting: "
+ << (allowed ? "ENABLED" : "DISABLED");
+ }
+
+ if (!FLAGS_powerwash && !FLAGS_rollback && FLAGS_channel.empty()) {
+ LOG(ERROR) << "powerwash flag only makes sense rollback or channel change";
+ return 1;
+ }
+
+ // Change the P2P enabled setting.
+ if (!FLAGS_p2p_update.empty()) {
+ bool enabled = FLAGS_p2p_update == "yes";
+ if (!enabled && FLAGS_p2p_update != "no") {
+ LOG(ERROR) << "Unknown option: \"" << FLAGS_p2p_update
+ << "\". Please specify \"yes\" or \"no\".";
+ } else {
+ SetP2PUpdatePermission(enabled);
+ }
+ }
+
+ // Show the rollback availability.
+ if (FLAGS_can_rollback) {
+ string rollback_partition = GetRollbackPartition();
+ bool can_rollback = true;
+ if (rollback_partition.empty()) {
+ rollback_partition = "UNAVAILABLE";
+ can_rollback = false;
+ } else {
+ rollback_partition = "AVAILABLE: " + rollback_partition;
+ }
+
+ LOG(INFO) << "Rollback partition: " << rollback_partition;
+ if (!can_rollback) {
+ return 1;
+ }
+ }
+
+ // Show the current P2P enabled setting.
+ if (FLAGS_show_p2p_update) {
+ bool enabled = GetP2PUpdatePermission();
+ LOG(INFO) << "Current update using P2P setting: "
+ << (enabled ? "ENABLED" : "DISABLED");
+ }
+
+ // First, update the target channel if requested.
+ if (!FLAGS_channel.empty())
+ SetTargetChannel(FLAGS_channel, FLAGS_powerwash);
+
+ // Show the current and target channels if requested.
+ if (FLAGS_show_channel) {
+ string current_channel = GetChannel(true);
+ LOG(INFO) << "Current Channel: " << current_channel;
+
+ string target_channel = GetChannel(false);
+ if (!target_channel.empty())
+ LOG(INFO) << "Target Channel (pending update): " << target_channel;
+ }
+
+ bool do_update_request = FLAGS_check_for_update | FLAGS_update |
+ !FLAGS_app_version.empty() | !FLAGS_omaha_url.empty();
+ if (FLAGS_update)
+ FLAGS_follow = true;
+
+ if (do_update_request && FLAGS_rollback) {
+ LOG(ERROR) << "Incompatible flags specified with rollback."
+ << "Rollback should not include update-related flags.";
+ return 1;
+ }
+
+ if (FLAGS_rollback) {
+ LOG(INFO) << "Requesting rollback.";
+ Rollback(FLAGS_powerwash);
+ }
+
+ // Initiate an update check, if necessary.
+ if (do_update_request) {
+ LOG_IF(WARNING, FLAGS_reboot) << "-reboot flag ignored.";
+ string app_version = FLAGS_app_version;
+ if (FLAGS_update && app_version.empty()) {
+ app_version = "ForcedUpdate";
+ LOG(INFO) << "Forcing an update by setting app_version to ForcedUpdate.";
+ }
+ LOG(INFO) << "Initiating update check and install.";
+ CheckForUpdates(app_version, FLAGS_omaha_url, FLAGS_interactive);
+ }
+
+ // These final options are all mutually exclusive with one another.
+ if (FLAGS_follow + FLAGS_watch_for_updates + FLAGS_reboot +
+ FLAGS_status + FLAGS_is_reboot_needed +
+ FLAGS_block_until_reboot_is_needed > 1) {
+ LOG(ERROR) << "Multiple exclusive options selected. "
+ << "Select only one of --follow, --watch_for_updates, --reboot, "
+ << "--is_reboot_needed, --block_until_reboot_is_needed, "
+ << "or --status.";
+ return 1;
+ }
+
+ if (FLAGS_status) {
+ LOG(INFO) << "Querying Update Engine status...";
+ ShowStatus();
+ return 0;
+ }
+
+ if (FLAGS_follow) {
+ LOG(INFO) << "Waiting for update to complete.";
+ WaitForUpdateComplete();
+ return kContinueRunning;
+ }
+
+ if (FLAGS_watch_for_updates) {
+ LOG(INFO) << "Watching for status updates.";
+ WatchForUpdates();
+ return kContinueRunning;
+ }
+
+ if (FLAGS_reboot) {
+ LOG(INFO) << "Requesting a reboot...";
+ RebootIfNeeded();
+ return 0;
+ }
+
+ if (FLAGS_prev_version) {
+ ShowPrevVersion();
+ }
+
+ if (FLAGS_show_kernels) {
+ LOG(INFO) << "Kernel partitions:\n"
+ << GetKernelDevices();
+ }
+
+ if (FLAGS_is_reboot_needed) {
+ // In case of error GetIsRebootNeeded() will exit with 1.
+ if (GetIsRebootNeeded()) {
+ return 0;
+ } else {
+ return 2;
+ }
+ }
+
+ if (FLAGS_block_until_reboot_is_needed) {
+ WaitForRebootNeeded();
+ return kContinueRunning;
+ }
+
+ return 0;
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ UpdateEngineClient client(argc, argv);
+ return client.Run();
+}
diff --git a/update_manager/boxed_value.cc b/update_manager/boxed_value.cc
new file mode 100644
index 0000000..0acc8cd
--- /dev/null
+++ b/update_manager/boxed_value.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/boxed_value.h"
+
+#include <stdint.h>
+
+#include <set>
+#include <string>
+
+#include <base/strings/string_number_conversions.h>
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/updater_provider.h"
+#include "update_engine/utils.h"
+
+using std::set;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// Template instantiation for common types; used in BoxedValue::ToString().
+// Keep in sync with boxed_value_unitttest.cc.
+
+template<>
+string BoxedValue::ValuePrinter<string>(const void* value) {
+ const string* val = reinterpret_cast<const string*>(value);
+ return *val;
+}
+
+template<>
+string BoxedValue::ValuePrinter<int>(const void* value) {
+ const int* val = reinterpret_cast<const int*>(value);
+ return base::IntToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<unsigned int>(const void* value) {
+ const unsigned int* val = reinterpret_cast<const unsigned int*>(value);
+ return base::UintToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<int64_t>(const void* value) {
+ const int64_t* val = reinterpret_cast<const int64_t*>(value);
+ return base::Int64ToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<uint64_t>(const void* value) {
+ const uint64_t* val =
+ reinterpret_cast<const uint64_t*>(value);
+ return base::Uint64ToString(static_cast<uint64_t>(*val));
+}
+
+template<>
+string BoxedValue::ValuePrinter<bool>(const void* value) {
+ const bool* val = reinterpret_cast<const bool*>(value);
+ return *val ? "true" : "false";
+}
+
+template<>
+string BoxedValue::ValuePrinter<double>(const void* value) {
+ const double* val = reinterpret_cast<const double*>(value);
+ return base::DoubleToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<base::Time>(const void* value) {
+ const base::Time* val = reinterpret_cast<const base::Time*>(value);
+ return chromeos_update_engine::utils::ToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<base::TimeDelta>(const void* value) {
+ const base::TimeDelta* val = reinterpret_cast<const base::TimeDelta*>(value);
+ return chromeos_update_engine::utils::FormatTimeDelta(*val);
+}
+
+static string ConnectionTypeToString(ConnectionType type) {
+ switch (type) {
+ case ConnectionType::kEthernet:
+ return "Ethernet";
+ case ConnectionType::kWifi:
+ return "Wifi";
+ case ConnectionType::kWimax:
+ return "Wimax";
+ case ConnectionType::kBluetooth:
+ return "Bluetooth";
+ case ConnectionType::kCellular:
+ return "Cellular";
+ case ConnectionType::kUnknown:
+ return "Unknown";
+ }
+ NOTREACHED();
+ return "Unknown";
+}
+
+template<>
+string BoxedValue::ValuePrinter<ConnectionType>(const void* value) {
+ const ConnectionType* val = reinterpret_cast<const ConnectionType*>(value);
+ return ConnectionTypeToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<set<ConnectionType>>(const void* value) {
+ string ret = "";
+ const set<ConnectionType>* val =
+ reinterpret_cast<const set<ConnectionType>*>(value);
+ for (auto& it : *val) {
+ ConnectionType type = it;
+ if (ret.size() > 0)
+ ret += ",";
+ ret += ConnectionTypeToString(type);
+ }
+ return ret;
+}
+
+template<>
+string BoxedValue::ValuePrinter<ConnectionTethering>(const void* value) {
+ const ConnectionTethering* val =
+ reinterpret_cast<const ConnectionTethering*>(value);
+ switch (*val) {
+ case ConnectionTethering::kNotDetected:
+ return "Not Detected";
+ case ConnectionTethering::kSuspected:
+ return "Suspected";
+ case ConnectionTethering::kConfirmed:
+ return "Confirmed";
+ case ConnectionTethering::kUnknown:
+ return "Unknown";
+ }
+ NOTREACHED();
+ return "Unknown";
+}
+
+template<>
+string BoxedValue::ValuePrinter<Stage>(const void* value) {
+ const Stage* val = reinterpret_cast<const Stage*>(value);
+ switch (*val) {
+ case Stage::kIdle:
+ return "Idle";
+ case Stage::kCheckingForUpdate:
+ return "Checking For Update";
+ case Stage::kUpdateAvailable:
+ return "Update Available";
+ case Stage::kDownloading:
+ return "Downloading";
+ case Stage::kVerifying:
+ return "Verifying";
+ case Stage::kFinalizing:
+ return "Finalizing";
+ case Stage::kUpdatedNeedReboot:
+ return "Updated, Need Reboot";
+ case Stage::kReportingErrorEvent:
+ return "Reporting Error Event";
+ case Stage::kAttemptingRollback:
+ return "Attempting Rollback";
+ }
+ NOTREACHED();
+ return "Unknown";
+}
+
+template<>
+string BoxedValue::ValuePrinter<UpdateRequestStatus>(const void* value) {
+ const UpdateRequestStatus* val =
+ reinterpret_cast<const UpdateRequestStatus*>(value);
+ switch (*val) {
+ case UpdateRequestStatus::kNone:
+ return "None";
+ case UpdateRequestStatus::kInteractive:
+ return "Interactive";
+ case UpdateRequestStatus::kPeriodic:
+ return "Periodic";
+ }
+ NOTREACHED();
+ return "Unknown";
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/boxed_value.h b/update_manager/boxed_value.h
new file mode 100644
index 0000000..200464f
--- /dev/null
+++ b/update_manager/boxed_value.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_BOXED_VALUE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_BOXED_VALUE_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+
+namespace chromeos_update_manager {
+
+// BoxedValue is a class to hold pointers of a given type that deletes them when
+// the instance goes out of scope, as std::unique_ptr<T> does. The main
+// difference with it is that the type T is not part of the class, i.e., this
+// isn't a parametric class. The class has a parametric constructor that accepts
+// a const T* which will define the type of the object passed on delete.
+//
+// It is safe to use this class in linked containers such as std::list and
+// std::map but the object can't be copied. This means that you need to
+// construct the BoxedValue in place using a container method like emplace()
+// or move it with std::move().
+//
+// list<BoxedValue> lst;
+// lst.emplace_back(new const int(42));
+// lst.emplace_back(new const string("Hello world!"));
+//
+// map<int, BoxedValue> m;
+// m.emplace(123, std::move(BoxedValue(new const string("Hola mundo!"))));
+//
+// auto it = m.find(42);
+// if (it != m.end())
+// cout << "m[42] points to " << it->second.value() << endl;
+// cout << "m[33] points to " << m[33].value() << endl;
+//
+// Since copy and assign are not allowed, you can't create a copy of the
+// BoxedValue which means that you can only use a reference to it.
+//
+
+class BoxedValue {
+ public:
+ // Creates an empty BoxedValue. Since the pointer can't be assigned from other
+ // BoxedValues or pointers, this is only useful in places where a default
+ // constructor is required, such as std::map::operator[].
+ BoxedValue() : value_(nullptr), deleter_(nullptr), printer_(nullptr) {}
+
+ // Creates a BoxedValue for the passed pointer |value|. The BoxedValue keeps
+ // the ownership of this pointer and can't be released.
+ template<typename T>
+ explicit BoxedValue(const T* value)
+ : value_(static_cast<const void*>(value)), deleter_(ValueDeleter<T>),
+ printer_(ValuePrinter<T>) {}
+
+ // The move constructor takes ownership of the pointer since the semantics of
+ // it allows to render the passed BoxedValue undefined. You need to use the
+ // move constructor explicitly preventing it from accidental references,
+ // like in:
+ // BoxedValue new_box(std::move(other_box));
+ BoxedValue(BoxedValue&& other) // NOLINT(build/c++11)
+ : value_(other.value_), deleter_(other.deleter_),
+ printer_(other.printer_) {
+ other.value_ = nullptr;
+ other.deleter_ = nullptr;
+ other.printer_ = nullptr;
+ }
+
+ // Deletes the |value| passed on construction using the delete for the passed
+ // type.
+ ~BoxedValue() {
+ if (deleter_)
+ deleter_(value_);
+ }
+
+ const void* value() const { return value_; }
+
+ std::string ToString() const {
+ if (!printer_)
+ return "(no printer)";
+ if (!value_)
+ return "(no value)";
+ return printer_(value_);
+ }
+
+ // Static method to call the destructor of the right type.
+ template<typename T>
+ static void ValueDeleter(const void* value) {
+ delete reinterpret_cast<const T*>(value);
+ }
+
+ // Static method to print a type. See boxed_value.cc for common
+ // instantiations.
+ template<typename T>
+ static std::string ValuePrinter(const void* value);
+
+ private:
+ // A pointer to the cached value.
+ const void* value_;
+
+ // A function that calls delete for the right type of value_.
+ void (*deleter_)(const void*);
+
+ // A function that converts value_ to a string.
+ std::string (*printer_)(const void*);
+
+ DISALLOW_COPY_AND_ASSIGN(BoxedValue);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_BOXED_VALUE_H_
diff --git a/update_manager/boxed_value_unittest.cc b/update_manager/boxed_value_unittest.cc
new file mode 100644
index 0000000..4914a23
--- /dev/null
+++ b/update_manager/boxed_value_unittest.cc
@@ -0,0 +1,220 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/boxed_value.h"
+
+#include <gtest/gtest.h>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/umtest_utils.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::list;
+using std::map;
+using std::set;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// The DeleterMarker flags a bool variable when the class is destroyed.
+class DeleterMarker {
+ public:
+ explicit DeleterMarker(bool* marker) : marker_(marker) { *marker_ = false; }
+
+ ~DeleterMarker() { *marker_ = true; }
+
+ private:
+ friend string BoxedValue::ValuePrinter<DeleterMarker>(const void *);
+
+ // Pointer to the bool marker.
+ bool* marker_;
+};
+
+template<>
+string BoxedValue::ValuePrinter<DeleterMarker>(const void *value) {
+ const DeleterMarker* val = reinterpret_cast<const DeleterMarker*>(value);
+ return base::StringPrintf("DeleterMarker:%s",
+ *val->marker_ ? "true" : "false");
+}
+
+TEST(UmBoxedValueTest, Deleted) {
+ bool marker = true;
+ const DeleterMarker* deleter_marker = new DeleterMarker(&marker);
+
+ EXPECT_FALSE(marker);
+ BoxedValue* box = new BoxedValue(deleter_marker);
+ EXPECT_FALSE(marker);
+ delete box;
+ EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MoveConstructor) {
+ bool marker = true;
+ const DeleterMarker* deleter_marker = new DeleterMarker(&marker);
+
+ BoxedValue* box = new BoxedValue(deleter_marker);
+ BoxedValue* new_box = new BoxedValue(std::move(*box));
+ // box is now undefined but valid.
+ delete box;
+ EXPECT_FALSE(marker);
+ // The deleter_marker gets deleted at this point.
+ delete new_box;
+ EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MixedList) {
+ list<BoxedValue> lst;
+ // This is mostly a compile test.
+ lst.emplace_back(new const int{42});
+ lst.emplace_back(new const string("Hello world!"));
+ bool marker;
+ lst.emplace_back(new const DeleterMarker(&marker));
+ EXPECT_FALSE(marker);
+ lst.clear();
+ EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MixedMap) {
+ map<int, BoxedValue> m;
+ m.emplace(42, BoxedValue(new const string("Hola mundo!")));
+
+ auto it = m.find(42);
+ ASSERT_NE(it, m.end());
+ EXPECT_NE(nullptr, it->second.value());
+ EXPECT_EQ(nullptr, m[33].value());
+}
+
+TEST(UmBoxedValueTest, StringToString) {
+ EXPECT_EQ("Hej Verden!",
+ BoxedValue(new string("Hej Verden!")).ToString());
+}
+
+TEST(UmBoxedValueTest, IntToString) {
+ EXPECT_EQ("42", BoxedValue(new int(42)).ToString());
+}
+
+TEST(UmBoxedValueTest, Int64ToString) {
+ // -123456789012345 doesn't fit in 32-bit integers.
+ EXPECT_EQ("-123456789012345", BoxedValue(
+ new int64_t(-123456789012345LL)).ToString());
+}
+
+TEST(UmBoxedValueTest, UnsignedIntToString) {
+ // 4294967295 is the biggest possible 32-bit unsigned integer.
+ EXPECT_EQ("4294967295",
+ BoxedValue(new unsigned int(4294967295U)).ToString()); // NOLINT
+}
+
+TEST(UmBoxedValueTest, UnsignedInt64ToString) {
+ // 18446744073709551615 is the biggest possible 64-bit unsigned integer.
+ EXPECT_EQ("18446744073709551615", BoxedValue(
+ new uint64_t(18446744073709551615ULL)).ToString());
+}
+
+TEST(UmBoxedValueTest, BoolToString) {
+ EXPECT_EQ("false", BoxedValue(new bool(false)).ToString());
+ EXPECT_EQ("true", BoxedValue(new bool(true)).ToString());
+}
+
+TEST(UmBoxedValueTest, DoubleToString) {
+ EXPECT_EQ("1.501", BoxedValue(new double(1.501)).ToString());
+}
+
+TEST(UmBoxedValueTest, TimeToString) {
+ // Tue Apr 29 22:30:55 UTC 2014 is 1398810655 seconds since the Unix Epoch.
+ EXPECT_EQ("4/29/2014 22:30:55 GMT",
+ BoxedValue(new Time(Time::FromTimeT(1398810655))).ToString());
+}
+
+TEST(UmBoxedValueTest, TimeDeltaToString) {
+ // 12345 seconds is 3 hours, 25 minutes and 45 seconds.
+ EXPECT_EQ("3h25m45s",
+ BoxedValue(new TimeDelta(TimeDelta::FromSeconds(12345)))
+ .ToString());
+}
+
+TEST(UmBoxedValueTest, ConnectionTypeToString) {
+ EXPECT_EQ("Ethernet",
+ BoxedValue(new ConnectionType(ConnectionType::kEthernet))
+ .ToString());
+ EXPECT_EQ("Wifi",
+ BoxedValue(new ConnectionType(ConnectionType::kWifi)).ToString());
+ EXPECT_EQ("Wimax",
+ BoxedValue(new ConnectionType(ConnectionType::kWimax)).ToString());
+ EXPECT_EQ("Bluetooth",
+ BoxedValue(new ConnectionType(ConnectionType::kBluetooth))
+ .ToString());
+ EXPECT_EQ("Cellular",
+ BoxedValue(new ConnectionType(ConnectionType::kCellular))
+ .ToString());
+ EXPECT_EQ("Unknown",
+ BoxedValue(new ConnectionType(ConnectionType::kUnknown))
+ .ToString());
+}
+
+TEST(UmBoxedValueTest, ConnectionTetheringToString) {
+ EXPECT_EQ("Not Detected",
+ BoxedValue(new ConnectionTethering(
+ ConnectionTethering::kNotDetected)).ToString());
+ EXPECT_EQ("Suspected",
+ BoxedValue(new ConnectionTethering(ConnectionTethering::kSuspected))
+ .ToString());
+ EXPECT_EQ("Confirmed",
+ BoxedValue(new ConnectionTethering(ConnectionTethering::kConfirmed))
+ .ToString());
+ EXPECT_EQ("Unknown",
+ BoxedValue(new ConnectionTethering(ConnectionTethering::kUnknown))
+ .ToString());
+}
+
+TEST(UmBoxedValueTest, SetConnectionTypeToString) {
+ set<ConnectionType>* set1 = new set<ConnectionType>;
+ set1->insert(ConnectionType::kWimax);
+ set1->insert(ConnectionType::kEthernet);
+ EXPECT_EQ("Ethernet,Wimax", BoxedValue(set1).ToString());
+
+ set<ConnectionType>* set2 = new set<ConnectionType>;
+ set2->insert(ConnectionType::kWifi);
+ EXPECT_EQ("Wifi", BoxedValue(set2).ToString());
+}
+
+TEST(UmBoxedValueTest, StageToString) {
+ EXPECT_EQ("Idle",
+ BoxedValue(new Stage(Stage::kIdle)).ToString());
+ EXPECT_EQ("Checking For Update",
+ BoxedValue(new Stage(Stage::kCheckingForUpdate)).ToString());
+ EXPECT_EQ("Update Available",
+ BoxedValue(new Stage(Stage::kUpdateAvailable)).ToString());
+ EXPECT_EQ("Downloading",
+ BoxedValue(new Stage(Stage::kDownloading)).ToString());
+ EXPECT_EQ("Verifying",
+ BoxedValue(new Stage(Stage::kVerifying)).ToString());
+ EXPECT_EQ("Finalizing",
+ BoxedValue(new Stage(Stage::kFinalizing)).ToString());
+ EXPECT_EQ("Updated, Need Reboot",
+ BoxedValue(new Stage(Stage::kUpdatedNeedReboot)).ToString());
+ EXPECT_EQ("Reporting Error Event",
+ BoxedValue(new Stage(Stage::kReportingErrorEvent)).ToString());
+ EXPECT_EQ("Attempting Rollback",
+ BoxedValue(new Stage(Stage::kAttemptingRollback)).ToString());
+}
+
+TEST(UmBoxedValueTest, DeleterMarkerToString) {
+ bool marker = false;
+ BoxedValue value = BoxedValue(new DeleterMarker(&marker));
+ EXPECT_EQ("DeleterMarker:false", value.ToString());
+ marker = true;
+ EXPECT_EQ("DeleterMarker:true", value.ToString());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
new file mode 100644
index 0000000..2acccaf
--- /dev/null
+++ b/update_manager/chromeos_policy.cc
@@ -0,0 +1,921 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/chromeos_policy.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/time/time.h>
+
+#include "update_engine/error_code.h"
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/policy_utils.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ErrorCode;
+using std::get;
+using std::max;
+using std::min;
+using std::set;
+using std::string;
+
+namespace {
+
+// Examines |err_code| and decides whether the URL index needs to be advanced,
+// the error count for the URL incremented, or none of the above. In the first
+// case, returns true; in the second case, increments |*url_num_error_p| and
+// returns false; otherwise just returns false.
+//
+// TODO(garnold) Adapted from PayloadState::UpdateFailed() (to be retired).
+bool HandleErrorCode(ErrorCode err_code, int* url_num_error_p) {
+ err_code = chromeos_update_engine::utils::GetBaseErrorCode(err_code);
+ switch (err_code) {
+ // Errors which are good indicators of a problem with a particular URL or
+ // the protocol used in the URL or entities in the communication channel
+ // (e.g. proxies). We should try the next available URL in the next update
+ // check to quickly recover from these errors.
+ case ErrorCode::kPayloadHashMismatchError:
+ case ErrorCode::kPayloadSizeMismatchError:
+ case ErrorCode::kDownloadPayloadVerificationError:
+ case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+ case ErrorCode::kSignedDeltaPayloadExpectedError:
+ case ErrorCode::kDownloadInvalidMetadataMagicString:
+ case ErrorCode::kDownloadSignatureMissingInManifest:
+ case ErrorCode::kDownloadManifestParseError:
+ case ErrorCode::kDownloadMetadataSignatureError:
+ case ErrorCode::kDownloadMetadataSignatureVerificationError:
+ case ErrorCode::kDownloadMetadataSignatureMismatch:
+ case ErrorCode::kDownloadOperationHashVerificationError:
+ case ErrorCode::kDownloadOperationExecutionError:
+ case ErrorCode::kDownloadOperationHashMismatch:
+ case ErrorCode::kDownloadInvalidMetadataSize:
+ case ErrorCode::kDownloadInvalidMetadataSignature:
+ case ErrorCode::kDownloadOperationHashMissingError:
+ case ErrorCode::kDownloadMetadataSignatureMissingError:
+ case ErrorCode::kPayloadMismatchedType:
+ case ErrorCode::kUnsupportedMajorPayloadVersion:
+ case ErrorCode::kUnsupportedMinorPayloadVersion:
+ LOG(INFO) << "Advancing download URL due to error "
+ << chromeos_update_engine::utils::CodeToString(err_code)
+ << " (" << static_cast<int>(err_code) << ")";
+ return true;
+
+ // Errors which seem to be just transient network/communication related
+ // failures and do not indicate any inherent problem with the URL itself.
+ // So, we should keep the current URL but just increment the
+ // failure count to give it more chances. This way, while we maximize our
+ // chances of downloading from the URLs that appear earlier in the response
+ // (because download from a local server URL that appears earlier in a
+ // response is preferable than downloading from the next URL which could be
+ // an Internet URL and thus could be more expensive).
+ case ErrorCode::kError:
+ case ErrorCode::kDownloadTransferError:
+ case ErrorCode::kDownloadWriteError:
+ case ErrorCode::kDownloadStateInitializationError:
+ case ErrorCode::kOmahaErrorInHTTPResponse: // Aggregate for HTTP errors.
+ LOG(INFO) << "Incrementing URL failure count due to error "
+ << chromeos_update_engine::utils::CodeToString(err_code)
+ << " (" << static_cast<int>(err_code) << ")";
+ *url_num_error_p += 1;
+ return false;
+
+ // Errors which are not specific to a URL and hence shouldn't result in
+ // the URL being penalized. This can happen in two cases:
+ // 1. We haven't started downloading anything: These errors don't cost us
+ // anything in terms of actual payload bytes, so we should just do the
+ // regular retries at the next update check.
+ // 2. We have successfully downloaded the payload: In this case, the
+ // payload attempt number would have been incremented and would take care
+ // of the back-off at the next update check.
+ // In either case, there's no need to update URL index or failure count.
+ case ErrorCode::kOmahaRequestError:
+ case ErrorCode::kOmahaResponseHandlerError:
+ case ErrorCode::kPostinstallRunnerError:
+ case ErrorCode::kFilesystemCopierError:
+ case ErrorCode::kInstallDeviceOpenError:
+ case ErrorCode::kKernelDeviceOpenError:
+ case ErrorCode::kDownloadNewPartitionInfoError:
+ case ErrorCode::kNewRootfsVerificationError:
+ case ErrorCode::kNewKernelVerificationError:
+ case ErrorCode::kPostinstallBootedFromFirmwareB:
+ case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+ case ErrorCode::kOmahaRequestEmptyResponseError:
+ case ErrorCode::kOmahaRequestXMLParseError:
+ case ErrorCode::kOmahaResponseInvalid:
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ case ErrorCode::kPostinstallPowerwashError:
+ case ErrorCode::kUpdateCanceledByChannelChange:
+ case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+ case ErrorCode::kFilesystemVerifierError:
+ LOG(INFO) << "Not changing URL index or failure count due to error "
+ << chromeos_update_engine::utils::CodeToString(err_code)
+ << " (" << static_cast<int>(err_code) << ")";
+ return false;
+
+ case ErrorCode::kSuccess: // success code
+ case ErrorCode::kUmaReportedMax: // not an error code
+ case ErrorCode::kOmahaRequestHTTPResponseBase: // aggregated already
+ case ErrorCode::kDevModeFlag: // not an error code
+ case ErrorCode::kResumedFlag: // not an error code
+ case ErrorCode::kTestImageFlag: // not an error code
+ case ErrorCode::kTestOmahaUrlFlag: // not an error code
+ case ErrorCode::kSpecialFlags: // not an error code
+ // These shouldn't happen. Enumerating these explicitly here so that we
+ // can let the compiler warn about new error codes that are added to
+ // action_processor.h but not added here.
+ LOG(WARNING) << "Unexpected error "
+ << chromeos_update_engine::utils::CodeToString(err_code)
+ << " (" << static_cast<int>(err_code) << ")";
+ // Note: Not adding a default here so as to let the compiler warn us of
+ // any new enums that were added in the .h but not listed in this switch.
+ }
+ return false;
+}
+
+// Checks whether |url| can be used under given download restrictions.
+bool IsUrlUsable(const string& url, bool http_allowed) {
+ return http_allowed || !base::StartsWithASCII(url, "http://", false);
+}
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+const int ChromeOSPolicy::kTimeoutInitialInterval = 7 * 60;
+const int ChromeOSPolicy::kTimeoutPeriodicInterval = 45 * 60;
+const int ChromeOSPolicy::kTimeoutMaxBackoffInterval = 4 * 60 * 60;
+const int ChromeOSPolicy::kTimeoutRegularFuzz = 10 * 60;
+const int ChromeOSPolicy::kAttemptBackoffMaxIntervalInDays = 16;
+const int ChromeOSPolicy::kAttemptBackoffFuzzInHours = 12;
+const int ChromeOSPolicy::kMaxP2PAttempts = 10;
+const int ChromeOSPolicy::kMaxP2PAttemptsPeriodInSeconds = 5 * 24 * 60 * 60;
+
+EvalStatus ChromeOSPolicy::UpdateCheckAllowed(
+ EvaluationContext* ec, State* state, string* error,
+ UpdateCheckParams* result) const {
+ // Set the default return values.
+ result->updates_enabled = true;
+ result->target_channel.clear();
+ result->target_version_prefix.clear();
+ result->is_interactive = false;
+
+ DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+ UpdaterProvider* const updater_provider = state->updater_provider();
+ SystemProvider* const system_provider = state->system_provider();
+
+ // Do not perform any updates if booted from removable device. This decision
+ // is final.
+ const bool* is_boot_device_removable_p = ec->GetValue(
+ system_provider->var_is_boot_device_removable());
+ if (is_boot_device_removable_p && *is_boot_device_removable_p) {
+ LOG(INFO) << "Booted from removable device, disabling update checks.";
+ result->updates_enabled = false;
+ return EvalStatus::kSucceeded;
+ }
+
+ const bool* device_policy_is_loaded_p = ec->GetValue(
+ dp_provider->var_device_policy_is_loaded());
+ if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+ // Check whether updates are disabled by policy.
+ const bool* update_disabled_p = ec->GetValue(
+ dp_provider->var_update_disabled());
+ if (update_disabled_p && *update_disabled_p) {
+ LOG(INFO) << "Updates disabled by policy, blocking update checks.";
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ // Determine whether a target version prefix is dictated by policy.
+ const string* target_version_prefix_p = ec->GetValue(
+ dp_provider->var_target_version_prefix());
+ if (target_version_prefix_p)
+ result->target_version_prefix = *target_version_prefix_p;
+
+ // Determine whether a target channel is dictated by policy.
+ const bool* release_channel_delegated_p = ec->GetValue(
+ dp_provider->var_release_channel_delegated());
+ if (release_channel_delegated_p && !(*release_channel_delegated_p)) {
+ const string* release_channel_p = ec->GetValue(
+ dp_provider->var_release_channel());
+ if (release_channel_p)
+ result->target_channel = *release_channel_p;
+ }
+ }
+
+ // First, check to see if an interactive update was requested.
+ const UpdateRequestStatus* forced_update_requested_p = ec->GetValue(
+ updater_provider->var_forced_update_requested());
+ if (forced_update_requested_p &&
+ *forced_update_requested_p != UpdateRequestStatus::kNone) {
+ result->is_interactive =
+ (*forced_update_requested_p == UpdateRequestStatus::kInteractive);
+ LOG(INFO) << "Forced update signaled ("
+ << (result->is_interactive ? "interactive" : "periodic")
+ << "), allowing update check.";
+ return EvalStatus::kSucceeded;
+ }
+
+ // The logic thereafter applies to periodic updates. Bear in mind that we
+ // should not return a final "no" if any of these criteria are not satisfied,
+ // because the system may still update due to an interactive update request.
+
+ // Unofficial builds should not perform periodic update checks.
+ const bool* is_official_build_p = ec->GetValue(
+ system_provider->var_is_official_build());
+ if (is_official_build_p && !(*is_official_build_p)) {
+ LOG(INFO) << "Unofficial build, blocking periodic update checks.";
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ // If OOBE is enabled, wait until it is completed.
+ const bool* is_oobe_enabled_p = ec->GetValue(
+ state->config_provider()->var_is_oobe_enabled());
+ if (is_oobe_enabled_p && *is_oobe_enabled_p) {
+ const bool* is_oobe_complete_p = ec->GetValue(
+ system_provider->var_is_oobe_complete());
+ if (is_oobe_complete_p && !(*is_oobe_complete_p)) {
+ LOG(INFO) << "OOBE not completed, blocking update checks.";
+ return EvalStatus::kAskMeAgainLater;
+ }
+ }
+
+ // Ensure that periodic update checks are timed properly.
+ Time next_update_check;
+ if (NextUpdateCheckTime(ec, state, error, &next_update_check) !=
+ EvalStatus::kSucceeded) {
+ return EvalStatus::kFailed;
+ }
+ if (!ec->IsWallclockTimeGreaterThan(next_update_check)) {
+ LOG(INFO) << "Periodic check interval not satisfied, blocking until "
+ << chromeos_update_engine::utils::ToString(next_update_check);
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ // It is time to check for an update.
+ LOG(INFO) << "Allowing update check.";
+ return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::UpdateCanStart(
+ EvaluationContext* ec,
+ State* state,
+ string* error,
+ UpdateDownloadParams* result,
+ const UpdateState update_state) const {
+ // Set the default return values. Note that we set persisted values (backoff,
+ // scattering) to the same values presented in the update state. The reason is
+ // that preemptive returns, such as the case where an update check is due,
+ // should not clear off the said values; rather, it is the deliberate
+ // inference of new values that should cause them to be reset.
+ result->update_can_start = false;
+ result->cannot_start_reason = UpdateCannotStartReason::kUndefined;
+ result->download_url_idx = -1;
+ result->download_url_allowed = true;
+ result->download_url_num_errors = 0;
+ result->p2p_downloading_allowed = false;
+ result->p2p_sharing_allowed = false;
+ result->do_increment_failures = false;
+ result->backoff_expiry = update_state.backoff_expiry;
+ result->scatter_wait_period = update_state.scatter_wait_period;
+ result->scatter_check_threshold = update_state.scatter_check_threshold;
+
+ // Make sure that we're not due for an update check.
+ UpdateCheckParams check_result;
+ EvalStatus check_status = UpdateCheckAllowed(ec, state, error, &check_result);
+ if (check_status == EvalStatus::kFailed)
+ return EvalStatus::kFailed;
+ bool is_check_due = (check_status == EvalStatus::kSucceeded &&
+ check_result.updates_enabled == true);
+
+ // Check whether backoff applies, and if not then which URL can be used for
+ // downloading. These require scanning the download error log, and so they are
+ // done together.
+ UpdateBackoffAndDownloadUrlResult backoff_url_result;
+ EvalStatus backoff_url_status = UpdateBackoffAndDownloadUrl(
+ ec, state, error, &backoff_url_result, update_state);
+ if (backoff_url_status == EvalStatus::kFailed)
+ return EvalStatus::kFailed;
+ result->download_url_idx = backoff_url_result.url_idx;
+ result->download_url_num_errors = backoff_url_result.url_num_errors;
+ result->do_increment_failures = backoff_url_result.do_increment_failures;
+ result->backoff_expiry = backoff_url_result.backoff_expiry;
+ bool is_backoff_active =
+ (backoff_url_status == EvalStatus::kAskMeAgainLater) ||
+ !backoff_url_result.backoff_expiry.is_null();
+
+ DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+ bool is_scattering_active = false;
+ EvalStatus scattering_status = EvalStatus::kSucceeded;
+
+ const bool* device_policy_is_loaded_p = ec->GetValue(
+ dp_provider->var_device_policy_is_loaded());
+ if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+ // Check whether scattering applies to this update attempt. We should not be
+ // scattering if this is an interactive update check, or if OOBE is enabled
+ // but not completed.
+ //
+ // Note: current code further suppresses scattering if a "deadline"
+ // attribute is found in the Omaha response. However, it appears that the
+ // presence of this attribute is merely indicative of an OOBE update, during
+ // which we suppress scattering anyway.
+ bool is_scattering_applicable = false;
+ result->scatter_wait_period = kZeroInterval;
+ result->scatter_check_threshold = 0;
+ if (!update_state.is_interactive) {
+ const bool* is_oobe_enabled_p = ec->GetValue(
+ state->config_provider()->var_is_oobe_enabled());
+ if (is_oobe_enabled_p && !(*is_oobe_enabled_p)) {
+ is_scattering_applicable = true;
+ } else {
+ const bool* is_oobe_complete_p = ec->GetValue(
+ state->system_provider()->var_is_oobe_complete());
+ is_scattering_applicable = (is_oobe_complete_p && *is_oobe_complete_p);
+ }
+ }
+
+ // Compute scattering values.
+ if (is_scattering_applicable) {
+ UpdateScatteringResult scatter_result;
+ scattering_status = UpdateScattering(ec, state, error, &scatter_result,
+ update_state);
+ if (scattering_status == EvalStatus::kFailed) {
+ return EvalStatus::kFailed;
+ } else {
+ result->scatter_wait_period = scatter_result.wait_period;
+ result->scatter_check_threshold = scatter_result.check_threshold;
+ if (scattering_status == EvalStatus::kAskMeAgainLater ||
+ scatter_result.is_scattering)
+ is_scattering_active = true;
+ }
+ }
+ }
+
+ // Find out whether P2P is globally enabled.
+ bool p2p_enabled;
+ EvalStatus p2p_enabled_status = P2PEnabled(ec, state, error, &p2p_enabled);
+ if (p2p_enabled_status != EvalStatus::kSucceeded)
+ return EvalStatus::kFailed;
+
+ // Is P2P is enabled, consider allowing it for downloading and/or sharing.
+ if (p2p_enabled) {
+ // Sharing via P2P is allowed if not disabled by Omaha.
+ if (update_state.p2p_sharing_disabled) {
+ LOG(INFO) << "Blocked P2P sharing because it is disabled by Omaha.";
+ } else {
+ result->p2p_sharing_allowed = true;
+ }
+
+ // Downloading via P2P is allowed if not disabled by Omaha, an update is not
+ // interactive, and other limits haven't been reached.
+ if (update_state.p2p_downloading_disabled) {
+ LOG(INFO) << "Blocked P2P downloading because it is disabled by Omaha.";
+ } else if (update_state.is_interactive) {
+ LOG(INFO) << "Blocked P2P downloading because update is interactive.";
+ } else if (update_state.p2p_num_attempts >= kMaxP2PAttempts) {
+ LOG(INFO) << "Blocked P2P downloading as it was attempted too many "
+ "times.";
+ } else if (!update_state.p2p_first_attempted.is_null() &&
+ ec->IsWallclockTimeGreaterThan(
+ update_state.p2p_first_attempted +
+ TimeDelta::FromSeconds(kMaxP2PAttemptsPeriodInSeconds))) {
+ LOG(INFO) << "Blocked P2P downloading as its usage timespan exceeds "
+ "limit.";
+ } else {
+ // P2P download is allowed; if backoff or scattering are active, be sure
+ // to suppress them, yet prevent any download URL from being used.
+ result->p2p_downloading_allowed = true;
+ if (is_backoff_active || is_scattering_active) {
+ is_backoff_active = is_scattering_active = false;
+ result->download_url_allowed = false;
+ }
+ }
+ }
+
+ // Check for various deterrents.
+ if (is_check_due) {
+ result->cannot_start_reason = UpdateCannotStartReason::kCheckDue;
+ return EvalStatus::kSucceeded;
+ }
+ if (is_backoff_active) {
+ result->cannot_start_reason = UpdateCannotStartReason::kBackoff;
+ return backoff_url_status;
+ }
+ if (is_scattering_active) {
+ result->cannot_start_reason = UpdateCannotStartReason::kScattering;
+ return scattering_status;
+ }
+ if (result->download_url_idx < 0 && !result->p2p_downloading_allowed) {
+ result->cannot_start_reason = UpdateCannotStartReason::kCannotDownload;
+ return EvalStatus::kSucceeded;
+ }
+
+ // Update is good to go.
+ result->update_can_start = true;
+ return EvalStatus::kSucceeded;
+}
+
+// TODO(garnold) Logic in this method is based on
+// ConnectionManager::IsUpdateAllowedOver(); be sure to deprecate the latter.
+//
+// TODO(garnold) The current logic generally treats the list of allowed
+// connections coming from the device policy as a whitelist, meaning that it
+// can only be used for enabling connections, but not disable them. Further,
+// certain connection types (like Bluetooth) cannot be enabled even by policy.
+// In effect, the only thing that device policy can change is to enable
+// updates over a cellular network (disabled by default). We may want to
+// revisit this semantics, allowing greater flexibility in defining specific
+// permissions over all types of networks.
+EvalStatus ChromeOSPolicy::UpdateDownloadAllowed(
+ EvaluationContext* ec,
+ State* state,
+ string* error,
+ bool* result) const {
+ // Get the current connection type.
+ ShillProvider* const shill_provider = state->shill_provider();
+ const ConnectionType* conn_type_p = ec->GetValue(
+ shill_provider->var_conn_type());
+ POLICY_CHECK_VALUE_AND_FAIL(conn_type_p, error);
+ ConnectionType conn_type = *conn_type_p;
+
+ // If we're tethering, treat it as a cellular connection.
+ if (conn_type != ConnectionType::kCellular) {
+ const ConnectionTethering* conn_tethering_p = ec->GetValue(
+ shill_provider->var_conn_tethering());
+ POLICY_CHECK_VALUE_AND_FAIL(conn_tethering_p, error);
+ if (*conn_tethering_p == ConnectionTethering::kConfirmed)
+ conn_type = ConnectionType::kCellular;
+ }
+
+ // By default, we allow updates for all connection types, with exceptions as
+ // noted below. This also determines whether a device policy can override the
+ // default.
+ *result = true;
+ bool device_policy_can_override = false;
+ switch (conn_type) {
+ case ConnectionType::kBluetooth:
+ *result = false;
+ break;
+
+ case ConnectionType::kCellular:
+ *result = false;
+ device_policy_can_override = true;
+ break;
+
+ case ConnectionType::kUnknown:
+ if (error)
+ *error = "Unknown connection type";
+ return EvalStatus::kFailed;
+
+ default:
+ break; // Nothing to do.
+ }
+
+ // If update is allowed, we're done.
+ if (*result)
+ return EvalStatus::kSucceeded;
+
+ // Check whether the device policy specifically allows this connection.
+ if (device_policy_can_override) {
+ DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+ const bool* device_policy_is_loaded_p = ec->GetValue(
+ dp_provider->var_device_policy_is_loaded());
+ if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+ const set<ConnectionType>* allowed_conn_types_p = ec->GetValue(
+ dp_provider->var_allowed_connection_types_for_update());
+ if (allowed_conn_types_p) {
+ if (allowed_conn_types_p->count(conn_type)) {
+ *result = true;
+ return EvalStatus::kSucceeded;
+ }
+ } else if (conn_type == ConnectionType::kCellular) {
+ // Local user settings can allow updates over cellular iff a policy was
+ // loaded but no allowed connections were specified in it.
+ const bool* update_over_cellular_allowed_p = ec->GetValue(
+ state->updater_provider()->var_cellular_enabled());
+ if (update_over_cellular_allowed_p && *update_over_cellular_allowed_p)
+ *result = true;
+ }
+ }
+ }
+
+ return (*result ? EvalStatus::kSucceeded : EvalStatus::kAskMeAgainLater);
+}
+
+EvalStatus ChromeOSPolicy::P2PEnabled(EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ bool* result) const {
+ bool enabled = false;
+
+ // Determine whether use of P2P is allowed by policy. Even if P2P is not
+ // explicitly allowed, we allow it if the device is enterprise enrolled (that
+ // is, missing or empty owner string).
+ DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+ const bool* device_policy_is_loaded_p = ec->GetValue(
+ dp_provider->var_device_policy_is_loaded());
+ if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+ const bool* policy_au_p2p_enabled_p = ec->GetValue(
+ dp_provider->var_au_p2p_enabled());
+ if (policy_au_p2p_enabled_p) {
+ enabled = *policy_au_p2p_enabled_p;
+ } else {
+ const string* policy_owner_p = ec->GetValue(dp_provider->var_owner());
+ if (!policy_owner_p || policy_owner_p->empty())
+ enabled = true;
+ }
+ }
+
+ // Enable P2P, if so mandated by the updater configuration. This is additive
+ // to whether or not P2P is enabled by device policy.
+ if (!enabled) {
+ const bool* updater_p2p_enabled_p = ec->GetValue(
+ state->updater_provider()->var_p2p_enabled());
+ enabled = updater_p2p_enabled_p && *updater_p2p_enabled_p;
+ }
+
+ *result = enabled;
+ return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::P2PEnabledChanged(EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ bool* result,
+ bool prev_result) const {
+ EvalStatus status = P2PEnabled(ec, state, error, result);
+ if (status == EvalStatus::kSucceeded && *result == prev_result)
+ return EvalStatus::kAskMeAgainLater;
+ return status;
+}
+
+EvalStatus ChromeOSPolicy::NextUpdateCheckTime(EvaluationContext* ec,
+ State* state, string* error,
+ Time* next_update_check) const {
+ UpdaterProvider* const updater_provider = state->updater_provider();
+
+ // Don't check for updates too often. We limit the update checks to once every
+ // some interval. The interval is kTimeoutInitialInterval the first time and
+ // kTimeoutPeriodicInterval for the subsequent update checks. If the update
+ // check fails, we increase the interval between the update checks
+ // exponentially until kTimeoutMaxBackoffInterval. Finally, to avoid having
+ // many chromebooks running update checks at the exact same time, we add some
+ // fuzz to the interval.
+ const Time* updater_started_time =
+ ec->GetValue(updater_provider->var_updater_started_time());
+ POLICY_CHECK_VALUE_AND_FAIL(updater_started_time, error);
+
+ const Time* last_checked_time =
+ ec->GetValue(updater_provider->var_last_checked_time());
+
+ const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+ POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+
+ PRNG prng(*seed);
+
+ // If this is the first attempt, compute and return an initial value.
+ if (!last_checked_time || *last_checked_time < *updater_started_time) {
+ *next_update_check = *updater_started_time + FuzzedInterval(
+ &prng, kTimeoutInitialInterval, kTimeoutRegularFuzz);
+ return EvalStatus::kSucceeded;
+ }
+
+ // Check whether the server is enforcing a poll interval; if not, this value
+ // will be zero.
+ const unsigned int* server_dictated_poll_interval = ec->GetValue(
+ updater_provider->var_server_dictated_poll_interval());
+ POLICY_CHECK_VALUE_AND_FAIL(server_dictated_poll_interval, error);
+
+ int interval = *server_dictated_poll_interval;
+ int fuzz = 0;
+
+ // If no poll interval was dictated by server compute a back-off period,
+ // starting from a predetermined base periodic interval and increasing
+ // exponentially by the number of consecutive failed attempts.
+ if (interval == 0) {
+ const unsigned int* consecutive_failed_update_checks = ec->GetValue(
+ updater_provider->var_consecutive_failed_update_checks());
+ POLICY_CHECK_VALUE_AND_FAIL(consecutive_failed_update_checks, error);
+
+ interval = kTimeoutPeriodicInterval;
+ unsigned int num_failures = *consecutive_failed_update_checks;
+ while (interval < kTimeoutMaxBackoffInterval && num_failures) {
+ interval *= 2;
+ num_failures--;
+ }
+ }
+
+ // We cannot back off longer than the predetermined maximum interval.
+ if (interval > kTimeoutMaxBackoffInterval)
+ interval = kTimeoutMaxBackoffInterval;
+
+ // We cannot back off shorter than the predetermined periodic interval. Also,
+ // in this case set the fuzz to a predetermined regular value.
+ if (interval <= kTimeoutPeriodicInterval) {
+ interval = kTimeoutPeriodicInterval;
+ fuzz = kTimeoutRegularFuzz;
+ }
+
+ // If not otherwise determined, defer to a fuzz of +/-(interval / 2).
+ if (fuzz == 0)
+ fuzz = interval;
+
+ *next_update_check = *last_checked_time + FuzzedInterval(
+ &prng, interval, fuzz);
+ return EvalStatus::kSucceeded;
+}
+
+TimeDelta ChromeOSPolicy::FuzzedInterval(PRNG* prng, int interval, int fuzz) {
+ DCHECK_GE(interval, 0);
+ DCHECK_GE(fuzz, 0);
+ int half_fuzz = fuzz / 2;
+ // This guarantees the output interval is non negative.
+ int interval_min = max(interval - half_fuzz, 0);
+ int interval_max = interval + half_fuzz;
+ return TimeDelta::FromSeconds(prng->RandMinMax(interval_min, interval_max));
+}
+
+EvalStatus ChromeOSPolicy::UpdateBackoffAndDownloadUrl(
+ EvaluationContext* ec, State* state, string* error,
+ UpdateBackoffAndDownloadUrlResult* result,
+ const UpdateState& update_state) const {
+ // Sanity checks.
+ DCHECK_GE(update_state.download_errors_max, 0);
+
+ // Set default result values.
+ result->do_increment_failures = false;
+ result->backoff_expiry = update_state.backoff_expiry;
+ result->url_idx = -1;
+ result->url_num_errors = 0;
+
+ const bool* is_official_build_p = ec->GetValue(
+ state->system_provider()->var_is_official_build());
+ bool is_official_build = (is_official_build_p ? *is_official_build_p : true);
+
+ // Check whether backoff is enabled.
+ bool may_backoff = false;
+ if (update_state.is_backoff_disabled) {
+ LOG(INFO) << "Backoff disabled by Omaha.";
+ } else if (update_state.is_interactive) {
+ LOG(INFO) << "No backoff for interactive updates.";
+ } else if (update_state.is_delta_payload) {
+ LOG(INFO) << "No backoff for delta payloads.";
+ } else if (!is_official_build) {
+ LOG(INFO) << "No backoff for unofficial builds.";
+ } else {
+ may_backoff = true;
+ }
+
+ // If previous backoff still in effect, block.
+ if (may_backoff && !update_state.backoff_expiry.is_null() &&
+ !ec->IsWallclockTimeGreaterThan(update_state.backoff_expiry)) {
+ LOG(INFO) << "Previous backoff has not expired, waiting.";
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ // Determine whether HTTP downloads are forbidden by policy. This only
+ // applies to official system builds; otherwise, HTTP is always enabled.
+ bool http_allowed = true;
+ if (is_official_build) {
+ DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+ const bool* device_policy_is_loaded_p = ec->GetValue(
+ dp_provider->var_device_policy_is_loaded());
+ if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+ const bool* policy_http_downloads_enabled_p = ec->GetValue(
+ dp_provider->var_http_downloads_enabled());
+ http_allowed = (!policy_http_downloads_enabled_p ||
+ *policy_http_downloads_enabled_p);
+ }
+ }
+
+ int url_idx = update_state.last_download_url_idx;
+ if (url_idx < 0)
+ url_idx = -1;
+ bool do_advance_url = false;
+ bool is_failure_occurred = false;
+ Time err_time;
+
+ // Scan the relevant part of the download error log, tracking which URLs are
+ // being used, and accounting the number of errors for each URL. Note that
+ // this process may not traverse all errors provided, as it may decide to bail
+ // out midway depending on the particular errors exhibited, the number of
+ // failures allowed, etc. When this ends, |url_idx| will point to the last URL
+ // used (-1 if starting fresh), |do_advance_url| will determine whether the
+ // URL needs to be advanced, and |err_time| the point in time when the last
+ // reported error occurred. Additionally, if the error log indicates that an
+ // update attempt has failed (abnormal), then |is_failure_occurred| will be
+ // set to true.
+ const int num_urls = update_state.download_urls.size();
+ int prev_url_idx = -1;
+ int url_num_errors = update_state.last_download_url_num_errors;
+ Time prev_err_time;
+ bool is_first = true;
+ for (const auto& err_tuple : update_state.download_errors) {
+ // Do some sanity checks.
+ int used_url_idx = get<0>(err_tuple);
+ if (is_first && url_idx >= 0 && used_url_idx != url_idx) {
+ LOG(WARNING) << "First URL in error log (" << used_url_idx
+ << ") not as expected (" << url_idx << ")";
+ }
+ is_first = false;
+ url_idx = used_url_idx;
+ if (url_idx < 0 || url_idx >= num_urls) {
+ LOG(ERROR) << "Download error log contains an invalid URL index ("
+ << url_idx << ")";
+ return EvalStatus::kFailed;
+ }
+ err_time = get<2>(err_tuple);
+ if (!(prev_err_time.is_null() || err_time >= prev_err_time)) {
+ // TODO(garnold) Monotonicity cannot really be assumed when dealing with
+ // wallclock-based timestamps. However, we're making a simplifying
+ // assumption so as to keep the policy implementation straightforward, for
+ // now. In general, we should convert all timestamp handling in the
+ // UpdateManager to use monotonic time (instead of wallclock), including
+ // the computation of various expiration times (backoff, scattering, etc).
+ // The client will do whatever conversions necessary when
+ // persisting/retrieving these values across reboots. See chromium:408794.
+ LOG(ERROR) << "Download error timestamps not monotonically increasing.";
+ return EvalStatus::kFailed;
+ }
+ prev_err_time = err_time;
+
+ // Ignore errors that happened before the last known failed attempt.
+ if (!update_state.failures_last_updated.is_null() &&
+ err_time <= update_state.failures_last_updated)
+ continue;
+
+ if (prev_url_idx >= 0) {
+ if (url_idx < prev_url_idx) {
+ LOG(ERROR) << "The URLs in the download error log have wrapped around ("
+ << prev_url_idx << "->" << url_idx
+ << "). This should not have happened and means that there's "
+ "a bug. To be conservative, we record a failed attempt "
+ "(invalidating the rest of the error log) and resume "
+ "download from the first usable URL.";
+ url_idx = -1;
+ is_failure_occurred = true;
+ break;
+ }
+
+ if (url_idx > prev_url_idx) {
+ url_num_errors = 0;
+ do_advance_url = false;
+ }
+ }
+
+ if (HandleErrorCode(get<1>(err_tuple), &url_num_errors) ||
+ url_num_errors > update_state.download_errors_max)
+ do_advance_url = true;
+
+ prev_url_idx = url_idx;
+ }
+
+ // If required, advance to the next usable URL. If the URLs wraparound, we
+ // mark an update attempt failure. Also be sure to set the download error
+ // count to zero.
+ if (url_idx < 0 || do_advance_url) {
+ url_num_errors = 0;
+ int start_url_idx = -1;
+ do {
+ if (++url_idx == num_urls) {
+ url_idx = 0;
+ // We only mark failure if an actual advancing of a URL was required.
+ if (do_advance_url)
+ is_failure_occurred = true;
+ }
+
+ if (start_url_idx < 0)
+ start_url_idx = url_idx;
+ else if (url_idx == start_url_idx)
+ url_idx = -1; // No usable URL.
+ } while (url_idx >= 0 &&
+ !IsUrlUsable(update_state.download_urls[url_idx], http_allowed));
+ }
+
+ // If we have a download URL but a failure was observed, compute a new backoff
+ // expiry (if allowed). The backoff period is generally 2 ^ (num_failures - 1)
+ // days, bounded by the size of int and kAttemptBackoffMaxIntervalInDays, and
+ // fuzzed by kAttemptBackoffFuzzInHours hours. Backoff expiry is computed from
+ // the latest recorded time of error.
+ Time backoff_expiry;
+ if (url_idx >= 0 && is_failure_occurred && may_backoff) {
+ CHECK(!err_time.is_null())
+ << "We must have an error timestamp if a failure occurred!";
+ const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+ POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+ PRNG prng(*seed);
+ int exp = min(update_state.num_failures,
+ static_cast<int>(sizeof(int)) * 8 - 2);
+ TimeDelta backoff_interval = TimeDelta::FromDays(
+ min(1 << exp, kAttemptBackoffMaxIntervalInDays));
+ TimeDelta backoff_fuzz = TimeDelta::FromHours(kAttemptBackoffFuzzInHours);
+ TimeDelta wait_period = FuzzedInterval(&prng, backoff_interval.InSeconds(),
+ backoff_fuzz.InSeconds());
+ backoff_expiry = err_time + wait_period;
+
+ // If the newly computed backoff already expired, nullify it.
+ if (ec->IsWallclockTimeGreaterThan(backoff_expiry))
+ backoff_expiry = Time();
+ }
+
+ result->do_increment_failures = is_failure_occurred;
+ result->backoff_expiry = backoff_expiry;
+ result->url_idx = url_idx;
+ result->url_num_errors = url_num_errors;
+ return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::UpdateScattering(
+ EvaluationContext* ec,
+ State* state,
+ string* error,
+ UpdateScatteringResult* result,
+ const UpdateState& update_state) const {
+ // Preconditions. These stem from the postconditions and usage contract.
+ DCHECK(update_state.scatter_wait_period >= kZeroInterval);
+ DCHECK_GE(update_state.scatter_check_threshold, 0);
+
+ // Set default result values.
+ result->is_scattering = false;
+ result->wait_period = kZeroInterval;
+ result->check_threshold = 0;
+
+ DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+
+ // Ensure that a device policy is loaded.
+ const bool* device_policy_is_loaded_p = ec->GetValue(
+ dp_provider->var_device_policy_is_loaded());
+ if (!(device_policy_is_loaded_p && *device_policy_is_loaded_p))
+ return EvalStatus::kSucceeded;
+
+ // Is scattering enabled by policy?
+ const TimeDelta* scatter_factor_p = ec->GetValue(
+ dp_provider->var_scatter_factor());
+ if (!scatter_factor_p || *scatter_factor_p == kZeroInterval)
+ return EvalStatus::kSucceeded;
+
+ // Obtain a pseudo-random number generator.
+ const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+ POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+ PRNG prng(*seed);
+
+ // Step 1: Maintain the scattering wait period.
+ //
+ // If no wait period was previously determined, or it no longer fits in the
+ // scatter factor, then generate a new one. Otherwise, keep the one we have.
+ TimeDelta wait_period = update_state.scatter_wait_period;
+ if (wait_period == kZeroInterval || wait_period > *scatter_factor_p) {
+ wait_period = TimeDelta::FromSeconds(
+ prng.RandMinMax(1, scatter_factor_p->InSeconds()));
+ }
+
+ // If we surpassed the wait period or the max scatter period associated with
+ // the update, then no wait is needed.
+ Time wait_expires = (update_state.first_seen +
+ min(wait_period, update_state.scatter_wait_period_max));
+ if (ec->IsWallclockTimeGreaterThan(wait_expires))
+ wait_period = kZeroInterval;
+
+ // Step 2: Maintain the update check threshold count.
+ //
+ // If an update check threshold is not specified then generate a new
+ // one.
+ int check_threshold = update_state.scatter_check_threshold;
+ if (check_threshold == 0) {
+ check_threshold = prng.RandMinMax(
+ update_state.scatter_check_threshold_min,
+ update_state.scatter_check_threshold_max);
+ }
+
+ // If the update check threshold is not within allowed range then nullify it.
+ // TODO(garnold) This is compliant with current logic found in
+ // OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied(). We may want
+ // to change it so that it behaves similarly to the wait period case, namely
+ // if the current value exceeds the maximum, we set a new one within range.
+ if (check_threshold > update_state.scatter_check_threshold_max)
+ check_threshold = 0;
+
+ // If the update check threshold is non-zero and satisfied, then nullify it.
+ if (check_threshold > 0 && update_state.num_checks >= check_threshold)
+ check_threshold = 0;
+
+ bool is_scattering = (wait_period != kZeroInterval || check_threshold);
+ EvalStatus ret = EvalStatus::kSucceeded;
+ if (is_scattering && wait_period == update_state.scatter_wait_period &&
+ check_threshold == update_state.scatter_check_threshold)
+ ret = EvalStatus::kAskMeAgainLater;
+ result->is_scattering = is_scattering;
+ result->wait_period = wait_period;
+ result->check_threshold = check_threshold;
+ return ret;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/chromeos_policy.h b/update_manager/chromeos_policy.h
new file mode 100644
index 0000000..e400cdc
--- /dev/null
+++ b/update_manager/chromeos_policy.h
@@ -0,0 +1,191 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_CHROMEOS_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_CHROMEOS_POLICY_H_
+
+#include <string>
+
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/prng.h"
+
+namespace chromeos_update_manager {
+
+// Output information from UpdateBackoffAndDownloadUrl.
+struct UpdateBackoffAndDownloadUrlResult {
+ // Whether the failed attempt count (maintained by the caller) needs to be
+ // incremented.
+ bool do_increment_failures;
+ // The current backoff expiry. Null if backoff is not in effect.
+ base::Time backoff_expiry;
+ // The new URL index to use and number of download errors associated with it.
+ // Significant iff |do_increment_failures| is false and |backoff_expiry| is
+ // null. Negative value means no usable URL was found.
+ int url_idx;
+ int url_num_errors;
+};
+
+// Parameters for update scattering, as returned by UpdateScattering.
+struct UpdateScatteringResult {
+ bool is_scattering;
+ base::TimeDelta wait_period;
+ int check_threshold;
+};
+
+// ChromeOSPolicy implements the policy-related logic used in ChromeOS.
+class ChromeOSPolicy : public Policy {
+ public:
+ ChromeOSPolicy() {}
+ ~ChromeOSPolicy() override {}
+
+ // Policy overrides.
+ EvalStatus UpdateCheckAllowed(
+ EvaluationContext* ec, State* state, std::string* error,
+ UpdateCheckParams* result) const override;
+
+ EvalStatus UpdateCanStart(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ UpdateDownloadParams* result,
+ UpdateState update_state) const override;
+
+ EvalStatus UpdateDownloadAllowed(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ bool* result) const override;
+
+ EvalStatus P2PEnabled(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ bool* result) const override;
+
+ EvalStatus P2PEnabledChanged(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ bool* result,
+ bool prev_result) const override;
+
+ protected:
+ // Policy override.
+ std::string PolicyName() const override { return "ChromeOSPolicy"; }
+
+ private:
+ friend class UmChromeOSPolicyTest;
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ FirstCheckIsAtMostInitialIntervalAfterStart);
+ FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckBaseIntervalAndFuzz);
+ FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckBackoffIntervalAndFuzz);
+ FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckServerDictatedPollInterval);
+ FRIEND_TEST(UmChromeOSPolicyTest, ExponentialBackoffIsCapped);
+ FRIEND_TEST(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout);
+ FRIEND_TEST(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringNewWaitPeriodApplies);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringPrevWaitPeriodStillApplies);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringNewCountThresholdApplies);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringPrevCountThresholdStillApplies);
+ FRIEND_TEST(UmChromeOSPolicyTest, UpdateCanStartAllowedScatteringSatisfied);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedInteractivePreventsScattering);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PDownloadingBlockedDueToNumAttempts);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PDownloadingBlockedDueToAttemptsPeriod);
+
+ // Auxiliary constant (zero by default).
+ const base::TimeDelta kZeroInterval;
+
+ // Default update check timeout interval/fuzz values used to compute the
+ // NextUpdateCheckTime(), in seconds. Actual fuzz is within +/- half of the
+ // indicated value.
+ static const int kTimeoutInitialInterval;
+ static const int kTimeoutPeriodicInterval;
+ static const int kTimeoutMaxBackoffInterval;
+ static const int kTimeoutRegularFuzz;
+
+ // Maximum update attempt backoff interval and fuzz.
+ static const int kAttemptBackoffMaxIntervalInDays;
+ static const int kAttemptBackoffFuzzInHours;
+
+ // Maximum number of times we'll allow using P2P for the same update payload.
+ static const int kMaxP2PAttempts;
+ // Maximum period of time allowed for download a payload via P2P, in seconds.
+ static const int kMaxP2PAttemptsPeriodInSeconds;
+
+ // A private policy implementation returning the wallclock timestamp when
+ // the next update check should happen.
+ // TODO(garnold) We should probably change that to infer a monotonic
+ // timestamp, which will make the update check intervals more resilient to
+ // clock skews. Might require switching some of the variables exported by the
+ // UpdaterProvider to report monotonic time, as well.
+ EvalStatus NextUpdateCheckTime(EvaluationContext* ec, State* state,
+ std::string* error,
+ base::Time* next_update_check) const;
+
+ // Returns a TimeDelta based on the provided |interval| seconds +/- half
+ // |fuzz| seconds. The return value is guaranteed to be a non-negative
+ // TimeDelta.
+ static base::TimeDelta FuzzedInterval(PRNG* prng, int interval, int fuzz);
+
+ // A private policy for determining backoff and the download URL to use.
+ // Within |update_state|, |backoff_expiry| and |is_backoff_disabled| are used
+ // for determining whether backoff is still in effect; if not,
+ // |download_errors| is scanned past |failures_last_updated|, and a new
+ // download URL from |download_urls| is found and written to |result->url_idx|
+ // (-1 means no usable URL exists); |download_errors_max| determines the
+ // maximum number of attempts per URL, according to the Omaha response. If an
+ // update failure is identified then |result->do_increment_failures| is set to
+ // true; if backoff is enabled, a new backoff period is computed (from the
+ // time of failure) based on |num_failures|. Otherwise, backoff expiry is
+ // nullified, indicating that no backoff is in effect.
+ //
+ // If backing off but the previous backoff expiry is unchanged, returns
+ // |EvalStatus::kAskMeAgainLater|. Otherwise:
+ //
+ // * If backing off with a new expiry time, then |result->backoff_expiry| is
+ // set to this time.
+ //
+ // * Else, |result->backoff_expiry| is set to null, indicating that no backoff
+ // is in effect.
+ //
+ // In any of these cases, returns |EvalStatus::kSucceeded|. If an error
+ // occurred, returns |EvalStatus::kFailed|.
+ EvalStatus UpdateBackoffAndDownloadUrl(
+ EvaluationContext* ec, State* state, std::string* error,
+ UpdateBackoffAndDownloadUrlResult* result,
+ const UpdateState& update_state) const;
+
+ // A private policy for checking whether scattering is due. Writes in |result|
+ // the decision as to whether or not to scatter; a wallclock-based scatter
+ // wait period, which ranges from zero (do not wait) and no greater than the
+ // current scatter factor provided by the device policy (if available) or the
+ // maximum wait period determined by Omaha; and an update check-based
+ // threshold between zero (no threshold) and the maximum number determined by
+ // the update engine. Within |update_state|, |scatter_wait_period| should
+ // contain the last scattering period returned by this function, or zero if no
+ // wait period is known; |scatter_check_threshold| is the last update check
+ // threshold, or zero if no such threshold is known. If not scattering, or if
+ // any of the scattering values has changed, returns |EvalStatus::kSucceeded|;
+ // otherwise, |EvalStatus::kAskMeAgainLater|.
+ EvalStatus UpdateScattering(EvaluationContext* ec, State* state,
+ std::string* error,
+ UpdateScatteringResult* result,
+ const UpdateState& update_state) const;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeOSPolicy);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_CHROMEOS_POLICY_H_
diff --git a/update_manager/chromeos_policy_unittest.cc b/update_manager/chromeos_policy_unittest.cc
new file mode 100644
index 0000000..2c935fd
--- /dev/null
+++ b/update_manager/chromeos_policy_unittest.cc
@@ -0,0 +1,1608 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/chromeos_policy.h"
+
+#include <set>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <base/time/time.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/fake_state.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::FakeClock;
+using std::set;
+using std::string;
+using std::tuple;
+using std::vector;
+
+namespace chromeos_update_manager {
+
+class UmChromeOSPolicyTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ SetUpDefaultClock();
+ eval_ctx_ = new EvaluationContext(&fake_clock_, TimeDelta::FromSeconds(5));
+ SetUpDefaultState();
+ SetUpDefaultDevicePolicy();
+ }
+
+ void TearDown() override {
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ // Sets the clock to fixed values.
+ void SetUpDefaultClock() {
+ fake_clock_.SetMonotonicTime(Time::FromInternalValue(12345678L));
+ fake_clock_.SetWallclockTime(Time::FromInternalValue(12345678901234L));
+ }
+
+ void SetUpDefaultState() {
+ fake_state_.updater_provider()->var_updater_started_time()->reset(
+ new Time(fake_clock_.GetWallclockTime()));
+ fake_state_.updater_provider()->var_last_checked_time()->reset(
+ new Time(fake_clock_.GetWallclockTime()));
+ fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+ reset(new unsigned int{0});
+ fake_state_.updater_provider()->var_server_dictated_poll_interval()->
+ reset(new unsigned int{0});
+ fake_state_.updater_provider()->var_forced_update_requested()->
+ reset(new UpdateRequestStatus{UpdateRequestStatus::kNone});
+
+ fake_state_.random_provider()->var_seed()->reset(
+ new uint64_t(4)); // chosen by fair dice roll.
+ // guaranteed to be random.
+
+ // No device policy loaded by default.
+ fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+ new bool(false));
+
+ // OOBE is enabled by default.
+ fake_state_.config_provider()->var_is_oobe_enabled()->reset(
+ new bool(true));
+
+ // For the purpose of the tests, this is an official build and OOBE was
+ // completed.
+ fake_state_.system_provider()->var_is_official_build()->reset(
+ new bool(true));
+ fake_state_.system_provider()->var_is_oobe_complete()->reset(
+ new bool(true));
+ fake_state_.system_provider()->var_is_boot_device_removable()->reset(
+ new bool(false));
+
+ // Connection is wifi, untethered.
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kWifi));
+ fake_state_.shill_provider()->var_conn_tethering()->
+ reset(new ConnectionTethering(ConnectionTethering::kNotDetected));
+ }
+
+ // Sets up a default device policy that does not impose any restrictions
+ // (HTTP) nor enables any features (P2P).
+ void SetUpDefaultDevicePolicy() {
+ fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_update_disabled()->reset(
+ new bool(false));
+ fake_state_.device_policy_provider()->
+ var_allowed_connection_types_for_update()->reset(nullptr);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta());
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(false));
+ fake_state_.device_policy_provider()->var_release_channel_delegated()->
+ reset(new bool(true));
+ }
+
+ // Configures the UpdateCheckAllowed policy to return a desired value by
+ // faking the current wall clock time as needed. Restores the default state.
+ // This is used when testing policies that depend on this one.
+ void SetUpdateCheckAllowed(bool allow_check) {
+ Time next_update_check;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime,
+ &next_update_check);
+ SetUpDefaultState();
+ SetUpDefaultDevicePolicy();
+ Time curr_time = next_update_check;
+ if (allow_check)
+ curr_time += TimeDelta::FromSeconds(1);
+ else
+ curr_time -= TimeDelta::FromSeconds(1);
+ fake_clock_.SetWallclockTime(curr_time);
+ }
+
+ // Returns a default UpdateState structure:
+ UpdateState GetDefaultUpdateState(TimeDelta first_seen_period) {
+ Time first_seen_time = fake_clock_.GetWallclockTime() - first_seen_period;
+ UpdateState update_state = UpdateState();
+
+ // This is a non-interactive check returning a delta payload, seen for the
+ // first time (|first_seen_period| ago). Clearly, there were no failed
+ // attempts so far.
+ update_state.is_interactive = false;
+ update_state.is_delta_payload = false;
+ update_state.first_seen = first_seen_time;
+ update_state.num_checks = 1;
+ update_state.num_failures = 0;
+ update_state.failures_last_updated = Time(); // Needs to be zero.
+ // There's a single HTTP download URL with a maximum of 10 retries.
+ update_state.download_urls = vector<string>{"http://fake/url/"};
+ update_state.download_errors_max = 10;
+ // Download was never attempted.
+ update_state.last_download_url_idx = -1;
+ update_state.last_download_url_num_errors = 0;
+ // There were no download errors.
+ update_state.download_errors = vector<tuple<int, ErrorCode, Time>>();
+ // P2P is not disabled by Omaha.
+ update_state.p2p_downloading_disabled = false;
+ update_state.p2p_sharing_disabled = false;
+ // P2P was not attempted.
+ update_state.p2p_num_attempts = 0;
+ update_state.p2p_first_attempted = Time();
+ // No active backoff period, backoff is not disabled by Omaha.
+ update_state.backoff_expiry = Time();
+ update_state.is_backoff_disabled = false;
+ // There is no active scattering wait period (max 7 days allowed) nor check
+ // threshold (none allowed).
+ update_state.scatter_wait_period = TimeDelta();
+ update_state.scatter_check_threshold = 0;
+ update_state.scatter_wait_period_max = TimeDelta::FromDays(7);
+ update_state.scatter_check_threshold_min = 0;
+ update_state.scatter_check_threshold_max = 0;
+
+ return update_state;
+ }
+
+ // Runs the passed |policy_method| policy and expects it to return the
+ // |expected| return value.
+ template<typename T, typename R, typename... Args>
+ void ExpectPolicyStatus(
+ EvalStatus expected,
+ T policy_method,
+ R* result, Args... args) {
+ string error = "<None>";
+ eval_ctx_->ResetEvaluation();
+ EXPECT_EQ(expected,
+ (policy_.*policy_method)(eval_ctx_.get(), &fake_state_, &error,
+ result, args...))
+ << "Returned error: " << error
+ << "\nEvaluation context: " << eval_ctx_->DumpContext();
+ }
+
+ chromeos::FakeMessageLoop loop_{nullptr};
+ FakeClock fake_clock_;
+ FakeState fake_state_;
+ scoped_refptr<EvaluationContext> eval_ctx_;
+ ChromeOSPolicy policy_; // ChromeOSPolicy under test.
+};
+
+TEST_F(UmChromeOSPolicyTest, FirstCheckIsAtMostInitialIntervalAfterStart) {
+ Time next_update_check;
+
+ // Set the last update time so it'll appear as if this is a first update check
+ // in the lifetime of the current updater.
+ fake_state_.updater_provider()->var_last_checked_time()->reset(
+ new Time(fake_clock_.GetWallclockTime() - TimeDelta::FromMinutes(10)));
+
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ EXPECT_LE(fake_clock_.GetWallclockTime(), next_update_check);
+ EXPECT_GE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ ChromeOSPolicy::kTimeoutInitialInterval +
+ ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+ next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckBaseIntervalAndFuzz) {
+ // Ensure that we're using the correct interval (kPeriodicInterval) and fuzz
+ // (kTimeoutRegularFuzz) as base values for period updates.
+ Time next_update_check;
+
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ EXPECT_LE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ ChromeOSPolicy::kTimeoutPeriodicInterval -
+ ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+ next_update_check);
+ EXPECT_GE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ ChromeOSPolicy::kTimeoutPeriodicInterval +
+ ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+ next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckBackoffIntervalAndFuzz) {
+ // Ensure that we're properly backing off and fuzzing in the presence of
+ // failed updates attempts.
+ Time next_update_check;
+
+ fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+ reset(new unsigned int{2});
+
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ int expected_interval = ChromeOSPolicy::kTimeoutPeriodicInterval * 4;
+ EXPECT_LE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ expected_interval - expected_interval / 2),
+ next_update_check);
+ EXPECT_GE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ expected_interval + expected_interval / 2),
+ next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckServerDictatedPollInterval) {
+ // Policy honors the server provided check poll interval.
+ Time next_update_check;
+
+ const unsigned int kInterval = ChromeOSPolicy::kTimeoutPeriodicInterval * 4;
+ fake_state_.updater_provider()->var_server_dictated_poll_interval()->
+ reset(new unsigned int{kInterval});
+ // We should not be backing off in this case.
+ fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+ reset(new unsigned int{2});
+
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ EXPECT_LE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ kInterval - kInterval / 2),
+ next_update_check);
+ EXPECT_GE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ kInterval + kInterval / 2),
+ next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, ExponentialBackoffIsCapped) {
+ Time next_update_check;
+
+ fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+ reset(new unsigned int{100});
+
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ EXPECT_LE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ ChromeOSPolicy::kTimeoutMaxBackoffInterval -
+ ChromeOSPolicy::kTimeoutMaxBackoffInterval / 2),
+ next_update_check);
+ EXPECT_GE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ ChromeOSPolicy::kTimeoutMaxBackoffInterval +
+ ChromeOSPolicy::kTimeoutMaxBackoffInterval /2),
+ next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout) {
+ // We get the next update_check timestamp from the policy's private method
+ // and then we check the public method respects that value on the normal
+ // case.
+ Time next_update_check;
+ Time last_checked_time =
+ fake_clock_.GetWallclockTime() + TimeDelta::FromMinutes(1234);
+
+ fake_state_.updater_provider()->var_last_checked_time()->reset(
+ new Time(last_checked_time));
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ UpdateCheckParams result;
+
+ // Check that the policy blocks until the next_update_check is reached.
+ SetUpDefaultClock();
+ SetUpDefaultState();
+ fake_state_.updater_provider()->var_last_checked_time()->reset(
+ new Time(last_checked_time));
+ fake_clock_.SetWallclockTime(next_update_check - TimeDelta::FromSeconds(1));
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+ &Policy::UpdateCheckAllowed, &result);
+
+ SetUpDefaultClock();
+ SetUpDefaultState();
+ fake_state_.updater_provider()->var_last_checked_time()->reset(
+ new Time(last_checked_time));
+ fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1));
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCheckAllowed, &result);
+ EXPECT_TRUE(result.updates_enabled);
+ EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE) {
+ // Update checks are deferred until OOBE is completed.
+
+ // Ensure that update is not allowed even if wait period is satisfied.
+ Time next_update_check;
+ Time last_checked_time =
+ fake_clock_.GetWallclockTime() + TimeDelta::FromMinutes(1234);
+
+ fake_state_.updater_provider()->var_last_checked_time()->reset(
+ new Time(last_checked_time));
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ SetUpDefaultClock();
+ SetUpDefaultState();
+ fake_state_.updater_provider()->var_last_checked_time()->reset(
+ new Time(last_checked_time));
+ fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1));
+ fake_state_.system_provider()->var_is_oobe_complete()->reset(
+ new bool(false));
+
+ UpdateCheckParams result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+ &Policy::UpdateCheckAllowed, &result);
+
+ // Now check that it is allowed if OOBE is completed.
+ SetUpDefaultClock();
+ SetUpDefaultState();
+ fake_state_.updater_provider()->var_last_checked_time()->reset(
+ new Time(last_checked_time));
+ fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1));
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCheckAllowed, &result);
+ EXPECT_TRUE(result.updates_enabled);
+ EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWithAttributes) {
+ // Update check is allowed, response includes attributes for use in the
+ // request.
+ SetUpdateCheckAllowed(true);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_target_version_prefix()->
+ reset(new string("1.2"));
+ fake_state_.device_policy_provider()->var_release_channel_delegated()->
+ reset(new bool(false));
+ fake_state_.device_policy_provider()->var_release_channel()->
+ reset(new string("foo-channel"));
+
+ UpdateCheckParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCheckAllowed, &result);
+ EXPECT_TRUE(result.updates_enabled);
+ EXPECT_EQ("1.2", result.target_version_prefix);
+ EXPECT_EQ("foo-channel", result.target_channel);
+ EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCheckAllowedUpdatesDisabledForUnofficialBuilds) {
+ // UpdateCheckAllowed should return kAskMeAgainLater if this is an unofficial
+ // build; we don't want periodic update checks on developer images.
+
+ fake_state_.system_provider()->var_is_official_build()->reset(
+ new bool(false));
+
+ UpdateCheckParams result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+ &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCheckAllowedUpdatesDisabledForRemovableBootDevice) {
+ // UpdateCheckAllowed should return false (kSucceeded) if the image booted
+ // from a removable device.
+
+ fake_state_.system_provider()->var_is_boot_device_removable()->reset(
+ new bool(true));
+
+ UpdateCheckParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCheckAllowed, &result);
+ EXPECT_FALSE(result.updates_enabled);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedUpdatesDisabledByPolicy) {
+ // UpdateCheckAllowed should return kAskMeAgainLater because a device policy
+ // is loaded and prohibits updates.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_update_disabled()->reset(
+ new bool(true));
+
+ UpdateCheckParams result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+ &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCheckAllowedForcedUpdateRequestedInteractive) {
+ // UpdateCheckAllowed should return true because a forced update request was
+ // signaled for an interactive update.
+
+ SetUpdateCheckAllowed(true);
+ fake_state_.updater_provider()->var_forced_update_requested()->reset(
+ new UpdateRequestStatus(UpdateRequestStatus::kInteractive));
+
+ UpdateCheckParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCheckAllowed, &result);
+ EXPECT_TRUE(result.updates_enabled);
+ EXPECT_TRUE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedForcedUpdateRequestedPeriodic) {
+ // UpdateCheckAllowed should return true because a forced update request was
+ // signaled for a periodic check.
+
+ SetUpdateCheckAllowed(true);
+ fake_state_.updater_provider()->var_forced_update_requested()->reset(
+ new UpdateRequestStatus(UpdateRequestStatus::kPeriodic));
+
+ UpdateCheckParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCheckAllowed, &result);
+ EXPECT_TRUE(result.updates_enabled);
+ EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartFailsCheckAllowedError) {
+ // The UpdateCanStart policy fails, not being able to query
+ // UpdateCheckAllowed.
+
+ // Configure the UpdateCheckAllowed policy to fail.
+ fake_state_.updater_provider()->var_updater_started_time()->reset(nullptr);
+
+ // Check that the UpdateCanStart fails.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kFailed,
+ &Policy::UpdateCanStart, &result, update_state);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartNotAllowedCheckDue) {
+ // The UpdateCanStart policy returns false because we are due for another
+ // update check. Ensure that download related values are still returned.
+
+ SetUpdateCheckAllowed(true);
+
+ // Check that the UpdateCanStart returns false.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCanStart, &result, update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kCheckDue, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_EQ(0, result.download_url_num_errors);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoDevicePolicy) {
+ // The UpdateCanStart policy returns true; no device policy is loaded.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+ new bool(false));
+
+ // Check that the UpdateCanStart returns true with no further attributes.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCanStart, &result, update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_FALSE(result.p2p_downloading_allowed);
+ EXPECT_FALSE(result.p2p_sharing_allowed);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBlankPolicy) {
+ // The UpdateCanStart policy returns true; device policy is loaded but imposes
+ // no restrictions on updating.
+
+ SetUpdateCheckAllowed(false);
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCanStart, &result, update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_FALSE(result.p2p_downloading_allowed);
+ EXPECT_FALSE(result.p2p_sharing_allowed);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedBackoffNewWaitPeriodApplies) {
+ // The UpdateCanStart policy returns false; failures are reported and a new
+ // backoff period is enacted.
+
+ SetUpdateCheckAllowed(false);
+
+ const Time curr_time = fake_clock_.GetWallclockTime();
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+ update_state.download_errors_max = 1;
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kBackoff, result.cannot_start_reason);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedBackoffPrevWaitPeriodStillApplies) {
+ // The UpdateCanStart policy returns false; a previously enacted backoff
+ // period still applies.
+
+ SetUpdateCheckAllowed(false);
+
+ const Time curr_time = fake_clock_.GetWallclockTime();
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+ update_state.download_errors_max = 1;
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ update_state.failures_last_updated = curr_time;
+ update_state.backoff_expiry = curr_time + TimeDelta::FromMinutes(3);
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::UpdateCanStart,
+ &result, update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kBackoff, result.cannot_start_reason);
+ EXPECT_FALSE(result.do_increment_failures);
+ EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBackoffSatisfied) {
+ // The UpdateCanStart policy returns true; a previously enacted backoff period
+ // has elapsed, we're good to go.
+
+ SetUpdateCheckAllowed(false);
+
+ const Time curr_time = fake_clock_.GetWallclockTime();
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+ update_state.download_errors_max = 1;
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ update_state.failures_last_updated = curr_time - TimeDelta::FromSeconds(1);
+ update_state.backoff_expiry = curr_time - TimeDelta::FromSeconds(1);
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart,
+ &result, update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+ EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBackoffDisabled) {
+ // The UpdateCanStart policy returns false; failures are reported but backoff
+ // is disabled.
+
+ SetUpdateCheckAllowed(false);
+
+ const Time curr_time = fake_clock_.GetWallclockTime();
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+ update_state.download_errors_max = 1;
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ update_state.is_backoff_disabled = true;
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffInteractive) {
+ // The UpdateCanStart policy returns false; failures are reported but this is
+ // an interactive update check.
+
+ SetUpdateCheckAllowed(false);
+
+ const Time curr_time = fake_clock_.GetWallclockTime();
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+ update_state.download_errors_max = 1;
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ update_state.is_interactive = true;
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffDelta) {
+ // The UpdateCanStart policy returns false; failures are reported but this is
+ // a delta payload.
+
+ SetUpdateCheckAllowed(false);
+
+ const Time curr_time = fake_clock_.GetWallclockTime();
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+ update_state.download_errors_max = 1;
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ update_state.is_delta_payload = true;
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffUnofficialBuild) {
+ // The UpdateCanStart policy returns false; failures are reported but this is
+ // an unofficial build.
+
+ SetUpdateCheckAllowed(false);
+
+ const Time curr_time = fake_clock_.GetWallclockTime();
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+ update_state.download_errors_max = 1;
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+
+ fake_state_.system_provider()->var_is_official_build()->
+ reset(new bool(false));
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartFailsScatteringFailed) {
+ // The UpdateCanStart policy fails because the UpdateScattering policy it
+ // depends on fails (unset variable).
+
+ SetUpdateCheckAllowed(false);
+
+ // Override the default seed variable with a null value so that the policy
+ // request would fail.
+ // TODO(garnold) This failure may or may not fail a number
+ // sub-policies/decisions, like scattering and backoff. We'll need a more
+ // deliberate setup to ensure that we're failing what we want to be failing.
+ fake_state_.random_provider()->var_seed()->reset(nullptr);
+
+ // Check that the UpdateCanStart fails.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kFailed,
+ &Policy::UpdateCanStart, &result, update_state);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringNewWaitPeriodApplies) {
+ // The UpdateCanStart policy returns false; device policy is loaded and
+ // scattering applies due to an unsatisfied wait period, which was newly
+ // generated.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromMinutes(2)));
+
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+
+ // Check that the UpdateCanStart returns false and a new wait period
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+ EXPECT_LT(TimeDelta(), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringPrevWaitPeriodStillApplies) {
+ // The UpdateCanStart policy returns false w/ kAskMeAgainLater; device policy
+ // is loaded and a previously generated scattering period still applies, none
+ // of the scattering values has changed.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromMinutes(2)));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.scatter_wait_period = TimeDelta::FromSeconds(35);
+
+ // Check that the UpdateCanStart returns false and a new wait period
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::UpdateCanStart,
+ &result, update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+ EXPECT_EQ(TimeDelta::FromSeconds(35), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringNewCountThresholdApplies) {
+ // The UpdateCanStart policy returns false; device policy is loaded and
+ // scattering applies due to an unsatisfied update check count threshold.
+ //
+ // This ensures a non-zero check threshold, which may or may not be combined
+ // with a non-zero wait period (for which we cannot reliably control).
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromSeconds(1)));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 5;
+
+ // Check that the UpdateCanStart returns false.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+ EXPECT_LE(2, result.scatter_check_threshold);
+ EXPECT_GE(5, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringPrevCountThresholdStillApplies) {
+ // The UpdateCanStart policy returns false; device policy is loaded and
+ // scattering due to a previously generated count threshold still applies.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromSeconds(1)));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.scatter_check_threshold = 3;
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 5;
+
+ // Check that the UpdateCanStart returns false.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+ EXPECT_EQ(3, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedScatteringSatisfied) {
+ // The UpdateCanStart policy returns true; device policy is loaded and
+ // scattering is enabled, but both wait period and check threshold are
+ // satisfied.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromSeconds(120)));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(75));
+ update_state.num_checks = 4;
+ update_state.scatter_wait_period = TimeDelta::FromSeconds(60);
+ update_state.scatter_check_threshold = 3;
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 5;
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedInteractivePreventsScattering) {
+ // The UpdateCanStart policy returns true; device policy is loaded and
+ // scattering would have applied, except that the update check is interactive
+ // and so it is suppressed.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromSeconds(1)));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.is_interactive = true;
+ update_state.scatter_check_threshold = 0;
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 5;
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedOobePreventsScattering) {
+ // The UpdateCanStart policy returns true; device policy is loaded and
+ // scattering would have applied, except that OOBE was not completed and so it
+ // is suppressed.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromSeconds(1)));
+ fake_state_.system_provider()->var_is_oobe_complete()->reset(new bool(false));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.is_interactive = true;
+ update_state.scatter_check_threshold = 0;
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 5;
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithAttributes) {
+ // The UpdateCanStart policy returns true; device policy permits both HTTP and
+ // P2P updates, as well as a non-empty target channel string.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithP2PFromUpdater) {
+ // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+ // P2P updates, but the updater is configured to allow P2P and overrules the
+ // setting.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PDownloadingBlockedDueToOmaha) {
+ // The UpdateCanStart policy returns true; device policy permits HTTP, but
+ // policy blocks P2P downloading because Omaha forbids it. P2P sharing is
+ // still permitted.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.p2p_downloading_disabled = true;
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_FALSE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PSharingBlockedDueToOmaha) {
+ // The UpdateCanStart policy returns true; device policy permits HTTP, but
+ // policy blocks P2P sharing because Omaha forbids it. P2P downloading is
+ // still permitted.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.p2p_sharing_disabled = true;
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_FALSE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PDownloadingBlockedDueToNumAttempts) {
+ // The UpdateCanStart policy returns true; device policy permits HTTP but
+ // blocks P2P download, because the max number of P2P downloads have been
+ // attempted. P2P sharing is still permitted.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.p2p_num_attempts = ChromeOSPolicy::kMaxP2PAttempts;
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_FALSE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PDownloadingBlockedDueToAttemptsPeriod) {
+ // The UpdateCanStart policy returns true; device policy permits HTTP but
+ // blocks P2P download, because the max period for attempt to download via P2P
+ // has elapsed. P2P sharing is still permitted.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.p2p_num_attempts = 1;
+ update_state.p2p_first_attempted =
+ fake_clock_.GetWallclockTime() -
+ TimeDelta::FromSeconds(
+ ChromeOSPolicy::kMaxP2PAttemptsPeriodInSeconds + 1);
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_FALSE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedWithHttpUrlForUnofficialBuild) {
+ // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+ // P2P updates, but marking this an unofficial build overrules the HTTP
+ // setting.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(false));
+ fake_state_.system_provider()->var_is_official_build()->
+ reset(new bool(false));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithHttpsUrl) {
+ // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+ // P2P updates, but an HTTPS URL is provided and selected for download.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(false));
+
+ // Add an HTTPS URL.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.download_urls.emplace_back("https://secure/url/");
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(1, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedMaxErrorsNotExceeded) {
+ // The UpdateCanStart policy returns true; the first URL has download errors
+ // but does not exceed the maximum allowed number of failures, so it is stilli
+ // usable.
+
+ SetUpdateCheckAllowed(false);
+
+ // Add a second URL; update with this URL attempted and failed enough times to
+ // disqualify the current (first) URL.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.num_checks = 5;
+ update_state.download_urls.emplace_back("http://another/fake/url/");
+ Time t = fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(12);
+ for (int i = 0; i < 5; i++) {
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError, t);
+ t += TimeDelta::FromSeconds(1);
+ }
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(5, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithSecondUrlMaxExceeded) {
+ // The UpdateCanStart policy returns true; the first URL exceeded the maximum
+ // allowed number of failures, but a second URL is available.
+
+ SetUpdateCheckAllowed(false);
+
+ // Add a second URL; update with this URL attempted and failed enough times to
+ // disqualify the current (first) URL.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.num_checks = 10;
+ update_state.download_urls.emplace_back("http://another/fake/url/");
+ Time t = fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(12);
+ for (int i = 0; i < 11; i++) {
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError, t);
+ t += TimeDelta::FromSeconds(1);
+ }
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(1, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithSecondUrlHardError) {
+ // The UpdateCanStart policy returns true; the first URL fails with a hard
+ // error, but a second URL is available.
+
+ SetUpdateCheckAllowed(false);
+
+ // Add a second URL; update with this URL attempted and failed in a way that
+ // causes it to switch directly to the next URL.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.num_checks = 10;
+ update_state.download_urls.emplace_back("http://another/fake/url/");
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kPayloadHashMismatchError,
+ fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(1, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedUrlWrapsAround) {
+ // The UpdateCanStart policy returns true; URL search properly wraps around
+ // the last one on the list.
+
+ SetUpdateCheckAllowed(false);
+
+ // Add a second URL; update with this URL attempted and failed in a way that
+ // causes it to switch directly to the next URL. We must disable backoff in
+ // order for it not to interfere.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.num_checks = 1;
+ update_state.is_backoff_disabled = true;
+ update_state.download_urls.emplace_back("http://another/fake/url/");
+ update_state.download_errors.emplace_back(
+ 1, ErrorCode::kPayloadHashMismatchError,
+ fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartNotAllowedNoUsableUrls) {
+ // The UpdateCanStart policy returns false; there's a single HTTP URL but its
+ // use is forbidden by policy.
+ //
+ // Note: In the case where no usable URLs are found, the policy should not
+ // increment the number of failed attempts! Doing so would result in a
+ // non-idempotent semantics, and does not fall within the intended purpose of
+ // the backoff mechanism anyway.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(false));
+
+ // Check that the UpdateCanStart returns false.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kCannotDownload,
+ result.cannot_start_reason);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoUsableUrlsButP2PEnabled) {
+ // The UpdateCanStart policy returns true; there's a single HTTP URL but its
+ // use is forbidden by policy, however P2P is enabled. The result indicates
+ // that no URL can be used.
+ //
+ // Note: The number of failed attempts should not increase in this case (see
+ // above test).
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(false));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_GT(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedNoUsableUrlsButEnterpriseEnrolled) {
+ // The UpdateCanStart policy returns true; there's a single HTTP URL but its
+ // use is forbidden by policy, and P2P is unset on the policy, however the
+ // device is enterprise-enrolled so P2P is allowed. The result indicates that
+ // no URL can be used.
+ //
+ // Note: The number of failed attempts should not increase in this case (see
+ // above test).
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(nullptr);
+ fake_state_.device_policy_provider()->var_owner()->reset(nullptr);
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(false));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_GT(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedEthernetDefault) {
+ // Ethernet is always allowed.
+
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kEthernet));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateDownloadAllowed, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedWifiDefault) {
+ // Wifi is allowed if not tethered.
+
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kWifi));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateDownloadAllowed, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCurrentConnectionNotAllowedWifiTetheredDefault) {
+ // Tethered wifi is not allowed by default.
+
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kWifi));
+ fake_state_.shill_provider()->var_conn_tethering()->
+ reset(new ConnectionTethering(ConnectionTethering::kConfirmed));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+ &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateDownloadAllowedWifiTetheredPolicyOverride) {
+ // Tethered wifi can be allowed by policy.
+
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kWifi));
+ fake_state_.shill_provider()->var_conn_tethering()->
+ reset(new ConnectionTethering(ConnectionTethering::kConfirmed));
+ set<ConnectionType> allowed_connections;
+ allowed_connections.insert(ConnectionType::kCellular);
+ fake_state_.device_policy_provider()->
+ var_allowed_connection_types_for_update()->
+ reset(new set<ConnectionType>(allowed_connections));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateDownloadAllowed, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedWimaxDefault) {
+ // Wimax is always allowed.
+
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kWifi));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateDownloadAllowed, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCurrentConnectionNotAllowedBluetoothDefault) {
+ // Bluetooth is never allowed.
+
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kBluetooth));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+ &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCurrentConnectionNotAllowedBluetoothPolicyCannotOverride) {
+ // Bluetooth cannot be allowed even by policy.
+
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kBluetooth));
+ set<ConnectionType> allowed_connections;
+ allowed_connections.insert(ConnectionType::kBluetooth);
+ fake_state_.device_policy_provider()->
+ var_allowed_connection_types_for_update()->
+ reset(new set<ConnectionType>(allowed_connections));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+ &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCurrentConnectionNotAllowedCellularDefault) {
+ // Cellular is not allowed by default.
+
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kCellular));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+ &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateDownloadAllowedCellularPolicyOverride) {
+ // Update over cellular can be enabled by policy.
+
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kCellular));
+ set<ConnectionType> allowed_connections;
+ allowed_connections.insert(ConnectionType::kCellular);
+ fake_state_.device_policy_provider()->
+ var_allowed_connection_types_for_update()->
+ reset(new set<ConnectionType>(allowed_connections));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateDownloadAllowed, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateDownloadAllowedCellularUserOverride) {
+ // Update over cellular can be enabled by user settings, but only if policy
+ // is present and does not determine allowed connections.
+
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kCellular));
+ set<ConnectionType> allowed_connections;
+ allowed_connections.insert(ConnectionType::kCellular);
+ fake_state_.updater_provider()->var_cellular_enabled()->
+ reset(new bool(true));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateDownloadAllowed, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedScatteringSupressedDueToP2P) {
+ // The UpdateCanStart policy returns true; scattering should have applied, but
+ // P2P download is allowed. Scattering values are nonetheless returned, and so
+ // are download URL values, albeit the latter are not allowed to be used.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromMinutes(2)));
+ fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.scatter_wait_period = TimeDelta::FromSeconds(35);
+
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart,
+ &result, update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_FALSE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_FALSE(result.do_increment_failures);
+ EXPECT_EQ(TimeDelta::FromSeconds(35), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedBackoffSupressedDueToP2P) {
+ // The UpdateCanStart policy returns true; backoff should have applied, but
+ // P2P download is allowed. Backoff values are nonetheless returned, and so
+ // are download URL values, albeit the latter are not allowed to be used.
+
+ SetUpdateCheckAllowed(false);
+
+ const Time curr_time = fake_clock_.GetWallclockTime();
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+ update_state.download_errors_max = 1;
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_FALSE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledNotAllowed) {
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+ EXPECT_FALSE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedByDevicePolicy) {
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedByUpdater) {
+ fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedDeviceEnterpriseEnrolled) {
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(nullptr);
+ fake_state_.device_policy_provider()->var_owner()->reset(nullptr);
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledChangedBlocks) {
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::P2PEnabledChanged,
+ &result, false);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/config_provider.h b/update_manager/config_provider.h
new file mode 100644
index 0000000..1f6904a
--- /dev/null
+++ b/update_manager/config_provider.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_CONFIG_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for const system configurations. This provider reads the
+// configuration from a file on /etc.
+class ConfigProvider : public Provider {
+ public:
+ // Returns a variable stating whether the out of the box experience (OOBE) is
+ // enabled on this device. A value of false means that the device doesn't have
+ // an OOBE workflow.
+ virtual Variable<bool>* var_is_oobe_enabled() = 0;
+
+ protected:
+ ConfigProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConfigProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_CONFIG_PROVIDER_H_
diff --git a/update_manager/default_policy.cc b/update_manager/default_policy.cc
new file mode 100644
index 0000000..d49591a
--- /dev/null
+++ b/update_manager/default_policy.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/default_policy.h"
+
+namespace {
+
+// A fixed minimum interval between consecutive allowed update checks. This
+// needs to be long enough to prevent busywork and/or DDoS attacks on Omaha, but
+// at the same time short enough to allow the machine to update itself
+// reasonably soon.
+const int kCheckIntervalInSeconds = 15 * 60;
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+DefaultPolicy::DefaultPolicy(chromeos_update_engine::ClockInterface* clock)
+ : clock_(clock), aux_state_(new DefaultPolicyState()) {}
+
+EvalStatus DefaultPolicy::UpdateCheckAllowed(
+ EvaluationContext* ec, State* state, std::string* error,
+ UpdateCheckParams* result) const {
+ result->updates_enabled = true;
+ result->target_channel.clear();
+ result->target_version_prefix.clear();
+ result->is_interactive = false;
+
+ // Ensure that the minimum interval is set. If there's no clock, this defaults
+ // to always allowing the update.
+ if (!aux_state_->IsLastCheckAllowedTimeSet() ||
+ ec->IsMonotonicTimeGreaterThan(
+ aux_state_->last_check_allowed_time() +
+ base::TimeDelta::FromSeconds(kCheckIntervalInSeconds))) {
+ if (clock_)
+ aux_state_->set_last_check_allowed_time(clock_->GetMonotonicTime());
+ return EvalStatus::kSucceeded;
+ }
+
+ return EvalStatus::kAskMeAgainLater;
+}
+
+EvalStatus DefaultPolicy::UpdateCanStart(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ UpdateDownloadParams* result,
+ const UpdateState update_state) const {
+ result->update_can_start = true;
+ result->cannot_start_reason = UpdateCannotStartReason::kUndefined;
+ result->download_url_idx = 0;
+ result->download_url_allowed = true;
+ result->download_url_num_errors = 0;
+ result->p2p_downloading_allowed = false;
+ result->p2p_sharing_allowed = false;
+ result->do_increment_failures = false;
+ result->backoff_expiry = base::Time();
+ result->scatter_wait_period = base::TimeDelta();
+ result->scatter_check_threshold = 0;
+ return EvalStatus::kSucceeded;
+}
+
+EvalStatus DefaultPolicy::UpdateDownloadAllowed(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ bool* result) const {
+ *result = true;
+ return EvalStatus::kSucceeded;
+}
+
+EvalStatus DefaultPolicy::P2PEnabled(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ bool* result) const {
+ *result = false;
+ return EvalStatus::kSucceeded;
+}
+
+EvalStatus DefaultPolicy::P2PEnabledChanged(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ bool* result,
+ bool prev_result) const {
+ // This policy will always prohibit P2P, so this is signaling to the caller
+ // that the decision is final (because the current value is the same as the
+ // previous one) and there's no need to issue another call.
+ *result = false;
+ return EvalStatus::kSucceeded;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/default_policy.h b/update_manager/default_policy.h
new file mode 100644
index 0000000..294afea
--- /dev/null
+++ b/update_manager/default_policy.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_DEFAULT_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_DEFAULT_POLICY_H_
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/update_manager/policy.h"
+
+namespace chromeos_update_manager {
+
+// Auxiliary state class for DefaultPolicy evaluations.
+//
+// IMPORTANT: The use of a state object in policies is generally forbidden, as
+// it was a design decision to keep policy calls side-effect free. We make an
+// exception here to ensure that DefaultPolicy indeed serves as a safe (and
+// secure) fallback option. This practice should be avoided when imlpementing
+// other policies.
+class DefaultPolicyState {
+ public:
+ DefaultPolicyState() {}
+
+ bool IsLastCheckAllowedTimeSet() const {
+ return last_check_allowed_time_ != base::Time::Max();
+ }
+
+ // Sets/returns the point time on the monotonic time scale when the latest
+ // check allowed was recorded.
+ void set_last_check_allowed_time(base::Time timestamp) {
+ last_check_allowed_time_ = timestamp;
+ }
+ base::Time last_check_allowed_time() const {
+ return last_check_allowed_time_;
+ }
+
+ private:
+ base::Time last_check_allowed_time_ = base::Time::Max();
+};
+
+// The DefaultPolicy is a safe Policy implementation that doesn't fail. The
+// values returned by this policy are safe default in case of failure of the
+// actual policy being used by the UpdateManager.
+class DefaultPolicy : public Policy {
+ public:
+ explicit DefaultPolicy(chromeos_update_engine::ClockInterface* clock);
+ DefaultPolicy() : DefaultPolicy(nullptr) {}
+ ~DefaultPolicy() override {}
+
+ // Policy overrides.
+ EvalStatus UpdateCheckAllowed(
+ EvaluationContext* ec, State* state, std::string* error,
+ UpdateCheckParams* result) const override;
+
+ EvalStatus UpdateCanStart(
+ EvaluationContext* ec, State* state, std::string* error,
+ UpdateDownloadParams* result,
+ UpdateState update_state) const override;
+
+ EvalStatus UpdateDownloadAllowed(
+ EvaluationContext* ec, State* state, std::string* error,
+ bool* result) const override;
+
+ EvalStatus P2PEnabled(
+ EvaluationContext* ec, State* state, std::string* error,
+ bool* result) const override;
+
+ EvalStatus P2PEnabledChanged(
+ EvaluationContext* ec, State* state, std::string* error,
+ bool* result, bool prev_result) const override;
+
+ protected:
+ // Policy override.
+ std::string PolicyName() const override { return "DefaultPolicy"; }
+
+ private:
+ // A clock interface.
+ chromeos_update_engine::ClockInterface* clock_;
+
+ // An auxiliary state object.
+ std::unique_ptr<DefaultPolicyState> aux_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultPolicy);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_DEFAULT_POLICY_H_
diff --git a/update_manager/device_policy_provider.h b/update_manager/device_policy_provider.h
new file mode 100644
index 0000000..1259610
--- /dev/null
+++ b/update_manager/device_policy_provider.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include <base/time/time.h>
+#include <policy/libpolicy.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provides access to the current DevicePolicy.
+class DevicePolicyProvider : public Provider {
+ public:
+ ~DevicePolicyProvider() override {}
+
+ // Variable stating whether the DevicePolicy was loaded.
+ virtual Variable<bool>* var_device_policy_is_loaded() = 0;
+
+ // Variables mapping the information received on the DevicePolicy protobuf.
+ virtual Variable<std::string>* var_release_channel() = 0;
+
+ virtual Variable<bool>* var_release_channel_delegated() = 0;
+
+ virtual Variable<bool>* var_update_disabled() = 0;
+
+ virtual Variable<std::string>* var_target_version_prefix() = 0;
+
+ // Returns a non-negative scatter interval used for updates.
+ virtual Variable<base::TimeDelta>* var_scatter_factor() = 0;
+
+ // Variable returning the set of connection types allowed for updates. The
+ // identifiers returned are consistent with the ones returned by the
+ // ShillProvider.
+ virtual Variable<std::set<ConnectionType>>*
+ var_allowed_connection_types_for_update() = 0;
+
+ // Variable stating the name of the device owner. For enterprise enrolled
+ // devices, this will be an empty string.
+ virtual Variable<std::string>* var_owner() = 0;
+
+ virtual Variable<bool>* var_http_downloads_enabled() = 0;
+
+ virtual Variable<bool>* var_au_p2p_enabled() = 0;
+
+ protected:
+ DevicePolicyProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DevicePolicyProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_manager/evaluation_context-inl.h b/update_manager/evaluation_context-inl.h
new file mode 100644
index 0000000..9dfed38
--- /dev/null
+++ b/update_manager/evaluation_context-inl.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
+
+#include <string>
+
+#include <base/logging.h>
+
+namespace chromeos_update_manager {
+
+template<typename T>
+const T* EvaluationContext::GetValue(Variable<T>* var) {
+ if (var == nullptr) {
+ LOG(ERROR) << "GetValue received an uninitialized variable.";
+ return nullptr;
+ }
+
+ // Search for the value on the cache first.
+ ValueCacheMap::iterator it = value_cache_.find(var);
+ if (it != value_cache_.end())
+ return reinterpret_cast<const T*>(it->second.value());
+
+ // Get the value from the variable if not found on the cache.
+ std::string errmsg;
+ const T* result = var->GetValue(RemainingTime(evaluation_monotonic_deadline_),
+ &errmsg);
+ if (result == nullptr) {
+ LOG(WARNING) << "Error reading Variable " << var->GetName() << ": \""
+ << errmsg << "\"";
+ }
+ // Cache the value for the next time. The map of CachedValues keeps the
+ // ownership of the pointer until the map is destroyed.
+ value_cache_.emplace(
+ static_cast<BaseVariable*>(var), BoxedValue(result));
+ return result;
+}
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
diff --git a/update_manager/evaluation_context.cc b/update_manager/evaluation_context.cc
new file mode 100644
index 0000000..66275de
--- /dev/null
+++ b/update_manager/evaluation_context.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/evaluation_context.h"
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/json/json_writer.h>
+#include <base/location.h>
+#include <base/strings/string_util.h>
+#include <base/values.h>
+
+#include "update_engine/utils.h"
+
+using base::Callback;
+using base::Closure;
+using base::Time;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos_update_engine::ClockInterface;
+using std::string;
+using std::unique_ptr;
+
+namespace {
+
+// Returns whether |curr_time| surpassed |ref_time|; if not, also checks whether
+// |ref_time| is sooner than the current value of |*reeval_time|, in which case
+// the latter is updated to the former.
+bool IsTimeGreaterThanHelper(Time ref_time, Time curr_time,
+ Time* reeval_time) {
+ if (curr_time > ref_time)
+ return true;
+ // Remember the nearest reference we've checked against in this evaluation.
+ if (*reeval_time > ref_time)
+ *reeval_time = ref_time;
+ return false;
+}
+
+// If |expires| never happens (maximal value), returns the maximal interval;
+// otherwise, returns the difference between |expires| and |curr|.
+TimeDelta GetTimeout(Time curr, Time expires) {
+ if (expires.is_max())
+ return TimeDelta::Max();
+ return expires - curr;
+}
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+EvaluationContext::EvaluationContext(
+ ClockInterface* clock,
+ TimeDelta evaluation_timeout,
+ TimeDelta expiration_timeout,
+ unique_ptr<Callback<void(EvaluationContext*)>> unregister_cb)
+ : clock_(clock),
+ evaluation_timeout_(evaluation_timeout),
+ expiration_timeout_(expiration_timeout),
+ unregister_cb_(std::move(unregister_cb)),
+ weak_ptr_factory_(this) {
+ ResetEvaluation();
+ ResetExpiration();
+}
+
+EvaluationContext::~EvaluationContext() {
+ RemoveObserversAndTimeout();
+ if (unregister_cb_.get())
+ unregister_cb_->Run(this);
+}
+
+unique_ptr<Closure> EvaluationContext::RemoveObserversAndTimeout() {
+ for (auto& it : value_cache_) {
+ if (it.first->GetMode() == kVariableModeAsync)
+ it.first->RemoveObserver(this);
+ }
+ MessageLoop::current()->CancelTask(timeout_event_);
+ timeout_event_ = MessageLoop::kTaskIdNull;
+
+ return unique_ptr<Closure>(callback_.release());
+}
+
+TimeDelta EvaluationContext::RemainingTime(Time monotonic_deadline) const {
+ if (monotonic_deadline.is_max())
+ return TimeDelta::Max();
+ TimeDelta remaining = monotonic_deadline - clock_->GetMonotonicTime();
+ return std::max(remaining, TimeDelta());
+}
+
+Time EvaluationContext::MonotonicDeadline(TimeDelta timeout) {
+ return (timeout.is_max() ? Time::Max() :
+ clock_->GetMonotonicTime() + timeout);
+}
+
+void EvaluationContext::ValueChanged(BaseVariable* var) {
+ DLOG(INFO) << "ValueChanged() called for variable " << var->GetName();
+ OnValueChangedOrTimeout();
+}
+
+void EvaluationContext::OnTimeout() {
+ DLOG(INFO) << "OnTimeout() called due to "
+ << (timeout_marks_expiration_ ? "expiration" : "poll interval");
+ timeout_event_ = MessageLoop::kTaskIdNull;
+ is_expired_ = timeout_marks_expiration_;
+ OnValueChangedOrTimeout();
+}
+
+void EvaluationContext::OnValueChangedOrTimeout() {
+ // Copy the callback handle locally, allowing it to be reassigned.
+ unique_ptr<Closure> callback = RemoveObserversAndTimeout();
+
+ if (callback.get())
+ callback->Run();
+}
+
+bool EvaluationContext::IsWallclockTimeGreaterThan(Time timestamp) {
+ return IsTimeGreaterThanHelper(timestamp, evaluation_start_wallclock_,
+ &reevaluation_time_wallclock_);
+}
+
+bool EvaluationContext::IsMonotonicTimeGreaterThan(Time timestamp) {
+ return IsTimeGreaterThanHelper(timestamp, evaluation_start_monotonic_,
+ &reevaluation_time_monotonic_);
+}
+
+void EvaluationContext::ResetEvaluation() {
+ evaluation_start_wallclock_ = clock_->GetWallclockTime();
+ evaluation_start_monotonic_ = clock_->GetMonotonicTime();
+ reevaluation_time_wallclock_ = Time::Max();
+ reevaluation_time_monotonic_ = Time::Max();
+ evaluation_monotonic_deadline_ = MonotonicDeadline(evaluation_timeout_);
+
+ // Remove the cached values of non-const variables
+ for (auto it = value_cache_.begin(); it != value_cache_.end(); ) {
+ if (it->first->GetMode() == kVariableModeConst) {
+ ++it;
+ } else {
+ it = value_cache_.erase(it);
+ }
+ }
+}
+
+void EvaluationContext::ResetExpiration() {
+ expiration_monotonic_deadline_ = MonotonicDeadline(expiration_timeout_);
+ is_expired_ = false;
+}
+
+bool EvaluationContext::RunOnValueChangeOrTimeout(Closure callback) {
+ // Check that the method was not called more than once.
+ if (callback_.get()) {
+ LOG(ERROR) << "RunOnValueChangeOrTimeout called more than once.";
+ return false;
+ }
+
+ // Check that the context did not yet expire.
+ if (is_expired()) {
+ LOG(ERROR) << "RunOnValueChangeOrTimeout called on an expired context.";
+ return false;
+ }
+
+ // Handle reevaluation due to a Is{Wallclock,Monotonic}TimeGreaterThan(). We
+ // choose the smaller of the differences between evaluation start time and
+ // reevaluation time among the wallclock and monotonic scales.
+ TimeDelta timeout = std::min(
+ GetTimeout(evaluation_start_wallclock_, reevaluation_time_wallclock_),
+ GetTimeout(evaluation_start_monotonic_, reevaluation_time_monotonic_));
+
+ // Handle reevaluation due to async or poll variables.
+ bool waiting_for_value_change = false;
+ for (auto& it : value_cache_) {
+ switch (it.first->GetMode()) {
+ case kVariableModeAsync:
+ DLOG(INFO) << "Waiting for value on " << it.first->GetName();
+ it.first->AddObserver(this);
+ waiting_for_value_change = true;
+ break;
+ case kVariableModePoll:
+ timeout = std::min(timeout, it.first->GetPollInterval());
+ break;
+ case kVariableModeConst:
+ // Ignored.
+ break;
+ }
+ }
+
+ // Check if the re-evaluation is actually being scheduled. If there are no
+ // events waited for, this function should return false.
+ if (!waiting_for_value_change && timeout.is_max())
+ return false;
+
+ // Ensure that we take into account the expiration timeout.
+ TimeDelta expiration = RemainingTime(expiration_monotonic_deadline_);
+ timeout_marks_expiration_ = expiration < timeout;
+ if (timeout_marks_expiration_)
+ timeout = expiration;
+
+ // Store the reevaluation callback.
+ callback_.reset(new Closure(callback));
+
+ // Schedule a timeout event, if one is set.
+ if (!timeout.is_max()) {
+ DLOG(INFO) << "Waiting for timeout in "
+ << chromeos_update_engine::utils::FormatTimeDelta(timeout);
+ timeout_event_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&EvaluationContext::OnTimeout,
+ weak_ptr_factory_.GetWeakPtr()),
+ timeout);
+ }
+
+ return true;
+}
+
+string EvaluationContext::DumpContext() const {
+ base::DictionaryValue* variables = new base::DictionaryValue();
+ for (auto& it : value_cache_) {
+ variables->SetString(it.first->GetName(), it.second.ToString());
+ }
+
+ base::DictionaryValue value;
+ value.Set("variables", variables); // Adopts |variables|.
+ value.SetString(
+ "evaluation_start_wallclock",
+ chromeos_update_engine::utils::ToString(evaluation_start_wallclock_));
+ value.SetString(
+ "evaluation_start_monotonic",
+ chromeos_update_engine::utils::ToString(evaluation_start_monotonic_));
+
+ string json_str;
+ base::JSONWriter::WriteWithOptions(
+ value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_str);
+ base::TrimWhitespaceASCII(json_str, base::TRIM_TRAILING, &json_str);
+
+ return json_str;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/evaluation_context.h b/update_manager/evaluation_context.h
new file mode 100644
index 0000000..3d259f3
--- /dev/null
+++ b/update_manager/evaluation_context.h
@@ -0,0 +1,209 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/weak_ptr.h>
+#include <base/time/time.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/update_manager/boxed_value.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// The EvaluationContext class is the interface between a policy implementation
+// and the state. The EvaluationContext tracks the variables used by a policy
+// request and caches the returned values, owning those cached values.
+// The same EvaluationContext should be re-used for all the evaluations of the
+// same policy request (an AsyncPolicyRequest might involve several
+// re-evaluations). Each evaluation of the EvaluationContext is run at a given
+// point in time, which is used as a reference for the evaluation timeout and
+// the time based queries of the policy, such as
+// Is{Wallclock,Monotonic}TimeGreaterThan().
+//
+// Example:
+//
+// scoped_refptr<EvaluationContext> ec = new EvaluationContext(...);
+//
+// ...
+// // The following call to ResetEvaluation() is optional. Use it to reset the
+// // evaluation time if the EvaluationContext isn't used right after its
+// // construction.
+// ec->ResetEvaluation();
+// EvalStatus status = policy->SomeMethod(ec, state, &result, args...);
+//
+// ...
+// // Run a closure when any of the used async variables changes its value or
+// // the timeout for re-query the values happens again.
+// ec->RunOnValueChangeOrTimeout(closure);
+// // If the provided |closure| wants to re-evaluate the policy, it should
+// // call ec->ResetEvaluation() to start a new evaluation.
+//
+class EvaluationContext : public base::RefCounted<EvaluationContext>,
+ private BaseVariable::ObserverInterface {
+ public:
+ EvaluationContext(
+ chromeos_update_engine::ClockInterface* clock,
+ base::TimeDelta evaluation_timeout,
+ base::TimeDelta expiration_timeout,
+ std::unique_ptr<base::Callback<void(EvaluationContext*)>> unregister_cb);
+ EvaluationContext(chromeos_update_engine::ClockInterface* clock,
+ base::TimeDelta evaluation_timeout)
+ : EvaluationContext(
+ clock, evaluation_timeout, base::TimeDelta::Max(),
+ std::unique_ptr<base::Callback<void(EvaluationContext*)>>()) {}
+ ~EvaluationContext();
+
+ // Returns a pointer to the value returned by the passed variable |var|. The
+ // EvaluationContext instance keeps the ownership of the returned object. The
+ // returned object is valid during the life of the evaluation, even if the
+ // passed Variable changes it.
+ //
+ // In case of error, a null value is returned.
+ template<typename T>
+ const T* GetValue(Variable<T>* var);
+
+ // Returns whether the evaluation time has surpassed |timestamp|, on either
+ // the ClockInterface::GetWallclockTime() or
+ // ClockInterface::GetMonotonicTime() scales, respectively.
+ bool IsWallclockTimeGreaterThan(base::Time timestamp);
+ bool IsMonotonicTimeGreaterThan(base::Time timestamp);
+
+ // Returns whether the evaluation context has expired.
+ bool is_expired() const { return is_expired_; }
+
+ // TODO(deymo): Move the following methods to an interface only visible by the
+ // UpdateManager class and not the policy implementations.
+
+ // Resets the EvaluationContext to its initial state removing all the
+ // non-const cached variables and re-setting the evaluation time. This should
+ // be called right before any new evaluation starts.
+ void ResetEvaluation();
+
+ // Clears the expiration status of the EvaluationContext and resets its
+ // expiration timeout based on |expiration_timeout_|. This should be called if
+ // expiration occurred, prior to re-evaluating the policy.
+ void ResetExpiration();
+
+ // Schedules the passed |callback| closure to be called when a cached
+ // variable changes its value, a polling interval passes, or the context
+ // expiration occurs. If none of these events can happen, for example if
+ // there's no cached variable, this method returns false.
+ //
+ // Right before the passed closure is called the EvaluationContext is
+ // reseted, removing all the non-const cached values.
+ bool RunOnValueChangeOrTimeout(base::Closure callback);
+
+ // Returns a textual representation of the evaluation context,
+ // including the variables and their values. This is intended only
+ // to help with debugging and the format may change in the future.
+ std::string DumpContext() const;
+
+ // Removes all the Observers callbacks and timeout events scheduled by
+ // RunOnValueChangeOrTimeout(). Also releases and returns the closure
+ // associated with these events. This method is idempotent.
+ std::unique_ptr<base::Closure> RemoveObserversAndTimeout();
+
+ private:
+ friend class UmEvaluationContextTest;
+
+ // BaseVariable::ObserverInterface override.
+ void ValueChanged(BaseVariable* var) override;
+
+ // Called from the main loop when a scheduled timeout has passed.
+ void OnTimeout();
+
+ // Removes the observers from the used Variables and cancels the timeout,
+ // then executes the scheduled callback.
+ void OnValueChangedOrTimeout();
+
+ // If |monotonic_deadline| is not Time::Max(), returns the remaining time
+ // until it is reached, or zero if it has passed. Otherwise, returns
+ // TimeDelta::Max().
+ base::TimeDelta RemainingTime(base::Time monotonic_deadline) const;
+
+ // Returns a monotonic clock timestamp at which |timeout| will have elapsed
+ // since the current time.
+ base::Time MonotonicDeadline(base::TimeDelta timeout);
+
+ // A map to hold the cached values for every variable.
+ typedef std::map<BaseVariable*, BoxedValue> ValueCacheMap;
+
+ // The cached values of the called Variables.
+ ValueCacheMap value_cache_;
+
+ // A callback used for triggering re-evaluation upon a value change or poll
+ // timeout, or notifying about the evaluation context expiration. It is up to
+ // the caller to determine whether or not expiration occurred via
+ // is_expired().
+ std::unique_ptr<base::Closure> callback_;
+
+ // The TaskId returned by the message loop identifying the timeout callback.
+ // Used for canceling the timeout callback.
+ chromeos::MessageLoop::TaskId timeout_event_ =
+ chromeos::MessageLoop::kTaskIdNull;
+
+ // Whether a timeout event firing marks the expiration of the evaluation
+ // context.
+ bool timeout_marks_expiration_;
+
+ // Whether the evaluation context has indeed expired.
+ bool is_expired_ = false;
+
+ // Pointer to the mockable clock interface;
+ chromeos_update_engine::ClockInterface* const clock_;
+
+ // The timestamps when the evaluation of this EvaluationContext started,
+ // corresponding to ClockInterface::GetWallclockTime() and
+ // ClockInterface::GetMonotonicTime(), respectively. These values are reset
+ // every time ResetEvaluation() is called.
+ base::Time evaluation_start_wallclock_;
+ base::Time evaluation_start_monotonic_;
+
+ // The timestamps when a reevaluation should be triggered due to various
+ // expected value changes, corresponding to ClockInterface::GetWallclockTime()
+ // and ClockInterface::GetMonotonicTIme(), respectively. These timestamps are
+ // greater or equal to corresponding |evaluation_start_{wallclock,monotonic}_|
+ // counterparts since they are in the future; however, they may be smaller
+ // than the current corresponding times during the course of evaluation.
+ base::Time reevaluation_time_wallclock_;
+ base::Time reevaluation_time_monotonic_;
+
+ // The timeout of an evaluation.
+ const base::TimeDelta evaluation_timeout_;
+
+ // The timestamp in the ClockInterface::GetMonotonicTime() scale at which the
+ // current evaluation should finish.
+ base::Time evaluation_monotonic_deadline_;
+
+ // The expiration timeout of the evaluation context.
+ const base::TimeDelta expiration_timeout_;
+
+ // The monotonic clock deadline at which expiration occurs.
+ base::Time expiration_monotonic_deadline_;
+
+ // A callback for unregistering the context upon destruction.
+ std::unique_ptr<base::Callback<void(EvaluationContext*)>> unregister_cb_;
+
+ base::WeakPtrFactory<EvaluationContext> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(EvaluationContext);
+};
+
+} // namespace chromeos_update_manager
+
+// Include the implementation of the template methods.
+#include "update_engine/update_manager/evaluation_context-inl.h"
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
diff --git a/update_manager/evaluation_context_unittest.cc b/update_manager/evaluation_context_unittest.cc
new file mode 100644
index 0000000..2b59026
--- /dev/null
+++ b/update_manager/evaluation_context_unittest.cc
@@ -0,0 +1,477 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/evaluation_context.h"
+
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/mock_variable.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Bind;
+using base::Closure;
+using base::Time;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos::MessageLoopRunMaxIterations;
+using chromeos::MessageLoopRunUntil;
+using chromeos_update_engine::FakeClock;
+using std::string;
+using std::unique_ptr;
+using testing::Return;
+using testing::StrictMock;
+using testing::_;
+
+namespace chromeos_update_manager {
+
+namespace {
+
+void DoNothing() {}
+
+// Sets the value of the passed pointer to true.
+void SetTrue(bool* value) {
+ *value = true;
+}
+
+bool GetBoolean(bool* value) {
+ return *value;
+}
+
+template<typename T>
+void ReadVar(scoped_refptr<EvaluationContext> ec, Variable<T>* var) {
+ ec->GetValue(var);
+}
+
+// Runs |evaluation|; if the value pointed by |count_p| is greater than zero,
+// decrement it and schedule a reevaluation; otherwise, writes true to |done_p|.
+void EvaluateRepeatedly(Closure evaluation, scoped_refptr<EvaluationContext> ec,
+ int* count_p, bool* done_p) {
+ evaluation.Run();
+
+ // Schedule reevaluation if needed.
+ if (*count_p > 0) {
+ Closure closure = Bind(EvaluateRepeatedly, evaluation, ec, count_p, done_p);
+ ASSERT_TRUE(ec->RunOnValueChangeOrTimeout(closure))
+ << "Failed to schedule reevaluation, count_p=" << *count_p;
+ (*count_p)--;
+ } else {
+ *done_p = true;
+ }
+}
+
+} // namespace
+
+class UmEvaluationContextTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ // Apr 22, 2009 19:25:00 UTC (this is a random reference point).
+ fake_clock_.SetMonotonicTime(Time::FromTimeT(1240428300));
+ // Mar 2, 2006 1:23:45 UTC.
+ fake_clock_.SetWallclockTime(Time::FromTimeT(1141262625));
+ eval_ctx_ = new EvaluationContext(
+ &fake_clock_, default_timeout_, default_timeout_,
+ unique_ptr<base::Callback<void(EvaluationContext*)>>(nullptr));
+ }
+
+ void TearDown() override {
+ // Ensure that the evaluation context did not leak and is actually being
+ // destroyed.
+ if (eval_ctx_) {
+ base::WeakPtr<EvaluationContext> eval_ctx_weak_alias =
+ eval_ctx_->weak_ptr_factory_.GetWeakPtr();
+ ASSERT_NE(nullptr, eval_ctx_weak_alias.get());
+ eval_ctx_ = nullptr;
+ EXPECT_EQ(nullptr, eval_ctx_weak_alias.get())
+ << "The evaluation context was not destroyed! This is likely a bug "
+ "in how the test was written, look for leaking handles to the EC, "
+ "possibly through closure objects.";
+ }
+
+ // Check that the evaluation context removed all the observers.
+ EXPECT_TRUE(fake_int_var_.observer_list_.empty());
+ EXPECT_TRUE(fake_async_var_.observer_list_.empty());
+ EXPECT_TRUE(fake_const_var_.observer_list_.empty());
+ EXPECT_TRUE(fake_poll_var_.observer_list_.empty());
+
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ TimeDelta default_timeout_ = TimeDelta::FromSeconds(5);
+
+ chromeos::FakeMessageLoop loop_{nullptr};
+ FakeClock fake_clock_;
+ scoped_refptr<EvaluationContext> eval_ctx_;
+
+ // FakeVariables used for testing the EvaluationContext. These are required
+ // here to prevent them from going away *before* the EvaluationContext under
+ // test does, which keeps a reference to them.
+ FakeVariable<bool> fail_var_ = {"fail_var", kVariableModePoll};
+ FakeVariable<int> fake_int_var_ = {"fake_int", kVariableModePoll};
+ FakeVariable<string> fake_async_var_ = {"fake_async", kVariableModeAsync};
+ FakeVariable<string> fake_const_var_ = {"fake_const", kVariableModeConst};
+ FakeVariable<string> fake_poll_var_ = {"fake_poll",
+ TimeDelta::FromSeconds(1)};
+ StrictMock<MockVariable<string>> mock_var_async_ {
+ "mock_var_async", kVariableModeAsync};
+ StrictMock<MockVariable<string>> mock_var_poll_ {
+ "mock_var_poll", kVariableModePoll};
+};
+
+TEST_F(UmEvaluationContextTest, GetValueFails) {
+ // FakeVariable is initialized as returning null.
+ EXPECT_EQ(nullptr, eval_ctx_->GetValue(&fake_int_var_));
+}
+
+TEST_F(UmEvaluationContextTest, GetValueFailsWithInvalidVar) {
+ EXPECT_EQ(nullptr, eval_ctx_->GetValue(static_cast<Variable<int>*>(nullptr)));
+}
+
+TEST_F(UmEvaluationContextTest, GetValueReturns) {
+ const int* p_fake_int;
+
+ fake_int_var_.reset(new int(42));
+ p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+ ASSERT_NE(nullptr, p_fake_int);
+ EXPECT_EQ(42, *p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueCached) {
+ const int* p_fake_int;
+
+ fake_int_var_.reset(new int(42));
+ p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+
+ // Check that if the variable changes, the EvaluationContext keeps returning
+ // the cached value.
+ fake_int_var_.reset(new int(5));
+
+ p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+ ASSERT_NE(nullptr, p_fake_int);
+ EXPECT_EQ(42, *p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueCachesNull) {
+ const int* p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+ EXPECT_EQ(nullptr, p_fake_int);
+
+ fake_int_var_.reset(new int(42));
+ // A second attempt to read the variable should not work because this
+ // EvaluationContext already got a null value.
+ p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+ EXPECT_EQ(nullptr, p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueMixedTypes) {
+ const int* p_fake_int;
+ const string* p_fake_string;
+
+ fake_int_var_.reset(new int(42));
+ fake_poll_var_.reset(new string("Hello world!"));
+ // Check that the EvaluationContext can handle multiple Variable types. This
+ // is mostly a compile-time check due to the template nature of this method.
+ p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+ p_fake_string = eval_ctx_->GetValue(&fake_poll_var_);
+
+ ASSERT_NE(nullptr, p_fake_int);
+ EXPECT_EQ(42, *p_fake_int);
+
+ ASSERT_NE(nullptr, p_fake_string);
+ EXPECT_EQ("Hello world!", *p_fake_string);
+}
+
+// Test that we don't schedule an event if there's no variable to wait for.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutWithoutVariables) {
+ fake_const_var_.reset(new string("Hello world!"));
+ EXPECT_EQ(*eval_ctx_->GetValue(&fake_const_var_), "Hello world!");
+
+ EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+// Test that reevaluation occurs when an async variable it depends on changes.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutWithVariables) {
+ fake_async_var_.reset(new string("Async value"));
+ eval_ctx_->GetValue(&fake_async_var_);
+
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ // Check that the scheduled callback isn't run until we signal a ValueChaged.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(value);
+
+ fake_async_var_.NotifyValueChanged();
+ EXPECT_FALSE(value);
+ // Ensure that the scheduled callback isn't run until we are back on the main
+ // loop.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_TRUE(value);
+}
+
+// Test that we don't re-schedule the events if we are attending one.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutCalledTwice) {
+ fake_async_var_.reset(new string("Async value"));
+ eval_ctx_->GetValue(&fake_async_var_);
+
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+
+ // The scheduled event should still work.
+ fake_async_var_.NotifyValueChanged();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_TRUE(value);
+}
+
+// Test that reevaluation occurs when a polling timeout fires.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutRunsFromTimeout) {
+ fake_poll_var_.reset(new string("Polled value"));
+ eval_ctx_->GetValue(&fake_poll_var_);
+
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ // Check that the scheduled callback isn't run until the timeout occurs.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ EXPECT_FALSE(value);
+ MessageLoopRunUntil(MessageLoop::current(),
+ TimeDelta::FromSeconds(10),
+ Bind(&GetBoolean, &value));
+ EXPECT_TRUE(value);
+}
+
+// Test that callback is called when evaluation context expires, and that it
+// cannot be used again unless the expiration deadline is reset.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutExpires) {
+ fake_async_var_.reset(new string("Async value"));
+ eval_ctx_->GetValue(&fake_async_var_);
+
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ // Check that the scheduled callback isn't run until the timeout occurs.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ EXPECT_FALSE(value);
+ MessageLoopRunUntil(MessageLoop::current(),
+ TimeDelta::FromSeconds(10),
+ Bind(&GetBoolean, &value));
+ EXPECT_TRUE(value);
+
+ // Ensure that we cannot reschedule an evaluation.
+ EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+
+ // Ensure that we can reschedule an evaluation after resetting expiration.
+ eval_ctx_->ResetExpiration();
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+// Test that we clear the events when destroying the EvaluationContext.
+TEST_F(UmEvaluationContextTest, RemoveObserversAndTimeoutTest) {
+ fake_async_var_.reset(new string("Async value"));
+ eval_ctx_->GetValue(&fake_async_var_);
+
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ eval_ctx_ = nullptr;
+
+ // This should not trigger the callback since the EvaluationContext waiting
+ // for it is gone, and it should have remove all its observers.
+ fake_async_var_.NotifyValueChanged();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(value);
+}
+
+// Scheduling two reevaluations from the callback should succeed.
+TEST_F(UmEvaluationContextTest,
+ RunOnValueChangeOrTimeoutReevaluatesRepeatedly) {
+ fake_poll_var_.reset(new string("Polled value"));
+ Closure evaluation = Bind(ReadVar<string>, eval_ctx_, &fake_poll_var_);
+ int num_reevaluations = 2;
+ bool done = false;
+
+ // Run the evaluation once.
+ evaluation.Run();
+
+ // Schedule repeated reevaluations.
+ Closure closure = Bind(EvaluateRepeatedly, evaluation, eval_ctx_,
+ &num_reevaluations, &done);
+ ASSERT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(closure));
+ MessageLoopRunUntil(MessageLoop::current(),
+ TimeDelta::FromSeconds(10),
+ Bind(&GetBoolean, &done));
+ EXPECT_EQ(0, num_reevaluations);
+}
+
+// Test that we can delete the EvaluationContext while having pending events.
+TEST_F(UmEvaluationContextTest, ObjectDeletedWithPendingEventsTest) {
+ fake_async_var_.reset(new string("Async value"));
+ fake_poll_var_.reset(new string("Polled value"));
+ eval_ctx_->GetValue(&fake_async_var_);
+ eval_ctx_->GetValue(&fake_poll_var_);
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+ // TearDown() checks for leaked observers on this async_variable, which means
+ // that our object is still alive after removing its reference.
+}
+
+// Test that timed events fired after removal of the EvaluationContext don't
+// crash.
+TEST_F(UmEvaluationContextTest, TimeoutEventAfterDeleteTest) {
+ FakeVariable<string> fake_short_poll_var = {"fake_short_poll",
+ TimeDelta::FromSeconds(1)};
+ fake_short_poll_var.reset(new string("Polled value"));
+ eval_ctx_->GetValue(&fake_short_poll_var);
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ // Remove the last reference to the EvaluationContext and run the loop for
+ // 10 seconds to give time to the main loop to trigger the timeout Event (of 1
+ // second). Our callback should not be called because the EvaluationContext
+ // was removed before the timeout event is attended.
+ eval_ctx_ = nullptr;
+ MessageLoopRunUntil(MessageLoop::current(),
+ TimeDelta::FromSeconds(10),
+ Bind(&GetBoolean, &value));
+ EXPECT_FALSE(value);
+}
+
+TEST_F(UmEvaluationContextTest, DefaultTimeout) {
+ // Test that the evaluation timeout calculation uses the default timeout on
+ // setup.
+ EXPECT_CALL(mock_var_async_, GetValue(default_timeout_, _))
+ .WillOnce(Return(nullptr));
+ EXPECT_EQ(nullptr, eval_ctx_->GetValue(&mock_var_async_));
+}
+
+TEST_F(UmEvaluationContextTest, TimeoutUpdatesWithMonotonicTime) {
+ fake_clock_.SetMonotonicTime(
+ fake_clock_.GetMonotonicTime() + TimeDelta::FromSeconds(1));
+
+ TimeDelta timeout = default_timeout_ - TimeDelta::FromSeconds(1);
+
+ EXPECT_CALL(mock_var_async_, GetValue(timeout, _))
+ .WillOnce(Return(nullptr));
+ EXPECT_EQ(nullptr, eval_ctx_->GetValue(&mock_var_async_));
+}
+
+TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesWallclock) {
+ Time cur_time = fake_clock_.GetWallclockTime();
+ // Advance the time on the clock but don't call ResetEvaluation yet.
+ fake_clock_.SetWallclockTime(cur_time + TimeDelta::FromSeconds(4));
+
+ EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+ cur_time - TimeDelta::FromSeconds(1)));
+ EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
+ EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+ cur_time + TimeDelta::FromSeconds(1)));
+ // Call ResetEvaluation now, which should use the new evaluation time.
+ eval_ctx_->ResetEvaluation();
+
+ cur_time = fake_clock_.GetWallclockTime();
+ EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+ cur_time - TimeDelta::FromSeconds(1)));
+ EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
+ EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+ cur_time + TimeDelta::FromSeconds(1)));
+}
+
+TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesMonotonic) {
+ Time cur_time = fake_clock_.GetMonotonicTime();
+ // Advance the time on the clock but don't call ResetEvaluation yet.
+ fake_clock_.SetMonotonicTime(cur_time + TimeDelta::FromSeconds(4));
+
+ EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ cur_time - TimeDelta::FromSeconds(1)));
+ EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
+ EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ cur_time + TimeDelta::FromSeconds(1)));
+ // Call ResetEvaluation now, which should use the new evaluation time.
+ eval_ctx_->ResetEvaluation();
+
+ cur_time = fake_clock_.GetMonotonicTime();
+ EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ cur_time - TimeDelta::FromSeconds(1)));
+ EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
+ EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ cur_time + TimeDelta::FromSeconds(1)));
+}
+
+TEST_F(UmEvaluationContextTest,
+ IsWallclockTimeGreaterThanSignalsTriggerReevaluation) {
+ EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(1)));
+
+ // The "false" from IsWallclockTimeGreaterThan means that's not that timestamp
+ // yet, so this should schedule a callback for when that happens.
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+ IsMonotonicTimeGreaterThanSignalsTriggerReevaluation) {
+ EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ fake_clock_.GetMonotonicTime() + TimeDelta::FromSeconds(1)));
+
+ // The "false" from IsMonotonicTimeGreaterThan means that's not that timestamp
+ // yet, so this should schedule a callback for when that happens.
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+ IsWallclockTimeGreaterThanDoesntRecordPastTimestamps) {
+ // IsWallclockTimeGreaterThan() should ignore timestamps on the past for
+ // reevaluation.
+ EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+ fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(20)));
+ EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+ fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1)));
+
+ // Callback should not be scheduled.
+ EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+ IsMonotonicTimeGreaterThanDoesntRecordPastTimestamps) {
+ // IsMonotonicTimeGreaterThan() should ignore timestamps on the past for
+ // reevaluation.
+ EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(20)));
+ EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(1)));
+
+ // Callback should not be scheduled.
+ EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest, DumpContext) {
+ // |fail_var_| yield "(no value)" since it is unset.
+ eval_ctx_->GetValue(&fail_var_);
+
+ // Check that this is included.
+ fake_int_var_.reset(new int(42));
+ eval_ctx_->GetValue(&fake_int_var_);
+
+ // Check that double-quotes are escaped properly.
+ fake_poll_var_.reset(new string("Hello \"world\"!"));
+ eval_ctx_->GetValue(&fake_poll_var_);
+
+ // Note that the variables are printed in alphabetical order. Also
+ // see UmEvaluationContextText::SetUp() where the values used for
+ // |evaluation_start_{monotonic,wallclock| are set.
+ EXPECT_EQ("{\n"
+ " \"evaluation_start_monotonic\": \"4/22/2009 19:25:00 GMT\",\n"
+ " \"evaluation_start_wallclock\": \"3/2/2006 1:23:45 GMT\",\n"
+ " \"variables\": {\n"
+ " \"fail_var\": \"(no value)\",\n"
+ " \"fake_int\": \"42\",\n"
+ " \"fake_poll\": \"Hello \\\"world\\\"!\"\n"
+ " }\n"
+ "}",
+ eval_ctx_->DumpContext());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/fake_config_provider.h b/update_manager/fake_config_provider.h
new file mode 100644
index 0000000..5624943
--- /dev/null
+++ b/update_manager/fake_config_provider.h
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
+
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/fake_variable.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the ConfigProvider base class.
+class FakeConfigProvider : public ConfigProvider {
+ public:
+ FakeConfigProvider() {}
+
+ FakeVariable<bool>* var_is_oobe_enabled() override {
+ return &var_is_oobe_enabled_;
+ }
+
+ private:
+ FakeVariable<bool> var_is_oobe_enabled_{ // NOLINT(whitespace/braces)
+ "is_oobe_enabled", kVariableModeConst};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeConfigProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
diff --git a/update_manager/fake_device_policy_provider.h b/update_manager/fake_device_policy_provider.h
new file mode 100644
index 0000000..5819c43
--- /dev/null
+++ b/update_manager/fake_device_policy_provider.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/fake_variable.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the DevicePolicyProvider base class.
+class FakeDevicePolicyProvider : public DevicePolicyProvider {
+ public:
+ FakeDevicePolicyProvider() {}
+
+ FakeVariable<bool>* var_device_policy_is_loaded() override {
+ return &var_device_policy_is_loaded_;
+ }
+
+ FakeVariable<std::string>* var_release_channel() override {
+ return &var_release_channel_;
+ }
+
+ FakeVariable<bool>* var_release_channel_delegated() override {
+ return &var_release_channel_delegated_;
+ }
+
+ FakeVariable<bool>* var_update_disabled() override {
+ return &var_update_disabled_;
+ }
+
+ FakeVariable<std::string>* var_target_version_prefix() override {
+ return &var_target_version_prefix_;
+ }
+
+ FakeVariable<base::TimeDelta>* var_scatter_factor() override {
+ return &var_scatter_factor_;
+ }
+
+ FakeVariable<std::set<ConnectionType>>*
+ var_allowed_connection_types_for_update() override {
+ return &var_allowed_connection_types_for_update_;
+ }
+
+ FakeVariable<std::string>* var_owner() override {
+ return &var_owner_;
+ }
+
+ FakeVariable<bool>* var_http_downloads_enabled() override {
+ return &var_http_downloads_enabled_;
+ }
+
+ FakeVariable<bool>* var_au_p2p_enabled() override {
+ return &var_au_p2p_enabled_;
+ }
+
+ private:
+ FakeVariable<bool> var_device_policy_is_loaded_{
+ "policy_is_loaded", kVariableModePoll};
+ FakeVariable<std::string> var_release_channel_{
+ "release_channel", kVariableModePoll};
+ FakeVariable<bool> var_release_channel_delegated_{
+ "release_channel_delegated", kVariableModePoll};
+ FakeVariable<bool> var_update_disabled_{
+ "update_disabled", kVariableModePoll};
+ FakeVariable<std::string> var_target_version_prefix_{
+ "target_version_prefix", kVariableModePoll};
+ FakeVariable<base::TimeDelta> var_scatter_factor_{
+ "scatter_factor", kVariableModePoll};
+ FakeVariable<std::set<ConnectionType>>
+ var_allowed_connection_types_for_update_{
+ "allowed_connection_types_for_update", kVariableModePoll};
+ FakeVariable<std::string> var_owner_{"owner", kVariableModePoll};
+ FakeVariable<bool> var_http_downloads_enabled_{
+ "http_downloads_enabled", kVariableModePoll};
+ FakeVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled", kVariableModePoll};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDevicePolicyProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_manager/fake_random_provider.h b/update_manager/fake_random_provider.h
new file mode 100644
index 0000000..9f2bc35
--- /dev/null
+++ b/update_manager/fake_random_provider.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/random_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the RandomProvider base class.
+class FakeRandomProvider : public RandomProvider {
+ public:
+ FakeRandomProvider() {}
+
+ FakeVariable<uint64_t>* var_seed() override { return &var_seed_; }
+
+ private:
+ FakeVariable<uint64_t> var_seed_{"seed", kVariableModePoll};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeRandomProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
diff --git a/update_manager/fake_shill_provider.h b/update_manager/fake_shill_provider.h
new file mode 100644
index 0000000..12cfe1d
--- /dev/null
+++ b/update_manager/fake_shill_provider.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/shill_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the ShillProvider base class.
+class FakeShillProvider : public ShillProvider {
+ public:
+ FakeShillProvider() {}
+
+ FakeVariable<bool>* var_is_connected() override {
+ return &var_is_connected_;
+ }
+
+ FakeVariable<ConnectionType>* var_conn_type() override {
+ return &var_conn_type_;
+ }
+
+ FakeVariable<ConnectionTethering>*
+ var_conn_tethering() override {
+ return &var_conn_tethering_;
+ }
+
+ FakeVariable<base::Time>* var_conn_last_changed() override {
+ return &var_conn_last_changed_;
+ }
+
+ private:
+ FakeVariable<bool> var_is_connected_{"is_connected", kVariableModePoll};
+ FakeVariable<ConnectionType> var_conn_type_{"conn_type", kVariableModePoll};
+ FakeVariable<ConnectionTethering> var_conn_tethering_{
+ "conn_tethering", kVariableModePoll};
+ FakeVariable<base::Time> var_conn_last_changed_{
+ "conn_last_changed", kVariableModePoll};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeShillProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
diff --git a/update_manager/fake_state.h b/update_manager/fake_state.h
new file mode 100644
index 0000000..b803890
--- /dev/null
+++ b/update_manager/fake_state.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_FAKE_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_STATE_H_
+
+#include "update_engine/update_manager/fake_config_provider.h"
+#include "update_engine/update_manager/fake_device_policy_provider.h"
+#include "update_engine/update_manager/fake_random_provider.h"
+#include "update_engine/update_manager/fake_shill_provider.h"
+#include "update_engine/update_manager/fake_system_provider.h"
+#include "update_engine/update_manager/fake_time_provider.h"
+#include "update_engine/update_manager/fake_updater_provider.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// A fake State class that creates fake providers for all the providers.
+// This fake can be used in unit testing of Policy subclasses. To fake out the
+// value a variable is exposing, just call FakeVariable<T>::SetValue() on the
+// variable you fake out. For example:
+//
+// FakeState fake_state_;
+// fake_state_.random_provider_->var_seed()->SetValue(new uint64_t(12345));
+//
+// You can call SetValue more than once and the FakeVariable will take care of
+// the memory, but only the last value will remain.
+class FakeState : public State {
+ public:
+ // Creates and initializes the FakeState using fake providers.
+ FakeState() {}
+
+ ~FakeState() override {}
+
+ // Downcasted getters to access the fake instances during testing.
+ FakeConfigProvider* config_provider() override {
+ return &config_provider_;
+ }
+
+ FakeDevicePolicyProvider* device_policy_provider() override {
+ return &device_policy_provider_;
+ }
+
+ FakeRandomProvider* random_provider() override {
+ return &random_provider_;
+ }
+
+ FakeShillProvider* shill_provider() override {
+ return &shill_provider_;
+ }
+
+ FakeSystemProvider* system_provider() override {
+ return &system_provider_;
+ }
+
+ FakeTimeProvider* time_provider() override {
+ return &time_provider_;
+ }
+
+ FakeUpdaterProvider* updater_provider() override {
+ return &updater_provider_;
+ }
+
+ private:
+ FakeConfigProvider config_provider_;
+ FakeDevicePolicyProvider device_policy_provider_;
+ FakeRandomProvider random_provider_;
+ FakeShillProvider shill_provider_;
+ FakeSystemProvider system_provider_;
+ FakeTimeProvider time_provider_;
+ FakeUpdaterProvider updater_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeState);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_STATE_H_
diff --git a/update_manager/fake_system_provider.h b/update_manager/fake_system_provider.h
new file mode 100644
index 0000000..6b7151b
--- /dev/null
+++ b/update_manager/fake_system_provider.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/system_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the SystemProvider base class.
+class FakeSystemProvider : public SystemProvider {
+ public:
+ FakeSystemProvider() {}
+
+ FakeVariable<bool>* var_is_normal_boot_mode() override {
+ return &var_is_normal_boot_mode_;
+ }
+
+ FakeVariable<bool>* var_is_official_build() override {
+ return &var_is_official_build_;
+ }
+
+ FakeVariable<bool>* var_is_oobe_complete() override {
+ return &var_is_oobe_complete_;
+ }
+
+ FakeVariable<bool>* var_is_boot_device_removable() override {
+ return &var_is_boot_device_removable_;
+ }
+
+ private:
+ FakeVariable<bool> var_is_normal_boot_mode_{ // NOLINT(whitespace/braces)
+ "is_normal_boot_mode", kVariableModeConst};
+ FakeVariable<bool> var_is_official_build_{ // NOLINT(whitespace/braces)
+ "is_official_build", kVariableModeConst};
+ FakeVariable<bool> var_is_oobe_complete_{ // NOLINT(whitespace/braces)
+ "is_oobe_complete", kVariableModePoll};
+ FakeVariable<bool>
+ var_is_boot_device_removable_{ // NOLINT(whitespace/braces)
+ "is_boot_device_removable", kVariableModePoll};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeSystemProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
diff --git a/update_manager/fake_time_provider.h b/update_manager/fake_time_provider.h
new file mode 100644
index 0000000..a60405b
--- /dev/null
+++ b/update_manager/fake_time_provider.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/time_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the TimeProvider base class.
+class FakeTimeProvider : public TimeProvider {
+ public:
+ FakeTimeProvider() {}
+
+ FakeVariable<base::Time>* var_curr_date() override { return &var_curr_date_; }
+ FakeVariable<int>* var_curr_hour() override { return &var_curr_hour_; }
+
+ private:
+ FakeVariable<base::Time> var_curr_date_{"curr_date", kVariableModePoll};
+ FakeVariable<int> var_curr_hour_{"curr_hour", kVariableModePoll};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeTimeProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
diff --git a/update_manager/fake_update_manager.h b/update_manager/fake_update_manager.h
new file mode 100644
index 0000000..34a3813
--- /dev/null
+++ b/update_manager/fake_update_manager.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
+
+#include "update_engine/update_manager/update_manager.h"
+
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/fake_state.h"
+
+namespace chromeos_update_manager {
+
+class FakeUpdateManager : public UpdateManager {
+ public:
+ explicit FakeUpdateManager(chromeos_update_engine::ClockInterface* clock)
+ : UpdateManager(clock, base::TimeDelta::FromSeconds(5),
+ base::TimeDelta::FromHours(1), new FakeState()) {
+ // The FakeUpdateManager uses a DefaultPolicy.
+ set_policy(new DefaultPolicy(clock));
+ }
+
+ // UpdateManager overrides.
+ using UpdateManager::set_policy;
+
+ FakeState* state() {
+ return reinterpret_cast<FakeState*>(UpdateManager::state());
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FakeUpdateManager);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
diff --git a/update_manager/fake_updater_provider.h b/update_manager/fake_updater_provider.h
new file mode 100644
index 0000000..0820858
--- /dev/null
+++ b/update_manager/fake_updater_provider.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
+
+#include <string>
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the UpdaterProvider base class.
+class FakeUpdaterProvider : public UpdaterProvider {
+ public:
+ FakeUpdaterProvider() {}
+
+ FakeVariable<base::Time>* var_updater_started_time() override {
+ return &var_updater_started_time_;
+ }
+
+ FakeVariable<base::Time>* var_last_checked_time() override {
+ return &var_last_checked_time_;
+ }
+
+ FakeVariable<base::Time>* var_update_completed_time() override {
+ return &var_update_completed_time_;
+ }
+
+ FakeVariable<double>* var_progress() override {
+ return &var_progress_;
+ }
+
+ FakeVariable<Stage>* var_stage() override {
+ return &var_stage_;
+ }
+
+ FakeVariable<std::string>* var_new_version() override {
+ return &var_new_version_;
+ }
+
+ FakeVariable<int64_t>* var_payload_size() override {
+ return &var_payload_size_;
+ }
+
+ FakeVariable<std::string>* var_curr_channel() override {
+ return &var_curr_channel_;
+ }
+
+ FakeVariable<std::string>* var_new_channel() override {
+ return &var_new_channel_;
+ }
+
+ FakeVariable<bool>* var_p2p_enabled() override {
+ return &var_p2p_enabled_;
+ }
+
+ FakeVariable<bool>* var_cellular_enabled() override {
+ return &var_cellular_enabled_;
+ }
+
+ FakeVariable<unsigned int>* var_consecutive_failed_update_checks() override {
+ return &var_consecutive_failed_update_checks_;
+ }
+
+ FakeVariable<unsigned int>* var_server_dictated_poll_interval() override {
+ return &var_server_dictated_poll_interval_;
+ }
+
+ FakeVariable<UpdateRequestStatus>* var_forced_update_requested() override {
+ return &var_forced_update_requested_;
+ }
+
+ private:
+ FakeVariable<base::Time>
+ var_updater_started_time_{ // NOLINT(whitespace/braces)
+ "updater_started_time", kVariableModePoll};
+ FakeVariable<base::Time> var_last_checked_time_{ // NOLINT(whitespace/braces)
+ "last_checked_time", kVariableModePoll};
+ FakeVariable<base::Time>
+ var_update_completed_time_{ // NOLINT(whitespace/braces)
+ "update_completed_time", kVariableModePoll};
+ FakeVariable<double> var_progress_{ // NOLINT(whitespace/braces)
+ "progress", kVariableModePoll};
+ FakeVariable<Stage> var_stage_{ // NOLINT(whitespace/braces)
+ "stage", kVariableModePoll};
+ FakeVariable<std::string> var_new_version_{ // NOLINT(whitespace/braces)
+ "new_version", kVariableModePoll};
+ FakeVariable<int64_t> var_payload_size_{ // NOLINT(whitespace/braces)
+ "payload_size", kVariableModePoll};
+ FakeVariable<std::string> var_curr_channel_{ // NOLINT(whitespace/braces)
+ "curr_channel", kVariableModePoll};
+ FakeVariable<std::string> var_new_channel_{ // NOLINT(whitespace/braces)
+ "new_channel", kVariableModePoll};
+ FakeVariable<bool> var_p2p_enabled_{ // NOLINT(whitespace/braces)
+ "p2p_enabled", kVariableModePoll};
+ FakeVariable<bool> var_cellular_enabled_{ // NOLINT(whitespace/braces)
+ "cellular_enabled", kVariableModePoll};
+ FakeVariable<unsigned int>
+ var_consecutive_failed_update_checks_{ // NOLINT(whitespace/braces)
+ "consecutive_failed_update_checks", kVariableModePoll};
+ FakeVariable<unsigned int>
+ var_server_dictated_poll_interval_{ // NOLINT(whitespace/braces)
+ "server_dictated_poll_interval", kVariableModePoll};
+ FakeVariable<UpdateRequestStatus>
+ var_forced_update_requested_{ // NOLINT(whitespace/braces)
+ "forced_update_requested", kVariableModeAsync};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeUpdaterProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
diff --git a/update_manager/fake_variable.h b/update_manager/fake_variable.h
new file mode 100644
index 0000000..9e51f7b
--- /dev/null
+++ b/update_manager/fake_variable.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_FAKE_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_VARIABLE_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// A fake typed variable to use while testing policy implementations. The
+// variable can be instructed to return any object of its type.
+template<typename T>
+class FakeVariable : public Variable<T> {
+ public:
+ FakeVariable(const std::string& name, VariableMode mode)
+ : Variable<T>(name, mode) {}
+ FakeVariable(const std::string& name, base::TimeDelta poll_interval)
+ : Variable<T>(name, poll_interval) {}
+ ~FakeVariable() override {}
+
+ // Sets the next value of this variable to the passed |p_value| pointer. Once
+ // returned by GetValue(), the pointer is released and has to be set again.
+ // A value of null means that the GetValue() call will fail and return
+ // null.
+ void reset(const T* p_value) {
+ ptr_.reset(p_value);
+ }
+
+ // Make the NotifyValueChanged() public for FakeVariables.
+ void NotifyValueChanged() {
+ Variable<T>::NotifyValueChanged();
+ }
+
+ protected:
+ // Variable<T> overrides.
+ // Returns the pointer set with reset(). The ownership of the object is passed
+ // to the caller and the pointer is release from the FakeVariable. A second
+ // call to GetValue() without reset() will return null and set the error
+ // message.
+ const T* GetValue(base::TimeDelta /* timeout */,
+ std::string* errmsg) override {
+ if (ptr_ == nullptr && errmsg != nullptr)
+ *errmsg = this->GetName() + " is an empty FakeVariable";
+ // Passes the pointer ownership to the caller.
+ return ptr_.release();
+ }
+
+ private:
+ // The pointer returned by GetValue().
+ std::unique_ptr<const T> ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeVariable);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_VARIABLE_H_
diff --git a/update_manager/generic_variables.h b/update_manager/generic_variables.h
new file mode 100644
index 0000000..b1b059f
--- /dev/null
+++ b/update_manager/generic_variables.h
@@ -0,0 +1,206 @@
+// Copyright (c) 2014 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.
+
+// Generic and provider-independent Variable subclasses. These variables can be
+// used by any state provider to implement simple variables to avoid repeat the
+// same common code on different state providers.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
+
+#include <string>
+
+#include <base/callback.h>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Variable class returning a copy of a given object using the copy constructor.
+// This template class can be used to define variables that expose as a variable
+// any fixed object, such as the a provider's private member. The variable will
+// create copies of the provided object using the copy constructor of that
+// class.
+//
+// For example, a state provider exposing a private member as a variable can
+// implement this as follows:
+//
+// class SomethingProvider {
+// public:
+// SomethingProvider(...) {
+// var_something_foo = new PollCopyVariable<MyType>(foo_);
+// }
+// ...
+// private:
+// MyType foo_;
+// };
+template<typename T>
+class PollCopyVariable : public Variable<T> {
+ public:
+ // Creates the variable returning copies of the passed |ref|. The reference to
+ // this object is kept and it should be available whenever the GetValue()
+ // method is called. If |is_set_p| is not null, then this flag will be
+ // consulted prior to returning the value, and an |errmsg| will be returned if
+ // it is not set.
+ PollCopyVariable(const std::string& name, const T& ref, const bool* is_set_p,
+ const std::string& errmsg)
+ : Variable<T>(name, kVariableModePoll), ref_(ref), is_set_p_(is_set_p),
+ errmsg_(errmsg) {}
+ PollCopyVariable(const std::string& name, const T& ref, const bool* is_set_p)
+ : PollCopyVariable(name, ref, is_set_p, std::string()) {}
+ PollCopyVariable(const std::string& name, const T& ref)
+ : PollCopyVariable(name, ref, nullptr) {}
+
+ PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+ const T& ref, const bool* is_set_p,
+ const std::string& errmsg)
+ : Variable<T>(name, poll_interval), ref_(ref), is_set_p_(is_set_p),
+ errmsg_(errmsg) {}
+ PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+ const T& ref, const bool* is_set_p)
+ : PollCopyVariable(name, poll_interval, ref, is_set_p, std::string()) {}
+ PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+ const T& ref)
+ : PollCopyVariable(name, poll_interval, ref, nullptr) {}
+
+ protected:
+ FRIEND_TEST(UmPollCopyVariableTest, SimpleTest);
+ FRIEND_TEST(UmPollCopyVariableTest, UseCopyConstructorTest);
+
+ // Variable override.
+ inline const T* GetValue(base::TimeDelta /* timeout */,
+ std::string* errmsg) override {
+ if (is_set_p_ && !(*is_set_p_)) {
+ if (errmsg) {
+ if (errmsg_.empty())
+ *errmsg = "No value set for " + this->GetName();
+ else
+ *errmsg = errmsg_;
+ }
+ return nullptr;
+ }
+ return new T(ref_);
+ }
+
+ private:
+ // Reference to the object to be copied by GetValue().
+ const T& ref_;
+
+ // A pointer to a flag indicating whether the value is set. If null, then the
+ // value is assumed to be set.
+ const bool* const is_set_p_;
+
+ // An error message to be returned when attempting to get an unset value.
+ const std::string errmsg_;
+};
+
+// Variable class returning a constant value that is cached on the variable when
+// it is created.
+template<typename T>
+class ConstCopyVariable : public Variable<T> {
+ public:
+ // Creates the variable returning copies of the passed |obj|. The value passed
+ // is copied in this variable, and new copies of it will be returned by
+ // GetValue().
+ ConstCopyVariable(const std::string& name, const T& obj)
+ : Variable<T>(name, kVariableModeConst), obj_(obj) {}
+
+ protected:
+ // Variable override.
+ const T* GetValue(base::TimeDelta /* timeout */,
+ std::string* /* errmsg */) override {
+ return new T(obj_);
+ }
+
+ private:
+ // Value to be copied by GetValue().
+ const T obj_;
+};
+
+// Variable class returning a copy of a value returned by a given function. The
+// function is called every time the variable is being polled.
+template<typename T>
+class CallCopyVariable : public Variable<T> {
+ public:
+ CallCopyVariable(const std::string& name, base::Callback<T(void)> func)
+ : Variable<T>(name, kVariableModePoll), func_(func) {}
+ CallCopyVariable(const std::string& name,
+ const base::TimeDelta poll_interval,
+ base::Callback<T(void)> func)
+ : Variable<T>(name, poll_interval), func_(func) {}
+
+ protected:
+ // Variable override.
+ const T* GetValue(base::TimeDelta /* timeout */,
+ std::string* /* errmsg */) override {
+ if (func_.is_null())
+ return nullptr;
+ return new T(func_.Run());
+ }
+
+ private:
+ FRIEND_TEST(UmCallCopyVariableTest, SimpleTest);
+
+ // The function to be called, stored as a base::Callback.
+ base::Callback<T(void)> func_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallCopyVariable);
+};
+
+
+// A Variable class to implement simple Async variables. It provides two methods
+// SetValue and UnsetValue to modify the current value of the variable and
+// notify the registered observers whenever the value changed.
+//
+// The type T needs to be copy-constructible, default-constructible and have an
+// operator== (to determine if the value changed), which makes this class
+// suitable for basic types.
+template<typename T>
+class AsyncCopyVariable : public Variable<T> {
+ public:
+ explicit AsyncCopyVariable(const std::string& name)
+ : Variable<T>(name, kVariableModeAsync), has_value_(false) {}
+
+ AsyncCopyVariable(const std::string& name, const T value)
+ : Variable<T>(name, kVariableModeAsync),
+ has_value_(true), value_(value) {}
+
+ void SetValue(const T& new_value) {
+ bool should_notify = !(has_value_ && new_value == value_);
+ value_ = new_value;
+ has_value_ = true;
+ if (should_notify)
+ this->NotifyValueChanged();
+ }
+
+ void UnsetValue() {
+ if (has_value_) {
+ has_value_ = false;
+ this->NotifyValueChanged();
+ }
+ }
+
+ protected:
+ // Variable override.
+ const T* GetValue(base::TimeDelta /* timeout */,
+ std::string* errmsg) override {
+ if (!has_value_) {
+ if (errmsg)
+ *errmsg = "No value set for " + this->GetName();
+ return nullptr;
+ }
+ return new T(value_);
+ }
+
+ private:
+ // Whether the variable has a value set.
+ bool has_value_;
+
+ // Copy of the object to be returned by GetValue().
+ T value_;
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
diff --git a/update_manager/generic_variables_unittest.cc b/update_manager/generic_variables_unittest.cc
new file mode 100644
index 0000000..372670f
--- /dev/null
+++ b/update_manager/generic_variables_unittest.cc
@@ -0,0 +1,212 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/generic_variables.h"
+
+#include <memory>
+
+#include <base/callback.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/update_manager/umtest_utils.h"
+
+using chromeos::MessageLoop;
+using chromeos::MessageLoopRunMaxIterations;
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmPollCopyVariableTest : public ::testing::Test {};
+
+
+TEST_F(UmPollCopyVariableTest, SimpleTest) {
+ // Tests that copies are generated as intended.
+ int source = 5;
+ PollCopyVariable<int> var("var", source);
+
+ // Generate and validate a copy.
+ unique_ptr<const int> copy_1(var.GetValue(
+ UmTestUtils::DefaultTimeout(), nullptr));
+ ASSERT_NE(nullptr, copy_1.get());
+ EXPECT_EQ(5, *copy_1);
+
+ // Assign a different value to the source variable.
+ source = 42;
+
+ // Check that the content of the copy was not affected (distinct instance).
+ EXPECT_EQ(5, *copy_1);
+
+ // Generate and validate a second copy.
+ UmTestUtils::ExpectVariableHasValue(42, &var);
+}
+
+TEST_F(UmPollCopyVariableTest, SetFlagTest) {
+ // Tests that the set flag is being referred to as expected.
+ int source = 5;
+ bool is_set = false;
+ PollCopyVariable<int> var("var", source, &is_set);
+
+ // Flag marked unset, nothing should be returned.
+ UmTestUtils::ExpectVariableNotSet(&var);
+
+ // Flag marked set, we should be getting a value.
+ is_set = true;
+ UmTestUtils::ExpectVariableHasValue(5, &var);
+}
+
+
+class CopyConstructorTestClass {
+ public:
+ CopyConstructorTestClass(void) : copied_(false) {}
+ CopyConstructorTestClass(const CopyConstructorTestClass& other)
+ : copied_(true), val_(other.val_ * 2) {}
+
+ // Tells if the instance was constructed using the copy-constructor.
+ const bool copied_;
+
+ // An auxiliary internal value.
+ int val_ = 0;
+};
+
+
+TEST_F(UmPollCopyVariableTest, UseCopyConstructorTest) {
+ // Ensures that CopyVariables indeed uses the copy constructor.
+ const CopyConstructorTestClass source;
+ ASSERT_FALSE(source.copied_);
+
+ PollCopyVariable<CopyConstructorTestClass> var("var", source);
+ unique_ptr<const CopyConstructorTestClass> copy(
+ var.GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+ ASSERT_NE(nullptr, copy.get());
+ EXPECT_TRUE(copy->copied_);
+}
+
+
+class UmConstCopyVariableTest : public ::testing::Test {};
+
+TEST_F(UmConstCopyVariableTest, SimpleTest) {
+ int source = 5;
+ ConstCopyVariable<int> var("var", source);
+ UmTestUtils::ExpectVariableHasValue(5, &var);
+
+ // Ensure the value is cached.
+ source = 42;
+ UmTestUtils::ExpectVariableHasValue(5, &var);
+}
+
+
+class UmCallCopyVariableTest : public ::testing::Test {};
+
+CopyConstructorTestClass test_func(CopyConstructorTestClass* obj) {
+ obj->val_++; // So we can check that the function was called.
+ return *obj;
+}
+
+TEST_F(UmCallCopyVariableTest, SimpleTest) {
+ // Tests that the returned value is generated by copying the value returned by
+ // the function call.
+
+ CopyConstructorTestClass test_obj;
+ ASSERT_FALSE(test_obj.copied_);
+ test_obj.val_ = 5;
+
+ base::Callback<CopyConstructorTestClass(void)> cb = base::Bind(
+ test_func, &test_obj);
+ CallCopyVariable<CopyConstructorTestClass> var("var", cb);
+
+ unique_ptr<const CopyConstructorTestClass> copy(
+ var.GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+ EXPECT_EQ(6, test_obj.val_); // Check that the function was called.
+ ASSERT_NE(nullptr, copy.get());
+ EXPECT_TRUE(copy->copied_);
+ EXPECT_EQ(12, copy->val_); // Check that copying occurred once.
+}
+
+TEST_F(UmCallCopyVariableTest, NullTest) {
+ // Ensures that the variable returns null when the callback is null.
+
+ base::Callback<bool(void)> cb;
+ CallCopyVariable<bool> var("var", cb);
+ UmTestUtils::ExpectVariableNotSet(&var);
+}
+
+class UmAsyncCopyVariableTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ // No remaining event on the main loop.
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+
+ chromeos::FakeMessageLoop loop_{nullptr};
+};
+
+TEST_F(UmAsyncCopyVariableTest, ConstructorTest) {
+ AsyncCopyVariable<int> var("var");
+ UmTestUtils::ExpectVariableNotSet(&var);
+ EXPECT_EQ(kVariableModeAsync, var.GetMode());
+}
+
+TEST_F(UmAsyncCopyVariableTest, SetValueTest) {
+ AsyncCopyVariable<int> var("var");
+ var.SetValue(5);
+ UmTestUtils::ExpectVariableHasValue(5, &var);
+ // Execute all the pending observers.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+}
+
+TEST_F(UmAsyncCopyVariableTest, UnsetValueTest) {
+ AsyncCopyVariable<int> var("var", 42);
+ var.UnsetValue();
+ UmTestUtils::ExpectVariableNotSet(&var);
+ // Execute all the pending observers.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+}
+
+class CallCounterObserver : public BaseVariable::ObserverInterface {
+ public:
+ void ValueChanged(BaseVariable* variable) {
+ calls_count_++;
+ }
+
+ int calls_count_ = 0;
+};
+
+TEST_F(UmAsyncCopyVariableTest, ObserverCalledTest) {
+ AsyncCopyVariable<int> var("var", 42);
+ CallCounterObserver observer;
+ var.AddObserver(&observer);
+ EXPECT_EQ(0, observer.calls_count_);
+
+ // Check that a different value fires the notification.
+ var.SetValue(5);
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(1, observer.calls_count_);
+
+ // Check the same value doesn't.
+ var.SetValue(5);
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(1, observer.calls_count_);
+
+ // Check that unsetting a previously set value fires the notification.
+ var.UnsetValue();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(2, observer.calls_count_);
+
+ // Check that unsetting again doesn't.
+ var.UnsetValue();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(2, observer.calls_count_);
+
+ var.RemoveObserver(&observer);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/mock_policy.h b/update_manager/mock_policy.h
new file mode 100644
index 0000000..24a6ee8
--- /dev/null
+++ b/update_manager/mock_policy.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_MOCK_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_MOCK_POLICY_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/policy.h"
+
+namespace chromeos_update_manager {
+
+// A mocked implementation of Policy.
+class MockPolicy : public Policy {
+ public:
+ explicit MockPolicy(chromeos_update_engine::ClockInterface* clock)
+ : default_policy_(clock) {
+ // We defer to the corresponding DefaultPolicy methods, by default.
+ ON_CALL(*this, UpdateCheckAllowed(testing::_, testing::_, testing::_,
+ testing::_))
+ .WillByDefault(testing::Invoke(
+ &default_policy_, &DefaultPolicy::UpdateCheckAllowed));
+ ON_CALL(*this, UpdateCanStart(testing::_, testing::_, testing::_,
+ testing::_, testing::_))
+ .WillByDefault(testing::Invoke(
+ &default_policy_, &DefaultPolicy::UpdateCanStart));
+ ON_CALL(*this, UpdateDownloadAllowed(testing::_, testing::_, testing::_,
+ testing::_))
+ .WillByDefault(testing::Invoke(
+ &default_policy_, &DefaultPolicy::UpdateDownloadAllowed));
+ ON_CALL(*this, P2PEnabled(testing::_, testing::_, testing::_, testing::_))
+ .WillByDefault(testing::Invoke(
+ &default_policy_, &DefaultPolicy::P2PEnabled));
+ ON_CALL(*this, P2PEnabledChanged(testing::_, testing::_, testing::_,
+ testing::_, testing::_))
+ .WillByDefault(testing::Invoke(
+ &default_policy_, &DefaultPolicy::P2PEnabledChanged));
+ }
+
+ MockPolicy() : MockPolicy(nullptr) {}
+ ~MockPolicy() override {}
+
+ // Policy overrides.
+ MOCK_CONST_METHOD4(UpdateCheckAllowed,
+ EvalStatus(EvaluationContext*, State*, std::string*,
+ UpdateCheckParams*));
+
+ MOCK_CONST_METHOD5(UpdateCanStart,
+ EvalStatus(EvaluationContext*, State*, std::string*,
+ UpdateDownloadParams*, UpdateState));
+
+ MOCK_CONST_METHOD4(UpdateDownloadAllowed,
+ EvalStatus(EvaluationContext*, State*, std::string*,
+ bool*));
+
+ MOCK_CONST_METHOD4(P2PEnabled,
+ EvalStatus(EvaluationContext*, State*, std::string*,
+ bool*));
+
+ MOCK_CONST_METHOD5(P2PEnabledChanged,
+ EvalStatus(EvaluationContext*, State*, std::string*,
+ bool*, bool));
+
+ protected:
+ // Policy override.
+ std::string PolicyName() const override { return "MockPolicy"; }
+
+ private:
+ DefaultPolicy default_policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockPolicy);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_MOCK_POLICY_H_
diff --git a/update_manager/mock_variable.h b/update_manager/mock_variable.h
new file mode 100644
index 0000000..6be13e5
--- /dev/null
+++ b/update_manager/mock_variable.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_MOCK_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_MOCK_VARIABLE_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// This is a generic mock of the Variable class.
+template<typename T>
+class MockVariable : public Variable<T> {
+ public:
+ using Variable<T>::Variable;
+
+ MOCK_METHOD2_T(GetValue, const T*(base::TimeDelta, std::string*));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockVariable);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_MOCK_VARIABLE_H_
diff --git a/update_manager/policy.cc b/update_manager/policy.cc
new file mode 100644
index 0000000..5572974
--- /dev/null
+++ b/update_manager/policy.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/policy.h"
+
+#include <string>
+
+using std::string;
+
+namespace chromeos_update_manager {
+
+string ToString(EvalStatus status) {
+ switch (status) {
+ case EvalStatus::kFailed:
+ return "kFailed";
+ case EvalStatus::kSucceeded:
+ return "kSucceeded";
+ case EvalStatus::kAskMeAgainLater:
+ return "kAskMeAgainLater";
+ }
+ return "Invalid";
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/policy.h b/update_manager/policy.h
new file mode 100644
index 0000000..be30f26
--- /dev/null
+++ b/update_manager/policy.h
@@ -0,0 +1,278 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "update_engine/error_code.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// The three different results of a policy request.
+enum class EvalStatus {
+ kFailed,
+ kSucceeded,
+ kAskMeAgainLater,
+};
+
+std::string ToString(EvalStatus status);
+
+// Parameters of an update check. These parameters are determined by the
+// UpdateCheckAllowed policy.
+struct UpdateCheckParams {
+ bool updates_enabled; // Whether the auto-updates are enabled on this build.
+
+ // Attributes pertaining to the case where update checks are allowed.
+ //
+ // A target version prefix, if imposed by policy; otherwise, an empty string.
+ std::string target_version_prefix;
+ // A target channel, if so imposed by policy; otherwise, an empty string.
+ std::string target_channel;
+
+ // Whether the allowed update is interactive (user-initiated) or periodic.
+ bool is_interactive;
+};
+
+// Input arguments to UpdateCanStart.
+//
+// A snapshot of the state of the current update process. This includes
+// everything that a policy might need and that occurred since the first time
+// the current payload was first seen and attempted (consecutively).
+struct UpdateState {
+ // Information pertaining to the current update payload and/or check.
+ //
+ // Whether the current update check is an interactive one. The caller should
+ // feed the value returned by the preceding call to UpdateCheckAllowed().
+ bool is_interactive;
+ // Whether it is a delta payload.
+ bool is_delta_payload;
+ // Wallclock time when payload was first (consecutively) offered by Omaha.
+ base::Time first_seen;
+ // Number of consecutive update checks returning the current update.
+ int num_checks;
+ // Number of update payload failures and the wallclock time when it was last
+ // updated by the updater. These should both be nullified whenever a new
+ // update is seen; they are updated at the policy's descretion (via
+ // UpdateDownloadParams.do_increment_failures) once all of the usable download
+ // URLs for the payload have been used without success. They should be
+ // persisted across reboots.
+ int num_failures;
+ base::Time failures_last_updated;
+
+ // Information pertaining to downloading and applying of the current update.
+ //
+ // An array of download URLs provided by Omaha.
+ std::vector<std::string> download_urls;
+ // Max number of errors allowed per download URL.
+ int download_errors_max;
+ // The index of the URL to download from, as determined in the previous call
+ // to the policy. For a newly seen payload, this should be -1.
+ int last_download_url_idx;
+ // The number of successive download errors pertaining to this last URL, as
+ // determined in the previous call to the policy. For a newly seen payload,
+ // this should be zero.
+ int last_download_url_num_errors;
+ // An array of errors that occurred while trying to download this update since
+ // the previous call to this policy has returned, or since this payload was
+ // first seen, or since the updater process has started (whichever is later).
+ // Includes the URL index attempted, the error code, and the wallclock-based
+ // timestamp when it occurred.
+ std::vector<std::tuple<int, chromeos_update_engine::ErrorCode, base::Time>>
+ download_errors;
+ // Whether Omaha forbids use of P2P for downloading and/or sharing.
+ bool p2p_downloading_disabled;
+ bool p2p_sharing_disabled;
+ // The number of P2P download attempts and wallclock-based time when P2P
+ // download was first attempted.
+ int p2p_num_attempts;
+ base::Time p2p_first_attempted;
+
+ // Information pertaining to update backoff mechanism.
+ //
+ // The currently known (persisted) wallclock-based backoff expiration time;
+ // zero if none.
+ base::Time backoff_expiry;
+ // Whether backoff is disabled by Omaha.
+ bool is_backoff_disabled;
+
+ // Information pertaining to update scattering.
+ //
+ // The currently knwon (persisted) scattering wallclock-based wait period and
+ // update check threshold; zero if none.
+ base::TimeDelta scatter_wait_period;
+ int scatter_check_threshold;
+ // Maximum wait period allowed for this update, as determined by Omaha.
+ base::TimeDelta scatter_wait_period_max;
+ // Minimum/maximum check threshold values.
+ // TODO(garnold) These appear to not be related to the current update and so
+ // should probably be obtained as variables via UpdaterProvider.
+ int scatter_check_threshold_min;
+ int scatter_check_threshold_max;
+};
+
+// Results regarding the downloading and applying of an update, as determined by
+// UpdateCanStart.
+//
+// An enumerator for the reasons of not allowing an update to start.
+enum class UpdateCannotStartReason {
+ kUndefined,
+ kCheckDue,
+ kScattering,
+ kBackoff,
+ kCannotDownload,
+};
+
+struct UpdateDownloadParams {
+ // Whether the update attempt is allowed to proceed.
+ bool update_can_start;
+ // If update cannot proceed, a reason code for why it cannot do so.
+ UpdateCannotStartReason cannot_start_reason;
+
+ // Download related attributes. The update engine uses them to choose the
+ // means for downloading and applying an update.
+ //
+ // The index of the download URL to use (-1 means no suitable URL was found)
+ // and whether it can be used. Even if there's no URL or its use is not
+ // allowed (backoff, scattering) there may still be other means for download
+ // (like P2P). The URL index needs to be persisted and handed back to the
+ // policy on the next time it is called.
+ int download_url_idx;
+ bool download_url_allowed;
+ // The number of download errors associated with this download URL. This value
+ // needs to be persisted and handed back to the policy on the next time it is
+ // called.
+ int download_url_num_errors;
+ // Whether P2P download and sharing are allowed.
+ bool p2p_downloading_allowed;
+ bool p2p_sharing_allowed;
+
+ // Other values that need to be persisted and handed to the policy as need on
+ // the next call.
+ //
+ // Whether an update failure has been identified by the policy. The client
+ // should increment and persist its update failure count, and record the time
+ // when this was done; it needs to hand these values back to the policy
+ // (UpdateState.{num_failures,failures_last_updated}) on the next time it is
+ // called.
+ bool do_increment_failures;
+ // The current backof expiry.
+ base::Time backoff_expiry;
+ // The scattering wait period and check threshold.
+ base::TimeDelta scatter_wait_period;
+ int scatter_check_threshold;
+};
+
+// The Policy class is an interface to the ensemble of policy requests that the
+// client can make. A derived class includes the policy implementations of
+// these.
+//
+// When compile-time selection of the policy is required due to missing or extra
+// parts in a given platform, a different Policy subclass can be used.
+class Policy {
+ public:
+ virtual ~Policy() {}
+
+ // Returns the name of a public policy request.
+ // IMPORTANT: Be sure to add a conditional for each new public policy that is
+ // being added to this class in the future.
+ template<typename R, typename... Args>
+ std::string PolicyRequestName(
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ Args...) const) const {
+ std::string class_name = PolicyName() + "::";
+
+ if (reinterpret_cast<typeof(&Policy::UpdateCheckAllowed)>(
+ policy_method) == &Policy::UpdateCheckAllowed)
+ return class_name + "UpdateCheckAllowed";
+ if (reinterpret_cast<typeof(&Policy::UpdateCanStart)>(
+ policy_method) == &Policy::UpdateCanStart)
+ return class_name + "UpdateCanStart";
+ if (reinterpret_cast<typeof(&Policy::UpdateDownloadAllowed)>(
+ policy_method) == &Policy::UpdateDownloadAllowed)
+ return class_name + "UpdateDownloadAllowed";
+ if (reinterpret_cast<typeof(&Policy::P2PEnabled)>(
+ policy_method) == &Policy::P2PEnabled)
+ return class_name + "P2PEnabled";
+ if (reinterpret_cast<typeof(&Policy::P2PEnabledChanged)>(
+ policy_method) == &Policy::P2PEnabledChanged)
+ return class_name + "P2PEnabledChanged";
+
+ NOTREACHED();
+ return class_name + "(unknown)";
+ }
+
+
+ // List of policy requests. A policy request takes an EvaluationContext as the
+ // first argument, a State instance, a returned error message, a returned
+ // value and optionally followed by one or more arbitrary constant arguments.
+ //
+ // When the implementation fails, the method returns EvalStatus::kFailed and
+ // sets the |error| string.
+
+ // UpdateCheckAllowed returns whether it is allowed to request an update check
+ // to Omaha.
+ virtual EvalStatus UpdateCheckAllowed(
+ EvaluationContext* ec, State* state, std::string* error,
+ UpdateCheckParams* result) const = 0;
+
+ // Returns EvalStatus::kSucceeded if either an update can start being
+ // processed, or the attempt needs to be aborted. In cases where the update
+ // needs to wait for some condition to be satisfied, but none of the values
+ // that need to be persisted has changed, returns
+ // EvalStatus::kAskMeAgainLater. Arguments include an |update_state| that
+ // encapsulates data pertaining to the current ongoing update process.
+ virtual EvalStatus UpdateCanStart(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ UpdateDownloadParams* result,
+ UpdateState update_state) const = 0;
+
+ // Checks whether downloading of an update is allowed; currently, this checks
+ // whether the network connection type is suitable for updating over. May
+ // consult the shill provider as well as the device policy (if available).
+ // Returns |EvalStatus::kSucceeded|, setting |result| according to whether or
+ // not the current connection can be used; on error, returns
+ // |EvalStatus::kFailed| and sets |error| accordingly.
+ virtual EvalStatus UpdateDownloadAllowed(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ bool* result) const = 0;
+
+ // Checks whether P2P is enabled. This may consult device policy and other
+ // global settings.
+ virtual EvalStatus P2PEnabled(
+ EvaluationContext* ec, State* state, std::string* error,
+ bool* result) const = 0;
+
+ // Checks whether P2P is enabled, but blocks (returns
+ // |EvalStatus::kAskMeAgainLater|) until it is different from |prev_result|.
+ // If the P2P enabled status is not expected to change, will return
+ // immediately with |EvalStatus::kSucceeded|. This internally uses the
+ // P2PEnabled() policy above.
+ virtual EvalStatus P2PEnabledChanged(
+ EvaluationContext* ec, State* state, std::string* error,
+ bool* result, bool prev_result) const = 0;
+
+ protected:
+ Policy() {}
+
+ // Returns the name of the actual policy class.
+ virtual std::string PolicyName() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Policy);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_
diff --git a/update_manager/policy_utils.h b/update_manager/policy_utils.h
new file mode 100644
index 0000000..d612f20
--- /dev/null
+++ b/update_manager/policy_utils.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
+
+#include "update_engine/update_manager/policy.h"
+
+// Checks that the passed pointer value is not null, returning kFailed on the
+// current context and setting the *error description when it is null. The
+// intended use is to validate variable failures while using
+// EvaluationContext::GetValue, for example:
+//
+// const int* my_value = ec->GetValue(state->my_provider()->var_my_value());
+// POLICY_CHECK_VALUE_AND_FAIL(my_value, error);
+//
+#define POLICY_CHECK_VALUE_AND_FAIL(ptr, error) \
+ do { \
+ if ((ptr) == nullptr) { \
+ *(error) = #ptr " is required but is null."; \
+ return EvalStatus::kFailed; \
+ } \
+ } while (false)
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
diff --git a/update_manager/prng.h b/update_manager/prng.h
new file mode 100644
index 0000000..746e355
--- /dev/null
+++ b/update_manager/prng.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_PRNG_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_PRNG_H_
+
+#include <random>
+
+#include <base/logging.h>
+
+namespace chromeos_update_manager {
+
+// A thread-safe, unsecure, 32-bit pseudo-random number generator based on
+// std::mt19937.
+class PRNG {
+ public:
+ // Initializes the generator with the passed |seed| value.
+ explicit PRNG(uint32_t seed) : gen_(seed) {}
+
+ // Returns a random unsigned 32-bit integer.
+ uint32_t Rand() { return gen_(); }
+
+ // Returns a random integer uniformly distributed in the range [min, max].
+ int RandMinMax(int min, int max) {
+ DCHECK_LE(min, max);
+ return std::uniform_int_distribution<>(min, max)(gen_);
+ }
+
+ private:
+ // A pseudo-random number generator.
+ std::mt19937 gen_;
+
+ DISALLOW_COPY_AND_ASSIGN(PRNG);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_PRNG_H_
diff --git a/update_manager/prng_unittest.cc b/update_manager/prng_unittest.cc
new file mode 100644
index 0000000..4d165b3
--- /dev/null
+++ b/update_manager/prng_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/prng.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using std::vector;
+
+namespace chromeos_update_manager {
+
+TEST(UmPRNGTest, ShouldBeDeterministic) {
+ PRNG a(42);
+ PRNG b(42);
+
+ for (int i = 0; i < 1000; ++i) {
+ EXPECT_EQ(a.Rand(), b.Rand()) << "Iteration i=" << i;
+ }
+}
+
+TEST(UmPRNGTest, SeedChangesGeneratedSequence) {
+ PRNG a(42);
+ PRNG b(5);
+
+ vector<uint32_t> values_a;
+ vector<uint32_t> values_b;
+
+ for (int i = 0; i < 100; ++i) {
+ values_a.push_back(a.Rand());
+ values_b.push_back(b.Rand());
+ }
+ EXPECT_NE(values_a, values_b);
+}
+
+TEST(UmPRNGTest, IsNotConstant) {
+ PRNG prng(5);
+
+ uint32_t initial_value = prng.Rand();
+ bool prng_is_constant = true;
+ for (int i = 0; i < 100; ++i) {
+ if (prng.Rand() != initial_value) {
+ prng_is_constant = false;
+ break;
+ }
+ }
+ EXPECT_FALSE(prng_is_constant) << "After 100 iterations.";
+}
+
+TEST(UmPRNGTest, RandCoversRange) {
+ PRNG a(42);
+ int hits[11] = { 0 };
+
+ for (int i = 0; i < 1000; i++) {
+ int r = a.RandMinMax(0, 10);
+ ASSERT_LE(0, r);
+ ASSERT_GE(10, r);
+ hits[r]++;
+ }
+
+ for (auto& hit : hits)
+ EXPECT_LT(0, hit);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/provider.h b/update_manager/provider.h
new file mode 100644
index 0000000..aac92d4
--- /dev/null
+++ b/update_manager/provider.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_PROVIDER_H_
+
+#include <base/macros.h>
+
+namespace chromeos_update_manager {
+
+// Abstract base class for a policy provider.
+class Provider {
+ public:
+ virtual ~Provider() {}
+
+ protected:
+ Provider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Provider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_PROVIDER_H_
diff --git a/update_manager/random_provider.h b/update_manager/random_provider.h
new file mode 100644
index 0000000..7c22863
--- /dev/null
+++ b/update_manager/random_provider.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_RANDOM_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider of random values.
+class RandomProvider : public Provider {
+ public:
+ ~RandomProvider() override {}
+
+ // Return a random number every time it is requested. Note that values
+ // returned by the variables are cached by the EvaluationContext, so the
+ // returned value will be the same during the same policy request. If more
+ // random values are needed use a PRNG seeded with this value.
+ virtual Variable<uint64_t>* var_seed() = 0;
+
+ protected:
+ RandomProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RandomProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_RANDOM_PROVIDER_H_
diff --git a/update_manager/real_config_provider.cc b/update_manager/real_config_provider.cc
new file mode 100644
index 0000000..ac334c4
--- /dev/null
+++ b/update_manager/real_config_provider.cc
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_config_provider.h"
+
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <chromeos/key_value_store.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/utils.h"
+
+using chromeos::KeyValueStore;
+
+namespace {
+
+const char* kConfigFilePath = "/etc/update_manager.conf";
+
+// Config options:
+const char* kConfigOptsIsOOBEEnabled = "is_oobe_enabled";
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+bool RealConfigProvider::Init() {
+ KeyValueStore store;
+
+ if (hardware_->IsNormalBootMode()) {
+ store.Load(base::FilePath(root_prefix_ + kConfigFilePath));
+ } else {
+ if (store.Load(base::FilePath(root_prefix_ +
+ chromeos_update_engine::kStatefulPartition +
+ kConfigFilePath))) {
+ LOG(INFO) << "UpdateManager Config loaded from stateful partition.";
+ } else {
+ store.Load(base::FilePath(root_prefix_ + kConfigFilePath));
+ }
+ }
+
+ bool is_oobe_enabled;
+ if (!store.GetBoolean(kConfigOptsIsOOBEEnabled, &is_oobe_enabled))
+ is_oobe_enabled = true; // Default value.
+ var_is_oobe_enabled_.reset(
+ new ConstCopyVariable<bool>(kConfigOptsIsOOBEEnabled, is_oobe_enabled));
+
+ return true;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_config_provider.h b/update_manager/real_config_provider.h
new file mode 100644
index 0000000..82c9284
--- /dev/null
+++ b/update_manager/real_config_provider.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/hardware_interface.h"
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/generic_variables.h"
+
+namespace chromeos_update_manager {
+
+// ConfigProvider concrete implementation.
+class RealConfigProvider : public ConfigProvider {
+ public:
+ explicit RealConfigProvider(
+ chromeos_update_engine::HardwareInterface* hardware)
+ : hardware_(hardware) {}
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ Variable<bool>* var_is_oobe_enabled() override {
+ return var_is_oobe_enabled_.get();
+ }
+
+ private:
+ friend class UmRealConfigProviderTest;
+
+ // Used for testing. Sets the root prefix, which is by default "". Call this
+ // method before calling Init() in order to mock out the place where the files
+ // are being read from.
+ void SetRootPrefix(const std::string& prefix) {
+ root_prefix_ = prefix;
+ }
+
+ std::unique_ptr<ConstCopyVariable<bool>> var_is_oobe_enabled_;
+
+ chromeos_update_engine::HardwareInterface* hardware_;
+
+ // Prefix to prepend to the file paths. Useful for testing.
+ std::string root_prefix_;
+
+ DISALLOW_COPY_AND_ASSIGN(RealConfigProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
diff --git a/update_manager/real_config_provider_unittest.cc b/update_manager/real_config_provider_unittest.cc
new file mode 100644
index 0000000..f6ddb7c
--- /dev/null
+++ b/update_manager/real_config_provider_unittest.cc
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_config_provider.h"
+
+#include <memory>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_hardware.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::TimeDelta;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealConfigProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(root_dir_.CreateUniqueTempDir());
+ provider_.reset(new RealConfigProvider(&fake_hardware_));
+ provider_->SetRootPrefix(root_dir_.path().value());
+ }
+
+ void WriteStatefulConfig(const string& config) {
+ base::FilePath kFile(root_dir_.path().value()
+ + chromeos_update_engine::kStatefulPartition
+ + "/etc/update_manager.conf");
+ ASSERT_TRUE(base::CreateDirectory(kFile.DirName()));
+ ASSERT_TRUE(WriteFileString(kFile.value(), config));
+ }
+
+ void WriteRootfsConfig(const string& config) {
+ base::FilePath kFile(root_dir_.path().value()
+ + "/etc/update_manager.conf");
+ ASSERT_TRUE(base::CreateDirectory(kFile.DirName()));
+ ASSERT_TRUE(WriteFileString(kFile.value(), config));
+ }
+
+ unique_ptr<RealConfigProvider> provider_;
+ chromeos_update_engine::FakeHardware fake_hardware_;
+ TimeDelta default_timeout_ = TimeDelta::FromSeconds(1);
+ base::ScopedTempDir root_dir_;
+};
+
+TEST_F(UmRealConfigProviderTest, InitTest) {
+ EXPECT_TRUE(provider_->Init());
+ EXPECT_NE(nullptr, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, NoFileFoundReturnsDefault) {
+ EXPECT_TRUE(provider_->Init());
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, DontReadStatefulInNormalMode) {
+ fake_hardware_.SetIsNormalBootMode(true);
+ WriteStatefulConfig("is_oobe_enabled=false");
+
+ EXPECT_TRUE(provider_->Init());
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, ReadStatefulInDevMode) {
+ fake_hardware_.SetIsNormalBootMode(false);
+ WriteRootfsConfig("is_oobe_enabled=true");
+ // Since the stateful is present, this should read that one.
+ WriteStatefulConfig("is_oobe_enabled=false");
+
+ EXPECT_TRUE(provider_->Init());
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, ReadRootfsIfStatefulNotFound) {
+ fake_hardware_.SetIsNormalBootMode(false);
+ WriteRootfsConfig("is_oobe_enabled=false");
+
+ EXPECT_TRUE(provider_->Init());
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_enabled());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_device_policy_provider.cc b/update_manager/real_device_policy_provider.cc
new file mode 100644
index 0000000..6f484f7
--- /dev/null
+++ b/update_manager/real_device_policy_provider.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_device_policy_provider.h"
+
+#include <stdint.h>
+
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <chromeos/dbus/service_constants.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/glib_utils.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/real_shill_provider.h"
+#include "update_engine/utils.h"
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using policy::DevicePolicy;
+using std::set;
+using std::string;
+
+namespace {
+
+const int kDevicePolicyRefreshRateInMinutes = 60;
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+RealDevicePolicyProvider::~RealDevicePolicyProvider() {
+ MessageLoop::current()->CancelTask(scheduled_refresh_);
+ // Detach signal handler, free manager proxy.
+ dbus_->ProxyDisconnectSignal(manager_proxy_,
+ login_manager::kPropertyChangeCompleteSignal,
+ G_CALLBACK(HandlePropertyChangedCompletedStatic),
+ this);
+ dbus_->ProxyUnref(manager_proxy_);
+}
+
+bool RealDevicePolicyProvider::Init() {
+ CHECK(policy_provider_ != nullptr);
+
+ // On Init() we try to get the device policy and keep updating it.
+ RefreshDevicePolicyAndReschedule();
+
+ // We also listen for signals from the session manager to force a device
+ // policy refresh.
+ GError* error = nullptr;
+ DBusGConnection* connection = dbus_->BusGet(DBUS_BUS_SYSTEM, &error);
+ if (!connection) {
+ LOG(ERROR) << "Failed to initialize DBus connection: "
+ << chromeos_update_engine::utils::GetAndFreeGError(&error);
+ return false;
+ }
+ manager_proxy_ = dbus_->ProxyNewForName(
+ connection,
+ login_manager::kSessionManagerServiceName,
+ login_manager::kSessionManagerServicePath,
+ login_manager::kSessionManagerInterface);
+
+ // Subscribe to the session manager's PropertyChangeComplete signal.
+ dbus_->ProxyAddSignal_1(manager_proxy_,
+ login_manager::kPropertyChangeCompleteSignal,
+ G_TYPE_STRING);
+ dbus_->ProxyConnectSignal(manager_proxy_,
+ login_manager::kPropertyChangeCompleteSignal,
+ G_CALLBACK(HandlePropertyChangedCompletedStatic),
+ this, nullptr);
+ return true;
+}
+
+// static
+void RealDevicePolicyProvider::HandlePropertyChangedCompletedStatic(
+ DBusGProxy* proxy, const char* /* payload */, void* data) {
+ // We refresh the policy file even if the payload string is kSignalFailure.
+ RealDevicePolicyProvider* policy_provider =
+ reinterpret_cast<RealDevicePolicyProvider*>(data);
+ LOG(INFO) << "Reloading device policy due to signal received.";
+ policy_provider->RefreshDevicePolicy();
+}
+
+void RealDevicePolicyProvider::RefreshDevicePolicyAndReschedule() {
+ RefreshDevicePolicy();
+ scheduled_refresh_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&RealDevicePolicyProvider::RefreshDevicePolicyAndReschedule,
+ base::Unretained(this)),
+ TimeDelta::FromMinutes(kDevicePolicyRefreshRateInMinutes));
+}
+
+template<typename T>
+void RealDevicePolicyProvider::UpdateVariable(
+ AsyncCopyVariable<T>* var,
+ bool (DevicePolicy::*getter_method)(T*) const) {
+ T new_value;
+ if (policy_provider_->device_policy_is_loaded() &&
+ (policy_provider_->GetDevicePolicy().*getter_method)(&new_value)) {
+ var->SetValue(new_value);
+ } else {
+ var->UnsetValue();
+ }
+}
+
+template<typename T>
+void RealDevicePolicyProvider::UpdateVariable(
+ AsyncCopyVariable<T>* var,
+ bool (RealDevicePolicyProvider::*getter_method)(T*) const) {
+ T new_value;
+ if (policy_provider_->device_policy_is_loaded() &&
+ (this->*getter_method)(&new_value)) {
+ var->SetValue(new_value);
+ } else {
+ var->UnsetValue();
+ }
+}
+
+bool RealDevicePolicyProvider::ConvertAllowedConnectionTypesForUpdate(
+ set<ConnectionType>* allowed_types) const {
+ set<string> allowed_types_str;
+ if (!policy_provider_->GetDevicePolicy()
+ .GetAllowedConnectionTypesForUpdate(&allowed_types_str)) {
+ return false;
+ }
+ allowed_types->clear();
+ for (auto& type_str : allowed_types_str) {
+ ConnectionType type =
+ RealShillProvider::ParseConnectionType(type_str.c_str());
+ if (type != ConnectionType::kUnknown) {
+ allowed_types->insert(type);
+ } else {
+ LOG(WARNING) << "Policy includes unknown connection type: " << type_str;
+ }
+ }
+ return true;
+}
+
+bool RealDevicePolicyProvider::ConvertScatterFactor(
+ TimeDelta* scatter_factor) const {
+ int64_t scatter_factor_in_seconds;
+ if (!policy_provider_->GetDevicePolicy().GetScatterFactorInSeconds(
+ &scatter_factor_in_seconds)) {
+ return false;
+ }
+ if (scatter_factor_in_seconds < 0) {
+ LOG(WARNING) << "Ignoring negative scatter factor: "
+ << scatter_factor_in_seconds;
+ return false;
+ }
+ *scatter_factor = TimeDelta::FromSeconds(scatter_factor_in_seconds);
+ return true;
+}
+
+void RealDevicePolicyProvider::RefreshDevicePolicy() {
+ if (!policy_provider_->Reload()) {
+ LOG(INFO) << "No device policies/settings present.";
+ }
+
+ var_device_policy_is_loaded_.SetValue(
+ policy_provider_->device_policy_is_loaded());
+
+ UpdateVariable(&var_release_channel_, &DevicePolicy::GetReleaseChannel);
+ UpdateVariable(&var_release_channel_delegated_,
+ &DevicePolicy::GetReleaseChannelDelegated);
+ UpdateVariable(&var_update_disabled_, &DevicePolicy::GetUpdateDisabled);
+ UpdateVariable(&var_target_version_prefix_,
+ &DevicePolicy::GetTargetVersionPrefix);
+ UpdateVariable(&var_scatter_factor_,
+ &RealDevicePolicyProvider::ConvertScatterFactor);
+ UpdateVariable(
+ &var_allowed_connection_types_for_update_,
+ &RealDevicePolicyProvider::ConvertAllowedConnectionTypesForUpdate);
+ UpdateVariable(&var_owner_, &DevicePolicy::GetOwner);
+ UpdateVariable(&var_http_downloads_enabled_,
+ &DevicePolicy::GetHttpDownloadsEnabled);
+ UpdateVariable(&var_au_p2p_enabled_, &DevicePolicy::GetAuP2PEnabled);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_device_policy_provider.h b/update_manager/real_device_policy_provider.h
new file mode 100644
index 0000000..b052d59
--- /dev/null
+++ b/update_manager/real_device_policy_provider.h
@@ -0,0 +1,150 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include <chromeos/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+#include <policy/libpolicy.h>
+
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/generic_variables.h"
+
+namespace chromeos_update_manager {
+
+// DevicePolicyProvider concrete implementation.
+class RealDevicePolicyProvider : public DevicePolicyProvider {
+ public:
+ RealDevicePolicyProvider(
+ chromeos_update_engine::DBusWrapperInterface* const dbus,
+ policy::PolicyProvider* policy_provider)
+ : policy_provider_(policy_provider),
+ dbus_(dbus) {}
+ ~RealDevicePolicyProvider();
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ Variable<bool>* var_device_policy_is_loaded() override {
+ return &var_device_policy_is_loaded_;
+ }
+
+ Variable<std::string>* var_release_channel() override {
+ return &var_release_channel_;
+ }
+
+ Variable<bool>* var_release_channel_delegated() override {
+ return &var_release_channel_delegated_;
+ }
+
+ Variable<bool>* var_update_disabled() override {
+ return &var_update_disabled_;
+ }
+
+ Variable<std::string>* var_target_version_prefix() override {
+ return &var_target_version_prefix_;
+ }
+
+ Variable<base::TimeDelta>* var_scatter_factor() override {
+ return &var_scatter_factor_;
+ }
+
+ Variable<std::set<ConnectionType>>*
+ var_allowed_connection_types_for_update() override {
+ return &var_allowed_connection_types_for_update_;
+ }
+
+ Variable<std::string>* var_owner() override {
+ return &var_owner_;
+ }
+
+ Variable<bool>* var_http_downloads_enabled() override {
+ return &var_http_downloads_enabled_;
+ }
+
+ Variable<bool>* var_au_p2p_enabled() override {
+ return &var_au_p2p_enabled_;
+ }
+
+ private:
+ FRIEND_TEST(UmRealDevicePolicyProviderTest, RefreshScheduledTest);
+ FRIEND_TEST(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded);
+ FRIEND_TEST(UmRealDevicePolicyProviderTest, ValuesUpdated);
+
+ // A static handler for the PropertyChangedCompleted signal from the session
+ // manager used as a callback.
+ static void HandlePropertyChangedCompletedStatic(DBusGProxy* proxy,
+ const char* payload,
+ void* data);
+
+ // Schedules a call to periodically refresh the device policy.
+ void RefreshDevicePolicyAndReschedule();
+
+ // Reloads the device policy and updates all the exposed variables.
+ void RefreshDevicePolicy();
+
+ // Updates the async variable |var| based on the result value of the method
+ // passed, which is a DevicePolicy getter method.
+ template<typename T>
+ void UpdateVariable(AsyncCopyVariable<T>* var,
+ bool (policy::DevicePolicy::*getter_method)(T*) const);
+
+ // Updates the async variable |var| based on the result value of the getter
+ // method passed, which is a wrapper getter on this class.
+ template<typename T>
+ void UpdateVariable(
+ AsyncCopyVariable<T>* var,
+ bool (RealDevicePolicyProvider::*getter_method)(T*) const);
+
+ // Wrapper for DevicePolicy::GetScatterFactorInSeconds() that converts the
+ // result to a base::TimeDelta. It returns the same value as
+ // GetScatterFactorInSeconds().
+ bool ConvertScatterFactor(base::TimeDelta* scatter_factor) const;
+
+ // Wrapper for DevicePolicy::GetAllowedConnectionTypesForUpdate() that
+ // converts the result to a set of ConnectionType elements instead of strings.
+ bool ConvertAllowedConnectionTypesForUpdate(
+ std::set<ConnectionType>* allowed_types) const;
+
+ // Used for fetching information about the device policy.
+ policy::PolicyProvider* policy_provider_;
+
+ // Used to schedule refreshes of the device policy.
+ chromeos::MessageLoop::TaskId scheduled_refresh_ =
+ chromeos::MessageLoop::kTaskIdNull;
+
+ // The DBus interface (mockable) and a session manager proxy.
+ chromeos_update_engine::DBusWrapperInterface* const dbus_;
+ DBusGProxy* manager_proxy_ = nullptr;
+
+ // Variable exposing whether the policy is loaded.
+ AsyncCopyVariable<bool> var_device_policy_is_loaded_{
+ "policy_is_loaded", false};
+
+ // Variables mapping the exposed methods from the policy::DevicePolicy.
+ AsyncCopyVariable<std::string> var_release_channel_{"release_channel"};
+ AsyncCopyVariable<bool> var_release_channel_delegated_{
+ "release_channel_delegated"};
+ AsyncCopyVariable<bool> var_update_disabled_{"update_disabled"};
+ AsyncCopyVariable<std::string> var_target_version_prefix_{
+ "target_version_prefix"};
+ AsyncCopyVariable<base::TimeDelta> var_scatter_factor_{"scatter_factor"};
+ AsyncCopyVariable<std::set<ConnectionType>>
+ var_allowed_connection_types_for_update_{
+ "allowed_connection_types_for_update"};
+ AsyncCopyVariable<std::string> var_owner_{"owner"};
+ AsyncCopyVariable<bool> var_http_downloads_enabled_{"http_downloads_enabled"};
+ AsyncCopyVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled"};
+
+ DISALLOW_COPY_AND_ASSIGN(RealDevicePolicyProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_manager/real_device_policy_provider_unittest.cc b/update_manager/real_device_policy_provider_unittest.cc
new file mode 100644
index 0000000..75d7c48
--- /dev/null
+++ b/update_manager/real_device_policy_provider_unittest.cc
@@ -0,0 +1,247 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_device_policy_provider.h"
+
+#include <memory>
+
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+#include <policy/mock_device_policy.h>
+#include <policy/mock_libpolicy.h>
+
+#include "update_engine/mock_dbus_wrapper.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos::MessageLoopRunMaxIterations;
+using std::set;
+using std::string;
+using std::unique_ptr;
+using testing::DoAll;
+using testing::Mock;
+using testing::Return;
+using testing::ReturnRef;
+using testing::SaveArg;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::_;
+
+namespace {
+
+// Fake dbus-glib objects. These should be different values, to ease diagnosis
+// of errors.
+DBusGConnection* const kFakeConnection = reinterpret_cast<DBusGConnection*>(1);
+DBusGProxy* const kFakeManagerProxy = reinterpret_cast<DBusGProxy*>(2);
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+class UmRealDevicePolicyProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ provider_.reset(new RealDevicePolicyProvider(&mock_dbus_,
+ &mock_policy_provider_));
+ // By default, we have a device policy loaded. Tests can call
+ // SetUpNonExistentDevicePolicy() to override this.
+ SetUpExistentDevicePolicy();
+
+ SetUpDBusSignalExpectations();
+ }
+
+ void TearDown() override {
+ // Check for leaked callbacks on the main loop.
+ EXPECT_EQ(0, MessageLoopRunMaxIterations(MessageLoop::current(), 100));
+
+ // We need to set these expectation before the object is destroyed but
+ // after it finished running the test so the values of signal_callback_ and
+ // signal_callback_data_ are correct.
+ EXPECT_CALL(mock_dbus_, ProxyDisconnectSignal(
+ kFakeManagerProxy,
+ StrEq(login_manager::kPropertyChangeCompleteSignal),
+ signal_callback_,
+ signal_callback_data_));
+ EXPECT_CALL(mock_dbus_, ProxyUnref(kFakeManagerProxy));
+
+ provider_.reset();
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ void SetUpNonExistentDevicePolicy() {
+ ON_CALL(mock_policy_provider_, Reload())
+ .WillByDefault(Return(false));
+ ON_CALL(mock_policy_provider_, device_policy_is_loaded())
+ .WillByDefault(Return(false));
+ EXPECT_CALL(mock_policy_provider_, GetDevicePolicy()).Times(0);
+ }
+
+ void SetUpExistentDevicePolicy() {
+ // Setup the default behavior of the mocked PolicyProvider.
+ ON_CALL(mock_policy_provider_, Reload())
+ .WillByDefault(Return(true));
+ ON_CALL(mock_policy_provider_, device_policy_is_loaded())
+ .WillByDefault(Return(true));
+ ON_CALL(mock_policy_provider_, GetDevicePolicy())
+ .WillByDefault(ReturnRef(mock_device_policy_));
+ }
+
+ void SetUpDBusSignalExpectations() {
+ // Setup the DBus connection with default actions that should be performed
+ // once.
+ EXPECT_CALL(mock_dbus_, BusGet(_, _)).WillOnce(
+ Return(kFakeConnection));
+ EXPECT_CALL(mock_dbus_, ProxyNewForName(
+ kFakeConnection, StrEq(login_manager::kSessionManagerServiceName),
+ StrEq(login_manager::kSessionManagerServicePath),
+ StrEq(login_manager::kSessionManagerInterface)))
+ .WillOnce(Return(kFakeManagerProxy));
+
+ // Expect the signal to be added, registered and released.
+ EXPECT_CALL(mock_dbus_, ProxyAddSignal_1(
+ kFakeManagerProxy,
+ StrEq(login_manager::kPropertyChangeCompleteSignal),
+ G_TYPE_STRING));
+ EXPECT_CALL(mock_dbus_, ProxyConnectSignal(
+ kFakeManagerProxy,
+ StrEq(login_manager::kPropertyChangeCompleteSignal),
+ _ /* callback */, _ /* data */, _ /* free function */))
+ .WillOnce(DoAll(SaveArg<2>(&signal_callback_),
+ SaveArg<3>(&signal_callback_data_)));
+ }
+
+ chromeos::FakeMessageLoop loop_{nullptr};
+ chromeos_update_engine::MockDBusWrapper mock_dbus_;
+ testing::NiceMock<policy::MockDevicePolicy> mock_device_policy_;
+ testing::NiceMock<policy::MockPolicyProvider> mock_policy_provider_;
+ unique_ptr<RealDevicePolicyProvider> provider_;
+
+ // The registered signal handler for the signal.
+ GCallback signal_callback_ = nullptr;
+ void* signal_callback_data_ = nullptr;
+};
+
+TEST_F(UmRealDevicePolicyProviderTest, RefreshScheduledTest) {
+ // Check that the RefreshPolicy gets scheduled by checking the EventId.
+ EXPECT_TRUE(provider_->Init());
+ EXPECT_NE(MessageLoop::kTaskIdNull, provider_->scheduled_refresh_);
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, FirstReload) {
+ // Checks that the policy is reloaded and the DevicePolicy is consulted.
+ EXPECT_CALL(mock_policy_provider_, Reload());
+ EXPECT_TRUE(provider_->Init());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded) {
+ // Checks that the policy is reloaded by RefreshDevicePolicy().
+ SetUpNonExistentDevicePolicy();
+ EXPECT_CALL(mock_policy_provider_, Reload()).Times(2);
+ EXPECT_TRUE(provider_->Init());
+ // Force the policy refresh.
+ provider_->RefreshDevicePolicy();
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, SessionManagerSignalForcesReload) {
+ // Checks that a signal from the SessionManager forces a reload.
+ SetUpNonExistentDevicePolicy();
+ EXPECT_CALL(mock_policy_provider_, Reload()).Times(2);
+ EXPECT_TRUE(provider_->Init());
+
+ ASSERT_NE(nullptr, signal_callback_);
+ // Convert the GCallback to a function pointer and call it. GCallback is just
+ // a void function pointer to ensure that the type of the passed callback is a
+ // pointer. We need to cast it back to the right function type before calling
+ // it.
+ typedef void (*StaticSignalHandler)(DBusGProxy*, const char*, void*);
+ StaticSignalHandler signal_handler = reinterpret_cast<StaticSignalHandler>(
+ signal_callback_);
+ (*signal_handler)(kFakeManagerProxy, "success", signal_callback_data_);
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyEmptyVariables) {
+ SetUpNonExistentDevicePolicy();
+ EXPECT_CALL(mock_policy_provider_, GetDevicePolicy()).Times(0);
+ EXPECT_TRUE(provider_->Init());
+
+ UmTestUtils::ExpectVariableHasValue(false,
+ provider_->var_device_policy_is_loaded());
+
+ UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel_delegated());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_update_disabled());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_target_version_prefix());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_scatter_factor());
+ UmTestUtils::ExpectVariableNotSet(
+ provider_->var_allowed_connection_types_for_update());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_owner());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_http_downloads_enabled());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_au_p2p_enabled());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, ValuesUpdated) {
+ SetUpNonExistentDevicePolicy();
+ EXPECT_TRUE(provider_->Init());
+ Mock::VerifyAndClearExpectations(&mock_policy_provider_);
+
+ // Reload the policy with a good one and set some values as present. The
+ // remaining values are false.
+ SetUpExistentDevicePolicy();
+ EXPECT_CALL(mock_device_policy_, GetReleaseChannel(_))
+ .WillOnce(DoAll(SetArgumentPointee<0>(string("mychannel")),
+ Return(true)));
+ EXPECT_CALL(mock_device_policy_, GetAllowedConnectionTypesForUpdate(_))
+ .WillOnce(Return(false));
+
+ provider_->RefreshDevicePolicy();
+
+ UmTestUtils::ExpectVariableHasValue(true,
+ provider_->var_device_policy_is_loaded());
+
+ // Test that at least one variable is set, to ensure the refresh occurred.
+ UmTestUtils::ExpectVariableHasValue(string("mychannel"),
+ provider_->var_release_channel());
+ UmTestUtils::ExpectVariableNotSet(
+ provider_->var_allowed_connection_types_for_update());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, ScatterFactorConverted) {
+ SetUpExistentDevicePolicy();
+ EXPECT_CALL(mock_device_policy_, GetScatterFactorInSeconds(_))
+ .WillOnce(DoAll(SetArgumentPointee<0>(1234), Return(true)));
+ EXPECT_TRUE(provider_->Init());
+
+ UmTestUtils::ExpectVariableHasValue(TimeDelta::FromSeconds(1234),
+ provider_->var_scatter_factor());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, NegativeScatterFactorIgnored) {
+ SetUpExistentDevicePolicy();
+ EXPECT_CALL(mock_device_policy_, GetScatterFactorInSeconds(_))
+ .WillOnce(DoAll(SetArgumentPointee<0>(-1), Return(true)));
+ EXPECT_TRUE(provider_->Init());
+
+ UmTestUtils::ExpectVariableNotSet(provider_->var_scatter_factor());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, AllowedTypesConverted) {
+ SetUpExistentDevicePolicy();
+ EXPECT_CALL(mock_device_policy_, GetAllowedConnectionTypesForUpdate(_))
+ .WillOnce(DoAll(SetArgumentPointee<0>(
+ set<string>{"bluetooth", "wifi", "not-a-type"}),
+ Return(true)));
+ EXPECT_TRUE(provider_->Init());
+
+ UmTestUtils::ExpectVariableHasValue(
+ set<ConnectionType>{ConnectionType::kWifi, ConnectionType::kBluetooth},
+ provider_->var_allowed_connection_types_for_update());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_random_provider.cc b/update_manager/real_random_provider.cc
new file mode 100644
index 0000000..2aa8d57
--- /dev/null
+++ b/update_manager/real_random_provider.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_random_provider.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/scoped_file.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/update_manager/variable.h"
+
+using std::string;
+
+namespace {
+
+// The device providing randomness.
+const char* kRandomDevice = "/dev/urandom";
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+// A random seed variable.
+class RandomSeedVariable : public Variable<uint64_t> {
+ public:
+ // RandomSeedVariable is initialized as kVariableModeConst to let the
+ // EvaluationContext cache the value between different evaluations of the same
+ // policy request.
+ RandomSeedVariable(const string& name, FILE* fp)
+ : Variable<uint64_t>(name, kVariableModeConst), fp_(fp) {}
+ ~RandomSeedVariable() override {}
+
+ protected:
+ const uint64_t* GetValue(base::TimeDelta /* timeout */,
+ string* errmsg) override {
+ uint64_t result;
+ // Aliasing via char pointer abides by the C/C++ strict-aliasing rules.
+ char* const buf = reinterpret_cast<char*>(&result);
+ unsigned int buf_rd = 0;
+
+ while (buf_rd < sizeof(result)) {
+ int rd = fread(buf + buf_rd, 1, sizeof(result) - buf_rd, fp_.get());
+ if (rd == 0 || ferror(fp_.get())) {
+ // Either EOF on fp or read failed.
+ if (errmsg) {
+ *errmsg = base::StringPrintf(
+ "Error reading from the random device: %s", kRandomDevice);
+ }
+ return nullptr;
+ }
+ buf_rd += rd;
+ }
+
+ return new uint64_t(result);
+ }
+
+ private:
+ base::ScopedFILE fp_;
+
+ DISALLOW_COPY_AND_ASSIGN(RandomSeedVariable);
+};
+
+bool RealRandomProvider::Init(void) {
+ FILE* fp = fopen(kRandomDevice, "r");
+ if (!fp)
+ return false;
+ var_seed_.reset(new RandomSeedVariable("seed", fp));
+ return true;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_random_provider.h b/update_manager/real_random_provider.h
new file mode 100644
index 0000000..d159933
--- /dev/null
+++ b/update_manager/real_random_provider.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
+
+#include <memory>
+
+#include "update_engine/update_manager/random_provider.h"
+
+namespace chromeos_update_manager {
+
+// RandomProvider implementation class.
+class RealRandomProvider : public RandomProvider {
+ public:
+ RealRandomProvider() {}
+
+ Variable<uint64_t>* var_seed() override { return var_seed_.get(); }
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ private:
+ // The seed() scoped variable.
+ std::unique_ptr<Variable<uint64_t>> var_seed_;
+
+ DISALLOW_COPY_AND_ASSIGN(RealRandomProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
diff --git a/update_manager/real_random_provider_unittest.cc b/update_manager/real_random_provider_unittest.cc
new file mode 100644
index 0000000..899ef6f
--- /dev/null
+++ b/update_manager/real_random_provider_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_random_provider.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+
+#include "update_engine/update_manager/umtest_utils.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealRandomProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // The provider initializes correctly.
+ provider_.reset(new RealRandomProvider());
+ ASSERT_NE(nullptr, provider_.get());
+ ASSERT_TRUE(provider_->Init());
+
+ provider_->var_seed();
+ }
+
+ unique_ptr<RealRandomProvider> provider_;
+};
+
+TEST_F(UmRealRandomProviderTest, InitFinalize) {
+ // The provider initializes all variables with valid objects.
+ EXPECT_NE(nullptr, provider_->var_seed());
+}
+
+TEST_F(UmRealRandomProviderTest, GetRandomValues) {
+ // Should not return the same random seed repeatedly.
+ unique_ptr<const uint64_t> value(
+ provider_->var_seed()->GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+ ASSERT_NE(nullptr, value.get());
+
+ // Test that at least the returned values are different. This test fails,
+ // by design, once every 2^320 runs.
+ bool is_same_value = true;
+ for (int i = 0; i < 5; i++) {
+ unique_ptr<const uint64_t> other_value(
+ provider_->var_seed()->GetValue(UmTestUtils::DefaultTimeout(),
+ nullptr));
+ ASSERT_NE(nullptr, other_value.get());
+ is_same_value = is_same_value && *other_value == *value;
+ }
+ EXPECT_FALSE(is_same_value);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_shill_provider.cc b/update_manager/real_shill_provider.cc
new file mode 100644
index 0000000..609fd01
--- /dev/null
+++ b/update_manager/real_shill_provider.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_shill_provider.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "update_engine/glib_utils.h"
+
+using std::string;
+
+namespace {
+
+// Looks up a |key| in a GLib |hash_table| and returns the unboxed string from
+// the corresponding GValue, if found.
+const char* GetStrProperty(GHashTable* hash_table, const char* key) {
+ auto gval = reinterpret_cast<GValue*>(g_hash_table_lookup(hash_table, key));
+ return (gval ? g_value_get_string(gval) : nullptr);
+}
+
+}; // namespace
+
+
+namespace chromeos_update_manager {
+
+RealShillProvider::~RealShillProvider() {
+ // Detach signal handler, free manager proxy.
+ dbus_->ProxyDisconnectSignal(manager_proxy_, shill::kMonitorPropertyChanged,
+ G_CALLBACK(HandlePropertyChangedStatic),
+ this);
+ dbus_->ProxyUnref(manager_proxy_);
+}
+
+ConnectionType RealShillProvider::ParseConnectionType(const char* type_str) {
+ if (!strcmp(type_str, shill::kTypeEthernet))
+ return ConnectionType::kEthernet;
+ if (!strcmp(type_str, shill::kTypeWifi))
+ return ConnectionType::kWifi;
+ if (!strcmp(type_str, shill::kTypeWimax))
+ return ConnectionType::kWimax;
+ if (!strcmp(type_str, shill::kTypeBluetooth))
+ return ConnectionType::kBluetooth;
+ if (!strcmp(type_str, shill::kTypeCellular))
+ return ConnectionType::kCellular;
+
+ return ConnectionType::kUnknown;
+}
+
+ConnectionTethering RealShillProvider::ParseConnectionTethering(
+ const char* tethering_str) {
+ if (!strcmp(tethering_str, shill::kTetheringNotDetectedState))
+ return ConnectionTethering::kNotDetected;
+ if (!strcmp(tethering_str, shill::kTetheringSuspectedState))
+ return ConnectionTethering::kSuspected;
+ if (!strcmp(tethering_str, shill::kTetheringConfirmedState))
+ return ConnectionTethering::kConfirmed;
+
+ return ConnectionTethering::kUnknown;
+}
+
+bool RealShillProvider::Init() {
+ // Obtain a DBus connection.
+ GError* error = nullptr;
+ connection_ = dbus_->BusGet(DBUS_BUS_SYSTEM, &error);
+ if (!connection_) {
+ LOG(ERROR) << "Failed to initialize DBus connection: "
+ << chromeos_update_engine::utils::GetAndFreeGError(&error);
+ return false;
+ }
+
+ // Allocate a shill manager proxy.
+ manager_proxy_ = GetProxy(shill::kFlimflamServicePath,
+ shill::kFlimflamManagerInterface);
+
+ // Subscribe to the manager's PropertyChanged signal.
+ dbus_->ProxyAddSignal_2(manager_proxy_, shill::kMonitorPropertyChanged,
+ G_TYPE_STRING, G_TYPE_VALUE);
+ dbus_->ProxyConnectSignal(manager_proxy_, shill::kMonitorPropertyChanged,
+ G_CALLBACK(HandlePropertyChangedStatic),
+ this, nullptr);
+
+ // Attempt to read initial connection status. Even if this fails because shill
+ // is not responding (e.g. it is down) we'll be notified via "PropertyChanged"
+ // signal as soon as it comes up, so this is not a critical step.
+ GHashTable* hash_table = nullptr;
+ if (GetProperties(manager_proxy_, &hash_table)) {
+ GValue* value = reinterpret_cast<GValue*>(
+ g_hash_table_lookup(hash_table, shill::kDefaultServiceProperty));
+ ProcessDefaultService(value);
+ g_hash_table_unref(hash_table);
+ }
+
+ return true;
+}
+
+DBusGProxy* RealShillProvider::GetProxy(const char* path,
+ const char* interface) {
+ return dbus_->ProxyNewForName(connection_, shill::kFlimflamServiceName,
+ path, interface);
+}
+
+bool RealShillProvider::GetProperties(DBusGProxy* proxy,
+ GHashTable** result_p) {
+ GError* error = nullptr;
+ if (!dbus_->ProxyCall_0_1(proxy, shill::kGetPropertiesFunction, &error,
+ result_p)) {
+ LOG(ERROR) << "Calling shill via DBus proxy failed: "
+ << chromeos_update_engine::utils::GetAndFreeGError(&error);
+ return false;
+ }
+ return true;
+}
+
+bool RealShillProvider::ProcessDefaultService(GValue* value) {
+ // Decode the string from the boxed value.
+ const char* default_service_path_str = nullptr;
+ if (!(value && (default_service_path_str = g_value_get_string(value))))
+ return false;
+
+ // Anything changed?
+ if (default_service_path_ == default_service_path_str)
+ return true;
+
+ // Update the connection status.
+ default_service_path_ = default_service_path_str;
+ bool is_connected = (default_service_path_ != "/");
+ var_is_connected_.SetValue(is_connected);
+ var_conn_last_changed_.SetValue(clock_->GetWallclockTime());
+
+ // Update the connection attributes.
+ if (is_connected) {
+ DBusGProxy* service_proxy = GetProxy(default_service_path_.c_str(),
+ shill::kFlimflamServiceInterface);
+ GHashTable* hash_table = nullptr;
+ if (GetProperties(service_proxy, &hash_table)) {
+ // Get the connection type.
+ const char* type_str = GetStrProperty(hash_table, shill::kTypeProperty);
+ if (type_str && !strcmp(type_str, shill::kTypeVPN)) {
+ type_str = GetStrProperty(hash_table,
+ shill::kPhysicalTechnologyProperty);
+ }
+ if (type_str) {
+ var_conn_type_.SetValue(ParseConnectionType(type_str));
+ } else {
+ var_conn_type_.UnsetValue();
+ LOG(ERROR) << "Could not find connection type ("
+ << default_service_path_ << ")";
+ }
+
+ // Get the connection tethering mode.
+ const char* tethering_str = GetStrProperty(hash_table,
+ shill::kTetheringProperty);
+ if (tethering_str) {
+ var_conn_tethering_.SetValue(ParseConnectionTethering(tethering_str));
+ } else {
+ var_conn_tethering_.UnsetValue();
+ LOG(ERROR) << "Could not find connection tethering mode ("
+ << default_service_path_ << ")";
+ }
+
+ g_hash_table_unref(hash_table);
+ }
+ dbus_->ProxyUnref(service_proxy);
+ } else {
+ var_conn_type_.UnsetValue();
+ var_conn_tethering_.UnsetValue();
+ }
+
+ return true;
+}
+
+void RealShillProvider::HandlePropertyChanged(DBusGProxy* proxy,
+ const char* name, GValue* value) {
+ if (!strcmp(name, shill::kDefaultServiceProperty))
+ ProcessDefaultService(value);
+}
+
+void RealShillProvider::HandlePropertyChangedStatic(DBusGProxy* proxy,
+ const char* name,
+ GValue* value,
+ void* data) {
+ auto obj = reinterpret_cast<RealShillProvider*>(data);
+ obj->HandlePropertyChanged(proxy, name, value);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_shill_provider.h b/update_manager/real_shill_provider.h
new file mode 100644
index 0000000..5b50bf4
--- /dev/null
+++ b/update_manager/real_shill_provider.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
+
+// TODO(garnold) Much of the functionality in this module was adapted from the
+// update engine's connection_manager. We need to make sure to deprecate use of
+// connection manager when the time comes.
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/shill_provider.h"
+
+using chromeos_update_engine::ClockInterface;
+using chromeos_update_engine::DBusWrapperInterface;
+
+namespace chromeos_update_manager {
+
+// ShillProvider concrete implementation.
+class RealShillProvider : public ShillProvider {
+ public:
+ RealShillProvider(DBusWrapperInterface* dbus, ClockInterface* clock)
+ : dbus_(dbus), clock_(clock) {}
+
+ ~RealShillProvider() override;
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ Variable<bool>* var_is_connected() override {
+ return &var_is_connected_;
+ }
+
+ Variable<ConnectionType>* var_conn_type() override {
+ return &var_conn_type_;
+ }
+
+ Variable<ConnectionTethering>* var_conn_tethering() override {
+ return &var_conn_tethering_;
+ }
+
+ Variable<base::Time>* var_conn_last_changed() override {
+ return &var_conn_last_changed_;
+ }
+
+ // Helper methods for converting shill strings into symbolic values.
+ static ConnectionType ParseConnectionType(const char* type_str);
+ static ConnectionTethering ParseConnectionTethering(
+ const char* tethering_str);
+
+ private:
+ // Return a DBus proxy for a given |path| and |interface| within shill.
+ DBusGProxy* GetProxy(const char* path, const char* interface);
+
+ // Issues a GetProperties call through a given |proxy|, storing the result to
+ // |*result_p|. Returns true on success.
+ bool GetProperties(DBusGProxy* proxy, GHashTable** result_p);
+
+ // Process a default connection value, update last change time as needed.
+ bool ProcessDefaultService(GValue* value);
+
+ // A handler for manager PropertyChanged signal, and a static version.
+ void HandlePropertyChanged(DBusGProxy* proxy, const char* name,
+ GValue* value);
+ static void HandlePropertyChangedStatic(DBusGProxy* proxy, const char* name,
+ GValue* value, void* data);
+
+ // The current default service path, if connected.
+ std::string default_service_path_;
+
+ // The DBus interface (mockable), connection, and a shill manager proxy.
+ DBusWrapperInterface* const dbus_;
+ DBusGConnection* connection_ = nullptr;
+ DBusGProxy* manager_proxy_ = nullptr;
+
+ // A clock abstraction (mockable).
+ ClockInterface* const clock_;
+
+ // The provider's variables.
+ AsyncCopyVariable<bool> var_is_connected_{"is_connected"};
+ AsyncCopyVariable<ConnectionType> var_conn_type_{"conn_type"};
+ AsyncCopyVariable<ConnectionTethering> var_conn_tethering_{"conn_tethering"};
+ AsyncCopyVariable<base::Time> var_conn_last_changed_{"conn_last_changed"};
+
+ DISALLOW_COPY_AND_ASSIGN(RealShillProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
diff --git a/update_manager/real_shill_provider_unittest.cc b/update_manager/real_shill_provider_unittest.cc
new file mode 100644
index 0000000..78bdd93
--- /dev/null
+++ b/update_manager/real_shill_provider_unittest.cc
@@ -0,0 +1,542 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_shill_provider.h"
+
+#include <memory>
+#include <utility>
+
+#include <base/time/time.h>
+#include <chromeos/dbus/service_constants.h>
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/mock_dbus_wrapper.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::FakeClock;
+using chromeos_update_engine::MockDBusWrapper;
+using chromeos_update_engine::test_utils::GValueFree;
+using chromeos_update_engine::test_utils::GValueNewString;
+using std::pair;
+using std::unique_ptr;
+using testing::A;
+using testing::Eq;
+using testing::Mock;
+using testing::Return;
+using testing::SaveArg;
+using testing::SetArgPointee;
+using testing::StrEq;
+using testing::StrictMock;
+using testing::_;
+
+namespace {
+
+// Fake dbus-glib objects. These should be different values, to ease diagnosis
+// of errors.
+DBusGConnection* const kFakeConnection = reinterpret_cast<DBusGConnection*>(1);
+DBusGProxy* const kFakeManagerProxy = reinterpret_cast<DBusGProxy*>(2);
+DBusGProxy* const kFakeEthernetServiceProxy = reinterpret_cast<DBusGProxy*>(3);
+DBusGProxy* const kFakeWifiServiceProxy = reinterpret_cast<DBusGProxy*>(4);
+DBusGProxy* const kFakeWimaxServiceProxy = reinterpret_cast<DBusGProxy*>(5);
+DBusGProxy* const kFakeBluetoothServiceProxy = reinterpret_cast<DBusGProxy*>(6);
+DBusGProxy* const kFakeCellularServiceProxy = reinterpret_cast<DBusGProxy*>(7);
+DBusGProxy* const kFakeVpnServiceProxy = reinterpret_cast<DBusGProxy*>(8);
+DBusGProxy* const kFakeUnknownServiceProxy = reinterpret_cast<DBusGProxy*>(9);
+
+// Fake service paths.
+const char* const kFakeEthernetServicePath = "/fake-ethernet-service";
+const char* const kFakeWifiServicePath = "/fake-wifi-service";
+const char* const kFakeWimaxServicePath = "/fake-wimax-service";
+const char* const kFakeBluetoothServicePath = "/fake-bluetooth-service";
+const char* const kFakeCellularServicePath = "/fake-cellular-service";
+const char* const kFakeVpnServicePath = "/fake-vpn-service";
+const char* const kFakeUnknownServicePath = "/fake-unknown-service";
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+class UmRealShillProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // By default, initialize the provider so that it gets an initial connection
+ // status from shill. This simulates the common case where shill is
+ // available and responding during RealShillProvider initialization.
+ Init(true);
+ }
+
+ void TearDown() override {
+ Shutdown();
+ }
+
+ // Initialize the RealShillProvider under test. If |do_init_conn_status| is
+ // true, configure mocks to respond to the initial connection status check
+ // with shill. Otherwise, the initial check will fail.
+ void Init(bool do_init_conn_status) {
+ // Properly shutdown a previously initialized provider.
+ if (provider_.get())
+ Shutdown();
+
+ provider_.reset(new RealShillProvider(&mock_dbus_, &fake_clock_));
+ ASSERT_NE(nullptr, provider_.get());
+ fake_clock_.SetWallclockTime(InitTime());
+
+ // A DBus connection should only be obtained once.
+ EXPECT_CALL(mock_dbus_, BusGet(_, _)).WillOnce(
+ Return(kFakeConnection));
+
+ // A manager proxy should only be obtained once.
+ EXPECT_CALL(mock_dbus_, ProxyNewForName(
+ kFakeConnection, StrEq(shill::kFlimflamServiceName),
+ StrEq(shill::kFlimflamServicePath),
+ StrEq(shill::kFlimflamManagerInterface)))
+ .WillOnce(Return(kFakeManagerProxy));
+
+ // The PropertyChanged signal should be subscribed to.
+ EXPECT_CALL(mock_dbus_, ProxyAddSignal_2(
+ kFakeManagerProxy, StrEq(shill::kMonitorPropertyChanged),
+ G_TYPE_STRING, G_TYPE_VALUE))
+ .WillOnce(Return());
+ EXPECT_CALL(mock_dbus_, ProxyConnectSignal(
+ kFakeManagerProxy, StrEq(shill::kMonitorPropertyChanged),
+ _, _, _))
+ .WillOnce(
+ DoAll(SaveArg<2>(reinterpret_cast<void (**)()>(&signal_handler_)),
+ SaveArg<3>(&signal_data_),
+ Return()));
+
+ // Mock a response to an initial connection check (optional).
+ GHashTable* manager_properties = nullptr;
+ if (do_init_conn_status) {
+ pair<const char*, const char*> manager_pairs[] = {
+ {shill::kDefaultServiceProperty, "/"},
+ };
+ manager_properties = SetupGetPropertiesOkay(
+ kFakeManagerProxy, arraysize(manager_pairs), manager_pairs);
+ } else {
+ SetupGetPropertiesFail(kFakeManagerProxy);
+ }
+
+ // Check that provider initializes correctly.
+ ASSERT_TRUE(provider_->Init());
+
+ // All mocked calls should have been exercised by now.
+ Mock::VerifyAndClear(&mock_dbus_);
+
+ // Release properties hash table (if provided).
+ if (manager_properties)
+ g_hash_table_unref(manager_properties);
+ }
+
+ // Deletes the RealShillProvider under test.
+ void Shutdown() {
+ // Make sure that DBus resources get freed.
+ EXPECT_CALL(mock_dbus_, ProxyDisconnectSignal(
+ kFakeManagerProxy, StrEq(shill::kMonitorPropertyChanged),
+ Eq(reinterpret_cast<void (*)()>(signal_handler_)),
+ Eq(signal_data_)))
+ .WillOnce(Return());
+ EXPECT_CALL(mock_dbus_, ProxyUnref(kFakeManagerProxy)).WillOnce(Return());
+ provider_.reset();
+
+ // All mocked calls should have been exercised by now.
+ Mock::VerifyAndClear(&mock_dbus_);
+ }
+
+ // These methods generate fixed timestamps for use in faking the current time.
+ Time InitTime() {
+ Time::Exploded now_exp;
+ now_exp.year = 2014;
+ now_exp.month = 3;
+ now_exp.day_of_week = 2;
+ now_exp.day_of_month = 18;
+ now_exp.hour = 8;
+ now_exp.minute = 5;
+ now_exp.second = 33;
+ now_exp.millisecond = 675;
+ return Time::FromLocalExploded(now_exp);
+ }
+
+ Time ConnChangedTime() {
+ return InitTime() + TimeDelta::FromSeconds(10);
+ }
+
+ // Sets up a successful mock "GetProperties" call on |proxy|, writing a hash
+ // table containing |num_entries| entries formed by key/value pairs from
+ // |key_val_pairs| and returning true. Keys and values are plain C strings
+ // (const char*). The proxy call is expected to be made exactly once. Returns
+ // a pointer to a newly allocated hash table, which should be unreffed with
+ // g_hash_table_unref() when done.
+ GHashTable* SetupGetPropertiesOkay(
+ DBusGProxy* proxy, size_t num_entries,
+ pair<const char*, const char*>* key_val_pairs) {
+ // Allocate and populate the hash table.
+ GHashTable* properties = g_hash_table_new_full(g_str_hash, g_str_equal,
+ free, GValueFree);
+ for (size_t i = 0; i < num_entries; i++) {
+ g_hash_table_insert(properties, strdup(key_val_pairs[i].first),
+ GValueNewString(key_val_pairs[i].second));
+ }
+
+ // Set mock expectations.
+ EXPECT_CALL(mock_dbus_,
+ ProxyCall_0_1(proxy, StrEq(shill::kGetPropertiesFunction),
+ _, A<GHashTable**>()))
+ .WillOnce(DoAll(SetArgPointee<3>(g_hash_table_ref(properties)),
+ Return(true)));
+
+ return properties;
+ }
+
+ // Sets up a failing mock "GetProperties" call on |proxy|, returning false.
+ // The proxy call is expected to be made exactly once.
+ void SetupGetPropertiesFail(DBusGProxy* proxy) {
+ EXPECT_CALL(mock_dbus_,
+ ProxyCall_0_1(proxy, StrEq(shill::kGetPropertiesFunction),
+ _, A<GHashTable**>()))
+ .WillOnce(Return(false));
+ }
+
+ // Sends a signal informing the provider about a default connection
+ // |service_path|. Returns the fake connection change time.
+ Time SendDefaultServiceSignal(const char* service_path) {
+ auto default_service_gval = GValueNewString(service_path);
+ const Time conn_change_time = ConnChangedTime();
+ fake_clock_.SetWallclockTime(conn_change_time);
+ signal_handler_(kFakeManagerProxy, shill::kDefaultServiceProperty,
+ default_service_gval, signal_data_);
+ fake_clock_.SetWallclockTime(conn_change_time + TimeDelta::FromSeconds(5));
+ GValueFree(default_service_gval);
+ return conn_change_time;
+ }
+
+ // Sets up expectations for detection of a connection |service_path| with type
+ // |shill_type_str| and tethering mode |shill_tethering_str|. Ensures that the
+ // new connection status and change time are properly detected by the
+ // provider. Writes the fake connection change time to |conn_change_time_p|,
+ // if provided.
+ void SetupConnectionAndAttrs(const char* service_path,
+ DBusGProxy* service_proxy,
+ const char* shill_type_str,
+ const char* shill_tethering_str,
+ Time* conn_change_time_p) {
+ // Mock logic for querying the default service attributes.
+ EXPECT_CALL(mock_dbus_,
+ ProxyNewForName(
+ kFakeConnection, StrEq(shill::kFlimflamServiceName),
+ StrEq(service_path),
+ StrEq(shill::kFlimflamServiceInterface)))
+ .WillOnce(Return(service_proxy));
+ EXPECT_CALL(mock_dbus_, ProxyUnref(service_proxy)).WillOnce(Return());
+ pair<const char*, const char*> service_pairs[] = {
+ {shill::kTypeProperty, shill_type_str},
+ {shill::kTetheringProperty, shill_tethering_str},
+ };
+ auto service_properties = SetupGetPropertiesOkay(
+ service_proxy, arraysize(service_pairs), service_pairs);
+
+ // Send a signal about a new default service.
+ auto conn_change_time = SendDefaultServiceSignal(service_path);
+
+ // Release the service properties hash tables.
+ g_hash_table_unref(service_properties);
+
+ // Query the connection status, ensure last change time reported correctly.
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected());
+ UmTestUtils::ExpectVariableHasValue(conn_change_time,
+ provider_->var_conn_last_changed());
+
+ // Write the connection change time to the output argument.
+ if (conn_change_time_p)
+ *conn_change_time_p = conn_change_time;
+ }
+
+ // Sets up a connection and tests that its type is being properly detected by
+ // the provider.
+ void SetupConnectionAndTestType(const char* service_path,
+ DBusGProxy* service_proxy,
+ const char* shill_type_str,
+ ConnectionType expected_conn_type) {
+ // Set up and test the connection, record the change time.
+ Time conn_change_time;
+ SetupConnectionAndAttrs(service_path, service_proxy, shill_type_str,
+ shill::kTetheringNotDetectedState,
+ &conn_change_time);
+
+ // Query the connection type, ensure last change time did not change.
+ UmTestUtils::ExpectVariableHasValue(expected_conn_type,
+ provider_->var_conn_type());
+ UmTestUtils::ExpectVariableHasValue(conn_change_time,
+ provider_->var_conn_last_changed());
+ }
+
+ // Sets up a connection and tests that its tethering mode is being properly
+ // detected by the provider.
+ void SetupConnectionAndTestTethering(
+ const char* service_path, DBusGProxy* service_proxy,
+ const char* shill_tethering_str,
+ ConnectionTethering expected_conn_tethering) {
+ // Set up and test the connection, record the change time.
+ Time conn_change_time;
+ SetupConnectionAndAttrs(service_path, service_proxy, shill::kTypeEthernet,
+ shill_tethering_str, &conn_change_time);
+
+ // Query the connection tethering, ensure last change time did not change.
+ UmTestUtils::ExpectVariableHasValue(expected_conn_tethering,
+ provider_->var_conn_tethering());
+ UmTestUtils::ExpectVariableHasValue(conn_change_time,
+ provider_->var_conn_last_changed());
+ }
+
+ StrictMock<MockDBusWrapper> mock_dbus_;
+ FakeClock fake_clock_;
+ unique_ptr<RealShillProvider> provider_;
+ void (*signal_handler_)(DBusGProxy*, const char*, GValue*, void*);
+ void* signal_data_;
+};
+
+// Query the connection status, type and time last changed, as they were set
+// during initialization (no signals).
+TEST_F(UmRealShillProviderTest, ReadBaseValues) {
+ // Query the provider variables.
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_connected());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type());
+ UmTestUtils::ExpectVariableHasValue(InitTime(),
+ provider_->var_conn_last_changed());
+}
+
+// Test that Ethernet connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeEthernet) {
+ SetupConnectionAndTestType(kFakeEthernetServicePath,
+ kFakeEthernetServiceProxy,
+ shill::kTypeEthernet,
+ ConnectionType::kEthernet);
+}
+
+// Test that Wifi connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeWifi) {
+ SetupConnectionAndTestType(kFakeWifiServicePath,
+ kFakeWifiServiceProxy,
+ shill::kTypeWifi,
+ ConnectionType::kWifi);
+}
+
+// Test that Wimax connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeWimax) {
+ SetupConnectionAndTestType(kFakeWimaxServicePath,
+ kFakeWimaxServiceProxy,
+ shill::kTypeWimax,
+ ConnectionType::kWimax);
+}
+
+// Test that Bluetooth connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeBluetooth) {
+ SetupConnectionAndTestType(kFakeBluetoothServicePath,
+ kFakeBluetoothServiceProxy,
+ shill::kTypeBluetooth,
+ ConnectionType::kBluetooth);
+}
+
+// Test that Cellular connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeCellular) {
+ SetupConnectionAndTestType(kFakeCellularServicePath,
+ kFakeCellularServiceProxy,
+ shill::kTypeCellular,
+ ConnectionType::kCellular);
+}
+
+// Test that an unknown connection is identified as such.
+TEST_F(UmRealShillProviderTest, ReadConnTypeUnknown) {
+ SetupConnectionAndTestType(kFakeUnknownServicePath,
+ kFakeUnknownServiceProxy,
+ "FooConnectionType",
+ ConnectionType::kUnknown);
+}
+
+// Tests that VPN connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeVpn) {
+ // Mock logic for returning a default service path and its type.
+ EXPECT_CALL(mock_dbus_, ProxyNewForName(
+ kFakeConnection, StrEq(shill::kFlimflamServiceName),
+ StrEq(kFakeVpnServicePath), StrEq(shill::kFlimflamServiceInterface)))
+ .WillOnce(Return(kFakeVpnServiceProxy));
+ EXPECT_CALL(mock_dbus_, ProxyUnref(kFakeVpnServiceProxy)).WillOnce(Return());
+ pair<const char*, const char*> service_pairs[] = {
+ {shill::kTypeProperty, shill::kTypeVPN},
+ {shill::kPhysicalTechnologyProperty, shill::kTypeWifi},
+ };
+ auto service_properties = SetupGetPropertiesOkay(kFakeVpnServiceProxy,
+ arraysize(service_pairs),
+ service_pairs);
+
+ // Send a signal about a new default service.
+ Time conn_change_time = SendDefaultServiceSignal(kFakeVpnServicePath);
+
+ // Query the connection type, ensure last change time reported correctly.
+ UmTestUtils::ExpectVariableHasValue(ConnectionType::kWifi,
+ provider_->var_conn_type());
+ UmTestUtils::ExpectVariableHasValue(conn_change_time,
+ provider_->var_conn_last_changed());
+
+ // Release properties hash tables.
+ g_hash_table_unref(service_properties);
+}
+
+// Ensure that the connection type is properly cached in the provider through
+// subsequent variable readings.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheUsed) {
+ SetupConnectionAndTestType(kFakeEthernetServicePath,
+ kFakeEthernetServiceProxy,
+ shill::kTypeEthernet,
+ ConnectionType::kEthernet);
+
+ UmTestUtils::ExpectVariableHasValue(ConnectionType::kEthernet,
+ provider_->var_conn_type());
+}
+
+// Ensure that the cached connection type remains valid even when a default
+// connection signal occurs but the connection is not changed.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheRemainsValid) {
+ SetupConnectionAndTestType(kFakeEthernetServicePath,
+ kFakeEthernetServiceProxy,
+ shill::kTypeEthernet,
+ ConnectionType::kEthernet);
+
+ SendDefaultServiceSignal(kFakeEthernetServicePath);
+
+ UmTestUtils::ExpectVariableHasValue(ConnectionType::kEthernet,
+ provider_->var_conn_type());
+}
+
+// Ensure that the cached connection type is invalidated and re-read when the
+// default connection changes.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheInvalidated) {
+ SetupConnectionAndTestType(kFakeEthernetServicePath,
+ kFakeEthernetServiceProxy,
+ shill::kTypeEthernet,
+ ConnectionType::kEthernet);
+
+ SetupConnectionAndTestType(kFakeWifiServicePath,
+ kFakeWifiServiceProxy,
+ shill::kTypeWifi,
+ ConnectionType::kWifi);
+}
+
+// Test that a non-tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringNotDetected) {
+ SetupConnectionAndTestTethering(kFakeWifiServicePath,
+ kFakeWifiServiceProxy,
+ shill::kTetheringNotDetectedState,
+ ConnectionTethering::kNotDetected);
+}
+
+// Test that a suspected tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringSuspected) {
+ SetupConnectionAndTestTethering(kFakeWifiServicePath,
+ kFakeWifiServiceProxy,
+ shill::kTetheringSuspectedState,
+ ConnectionTethering::kSuspected);
+}
+
+// Test that a confirmed tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringConfirmed) {
+ SetupConnectionAndTestTethering(kFakeWifiServicePath,
+ kFakeWifiServiceProxy,
+ shill::kTetheringConfirmedState,
+ ConnectionTethering::kConfirmed);
+}
+
+// Test that an unknown tethering mode is identified as such.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringUnknown) {
+ SetupConnectionAndTestTethering(kFakeWifiServicePath,
+ kFakeWifiServiceProxy,
+ "FooConnTethering",
+ ConnectionTethering::kUnknown);
+}
+
+// Ensure that the connection tethering mode is properly cached in the provider.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheUsed) {
+ SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+ kFakeEthernetServiceProxy,
+ shill::kTetheringNotDetectedState,
+ ConnectionTethering::kNotDetected);
+
+ UmTestUtils::ExpectVariableHasValue(ConnectionTethering::kNotDetected,
+ provider_->var_conn_tethering());
+}
+
+// Ensure that the cached connection tethering mode remains valid even when a
+// default connection signal occurs but the connection is not changed.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheRemainsValid) {
+ SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+ kFakeEthernetServiceProxy,
+ shill::kTetheringNotDetectedState,
+ ConnectionTethering::kNotDetected);
+
+ SendDefaultServiceSignal(kFakeEthernetServicePath);
+
+ UmTestUtils::ExpectVariableHasValue(ConnectionTethering::kNotDetected,
+ provider_->var_conn_tethering());
+}
+
+// Ensure that the cached connection tethering mode is invalidated and re-read
+// when the default connection changes.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheInvalidated) {
+ SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+ kFakeEthernetServiceProxy,
+ shill::kTetheringNotDetectedState,
+ ConnectionTethering::kNotDetected);
+
+ SetupConnectionAndTestTethering(kFakeWifiServicePath,
+ kFakeWifiServiceProxy,
+ shill::kTetheringConfirmedState,
+ ConnectionTethering::kConfirmed);
+}
+
+// Fake two DBus signals prompting a default connection change, but otherwise
+// give the same service path. Check connection status and the time it was last
+// changed, making sure that it is the time when the first signal was sent (and
+// not the second).
+TEST_F(UmRealShillProviderTest, ReadLastChangedTimeTwoSignals) {
+ // Send a default service signal twice, advancing the clock in between.
+ Time conn_change_time;
+ SetupConnectionAndAttrs(kFakeEthernetServicePath, kFakeEthernetServiceProxy,
+ shill::kTypeEthernet,
+ shill::kTetheringNotDetectedState, &conn_change_time);
+ SendDefaultServiceSignal(kFakeEthernetServicePath);
+
+ // Query the connection status, ensure last change time reported correctly.
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected());
+ UmTestUtils::ExpectVariableHasValue(conn_change_time,
+ provider_->var_conn_last_changed());
+}
+
+// Make sure that the provider initializes correctly even if shill is not
+// responding, that variables can be obtained, and that they all return a null
+// value (indicating that the underlying values were not set).
+TEST_F(UmRealShillProviderTest, NoInitConnStatusReadBaseValues) {
+ // Re-initialize the provider, no initial connection status response.
+ Init(false);
+ UmTestUtils::ExpectVariableNotSet(provider_->var_is_connected());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_conn_last_changed());
+}
+
+// Test that, once a signal is received, the connection status and other info
+// can be read correctly.
+TEST_F(UmRealShillProviderTest, NoInitConnStatusReadConnTypeEthernet) {
+ // Re-initialize the provider, no initial connection status response.
+ Init(false);
+ SetupConnectionAndTestType(kFakeEthernetServicePath,
+ kFakeEthernetServiceProxy,
+ shill::kTypeEthernet,
+ ConnectionType::kEthernet);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_state.h b/update_manager/real_state.h
new file mode 100644
index 0000000..ac70648
--- /dev/null
+++ b/update_manager/real_state.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_REAL_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_STATE_H_
+
+#include <memory>
+
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// State concrete implementation.
+class RealState : public State {
+ public:
+ ~RealState() override {}
+
+ RealState(ConfigProvider* config_provider,
+ DevicePolicyProvider* device_policy_provider,
+ RandomProvider* random_provider,
+ ShillProvider* shill_provider,
+ SystemProvider* system_provider,
+ TimeProvider* time_provider,
+ UpdaterProvider* updater_provider) :
+ config_provider_(config_provider),
+ device_policy_provider_(device_policy_provider),
+ random_provider_(random_provider),
+ shill_provider_(shill_provider),
+ system_provider_(system_provider),
+ time_provider_(time_provider),
+ updater_provider_(updater_provider) {}
+
+ // These methods return the given provider.
+ ConfigProvider* config_provider() override {
+ return config_provider_.get();
+ }
+ DevicePolicyProvider* device_policy_provider() override {
+ return device_policy_provider_.get();
+ }
+ RandomProvider* random_provider() override {
+ return random_provider_.get();
+ }
+ ShillProvider* shill_provider() override {
+ return shill_provider_.get();
+ }
+ SystemProvider* system_provider() override {
+ return system_provider_.get();
+ }
+ TimeProvider* time_provider() override {
+ return time_provider_.get();
+ }
+ UpdaterProvider* updater_provider() override {
+ return updater_provider_.get();
+ }
+
+ private:
+ // Instances of the providers.
+ std::unique_ptr<ConfigProvider> config_provider_;
+ std::unique_ptr<DevicePolicyProvider> device_policy_provider_;
+ std::unique_ptr<RandomProvider> random_provider_;
+ std::unique_ptr<ShillProvider> shill_provider_;
+ std::unique_ptr<SystemProvider> system_provider_;
+ std::unique_ptr<TimeProvider> time_provider_;
+ std::unique_ptr<UpdaterProvider> updater_provider_;
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_STATE_H_
diff --git a/update_manager/real_system_provider.cc b/update_manager/real_system_provider.cc
new file mode 100644
index 0000000..1410106
--- /dev/null
+++ b/update_manager/real_system_provider.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_system_provider.h"
+
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <vboot/crossystem.h>
+
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_manager {
+
+bool RealSystemProvider::Init() {
+ var_is_normal_boot_mode_.reset(
+ new ConstCopyVariable<bool>("is_normal_boot_mode",
+ VbGetSystemPropertyInt("devsw_boot") != 0));
+
+ var_is_official_build_.reset(
+ new ConstCopyVariable<bool>("is_official_build",
+ VbGetSystemPropertyInt("debug_build") == 0));
+
+ var_is_oobe_complete_.reset(
+ new CallCopyVariable<bool>(
+ "is_oobe_complete",
+ base::Bind(&chromeos_update_engine::HardwareInterface::IsOOBEComplete,
+ base::Unretained(hardware_), nullptr)));
+
+ var_is_boot_device_removable_.reset(
+ new ConstCopyVariable<bool>("is_boot_device_removable",
+ hardware_->IsBootDeviceRemovable()));
+
+ return true;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_system_provider.h b/update_manager/real_system_provider.h
new file mode 100644
index 0000000..9081d94
--- /dev/null
+++ b/update_manager/real_system_provider.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/hardware_interface.h"
+#include "update_engine/update_manager/system_provider.h"
+
+namespace chromeos_update_manager {
+
+// SystemProvider concrete implementation.
+class RealSystemProvider : public SystemProvider {
+ public:
+ explicit RealSystemProvider(
+ chromeos_update_engine::HardwareInterface* hardware)
+ : hardware_(hardware) {}
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ Variable<bool>* var_is_normal_boot_mode() override {
+ return var_is_normal_boot_mode_.get();
+ }
+
+ Variable<bool>* var_is_official_build() override {
+ return var_is_official_build_.get();
+ }
+
+ Variable<bool>* var_is_oobe_complete() override {
+ return var_is_oobe_complete_.get();
+ }
+
+ Variable<bool>* var_is_boot_device_removable() override {
+ return var_is_boot_device_removable_.get();
+ }
+
+ private:
+ std::unique_ptr<Variable<bool>> var_is_normal_boot_mode_;
+ std::unique_ptr<Variable<bool>> var_is_official_build_;
+ std::unique_ptr<Variable<bool>> var_is_oobe_complete_;
+ std::unique_ptr<Variable<bool>> var_is_boot_device_removable_;
+
+ chromeos_update_engine::HardwareInterface* hardware_;
+
+ DISALLOW_COPY_AND_ASSIGN(RealSystemProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
diff --git a/update_manager/real_system_provider_unittest.cc b/update_manager/real_system_provider_unittest.cc
new file mode 100644
index 0000000..41222d9
--- /dev/null
+++ b/update_manager/real_system_provider_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_system_provider.h"
+
+#include <memory>
+
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_hardware.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealSystemProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ provider_.reset(new RealSystemProvider(&fake_hardware_));
+ EXPECT_TRUE(provider_->Init());
+ }
+
+ chromeos_update_engine::FakeHardware fake_hardware_;
+ unique_ptr<RealSystemProvider> provider_;
+};
+
+TEST_F(UmRealSystemProviderTest, InitTest) {
+ EXPECT_NE(nullptr, provider_->var_is_normal_boot_mode());
+ EXPECT_NE(nullptr, provider_->var_is_official_build());
+ EXPECT_NE(nullptr, provider_->var_is_oobe_complete());
+}
+
+TEST_F(UmRealSystemProviderTest, IsOOBECompleteTrue) {
+ fake_hardware_.SetIsOOBEComplete(base::Time());
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_complete());
+}
+
+TEST_F(UmRealSystemProviderTest, IsOOBECompleteFalse) {
+ fake_hardware_.UnsetIsOOBEComplete();
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_complete());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_time_provider.cc b/update_manager/real_time_provider.cc
new file mode 100644
index 0000000..dab9829
--- /dev/null
+++ b/update_manager/real_time_provider.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_time_provider.h"
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/clock_interface.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ClockInterface;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// A variable returning the current date.
+class CurrDateVariable : public Variable<Time> {
+ public:
+ // TODO(garnold) Turn this into an async variable with the needed callback
+ // logic for when it value changes.
+ CurrDateVariable(const string& name, ClockInterface* clock)
+ : Variable<Time>(name, TimeDelta::FromHours(1)), clock_(clock) {}
+
+ protected:
+ virtual const Time* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) {
+ Time::Exploded now_exp;
+ clock_->GetWallclockTime().LocalExplode(&now_exp);
+ now_exp.hour = now_exp.minute = now_exp.second = now_exp.millisecond = 0;
+ return new Time(Time::FromLocalExploded(now_exp));
+ }
+
+ private:
+ ClockInterface* clock_;
+
+ DISALLOW_COPY_AND_ASSIGN(CurrDateVariable);
+};
+
+// A variable returning the current hour in local time.
+class CurrHourVariable : public Variable<int> {
+ public:
+ // TODO(garnold) Turn this into an async variable with the needed callback
+ // logic for when it value changes.
+ CurrHourVariable(const string& name, ClockInterface* clock)
+ : Variable<int>(name, TimeDelta::FromMinutes(5)), clock_(clock) {}
+
+ protected:
+ virtual const int* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) {
+ Time::Exploded exploded;
+ clock_->GetWallclockTime().LocalExplode(&exploded);
+ return new int(exploded.hour);
+ }
+
+ private:
+ ClockInterface* clock_;
+
+ DISALLOW_COPY_AND_ASSIGN(CurrHourVariable);
+};
+
+bool RealTimeProvider::Init() {
+ var_curr_date_.reset(new CurrDateVariable("curr_date", clock_));
+ var_curr_hour_.reset(new CurrHourVariable("curr_hour", clock_));
+ return true;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_time_provider.h b/update_manager/real_time_provider.h
new file mode 100644
index 0000000..63fc116
--- /dev/null
+++ b/update_manager/real_time_provider.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
+
+#include <memory>
+
+#include <base/time/time.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/update_manager/time_provider.h"
+
+namespace chromeos_update_manager {
+
+// TimeProvider concrete implementation.
+class RealTimeProvider : public TimeProvider {
+ public:
+ explicit RealTimeProvider(chromeos_update_engine::ClockInterface* clock)
+ : clock_(clock) {}
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ Variable<base::Time>* var_curr_date() override {
+ return var_curr_date_.get();
+ }
+
+ Variable<int>* var_curr_hour() override {
+ return var_curr_hour_.get();
+ }
+
+ private:
+ // A clock abstraction (fakeable).
+ chromeos_update_engine::ClockInterface* const clock_;
+
+ std::unique_ptr<Variable<base::Time>> var_curr_date_;
+ std::unique_ptr<Variable<int>> var_curr_hour_;
+
+ DISALLOW_COPY_AND_ASSIGN(RealTimeProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
diff --git a/update_manager/real_time_provider_unittest.cc b/update_manager/real_time_provider_unittest.cc
new file mode 100644
index 0000000..90bd3c1
--- /dev/null
+++ b/update_manager/real_time_provider_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_time_provider.h"
+
+#include <memory>
+
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using chromeos_update_engine::FakeClock;
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealTimeProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // The provider initializes correctly.
+ provider_.reset(new RealTimeProvider(&fake_clock_));
+ ASSERT_NE(nullptr, provider_.get());
+ ASSERT_TRUE(provider_->Init());
+ }
+
+ // Generates a fixed timestamp for use in faking the current time.
+ Time CurrTime() {
+ Time::Exploded now_exp;
+ now_exp.year = 2014;
+ now_exp.month = 3;
+ now_exp.day_of_week = 2;
+ now_exp.day_of_month = 18;
+ now_exp.hour = 8;
+ now_exp.minute = 5;
+ now_exp.second = 33;
+ now_exp.millisecond = 675;
+ return Time::FromLocalExploded(now_exp);
+ }
+
+ FakeClock fake_clock_;
+ unique_ptr<RealTimeProvider> provider_;
+};
+
+TEST_F(UmRealTimeProviderTest, CurrDateValid) {
+ const Time now = CurrTime();
+ Time::Exploded exploded;
+ now.LocalExplode(&exploded);
+ exploded.hour = 0;
+ exploded.minute = 0;
+ exploded.second = 0;
+ exploded.millisecond = 0;
+ const Time expected = Time::FromLocalExploded(exploded);
+
+ fake_clock_.SetWallclockTime(now);
+ UmTestUtils::ExpectVariableHasValue(expected, provider_->var_curr_date());
+}
+
+TEST_F(UmRealTimeProviderTest, CurrHourValid) {
+ const Time now = CurrTime();
+ Time::Exploded expected;
+ now.LocalExplode(&expected);
+ fake_clock_.SetWallclockTime(now);
+ UmTestUtils::ExpectVariableHasValue(expected.hour,
+ provider_->var_curr_hour());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_updater_provider.cc b/update_manager/real_updater_provider.cc
new file mode 100644
index 0000000..ac70746
--- /dev/null
+++ b/update_manager/real_updater_provider.cc
@@ -0,0 +1,424 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_updater_provider.h"
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/prefs.h"
+#include "update_engine/update_attempter.h"
+
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::OmahaRequestParams;
+using chromeos_update_engine::SystemState;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// A templated base class for all update related variables. Provides uniform
+// construction and a system state handle.
+template<typename T>
+class UpdaterVariableBase : public Variable<T> {
+ public:
+ UpdaterVariableBase(const string& name, VariableMode mode,
+ SystemState* system_state)
+ : Variable<T>(name, mode), system_state_(system_state) {}
+
+ protected:
+ // The system state used for pulling information from the updater.
+ inline SystemState* system_state() const { return system_state_; }
+
+ private:
+ SystemState* const system_state_;
+};
+
+// Helper class for issuing a GetStatus() to the UpdateAttempter.
+class GetStatusHelper {
+ public:
+ GetStatusHelper(SystemState* system_state, string* errmsg) {
+ is_success_ = system_state->update_attempter()->GetStatus(
+ &last_checked_time_, &progress_, &update_status_, &new_version_,
+ &payload_size_);
+ if (!is_success_ && errmsg)
+ *errmsg = "Failed to get a status update from the update engine";
+ }
+
+ inline bool is_success() { return is_success_; }
+ inline int64_t last_checked_time() { return last_checked_time_; }
+ inline double progress() { return progress_; }
+ inline const string& update_status() { return update_status_; }
+ inline const string& new_version() { return new_version_; }
+ inline int64_t payload_size() { return payload_size_; }
+
+ private:
+ bool is_success_;
+ int64_t last_checked_time_;
+ double progress_;
+ string update_status_;
+ string new_version_;
+ int64_t payload_size_;
+};
+
+// A variable reporting the time when a last update check was issued.
+class LastCheckedTimeVariable : public UpdaterVariableBase<Time> {
+ public:
+ LastCheckedTimeVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<Time>(name, kVariableModePoll, system_state) {}
+
+ private:
+ const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ GetStatusHelper raw(system_state(), errmsg);
+ if (!raw.is_success())
+ return nullptr;
+
+ return new Time(Time::FromTimeT(raw.last_checked_time()));
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(LastCheckedTimeVariable);
+};
+
+// A variable reporting the update (download) progress as a decimal fraction
+// between 0.0 and 1.0.
+class ProgressVariable : public UpdaterVariableBase<double> {
+ public:
+ ProgressVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<double>(name, kVariableModePoll, system_state) {}
+
+ private:
+ const double* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ GetStatusHelper raw(system_state(), errmsg);
+ if (!raw.is_success())
+ return nullptr;
+
+ if (raw.progress() < 0.0 || raw.progress() > 1.0) {
+ if (errmsg) {
+ *errmsg = StringPrintf("Invalid progress value received: %f",
+ raw.progress());
+ }
+ return nullptr;
+ }
+
+ return new double(raw.progress());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(ProgressVariable);
+};
+
+// A variable reporting the stage in which the update process is.
+class StageVariable : public UpdaterVariableBase<Stage> {
+ public:
+ StageVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<Stage>(name, kVariableModePoll, system_state) {}
+
+ private:
+ struct CurrOpStrToStage {
+ const char* str;
+ Stage stage;
+ };
+ static const CurrOpStrToStage curr_op_str_to_stage[];
+
+ // Note: the method is defined outside the class so arraysize can work.
+ const Stage* GetValue(TimeDelta /* timeout */, string* errmsg) override;
+
+ DISALLOW_COPY_AND_ASSIGN(StageVariable);
+};
+
+const StageVariable::CurrOpStrToStage StageVariable::curr_op_str_to_stage[] = {
+ {update_engine::kUpdateStatusIdle, Stage::kIdle},
+ {update_engine::kUpdateStatusCheckingForUpdate, Stage::kCheckingForUpdate},
+ {update_engine::kUpdateStatusUpdateAvailable, Stage::kUpdateAvailable},
+ {update_engine::kUpdateStatusDownloading, Stage::kDownloading},
+ {update_engine::kUpdateStatusVerifying, Stage::kVerifying},
+ {update_engine::kUpdateStatusFinalizing, Stage::kFinalizing},
+ {update_engine::kUpdateStatusUpdatedNeedReboot, Stage::kUpdatedNeedReboot},
+ { // NOLINT(whitespace/braces)
+ update_engine::kUpdateStatusReportingErrorEvent,
+ Stage::kReportingErrorEvent
+ },
+ {update_engine::kUpdateStatusAttemptingRollback, Stage::kAttemptingRollback},
+};
+
+const Stage* StageVariable::GetValue(TimeDelta /* timeout */,
+ string* errmsg) {
+ GetStatusHelper raw(system_state(), errmsg);
+ if (!raw.is_success())
+ return nullptr;
+
+ for (auto& key_val : curr_op_str_to_stage)
+ if (raw.update_status() == key_val.str)
+ return new Stage(key_val.stage);
+
+ if (errmsg)
+ *errmsg = string("Unknown update status: ") + raw.update_status();
+ return nullptr;
+}
+
+// A variable reporting the version number that an update is updating to.
+class NewVersionVariable : public UpdaterVariableBase<string> {
+ public:
+ NewVersionVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}
+
+ private:
+ const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ GetStatusHelper raw(system_state(), errmsg);
+ if (!raw.is_success())
+ return nullptr;
+
+ return new string(raw.new_version());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(NewVersionVariable);
+};
+
+// A variable reporting the size of the update being processed in bytes.
+class PayloadSizeVariable : public UpdaterVariableBase<int64_t> {
+ public:
+ PayloadSizeVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<int64_t>(name, kVariableModePoll, system_state) {}
+
+ private:
+ const int64_t* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ GetStatusHelper raw(system_state(), errmsg);
+ if (!raw.is_success())
+ return nullptr;
+
+ if (raw.payload_size() < 0) {
+ if (errmsg)
+ *errmsg = string("Invalid payload size: %" PRId64, raw.payload_size());
+ return nullptr;
+ }
+
+ return new int64_t(raw.payload_size());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(PayloadSizeVariable);
+};
+
+// A variable reporting the point in time an update last completed in the
+// current boot cycle.
+//
+// TODO(garnold) In general, both the current boottime and wallclock time
+// readings should come from the time provider and be moderated by the
+// evaluation context, so that they are uniform throughout the evaluation of a
+// policy request.
+class UpdateCompletedTimeVariable : public UpdaterVariableBase<Time> {
+ public:
+ UpdateCompletedTimeVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<Time>(name, kVariableModePoll, system_state) {}
+
+ private:
+ const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ Time update_boottime;
+ if (!system_state()->update_attempter()->GetBootTimeAtUpdate(
+ &update_boottime)) {
+ if (errmsg)
+ *errmsg = "Update completed time could not be read";
+ return nullptr;
+ }
+
+ chromeos_update_engine::ClockInterface* clock = system_state()->clock();
+ Time curr_boottime = clock->GetBootTime();
+ if (curr_boottime < update_boottime) {
+ if (errmsg)
+ *errmsg = "Update completed time more recent than current time";
+ return nullptr;
+ }
+ TimeDelta duration_since_update = curr_boottime - update_boottime;
+ return new Time(clock->GetWallclockTime() - duration_since_update);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateCompletedTimeVariable);
+};
+
+// Variables reporting the current image channel.
+class CurrChannelVariable : public UpdaterVariableBase<string> {
+ public:
+ CurrChannelVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}
+
+ private:
+ const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ OmahaRequestParams* request_params = system_state()->request_params();
+ string channel = request_params->current_channel();
+ if (channel.empty()) {
+ if (errmsg)
+ *errmsg = "No current channel";
+ return nullptr;
+ }
+ return new string(channel);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(CurrChannelVariable);
+};
+
+// Variables reporting the new image channel.
+class NewChannelVariable : public UpdaterVariableBase<string> {
+ public:
+ NewChannelVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}
+
+ private:
+ const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ OmahaRequestParams* request_params = system_state()->request_params();
+ string channel = request_params->target_channel();
+ if (channel.empty()) {
+ if (errmsg)
+ *errmsg = "No new channel";
+ return nullptr;
+ }
+ return new string(channel);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(NewChannelVariable);
+};
+
+// A variable class for reading Boolean prefs values.
+class BooleanPrefVariable : public UpdaterVariableBase<bool> {
+ public:
+ BooleanPrefVariable(const string& name, SystemState* system_state,
+ const char* key, bool default_val)
+ : UpdaterVariableBase<bool>(name, kVariableModePoll, system_state),
+ key_(key), default_val_(default_val) {}
+
+ private:
+ const bool* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ bool result = default_val_;
+ chromeos_update_engine::PrefsInterface* prefs = system_state()->prefs();
+ if (prefs && prefs->Exists(key_) && !prefs->GetBoolean(key_, &result)) {
+ if (errmsg)
+ *errmsg = string("Could not read boolean pref ") + key_;
+ return nullptr;
+ }
+ return new bool(result);
+ }
+
+ // The Boolean preference key and default value.
+ const char* const key_;
+ const bool default_val_;
+
+ DISALLOW_COPY_AND_ASSIGN(BooleanPrefVariable);
+};
+
+// A variable returning the number of consecutive failed update checks.
+class ConsecutiveFailedUpdateChecksVariable
+ : public UpdaterVariableBase<unsigned int> {
+ public:
+ ConsecutiveFailedUpdateChecksVariable(const string& name,
+ SystemState* system_state)
+ : UpdaterVariableBase<unsigned int>(name, kVariableModePoll,
+ system_state) {}
+
+ private:
+ const unsigned int* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) override {
+ return new unsigned int(
+ system_state()->update_attempter()->consecutive_failed_update_checks());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(ConsecutiveFailedUpdateChecksVariable);
+};
+
+// A variable returning the server-dictated poll interval.
+class ServerDictatedPollIntervalVariable
+ : public UpdaterVariableBase<unsigned int> {
+ public:
+ ServerDictatedPollIntervalVariable(const string& name,
+ SystemState* system_state)
+ : UpdaterVariableBase<unsigned int>(name, kVariableModePoll,
+ system_state) {}
+
+ private:
+ const unsigned int* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) override {
+ return new unsigned int(
+ system_state()->update_attempter()->server_dictated_poll_interval());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(ServerDictatedPollIntervalVariable);
+};
+
+// An async variable that tracks changes to forced update requests.
+class ForcedUpdateRequestedVariable
+ : public UpdaterVariableBase<UpdateRequestStatus> {
+ public:
+ ForcedUpdateRequestedVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<UpdateRequestStatus>::UpdaterVariableBase(
+ name, kVariableModeAsync, system_state) {
+ system_state->update_attempter()->set_forced_update_pending_callback(
+ new base::Callback<void(bool, bool)>( // NOLINT(readability/function)
+ base::Bind(&ForcedUpdateRequestedVariable::Reset,
+ base::Unretained(this))));
+ }
+
+ private:
+ const UpdateRequestStatus* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) override {
+ return new UpdateRequestStatus(update_request_status_);
+ }
+
+ void Reset(bool forced_update_requested, bool is_interactive) {
+ UpdateRequestStatus new_value = UpdateRequestStatus::kNone;
+ if (forced_update_requested)
+ new_value = (is_interactive ? UpdateRequestStatus::kInteractive :
+ UpdateRequestStatus::kPeriodic);
+ if (update_request_status_ != new_value) {
+ update_request_status_ = new_value;
+ NotifyValueChanged();
+ }
+ }
+
+ UpdateRequestStatus update_request_status_ = UpdateRequestStatus::kNone;
+
+ DISALLOW_COPY_AND_ASSIGN(ForcedUpdateRequestedVariable);
+};
+
+// RealUpdaterProvider methods.
+
+RealUpdaterProvider::RealUpdaterProvider(SystemState* system_state)
+ : system_state_(system_state),
+ var_updater_started_time_("updater_started_time",
+ system_state->clock()->GetWallclockTime()),
+ var_last_checked_time_(
+ new LastCheckedTimeVariable("last_checked_time", system_state_)),
+ var_update_completed_time_(
+ new UpdateCompletedTimeVariable("update_completed_time",
+ system_state_)),
+ var_progress_(new ProgressVariable("progress", system_state_)),
+ var_stage_(new StageVariable("stage", system_state_)),
+ var_new_version_(new NewVersionVariable("new_version", system_state_)),
+ var_payload_size_(new PayloadSizeVariable("payload_size", system_state_)),
+ var_curr_channel_(new CurrChannelVariable("curr_channel", system_state_)),
+ var_new_channel_(new NewChannelVariable("new_channel", system_state_)),
+ var_p2p_enabled_(
+ new BooleanPrefVariable("p2p_enabled", system_state_,
+ chromeos_update_engine::kPrefsP2PEnabled,
+ false)),
+ var_cellular_enabled_(
+ new BooleanPrefVariable(
+ "cellular_enabled", system_state_,
+ chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+ false)),
+ var_consecutive_failed_update_checks_(
+ new ConsecutiveFailedUpdateChecksVariable(
+ "consecutive_failed_update_checks", system_state_)),
+ var_server_dictated_poll_interval_(
+ new ServerDictatedPollIntervalVariable(
+ "server_dictated_poll_interval", system_state_)),
+ var_forced_update_requested_(
+ new ForcedUpdateRequestedVariable(
+ "forced_update_requested", system_state_)) {}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_updater_provider.h b/update_manager/real_updater_provider.h
new file mode 100644
index 0000000..7317417
--- /dev/null
+++ b/update_manager/real_updater_provider.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+namespace chromeos_update_manager {
+
+// A concrete UpdaterProvider implementation using local (in-process) bindings.
+class RealUpdaterProvider : public UpdaterProvider {
+ public:
+ // We assume that any other object handle we get from the system state is
+ // "volatile", and so must be re-acquired whenever access is needed; this
+ // guarantees that parts of the system state can be mocked out at any time
+ // during testing. We further assume that, by the time Init() is called, the
+ // system state object is fully populated and usable.
+ explicit RealUpdaterProvider(
+ chromeos_update_engine::SystemState* system_state);
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init() { return true; }
+
+ Variable<base::Time>* var_updater_started_time() override {
+ return &var_updater_started_time_;
+ }
+
+ Variable<base::Time>* var_last_checked_time() override {
+ return var_last_checked_time_.get();
+ }
+
+ Variable<base::Time>* var_update_completed_time() override {
+ return var_update_completed_time_.get();
+ }
+
+ Variable<double>* var_progress() override {
+ return var_progress_.get();
+ }
+
+ Variable<Stage>* var_stage() override {
+ return var_stage_.get();
+ }
+
+ Variable<std::string>* var_new_version() override {
+ return var_new_version_.get();
+ }
+
+ Variable<int64_t>* var_payload_size() override {
+ return var_payload_size_.get();
+ }
+
+ Variable<std::string>* var_curr_channel() override {
+ return var_curr_channel_.get();
+ }
+
+ Variable<std::string>* var_new_channel() override {
+ return var_new_channel_.get();
+ }
+
+ Variable<bool>* var_p2p_enabled() override {
+ return var_p2p_enabled_.get();
+ }
+
+ Variable<bool>* var_cellular_enabled() override {
+ return var_cellular_enabled_.get();
+ }
+
+ Variable<unsigned int>* var_consecutive_failed_update_checks() override {
+ return var_consecutive_failed_update_checks_.get();
+ }
+
+ Variable<unsigned int>* var_server_dictated_poll_interval() override {
+ return var_server_dictated_poll_interval_.get();
+ }
+
+ Variable<UpdateRequestStatus>* var_forced_update_requested() override {
+ return var_forced_update_requested_.get();
+ }
+
+ private:
+ // A pointer to the update engine's system state aggregator.
+ chromeos_update_engine::SystemState* system_state_;
+
+ // Variable implementations.
+ ConstCopyVariable<base::Time> var_updater_started_time_;
+ std::unique_ptr<Variable<base::Time>> var_last_checked_time_;
+ std::unique_ptr<Variable<base::Time>> var_update_completed_time_;
+ std::unique_ptr<Variable<double>> var_progress_;
+ std::unique_ptr<Variable<Stage>> var_stage_;
+ std::unique_ptr<Variable<std::string>> var_new_version_;
+ std::unique_ptr<Variable<int64_t>> var_payload_size_;
+ std::unique_ptr<Variable<std::string>> var_curr_channel_;
+ std::unique_ptr<Variable<std::string>> var_new_channel_;
+ std::unique_ptr<Variable<bool>> var_p2p_enabled_;
+ std::unique_ptr<Variable<bool>> var_cellular_enabled_;
+ std::unique_ptr<Variable<unsigned int>> var_consecutive_failed_update_checks_;
+ std::unique_ptr<Variable<unsigned int>> var_server_dictated_poll_interval_;
+ std::unique_ptr<Variable<UpdateRequestStatus>> var_forced_update_requested_;
+
+ DISALLOW_COPY_AND_ASSIGN(RealUpdaterProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
diff --git a/update_manager/real_updater_provider_unittest.cc b/update_manager/real_updater_provider_unittest.cc
new file mode 100644
index 0000000..f1511dd
--- /dev/null
+++ b/update_manager/real_updater_provider_unittest.cc
@@ -0,0 +1,466 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/real_updater_provider.h"
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+#include <chromeos/dbus/service_constants.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/mock_update_attempter.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::FakeClock;
+using chromeos_update_engine::FakeSystemState;
+using chromeos_update_engine::MockPrefs;
+using chromeos_update_engine::OmahaRequestParams;
+using std::string;
+using std::unique_ptr;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::StrEq;
+using testing::_;
+
+namespace {
+
+// Generates a fixed timestamp for use in faking the current time.
+Time FixedTime() {
+ Time::Exploded now_exp;
+ now_exp.year = 2014;
+ now_exp.month = 3;
+ now_exp.day_of_week = 2;
+ now_exp.day_of_month = 18;
+ now_exp.hour = 8;
+ now_exp.minute = 5;
+ now_exp.second = 33;
+ now_exp.millisecond = 675;
+ return Time::FromLocalExploded(now_exp);
+}
+
+// Rounds down a timestamp to the nearest second. This is useful when faking
+// times that are converted to time_t (no sub-second resolution).
+Time RoundedToSecond(Time time) {
+ Time::Exploded exp;
+ time.LocalExplode(&exp);
+ exp.millisecond = 0;
+ return Time::FromLocalExploded(exp);
+}
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+class UmRealUpdaterProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ fake_clock_ = fake_sys_state_.fake_clock();
+ provider_.reset(new RealUpdaterProvider(&fake_sys_state_));
+ ASSERT_NE(nullptr, provider_.get());
+ // Check that provider initializes correctly.
+ ASSERT_TRUE(provider_->Init());
+ }
+
+ // Sets up mock expectations for testing a variable that reads a Boolean pref
+ // |key|. |key_exists| determines whether the key is present. If it is, then
+ // |get_boolean_success| determines whether reading it is successful, and if
+ // so |output| is the value being read.
+ void SetupReadBooleanPref(const char* key, bool key_exists,
+ bool get_boolean_success, bool output) {
+ MockPrefs* const mock_prefs = fake_sys_state_.mock_prefs();
+ EXPECT_CALL(*mock_prefs, Exists(StrEq(key))).WillOnce(Return(key_exists));
+ if (key_exists) {
+ auto& get_boolean = EXPECT_CALL(
+ *fake_sys_state_.mock_prefs(), GetBoolean(StrEq(key), _));
+ if (get_boolean_success)
+ get_boolean.WillOnce(DoAll(SetArgPointee<1>(output), Return(true)));
+ else
+ get_boolean.WillOnce(Return(false));
+ }
+ }
+
+ // Sets up mock expectations for testing the update completed time reporting.
+ // |valid| determines whether the returned time is valid. Returns the expected
+ // update completed time value.
+ Time SetupUpdateCompletedTime(bool valid) {
+ const TimeDelta kDurationSinceUpdate = TimeDelta::FromMinutes(7);
+ const Time kUpdateBootTime = Time() + kDurationSinceUpdate * 2;
+ const Time kCurrBootTime = (valid ?
+ kUpdateBootTime + kDurationSinceUpdate :
+ kUpdateBootTime - kDurationSinceUpdate);
+ const Time kCurrWallclockTime = FixedTime();
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetBootTimeAtUpdate(_))
+ .WillOnce(DoAll(SetArgPointee<0>(kUpdateBootTime), Return(true)));
+ fake_clock_->SetBootTime(kCurrBootTime);
+ fake_clock_->SetWallclockTime(kCurrWallclockTime);
+ return kCurrWallclockTime - kDurationSinceUpdate;
+ }
+
+ FakeSystemState fake_sys_state_;
+ FakeClock* fake_clock_; // Short for fake_sys_state_.fake_clock()
+ unique_ptr<RealUpdaterProvider> provider_;
+};
+
+TEST_F(UmRealUpdaterProviderTest, UpdaterStartedTimeIsWallclockTime) {
+ fake_clock_->SetWallclockTime(Time::FromDoubleT(123.456));
+ fake_clock_->SetMonotonicTime(Time::FromDoubleT(456.123));
+ // Run SetUp again to re-setup the provider under test to use these values.
+ SetUp();
+ UmTestUtils::ExpectVariableHasValue(Time::FromDoubleT(123.456),
+ provider_->var_updater_started_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeOkay) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(FixedTime().ToTimeT()), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(RoundedToSecond(FixedTime()),
+ provider_->var_last_checked_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(Return(false));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_last_checked_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMin) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(0.0), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(0.0, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMid) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(0.3), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(0.3, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMax) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(1.0), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(1.0, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(Return(false));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressFailTooSmall) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(-2.0), Return(true)));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressFailTooBig) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(2.0), Return(true)));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayIdle) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusIdle),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kIdle, provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayCheckingForUpdate) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<2>(update_engine::kUpdateStatusCheckingForUpdate),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kCheckingForUpdate,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdateAvailable) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<2>(update_engine::kUpdateStatusUpdateAvailable),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kUpdateAvailable,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayDownloading) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusDownloading),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kDownloading,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayVerifying) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusVerifying),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kVerifying,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayFinalizing) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusFinalizing),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kFinalizing,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdatedNeedReboot) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<2>(update_engine::kUpdateStatusUpdatedNeedReboot),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kUpdatedNeedReboot,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayReportingErrorEvent) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<2>(update_engine::kUpdateStatusReportingErrorEvent),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kReportingErrorEvent,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayAttemptingRollback) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<2>(update_engine::kUpdateStatusAttemptingRollback),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kAttemptingRollback,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(Return(false));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailUnknown) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>("FooUpdateEngineState"),
+ Return(true)));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailEmpty) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(""), Return(true)));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewVersionOkay) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<3>("1.2.0"), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(string("1.2.0"),
+ provider_->var_new_version());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewVersionFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(Return(false));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_new_version());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayZero) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(0)), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(0),
+ provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayArbitrary) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(567890)),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(567890),
+ provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayTwoGigabytes) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(1) << 31),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(1) << 31,
+ provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(Return(false));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeFailNegative) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(-1024)),
+ Return(true)));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCurrChannelOkay) {
+ const string kChannelName("foo-channel");
+ OmahaRequestParams request_params(&fake_sys_state_);
+ request_params.Init("", "", false);
+ request_params.set_current_channel(kChannelName);
+ fake_sys_state_.set_request_params(&request_params);
+ UmTestUtils::ExpectVariableHasValue(kChannelName,
+ provider_->var_curr_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCurrChannelFailEmpty) {
+ OmahaRequestParams request_params(&fake_sys_state_);
+ request_params.Init("", "", false);
+ request_params.set_current_channel("");
+ fake_sys_state_.set_request_params(&request_params);
+ UmTestUtils::ExpectVariableNotSet(provider_->var_curr_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewChannelOkay) {
+ const string kChannelName("foo-channel");
+ OmahaRequestParams request_params(&fake_sys_state_);
+ request_params.Init("", "", false);
+ request_params.set_target_channel(kChannelName);
+ fake_sys_state_.set_request_params(&request_params);
+ UmTestUtils::ExpectVariableHasValue(kChannelName,
+ provider_->var_new_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewChannelFailEmpty) {
+ OmahaRequestParams request_params(&fake_sys_state_);
+ request_params.Init("", "", false);
+ request_params.set_target_channel("");
+ fake_sys_state_.set_request_params(&request_params);
+ UmTestUtils::ExpectVariableNotSet(provider_->var_new_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefDoesntExist) {
+ SetupReadBooleanPref(chromeos_update_engine::kPrefsP2PEnabled,
+ false, false, false);
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefReadsFalse) {
+ SetupReadBooleanPref(chromeos_update_engine::kPrefsP2PEnabled,
+ true, true, false);
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefReadsTrue) {
+ SetupReadBooleanPref(chromeos_update_engine::kPrefsP2PEnabled,
+ true, true, true);
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledFailCannotReadPref) {
+ SetupReadBooleanPref(chromeos_update_engine::kPrefsP2PEnabled,
+ true, false, false);
+ UmTestUtils::ExpectVariableNotSet(provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefDoesntExist) {
+ SetupReadBooleanPref(
+ chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+ false, false, false);
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefReadsFalse) {
+ SetupReadBooleanPref(
+ chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+ true, true, false);
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefReadsTrue) {
+ SetupReadBooleanPref(
+ chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+ true, true, true);
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledFailCannotReadPref) {
+ SetupReadBooleanPref(
+ chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+ true, false, false);
+ UmTestUtils::ExpectVariableNotSet(provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeOkay) {
+ Time expected = SetupUpdateCompletedTime(true);
+ UmTestUtils::ExpectVariableHasValue(expected,
+ provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetBootTimeAtUpdate(_))
+ .WillOnce(Return(false));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeFailInvalidValue) {
+ SetupUpdateCompletedTime(false);
+ UmTestUtils::ExpectVariableNotSet(provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetConsecutiveFailedUpdateChecks) {
+ const unsigned int kNumFailedChecks = 3;
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ consecutive_failed_update_checks())
+ .WillRepeatedly(Return(kNumFailedChecks));
+ UmTestUtils::ExpectVariableHasValue(
+ kNumFailedChecks, provider_->var_consecutive_failed_update_checks());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetServerDictatedPollInterval) {
+ const unsigned int kPollInterval = 2 * 60 * 60; // Two hours.
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ server_dictated_poll_interval())
+ .WillRepeatedly(Return(kPollInterval));
+ UmTestUtils::ExpectVariableHasValue(
+ kPollInterval, provider_->var_server_dictated_poll_interval());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/shill_provider.h b/update_manager/shill_provider.h
new file mode 100644
index 0000000..89d3b97
--- /dev/null
+++ b/update_manager/shill_provider.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_SHILL_PROVIDER_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+enum class ConnectionType {
+ kEthernet,
+ kWifi,
+ kWimax,
+ kBluetooth,
+ kCellular,
+ kUnknown
+};
+
+enum class ConnectionTethering {
+ kNotDetected,
+ kSuspected,
+ kConfirmed,
+ kUnknown,
+};
+
+// Provider for networking related information.
+class ShillProvider : public Provider {
+ public:
+ ~ShillProvider() override {}
+
+ // A variable returning whether we currently have network connectivity.
+ virtual Variable<bool>* var_is_connected() = 0;
+
+ // A variable returning the current network connection type. Unknown if not
+ // connected.
+ virtual Variable<ConnectionType>* var_conn_type() = 0;
+
+ // A variable returning the tethering mode of a network connection. Unknown if
+ // not connected.
+ virtual Variable<ConnectionTethering>* var_conn_tethering() = 0;
+
+ // A variable returning the time when network connection last changed.
+ // Initialized to current time.
+ virtual Variable<base::Time>* var_conn_last_changed() = 0;
+
+ protected:
+ ShillProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShillProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_SHILL_PROVIDER_H_
diff --git a/update_manager/state.h b/update_manager/state.h
new file mode 100644
index 0000000..fdbb38b
--- /dev/null
+++ b/update_manager/state.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_STATE_H_
+
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/random_provider.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/system_provider.h"
+#include "update_engine/update_manager/time_provider.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+namespace chromeos_update_manager {
+
+// The State class is an interface to the ensemble of providers. This class
+// gives visibility of the state providers to policy implementations.
+class State {
+ public:
+ virtual ~State() {}
+
+ // These methods return the given provider.
+ virtual ConfigProvider* config_provider() = 0;
+ virtual DevicePolicyProvider* device_policy_provider() = 0;
+ virtual RandomProvider* random_provider() = 0;
+ virtual ShillProvider* shill_provider() = 0;
+ virtual SystemProvider* system_provider() = 0;
+ virtual TimeProvider* time_provider() = 0;
+ virtual UpdaterProvider* updater_provider() = 0;
+
+ protected:
+ State() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(State);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_STATE_H_
diff --git a/update_manager/state_factory.cc b/update_manager/state_factory.cc
new file mode 100644
index 0000000..54c0205
--- /dev/null
+++ b/update_manager/state_factory.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/state_factory.h"
+
+#include <memory>
+
+#include <base/logging.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/update_manager/real_config_provider.h"
+#include "update_engine/update_manager/real_device_policy_provider.h"
+#include "update_engine/update_manager/real_random_provider.h"
+#include "update_engine/update_manager/real_shill_provider.h"
+#include "update_engine/update_manager/real_state.h"
+#include "update_engine/update_manager/real_system_provider.h"
+#include "update_engine/update_manager/real_time_provider.h"
+#include "update_engine/update_manager/real_updater_provider.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+State* DefaultStateFactory(policy::PolicyProvider* policy_provider,
+ chromeos_update_engine::DBusWrapperInterface* dbus,
+ chromeos_update_engine::SystemState* system_state) {
+ chromeos_update_engine::ClockInterface* const clock = system_state->clock();
+ unique_ptr<RealConfigProvider> config_provider(
+ new RealConfigProvider(system_state->hardware()));
+ unique_ptr<RealDevicePolicyProvider> device_policy_provider(
+ new RealDevicePolicyProvider(dbus, policy_provider));
+ unique_ptr<RealRandomProvider> random_provider(new RealRandomProvider());
+ unique_ptr<RealShillProvider> shill_provider(
+ new RealShillProvider(dbus, clock));
+ unique_ptr<RealSystemProvider> system_provider(
+ new RealSystemProvider(system_state->hardware()));
+ unique_ptr<RealTimeProvider> time_provider(new RealTimeProvider(clock));
+ unique_ptr<RealUpdaterProvider> updater_provider(
+ new RealUpdaterProvider(system_state));
+
+ if (!(config_provider->Init() &&
+ device_policy_provider->Init() &&
+ random_provider->Init() &&
+ shill_provider->Init() &&
+ system_provider->Init() &&
+ time_provider->Init() &&
+ updater_provider->Init())) {
+ LOG(ERROR) << "Error initializing providers";
+ return nullptr;
+ }
+
+ return new RealState(config_provider.release(),
+ device_policy_provider.release(),
+ random_provider.release(),
+ shill_provider.release(),
+ system_provider.release(),
+ time_provider.release(),
+ updater_provider.release());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/state_factory.h b/update_manager/state_factory.h
new file mode 100644
index 0000000..fa1164c
--- /dev/null
+++ b/update_manager/state_factory.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_
+
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// Creates and initializes a new UpdateManager State instance containing real
+// providers instantiated using the passed interfaces. The State doesn't take
+// ownership of the passed interfaces, which need to remain available during the
+// life of this instance. Returns null if one of the underlying providers fails
+// to initialize.
+State* DefaultStateFactory(
+ policy::PolicyProvider* policy_provider,
+ chromeos_update_engine::DBusWrapperInterface* dbus,
+ chromeos_update_engine::SystemState* system_state);
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_
diff --git a/update_manager/system_provider.h b/update_manager/system_provider.h
new file mode 100644
index 0000000..ea5f79f
--- /dev/null
+++ b/update_manager/system_provider.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for system information, mostly constant, such as the information
+// reported by crossystem, the kernel boot command line and the partition table.
+class SystemProvider : public Provider {
+ public:
+ ~SystemProvider() override {}
+
+ // Returns true if the boot mode is normal or if it's unable to
+ // determine the boot mode. Returns false if the boot mode is
+ // developer.
+ virtual Variable<bool>* var_is_normal_boot_mode() = 0;
+
+ // Returns whether this is an official Chrome OS build.
+ virtual Variable<bool>* var_is_official_build() = 0;
+
+ // Returns a variable that tells whether OOBE was completed.
+ virtual Variable<bool>* var_is_oobe_complete() = 0;
+
+ // Returns a variable that tells the boot device is removable (USB stick etc).
+ virtual Variable<bool>* var_is_boot_device_removable() = 0;
+
+ protected:
+ SystemProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SystemProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
diff --git a/update_manager/time_provider.h b/update_manager/time_provider.h
new file mode 100644
index 0000000..a30fdb3
--- /dev/null
+++ b/update_manager/time_provider.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_TIME_PROVIDER_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for time related information.
+class TimeProvider : public Provider {
+ public:
+ ~TimeProvider() override {}
+
+ // Returns the current date. The time of day component will be zero.
+ virtual Variable<base::Time>* var_curr_date() = 0;
+
+ // Returns the current hour (0 to 23) in local time. The type is int to keep
+ // consistent with base::Time.
+ virtual Variable<int>* var_curr_hour() = 0;
+
+ protected:
+ TimeProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TimeProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_TIME_PROVIDER_H_
diff --git a/update_manager/umtest_utils.cc b/update_manager/umtest_utils.cc
new file mode 100644
index 0000000..be6a86f
--- /dev/null
+++ b/update_manager/umtest_utils.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/umtest_utils.h"
+
+#include <base/time/time.h>
+
+namespace chromeos_update_manager {
+
+const unsigned UmTestUtils::kDefaultTimeoutInSeconds = 1;
+
+void PrintTo(const EvalStatus& status, ::std::ostream* os) {
+ *os << ToString(status);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/umtest_utils.h b/update_manager/umtest_utils.h
new file mode 100644
index 0000000..c50a5a2
--- /dev/null
+++ b/update_manager/umtest_utils.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_UMTEST_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UMTEST_UTILS_H_
+
+#include <iostream> // NOLINT(readability/streams)
+#include <memory>
+
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// A help class with common functionality for use in Update Manager testing.
+class UmTestUtils {
+ public:
+ // A default timeout to use when making various queries.
+ static const base::TimeDelta DefaultTimeout() {
+ return base::TimeDelta::FromSeconds(kDefaultTimeoutInSeconds);
+ }
+
+ // Calls GetValue on |variable| and expects its result to be |expected|.
+ template<typename T>
+ static void ExpectVariableHasValue(const T& expected, Variable<T>* variable) {
+ ASSERT_NE(nullptr, variable);
+ std::unique_ptr<const T> value(
+ variable->GetValue(DefaultTimeout(), nullptr));
+ ASSERT_NE(nullptr, value.get()) << "Variable: " << variable->GetName();
+ EXPECT_EQ(expected, *value) << "Variable: " << variable->GetName();
+ }
+
+ // Calls GetValue on |variable| and expects its result to be null.
+ template<typename T>
+ static void ExpectVariableNotSet(Variable<T>* variable) {
+ ASSERT_NE(nullptr, variable);
+ std::unique_ptr<const T> value(
+ variable->GetValue(DefaultTimeout(), nullptr));
+ EXPECT_EQ(nullptr, value.get()) << "Variable: " << variable->GetName();
+ }
+
+ private:
+ static const unsigned kDefaultTimeoutInSeconds;
+};
+
+// PrintTo() functions are used by gtest to print these values. They need to be
+// defined on the same namespace where the type was defined.
+void PrintTo(const EvalStatus& status, ::std::ostream* os);
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_UMTEST_UTILS_H_
diff --git a/update_manager/update_manager-inl.h b/update_manager/update_manager-inl.h
new file mode 100644
index 0000000..b8fef28
--- /dev/null
+++ b/update_manager/update_manager-inl.h
@@ -0,0 +1,153 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
+
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/update_manager/evaluation_context.h"
+
+namespace chromeos_update_manager {
+
+template<typename R, typename... Args>
+EvalStatus UpdateManager::EvaluatePolicy(
+ EvaluationContext* ec,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ Args...) const,
+ R* result, Args... args) {
+ // If expiration timeout fired, dump the context and reset expiration.
+ // IMPORTANT: We must still proceed with evaluation of the policy in this
+ // case, so that the evaluation time (and corresponding reevaluation timeouts)
+ // are readjusted.
+ if (ec->is_expired()) {
+ LOG(WARNING) << "Request timed out, evaluation context: "
+ << ec->DumpContext();
+ ec->ResetExpiration();
+ }
+
+ // Reset the evaluation context.
+ ec->ResetEvaluation();
+
+ const std::string policy_name = policy_->PolicyRequestName(policy_method);
+ LOG(INFO) << policy_name << ": START";
+
+ // First try calling the actual policy.
+ std::string error;
+ EvalStatus status = (policy_.get()->*policy_method)(ec, state_.get(), &error,
+ result, args...);
+ // If evaluating the main policy failed, defer to the default policy.
+ if (status == EvalStatus::kFailed) {
+ LOG(WARNING) << "Evaluating policy failed: " << error
+ << "\nEvaluation context: " << ec->DumpContext();
+ error.clear();
+ status = (default_policy_.*policy_method)(ec, state_.get(), &error, result,
+ args...);
+ if (status == EvalStatus::kFailed) {
+ LOG(WARNING) << "Evaluating default policy failed: " << error;
+ } else if (status == EvalStatus::kAskMeAgainLater) {
+ LOG(ERROR)
+ << "Default policy would block; this is a bug, forcing failure.";
+ status = EvalStatus::kFailed;
+ }
+ }
+
+ LOG(INFO) << policy_name << ": END";
+
+ return status;
+}
+
+template<typename R, typename... Args>
+void UpdateManager::OnPolicyReadyToEvaluate(
+ scoped_refptr<EvaluationContext> ec,
+ base::Callback<void(EvalStatus status, const R& result)> callback,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ Args...) const,
+ Args... args) {
+ // Evaluate the policy.
+ R result;
+ EvalStatus status = EvaluatePolicy(ec.get(), policy_method, &result, args...);
+
+ if (status != EvalStatus::kAskMeAgainLater) {
+ // AsyncPolicyRequest finished.
+ callback.Run(status, result);
+ return;
+ }
+
+ // Re-schedule the policy request based on used variables.
+ base::Closure reeval_callback = base::Bind(
+ &UpdateManager::OnPolicyReadyToEvaluate<R, Args...>,
+ base::Unretained(this), ec, callback,
+ policy_method, args...);
+ if (ec->RunOnValueChangeOrTimeout(reeval_callback))
+ return; // Reevaluation scheduled successfully.
+
+ // Scheduling a reevaluation can fail because policy method didn't use any
+ // non-const variable nor there's any time-based event that will change the
+ // status of evaluation. Alternatively, this may indicate an error in the use
+ // of the scheduling interface.
+ LOG(ERROR) << "Failed to schedule a reevaluation of policy "
+ << policy_->PolicyRequestName(policy_method) << "; this is a bug.";
+ callback.Run(status, result);
+}
+
+template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+EvalStatus UpdateManager::PolicyRequest(
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ ExpectedArgs...) const,
+ R* result, ActualArgs... args) {
+ scoped_refptr<EvaluationContext> ec(
+ new EvaluationContext(clock_, evaluation_timeout_));
+ // A PolicyRequest always consists on a single evaluation on a new
+ // EvaluationContext.
+ // IMPORTANT: To ensure that ActualArgs can be converted to ExpectedArgs, we
+ // explicitly instantiate EvaluatePolicy with the latter in lieu of the
+ // former.
+ EvalStatus ret = EvaluatePolicy<R, ExpectedArgs...>(ec.get(), policy_method,
+ result, args...);
+ // Sync policy requests must not block, if they do then this is an error.
+ DCHECK(EvalStatus::kAskMeAgainLater != ret);
+ LOG_IF(WARNING, EvalStatus::kAskMeAgainLater == ret)
+ << "Sync request used with an async policy; this is a bug";
+ return ret;
+}
+
+template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+void UpdateManager::AsyncPolicyRequest(
+ base::Callback<void(EvalStatus, const R& result)> callback,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ ExpectedArgs...) const,
+ ActualArgs... args) {
+ scoped_refptr<EvaluationContext> ec =
+ new EvaluationContext(
+ clock_, evaluation_timeout_, expiration_timeout_,
+ std::unique_ptr<base::Callback<void(EvaluationContext*)>>(
+ new base::Callback<void(EvaluationContext*)>(
+ base::Bind(&UpdateManager::UnregisterEvalContext,
+ weak_ptr_factory_.GetWeakPtr()))));
+ if (!ec_repo_.insert(ec.get()).second) {
+ LOG(ERROR) << "Failed to register evaluation context; this is a bug.";
+ }
+
+ // IMPORTANT: To ensure that ActualArgs can be converted to ExpectedArgs, we
+ // explicitly instantiate UpdateManager::OnPolicyReadyToEvaluate with the
+ // latter in lieu of the former.
+ base::Closure eval_callback = base::Bind(
+ &UpdateManager::OnPolicyReadyToEvaluate<R, ExpectedArgs...>,
+ base::Unretained(this), ec, callback, policy_method, args...);
+ chromeos::MessageLoop::current()->PostTask(FROM_HERE, eval_callback);
+}
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
diff --git a/update_manager/update_manager.cc b/update_manager/update_manager.cc
new file mode 100644
index 0000000..13c208e
--- /dev/null
+++ b/update_manager/update_manager.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/update_manager.h"
+
+#include "update_engine/update_manager/chromeos_policy.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+UpdateManager::UpdateManager(chromeos_update_engine::ClockInterface* clock,
+ base::TimeDelta evaluation_timeout,
+ base::TimeDelta expiration_timeout, State* state)
+ : default_policy_(clock), state_(state), clock_(clock),
+ evaluation_timeout_(evaluation_timeout),
+ expiration_timeout_(expiration_timeout),
+ weak_ptr_factory_(this) {
+ // TODO(deymo): Make it possible to replace this policy with a different
+ // implementation with a build-time flag.
+ policy_.reset(new ChromeOSPolicy());
+}
+
+UpdateManager::~UpdateManager() {
+ // Remove pending main loop events associated with any of the outstanding
+ // evaluation contexts. This will prevent dangling pending events, causing
+ // these contexts to be destructed once the repo itself is destructed.
+ for (auto& ec : ec_repo_)
+ ec->RemoveObserversAndTimeout();
+}
+
+void UpdateManager::UnregisterEvalContext(EvaluationContext* ec) {
+ if (!ec_repo_.erase(ec)) {
+ LOG(ERROR) << "Unregistering an unknown evaluation context, this is a bug.";
+ }
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/update_manager.conf.example b/update_manager/update_manager.conf.example
new file mode 100644
index 0000000..2d77974
--- /dev/null
+++ b/update_manager/update_manager.conf.example
@@ -0,0 +1,18 @@
+# Configuration file for the update-manager component of update_engine.
+#
+# Normally this file is loaded from /etc/update_manager.conf. If
+# running update_engine in developer mode (and only if running in
+# developer mode), we attempt to load
+#
+# /mnt/stateful_partition/etc/update_manager.conf
+#
+# and use it if it exists. If it doesn't exist, we fall back to
+# /etc/update_manager.conf.
+#
+# Note: changes to this file are not automatically applied. Use the
+# command "restart update-engine" from a root shell to make your
+# changes take effect.
+
+# Set to true if the device supports the concept of OOBE
+# (Out-Of-the-Box-Experience), false if it doesn't.
+is_oobe_enabled=true
diff --git a/update_manager/update_manager.h b/update_manager/update_manager.h
new file mode 100644
index 0000000..cf7c6d5
--- /dev/null
+++ b/update_manager/update_manager.h
@@ -0,0 +1,166 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include <base/callback.h>
+#include <base/memory/ref_counted.h>
+#include <base/time/time.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// Comparator for scoped_refptr objects.
+template<typename T>
+struct ScopedRefPtrLess {
+ bool operator()(const scoped_refptr<T>& first,
+ const scoped_refptr<T>& second) const {
+ return first.get() < second.get();
+ }
+};
+
+// The main Update Manager singleton class.
+class UpdateManager {
+ public:
+ // Creates the UpdateManager instance, assuming ownership on the provided
+ // |state|.
+ UpdateManager(chromeos_update_engine::ClockInterface* clock,
+ base::TimeDelta evaluation_timeout,
+ base::TimeDelta expiration_timeout, State* state);
+
+ virtual ~UpdateManager();
+
+ // PolicyRequest() evaluates the given policy with the provided arguments and
+ // returns the result. The |policy_method| is the pointer-to-method of the
+ // Policy class for the policy request to call. The UpdateManager will call
+ // this method on the right policy. The pointer |result| must not be null
+ // and the remaining |args| depend on the arguments required by the passed
+ // |policy_method|.
+ //
+ // When the policy request succeeds, the |result| is set and the method
+ // returns EvalStatus::kSucceeded, otherwise, the |result| may not be set. A
+ // policy called with this method should not block (i.e. return
+ // EvalStatus::kAskMeAgainLater), which is considered a programming error. On
+ // failure, EvalStatus::kFailed is returned.
+ //
+ // An example call to this method is:
+ // um.PolicyRequest(&Policy::SomePolicyMethod, &bool_result, arg1, arg2);
+ template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+ EvalStatus PolicyRequest(
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ ExpectedArgs...) const,
+ R* result, ActualArgs...);
+
+ // Evaluates the given |policy_method| policy with the provided |args|
+ // arguments and calls the |callback| callback with the result when done.
+ //
+ // If the policy implementation should block, returning a
+ // EvalStatus::kAskMeAgainLater status the Update Manager will re-evaluate the
+ // policy until another status is returned. If the policy implementation based
+ // its return value solely on const variables, the callback will be called
+ // with the EvalStatus::kAskMeAgainLater status (which indicates an error).
+ template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+ void AsyncPolicyRequest(
+ base::Callback<void(EvalStatus, const R& result)> callback,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ ExpectedArgs...) const,
+ ActualArgs... args);
+
+ protected:
+ // The UpdateManager receives ownership of the passed Policy instance.
+ void set_policy(const Policy* policy) {
+ policy_.reset(policy);
+ }
+
+ // State getter used for testing.
+ State* state() { return state_.get(); }
+
+ private:
+ FRIEND_TEST(UmUpdateManagerTest, PolicyRequestCallsPolicy);
+ FRIEND_TEST(UmUpdateManagerTest, PolicyRequestCallsDefaultOnError);
+ FRIEND_TEST(UmUpdateManagerTest, PolicyRequestDoesntBlockDeathTest);
+ FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestDelaysEvaluation);
+ FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestTimeoutDoesNotFire);
+ FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestTimesOut);
+
+ // EvaluatePolicy() evaluates the passed |policy_method| method on the current
+ // policy with the given |args| arguments. If the method fails, the default
+ // policy is used instead.
+ template<typename R, typename... Args>
+ EvalStatus EvaluatePolicy(
+ EvaluationContext* ec,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ Args...) const,
+ R* result, Args... args);
+
+ // OnPolicyReadyToEvaluate() is called by the main loop when the evaluation
+ // of the given |policy_method| should be executed. If the evaluation finishes
+ // the |callback| callback is called passing the |result| and the |status|
+ // returned by the policy. If the evaluation returns an
+ // EvalStatus::kAskMeAgainLater state, the |callback| will NOT be called and
+ // the evaluation will be re-scheduled to be called later.
+ template<typename R, typename... Args>
+ void OnPolicyReadyToEvaluate(
+ scoped_refptr<EvaluationContext> ec,
+ base::Callback<void(EvalStatus status, const R& result)> callback,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ Args...) const,
+ Args... args);
+
+ // Unregisters (removes from repo) a previously created EvaluationContext.
+ void UnregisterEvalContext(EvaluationContext* ec);
+
+ // The policy used by the UpdateManager. Note that since it is a const Policy,
+ // policy implementations are not allowed to persist state on this class.
+ std::unique_ptr<const Policy> policy_;
+
+ // A safe default value to the current policy. This policy is used whenever
+ // a policy implementation fails with EvalStatus::kFailed.
+ const DefaultPolicy default_policy_;
+
+ // State Providers.
+ std::unique_ptr<State> state_;
+
+ // Pointer to the mockable clock interface;
+ chromeos_update_engine::ClockInterface* clock_;
+
+ // Timeout for a policy evaluation.
+ const base::TimeDelta evaluation_timeout_;
+
+ // Timeout for expiration of the evaluation context, used for async requests.
+ const base::TimeDelta expiration_timeout_;
+
+ // Repository of previously created EvaluationContext objects. These are being
+ // unregistered (and the reference released) when the context is being
+ // destructed; alternatively, when the UpdateManager instance is destroyed, it
+ // will remove all pending events associated with all outstanding contexts
+ // (which should, in turn, trigger their destruction).
+ std::set<scoped_refptr<EvaluationContext>,
+ ScopedRefPtrLess<EvaluationContext>> ec_repo_;
+
+ base::WeakPtrFactory<UpdateManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateManager);
+};
+
+} // namespace chromeos_update_manager
+
+// Include the implementation of the template methods.
+#include "update_engine/update_manager/update_manager-inl.h"
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_H_
diff --git a/update_manager/update_manager_unittest.cc b/update_manager/update_manager_unittest.cc
new file mode 100644
index 0000000..cf097fa
--- /dev/null
+++ b/update_manager/update_manager_unittest.cc
@@ -0,0 +1,313 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/update_manager.h"
+
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/test/simple_test_clock.h>
+#include <base/time/time.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/fake_state.h"
+#include "update_engine/update_manager/mock_policy.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Bind;
+using base::Callback;
+using base::Time;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos::MessageLoopRunMaxIterations;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::FakeClock;
+using std::pair;
+using std::string;
+using std::tuple;
+using std::unique_ptr;
+using std::vector;
+
+namespace {
+
+// Generates a fixed timestamp for use in faking the current time.
+Time FixedTime() {
+ Time::Exploded now_exp;
+ now_exp.year = 2014;
+ now_exp.month = 3;
+ now_exp.day_of_week = 2;
+ now_exp.day_of_month = 18;
+ now_exp.hour = 8;
+ now_exp.minute = 5;
+ now_exp.second = 33;
+ now_exp.millisecond = 675;
+ return Time::FromLocalExploded(now_exp);
+}
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+class UmUpdateManagerTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ fake_state_ = new FakeState();
+ umut_.reset(new UpdateManager(&fake_clock_, TimeDelta::FromSeconds(5),
+ TimeDelta::FromSeconds(1), fake_state_));
+ }
+
+ void TearDown() override {
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ base::SimpleTestClock test_clock_;
+ chromeos::FakeMessageLoop loop_{&test_clock_};
+ FakeState* fake_state_; // Owned by the umut_.
+ FakeClock fake_clock_;
+ unique_ptr<UpdateManager> umut_;
+};
+
+// The FailingPolicy implements a single method and make it always fail. This
+// class extends the DefaultPolicy class to allow extensions of the Policy
+// class without extending nor changing this test.
+class FailingPolicy : public DefaultPolicy {
+ public:
+ explicit FailingPolicy(int* num_called_p) : num_called_p_(num_called_p) {}
+ FailingPolicy() : FailingPolicy(nullptr) {}
+ EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+ string* error,
+ UpdateCheckParams* result) const override {
+ if (num_called_p_)
+ (*num_called_p_)++;
+ *error = "FailingPolicy failed.";
+ return EvalStatus::kFailed;
+ }
+
+ protected:
+ string PolicyName() const override { return "FailingPolicy"; }
+
+ private:
+ int* num_called_p_;
+};
+
+// The LazyPolicy always returns EvalStatus::kAskMeAgainLater.
+class LazyPolicy : public DefaultPolicy {
+ EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+ string* error,
+ UpdateCheckParams* result) const override {
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ protected:
+ string PolicyName() const override { return "LazyPolicy"; }
+};
+
+// A policy that sleeps for a predetermined amount of time, then checks for a
+// wallclock-based time threshold (if given) and returns
+// EvalStatus::kAskMeAgainLater if not passed; otherwise, returns
+// EvalStatus::kSucceeded. Increments a counter every time it is being queried,
+// if a pointer to it is provided.
+class DelayPolicy : public DefaultPolicy {
+ public:
+ DelayPolicy(int sleep_secs, Time time_threshold, int* num_called_p)
+ : sleep_secs_(sleep_secs), time_threshold_(time_threshold),
+ num_called_p_(num_called_p) {}
+ EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+ string* error,
+ UpdateCheckParams* result) const override {
+ if (num_called_p_)
+ (*num_called_p_)++;
+
+ // Sleep for a predetermined amount of time.
+ if (sleep_secs_ > 0)
+ sleep(sleep_secs_);
+
+ // Check for a time threshold. This can be used to ensure that the policy
+ // has some non-constant dependency.
+ if (time_threshold_ < Time::Max() &&
+ ec->IsWallclockTimeGreaterThan(time_threshold_))
+ return EvalStatus::kSucceeded;
+
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ protected:
+ string PolicyName() const override { return "DelayPolicy"; }
+
+ private:
+ int sleep_secs_;
+ Time time_threshold_;
+ int* num_called_p_;
+};
+
+// AccumulateCallsCallback() adds to the passed |acc| accumulator vector pairs
+// of EvalStatus and T instances. This allows to create a callback that keeps
+// track of when it is called and the arguments passed to it, to be used with
+// the UpdateManager::AsyncPolicyRequest().
+template<typename T>
+static void AccumulateCallsCallback(vector<pair<EvalStatus, T>>* acc,
+ EvalStatus status, const T& result) {
+ acc->push_back(std::make_pair(status, result));
+}
+
+// Tests that policy requests are completed successfully. It is important that
+// this tests cover all policy requests as defined in Policy.
+TEST_F(UmUpdateManagerTest, PolicyRequestCallUpdateCheckAllowed) {
+ UpdateCheckParams result;
+ EXPECT_EQ(EvalStatus::kSucceeded, umut_->PolicyRequest(
+ &Policy::UpdateCheckAllowed, &result));
+}
+
+TEST_F(UmUpdateManagerTest, PolicyRequestCallUpdateCanStart) {
+ UpdateState update_state = UpdateState();
+ update_state.is_interactive = true;
+ update_state.is_delta_payload = false;
+ update_state.first_seen = FixedTime();
+ update_state.num_checks = 1;
+ update_state.num_failures = 0;
+ update_state.failures_last_updated = Time();
+ update_state.download_urls = vector<string>{"http://fake/url/"};
+ update_state.download_errors_max = 10;
+ update_state.p2p_downloading_disabled = false;
+ update_state.p2p_sharing_disabled = false;
+ update_state.p2p_num_attempts = 0;
+ update_state.p2p_first_attempted = Time();
+ update_state.last_download_url_idx = -1;
+ update_state.last_download_url_num_errors = 0;
+ update_state.download_errors = vector<tuple<int, ErrorCode, Time>>();
+ update_state.backoff_expiry = Time();
+ update_state.is_backoff_disabled = false;
+ update_state.scatter_wait_period = TimeDelta::FromSeconds(15);
+ update_state.scatter_check_threshold = 4;
+ update_state.scatter_wait_period_max = TimeDelta::FromSeconds(60);
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 8;
+
+ UpdateDownloadParams result;
+ EXPECT_EQ(EvalStatus::kSucceeded,
+ umut_->PolicyRequest(&Policy::UpdateCanStart, &result,
+ update_state));
+}
+
+TEST_F(UmUpdateManagerTest, PolicyRequestCallsDefaultOnError) {
+ umut_->set_policy(new FailingPolicy());
+
+ // Tests that the DefaultPolicy instance is called when the method fails,
+ // which will set this as true.
+ UpdateCheckParams result;
+ result.updates_enabled = false;
+ EvalStatus status = umut_->PolicyRequest(
+ &Policy::UpdateCheckAllowed, &result);
+ EXPECT_EQ(EvalStatus::kSucceeded, status);
+ EXPECT_TRUE(result.updates_enabled);
+}
+
+// This test only applies to debug builds where DCHECK is enabled.
+#if DCHECK_IS_ON
+TEST_F(UmUpdateManagerTest, PolicyRequestDoesntBlockDeathTest) {
+ // The update manager should die (DCHECK) if a policy called synchronously
+ // returns a kAskMeAgainLater value.
+ UpdateCheckParams result;
+ umut_->set_policy(new LazyPolicy());
+ EXPECT_DEATH(umut_->PolicyRequest(&Policy::UpdateCheckAllowed, &result), "");
+}
+#endif // DCHECK_IS_ON
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestDelaysEvaluation) {
+ // To avoid differences in code execution order between an AsyncPolicyRequest
+ // call on a policy that returns AskMeAgainLater the first time and one that
+ // succeeds the first time, we ensure that the passed callback is called from
+ // the main loop in both cases even when we could evaluate it right now.
+ umut_->set_policy(new FailingPolicy());
+
+ vector<pair<EvalStatus, UpdateCheckParams>> calls;
+ Callback<void(EvalStatus, const UpdateCheckParams&)> callback = Bind(
+ AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+ umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+ // The callback should wait until we run the main loop for it to be executed.
+ EXPECT_EQ(0, calls.size());
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(1, calls.size());
+}
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimeoutDoesNotFire) {
+ // Set up an async policy call to return immediately, then wait a little and
+ // ensure that the timeout event does not fire.
+ int num_called = 0;
+ umut_->set_policy(new FailingPolicy(&num_called));
+
+ vector<pair<EvalStatus, UpdateCheckParams>> calls;
+ Callback<void(EvalStatus, const UpdateCheckParams&)> callback =
+ Bind(AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+ umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+ // Run the main loop, ensure that policy was attempted once before deferring
+ // to the default.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(1, num_called);
+ ASSERT_EQ(1, calls.size());
+ EXPECT_EQ(EvalStatus::kSucceeded, calls[0].first);
+ // Wait for the timeout to expire, run the main loop again, ensure that
+ // nothing happened.
+ test_clock_.Advance(TimeDelta::FromSeconds(2));
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ EXPECT_EQ(1, num_called);
+ EXPECT_EQ(1, calls.size());
+}
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimesOut) {
+ // Set up an async policy call to exceed its expiration timeout, make sure
+ // that the default policy was not used (no callback) and that evaluation is
+ // reattempted.
+ int num_called = 0;
+ umut_->set_policy(new DelayPolicy(
+ 0, fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(3),
+ &num_called));
+
+ vector<pair<EvalStatus, UpdateCheckParams>> calls;
+ Callback<void(EvalStatus, const UpdateCheckParams&)> callback =
+ Bind(AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+ umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+ // Run the main loop, ensure that policy was attempted once but the callback
+ // was not invoked.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(1, num_called);
+ EXPECT_EQ(0, calls.size());
+ // Wait for the expiration timeout to expire, run the main loop again,
+ // ensure that reevaluation occurred but callback was not invoked (i.e.
+ // default policy was not consulted).
+ test_clock_.Advance(TimeDelta::FromSeconds(2));
+ fake_clock_.SetWallclockTime(fake_clock_.GetWallclockTime() +
+ TimeDelta::FromSeconds(2));
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ EXPECT_EQ(2, num_called);
+ EXPECT_EQ(0, calls.size());
+ // Wait for reevaluation due to delay to happen, ensure that it occurs and
+ // that the callback is invoked.
+ test_clock_.Advance(TimeDelta::FromSeconds(2));
+ fake_clock_.SetWallclockTime(fake_clock_.GetWallclockTime() +
+ TimeDelta::FromSeconds(2));
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ EXPECT_EQ(3, num_called);
+ ASSERT_EQ(1, calls.size());
+ EXPECT_EQ(EvalStatus::kSucceeded, calls[0].first);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/updater_provider.h b/update_manager/updater_provider.h
new file mode 100644
index 0000000..1ff1085
--- /dev/null
+++ b/update_manager/updater_provider.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATER_PROVIDER_H_
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+enum class Stage {
+ kIdle,
+ kCheckingForUpdate,
+ kUpdateAvailable,
+ kDownloading,
+ kVerifying,
+ kFinalizing,
+ kUpdatedNeedReboot,
+ kReportingErrorEvent,
+ kAttemptingRollback,
+};
+
+enum class UpdateRequestStatus {
+ kNone,
+ kInteractive,
+ kPeriodic,
+};
+
+// Provider for Chrome OS update related information.
+class UpdaterProvider : public Provider {
+ public:
+ ~UpdaterProvider() override {}
+
+ // A variable returning the timestamp when the update engine was started in
+ // wallclock time.
+ virtual Variable<base::Time>* var_updater_started_time() = 0;
+
+ // A variable returning the last update check time.
+ virtual Variable<base::Time>* var_last_checked_time() = 0;
+
+ // A variable reporting the time when an update was last completed in the
+ // current boot cycle. Returns an error if an update completed time could not
+ // be read (e.g. no update was completed in the current boot cycle) or is
+ // invalid.
+ //
+ // IMPORTANT: The time reported is not the wallclock time reading at the time
+ // of the update, rather it is the point in time when the update completed
+ // relative to the current wallclock time reading. Therefore, the gap between
+ // the reported value and the current wallclock time is guaranteed to be
+ // monotonically increasing.
+ virtual Variable<base::Time>* var_update_completed_time() = 0;
+
+ // A variable returning the update progress (0.0 to 1.0).
+ virtual Variable<double>* var_progress() = 0;
+
+ // A variable returning the current update status.
+ virtual Variable<Stage>* var_stage() = 0;
+
+ // A variable returning the update target version.
+ virtual Variable<std::string>* var_new_version() = 0;
+
+ // A variable returning the update payload size. The payload size is
+ // guaranteed to be non-negative.
+ virtual Variable<int64_t>* var_payload_size() = 0;
+
+ // A variable returning the current channel.
+ virtual Variable<std::string>* var_curr_channel() = 0;
+
+ // A variable returning the update target channel.
+ virtual Variable<std::string>* var_new_channel() = 0;
+
+ // A variable indicating whether user settings allow P2P updates.
+ virtual Variable<bool>* var_p2p_enabled() = 0;
+
+ // A variable indicating whether user settings allow updates over a cellular
+ // network.
+ virtual Variable<bool>* var_cellular_enabled() = 0;
+
+ // A variable returning the number of consecutive failed update checks.
+ virtual Variable<unsigned int>* var_consecutive_failed_update_checks() = 0;
+
+ // A server-dictated update check interval in seconds, if one was given.
+ virtual Variable<unsigned int>* var_server_dictated_poll_interval() = 0;
+
+ // A variable denoting whether a forced update was request but no update check
+ // performed yet; also tells whether this request is for an interactive or
+ // scheduled update.
+ virtual Variable<UpdateRequestStatus>* var_forced_update_requested() = 0;
+
+ protected:
+ UpdaterProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UpdaterProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_UPDATER_PROVIDER_H_
diff --git a/update_manager/variable.h b/update_manager/variable.h
new file mode 100644
index 0000000..7ce2af8
--- /dev/null
+++ b/update_manager/variable.h
@@ -0,0 +1,205 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_VARIABLE_H_
+
+#include <algorithm>
+#include <list>
+#include <string>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+namespace chromeos_update_manager {
+
+// The VariableMode specifies important behavior of the variable in terms of
+// whether, how and when the value of the variable changes.
+enum VariableMode {
+ // Const variables never changes during the life of a policy request, so the
+ // EvaluationContext caches the value even between different evaluations of
+ // the same policy request.
+ kVariableModeConst,
+
+ // Poll variables, or synchronous variables, represent a variable with a value
+ // that can be queried at any time, but it is not known when the value
+ // changes on the source of information. In order to detect if the value of
+ // the variable changes, it has to be queried again.
+ kVariableModePoll,
+
+ // Async variables are able to produce a signal or callback whenever the
+ // value changes. This means that it's not required to poll the value to
+ // detect when it changes, instead, you should register an observer to get
+ // a notification when that happens.
+ kVariableModeAsync,
+};
+
+// This class is a base class with the common functionality that doesn't
+// depend on the variable's type, implemented by all the variables.
+class BaseVariable {
+ public:
+ // Interface for observing changes on variable value.
+ class ObserverInterface {
+ public:
+ virtual ~ObserverInterface() {}
+
+ // Called when the value on the variable changes.
+ virtual void ValueChanged(BaseVariable* variable) = 0;
+ };
+
+ virtual ~BaseVariable() {
+ if (!observer_list_.empty()) {
+ LOG(WARNING) << "Variable " << name_ << " deleted with "
+ << observer_list_.size() << " observers.";
+ }
+ DCHECK(observer_list_.empty()) << "Don't destroy the variable without "
+ "removing the observers.";
+ }
+
+ // Returns the variable name as a string.
+ const std::string& GetName() const {
+ return name_;
+ }
+
+ // Returns the variable mode.
+ VariableMode GetMode() const {
+ return mode_;
+ }
+
+ // For VariableModePoll variables, it returns the polling interval of this
+ // variable. In other case, it returns 0.
+ base::TimeDelta GetPollInterval() const {
+ return poll_interval_;
+ }
+
+ // Adds and removes observers for value changes on the variable. This only
+ // works for kVariableAsync variables since the other modes don't track value
+ // changes. Adding the same observer twice has no effect.
+ virtual void AddObserver(BaseVariable::ObserverInterface* observer) {
+ if (std::find(observer_list_.begin(), observer_list_.end(), observer) ==
+ observer_list_.end()) {
+ observer_list_.push_back(observer);
+ }
+ }
+
+ virtual void RemoveObserver(BaseVariable::ObserverInterface* observer) {
+ observer_list_.remove(observer);
+ }
+
+ protected:
+ // Creates a BaseVariable using the default polling interval (5 minutes).
+ BaseVariable(const std::string& name, VariableMode mode)
+ : BaseVariable(name, mode,
+ base::TimeDelta::FromMinutes(kDefaultPollMinutes)) {}
+
+ // Creates a BaseVariable with mode kVariableModePoll and the provided
+ // polling interval.
+ BaseVariable(const std::string& name, base::TimeDelta poll_interval)
+ : BaseVariable(name, kVariableModePoll, poll_interval) {}
+
+ // Calls ValueChanged on all the observers.
+ void NotifyValueChanged() {
+ // Fire all the observer methods from the main loop as single call. In order
+ // to avoid scheduling these callbacks when it is not needed, we check
+ // first the list of observers.
+ if (!observer_list_.empty()) {
+ chromeos::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BaseVariable::OnValueChangedNotification,
+ base::Unretained(this)));
+ }
+ }
+
+ private:
+ friend class UmEvaluationContextTest;
+ FRIEND_TEST(UmBaseVariableTest, RepeatedObserverTest);
+ FRIEND_TEST(UmBaseVariableTest, NotifyValueChangedTest);
+ FRIEND_TEST(UmBaseVariableTest, NotifyValueRemovesObserversTest);
+
+ BaseVariable(const std::string& name, VariableMode mode,
+ base::TimeDelta poll_interval)
+ : name_(name), mode_(mode),
+ poll_interval_(mode == kVariableModePoll ?
+ poll_interval : base::TimeDelta()) {}
+
+ void OnValueChangedNotification() {
+ // A ValueChanged() method can change the list of observers, for example
+ // removing itself and invalidating the iterator, so we create a snapshot
+ // of the observers first. Also, to support the case when *another* observer
+ // is removed, we check for them.
+ std::list<BaseVariable::ObserverInterface*> observer_list_copy(
+ observer_list_);
+
+ for (auto& observer : observer_list_copy) {
+ if (std::find(observer_list_.begin(), observer_list_.end(), observer) !=
+ observer_list_.end()) {
+ observer->ValueChanged(this);
+ }
+ }
+ }
+
+ // The default PollInterval in minutes.
+ static constexpr int kDefaultPollMinutes = 5;
+
+ // The variable's name as a string.
+ const std::string name_;
+
+ // The variable's mode.
+ const VariableMode mode_;
+
+ // The variable's polling interval for VariableModePoll variable and 0 for
+ // other modes.
+ const base::TimeDelta poll_interval_;
+
+ // The list of value changes observers.
+ std::list<BaseVariable::ObserverInterface*> observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(BaseVariable);
+};
+
+// Interface to an Update Manager variable of a given type. Implementation
+// internals are hidden as protected members, since policies should not be
+// using them directly.
+template<typename T>
+class Variable : public BaseVariable {
+ public:
+ ~Variable() override {}
+
+ protected:
+ // Only allow to get values through the EvaluationContext class and not
+ // directly from the variable.
+ friend class EvaluationContext;
+
+ // Needed to be able to verify variable contents during unit testing.
+ friend class UmTestUtils;
+ FRIEND_TEST(UmRealRandomProviderTest, GetRandomValues);
+
+ Variable(const std::string& name, VariableMode mode)
+ : BaseVariable(name, mode) {}
+
+ Variable(const std::string& name, const base::TimeDelta poll_interval)
+ : BaseVariable(name, poll_interval) {}
+
+ // Gets the current value of the variable. The current value is copied to a
+ // new object and returned. The caller of this method owns the object and
+ // should delete it.
+ //
+ // In case of and error getting the current value or the |timeout| timeout is
+ // exceeded, a null value is returned and the |errmsg| is set.
+ //
+ // The caller can pass a null value for |errmsg|, in which case the error
+ // message won't be set.
+ virtual const T* GetValue(base::TimeDelta timeout, std::string* errmsg) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Variable);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_VARIABLE_H_
diff --git a/update_manager/variable_unittest.cc b/update_manager/variable_unittest.cc
new file mode 100644
index 0000000..000176b
--- /dev/null
+++ b/update_manager/variable_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/update_manager/variable.h"
+
+#include <vector>
+
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos::MessageLoopRunMaxIterations;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_manager {
+
+// Variable class that returns a value constructed with the default value.
+template <typename T>
+class DefaultVariable : public Variable<T> {
+ public:
+ DefaultVariable(const string& name, VariableMode mode)
+ : Variable<T>(name, mode) {}
+ DefaultVariable(const string& name, const TimeDelta& poll_interval)
+ : Variable<T>(name, poll_interval) {}
+ ~DefaultVariable() override {}
+
+ protected:
+ const T* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) override {
+ return new T();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DefaultVariable);
+};
+
+class UmBaseVariableTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ chromeos::FakeMessageLoop loop_{nullptr};
+};
+
+TEST_F(UmBaseVariableTest, GetNameTest) {
+ DefaultVariable<int> var("var", kVariableModeConst);
+ EXPECT_EQ(var.GetName(), string("var"));
+}
+
+TEST_F(UmBaseVariableTest, GetModeTest) {
+ DefaultVariable<int> var("var", kVariableModeConst);
+ EXPECT_EQ(var.GetMode(), kVariableModeConst);
+ DefaultVariable<int> other_var("other_var", kVariableModePoll);
+ EXPECT_EQ(other_var.GetMode(), kVariableModePoll);
+}
+
+TEST_F(UmBaseVariableTest, DefaultPollIntervalTest) {
+ DefaultVariable<int> const_var("const_var", kVariableModeConst);
+ EXPECT_EQ(const_var.GetPollInterval(), TimeDelta());
+ DefaultVariable<int> poll_var("poll_var", kVariableModePoll);
+ EXPECT_EQ(poll_var.GetPollInterval(), TimeDelta::FromMinutes(5));
+}
+
+TEST_F(UmBaseVariableTest, GetPollIntervalTest) {
+ DefaultVariable<int> var("var", TimeDelta::FromMinutes(3));
+ EXPECT_EQ(var.GetMode(), kVariableModePoll);
+ EXPECT_EQ(var.GetPollInterval(), TimeDelta::FromMinutes(3));
+}
+
+class BaseVariableObserver : public BaseVariable::ObserverInterface {
+ public:
+ void ValueChanged(BaseVariable* variable) {
+ calls_.push_back(variable);
+ }
+
+ // List of called functions.
+ vector<BaseVariable*> calls_;
+};
+
+TEST_F(UmBaseVariableTest, RepeatedObserverTest) {
+ DefaultVariable<int> var("var", kVariableModeAsync);
+ BaseVariableObserver observer;
+ var.AddObserver(&observer);
+ EXPECT_EQ(var.observer_list_.size(), 1);
+ var.AddObserver(&observer);
+ EXPECT_EQ(var.observer_list_.size(), 1);
+ var.RemoveObserver(&observer);
+ EXPECT_EQ(var.observer_list_.size(), 0);
+ var.RemoveObserver(&observer);
+ EXPECT_EQ(var.observer_list_.size(), 0);
+}
+
+TEST_F(UmBaseVariableTest, NotifyValueChangedTest) {
+ DefaultVariable<int> var("var", kVariableModeAsync);
+ BaseVariableObserver observer1;
+ var.AddObserver(&observer1);
+ // Simulate a value change on the variable's implementation.
+ var.NotifyValueChanged();
+ ASSERT_EQ(0, observer1.calls_.size());
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+ ASSERT_EQ(1, observer1.calls_.size());
+ // Check that the observer is called with the right argument.
+ EXPECT_EQ(&var, observer1.calls_[0]);
+
+ BaseVariableObserver observer2;
+ var.AddObserver(&observer2);
+ var.NotifyValueChanged();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+ // Check that all the observers are called.
+ EXPECT_EQ(2, observer1.calls_.size());
+ EXPECT_EQ(1, observer2.calls_.size());
+
+ var.RemoveObserver(&observer1);
+ var.RemoveObserver(&observer2);
+}
+
+class BaseVariableObserverRemover : public BaseVariable::ObserverInterface {
+ public:
+ BaseVariableObserverRemover() : calls_(0) {}
+
+ void ValueChanged(BaseVariable* variable) override {
+ for (auto& observer : remove_observers_) {
+ variable->RemoveObserver(observer);
+ }
+ calls_++;
+ }
+
+ void OnCallRemoveObserver(BaseVariable::ObserverInterface* observer) {
+ remove_observers_.push_back(observer);
+ }
+
+ int get_calls() { return calls_; }
+
+ private:
+ vector<BaseVariable::ObserverInterface*> remove_observers_;
+ int calls_;
+};
+
+// Tests that we can remove an observer from a Variable on the ValueChanged()
+// call to that observer.
+TEST_F(UmBaseVariableTest, NotifyValueRemovesObserversTest) {
+ DefaultVariable<int> var("var", kVariableModeAsync);
+ BaseVariableObserverRemover observer1;
+ BaseVariableObserverRemover observer2;
+
+ var.AddObserver(&observer1);
+ var.AddObserver(&observer2);
+
+ // Make each observer remove both observers on ValueChanged.
+ observer1.OnCallRemoveObserver(&observer1);
+ observer1.OnCallRemoveObserver(&observer2);
+ observer2.OnCallRemoveObserver(&observer1);
+ observer2.OnCallRemoveObserver(&observer2);
+
+ var.NotifyValueChanged();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+ EXPECT_EQ(1, observer1.get_calls() + observer2.get_calls());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_metadata.proto b/update_metadata.proto
new file mode 100644
index 0000000..bf0cb45
--- /dev/null
+++ b/update_metadata.proto
@@ -0,0 +1,177 @@
+// Copyright (c) 2010 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.
+
+// Update file format: A delta update file contains all the deltas needed
+// to update a system from one specific version to another specific
+// version. The update format is represented by this struct pseudocode:
+// struct delta_update_file {
+// char magic[4] = "CrAU";
+// uint64 file_format_version = 1;
+// uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
+// // The Bzip2 compressed DeltaArchiveManifest
+// char manifest[];
+//
+// // Data blobs for files, no specific format. The specific offset
+// // and length of each data blob is recorded in the DeltaArchiveManifest.
+// struct {
+// char data[];
+// } blobs[];
+//
+// // These two are not signed:
+// uint64 signatures_message_size;
+// char signatures_message[];
+//
+// };
+
+// The DeltaArchiveManifest protobuf is an ordered list of InstallOperation
+// objects. These objects are stored in a linear array in the
+// DeltaArchiveManifest. Each operation is applied in order by the client.
+
+// The DeltaArchiveManifest also contains the initial and final
+// checksums for the device.
+
+// The client will perform each InstallOperation in order, beginning even
+// before the entire delta file is downloaded (but after at least the
+// protobuf is downloaded). The types of operations are explained:
+// - REPLACE: Replace the dst_extents on the drive with the attached data,
+// zero padding out to block size.
+// - REPLACE_BZ: bzip2-uncompress the attached data and write it into
+// dst_extents on the drive, zero padding to block size.
+// - MOVE: Copy the data in src_extents to dst_extents. Extents may overlap,
+// so it may be desirable to read all src_extents data into memory before
+// writing it out.
+// - BSDIFF: Read src_length bytes from src_extents into memory, perform
+// bspatch with attached data, write new data to dst_extents, zero padding
+// to block size.
+
+package chromeos_update_engine;
+option optimize_for = LITE_RUNTIME;
+
+// Data is packed into blocks on disk, always starting from the beginning
+// of the block. If a file's data is too large for one block, it overflows
+// into another block, which may or may not be the following block on the
+// physical partition. An ordered list of extents is another
+// representation of an ordered list of blocks. For example, a file stored
+// in blocks 9, 10, 11, 2, 18, 12 (in that order) would be stored in
+// extents { {9, 3}, {2, 1}, {18, 1}, {12, 1} } (in that order).
+// In general, files are stored sequentially on disk, so it's more efficient
+// to use extents to encode the block lists (this is effectively
+// run-length encoding).
+// A sentinel value (kuint64max) as the start block denotes a sparse-hole
+// in a file whose block-length is specified by num_blocks.
+
+// Signatures: Updates may be signed by the OS vendor. The client verifies
+// an update's signature by hashing the entire download. The section of the
+// download that contains the signature is at the end of the file, so when
+// signing a file, only the part up to the signature part is signed.
+// Then, the client looks inside the download's Signatures message for a
+// Signature message that it knows how to handle. Generally, a client will
+// only know how to handle one type of signature, but an update may contain
+// many signatures to support many different types of client. Then client
+// selects a Signature message and uses that, along with a known public key,
+// to verify the download. The public key is expected to be part of the
+// client.
+
+message Extent {
+ optional uint64 start_block = 1;
+ optional uint64 num_blocks = 2;
+}
+
+message Signatures {
+ message Signature {
+ optional uint32 version = 1;
+ optional bytes data = 2;
+ }
+ repeated Signature signatures = 1;
+}
+
+message PartitionInfo {
+ optional uint64 size = 1;
+ optional bytes hash = 2;
+}
+
+// Describe an image we are based on in a human friendly way.
+// Examples:
+// dev-channel, x86-alex, 1.2.3, mp-v3
+// nplusone-channel, x86-alex, 1.2.4, mp-v3, dev-channel, 1.2.3
+//
+// All fields will be set, if this message is present.
+message ImageInfo {
+ optional string board = 1;
+ optional string key = 2;
+ optional string channel = 3;
+ optional string version = 4;
+
+ // If these values aren't present, they should be assumed to match
+ // the equivalent value above. They are normally only different for
+ // special image types such as nplusone images.
+ optional string build_channel = 5;
+ optional string build_version = 6;
+}
+
+message DeltaArchiveManifest {
+ message InstallOperation {
+ enum Type {
+ REPLACE = 0; // Replace destination extents w/ attached data
+ REPLACE_BZ = 1; // Replace destination extents w/ attached bzipped data
+ MOVE = 2; // Move source extents to destination extents
+ BSDIFF = 3; // The data is a bsdiff binary diff
+ // SOURCE_COPY and SOURCE_BSDIFF are only supported on minor version 2.
+ SOURCE_COPY = 4; // Copy from source to target partition
+ SOURCE_BSDIFF = 5; // Like BSDIFF, but read from source partition
+ }
+ required Type type = 1;
+ // The offset into the delta file (after the protobuf)
+ // where the data (if any) is stored
+ optional uint32 data_offset = 2;
+ // The length of the data in the delta file
+ optional uint32 data_length = 3;
+
+ // Ordered list of extents that are read from (if any) and written to.
+ repeated Extent src_extents = 4;
+ // Byte length of src, equal to the number of blocks in src_extents *
+ // block_size. It is used for BSDIFF, because we need to pass that
+ // external program the number of bytes to read from the blocks we pass it.
+ // This is not used in any other operation.
+ optional uint64 src_length = 5;
+
+ repeated Extent dst_extents = 6;
+ // Byte length of dst, equal to the number of blocks in dst_extents *
+ // block_size. Used for BSDIFF, but not in any other operation.
+ optional uint64 dst_length = 7;
+
+ // Optional SHA 256 hash of the blob associated with this operation.
+ // This is used as a primary validation for http-based downloads and
+ // as a defense-in-depth validation for https-based downloads. If
+ // the operation doesn't refer to any blob, this field will have
+ // zero bytes.
+ optional bytes data_sha256_hash = 8;
+ }
+ repeated InstallOperation install_operations = 1;
+ repeated InstallOperation kernel_install_operations = 2;
+
+ // (At time of writing) usually 4096
+ optional uint32 block_size = 3 [default = 4096];
+
+ // If signatures are present, the offset into the blobs, generally
+ // tacked onto the end of the file, and the length. We use an offset
+ // rather than a bool to allow for more flexibility in future file formats.
+ // If either is absent, it means signatures aren't supported in this
+ // file.
+ optional uint64 signatures_offset = 4;
+ optional uint64 signatures_size = 5;
+
+ // Partition data that can be used to validate the update.
+ optional PartitionInfo old_kernel_info = 6;
+ optional PartitionInfo new_kernel_info = 7;
+ optional PartitionInfo old_rootfs_info = 8;
+ optional PartitionInfo new_rootfs_info = 9;
+
+ // old_image_info will only be present for delta images.
+ optional ImageInfo old_image_info = 10;
+
+ optional ImageInfo new_image_info = 11;
+
+ optional uint32 minor_version = 12 [default = 0];
+}
diff --git a/utils.cc b/utils.cc
new file mode 100644
index 0000000..97ce712
--- /dev/null
+++ b/utils.cc
@@ -0,0 +1,1671 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/utils.h"
+
+#include <stdint.h>
+
+#include <dirent.h>
+#include <elf.h>
+#include <errno.h>
+#include <ext2fs/ext2fs.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_file.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/data_encoding.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/constants.h"
+#include "update_engine/file_descriptor.h"
+#include "update_engine/file_writer.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_attempter.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::min;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The following constants control how UnmountFilesystem should retry if
+// umount() fails with an errno EBUSY, i.e. retry 5 times over the course of
+// one second.
+const int kUnmountMaxNumOfRetries = 5;
+const int kUnmountRetryIntervalInMicroseconds = 200 * 1000; // 200 ms
+
+// Number of bytes to read from a file to attempt to detect its contents. Used
+// in GetFileFormat.
+const int kGetFileFormatMaxHeaderSize = 32;
+
+// Return true if |disk_name| is an MTD or a UBI device. Note that this test is
+// simply based on the name of the device.
+bool IsMtdDeviceName(const string& disk_name) {
+ return base::StartsWithASCII(disk_name, "/dev/ubi", true) ||
+ base::StartsWithASCII(disk_name, "/dev/mtd", true);
+}
+
+// Return the device name for the corresponding partition on a NAND device.
+// WARNING: This function returns device names that are not mountable.
+string MakeNandPartitionName(int partition_num) {
+ switch (partition_num) {
+ case 2:
+ case 4:
+ case 6: {
+ return base::StringPrintf("/dev/mtd%d", partition_num);
+ }
+ default: {
+ return base::StringPrintf("/dev/ubi%d_0", partition_num);
+ }
+ }
+}
+
+// Return the device name for the corresponding partition on a NAND device that
+// may be mountable (but may not be writable).
+string MakeNandPartitionNameForMount(int partition_num) {
+ switch (partition_num) {
+ case 2:
+ case 4:
+ case 6: {
+ return base::StringPrintf("/dev/mtd%d", partition_num);
+ }
+ case 3:
+ case 5:
+ case 7: {
+ return base::StringPrintf("/dev/ubiblock%d_0", partition_num);
+ }
+ default: {
+ return base::StringPrintf("/dev/ubi%d_0", partition_num);
+ }
+ }
+}
+
+} // namespace
+
+namespace utils {
+
+// Cgroup container is created in update-engine's upstart script located at
+// /etc/init/update-engine.conf.
+static const char kCGroupDir[] = "/sys/fs/cgroup/cpu/update-engine";
+
+string ParseECVersion(string input_line) {
+ base::TrimWhitespaceASCII(input_line, base::TRIM_ALL, &input_line);
+
+ // At this point we want to convert the format key=value pair from mosys to
+ // a vector of key value pairs.
+ vector<pair<string, string>> kv_pairs;
+ if (base::SplitStringIntoKeyValuePairs(input_line, '=', ' ', &kv_pairs)) {
+ for (const pair<string, string>& kv_pair : kv_pairs) {
+ // Finally match against the fw_verion which may have quotes.
+ if (kv_pair.first == "fw_version") {
+ string output;
+ // Trim any quotes.
+ base::TrimString(kv_pair.second, "\"", &output);
+ return output;
+ }
+ }
+ }
+ LOG(ERROR) << "Unable to parse fwid from ec info.";
+ return "";
+}
+
+
+const string KernelDeviceOfBootDevice(const string& boot_device) {
+ string kernel_partition_name;
+
+ string disk_name;
+ int partition_num;
+ if (SplitPartitionName(boot_device, &disk_name, &partition_num)) {
+ // Currently this assumes the partition number of the boot device is
+ // 3, 5, or 7, and changes it to 2, 4, or 6, respectively, to
+ // get the kernel device.
+ if (partition_num == 3 || partition_num == 5 || partition_num == 7) {
+ kernel_partition_name = MakePartitionName(disk_name, partition_num - 1);
+ }
+ }
+
+ return kernel_partition_name;
+}
+
+
+bool WriteFile(const char* path, const void* data, int data_len) {
+ DirectFileWriter writer;
+ TEST_AND_RETURN_FALSE_ERRNO(0 == writer.Open(path,
+ O_WRONLY | O_CREAT | O_TRUNC,
+ 0600));
+ ScopedFileWriterCloser closer(&writer);
+ TEST_AND_RETURN_FALSE_ERRNO(writer.Write(data, data_len));
+ return true;
+}
+
+bool WriteAll(int fd, const void* buf, size_t count) {
+ const char* c_buf = static_cast<const char*>(buf);
+ ssize_t bytes_written = 0;
+ while (bytes_written < static_cast<ssize_t>(count)) {
+ ssize_t rc = write(fd, c_buf + bytes_written, count - bytes_written);
+ TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+ bytes_written += rc;
+ }
+ return true;
+}
+
+bool PWriteAll(int fd, const void* buf, size_t count, off_t offset) {
+ const char* c_buf = static_cast<const char*>(buf);
+ size_t bytes_written = 0;
+ int num_attempts = 0;
+ while (bytes_written < count) {
+ num_attempts++;
+ ssize_t rc = pwrite(fd, c_buf + bytes_written, count - bytes_written,
+ offset + bytes_written);
+ // TODO(garnold) for debugging failure in chromium-os:31077; to be removed.
+ if (rc < 0) {
+ PLOG(ERROR) << "pwrite error; num_attempts=" << num_attempts
+ << " bytes_written=" << bytes_written
+ << " count=" << count << " offset=" << offset;
+ }
+ TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+ bytes_written += rc;
+ }
+ return true;
+}
+
+bool WriteAll(FileDescriptorPtr fd, const void* buf, size_t count) {
+ const char* c_buf = static_cast<const char*>(buf);
+ ssize_t bytes_written = 0;
+ while (bytes_written < static_cast<ssize_t>(count)) {
+ ssize_t rc = fd->Write(c_buf + bytes_written, count - bytes_written);
+ TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+ bytes_written += rc;
+ }
+ return true;
+}
+
+bool PWriteAll(FileDescriptorPtr fd,
+ const void* buf,
+ size_t count,
+ off_t offset) {
+ TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) !=
+ static_cast<off_t>(-1));
+ return WriteAll(fd, buf, count);
+}
+
+bool PReadAll(int fd, void* buf, size_t count, off_t offset,
+ ssize_t* out_bytes_read) {
+ char* c_buf = static_cast<char*>(buf);
+ ssize_t bytes_read = 0;
+ while (bytes_read < static_cast<ssize_t>(count)) {
+ ssize_t rc = pread(fd, c_buf + bytes_read, count - bytes_read,
+ offset + bytes_read);
+ TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+ if (rc == 0) {
+ break;
+ }
+ bytes_read += rc;
+ }
+ *out_bytes_read = bytes_read;
+ return true;
+}
+
+bool PReadAll(FileDescriptorPtr fd, void* buf, size_t count, off_t offset,
+ ssize_t* out_bytes_read) {
+ TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) !=
+ static_cast<off_t>(-1));
+ char* c_buf = static_cast<char*>(buf);
+ ssize_t bytes_read = 0;
+ while (bytes_read < static_cast<ssize_t>(count)) {
+ ssize_t rc = fd->Read(c_buf + bytes_read, count - bytes_read);
+ TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+ if (rc == 0) {
+ break;
+ }
+ bytes_read += rc;
+ }
+ *out_bytes_read = bytes_read;
+ return true;
+}
+
+// Append |nbytes| of content from |buf| to the vector pointed to by either
+// |vec_p| or |str_p|.
+static void AppendBytes(const uint8_t* buf, size_t nbytes,
+ chromeos::Blob* vec_p) {
+ CHECK(buf);
+ CHECK(vec_p);
+ vec_p->insert(vec_p->end(), buf, buf + nbytes);
+}
+static void AppendBytes(const uint8_t* buf, size_t nbytes,
+ string* str_p) {
+ CHECK(buf);
+ CHECK(str_p);
+ str_p->append(buf, buf + nbytes);
+}
+
+// Reads from an open file |fp|, appending the read content to the container
+// pointer to by |out_p|. Returns true upon successful reading all of the
+// file's content, false otherwise. If |size| is not -1, reads up to |size|
+// bytes.
+template <class T>
+static bool Read(FILE* fp, off_t size, T* out_p) {
+ CHECK(fp);
+ CHECK(size == -1 || size >= 0);
+ uint8_t buf[1024];
+ while (size == -1 || size > 0) {
+ off_t bytes_to_read = sizeof(buf);
+ if (size > 0 && bytes_to_read > size) {
+ bytes_to_read = size;
+ }
+ size_t nbytes = fread(buf, 1, bytes_to_read, fp);
+ if (!nbytes) {
+ break;
+ }
+ AppendBytes(buf, nbytes, out_p);
+ if (size != -1) {
+ CHECK(size >= static_cast<off_t>(nbytes));
+ size -= nbytes;
+ }
+ }
+ if (ferror(fp)) {
+ return false;
+ }
+ return size == 0 || feof(fp);
+}
+
+// Opens a file |path| for reading and appends its the contents to a container
+// |out_p|. Starts reading the file from |offset|. If |offset| is beyond the end
+// of the file, returns success. If |size| is not -1, reads up to |size| bytes.
+template <class T>
+static bool ReadFileChunkAndAppend(const string& path,
+ off_t offset,
+ off_t size,
+ T* out_p) {
+ CHECK_GE(offset, 0);
+ CHECK(size == -1 || size >= 0);
+ base::ScopedFILE fp(fopen(path.c_str(), "r"));
+ if (!fp.get())
+ return false;
+ if (offset) {
+ // Return success without appending any data if a chunk beyond the end of
+ // the file is requested.
+ if (offset >= FileSize(path)) {
+ return true;
+ }
+ TEST_AND_RETURN_FALSE_ERRNO(fseek(fp.get(), offset, SEEK_SET) == 0);
+ }
+ return Read(fp.get(), size, out_p);
+}
+
+// TODO(deymo): This is only used in unittest, but requires the private
+// Read<string>() defined here. Expose Read<string>() or move to base/ version.
+bool ReadPipe(const string& cmd, string* out_p) {
+ FILE* fp = popen(cmd.c_str(), "r");
+ if (!fp)
+ return false;
+ bool success = Read(fp, -1, out_p);
+ return (success && pclose(fp) >= 0);
+}
+
+bool ReadFile(const string& path, chromeos::Blob* out_p) {
+ return ReadFileChunkAndAppend(path, 0, -1, out_p);
+}
+
+bool ReadFile(const string& path, string* out_p) {
+ return ReadFileChunkAndAppend(path, 0, -1, out_p);
+}
+
+bool ReadFileChunk(const string& path, off_t offset, off_t size,
+ chromeos::Blob* out_p) {
+ return ReadFileChunkAndAppend(path, offset, size, out_p);
+}
+
+off_t BlockDevSize(int fd) {
+ uint64_t dev_size;
+ int rc = ioctl(fd, BLKGETSIZE64, &dev_size);
+ if (rc == -1) {
+ dev_size = -1;
+ PLOG(ERROR) << "Error running ioctl(BLKGETSIZE64) on " << fd;
+ }
+ return dev_size;
+}
+
+off_t BlockDevSize(const string& path) {
+ int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error opening " << path;
+ return fd;
+ }
+
+ off_t dev_size = BlockDevSize(fd);
+ if (dev_size == -1)
+ PLOG(ERROR) << "Error getting block device size on " << path;
+
+ close(fd);
+ return dev_size;
+}
+
+off_t FileSize(int fd) {
+ struct stat stbuf;
+ int rc = fstat(fd, &stbuf);
+ CHECK_EQ(rc, 0);
+ if (rc < 0) {
+ PLOG(ERROR) << "Error stat-ing " << fd;
+ return rc;
+ }
+ if (S_ISREG(stbuf.st_mode))
+ return stbuf.st_size;
+ if (S_ISBLK(stbuf.st_mode))
+ return BlockDevSize(fd);
+ LOG(ERROR) << "Couldn't determine the type of " << fd;
+ return -1;
+}
+
+off_t FileSize(const string& path) {
+ int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error opening " << path;
+ return fd;
+ }
+ off_t size = FileSize(fd);
+ if (size == -1)
+ PLOG(ERROR) << "Error getting file size of " << path;
+ close(fd);
+ return size;
+}
+
+void HexDumpArray(const uint8_t* const arr, const size_t length) {
+ LOG(INFO) << "Logging array of length: " << length;
+ const unsigned int bytes_per_line = 16;
+ for (uint32_t i = 0; i < length; i += bytes_per_line) {
+ const unsigned int bytes_remaining = length - i;
+ const unsigned int bytes_per_this_line = min(bytes_per_line,
+ bytes_remaining);
+ char header[100];
+ int r = snprintf(header, sizeof(header), "0x%08x : ", i);
+ TEST_AND_RETURN(r == 13);
+ string line = header;
+ for (unsigned int j = 0; j < bytes_per_this_line; j++) {
+ char buf[20];
+ uint8_t c = arr[i + j];
+ r = snprintf(buf, sizeof(buf), "%02x ", static_cast<unsigned int>(c));
+ TEST_AND_RETURN(r == 3);
+ line += buf;
+ }
+ LOG(INFO) << line;
+ }
+}
+
+string GetDiskName(const string& partition_name) {
+ string disk_name;
+ return SplitPartitionName(partition_name, &disk_name, nullptr) ?
+ disk_name : string();
+}
+
+int GetPartitionNumber(const string& partition_name) {
+ int partition_num = 0;
+ return SplitPartitionName(partition_name, nullptr, &partition_num) ?
+ partition_num : 0;
+}
+
+bool SplitPartitionName(const string& partition_name,
+ string* out_disk_name,
+ int* out_partition_num) {
+ if (!base::StartsWithASCII(partition_name, "/dev/", true)) {
+ LOG(ERROR) << "Invalid partition device name: " << partition_name;
+ return false;
+ }
+
+ size_t last_nondigit_pos = partition_name.find_last_not_of("0123456789");
+ if (last_nondigit_pos == string::npos ||
+ (last_nondigit_pos + 1) == partition_name.size()) {
+ LOG(ERROR) << "Unable to parse partition device name: " << partition_name;
+ return false;
+ }
+
+ size_t partition_name_len = string::npos;
+ if (partition_name[last_nondigit_pos] == '_') {
+ // NAND block devices have weird naming which could be something
+ // like "/dev/ubiblock2_0". We discard "_0" in such a case.
+ size_t prev_nondigit_pos =
+ partition_name.find_last_not_of("0123456789", last_nondigit_pos - 1);
+ if (prev_nondigit_pos == string::npos ||
+ (prev_nondigit_pos + 1) == last_nondigit_pos) {
+ LOG(ERROR) << "Unable to parse partition device name: " << partition_name;
+ return false;
+ }
+
+ partition_name_len = last_nondigit_pos - prev_nondigit_pos;
+ last_nondigit_pos = prev_nondigit_pos;
+ }
+
+ if (out_disk_name) {
+ // Special case for MMC devices which have the following naming scheme:
+ // mmcblk0p2
+ size_t disk_name_len = last_nondigit_pos;
+ if (partition_name[last_nondigit_pos] != 'p' ||
+ last_nondigit_pos == 0 ||
+ !isdigit(partition_name[last_nondigit_pos - 1])) {
+ disk_name_len++;
+ }
+ *out_disk_name = partition_name.substr(0, disk_name_len);
+ }
+
+ if (out_partition_num) {
+ string partition_str = partition_name.substr(last_nondigit_pos + 1,
+ partition_name_len);
+ *out_partition_num = atoi(partition_str.c_str());
+ }
+ return true;
+}
+
+string MakePartitionName(const string& disk_name, int partition_num) {
+ if (partition_num < 1) {
+ LOG(ERROR) << "Invalid partition number: " << partition_num;
+ return string();
+ }
+
+ if (!base::StartsWithASCII(disk_name, "/dev/", true)) {
+ LOG(ERROR) << "Invalid disk name: " << disk_name;
+ return string();
+ }
+
+ if (IsMtdDeviceName(disk_name)) {
+ // Special case for UBI block devices.
+ // 1. ubiblock is not writable, we need to use plain "ubi".
+ // 2. There is a "_0" suffix.
+ return MakeNandPartitionName(partition_num);
+ }
+
+ string partition_name = disk_name;
+ if (isdigit(partition_name.back())) {
+ // Special case for devices with names ending with a digit.
+ // Add "p" to separate the disk name from partition number,
+ // e.g. "/dev/loop0p2"
+ partition_name += 'p';
+ }
+
+ partition_name += std::to_string(partition_num);
+
+ return partition_name;
+}
+
+string MakePartitionNameForMount(const string& part_name) {
+ if (IsMtdDeviceName(part_name)) {
+ int partition_num;
+ if (!SplitPartitionName(part_name, nullptr, &partition_num)) {
+ return "";
+ }
+ return MakeNandPartitionNameForMount(partition_num);
+ }
+ return part_name;
+}
+
+string SysfsBlockDevice(const string& device) {
+ base::FilePath device_path(device);
+ if (device_path.DirName().value() != "/dev") {
+ return "";
+ }
+ return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
+}
+
+bool IsRemovableDevice(const string& device) {
+ string sysfs_block = SysfsBlockDevice(device);
+ string removable;
+ if (sysfs_block.empty() ||
+ !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
+ &removable)) {
+ return false;
+ }
+ base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
+ return removable == "1";
+}
+
+string ErrnoNumberAsString(int err) {
+ char buf[100];
+ buf[0] = '\0';
+ return strerror_r(err, buf, sizeof(buf));
+}
+
+bool FileExists(const char* path) {
+ struct stat stbuf;
+ return 0 == lstat(path, &stbuf);
+}
+
+bool IsSymlink(const char* path) {
+ struct stat stbuf;
+ return lstat(path, &stbuf) == 0 && S_ISLNK(stbuf.st_mode) != 0;
+}
+
+bool TryAttachingUbiVolume(int volume_num, int timeout) {
+ const string volume_path = base::StringPrintf("/dev/ubi%d_0", volume_num);
+ if (FileExists(volume_path.c_str())) {
+ return true;
+ }
+
+ int exit_code;
+ vector<string> cmd = {
+ "ubiattach",
+ "-m",
+ base::StringPrintf("%d", volume_num),
+ "-d",
+ base::StringPrintf("%d", volume_num)
+ };
+ TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &exit_code, nullptr));
+ TEST_AND_RETURN_FALSE(exit_code == 0);
+
+ cmd = {
+ "ubiblock",
+ "--create",
+ volume_path
+ };
+ TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &exit_code, nullptr));
+ TEST_AND_RETURN_FALSE(exit_code == 0);
+
+ while (timeout > 0 && !FileExists(volume_path.c_str())) {
+ sleep(1);
+ timeout--;
+ }
+
+ return FileExists(volume_path.c_str());
+}
+
+// If |path| is absolute, or explicit relative to the current working directory,
+// leaves it as is. Otherwise, if TMPDIR is defined in the environment and is
+// non-empty, prepends it to |path|. Otherwise, prepends /tmp. Returns the
+// resulting path.
+static const string PrependTmpdir(const string& path) {
+ if (path[0] == '/' || base::StartsWithASCII(path, "./", true) ||
+ base::StartsWithASCII(path, "../", true))
+ return path;
+
+ const char *tmpdir = getenv("TMPDIR");
+ const string prefix = (tmpdir && *tmpdir ? tmpdir : "/tmp");
+ return prefix + "/" + path;
+}
+
+bool MakeTempFile(const string& base_filename_template,
+ string* filename,
+ int* fd) {
+ const string filename_template = PrependTmpdir(base_filename_template);
+ DCHECK(filename || fd);
+ vector<char> buf(filename_template.size() + 1);
+ memcpy(buf.data(), filename_template.data(), filename_template.size());
+ buf[filename_template.size()] = '\0';
+
+ int mkstemp_fd = mkstemp(buf.data());
+ TEST_AND_RETURN_FALSE_ERRNO(mkstemp_fd >= 0);
+ if (filename) {
+ *filename = buf.data();
+ }
+ if (fd) {
+ *fd = mkstemp_fd;
+ } else {
+ close(mkstemp_fd);
+ }
+ return true;
+}
+
+bool MakeTempDirectory(const string& base_dirname_template,
+ string* dirname) {
+ const string dirname_template = PrependTmpdir(base_dirname_template);
+ DCHECK(dirname);
+ vector<char> buf(dirname_template.size() + 1);
+ memcpy(buf.data(), dirname_template.data(), dirname_template.size());
+ buf[dirname_template.size()] = '\0';
+
+ char* return_code = mkdtemp(buf.data());
+ TEST_AND_RETURN_FALSE_ERRNO(return_code != nullptr);
+ *dirname = buf.data();
+ return true;
+}
+
+bool MountFilesystem(const string& device,
+ const string& mountpoint,
+ unsigned long mountflags) { // NOLINT(runtime/int)
+ // TODO(sosa): Remove "ext3" once crbug.com/208022 is resolved.
+ const vector<const char*> fstypes{"ext2", "ext3", "squashfs"};
+ for (const char* fstype : fstypes) {
+ int rc = mount(device.c_str(), mountpoint.c_str(), fstype, mountflags,
+ nullptr);
+ if (rc == 0)
+ return true;
+
+ PLOG(WARNING) << "Unable to mount destination device " << device
+ << " on " << mountpoint << " as " << fstype;
+ }
+ LOG(ERROR) << "Unable to mount " << device << " with any supported type";
+ return false;
+}
+
+bool UnmountFilesystem(const string& mountpoint) {
+ for (int num_retries = 0; ; ++num_retries) {
+ if (umount(mountpoint.c_str()) == 0)
+ break;
+
+ TEST_AND_RETURN_FALSE_ERRNO(errno == EBUSY &&
+ num_retries < kUnmountMaxNumOfRetries);
+ usleep(kUnmountRetryIntervalInMicroseconds);
+ }
+ return true;
+}
+
+bool GetFilesystemSize(const string& device,
+ int* out_block_count,
+ int* out_block_size) {
+ int fd = HANDLE_EINTR(open(device.c_str(), O_RDONLY));
+ TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+ ScopedFdCloser fd_closer(&fd);
+ return GetFilesystemSizeFromFD(fd, out_block_count, out_block_size);
+}
+
+bool GetFilesystemSizeFromFD(int fd,
+ int* out_block_count,
+ int* out_block_size) {
+ TEST_AND_RETURN_FALSE(fd >= 0);
+
+ // Determine the filesystem size by directly reading the block count and
+ // block size information from the superblock. Supported FS are ext3 and
+ // squashfs.
+
+ // Read from the fd only once and detect in memory. The first 2 KiB is enough
+ // to read the ext2 superblock (located at offset 1024) and the squashfs
+ // superblock (located at offset 0).
+ const ssize_t kBufferSize = 2048;
+
+ uint8_t buffer[kBufferSize];
+ if (HANDLE_EINTR(pread(fd, buffer, kBufferSize, 0)) != kBufferSize) {
+ PLOG(ERROR) << "Unable to read the file system header:";
+ return false;
+ }
+
+ if (GetSquashfs4Size(buffer, kBufferSize, out_block_count, out_block_size))
+ return true;
+ if (GetExt3Size(buffer, kBufferSize, out_block_count, out_block_size))
+ return true;
+
+ LOG(ERROR) << "Unable to determine file system type.";
+ return false;
+}
+
+bool GetExt3Size(const uint8_t* buffer, size_t buffer_size,
+ int* out_block_count,
+ int* out_block_size) {
+ // See include/linux/ext2_fs.h for more details on the structure. We obtain
+ // ext2 constants from ext2fs/ext2fs.h header but we don't link with the
+ // library.
+ if (buffer_size < SUPERBLOCK_OFFSET + SUPERBLOCK_SIZE)
+ return false;
+
+ const uint8_t* superblock = buffer + SUPERBLOCK_OFFSET;
+
+ // ext3_fs.h: ext3_super_block.s_blocks_count
+ uint32_t block_count =
+ *reinterpret_cast<const uint32_t*>(superblock + 1 * sizeof(int32_t));
+
+ // ext3_fs.h: ext3_super_block.s_log_block_size
+ uint32_t log_block_size =
+ *reinterpret_cast<const uint32_t*>(superblock + 6 * sizeof(int32_t));
+
+ // ext3_fs.h: ext3_super_block.s_magic
+ uint16_t magic =
+ *reinterpret_cast<const uint16_t*>(superblock + 14 * sizeof(int32_t));
+
+ block_count = le32toh(block_count);
+ log_block_size = le32toh(log_block_size) + EXT2_MIN_BLOCK_LOG_SIZE;
+ magic = le16toh(magic);
+
+ // Sanity check the parameters.
+ TEST_AND_RETURN_FALSE(magic == EXT2_SUPER_MAGIC);
+ TEST_AND_RETURN_FALSE(log_block_size >= EXT2_MIN_BLOCK_LOG_SIZE &&
+ log_block_size <= EXT2_MAX_BLOCK_LOG_SIZE);
+ TEST_AND_RETURN_FALSE(block_count > 0);
+
+ if (out_block_count)
+ *out_block_count = block_count;
+ if (out_block_size)
+ *out_block_size = 1 << log_block_size;
+ return true;
+}
+
+bool GetSquashfs4Size(const uint8_t* buffer, size_t buffer_size,
+ int* out_block_count,
+ int* out_block_size) {
+ // See fs/squashfs/squashfs_fs.h for format details. We only support
+ // Squashfs 4.x little endian.
+
+ // sizeof(struct squashfs_super_block)
+ const size_t kSquashfsSuperBlockSize = 96;
+ if (buffer_size < kSquashfsSuperBlockSize)
+ return false;
+
+ // Check magic, squashfs_fs.h: SQUASHFS_MAGIC
+ if (memcmp(buffer, "hsqs", 4) != 0)
+ return false; // Only little endian is supported.
+
+ // squashfs_fs.h: struct squashfs_super_block.s_major
+ uint16_t s_major = *reinterpret_cast<const uint16_t*>(
+ buffer + 5 * sizeof(uint32_t) + 4 * sizeof(uint16_t));
+
+ if (s_major != 4) {
+ LOG(ERROR) << "Found unsupported squashfs major version " << s_major;
+ return false;
+ }
+
+ // squashfs_fs.h: struct squashfs_super_block.bytes_used
+ uint64_t bytes_used = *reinterpret_cast<const int64_t*>(
+ buffer + 5 * sizeof(uint32_t) + 6 * sizeof(uint16_t) + sizeof(uint64_t));
+
+ const int block_size = 4096;
+
+ // The squashfs' bytes_used doesn't need to be aligned with the block boundary
+ // so we round up to the nearest blocksize.
+ if (out_block_count)
+ *out_block_count = (bytes_used + block_size - 1) / block_size;
+ if (out_block_size)
+ *out_block_size = block_size;
+ return true;
+}
+
+bool IsExtFilesystem(const string& device) {
+ chromeos::Blob header;
+ // The first 2 KiB is enough to read the ext2 superblock (located at offset
+ // 1024).
+ if (!ReadFileChunk(device, 0, 2048, &header))
+ return false;
+ return GetExt3Size(header.data(), header.size(), nullptr, nullptr);
+}
+
+bool IsSquashfsFilesystem(const string& device) {
+ chromeos::Blob header;
+ // The first 96 is enough to read the squashfs superblock.
+ const ssize_t kSquashfsSuperBlockSize = 96;
+ if (!ReadFileChunk(device, 0, kSquashfsSuperBlockSize, &header))
+ return false;
+ return GetSquashfs4Size(header.data(), header.size(), nullptr, nullptr);
+}
+
+// Tries to parse the header of an ELF file to obtain a human-readable
+// description of it on the |output| string.
+static bool GetFileFormatELF(const uint8_t* buffer, size_t size,
+ string* output) {
+ // 0x00: EI_MAG - ELF magic header, 4 bytes.
+ if (size < SELFMAG || memcmp(buffer, ELFMAG, SELFMAG) != 0)
+ return false;
+ *output = "ELF";
+
+ // 0x04: EI_CLASS, 1 byte.
+ if (size < EI_CLASS + 1)
+ return true;
+ switch (buffer[EI_CLASS]) {
+ case ELFCLASS32:
+ *output += " 32-bit";
+ break;
+ case ELFCLASS64:
+ *output += " 64-bit";
+ break;
+ default:
+ *output += " ?-bit";
+ }
+
+ // 0x05: EI_DATA, endianness, 1 byte.
+ if (size < EI_DATA + 1)
+ return true;
+ uint8_t ei_data = buffer[EI_DATA];
+ switch (ei_data) {
+ case ELFDATA2LSB:
+ *output += " little-endian";
+ break;
+ case ELFDATA2MSB:
+ *output += " big-endian";
+ break;
+ default:
+ *output += " ?-endian";
+ // Don't parse anything after the 0x10 offset if endianness is unknown.
+ return true;
+ }
+
+ const Elf32_Ehdr* hdr = reinterpret_cast<const Elf32_Ehdr*>(buffer);
+ // 0x12: e_machine, 2 byte endianness based on ei_data. The position (0x12)
+ // and size is the same for both 32 and 64 bits.
+ if (size < offsetof(Elf32_Ehdr, e_machine) + sizeof(hdr->e_machine))
+ return true;
+ uint16_t e_machine;
+ // Fix endianess regardless of the host endianess.
+ if (ei_data == ELFDATA2LSB)
+ e_machine = le16toh(hdr->e_machine);
+ else
+ e_machine = be16toh(hdr->e_machine);
+
+ switch (e_machine) {
+ case EM_386:
+ *output += " x86";
+ break;
+ case EM_MIPS:
+ *output += " mips";
+ break;
+ case EM_ARM:
+ *output += " arm";
+ break;
+ case EM_X86_64:
+ *output += " x86-64";
+ break;
+ default:
+ *output += " unknown-arch";
+ }
+ return true;
+}
+
+string GetFileFormat(const string& path) {
+ chromeos::Blob buffer;
+ if (!ReadFileChunkAndAppend(path, 0, kGetFileFormatMaxHeaderSize, &buffer))
+ return "File not found.";
+
+ string result;
+ if (GetFileFormatELF(buffer.data(), buffer.size(), &result))
+ return result;
+
+ return "data";
+}
+
+namespace {
+// Do the actual trigger. We do it as a main-loop callback to (try to) get a
+// consistent stack trace.
+void TriggerCrashReporterUpload() {
+ pid_t pid = fork();
+ CHECK_GE(pid, 0) << "fork failed"; // fork() failed. Something is very wrong.
+ if (pid == 0) {
+ // We are the child. Crash.
+ abort(); // never returns
+ }
+ // We are the parent. Wait for child to terminate.
+ pid_t result = waitpid(pid, nullptr, 0);
+ LOG_IF(ERROR, result < 0) << "waitpid() failed";
+}
+} // namespace
+
+void ScheduleCrashReporterUpload() {
+ chromeos::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&TriggerCrashReporterUpload));
+}
+
+bool SetCpuShares(CpuShares shares) {
+ string string_shares = base::IntToString(static_cast<int>(shares));
+ string cpu_shares_file = string(utils::kCGroupDir) + "/cpu.shares";
+ LOG(INFO) << "Setting cgroup cpu shares to " << string_shares;
+ if (utils::WriteFile(cpu_shares_file.c_str(), string_shares.c_str(),
+ string_shares.size())) {
+ return true;
+ } else {
+ LOG(ERROR) << "Failed to change cgroup cpu shares to "<< string_shares
+ << " using " << cpu_shares_file;
+ return false;
+ }
+}
+
+int FuzzInt(int value, unsigned int range) {
+ int min = value - range / 2;
+ int max = value + range - range / 2;
+ return base::RandInt(min, max);
+}
+
+string FormatSecs(unsigned secs) {
+ return FormatTimeDelta(TimeDelta::FromSeconds(secs));
+}
+
+string FormatTimeDelta(TimeDelta delta) {
+ string str;
+
+ // Handle negative durations by prefixing with a minus.
+ if (delta.ToInternalValue() < 0) {
+ delta *= -1;
+ str = "-";
+ }
+
+ // Canonicalize into days, hours, minutes, seconds and microseconds.
+ unsigned days = delta.InDays();
+ delta -= TimeDelta::FromDays(days);
+ unsigned hours = delta.InHours();
+ delta -= TimeDelta::FromHours(hours);
+ unsigned mins = delta.InMinutes();
+ delta -= TimeDelta::FromMinutes(mins);
+ unsigned secs = delta.InSeconds();
+ delta -= TimeDelta::FromSeconds(secs);
+ unsigned usecs = delta.InMicroseconds();
+
+ if (days)
+ base::StringAppendF(&str, "%ud", days);
+ if (days || hours)
+ base::StringAppendF(&str, "%uh", hours);
+ if (days || hours || mins)
+ base::StringAppendF(&str, "%um", mins);
+ base::StringAppendF(&str, "%u", secs);
+ if (usecs) {
+ int width = 6;
+ while ((usecs / 10) * 10 == usecs) {
+ usecs /= 10;
+ width--;
+ }
+ base::StringAppendF(&str, ".%0*u", width, usecs);
+ }
+ base::StringAppendF(&str, "s");
+ return str;
+}
+
+string ToString(const Time utc_time) {
+ Time::Exploded exp_time;
+ utc_time.UTCExplode(&exp_time);
+ return base::StringPrintf("%d/%d/%d %d:%02d:%02d GMT",
+ exp_time.month,
+ exp_time.day_of_month,
+ exp_time.year,
+ exp_time.hour,
+ exp_time.minute,
+ exp_time.second);
+}
+
+string ToString(bool b) {
+ return (b ? "true" : "false");
+}
+
+string ToString(DownloadSource source) {
+ switch (source) {
+ case kDownloadSourceHttpsServer: return "HttpsServer";
+ case kDownloadSourceHttpServer: return "HttpServer";
+ case kDownloadSourceHttpPeer: return "HttpPeer";
+ case kNumDownloadSources: return "Unknown";
+ // Don't add a default case to let the compiler warn about newly added
+ // download sources which should be added here.
+ }
+
+ return "Unknown";
+}
+
+string ToString(PayloadType payload_type) {
+ switch (payload_type) {
+ case kPayloadTypeDelta: return "Delta";
+ case kPayloadTypeFull: return "Full";
+ case kPayloadTypeForcedFull: return "ForcedFull";
+ case kNumPayloadTypes: return "Unknown";
+ // Don't add a default case to let the compiler warn about newly added
+ // payload types which should be added here.
+ }
+
+ return "Unknown";
+}
+
+ErrorCode GetBaseErrorCode(ErrorCode code) {
+ // Ignore the higher order bits in the code by applying the mask as
+ // we want the enumerations to be in the small contiguous range
+ // with values less than ErrorCode::kUmaReportedMax.
+ ErrorCode base_code = static_cast<ErrorCode>(
+ static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+ // Make additional adjustments required for UMA and error classification.
+ // TODO(jaysri): Move this logic to UeErrorCode.cc when we fix
+ // chromium-os:34369.
+ if (base_code >= ErrorCode::kOmahaRequestHTTPResponseBase) {
+ // Since we want to keep the enums to a small value, aggregate all HTTP
+ // errors into this one bucket for UMA and error classification purposes.
+ LOG(INFO) << "Converting error code " << base_code
+ << " to ErrorCode::kOmahaErrorInHTTPResponse";
+ base_code = ErrorCode::kOmahaErrorInHTTPResponse;
+ }
+
+ return base_code;
+}
+
+metrics::AttemptResult GetAttemptResult(ErrorCode code) {
+ ErrorCode base_code = static_cast<ErrorCode>(
+ static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+ switch (base_code) {
+ case ErrorCode::kSuccess:
+ return metrics::AttemptResult::kUpdateSucceeded;
+
+ case ErrorCode::kDownloadTransferError:
+ return metrics::AttemptResult::kPayloadDownloadError;
+
+ case ErrorCode::kDownloadInvalidMetadataSize:
+ case ErrorCode::kDownloadInvalidMetadataMagicString:
+ case ErrorCode::kDownloadMetadataSignatureError:
+ case ErrorCode::kDownloadMetadataSignatureVerificationError:
+ case ErrorCode::kPayloadMismatchedType:
+ case ErrorCode::kUnsupportedMajorPayloadVersion:
+ case ErrorCode::kUnsupportedMinorPayloadVersion:
+ case ErrorCode::kDownloadNewPartitionInfoError:
+ case ErrorCode::kDownloadSignatureMissingInManifest:
+ case ErrorCode::kDownloadManifestParseError:
+ case ErrorCode::kDownloadOperationHashMissingError:
+ return metrics::AttemptResult::kMetadataMalformed;
+
+ case ErrorCode::kDownloadOperationHashMismatch:
+ case ErrorCode::kDownloadOperationHashVerificationError:
+ return metrics::AttemptResult::kOperationMalformed;
+
+ case ErrorCode::kDownloadOperationExecutionError:
+ case ErrorCode::kInstallDeviceOpenError:
+ case ErrorCode::kKernelDeviceOpenError:
+ case ErrorCode::kDownloadWriteError:
+ case ErrorCode::kFilesystemCopierError:
+ case ErrorCode::kFilesystemVerifierError:
+ return metrics::AttemptResult::kOperationExecutionError;
+
+ case ErrorCode::kDownloadMetadataSignatureMismatch:
+ return metrics::AttemptResult::kMetadataVerificationFailed;
+
+ case ErrorCode::kPayloadSizeMismatchError:
+ case ErrorCode::kPayloadHashMismatchError:
+ case ErrorCode::kDownloadPayloadVerificationError:
+ case ErrorCode::kSignedDeltaPayloadExpectedError:
+ case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+ return metrics::AttemptResult::kPayloadVerificationFailed;
+
+ case ErrorCode::kNewRootfsVerificationError:
+ case ErrorCode::kNewKernelVerificationError:
+ return metrics::AttemptResult::kVerificationFailed;
+
+ case ErrorCode::kPostinstallRunnerError:
+ case ErrorCode::kPostinstallBootedFromFirmwareB:
+ case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+ return metrics::AttemptResult::kPostInstallFailed;
+
+ // We should never get these errors in the update-attempt stage so
+ // return internal error if this happens.
+ case ErrorCode::kError:
+ case ErrorCode::kOmahaRequestXMLParseError:
+ case ErrorCode::kOmahaRequestError:
+ case ErrorCode::kOmahaResponseHandlerError:
+ case ErrorCode::kDownloadStateInitializationError:
+ case ErrorCode::kOmahaRequestEmptyResponseError:
+ case ErrorCode::kDownloadInvalidMetadataSignature:
+ case ErrorCode::kOmahaResponseInvalid:
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kOmahaErrorInHTTPResponse:
+ case ErrorCode::kDownloadMetadataSignatureMissingError:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ case ErrorCode::kPostinstallPowerwashError:
+ case ErrorCode::kUpdateCanceledByChannelChange:
+ case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+ return metrics::AttemptResult::kInternalError;
+
+ // Special flags. These can't happen (we mask them out above) but
+ // the compiler doesn't know that. Just break out so we can warn and
+ // return |kInternalError|.
+ case ErrorCode::kUmaReportedMax:
+ case ErrorCode::kOmahaRequestHTTPResponseBase:
+ case ErrorCode::kDevModeFlag:
+ case ErrorCode::kResumedFlag:
+ case ErrorCode::kTestImageFlag:
+ case ErrorCode::kTestOmahaUrlFlag:
+ case ErrorCode::kSpecialFlags:
+ break;
+ }
+
+ LOG(ERROR) << "Unexpected error code " << base_code;
+ return metrics::AttemptResult::kInternalError;
+}
+
+
+metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code) {
+ ErrorCode base_code = static_cast<ErrorCode>(
+ static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+ if (base_code >= ErrorCode::kOmahaRequestHTTPResponseBase) {
+ int http_status =
+ static_cast<int>(base_code) -
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase);
+ if (http_status >= 200 && http_status <= 599) {
+ return static_cast<metrics::DownloadErrorCode>(
+ static_cast<int>(metrics::DownloadErrorCode::kHttpStatus200) +
+ http_status - 200);
+ } else if (http_status == 0) {
+ // The code is using HTTP Status 0 for "Unable to get http
+ // response code."
+ return metrics::DownloadErrorCode::kDownloadError;
+ }
+ LOG(WARNING) << "Unexpected HTTP status code " << http_status;
+ return metrics::DownloadErrorCode::kHttpStatusOther;
+ }
+
+ switch (base_code) {
+ // Unfortunately, ErrorCode::kDownloadTransferError is returned for a wide
+ // variety of errors (proxy errors, host not reachable, timeouts etc.).
+ //
+ // For now just map that to kDownloading. See http://crbug.com/355745
+ // for how we plan to add more detail in the future.
+ case ErrorCode::kDownloadTransferError:
+ return metrics::DownloadErrorCode::kDownloadError;
+
+ // All of these error codes are not related to downloading so break
+ // out so we can warn and return InputMalformed.
+ case ErrorCode::kSuccess:
+ case ErrorCode::kError:
+ case ErrorCode::kOmahaRequestError:
+ case ErrorCode::kOmahaResponseHandlerError:
+ case ErrorCode::kFilesystemCopierError:
+ case ErrorCode::kPostinstallRunnerError:
+ case ErrorCode::kPayloadMismatchedType:
+ case ErrorCode::kInstallDeviceOpenError:
+ case ErrorCode::kKernelDeviceOpenError:
+ case ErrorCode::kPayloadHashMismatchError:
+ case ErrorCode::kPayloadSizeMismatchError:
+ case ErrorCode::kDownloadPayloadVerificationError:
+ case ErrorCode::kDownloadNewPartitionInfoError:
+ case ErrorCode::kDownloadWriteError:
+ case ErrorCode::kNewRootfsVerificationError:
+ case ErrorCode::kNewKernelVerificationError:
+ case ErrorCode::kSignedDeltaPayloadExpectedError:
+ case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+ case ErrorCode::kPostinstallBootedFromFirmwareB:
+ case ErrorCode::kDownloadStateInitializationError:
+ case ErrorCode::kDownloadInvalidMetadataMagicString:
+ case ErrorCode::kDownloadSignatureMissingInManifest:
+ case ErrorCode::kDownloadManifestParseError:
+ case ErrorCode::kDownloadMetadataSignatureError:
+ case ErrorCode::kDownloadMetadataSignatureVerificationError:
+ case ErrorCode::kDownloadMetadataSignatureMismatch:
+ case ErrorCode::kDownloadOperationHashVerificationError:
+ case ErrorCode::kDownloadOperationExecutionError:
+ case ErrorCode::kDownloadOperationHashMismatch:
+ case ErrorCode::kOmahaRequestEmptyResponseError:
+ case ErrorCode::kOmahaRequestXMLParseError:
+ case ErrorCode::kDownloadInvalidMetadataSize:
+ case ErrorCode::kDownloadInvalidMetadataSignature:
+ case ErrorCode::kOmahaResponseInvalid:
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kOmahaErrorInHTTPResponse:
+ case ErrorCode::kDownloadOperationHashMissingError:
+ case ErrorCode::kDownloadMetadataSignatureMissingError:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ case ErrorCode::kPostinstallPowerwashError:
+ case ErrorCode::kUpdateCanceledByChannelChange:
+ case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+ case ErrorCode::kUnsupportedMajorPayloadVersion:
+ case ErrorCode::kUnsupportedMinorPayloadVersion:
+ case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+ case ErrorCode::kFilesystemVerifierError:
+ break;
+
+ // Special flags. These can't happen (we mask them out above) but
+ // the compiler doesn't know that. Just break out so we can warn and
+ // return |kInputMalformed|.
+ case ErrorCode::kUmaReportedMax:
+ case ErrorCode::kOmahaRequestHTTPResponseBase:
+ case ErrorCode::kDevModeFlag:
+ case ErrorCode::kResumedFlag:
+ case ErrorCode::kTestImageFlag:
+ case ErrorCode::kTestOmahaUrlFlag:
+ case ErrorCode::kSpecialFlags:
+ LOG(ERROR) << "Unexpected error code " << base_code;
+ break;
+ }
+
+ return metrics::DownloadErrorCode::kInputMalformed;
+}
+
+metrics::ConnectionType GetConnectionType(
+ NetworkConnectionType type,
+ NetworkTethering tethering) {
+ switch (type) {
+ case kNetUnknown:
+ return metrics::ConnectionType::kUnknown;
+
+ case kNetEthernet:
+ if (tethering == NetworkTethering::kConfirmed)
+ return metrics::ConnectionType::kTetheredEthernet;
+ else
+ return metrics::ConnectionType::kEthernet;
+
+ case kNetWifi:
+ if (tethering == NetworkTethering::kConfirmed)
+ return metrics::ConnectionType::kTetheredWifi;
+ else
+ return metrics::ConnectionType::kWifi;
+
+ case kNetWimax:
+ return metrics::ConnectionType::kWimax;
+
+ case kNetBluetooth:
+ return metrics::ConnectionType::kBluetooth;
+
+ case kNetCellular:
+ return metrics::ConnectionType::kCellular;
+ }
+
+ LOG(ERROR) << "Unexpected network connection type: type=" << type
+ << ", tethering=" << static_cast<int>(tethering);
+
+ return metrics::ConnectionType::kUnknown;
+}
+
+// Returns a printable version of the various flags denoted in the higher order
+// bits of the given code. Returns an empty string if none of those bits are
+// set.
+string GetFlagNames(uint32_t code) {
+ uint32_t flags = (static_cast<uint32_t>(code) &
+ static_cast<uint32_t>(ErrorCode::kSpecialFlags));
+ string flag_names;
+ string separator = "";
+ for (size_t i = 0; i < sizeof(flags) * 8; i++) {
+ uint32_t flag = flags & (1 << i);
+ if (flag) {
+ flag_names += separator + CodeToString(static_cast<ErrorCode>(flag));
+ separator = ", ";
+ }
+ }
+
+ return flag_names;
+}
+
+void SendErrorCodeToUma(SystemState* system_state, ErrorCode code) {
+ if (!system_state)
+ return;
+
+ ErrorCode uma_error_code = GetBaseErrorCode(code);
+
+ // If the code doesn't have flags computed already, compute them now based on
+ // the state of the current update attempt.
+ uint32_t flags =
+ static_cast<int>(code) & static_cast<int>(ErrorCode::kSpecialFlags);
+ if (!flags)
+ flags = system_state->update_attempter()->GetErrorCodeFlags();
+
+ // Determine the UMA bucket depending on the flags. But, ignore the resumed
+ // flag, as it's perfectly normal for production devices to resume their
+ // downloads and so we want to record those cases also in NormalErrorCodes
+ // bucket.
+ string metric =
+ flags & ~static_cast<uint32_t>(ErrorCode::kResumedFlag) ?
+ "Installer.DevModeErrorCodes" : "Installer.NormalErrorCodes";
+
+ LOG(INFO) << "Sending error code " << uma_error_code
+ << " (" << CodeToString(uma_error_code) << ")"
+ << " to UMA metric: " << metric
+ << ". Flags = " << (flags ? GetFlagNames(flags) : "None");
+
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric, static_cast<int>(uma_error_code),
+ static_cast<int>(ErrorCode::kUmaReportedMax));
+}
+
+string CodeToString(ErrorCode code) {
+ // If the given code has both parts (i.e. the error code part and the flags
+ // part) then strip off the flags part since the switch statement below
+ // has case statements only for the base error code or a single flag but
+ // doesn't support any combinations of those.
+ if ((static_cast<int>(code) & static_cast<int>(ErrorCode::kSpecialFlags)) &&
+ (static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags)))
+ code = static_cast<ErrorCode>(
+ static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+ switch (code) {
+ case ErrorCode::kSuccess: return "ErrorCode::kSuccess";
+ case ErrorCode::kError: return "ErrorCode::kError";
+ case ErrorCode::kOmahaRequestError: return "ErrorCode::kOmahaRequestError";
+ case ErrorCode::kOmahaResponseHandlerError:
+ return "ErrorCode::kOmahaResponseHandlerError";
+ case ErrorCode::kFilesystemCopierError:
+ return "ErrorCode::kFilesystemCopierError";
+ case ErrorCode::kPostinstallRunnerError:
+ return "ErrorCode::kPostinstallRunnerError";
+ case ErrorCode::kPayloadMismatchedType:
+ return "ErrorCode::kPayloadMismatchedType";
+ case ErrorCode::kInstallDeviceOpenError:
+ return "ErrorCode::kInstallDeviceOpenError";
+ case ErrorCode::kKernelDeviceOpenError:
+ return "ErrorCode::kKernelDeviceOpenError";
+ case ErrorCode::kDownloadTransferError:
+ return "ErrorCode::kDownloadTransferError";
+ case ErrorCode::kPayloadHashMismatchError:
+ return "ErrorCode::kPayloadHashMismatchError";
+ case ErrorCode::kPayloadSizeMismatchError:
+ return "ErrorCode::kPayloadSizeMismatchError";
+ case ErrorCode::kDownloadPayloadVerificationError:
+ return "ErrorCode::kDownloadPayloadVerificationError";
+ case ErrorCode::kDownloadNewPartitionInfoError:
+ return "ErrorCode::kDownloadNewPartitionInfoError";
+ case ErrorCode::kDownloadWriteError:
+ return "ErrorCode::kDownloadWriteError";
+ case ErrorCode::kNewRootfsVerificationError:
+ return "ErrorCode::kNewRootfsVerificationError";
+ case ErrorCode::kNewKernelVerificationError:
+ return "ErrorCode::kNewKernelVerificationError";
+ case ErrorCode::kSignedDeltaPayloadExpectedError:
+ return "ErrorCode::kSignedDeltaPayloadExpectedError";
+ case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+ return "ErrorCode::kDownloadPayloadPubKeyVerificationError";
+ case ErrorCode::kPostinstallBootedFromFirmwareB:
+ return "ErrorCode::kPostinstallBootedFromFirmwareB";
+ case ErrorCode::kDownloadStateInitializationError:
+ return "ErrorCode::kDownloadStateInitializationError";
+ case ErrorCode::kDownloadInvalidMetadataMagicString:
+ return "ErrorCode::kDownloadInvalidMetadataMagicString";
+ case ErrorCode::kDownloadSignatureMissingInManifest:
+ return "ErrorCode::kDownloadSignatureMissingInManifest";
+ case ErrorCode::kDownloadManifestParseError:
+ return "ErrorCode::kDownloadManifestParseError";
+ case ErrorCode::kDownloadMetadataSignatureError:
+ return "ErrorCode::kDownloadMetadataSignatureError";
+ case ErrorCode::kDownloadMetadataSignatureVerificationError:
+ return "ErrorCode::kDownloadMetadataSignatureVerificationError";
+ case ErrorCode::kDownloadMetadataSignatureMismatch:
+ return "ErrorCode::kDownloadMetadataSignatureMismatch";
+ case ErrorCode::kDownloadOperationHashVerificationError:
+ return "ErrorCode::kDownloadOperationHashVerificationError";
+ case ErrorCode::kDownloadOperationExecutionError:
+ return "ErrorCode::kDownloadOperationExecutionError";
+ case ErrorCode::kDownloadOperationHashMismatch:
+ return "ErrorCode::kDownloadOperationHashMismatch";
+ case ErrorCode::kOmahaRequestEmptyResponseError:
+ return "ErrorCode::kOmahaRequestEmptyResponseError";
+ case ErrorCode::kOmahaRequestXMLParseError:
+ return "ErrorCode::kOmahaRequestXMLParseError";
+ case ErrorCode::kDownloadInvalidMetadataSize:
+ return "ErrorCode::kDownloadInvalidMetadataSize";
+ case ErrorCode::kDownloadInvalidMetadataSignature:
+ return "ErrorCode::kDownloadInvalidMetadataSignature";
+ case ErrorCode::kOmahaResponseInvalid:
+ return "ErrorCode::kOmahaResponseInvalid";
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ return "ErrorCode::kOmahaUpdateIgnoredPerPolicy";
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ return "ErrorCode::kOmahaUpdateDeferredPerPolicy";
+ case ErrorCode::kOmahaErrorInHTTPResponse:
+ return "ErrorCode::kOmahaErrorInHTTPResponse";
+ case ErrorCode::kDownloadOperationHashMissingError:
+ return "ErrorCode::kDownloadOperationHashMissingError";
+ case ErrorCode::kDownloadMetadataSignatureMissingError:
+ return "ErrorCode::kDownloadMetadataSignatureMissingError";
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ return "ErrorCode::kOmahaUpdateDeferredForBackoff";
+ case ErrorCode::kPostinstallPowerwashError:
+ return "ErrorCode::kPostinstallPowerwashError";
+ case ErrorCode::kUpdateCanceledByChannelChange:
+ return "ErrorCode::kUpdateCanceledByChannelChange";
+ case ErrorCode::kUmaReportedMax:
+ return "ErrorCode::kUmaReportedMax";
+ case ErrorCode::kOmahaRequestHTTPResponseBase:
+ return "ErrorCode::kOmahaRequestHTTPResponseBase";
+ case ErrorCode::kResumedFlag:
+ return "Resumed";
+ case ErrorCode::kDevModeFlag:
+ return "DevMode";
+ case ErrorCode::kTestImageFlag:
+ return "TestImage";
+ case ErrorCode::kTestOmahaUrlFlag:
+ return "TestOmahaUrl";
+ case ErrorCode::kSpecialFlags:
+ return "ErrorCode::kSpecialFlags";
+ case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+ return "ErrorCode::kPostinstallFirmwareRONotUpdatable";
+ case ErrorCode::kUnsupportedMajorPayloadVersion:
+ return "ErrorCode::kUnsupportedMajorPayloadVersion";
+ case ErrorCode::kUnsupportedMinorPayloadVersion:
+ return "ErrorCode::kUnsupportedMinorPayloadVersion";
+ case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+ return "ErrorCode::kOmahaRequestXMLHasEntityDecl";
+ case ErrorCode::kFilesystemVerifierError:
+ return "ErrorCode::kFilesystemVerifierError";
+ // Don't add a default case to let the compiler warn about newly added
+ // error codes which should be added here.
+ }
+
+ return "Unknown error: " + base::UintToString(static_cast<unsigned>(code));
+}
+
+bool CreatePowerwashMarkerFile(const char* file_path) {
+ const char* marker_file = file_path ? file_path : kPowerwashMarkerFile;
+ bool result = utils::WriteFile(marker_file,
+ kPowerwashCommand,
+ strlen(kPowerwashCommand));
+ if (result) {
+ LOG(INFO) << "Created " << marker_file << " to powerwash on next reboot";
+ } else {
+ PLOG(ERROR) << "Error in creating powerwash marker file: " << marker_file;
+ }
+
+ return result;
+}
+
+bool DeletePowerwashMarkerFile(const char* file_path) {
+ const char* marker_file = file_path ? file_path : kPowerwashMarkerFile;
+ const base::FilePath kPowerwashMarkerPath(marker_file);
+ bool result = base::DeleteFile(kPowerwashMarkerPath, false);
+
+ if (result)
+ LOG(INFO) << "Successfully deleted the powerwash marker file : "
+ << marker_file;
+ else
+ PLOG(ERROR) << "Could not delete the powerwash marker file : "
+ << marker_file;
+
+ return result;
+}
+
+bool GetInstallDev(const string& boot_dev, string* install_dev) {
+ string disk_name;
+ int partition_num;
+ if (!SplitPartitionName(boot_dev, &disk_name, &partition_num))
+ return false;
+
+ // Right now, we just switch '3' and '5' partition numbers.
+ if (partition_num == 3) {
+ partition_num = 5;
+ } else if (partition_num == 5) {
+ partition_num = 3;
+ } else {
+ return false;
+ }
+
+ if (install_dev)
+ *install_dev = MakePartitionName(disk_name, partition_num);
+
+ return true;
+}
+
+Time TimeFromStructTimespec(struct timespec *ts) {
+ int64_t us = static_cast<int64_t>(ts->tv_sec) * Time::kMicrosecondsPerSecond +
+ static_cast<int64_t>(ts->tv_nsec) / Time::kNanosecondsPerMicrosecond;
+ return Time::UnixEpoch() + TimeDelta::FromMicroseconds(us);
+}
+
+string StringVectorToString(const vector<string> &vec_str) {
+ string str = "[";
+ for (vector<string>::const_iterator i = vec_str.begin();
+ i != vec_str.end(); ++i) {
+ if (i != vec_str.begin())
+ str += ", ";
+ str += '"';
+ str += *i;
+ str += '"';
+ }
+ str += "]";
+ return str;
+}
+
+string CalculateP2PFileId(const string& payload_hash, size_t payload_size) {
+ string encoded_hash = chromeos::data_encoding::Base64Encode(payload_hash);
+ return base::StringPrintf("cros_update_size_%zu_hash_%s",
+ payload_size,
+ encoded_hash.c_str());
+}
+
+bool DecodeAndStoreBase64String(const string& base64_encoded,
+ base::FilePath *out_path) {
+ chromeos::Blob contents;
+
+ out_path->clear();
+
+ if (base64_encoded.size() == 0) {
+ LOG(ERROR) << "Can't decode empty string.";
+ return false;
+ }
+
+ if (!chromeos::data_encoding::Base64Decode(base64_encoded, &contents) ||
+ contents.size() == 0) {
+ LOG(ERROR) << "Error decoding base64.";
+ return false;
+ }
+
+ FILE *file = base::CreateAndOpenTemporaryFile(out_path);
+ if (file == nullptr) {
+ LOG(ERROR) << "Error creating temporary file.";
+ return false;
+ }
+
+ if (fwrite(contents.data(), 1, contents.size(), file) != contents.size()) {
+ PLOG(ERROR) << "Error writing to temporary file.";
+ if (fclose(file) != 0)
+ PLOG(ERROR) << "Error closing temporary file.";
+ if (unlink(out_path->value().c_str()) != 0)
+ PLOG(ERROR) << "Error unlinking temporary file.";
+ out_path->clear();
+ return false;
+ }
+
+ if (fclose(file) != 0) {
+ PLOG(ERROR) << "Error closing temporary file.";
+ out_path->clear();
+ return false;
+ }
+
+ return true;
+}
+
+bool ConvertToOmahaInstallDate(Time time, int *out_num_days) {
+ time_t unix_time = time.ToTimeT();
+ // Output of: date +"%s" --date="Jan 1, 2007 0:00 PST".
+ const time_t kOmahaEpoch = 1167638400;
+ const int64_t kNumSecondsPerWeek = 7*24*3600;
+ const int64_t kNumDaysPerWeek = 7;
+
+ time_t omaha_time = unix_time - kOmahaEpoch;
+
+ if (omaha_time < 0)
+ return false;
+
+ // Note, as per the comment in utils.h we are deliberately not
+ // handling DST correctly.
+
+ int64_t num_weeks_since_omaha_epoch = omaha_time / kNumSecondsPerWeek;
+ *out_num_days = num_weeks_since_omaha_epoch * kNumDaysPerWeek;
+
+ return true;
+}
+
+bool WallclockDurationHelper(SystemState* system_state,
+ const string& state_variable_key,
+ TimeDelta* out_duration) {
+ bool ret = false;
+
+ Time now = system_state->clock()->GetWallclockTime();
+ int64_t stored_value;
+ if (system_state->prefs()->GetInt64(state_variable_key, &stored_value)) {
+ Time stored_time = Time::FromInternalValue(stored_value);
+ if (stored_time > now) {
+ LOG(ERROR) << "Stored time-stamp used for " << state_variable_key
+ << " is in the future.";
+ } else {
+ *out_duration = now - stored_time;
+ ret = true;
+ }
+ }
+
+ if (!system_state->prefs()->SetInt64(state_variable_key,
+ now.ToInternalValue())) {
+ LOG(ERROR) << "Error storing time-stamp in " << state_variable_key;
+ }
+
+ return ret;
+}
+
+bool MonotonicDurationHelper(SystemState* system_state,
+ int64_t* storage,
+ TimeDelta* out_duration) {
+ bool ret = false;
+
+ Time now = system_state->clock()->GetMonotonicTime();
+ if (*storage != 0) {
+ Time stored_time = Time::FromInternalValue(*storage);
+ *out_duration = now - stored_time;
+ ret = true;
+ }
+ *storage = now.ToInternalValue();
+
+ return ret;
+}
+
+bool GetMinorVersion(const chromeos::KeyValueStore& store,
+ uint32_t* minor_version) {
+ string result;
+ if (store.GetString("PAYLOAD_MINOR_VERSION", &result)) {
+ if (!base::StringToUint(result, minor_version)) {
+ LOG(ERROR) << "StringToUint failed when parsing delta minor version.";
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool ReadExtents(const string& path, const vector<Extent>& extents,
+ chromeos::Blob* out_data, ssize_t out_data_size,
+ size_t block_size) {
+ chromeos::Blob data(out_data_size);
+ ssize_t bytes_read = 0;
+ int fd = open(path.c_str(), O_RDONLY);
+ TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+ ScopedFdCloser fd_closer(&fd);
+
+ for (const Extent& extent : extents) {
+ ssize_t bytes_read_this_iteration = 0;
+ ssize_t bytes = extent.num_blocks() * block_size;
+ TEST_AND_RETURN_FALSE(bytes_read + bytes <= out_data_size);
+ TEST_AND_RETURN_FALSE(utils::PReadAll(fd,
+ &data[bytes_read],
+ bytes,
+ extent.start_block() * block_size,
+ &bytes_read_this_iteration));
+ TEST_AND_RETURN_FALSE(bytes_read_this_iteration == bytes);
+ bytes_read += bytes_read_this_iteration;
+ }
+ TEST_AND_RETURN_FALSE(out_data_size == bytes_read);
+ *out_data = data;
+ return true;
+}
+
+} // namespace utils
+
+} // namespace chromeos_update_engine
diff --git a/utils.h b/utils.h
new file mode 100644
index 0000000..b661b43
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,611 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_UTILS_H_
+#define UPDATE_ENGINE_UTILS_H_
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/time/time.h>
+#include <chromeos/secure_blob.h>
+#include <chromeos/key_value_store.h>
+#include "metrics/metrics_library.h"
+
+#include "update_engine/action.h"
+#include "update_engine/action_processor.h"
+#include "update_engine/connection_manager.h"
+#include "update_engine/constants.h"
+#include "update_engine/file_descriptor.h"
+#include "update_engine/metrics.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+namespace utils {
+
+// Converts a struct timespec representing a number of seconds since
+// the Unix epoch to a base::Time. Sub-microsecond time is rounded
+// down.
+base::Time TimeFromStructTimespec(struct timespec *ts);
+
+// Formats |vec_str| as a string of the form ["<elem1>", "<elem2>"].
+// Does no escaping, only use this for presentation in error messages.
+std::string StringVectorToString(const std::vector<std::string> &vec_str);
+
+// Calculates the p2p file id from payload hash and size
+std::string CalculateP2PFileId(const std::string& payload_hash,
+ size_t payload_size);
+
+// Parse the firmware version from one line of output from the
+// "mosys" command.
+std::string ParseECVersion(std::string input_line);
+
+// Given the name of the block device of a boot partition, return the
+// name of the associated kernel partition (e.g. given "/dev/sda3",
+// return "/dev/sda2").
+const std::string KernelDeviceOfBootDevice(const std::string& boot_device);
+
+// Writes the data passed to path. The file at path will be overwritten if it
+// exists. Returns true on success, false otherwise.
+bool WriteFile(const char* path, const void* data, int data_len);
+
+// Calls write() or pwrite() repeatedly until all count bytes at buf are
+// written to fd or an error occurs. Returns true on success.
+bool WriteAll(int fd, const void* buf, size_t count);
+bool PWriteAll(int fd, const void* buf, size_t count, off_t offset);
+
+bool WriteAll(FileDescriptorPtr fd, const void* buf, size_t count);
+bool PWriteAll(FileDescriptorPtr fd,
+ const void* buf,
+ size_t count,
+ off_t offset);
+
+// Calls pread() repeatedly until count bytes are read, or EOF is reached.
+// Returns number of bytes read in *bytes_read. Returns true on success.
+bool PReadAll(int fd, void* buf, size_t count, off_t offset,
+ ssize_t* out_bytes_read);
+
+bool PReadAll(FileDescriptorPtr fd, void* buf, size_t count, off_t offset,
+ ssize_t* out_bytes_read);
+
+// Opens |path| for reading and appends its entire content to the container
+// pointed to by |out_p|. Returns true upon successfully reading all of the
+// file's content, false otherwise, in which case the state of the output
+// container is unknown. ReadFileChunk starts reading the file from |offset|; if
+// |size| is not -1, only up to |size| bytes are read in.
+bool ReadFile(const std::string& path, chromeos::Blob* out_p);
+bool ReadFile(const std::string& path, std::string* out_p);
+bool ReadFileChunk(const std::string& path, off_t offset, off_t size,
+ chromeos::Blob* out_p);
+
+// Invokes |cmd| in a pipe and appends its stdout to the container pointed to by
+// |out_p|. Returns true upon successfully reading all of the output, false
+// otherwise, in which case the state of the output container is unknown.
+bool ReadPipe(const std::string& cmd, std::string* out_p);
+
+// Returns the size of the block device at path, or the file descriptor fd. If
+// an error occurs, -1 is returned.
+off_t BlockDevSize(const std::string& path);
+off_t BlockDevSize(int fd);
+
+// Returns the size of the file at path, or the file desciptor fd. If the file
+// is actually a block device, this function will automatically call
+// BlockDevSize. If the file doesn't exist or some error occurrs, -1 is
+// returned.
+off_t FileSize(const std::string& path);
+off_t FileSize(int fd);
+
+std::string ErrnoNumberAsString(int err);
+
+// Returns true if the file exists for sure. Returns false if it doesn't exist,
+// or an error occurs.
+bool FileExists(const char* path);
+
+// Returns true if |path| exists and is a symbolic link.
+bool IsSymlink(const char* path);
+
+// Try attaching UBI |volume_num|. If there is any error executing required
+// commands to attach the volume, this function returns false. This function
+// only returns true if "/dev/ubi%d_0" becomes available in |timeout| seconds.
+bool TryAttachingUbiVolume(int volume_num, int timeout);
+
+// If |base_filename_template| is neither absolute (starts with "/") nor
+// explicitly relative to the current working directory (starts with "./" or
+// "../"), then it is prepended the value of TMPDIR, which defaults to /tmp if
+// it isn't set or is empty. It then calls mkstemp(3) with the resulting
+// template. Writes the name of a new temporary file to |filename|. If |fd| is
+// non-null, the file descriptor returned by mkstemp is written to it and
+// kept open; otherwise, it is closed. The template must end with "XXXXXX".
+// Returns true on success.
+bool MakeTempFile(const std::string& base_filename_template,
+ std::string* filename,
+ int* fd);
+
+// If |base_filename_template| is neither absolute (starts with "/") nor
+// explicitly relative to the current working directory (starts with "./" or
+// "../"), then it is prepended the value of TMPDIR, which defaults to /tmp if
+// it isn't set or is empty. It then calls mkdtemp() with the resulting
+// template. Writes the name of the new temporary directory to |dirname|.
+// The template must end with "XXXXXX". Returns true on success.
+bool MakeTempDirectory(const std::string& base_dirname_template,
+ std::string* dirname);
+
+// Returns the disk device name for a partition. For example,
+// GetDiskName("/dev/sda3") returns "/dev/sda". Returns an empty string
+// if the input device is not of the "/dev/xyz#" form.
+std::string GetDiskName(const std::string& partition_name);
+
+// Returns the partition number, of partition device name. For example,
+// GetPartitionNumber("/dev/sda3") returns 3.
+// Returns 0 on failure
+int GetPartitionNumber(const std::string& partition_name);
+
+// Splits the partition device name into the block device name and partition
+// number. For example, "/dev/sda3" will be split into {"/dev/sda", 3} and
+// "/dev/mmcblk0p2" into {"/dev/mmcblk0", 2}
+// Returns false when malformed device name is passed in.
+// If both output parameters are omitted (null), can be used
+// just to test the validity of the device name. Note that the function
+// simply checks if the device name looks like a valid device, no other
+// checks are performed (i.e. it doesn't check if the device actually exists).
+bool SplitPartitionName(const std::string& partition_name,
+ std::string* out_disk_name,
+ int* out_partition_num);
+
+// Builds a partition device name from the block device name and partition
+// number. For example:
+// {"/dev/sda", 1} => "/dev/sda1"
+// {"/dev/mmcblk2", 12} => "/dev/mmcblk2p12"
+// Returns empty string when invalid parameters are passed in
+std::string MakePartitionName(const std::string& disk_name,
+ int partition_num);
+
+// Similar to "MakePartitionName" but returns a name that is suitable for
+// mounting. On NAND system we can write to "/dev/ubiX_0", which is what
+// MakePartitionName returns, but we cannot mount that device. To mount, we
+// have to use "/dev/ubiblockX_0" for rootfs. Stateful and OEM partitions are
+// mountable with "/dev/ubiX_0". The input is a partition device such as
+// /dev/sda3. Return empty string on error.
+std::string MakePartitionNameForMount(const std::string& part_name);
+
+// Returns the sysfs block device for a root block device. For
+// example, SysfsBlockDevice("/dev/sda") returns
+// "/sys/block/sda". Returns an empty string if the input device is
+// not of the "/dev/xyz" form.
+std::string SysfsBlockDevice(const std::string& device);
+
+// Returns true if the root |device| (e.g., "/dev/sdb") is known to be
+// removable, false otherwise.
+bool IsRemovableDevice(const std::string& device);
+
+// Synchronously mount or unmount a filesystem. Return true on success.
+// When mounting, it will attempt to mount the the device as "ext3", "ext2" and
+// "squashfs", with the passed |flags| options.
+bool MountFilesystem(const std::string& device, const std::string& mountpoint,
+ unsigned long flags); // NOLINT(runtime/int)
+bool UnmountFilesystem(const std::string& mountpoint);
+
+// Returns the block count and the block byte size of the file system on
+// |device| (which may be a real device or a path to a filesystem image) or on
+// an opened file descriptor |fd|. The actual file-system size is |block_count|
+// * |block_size| bytes. Returns true on success, false otherwise.
+bool GetFilesystemSize(const std::string& device,
+ int* out_block_count,
+ int* out_block_size);
+bool GetFilesystemSizeFromFD(int fd,
+ int* out_block_count,
+ int* out_block_size);
+
+// Determines the block count and block size of the ext3 fs. At least 2048 bytes
+// are required to parse the first superblock. Returns whether the buffer
+// contains a valid ext3 filesystem and the values were parsed.
+bool GetExt3Size(const uint8_t* buffer, size_t buffer_size,
+ int* out_block_count,
+ int* out_block_size);
+
+// Determines the block count and block size of the squashfs v4 fs. At least 96
+// bytes are required to parse the header of the filesystem. Since squashfs
+// doesn't define a physical block size, a value of 4096 is used for the block
+// size, which is the default padding when creating the filesystem.
+// Returns whether the buffer contains a valid squashfs v4 header and the size
+// was parsed. Only little endian squashfs is supported.
+bool GetSquashfs4Size(const uint8_t* buffer, size_t buffer_size,
+ int* out_block_count,
+ int* out_block_size);
+
+// Returns whether the filesystem is an ext[234] filesystem. In case of failure,
+// such as if the file |device| doesn't exists or can't be read, it returns
+// false.
+bool IsExtFilesystem(const std::string& device);
+
+// Returns whether the filesystem is a squashfs filesystem. In case of failure,
+// such as if the file |device| doesn't exists or can't be read, it returns
+// false.
+bool IsSquashfsFilesystem(const std::string& device);
+
+// Returns a human-readable string with the file format based on magic constants
+// on the header of the file.
+std::string GetFileFormat(const std::string& path);
+
+// Returns the string representation of the given UTC time.
+// such as "11/14/2011 14:05:30 GMT".
+std::string ToString(const base::Time utc_time);
+
+// Returns true or false depending on the value of b.
+std::string ToString(bool b);
+
+// Returns a string representation of the given enum.
+std::string ToString(DownloadSource source);
+
+// Returns a string representation of the given enum.
+std::string ToString(PayloadType payload_type);
+
+// Schedules a Main Loop callback to trigger the crash reporter to perform an
+// upload as if this process had crashed.
+void ScheduleCrashReporterUpload();
+
+// Fuzzes an integer |value| randomly in the range:
+// [value - range / 2, value + range - range / 2]
+int FuzzInt(int value, unsigned int range);
+
+// Log a string in hex to LOG(INFO). Useful for debugging.
+void HexDumpArray(const uint8_t* const arr, const size_t length);
+inline void HexDumpString(const std::string& str) {
+ HexDumpArray(reinterpret_cast<const uint8_t*>(str.data()), str.size());
+}
+inline void HexDumpVector(const chromeos::Blob& vect) {
+ HexDumpArray(vect.data(), vect.size());
+}
+
+template<typename KeyType, typename ValueType>
+bool MapContainsKey(const std::map<KeyType, ValueType>& m, const KeyType& k) {
+ return m.find(k) != m.end();
+}
+template<typename KeyType>
+bool SetContainsKey(const std::set<KeyType>& s, const KeyType& k) {
+ return s.find(k) != s.end();
+}
+
+template<typename T>
+bool VectorContainsValue(const std::vector<T>& vect, const T& value) {
+ return std::find(vect.begin(), vect.end(), value) != vect.end();
+}
+
+template<typename T>
+bool VectorIndexOf(const std::vector<T>& vect, const T& value,
+ typename std::vector<T>::size_type* out_index) {
+ typename std::vector<T>::const_iterator it = std::find(vect.begin(),
+ vect.end(),
+ value);
+ if (it == vect.end()) {
+ return false;
+ } else {
+ *out_index = it - vect.begin();
+ return true;
+ }
+}
+
+// Cgroups cpu shares constants. 1024 is the default shares a standard process
+// gets and 2 is the minimum value. We set High as a value that gives the
+// update-engine 2x the cpu share of a standard process.
+enum CpuShares {
+ kCpuSharesHigh = 2048,
+ kCpuSharesNormal = 1024,
+ kCpuSharesLow = 2,
+};
+
+// Sets the current process shares to |shares|. Returns true on
+// success, false otherwise.
+bool SetCpuShares(CpuShares shares);
+
+// Converts seconds into human readable notation including days, hours, minutes
+// and seconds. For example, 185 will yield 3m5s, 4300 will yield 1h11m40s, and
+// 360000 will yield 4d4h0m0s. Zero padding not applied. Seconds are always
+// shown in the result.
+std::string FormatSecs(unsigned secs);
+
+// Converts a TimeDelta into human readable notation including days, hours,
+// minutes, seconds and fractions of a second down to microsecond granularity,
+// as necessary; for example, an output of 5d2h0m15.053s means that the input
+// time was precise to the milliseconds only. Zero padding not applied, except
+// for fractions. Seconds are always shown, but fractions thereof are only shown
+// when applicable. If |delta| is negative, the output will have a leading '-'
+// followed by the absolute duration.
+std::string FormatTimeDelta(base::TimeDelta delta);
+
+// This method transforms the given error code to be suitable for UMA and
+// for error classification purposes by removing the higher order bits and
+// aggregating error codes beyond the enum range, etc. This method is
+// idempotent, i.e. if called with a value previously returned by this method,
+// it'll return the same value again.
+ErrorCode GetBaseErrorCode(ErrorCode code);
+
+// Transforms a ErrorCode value into a metrics::DownloadErrorCode.
+// This obviously only works for errors related to downloading so if |code|
+// is e.g. |ErrorCode::kFilesystemCopierError| then
+// |kDownloadErrorCodeInputMalformed| is returned.
+metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code);
+
+// Transforms a ErrorCode value into a metrics::AttemptResult.
+//
+// If metrics::AttemptResult::kPayloadDownloadError is returned, you
+// can use utils::GetDownloadError() to get more detail.
+metrics::AttemptResult GetAttemptResult(ErrorCode code);
+
+// Calculates the internet connection type given |type| and |tethering|.
+metrics::ConnectionType GetConnectionType(NetworkConnectionType type,
+ NetworkTethering tethering);
+
+// Sends the error code to UMA using the metrics interface object in the given
+// system state. It also uses the system state to determine the right UMA
+// bucket for the error code.
+void SendErrorCodeToUma(SystemState* system_state, ErrorCode code);
+
+// Returns a string representation of the ErrorCodes (either the base
+// error codes or the bit flags) for logging purposes.
+std::string CodeToString(ErrorCode code);
+
+// Creates the powerwash marker file with the appropriate commands in it. Uses
+// |file_path| as the path to the marker file if non-null, otherwise uses the
+// global default. Returns true if successfully created. False otherwise.
+bool CreatePowerwashMarkerFile(const char* file_path);
+
+// Deletes the marker file used to trigger Powerwash using clobber-state. Uses
+// |file_path| as the path to the marker file if non-null, otherwise uses the
+// global default. Returns true if successfully deleted. False otherwise.
+bool DeletePowerwashMarkerFile(const char* file_path);
+
+// Assumes you want to install on the "other" device, where the other
+// device is what you get if you swap 1 for 2 or 3 for 4 or vice versa
+// for the number at the end of the boot device. E.g., /dev/sda1 -> /dev/sda2
+// or /dev/sda4 -> /dev/sda3. See
+// http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
+bool GetInstallDev(const std::string& boot_dev, std::string* install_dev);
+
+// Decodes the data in |base64_encoded| and stores it in a temporary
+// file. Returns false if the given data is empty, not well-formed
+// base64 or if an error occurred. If true is returned, the decoded
+// data is stored in the file returned in |out_path|. The file should
+// be deleted when no longer needed.
+bool DecodeAndStoreBase64String(const std::string& base64_encoded,
+ base::FilePath *out_path);
+
+// Converts |time| to an Omaha InstallDate which is defined as "the
+// number of PST8PDT calendar weeks since Jan 1st 2007 0:00 PST, times
+// seven" with PST8PDT defined as "Pacific Time" (e.g. UTC-07:00 if
+// daylight savings is observed and UTC-08:00 otherwise.)
+//
+// If the passed in |time| variable is before Monday January 1st 2007
+// 0:00 PST, False is returned and the value returned in
+// |out_num_days| is undefined. Otherwise the number of PST8PDT
+// calendar weeks since that date times seven is returned in
+// |out_num_days| and the function returns True.
+//
+// (NOTE: This function does not currently take daylight savings time
+// into account so the result may up to one hour off. This is because
+// the glibc date and timezone routines depend on the TZ environment
+// variable and changing environment variables is not thread-safe. An
+// alternative, thread-safe API to use would be GDateTime/GTimeZone
+// available in GLib 2.26 or later.)
+bool ConvertToOmahaInstallDate(base::Time time, int *out_num_days);
+
+// This function returns the duration on the wallclock since the last
+// time it was called for the same |state_variable_key| value.
+//
+// If the function returns |true|, the duration (always non-negative)
+// is returned in |out_duration|. If the function returns |false|
+// something went wrong or there was no previous measurement.
+bool WallclockDurationHelper(SystemState* system_state,
+ const std::string& state_variable_key,
+ base::TimeDelta* out_duration);
+
+// This function returns the duration on the monotonic clock since the
+// last time it was called for the same |storage| pointer.
+//
+// You should pass a pointer to a 64-bit integer in |storage| which
+// should be initialized to 0.
+//
+// If the function returns |true|, the duration (always non-negative)
+// is returned in |out_duration|. If the function returns |false|
+// something went wrong or there was no previous measurement.
+bool MonotonicDurationHelper(SystemState* system_state,
+ int64_t* storage,
+ base::TimeDelta* out_duration);
+
+// Look for the minor version value in the passed |store| and set
+// |minor_version| to that value. Return whether the value was found and valid.
+bool GetMinorVersion(const chromeos::KeyValueStore& store,
+ uint32_t* minor_version);
+
+// This function reads the specified data in |extents| into |out_data|. The
+// extents are read from the file at |path|. |out_data_size| is the size of
+// |out_data|. Returns false if the number of bytes to read given in
+// |extents| does not equal |out_data_size|.
+bool ReadExtents(const std::string& path, const std::vector<Extent>& extents,
+ chromeos::Blob* out_data, ssize_t out_data_size,
+ size_t block_size);
+
+} // namespace utils
+
+
+// Utility class to close a file descriptor
+class ScopedFdCloser {
+ public:
+ explicit ScopedFdCloser(int* fd) : fd_(fd), should_close_(true) {}
+ ~ScopedFdCloser() {
+ if (should_close_ && fd_ && (*fd_ >= 0)) {
+ if (!close(*fd_))
+ *fd_ = -1;
+ }
+ }
+ void set_should_close(bool should_close) { should_close_ = should_close; }
+ private:
+ int* fd_;
+ bool should_close_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedFdCloser);
+};
+
+// An EINTR-immune file descriptor closer.
+class ScopedEintrSafeFdCloser {
+ public:
+ explicit ScopedEintrSafeFdCloser(int* fd) : fd_(fd), should_close_(true) {}
+ ~ScopedEintrSafeFdCloser() {
+ if (should_close_ && fd_ && (*fd_ >= 0)) {
+ if (!IGNORE_EINTR(close(*fd_)))
+ *fd_ = -1;
+ }
+ }
+ void set_should_close(bool should_close) { should_close_ = should_close; }
+ private:
+ int* fd_;
+ bool should_close_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedEintrSafeFdCloser);
+};
+
+// Utility class to delete a file when it goes out of scope.
+class ScopedPathUnlinker {
+ public:
+ explicit ScopedPathUnlinker(const std::string& path)
+ : path_(path),
+ should_remove_(true) {}
+ ~ScopedPathUnlinker() {
+ if (should_remove_ && unlink(path_.c_str()) < 0) {
+ PLOG(ERROR) << "Unable to unlink path " << path_;
+ }
+ }
+ void set_should_remove(bool should_remove) { should_remove_ = should_remove; }
+
+ private:
+ const std::string path_;
+ bool should_remove_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedPathUnlinker);
+};
+
+// Utility class to delete an empty directory when it goes out of scope.
+class ScopedDirRemover {
+ public:
+ explicit ScopedDirRemover(const std::string& path)
+ : path_(path),
+ should_remove_(true) {}
+ ~ScopedDirRemover() {
+ if (should_remove_ && (rmdir(path_.c_str()) < 0)) {
+ PLOG(ERROR) << "Unable to remove dir " << path_;
+ }
+ }
+ void set_should_remove(bool should_remove) { should_remove_ = should_remove; }
+
+ protected:
+ const std::string path_;
+
+ private:
+ bool should_remove_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedDirRemover);
+};
+
+// Utility class to unmount a filesystem mounted on a temporary directory and
+// delete the temporary directory when it goes out of scope.
+class ScopedTempUnmounter : public ScopedDirRemover {
+ public:
+ explicit ScopedTempUnmounter(const std::string& path) :
+ ScopedDirRemover(path) {}
+ ~ScopedTempUnmounter() {
+ utils::UnmountFilesystem(path_);
+ }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedTempUnmounter);
+};
+
+// A little object to call ActionComplete on the ActionProcessor when
+// it's destructed.
+class ScopedActionCompleter {
+ public:
+ explicit ScopedActionCompleter(ActionProcessor* processor,
+ AbstractAction* action)
+ : processor_(processor),
+ action_(action),
+ code_(ErrorCode::kError),
+ should_complete_(true) {}
+ ~ScopedActionCompleter() {
+ if (should_complete_)
+ processor_->ActionComplete(action_, code_);
+ }
+ void set_code(ErrorCode code) { code_ = code; }
+ void set_should_complete(bool should_complete) {
+ should_complete_ = should_complete;
+ }
+ ErrorCode get_code() const { return code_; }
+
+ private:
+ ActionProcessor* processor_;
+ AbstractAction* action_;
+ ErrorCode code_;
+ bool should_complete_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedActionCompleter);
+};
+
+} // namespace chromeos_update_engine
+
+#define TEST_AND_RETURN_FALSE_ERRNO(_x) \
+ do { \
+ bool _success = static_cast<bool>(_x); \
+ if (!_success) { \
+ std::string _msg = \
+ chromeos_update_engine::utils::ErrnoNumberAsString(errno); \
+ LOG(ERROR) << #_x " failed: " << _msg; \
+ return false; \
+ } \
+ } while (0)
+
+#define TEST_AND_RETURN_FALSE(_x) \
+ do { \
+ bool _success = static_cast<bool>(_x); \
+ if (!_success) { \
+ LOG(ERROR) << #_x " failed."; \
+ return false; \
+ } \
+ } while (0)
+
+#define TEST_AND_RETURN_ERRNO(_x) \
+ do { \
+ bool _success = static_cast<bool>(_x); \
+ if (!_success) { \
+ std::string _msg = \
+ chromeos_update_engine::utils::ErrnoNumberAsString(errno); \
+ LOG(ERROR) << #_x " failed: " << _msg; \
+ return; \
+ } \
+ } while (0)
+
+#define TEST_AND_RETURN(_x) \
+ do { \
+ bool _success = static_cast<bool>(_x); \
+ if (!_success) { \
+ LOG(ERROR) << #_x " failed."; \
+ return; \
+ } \
+ } while (0)
+
+#define TEST_AND_RETURN_FALSE_ERRCODE(_x) \
+ do { \
+ errcode_t _error = (_x); \
+ if (_error) { \
+ errno = _error; \
+ LOG(ERROR) << #_x " failed: " << _error; \
+ return false; \
+ } \
+ } while (0)
+
+#endif // UPDATE_ENGINE_UTILS_H_
diff --git a/utils_unittest.cc b/utils_unittest.cc
new file mode 100644
index 0000000..4787e84
--- /dev/null
+++ b/utils_unittest.cc
@@ -0,0 +1,796 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/utils.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_prefs.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/prefs.h"
+#include "update_engine/test_utils.h"
+
+using chromeos::FakeMessageLoop;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class UtilsTest : public ::testing::Test { };
+
+TEST(UtilsTest, CanParseECVersion) {
+ // Should be able to parse and valid key value line.
+ EXPECT_EQ("12345", utils::ParseECVersion("fw_version=12345"));
+ EXPECT_EQ("123456", utils::ParseECVersion(
+ "b=1231a fw_version=123456 a=fasd2"));
+ EXPECT_EQ("12345", utils::ParseECVersion("fw_version=12345"));
+ EXPECT_EQ("00VFA616", utils::ParseECVersion(
+ "vendor=\"sam\" fw_version=\"00VFA616\""));
+
+ // For invalid entries, should return the empty string.
+ EXPECT_EQ("", utils::ParseECVersion("b=1231a fw_version a=fasd2"));
+}
+
+
+TEST(UtilsTest, KernelDeviceOfBootDevice) {
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice(""));
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice("foo"));
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda0"));
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda1"));
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda2"));
+ EXPECT_EQ("/dev/sda2", utils::KernelDeviceOfBootDevice("/dev/sda3"));
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda4"));
+ EXPECT_EQ("/dev/sda4", utils::KernelDeviceOfBootDevice("/dev/sda5"));
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda6"));
+ EXPECT_EQ("/dev/sda6", utils::KernelDeviceOfBootDevice("/dev/sda7"));
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda8"));
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda9"));
+
+ EXPECT_EQ("/dev/mmcblk0p2",
+ utils::KernelDeviceOfBootDevice("/dev/mmcblk0p3"));
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/mmcblk0p4"));
+
+ EXPECT_EQ("/dev/mtd2", utils::KernelDeviceOfBootDevice("/dev/ubi3"));
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/ubi4"));
+
+ EXPECT_EQ("/dev/mtd2",
+ utils::KernelDeviceOfBootDevice("/dev/ubiblock3_0"));
+ EXPECT_EQ("/dev/mtd4",
+ utils::KernelDeviceOfBootDevice("/dev/ubiblock5_0"));
+ EXPECT_EQ("/dev/mtd6",
+ utils::KernelDeviceOfBootDevice("/dev/ubiblock7_0"));
+ EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/ubiblock4_0"));
+}
+
+TEST(UtilsTest, ReadFileFailure) {
+ chromeos::Blob empty;
+ EXPECT_FALSE(utils::ReadFile("/this/doesn't/exist", &empty));
+}
+
+TEST(UtilsTest, ReadFileChunk) {
+ base::FilePath file;
+ EXPECT_TRUE(base::CreateTemporaryFile(&file));
+ ScopedPathUnlinker unlinker(file.value());
+ chromeos::Blob data;
+ const size_t kSize = 1024 * 1024;
+ for (size_t i = 0; i < kSize; i++) {
+ data.push_back(i % 255);
+ }
+ EXPECT_TRUE(utils::WriteFile(file.value().c_str(), data.data(), data.size()));
+ chromeos::Blob in_data;
+ EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), kSize, 10, &in_data));
+ EXPECT_TRUE(in_data.empty());
+ EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), 0, -1, &in_data));
+ EXPECT_TRUE(data == in_data);
+ in_data.clear();
+ EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), 10, 20, &in_data));
+ EXPECT_TRUE(chromeos::Blob(data.begin() + 10, data.begin() + 10 + 20) ==
+ in_data);
+}
+
+TEST(UtilsTest, ErrnoNumberAsStringTest) {
+ EXPECT_EQ("No such file or directory", utils::ErrnoNumberAsString(ENOENT));
+}
+
+TEST(UtilsTest, IsSymlinkTest) {
+ string temp_dir;
+ EXPECT_TRUE(utils::MakeTempDirectory("symlink-test.XXXXXX", &temp_dir));
+ string temp_file = temp_dir + "/temp-file";
+ EXPECT_TRUE(utils::WriteFile(temp_file.c_str(), "", 0));
+ string temp_symlink = temp_dir + "/temp-symlink";
+ EXPECT_EQ(0, symlink(temp_file.c_str(), temp_symlink.c_str()));
+ EXPECT_FALSE(utils::IsSymlink(temp_dir.c_str()));
+ EXPECT_FALSE(utils::IsSymlink(temp_file.c_str()));
+ EXPECT_TRUE(utils::IsSymlink(temp_symlink.c_str()));
+ EXPECT_FALSE(utils::IsSymlink("/non/existent/path"));
+ EXPECT_TRUE(test_utils::RecursiveUnlinkDir(temp_dir));
+}
+
+TEST(UtilsTest, GetDiskNameTest) {
+ EXPECT_EQ("/dev/sda", utils::GetDiskName("/dev/sda3"));
+ EXPECT_EQ("/dev/sdp", utils::GetDiskName("/dev/sdp1234"));
+ EXPECT_EQ("/dev/mmcblk0", utils::GetDiskName("/dev/mmcblk0p3"));
+ EXPECT_EQ("", utils::GetDiskName("/dev/mmcblk0p"));
+ EXPECT_EQ("", utils::GetDiskName("/dev/sda"));
+ EXPECT_EQ("/dev/ubiblock", utils::GetDiskName("/dev/ubiblock3_2"));
+ EXPECT_EQ("", utils::GetDiskName("/dev/foo/bar"));
+ EXPECT_EQ("", utils::GetDiskName("/"));
+ EXPECT_EQ("", utils::GetDiskName(""));
+}
+
+TEST(UtilsTest, SysfsBlockDeviceTest) {
+ EXPECT_EQ("/sys/block/sda", utils::SysfsBlockDevice("/dev/sda"));
+ EXPECT_EQ("", utils::SysfsBlockDevice("/foo/sda"));
+ EXPECT_EQ("", utils::SysfsBlockDevice("/dev/foo/bar"));
+ EXPECT_EQ("", utils::SysfsBlockDevice("/"));
+ EXPECT_EQ("", utils::SysfsBlockDevice("./"));
+ EXPECT_EQ("", utils::SysfsBlockDevice(""));
+}
+
+TEST(UtilsTest, IsRemovableDeviceTest) {
+ EXPECT_FALSE(utils::IsRemovableDevice(""));
+ EXPECT_FALSE(utils::IsRemovableDevice("/dev/non-existent-device"));
+}
+
+TEST(UtilsTest, GetPartitionNumberTest) {
+ EXPECT_EQ(3, utils::GetPartitionNumber("/dev/sda3"));
+ EXPECT_EQ(3, utils::GetPartitionNumber("/dev/sdz3"));
+ EXPECT_EQ(123, utils::GetPartitionNumber("/dev/sda123"));
+ EXPECT_EQ(2, utils::GetPartitionNumber("/dev/mmcblk0p2"));
+ EXPECT_EQ(0, utils::GetPartitionNumber("/dev/mmcblk0p"));
+ EXPECT_EQ(3, utils::GetPartitionNumber("/dev/ubiblock3_2"));
+ EXPECT_EQ(0, utils::GetPartitionNumber(""));
+ EXPECT_EQ(0, utils::GetPartitionNumber("/"));
+ EXPECT_EQ(0, utils::GetPartitionNumber("/dev/"));
+ EXPECT_EQ(0, utils::GetPartitionNumber("/dev/sda"));
+ EXPECT_EQ(10, utils::GetPartitionNumber("/dev/loop10"));
+ EXPECT_EQ(11, utils::GetPartitionNumber("/dev/loop28p11"));
+ EXPECT_EQ(10, utils::GetPartitionNumber("/dev/loop10_0"));
+ EXPECT_EQ(11, utils::GetPartitionNumber("/dev/loop28p11_0"));
+}
+
+TEST(UtilsTest, MakePartitionNameTest) {
+ EXPECT_EQ("/dev/sda4", utils::MakePartitionName("/dev/sda", 4));
+ EXPECT_EQ("/dev/sda123", utils::MakePartitionName("/dev/sda", 123));
+ EXPECT_EQ("/dev/mmcblk2", utils::MakePartitionName("/dev/mmcblk", 2));
+ EXPECT_EQ("/dev/mmcblk0p2", utils::MakePartitionName("/dev/mmcblk0", 2));
+ EXPECT_EQ("/dev/loop8", utils::MakePartitionName("/dev/loop", 8));
+ EXPECT_EQ("/dev/loop12p2", utils::MakePartitionName("/dev/loop12", 2));
+ EXPECT_EQ("/dev/ubi5_0", utils::MakePartitionName("/dev/ubiblock", 5));
+ EXPECT_EQ("/dev/mtd4", utils::MakePartitionName("/dev/ubiblock", 4));
+ EXPECT_EQ("/dev/ubi3_0", utils::MakePartitionName("/dev/ubiblock", 3));
+ EXPECT_EQ("/dev/mtd2", utils::MakePartitionName("/dev/ubiblock", 2));
+ EXPECT_EQ("/dev/ubi1_0", utils::MakePartitionName("/dev/ubiblock", 1));
+}
+
+TEST(UtilsTest, MakePartitionNameForMountTest) {
+ EXPECT_EQ("/dev/sda4", utils::MakePartitionNameForMount("/dev/sda4"));
+ EXPECT_EQ("/dev/sda123", utils::MakePartitionNameForMount("/dev/sda123"));
+ EXPECT_EQ("/dev/mmcblk2", utils::MakePartitionNameForMount("/dev/mmcblk2"));
+ EXPECT_EQ("/dev/mmcblk0p2",
+ utils::MakePartitionNameForMount("/dev/mmcblk0p2"));
+ EXPECT_EQ("/dev/loop0", utils::MakePartitionNameForMount("/dev/loop0"));
+ EXPECT_EQ("/dev/loop8", utils::MakePartitionNameForMount("/dev/loop8"));
+ EXPECT_EQ("/dev/loop12p2",
+ utils::MakePartitionNameForMount("/dev/loop12p2"));
+ EXPECT_EQ("/dev/ubiblock5_0",
+ utils::MakePartitionNameForMount("/dev/ubiblock5_0"));
+ EXPECT_EQ("/dev/mtd4",
+ utils::MakePartitionNameForMount("/dev/ubi4_0"));
+ EXPECT_EQ("/dev/ubiblock3_0",
+ utils::MakePartitionNameForMount("/dev/ubiblock3"));
+ EXPECT_EQ("/dev/mtd2", utils::MakePartitionNameForMount("/dev/ubi2"));
+ EXPECT_EQ("/dev/ubi1_0",
+ utils::MakePartitionNameForMount("/dev/ubiblock1"));
+}
+
+namespace {
+// Compares cpu shares and returns an integer that is less
+// than, equal to or greater than 0 if |shares_lhs| is,
+// respectively, lower than, same as or higher than |shares_rhs|.
+int CompareCpuShares(utils::CpuShares shares_lhs,
+ utils::CpuShares shares_rhs) {
+ return static_cast<int>(shares_lhs) - static_cast<int>(shares_rhs);
+}
+} // namespace
+
+// Tests the CPU shares enum is in the order we expect it.
+TEST(UtilsTest, CompareCpuSharesTest) {
+ EXPECT_LT(CompareCpuShares(utils::kCpuSharesLow,
+ utils::kCpuSharesNormal), 0);
+ EXPECT_GT(CompareCpuShares(utils::kCpuSharesNormal,
+ utils::kCpuSharesLow), 0);
+ EXPECT_EQ(CompareCpuShares(utils::kCpuSharesNormal,
+ utils::kCpuSharesNormal), 0);
+ EXPECT_GT(CompareCpuShares(utils::kCpuSharesHigh,
+ utils::kCpuSharesNormal), 0);
+}
+
+TEST(UtilsTest, FuzzIntTest) {
+ static const unsigned int kRanges[] = { 0, 1, 2, 20 };
+ for (unsigned int range : kRanges) {
+ const int kValue = 50;
+ for (int tries = 0; tries < 100; ++tries) {
+ int value = utils::FuzzInt(kValue, range);
+ EXPECT_GE(value, kValue - range / 2);
+ EXPECT_LE(value, kValue + range - range / 2);
+ }
+ }
+}
+
+TEST(UtilsTest, RunAsRootGetFilesystemSizeTest) {
+ string img;
+ EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
+ ScopedPathUnlinker img_unlinker(img);
+ test_utils::CreateExtImageAtPath(img, nullptr);
+ // Extend the "partition" holding the file system from 10MiB to 20MiB.
+ EXPECT_EQ(0, test_utils::System(base::StringPrintf(
+ "dd if=/dev/zero of=%s seek=20971519 bs=1 count=1 status=none",
+ img.c_str())));
+ EXPECT_EQ(20 * 1024 * 1024, utils::FileSize(img));
+ int block_count = 0;
+ int block_size = 0;
+ EXPECT_TRUE(utils::GetFilesystemSize(img, &block_count, &block_size));
+ EXPECT_EQ(4096, block_size);
+ EXPECT_EQ(10 * 1024 * 1024 / 4096, block_count);
+}
+
+// Squashfs example filesystem, generated with:
+// echo hola>hola
+// mksquashfs hola hola.sqfs -noappend -nopad
+// hexdump hola.sqfs -e '16/1 "%02x, " "\n"'
+const uint8_t kSquashfsFile[] = {
+ 0x68, 0x73, 0x71, 0x73, 0x02, 0x00, 0x00, 0x00, // magic, inodes
+ 0x3e, 0x49, 0x61, 0x54, 0x00, 0x00, 0x02, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x00,
+ 0xc0, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, // flags, noids, major, minor
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // root_inode
+ 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes_used
+ 0xe7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x68, 0x6f, 0x6c, 0x61, 0x0a, 0x2c, 0x00, 0x78,
+ 0xda, 0x63, 0x62, 0x58, 0xc2, 0xc8, 0xc0, 0xc0,
+ 0xc8, 0xd0, 0x6b, 0x91, 0x18, 0x02, 0x64, 0xa0,
+ 0x00, 0x56, 0x06, 0x90, 0xcc, 0x7f, 0xb0, 0xbc,
+ 0x9d, 0x67, 0x62, 0x08, 0x13, 0x54, 0x1c, 0x44,
+ 0x4b, 0x03, 0x31, 0x33, 0x10, 0x03, 0x00, 0xb5,
+ 0x87, 0x04, 0x89, 0x16, 0x00, 0x78, 0xda, 0x63,
+ 0x60, 0x80, 0x00, 0x46, 0x28, 0xcd, 0xc4, 0xc0,
+ 0xcc, 0x90, 0x91, 0x9f, 0x93, 0x08, 0x00, 0x04,
+ 0x70, 0x01, 0xab, 0x10, 0x80, 0x60, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x78,
+ 0xda, 0x63, 0x60, 0x80, 0x00, 0x05, 0x28, 0x0d,
+ 0x00, 0x01, 0x10, 0x00, 0x21, 0xc5, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x99,
+ 0xcd, 0x02, 0x00, 0x88, 0x13, 0x00, 0x00, 0xdd,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+TEST(UtilsTest, GetSquashfs4Size) {
+ uint8_t buffer[sizeof(kSquashfsFile)];
+ memcpy(buffer, kSquashfsFile, sizeof(kSquashfsFile));
+
+ int block_count = -1;
+ int block_size = -1;
+ // Not enough bytes passed.
+ EXPECT_FALSE(utils::GetSquashfs4Size(buffer, 10, nullptr, nullptr));
+
+ // The whole file system is passed, which is enough for parsing.
+ EXPECT_TRUE(utils::GetSquashfs4Size(buffer, sizeof(kSquashfsFile),
+ &block_count, &block_size));
+ EXPECT_EQ(4096, block_size);
+ EXPECT_EQ(1, block_count);
+
+ // Modify the major version to 5.
+ uint16_t* s_major = reinterpret_cast<uint16_t*>(buffer + 0x1c);
+ *s_major = 5;
+ EXPECT_FALSE(utils::GetSquashfs4Size(buffer, 10, nullptr, nullptr));
+ memcpy(buffer, kSquashfsFile, sizeof(kSquashfsFile));
+
+ // Modify the bytes_used to have 6 blocks.
+ int64_t* bytes_used = reinterpret_cast<int64_t*>(buffer + 0x28);
+ *bytes_used = 4096 * 5 + 1; // 6 "blocks".
+ EXPECT_TRUE(utils::GetSquashfs4Size(buffer, sizeof(kSquashfsFile),
+ &block_count, &block_size));
+ EXPECT_EQ(4096, block_size);
+ EXPECT_EQ(6, block_count);
+}
+
+TEST(UtilsTest, GetInstallDevTest) {
+ string boot_dev = "/dev/sda5";
+ string install_dev;
+ EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
+ EXPECT_EQ(install_dev, "/dev/sda3");
+
+ boot_dev = "/dev/sda3";
+ EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
+ EXPECT_EQ(install_dev, "/dev/sda5");
+
+ boot_dev = "/dev/sda12";
+ EXPECT_FALSE(utils::GetInstallDev(boot_dev, &install_dev));
+
+ boot_dev = "/dev/ubiblock3_0";
+ EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
+ EXPECT_EQ(install_dev, "/dev/ubi5_0");
+
+ boot_dev = "/dev/ubiblock5_0";
+ EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
+ EXPECT_EQ(install_dev, "/dev/ubi3_0");
+
+ boot_dev = "/dev/ubiblock12_0";
+ EXPECT_FALSE(utils::GetInstallDev(boot_dev, &install_dev));
+}
+
+namespace {
+void GetFileFormatTester(const string& expected,
+ const vector<uint8_t>& contents) {
+ test_utils::ScopedTempFile file;
+ ASSERT_TRUE(utils::WriteFile(file.GetPath().c_str(),
+ reinterpret_cast<const char*>(contents.data()),
+ contents.size()));
+ EXPECT_EQ(expected, utils::GetFileFormat(file.GetPath()));
+}
+} // namespace
+
+TEST(UtilsTest, GetFileFormatTest) {
+ EXPECT_EQ("File not found.", utils::GetFileFormat("/path/to/nowhere"));
+ GetFileFormatTester("data", vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8});
+ GetFileFormatTester("ELF", vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46});
+
+ // Real tests from cros_installer on different boards.
+ // ELF 32-bit LSB executable, Intel 80386
+ GetFileFormatTester(
+ "ELF 32-bit little-endian x86",
+ vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x90, 0x83, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00});
+
+ // ELF 32-bit LSB executable, MIPS
+ GetFileFormatTester(
+ "ELF 32-bit little-endian mips",
+ vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xc0, 0x12, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00});
+
+ // ELF 32-bit LSB executable, ARM
+ GetFileFormatTester(
+ "ELF 32-bit little-endian arm",
+ vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x85, 0x8b, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00});
+
+ // ELF 64-bit LSB executable, x86-64
+ GetFileFormatTester(
+ "ELF 64-bit little-endian x86-64",
+ vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xb0, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00});
+}
+
+TEST(UtilsTest, ScheduleCrashReporterUploadTest) {
+ // Not much to test. At least this tests for memory leaks, crashes,
+ // log errors.
+ FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+ utils::ScheduleCrashReporterUpload();
+ // Test that we scheduled one callback from the crash reporter.
+ EXPECT_EQ(1, chromeos::MessageLoopRunMaxIterations(&loop, 100));
+ EXPECT_FALSE(loop.PendingTasks());
+}
+
+TEST(UtilsTest, FormatTimeDeltaTest) {
+ // utils::FormatTimeDelta() is not locale-aware (it's only used for logging
+ // which is not localized) so we only need to test the C locale
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromMilliseconds(100)),
+ "0.1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(0)),
+ "0s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(1)),
+ "1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(59)),
+ "59s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(60)),
+ "1m0s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(61)),
+ "1m1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(90)),
+ "1m30s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(1205)),
+ "20m5s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3600)),
+ "1h0m0s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3601)),
+ "1h0m1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3661)),
+ "1h1m1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(7261)),
+ "2h1m1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(86400)),
+ "1d0h0m0s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(86401)),
+ "1d0h0m1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(200000)),
+ "2d7h33m20s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(200000) +
+ base::TimeDelta::FromMilliseconds(1)),
+ "2d7h33m20.001s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(-1)),
+ "-1s");
+}
+
+TEST(UtilsTest, TimeFromStructTimespecTest) {
+ struct timespec ts;
+
+ // Unix epoch (Thursday 00:00:00 UTC on Jan 1, 1970)
+ ts = (struct timespec) {.tv_sec = 0, .tv_nsec = 0};
+ EXPECT_EQ(base::Time::UnixEpoch(), utils::TimeFromStructTimespec(&ts));
+
+ // 42 ms after the Unix billennium (Sunday 01:46:40 UTC on September 9, 2001)
+ ts = (struct timespec) {.tv_sec = 1000 * 1000 * 1000,
+ .tv_nsec = 42 * 1000 * 1000};
+ base::Time::Exploded exploded = (base::Time::Exploded) {
+ .year = 2001, .month = 9, .day_of_week = 0, .day_of_month = 9,
+ .hour = 1, .minute = 46, .second = 40, .millisecond = 42};
+ EXPECT_EQ(base::Time::FromUTCExploded(exploded),
+ utils::TimeFromStructTimespec(&ts));
+}
+
+TEST(UtilsTest, DecodeAndStoreBase64String) {
+ base::FilePath path;
+
+ // Ensure we return false on empty strings or invalid base64.
+ EXPECT_FALSE(utils::DecodeAndStoreBase64String("", &path));
+ EXPECT_FALSE(utils::DecodeAndStoreBase64String("not valid base64", &path));
+
+ // Pass known base64 and check that it matches. This string was generated
+ // the following way:
+ //
+ // $ echo "Update Engine" | base64
+ // VXBkYXRlIEVuZ2luZQo=
+ EXPECT_TRUE(utils::DecodeAndStoreBase64String("VXBkYXRlIEVuZ2luZQo=",
+ &path));
+ ScopedPathUnlinker unlinker(path.value());
+ string expected_contents = "Update Engine\n";
+ string contents;
+ EXPECT_TRUE(utils::ReadFile(path.value(), &contents));
+ EXPECT_EQ(contents, expected_contents);
+ EXPECT_EQ(utils::FileSize(path.value()), expected_contents.size());
+}
+
+TEST(UtilsTest, ConvertToOmahaInstallDate) {
+ // The Omaha Epoch starts at Jan 1, 2007 0:00 PST which is a
+ // Monday. In Unix time, this point in time is easily obtained via
+ // the date(1) command like this:
+ //
+ // $ date +"%s" --date="Jan 1, 2007 0:00 PST"
+ const time_t omaha_epoch = 1167638400;
+ int value;
+
+ // Points in time *on and after* the Omaha epoch should not fail.
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch), &value));
+ EXPECT_GE(value, 0);
+
+ // Anything before the Omaha epoch should fail. We test it for two points.
+ EXPECT_FALSE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch - 1), &value));
+ EXPECT_FALSE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch - 100*24*3600), &value));
+
+ // Check that we jump from 0 to 7 exactly on the one-week mark, e.g.
+ // on Jan 8, 2007 0:00 PST.
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 7*24*3600 - 1), &value));
+ EXPECT_EQ(value, 0);
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 7*24*3600), &value));
+ EXPECT_EQ(value, 7);
+
+ // Check a couple of more values.
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 10*24*3600), &value));
+ EXPECT_EQ(value, 7);
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 20*24*3600), &value));
+ EXPECT_EQ(value, 14);
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 26*24*3600), &value));
+ EXPECT_EQ(value, 21);
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 29*24*3600), &value));
+ EXPECT_EQ(value, 28);
+
+ // The date Jun 4, 2007 0:00 PDT is a Monday and is hence a point
+ // where the Omaha InstallDate jumps 7 days. Its unix time is
+ // 1180940400. Notably, this is a point in time where Daylight
+ // Savings Time (DST) was is in effect (e.g. it's PDT, not PST).
+ //
+ // Note that as utils::ConvertToOmahaInstallDate() _deliberately_
+ // ignores DST (as it's hard to implement in a thread-safe way using
+ // glibc, see comments in utils.h) we have to fudge by the DST
+ // offset which is one hour. Conveniently, if the function were
+ // someday modified to be DST aware, this test would have to be
+ // modified as well.
+ const time_t dst_time = 1180940400; // Jun 4, 2007 0:00 PDT.
+ const time_t fudge = 3600;
+ int value1, value2;
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(dst_time + fudge - 1), &value1));
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(dst_time + fudge), &value2));
+ EXPECT_EQ(value1, value2 - 7);
+}
+
+TEST(UtilsTest, WallclockDurationHelper) {
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ base::TimeDelta duration;
+ string state_variable_key = "test-prefs";
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+
+ // Initialize wallclock to 1 sec.
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(1000000));
+
+ // First time called so no previous measurement available.
+ EXPECT_FALSE(utils::WallclockDurationHelper(&fake_system_state,
+ state_variable_key,
+ &duration));
+
+ // Next time, we should get zero since the clock didn't advance.
+ EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+ state_variable_key,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // We can also call it as many times as we want with it being
+ // considered a failure.
+ EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+ state_variable_key,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+ EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+ state_variable_key,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // Advance the clock one second, then we should get 1 sec on the
+ // next call and 0 sec on the subsequent call.
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(2000000));
+ EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+ state_variable_key,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 1);
+ EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+ state_variable_key,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // Advance clock two seconds and we should get 2 sec and then 0 sec.
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(4000000));
+ EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+ state_variable_key,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 2);
+ EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+ state_variable_key,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // There's a possibility that the wallclock can go backwards (NTP
+ // adjustments, for example) so check that we properly handle this
+ // case.
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(3000000));
+ EXPECT_FALSE(utils::WallclockDurationHelper(&fake_system_state,
+ state_variable_key,
+ &duration));
+ fake_clock.SetWallclockTime(base::Time::FromInternalValue(4000000));
+ EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+ state_variable_key,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 1);
+}
+
+TEST(UtilsTest, MonotonicDurationHelper) {
+ int64_t storage = 0;
+ FakeSystemState fake_system_state;
+ FakeClock fake_clock;
+ base::TimeDelta duration;
+
+ fake_system_state.set_clock(&fake_clock);
+
+ // Initialize monotonic clock to 1 sec.
+ fake_clock.SetMonotonicTime(base::Time::FromInternalValue(1000000));
+
+ // First time called so no previous measurement available.
+ EXPECT_FALSE(utils::MonotonicDurationHelper(&fake_system_state,
+ &storage,
+ &duration));
+
+ // Next time, we should get zero since the clock didn't advance.
+ EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+ &storage,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // We can also call it as many times as we want with it being
+ // considered a failure.
+ EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+ &storage,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+ EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+ &storage,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // Advance the clock one second, then we should get 1 sec on the
+ // next call and 0 sec on the subsequent call.
+ fake_clock.SetMonotonicTime(base::Time::FromInternalValue(2000000));
+ EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+ &storage,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 1);
+ EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+ &storage,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+
+ // Advance clock two seconds and we should get 2 sec and then 0 sec.
+ fake_clock.SetMonotonicTime(base::Time::FromInternalValue(4000000));
+ EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+ &storage,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 2);
+ EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+ &storage,
+ &duration));
+ EXPECT_EQ(duration.InSeconds(), 0);
+}
+
+TEST(UtilsTest, GetConnectionType) {
+ // Check that expected combinations map to the right value.
+ EXPECT_EQ(metrics::ConnectionType::kUnknown,
+ utils::GetConnectionType(kNetUnknown,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ utils::GetConnectionType(kNetEthernet,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ utils::GetConnectionType(kNetWifi,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kWimax,
+ utils::GetConnectionType(kNetWimax,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kBluetooth,
+ utils::GetConnectionType(kNetBluetooth,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kCellular,
+ utils::GetConnectionType(kNetCellular,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kTetheredEthernet,
+ utils::GetConnectionType(kNetEthernet,
+ NetworkTethering::kConfirmed));
+ EXPECT_EQ(metrics::ConnectionType::kTetheredWifi,
+ utils::GetConnectionType(kNetWifi,
+ NetworkTethering::kConfirmed));
+
+ // Ensure that we don't report tethered ethernet unless it's confirmed.
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ utils::GetConnectionType(kNetEthernet,
+ NetworkTethering::kNotDetected));
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ utils::GetConnectionType(kNetEthernet,
+ NetworkTethering::kSuspected));
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ utils::GetConnectionType(kNetEthernet,
+ NetworkTethering::kUnknown));
+
+ // Ditto for tethered wifi.
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ utils::GetConnectionType(kNetWifi,
+ NetworkTethering::kNotDetected));
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ utils::GetConnectionType(kNetWifi,
+ NetworkTethering::kSuspected));
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ utils::GetConnectionType(kNetWifi,
+ NetworkTethering::kUnknown));
+}
+
+TEST(UtilsTest, GetMinorVersion) {
+ // Test GetMinorVersion by verifying that it parses the conf file and returns
+ // the correct value.
+ uint32_t minor_version;
+
+ chromeos::KeyValueStore store;
+ EXPECT_FALSE(utils::GetMinorVersion(store, &minor_version));
+
+ EXPECT_TRUE(store.LoadFromString("PAYLOAD_MINOR_VERSION=one-two-three\n"));
+ EXPECT_FALSE(utils::GetMinorVersion(store, &minor_version));
+
+ EXPECT_TRUE(store.LoadFromString("PAYLOAD_MINOR_VERSION=123\n"));
+ EXPECT_TRUE(utils::GetMinorVersion(store, &minor_version));
+ EXPECT_EQ(minor_version, 123);
+}
+
+static bool BoolMacroTestHelper() {
+ int i = 1;
+ unsigned int ui = 1;
+ bool b = 1;
+ std::unique_ptr<char> cptr(new char);
+
+ TEST_AND_RETURN_FALSE(i);
+ TEST_AND_RETURN_FALSE(ui);
+ TEST_AND_RETURN_FALSE(b);
+ TEST_AND_RETURN_FALSE(cptr);
+
+ TEST_AND_RETURN_FALSE_ERRNO(i);
+ TEST_AND_RETURN_FALSE_ERRNO(ui);
+ TEST_AND_RETURN_FALSE_ERRNO(b);
+ TEST_AND_RETURN_FALSE_ERRNO(cptr);
+
+ return true;
+}
+
+static void VoidMacroTestHelper(bool* ret) {
+ int i = 1;
+ unsigned int ui = 1;
+ bool b = 1;
+ std::unique_ptr<char> cptr(new char);
+
+ *ret = false;
+
+ TEST_AND_RETURN(i);
+ TEST_AND_RETURN(ui);
+ TEST_AND_RETURN(b);
+ TEST_AND_RETURN(cptr);
+
+ TEST_AND_RETURN_ERRNO(i);
+ TEST_AND_RETURN_ERRNO(ui);
+ TEST_AND_RETURN_ERRNO(b);
+ TEST_AND_RETURN_ERRNO(cptr);
+
+ *ret = true;
+}
+
+TEST(UtilsTest, TestMacros) {
+ bool void_test = false;
+ VoidMacroTestHelper(&void_test);
+ EXPECT_TRUE(void_test);
+
+ EXPECT_TRUE(BoolMacroTestHelper());
+}
+
+} // namespace chromeos_update_engine
diff --git a/zip_unittest.cc b/zip_unittest.cc
new file mode 100644
index 0000000..4dcee59
--- /dev/null
+++ b/zip_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/bzip.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos_update_engine::test_utils::kRandomString;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+template <typename T>
+class ZipTest : public ::testing::Test {
+ public:
+ bool ZipDecompress(const chromeos::Blob& in,
+ chromeos::Blob* out) const = 0;
+ bool ZipCompress(const chromeos::Blob& in,
+ chromeos::Blob* out) const = 0;
+ bool ZipCompressString(const string& str,
+ chromeos::Blob* out) const = 0;
+ bool ZipDecompressString(const string& str,
+ chromeos::Blob* out) const = 0;
+};
+
+class BzipTest {};
+
+template <>
+class ZipTest<BzipTest> : public ::testing::Test {
+ public:
+ bool ZipDecompress(const chromeos::Blob& in,
+ chromeos::Blob* out) const {
+ return BzipDecompress(in, out);
+ }
+ bool ZipCompress(const chromeos::Blob& in,
+ chromeos::Blob* out) const {
+ return BzipCompress(in, out);
+ }
+ bool ZipCompressString(const string& str,
+ chromeos::Blob* out) const {
+ return BzipCompressString(str, out);
+ }
+ bool ZipDecompressString(const string& str,
+ chromeos::Blob* out) const {
+ return BzipDecompressString(str, out);
+ }
+};
+
+typedef ::testing::Types<BzipTest> ZipTestTypes;
+TYPED_TEST_CASE(ZipTest, ZipTestTypes);
+
+
+
+TYPED_TEST(ZipTest, SimpleTest) {
+ string in("this should compress well xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+ chromeos::Blob out;
+ EXPECT_TRUE(this->ZipCompressString(in, &out));
+ EXPECT_LT(out.size(), in.size());
+ EXPECT_GT(out.size(), 0);
+ chromeos::Blob decompressed;
+ EXPECT_TRUE(this->ZipDecompress(out, &decompressed));
+ EXPECT_EQ(in.size(), decompressed.size());
+ EXPECT_TRUE(!memcmp(in.data(), decompressed.data(), in.size()));
+}
+
+TYPED_TEST(ZipTest, PoorCompressionTest) {
+ string in(reinterpret_cast<const char*>(kRandomString),
+ sizeof(kRandomString));
+ chromeos::Blob out;
+ EXPECT_TRUE(this->ZipCompressString(in, &out));
+ EXPECT_GT(out.size(), in.size());
+ string out_string(out.begin(), out.end());
+ chromeos::Blob decompressed;
+ EXPECT_TRUE(this->ZipDecompressString(out_string, &decompressed));
+ EXPECT_EQ(in.size(), decompressed.size());
+ EXPECT_TRUE(!memcmp(in.data(), decompressed.data(), in.size()));
+}
+
+TYPED_TEST(ZipTest, MalformedZipTest) {
+ string in(reinterpret_cast<const char*>(kRandomString),
+ sizeof(kRandomString));
+ chromeos::Blob out;
+ EXPECT_FALSE(this->ZipDecompressString(in, &out));
+}
+
+TYPED_TEST(ZipTest, EmptyInputsTest) {
+ string in;
+ chromeos::Blob out;
+ EXPECT_TRUE(this->ZipDecompressString(in, &out));
+ EXPECT_EQ(0, out.size());
+
+ EXPECT_TRUE(this->ZipCompressString(in, &out));
+ EXPECT_EQ(0, out.size());
+}
+
+} // namespace chromeos_update_engine