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