• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright (C) 2020 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 <sys/types.h>
18 #include <unistd.h>
19 
20 #include <limits>
21 #include <queue>
22 
23 #include <android-base/file.h>
24 #include <android-base/logging.h>
25 #include <android-base/unique_fd.h>
26 #include <brotli/encode.h>
27 #include <libsnapshot/cow_reader.h>
28 #include <libsnapshot/cow_writer.h>
29 #include <zlib.h>
30 
31 namespace android {
32 namespace snapshot {
33 
34 static_assert(sizeof(off_t) == sizeof(uint64_t));
35 
36 using android::base::borrowed_fd;
37 using android::base::unique_fd;
38 
AddCopy(uint64_t new_block,uint64_t old_block)39 bool ICowWriter::AddCopy(uint64_t new_block, uint64_t old_block) {
40     if (!ValidateNewBlock(new_block)) {
41         return false;
42     }
43     return EmitCopy(new_block, old_block);
44 }
45 
AddRawBlocks(uint64_t new_block_start,const void * data,size_t size)46 bool ICowWriter::AddRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
47     if (size % options_.block_size != 0) {
48         LOG(ERROR) << "AddRawBlocks: size " << size << " is not a multiple of "
49                    << options_.block_size;
50         return false;
51     }
52 
53     uint64_t num_blocks = size / options_.block_size;
54     uint64_t last_block = new_block_start + num_blocks - 1;
55     if (!ValidateNewBlock(last_block)) {
56         return false;
57     }
58     return EmitRawBlocks(new_block_start, data, size);
59 }
60 
AddZeroBlocks(uint64_t new_block_start,uint64_t num_blocks)61 bool ICowWriter::AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
62     uint64_t last_block = new_block_start + num_blocks - 1;
63     if (!ValidateNewBlock(last_block)) {
64         return false;
65     }
66     return EmitZeroBlocks(new_block_start, num_blocks);
67 }
68 
AddLabel(uint64_t label)69 bool ICowWriter::AddLabel(uint64_t label) {
70     return EmitLabel(label);
71 }
72 
ValidateNewBlock(uint64_t new_block)73 bool ICowWriter::ValidateNewBlock(uint64_t new_block) {
74     if (options_.max_blocks && new_block >= options_.max_blocks.value()) {
75         LOG(ERROR) << "New block " << new_block << " exceeds maximum block count "
76                    << options_.max_blocks.value();
77         return false;
78     }
79     return true;
80 }
81 
CowWriter(const CowOptions & options)82 CowWriter::CowWriter(const CowOptions& options) : ICowWriter(options), fd_(-1) {
83     SetupHeaders();
84 }
85 
SetupHeaders()86 void CowWriter::SetupHeaders() {
87     header_ = {};
88     header_.magic = kCowMagicNumber;
89     header_.major_version = kCowVersionMajor;
90     header_.minor_version = kCowVersionMinor;
91     header_.header_size = sizeof(CowHeader);
92     header_.footer_size = sizeof(CowFooter);
93     header_.op_size = sizeof(CowOperation);
94     header_.block_size = options_.block_size;
95     header_.num_merge_ops = 0;
96     header_.cluster_ops = options_.cluster_ops;
97     header_.buffer_size = 0;
98     footer_ = {};
99     footer_.op.data_length = 64;
100     footer_.op.type = kCowFooterOp;
101 }
102 
ParseOptions()103 bool CowWriter::ParseOptions() {
104     if (options_.compression == "gz") {
105         compression_ = kCowCompressGz;
106     } else if (options_.compression == "brotli") {
107         compression_ = kCowCompressBrotli;
108     } else if (options_.compression == "none") {
109         compression_ = kCowCompressNone;
110     } else if (!options_.compression.empty()) {
111         LOG(ERROR) << "unrecognized compression: " << options_.compression;
112         return false;
113     }
114     if (options_.cluster_ops == 1) {
115         LOG(ERROR) << "Clusters must contain at least two operations to function.";
116         return false;
117     }
118     return true;
119 }
120 
SetFd(android::base::borrowed_fd fd)121 bool CowWriter::SetFd(android::base::borrowed_fd fd) {
122     if (fd.get() < 0) {
123         owned_fd_.reset(open("/dev/null", O_RDWR | O_CLOEXEC));
124         if (owned_fd_ < 0) {
125             PLOG(ERROR) << "open /dev/null failed";
126             return false;
127         }
128         fd_ = owned_fd_;
129         is_dev_null_ = true;
130     } else {
131         fd_ = fd;
132 
133         struct stat stat;
134         if (fstat(fd.get(), &stat) < 0) {
135             PLOG(ERROR) << "fstat failed";
136             return false;
137         }
138         is_block_device_ = S_ISBLK(stat.st_mode);
139     }
140     return true;
141 }
142 
Initialize(unique_fd && fd)143 bool CowWriter::Initialize(unique_fd&& fd) {
144     owned_fd_ = std::move(fd);
145     return Initialize(borrowed_fd{owned_fd_});
146 }
147 
Initialize(borrowed_fd fd)148 bool CowWriter::Initialize(borrowed_fd fd) {
149     if (!SetFd(fd) || !ParseOptions()) {
150         return false;
151     }
152 
153     return OpenForWrite();
154 }
155 
InitializeAppend(android::base::unique_fd && fd,uint64_t label)156 bool CowWriter::InitializeAppend(android::base::unique_fd&& fd, uint64_t label) {
157     owned_fd_ = std::move(fd);
158     return InitializeAppend(android::base::borrowed_fd{owned_fd_}, label);
159 }
160 
InitializeAppend(android::base::borrowed_fd fd,uint64_t label)161 bool CowWriter::InitializeAppend(android::base::borrowed_fd fd, uint64_t label) {
162     if (!SetFd(fd) || !ParseOptions()) {
163         return false;
164     }
165 
166     return OpenForAppend(label);
167 }
168 
InitPos()169 void CowWriter::InitPos() {
170     next_op_pos_ = sizeof(header_) + header_.buffer_size;
171     cluster_size_ = header_.cluster_ops * sizeof(CowOperation);
172     if (header_.cluster_ops) {
173         next_data_pos_ = next_op_pos_ + cluster_size_;
174     } else {
175         next_data_pos_ = next_op_pos_ + sizeof(CowOperation);
176     }
177     ops_.clear();
178     current_cluster_size_ = 0;
179     current_data_size_ = 0;
180 }
181 
OpenForWrite()182 bool CowWriter::OpenForWrite() {
183     // This limitation is tied to the data field size in CowOperation.
184     if (header_.block_size > std::numeric_limits<uint16_t>::max()) {
185         LOG(ERROR) << "Block size is too large";
186         return false;
187     }
188 
189     if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
190         PLOG(ERROR) << "lseek failed";
191         return false;
192     }
193 
194     if (options_.scratch_space) {
195         header_.buffer_size = BUFFER_REGION_DEFAULT_SIZE;
196     }
197 
198     // Headers are not complete, but this ensures the file is at the right
199     // position.
200     if (!android::base::WriteFully(fd_, &header_, sizeof(header_))) {
201         PLOG(ERROR) << "write failed";
202         return false;
203     }
204 
205     if (options_.scratch_space) {
206         // Initialize the scratch space
207         std::string data(header_.buffer_size, 0);
208         if (!android::base::WriteFully(fd_, data.data(), header_.buffer_size)) {
209             PLOG(ERROR) << "writing scratch space failed";
210             return false;
211         }
212     }
213 
214     if (!Sync()) {
215         LOG(ERROR) << "Header sync failed";
216         return false;
217     }
218 
219     if (lseek(fd_.get(), sizeof(header_) + header_.buffer_size, SEEK_SET) < 0) {
220         PLOG(ERROR) << "lseek failed";
221         return false;
222     }
223 
224     InitPos();
225 
226     return true;
227 }
228 
OpenForAppend(uint64_t label)229 bool CowWriter::OpenForAppend(uint64_t label) {
230     auto reader = std::make_unique<CowReader>();
231     std::queue<CowOperation> toAdd;
232 
233     if (!reader->Parse(fd_, {label}) || !reader->GetHeader(&header_)) {
234         return false;
235     }
236 
237     options_.block_size = header_.block_size;
238     options_.cluster_ops = header_.cluster_ops;
239 
240     // Reset this, since we're going to reimport all operations.
241     footer_.op.num_ops = 0;
242     InitPos();
243 
244     auto iter = reader->GetOpIter();
245 
246     while (!iter->Done()) {
247         AddOperation(iter->Get());
248         iter->Next();
249     }
250 
251     // Free reader so we own the descriptor position again.
252     reader = nullptr;
253 
254     if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
255         PLOG(ERROR) << "lseek failed";
256         return false;
257     }
258     return EmitClusterIfNeeded();
259 }
260 
EmitCopy(uint64_t new_block,uint64_t old_block)261 bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block) {
262     CHECK(!merge_in_progress_);
263     CowOperation op = {};
264     op.type = kCowCopyOp;
265     op.new_block = new_block;
266     op.source = old_block;
267     return WriteOperation(op);
268 }
269 
EmitRawBlocks(uint64_t new_block_start,const void * data,size_t size)270 bool CowWriter::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
271     const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
272     CHECK(!merge_in_progress_);
273     for (size_t i = 0; i < size / header_.block_size; i++) {
274         CowOperation op = {};
275         op.type = kCowReplaceOp;
276         op.new_block = new_block_start + i;
277         op.source = next_data_pos_;
278 
279         if (compression_) {
280             auto data = Compress(iter, header_.block_size);
281             if (data.empty()) {
282                 PLOG(ERROR) << "AddRawBlocks: compression failed";
283                 return false;
284             }
285             if (data.size() > std::numeric_limits<uint16_t>::max()) {
286                 LOG(ERROR) << "Compressed block is too large: " << data.size() << " bytes";
287                 return false;
288             }
289             op.compression = compression_;
290             op.data_length = static_cast<uint16_t>(data.size());
291 
292             if (!WriteOperation(op, data.data(), data.size())) {
293                 PLOG(ERROR) << "AddRawBlocks: write failed";
294                 return false;
295             }
296         } else {
297             op.data_length = static_cast<uint16_t>(header_.block_size);
298             if (!WriteOperation(op, iter, header_.block_size)) {
299                 PLOG(ERROR) << "AddRawBlocks: write failed";
300                 return false;
301             }
302         }
303 
304         iter += header_.block_size;
305     }
306     return true;
307 }
308 
EmitZeroBlocks(uint64_t new_block_start,uint64_t num_blocks)309 bool CowWriter::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
310     CHECK(!merge_in_progress_);
311     for (uint64_t i = 0; i < num_blocks; i++) {
312         CowOperation op = {};
313         op.type = kCowZeroOp;
314         op.new_block = new_block_start + i;
315         op.source = 0;
316         WriteOperation(op);
317     }
318     return true;
319 }
320 
EmitLabel(uint64_t label)321 bool CowWriter::EmitLabel(uint64_t label) {
322     CHECK(!merge_in_progress_);
323     CowOperation op = {};
324     op.type = kCowLabelOp;
325     op.source = label;
326     return WriteOperation(op) && Sync();
327 }
328 
EmitCluster()329 bool CowWriter::EmitCluster() {
330     CowOperation op = {};
331     op.type = kCowClusterOp;
332     // Next cluster starts after remainder of current cluster and the next data block.
333     op.source = current_data_size_ + cluster_size_ - current_cluster_size_ - sizeof(CowOperation);
334     return WriteOperation(op);
335 }
336 
EmitClusterIfNeeded()337 bool CowWriter::EmitClusterIfNeeded() {
338     // If there isn't room for another op and the cluster end op, end the current cluster
339     if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperation)) {
340         if (!EmitCluster()) return false;
341     }
342     return true;
343 }
344 
Compress(const void * data,size_t length)345 std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) {
346     switch (compression_) {
347         case kCowCompressGz: {
348             auto bound = compressBound(length);
349             auto buffer = std::make_unique<uint8_t[]>(bound);
350 
351             uLongf dest_len = bound;
352             auto rv = compress2(buffer.get(), &dest_len, reinterpret_cast<const Bytef*>(data),
353                                 length, Z_BEST_COMPRESSION);
354             if (rv != Z_OK) {
355                 LOG(ERROR) << "compress2 returned: " << rv;
356                 return {};
357             }
358             return std::basic_string<uint8_t>(buffer.get(), dest_len);
359         }
360         case kCowCompressBrotli: {
361             auto bound = BrotliEncoderMaxCompressedSize(length);
362             if (!bound) {
363                 LOG(ERROR) << "BrotliEncoderMaxCompressedSize returned 0";
364                 return {};
365             }
366             auto buffer = std::make_unique<uint8_t[]>(bound);
367 
368             size_t encoded_size = bound;
369             auto rv = BrotliEncoderCompress(
370                     BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, length,
371                     reinterpret_cast<const uint8_t*>(data), &encoded_size, buffer.get());
372             if (!rv) {
373                 LOG(ERROR) << "BrotliEncoderCompress failed";
374                 return {};
375             }
376             return std::basic_string<uint8_t>(buffer.get(), encoded_size);
377         }
378         default:
379             LOG(ERROR) << "unhandled compression type: " << compression_;
380             break;
381     }
382     return {};
383 }
384 
385 // TODO: Fix compilation issues when linking libcrypto library
386 // when snapuserd is compiled as part of ramdisk.
SHA256(const void *,size_t,uint8_t[])387 static void SHA256(const void*, size_t, uint8_t[]) {
388 #if 0
389     SHA256_CTX c;
390     SHA256_Init(&c);
391     SHA256_Update(&c, data, length);
392     SHA256_Final(out, &c);
393 #endif
394 }
395 
Finalize()396 bool CowWriter::Finalize() {
397     auto continue_cluster_size = current_cluster_size_;
398     auto continue_data_size = current_data_size_;
399     auto continue_data_pos = next_data_pos_;
400     auto continue_op_pos = next_op_pos_;
401     auto continue_size = ops_.size();
402     auto continue_num_ops = footer_.op.num_ops;
403     bool extra_cluster = false;
404 
405     // Blank out extra ops, in case we're in append mode and dropped ops.
406     if (cluster_size_) {
407         auto unused_cluster_space = cluster_size_ - current_cluster_size_;
408         std::string clr;
409         clr.resize(unused_cluster_space, '\0');
410         if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
411             PLOG(ERROR) << "Failed to seek to footer position.";
412             return false;
413         }
414         if (!android::base::WriteFully(fd_, clr.data(), clr.size())) {
415             PLOG(ERROR) << "clearing unused cluster area failed";
416             return false;
417         }
418     }
419 
420     // Footer should be at the end of a file, so if there is data after the current block, end it
421     // and start a new cluster.
422     if (cluster_size_ && current_data_size_ > 0) {
423         EmitCluster();
424         extra_cluster = true;
425     }
426 
427     footer_.op.ops_size = ops_.size();
428     if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
429         PLOG(ERROR) << "Failed to seek to footer position.";
430         return false;
431     }
432     memset(&footer_.data.ops_checksum, 0, sizeof(uint8_t) * 32);
433     memset(&footer_.data.footer_checksum, 0, sizeof(uint8_t) * 32);
434 
435     SHA256(ops_.data(), ops_.size(), footer_.data.ops_checksum);
436     SHA256(&footer_.op, sizeof(footer_.op), footer_.data.footer_checksum);
437     // Write out footer at end of file
438     if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&footer_),
439                                    sizeof(footer_))) {
440         PLOG(ERROR) << "write footer failed";
441         return false;
442     }
443 
444     // Remove excess data, if we're in append mode and threw away more data
445     // than we wrote before.
446     off_t offs = lseek(fd_.get(), 0, SEEK_CUR);
447     if (offs < 0) {
448         PLOG(ERROR) << "Failed to lseek to find current position";
449         return false;
450     }
451     if (!Truncate(offs)) {
452         return false;
453     }
454 
455     // Reposition for additional Writing
456     if (extra_cluster) {
457         current_cluster_size_ = continue_cluster_size;
458         current_data_size_ = continue_data_size;
459         next_data_pos_ = continue_data_pos;
460         next_op_pos_ = continue_op_pos;
461         footer_.op.num_ops = continue_num_ops;
462         ops_.resize(continue_size);
463     }
464     return Sync();
465 }
466 
GetCowSize()467 uint64_t CowWriter::GetCowSize() {
468     if (current_data_size_ > 0) {
469         return next_data_pos_ + sizeof(footer_);
470     } else {
471         return next_op_pos_ + sizeof(footer_);
472     }
473 }
474 
GetDataPos(uint64_t * pos)475 bool CowWriter::GetDataPos(uint64_t* pos) {
476     off_t offs = lseek(fd_.get(), 0, SEEK_CUR);
477     if (offs < 0) {
478         PLOG(ERROR) << "lseek failed";
479         return false;
480     }
481     *pos = offs;
482     return true;
483 }
484 
WriteOperation(const CowOperation & op,const void * data,size_t size)485 bool CowWriter::WriteOperation(const CowOperation& op, const void* data, size_t size) {
486     if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
487         PLOG(ERROR) << "lseek failed for writing operation.";
488         return false;
489     }
490     if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&op), sizeof(op))) {
491         return false;
492     }
493     if (data != nullptr && size > 0) {
494         if (!WriteRawData(data, size)) return false;
495     }
496     AddOperation(op);
497     return EmitClusterIfNeeded();
498 }
499 
AddOperation(const CowOperation & op)500 void CowWriter::AddOperation(const CowOperation& op) {
501     footer_.op.num_ops++;
502 
503     if (op.type == kCowClusterOp) {
504         current_cluster_size_ = 0;
505         current_data_size_ = 0;
506     } else if (header_.cluster_ops) {
507         current_cluster_size_ += sizeof(op);
508         current_data_size_ += op.data_length;
509     }
510 
511     next_data_pos_ += op.data_length + GetNextDataOffset(op, header_.cluster_ops);
512     next_op_pos_ += sizeof(CowOperation) + GetNextOpOffset(op, header_.cluster_ops);
513     ops_.insert(ops_.size(), reinterpret_cast<const uint8_t*>(&op), sizeof(op));
514 }
515 
WriteRawData(const void * data,size_t size)516 bool CowWriter::WriteRawData(const void* data, size_t size) {
517     if (lseek(fd_.get(), next_data_pos_, SEEK_SET) < 0) {
518         PLOG(ERROR) << "lseek failed for writing data.";
519         return false;
520     }
521 
522     if (!android::base::WriteFully(fd_, data, size)) {
523         return false;
524     }
525     return true;
526 }
527 
Sync()528 bool CowWriter::Sync() {
529     if (is_dev_null_) {
530         return true;
531     }
532     if (fsync(fd_.get()) < 0) {
533         PLOG(ERROR) << "fsync failed";
534         return false;
535     }
536     return true;
537 }
538 
Truncate(off_t length)539 bool CowWriter::Truncate(off_t length) {
540     if (is_dev_null_ || is_block_device_) {
541         return true;
542     }
543     if (ftruncate(fd_.get(), length) < 0) {
544         PLOG(ERROR) << "Failed to truncate.";
545         return false;
546     }
547     return true;
548 }
549 
550 }  // namespace snapshot
551 }  // namespace android
552