Add a launcher -data_policy=resize_up_to mode.

This allows increasing the size of userdata.img from the launcher
without data loss. The implementation delegates to /sbin/e2fsck
and /sbin/resize2fs.

The blank_data_image_mb flag is reused to describe the desired size,
though the name doesn't fit perfectly. If the target size is smaller
than the current size, the resize will cause the launcher to fail. If
the target size is equal to the current size, the resize will not run.

Bug: 120573603
Test: Ran with -data_policy=resize_up_to with incrementally larger sizes
Change-Id: I432b4687215ec27806f985889f48beb78f9c5612
diff --git a/common/libs/utils/files.cpp b/common/libs/utils/files.cpp
index 1eb91d8..654d971 100644
--- a/common/libs/utils/files.cpp
+++ b/common/libs/utils/files.cpp
@@ -28,14 +28,7 @@
 namespace cvd {
 
 bool FileHasContent(const std::string& path) {
-  struct stat st;
-  if (stat(path.c_str(), &st) == -1) {
-    return false;
-  }
-  if (st.st_size == 0) {
-    return false;
-  }
-  return true;
+  return FileSize(path) > 0;
 }
 
 bool DirectoryExists(const std::string& path) {
@@ -66,4 +59,12 @@
   return std::string{buffer.data()} + "/" + path;
 }
 
+off_t FileSize(const std::string& path) {
+  struct stat st;
+  if (stat(path.c_str(), &st) == -1) {
+    return 0;
+  }
+  return st.st_size;
+}
+
 }  // namespace cvd
diff --git a/common/libs/utils/files.h b/common/libs/utils/files.h
index 07d5d2f..ce689ac 100644
--- a/common/libs/utils/files.h
+++ b/common/libs/utils/files.h
@@ -15,11 +15,14 @@
  */
 #pragma once
 
+#include <sys/types.h>
+
 #include <string>
 
 namespace cvd {
 bool FileHasContent(const std::string& path);
 bool DirectoryExists(const std::string& path);
+off_t FileSize(const std::string& path);
 
 // The returned value may contain .. or . if these are present in the path
 // argument.
diff --git a/host/commands/launch/main.cc b/host/commands/launch/main.cc
index 3b685f1..4348c5f 100644
--- a/host/commands/launch/main.cc
+++ b/host/commands/launch/main.cc
@@ -68,7 +68,8 @@
 DEFINE_int32(cpus, 2, "Virtual CPU count.");
 DEFINE_string(data_image, "", "Location of the data partition image.");
 DEFINE_string(data_policy, "use_existing", "How to handle userdata partition."
-            " Either 'use_existing', 'create_if_missing', or 'always_create'.");
+            " Either 'use_existing', 'create_if_missing', 'resize_up_to', or "
+            "'always_create'.");
 DEFINE_int32(blank_data_image_mb, 0,
              "The size of the blank data image to generate, MB.");
 DEFINE_string(blank_data_image_fmt, "ext4",
@@ -196,6 +197,7 @@
 const std::string kDataPolicyUseExisting = "use_existing";
 const std::string kDataPolicyCreateIfMissing = "create_if_missing";
 const std::string kDataPolicyAlwaysCreate = "always_create";
+const std::string kDataPolicyResizeUpTo= "resize_up_to";
 
 constexpr char kAdbModeTunnel[] = "tunnel";
 constexpr char kAdbModeUsb[] = "usb";
@@ -216,10 +218,60 @@
   cvd::execute({"/bin/rm", "-f", file});
 }
 
+const int FSCK_ERROR_CORRECTED = 1;
+const int FSCK_ERROR_CORRECTED_REQUIRES_REBOOT = 2;
+
+bool ForceFsckImage(const char* data_image) {
+  int fsck_status = cvd::execute({"/sbin/e2fsck", "-y", "-f", data_image});
+  if (fsck_status & ~(FSCK_ERROR_CORRECTED|FSCK_ERROR_CORRECTED_REQUIRES_REBOOT)) {
+    LOG(ERROR) << "`e2fsck -y -f " << data_image << "` failed with code "
+               << fsck_status;
+    return false;
+  }
+  return true;
+}
+
+bool ResizeImage(const char* data_image, int data_image_mb) {
+  auto file_mb = cvd::FileSize(data_image) >> 20;
+  if (file_mb > data_image_mb) {
+    LOG(ERROR) << data_image << " is already " << file_mb << " MB, will not "
+               << "resize down.";
+    return false;
+  } else if (file_mb == data_image_mb) {
+    LOG(INFO) << data_image << " is already the right size";
+    return true;
+  } else {
+    off_t raw_target = static_cast<off_t>(data_image_mb) << 20;
+    int truncate_status =
+        cvd::SharedFD::Open(data_image, O_RDWR)->Truncate(raw_target);
+    if (truncate_status != 0) {
+      LOG(ERROR) << "`truncate --size=" << data_image_mb << "M "
+                  << data_image << "` failed with code " << truncate_status;
+      return false;
+    }
+    bool fsck_success = ForceFsckImage(data_image);
+    if (!fsck_success) {
+      return false;
+    }
+    int resize_status = cvd::execute({"/sbin/resize2fs", data_image});
+    if (resize_status != 0) {
+      LOG(ERROR) << "`resize2fs " << data_image << "` failed with code "
+                 << resize_status;
+      return false;
+    }
+    fsck_success = ForceFsckImage(data_image);
+    if (!fsck_success) {
+      return false;
+    }
+  }
+  return true;
+}
+
 bool ApplyDataImagePolicy(const char* data_image) {
   bool data_exists = cvd::FileHasContent(data_image);
   bool remove{};
   bool create{};
+  bool resize{};
 
   if (FLAGS_data_policy == kDataPolicyUseExisting) {
     if (!data_exists) {
@@ -233,12 +285,19 @@
     }
     create = false;
     remove = false;
+    resize = false;
   } else if (FLAGS_data_policy == kDataPolicyAlwaysCreate) {
     remove = data_exists;
     create = true;
+    resize = false;
   } else if (FLAGS_data_policy == kDataPolicyCreateIfMissing) {
     create = !data_exists;
     remove = false;
+    resize = false;
+  } else if (FLAGS_data_policy == kDataPolicyResizeUpTo) {
+    create = false;
+    remove = false;
+    resize = true;
   } else {
     LOG(ERROR) << "Invalid data_policy: " << FLAGS_data_policy;
     return false;
@@ -255,6 +314,12 @@
     }
     CreateBlankImage(
         data_image, FLAGS_blank_data_image_mb, FLAGS_blank_data_image_fmt);
+  } else if (resize) {
+    if (!data_exists) {
+      LOG(ERROR) << data_image << " does not exist, but resizing was requested";
+      return false;
+    }
+    return ResizeImage(data_image, FLAGS_blank_data_image_mb);
   } else {
     LOG(INFO) << data_image << " exists. Not creating it.";
   }