update_engine: Merge remote-tracking branch 'cros/upstream' into cros/master
Done with:
git merge cros/upstream --commit -s recursive
- Added EC key support and its unittests.
- Resolved a conlict on error codes. Since Android versions are not
uploading any UMA metrics, I gave the priority to the Android version
Since they can't be changed.
- Changed the openssl functions to get1 version (from get0) version
because of a current issue with gale. Once the issue is resolved we
need to change them back.
- Some remaining styling issues fixed by clang-format
BUG=b:163153182
TEST=CQ passes
TEST=unittests
Change-Id: Ib95034422b92433ce26e28336bc4806b34910d38
diff --git a/common/action.h b/common/action.h
index 9e2f5ff..fd82c2d 100644
--- a/common/action.h
+++ b/common/action.h
@@ -222,6 +222,17 @@
out_pipe_;
};
+// An action that does nothing and completes with kSuccess immediately.
+class NoOpAction : public AbstractAction {
+ public:
+ ~NoOpAction() override {}
+ void PerformAction() override {
+ processor_->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ static std::string StaticType() { return "NoOpAction"; }
+ std::string Type() const override { return StaticType(); }
+};
+
}; // namespace chromeos_update_engine
#endif // UPDATE_ENGINE_COMMON_ACTION_H_
diff --git a/common/action_pipe.h b/common/action_pipe.h
index 0c98ee1..4c56812 100644
--- a/common/action_pipe.h
+++ b/common/action_pipe.h
@@ -79,6 +79,8 @@
private:
ObjectType contents_;
+ // Give unit test access
+ friend class DownloadActionTest;
// The ctor is private. This is because this class should construct itself
// via the static Bond() method.
diff --git a/common/action_processor.h b/common/action_processor.h
index 735a106..ad98cc9 100644
--- a/common/action_processor.h
+++ b/common/action_processor.h
@@ -89,7 +89,7 @@
// But this call deletes the action if there no other object has a reference
// to it, so in that case, the caller should not try to access any of its
// member variables after this call.
- void ActionComplete(AbstractAction* actionptr, ErrorCode code);
+ virtual void ActionComplete(AbstractAction* actionptr, ErrorCode code);
private:
FRIEND_TEST(ActionProcessorTest, ChainActionsTest);
diff --git a/common/boot_control_interface.h b/common/boot_control_interface.h
index 392d785..c93de5c 100644
--- a/common/boot_control_interface.h
+++ b/common/boot_control_interface.h
@@ -25,6 +25,9 @@
#include <base/callback.h>
#include <base/macros.h>
+#include "update_engine/common/dynamic_partition_control_interface.h"
+#include "update_engine/update_metadata.pb.h"
+
namespace chromeos_update_engine {
// The abstract boot control interface defines the interaction with the
@@ -35,19 +38,6 @@
public:
using Slot = unsigned int;
- struct PartitionMetadata {
- struct Partition {
- std::string name;
- uint64_t size;
- };
- struct Group {
- std::string name;
- uint64_t size;
- std::vector<Partition> partitions;
- };
- std::vector<Group> groups;
- };
-
static const Slot kInvalidSlot = UINT_MAX;
virtual ~BootControlInterface() = default;
@@ -67,9 +57,20 @@
// 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. In order to access the dynamic partitions in the target slot,
- // InitPartitionMetadata() must be called (once per payload) prior to calling
- // this function. On success, returns true and stores the block device in
- // |device|.
+ // GetDynamicPartitionControl()->PreparePartitionsForUpdate() must be called
+ // (with |update| == true for the first time for a payload, and |false| for
+ // for the rest of the times) prior to calling this function.
+ // The handling may be different based on whether the partition is included
+ // in the update payload. On success, returns true; and stores the block
+ // device in |device|, if the partition is dynamic in |is_dynamic|.
+ virtual bool GetPartitionDevice(const std::string& partition_name,
+ Slot slot,
+ bool not_in_payload,
+ std::string* device,
+ bool* is_dynamic) const = 0;
+
+ // Overload of the above function. We assume the partition is always included
+ // in the payload.
virtual bool GetPartitionDevice(const std::string& partition_name,
Slot slot,
std::string* device) const = 0;
@@ -94,17 +95,11 @@
// of the operation.
virtual bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) = 0;
- // Initializes the metadata of the underlying partitions for a given |slot|
- // and sets up the states for accessing dynamic partitions.
- // |partition_metadata| will be written to the specified |slot| if
- // |update_metadata| is set.
- virtual bool InitPartitionMetadata(
- Slot slot,
- const PartitionMetadata& partition_metadata,
- bool update_metadata) = 0;
+ // Check if |slot| is marked boot successfully.
+ virtual bool IsSlotMarkedSuccessful(Slot slot) const = 0;
- // Do necessary clean-up operations after the whole update.
- virtual void Cleanup() = 0;
+ // Return the dynamic partition control interface.
+ virtual DynamicPartitionControlInterface* GetDynamicPartitionControl() = 0;
// Return a human-readable slot name used for logging.
static std::string SlotName(Slot slot) {
diff --git a/common/boot_control_stub.cc b/common/boot_control_stub.cc
index 0fe8a98..907f670 100644
--- a/common/boot_control_stub.cc
+++ b/common/boot_control_stub.cc
@@ -15,6 +15,7 @@
//
#include "update_engine/common/boot_control_stub.h"
+#include "update_engine/common/dynamic_partition_control_stub.h"
#include <base/logging.h>
@@ -22,6 +23,9 @@
namespace chromeos_update_engine {
+BootControlStub::BootControlStub()
+ : dynamic_partition_control_(new DynamicPartitionControlStub()) {}
+
unsigned int BootControlStub::GetNumSlots() const {
return 0;
}
@@ -31,6 +35,15 @@
return 0;
}
+bool BootControlStub::GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ bool not_in_payload,
+ std::string* device,
+ bool* is_dynamic) const {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return false;
+}
+
bool BootControlStub::GetPartitionDevice(const string& partition_name,
Slot slot,
string* device) const {
@@ -59,16 +72,14 @@
return false;
}
-bool BootControlStub::InitPartitionMetadata(
- Slot slot,
- const PartitionMetadata& partition_metadata,
- bool update_metadata) {
+bool BootControlStub::IsSlotMarkedSuccessful(Slot slot) const {
LOG(ERROR) << __FUNCTION__ << " should never be called.";
return false;
}
-void BootControlStub::Cleanup() {
- LOG(ERROR) << __FUNCTION__ << " should never be called.";
+DynamicPartitionControlInterface*
+BootControlStub::GetDynamicPartitionControl() {
+ return dynamic_partition_control_.get();
}
} // namespace chromeos_update_engine
diff --git a/common/boot_control_stub.h b/common/boot_control_stub.h
index 8dfaffc..a1bdb96 100644
--- a/common/boot_control_stub.h
+++ b/common/boot_control_stub.h
@@ -17,9 +17,11 @@
#ifndef UPDATE_ENGINE_COMMON_BOOT_CONTROL_STUB_H_
#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_STUB_H_
+#include <memory>
#include <string>
#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/dynamic_partition_control_interface.h"
namespace chromeos_update_engine {
@@ -32,25 +34,30 @@
// implementation is in use.
class BootControlStub : public BootControlInterface {
public:
- BootControlStub() = default;
+ BootControlStub();
~BootControlStub() = default;
// BootControlInterface overrides.
unsigned int GetNumSlots() const override;
BootControlInterface::Slot GetCurrentSlot() const override;
bool GetPartitionDevice(const std::string& partition_name,
+ Slot slot,
+ bool not_in_payload,
+ std::string* device,
+ bool* is_dynamic) 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;
- bool InitPartitionMetadata(Slot slot,
- const PartitionMetadata& partition_metadata,
- bool update_metadata) override;
- void Cleanup() override;
+ bool IsSlotMarkedSuccessful(BootControlInterface::Slot slot) const override;
+ DynamicPartitionControlInterface* GetDynamicPartitionControl() override;
private:
+ std::unique_ptr<DynamicPartitionControlInterface> dynamic_partition_control_;
+
DISALLOW_COPY_AND_ASSIGN(BootControlStub);
};
diff --git a/common/cleanup_previous_update_action_delegate.h b/common/cleanup_previous_update_action_delegate.h
new file mode 100644
index 0000000..7dad9c5
--- /dev/null
+++ b/common/cleanup_previous_update_action_delegate.h
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_
+#define UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_
+
+namespace chromeos_update_engine {
+
+// Delegate interface for CleanupPreviousUpdateAction.
+class CleanupPreviousUpdateActionDelegateInterface {
+ public:
+ virtual ~CleanupPreviousUpdateActionDelegateInterface() {}
+ // |progress| is within [0, 1]
+ virtual void OnCleanupProgressUpdate(double progress) = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_
diff --git a/common/constants.cc b/common/constants.cc
index ad511d5..8883668 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -110,6 +110,7 @@
const char kPrefsWallClockScatteringWaitPeriod[] = "wall-clock-wait-period";
const char kPrefsWallClockStagingWaitPeriod[] =
"wall-clock-staging-wait-period";
+const char kPrefsManifestBytes[] = "manifest-bytes";
// These four fields are generated by scripts/brillo_update_payload.
const char kPayloadPropertyFileSize[] = "FILE_SIZE";
@@ -130,8 +131,7 @@
// Set "SWITCH_SLOT_ON_REBOOT=0" to skip marking the updated partitions active.
// The default is 1 (always switch slot if update succeeded).
const char kPayloadPropertySwitchSlotOnReboot[] = "SWITCH_SLOT_ON_REBOOT";
-// Set "RUN_POST_INSTALL=0" to skip running post install, this will only be
-// honored if we're resuming an update and post install has already succeeded.
+// Set "RUN_POST_INSTALL=0" to skip running optional post install.
// The default is 1 (always run post install).
const char kPayloadPropertyRunPostInstall[] = "RUN_POST_INSTALL";
diff --git a/common/constants.h b/common/constants.h
index 446b147..3685102 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -105,6 +105,7 @@
extern const char kPrefsVerityWritten[];
extern const char kPrefsWallClockScatteringWaitPeriod[];
extern const char kPrefsWallClockStagingWaitPeriod[];
+extern const char kPrefsManifestBytes[];
// Keys used when storing and loading payload properties.
extern const char kPayloadPropertyFileSize[];
@@ -215,6 +216,9 @@
const int kDownloadConnectTimeoutSeconds = 30;
const int kDownloadP2PConnectTimeoutSeconds = 5;
+// Size in bytes of SHA256 hash.
+const int kSHA256Size = 32;
+
} // namespace chromeos_update_engine
#endif // UPDATE_ENGINE_COMMON_CONSTANTS_H_
diff --git a/common/dynamic_partition_control_interface.h b/common/dynamic_partition_control_interface.h
new file mode 100644
index 0000000..7c2d0b0
--- /dev/null
+++ b/common/dynamic_partition_control_interface.h
@@ -0,0 +1,144 @@
+//
+// Copyright (C) 2018 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_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/cleanup_previous_update_action_delegate.h"
+#include "update_engine/common/error_code.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+struct FeatureFlag {
+ enum class Value { NONE = 0, RETROFIT, LAUNCH };
+ constexpr explicit FeatureFlag(Value value) : value_(value) {}
+ constexpr bool IsEnabled() const { return value_ != Value::NONE; }
+ constexpr bool IsRetrofit() const { return value_ == Value::RETROFIT; }
+ constexpr bool IsLaunch() const { return value_ == Value::LAUNCH; }
+
+ private:
+ Value value_;
+};
+
+class BootControlInterface;
+class PrefsInterface;
+
+class DynamicPartitionControlInterface {
+ public:
+ virtual ~DynamicPartitionControlInterface() = default;
+
+ // Return the feature flags of dynamic partitions on this device.
+ // Return RETROFIT iff dynamic partitions is retrofitted on this device,
+ // LAUNCH iff this device is launched with dynamic partitions,
+ // NONE iff dynamic partitions is disabled on this device.
+ virtual FeatureFlag GetDynamicPartitionsFeatureFlag() = 0;
+
+ // Return the feature flags of Virtual A/B on this device.
+ virtual FeatureFlag GetVirtualAbFeatureFlag() = 0;
+
+ // Attempt to optimize |operation|.
+ // If successful, |optimized| contains an operation with extents that
+ // needs to be written.
+ // If failed, no optimization is available, and caller should perform
+ // |operation| directly.
+ // |partition_name| should not have the slot suffix; implementation of
+ // DynamicPartitionControlInterface checks partition at the target slot
+ // previously set with PreparePartitionsForUpdate().
+ virtual bool OptimizeOperation(const std::string& partition_name,
+ const InstallOperation& operation,
+ InstallOperation* optimized) = 0;
+
+ // Do necessary cleanups before destroying the object.
+ virtual void Cleanup() = 0;
+
+ // Prepare all partitions for an update specified in |manifest|.
+ // This is needed before calling MapPartitionOnDeviceMapper(), otherwise the
+ // device would be mapped in an inconsistent way.
+ // If |update| is set, create snapshots and writes super partition metadata.
+ // If |required_size| is not null and call fails due to insufficient space,
+ // |required_size| will be set to total free space required on userdata
+ // partition to apply the update. Otherwise (call succeeds, or fails
+ // due to other errors), |required_size| is set to zero.
+ virtual bool PreparePartitionsForUpdate(uint32_t source_slot,
+ uint32_t target_slot,
+ const DeltaArchiveManifest& manifest,
+ bool update,
+ uint64_t* required_size) = 0;
+
+ // After writing to new partitions, before rebooting into the new slot, call
+ // this function to indicate writes to new partitions are done.
+ virtual bool FinishUpdate(bool powerwash_required) = 0;
+
+ // Get an action to clean up previous update.
+ // Return NoOpAction on non-Virtual A/B devices.
+ // Before applying the next update, run this action to clean up previous
+ // update files. This function blocks until delta files are merged into
+ // current OS partitions and finished cleaning up.
+ // - If successful, action completes with kSuccess.
+ // - If any error, but caller should retry after reboot, action completes with
+ // kError.
+ // - If any irrecoverable failures, action completes with kDeviceCorrupted.
+ //
+ // See ResetUpdate for differences between CleanuPreviousUpdateAction and
+ // ResetUpdate.
+ virtual std::unique_ptr<AbstractAction> GetCleanupPreviousUpdateAction(
+ BootControlInterface* boot_control,
+ PrefsInterface* prefs,
+ CleanupPreviousUpdateActionDelegateInterface* delegate) = 0;
+
+ // Called after an unwanted payload has been successfully applied and the
+ // device has not yet been rebooted.
+ //
+ // For snapshot updates (Virtual A/B), it calls
+ // DeltaPerformer::ResetUpdateProgress(false /* quick */) and
+ // frees previously allocated space; the next update will need to be
+ // started over.
+ //
+ // Note: CleanupPreviousUpdateAction does not do anything if an update is in
+ // progress, while ResetUpdate() forcefully free previously
+ // allocated space for snapshot updates.
+ virtual bool ResetUpdate(PrefsInterface* prefs) = 0;
+
+ // Reads the dynamic partitions metadata from the current slot, and puts the
+ // name of the dynamic partitions with the current suffix to |partitions|.
+ // Returns true on success.
+ virtual bool ListDynamicPartitionsForSlot(
+ uint32_t current_slot, std::vector<std::string>* partitions) = 0;
+
+ // Finds a possible location that list all block devices by name; and puts
+ // the result in |path|. Returns true on success.
+ // Sample result: /dev/block/by-name/
+ virtual bool GetDeviceDir(std::string* path) = 0;
+
+ // Verifies that the untouched dynamic partitions in the target metadata have
+ // the same extents as the source metadata.
+ virtual bool VerifyExtentsForUntouchedPartitions(
+ uint32_t source_slot,
+ uint32_t target_slot,
+ const std::vector<std::string>& partitions) = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_
diff --git a/common/dynamic_partition_control_stub.cc b/common/dynamic_partition_control_stub.cc
new file mode 100644
index 0000000..5a8ca43
--- /dev/null
+++ b/common/dynamic_partition_control_stub.cc
@@ -0,0 +1,86 @@
+//
+// Copyright (C) 2019 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 <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include <base/logging.h>
+
+#include "update_engine/common/dynamic_partition_control_stub.h"
+
+namespace chromeos_update_engine {
+
+FeatureFlag DynamicPartitionControlStub::GetDynamicPartitionsFeatureFlag() {
+ return FeatureFlag(FeatureFlag::Value::NONE);
+}
+
+FeatureFlag DynamicPartitionControlStub::GetVirtualAbFeatureFlag() {
+ return FeatureFlag(FeatureFlag::Value::NONE);
+}
+
+bool DynamicPartitionControlStub::OptimizeOperation(
+ const std::string& partition_name,
+ const InstallOperation& operation,
+ InstallOperation* optimized) {
+ return false;
+}
+
+void DynamicPartitionControlStub::Cleanup() {}
+
+bool DynamicPartitionControlStub::PreparePartitionsForUpdate(
+ uint32_t source_slot,
+ uint32_t target_slot,
+ const DeltaArchiveManifest& manifest,
+ bool update,
+ uint64_t* required_size) {
+ return true;
+}
+
+bool DynamicPartitionControlStub::FinishUpdate(bool powerwash_required) {
+ return true;
+}
+
+std::unique_ptr<AbstractAction>
+DynamicPartitionControlStub::GetCleanupPreviousUpdateAction(
+ BootControlInterface* boot_control,
+ PrefsInterface* prefs,
+ CleanupPreviousUpdateActionDelegateInterface* delegate) {
+ return std::make_unique<NoOpAction>();
+}
+
+bool DynamicPartitionControlStub::ResetUpdate(PrefsInterface* prefs) {
+ return false;
+}
+
+bool DynamicPartitionControlStub::ListDynamicPartitionsForSlot(
+ uint32_t current_slot, std::vector<std::string>* partitions) {
+ return true;
+}
+
+bool DynamicPartitionControlStub::GetDeviceDir(std::string* path) {
+ return true;
+}
+
+bool DynamicPartitionControlStub::VerifyExtentsForUntouchedPartitions(
+ uint32_t source_slot,
+ uint32_t target_slot,
+ const std::vector<std::string>& partitions) {
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/dynamic_partition_control_stub.h b/common/dynamic_partition_control_stub.h
new file mode 100644
index 0000000..94dba1b
--- /dev/null
+++ b/common/dynamic_partition_control_stub.h
@@ -0,0 +1,63 @@
+//
+// Copyright (C) 2019 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_DYNAMIC_PARTITION_CONTROL_STUB_H_
+#define UPDATE_ENGINE_COMMON_DYNAMIC_PARTITION_CONTROL_STUB_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "update_engine/common/dynamic_partition_control_interface.h"
+
+namespace chromeos_update_engine {
+
+class DynamicPartitionControlStub : public DynamicPartitionControlInterface {
+ public:
+ FeatureFlag GetDynamicPartitionsFeatureFlag() override;
+ FeatureFlag GetVirtualAbFeatureFlag() override;
+ bool OptimizeOperation(const std::string& partition_name,
+ const InstallOperation& operation,
+ InstallOperation* optimized) override;
+ void Cleanup() override;
+ bool PreparePartitionsForUpdate(uint32_t source_slot,
+ uint32_t target_slot,
+ const DeltaArchiveManifest& manifest,
+ bool update,
+ uint64_t* required_size) override;
+
+ bool FinishUpdate(bool powerwash_required) override;
+ std::unique_ptr<AbstractAction> GetCleanupPreviousUpdateAction(
+ BootControlInterface* boot_control,
+ PrefsInterface* prefs,
+ CleanupPreviousUpdateActionDelegateInterface* delegate) override;
+ bool ResetUpdate(PrefsInterface* prefs) override;
+
+ bool ListDynamicPartitionsForSlot(
+ uint32_t current_slot, std::vector<std::string>* partitions) override;
+ bool GetDeviceDir(std::string* path) override;
+
+ bool VerifyExtentsForUntouchedPartitions(
+ uint32_t source_slot,
+ uint32_t target_slot,
+ const std::vector<std::string>& partitions) override;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_DYNAMIC_PARTITION_CONTROL_STUB_H_
diff --git a/common/error_code.h b/common/error_code.h
index 7acb3b6..7d9cfff 100644
--- a/common/error_code.h
+++ b/common/error_code.h
@@ -83,7 +83,9 @@
kInternalLibCurlError = 57,
kUnresolvedHostError = 58,
kUnresolvedHostRecovered = 59,
- kPackageExcludedFromUpdate = 60,
+ kNotEnoughSpace = 60,
+ kDeviceCorrupted = 61,
+ kPackageExcludedFromUpdate = 62,
// VERY IMPORTANT! When adding new error codes:
//
diff --git a/common/error_code_utils.cc b/common/error_code_utils.cc
index 55d876f..cda4c7e 100644
--- a/common/error_code_utils.cc
+++ b/common/error_code_utils.cc
@@ -167,6 +167,10 @@
return "ErrorCode::kUnresolvedHostError";
case ErrorCode::kUnresolvedHostRecovered:
return "ErrorCode::kUnresolvedHostRecovered";
+ case ErrorCode::kNotEnoughSpace:
+ return "ErrorCode::kNotEnoughSpace";
+ case ErrorCode::kDeviceCorrupted:
+ return "ErrorCode::kDeviceCorrupted";
case ErrorCode::kPackageExcludedFromUpdate:
return "ErrorCode::kPackageExcludedFromUpdate";
// Don't add a default case to let the compiler warn about newly added
diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h
index 3d65075..98b93e6 100644
--- a/common/fake_boot_control.h
+++ b/common/fake_boot_control.h
@@ -18,12 +18,14 @@
#define UPDATE_ENGINE_COMMON_FAKE_BOOT_CONTROL_H_
#include <map>
+#include <memory>
#include <string>
#include <vector>
#include <base/time/time.h>
#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/dynamic_partition_control_stub.h"
namespace chromeos_update_engine {
@@ -34,6 +36,8 @@
SetNumSlots(num_slots_);
// The current slot should be bootable.
is_bootable_[current_slot_] = true;
+
+ dynamic_partition_control_.reset(new DynamicPartitionControlStub());
}
// BootControlInterface overrides.
@@ -44,16 +48,27 @@
bool GetPartitionDevice(const std::string& partition_name,
BootControlInterface::Slot slot,
- std::string* device) const override {
+ bool not_in_payload,
+ std::string* device,
+ bool* is_dynamic) 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;
+ if (is_dynamic != nullptr) {
+ *is_dynamic = false;
+ }
return true;
}
+ bool GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ std::string* device) const override {
+ return GetPartitionDevice(partition_name, slot, false, device, nullptr);
+ }
+
bool IsSlotBootable(BootControlInterface::Slot slot) const override {
return slot < num_slots_ && is_bootable_[slot];
}
@@ -70,22 +85,20 @@
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.
+ is_marked_successful_[GetCurrentSlot()] = true;
callback.Run(true);
return true;
}
- bool InitPartitionMetadata(Slot slot,
- const PartitionMetadata& partition_metadata,
- bool update_metadata) override {
- return true;
+ bool IsSlotMarkedSuccessful(Slot slot) const override {
+ return slot < num_slots_ && is_marked_successful_[slot];
}
- void Cleanup() override {}
-
// Setters
void SetNumSlots(unsigned int num_slots) {
num_slots_ = num_slots;
is_bootable_.resize(num_slots_, false);
+ is_marked_successful_.resize(num_slots_, false);
devices_.resize(num_slots_);
}
@@ -103,13 +116,20 @@
is_bootable_[slot] = bootable;
}
+ DynamicPartitionControlInterface* GetDynamicPartitionControl() override {
+ return dynamic_partition_control_.get();
+ }
+
private:
BootControlInterface::Slot num_slots_{2};
BootControlInterface::Slot current_slot_{0};
std::vector<bool> is_bootable_;
+ std::vector<bool> is_marked_successful_;
std::vector<std::map<std::string, std::string>> devices_;
+ std::unique_ptr<DynamicPartitionControlInterface> dynamic_partition_control_;
+
DISALLOW_COPY_AND_ASSIGN(FakeBootControl);
};
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
index 6604534..82382ff 100644
--- a/common/fake_hardware.h
+++ b/common/fake_hardware.h
@@ -19,10 +19,13 @@
#include <map>
#include <string>
+#include <utility>
#include <base/time/time.h>
+#include "update_engine/common/error_code.h"
#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/utils.h"
namespace chromeos_update_engine {
@@ -132,6 +135,8 @@
int64_t GetBuildTimestamp() const override { return build_timestamp_; }
+ bool AllowDowngrade() const override { return false; }
+
bool GetFirstActiveOmahaPingSent() const override {
return first_active_omaha_ping_sent_;
}
@@ -197,12 +202,27 @@
build_timestamp_ = build_timestamp;
}
+ void SetWarmReset(bool warm_reset) override { warm_reset_ = warm_reset; }
+
// Getters to verify state.
int GetMaxKernelKeyRollforward() const { return kernel_max_rollforward_; }
bool GetIsRollbackPowerwashScheduled() const {
return powerwash_scheduled_ && save_rollback_data_;
}
+ std::string GetVersionForLogging(
+ const std::string& partition_name) const override {
+ return partition_timestamps_[partition_name];
+ }
+ void SetVersion(const std::string& partition_name, std::string timestamp) {
+ partition_timestamps_[partition_name] = std::move(timestamp);
+ }
+ ErrorCode IsPartitionUpdateValid(
+ const std::string& partition_name,
+ const std::string& new_version) const override {
+ const auto old_version = GetVersionForLogging(partition_name);
+ return utils::IsTimestampNewer(old_version, new_version);
+ }
private:
bool is_official_build_{true};
@@ -225,6 +245,8 @@
bool save_rollback_data_{false};
int64_t build_timestamp_{0};
bool first_active_omaha_ping_sent_{false};
+ bool warm_reset_{false};
+ mutable std::map<std::string, std::string> partition_timestamps_;
DISALLOW_COPY_AND_ASSIGN(FakeHardware);
};
diff --git a/common/file_fetcher.cc b/common/file_fetcher.cc
index 3836e54..7134fd6 100644
--- a/common/file_fetcher.cc
+++ b/common/file_fetcher.cc
@@ -43,8 +43,9 @@
// static
bool FileFetcher::SupportedUrl(const string& url) {
// Note that we require the file path to start with a "/".
- return base::StartsWith(
- url, "file:///", base::CompareCase::INSENSITIVE_ASCII);
+ return (
+ base::StartsWith(url, "file:///", base::CompareCase::INSENSITIVE_ASCII) ||
+ base::StartsWith(url, "fd://", base::CompareCase::INSENSITIVE_ASCII));
}
FileFetcher::~FileFetcher() {
@@ -67,12 +68,20 @@
return;
}
- string file_path = url.substr(strlen("file://"));
- stream_ =
- brillo::FileStream::Open(base::FilePath(file_path),
- brillo::Stream::AccessMode::READ,
- brillo::FileStream::Disposition::OPEN_EXISTING,
- nullptr);
+ string file_path;
+
+ if (base::StartsWith(url, "fd://", base::CompareCase::INSENSITIVE_ASCII)) {
+ int fd = std::stoi(url.substr(strlen("fd://")));
+ file_path = url;
+ stream_ = brillo::FileStream::FromFileDescriptor(fd, false, nullptr);
+ } else {
+ file_path = url.substr(strlen("file://"));
+ stream_ =
+ brillo::FileStream::Open(base::FilePath(file_path),
+ brillo::Stream::AccessMode::READ,
+ brillo::FileStream::Disposition::OPEN_EXISTING,
+ nullptr);
+ }
if (!stream_) {
LOG(ERROR) << "Couldn't open " << file_path;
@@ -183,5 +192,4 @@
transfer_in_progress_ = false;
transfer_paused_ = false;
}
-
} // namespace chromeos_update_engine
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
index da9f10e..b37b007 100644
--- a/common/hardware_interface.h
+++ b/common/hardware_interface.h
@@ -25,6 +25,8 @@
#include <base/files/file_path.h>
#include <base/time/time.h>
+#include "update_engine/common/error_code.h"
+
namespace chromeos_update_engine {
// The hardware interface allows access to the crossystem exposed properties,
@@ -126,6 +128,10 @@
// Returns the timestamp of the current OS build.
virtual int64_t GetBuildTimestamp() const = 0;
+ // Returns true if the current OS build allows installing the payload with an
+ // older timestamp.
+ virtual bool AllowDowngrade() const = 0;
+
// Returns whether the first active ping was sent to Omaha at some point, and
// that the value is persisted across recovery (and powerwash) once set with
// |SetFirstActiveOmahaPingSent()|.
@@ -134,6 +140,30 @@
// Persist the fact that first active ping was sent to omaha and returns false
// if failed to persist it.
virtual bool SetFirstActiveOmahaPingSent() = 0;
+
+ // If |warm_reset| is true, sets the warm reset to indicate a warm reset is
+ // needed on the next reboot. Otherwise, clears the flag.
+ virtual void SetWarmReset(bool warm_reset) = 0;
+
+ // Return the version/timestamp for partition `partition_name`.
+ // Don't make any assumption about the formatting of returned string.
+ // Only used for logging/debugging purposes.
+ virtual std::string GetVersionForLogging(
+ const std::string& partition_name) const = 0;
+
+ // Return true if and only if `new_version` is "newer" than the
+ // version number of partition `partition_name`. The notion of
+ // "newer" is defined by this function. Caller should not make
+ // any assumption about the underlying logic.
+ // Return:
+ // - kSuccess if update is valid.
+ // - kPayloadTimestampError if downgrade is detected
+ // - kDownloadManifestParseError if |new_version| has an incorrect format
+ // - Other error values if the source of error is known, or kError for
+ // a generic error on the device.
+ virtual ErrorCode IsPartitionUpdateValid(
+ const std::string& partition_name,
+ const std::string& new_version) const = 0;
};
} // namespace chromeos_update_engine
diff --git a/common/http_fetcher_unittest.cc b/common/http_fetcher_unittest.cc
index 3ecb996..9338087 100644
--- a/common/http_fetcher_unittest.cc
+++ b/common/http_fetcher_unittest.cc
@@ -37,7 +37,11 @@
#include <brillo/message_loops/base_message_loop.h>
#include <brillo/message_loops/message_loop.h>
#include <brillo/message_loops/message_loop_utils.h>
+#ifdef __CHROMEOS__
#include <brillo/process/process.h>
+#else
+#include <brillo/process.h>
+#endif // __CHROMEOS__
#include <brillo/streams/file_stream.h>
#include <brillo/streams/stream.h>
#include <gtest/gtest.h>
diff --git a/common/mock_action_processor.h b/common/mock_action_processor.h
index 4c62109..9785776 100644
--- a/common/mock_action_processor.h
+++ b/common/mock_action_processor.h
@@ -32,6 +32,8 @@
MOCK_METHOD0(StartProcessing, void());
MOCK_METHOD1(EnqueueAction, void(AbstractAction* action));
+ MOCK_METHOD2(ActionComplete, void(AbstractAction*, ErrorCode));
+
// This is a legacy workaround described in:
// https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md#legacy-workarounds-for-move-only-types-legacymoveonly
void EnqueueAction(std::unique_ptr<AbstractAction> action) override {
diff --git a/common/mock_http_fetcher.cc b/common/mock_http_fetcher.cc
index 10e3b9e..1b3cd7d 100644
--- a/common/mock_http_fetcher.cc
+++ b/common/mock_http_fetcher.cc
@@ -22,6 +22,7 @@
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/time/time.h>
+#include <brillo/message_loops/message_loop.h>
#include <gtest/gtest.h>
// This is a mock implementation of HttpFetcher which is useful for testing.
@@ -43,12 +44,12 @@
SignalTransferComplete();
return;
}
- if (sent_size_ < data_.size())
+ if (sent_offset_ < data_.size())
SendData(true);
}
void MockHttpFetcher::SendData(bool skip_delivery) {
- if (fail_transfer_ || sent_size_ == data_.size()) {
+ if (fail_transfer_ || sent_offset_ == data_.size()) {
SignalTransferComplete();
return;
}
@@ -60,19 +61,22 @@
// Setup timeout callback even if the transfer is about to be completed in
// order to get a call to |TransferComplete|.
- if (timeout_id_ == MessageLoop::kTaskIdNull) {
+ if (timeout_id_ == MessageLoop::kTaskIdNull && delay_) {
+ CHECK(MessageLoop::current());
timeout_id_ = MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)),
base::TimeDelta::FromMilliseconds(10));
}
- if (!skip_delivery) {
+ if (!skip_delivery || !delay_) {
const size_t chunk_size =
- min(kMockHttpFetcherChunkSize, data_.size() - sent_size_);
- sent_size_ += chunk_size;
+ min(kMockHttpFetcherChunkSize, data_.size() - sent_offset_);
+ sent_offset_ += chunk_size;
+ bytes_sent_ += chunk_size;
CHECK(delegate_);
- delegate_->ReceivedBytes(this, &data_[sent_size_ - chunk_size], chunk_size);
+ delegate_->ReceivedBytes(
+ this, &data_[sent_offset_ - chunk_size], chunk_size);
}
// We may get terminated and deleted right after |ReceivedBytes| call, so we
// should not access any class member variable after this call.
@@ -81,7 +85,7 @@
void MockHttpFetcher::TimeoutCallback() {
CHECK(!paused_);
timeout_id_ = MessageLoop::kTaskIdNull;
- CHECK_LE(sent_size_, data_.size());
+ CHECK_LE(sent_offset_, data_.size());
// Same here, we should not access any member variable after this call.
SendData(false);
}
@@ -90,10 +94,15 @@
// The transfer cannot be resumed.
void MockHttpFetcher::TerminateTransfer() {
LOG(INFO) << "Terminating transfer.";
- // Kill any timeout, it is ok to call with kTaskIdNull.
- MessageLoop::current()->CancelTask(timeout_id_);
- timeout_id_ = MessageLoop::kTaskIdNull;
- delegate_->TransferTerminated(this);
+ // During testing, MessageLoop may or may not be available.
+ // So don't call CancelTask() unless necessary.
+ if (timeout_id_ != MessageLoop::kTaskIdNull) {
+ MessageLoop::current()->CancelTask(timeout_id_);
+ timeout_id_ = MessageLoop::kTaskIdNull;
+ }
+ if (delegate_) {
+ delegate_->TransferTerminated(this);
+ }
}
void MockHttpFetcher::SetHeader(const std::string& header_name,
diff --git a/common/mock_http_fetcher.h b/common/mock_http_fetcher.h
index 0f04319..ea5b83d 100644
--- a/common/mock_http_fetcher.h
+++ b/common/mock_http_fetcher.h
@@ -46,7 +46,7 @@
size_t size,
ProxyResolver* proxy_resolver)
: HttpFetcher(proxy_resolver),
- sent_size_(0),
+ sent_offset_(0),
timeout_id_(brillo::MessageLoop::kTaskIdNull),
paused_(false),
fail_transfer_(false),
@@ -64,7 +64,7 @@
// Ignores this.
void SetOffset(off_t offset) override {
- sent_size_ = offset;
+ sent_offset_ = offset;
if (delegate_)
delegate_->SeekToOffset(offset);
}
@@ -76,8 +76,8 @@
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_; }
+ // No bytes were downloaded in the mock class.
+ size_t GetBytesDownloaded() override { return bytes_sent_; }
// Begins the transfer if it hasn't already begun.
void BeginTransfer(const std::string& url) override;
@@ -113,6 +113,8 @@
const brillo::Blob& post_data() const { return post_data_; }
+ void set_delay(bool delay) { delay_ = delay; }
+
private:
// Sends data to the delegate and sets up a timeout callback if needed. There
// must be a delegate. If |skip_delivery| is true, no bytes will be delivered,
@@ -129,8 +131,11 @@
// 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 current offset, marks the first byte that will be sent next
+ size_t sent_offset_{0};
+
+ // Total number of bytes transferred
+ size_t bytes_sent_{0};
// The extra headers set.
std::map<std::string, std::string> extra_headers_;
@@ -140,13 +145,16 @@
brillo::MessageLoop::TaskId timeout_id_;
// True iff the fetcher is paused.
- bool paused_;
+ bool paused_{false};
// Set to true if the transfer should fail.
- bool fail_transfer_;
+ bool fail_transfer_{false};
// Set to true if BeginTransfer should EXPECT fail.
- bool never_use_;
+ bool never_use_{false};
+
+ // Whether it should wait for 10ms before sending data to delegates
+ bool delay_{true};
DISALLOW_COPY_AND_ASSIGN(MockHttpFetcher);
};
diff --git a/common/platform_constants.h b/common/platform_constants.h
index 6eaa940..243af69 100644
--- a/common/platform_constants.h
+++ b/common/platform_constants.h
@@ -38,6 +38,10 @@
// whole payload.
extern const char kUpdatePayloadPublicKeyPath[];
+// Path to the location of the zip archive file that contains PEM encoded X509
+// certificates. e.g. 'system/etc/security/otacerts.zip'.
+extern const char kUpdateCertificatesPath[];
+
// 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).
diff --git a/common/platform_constants_android.cc b/common/platform_constants_android.cc
index 9d8d30e..f468c3b 100644
--- a/common/platform_constants_android.cc
+++ b/common/platform_constants_android.cc
@@ -25,8 +25,8 @@
"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 kUpdatePayloadPublicKeyPath[] = "";
+const char kUpdateCertificatesPath[] = "/system/etc/security/otacerts.zip";
const char kCACertificatesPath[] = "/system/etc/security/cacerts_google";
// No deadline file API support on Android.
const char kOmahaResponseDeadlineFile[] = "";
diff --git a/common/platform_constants_chromeos.cc b/common/platform_constants_chromeos.cc
index f1ac490..fe94a45 100644
--- a/common/platform_constants_chromeos.cc
+++ b/common/platform_constants_chromeos.cc
@@ -27,6 +27,7 @@
const char kOmahaPlatformName[] = "Chrome OS";
const char kUpdatePayloadPublicKeyPath[] =
"/usr/share/update_engine/update-payload-key.pub.pem";
+const char kUpdateCertificatesPath[] = "";
const char kCACertificatesPath[] = "/usr/share/chromeos-ca-certificates";
const char kOmahaResponseDeadlineFile[] = "/tmp/update-check-response-deadline";
// This directory is wiped during powerwash.
diff --git a/common/subprocess.cc b/common/subprocess.cc
index ff37472..023017b 100644
--- a/common/subprocess.cc
+++ b/common/subprocess.cc
@@ -32,11 +32,11 @@
#include <base/stl_util.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
-#include <brillo/process/process.h>
#include <brillo/secure_blob.h>
#include "update_engine/common/utils.h"
+using brillo::MessageLoop;
using std::string;
using std::unique_ptr;
using std::vector;
diff --git a/common/subprocess.h b/common/subprocess.h
index e1a7ce3..179a5c5 100644
--- a/common/subprocess.h
+++ b/common/subprocess.h
@@ -30,8 +30,13 @@
#include <base/macros.h>
#include <brillo/asynchronous_signal_handler_interface.h>
#include <brillo/message_loops/message_loop.h>
+#ifdef __CHROMEOS__
#include <brillo/process/process.h>
#include <brillo/process/process_reaper.h>
+#else
+#include <brillo/process.h>
+#include <brillo/process_reaper.h>
+#endif // __CHROMEOS__
#include <gtest/gtest_prod.h> // for FRIEND_TEST
// The Subprocess class is a singleton. It's used to spawn off a subprocess
@@ -124,6 +129,7 @@
// These are used to monitor the stdout of the running process, including
// the stderr if it was redirected.
std::unique_ptr<base::FileDescriptorWatcher::Controller> stdout_controller;
+
int stdout_fd{-1};
std::string stdout;
};
diff --git a/common/subprocess_unittest.cc b/common/subprocess_unittest.cc
index 19b24f4..b4d068f 100644
--- a/common/subprocess_unittest.cc
+++ b/common/subprocess_unittest.cc
@@ -279,7 +279,7 @@
// 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(120), base::Bind([] {
+ &loop_, TimeDelta::FromSeconds(20), base::Bind([] {
return Subprocess::Get().subprocess_records_.empty();
}));
EXPECT_TRUE(Subprocess::Get().subprocess_records_.empty());
diff --git a/common/test_utils.cc b/common/test_utils.cc
index 50b0962..bd69d03 100644
--- a/common/test_utils.cc
+++ b/common/test_utils.cc
@@ -37,6 +37,10 @@
#include <base/files/file_util.h>
#include <base/logging.h>
+#ifdef __ANDROID__
+#include <libdm/loop_control.h>
+#endif
+
#include "update_engine/common/error_code_utils.h"
#include "update_engine/common/utils.h"
#include "update_engine/payload_consumer/file_writer.h"
@@ -44,16 +48,7 @@
using std::set;
using std::string;
using std::vector;
-
-namespace {
-
-#ifdef __ANDROID__
-#define kLoopDevicePrefix "/dev/block/loop"
-#else
-#define kLoopDevicePrefix "/dev/loop"
-#endif // __ANDROID__
-
-} // namespace
+using namespace std::chrono_literals;
namespace chromeos_update_engine {
@@ -112,17 +107,43 @@
return utils::WriteFile(path.c_str(), data.data(), data.size());
}
-bool BindToUnusedLoopDevice(const string& filename,
- bool writable,
- string* out_lo_dev_name) {
- CHECK(out_lo_dev_name);
+bool SetLoopDeviceStatus(int loop_device_fd,
+ const std::string& filename,
+ int loop_number,
+ bool writable) {
+ struct loop_info64 device_info {};
+ device_info.lo_offset = 0;
+ device_info.lo_sizelimit = 0; // 0 means whole file.
+ device_info.lo_flags = (writable ? 0 : LO_FLAGS_READ_ONLY);
+ device_info.lo_number = loop_number;
+ strncpy(reinterpret_cast<char*>(device_info.lo_file_name),
+ base::FilePath(filename).BaseName().value().c_str(),
+ LO_NAME_SIZE - 1);
+ device_info.lo_file_name[LO_NAME_SIZE - 1] = '\0';
+ TEST_AND_RETURN_FALSE_ERRNO(
+ ioctl(loop_device_fd, LOOP_SET_STATUS64, &device_info) == 0);
+ if (writable) {
+ // Make sure loop device isn't read only.
+ int ro = 0;
+ if (ioctl(loop_device_fd, BLKROSET, &ro) != 0) {
+ PLOG(WARNING) << "Failed to mark loop device writable.";
+ }
+ }
+
+ return true;
+}
+
+bool BindToUnusedLoopDeviceLegacy(int data_fd,
+ const string& filename,
+ bool writable,
+ string* out_lo_dev_name) {
// Get the next available loop-device.
int control_fd =
HANDLE_EINTR(open("/dev/loop-control", O_RDWR | O_LARGEFILE));
TEST_AND_RETURN_FALSE_ERRNO(control_fd >= 0);
int loop_number = ioctl(control_fd, LOOP_CTL_GET_FREE);
IGNORE_EINTR(close(control_fd));
- *out_lo_dev_name = kLoopDevicePrefix + std::to_string(loop_number);
+ *out_lo_dev_name = "/dev/loop" + std::to_string(loop_number);
// Double check that the loop exists and is free.
int loop_device_fd =
@@ -146,32 +167,35 @@
return false;
}
- // Open our data file and assign it to the loop device.
+ // Assign the data fd to the loop device.
+ TEST_AND_RETURN_FALSE_ERRNO(ioctl(loop_device_fd, LOOP_SET_FD, data_fd) == 0);
+ return SetLoopDeviceStatus(loop_device_fd, filename, loop_number, writable);
+}
+
+bool BindToUnusedLoopDevice(const string& filename,
+ bool writable,
+ string* out_lo_dev_name) {
+ CHECK(out_lo_dev_name);
int data_fd = open(filename.c_str(),
(writable ? O_RDWR : O_RDONLY) | O_LARGEFILE | O_CLOEXEC);
TEST_AND_RETURN_FALSE_ERRNO(data_fd >= 0);
ScopedFdCloser data_fd_closer(&data_fd);
- TEST_AND_RETURN_FALSE_ERRNO(ioctl(loop_device_fd, LOOP_SET_FD, data_fd) == 0);
- memset(&device_info, 0, sizeof(device_info));
- device_info.lo_offset = 0;
- device_info.lo_sizelimit = 0; // 0 means whole file.
- device_info.lo_flags = (writable ? 0 : LO_FLAGS_READ_ONLY);
- device_info.lo_number = loop_number;
- strncpy(reinterpret_cast<char*>(device_info.lo_file_name),
- base::FilePath(filename).BaseName().value().c_str(),
- LO_NAME_SIZE - 1);
- device_info.lo_file_name[LO_NAME_SIZE - 1] = '\0';
- TEST_AND_RETURN_FALSE_ERRNO(
- ioctl(loop_device_fd, LOOP_SET_STATUS64, &device_info) == 0);
- if (writable) {
- // Make sure loop device isn't read only.
- int ro = 0;
- if (ioctl(loop_device_fd, BLKROSET, &ro) != 0) {
- PLOG(WARNING) << "Failed to mark loop device writable.";
- }
- }
- return true;
+#ifdef __ANDROID__
+ // Use libdm to bind a free loop device. The library internally handles the
+ // race condition.
+ android::dm::LoopControl loop_control;
+ TEST_AND_RETURN_FALSE(loop_control.Attach(data_fd, 5s, out_lo_dev_name));
+ int loop_device_fd = open(out_lo_dev_name->c_str(), O_RDWR | O_CLOEXEC);
+ ScopedFdCloser loop_fd_closer(&loop_device_fd);
+ int loop_number;
+ TEST_AND_RETURN_FALSE(
+ sscanf(out_lo_dev_name->c_str(), "/dev/block/loop%d", &loop_number) == 1);
+ return SetLoopDeviceStatus(loop_device_fd, filename, loop_number, writable);
+#else
+ return BindToUnusedLoopDeviceLegacy(
+ data_fd, filename, writable, out_lo_dev_name);
+#endif
}
bool UnbindLoopDevice(const string& lo_dev_name) {
diff --git a/common/utils.cc b/common/utils.cc
index 50b45fa..9e1e6c5 100644
--- a/common/utils.cc
+++ b/common/utils.cc
@@ -30,6 +30,7 @@
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <time.h>
#include <unistd.h>
#include <algorithm>
@@ -819,7 +820,7 @@
return base_code;
}
-string StringVectorToString(const vector<string> &vec_str) {
+string StringVectorToString(const vector<string>& vec_str) {
string str = "[";
for (vector<string>::const_iterator i = vec_str.begin(); i != vec_str.end();
++i) {
@@ -848,7 +849,7 @@
encoded_hash.c_str());
}
-bool ConvertToOmahaInstallDate(Time time, int *out_num_days) {
+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;
@@ -978,10 +979,58 @@
}
}
+string GetFilePath(int fd) {
+ base::FilePath proc("/proc/self/fd/" + std::to_string(fd));
+ base::FilePath file_name;
+
+ if (!base::ReadSymbolicLink(proc, &file_name)) {
+ return "not found";
+ }
+ return file_name.value();
+}
+
+string GetTimeAsString(time_t utime) {
+ struct tm tm;
+ CHECK_EQ(localtime_r(&utime, &tm), &tm);
+ char str[16];
+ CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15u);
+ return str;
+}
+
string GetExclusionName(const string& str_to_convert) {
return base::NumberToString(base::StringPieceHash()(str_to_convert));
}
+static bool ParseTimestamp(const std::string& str, int64_t* out) {
+ if (!base::StringToInt64(str, out)) {
+ LOG(WARNING) << "Invalid timestamp: " << str;
+ return false;
+ }
+ return true;
+}
+
+ErrorCode IsTimestampNewer(const std::string& old_version,
+ const std::string& new_version) {
+ if (old_version.empty() || new_version.empty()) {
+ LOG(WARNING)
+ << "One of old/new timestamp is empty, permit update anyway. Old: "
+ << old_version << " New: " << new_version;
+ return ErrorCode::kSuccess;
+ }
+ int64_t old_ver = 0;
+ if (!ParseTimestamp(old_version, &old_ver)) {
+ return ErrorCode::kError;
+ }
+ int64_t new_ver = 0;
+ if (!ParseTimestamp(new_version, &new_ver)) {
+ return ErrorCode::kDownloadManifestParseError;
+ }
+ if (old_ver > new_ver) {
+ return ErrorCode::kPayloadTimestampError;
+ }
+ return ErrorCode::kSuccess;
+}
+
} // namespace utils
} // namespace chromeos_update_engine
diff --git a/common/utils.h b/common/utils.h
index b6880ed..bcaed31 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -18,6 +18,7 @@
#define UPDATE_ENGINE_COMMON_UTILS_H_
#include <errno.h>
+#include <time.h>
#include <unistd.h>
#include <algorithm>
@@ -295,6 +296,9 @@
// shell command. Returns true on success.
bool GetVpdValue(std::string key, std::string* result);
+// This function gets the file path of the file pointed to by FileDiscriptor.
+std::string GetFilePath(int fd);
+
// Divide |x| by |y| and round up to the nearest integer.
constexpr uint64_t DivRoundUp(uint64_t x, uint64_t y) {
return (x + y - 1) / y;
@@ -317,10 +321,23 @@
uint16_t* high_version,
uint16_t* low_version);
+// Return a string representation of |utime| for log file names.
+std::string GetTimeAsString(time_t utime);
// Returns the string format of the hashed |str_to_convert| that can be used
// with |Excluder| as the exclusion name.
std::string GetExclusionName(const std::string& str_to_convert);
+// Parse `old_version` and `new_version` as integer timestamps and
+// Return kSuccess if `new_version` is larger/newer.
+// Return kSuccess if either one is empty.
+// Return kError if |old_version| is not empty and not an integer.
+// Return kDownloadManifestParseError if |new_version| is not empty and not an
+// integer.
+// Return kPayloadTimestampError if both are integers but |new_version| <
+// |old_version|.
+ErrorCode IsTimestampNewer(const std::string& old_version,
+ const std::string& new_version);
+
} // namespace utils
// Utility class to close a file descriptor
diff --git a/common/utils_unittest.cc b/common/utils_unittest.cc
index f9eb596..d73b3da 100644
--- a/common/utils_unittest.cc
+++ b/common/utils_unittest.cc
@@ -472,4 +472,23 @@
ExpectInvalidParseRollbackKeyVersion("1.99999");
}
+TEST(UtilsTest, GetFilePathTest) {
+ test_utils::ScopedTempFile file;
+ int fd = HANDLE_EINTR(open(file.path().c_str(), O_RDONLY));
+ EXPECT_GE(fd, 0);
+ EXPECT_EQ(file.path(), utils::GetFilePath(fd));
+ EXPECT_EQ("not found", utils::GetFilePath(-1));
+ IGNORE_EINTR(close(fd));
+}
+
+TEST(UtilsTest, ValidatePerPartitionTimestamp) {
+ ASSERT_EQ(ErrorCode::kPayloadTimestampError,
+ utils::IsTimestampNewer("10", "5"));
+ ASSERT_EQ(ErrorCode::kSuccess, utils::IsTimestampNewer("10", "11"));
+ ASSERT_EQ(ErrorCode::kDownloadManifestParseError,
+ utils::IsTimestampNewer("10", "lol"));
+ ASSERT_EQ(ErrorCode::kError, utils::IsTimestampNewer("lol", "ZZZ"));
+ ASSERT_EQ(ErrorCode::kSuccess, utils::IsTimestampNewer("10", ""));
+}
+
} // namespace chromeos_update_engine