update_engine: Handle p2p file sharing `size_t` overflow
Also create interface to allow for finer grain testing.
BUG=b:227282649
TEST=FEATURES=test emerge-$B update_engine
TEST=p2p file sharing
Change-Id: I81361299577d748e69983f5ce0564529ff47e1b2
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/3633019
Tested-by: Jae Hoon Kim <kimjae@chromium.org>
Reviewed-by: Yuanpeng Ni <yuanpengni@chromium.org>
Reviewed-by: Henry Barnor <hbarnor@chromium.org>
Commit-Queue: Jae Hoon Kim <kimjae@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 4041451..568c923 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -181,6 +181,7 @@
static_library("libupdate_engine") {
sources = [
"certificate_checker.cc",
+ "common/call_wrapper.cc",
"common/connection_utils.cc",
"common/system_state.cc",
"cros/boot_control_chromeos.cc",
diff --git a/common/call_wrapper.cc b/common/call_wrapper.cc
new file mode 100644
index 0000000..844461c
--- /dev/null
+++ b/common/call_wrapper.cc
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2022 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/call_wrapper.h"
+
+#include <memory>
+
+#include <base/files/file_path.h>
+#include <base/system/sys_info.h>
+
+namespace chromeos_update_engine {
+
+std::unique_ptr<CallWrapperInterface> CreateCallWrapper() {
+ return std::make_unique<CallWrapper>();
+}
+
+int64_t CallWrapper::AmountOfFreeDiskSpace(const base::FilePath& path) {
+ return base::SysInfo::AmountOfFreeDiskSpace(path);
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/call_wrapper.h b/common/call_wrapper.h
new file mode 100644
index 0000000..bb24774
--- /dev/null
+++ b/common/call_wrapper.h
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2022 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_CALL_WRAPPER_H_
+#define UPDATE_ENGINE_COMMON_CALL_WRAPPER_H_
+
+#include "update_engine/common/call_wrapper_interface.h"
+
+#include <base/files/file_path.h>
+
+namespace chromeos_update_engine {
+
+class CallWrapper : public CallWrapperInterface {
+ public:
+ CallWrapper() = default;
+ CallWrapper(const CallWrapper&) = delete;
+ CallWrapper& operator=(const CallWrapper&) = delete;
+
+ ~CallWrapper() = default;
+
+ // CallWrapperInterface overrides.
+ int64_t AmountOfFreeDiskSpace(const base::FilePath& path) override;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_CALL_WRAPPER_H_
diff --git a/common/call_wrapper_interface.h b/common/call_wrapper_interface.h
new file mode 100644
index 0000000..9eb22b5
--- /dev/null
+++ b/common/call_wrapper_interface.h
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2022 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_CALL_WRAPPER_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_CALL_WRAPPER_INTERFACE_H_
+
+#include <memory>
+
+#include <base/files/file_path.h>
+
+namespace chromeos_update_engine {
+
+// The abstract call_wrapper interface used mainly to intercept libc or system
+// calls for testing.
+class CallWrapperInterface {
+ public:
+ CallWrapperInterface(const CallWrapperInterface&) = delete;
+ CallWrapperInterface& operator=(const CallWrapperInterface&) = delete;
+
+ virtual ~CallWrapperInterface() = default;
+
+ virtual int64_t AmountOfFreeDiskSpace(const base::FilePath& path) = 0;
+
+ protected:
+ CallWrapperInterface() = default;
+};
+
+// This factory function creates a new CallWrapperInterface instance for the
+// current platform.
+std::unique_ptr<CallWrapperInterface> CreateCallWrapper();
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_CALL_WRAPPER_INTERFACE_H_
diff --git a/common/mock_call_wrapper.h b/common/mock_call_wrapper.h
new file mode 100644
index 0000000..65a5705
--- /dev/null
+++ b/common/mock_call_wrapper.h
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2022 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_CALL_WRAPPER_H_
+#define UPDATE_ENGINE_COMMON_MOCK_CALL_WRAPPER_H_
+
+#include "update_engine/common/call_wrapper_interface.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+class MockCallWrapper : public CallWrapperInterface {
+ public:
+ MOCK_METHOD(int64_t,
+ AmountOfFreeDiskSpace,
+ (const base::FilePath& path),
+ (override));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_MOCK_CALL_WRAPPER_H_
diff --git a/common/system_state.h b/common/system_state.h
index 703e9b1..4fc1aac 100644
--- a/common/system_state.h
+++ b/common/system_state.h
@@ -52,6 +52,7 @@
class PayloadStateInterface;
class PowerManagerInterface;
class UpdateAttempter;
+class CallWrapperInterface;
// An interface to global system context, including platform resources,
// the current state of the system, high-level objects whose lifetime is same
@@ -124,6 +125,9 @@
// Returns a pointer to the CrosHealthdInteraface singleton.
virtual CrosHealthdInterface* cros_healthd() = 0;
+ // Returns a pointer to the CallWrapperInterface singleton.
+ virtual CallWrapperInterface* call_wrapper() = 0;
+
protected:
static SystemState* g_pointer_;
};
diff --git a/cros/download_action_chromeos_unittest.cc b/cros/download_action_chromeos_unittest.cc
index cf46c9c..6567084 100644
--- a/cros/download_action_chromeos_unittest.cc
+++ b/cros/download_action_chromeos_unittest.cc
@@ -546,6 +546,9 @@
EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(),
GetUsingP2PForSharing())
.WillRepeatedly(Return(use_p2p_to_share));
+ EXPECT_CALL(*FakeSystemState::Get()->mock_call_wrapper(),
+ AmountOfFreeDiskSpace(_))
+ .WillRepeatedly(Return(data_.length() * 2));
ScopedTempFile output_temp_file;
TestDirectFileWriter writer;
@@ -635,6 +638,9 @@
TEST_F(P2PDownloadActionTest, CanAppend) {
SetupDownload(1000); // starting_offset
+ EXPECT_CALL(*FakeSystemState::Get()->mock_call_wrapper(),
+ AmountOfFreeDiskSpace(_))
+ .WillOnce(Return(data_.length() * 2));
// Prepare the file with existing data before starting to write to
// it via DownloadAction.
@@ -669,6 +675,9 @@
TEST_F(P2PDownloadActionTest, DeletePartialP2PFileIfResumingWithoutP2P) {
SetupDownload(1000); // starting_offset
+ EXPECT_CALL(*FakeSystemState::Get()->mock_call_wrapper(),
+ AmountOfFreeDiskSpace(_))
+ .WillOnce(Return(data_.length() * 2));
// Prepare the file with all existing data before starting to write
// to it via DownloadAction.
@@ -699,6 +708,9 @@
EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(),
GetUsingP2PForSharing())
.WillRepeatedly(Return(true));
+ EXPECT_CALL(*FakeSystemState::Get()->mock_call_wrapper(),
+ AmountOfFreeDiskSpace(_))
+ .WillRepeatedly(Return(data_.length() * 2));
EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), NextPayload())
.WillOnce(Return(true));
diff --git a/cros/fake_system_state.cc b/cros/fake_system_state.cc
index 7673b1d..8ca424d 100644
--- a/cros/fake_system_state.cc
+++ b/cros/fake_system_state.cc
@@ -33,6 +33,7 @@
request_params_(&mock_request_params_),
p2p_manager_(&mock_p2p_manager_),
update_manager_(&fake_update_manager_),
+ call_wrapper_(&mock_call_wrapper_),
device_policy_(nullptr),
fake_system_rebooted_(false) {
mock_payload_state_.Initialize();
diff --git a/cros/fake_system_state.h b/cros/fake_system_state.h
index 652a3bc..2446895 100644
--- a/cros/fake_system_state.h
+++ b/cros/fake_system_state.h
@@ -28,6 +28,7 @@
#include "update_engine/common/fake_clock.h"
#include "update_engine/common/fake_hardware.h"
#include "update_engine/common/fake_prefs.h"
+#include "update_engine/common/mock_call_wrapper.h"
#include "update_engine/common/mock_metrics_reporter.h"
#include "update_engine/common/mock_prefs.h"
#include "update_engine/common/system_state.h"
@@ -115,6 +116,8 @@
inline CrosHealthdInterface* cros_healthd() override { return cros_healthd_; }
+ inline CallWrapperInterface* call_wrapper() override { return call_wrapper_; }
+
inline bool system_rebooted() override { return fake_system_rebooted_; }
// Setters for the various members, can be used for overriding the default
@@ -188,6 +191,15 @@
cros_healthd_ = cros_healthd;
}
+ inline void set_call_wrapper(CallWrapperInterface* call_wrapper) {
+ call_wrapper_ = call_wrapper;
+ }
+
+ inline testing::StrictMock<MockCallWrapper>* mock_call_wrapper() {
+ CHECK(call_wrapper_ == &mock_call_wrapper_);
+ return &mock_call_wrapper_;
+ }
+
// Getters for the built-in default implementations. These return the actual
// concrete type of each implementation. For additional safety, they will fail
// whenever the requested default was overridden by a different
@@ -284,6 +296,7 @@
testing::NiceMock<MockOmahaRequestParams> mock_request_params_;
testing::NiceMock<MockP2PManager> mock_p2p_manager_;
testing::NiceMock<MockPowerManager> mock_power_manager_;
+ testing::StrictMock<MockCallWrapper> mock_call_wrapper_;
// Pointers to objects that client code can override. They are initialized to
// the default implementations above.
@@ -302,6 +315,7 @@
PowerManagerInterface* power_manager_{&mock_power_manager_};
DlcServiceInterface* dlcservice_;
CrosHealthdInterface* cros_healthd_;
+ CallWrapperInterface* call_wrapper_;
// Other object pointers (not preinitialized).
const policy::DevicePolicy* device_policy_;
diff --git a/cros/p2p_manager.cc b/cros/p2p_manager.cc
index abf894c..40f23a4 100644
--- a/cros/p2p_manager.cc
+++ b/cros/p2p_manager.cc
@@ -50,6 +50,7 @@
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
+#include "update_engine/common/call_wrapper.h"
#include "update_engine/common/subprocess.h"
#include "update_engine/common/system_state.h"
#include "update_engine/common/utils.h"
@@ -514,15 +515,16 @@
// Before creating the file, bail if statvfs(3) indicates that at
// least twice the size is not available in P2P_DIR.
- struct statvfs statvfsbuf;
FilePath p2p_dir = configuration_->GetP2PDir();
- if (statvfs(p2p_dir.value().c_str(), &statvfsbuf) != 0) {
- PLOG(ERROR) << "Error calling statvfs() for dir " << p2p_dir.value();
+ int64_t free_bytes =
+ SystemState::Get()->call_wrapper()->AmountOfFreeDiskSpace(p2p_dir);
+ if (free_bytes < 0) {
+ PLOG(ERROR) << "Error getting amount of free disk space from "
+ << p2p_dir.value();
return false;
}
- size_t free_bytes =
- static_cast<size_t>(statvfsbuf.f_bsize) * statvfsbuf.f_bavail;
- if (free_bytes < 2 * expected_size) {
+ // Compare sizes this way to handle overflows.
+ if (free_bytes / 2 < expected_size) {
// This can easily happen and is worth reporting.
LOG(INFO) << "Refusing to allocate p2p file of " << expected_size
<< " bytes since the directory " << p2p_dir.value()
diff --git a/cros/p2p_manager_unittest.cc b/cros/p2p_manager_unittest.cc
index 1a7d1ee..f822027 100644
--- a/cros/p2p_manager_unittest.cc
+++ b/cros/p2p_manager_unittest.cc
@@ -49,6 +49,7 @@
#include <policy/libpolicy.h>
#include <policy/mock_device_policy.h>
+#include "update_engine/common/mock_call_wrapper.h"
#include "update_engine/common/prefs.h"
#include "update_engine/common/subprocess.h"
#include "update_engine/common/test_utils.h"
@@ -79,6 +80,7 @@
FakeSystemState::CreateInstance();
test_conf_ = new FakeP2PManagerConfiguration();
fake_um_ = FakeSystemState::Get()->fake_update_manager();
+ mock_call_wrapper_ = FakeSystemState::Get()->mock_call_wrapper();
// Construct the P2P manager under test.
manager_.reset(P2PManager::Construct(
@@ -94,6 +96,8 @@
chromeos_update_manager::FakeUpdateManager* fake_um_;
unique_ptr<P2PManager> manager_;
+
+ MockCallWrapper* mock_call_wrapper_;
};
// Check that |IsP2PEnabled()| polls the policy correctly, with the value not
@@ -357,6 +361,8 @@
// Check that sharing a *new* file works.
TEST_F(P2PManagerTest, ShareFile) {
const int kP2PTestFileSize = 1000 * 8; // 8 KB
+ EXPECT_CALL(*mock_call_wrapper_, AmountOfFreeDiskSpace(_))
+ .WillOnce(Return(kP2PTestFileSize * 2));
EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
EXPECT_EQ(manager_->FileGetPath("foo"),
@@ -376,6 +382,8 @@
// Check that making a shared file visible, does what is expected.
TEST_F(P2PManagerTest, MakeFileVisible) {
const int kP2PTestFileSize = 1000 * 8; // 8 KB
+ EXPECT_CALL(*mock_call_wrapper_, AmountOfFreeDiskSpace(_))
+ .WillOnce(Return(kP2PTestFileSize * 2));
// First, check that it's not visible.
manager_->FileShare("foo", kP2PTestFileSize);
@@ -398,6 +406,20 @@
}
}
+TEST_F(P2PManagerTest, SharingFileBytesMoreThanNecessaryStorageSpace) {
+ const int kP2PTestFileSize = 16 * (1 << 10); // 16 KB
+ EXPECT_CALL(*mock_call_wrapper_, AmountOfFreeDiskSpace(_))
+ .WillOnce(Return(kP2PTestFileSize + 1));
+ EXPECT_FALSE(manager_->FileShare("foo", kP2PTestFileSize));
+}
+
+TEST_F(P2PManagerTest, SharingFileBytesLessThanNecessaryStorageSpace) {
+ const int kP2PTestFileSize = 16 * (1 << 10); // 16 KB
+ EXPECT_CALL(*mock_call_wrapper_, AmountOfFreeDiskSpace(_))
+ .WillOnce(Return(kP2PTestFileSize * 2));
+ EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
+}
+
// Check that we return the right values for existing files in P2P_DIR.
TEST_F(P2PManagerTest, ExistingFiles) {
bool visible;
diff --git a/cros/real_system_state.cc b/cros/real_system_state.cc
index 552b5bc..8e8d0fd 100644
--- a/cros/real_system_state.cc
+++ b/cros/real_system_state.cc
@@ -84,6 +84,12 @@
return false;
}
+ call_wrapper_ = CreateCallWrapper();
+ if (!call_wrapper_) {
+ LOG(ERROR) << "Error initializing the CallWrapperInterface.";
+ return false;
+ }
+
// Initialize standard and powerwash-safe prefs.
base::FilePath non_volatile_path;
// TODO(deymo): Fall back to in-memory prefs if there's no physical directory
diff --git a/cros/real_system_state.h b/cros/real_system_state.h
index 2c0362b..1c7ea7b 100644
--- a/cros/real_system_state.h
+++ b/cros/real_system_state.h
@@ -27,6 +27,7 @@
#include "update_engine/certificate_checker.h"
#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/call_wrapper_interface.h"
#include "update_engine/common/clock.h"
#include "update_engine/common/cros_healthd_interface.h"
#include "update_engine/common/daemon_state_interface.h"
@@ -115,6 +116,8 @@
CrosHealthdInterface* cros_healthd() override { return cros_healthd_.get(); }
+ CallWrapperInterface* call_wrapper() override { return call_wrapper_.get(); }
+
private:
// Initializes and sets systems objects that require an initialization
// separately from construction. Returns |true| on success.
@@ -178,6 +181,9 @@
policy::PolicyProvider policy_provider_;
+ // Interface for cros_healthd.
+ std::unique_ptr<CallWrapperInterface> call_wrapper_;
+
// If true, this is the first instance of the update engine since the system
// rebooted. Important for tracking whether you are running instance of the
// update engine on first boot or due to a crash/restart.