Home | History | Annotate | Download | only in payload_generator
      1 //
      2 // Copyright (C) 2017 The Android Open Source Project
      3 //
      4 // Licensed under the Apache License, Version 2.0 (the "License");
      5 // you may not use this file except in compliance with the License.
      6 // You may obtain a copy of the License at
      7 //
      8 //      http://www.apache.org/licenses/LICENSE-2.0
      9 //
     10 // Unless required by applicable law or agreed to in writing, software
     11 // distributed under the License is distributed on an "AS IS" BASIS,
     12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 // See the License for the specific language governing permissions and
     14 // limitations under the License.
     15 //
     16 
     17 #include "update_engine/payload_generator/squashfs_filesystem.h"
     18 
     19 #include <unistd.h>
     20 
     21 #include <algorithm>
     22 #include <map>
     23 #include <set>
     24 #include <string>
     25 #include <vector>
     26 
     27 #include <base/format_macros.h>
     28 #include <base/logging.h>
     29 #include <base/strings/string_number_conversions.h>
     30 #include <base/strings/string_util.h>
     31 #include <base/strings/stringprintf.h>
     32 #include <gtest/gtest.h>
     33 
     34 #include "update_engine/common/test_utils.h"
     35 #include "update_engine/common/utils.h"
     36 #include "update_engine/payload_generator/extent_utils.h"
     37 
     38 namespace chromeos_update_engine {
     39 
     40 using std::map;
     41 using std::set;
     42 using std::string;
     43 using std::unique_ptr;
     44 using std::vector;
     45 
     46 using test_utils::GetBuildArtifactsPath;
     47 
     48 namespace {
     49 
     50 constexpr uint64_t kTestBlockSize = 4096;
     51 constexpr uint64_t kTestSqfsBlockSize = 1 << 15;
     52 
     53 // Checks that all the blocks in |extents| are in the range [0, total_blocks).
     54 void ExpectBlocksInRange(const vector<Extent>& extents, uint64_t total_blocks) {
     55   for (const Extent& extent : extents) {
     56     EXPECT_LE(0U, extent.start_block());
     57     EXPECT_LE(extent.start_block() + extent.num_blocks(), total_blocks);
     58   }
     59 }
     60 
     61 SquashfsFilesystem::SquashfsHeader GetSimpleHeader() {
     62   // These properties are enough for now. Add more as needed.
     63   return {
     64       .magic = 0x73717368,
     65       .block_size = kTestSqfsBlockSize,
     66       .compression_type = 1,  // For gzip.
     67       .major_version = 4,
     68   };
     69 }
     70 
     71 }  // namespace
     72 
     73 class SquashfsFilesystemTest : public ::testing::Test {
     74  public:
     75   void CheckSquashfs(const unique_ptr<SquashfsFilesystem>& fs) {
     76     ASSERT_TRUE(fs);
     77     EXPECT_EQ(kTestBlockSize, fs->GetBlockSize());
     78 
     79     vector<FilesystemInterface::File> files;
     80     ASSERT_TRUE(fs->GetFiles(&files));
     81 
     82     map<string, FilesystemInterface::File> map_files;
     83     for (const auto& file : files) {
     84       EXPECT_EQ(map_files.end(), map_files.find(file.name))
     85           << "File " << file.name << " repeated in the list.";
     86       map_files[file.name] = file;
     87       ExpectBlocksInRange(file.extents, fs->GetBlockCount());
     88     }
     89 
     90     // Checking the sortness.
     91     EXPECT_TRUE(std::is_sorted(files.begin(),
     92                                files.end(),
     93                                [](const FilesystemInterface::File& a,
     94                                   const FilesystemInterface::File& b) {
     95                                  return a.extents[0].start_block() <
     96                                         b.extents[0].start_block();
     97                                }));
     98 
     99     auto overlap_check = [](const FilesystemInterface::File& a,
    100                             const FilesystemInterface::File& b) {
    101       // Return true if overlapping.
    102       return a.extents[0].start_block() + a.extents[0].num_blocks() >
    103              b.extents[0].start_block();
    104     };
    105     // Check files are not overlapping.
    106     EXPECT_EQ(std::adjacent_find(files.begin(), files.end(), overlap_check),
    107               files.end());
    108   }
    109 };
    110 
    111 // CreateFromFile() depends on unsquashfs -m, which only exists in Chrome OS.
    112 #ifdef __CHROMEOS__
    113 TEST_F(SquashfsFilesystemTest, EmptyFilesystemTest) {
    114   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile(
    115       GetBuildArtifactsPath("gen/disk_sqfs_empty.img"), true);
    116   CheckSquashfs(fs);
    117 
    118   // Even an empty squashfs filesystem is rounded up to 4K.
    119   EXPECT_EQ(4096 / kTestBlockSize, fs->GetBlockCount());
    120 
    121   vector<FilesystemInterface::File> files;
    122   ASSERT_TRUE(fs->GetFiles(&files));
    123   ASSERT_EQ(files.size(), 1u);
    124 
    125   FilesystemInterface::File file;
    126   file.name = "<metadata-0>";
    127   file.extents.emplace_back();
    128   file.extents[0].set_start_block(0);
    129   file.extents[0].set_num_blocks(1);
    130   EXPECT_EQ(files[0].name, file.name);
    131   EXPECT_EQ(files[0].extents, file.extents);
    132 }
    133 
    134 TEST_F(SquashfsFilesystemTest, DefaultFilesystemTest) {
    135   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile(
    136       GetBuildArtifactsPath("gen/disk_sqfs_default.img"), true);
    137   CheckSquashfs(fs);
    138 
    139   vector<FilesystemInterface::File> files;
    140   ASSERT_TRUE(fs->GetFiles(&files));
    141   ASSERT_EQ(files.size(), 1u);
    142 
    143   FilesystemInterface::File file;
    144   file.name = "<fragment-0>";
    145   file.extents.emplace_back();
    146   file.extents[0].set_start_block(0);
    147   file.extents[0].set_num_blocks(1);
    148   EXPECT_EQ(files[0].name, file.name);
    149   EXPECT_EQ(files[0].extents, file.extents);
    150 }
    151 #endif  // __CHROMEOS__
    152 
    153 TEST_F(SquashfsFilesystemTest, SimpleFileMapTest) {
    154   string filemap = R"(dir1/file1 96 4000
    155                       dir1/file2 4096 100)";
    156   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
    157       filemap, kTestBlockSize * 2, GetSimpleHeader());
    158   CheckSquashfs(fs);
    159 
    160   vector<FilesystemInterface::File> files;
    161   ASSERT_TRUE(fs->GetFiles(&files));
    162   EXPECT_EQ(files.size(), 2u);
    163 }
    164 
    165 TEST_F(SquashfsFilesystemTest, FileMapZeroSizeFileTest) {
    166   // The second file's size is zero.
    167   string filemap = R"(dir1/file1 96 4000
    168                       dir1/file2 4096
    169                       dir1/file3 4096 100)";
    170   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
    171       filemap, kTestBlockSize * 2, GetSimpleHeader());
    172   CheckSquashfs(fs);
    173 
    174   vector<FilesystemInterface::File> files;
    175   ASSERT_TRUE(fs->GetFiles(&files));
    176   // The second and third files are removed. The file with size zero is removed.
    177   EXPECT_EQ(files.size(), 2u);
    178 }
    179 
    180 // Testing the compressed bit.
    181 TEST_F(SquashfsFilesystemTest, CompressedBitTest) {
    182   string filemap = "dir1/file1 0 " + std::to_string(4000 | (1 << 24)) + "\n";
    183   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
    184       filemap, kTestBlockSize, GetSimpleHeader());
    185   CheckSquashfs(fs);
    186 
    187   vector<FilesystemInterface::File> files;
    188   ASSERT_TRUE(fs->GetFiles(&files));
    189   ASSERT_EQ(files.size(), 1u);
    190   EXPECT_EQ(files[0].extents[0].num_blocks(), 1u);
    191 }
    192 
    193 // Test overlap.
    194 TEST_F(SquashfsFilesystemTest, OverlapingFiles1Test) {
    195   string filemap = R"(file1 0 6000
    196                       file2 5000 5000)";
    197   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
    198       filemap, kTestBlockSize * 3, GetSimpleHeader());
    199   CheckSquashfs(fs);
    200 
    201   vector<FilesystemInterface::File> files;
    202   ASSERT_TRUE(fs->GetFiles(&files));
    203   ASSERT_EQ(files.size(), 2u);
    204   EXPECT_EQ(files[0].extents[0].num_blocks(), 1u);
    205   EXPECT_EQ(files[1].extents[0].num_blocks(), 2u);
    206 }
    207 
    208 // Test overlap, first inside second.
    209 TEST_F(SquashfsFilesystemTest, OverlapingFiles2Test) {
    210   string filemap = R"(file1 0 4000
    211                       file2 0 6000)";
    212   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
    213       filemap, kTestBlockSize * 2, GetSimpleHeader());
    214   CheckSquashfs(fs);
    215 
    216   vector<FilesystemInterface::File> files;
    217   ASSERT_TRUE(fs->GetFiles(&files));
    218   ASSERT_EQ(files.size(), 1u);
    219   EXPECT_EQ(files[0].name, "file2");
    220   EXPECT_EQ(files[0].extents[0].num_blocks(), 2u);
    221 }
    222 
    223 // Test overlap, second inside first.
    224 TEST_F(SquashfsFilesystemTest, OverlapingFiles3Test) {
    225   string filemap = R"(file1 0 8000
    226                       file2 100 100)";
    227   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
    228       filemap, kTestBlockSize * 2, GetSimpleHeader());
    229   CheckSquashfs(fs);
    230 
    231   vector<FilesystemInterface::File> files;
    232   ASSERT_TRUE(fs->GetFiles(&files));
    233   ASSERT_EQ(files.size(), 1u);
    234   EXPECT_EQ(files[0].name, "file1");
    235   EXPECT_EQ(files[0].extents[0].num_blocks(), 2u);
    236 }
    237 
    238 // Fail a line with only one argument.
    239 TEST_F(SquashfsFilesystemTest, FailOnlyFileNameTest) {
    240   string filemap = "dir1/file1\n";
    241   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
    242       filemap, kTestBlockSize, GetSimpleHeader());
    243   EXPECT_FALSE(fs);
    244 }
    245 
    246 // Fail a line with space separated filen name
    247 TEST_F(SquashfsFilesystemTest, FailSpaceInFileNameTest) {
    248   string filemap = "dir1 file1 0 10\n";
    249   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
    250       filemap, kTestBlockSize, GetSimpleHeader());
    251   EXPECT_FALSE(fs);
    252 }
    253 
    254 // Fail empty line
    255 TEST_F(SquashfsFilesystemTest, FailEmptyLineTest) {
    256   // The second file's size is zero.
    257   string filemap = R"(
    258   /t
    259                       dir1/file3 4096 100)";
    260   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
    261       filemap, kTestBlockSize * 2, GetSimpleHeader());
    262   EXPECT_FALSE(fs);
    263 }
    264 
    265 // Fail on bad magic or major
    266 TEST_F(SquashfsFilesystemTest, FailBadMagicOrMajorTest) {
    267   string filemap = "dir1/file1 0 10\n";
    268   auto header = GetSimpleHeader();
    269   header.magic = 1;
    270   EXPECT_FALSE(
    271       SquashfsFilesystem::CreateFromFileMap(filemap, kTestBlockSize, header));
    272 
    273   header = GetSimpleHeader();
    274   header.major_version = 3;
    275   EXPECT_FALSE(
    276       SquashfsFilesystem::CreateFromFileMap(filemap, kTestBlockSize, header));
    277 }
    278 
    279 // Fail size with larger than block_size
    280 TEST_F(SquashfsFilesystemTest, FailLargerThanBlockSizeTest) {
    281   string filemap = "file1 0 " + std::to_string(kTestSqfsBlockSize + 1) + "\n";
    282   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
    283       filemap, kTestBlockSize, GetSimpleHeader());
    284   EXPECT_FALSE(fs);
    285 }
    286 
    287 // Test is squashfs image.
    288 TEST_F(SquashfsFilesystemTest, IsSquashfsImageTest) {
    289   // Some sample from a recent squashfs file.
    290   brillo::Blob super_block = {
    291       0x68, 0x73, 0x71, 0x73, 0x59, 0x05, 0x00, 0x00, 0x09, 0x3a, 0x89, 0x58,
    292       0x00, 0x00, 0x02, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x00,
    293       0xc0, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x89, 0x18, 0xf7, 0x7c,
    294       0x00, 0x00, 0x00, 0x00, 0x2e, 0x33, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00,
    295       0x3a, 0x30, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00, 0x16, 0x33, 0xcd, 0x16,
    296       0x00, 0x00, 0x00, 0x00, 0x07, 0x62, 0xcc, 0x16, 0x00, 0x00, 0x00, 0x00,
    297       0x77, 0xe6, 0xcc, 0x16, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x25, 0xcd, 0x16,
    298       0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00};
    299 
    300   EXPECT_TRUE(SquashfsFilesystem::IsSquashfsImage(super_block));
    301 
    302   // Bad magic
    303   auto bad_super_block = super_block;
    304   bad_super_block[1] = 0x02;
    305   EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
    306 
    307   // Bad major
    308   bad_super_block = super_block;
    309   bad_super_block[28] = 0x03;
    310   EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
    311 
    312   // Small size;
    313   bad_super_block = super_block;
    314   bad_super_block.resize(10);
    315   EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
    316 }
    317 
    318 }  // namespace chromeos_update_engine
    319