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).
ExpectBlocksInRange(const vector<Extent> & extents,uint64_t 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
GetSimpleHeader()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:
CheckSquashfs(const unique_ptr<SquashfsFilesystem> & fs)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__
TEST_F(SquashfsFilesystemTest,EmptyFilesystemTest)113 TEST_F(SquashfsFilesystemTest, EmptyFilesystemTest) {
114 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile(
115 GetBuildArtifactsPath("gen/disk_sqfs_empty.img"), true, false);
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
TEST_F(SquashfsFilesystemTest,DefaultFilesystemTest)134 TEST_F(SquashfsFilesystemTest, DefaultFilesystemTest) {
135 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile(
136 GetBuildArtifactsPath("gen/disk_sqfs_default.img"), true, false);
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
TEST_F(SquashfsFilesystemTest,UpdateEngineConfigTest)152 TEST_F(SquashfsFilesystemTest, UpdateEngineConfigTest) {
153 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile(
154 GetBuildArtifactsPath("gen/disk_sqfs_unittest.img"), true, true);
155 CheckSquashfs(fs);
156
157 brillo::KeyValueStore kvs;
158 EXPECT_TRUE(fs->LoadSettings(&kvs));
159 string minor_version;
160 EXPECT_TRUE(kvs.GetString("PAYLOAD_MINOR_VERSION", &minor_version));
161 EXPECT_EQ(minor_version, "1234");
162 }
163 #endif // __CHROMEOS__
164
TEST_F(SquashfsFilesystemTest,SimpleFileMapTest)165 TEST_F(SquashfsFilesystemTest, SimpleFileMapTest) {
166 string filemap = R"(dir1/file1 96 4000
167 dir1/file2 4096 100)";
168 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
169 filemap, kTestBlockSize * 2, GetSimpleHeader());
170 CheckSquashfs(fs);
171
172 vector<FilesystemInterface::File> files;
173 ASSERT_TRUE(fs->GetFiles(&files));
174 EXPECT_EQ(files.size(), 2u);
175 }
176
TEST_F(SquashfsFilesystemTest,FileMapZeroSizeFileTest)177 TEST_F(SquashfsFilesystemTest, FileMapZeroSizeFileTest) {
178 // The second file's size is zero.
179 string filemap = R"(dir1/file1 96 4000
180 dir1/file2 4096
181 dir1/file3 4096 100)";
182 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
183 filemap, kTestBlockSize * 2, GetSimpleHeader());
184 CheckSquashfs(fs);
185
186 vector<FilesystemInterface::File> files;
187 ASSERT_TRUE(fs->GetFiles(&files));
188 // The second and third files are removed. The file with size zero is removed.
189 EXPECT_EQ(files.size(), 2u);
190 }
191
192 // Testing the compressed bit.
TEST_F(SquashfsFilesystemTest,CompressedBitTest)193 TEST_F(SquashfsFilesystemTest, CompressedBitTest) {
194 string filemap = "dir1/file1 0 " + std::to_string(4000 | (1 << 24)) + "\n";
195 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
196 filemap, kTestBlockSize, GetSimpleHeader());
197 CheckSquashfs(fs);
198
199 vector<FilesystemInterface::File> files;
200 ASSERT_TRUE(fs->GetFiles(&files));
201 ASSERT_EQ(files.size(), 1u);
202 EXPECT_EQ(files[0].extents[0].num_blocks(), 1u);
203 }
204
205 // Test overlap.
TEST_F(SquashfsFilesystemTest,OverlapingFiles1Test)206 TEST_F(SquashfsFilesystemTest, OverlapingFiles1Test) {
207 string filemap = R"(file1 0 6000
208 file2 5000 5000)";
209 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
210 filemap, kTestBlockSize * 3, GetSimpleHeader());
211 CheckSquashfs(fs);
212
213 vector<FilesystemInterface::File> files;
214 ASSERT_TRUE(fs->GetFiles(&files));
215 ASSERT_EQ(files.size(), 2u);
216 EXPECT_EQ(files[0].extents[0].num_blocks(), 1u);
217 EXPECT_EQ(files[1].extents[0].num_blocks(), 2u);
218 }
219
220 // Test overlap, first inside second.
TEST_F(SquashfsFilesystemTest,OverlapingFiles2Test)221 TEST_F(SquashfsFilesystemTest, OverlapingFiles2Test) {
222 string filemap = R"(file1 0 4000
223 file2 0 6000)";
224 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
225 filemap, kTestBlockSize * 2, GetSimpleHeader());
226 CheckSquashfs(fs);
227
228 vector<FilesystemInterface::File> files;
229 ASSERT_TRUE(fs->GetFiles(&files));
230 ASSERT_EQ(files.size(), 1u);
231 EXPECT_EQ(files[0].name, "file2");
232 EXPECT_EQ(files[0].extents[0].num_blocks(), 2u);
233 }
234
235 // Test overlap, second inside first.
TEST_F(SquashfsFilesystemTest,OverlapingFiles3Test)236 TEST_F(SquashfsFilesystemTest, OverlapingFiles3Test) {
237 string filemap = R"(file1 0 8000
238 file2 100 100)";
239 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
240 filemap, kTestBlockSize * 2, GetSimpleHeader());
241 CheckSquashfs(fs);
242
243 vector<FilesystemInterface::File> files;
244 ASSERT_TRUE(fs->GetFiles(&files));
245 ASSERT_EQ(files.size(), 1u);
246 EXPECT_EQ(files[0].name, "file1");
247 EXPECT_EQ(files[0].extents[0].num_blocks(), 2u);
248 }
249
250 // Fail a line with only one argument.
TEST_F(SquashfsFilesystemTest,FailOnlyFileNameTest)251 TEST_F(SquashfsFilesystemTest, FailOnlyFileNameTest) {
252 string filemap = "dir1/file1\n";
253 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
254 filemap, kTestBlockSize, GetSimpleHeader());
255 EXPECT_FALSE(fs);
256 }
257
258 // Fail a line with space separated filen name
TEST_F(SquashfsFilesystemTest,FailSpaceInFileNameTest)259 TEST_F(SquashfsFilesystemTest, FailSpaceInFileNameTest) {
260 string filemap = "dir1 file1 0 10\n";
261 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
262 filemap, kTestBlockSize, GetSimpleHeader());
263 EXPECT_FALSE(fs);
264 }
265
266 // Fail empty line
TEST_F(SquashfsFilesystemTest,FailEmptyLineTest)267 TEST_F(SquashfsFilesystemTest, FailEmptyLineTest) {
268 // The second file's size is zero.
269 string filemap = R"(
270 /t
271 dir1/file3 4096 100)";
272 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
273 filemap, kTestBlockSize * 2, GetSimpleHeader());
274 EXPECT_FALSE(fs);
275 }
276
277 // Fail on bad magic or major
TEST_F(SquashfsFilesystemTest,FailBadMagicOrMajorTest)278 TEST_F(SquashfsFilesystemTest, FailBadMagicOrMajorTest) {
279 string filemap = "dir1/file1 0 10\n";
280 auto header = GetSimpleHeader();
281 header.magic = 1;
282 EXPECT_FALSE(
283 SquashfsFilesystem::CreateFromFileMap(filemap, kTestBlockSize, header));
284
285 header = GetSimpleHeader();
286 header.major_version = 3;
287 EXPECT_FALSE(
288 SquashfsFilesystem::CreateFromFileMap(filemap, kTestBlockSize, header));
289 }
290
291 // Fail size with larger than block_size
TEST_F(SquashfsFilesystemTest,FailLargerThanBlockSizeTest)292 TEST_F(SquashfsFilesystemTest, FailLargerThanBlockSizeTest) {
293 string filemap = "file1 0 " + std::to_string(kTestSqfsBlockSize + 1) + "\n";
294 unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
295 filemap, kTestBlockSize, GetSimpleHeader());
296 EXPECT_FALSE(fs);
297 }
298
299 // Test is squashfs image.
TEST_F(SquashfsFilesystemTest,IsSquashfsImageTest)300 TEST_F(SquashfsFilesystemTest, IsSquashfsImageTest) {
301 // Some sample from a recent squashfs file.
302 brillo::Blob super_block = {
303 0x68, 0x73, 0x71, 0x73, 0x59, 0x05, 0x00, 0x00, 0x09, 0x3a, 0x89, 0x58,
304 0x00, 0x00, 0x02, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x00,
305 0xc0, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x89, 0x18, 0xf7, 0x7c,
306 0x00, 0x00, 0x00, 0x00, 0x2e, 0x33, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00,
307 0x3a, 0x30, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00, 0x16, 0x33, 0xcd, 0x16,
308 0x00, 0x00, 0x00, 0x00, 0x07, 0x62, 0xcc, 0x16, 0x00, 0x00, 0x00, 0x00,
309 0x77, 0xe6, 0xcc, 0x16, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x25, 0xcd, 0x16,
310 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00};
311
312 EXPECT_TRUE(SquashfsFilesystem::IsSquashfsImage(super_block));
313
314 // Bad magic
315 auto bad_super_block = super_block;
316 bad_super_block[1] = 0x02;
317 EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
318
319 // Bad major
320 bad_super_block = super_block;
321 bad_super_block[28] = 0x03;
322 EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
323
324 // Small size;
325 bad_super_block = super_block;
326 bad_super_block.resize(10);
327 EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
328 }
329
330 } // namespace chromeos_update_engine
331