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