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 <sys/ioctl.h>
8
9#include <fvm/fvm.h>
10
11#include <lib/fit/defer.h>
12
13#include "fvm/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#define IOCTL_GET_BLOCK_COUNT BLKGETSIZE
23#endif
24
25zx_status_t FvmContainer::Create(const char* path, size_t slice_size, off_t offset, off_t length,
26                                 fbl::unique_ptr<FvmContainer>* out) {
27    fbl::AllocChecker ac;
28    fbl::unique_ptr<FvmContainer> fvmContainer(new (&ac) FvmContainer(path, slice_size, offset,
29                                                                      length));
30    if (!ac.check()) {
31        return ZX_ERR_NO_MEMORY;
32    }
33
34    zx_status_t status;
35    if ((status = fvmContainer->Init()) != ZX_OK) {
36        return status;
37    }
38
39    *out = fbl::move(fvmContainer);
40    return ZX_OK;
41}
42
43FvmContainer::FvmContainer(const char* path, size_t slice_size, off_t offset, off_t length)
44    : Container(path, slice_size, 0), valid_(false), disk_offset_(offset), disk_size_(length),
45      vpart_hint_(1), pslice_hint_(1) {
46    fd_.reset(open(path, O_RDWR, 0644));
47    if (!fd_) {
48        if (errno == ENOENT) {
49            fd_.reset(open(path, O_RDWR | O_CREAT | O_EXCL, 0644));
50
51            if (!fd_) {
52                fprintf(stderr, "Failed to create path %s\n", path);
53                exit(-1);
54            }
55
56            xprintf("Created path %s\n", path);
57        } else {
58            fprintf(stderr, "Failed to open path %s: %s\n", path, strerror(errno));
59            exit(-1);
60        }
61    }
62
63    struct stat s;
64    if (fstat(fd_.get(), &s) < 0) {
65        fprintf(stderr, "Failed to stat %s\n", path);
66        exit(-1);
67    }
68
69    uint64_t size = s.st_size;
70
71    if (S_ISBLK(s.st_mode)) {
72        uint64_t block_count;
73        if (ioctl(fd_.get(), IOCTL_GET_BLOCK_COUNT, &block_count) >= 0) {
74            size = block_count * 512;
75        }
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    // Even if disk size is 0, this will default to at least FVM_BLOCK_SIZE
84    metadata_size_ = fvm::MetadataSize(disk_size_, slice_size_);
85    metadata_.reset(new uint8_t[metadata_size_ * 2]);
86
87    // Clear entire primary copy of metadata
88    memset(metadata_.get(), 0, metadata_size_);
89
90    // If Container already exists, read metadata from disk.
91    if (disk_size_ > 0) {
92        if (lseek(fd_.get(), disk_offset_, SEEK_SET) < 0) {
93            fprintf(stderr, "Seek reset failed\n");
94            exit(-1);
95        }
96
97        // Read superblock first so we can determine if container has a different slice size.
98        ssize_t rd = read(fd_.get(), metadata_.get(), sizeof(fvm::fvm_t));
99        if (rd != static_cast<ssize_t>(sizeof(fvm::fvm_t))) {
100            fprintf(stderr, "Superblock read failed: expected %ld, actual %ld\n",
101                    sizeof(fvm::fvm_t), rd);
102            exit(-1);
103        }
104
105        // If the image is obviously not an FVM header, bail out early.
106        // Otherwise, we go through the effort of ensuring the header is
107        // valid before using it.
108        if (SuperBlock()->magic != FVM_MAGIC) {
109            return;
110        }
111
112        // Recalculate metadata size.
113        size_t old_slice_size = SuperBlock()->slice_size;
114        size_t old_metadata_size = fvm::MetadataSize(disk_size_, old_slice_size);
115        auto old_metadata = fbl::unique_ptr<uint8_t[]>(new uint8_t[old_metadata_size * 2]);
116
117        if (lseek(fd_.get(), disk_offset_, SEEK_SET) < 0) {
118            fprintf(stderr, "Seek reset failed\n");
119            exit(-1);
120        }
121
122        // Read remainder of metadata.
123        rd = read(fd_.get(), old_metadata.get(), old_metadata_size * 2);
124        if (rd != static_cast<ssize_t>(old_metadata_size * 2)) {
125            fprintf(stderr, "Metadata read failed: expected %ld, actual %ld\n",
126                    old_metadata_size * 2, rd);
127            exit(-1);
128        }
129
130        const void* backup = reinterpret_cast<void*>(
131            reinterpret_cast<uintptr_t>(old_metadata.get()) + old_metadata_size);
132        const void* primary = nullptr;
133        if (fvm_validate_header(old_metadata.get(), backup, old_metadata_size, &primary) == ZX_OK) {
134            if (primary != old_metadata.get()) {
135                fprintf(stderr, "Can only update FVM with valid primary as first copy\n");
136                exit(-1);
137            }
138
139            valid_ = true;
140            slice_size_ = old_slice_size;
141            metadata_size_ = old_metadata_size;
142            metadata_.reset(old_metadata.release());
143        }
144    }
145}
146
147FvmContainer::~FvmContainer() = default;
148
149zx_status_t FvmContainer::Init() {
150    // Clear entire primary copy of metadata.
151    memset(metadata_.get(), 0, metadata_size_);
152
153    // Superblock
154    fvm::fvm_t* sb = SuperBlock();
155    sb->magic = FVM_MAGIC;
156    sb->version = FVM_VERSION;
157    sb->pslice_count = fvm::UsableSlicesCount(disk_size_, slice_size_);
158    sb->slice_size = slice_size_;
159    sb->fvm_partition_size = disk_size_;
160    sb->vpartition_table_size = fvm::kVPartTableLength;
161    sb->allocation_table_size = fvm::AllocTableLength(disk_size_, slice_size_);
162    sb->generation = 0;
163
164    if (sb->pslice_count == 0) {
165        fprintf(stderr, "No space available for slices\n");
166        return ZX_ERR_NO_SPACE;
167    }
168
169    dirty_ = true;
170    valid_ = true;
171
172    xprintf("fvm_init: Success\n");
173    xprintf("fvm_init: Slice Count: %" PRIu64 ", size: %" PRIu64 "\n", sb->pslice_count,
174            sb->slice_size);
175    xprintf("fvm_init: Vpart offset: %zu, length: %zu\n",
176            fvm::kVPartTableOffset, fvm::kVPartTableLength);
177    xprintf("fvm_init: Atable offset: %zu, length: %zu\n",
178            fvm::kAllocTableOffset, fvm::AllocTableLength(disk_size_, slice_size_));
179    xprintf("fvm_init: Backup meta starts at: %zu\n",
180            fvm::BackupStart(disk_size_, slice_size_));
181    xprintf("fvm_init: Slices start at %zu, there are %zu of them\n",
182            fvm::SlicesStart(disk_size_, slice_size_),
183            fvm::UsableSlicesCount(disk_size_, slice_size_));
184    return ZX_OK;
185}
186
187zx_status_t FvmContainer::Verify() const {
188    CheckValid();
189    const void* backup = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(metadata_.get()) +
190                                                 metadata_size_);
191
192    if (fvm_validate_header(metadata_.get(), backup, metadata_size_, nullptr) != ZX_OK) {
193        fprintf(stderr, "Failed to validate header\n");
194        return ZX_ERR_BAD_STATE;
195    }
196
197    fvm::fvm_t* sb = SuperBlock();
198
199    xprintf("Total size is %zu\n", disk_size_);
200    xprintf("Metadata size is %zu\n", metadata_size_);
201    xprintf("Slice size is %" PRIu64 "\n", sb->slice_size);
202    xprintf("Slice count is %" PRIu64 "\n", sb->pslice_count);
203
204    off_t start = 0;
205    off_t end = disk_offset_ + metadata_size_ * 2;
206    size_t slice_index = 1;
207    for (size_t vpart_index = 1; vpart_index < FVM_MAX_ENTRIES; ++vpart_index) {
208        fvm::vpart_entry_t* vpart = nullptr;
209        start = end;
210
211        zx_status_t status;
212        if ((status = GetPartition(vpart_index, &vpart)) != ZX_OK) {
213            return status;
214        }
215
216        if (vpart->slices == 0) {
217            break;
218        }
219
220        fbl::Vector<size_t> extent_lengths;
221        size_t last_vslice = 0;
222
223        for (; slice_index <= sb->pslice_count; ++slice_index) {
224            fvm::slice_entry_t* slice = nullptr;
225            if ((status = GetSlice(slice_index, &slice)) != ZX_OK) {
226                return status;
227            }
228
229            if (slice->Vpart() != vpart_index) {
230                break;
231            }
232
233            end += slice_size_;
234
235            if (slice->Vslice() == last_vslice + 1) {
236                extent_lengths[extent_lengths.size() - 1] += slice_size_;
237            } else {
238                extent_lengths.push_back(slice_size_);
239            }
240
241            last_vslice = slice->Vslice();
242        }
243
244        disk_format_t part;
245        if ((status = Format::Detect(fd_.get(), start, &part)) != ZX_OK) {
246            return status;
247        }
248
249        fbl::unique_fd dupfd(dup(fd_.get()));
250        if (!dupfd) {
251            fprintf(stderr, "Failed to duplicate fd\n");
252            return ZX_ERR_INTERNAL;
253        }
254
255        if ((status = Format::Check(fbl::move(dupfd), start, end, extent_lengths, part)) != ZX_OK) {
256            fprintf(stderr, "%s fsck returned an error.\n", vpart->name);
257            return status;
258        }
259
260        xprintf("Found valid %s partition\n", vpart->name);
261    }
262
263    return ZX_OK;
264}
265
266zx_status_t FvmContainer::Extend(size_t disk_size) {
267    if (disk_size <= disk_size_) {
268        fprintf(stderr, "Cannot extend to disk size %zu smaller than current size %lu\n", disk_size,
269                disk_size_);
270        return ZX_ERR_INVALID_ARGS;
271    } else if (disk_offset_) {
272        fprintf(stderr, "Cannot extend FVM within another container\n");
273        return ZX_ERR_BAD_STATE;
274    }
275
276    const char* temp = ".tmp";
277
278    if (strlen(path_) >= PATH_MAX - strlen(temp) - 1) {
279        fprintf(stderr, "Path name exceeds maximum length\n");
280        return ZX_ERR_INVALID_ARGS;
281    }
282
283    char path[PATH_MAX];
284    strncpy(path, path_, PATH_MAX);
285    path[sizeof(path) - 1] = '\0';
286
287    strncat(path, temp, PATH_MAX - strlen(path) - 1);
288    fbl::unique_fd fd(open(path, O_RDWR | O_CREAT, 0644));
289
290    if (!fd) {
291        fprintf(stderr, "Unable to open temp file %s\n", path);
292        return ZX_ERR_IO;
293    }
294
295    auto cleanup = fit::defer([path]() {
296        if (unlink(path) < 0) {
297            fprintf(stderr, "Failed to unlink path %s\n", path);
298        }
299    });
300
301    if (ftruncate(fd.get(), disk_size) != 0) {
302        fprintf(stderr, "Failed to truncate fvm container");
303        return ZX_ERR_IO;
304    }
305
306    // Since the size and location of both metadata in an FVM is dependent on the size of
307    // the FVM partition, we must relocate any data that already exists within the volume
308    // manager.
309    //
310    // First, we read all old slices from the original device, and write them to their
311    // new locations.
312    //
313    // Then, we update the on-disk metadata to reflect the new size of the disk.
314    // To avoid collision between relocated slices, this is done on a temporary file.
315    uint64_t pslice_count = SuperBlock()->pslice_count;
316    for (uint32_t index = 1; index <= pslice_count; index++) {
317        zx_status_t status;
318        fvm::slice_entry_t* slice = nullptr;
319        if ((status = GetSlice(index, &slice)) != ZX_OK) {
320            fprintf(stderr, "Failed to retrieve slice %u\n", index);
321            return status;
322        }
323
324        if (slice->Vpart() == FVM_SLICE_ENTRY_FREE) {
325            continue;
326        }
327
328        fbl::AllocChecker ac;
329        fbl::Array<uint8_t> data(new (&ac) uint8_t[slice_size_], slice_size_);
330
331        if (!ac.check()) {
332            return ZX_ERR_NO_MEMORY;
333        }
334
335        if (lseek(fd_.get(), fvm::SliceStart(disk_size_, slice_size_, index), SEEK_SET) < 0) {
336            fprintf(stderr, "Cannot seek to slice %u in current FVM\n", index);
337            return ZX_ERR_BAD_STATE;
338        }
339
340        ssize_t r = read(fd_.get(), data.get(), slice_size_);
341        if (r != slice_size_) {
342            fprintf(stderr, "Failed to read data from FVM: %ld\n", r);
343            return ZX_ERR_BAD_STATE;
344        }
345
346        if (lseek(fd.get(), fvm::SliceStart(disk_size, slice_size_, index), SEEK_SET) < 0) {
347            fprintf(stderr, "Cannot seek to slice %u in new FVM\n", index);
348            return ZX_ERR_BAD_STATE;
349        }
350
351        r = write(fd.get(), data.get(), slice_size_);
352        if (r != slice_size_) {
353            fprintf(stderr, "Failed to write data to FVM: %ld\n", r);
354            return ZX_ERR_BAD_STATE;
355        }
356    }
357
358    size_t metadata_size = fvm::MetadataSize(disk_size, slice_size_);
359    zx_status_t status = GrowMetadata(metadata_size);
360    if (status != ZX_OK) {
361        return status;
362    }
363
364    fvm::fvm_t* sb = SuperBlock();
365    sb->pslice_count = fvm::UsableSlicesCount(disk_size, slice_size_);
366    sb->fvm_partition_size = disk_size;
367    sb->allocation_table_size = fvm::AllocTableLength(disk_size, slice_size_);
368    fvm_update_hash(metadata_.get(), metadata_size_);
369
370    if (lseek(fd.get(), 0, SEEK_SET) < 0) {
371        fprintf(stderr, "Failed to seek\n");
372        return ZX_ERR_BAD_STATE;
373    }
374
375    if (write(fd.get(), metadata_.get(), metadata_size_) != static_cast<ssize_t>(metadata_size_)) {
376        fprintf(stderr, "Error writing metadata to disk\n");
377        return ZX_ERR_IO;
378    }
379
380    if (write(fd.get(), metadata_.get(), metadata_size_) != static_cast<ssize_t>(metadata_size_)) {
381        fprintf(stderr, "Error writing metadata to disk\n");
382        return ZX_ERR_IO;
383    }
384
385    fd_.reset(fd.release());
386    disk_size_ = disk_size;
387
388    if ((status = Verify()) != ZX_OK) {
389        fprintf(stderr, "Verify failed - cancelling extension\n");
390        return status;
391    }
392
393    if (rename(path, path_) < 0) {
394        fprintf(stderr, "Failed to copy over temp file\n");
395        return ZX_ERR_IO;
396    }
397
398    cleanup.cancel();
399    return ZX_OK;
400}
401
402zx_status_t FvmContainer::Commit() {
403    if (!dirty_) {
404        fprintf(stderr, "Commit: Nothing to write\n");
405        return ZX_OK;
406    }
407
408    // If the FVM container has just been created, truncate it to an appropriate size
409    if (disk_size_ == 0) {
410        if (partitions_.is_empty()) {
411            fprintf(stderr, "Cannot create new FVM container with 0 partitions\n");
412            return ZX_ERR_INVALID_ARGS;
413        }
414
415        size_t required_size = 0;
416        for (unsigned i = 0; i < partitions_.size(); i++) {
417            required_size += partitions_[i].slice_count * slice_size_;
418        }
419
420        size_t total_size = required_size;
421        size_t metadata_size = 0;
422
423        while (total_size - (metadata_size * 2) < required_size || metadata_size < metadata_size_) {
424            total_size = required_size + (metadata_size * 2);
425            metadata_size = fvm::MetadataSize(total_size, slice_size_);
426        }
427
428        zx_status_t status;
429        if ((status = GrowMetadata(metadata_size)) != ZX_OK) {
430            return status;
431        }
432
433        if (ftruncate(fd_.get(), total_size) != 0) {
434            fprintf(stderr, "Failed to truncate fvm container");
435            return ZX_ERR_IO;
436        }
437
438        struct stat s;
439        if (fstat(fd_.get(), &s) < 0) {
440            fprintf(stderr, "Failed to stat container\n");
441            return ZX_ERR_IO;
442        }
443
444        disk_size_ = s.st_size;
445
446        if (disk_size_ != total_size) {
447            fprintf(stderr, "Truncated to incorrect size\n");
448            return ZX_ERR_IO;
449        }
450
451        fvm::fvm_t* sb = SuperBlock();
452        sb->pslice_count = (disk_size_ - metadata_size_ * 2) / slice_size_;
453        sb->fvm_partition_size = disk_size_;
454        sb->allocation_table_size = fvm::AllocTableLength(disk_size_, slice_size_);
455    }
456
457    fvm_update_hash(metadata_.get(), metadata_size_);
458
459    if (lseek(fd_.get(), disk_offset_, SEEK_SET) < 0) {
460        fprintf(stderr, "Error seeking disk\n");
461        return ZX_ERR_IO;
462    }
463
464    if (write(fd_.get(), metadata_.get(), metadata_size_) != static_cast<ssize_t>(metadata_size_)) {
465        fprintf(stderr, "Error writing metadata to disk\n");
466        return ZX_ERR_IO;
467    }
468
469    if (write(fd_.get(), metadata_.get(), metadata_size_) != static_cast<ssize_t>(metadata_size_)) {
470        fprintf(stderr, "Error writing metadata to disk\n");
471        return ZX_ERR_IO;
472    }
473
474    for (unsigned i = 0; i < partitions_.size(); i++) {
475        zx_status_t status;
476        if ((status = WritePartition(i)) != ZX_OK) {
477            return status;
478        }
479    }
480
481    xprintf("Successfully wrote FVM data to disk\n");
482    return ZX_OK;
483}
484
485size_t FvmContainer::SliceSize() const {
486    CheckValid();
487    return slice_size_;
488}
489
490zx_status_t FvmContainer::AddPartition(const char* path, const char* type_name) {
491    fbl::unique_ptr<Format> format;
492    zx_status_t status;
493    if ((status = Format::Create(path, type_name, &format)) != ZX_OK) {
494        fprintf(stderr, "Failed to initialize partition\n");
495        return status;
496    }
497
498    uint8_t guid[FVM_GUID_LEN];
499    uint8_t type[FVM_GUID_LEN];
500    char name[FVM_NAME_LEN];
501    format->Guid(guid);
502    format->Type(type);
503    format->Name(name);
504    uint32_t vpart_index;
505    uint32_t flags = flags_ & format->FlagMask();
506    if ((status = AllocatePartition(type, guid, name, 1, flags, &vpart_index)) != ZX_OK) {
507        return status;
508    }
509
510    if ((status = format->MakeFvmReady(SliceSize(), vpart_index)) != ZX_OK) {
511        return status;
512    }
513
514    uint32_t slice_count = 0;
515    if ((status = format->GetSliceCount(&slice_count)) != ZX_OK) {
516        return status;
517    }
518
519    // If allocated metadata is too small, grow it to an appropriate size
520    size_t required_size = fvm::kAllocTableOffset + (pslice_hint_ + slice_count) * sizeof(fvm::slice_entry_t);
521    if ((status = GrowMetadata(required_size)) != ZX_OK) {
522        return status;
523    }
524
525    // Allocate all slices for this partition
526    uint32_t pslice_start = 0;
527    uint32_t pslice_total = 0;
528    unsigned extent_index = 0;
529    while (true) {
530        vslice_info_t vslice_info;
531        zx_status_t status;
532        if ((status = format->GetVsliceRange(extent_index, &vslice_info)) != ZX_OK) {
533            if (status == ZX_ERR_OUT_OF_RANGE) {
534                break;
535            }
536            return status;
537        }
538
539        uint32_t vslice = vslice_info.vslice_start / format->BlocksPerSlice();
540
541        for (unsigned i = 0; i < vslice_info.slice_count; i++) {
542            uint32_t pslice;
543
544            if ((status = AllocateSlice(format->VpartIndex(), vslice + i, &pslice)) != ZX_OK) {
545                return status;
546            }
547
548            if (!pslice_start) {
549                pslice_start = pslice;
550            }
551
552            // On a new FVM container, pslice allocation is expected to be contiguous.
553            if (pslice != pslice_start + pslice_total) {
554                fprintf(stderr, "Unexpected error during slice allocation\n");
555                return ZX_ERR_INTERNAL;
556            }
557
558            pslice_total++;
559        }
560
561        extent_index++;
562    }
563
564    partition_info_t partition;
565    partition.format = fbl::move(format);
566    partition.vpart_index = vpart_index;
567    partition.pslice_start = pslice_start;
568    partition.slice_count = slice_count;
569    partitions_.push_back(fbl::move(partition));
570    return ZX_OK;
571}
572
573void FvmContainer::CheckValid() const {
574    if (!valid_) {
575        fprintf(stderr, "Error: FVM is invalid\n");
576        exit(-1);
577    }
578}
579
580zx_status_t FvmContainer::GrowMetadata(size_t new_size) {
581    if (new_size <= metadata_size_) {
582        return ZX_OK;
583    }
584
585    xprintf("Growing metadata from %zu to %zu\n", metadata_size_, new_size);
586    fbl::AllocChecker ac;
587    fbl::unique_ptr<uint8_t[]> new_metadata(new (&ac) uint8_t[new_size * 2]);
588    if (!ac.check()) {
589        fprintf(stderr, "Unable to acquire resources for new metadata\n");
590        return ZX_ERR_NO_MEMORY;
591    }
592
593    memcpy(new_metadata.get(), metadata_.get(), metadata_size_);
594    memset(new_metadata.get() + metadata_size_, 0, new_size - metadata_size_);
595
596    metadata_.reset(new_metadata.release());
597    metadata_size_ = new_size;
598    return ZX_OK;
599}
600
601zx_status_t FvmContainer::AllocatePartition(uint8_t* type, uint8_t* guid, const char* name,
602                                            uint32_t slices, uint32_t flags, uint32_t* vpart_index) {
603    CheckValid();
604    for (unsigned index = vpart_hint_; index < FVM_MAX_ENTRIES; index++) {
605        zx_status_t status;
606        fvm::vpart_entry_t* vpart = nullptr;
607        if ((status = GetPartition(index, &vpart)) != ZX_OK) {
608            fprintf(stderr, "Failed to retrieve partition %u\n", index);
609            return status;
610        }
611
612        // Make sure this vpartition has not already been allocated
613        if (vpart->slices == 0) {
614            vpart->init(type, guid, slices, name, flags);
615            vpart_hint_ = index + 1;
616            dirty_ = true;
617            *vpart_index = index;
618            return ZX_OK;
619        }
620    }
621
622    fprintf(stderr, "Unable to find any free partitions\n");
623    return ZX_ERR_INTERNAL;
624}
625
626zx_status_t FvmContainer::AllocateSlice(uint32_t vpart, uint32_t vslice, uint32_t* pslice) {
627    CheckValid();
628    fvm::fvm_t* sb = SuperBlock();
629
630    for (uint32_t index = pslice_hint_; index <= sb->pslice_count; index++) {
631        zx_status_t status;
632        fvm::slice_entry_t* slice = nullptr;
633        if ((status = GetSlice(index, &slice)) != ZX_OK) {
634            fprintf(stderr, "Failed to retrieve slice %u\n", index);
635            return status;
636        }
637
638        if (slice->Vpart() != FVM_SLICE_ENTRY_FREE) {
639            continue;
640        }
641
642        pslice_hint_ = index + 1;
643
644        slice->SetVpart(vpart);
645        slice->SetVslice(vslice);
646        dirty_ = true;
647        *pslice = index;
648        return ZX_OK;
649    }
650
651    fprintf(stderr, "Unable to find any free slices\n");
652    return ZX_ERR_INTERNAL;
653}
654
655zx_status_t FvmContainer::GetPartition(size_t index, fvm::vpart_entry_t** out) const {
656    CheckValid();
657
658    if (index < 1 || index > FVM_MAX_ENTRIES) {
659        return ZX_ERR_OUT_OF_RANGE;
660    }
661
662    uintptr_t metadata_start = reinterpret_cast<uintptr_t>(metadata_.get());
663    uintptr_t offset = static_cast<uintptr_t>(fvm::kVPartTableOffset +
664                                              index * sizeof(fvm::vpart_entry_t));
665    *out = reinterpret_cast<fvm::vpart_entry_t*>(metadata_start + offset);
666    return ZX_OK;
667}
668
669zx_status_t FvmContainer::GetSlice(size_t index, fvm::slice_entry_t** out) const {
670    CheckValid();
671
672    if (index < 1 || index > SuperBlock()->pslice_count) {
673        return ZX_ERR_OUT_OF_RANGE;
674    }
675
676    uintptr_t metadata_start = reinterpret_cast<uintptr_t>(metadata_.get());
677    uintptr_t offset = static_cast<uintptr_t>(fvm::kAllocTableOffset +
678                                              index * sizeof(fvm::slice_entry_t));
679    *out = reinterpret_cast<fvm::slice_entry_t*>(metadata_start + offset);
680    return ZX_OK;
681}
682
683zx_status_t FvmContainer::WritePartition(unsigned part_index) {
684    CheckValid();
685    if (part_index > partitions_.size()) {
686        fprintf(stderr, "Error: Tried to access partition %u / %zu\n",
687                part_index, partitions_.size());
688        return ZX_ERR_OUT_OF_RANGE;
689    }
690
691    unsigned extent_index = 0;
692    partition_info_t* partition = &partitions_[part_index];
693    Format* format = partition->format.get();
694    uint32_t pslice_start = partition->pslice_start;
695
696    while (true) {
697        zx_status_t status;
698        if ((status = WriteExtent(extent_index++, format, &pslice_start)) != ZX_OK) {
699            if (status != ZX_ERR_OUT_OF_RANGE) {
700                return status;
701            }
702
703            return ZX_OK;
704        }
705    }
706}
707
708zx_status_t FvmContainer::WriteExtent(unsigned extent_index, Format* format, uint32_t* pslice) {
709    vslice_info_t vslice_info{};
710    zx_status_t status;
711    if ((status = format->GetVsliceRange(extent_index, &vslice_info)) != ZX_OK) {
712        return status;
713    }
714
715    // Write each slice in the given extent
716    uint32_t current_block = 0;
717    for (unsigned i = 0; i < vslice_info.slice_count; i++) {
718        // Write each block in this slice
719        for (uint32_t j = 0; j < format->BlocksPerSlice(); j++) {
720            // If we have gone beyond the blocks written to partition file, write empty block
721            if (current_block >= vslice_info.block_count) {
722                if (!vslice_info.zero_fill) {
723                    break;
724                }
725                format->EmptyBlock();
726            } else {
727                if ((status = format->FillBlock(vslice_info.block_offset + current_block)) != ZX_OK) {
728                    fprintf(stderr, "Failed to read block from minfs\n");
729                    return status;
730                }
731
732                current_block++;
733            }
734
735            if ((status = WriteData(*pslice, j, format->BlockSize(), format->Data())) != ZX_OK) {
736                fprintf(stderr, "Failed to write data to FVM\n");
737                return status;
738            }
739        }
740        (*pslice)++;
741    }
742
743    return ZX_OK;
744}
745
746zx_status_t FvmContainer::WriteData(uint32_t pslice, uint32_t block_offset, size_t block_size,
747                                    void* data) {
748    CheckValid();
749
750    if (block_offset * block_size > slice_size_) {
751        fprintf(stderr, "Not enough space in slice\n");
752        return ZX_ERR_OUT_OF_RANGE;
753    }
754
755    if (lseek(fd_.get(), disk_offset_ + fvm::SliceStart(disk_size_, slice_size_, pslice) + block_offset * block_size, SEEK_SET) < 0) {
756        return ZX_ERR_BAD_STATE;
757    }
758
759    ssize_t r = write(fd_.get(), data, block_size);
760    if (r != block_size) {
761        fprintf(stderr, "Failed to write data to FVM\n");
762        return ZX_ERR_BAD_STATE;
763    }
764
765    return ZX_OK;
766}
767
768fvm::fvm_t* FvmContainer::SuperBlock() const {
769    return static_cast<fvm::fvm_t*>((void*)metadata_.get());
770}
771