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/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",