blob: 22b431d3fbca2ce5ba130e1909418a824a4cf60d [file] [log] [blame]
Amin Hassanid7da8f42017-08-23 14:29:40 -07001//
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 <fcntl.h>
20
21#include <algorithm>
22#include <string>
Amin Hassani3cd4df12017-08-25 11:21:53 -070023#include <utility>
Amin Hassanid7da8f42017-08-23 14:29:40 -070024
25#include <base/files/file_util.h>
Amin Hassani77c25fc2019-01-29 10:24:19 -080026#include <base/files/scoped_temp_dir.h>
Amin Hassanid7da8f42017-08-23 14:29:40 -070027#include <base/logging.h>
28#include <base/strings/string_number_conversions.h>
29#include <base/strings/string_split.h>
30#include <brillo/streams/file_stream.h>
31
32#include "update_engine/common/subprocess.h"
33#include "update_engine/common/utils.h"
Amin Hassani3cd4df12017-08-25 11:21:53 -070034#include "update_engine/payload_generator/deflate_utils.h"
Amin Hassanid7da8f42017-08-23 14:29:40 -070035#include "update_engine/payload_generator/delta_diff_generator.h"
36#include "update_engine/payload_generator/extent_ranges.h"
Amin Hassanid7da8f42017-08-23 14:29:40 -070037#include "update_engine/update_metadata.pb.h"
38
Amin Hassani77c25fc2019-01-29 10:24:19 -080039using base::FilePath;
40using base::ScopedTempDir;
Amin Hassanid7da8f42017-08-23 14:29:40 -070041using std::string;
42using std::unique_ptr;
43using std::vector;
44
45namespace chromeos_update_engine {
46
47namespace {
48
Amin Hassanid7da8f42017-08-23 14:29:40 -070049// The size of the squashfs super block.
50constexpr size_t kSquashfsSuperBlockSize = 96;
51constexpr uint64_t kSquashfsCompressedBit = 1 << 24;
Amin Hassani3cd4df12017-08-25 11:21:53 -070052constexpr uint32_t kSquashfsZlibCompression = 1;
Amin Hassanid7da8f42017-08-23 14:29:40 -070053
54bool ReadSquashfsHeader(const brillo::Blob blob,
55 SquashfsFilesystem::SquashfsHeader* header) {
56 if (blob.size() < kSquashfsSuperBlockSize) {
57 return false;
58 }
59
60 memcpy(&header->magic, blob.data(), 4);
61 memcpy(&header->block_size, blob.data() + 12, 4);
62 memcpy(&header->compression_type, blob.data() + 20, 2);
63 memcpy(&header->major_version, blob.data() + 28, 2);
64 return true;
65}
66
67bool CheckHeader(const SquashfsFilesystem::SquashfsHeader& header) {
68 return header.magic == 0x73717368 && header.major_version == 4;
69}
70
71bool GetFileMapContent(const string& sqfs_path, string* map) {
Amin Hassanied03b442020-10-26 17:21:29 -070072 ScopedTempFile map_file("squashfs_file_map.XXXXXX");
Amin Hassanid7da8f42017-08-23 14:29:40 -070073 // Run unsquashfs to get the system file map.
74 // unsquashfs -m <map-file> <squashfs-file>
Amin Hassanied03b442020-10-26 17:21:29 -070075 vector<string> cmd = {"unsquashfs", "-m", map_file.path(), sqfs_path};
Colin Crossd76a8ac2021-12-21 13:08:20 -080076 string stdout_str, stderr_str;
Amin Hassanid7da8f42017-08-23 14:29:40 -070077 int exit_code;
Colin Crossd76a8ac2021-12-21 13:08:20 -080078 if (!Subprocess::SynchronousExec(cmd, &exit_code, &stdout_str, &stderr_str) ||
Amin Hassanid7da8f42017-08-23 14:29:40 -070079 exit_code != 0) {
Amin Hassani3a4caa12019-11-06 11:12:28 -080080 LOG(ERROR) << "Failed to run `unsquashfs -m` with stdout content: "
Colin Crossd76a8ac2021-12-21 13:08:20 -080081 << stdout_str << " and stderr content: " << stderr_str;
Amin Hassanid7da8f42017-08-23 14:29:40 -070082 return false;
83 }
Amin Hassanied03b442020-10-26 17:21:29 -070084 TEST_AND_RETURN_FALSE(utils::ReadFile(map_file.path(), map));
Amin Hassanid7da8f42017-08-23 14:29:40 -070085 return true;
86}
87
88} // namespace
89
90bool SquashfsFilesystem::Init(const string& map,
Amin Hassani3cd4df12017-08-25 11:21:53 -070091 const string& sqfs_path,
Amin Hassanid7da8f42017-08-23 14:29:40 -070092 size_t size,
Amin Hassani3cd4df12017-08-25 11:21:53 -070093 const SquashfsHeader& header,
94 bool extract_deflates) {
Amin Hassanid7da8f42017-08-23 14:29:40 -070095 size_ = size;
Amin Hassani3cd4df12017-08-25 11:21:53 -070096
97 bool is_zlib = header.compression_type == kSquashfsZlibCompression;
98 if (!is_zlib) {
99 LOG(WARNING) << "Filesystem is not Gzipped. Not filling deflates!";
100 }
101 vector<puffin::ByteExtent> zlib_blks;
102
Amin Hassanid7da8f42017-08-23 14:29:40 -0700103 // Reading files map. For the format of the file map look at the comments for
104 // |CreateFromFileMap()|.
105 auto lines = base::SplitStringPiece(map,
106 "\n",
107 base::WhitespaceHandling::KEEP_WHITESPACE,
108 base::SplitResult::SPLIT_WANT_NONEMPTY);
109 for (const auto& line : lines) {
110 auto splits =
111 base::SplitStringPiece(line,
112 " \t",
113 base::WhitespaceHandling::TRIM_WHITESPACE,
114 base::SplitResult::SPLIT_WANT_NONEMPTY);
115 // Only filename is invalid.
116 TEST_AND_RETURN_FALSE(splits.size() > 1);
117 uint64_t start;
118 TEST_AND_RETURN_FALSE(base::StringToUint64(splits[1], &start));
119 uint64_t cur_offset = start;
Amin Hassani1a200c12020-02-26 14:47:23 -0800120 bool is_compressed = false;
Amin Hassanid7da8f42017-08-23 14:29:40 -0700121 for (size_t i = 2; i < splits.size(); ++i) {
122 uint64_t blk_size;
123 TEST_AND_RETURN_FALSE(base::StringToUint64(splits[i], &blk_size));
124 // TODO(ahassani): For puffin push it into a proper list if uncompressed.
125 auto new_blk_size = blk_size & ~kSquashfsCompressedBit;
126 TEST_AND_RETURN_FALSE(new_blk_size <= header.block_size);
Amin Hassani3cd4df12017-08-25 11:21:53 -0700127 if (new_blk_size > 0 && !(blk_size & kSquashfsCompressedBit)) {
Amin Hassani1a200c12020-02-26 14:47:23 -0800128 // It is a compressed block.
Amin Hassani3cd4df12017-08-25 11:21:53 -0700129 if (is_zlib && extract_deflates) {
130 zlib_blks.emplace_back(cur_offset, new_blk_size);
131 }
Amin Hassani1a200c12020-02-26 14:47:23 -0800132 is_compressed = true;
Amin Hassani3cd4df12017-08-25 11:21:53 -0700133 }
Amin Hassanid7da8f42017-08-23 14:29:40 -0700134 cur_offset += new_blk_size;
135 }
136
137 // If size is zero do not add the file.
138 if (cur_offset - start > 0) {
139 File file;
140 file.name = splits[0].as_string();
141 file.extents = {ExtentForBytes(kBlockSize, start, cur_offset - start)};
Amin Hassani1a200c12020-02-26 14:47:23 -0800142 file.is_compressed = is_compressed;
Amin Hassanid7da8f42017-08-23 14:29:40 -0700143 files_.emplace_back(file);
144 }
145 }
146
147 // Sort all files by their offset in the squashfs.
148 std::sort(files_.begin(), files_.end(), [](const File& a, const File& b) {
149 return a.extents[0].start_block() < b.extents[0].start_block();
150 });
151 // If there is any overlap between two consecutive extents, remove them. Here
152 // we are assuming all files have exactly one extent. If this assumption
153 // changes then this implementation needs to change too.
Jae Hoon Kim3f894a82020-05-20 19:26:19 -0700154 for (auto first = files_.begin(),
155 second = first + (first == files_.end() ? 0 : 1);
Amin Hassanid7da8f42017-08-23 14:29:40 -0700156 first != files_.end() && second != files_.end();
157 second = first + 1) {
158 auto first_begin = first->extents[0].start_block();
159 auto first_end = first_begin + first->extents[0].num_blocks();
160 auto second_begin = second->extents[0].start_block();
161 auto second_end = second_begin + second->extents[0].num_blocks();
162 // Remove the first file if the size is zero.
163 if (first_end == first_begin) {
164 first = files_.erase(first);
165 } else if (first_end > second_begin) { // We found a collision.
166 if (second_end <= first_end) {
167 // Second file is inside the first file, remove the second file.
168 second = files_.erase(second);
169 } else if (first_begin == second_begin) {
170 // First file is inside the second file, remove the first file.
171 first = files_.erase(first);
172 } else {
173 // Remove overlapping extents from the first file.
174 first->extents[0].set_num_blocks(second_begin - first_begin);
175 ++first;
176 }
177 } else {
178 ++first;
179 }
180 }
181
182 // Find all the metadata including superblock and add them to the list of
183 // files.
184 ExtentRanges file_extents;
185 for (const auto& file : files_) {
186 file_extents.AddExtents(file.extents);
187 }
Sen Jiang0a582fb2018-06-26 19:27:21 -0700188 vector<Extent> full = {ExtentForBytes(kBlockSize, 0, size_)};
Amin Hassanid7da8f42017-08-23 14:29:40 -0700189 auto metadata_extents = FilterExtentRanges(full, file_extents);
190 // For now there should be at most two extents. One for superblock and one for
191 // metadata at the end. Just create appropriate files with <metadata-i> name.
192 // We can add all these extents as one metadata too, but that violates the
193 // contiguous write optimization.
194 for (size_t i = 0; i < metadata_extents.size(); i++) {
195 File file;
196 file.name = "<metadata-" + std::to_string(i) + ">";
197 file.extents = {metadata_extents[i]};
198 files_.emplace_back(file);
199 }
200
201 // Do one last sort before returning.
202 std::sort(files_.begin(), files_.end(), [](const File& a, const File& b) {
203 return a.extents[0].start_block() < b.extents[0].start_block();
204 });
Amin Hassani3cd4df12017-08-25 11:21:53 -0700205
206 if (is_zlib && extract_deflates) {
207 // If it is infact gzipped, then the sqfs_path should be valid to read its
208 // content.
209 TEST_AND_RETURN_FALSE(!sqfs_path.empty());
210 if (zlib_blks.empty()) {
211 return true;
212 }
213
214 // Sort zlib blocks.
215 std::sort(zlib_blks.begin(),
216 zlib_blks.end(),
217 [](const puffin::ByteExtent& a, const puffin::ByteExtent& b) {
218 return a.offset < b.offset;
219 });
220
Amin Hassani5d185052019-04-23 07:28:30 -0700221 // Sometimes a squashfs can have a two files that are hard linked. In this
222 // case both files will have the same starting offset in the image and hence
223 // the same zlib blocks. So we need to remove these duplicates to eliminate
224 // further potential probems. As a matter of fact the next statement will
225 // fail if there are duplicates (there will be overlap between two blocks).
226 auto last = std::unique(zlib_blks.begin(), zlib_blks.end());
227 zlib_blks.erase(last, zlib_blks.end());
228
Tianjiee283ce42020-07-29 11:37:51 -0700229 // Make sure zlib blocks are not overlapping.
Amin Hassani3cd4df12017-08-25 11:21:53 -0700230 auto result = std::adjacent_find(
231 zlib_blks.begin(),
232 zlib_blks.end(),
233 [](const puffin::ByteExtent& a, const puffin::ByteExtent& b) {
234 return (a.offset + a.length) > b.offset;
235 });
236 TEST_AND_RETURN_FALSE(result == zlib_blks.end());
237
238 vector<puffin::BitExtent> deflates;
239 TEST_AND_RETURN_FALSE(
240 puffin::LocateDeflatesInZlibBlocks(sqfs_path, zlib_blks, &deflates));
241
242 // Add deflates for each file.
243 for (auto& file : files_) {
244 file.deflates = deflate_utils::FindDeflates(file.extents, deflates);
245 }
246 }
Amin Hassanid7da8f42017-08-23 14:29:40 -0700247 return true;
248}
249
250unique_ptr<SquashfsFilesystem> SquashfsFilesystem::CreateFromFile(
Kelvin Zhanged9b2082023-05-25 13:58:19 -0700251 const string& sqfs_path, bool extract_deflates) {
Amin Hassanid7da8f42017-08-23 14:29:40 -0700252 if (sqfs_path.empty())
253 return nullptr;
254
255 brillo::StreamPtr sqfs_file =
Amin Hassani77c25fc2019-01-29 10:24:19 -0800256 brillo::FileStream::Open(FilePath(sqfs_path),
Amin Hassanid7da8f42017-08-23 14:29:40 -0700257 brillo::Stream::AccessMode::READ,
258 brillo::FileStream::Disposition::OPEN_EXISTING,
259 nullptr);
260 if (!sqfs_file) {
261 LOG(ERROR) << "Unable to open " << sqfs_path << " for reading.";
262 return nullptr;
263 }
264
265 SquashfsHeader header;
266 brillo::Blob blob(kSquashfsSuperBlockSize);
267 if (!sqfs_file->ReadAllBlocking(blob.data(), blob.size(), nullptr)) {
268 LOG(ERROR) << "Unable to read from file: " << sqfs_path;
269 return nullptr;
270 }
271 if (!ReadSquashfsHeader(blob, &header) || !CheckHeader(header)) {
272 // This is not necessary an error.
273 return nullptr;
274 }
275
276 // Read the map file.
277 string filemap;
278 if (!GetFileMapContent(sqfs_path, &filemap)) {
279 LOG(ERROR) << "Failed to produce squashfs map file: " << sqfs_path;
280 return nullptr;
281 }
282
283 unique_ptr<SquashfsFilesystem> sqfs(new SquashfsFilesystem());
Amin Hassani3cd4df12017-08-25 11:21:53 -0700284 if (!sqfs->Init(
285 filemap, sqfs_path, sqfs_file->GetSize(), header, extract_deflates)) {
Amin Hassanid7da8f42017-08-23 14:29:40 -0700286 LOG(ERROR) << "Failed to initialized the Squashfs file system";
287 return nullptr;
288 }
Amin Hassani3cd4df12017-08-25 11:21:53 -0700289
Amin Hassanid7da8f42017-08-23 14:29:40 -0700290 return sqfs;
291}
292
293unique_ptr<SquashfsFilesystem> SquashfsFilesystem::CreateFromFileMap(
294 const string& filemap, size_t size, const SquashfsHeader& header) {
295 if (!CheckHeader(header)) {
296 LOG(ERROR) << "Invalid Squashfs super block!";
297 return nullptr;
298 }
299
300 unique_ptr<SquashfsFilesystem> sqfs(new SquashfsFilesystem());
Amin Hassani3cd4df12017-08-25 11:21:53 -0700301 if (!sqfs->Init(filemap, "", size, header, false)) {
Amin Hassanid7da8f42017-08-23 14:29:40 -0700302 LOG(ERROR) << "Failed to initialize the Squashfs file system using filemap";
303 return nullptr;
304 }
305 // TODO(ahassani): Add a function that initializes the puffin related extents.
306 return sqfs;
307}
308
309size_t SquashfsFilesystem::GetBlockSize() const {
310 return kBlockSize;
311}
312
313size_t SquashfsFilesystem::GetBlockCount() const {
314 return size_ / kBlockSize;
315}
316
317bool SquashfsFilesystem::GetFiles(vector<File>* files) const {
318 files->insert(files->end(), files_.begin(), files_.end());
319 return true;
320}
321
Amin Hassanid7da8f42017-08-23 14:29:40 -0700322bool SquashfsFilesystem::IsSquashfsImage(const brillo::Blob& blob) {
323 SquashfsHeader header;
324 return ReadSquashfsHeader(blob, &header) && CheckHeader(header);
325}
Amin Hassanid7da8f42017-08-23 14:29:40 -0700326} // namespace chromeos_update_engine