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