Split payload application code into a subdirectory.
This patch splits from the main libupdate_engine code the part that
is strictly used to download and apply a payload into a new static
library, moving the code to subdirectories. The new library is divided
in two subdirectories: common/ and payload_consumer/, and should not
depend on other update_engine files outside those two subdirectories.
The main difference between those two is that the common/ tools are more
generic and not tied to the payload consumer process, but otherwise they
are both compiled together.
There are still dependencies from the new libpayload_consumer library
into the main directory files and DBus generated files. Those will be
addressed in follow up CLs.
Bug: 25197634
Test: FEATURES=test emerge-link update_engine; `mm` on Brillo.
Change-Id: Id8d0204ea573627e6e26ca9ea17b9592ca95bc23
diff --git a/common/action.h b/common/action.h
new file mode 100644
index 0000000..d8049ac
--- /dev/null
+++ b/common/action.h
@@ -0,0 +1,218 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_ACTION_H_
+#define UPDATE_ENGINE_COMMON_ACTION_H_
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+#include "update_engine/common/action_pipe.h"
+#include "update_engine/common/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 message 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 message loop: timer alarm or file
+// descriptors. If either of these happens, the message loop 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_COMMON_ACTION_H_
diff --git a/common/action_pipe.h b/common/action_pipe.h
new file mode 100644
index 0000000..362817d
--- /dev/null
+++ b/common/action_pipe.h
@@ -0,0 +1,101 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_ACTION_PIPE_H_
+#define UPDATE_ENGINE_COMMON_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_COMMON_ACTION_PIPE_H_
diff --git a/common/action_pipe_unittest.cc b/common/action_pipe_unittest.cc
new file mode 100644
index 0000000..9bfbc83
--- /dev/null
+++ b/common/action_pipe_unittest.cc
@@ -0,0 +1,60 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/action_pipe.h"
+
+#include <gtest/gtest.h>
+#include <string>
+#include "update_engine/common/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/common/action_processor.cc b/common/action_processor.cc
new file mode 100644
index 0000000..c5270a4
--- /dev/null
+++ b/common/action_processor.cc
@@ -0,0 +1,103 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/action_processor.h"
+
+#include <string>
+
+#include <base/logging.h>
+
+#include "update_engine/common/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/common/action_processor.h b/common/action_processor.h
new file mode 100644
index 0000000..d61e12d
--- /dev/null
+++ b/common/action_processor.h
@@ -0,0 +1,116 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_ACTION_PROCESSOR_H_
+#define UPDATE_ENGINE_COMMON_ACTION_PROCESSOR_H_
+
+#include <deque>
+
+#include <base/macros.h>
+
+#include "update_engine/common/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_COMMON_ACTION_PROCESSOR_H_
diff --git a/common/action_processor_unittest.cc b/common/action_processor_unittest.cc
new file mode 100644
index 0000000..8285470
--- /dev/null
+++ b/common/action_processor_unittest.cc
@@ -0,0 +1,190 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/action_processor.h"
+
+#include <gtest/gtest.h>
+#include <string>
+#include "update_engine/common/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/common/action_unittest.cc b/common/action_unittest.cc
new file mode 100644
index 0000000..dcdce17
--- /dev/null
+++ b/common/action_unittest.cc
@@ -0,0 +1,76 @@
+//
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/action.h"
+
+#include <gtest/gtest.h>
+#include <string>
+#include "update_engine/common/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/common/boot_control.h b/common/boot_control.h
new file mode 100644
index 0000000..1463015
--- /dev/null
+++ b/common/boot_control.h
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_BOOT_CONTROL_H_
+#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_H_
+
+#include <memory>
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+namespace boot_control {
+
+// The real BootControlInterface is platform-specific. This factory function
+// creates a new BootControlInterface instance for the current platform. If
+// this fails nullptr is returned.
+std::unique_ptr<BootControlInterface> CreateBootControl();
+
+} // namespace boot_control
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_BOOT_CONTROL_H_
diff --git a/common/boot_control_android.cc b/common/boot_control_android.cc
new file mode 100644
index 0000000..a00fcf4
--- /dev/null
+++ b/common/boot_control_android.cc
@@ -0,0 +1,200 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/boot_control_android.h"
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/message_loop.h>
+#include <cutils/properties.h>
+#include <fs_mgr.h>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace {
+
+// Open the appropriate fstab file and fallback to /fstab.device if
+// that's what's being used.
+static struct fstab* OpenFSTab() {
+ char propbuf[PROPERTY_VALUE_MAX];
+ struct fstab* fstab;
+
+ property_get("ro.hardware", propbuf, "");
+ string fstab_name = string("/fstab.") + propbuf;
+ fstab = fs_mgr_read_fstab(fstab_name.c_str());
+ if (fstab != nullptr)
+ return fstab;
+
+ fstab = fs_mgr_read_fstab("/fstab.device");
+ return fstab;
+}
+
+} // namespace
+
+
+namespace chromeos_update_engine {
+
+namespace boot_control {
+
+// Factory defined in boot_control.h.
+std::unique_ptr<BootControlInterface> CreateBootControl() {
+ std::unique_ptr<BootControlAndroid> boot_control(new BootControlAndroid());
+ if (!boot_control->Init()) {
+ return nullptr;
+ }
+ return brillo::make_unique_ptr(boot_control.release());
+}
+
+} // namespace boot_control
+
+bool BootControlAndroid::Init() {
+ const hw_module_t* hw_module;
+ int ret;
+
+ ret = hw_get_module(BOOT_CONTROL_HARDWARE_MODULE_ID, &hw_module);
+ if (ret != 0) {
+ LOG(ERROR) << "Error loading boot_control HAL implementation.";
+ return false;
+ }
+
+ module_ = reinterpret_cast<boot_control_module_t*>(const_cast<hw_module_t*>(hw_module));
+ module_->init(module_);
+
+ LOG(INFO) << "Loaded boot_control HAL "
+ << "'" << hw_module->name << "' "
+ << "version " << (hw_module->module_api_version>>8) << "."
+ << (hw_module->module_api_version&0xff) << " "
+ << "authored by '" << hw_module->author << "'.";
+ return true;
+}
+
+unsigned int BootControlAndroid::GetNumSlots() const {
+ return module_->getNumberSlots(module_);
+}
+
+BootControlInterface::Slot BootControlAndroid::GetCurrentSlot() const {
+ return module_->getCurrentSlot(module_);
+}
+
+bool BootControlAndroid::GetPartitionDevice(const string& partition_name,
+ Slot slot,
+ string* device) const {
+ struct fstab* fstab;
+ struct fstab_rec* record;
+
+ // We can't use fs_mgr to look up |partition_name| because fstab
+ // doesn't list every slot partition (it uses the slotselect option
+ // to mask the suffix).
+ //
+ // We can however assume that there's an entry for the /misc mount
+ // point and use that to get the device file for the misc
+ // partition. This helps us locate the disk that |partition_name|
+ // resides on. From there we'll assume that a by-name scheme is used
+ // so we can just replace the trailing "misc" by the given
+ // |partition_name| and suffix corresponding to |slot|, e.g.
+ //
+ // /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
+ // /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
+ //
+ // If needed, it's possible to relax the by-name assumption in the
+ // future by trawling /sys/block looking for the appropriate sibling
+ // of misc and then finding an entry in /dev matching the sysfs
+ // entry.
+
+ fstab = OpenFSTab();
+ if (fstab == nullptr) {
+ LOG(ERROR) << "Error opening fstab file.";
+ return false;
+ }
+ record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
+ if (record == nullptr) {
+ LOG(ERROR) << "Error finding /misc entry in fstab file.";
+ fs_mgr_free_fstab(fstab);
+ return false;
+ }
+
+ base::FilePath misc_device = base::FilePath(record->blk_device);
+ fs_mgr_free_fstab(fstab);
+
+ if (misc_device.BaseName() != base::FilePath("misc")) {
+ LOG(ERROR) << "Device file " << misc_device.value() << " for /misc "
+ << "is not in the expected format.";
+ return false;
+ }
+
+ const char* suffix = module_->getSuffix(module_, slot);
+ if (suffix == nullptr) {
+ LOG(ERROR) << "boot_control impl returned no suffix for slot "
+ << SlotName(slot);
+ return false;
+ }
+
+ base::FilePath path = misc_device.DirName().Append(partition_name + suffix);
+ if (!base::PathExists(path)) {
+ LOG(ERROR) << "Device file " << path.value() << " does not exist.";
+ return false;
+ }
+
+ *device = path.value();
+ return true;
+}
+
+bool BootControlAndroid::IsSlotBootable(Slot slot) const {
+ int ret = module_->isSlotBootable(module_, slot);
+ if (ret < 0) {
+ LOG(ERROR) << "Unable to determine if slot " << SlotName(slot)
+ << " is bootable: " << strerror(-ret);
+ return false;
+ }
+ return ret == 1;
+}
+
+bool BootControlAndroid::MarkSlotUnbootable(Slot slot) {
+ int ret = module_->setSlotAsUnbootable(module_, slot);
+ if (ret < 0) {
+ LOG(ERROR) << "Unable to mark slot " << SlotName(slot)
+ << " as bootable: " << strerror(-ret);
+ return false;
+ }
+ return ret == 0;
+}
+
+bool BootControlAndroid::SetActiveBootSlot(Slot slot) {
+ int ret = module_->setActiveBootSlot(module_, slot);
+ if (ret < 0) {
+ LOG(ERROR) << "Unable to set the active slot to slot " << SlotName(slot)
+ << ": " << strerror(-ret);
+ }
+ return ret == 0;
+}
+
+bool BootControlAndroid::MarkBootSuccessfulAsync(
+ base::Callback<void(bool)> callback) {
+ int ret = module_->markBootSuccessful(module_);
+ if (ret < 0) {
+ LOG(ERROR) << "Unable to mark boot successful: " << strerror(-ret);
+ }
+ return brillo::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(callback, ret == 0)) !=
+ brillo::MessageLoop::kTaskIdNull;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/boot_control_android.h b/common/boot_control_android.h
new file mode 100644
index 0000000..c857e04
--- /dev/null
+++ b/common/boot_control_android.h
@@ -0,0 +1,61 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_BOOT_CONTROL_ANDROID_H_
+#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_ANDROID_H_
+
+#include <string>
+
+#include <hardware/boot_control.h>
+#include <hardware/hardware.h>
+
+#include "update_engine/common/boot_control.h"
+
+namespace chromeos_update_engine {
+
+// The Android implementation of the BootControlInterface. This implementation
+// uses the libhardware's boot_control HAL to access the bootloader.
+class BootControlAndroid : public BootControlInterface {
+ public:
+ BootControlAndroid() = default;
+ ~BootControlAndroid() = default;
+
+ // Load boot_control HAL implementation using libhardware and
+ // initializes it. Returns false if an error occurred.
+ bool Init();
+
+ // BootControlInterface overrides.
+ unsigned int GetNumSlots() const override;
+ BootControlInterface::Slot GetCurrentSlot() const override;
+ bool GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ std::string* device) const override;
+ bool IsSlotBootable(BootControlInterface::Slot slot) const override;
+ bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
+ bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
+ bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+
+ private:
+ // NOTE: There is no way to release/unload HAL implementations so
+ // this is essentially leaked on object destruction.
+ boot_control_module_t* module_;
+
+ DISALLOW_COPY_AND_ASSIGN(BootControlAndroid);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_BOOT_CONTROL_ANDROID_H_
diff --git a/common/boot_control_chromeos.cc b/common/boot_control_chromeos.cc
new file mode 100644
index 0000000..d053b68
--- /dev/null
+++ b/common/boot_control_chromeos.cc
@@ -0,0 +1,304 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/boot_control_chromeos.h"
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <brillo/make_unique_ptr.h>
+#include <rootdev/rootdev.h>
+
+extern "C" {
+#include <vboot/vboot_host.h>
+}
+
+#include "update_engine/common/boot_control.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace {
+
+const char* kChromeOSPartitionNameKernel = "kernel";
+const char* kChromeOSPartitionNameRoot = "root";
+const char* kAndroidPartitionNameKernel = "boot";
+const char* kAndroidPartitionNameRoot = "system";
+
+// Returns the currently booted rootfs partition. "/dev/sda3", for example.
+string GetBootDevice() {
+ 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;
+}
+
+// ExecCallback called when the execution of setgoodkernel finishes. Notifies
+// the caller of MarkBootSuccessfullAsync() by calling |callback| with the
+// result.
+void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
+ int return_code,
+ const string& output) {
+ callback.Run(return_code == 0);
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+namespace boot_control {
+
+// Factory defined in boot_control.h.
+std::unique_ptr<BootControlInterface> CreateBootControl() {
+ std::unique_ptr<BootControlChromeOS> boot_control_chromeos(
+ new BootControlChromeOS());
+ if (!boot_control_chromeos->Init()) {
+ LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
+ }
+ return brillo::make_unique_ptr(boot_control_chromeos.release());
+}
+
+} // namespace boot_control
+
+bool BootControlChromeOS::Init() {
+ string boot_device = GetBootDevice();
+ if (boot_device.empty())
+ return false;
+
+ int partition_num;
+ if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
+ return false;
+
+ // All installed Chrome OS devices have two slots. We don't update removable
+ // devices, so we will pretend we have only one slot in that case.
+ if (IsRemovableDevice(boot_disk_name_)) {
+ LOG(INFO)
+ << "Booted from a removable device, pretending we have only one slot.";
+ num_slots_ = 1;
+ } else {
+ // TODO(deymo): Look at the actual number of slots reported in the GPT.
+ num_slots_ = 2;
+ }
+
+ // Search through the slots to see which slot has the partition_num we booted
+ // from. This should map to one of the existing slots, otherwise something is
+ // very wrong.
+ current_slot_ = 0;
+ while (current_slot_ < num_slots_ &&
+ partition_num !=
+ GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
+ current_slot_++;
+ }
+ if (current_slot_ >= num_slots_) {
+ LOG(ERROR) << "Couldn't find the slot number corresponding to the "
+ "partition " << boot_device
+ << ", number of slots: " << num_slots_
+ << ". This device is not updateable.";
+ num_slots_ = 1;
+ current_slot_ = BootControlInterface::kInvalidSlot;
+ return false;
+ }
+
+ LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
+ << SlotName(current_slot_) << ") of " << num_slots_
+ << " slots present on disk " << boot_disk_name_;
+ return true;
+}
+
+unsigned int BootControlChromeOS::GetNumSlots() const {
+ return num_slots_;
+}
+
+BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
+ return current_slot_;
+}
+
+bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
+ unsigned int slot,
+ string* device) const {
+ int partition_num = GetPartitionNumber(partition_name, slot);
+ if (partition_num < 0)
+ return false;
+
+ string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
+ if (part_device.empty())
+ return false;
+
+ *device = part_device;
+ return true;
+}
+
+bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
+ int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+ if (partition_num < 0)
+ return false;
+
+ CgptAddParams params;
+ memset(¶ms, '\0', sizeof(params));
+ params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+ params.partition = partition_num;
+
+ int retval = CgptGetPartitionDetails(¶ms);
+ if (retval != CGPT_OK)
+ return false;
+
+ return params.successful || params.tries > 0;
+}
+
+bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
+ LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
+
+ if (slot == current_slot_) {
+ LOG(ERROR) << "Refusing to mark current slot as unbootable.";
+ return false;
+ }
+
+ int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+ if (partition_num < 0)
+ return false;
+
+ CgptAddParams params;
+ memset(¶ms, 0, sizeof(params));
+
+ params.drive_name = const_cast<char*>(boot_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 BootControlChromeOS::SetActiveBootSlot(Slot slot) {
+ LOG(INFO) << "Marking slot " << SlotName(slot) << " active.";
+
+ int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+ if (partition_num < 0)
+ return false;
+
+ CgptPrioritizeParams prio_params;
+ memset(&prio_params, 0, sizeof(prio_params));
+
+ prio_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+ prio_params.set_partition = partition_num;
+
+ prio_params.max_priority = 0;
+
+ int retval = CgptPrioritize(&prio_params);
+ if (retval != CGPT_OK) {
+ LOG(ERROR) << "Unable to set highest priority for slot " << SlotName(slot)
+ << " (partition " << partition_num << ").";
+ return false;
+ }
+
+ CgptAddParams add_params;
+ memset(&add_params, 0, sizeof(add_params));
+
+ add_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+ add_params.partition = partition_num;
+
+ add_params.tries = 6;
+ add_params.set_tries = true;
+
+ retval = CgptSetAttributes(&add_params);
+ if (retval != CGPT_OK) {
+ LOG(ERROR) << "Unable to set NumTriesLeft to " << add_params.tries
+ << " for slot " << SlotName(slot) << " (partition "
+ << partition_num << ").";
+ return false;
+ }
+
+ return true;
+}
+
+bool BootControlChromeOS::MarkBootSuccessfulAsync(
+ base::Callback<void(bool)> callback) {
+ return Subprocess::Get().Exec(
+ {"/usr/sbin/chromeos-setgoodkernel"},
+ base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0;
+}
+
+// static
+string BootControlChromeOS::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();
+}
+
+// static
+bool BootControlChromeOS::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";
+}
+
+int BootControlChromeOS::GetPartitionNumber(
+ const string partition_name,
+ BootControlInterface::Slot slot) const {
+ if (slot >= num_slots_) {
+ LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
+ << num_slots_ << " slot(s)";
+ return -1;
+ }
+
+ // In Chrome OS, the partition numbers are hard-coded:
+ // KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
+ // To help compatibility between different we accept both lowercase and
+ // uppercase names in the ChromeOS or Brillo standard names.
+ // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
+ string partition_lower = base::StringToLowerASCII(partition_name);
+ int base_part_num = 2 + 2 * slot;
+ if (partition_lower == kChromeOSPartitionNameKernel ||
+ partition_lower == kAndroidPartitionNameKernel)
+ return base_part_num + 0;
+ if (partition_lower == kChromeOSPartitionNameRoot ||
+ partition_lower == kAndroidPartitionNameRoot)
+ return base_part_num + 1;
+ LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
+ return -1;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/boot_control_chromeos.h b/common/boot_control_chromeos.h
new file mode 100644
index 0000000..be661e8
--- /dev/null
+++ b/common/boot_control_chromeos.h
@@ -0,0 +1,85 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_BOOT_CONTROL_CHROMEOS_H_
+#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_CHROMEOS_H_
+
+#include <string>
+
+#include <base/callback.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+
+// The Chrome OS implementation of the BootControlInterface. This interface
+// assumes the partition names and numbers used in Chrome OS devices.
+class BootControlChromeOS : public BootControlInterface {
+ public:
+ BootControlChromeOS() = default;
+ ~BootControlChromeOS() = default;
+
+ // Initialize the BootControl instance loading the constant values. Returns
+ // whether the operation succeeded. In case of failure, normally meaning
+ // some critical failure such as we couldn't determine the slot that we
+ // booted from, the implementation will pretend that there's only one slot and
+ // therefore A/B updates are disabled.
+ bool Init();
+
+ // BootControlInterface overrides.
+ unsigned int GetNumSlots() const override;
+ BootControlInterface::Slot GetCurrentSlot() const override;
+ bool GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ std::string* device) const override;
+ bool IsSlotBootable(BootControlInterface::Slot slot) const override;
+ bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
+ bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
+ bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+
+ private:
+ friend class BootControlChromeOSTest;
+ FRIEND_TEST(BootControlChromeOSTest, SysfsBlockDeviceTest);
+ FRIEND_TEST(BootControlChromeOSTest, GetPartitionNumberTest);
+
+ // 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.
+ static std::string SysfsBlockDevice(const std::string& device);
+
+ // Returns true if the root |device| (e.g., "/dev/sdb") is known to be
+ // removable, false otherwise.
+ static bool IsRemovableDevice(const std::string& device);
+
+ // Return the hard-coded partition number used in Chrome OS for the passed
+ // |partition_name| and |slot|. In case of invalid data, returns -1.
+ int GetPartitionNumber(const std::string partition_name,
+ BootControlInterface::Slot slot) const;
+
+ // Cached values for GetNumSlots() and GetCurrentSlot().
+ BootControlInterface::Slot num_slots_{1};
+ BootControlInterface::Slot current_slot_{BootControlInterface::kInvalidSlot};
+
+ // The block device of the disk we booted from, without the partition number.
+ std::string boot_disk_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(BootControlChromeOS);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_BOOT_CONTROL_CHROMEOS_H_
diff --git a/common/boot_control_chromeos_unittest.cc b/common/boot_control_chromeos_unittest.cc
new file mode 100644
index 0000000..4c218c0
--- /dev/null
+++ b/common/boot_control_chromeos_unittest.cc
@@ -0,0 +1,70 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/boot_control_chromeos.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+class BootControlChromeOSTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // We don't run Init() for bootctl_, we set its internal values instead.
+ bootctl_.num_slots_ = 2;
+ bootctl_.current_slot_ = 0;
+ bootctl_.boot_disk_name_ = "/dev/null";
+ }
+
+ BootControlChromeOS bootctl_; // BootControlChromeOS under test.
+};
+
+TEST_F(BootControlChromeOSTest, SysfsBlockDeviceTest) {
+ EXPECT_EQ("/sys/block/sda", bootctl_.SysfsBlockDevice("/dev/sda"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("/foo/sda"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("/dev/foo/bar"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("/"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("./"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice(""));
+}
+
+TEST_F(BootControlChromeOSTest, GetPartitionNumberTest) {
+ // The partition name should not be case-sensitive.
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("kernel", 0));
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("boot", 0));
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("KERNEL", 0));
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("BOOT", 0));
+
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("ROOT", 0));
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("system", 0));
+
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("ROOT", 0));
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("system", 0));
+
+ // Slot B.
+ EXPECT_EQ(4, bootctl_.GetPartitionNumber("KERNEL", 1));
+ EXPECT_EQ(5, bootctl_.GetPartitionNumber("ROOT", 1));
+
+ // Slot C doesn't exists.
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("KERNEL", 2));
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("ROOT", 2));
+
+ // Non A/B partitions are ignored.
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("OEM", 0));
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("A little panda", 0));
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/boot_control_interface.h b/common/boot_control_interface.h
new file mode 100644
index 0000000..659b388
--- /dev/null
+++ b/common/boot_control_interface.h
@@ -0,0 +1,98 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_BOOT_CONTROL_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_INTERFACE_H_
+
+#include <climits>
+#include <string>
+
+#include <base/callback.h>
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+// The abstract boot control interface defines the interaction with the
+// platform's bootloader hiding vendor-specific details from the rest of
+// update_engine. This interface is used for controlling where the device should
+// boot from.
+class BootControlInterface {
+ public:
+ using Slot = unsigned int;
+
+ static const Slot kInvalidSlot = UINT_MAX;
+
+ virtual ~BootControlInterface() = default;
+
+ // Return the number of update slots in the system. A system will normally
+ // have two slots, named "A" and "B" in the documentation, but sometimes
+ // images running from other media can have only one slot, like some USB
+ // image. Systems with only one slot won't be able to update.
+ virtual unsigned int GetNumSlots() const = 0;
+
+ // Return the slot where we are running the system from. On success, the
+ // result is a number between 0 and GetNumSlots() - 1. Otherwise, log an error
+ // and return kInvalidSlot.
+ virtual Slot GetCurrentSlot() const = 0;
+
+ // Determines the block device for the given partition name and slot number.
+ // The |slot| number must be between 0 and GetNumSlots() - 1 and the
+ // |partition_name| is a platform-specific name that identifies a partition on
+ // every slot. On success, returns true and stores the block device in
+ // |device|.
+ virtual bool GetPartitionDevice(const std::string& partition_name,
+ Slot slot,
+ std::string* device) const = 0;
+
+ // Returns whether the passed |slot| is marked as bootable. Returns false if
+ // the slot is invalid.
+ virtual bool IsSlotBootable(Slot slot) const = 0;
+
+ // Mark the specified slot unbootable. No other slot flags are modified.
+ // Returns true on success.
+ virtual bool MarkSlotUnbootable(Slot slot) = 0;
+
+ // Set the passed |slot| as the preferred boot slot. Returns whether it
+ // succeeded setting the active slot. If succeeded, on next boot the
+ // bootloader will attempt to load the |slot| marked as active. Note that this
+ // method doesn't change the value of GetCurrentSlot() on the current boot.
+ virtual bool SetActiveBootSlot(Slot slot) = 0;
+
+ // Mark the current slot as successfully booted asynchronously. No other slot
+ // flags are modified. Returns false if it was not able to schedule the
+ // operation, otherwise, returns true and calls the |callback| with the result
+ // of the operation.
+ virtual bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) = 0;
+
+ // Return a human-readable slot name used for logging.
+ static std::string SlotName(Slot slot) {
+ if (slot == kInvalidSlot)
+ return "INVALID";
+ if (slot < 26)
+ return std::string(1, 'A' + slot);
+ return "TOO_BIG";
+ }
+
+ protected:
+ BootControlInterface() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BootControlInterface);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_BOOT_CONTROL_INTERFACE_H_
diff --git a/common/boot_control_stub.cc b/common/boot_control_stub.cc
new file mode 100644
index 0000000..2de0c82
--- /dev/null
+++ b/common/boot_control_stub.cc
@@ -0,0 +1,62 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/boot_control_stub.h"
+
+#include <base/logging.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+unsigned int BootControlStub::GetNumSlots() const {
+ return 0;
+}
+
+BootControlInterface::Slot BootControlStub::GetCurrentSlot() const {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return 0;
+}
+
+bool BootControlStub::GetPartitionDevice(const string& partition_name,
+ Slot slot,
+ string* device) const {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return false;
+}
+
+bool BootControlStub::IsSlotBootable(Slot slot) const {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return false;
+}
+
+bool BootControlStub::MarkSlotUnbootable(Slot slot) {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return false;
+}
+
+bool BootControlStub::SetActiveBootSlot(Slot slot) {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return false;
+}
+
+bool BootControlStub::MarkBootSuccessfulAsync(
+ base::Callback<void(bool)> callback) {
+ // This is expected to be called on update_engine startup.
+ return false;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/boot_control_stub.h b/common/boot_control_stub.h
new file mode 100644
index 0000000..7832adc
--- /dev/null
+++ b/common/boot_control_stub.h
@@ -0,0 +1,55 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_BOOT_CONTROL_STUB_H_
+#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_STUB_H_
+
+#include <string>
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+
+// An implementation of the BootControlInterface that does nothing,
+// typically used when e.g. an underlying HAL implementation cannot be
+// loaded or doesn't exist.
+//
+// You are gauranteed that the implementation of GetNumSlots() method
+// always returns 0. This can be used to identify that this
+// implementation is in use.
+class BootControlStub : public BootControlInterface {
+ public:
+ BootControlStub() = default;
+ ~BootControlStub() = default;
+
+ // BootControlInterface overrides.
+ unsigned int GetNumSlots() const override;
+ BootControlInterface::Slot GetCurrentSlot() const override;
+ bool GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ std::string* device) const override;
+ bool IsSlotBootable(BootControlInterface::Slot slot) const override;
+ bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
+ bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
+ bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BootControlStub);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_BOOT_CONTROL_STUB_H_
diff --git a/common/certificate_checker.cc b/common/certificate_checker.cc
new file mode 100644
index 0000000..87f9848
--- /dev/null
+++ b/common/certificate_checker.cc
@@ -0,0 +1,202 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/certificate_checker.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <curl/curl.h>
+#include <openssl/evp.h>
+#include <openssl/ssl.h>
+
+#include "metrics/metrics_library.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/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/common/certificate_checker.h b/common/certificate_checker.h
new file mode 100644
index 0000000..83c09e1
--- /dev/null
+++ b/common/certificate_checker.h
@@ -0,0 +1,135 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_CERTIFICATE_CHECKER_H_
+#define UPDATE_ENGINE_COMMON_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_COMMON_CERTIFICATE_CHECKER_H_
diff --git a/common/certificate_checker_unittest.cc b/common/certificate_checker_unittest.cc
new file mode 100644
index 0000000..3dfdadb
--- /dev/null
+++ b/common/certificate_checker_unittest.cc
@@ -0,0 +1,184 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/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/common/constants.h"
+#include "update_engine/common/mock_certificate_checker.h"
+#include "update_engine/common/mock_prefs.h"
+#include "update_engine/fake_system_state.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/common/clock.cc b/common/clock.cc
new file mode 100644
index 0000000..f0eff44
--- /dev/null
+++ b/common/clock.cc
@@ -0,0 +1,58 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/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/common/clock.h b/common/clock.h
new file mode 100644
index 0000000..2f373a7
--- /dev/null
+++ b/common/clock.h
@@ -0,0 +1,41 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_CLOCK_H_
+#define UPDATE_ENGINE_COMMON_CLOCK_H_
+
+#include "update_engine/common/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_COMMON_CLOCK_H_
diff --git a/common/clock_interface.h b/common/clock_interface.h
new file mode 100644
index 0000000..2228983
--- /dev/null
+++ b/common/clock_interface.h
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_CLOCK_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_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_COMMON_CLOCK_INTERFACE_H_
diff --git a/common/constants.cc b/common/constants.cc
new file mode 100644
index 0000000..26f3628
--- /dev/null
+++ b/common/constants.cc
@@ -0,0 +1,94 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/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 kPowerwashSafePrefsSubDirectory[] = "update_engine/prefs";
+
+const char kPrefsSubDirectory[] = "prefs";
+
+const char kStatefulPartition[] = "/mnt/stateful_partition";
+
+// Constants defining keys for the persisted state of update engine.
+const char kPrefsAttemptInProgress[] = "attempt-in-progress";
+const char kPrefsBackoffExpiryTime[] = "backoff-expiry-time";
+const char kPrefsBootId[] = "boot-id";
+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 kPrefsChannelOnSlotPrefix[] = "channel-on-slot-";
+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 kPrefsUpdateCompletedBootTime[] = "update-completed-boot-time";
+const char kPrefsUpdateCompletedOnBootId[] = "update-completed-on-boot-id";
+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/common/constants.h b/common/constants.h
new file mode 100644
index 0000000..ecdf51c
--- /dev/null
+++ b/common/constants.h
@@ -0,0 +1,182 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_CONSTANTS_H_
+#define UPDATE_ENGINE_COMMON_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[];
+
+// The contents of the powerwash marker file.
+extern const char kPowerwashCommand[];
+
+// Directory for AU prefs that are preserved across powerwash.
+extern const char kPowerwashSafePrefsSubDirectory[];
+
+// The location where we store the AU preferences (state etc).
+extern const char kPrefsSubDirectory[];
+
+// 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 kPrefsBootId[];
+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 kPrefsChannelOnSlotPrefix[];
+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 kPrefsUpdateCompletedBootTime[];
+extern const char kPrefsUpdateCompletedOnBootId[];
+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_COMMON_CONSTANTS_H_
diff --git a/common/error_code.h b/common/error_code.h
new file mode 100644
index 0000000..2bbdcfa
--- /dev/null
+++ b/common/error_code.h
@@ -0,0 +1,135 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_ERROR_CODE_H_
+#define UPDATE_ENGINE_COMMON_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
+ // metrics::ReportUpdateAttemptMetrics() 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_COMMON_ERROR_CODE_H_
diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h
new file mode 100644
index 0000000..5c6c160
--- /dev/null
+++ b/common/fake_boot_control.h
@@ -0,0 +1,112 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_FAKE_BOOT_CONTROL_H_
+#define UPDATE_ENGINE_COMMON_FAKE_BOOT_CONTROL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake bootloader control interface used for testing.
+class FakeBootControl : public BootControlInterface {
+ public:
+ FakeBootControl() {
+ SetNumSlots(num_slots_);
+ // The current slot should be bootable.
+ is_bootable_[current_slot_] = true;
+ }
+
+ // BootControlInterface overrides.
+ unsigned int GetNumSlots() const override { return num_slots_; }
+ BootControlInterface::Slot GetCurrentSlot() const override {
+ return current_slot_;
+ }
+
+ bool GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ std::string* device) const override {
+ if (slot >= num_slots_)
+ return false;
+ auto part_it = devices_[slot].find(partition_name);
+ if (part_it == devices_[slot].end())
+ return false;
+ *device = part_it->second;
+ return true;
+ }
+
+ bool IsSlotBootable(BootControlInterface::Slot slot) const override {
+ return slot < num_slots_ && is_bootable_[slot];
+ }
+
+ bool MarkSlotUnbootable(BootControlInterface::Slot slot) override {
+ if (slot >= num_slots_)
+ return false;
+ is_bootable_[slot] = false;
+ return true;
+ }
+
+ bool SetActiveBootSlot(Slot slot) override { return true; }
+
+ bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override {
+ // We run the callback directly from here to avoid having to setup a message
+ // loop in the test environment.
+ callback.Run(true);
+ return true;
+ }
+
+ // Setters
+ void SetNumSlots(unsigned int num_slots) {
+ num_slots_ = num_slots;
+ is_bootable_.resize(num_slots_, false);
+ devices_.resize(num_slots_);
+ }
+
+ void SetCurrentSlot(BootControlInterface::Slot slot) {
+ current_slot_ = slot;
+ }
+
+ void SetPartitionDevice(const std::string partition_name,
+ BootControlInterface::Slot slot,
+ const std::string device) {
+ DCHECK(slot < num_slots_);
+ devices_[slot][partition_name] = device;
+ }
+
+ void SetSlotBootable(BootControlInterface::Slot slot, bool bootable) {
+ DCHECK(slot < num_slots_);
+ is_bootable_[slot] = bootable;
+ }
+
+ private:
+ BootControlInterface::Slot num_slots_{2};
+ BootControlInterface::Slot current_slot_{0};
+
+ std::vector<bool> is_bootable_;
+ std::vector<std::map<std::string, std::string>> devices_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeBootControl);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_FAKE_BOOT_CONTROL_H_
diff --git a/common/fake_clock.h b/common/fake_clock.h
new file mode 100644
index 0000000..3d3bad8
--- /dev/null
+++ b/common/fake_clock.h
@@ -0,0 +1,63 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_FAKE_CLOCK_H_
+#define UPDATE_ENGINE_COMMON_FAKE_CLOCK_H_
+
+#include "update_engine/common/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_COMMON_FAKE_CLOCK_H_
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
new file mode 100644
index 0000000..23d6498
--- /dev/null
+++ b/common/fake_hardware.h
@@ -0,0 +1,124 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_FAKE_HARDWARE_H_
+#define UPDATE_ENGINE_COMMON_FAKE_HARDWARE_H_
+
+#include <map>
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/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()
+ : 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.
+ 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_; }
+
+ bool GetNonVolatileDirectory(base::FilePath* path) const override {
+ return false;
+ }
+
+ bool GetPowerwashSafeDirectory(base::FilePath* path) const override {
+ return false;
+ }
+
+ // Setters
+ 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:
+ 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_COMMON_FAKE_HARDWARE_H_
diff --git a/common/fake_prefs.cc b/common/fake_prefs.cc
new file mode 100644
index 0000000..5a0a3af
--- /dev/null
+++ b/common/fake_prefs.cc
@@ -0,0 +1,168 @@
+//
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/fake_prefs.h"
+
+#include <algorithm>
+
+#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 {
+
+FakePrefs::~FakePrefs() {
+ EXPECT_TRUE(observers_.empty());
+}
+
+// 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) const {
+ 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) const {
+ 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) const {
+ return GetValue(key, value);
+}
+
+bool FakePrefs::SetBoolean(const string& key, const bool value) {
+ SetValue(key, value);
+ return true;
+}
+
+bool FakePrefs::Exists(const string& key) const {
+ return values_.find(key) != values_.end();
+}
+
+bool FakePrefs::Delete(const string& key) {
+ if (values_.find(key) == values_.end())
+ return false;
+ values_.erase(key);
+ const auto observers_for_key = observers_.find(key);
+ if (observers_for_key != observers_.end()) {
+ std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+ for (ObserverInterface* observer : copy_observers)
+ observer->OnPrefDeleted(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;
+ const auto observers_for_key = observers_.find(key);
+ if (observers_for_key != observers_.end()) {
+ std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+ for (ObserverInterface* observer : copy_observers)
+ observer->OnPrefSet(key);
+ }
+}
+
+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;
+}
+
+void FakePrefs::AddObserver(const string& key, ObserverInterface* observer) {
+ observers_[key].push_back(observer);
+}
+
+void FakePrefs::RemoveObserver(const string& key, ObserverInterface* observer) {
+ std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+ auto observer_it =
+ std::find(observers_for_key.begin(), observers_for_key.end(), observer);
+ EXPECT_NE(observer_it, observers_for_key.end())
+ << "Trying to remove an observer instance not watching the key "
+ << key;
+ if (observer_it != observers_for_key.end())
+ observers_for_key.erase(observer_it);
+ if (observers_for_key.empty())
+ observers_.erase(key);
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/fake_prefs.h b/common/fake_prefs.h
new file mode 100644
index 0000000..d194060
--- /dev/null
+++ b/common/fake_prefs.h
@@ -0,0 +1,113 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
+#define UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/common/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() = default;
+ ~FakePrefs();
+
+ // PrefsInterface methods.
+ bool GetString(const std::string& key, std::string* value) const override;
+ bool SetString(const std::string& key, const std::string& value) override;
+ bool GetInt64(const std::string& key, int64_t* value) const override;
+ bool SetInt64(const std::string& key, const int64_t value) override;
+ bool GetBoolean(const std::string& key, bool* value) const override;
+ bool SetBoolean(const std::string& key, const bool value) override;
+
+ bool Exists(const std::string& key) const override;
+ bool Delete(const std::string& key) override;
+
+ void AddObserver(const std::string& key,
+ ObserverInterface* observer) override;
+ void RemoveObserver(const std::string& key,
+ ObserverInterface* observer) 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_;
+
+ // The registered observers watching for changes.
+ std::map<std::string, std::vector<ObserverInterface*>> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakePrefs);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
diff --git a/common/hardware.h b/common/hardware.h
new file mode 100644
index 0000000..f1365e0
--- /dev/null
+++ b/common/hardware.h
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_HARDWARE_H_
+#define UPDATE_ENGINE_COMMON_HARDWARE_H_
+
+#include <memory>
+
+#include "update_engine/common/hardware_interface.h"
+
+namespace chromeos_update_engine {
+namespace hardware {
+
+// The real HardwareInterface is platform-specific. This factory function
+// creates a new HardwareInterface instance for the current platform.
+std::unique_ptr<HardwareInterface> CreateHardware();
+
+} // namespace hardware
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_HARDWARE_H_
diff --git a/common/hardware_android.cc b/common/hardware_android.cc
new file mode 100644
index 0000000..4f06ff2
--- /dev/null
+++ b/common/hardware_android.cc
@@ -0,0 +1,119 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/hardware_android.h"
+
+#include <base/files/file_util.h>
+#include <brillo/make_unique_ptr.h>
+#include <cutils/properties.h>
+
+#include "update_engine/common/hardware.h"
+
+using std::string;
+
+namespace {
+
+// The stateful directory used by update_engine.
+const char kNonVolatileDirectory[] = "/data/misc/update_engine";
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+namespace hardware {
+
+// Factory defined in hardware.h.
+std::unique_ptr<HardwareInterface> CreateHardware() {
+ return brillo::make_unique_ptr(new HardwareAndroid());
+}
+
+} // namespace hardware
+
+// In Android there are normally three kinds of builds: eng, userdebug and user.
+// These builds target respectively a developer build, a debuggable version of
+// the final product and the pristine final product the end user will run.
+// Apart from the ro.build.type property name, they differ in the following
+// properties that characterize the builds:
+// * eng builds: ro.secure=0 and ro.debuggable=1
+// * userdebug builds: ro.secure=1 and ro.debuggable=1
+// * user builds: ro.secure=1 and ro.debuggable=0
+//
+// See IsOfficialBuild() and IsNormalMode() for the meaning of these options in
+// Android.
+
+bool HardwareAndroid::IsOfficialBuild() const {
+ // We run an official build iff ro.secure == 1, because we expect the build to
+ // behave like the end user product and check for updates. Note that while
+ // developers are able to build "official builds" by just running "make user",
+ // that will only result in a more restrictive environment. The important part
+ // is that we don't produce and push "non-official" builds to the end user.
+ //
+ // In case of a non-bool value, we take the most restrictive option and
+ // assume we are in an official-build.
+ return property_get_bool("ro.secure", 1) != 0;
+}
+
+bool HardwareAndroid::IsNormalBootMode() const {
+ // We are running in "dev-mode" iff ro.debuggable == 1. In dev-mode the
+ // update_engine will allow extra developers options, such as providing a
+ // different update URL. In case of error, we assume the build is in
+ // normal-mode.
+ return property_get_bool("ro.debuggable", 0) != 1;
+}
+
+bool HardwareAndroid::IsOOBEComplete(base::Time* out_time_of_oobe) const {
+ LOG(WARNING) << "STUB: Assuming OOBE is complete.";
+ if (out_time_of_oobe)
+ *out_time_of_oobe = base::Time();
+ return true;
+}
+
+string HardwareAndroid::GetHardwareClass() const {
+ LOG(WARNING) << "STUB: GetHardwareClass().";
+ return "ANDROID";
+}
+
+string HardwareAndroid::GetFirmwareVersion() const {
+ LOG(WARNING) << "STUB: GetFirmwareVersion().";
+ return "0";
+}
+
+string HardwareAndroid::GetECVersion() const {
+ LOG(WARNING) << "STUB: GetECVersion().";
+ return "0";
+}
+
+int HardwareAndroid::GetPowerwashCount() const {
+ LOG(WARNING) << "STUB: Assuming no factory reset was performed.";
+ return 0;
+}
+
+bool HardwareAndroid::GetNonVolatileDirectory(base::FilePath* path) const {
+ base::FilePath local_path(kNonVolatileDirectory);
+ if (!base::PathExists(local_path)) {
+ LOG(ERROR) << "Non-volatile directory not found: " << local_path.value();
+ return false;
+ }
+ *path = local_path;
+ return true;
+}
+
+bool HardwareAndroid::GetPowerwashSafeDirectory(base::FilePath* path) const {
+ // On Android, we don't have a directory persisted across powerwash.
+ return false;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/hardware_android.h b/common/hardware_android.h
new file mode 100644
index 0000000..071f7d5
--- /dev/null
+++ b/common/hardware_android.h
@@ -0,0 +1,53 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_HARDWARE_ANDROID_H_
+#define UPDATE_ENGINE_COMMON_HARDWARE_ANDROID_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/hardware.h"
+#include "update_engine/common/hardware_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements the real interface with the hardware in the Android platform.
+class HardwareAndroid final : public HardwareInterface {
+ public:
+ HardwareAndroid() = default;
+ ~HardwareAndroid() override = default;
+
+ // HardwareInterface methods.
+ 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;
+ bool GetNonVolatileDirectory(base::FilePath* path) const override;
+ bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HardwareAndroid);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_HARDWARE_ANDROID_H_
diff --git a/common/hardware_chromeos.cc b/common/hardware_chromeos.cc
new file mode 100644
index 0000000..ae27e36
--- /dev/null
+++ b/common/hardware_chromeos.cc
@@ -0,0 +1,156 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/hardware_chromeos.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 <brillo/make_unique_ptr.h>
+#include <vboot/crossystem.h>
+
+extern "C" {
+#include "vboot/vboot_host.h"
+}
+
+#include "update_engine/common/hardware.h"
+#include "update_engine/common/hwid_override.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+const char kOOBECompletedMarker[] = "/home/chronos/.oobe_completed";
+
+// The stateful directory used by update_engine to store powerwash-safe files.
+// The files stored here must be whitelisted in the powerwash scripts.
+const char kPowerwashSafeDirectory[] =
+ "/mnt/stateful_partition/unencrypted/preserve";
+
+// 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.
+const char kPowerwashCountMarker[] = "powerwash_count";
+
+// The stateful directory used by update_engine. This directory is wiped during
+// powerwash.
+const char kNonVolatileDirectory[] = "/var/lib/update_engine";
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+namespace hardware {
+
+// Factory defined in hardware.h.
+std::unique_ptr<HardwareInterface> CreateHardware() {
+ return brillo::make_unique_ptr(new HardwareChromeOS());
+}
+
+} // namespace hardware
+
+bool HardwareChromeOS::IsOfficialBuild() const {
+ return VbGetSystemPropertyInt("debug_build") == 0;
+}
+
+bool HardwareChromeOS::IsNormalBootMode() const {
+ bool dev_mode = VbGetSystemPropertyInt("devsw_boot") != 0;
+ return !dev_mode;
+}
+
+bool HardwareChromeOS::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 HardwareChromeOS::GetHardwareClass() const {
+ if (USE_HWID_OVERRIDE) {
+ return HwidOverride::Read(base::FilePath("/"));
+ }
+ return ReadValueFromCrosSystem("hwid");
+}
+
+string HardwareChromeOS::GetFirmwareVersion() const {
+ return ReadValueFromCrosSystem("fwid");
+}
+
+string HardwareChromeOS::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 HardwareChromeOS::GetPowerwashCount() const {
+ int powerwash_count;
+ base::FilePath marker_path = base::FilePath(kPowerwashSafeDirectory).Append(
+ kPowerwashCountMarker);
+ string contents;
+ if (!utils::ReadFile(marker_path.value(), &contents))
+ return -1;
+ base::TrimWhitespaceASCII(contents, base::TRIM_TRAILING, &contents);
+ if (!base::StringToInt(contents, &powerwash_count))
+ return -1;
+ return powerwash_count;
+}
+
+bool HardwareChromeOS::GetNonVolatileDirectory(base::FilePath* path) const {
+ *path = base::FilePath(kNonVolatileDirectory);
+ return true;
+}
+
+bool HardwareChromeOS::GetPowerwashSafeDirectory(base::FilePath* path) const {
+ *path = base::FilePath(kPowerwashSafeDirectory);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/hardware_chromeos.h b/common/hardware_chromeos.h
new file mode 100644
index 0000000..8ac17e8
--- /dev/null
+++ b/common/hardware_chromeos.h
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_HARDWARE_CHROMEOS_H_
+#define UPDATE_ENGINE_COMMON_HARDWARE_CHROMEOS_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/hardware_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements the real interface with Chrome OS verified boot and recovery
+// process.
+class HardwareChromeOS final : public HardwareInterface {
+ public:
+ HardwareChromeOS() = default;
+ ~HardwareChromeOS() override = default;
+
+ // HardwareInterface methods.
+ 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;
+ bool GetNonVolatileDirectory(base::FilePath* path) const override;
+ bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HardwareChromeOS);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_HARDWARE_CHROMEOS_H_
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
new file mode 100644
index 0000000..17ce694
--- /dev/null
+++ b/common/hardware_interface.h
@@ -0,0 +1,81 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/time/time.h>
+
+namespace chromeos_update_engine {
+
+// The hardware interface allows access to the crossystem exposed properties,
+// such as the firmware version, hwid, verified boot mode.
+// These stateless functions are tied together in this interface to facilitate
+// unit testing.
+class HardwareInterface {
+ public:
+ virtual ~HardwareInterface() {}
+
+ // Returns whether this is an official build. Official build means that the
+ // server maintains and updates the build, so update_engine should run and
+ // periodically check for updates.
+ 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. A dev-mode boot will allow the user to access developer-only
+ // features.
+ 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;
+
+ // Store in |path| the path to a non-volatile directory (persisted across
+ // reboots) available for this daemon. In case of an error, such as no
+ // directory available, returns false.
+ virtual bool GetNonVolatileDirectory(base::FilePath* path) const = 0;
+
+ // Store in |path| the path to a non-volatile directory persisted across
+ // powerwash cycles. In case of an error, such as no directory available,
+ // returns false.
+ virtual bool GetPowerwashSafeDirectory(base::FilePath* path) const = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
diff --git a/common/hash_calculator.cc b/common/hash_calculator.cc
new file mode 100644
index 0000000..de6e0f9
--- /dev/null
+++ b/common/hash_calculator.cc
@@ -0,0 +1,143 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/hash_calculator.h"
+
+#include <fcntl.h>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <brillo/data_encoding.h>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+HashCalculator::HashCalculator() : 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 HashCalculator::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 HashCalculator::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
+ brillo::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 HashCalculator::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_ = brillo::data_encoding::Base64Encode(raw_hash_.data(),
+ raw_hash_.size());
+ return true;
+}
+
+bool HashCalculator::RawHashOfBytes(const void* data,
+ size_t length,
+ brillo::Blob* out_hash) {
+ HashCalculator calc;
+ TEST_AND_RETURN_FALSE(calc.Update(data, length));
+ TEST_AND_RETURN_FALSE(calc.Finalize());
+ *out_hash = calc.raw_hash();
+ return true;
+}
+
+bool HashCalculator::RawHashOfData(const brillo::Blob& data,
+ brillo::Blob* out_hash) {
+ return RawHashOfBytes(data.data(), data.size(), out_hash);
+}
+
+off_t HashCalculator::RawHashOfFile(const string& name, off_t length,
+ brillo::Blob* out_hash) {
+ HashCalculator 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 HashCalculator::HashOfBytes(const void* data, size_t length) {
+ HashCalculator calc;
+ calc.Update(data, length);
+ calc.Finalize();
+ return calc.hash();
+}
+
+string HashCalculator::HashOfString(const string& str) {
+ return HashOfBytes(str.data(), str.size());
+}
+
+string HashCalculator::HashOfData(const brillo::Blob& data) {
+ return HashOfBytes(data.data(), data.size());
+}
+
+string HashCalculator::GetContext() const {
+ return string(reinterpret_cast<const char*>(&ctx_), sizeof(ctx_));
+}
+
+bool HashCalculator::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/common/hash_calculator.h b/common/hash_calculator.h
new file mode 100644
index 0000000..f749585
--- /dev/null
+++ b/common/hash_calculator.h
@@ -0,0 +1,107 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_HASH_CALCULATOR_H_
+#define UPDATE_ENGINE_COMMON_HASH_CALCULATOR_H_
+
+#include <openssl/sha.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/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 HashCalculator {
+ public:
+ HashCalculator();
+
+ // 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 brillo::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 HashCalculator::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,
+ brillo::Blob* out_hash);
+ static bool RawHashOfData(const brillo::Blob& data,
+ brillo::Blob* out_hash);
+ static off_t RawHashOfFile(const std::string& name, off_t length,
+ brillo::Blob* out_hash);
+
+ // Used by tests
+ static std::string HashOfBytes(const void* data, size_t length);
+ static std::string HashOfString(const std::string& str);
+ static std::string HashOfData(const brillo::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_;
+ brillo::Blob raw_hash_;
+
+ // Init success
+ bool valid_;
+
+ // The hash state used by OpenSSL
+ SHA256_CTX ctx_;
+ DISALLOW_COPY_AND_ASSIGN(HashCalculator);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_HASH_CALCULATOR_H_
diff --git a/common/hash_calculator_unittest.cc b/common/hash_calculator_unittest.cc
new file mode 100644
index 0000000..27dbc56
--- /dev/null
+++ b/common/hash_calculator_unittest.cc
@@ -0,0 +1,174 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/hash_calculator.h"
+
+#include <math.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/libcurl_http_fetcher.h"
+#include "update_engine/common/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 HashCalculatorTest : public ::testing::Test {
+ public:
+ HashCalculatorTest() {}
+};
+
+TEST_F(HashCalculatorTest, SimpleTest) {
+ HashCalculator calc;
+ calc.Update("hi", 2);
+ calc.Finalize();
+ EXPECT_EQ(kExpectedHash, calc.hash());
+ brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc.raw_hash());
+}
+
+TEST_F(HashCalculatorTest, MultiUpdateTest) {
+ HashCalculator calc;
+ calc.Update("h", 1);
+ calc.Update("i", 1);
+ calc.Finalize();
+ EXPECT_EQ(kExpectedHash, calc.hash());
+ brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc.raw_hash());
+}
+
+TEST_F(HashCalculatorTest, ContextTest) {
+ HashCalculator calc;
+ calc.Update("h", 1);
+ string calc_context = calc.GetContext();
+ calc.Finalize();
+ HashCalculator calc_next;
+ calc_next.SetContext(calc_context);
+ calc_next.Update("i", 1);
+ calc_next.Finalize();
+ EXPECT_EQ(kExpectedHash, calc_next.hash());
+ brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc_next.raw_hash());
+}
+
+TEST_F(HashCalculatorTest, BigTest) {
+ HashCalculator 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(HashCalculatorTest, 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++) {
+ HashCalculator calc;
+ EXPECT_EQ(2, calc.UpdateFile(data_path, kLengths[i]));
+ EXPECT_TRUE(calc.Finalize());
+ EXPECT_EQ(kExpectedHash, calc.hash());
+ brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc.raw_hash());
+ }
+
+ HashCalculator 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(HashCalculatorTest, 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++) {
+ brillo::Blob exp_raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ brillo::Blob raw_hash;
+ EXPECT_EQ(2, HashCalculator::RawHashOfFile(data_path,
+ kLengths[i],
+ &raw_hash));
+ EXPECT_TRUE(exp_raw_hash == raw_hash);
+ }
+}
+
+TEST_F(HashCalculatorTest, UpdateFileNonexistentTest) {
+ HashCalculator calc;
+ EXPECT_EQ(-1, calc.UpdateFile("/some/non-existent/file", -1));
+}
+
+TEST_F(HashCalculatorTest, AbortTest) {
+ // Just make sure we don't crash and valgrind doesn't detect memory leaks
+ {
+ HashCalculator calc;
+ }
+ {
+ HashCalculator calc;
+ calc.Update("h", 1);
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/http_common.cc b/common/http_common.cc
new file mode 100644
index 0000000..7d98889
--- /dev/null
+++ b/common/http_common.cc
@@ -0,0 +1,86 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// Implementation of common HTTP related functions.
+
+#include "update_engine/common/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/common/http_common.h b/common/http_common.h
new file mode 100644
index 0000000..041cad5
--- /dev/null
+++ b/common/http_common.h
@@ -0,0 +1,74 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// This file contains general definitions used in implementing, testing and
+// emulating communication over HTTP.
+
+#ifndef UPDATE_ENGINE_COMMON_HTTP_COMMON_H_
+#define UPDATE_ENGINE_COMMON_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_COMMON_HTTP_COMMON_H_
diff --git a/common/http_fetcher.cc b/common/http_fetcher.cc
new file mode 100644
index 0000000..400b43c
--- /dev/null
+++ b/common/http_fetcher.cc
@@ -0,0 +1,82 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/http_fetcher.h"
+
+#include <base/bind.h>
+
+using base::Closure;
+using brillo::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/common/http_fetcher.h b/common/http_fetcher.h
new file mode 100644
index 0000000..1d4dba9
--- /dev/null
+++ b/common/http_fetcher.h
@@ -0,0 +1,207 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_HTTP_FETCHER_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/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_;
+ brillo::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.
+ brillo::MessageLoop::TaskId no_resolver_idle_id_{
+ brillo::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_COMMON_HTTP_FETCHER_H_
diff --git a/common/http_fetcher_unittest.cc b/common/http_fetcher_unittest.cc
new file mode 100644
index 0000000..a6ba9c8
--- /dev/null
+++ b/common/http_fetcher_unittest.cc
@@ -0,0 +1,1127 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#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/message_loop/message_loop.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 <brillo/message_loops/base_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <brillo/process.h>
+#include <brillo/streams/file_stream.h>
+#include <brillo/streams/stream.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/http_common.h"
+#include "update_engine/common/libcurl_http_fetcher.h"
+#include "update_engine/common/mock_http_fetcher.h"
+#include "update_engine/common/multi_range_http_fetcher.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/proxy_resolver.h"
+
+using brillo::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() : port_(0) {
+ started_ = false;
+
+ // Spawn the server process.
+ unique_ptr<brillo::Process> http_server(new brillo::ProcessImpl());
+ base::FilePath test_server_path =
+ test_utils::GetBuildArtifactsPath().Append("test_http_server");
+ http_server->AddArg(test_server_path.value());
+ http_server->RedirectUsingPipe(STDOUT_FILENO, false);
+
+ if (!http_server->Start()) {
+ ADD_FAILURE() << "failed to spawn http server process";
+ return;
+ }
+ LOG(INFO) << "started http server with pid " << http_server->pid();
+
+ // Wait for server to begin accepting connections, obtain its port.
+ brillo::StreamPtr stdout = brillo::FileStream::FromFileDescriptor(
+ http_server->GetPipe(STDOUT_FILENO), false /* own */, nullptr);
+ if (!stdout)
+ return;
+
+ vector<char> buf(128);
+ string line;
+ while (line.find('\n') == string::npos) {
+ size_t read;
+ if (!stdout->ReadBlocking(buf.data(), buf.size(), &read, nullptr)) {
+ ADD_FAILURE() << "error reading http server stdout";
+ return;
+ }
+ line.append(buf.data(), read);
+ if (read == 0)
+ break;
+ }
+ // Parse the port from the output line.
+ const size_t listening_msg_prefix_len = strlen(kServerListeningMsgPrefix);
+ if (line.size() < listening_msg_prefix_len) {
+ ADD_FAILURE() << "server output too short";
+ return;
+ }
+
+ EXPECT_EQ(kServerListeningMsgPrefix,
+ line.substr(0, listening_msg_prefix_len));
+ string port_str = line.substr(listening_msg_prefix_len);
+ port_str.resize(port_str.find('\n'));
+ EXPECT_TRUE(base::StringToUint(port_str, &port_));
+
+ started_ = true;
+ LOG(INFO) << "server running, listening on port " << port_;
+
+ // Any failure before this point will SIGKILL the test server if started
+ // when the |http_server| goes out of scope.
+ http_server_ = std::move(http_server);
+ }
+
+ ~PythonHttpServer() {
+ // If there's no process, do nothing.
+ if (!http_server_)
+ return;
+ // Wait up to 10 seconds for the process to finish. Destroying the process
+ // will kill it with a SIGKILL otherwise.
+ http_server_->Kill(SIGTERM, 10);
+ }
+
+ in_port_t GetPort() const override {
+ return port_;
+ }
+
+ private:
+ static const char* kServerListeningMsgPrefix;
+
+ unique_ptr<brillo::Process> http_server_;
+ unsigned int 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 {
+ brillo::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:
+ base::MessageLoopForIO base_loop_;
+ brillo::BaseMessageLoop loop_{&base_loop_};
+
+ T test_;
+
+ protected:
+ HttpFetcherTest() {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ EXPECT_EQ(0, brillo::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, NoResponseTest) {
+ // This test starts a new http server but the server doesn't respond and just
+ // closes the connection.
+ 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);
+ // The server will not reply at all, so we can limit the execution time of the
+ // test by reducing the low-speed timeout to something small. The test will
+ // finish once the TimeoutCallback() triggers (every second) and the timeout
+ // expired.
+ fetcher->set_low_speed_limit(kDownloadLowSpeedLimitBps, 1);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(port, "/hang")));
+ this->loop_.Run();
+
+ // Check that no other callback runs in the next two seconds. That would
+ // indicate a leaked callback.
+ bool timeout = false;
+ auto callback = base::Bind([&timeout]{ timeout = true;});
+ this->loop_.PostDelayedTask(FROM_HERE, callback,
+ base::TimeDelta::FromSeconds(2));
+ EXPECT_TRUE(this->loop_.RunOnce(true));
+ EXPECT_TRUE(timeout);
+}
+
+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/common/hwid_override.cc b/common/hwid_override.cc
new file mode 100644
index 0000000..8800e94
--- /dev/null
+++ b/common/hwid_override.cc
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/hwid_override.h"
+
+#include <map>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <brillo/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) {
+ brillo::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/common/hwid_override.h b/common/hwid_override.h
new file mode 100644
index 0000000..d39b572
--- /dev/null
+++ b/common/hwid_override.h
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_HWID_OVERRIDE_H_
+#define UPDATE_ENGINE_COMMON_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_COMMON_HWID_OVERRIDE_H_
diff --git a/common/hwid_override_unittest.cc b/common/hwid_override_unittest.cc
new file mode 100644
index 0000000..fff64bc
--- /dev/null
+++ b/common/hwid_override_unittest.cc
@@ -0,0 +1,67 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/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/common/libcurl_http_fetcher.cc b/common/libcurl_http_fetcher.cc
new file mode 100644
index 0000000..d36e32b
--- /dev/null
+++ b/common/libcurl_http_fetcher.cc
@@ -0,0 +1,576 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/libcurl_http_fetcher.h"
+
+#include <algorithm>
+#include <string>
+
+#include <base/bind.h>
+#include <base/format_macros.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/certificate_checker.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/platform_constants.h"
+
+using base::TimeDelta;
+using brillo::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;
+} // 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 = base::StringPrintf(
+ "%" PRIu64 "-", static_cast<uint64_t>(resume_offset_));
+ if (end_offset)
+ range_str += std::to_string(end_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,
+ constants::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() {
+ // 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_));
+
+ // CurlPerformOnce() may call CleanUp(), so we need to schedule our callback
+ // first, since it could be canceled by this call.
+ if (transfer_in_progress_)
+ CurlPerformOnce();
+}
+
+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/common/libcurl_http_fetcher.h b/common/libcurl_http_fetcher.h
new file mode 100644
index 0000000..52a4111
--- /dev/null
+++ b/common/libcurl_http_fetcher.h
@@ -0,0 +1,257 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_LIBCURL_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_LIBCURL_HTTP_FETCHER_H_
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include <curl/curl.h>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/certificate_checker.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/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 message
+ // 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, brillo::MessageLoop::TaskId> fd_task_maps_[2];
+
+ // The TaskId of the timer we're waiting on. kTaskIdNull if we are not waiting
+ // on it.
+ brillo::MessageLoop::TaskId timeout_id_{brillo::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_COMMON_LIBCURL_HTTP_FETCHER_H_
diff --git a/common/mock_certificate_checker.h b/common/mock_certificate_checker.h
new file mode 100644
index 0000000..1f55ca1
--- /dev/null
+++ b/common/mock_certificate_checker.h
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_MOCK_CERTIFICATE_CHECKER_H_
+#define UPDATE_ENGINE_COMMON_MOCK_CERTIFICATE_CHECKER_H_
+
+#include <gmock/gmock.h>
+#include <openssl/ssl.h>
+
+#include "update_engine/common/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_COMMON_MOCK_CERTIFICATE_CHECKER_H_
diff --git a/common/mock_hardware.h b/common/mock_hardware.h
new file mode 100644
index 0000000..451af91
--- /dev/null
+++ b/common/mock_hardware.h
@@ -0,0 +1,90 @@
+//
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_MOCK_HARDWARE_H_
+#define UPDATE_ENGINE_COMMON_MOCK_HARDWARE_H_
+
+#include <string>
+
+#include "update_engine/common/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, 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));
+ ON_CALL(*this, GetNonVolatileDirectory(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::GetNonVolatileDirectory));
+ ON_CALL(*this, GetPowerwashSafeDirectory(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::GetPowerwashSafeDirectory));
+ }
+
+ ~MockHardware() override = default;
+
+ // Hardware overrides.
+ 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());
+ MOCK_CONST_METHOD1(GetNonVolatileDirectory, bool(base::FilePath*));
+ MOCK_CONST_METHOD1(GetPowerwashSafeDirectory, bool(base::FilePath*));
+
+ // 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_COMMON_MOCK_HARDWARE_H_
diff --git a/common/mock_http_fetcher.cc b/common/mock_http_fetcher.cc
new file mode 100644
index 0000000..f556c34
--- /dev/null
+++ b/common/mock_http_fetcher.cc
@@ -0,0 +1,147 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/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 brillo::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/common/mock_http_fetcher.h b/common/mock_http_fetcher.h
new file mode 100644
index 0000000..16d0504
--- /dev/null
+++ b/common/mock_http_fetcher.h
@@ -0,0 +1,152 @@
+//
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_MOCK_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_MOCK_HTTP_FETCHER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/http_fetcher.h"
+#include "update_engine/fake_system_state.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_(brillo::MessageLoop::kTaskIdNull),
+ paused_(false),
+ fail_transfer_(false),
+ never_use_(false) {
+ 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 brillo::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
+ brillo::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.
+ brillo::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_COMMON_MOCK_HTTP_FETCHER_H_
diff --git a/common/mock_prefs.h b/common/mock_prefs.h
new file mode 100644
index 0000000..0e639a2
--- /dev/null
+++ b/common/mock_prefs.h
@@ -0,0 +1,51 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_MOCK_PREFS_H_
+#define UPDATE_ENGINE_COMMON_MOCK_PREFS_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockPrefs : public PrefsInterface {
+ public:
+ MOCK_CONST_METHOD2(GetString,
+ bool(const std::string& key, std::string* value));
+ MOCK_METHOD2(SetString, bool(const std::string& key,
+ const std::string& value));
+ MOCK_CONST_METHOD2(GetInt64, bool(const std::string& key, int64_t* value));
+ MOCK_METHOD2(SetInt64, bool(const std::string& key, const int64_t value));
+
+ MOCK_CONST_METHOD2(GetBoolean, bool(const std::string& key, bool* value));
+ MOCK_METHOD2(SetBoolean, bool(const std::string& key, const bool value));
+
+ MOCK_CONST_METHOD1(Exists, bool(const std::string& key));
+ MOCK_METHOD1(Delete, bool(const std::string& key));
+
+ MOCK_METHOD2(AddObserver, void(const std::string& key, ObserverInterface*));
+ MOCK_METHOD2(RemoveObserver,
+ void(const std::string& key, ObserverInterface*));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_MOCK_PREFS_H_
diff --git a/common/multi_range_http_fetcher.cc b/common/multi_range_http_fetcher.cc
new file mode 100644
index 0000000..0a97b6e
--- /dev/null
+++ b/common/multi_range_http_fetcher.cc
@@ -0,0 +1,190 @@
+//
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/multi_range_http_fetcher.h"
+
+#include <base/strings/stringprintf.h>
+
+#include <algorithm>
+#include <string>
+
+#include "update_engine/common/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())
+ range_str += std::to_string(length());
+ else
+ range_str += "?";
+ return range_str;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/multi_range_http_fetcher.h b/common/multi_range_http_fetcher.h
new file mode 100644
index 0000000..265d4cc
--- /dev/null
+++ b/common/multi_range_http_fetcher.h
@@ -0,0 +1,180 @@
+//
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_MULTI_RANGE_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_MULTI_RANGE_HTTP_FETCHER_H_
+
+#include <deque>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "update_engine/common/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_COMMON_MULTI_RANGE_HTTP_FETCHER_H_
diff --git a/common/platform_constants.h b/common/platform_constants.h
new file mode 100644
index 0000000..8331064
--- /dev/null
+++ b/common/platform_constants.h
@@ -0,0 +1,53 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_PLATFORM_CONSTANTS_H_
+#define UPDATE_ENGINE_COMMON_PLATFORM_CONSTANTS_H_
+
+namespace chromeos_update_engine {
+namespace constants {
+
+// The default URL used by all products when running in normal mode. The AUTest
+// URL is used when testing normal images against the alternative AUTest server.
+// Note that the URL can be override in run-time in certain cases.
+extern const char kOmahaDefaultProductionURL[];
+extern const char kOmahaDefaultAUTestURL[];
+
+// Our product name used in Omaha. This value must match the one configured in
+// the server side and is sent on every request.
+extern const char kOmahaUpdaterID[];
+
+// The name of the platform as sent to Omaha.
+extern const char kOmahaPlatformName[];
+
+// Path to the location of the public half of the payload key. The payload key
+// is used to sign the contents of the payload binary file: the manifest and the
+// whole payload.
+extern const char kUpdatePayloadPublicKeyPath[];
+
+// Path to the directory containing all the SSL certificates accepted by
+// update_engine when sending requests to Omaha and the download server (if
+// HTTPS is used for that as well).
+extern const char kCACertificatesPath[];
+
+// Path to the file used to notify chrome about the deadline of the last omaha
+// response. Empty if not supported.
+extern const char kOmahaResponseDeadlineFile[];
+
+} // namespace constants
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_PLATFORM_CONSTANTS_H_
diff --git a/common/platform_constants_android.cc b/common/platform_constants_android.cc
new file mode 100644
index 0000000..b6084ac
--- /dev/null
+++ b/common/platform_constants_android.cc
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/platform_constants.h"
+
+namespace chromeos_update_engine {
+namespace constants {
+
+const char kOmahaDefaultProductionURL[] =
+ "https://clients2.google.com/service/update2/brillo";
+const char kOmahaDefaultAUTestURL[] =
+ "https://clients2.google.com/service/update2/brillo";
+const char kOmahaUpdaterID[] = "Brillo";
+const char kOmahaPlatformName[] = "Brillo";
+const char kUpdatePayloadPublicKeyPath[] =
+ "/etc/update_engine/update-payload-key.pub.pem";
+const char kCACertificatesPath[] = "/system/etc/security/cacerts";
+// No deadline file API support on Android.
+const char kOmahaResponseDeadlineFile[] = "";
+
+} // namespace constants
+} // namespace chromeos_update_engine
diff --git a/common/platform_constants_chromeos.cc b/common/platform_constants_chromeos.cc
new file mode 100644
index 0000000..45ca309
--- /dev/null
+++ b/common/platform_constants_chromeos.cc
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/platform_constants.h"
+
+namespace chromeos_update_engine {
+namespace constants {
+
+const char kOmahaDefaultProductionURL[] =
+ "https://tools.google.com/service/update2";
+const char kOmahaDefaultAUTestURL[] =
+ "https://omaha.sandbox.google.com/service/update2";
+const char kOmahaUpdaterID[] = "ChromeOSUpdateEngine";
+const char kOmahaPlatformName[] = "Chrome OS";
+const char kUpdatePayloadPublicKeyPath[] =
+ "/usr/share/update_engine/update-payload-key.pub.pem";
+const char kCACertificatesPath[] = "/usr/share/chromeos-ca-certificates";
+const char kOmahaResponseDeadlineFile[] =
+ "/tmp/update-check-response-deadline";
+
+} // namespace constants
+} // namespace chromeos_update_engine
diff --git a/common/prefs.cc b/common/prefs.cc
new file mode 100644
index 0000000..9d3a30f
--- /dev/null
+++ b/common/prefs.cc
@@ -0,0 +1,143 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/prefs.h"
+
+#include <algorithm>
+
+#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/common/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) const {
+ 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));
+ if (!base::DirectoryExists(filename.DirName())) {
+ // Only attempt to create the directory if it doesn't exist to avoid calls
+ // to parent directories where we might not have permission to write to.
+ TEST_AND_RETURN_FALSE(base::CreateDirectory(filename.DirName()));
+ }
+ TEST_AND_RETURN_FALSE(base::WriteFile(filename, value.data(), value.size()) ==
+ static_cast<int>(value.size()));
+ const auto observers_for_key = observers_.find(key);
+ if (observers_for_key != observers_.end()) {
+ std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+ for (ObserverInterface* observer : copy_observers)
+ observer->OnPrefSet(key);
+ }
+ return true;
+}
+
+bool Prefs::GetInt64(const string& key, int64_t* value) const {
+ 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) const {
+ 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) const {
+ 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));
+ TEST_AND_RETURN_FALSE(base::DeleteFile(filename, false));
+ const auto observers_for_key = observers_.find(key);
+ if (observers_for_key != observers_.end()) {
+ std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+ for (ObserverInterface* observer : copy_observers)
+ observer->OnPrefDeleted(key);
+ }
+ return true;
+}
+
+void Prefs::AddObserver(const string& key, ObserverInterface* observer) {
+ observers_[key].push_back(observer);
+}
+
+void Prefs::RemoveObserver(const string& key, ObserverInterface* observer) {
+ std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+ auto observer_it =
+ std::find(observers_for_key.begin(), observers_for_key.end(), observer);
+ if (observer_it != observers_for_key.end())
+ observers_for_key.erase(observer_it);
+}
+
+bool Prefs::GetFileNameForKey(const string& key,
+ base::FilePath* filename) const {
+ // 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/common/prefs.h b/common/prefs.h
new file mode 100644
index 0000000..f11abc3
--- /dev/null
+++ b/common/prefs.h
@@ -0,0 +1,81 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_PREFS_H_
+#define UPDATE_ENGINE_COMMON_PREFS_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+
+#include "gtest/gtest_prod.h" // for FRIEND_TEST
+#include "update_engine/common/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() = default;
+
+ // 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) const override;
+ bool SetString(const std::string& key, const std::string& value) override;
+ bool GetInt64(const std::string& key, int64_t* value) const override;
+ bool SetInt64(const std::string& key, const int64_t value) override;
+ bool GetBoolean(const std::string& key, bool* value) const override;
+ bool SetBoolean(const std::string& key, const bool value) override;
+
+ bool Exists(const std::string& key) const override;
+ bool Delete(const std::string& key) override;
+
+ void AddObserver(const std::string& key,
+ ObserverInterface* observer) override;
+ void RemoveObserver(const std::string& key,
+ ObserverInterface* observer) 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) const;
+
+ // Preference store directory.
+ base::FilePath prefs_dir_;
+
+ // The registered observers watching for changes.
+ std::map<std::string, std::vector<ObserverInterface*>> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(Prefs);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_PREFS_H_
diff --git a/common/prefs_interface.h b/common/prefs_interface.h
new file mode 100644
index 0000000..03ae3ec
--- /dev/null
+++ b/common/prefs_interface.h
@@ -0,0 +1,97 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_PREFS_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_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:
+ // Observer class to be notified about key value changes.
+ class ObserverInterface {
+ public:
+ virtual ~ObserverInterface() = default;
+
+ // Called when the value is set for the observed |key|.
+ virtual void OnPrefSet(const std::string& key) = 0;
+
+ // Called when the observed |key| is deleted.
+ virtual void OnPrefDeleted(const std::string& key) = 0;
+ };
+
+ virtual ~PrefsInterface() = default;
+
+ // 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) const = 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) const = 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) const = 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) const = 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;
+
+ // Add an observer to watch whenever the given |key| is modified. The
+ // OnPrefSet() and OnPrefDelete() methods will be called whenever any of the
+ // Set*() methods or the Delete() method are called on the given key,
+ // respectively.
+ virtual void AddObserver(const std::string& key,
+ ObserverInterface* observer) = 0;
+
+ // Remove an observer added with AddObserver(). The observer won't be called
+ // anymore for future Set*() and Delete() method calls.
+ virtual void RemoveObserver(const std::string& key,
+ ObserverInterface* observer) = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_PREFS_INTERFACE_H_
diff --git a/common/prefs_unittest.cc b/common/prefs_unittest.cc
new file mode 100644
index 0000000..354b05b
--- /dev/null
+++ b/common/prefs_unittest.cc
@@ -0,0 +1,337 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using std::string;
+using testing::Eq;
+using testing::_;
+
+namespace {
+// Test key used along the tests.
+const char kKey[] = "test-key";
+}
+
+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 kAllvalidCharsKey[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-";
+ base::FilePath path;
+ EXPECT_TRUE(prefs_.GetFileNameForKey(kAllvalidCharsKey, &path));
+ EXPECT_EQ(prefs_dir_.Append(kAllvalidCharsKey).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 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 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 kKeyWithDots[] = ".no-dots";
+ EXPECT_FALSE(prefs_.SetString(kKeyWithDots, "some value"));
+ EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKeyWithDots)));
+}
+
+TEST_F(PrefsTest, SetStringCreateDir) {
+ 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")));
+ EXPECT_FALSE(prefs_.SetString(kKey, "test value"));
+}
+
+TEST_F(PrefsTest, SetStringFileCreationFailure) {
+ 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) {
+ ASSERT_TRUE(SetValue(kKey, " \n 25 \t "));
+ int64_t value;
+ EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+ EXPECT_EQ(25, value);
+}
+
+TEST_F(PrefsTest, GetInt64BadValue) {
+ ASSERT_TRUE(SetValue(kKey, "30a"));
+ int64_t value;
+ EXPECT_FALSE(prefs_.GetInt64(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetInt64Max) {
+ 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) {
+ 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) {
+ 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) {
+ 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 kKeyWithSpaces[] = "s p a c e s";
+ EXPECT_FALSE(prefs_.SetInt64(kKeyWithSpaces, 20));
+ EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKeyWithSpaces)));
+}
+
+TEST_F(PrefsTest, SetInt64Max) {
+ 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) {
+ 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) {
+ 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) {
+ // 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) {
+ // 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));
+}
+
+class MockPrefsObserver : public PrefsInterface::ObserverInterface {
+ public:
+ MOCK_METHOD1(OnPrefSet, void(const string&));
+ MOCK_METHOD1(OnPrefDeleted, void(const string& key));
+};
+
+TEST_F(PrefsTest, ObserversCalled) {
+ MockPrefsObserver mock_obserser;
+ prefs_.AddObserver(kKey, &mock_obserser);
+
+ EXPECT_CALL(mock_obserser, OnPrefSet(Eq(kKey)));
+ EXPECT_CALL(mock_obserser, OnPrefDeleted(_)).Times(0);
+ prefs_.SetString(kKey, "value");
+ testing::Mock::VerifyAndClearExpectations(&mock_obserser);
+
+ EXPECT_CALL(mock_obserser, OnPrefSet(_)).Times(0);
+ EXPECT_CALL(mock_obserser, OnPrefDeleted(Eq(kKey)));
+ prefs_.Delete(kKey);
+ testing::Mock::VerifyAndClearExpectations(&mock_obserser);
+
+ prefs_.RemoveObserver(kKey, &mock_obserser);
+}
+
+TEST_F(PrefsTest, OnlyCalledOnObservedKeys) {
+ MockPrefsObserver mock_obserser;
+ const char kUnusedKey[] = "unused-key";
+ prefs_.AddObserver(kUnusedKey, &mock_obserser);
+
+ EXPECT_CALL(mock_obserser, OnPrefSet(_)).Times(0);
+ EXPECT_CALL(mock_obserser, OnPrefDeleted(_)).Times(0);
+ prefs_.SetString(kKey, "value");
+ prefs_.Delete(kKey);
+
+ prefs_.RemoveObserver(kUnusedKey, &mock_obserser);
+}
+
+TEST_F(PrefsTest, RemovedObserversNotCalled) {
+ MockPrefsObserver mock_obserser_a, mock_obserser_b;
+ prefs_.AddObserver(kKey, &mock_obserser_a);
+ prefs_.AddObserver(kKey, &mock_obserser_b);
+ EXPECT_CALL(mock_obserser_a, OnPrefSet(_)).Times(2);
+ EXPECT_CALL(mock_obserser_b, OnPrefSet(_)).Times(1);
+ EXPECT_TRUE(prefs_.SetString(kKey, "value"));
+ prefs_.RemoveObserver(kKey, &mock_obserser_b);
+ EXPECT_TRUE(prefs_.SetString(kKey, "other value"));
+ prefs_.RemoveObserver(kKey, &mock_obserser_a);
+ EXPECT_TRUE(prefs_.SetString(kKey, "yet another value"));
+}
+
+TEST_F(PrefsTest, UnsuccessfulCallsNotObserved) {
+ MockPrefsObserver mock_obserser;
+ const char kInvalidKey[] = "no spaces or .";
+ prefs_.AddObserver(kInvalidKey, &mock_obserser);
+
+ EXPECT_CALL(mock_obserser, OnPrefSet(_)).Times(0);
+ EXPECT_CALL(mock_obserser, OnPrefDeleted(_)).Times(0);
+ EXPECT_FALSE(prefs_.SetString(kInvalidKey, "value"));
+ EXPECT_FALSE(prefs_.Delete(kInvalidKey));
+
+ prefs_.RemoveObserver(kInvalidKey, &mock_obserser);
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/subprocess.cc b/common/subprocess.cc
new file mode 100644
index 0000000..f43aaac
--- /dev/null
+++ b/common/subprocess.cc
@@ -0,0 +1,271 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/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 <brillo/process.h>
+#include <brillo/secure_blob.h>
+
+using brillo::MessageLoop;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+bool SetupChild(const std::map<string, string>& env, uint32_t flags) {
+ // Setup the environment variables.
+ clearenv();
+ for (const auto& key_value : env) {
+ setenv(key_value.first.c_str(), key_value.second.c_str(), 0);
+ }
+
+ if ((flags & Subprocess::kRedirectStderrToStdout) != 0) {
+ if (HANDLE_EINTR(dup2(STDOUT_FILENO, STDERR_FILENO)) != STDERR_FILENO)
+ return false;
+ }
+
+ int fd = HANDLE_EINTR(open("/dev/null", O_RDONLY));
+ if (fd < 0)
+ return false;
+ if (HANDLE_EINTR(dup2(fd, STDIN_FILENO)) != STDIN_FILENO)
+ return false;
+ IGNORE_EINTR(close(fd));
+
+ return true;
+}
+
+// Helper function to launch a process with the given Subprocess::Flags.
+// This function only sets up and starts the process according to the |flags|.
+// The caller is responsible for watching the termination of the subprocess.
+// Return whether the process was successfully launched and fills in the |proc|
+// Process.
+bool LaunchProcess(const vector<string>& cmd,
+ uint32_t flags,
+ brillo::Process* proc) {
+ for (const string& arg : cmd)
+ proc->AddArg(arg);
+ proc->SetSearchPath((flags & Subprocess::kSearchPath) != 0);
+
+ // Create an environment for the child process with just the required PATHs.
+ std::map<string, string> env;
+ for (const char* key : {"LD_LIBRARY_PATH", "PATH"}) {
+ const char* value = getenv(key);
+ if (value)
+ env.emplace(key, value);
+ }
+
+ proc->RedirectUsingPipe(STDOUT_FILENO, false);
+ proc->SetPreExecCallback(base::Bind(&SetupChild, env, flags));
+
+ return proc->Start();
+}
+
+} // namespace
+
+void Subprocess::Init(
+ brillo::AsynchronousSignalHandlerInterface* async_signal_handler) {
+ if (subprocess_singleton_ == this)
+ return;
+ CHECK(subprocess_singleton_ == nullptr);
+ subprocess_singleton_ = this;
+
+ process_reaper_.Register(async_signal_handler);
+}
+
+Subprocess::~Subprocess() {
+ if (subprocess_singleton_ == this)
+ subprocess_singleton_ = nullptr;
+}
+
+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;
+ MessageLoop::current()->CancelTask(record->stdout_task_id);
+ record->stdout_task_id = MessageLoop::kTaskIdNull;
+ }
+ } else if (rc == 0) {
+ // A value of 0 means that the child closed its end of the pipe and there
+ // is nothing else to read from stdout.
+ MessageLoop::current()->CancelTask(record->stdout_task_id);
+ record->stdout_task_id = MessageLoop::kTaskIdNull;
+ } else {
+ record->stdout.append(buf, rc);
+ }
+ } while (rc > 0);
+}
+
+void Subprocess::ChildExitedCallback(const siginfo_t& info) {
+ auto pid_record = subprocess_records_.find(info.si_pid);
+ if (pid_record == subprocess_records_.end())
+ return;
+ SubprocessRecord* record = pid_record->second.get();
+
+ // Make sure we read any remaining process output and then close the pipe.
+ OnStdoutReady(record);
+
+ MessageLoop::current()->CancelTask(record->stdout_task_id);
+ record->stdout_task_id = MessageLoop::kTaskIdNull;
+
+ // Release and close all the pipes now.
+ record->proc.Release();
+ record->proc.Reset(0);
+
+ // Don't print any log if the subprocess exited with exit code 0.
+ if (info.si_code != CLD_EXITED) {
+ LOG(INFO) << "Subprocess terminated with si_code " << info.si_code;
+ } else if (info.si_status != 0) {
+ LOG(INFO) << "Subprocess exited with si_status: " << info.si_status;
+ }
+
+ if (!record->stdout.empty()) {
+ LOG(INFO) << "Subprocess output:\n" << record->stdout;
+ }
+ if (!record->callback.is_null()) {
+ record->callback.Run(info.si_status, record->stdout);
+ }
+ subprocess_records_.erase(pid_record);
+}
+
+pid_t Subprocess::Exec(const vector<string>& cmd,
+ const ExecCallback& callback) {
+ return ExecFlags(cmd, kRedirectStderrToStdout, callback);
+}
+
+pid_t Subprocess::ExecFlags(const vector<string>& cmd,
+ uint32_t flags,
+ const ExecCallback& callback) {
+ unique_ptr<SubprocessRecord> record(new SubprocessRecord(callback));
+
+ if (!LaunchProcess(cmd, flags, &record->proc)) {
+ LOG(ERROR) << "Failed to launch subprocess";
+ return 0;
+ }
+
+ pid_t pid = record->proc.pid();
+ CHECK(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind(
+ &Subprocess::ChildExitedCallback,
+ base::Unretained(this))));
+
+ record->stdout_fd = record->proc.GetPipe(STDOUT_FILENO);
+ // Capture the subprocess output. Make our end of the pipe non-blocking.
+ int fd_flags = fcntl(record->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->stdout_task_id = MessageLoop::current()->WatchFileDescriptor(
+ FROM_HERE,
+ record->stdout_fd,
+ MessageLoop::WatchMode::kWatchRead,
+ true,
+ base::Bind(&Subprocess::OnStdoutReady, record.get()));
+
+ subprocess_records_[pid].reset(record.release());
+ return pid;
+}
+
+void Subprocess::KillExec(pid_t pid) {
+ auto pid_record = subprocess_records_.find(pid);
+ if (pid_record == subprocess_records_.end())
+ return;
+ pid_record->second->callback.Reset();
+ kill(pid, SIGTERM);
+}
+
+bool Subprocess::SynchronousExec(const vector<string>& cmd,
+ int* return_code,
+ string* stdout) {
+ // The default for SynchronousExec is to use kSearchPath since the code relies
+ // on that.
+ return SynchronousExecFlags(
+ cmd,
+ kRedirectStderrToStdout | kSearchPath,
+ return_code,
+ stdout);
+}
+
+bool Subprocess::SynchronousExecFlags(const vector<string>& cmd,
+ uint32_t flags,
+ int* return_code,
+ string* stdout) {
+ brillo::ProcessImpl proc;
+ if (!LaunchProcess(cmd, flags, &proc)) {
+ LOG(ERROR) << "Failed to launch subprocess";
+ return false;
+ }
+
+ if (stdout) {
+ stdout->clear();
+ }
+
+ int fd = proc.GetPipe(STDOUT_FILENO);
+ vector<char> buffer(32 * 1024);
+ while (true) {
+ int rc = HANDLE_EINTR(read(fd, buffer.data(), buffer.size()));
+ if (rc < 0) {
+ PLOG(ERROR) << "Reading from child's output";
+ break;
+ } else if (rc == 0) {
+ break;
+ } else {
+ if (stdout)
+ stdout->append(buffer.data(), rc);
+ }
+ }
+ // At this point, the subprocess already closed the output, so we only need to
+ // wait for it to finish.
+ int proc_return_code = proc.Wait();
+ if (return_code)
+ *return_code = proc_return_code;
+ return proc_return_code != brillo::Process::kErrorExitStatus;
+}
+
+bool Subprocess::SubprocessInFlight() {
+ for (const auto& pid_record : subprocess_records_) {
+ if (!pid_record.second->callback.is_null())
+ return true;
+ }
+ return false;
+}
+
+Subprocess* Subprocess::subprocess_singleton_ = nullptr;
+
+} // namespace chromeos_update_engine
diff --git a/common/subprocess.h b/common/subprocess.h
new file mode 100644
index 0000000..6b952dc
--- /dev/null
+++ b/common/subprocess.h
@@ -0,0 +1,141 @@
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_SUBPROCESS_H_
+#define UPDATE_ENGINE_COMMON_SUBPROCESS_H_
+
+#include <unistd.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/asynchronous_signal_handler_interface.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/process.h>
+#include <brillo/process_reaper.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().
+
+// To create the Subprocess singleton just instantiate it with and call Init().
+// You can't have two Subprocess instances initialized at the same time.
+
+namespace chromeos_update_engine {
+
+class Subprocess {
+ public:
+ enum Flags {
+ kSearchPath = 1 << 0,
+ kRedirectStderrToStdout = 1 << 1,
+ };
+
+ // Callback type used when an async process terminates. It receives the exit
+ // code and the stdout output (and stderr if redirected).
+ using ExecCallback = base::Callback<void(int, const std::string&)>;
+
+ Subprocess() = default;
+
+ // Destroy and unregister the Subprocess singleton.
+ ~Subprocess();
+
+ // Initialize and register the Subprocess singleton.
+ void Init(brillo::AsynchronousSignalHandlerInterface* async_signal_handler);
+
+ // Launches a process in the background and calls the passed |callback| when
+ // the process exits.
+ // Returns the process id of the new launched process or 0 in case of failure.
+ pid_t Exec(const std::vector<std::string>& cmd, const ExecCallback& callback);
+ pid_t ExecFlags(const std::vector<std::string>& cmd,
+ uint32_t flags,
+ const ExecCallback& callback);
+
+ // Kills the running process with SIGTERM and ignores the callback.
+ void KillExec(pid_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 SynchronousExec(const std::vector<std::string>& cmd,
+ int* return_code,
+ std::string* stdout);
+ static bool SynchronousExecFlags(const std::vector<std::string>& cmd,
+ uint32_t flags,
+ 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 {
+ explicit SubprocessRecord(const ExecCallback& callback)
+ : callback(callback) {}
+
+ // The callback supplied by the caller.
+ ExecCallback callback;
+
+ // The ProcessImpl instance managing the child process. Destroying this
+ // will close our end of the pipes we have open.
+ brillo::ProcessImpl proc;
+
+ // These are used to monitor the stdout of the running process, including
+ // the stderr if it was redirected.
+ brillo::MessageLoop::TaskId stdout_task_id{
+ brillo::MessageLoop::kTaskIdNull};
+ int stdout_fd{-1};
+ std::string stdout;
+ };
+
+ // Callback which runs whenever there is input available on the subprocess
+ // stdout pipe.
+ static void OnStdoutReady(SubprocessRecord* record);
+
+ // Callback for when any subprocess terminates. This calls the user
+ // requested callback.
+ void ChildExitedCallback(const siginfo_t& info);
+
+ // 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<pid_t, std::unique_ptr<SubprocessRecord>> subprocess_records_;
+
+ // Used to watch for child processes.
+ brillo::ProcessReaper process_reaper_;
+
+ DISALLOW_COPY_AND_ASSIGN(Subprocess);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_SUBPROCESS_H_
diff --git a/common/subprocess_unittest.cc b/common/subprocess_unittest.cc
new file mode 100644
index 0000000..b37dc91
--- /dev/null
+++ b/common/subprocess_unittest.cc
@@ -0,0 +1,263 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/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 <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <brillo/strings/string_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class SubprocessTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ async_signal_handler_.Init();
+ subprocess_.Init(&async_signal_handler_);
+ }
+
+ base::MessageLoopForIO base_loop_;
+ brillo::BaseMessageLoop loop_{&base_loop_};
+ brillo::AsynchronousSignalHandler async_signal_handler_;
+ Subprocess subprocess_;
+};
+
+namespace {
+
+int local_server_port = 0;
+
+void ExpectedResults(int expected_return_code, const string& expected_output,
+ int return_code, const string& output) {
+ EXPECT_EQ(expected_return_code, return_code);
+ EXPECT_EQ(expected_output, output);
+ MessageLoop::current()->BreakLoop();
+}
+
+void ExpectedEnvVars(int return_code, const string& output) {
+ EXPECT_EQ(0, return_code);
+ const std::set<string> allowed_envs = {"LD_LIBRARY_PATH", "PATH"};
+ for (string key_value : brillo::string_utils::Split(output, "\n")) {
+ auto key_value_pair = brillo::string_utils::SplitAtFirst(
+ key_value, "=", true);
+ EXPECT_NE(allowed_envs.end(), allowed_envs.find(key_value_pair.first));
+ }
+ MessageLoop::current()->BreakLoop();
+}
+
+} // namespace
+
+TEST_F(SubprocessTest, IsASingleton) {
+ EXPECT_EQ(&subprocess_, &Subprocess::Get());
+}
+
+TEST_F(SubprocessTest, InactiveInstancesDontChangeTheSingleton) {
+ std::unique_ptr<Subprocess> another_subprocess(new Subprocess());
+ EXPECT_EQ(&subprocess_, &Subprocess::Get());
+ another_subprocess.reset();
+ EXPECT_EQ(&subprocess_, &Subprocess::Get());
+}
+
+TEST_F(SubprocessTest, SimpleTest) {
+ EXPECT_TRUE(subprocess_.Exec({"/bin/false"},
+ base::Bind(&ExpectedResults, 1, "")));
+ loop_.Run();
+}
+
+TEST_F(SubprocessTest, EchoTest) {
+ EXPECT_TRUE(subprocess_.Exec(
+ {"/bin/sh", "-c", "echo this is stdout; echo this is stderr >&2"},
+ base::Bind(&ExpectedResults, 0, "this is stdout\nthis is stderr\n")));
+ loop_.Run();
+}
+
+TEST_F(SubprocessTest, StderrNotIncludedInOutputTest) {
+ EXPECT_TRUE(subprocess_.ExecFlags(
+ {"/bin/sh", "-c", "echo on stdout; echo on stderr >&2"},
+ 0,
+ base::Bind(&ExpectedResults, 0, "on stdout\n")));
+ loop_.Run();
+}
+
+TEST_F(SubprocessTest, EnvVarsAreFiltered) {
+ EXPECT_TRUE(subprocess_.Exec({"/usr/bin/env"}, base::Bind(&ExpectedEnvVars)));
+ loop_.Run();
+}
+
+TEST_F(SubprocessTest, SynchronousTrueSearchsOnPath) {
+ int rc = -1;
+ EXPECT_TRUE(Subprocess::SynchronousExecFlags(
+ {"true"}, Subprocess::kSearchPath, &rc, nullptr));
+ EXPECT_EQ(0, rc);
+}
+
+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) {
+ int rc = -1;
+ ASSERT_TRUE(Subprocess::SynchronousExec(
+ {"/bin/sh", "-c", "echo test"},
+ &rc, nullptr));
+ EXPECT_EQ(0, rc);
+}
+
+namespace {
+void CallbackBad(int return_code, const string& output) {
+ ADD_FAILURE() << "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, base::Bind(&CallbackBad));
+ 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;
+ }
+ 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.
+ brillo::MessageLoopRunUntil(
+ &loop_,
+ TimeDelta::FromSeconds(10),
+ base::Bind([] {
+ return Subprocess::Get().subprocess_records_.empty();
+ }));
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/terminator.cc b/common/terminator.cc
new file mode 100644
index 0000000..62adafd
--- /dev/null
+++ b/common/terminator.cc
@@ -0,0 +1,56 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/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/common/terminator.h b/common/terminator.h
new file mode 100644
index 0000000..20616f6
--- /dev/null
+++ b/common/terminator.h
@@ -0,0 +1,65 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_TERMINATOR_H_
+#define UPDATE_ENGINE_COMMON_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_COMMON_TERMINATOR_H_
diff --git a/common/terminator_unittest.cc b/common/terminator_unittest.cc
new file mode 100644
index 0000000..5e8302f
--- /dev/null
+++ b/common/terminator_unittest.cc
@@ -0,0 +1,85 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/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/common/test_utils.cc b/common/test_utils.cc
new file mode 100644
index 0000000..f89c448
--- /dev/null
+++ b/common/test_utils.cc
@@ -0,0 +1,268 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/test_utils.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/file_writer.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 brillo::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 brillo::Blob& expected,
+ const brillo::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(brillo::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=%" PRIuS " 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));
+}
+
+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/common/test_utils.h b/common/test_utils.h
new file mode 100644
index 0000000..616bdd3
--- /dev/null
+++ b/common/test_utils.h
@@ -0,0 +1,271 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_TEST_UTILS_H_
+#define UPDATE_ENGINE_COMMON_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 <gtest/gtest.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_metadata.pb.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 brillo::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 brillo::Blob& a, const brillo::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(brillo::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("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_;
+};
+
+// 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_COMMON_TEST_UTILS_H_
diff --git a/common/utils.cc b/common/utils.cc
new file mode 100644
index 0000000..dd08a89
--- /dev/null
+++ b/common/utils.cc
@@ -0,0 +1,1567 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/utils.h"
+
+#include <stdint.h>
+
+#include <dirent.h>
+#include <elf.h>
+#include <endian.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/format_macros.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 <brillo/data_encoding.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/payload_consumer/file_writer.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;
+
+// The path to the kernel's boot_id.
+const char kBootIdPath[] = "/proc/sys/kernel/random/boot_id";
+
+// 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);
+ }
+ }
+}
+
+// If |path| is absolute, or explicit relative to the current working directory,
+// leaves it as is. Otherwise, uses the system's temp directory, as defined by
+// base::GetTempDir() and prepends it to |path|. On success stores the full
+// temporary path in |template_path| and returns true.
+bool GetTempName(const string& path, base::FilePath* template_path) {
+ if (path[0] == '/' || base::StartsWithASCII(path, "./", true) ||
+ base::StartsWithASCII(path, "../", true)) {
+ *template_path = base::FilePath(path);
+ return true;
+ }
+
+ base::FilePath temp_dir;
+ TEST_AND_RETURN_FALSE(base::GetTempDir(&temp_dir));
+ *template_path = temp_dir.Append(path);
+ return true;
+}
+
+} // 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 "";
+}
+
+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,
+ brillo::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, brillo::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,
+ brillo::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 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;
+ }
+}
+
+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 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());
+}
+
+bool MakeTempFile(const string& base_filename_template,
+ string* filename,
+ int* fd) {
+ base::FilePath filename_template;
+ TEST_AND_RETURN_FALSE(
+ GetTempName(base_filename_template, &filename_template));
+ DCHECK(filename || fd);
+ vector<char> buf(filename_template.value().size() + 1);
+ memcpy(buf.data(), filename_template.value().data(),
+ filename_template.value().size());
+ buf[filename_template.value().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) {
+ base::FilePath dirname_template;
+ TEST_AND_RETURN_FALSE(GetTempName(base_dirname_template, &dirname_template));
+ DCHECK(dirname);
+ vector<char> buf(dirname_template.value().size() + 1);
+ memcpy(buf.data(), dirname_template.value().data(),
+ dirname_template.value().size());
+ buf[dirname_template.value().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", "ext4", "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) {
+ brillo::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) {
+ brillo::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) {
+ brillo::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() {
+ brillo::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 NetworkConnectionType::kUnknown:
+ return metrics::ConnectionType::kUnknown;
+
+ case NetworkConnectionType::kEthernet:
+ if (tethering == NetworkTethering::kConfirmed)
+ return metrics::ConnectionType::kTetheredEthernet;
+ else
+ return metrics::ConnectionType::kEthernet;
+
+ case NetworkConnectionType::kWifi:
+ if (tethering == NetworkTethering::kConfirmed)
+ return metrics::ConnectionType::kTetheredWifi;
+ else
+ return metrics::ConnectionType::kWifi;
+
+ case NetworkConnectionType::kWimax:
+ return metrics::ConnectionType::kWimax;
+
+ case NetworkConnectionType::kBluetooth:
+ return metrics::ConnectionType::kBluetooth;
+
+ case NetworkConnectionType::kCellular:
+ return metrics::ConnectionType::kCellular;
+ }
+
+ LOG(ERROR) << "Unexpected network connection type: type="
+ << static_cast<int>(type)
+ << ", tethering=" << static_cast<int>(tethering);
+
+ return metrics::ConnectionType::kUnknown;
+}
+
+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;
+}
+
+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 = brillo::data_encoding::Base64Encode(payload_hash);
+ return base::StringPrintf("cros_update_size_%" PRIuS "_hash_%s",
+ payload_size,
+ encoded_hash.c_str());
+}
+
+bool DecodeAndStoreBase64String(const string& base64_encoded,
+ base::FilePath *out_path) {
+ brillo::Blob contents;
+
+ out_path->clear();
+
+ if (base64_encoded.size() == 0) {
+ LOG(ERROR) << "Can't decode empty string.";
+ return false;
+ }
+
+ if (!brillo::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 brillo::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,
+ brillo::Blob* out_data, ssize_t out_data_size,
+ size_t block_size) {
+ brillo::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;
+}
+
+bool GetBootId(string* boot_id) {
+ TEST_AND_RETURN_FALSE(
+ base::ReadFileToString(base::FilePath(kBootIdPath), boot_id));
+ base::TrimWhitespaceASCII(*boot_id, base::TRIM_TRAILING, boot_id);
+ return true;
+}
+
+} // namespace utils
+
+} // namespace chromeos_update_engine
diff --git a/common/utils.h b/common/utils.h
new file mode 100644
index 0000000..b50cabf
--- /dev/null
+++ b/common/utils.h
@@ -0,0 +1,554 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_UTILS_H_
+#define UPDATE_ENGINE_COMMON_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 <brillo/key_value_store.h>
+#include <brillo/secure_blob.h>
+#include "metrics/metrics_library.h"
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/action_processor.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/connection_manager_interface.h"
+#include "update_engine/metrics.h"
+#include "update_engine/payload_consumer/file_descriptor.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);
+
+// 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, brillo::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,
+ brillo::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 the file descriptor fd. If an error
+// occurs, -1 is returned.
+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 system's temporary directory. On success,
+// stores the name of the new temporary file in |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_dirname_template| is neither absolute (starts with "/") nor
+// explicitly relative to the current working directory (starts with "./" or
+// "../"), then it is prepended the system's temporary directory. On success,
+// stores the name of the new temporary directory in |dirname|. The template
+// must end with "XXXXXX". Returns true on success.
+bool MakeTempDirectory(const std::string& base_dirname_template,
+ std::string* dirname);
+
+// 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);
+
+// 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 brillo::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);
+
+// 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);
+
+// 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.
+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 brillo::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,
+ brillo::Blob* out_data, ssize_t out_data_size,
+ size_t block_size);
+
+// Read the current boot identifier and store it in |boot_id|. This identifier
+// is constants during the same boot of the kernel and is regenerated after
+// reboot. Returns whether it succeeded getting the boot_id.
+bool GetBootId(std::string* boot_id);
+
+} // namespace utils
+
+
+// Utility class to close a file descriptor
+class ScopedFdCloser {
+ public:
+ explicit ScopedFdCloser(int* fd) : fd_(fd) {}
+ ~ScopedFdCloser() {
+ if (should_close_ && fd_ && (*fd_ >= 0) && !IGNORE_EINTR(close(*fd_)))
+ *fd_ = -1;
+ }
+ void set_should_close(bool should_close) { should_close_ = should_close; }
+ private:
+ int* fd_;
+ bool should_close_ = true;
+ DISALLOW_COPY_AND_ASSIGN(ScopedFdCloser);
+};
+
+// 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);
+};
+
+// 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_COMMON_UTILS_H_
diff --git a/common/utils_unittest.cc b/common/utils_unittest.cc
new file mode 100644
index 0000000..2fd58e9
--- /dev/null
+++ b/common/utils_unittest.cc
@@ -0,0 +1,752 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/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 <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/fake_system_state.h"
+
+using brillo::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, ReadFileFailure) {
+ brillo::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());
+ brillo::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()));
+ brillo::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(brillo::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(base::DeleteFile(base::FilePath(temp_dir), true));
+}
+
+TEST(UtilsTest, SplitPartitionNameTest) {
+ string disk;
+ int part_num;
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/sda3", &disk, &part_num));
+ EXPECT_EQ("/dev/sda", disk);
+ EXPECT_EQ(3, part_num);
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/sdp1234", &disk, &part_num));
+ EXPECT_EQ("/dev/sdp", disk);
+ EXPECT_EQ(1234, part_num);
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/mmcblk0p3", &disk, &part_num));
+ EXPECT_EQ("/dev/mmcblk0", disk);
+ EXPECT_EQ(3, part_num);
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/ubiblock3_2", &disk, &part_num));
+ EXPECT_EQ("/dev/ubiblock", disk);
+ EXPECT_EQ(3, part_num);
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/loop10", &disk, &part_num));
+ EXPECT_EQ("/dev/loop", disk);
+ EXPECT_EQ(10, part_num);
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/loop28p11", &disk, &part_num));
+ EXPECT_EQ("/dev/loop28", disk);
+ EXPECT_EQ(11, part_num);
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/loop10_0", &disk, &part_num));
+ EXPECT_EQ("/dev/loop", disk);
+ EXPECT_EQ(10, part_num);
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/loop28p11_0", &disk, &part_num));
+ EXPECT_EQ("/dev/loop28", disk);
+ EXPECT_EQ(11, part_num);
+
+ EXPECT_FALSE(utils::SplitPartitionName("/dev/mmcblk0p", &disk, &part_num));
+ EXPECT_FALSE(utils::SplitPartitionName("/dev/sda", &disk, &part_num));
+ EXPECT_FALSE(utils::SplitPartitionName("/dev/foo/bar", &disk, &part_num));
+ EXPECT_FALSE(utils::SplitPartitionName("/", &disk, &part_num));
+ EXPECT_FALSE(utils::SplitPartitionName("", &disk, &part_num));
+}
+
+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);
+}
+
+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, brillo::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(NetworkConnectionType::kUnknown,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ utils::GetConnectionType(NetworkConnectionType::kEthernet,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ utils::GetConnectionType(NetworkConnectionType::kWifi,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kWimax,
+ utils::GetConnectionType(NetworkConnectionType::kWimax,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kBluetooth,
+ utils::GetConnectionType(NetworkConnectionType::kBluetooth,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kCellular,
+ utils::GetConnectionType(NetworkConnectionType::kCellular,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kTetheredEthernet,
+ utils::GetConnectionType(NetworkConnectionType::kEthernet,
+ NetworkTethering::kConfirmed));
+ EXPECT_EQ(metrics::ConnectionType::kTetheredWifi,
+ utils::GetConnectionType(NetworkConnectionType::kWifi,
+ NetworkTethering::kConfirmed));
+
+ // Ensure that we don't report tethered ethernet unless it's confirmed.
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ utils::GetConnectionType(NetworkConnectionType::kEthernet,
+ NetworkTethering::kNotDetected));
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ utils::GetConnectionType(NetworkConnectionType::kEthernet,
+ NetworkTethering::kSuspected));
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ utils::GetConnectionType(NetworkConnectionType::kEthernet,
+ NetworkTethering::kUnknown));
+
+ // Ditto for tethered wifi.
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ utils::GetConnectionType(NetworkConnectionType::kWifi,
+ NetworkTethering::kNotDetected));
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ utils::GetConnectionType(NetworkConnectionType::kWifi,
+ NetworkTethering::kSuspected));
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ utils::GetConnectionType(NetworkConnectionType::kWifi,
+ NetworkTethering::kUnknown));
+}
+
+TEST(UtilsTest, GetMinorVersion) {
+ // Test GetMinorVersion by verifying that it parses the conf file and returns
+ // the correct value.
+ uint32_t minor_version;
+
+ brillo::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