1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <errno.h>
6 #include <inttypes.h>
7 #include <utility>
8 
9 #include <fvm/fvm.h>
10 #include <lib/fit/defer.h>
11 #include <sys/ioctl.h>
12 
13 #include "fvm-host/container.h"
14 
15 #if defined(__APPLE__)
16 #include <sys/disk.h>
17 #define IOCTL_GET_BLOCK_COUNT DKIOCGETBLOCKCOUNT
18 #endif
19 
20 #if defined(__linux__)
21 #include <linux/fs.h>
22 
23 #define IOCTL_GET_BLOCK_COUNT BLKGETSIZE
24 #endif
25 
Create(const char * path,size_t slice_size,off_t offset,off_t length,fbl::unique_ptr<FvmContainer> * out)26 zx_status_t FvmContainer::Create(const char* path, size_t slice_size, off_t offset, off_t length,
27                                  fbl::unique_ptr<FvmContainer>* out) {
28     fbl::unique_ptr<FvmContainer> fvmContainer(new FvmContainer(path, slice_size, offset,
29                                                                 length));
30 
31     zx_status_t status;
32     if ((status = fvmContainer->Init()) != ZX_OK) {
33         return status;
34     }
35 
36     *out = std::move(fvmContainer);
37     return ZX_OK;
38 }
39 
FvmContainer(const char * path,size_t slice_size,off_t offset,off_t length)40 FvmContainer::FvmContainer(const char* path, size_t slice_size, off_t offset, off_t length)
41     : Container(path, slice_size, 0), disk_offset_(offset), disk_size_(length) {
42     fd_.reset(open(path, O_RDWR, 0644));
43     if (!fd_) {
44         if (errno == ENOENT) {
45             fd_.reset(open(path, O_RDWR | O_CREAT | O_EXCL, 0644));
46 
47             if (!fd_) {
48                 fprintf(stderr, "Failed to create path %s\n", path);
49                 exit(-1);
50             }
51 
52             xprintf("Created path %s\n", path);
53         } else {
54             fprintf(stderr, "Failed to open path %s: %s\n", path, strerror(errno));
55             exit(-1);
56         }
57     }
58 
59     struct stat s;
60     if (fstat(fd_.get(), &s) < 0) {
61         fprintf(stderr, "Failed to stat %s\n", path);
62         exit(-1);
63     }
64 
65     uint64_t size = s.st_size;
66 
67     if (S_ISBLK(s.st_mode)) {
68         uint64_t block_count;
69         if (ioctl(fd_.get(), IOCTL_GET_BLOCK_COUNT, &block_count) >= 0) {
70             size = block_count * 512;
71         }
72     }
73 
74     if (disk_size_ == 0) {
75         disk_size_ = size;
76     }
77 
78     if (size < disk_offset_ + disk_size_) {
79         fprintf(stderr, "Invalid file size %" PRIu64 " for specified offset+length\n", size);
80         exit(-1);
81     }
82 
83     // Attempt to load metadata from disk
84     if (info_.Load(fd_, disk_offset_, disk_size_) != ZX_OK) {
85         exit(-1);
86     }
87 
88     if (info_.IsValid()) {
89         slice_size_ = info_.SliceSize();
90     }
91 }
92 
93 FvmContainer::~FvmContainer() = default;
94 
Init()95 zx_status_t FvmContainer::Init() {
96     return info_.Reset(disk_size_, slice_size_);
97 }
98 
Verify() const99 zx_status_t FvmContainer::Verify() const {
100     info_.CheckValid();
101 
102     zx_status_t status = info_.Validate();
103     if (status != ZX_OK) {
104         return status;
105     }
106 
107     fvm::fvm_t* sb = info_.SuperBlock();
108 
109     xprintf("Total size is %zu\n", disk_size_);
110     xprintf("Metadata size is %zu\n", info_.MetadataSize());
111     xprintf("Slice size is %" PRIu64 "\n", info_.SliceSize());
112     xprintf("Slice count is %" PRIu64 "\n", info_.SuperBlock()->pslice_count);
113 
114     off_t start = 0;
115     off_t end = disk_offset_ + info_.MetadataSize() * 2;
116     size_t slice_index = 1;
117     for (size_t vpart_index = 1; vpart_index < FVM_MAX_ENTRIES; ++vpart_index) {
118         fvm::vpart_entry_t* vpart = nullptr;
119         start = end;
120 
121         zx_status_t status;
122         if ((status = info_.GetPartition(vpart_index, &vpart)) != ZX_OK) {
123             return status;
124         }
125 
126         if (vpart->slices == 0) {
127             break;
128         }
129 
130         fbl::Vector<size_t> extent_lengths;
131         size_t last_vslice = 0;
132         size_t slice_count = 0;
133         for (; slice_index <= sb->pslice_count; ++slice_index) {
134             fvm::slice_entry_t* slice = nullptr;
135             if ((status = info_.GetSlice(slice_index, &slice)) != ZX_OK) {
136                 return status;
137             }
138 
139             if (slice->Vpart() != vpart_index) {
140                 break;
141             }
142 
143             end += slice_size_;
144             slice_count++;
145 
146             if (slice->Vslice() == last_vslice + 1) {
147                 extent_lengths[extent_lengths.size() - 1] += slice_size_;
148             } else {
149                 extent_lengths.push_back(slice_size_);
150             }
151 
152             last_vslice = slice->Vslice();
153         }
154 
155         if (vpart->slices != slice_count) {
156             fprintf(stderr, "Reported partition slices do not match expected\n");
157             return ZX_ERR_BAD_STATE;
158         }
159 
160         disk_format_t part;
161         if ((status = Format::Detect(fd_.get(), start, &part)) != ZX_OK) {
162             return status;
163         }
164 
165         fbl::unique_fd dupfd(dup(fd_.get()));
166         if (!dupfd) {
167             fprintf(stderr, "Failed to duplicate fd\n");
168             return ZX_ERR_INTERNAL;
169         }
170 
171         if ((status = Format::Check(std::move(dupfd), start, end, extent_lengths, part)) != ZX_OK) {
172             fprintf(stderr, "%s fsck returned an error.\n", vpart->name);
173             return status;
174         }
175 
176         xprintf("Found valid %s partition\n", vpart->name);
177     }
178 
179     return ZX_OK;
180 }
181 
Extend(size_t disk_size)182 zx_status_t FvmContainer::Extend(size_t disk_size) {
183     if (disk_size <= disk_size_) {
184         fprintf(stderr, "Cannot extend to disk size %zu smaller than current size %" PRIu64 "\n",
185                 disk_size, disk_size_);
186         return ZX_ERR_INVALID_ARGS;
187     } else if (disk_offset_) {
188         fprintf(stderr, "Cannot extend FVM within another container\n");
189         return ZX_ERR_BAD_STATE;
190     }
191 
192     const char* temp = ".tmp";
193 
194     if (path_.length() >= PATH_MAX - strlen(temp) - 1) {
195         fprintf(stderr, "Path name exceeds maximum length\n");
196         return ZX_ERR_INVALID_ARGS;
197     }
198 
199     fbl::StringBuffer<PATH_MAX> path;
200     path.AppendPrintf("%s%s", path_.c_str(), temp);
201     fbl::unique_fd fd(open(path.c_str(), O_RDWR | O_CREAT, 0644));
202 
203     if (!fd) {
204         fprintf(stderr, "Unable to open temp file %s\n", path.c_str());
205         return ZX_ERR_IO;
206     }
207 
208     auto cleanup = fit::defer([path]() {
209         if (unlink(path.c_str()) < 0) {
210             fprintf(stderr, "Failed to unlink path %s\n", path.c_str());
211         }
212     });
213 
214     if (ftruncate(fd.get(), disk_size) != 0) {
215         fprintf(stderr, "Failed to truncate fvm container");
216         return ZX_ERR_IO;
217     }
218 
219     // Since the size and location of both metadata in an FVM is dependent on the size of
220     // the FVM partition, we must relocate any data that already exists within the volume
221     // manager.
222     //
223     // First, we read all old slices from the original device, and write them to their
224     // new locations.
225     //
226     // Then, we update the on-disk metadata to reflect the new size of the disk.
227     // To avoid collision between relocated slices, this is done on a temporary file.
228     uint64_t pslice_count = info_.SuperBlock()->pslice_count;
229     for (uint32_t index = 1; index <= pslice_count; index++) {
230         zx_status_t status;
231         fvm::slice_entry_t* slice = nullptr;
232         if ((status = info_.GetSlice(index, &slice)) != ZX_OK) {
233             fprintf(stderr, "Failed to retrieve slice %u\n", index);
234             return status;
235         }
236 
237         if (slice->Vpart() == FVM_SLICE_ENTRY_FREE) {
238             continue;
239         }
240 
241         fbl::Array<uint8_t> data(new uint8_t[slice_size_], slice_size_);
242 
243         if (lseek(fd_.get(), fvm::SliceStart(disk_size_, slice_size_, index), SEEK_SET) < 0) {
244             fprintf(stderr, "Cannot seek to slice %u in current FVM\n", index);
245             return ZX_ERR_BAD_STATE;
246         }
247 
248         ssize_t r = read(fd_.get(), data.get(), slice_size_);
249         if (r != slice_size_) {
250             fprintf(stderr, "Failed to read data from FVM: %ld\n", r);
251             return ZX_ERR_BAD_STATE;
252         }
253 
254         if (lseek(fd.get(), fvm::SliceStart(disk_size, slice_size_, index), SEEK_SET) < 0) {
255             fprintf(stderr, "Cannot seek to slice %u in new FVM\n", index);
256             return ZX_ERR_BAD_STATE;
257         }
258 
259         r = write(fd.get(), data.get(), slice_size_);
260         if (r != slice_size_) {
261             fprintf(stderr, "Failed to write data to FVM: %ld\n", r);
262             return ZX_ERR_BAD_STATE;
263         }
264     }
265 
266     size_t metadata_size = fvm::MetadataSize(disk_size, slice_size_);
267     zx_status_t status = info_.Grow(metadata_size);
268     if (status != ZX_OK) {
269         return status;
270     }
271 
272     if ((status = info_.Write(fd, 0, disk_size)) != ZX_OK) {
273         return status;
274     }
275 
276     fd_.reset(fd.release());
277     disk_size_ = disk_size;
278 
279     if ((status = Verify()) != ZX_OK) {
280         fprintf(stderr, "Verify failed - cancelling extension\n");
281         return status;
282     }
283 
284     if (rename(path.c_str(), path_.c_str()) < 0) {
285         fprintf(stderr, "Failed to copy over temp file\n");
286         return ZX_ERR_IO;
287     }
288 
289     cleanup.cancel();
290     return ZX_OK;
291 }
292 
Commit()293 zx_status_t FvmContainer::Commit() {
294     if (!info_.IsDirty()) {
295         fprintf(stderr, "Commit: Nothing to write\n");
296         return ZX_OK;
297     }
298 
299     // If the FVM container has just been created, truncate it to an appropriate size
300     if (disk_size_ == 0) {
301         if (partitions_.is_empty()) {
302             fprintf(stderr, "Cannot create new FVM container with 0 partitions\n");
303             return ZX_ERR_INVALID_ARGS;
304         }
305 
306         uint64_t total_size = CalculateDiskSize();
307         zx_status_t status = info_.Grow(fvm::MetadataSize(total_size, slice_size_));
308         if (status != ZX_OK) {
309             return status;
310         }
311 
312         if (ftruncate(fd_.get(), total_size) != 0) {
313             fprintf(stderr, "Failed to truncate fvm container");
314             return ZX_ERR_IO;
315         }
316 
317         struct stat s;
318         if (fstat(fd_.get(), &s) < 0) {
319             fprintf(stderr, "Failed to stat container\n");
320             return ZX_ERR_IO;
321         }
322 
323         disk_size_ = s.st_size;
324 
325         if (disk_size_ != total_size) {
326             fprintf(stderr, "Truncated to incorrect size\n");
327             return ZX_ERR_IO;
328         }
329     }
330 
331     zx_status_t status = info_.Write(fd_, disk_offset_, disk_size_);
332     if (status != ZX_OK) {
333         return status;
334     }
335 
336     for (unsigned i = 0; i < partitions_.size(); i++) {
337         if ((status = WritePartition(i)) != ZX_OK) {
338             return status;
339         }
340     }
341 
342     xprintf("Successfully wrote FVM data to disk\n");
343     return ZX_OK;
344 }
345 
SliceSize() const346 size_t FvmContainer::SliceSize() const {
347     info_.CheckValid();
348     return slice_size_;
349 }
350 
AddPartition(const char * path,const char * type_name)351 zx_status_t FvmContainer::AddPartition(const char* path, const char* type_name) {
352     info_.CheckValid();
353 
354     fbl::unique_ptr<Format> format;
355     zx_status_t status;
356     if ((status = Format::Create(path, type_name, &format)) != ZX_OK) {
357         fprintf(stderr, "Failed to initialize partition\n");
358         return status;
359     }
360 
361     uint32_t vpart_index;
362     uint8_t guid[FVM_GUID_LEN];
363     format->Guid(guid);
364     fvm::partition_descriptor_t descriptor;
365     format->GetPartitionInfo(&descriptor);
366     if ((status = info_.AllocatePartition(&descriptor, guid, &vpart_index)) != ZX_OK) {
367         return status;
368     }
369 
370     if ((status = format->MakeFvmReady(slice_size_, vpart_index)) != ZX_OK) {
371         return status;
372     }
373 
374     uint32_t slice_count = 0;
375     if ((status = format->GetSliceCount(&slice_count)) != ZX_OK) {
376         return status;
377     }
378 
379     // If allocated metadata is too small, grow it to an appropriate size
380     if ((status = info_.GrowForSlices(slice_count)) != ZX_OK) {
381         return status;
382     }
383 
384     // Allocate all slices for this partition
385     uint32_t pslice_start = 0;
386     uint32_t pslice_total = 0;
387     unsigned extent_index = 0;
388     while (true) {
389         vslice_info_t vslice_info;
390         zx_status_t status;
391         if ((status = format->GetVsliceRange(extent_index, &vslice_info)) != ZX_OK) {
392             if (status == ZX_ERR_OUT_OF_RANGE) {
393                 break;
394             }
395             return status;
396         }
397 
398         uint32_t vslice = vslice_info.vslice_start / format->BlocksPerSlice();
399 
400         for (unsigned i = 0; i < vslice_info.slice_count; i++) {
401             uint32_t pslice;
402 
403             if ((status = info_.AllocateSlice(format->VpartIndex(), vslice + i, &pslice))
404                 != ZX_OK) {
405                 return status;
406             }
407 
408             if (!pslice_start) {
409                 pslice_start = pslice;
410             }
411 
412             // On a new FVM container, pslice allocation is expected to be contiguous.
413             if (pslice != pslice_start + pslice_total) {
414                 fprintf(stderr, "Unexpected error during slice allocation\n");
415                 return ZX_ERR_INTERNAL;
416             }
417 
418             pslice_total++;
419         }
420 
421         extent_index++;
422     }
423 
424     fvm::vpart_entry_t* entry;
425     if ((status = info_.GetPartition(format->VpartIndex(), &entry)) != ZX_OK) {
426         return status;
427     }
428 
429     ZX_ASSERT(entry->slices == slice_count);
430 
431     FvmPartitionInfo partition;
432     partition.format = std::move(format);
433     partition.vpart_index = vpart_index;
434     partition.pslice_start = pslice_start;
435     partition.slice_count = slice_count;
436     partitions_.push_back(std::move(partition));
437     return ZX_OK;
438 }
439 
CalculateDiskSize() const440 uint64_t FvmContainer::CalculateDiskSize() const {
441     info_.CheckValid();
442 
443     size_t required_slices = 0;
444 
445     for (size_t index = 1; index < FVM_MAX_ENTRIES; index++) {
446         fvm::vpart_entry_t* vpart;
447         ZX_ASSERT(info_.GetPartition(index, &vpart) == ZX_OK);
448 
449         if (vpart->slices == 0) {
450             break;
451         }
452 
453         required_slices += vpart->slices;
454     }
455 
456     return CalculateDiskSizeForSlices(required_slices);
457 }
458 
GetDiskSize() const459 uint64_t FvmContainer::GetDiskSize() const {
460     return disk_size_;
461 }
462 
WritePartition(unsigned part_index)463 zx_status_t FvmContainer::WritePartition(unsigned part_index) {
464     info_.CheckValid();
465     if (part_index > partitions_.size()) {
466         fprintf(stderr, "Error: Tried to access partition %u / %zu\n",
467                 part_index, partitions_.size());
468         return ZX_ERR_OUT_OF_RANGE;
469     }
470 
471     unsigned extent_index = 0;
472     FvmPartitionInfo* partition = &partitions_[part_index];
473     Format* format = partition->format.get();
474     uint32_t pslice_start = partition->pslice_start;
475 
476     while (true) {
477         zx_status_t status;
478         if ((status = WriteExtent(extent_index++, format, &pslice_start)) != ZX_OK) {
479             if (status != ZX_ERR_OUT_OF_RANGE) {
480                 return status;
481             }
482 
483             return ZX_OK;
484         }
485     }
486 }
487 
WriteExtent(unsigned extent_index,Format * format,uint32_t * pslice)488 zx_status_t FvmContainer::WriteExtent(unsigned extent_index, Format* format, uint32_t* pslice) {
489     vslice_info_t vslice_info{};
490     zx_status_t status;
491     if ((status = format->GetVsliceRange(extent_index, &vslice_info)) != ZX_OK) {
492         return status;
493     }
494 
495     // Write each slice in the given extent
496     uint32_t current_block = 0;
497     for (unsigned i = 0; i < vslice_info.slice_count; i++) {
498         // Write each block in this slice
499         for (uint32_t j = 0; j < format->BlocksPerSlice(); j++) {
500             // If we have gone beyond the blocks written to partition file, write empty block
501             if (current_block >= vslice_info.block_count) {
502                 if (!vslice_info.zero_fill) {
503                     break;
504                 }
505                 format->EmptyBlock();
506             } else {
507                 if ((status = format->FillBlock(vslice_info.block_offset + current_block)) != ZX_OK) {
508                     fprintf(stderr, "Failed to read block from minfs\n");
509                     return status;
510                 }
511 
512                 current_block++;
513             }
514 
515             if ((status = WriteData(*pslice, j, format->BlockSize(), format->Data())) != ZX_OK) {
516                 fprintf(stderr, "Failed to write data to FVM\n");
517                 return status;
518             }
519         }
520         (*pslice)++;
521     }
522 
523     return ZX_OK;
524 }
525 
WriteData(uint32_t pslice,uint32_t block_offset,size_t block_size,void * data)526 zx_status_t FvmContainer::WriteData(uint32_t pslice, uint32_t block_offset, size_t block_size,
527                                     void* data) {
528     info_.CheckValid();
529 
530     if (block_offset * block_size > slice_size_) {
531         fprintf(stderr, "Not enough space in slice\n");
532         return ZX_ERR_OUT_OF_RANGE;
533     }
534 
535     if (lseek(fd_.get(), disk_offset_ + fvm::SliceStart(disk_size_, slice_size_, pslice) + block_offset * block_size, SEEK_SET) < 0) {
536         return ZX_ERR_BAD_STATE;
537     }
538 
539     ssize_t r = write(fd_.get(), data, block_size);
540     if (r != block_size) {
541         fprintf(stderr, "Failed to write data to FVM\n");
542         return ZX_ERR_BAD_STATE;
543     }
544 
545     return ZX_OK;
546 }
547