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;