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