Reserve space for apex by creating a file and write 0s
For first version, we let update_engine compute size requirements by
adding up decompressed sizes. Then update_engine creates a file at a
fixed path and write 0s to that file to reserve space.
Test: treehugger, serve an OTA, make sure /data/apex/ota_reserved is present
Bug: 172911822
Change-Id: I2f44289711b1daa064af8ead66cc1293dad89dc7
diff --git a/aosp/update_attempter_android.cc b/aosp/update_attempter_android.cc
index 79840e8..5523a58 100644
--- a/aosp/update_attempter_android.cc
+++ b/aosp/update_attempter_android.cc
@@ -71,6 +71,9 @@
namespace chromeos_update_engine {
+// Don't change this path... apexd relies on it.
+constexpr const char* kApexReserveSpaceDir = "/data/apex/ota_reserved";
+
namespace {
// Minimum threshold to broadcast an status update in progress and time.
@@ -964,6 +967,28 @@
return GetCurrentSlot() == 0 ? 1 : 0;
}
+static uint64_t allocateSpaceForApex(const DeltaArchiveManifest& manifest) {
+ // TODO(b/178696931) call apexd's binder once there is one.
+ uint64_t size_required = 0;
+ for (const auto& apex_info : manifest.apex_info()) {
+ if (apex_info.is_compressed()) {
+ size_required += apex_info.decompressed_size();
+ }
+ }
+ if (size_required == 0) {
+ return 0;
+ }
+ base::FilePath path{kApexReserveSpaceDir};
+ // The filename is not important, it just needs to be under
+ // kApexReserveSpaceDir. We call it "full.tmp" because the current space
+ // estimation is simply adding up all decompressed sizes.
+ path = path.Append("full.tmp");
+ if (!utils::ReserveStorageSpace(path.value().c_str(), size_required)) {
+ return size_required;
+ }
+ return 0;
+}
+
uint64_t UpdateAttempterAndroid::AllocateSpaceForPayload(
const std::string& metadata_filename,
const vector<string>& key_value_pair_headers,
@@ -994,6 +1019,11 @@
return required_size;
}
}
+ const auto apex_size = allocateSpaceForApex(manifest);
+ if (apex_size != 0) {
+ LOG(ERROR) << "Insufficient space for apex decompression: " << apex_size;
+ return required_size + apex_size;
+ }
LOG(INFO) << "Successfully allocated space for payload.";
return 0;
diff --git a/common/utils.cc b/common/utils.cc
index 0f3b6c6..f5532ff 100644
--- a/common/utils.cc
+++ b/common/utils.cc
@@ -28,6 +28,7 @@
#include <string.h>
#include <sys/mount.h>
#include <sys/resource.h>
+#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
@@ -547,6 +548,31 @@
return true;
}
+bool ReserveStorageSpace(const char* path, uint64_t size) {
+ int fd = HANDLE_EINTR(open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600));
+
+ TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+ ScopedFdCloser closer1{&fd};
+ if (ftruncate(fd, size) < 0) {
+ PLOG(WARNING) << "Failed to ftruncate " << path;
+ }
+ // 1MB buffer
+ std::vector<unsigned char> buffer(1 << 20);
+
+ while (size > 0) {
+ uint64_t bytes_to_write = std::min(size, (uint64_t)buffer.size());
+ if (!utils::WriteAll(fd, buffer.data(), bytes_to_write)) {
+ auto off = lseek64(fd, 0, SEEK_CUR);
+ PLOG(ERROR) << "Failed to write 0 to " << path << "offset: " << off
+ << " size: " << size;
+ unlink(path);
+ return false;
+ }
+ size -= bytes_to_write;
+ }
+ return true;
+}
+
bool MountFilesystem(const string& device,
const string& mountpoint,
unsigned long mountflags, // NOLINT(runtime/int)
diff --git a/common/utils.h b/common/utils.h
index 5f6e475..9a278eb 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -23,6 +23,7 @@
#include <unistd.h>
#include <algorithm>
+#include <cstdint>
#include <limits>
#include <map>
#include <memory>
@@ -181,6 +182,10 @@
// in |read_only|. Return whether the operation succeeded.
bool SetBlockDeviceReadOnly(const std::string& device, bool read_only);
+// Reserve |size| bytes on space on |path| by creating a file at |path| and
+// write 0s into it. Return true iff both creation and writing succeed.
+[[nodiscard]] bool ReserveStorageSpace(const char* path, uint64_t size);
+
// Synchronously mount or unmount a filesystem. Return true on success.
// When mounting, it will attempt to mount the device as the passed filesystem
// type |type|, with the passed |flags| options. If |type| is empty, "ext2",