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;
 }
 
diff --git a/utils.h b/utils.h
index 2c0af3e..19bb9d6 100644
--- a/utils.h
+++ b/utils.h
@@ -191,7 +191,7 @@
                      unsigned long flags);  // NOLINT(runtime/int)
 bool UnmountFilesystem(const std::string& mountpoint);
 
-// Returns the block count and the block byte size of the ext3 file system on
+// Returns the block count and the block byte size of the 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.
@@ -202,6 +202,23 @@
                              int* out_block_count,
                              int* out_block_size);
 
+// Determines the block count and block size of the ext3 fs. At least 2048 bytes
+// are required to parse the first superblock. Returns whether the buffer
+// contains a valid ext3 filesystem and the values were parsed.
+bool GetExt3Size(const uint8_t* buffer, size_t buffer_size,
+                 int* out_block_count,
+                 int* out_block_size);
+
+// Determines the block count and block size of the squashfs v4 fs. At least 96
+// bytes are required to parse the header of the filesystem. Since squashfs
+// doesn't define a physical block size, a value of 4096 is used for the block
+// size, which is the default padding when creating the filesystem.
+// Returns whether the buffer contains a valid squashfs v4 header and the size
+// was parsed. Only little endian squashfs is supported.
+bool GetSquashfs4Size(const uint8_t* buffer, size_t buffer_size,
+                      int* out_block_count,
+                      int* out_block_size);
+
 // Returns the path of the passed |command| on the board. This uses the
 // environment variable SYSROOT to determine the path to the command on the
 // board instead of the path on the running environment.
@@ -643,6 +660,4 @@
     }                                                                          \
   } while (0)
 
-
-
 #endif  // UPDATE_ENGINE_UTILS_H_
diff --git a/utils_unittest.cc b/utils_unittest.cc
index 0e29113..e3133f9 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -331,6 +331,73 @@
   EXPECT_EQ(10 * 1024 * 1024 / 4096, block_count);
 }
 
+// Squashfs example filesystem, generated with:
+//   echo hola>hola
+//   mksquashfs hola hola.sqfs -noappend -nopad
+//   hexdump hola.sqfs -e '16/1 "%02x, " "\n"'
+const unsigned char kSquashfsFile[] = {
+  0x68, 0x73, 0x71, 0x73, 0x02, 0x00, 0x00, 0x00,  // magic, inodes
+  0x3e, 0x49, 0x61, 0x54, 0x00, 0x00, 0x02, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x00,
+  0xc0, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00,  // flags, noids, major, minor
+  0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // root_inode
+  0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // bytes_used
+  0xe7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x68, 0x6f, 0x6c, 0x61, 0x0a, 0x2c, 0x00, 0x78,
+  0xda, 0x63, 0x62, 0x58, 0xc2, 0xc8, 0xc0, 0xc0,
+  0xc8, 0xd0, 0x6b, 0x91, 0x18, 0x02, 0x64, 0xa0,
+  0x00, 0x56, 0x06, 0x90, 0xcc, 0x7f, 0xb0, 0xbc,
+  0x9d, 0x67, 0x62, 0x08, 0x13, 0x54, 0x1c, 0x44,
+  0x4b, 0x03, 0x31, 0x33, 0x10, 0x03, 0x00, 0xb5,
+  0x87, 0x04, 0x89, 0x16, 0x00, 0x78, 0xda, 0x63,
+  0x60, 0x80, 0x00, 0x46, 0x28, 0xcd, 0xc4, 0xc0,
+  0xcc, 0x90, 0x91, 0x9f, 0x93, 0x08, 0x00, 0x04,
+  0x70, 0x01, 0xab, 0x10, 0x80, 0x60, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x78,
+  0xda, 0x63, 0x60, 0x80, 0x00, 0x05, 0x28, 0x0d,
+  0x00, 0x01, 0x10, 0x00, 0x21, 0xc5, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x99,
+  0xcd, 0x02, 0x00, 0x88, 0x13, 0x00, 0x00, 0xdd,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+TEST(UtilsTest, GetSquashfs4Size) {
+  unsigned char buffer[sizeof(kSquashfsFile)];
+  memcpy(buffer, kSquashfsFile, sizeof(kSquashfsFile));
+
+  int block_count = -1;
+  int block_size = -1;
+  // Not enough bytes passed.
+  EXPECT_FALSE(utils::GetSquashfs4Size(buffer, 10, nullptr, nullptr));
+
+  // The whole file system is passed, which is enough for parsing.
+  EXPECT_TRUE(utils::GetSquashfs4Size(buffer, sizeof(kSquashfsFile),
+                                      &block_count, &block_size));
+  EXPECT_EQ(4096, block_size);
+  EXPECT_EQ(1, block_count);
+
+  // Modify the major version to 5.
+  uint16_t* s_major = reinterpret_cast<uint16_t*>(buffer + 0x1c);
+  *s_major = 5;
+  EXPECT_FALSE(utils::GetSquashfs4Size(buffer, 10, nullptr, nullptr));
+  memcpy(buffer, kSquashfsFile, sizeof(kSquashfsFile));
+
+  // Modify the bytes_used to have 6 blocks.
+  int64_t* bytes_used = reinterpret_cast<int64_t*>(buffer + 0x28);
+  *bytes_used = 4096 * 5 + 1;  // 6 "blocks".
+  EXPECT_TRUE(utils::GetSquashfs4Size(buffer, sizeof(kSquashfsFile),
+                                      &block_count, &block_size));
+  EXPECT_EQ(4096, block_size);
+  EXPECT_EQ(6, block_count);
+}
+
 TEST(UtilsTest, GetInstallDevTest) {
   string boot_dev = "/dev/sda5";
   string install_dev;