squashfs_filesystem_unittest.cc revision 3cd4df127c29eb90c0e203373edfbc9534c79169
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
38namespace chromeos_update_engine {
39
40using std::map;
41using std::set;
42using std::string;
43using std::unique_ptr;
44using std::vector;
45
46using test_utils::GetBuildArtifactsPath;
47
48namespace {
49
50constexpr uint64_t kTestBlockSize = 4096;
51constexpr uint64_t kTestSqfsBlockSize = 1 << 15;
52
53// Checks that all the blocks in |extents| are in the range [0, total_blocks).
54void 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
61SquashfsFilesystem::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
73class 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
111TEST_F(SquashfsFilesystemTest, EmptyFilesystemTest) {
112  unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile(
113      GetBuildArtifactsPath("gen/disk_sqfs_empty.img"), true);
114  CheckSquashfs(fs);
115
116  // Even an empty squashfs filesystem is rounded up to 4K.
117  EXPECT_EQ(4096 / kTestBlockSize, fs->GetBlockCount());
118
119  vector<FilesystemInterface::File> files;
120  ASSERT_TRUE(fs->GetFiles(&files));
121  ASSERT_EQ(files.size(), 1u);
122
123  FilesystemInterface::File file;
124  file.name = "<metadata-0>";
125  file.extents.emplace_back();
126  file.extents[0].set_start_block(0);
127  file.extents[0].set_num_blocks(1);
128  EXPECT_EQ(files[0].name, file.name);
129  EXPECT_EQ(files[0].extents, file.extents);
130}
131
132TEST_F(SquashfsFilesystemTest, DefaultFilesystemTest) {
133  unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile(
134      GetBuildArtifactsPath("gen/disk_sqfs_default.img"), true);
135  CheckSquashfs(fs);
136
137  vector<FilesystemInterface::File> files;
138  ASSERT_TRUE(fs->GetFiles(&files));
139  ASSERT_EQ(files.size(), 1u);
140
141  FilesystemInterface::File file;
142  file.name = "<fragment-0>";
143  file.extents.emplace_back();
144  file.extents[0].set_start_block(0);
145  file.extents[0].set_num_blocks(1);
146  EXPECT_EQ(files[0].name, file.name);
147  EXPECT_EQ(files[0].extents, file.extents);
148}
149
150TEST_F(SquashfsFilesystemTest, SimpleFileMapTest) {
151  string filemap = R"(dir1/file1 96 4000
152                      dir1/file2 4096 100)";
153  unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
154      filemap, kTestBlockSize * 2, GetSimpleHeader());
155  CheckSquashfs(fs);
156
157  vector<FilesystemInterface::File> files;
158  ASSERT_TRUE(fs->GetFiles(&files));
159  EXPECT_EQ(files.size(), 2u);
160}
161
162TEST_F(SquashfsFilesystemTest, FileMapZeroSizeFileTest) {
163  // The second file's size is zero.
164  string filemap = R"(dir1/file1 96 4000
165                      dir1/file2 4096
166                      dir1/file3 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  // The second and third files are removed. The file with size zero is removed.
174  EXPECT_EQ(files.size(), 2u);
175}
176
177// Testing the compressed bit.
178TEST_F(SquashfsFilesystemTest, CompressedBitTest) {
179  string filemap = "dir1/file1 0 " + std::to_string(4000 | (1 << 24)) + "\n";
180  unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
181      filemap, kTestBlockSize, GetSimpleHeader());
182  CheckSquashfs(fs);
183
184  vector<FilesystemInterface::File> files;
185  ASSERT_TRUE(fs->GetFiles(&files));
186  ASSERT_EQ(files.size(), 1u);
187  EXPECT_EQ(files[0].extents[0].num_blocks(), 1u);
188}
189
190// Test overlap.
191TEST_F(SquashfsFilesystemTest, OverlapingFiles1Test) {
192  string filemap = R"(file1 0 6000
193                      file2 5000 5000)";
194  unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
195      filemap, kTestBlockSize * 3, GetSimpleHeader());
196  CheckSquashfs(fs);
197
198  vector<FilesystemInterface::File> files;
199  ASSERT_TRUE(fs->GetFiles(&files));
200  ASSERT_EQ(files.size(), 2u);
201  EXPECT_EQ(files[0].extents[0].num_blocks(), 1u);
202  EXPECT_EQ(files[1].extents[0].num_blocks(), 2u);
203}
204
205// Test overlap, first inside second.
206TEST_F(SquashfsFilesystemTest, OverlapingFiles2Test) {
207  string filemap = R"(file1 0 4000
208                      file2 0 6000)";
209  unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
210      filemap, kTestBlockSize * 2, GetSimpleHeader());
211  CheckSquashfs(fs);
212
213  vector<FilesystemInterface::File> files;
214  ASSERT_TRUE(fs->GetFiles(&files));
215  ASSERT_EQ(files.size(), 1u);
216  EXPECT_EQ(files[0].name, "file2");
217  EXPECT_EQ(files[0].extents[0].num_blocks(), 2u);
218}
219
220// Test overlap, second inside first.
221TEST_F(SquashfsFilesystemTest, OverlapingFiles3Test) {
222  string filemap = R"(file1 0 8000
223                      file2 100 100)";
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, "file1");
232  EXPECT_EQ(files[0].extents[0].num_blocks(), 2u);
233}
234
235// Fail a line with only one argument.
236TEST_F(SquashfsFilesystemTest, FailOnlyFileNameTest) {
237  string filemap = "dir1/file1\n";
238  unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
239      filemap, kTestBlockSize, GetSimpleHeader());
240  EXPECT_FALSE(fs);
241}
242
243// Fail a line with space separated filen name
244TEST_F(SquashfsFilesystemTest, FailSpaceInFileNameTest) {
245  string filemap = "dir1 file1 0 10\n";
246  unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
247      filemap, kTestBlockSize, GetSimpleHeader());
248  EXPECT_FALSE(fs);
249}
250
251// Fail empty line
252TEST_F(SquashfsFilesystemTest, FailEmptyLineTest) {
253  // The second file's size is zero.
254  string filemap = R"(
255  /t
256                      dir1/file3 4096 100)";
257  unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
258      filemap, kTestBlockSize * 2, GetSimpleHeader());
259  EXPECT_FALSE(fs);
260}
261
262// Fail on bad magic or major
263TEST_F(SquashfsFilesystemTest, FailBadMagicOrMajorTest) {
264  string filemap = "dir1/file1 0 10\n";
265  auto header = GetSimpleHeader();
266  header.magic = 1;
267  EXPECT_FALSE(
268      SquashfsFilesystem::CreateFromFileMap(filemap, kTestBlockSize, header));
269
270  header = GetSimpleHeader();
271  header.major_version = 3;
272  EXPECT_FALSE(
273      SquashfsFilesystem::CreateFromFileMap(filemap, kTestBlockSize, header));
274}
275
276// Fail size with larger than block_size
277TEST_F(SquashfsFilesystemTest, FailLargerThanBlockSizeTest) {
278  string filemap = "file1 0 " + std::to_string(kTestSqfsBlockSize + 1) + "\n";
279  unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFileMap(
280      filemap, kTestBlockSize, GetSimpleHeader());
281  EXPECT_FALSE(fs);
282}
283
284// Test is squashfs image.
285TEST_F(SquashfsFilesystemTest, IsSquashfsImageTest) {
286  // Some sample from a recent squashfs file.
287  brillo::Blob super_block = {
288      0x68, 0x73, 0x71, 0x73, 0x59, 0x05, 0x00, 0x00, 0x09, 0x3a, 0x89, 0x58,
289      0x00, 0x00, 0x02, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x00,
290      0xc0, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x89, 0x18, 0xf7, 0x7c,
291      0x00, 0x00, 0x00, 0x00, 0x2e, 0x33, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00,
292      0x3a, 0x30, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00, 0x16, 0x33, 0xcd, 0x16,
293      0x00, 0x00, 0x00, 0x00, 0x07, 0x62, 0xcc, 0x16, 0x00, 0x00, 0x00, 0x00,
294      0x77, 0xe6, 0xcc, 0x16, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x25, 0xcd, 0x16,
295      0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0xcd, 0x16, 0x00, 0x00, 0x00, 0x00};
296
297  EXPECT_TRUE(SquashfsFilesystem::IsSquashfsImage(super_block));
298
299  // Bad magic
300  auto bad_super_block = super_block;
301  bad_super_block[1] = 0x02;
302  EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
303
304  // Bad major
305  bad_super_block = super_block;
306  bad_super_block[28] = 0x03;
307  EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
308
309  // Small size;
310  bad_super_block = super_block;
311  bad_super_block.resize(10);
312  EXPECT_FALSE(SquashfsFilesystem::IsSquashfsImage(bad_super_block));
313}
314
315}  // namespace chromeos_update_engine
316