1 // Copyright 2018 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 "fvm-host/format.h"
6 #include "fvm-host/fvm-info.h"
7 
Reset(size_t disk_size,size_t slice_size)8 zx_status_t FvmInfo::Reset(size_t disk_size, size_t slice_size) {
9     valid_ = false;
10 
11     if (slice_size == 0) {
12         fprintf(stderr, "Invalid slice size\n");
13         return ZX_ERR_INVALID_ARGS;
14     }
15 
16     // Even if disk size is 0, this will default to at least FVM_BLOCK_SIZE
17     metadata_size_ = fvm::MetadataSize(disk_size, slice_size);
18     metadata_.reset(new uint8_t[metadata_size_ * 2]);
19 
20     // Clear entire primary copy of metadata
21     memset(metadata_.get(), 0, metadata_size_);
22 
23     // Superblock
24     fvm::fvm_t* sb = SuperBlock();
25     sb->magic = FVM_MAGIC;
26     sb->version = FVM_VERSION;
27     sb->pslice_count = fvm::UsableSlicesCount(disk_size, slice_size);
28     sb->slice_size = slice_size;
29     sb->fvm_partition_size = disk_size;
30     sb->vpartition_table_size = fvm::kVPartTableLength;
31     sb->allocation_table_size = fvm::AllocTableLength(disk_size, slice_size);
32     sb->generation = 0;
33 
34     if (sb->pslice_count == 0) {
35         fprintf(stderr, "No space available for slices\n");
36         return ZX_ERR_NO_SPACE;
37     }
38 
39     valid_ = true;
40     dirty_ = true;
41 
42     xprintf("fvm_init: Success\n");
43     xprintf("fvm_init: Slice Count: %" PRIu64 ", size: %" PRIu64 "\n", sb->pslice_count,
44             sb->slice_size);
45     xprintf("fvm_init: Vpart offset: %zu, length: %zu\n",
46             fvm::kVPartTableOffset, fvm::kVPartTableLength);
47     xprintf("fvm_init: Atable offset: %zu, length: %zu\n",
48             fvm::kAllocTableOffset, fvm::AllocTableLength(disk_size_, slice_size_));
49     xprintf("fvm_init: Backup meta starts at: %zu\n",
50             fvm::BackupStart(disk_size_, slice_size_));
51     xprintf("fvm_init: Slices start at %zu, there are %zu of them\n",
52             fvm::SlicesStart(disk_size_, slice_size_),
53             fvm::UsableSlicesCount(disk_size_, slice_size_));
54 
55     return ZX_OK;
56 }
57 
Load(const fbl::unique_fd & fd,uint64_t disk_offset,uint64_t disk_size)58 zx_status_t FvmInfo::Load(const fbl::unique_fd& fd, uint64_t disk_offset, uint64_t disk_size) {
59     valid_ = false;
60 
61     if (disk_size == 0) {
62         return ZX_OK;
63     }
64 
65     // For now just reset this to the fvm header size - we can grow it to the full metadata size
66     // later.
67     metadata_.reset(new uint8_t[sizeof(fvm::fvm_t)]);
68 
69     // If Container already exists, read metadata from disk.
70     // Read superblock first so we can determine if container has a different slice size.
71     ssize_t result = pread(fd.get(), metadata_.get(), sizeof(fvm::fvm_t), disk_offset);
72     if (result != static_cast<ssize_t>(sizeof(fvm::fvm_t))) {
73         fprintf(stderr, "Superblock read failed: expected %ld, actual %ld\n",
74                 sizeof(fvm::fvm_t), result);
75         return ZX_ERR_IO;
76     }
77 
78     // If the image is obviously not an FVM header, bail out early.
79     // Otherwise, we go through the effort of ensuring the header is
80     // valid before using it.
81     if (SuperBlock()->magic != FVM_MAGIC) {
82         return ZX_OK;
83     }
84 
85     if (DiskSize() != disk_size) {
86         fprintf(stderr, "Disk size does not match expected");
87         return ZX_ERR_BAD_STATE;
88     }
89 
90     // Recalculate metadata size.
91     size_t old_slice_size = SuperBlock()->slice_size;
92     size_t old_metadata_size = fvm::MetadataSize(disk_size, old_slice_size);
93     fbl::unique_ptr<uint8_t[]> old_metadata =
94         fbl::make_unique<uint8_t[]>(old_metadata_size * 2);
95 
96     // Read remainder of metadata.
97     result = pread(fd.get(), old_metadata.get(), old_metadata_size * 2, disk_offset);
98     if (result != static_cast<ssize_t>(old_metadata_size * 2)) {
99         fprintf(stderr, "Metadata read failed: expected %ld, actual %ld\n",
100                 old_metadata_size * 2, result);
101         return ZX_ERR_IO;
102     }
103 
104     metadata_size_ = old_metadata_size;
105     metadata_.reset(old_metadata.release());
106 
107     if (Validate() == ZX_OK) {
108         valid_ = true;
109     }
110 
111     return ZX_OK;
112 }
113 
Validate() const114 zx_status_t FvmInfo::Validate() const {
115     const void* primary = nullptr;
116     const void* backup = reinterpret_cast<void*>(
117         reinterpret_cast<uintptr_t>(metadata_.get()) + metadata_size_);
118     zx_status_t status = fvm_validate_header(metadata_.get(), backup, metadata_size_, &primary);
119 
120     if (status != ZX_OK) {
121         fprintf(stderr, "Header validation failed with status %d\n", status);
122         return status;
123     }
124 
125     if (primary != metadata_.get()) {
126         fprintf(stderr, "Can only update FVM with valid primary as first copy\n");
127         return ZX_ERR_NOT_SUPPORTED;
128     }
129 
130     return ZX_OK;
131 }
132 
Write(const fbl::unique_fd & fd,size_t disk_offset,size_t disk_size)133 zx_status_t FvmInfo::Write(const fbl::unique_fd& fd, size_t disk_offset, size_t disk_size) {
134     fvm::fvm_t* sb = SuperBlock();
135     if (disk_size != sb->fvm_partition_size) {
136         // If disk size has changed, update and attempt to grow metadata.
137         sb->pslice_count = fvm::UsableSlicesCount(disk_size, SliceSize());
138         sb->fvm_partition_size = disk_size;
139         sb->allocation_table_size = fvm::AllocTableLength(disk_size, SliceSize());
140 
141         size_t new_metadata_size = fvm::MetadataSize(disk_size, SliceSize());
142         zx_status_t status = Grow(new_metadata_size);
143         if (status != ZX_OK) {
144             return status;
145         }
146     }
147 
148     fvm_update_hash(metadata_.get(), metadata_size_);
149 
150     if (Validate() != ZX_OK) {
151         fprintf(stderr, "Metadata is invalid");
152         return ZX_ERR_BAD_STATE;
153     }
154 
155     if (lseek(fd.get(), disk_offset, SEEK_SET) < 0) {
156         fprintf(stderr, "Error seeking disk\n");
157         return ZX_ERR_IO;
158     }
159 
160     if (write(fd.get(), metadata_.get(), metadata_size_) != static_cast<ssize_t>(metadata_size_)) {
161         fprintf(stderr, "Error writing metadata to disk\n");
162         return ZX_ERR_IO;
163     }
164 
165     if (write(fd.get(), metadata_.get(), metadata_size_) != static_cast<ssize_t>(metadata_size_)) {
166         fprintf(stderr, "Error writing metadata to disk\n");
167         return ZX_ERR_IO;
168     }
169 
170     return ZX_OK;
171 }
172 
CheckValid() const173 void FvmInfo::CheckValid() const {
174     if (!valid_) {
175         fprintf(stderr, "Error: FVM is invalid\n");
176         exit(-1);
177     }
178 }
179 
Grow(size_t new_size)180 zx_status_t FvmInfo::Grow(size_t new_size) {
181     if (new_size <= metadata_size_) {
182         return ZX_OK;
183     }
184 
185     xprintf("Growing metadata from %zu to %zu\n", metadata_size_, new_size);
186     fbl::unique_ptr<uint8_t[]> new_metadata(new uint8_t[new_size * 2]);
187 
188     memcpy(new_metadata.get(), metadata_.get(), metadata_size_);
189     memset(new_metadata.get() + metadata_size_, 0, new_size - metadata_size_);
190 
191     metadata_.reset(new_metadata.release());
192     metadata_size_ = new_size;
193     return ZX_OK;
194 }
195 
GrowForSlices(size_t slice_count)196 zx_status_t FvmInfo::GrowForSlices(size_t slice_count) {
197     size_t required_size = fvm::kAllocTableOffset + (pslice_hint_ + slice_count)
198                            * sizeof(fvm::slice_entry_t);
199     return Grow(required_size);
200 }
201 
AllocatePartition(const fvm::partition_descriptor_t * partition,uint8_t * guid,uint32_t * vpart_index)202 zx_status_t FvmInfo::AllocatePartition(const fvm::partition_descriptor_t* partition, uint8_t* guid,
203                                        uint32_t* vpart_index) {
204     CheckValid();
205     for (unsigned index = vpart_hint_; index < FVM_MAX_ENTRIES; index++) {
206         zx_status_t status;
207         fvm::vpart_entry_t* vpart = nullptr;
208         if ((status = GetPartition(index, &vpart)) != ZX_OK) {
209             fprintf(stderr, "Failed to retrieve partition %u\n", index);
210             return status;
211         }
212 
213         // Make sure this vpartition has not already been allocated
214         if (vpart->slices == 0) {
215             vpart->init(partition->type, guid, 0, reinterpret_cast<const char*>(partition->name),
216                         partition->flags);
217             vpart_hint_ = index + 1;
218             dirty_ = true;
219             *vpart_index = index;
220             return ZX_OK;
221         }
222     }
223 
224     fprintf(stderr, "Unable to find any free partitions\n");
225     return ZX_ERR_INTERNAL;
226 }
227 
AllocateSlice(uint32_t vpart,uint32_t vslice,uint32_t * pslice)228 zx_status_t FvmInfo::AllocateSlice(uint32_t vpart, uint32_t vslice, uint32_t* pslice) {
229     CheckValid();
230     fvm::fvm_t* sb = SuperBlock();
231 
232     for (uint32_t index = pslice_hint_; index <= sb->pslice_count; index++) {
233         zx_status_t status;
234         fvm::slice_entry_t* slice = nullptr;
235         if ((status = GetSlice(index, &slice)) != ZX_OK) {
236             fprintf(stderr, "Failed to retrieve slice %u\n", index);
237             return status;
238         }
239 
240         if (slice->Vpart() != FVM_SLICE_ENTRY_FREE) {
241             continue;
242         }
243 
244         pslice_hint_ = index + 1;
245 
246         fvm::vpart_entry_t* partition;
247         if ((status = GetPartition(vpart, &partition)) != ZX_OK) {
248             return status;
249         }
250 
251         slice->SetVpart(vpart);
252         slice->SetVslice(vslice);
253         partition->slices++;
254 
255         dirty_ = true;
256         *pslice = index;
257         return ZX_OK;
258     }
259 
260     fprintf(stderr, "Unable to find any free slices\n");
261     return ZX_ERR_INTERNAL;
262 }
263 
GetPartition(size_t index,fvm::vpart_entry_t ** out) const264 zx_status_t FvmInfo::GetPartition(size_t index, fvm::vpart_entry_t** out) const {
265     CheckValid();
266 
267     if (index < 1 || index > FVM_MAX_ENTRIES) {
268         return ZX_ERR_OUT_OF_RANGE;
269     }
270 
271     uintptr_t metadata_start = reinterpret_cast<uintptr_t>(metadata_.get());
272     uintptr_t offset = static_cast<uintptr_t>(fvm::kVPartTableOffset +
273                                               index * sizeof(fvm::vpart_entry_t));
274     *out = reinterpret_cast<fvm::vpart_entry_t*>(metadata_start + offset);
275     return ZX_OK;
276 }
277 
GetSlice(size_t index,fvm::slice_entry_t ** out) const278 zx_status_t FvmInfo::GetSlice(size_t index, fvm::slice_entry_t** out) const {
279     CheckValid();
280 
281     if (index < 1 || index > SuperBlock()->pslice_count) {
282         return ZX_ERR_OUT_OF_RANGE;
283     }
284 
285     uintptr_t metadata_start = reinterpret_cast<uintptr_t>(metadata_.get());
286     uintptr_t offset = static_cast<uintptr_t>(fvm::kAllocTableOffset +
287                                               index * sizeof(fvm::slice_entry_t));
288     *out = reinterpret_cast<fvm::slice_entry_t*>(metadata_start + offset);
289     return ZX_OK;
290 }
291 
SuperBlock() const292 fvm::fvm_t* FvmInfo::SuperBlock() const {
293     return static_cast<fvm::fvm_t*>((void*)metadata_.get());
294 }
295