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