blob: c98ad1280cfdfc52b52e1489927c59a5cc856fb0 [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>
26#include <base/logging.h>
27#include <base/strings/string_number_conversions.h>
28#include <base/strings/string_split.h>
29#include <brillo/streams/file_stream.h>
30
31#include "update_engine/common/subprocess.h"
32#include "update_engine/common/utils.h"
Amin Hassani3cd4df12017-08-25 11:21:53 -070033#include "update_engine/payload_generator/deflate_utils.h"
Amin Hassanid7da8f42017-08-23 14:29:40 -070034#include "update_engine/payload_generator/delta_diff_generator.h"
35#include "update_engine/payload_generator/extent_ranges.h"
36#include "update_engine/payload_generator/extent_utils.h"
37#include "update_engine/update_metadata.pb.h"
38
39using std::string;
40using std::unique_ptr;
41using std::vector;
42
43namespace chromeos_update_engine {
44
45namespace {
46
47Extent ExtentForBytes(uint64_t block_size,
48 uint64_t start_bytes,
49 uint64_t size_bytes) {
50 uint64_t start_block = start_bytes / block_size;
51 uint64_t end_block = (start_bytes + size_bytes + block_size - 1) / block_size;
52 return ExtentForRange(start_block, end_block - start_block);
53}
54
55// The size of the squashfs super block.
56constexpr size_t kSquashfsSuperBlockSize = 96;
57constexpr uint64_t kSquashfsCompressedBit = 1 << 24;
Amin Hassani3cd4df12017-08-25 11:21:53 -070058constexpr uint32_t kSquashfsZlibCompression = 1;
Amin Hassanid7da8f42017-08-23 14:29:40 -070059
60bool ReadSquashfsHeader(const brillo::Blob blob,
61 SquashfsFilesystem::SquashfsHeader* header) {
62 if (blob.size() < kSquashfsSuperBlockSize) {
63 return false;
64 }
65
66 memcpy(&header->magic, blob.data(), 4);
67 memcpy(&header->block_size, blob.data() + 12, 4);
68 memcpy(&header->compression_type, blob.data() + 20, 2);
69 memcpy(&header->major_version, blob.data() + 28, 2);
70 return true;
71}
72
73bool CheckHeader(const SquashfsFilesystem::SquashfsHeader& header) {
74 return header.magic == 0x73717368 && header.major_version == 4;
75}
76
77bool GetFileMapContent(const string& sqfs_path, string* map) {
78 // Create a tmp file
79 string map_file;
80 TEST_AND_RETURN_FALSE(
81 utils::MakeTempFile("squashfs_file_map.XXXXXX", &map_file, nullptr));
82 ScopedPathUnlinker map_unlinker(map_file);
83
84 // Run unsquashfs to get the system file map.
85 // unsquashfs -m <map-file> <squashfs-file>
86 vector<string> cmd = {"unsquashfs", "-m", map_file, sqfs_path};
87 string stdout;
88 int exit_code;
89 if (!Subprocess::SynchronousExec(cmd, &exit_code, &stdout) ||
90 exit_code != 0) {
91 LOG(ERROR) << "Failed to run unsquashfs -m. The stdout content was: "
92 << stdout;
93 return false;
94 }
95 TEST_AND_RETURN_FALSE(utils::ReadFile(map_file, map));
96 return true;
97}
98
99} // namespace
100
101bool SquashfsFilesystem::Init(const string& map,
Amin Hassani3cd4df12017-08-25 11:21:53 -0700102 const string& sqfs_path,
Amin Hassanid7da8f42017-08-23 14:29:40 -0700103 size_t size,
Amin Hassani3cd4df12017-08-25 11:21:53 -0700104 const SquashfsHeader& header,
105 bool extract_deflates) {
Amin Hassanid7da8f42017-08-23 14:29:40 -0700106 size_ = size;
Amin Hassani3cd4df12017-08-25 11:21:53 -0700107
108 bool is_zlib = header.compression_type == kSquashfsZlibCompression;
109 if (!is_zlib) {
110 LOG(WARNING) << "Filesystem is not Gzipped. Not filling deflates!";
111 }
112 vector<puffin::ByteExtent> zlib_blks;
113
Amin Hassanid7da8f42017-08-23 14:29:40 -0700114 // Reading files map. For the format of the file map look at the comments for
115 // |CreateFromFileMap()|.
116 auto lines = base::SplitStringPiece(map,
117 "\n",
118 base::WhitespaceHandling::KEEP_WHITESPACE,
119 base::SplitResult::SPLIT_WANT_NONEMPTY);
120 for (const auto& line : lines) {
121 auto splits =
122 base::SplitStringPiece(line,
123 " \t",
124 base::WhitespaceHandling::TRIM_WHITESPACE,
125 base::SplitResult::SPLIT_WANT_NONEMPTY);
126 // Only filename is invalid.
127 TEST_AND_RETURN_FALSE(splits.size() > 1);
128 uint64_t start;
129 TEST_AND_RETURN_FALSE(base::StringToUint64(splits[1], &start));
130 uint64_t cur_offset = start;
131 for (size_t i = 2; i < splits.size(); ++i) {
132 uint64_t blk_size;
133 TEST_AND_RETURN_FALSE(base::StringToUint64(splits[i], &blk_size));
134 // TODO(ahassani): For puffin push it into a proper list if uncompressed.
135 auto new_blk_size = blk_size & ~kSquashfsCompressedBit;
136 TEST_AND_RETURN_FALSE(new_blk_size <= header.block_size);
Amin Hassani3cd4df12017-08-25 11:21:53 -0700137 if (new_blk_size > 0 && !(blk_size & kSquashfsCompressedBit)) {
138 // Compressed block
139 if (is_zlib && extract_deflates) {
140 zlib_blks.emplace_back(cur_offset, new_blk_size);
141 }
142 }
Amin Hassanid7da8f42017-08-23 14:29:40 -0700143 cur_offset += new_blk_size;
144 }
145
146 // If size is zero do not add the file.
147 if (cur_offset - start > 0) {
148 File file;
149 file.name = splits[0].as_string();
150 file.extents = {ExtentForBytes(kBlockSize, start, cur_offset - start)};
151 files_.emplace_back(file);
152 }
153 }
154
155 // Sort all files by their offset in the squashfs.
156 std::sort(files_.begin(), files_.end(), [](const File& a, const File& b) {
157 return a.extents[0].start_block() < b.extents[0].start_block();
158 });
159 // If there is any overlap between two consecutive extents, remove them. Here
160 // we are assuming all files have exactly one extent. If this assumption
161 // changes then this implementation needs to change too.
162 for (auto first = files_.begin(), second = first + 1;
163 first != files_.end() && second != files_.end();
164 second = first + 1) {
165 auto first_begin = first->extents[0].start_block();
166 auto first_end = first_begin + first->extents[0].num_blocks();
167 auto second_begin = second->extents[0].start_block();
168 auto second_end = second_begin + second->extents[0].num_blocks();
169 // Remove the first file if the size is zero.
170 if (first_end == first_begin) {
171 first = files_.erase(first);
172 } else if (first_end > second_begin) { // We found a collision.
173 if (second_end <= first_end) {
174 // Second file is inside the first file, remove the second file.
175 second = files_.erase(second);
176 } else if (first_begin == second_begin) {
177 // First file is inside the second file, remove the first file.
178 first = files_.erase(first);
179 } else {
180 // Remove overlapping extents from the first file.
181 first->extents[0].set_num_blocks(second_begin - first_begin);
182 ++first;
183 }
184 } else {
185 ++first;
186 }
187 }
188
189 // Find all the metadata including superblock and add them to the list of
190 // files.
191 ExtentRanges file_extents;
192 for (const auto& file : files_) {
193 file_extents.AddExtents(file.extents);
194 }
195 vector<Extent> full = {
196 ExtentForRange(0, (size_ + kBlockSize - 1) / kBlockSize)};
197 auto metadata_extents = FilterExtentRanges(full, file_extents);
198 // For now there should be at most two extents. One for superblock and one for
199 // metadata at the end. Just create appropriate files with <metadata-i> name.
200 // We can add all these extents as one metadata too, but that violates the
201 // contiguous write optimization.
202 for (size_t i = 0; i < metadata_extents.size(); i++) {
203 File file;
204 file.name = "<metadata-" + std::to_string(i) + ">";
205 file.extents = {metadata_extents[i]};
206 files_.emplace_back(file);
207 }
208
209 // Do one last sort before returning.
210 std::sort(files_.begin(), files_.end(), [](const File& a, const File& b) {
211 return a.extents[0].start_block() < b.extents[0].start_block();
212 });
Amin Hassani3cd4df12017-08-25 11:21:53 -0700213
214 if (is_zlib && extract_deflates) {
215 // If it is infact gzipped, then the sqfs_path should be valid to read its
216 // content.
217 TEST_AND_RETURN_FALSE(!sqfs_path.empty());
218 if (zlib_blks.empty()) {
219 return true;
220 }
221
222 // Sort zlib blocks.
223 std::sort(zlib_blks.begin(),
224 zlib_blks.end(),
225 [](const puffin::ByteExtent& a, const puffin::ByteExtent& b) {
226 return a.offset < b.offset;
227 });
228
229 // Sanity check. Make sure zlib blocks are not overlapping.
230 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(
Amin Hassani3cd4df12017-08-25 11:21:53 -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 =
256 brillo::FileStream::Open(base::FilePath(sqfs_path),
257 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
322bool SquashfsFilesystem::LoadSettings(brillo::KeyValueStore* store) const {
323 // Settings not supported in squashfs.
324 LOG(ERROR) << "squashfs doesn't support LoadSettings().";
325 return false;
326}
327
328bool SquashfsFilesystem::IsSquashfsImage(const brillo::Blob& blob) {
329 SquashfsHeader header;
330 return ReadSquashfsHeader(blob, &header) && CheckHeader(header);
331}
Amin Hassanid7da8f42017-08-23 14:29:40 -0700332} // namespace chromeos_update_engine