update_engine: Add support for parsing the size of a squashfs.

This patch extends the utils::GetFilesystemSize() function to support
squashfs (in addition to the existing ext3 support). The filesystem
type is detected automatically.

squashfs doesn't define a block size for the physical device that it
relies on. There's a definition of block size for the data before
compression that doesn't affect the block size used for the underlying
device. When creating the squashfs, it is by default padded to 4 KiB,
so we assume 4 KiB as the block size. This is also the minimum block
size required by verity to compute hashes in all supported archs.

BUG=chromium:430956
TEST=Unittest added.

Change-Id: Ia683a27c094e25952e8d665535dc6219c849bdd0
Reviewed-on: https://chromium-review.googlesource.com/228906
Reviewed-by: Gaurav Shah <gauravsh@chromium.org>
Commit-Queue: Alex Deymo <deymo@chromium.org>
Tested-by: Alex Deymo <deymo@chromium.org>
diff --git a/utils.cc b/utils.cc
index f0bf6f7..c6c5226 100644
--- a/utils.cc
+++ b/utils.cc
@@ -10,6 +10,7 @@
 #include <dirent.h>
 #include <elf.h>
 #include <errno.h>
+#include <ext2fs/ext2fs.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -26,8 +27,8 @@
 #include <vector>
 
 #include <base/callback.h>
-#include <base/files/file_util.h>
 #include <base/files/file_path.h>
+#include <base/files/file_util.h>
 #include <base/files/scoped_file.h>
 #include <base/logging.h>
 #include <base/posix/eintr_wrapper.h>
@@ -666,44 +667,106 @@
                              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:";
+  // Determine the filesystem size by directly reading the block count and
+  // block size information from the superblock. Supported FS are ext3 and
+  // squashfs.
+
+  // Read from the fd only once and detect in memory. The first 2 KiB is enough
+  // to read the ext2 superblock (located at offset 1024) and the squashfs
+  // superblock (located at offset 0).
+  const ssize_t kBufferSize = 2048;
+
+  uint8_t buffer[kBufferSize];
+  if (HANDLE_EINTR(pread(fd, buffer, kBufferSize, 0)) != kBufferSize) {
+    PLOG(ERROR) << "Unable to read the file system header:";
     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));
+
+  if (GetSquashfs4Size(buffer, kBufferSize, out_block_count, out_block_size))
+    return true;
+  if (GetExt3Size(buffer, kBufferSize, out_block_count, out_block_size))
+    return true;
+
+  LOG(ERROR) << "Unable to determine file system type.";
+  return false;
+}
+
+bool GetExt3Size(const uint8_t* buffer, size_t buffer_size,
+                 int* out_block_count,
+                 int* out_block_size) {
+  // See include/linux/ext2_fs.h for more details on the structure. We obtain
+  // ext2 constants from ext2fs/ext2fs.h header but we don't link with the
+  // library.
+  if (buffer_size < SUPERBLOCK_OFFSET + SUPERBLOCK_SIZE)
+    return false;
+
+  const uint8_t* superblock = buffer + SUPERBLOCK_OFFSET;
+
+  // ext3_fs.h: ext3_super_block.s_blocks_count
+  uint32_t block_count =
+      *reinterpret_cast<const uint32_t*>(superblock + 1 * sizeof(int32_t));
+
+  // ext3_fs.h: ext3_super_block.s_log_block_size
+  uint32_t log_block_size =
+      *reinterpret_cast<const uint32_t*>(superblock + 6 * sizeof(int32_t));
+
+  // ext3_fs.h: ext3_super_block.s_magic
+  uint16_t magic =
+      *reinterpret_cast<const uint16_t*>(superblock + 14 * sizeof(int32_t));
+
   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;
+  log_block_size = le32toh(log_block_size) + EXT2_MIN_BLOCK_LOG_SIZE;
   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(magic == EXT2_SUPER_MAGIC);
+  TEST_AND_RETURN_FALSE(log_block_size >= EXT2_MIN_BLOCK_LOG_SIZE &&
+                        log_block_size <= EXT2_MAX_BLOCK_LOG_SIZE);
   TEST_AND_RETURN_FALSE(block_count > 0);
 
-  if (out_block_count) {
+  if (out_block_count)
     *out_block_count = block_count;
+  if (out_block_size)
+    *out_block_size = 1 << log_block_size;
+  return true;
+}
+
+bool GetSquashfs4Size(const unsigned char* buffer, size_t buffer_size,
+                      int* out_block_count,
+                      int* out_block_size) {
+  // See fs/squashfs/squashfs_fs.h for format details. We only support
+  // Squashfs 4.x little endian.
+
+  // sizeof(struct squashfs_super_block)
+  const size_t kSquashfsSuperBlockSize = 96;
+  if (buffer_size < kSquashfsSuperBlockSize)
+    return false;
+
+  // Check magic, squashfs_fs.h: SQUASHFS_MAGIC
+  if (memcmp(buffer, "hsqs", 4) != 0)
+    return false;  // Only little endian is supported.
+
+  // squashfs_fs.h: struct squashfs_super_block.s_major
+  uint16_t s_major = *reinterpret_cast<const uint16_t*>(
+      buffer + 5 * sizeof(uint32_t) + 4 * sizeof(uint16_t));
+
+  if (s_major != 4) {
+    LOG(ERROR) << "Found unsupported squashfs major version " << s_major;
+    return false;
   }
-  if (out_block_size) {
+
+  // squashfs_fs.h: struct squashfs_super_block.bytes_used
+  uint64_t bytes_used = *reinterpret_cast<const int64_t*>(
+      buffer + 5 * sizeof(uint32_t) + 6 * sizeof(uint16_t) + sizeof(uint64_t));
+
+  const int block_size = 4096;
+
+  // The squashfs' bytes_used doesn't need to be aligned with the block boundary
+  // so we round up to the nearest blocksize.
+  if (out_block_count)
+    *out_block_count = (bytes_used + block_size - 1) / block_size;
+  if (out_block_size)
     *out_block_size = block_size;
-  }
   return true;
 }