AU: Add a utility routine to get the filesystem size from a device.

BUG=7678
TEST=unit tests

Change-Id: I177e95014e09c64844fa7ecc6efb91c0997cb9bd

Review URL: http://codereview.chromium.org/3706006
diff --git a/utils.cc b/utils.cc
index 6006eb8..c8a4da6 100644
--- a/utils.cc
+++ b/utils.cc
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 
+#include <base/eintr_wrapper.h>
 #include <base/file_path.h>
 #include <base/file_util.h>
 #include <base/rand_util.h>
@@ -421,6 +422,61 @@
   return true;
 }
 
+bool GetFilesystemSize(const std::string& device,
+                       int* out_block_count,
+                       int* out_block_size) {
+  int fd = HANDLE_EINTR(open(device.c_str(), O_RDONLY));
+  TEST_AND_RETURN_FALSE(fd >= 0);
+  ScopedFdCloser fd_closer(&fd);
+  return GetFilesystemSizeFromFD(fd, out_block_count, out_block_size);
+}
+
+bool GetFilesystemSizeFromFD(int fd,
+                             int* out_block_count,
+                             int* out_block_size) {
+  TEST_AND_RETURN_FALSE(fd >= 0);
+
+  // Determine the ext3 filesystem size by directly reading the block count and
+  // block size information from the superblock. See include/linux/ext3_fs.h for
+  // more details on the structure.
+  ssize_t kBufferSize = 16 * sizeof(uint32_t);
+  char buffer[kBufferSize];
+  const int kSuperblockOffset = 1024;
+  if (HANDLE_EINTR(pread(fd, buffer, kBufferSize, kSuperblockOffset)) !=
+      kBufferSize) {
+    PLOG(ERROR) << "Unable to determine file system size:";
+    return false;
+  }
+  uint32_t block_count;  // ext3_fs.h: ext3_super_block.s_blocks_count
+  uint32_t log_block_size;  // ext3_fs.h: ext3_super_block.s_log_block_size
+  uint16_t magic;  // ext3_fs.h: ext3_super_block.s_magic
+  memcpy(&block_count, &buffer[1 * sizeof(int32_t)], sizeof(block_count));
+  memcpy(&log_block_size, &buffer[6 * sizeof(int32_t)], sizeof(log_block_size));
+  memcpy(&magic, &buffer[14 * sizeof(int32_t)], sizeof(magic));
+  block_count = le32toh(block_count);
+  const int kExt3MinBlockLogSize = 10;  // ext3_fs.h: EXT3_MIN_BLOCK_LOG_SIZE
+  log_block_size = le32toh(log_block_size) + kExt3MinBlockLogSize;
+  magic = le16toh(magic);
+
+  // Sanity check the parameters.
+  const uint16_t kExt3SuperMagic = 0xef53;  // ext3_fs.h: EXT3_SUPER_MAGIC
+  TEST_AND_RETURN_FALSE(magic == kExt3SuperMagic);
+  const int kExt3MinBlockSize = 1024;  // ext3_fs.h: EXT3_MIN_BLOCK_SIZE
+  const int kExt3MaxBlockSize = 4096;  // ext3_fs.h: EXT3_MAX_BLOCK_SIZE
+  int block_size = 1 << log_block_size;
+  TEST_AND_RETURN_FALSE(block_size >= kExt3MinBlockSize &&
+                        block_size <= kExt3MaxBlockSize);
+  TEST_AND_RETURN_FALSE(block_count > 0);
+
+  if (out_block_count) {
+    *out_block_count = block_count;
+  }
+  if (out_block_size) {
+    *out_block_size = block_size;
+  }
+  return true;
+}
+
 bool GetBootloader(BootLoader* out_bootloader) {
   // For now, hardcode to syslinux.
   *out_bootloader = BootLoader_SYSLINUX;
diff --git a/utils.h b/utils.h
index bfe7378..9beae04 100644
--- a/utils.h
+++ b/utils.h
@@ -115,6 +115,17 @@
                      unsigned long flags);
 bool UnmountFilesystem(const std::string& mountpoint);
 
+// Returns the block count and the block byte size of the ext3 file system on
+// |device| (which may be a real device or a path to a filesystem image) or on
+// an opened file descriptor |fd|. The actual file-system size is |block_count|
+// * |block_size| bytes. Returns true on success, false otherwise.
+bool GetFilesystemSize(const std::string& device,
+                       int* out_block_count,
+                       int* out_block_size);
+bool GetFilesystemSizeFromFD(int fd,
+                             int* out_block_count,
+                             int* out_block_size);
+
 enum BootLoader {
   BootLoader_SYSLINUX = 0,
   BootLoader_CHROME_FIRMWARE = 1
diff --git a/utils_unittest.cc b/utils_unittest.cc
index a5550d3..4f17305 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -10,7 +10,10 @@
 #include <string>
 #include <vector>
 
-#include "gtest/gtest.h"
+#include <base/string_util.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/test_utils.h"
 #include "update_engine/utils.h"
 
 using std::map;
@@ -205,4 +208,21 @@
   }
 }
 
+TEST(UtilsTest, RunAsRootGetFilesystemSizeTest) {
+  string img;
+  EXPECT_TRUE(utils::MakeTempFile("/tmp/img.XXXXXX", &img, NULL));
+  ScopedPathUnlinker img_unlinker(img);
+  CreateExtImageAtPath(img, NULL);
+  // Extend the "partition" holding the file system from 10MiB to 20MiB.
+  EXPECT_EQ(0, System(base::StringPrintf(
+      "dd if=/dev/zero of=%s seek=20971519 bs=1 count=1",
+      img.c_str())));
+  EXPECT_EQ(20 * 1024 * 1024, utils::FileSize(img));
+  int block_count = 0;
+  int block_size = 0;
+  EXPECT_TRUE(utils::GetFilesystemSize(img, &block_count, &block_size));
+  EXPECT_EQ(4096, block_size);
+  EXPECT_EQ(10 * 1024 * 1024 / 4096, block_count);
+}
+
 }  // namespace chromeos_update_engine