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 <dirent.h>
6#include <errno.h>
7#include <fcntl.h>
8
9#include <chromeos-disk-setup/chromeos-disk-setup.h>
10#include <fbl/auto_call.h>
11#include <fbl/function.h>
12#include <fs-management/fvm.h>
13#include <gpt/cros.h>
14#include <lib/fdio/watcher.h>
15#include <zircon/device/device.h>
16#include <zircon/device/skip-block.h>
17#include <zircon/status.h>
18#include <zxcrypt/volume.h>
19
20#include "device-partitioner.h"
21#include "pave-logging.h"
22#include "pave-utils.h"
23
24namespace paver {
25
26bool (*TestBlockFilter)(const fbl::unique_fd&) = nullptr;
27bool (*TestSkipBlockFilter)(const fbl::unique_fd&) = nullptr;
28
29namespace {
30
31bool KernelFilterCallback(const gpt_partition_t& part, fbl::StringPiece partition_name) {
32    const uint8_t kern_type[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE;
33    char cstring_name[GPT_NAME_LEN];
34    utf16_to_cstring(cstring_name, reinterpret_cast<const uint16_t*>(part.name), GPT_NAME_LEN);
35    return memcmp(part.type, kern_type, GPT_GUID_LEN) == 0 &&
36           strncmp(cstring_name, partition_name.data(), partition_name.length()) == 0;
37}
38
39bool FvmFilterCallback(const gpt_partition_t& part) {
40    const uint8_t partition_type[GPT_GUID_LEN] = GUID_FVM_VALUE;
41    return memcmp(part.type, partition_type, GPT_GUID_LEN) == 0;
42}
43
44constexpr size_t ReservedHeaderBlocks(size_t blk_size) {
45    constexpr size_t kReservedEntryBlocks = (16 * 1024);
46    return (kReservedEntryBlocks + 2 * blk_size) / blk_size;
47};
48
49constexpr char kGptDriverName[] = "/boot/driver/gpt.so";
50constexpr char kFvmPartitionName[] = "fvm";
51
52// Helper function to auto-deduce type.
53template <typename T>
54fbl::unique_ptr<T> WrapUnique(T* ptr) {
55    return fbl::unique_ptr<T>(ptr);
56}
57
58
59zx_status_t OpenPartition(const char* path,
60                          fbl::Function<bool(const fbl::unique_fd&)> should_filter_file,
61                          zx_duration_t timeout, fbl::unique_fd* out_partition) {
62    ZX_ASSERT(path != nullptr);
63
64    struct CallbackInfo {
65        fbl::unique_fd* out_partition;
66        fbl::Function<bool(const fbl::unique_fd&)> should_filter_file;
67    };
68
69    CallbackInfo info = {
70        .out_partition = out_partition,
71        .should_filter_file = fbl::move(should_filter_file),
72    };
73
74    auto cb = [](int dirfd, int event, const char* filename, void* cookie) {
75        if (event != WATCH_EVENT_ADD_FILE) {
76            return ZX_OK;
77        }
78        if ((strcmp(filename, ".") == 0) || strcmp(filename, "..") == 0) {
79            return ZX_OK;
80        }
81        fbl::unique_fd devfd(openat(dirfd, filename, O_RDWR));
82        if (!devfd) {
83            return ZX_OK;
84        }
85        auto info = static_cast<CallbackInfo*>(cookie);
86        if (info->should_filter_file(devfd)) {
87            return ZX_OK;
88        }
89        if (info->out_partition) {
90            *(info->out_partition) = fbl::move(devfd);
91        }
92        return ZX_ERR_STOP;
93    };
94
95    DIR* dir = opendir(path);
96    if (dir == nullptr) {
97        return ZX_ERR_IO;
98    }
99    const auto closer = fbl::MakeAutoCall([&dir]() { closedir(dir); });
100
101    zx_time_t deadline = zx_deadline_after(timeout);
102    if (fdio_watch_directory(dirfd(dir), cb, deadline, &info) != ZX_ERR_STOP) {
103        return ZX_ERR_NOT_FOUND;
104    }
105    return ZX_OK;
106}
107
108constexpr char kBlockDevPath[] = "/dev/class/block/";
109
110zx_status_t OpenBlockPartition(const uint8_t* unique_guid, const uint8_t* type_guid,
111                               zx_duration_t timeout, fbl::unique_fd* out_fd) {
112    ZX_ASSERT(unique_guid || type_guid);
113
114    auto cb = [&](const fbl::unique_fd& fd) {
115        if (TestBlockFilter && TestBlockFilter(fd)) {
116            return true;
117        }
118        uint8_t buf[GUID_LEN];
119        if (type_guid) {
120            if (ioctl_block_get_type_guid(fd.get(), buf, sizeof(buf)) < 0 ||
121                memcmp(buf, type_guid, GUID_LEN) != 0) {
122                return true;
123            }
124        }
125        if (unique_guid) {
126            if (ioctl_block_get_partition_guid(fd.get(), buf, sizeof(buf)) < 0 ||
127                memcmp(buf, unique_guid, GUID_LEN) != 0) {
128                return true;
129            }
130        }
131        return false;
132    };
133
134    return OpenPartition(kBlockDevPath, cb, timeout, out_fd);
135}
136
137constexpr char kSkipBlockDevPath[] = "/dev/class/skip-block/";
138
139zx_status_t OpenSkipBlockPartition(const uint8_t* type_guid, zx_duration_t timeout,
140                                   fbl::unique_fd* out_fd) {
141    ZX_ASSERT(type_guid);
142
143    auto cb = [&](const fbl::unique_fd& fd) {
144        if (TestSkipBlockFilter && TestSkipBlockFilter(fd)) {
145            return true;
146        }
147        skip_block_partition_info_t part_info;
148        if (ioctl_skip_block_get_partition_info(fd.get(), &part_info) < 0 ||
149            memcmp(part_info.partition_guid, type_guid, GUID_LEN) != 0) {
150            return true;
151        }
152        return false;
153    };
154
155    return OpenPartition(kSkipBlockDevPath, cb, timeout, out_fd);
156}
157
158bool HasSkipBlockDevice() {
159    // Our proxy for detected a skip-block device is by checking for the
160    // existence of a device enumerated under the skip-block class.
161    const uint8_t type[GPT_GUID_LEN] = GUID_ZIRCON_A_VALUE;
162    return OpenSkipBlockPartition(type, ZX_SEC(1), nullptr) == ZX_OK;
163}
164
165// Attempts to open and overwrite the first block of the underlying
166// partition. Does not rebind partition drivers.
167//
168// At most one of |unique_guid| and |type_guid| may be nullptr.
169zx_status_t WipeBlockPartition(const uint8_t* unique_guid, const uint8_t* type_guid) {
170    zx_status_t status = ZX_OK;
171    fbl::unique_fd fd;
172    if ((status = OpenBlockPartition(unique_guid, type_guid, ZX_SEC(3), &fd)) != ZX_OK) {
173        ERROR("Warning: Could not open partition to wipe: %s\n",
174              zx_status_get_string(status));
175        return status;
176    }
177
178    block_info_t info;
179    ssize_t result = ZX_OK;
180    if ((result = ioctl_block_get_info(fd.get(), &info)) < 0) {
181        status = static_cast<zx_status_t>(result);
182        ERROR("Warning: Could not acquire block info: %s\n",
183              zx_status_get_string(status));
184        return status;
185    }
186
187    // Overwrite the first block to (hackily) ensure the destroyed partition
188    // doesn't "reappear" in place.
189    char buf[info.block_size];
190    memset(buf, 0, info.block_size);
191
192    if (pwrite(fd.get(), buf, info.block_size, 0) != info.block_size) {
193        ERROR("Warning: Could not write to block device: %s\n", strerror(errno));
194        return ZX_ERR_IO;
195    }
196
197    if ((status = FlushBlockDevice(fd)) != ZX_OK) {
198        ERROR("Warning: Failed to synchronize block device: %s\n",
199              zx_status_get_string(status));
200        return status;
201    }
202
203    return ZX_OK;
204}
205
206} // namespace
207
208fbl::unique_ptr<DevicePartitioner> DevicePartitioner::Create() {
209    fbl::unique_ptr<DevicePartitioner> device_partitioner;
210#if defined(__x86_64__)
211    if ((CrosDevicePartitioner::Initialize(&device_partitioner) == ZX_OK) ||
212        (EfiDevicePartitioner::Initialize(&device_partitioner) == ZX_OK)) {
213        return fbl::move(device_partitioner);
214    }
215#elif defined(__aarch64__)
216    if ((SkipBlockDevicePartitioner::Initialize(&device_partitioner) == ZX_OK) ||
217        (FixedDevicePartitioner::Initialize(&device_partitioner) == ZX_OK)) {
218        return fbl::move(device_partitioner);
219    }
220#endif
221    return nullptr;
222}
223
224/*====================================================*
225 *                  GPT Common                        *
226 *====================================================*/
227
228bool GptDevicePartitioner::FindTargetGptPath(fbl::String* out) {
229    DIR* d = opendir(kBlockDevPath);
230    if (d == nullptr) {
231        ERROR("Cannot inspect block devices\n");
232        return false;
233    }
234    const auto closer = fbl::MakeAutoCall([&]() { closedir(d); });
235
236    struct dirent* de;
237    while ((de = readdir(d)) != nullptr) {
238        fbl::unique_fd fd(openat(dirfd(d), de->d_name, O_RDWR));
239        if (!fd) {
240            continue;
241        }
242        out->Set(PATH_MAX, '\0');
243        ssize_t r = ioctl_device_get_topo_path(fd.get(), const_cast<char*>(out->data()), PATH_MAX);
244        if (r < 0) {
245            continue;
246        }
247
248        block_info_t info;
249        if ((r = ioctl_block_get_info(fd.get(), &info) < 0)) {
250            continue;
251        }
252
253        // TODO(ZX-1344): This is a hack, but practically, will work for our
254        // usage.
255        //
256        // The GPT which will contain an FVM should be the first non-removable
257        // block device that isn't a partition itself.
258        if (!(info.flags & BLOCK_FLAG_REMOVABLE) && strstr(out->c_str(), "part-") == nullptr) {
259            return true;
260        }
261    }
262
263    ERROR("No candidate GPT found\n");
264    return false;
265}
266
267zx_status_t GptDevicePartitioner::InitializeGpt(fbl::unique_ptr<GptDevicePartitioner>* gpt_out) {
268    fbl::String gpt_path;
269    if (!FindTargetGptPath(&gpt_path)) {
270        ERROR("Failed to find GPT\n");
271        return ZX_ERR_NOT_FOUND;
272    }
273    fbl::unique_fd fd(open(gpt_path.c_str(), O_RDWR));
274    if (!fd) {
275        ERROR("Failed to open GPT\n");
276        return ZX_ERR_NOT_FOUND;
277    }
278    block_info_t block_info;
279    ssize_t rc = ioctl_block_get_info(fd.get(), &block_info);
280    if (rc < 0) {
281        ERROR("Couldn't get GPT block info\n");
282        return ZX_ERR_NOT_FOUND;
283    }
284
285    gpt_device_t* gpt;
286    if (gpt_device_init(fd.get(), block_info.block_size, block_info.block_count, &gpt)) {
287        ERROR("Failed to get GPT info\n");
288        return ZX_ERR_BAD_STATE;
289    }
290
291    auto releaser = fbl::MakeAutoCall([&]() { gpt_device_release(gpt); });
292    if (!gpt->valid) {
293        ERROR("Located GPT is invalid; Attempting to initialize\n");
294        if (gpt_partition_remove_all(gpt)) {
295            ERROR("Failed to create empty GPT\n");
296            return ZX_ERR_BAD_STATE;
297        }
298        if (gpt_device_sync(gpt)) {
299            ERROR("Failed to sync empty GPT\n");
300            return ZX_ERR_BAD_STATE;
301        }
302        // Try to rebind the GPT, in case a prior GPT driver was actually
303        // up and running.
304        if ((rc = ioctl_block_rr_part(fd.get())) != ZX_OK) {
305            ERROR("Failed to re-read GPT\n");
306            return ZX_ERR_BAD_STATE;
307        }
308        // Manually re-bind the GPT driver, since it is almost certainly
309        // too late to be noticed by the block watcher.
310        if ((rc = ioctl_device_bind(fd.get(), kGptDriverName, strlen(kGptDriverName))) != ZX_OK) {
311            ERROR("Failed to bind GPT\n");
312            return ZX_ERR_BAD_STATE;
313        }
314    }
315
316    releaser.cancel();
317    *gpt_out = fbl::move(WrapUnique(new GptDevicePartitioner(fbl::move(fd), gpt, block_info)));
318    return ZX_OK;
319}
320
321struct PartitionPosition {
322    size_t start;  // Block, inclusive
323    size_t length; // In Blocks
324};
325
326zx_status_t GptDevicePartitioner::FindFirstFit(size_t bytes_requested, size_t* start_out,
327                                               size_t* length_out) const {
328    LOG("Looking for space\n");
329    // Gather GPT-related information.
330    size_t blocks_requested =
331        (bytes_requested + block_info_.block_size - 1) / block_info_.block_size;
332
333    // Sort all partitions by starting block.
334    // For simplicity, include the 'start' and 'end' reserved spots as
335    // partitions.
336    size_t partition_count = 0;
337    PartitionPosition partitions[PARTITIONS_COUNT + 2];
338    const size_t reserved_blocks = ReservedHeaderBlocks(block_info_.block_size);
339    partitions[partition_count].start = 0;
340    partitions[partition_count++].length = reserved_blocks;
341    partitions[partition_count].start = block_info_.block_count - reserved_blocks;
342    partitions[partition_count++].length = reserved_blocks;
343
344    for (size_t i = 0; i < PARTITIONS_COUNT; i++) {
345        const gpt_partition_t* p = gpt_->partitions[i];
346        if (!p) {
347            continue;
348        }
349        partitions[partition_count].start = p->first;
350        partitions[partition_count].length = p->last - p->first + 1;
351        LOG("Partition seen with start %zu, end %zu (length %zu)\n", p->first, p->last,
352            partitions[partition_count].length);
353        partition_count++;
354    }
355    LOG("Sorting\n");
356    qsort(partitions, partition_count, sizeof(PartitionPosition),
357          [](const void* p1, const void* p2) {
358              ssize_t s1 = static_cast<ssize_t>(static_cast<const PartitionPosition*>(p1)->start);
359              ssize_t s2 = static_cast<ssize_t>(static_cast<const PartitionPosition*>(p2)->start);
360              return static_cast<int>(s1 - s2);
361          });
362
363    // Look for space between the partitions. Since the reserved spots of the
364    // GPT were included in |partitions|, all available space will be located
365    // "between" partitions.
366    for (size_t i = 0; i < partition_count - 1; i++) {
367        const size_t next = partitions[i].start + partitions[i].length;
368        LOG("Partition[%zu] From Block [%zu, %zu) ... (next partition starts at block %zu)\n",
369            i, partitions[i].start, next, partitions[i + 1].start);
370
371        if (next > partitions[i + 1].start) {
372            ERROR("Corrupted GPT\n");
373            return ZX_ERR_IO;
374        }
375        const size_t free_blocks = partitions[i + 1].start - next;
376        LOG("    There are %zu free blocks (%zu requested)\n", free_blocks, blocks_requested);
377        if (free_blocks >= blocks_requested) {
378            *start_out = next;
379            *length_out = free_blocks;
380            return ZX_OK;
381        }
382    }
383    ERROR("No GPT space found\n");
384    return ZX_ERR_NO_RESOURCES;
385}
386
387zx_status_t GptDevicePartitioner::CreateGptPartition(const char* name, uint8_t* type,
388                                                     uint64_t offset, uint64_t blocks,
389                                                     uint8_t* out_guid) {
390    zx_cprng_draw(out_guid, GPT_GUID_LEN);
391
392    zx_status_t status;
393    if ((status = gpt_partition_add(gpt_, name, type, out_guid, offset, blocks, 0))) {
394        ERROR("Failed to add partition\n");
395        return ZX_ERR_IO;
396    }
397    if ((status = gpt_device_sync(gpt_))) {
398        ERROR("Failed to sync GPT\n");
399        return ZX_ERR_IO;
400    }
401    if ((status = gpt_partition_clear(gpt_, offset, 1))) {
402        ERROR("Failed to clear first block of new partition\n");
403        return ZX_ERR_IO;
404    }
405    if ((status = static_cast<zx_status_t>(ioctl_block_rr_part(fd_.get()))) < 0) {
406        ERROR("Failed to rebind GPT\n");
407        return status;
408    }
409
410    return ZX_OK;
411}
412
413zx_status_t GptDevicePartitioner::AddPartition(
414    const char* name, uint8_t* type, size_t minimum_size_bytes,
415    size_t optional_reserve_bytes, fbl::unique_fd* out_fd) {
416
417    uint64_t start, length;
418    zx_status_t status;
419    if ((status = FindFirstFit(minimum_size_bytes, &start, &length)) != ZX_OK) {
420        ERROR("Couldn't find fit\n");
421        return status;
422    }
423    LOG("Found space in GPT - OK %zu @ %zu\n", length, start);
424
425    if (optional_reserve_bytes) {
426        // If we can fulfill the requested size, and we still have space for the
427        // optional reserve section, then we should shorten the amount of blocks
428        // we're asking for.
429        //
430        // This isn't necessary, but it allows growing the GPT later, if necessary.
431        const size_t optional_reserve_blocks = optional_reserve_bytes / block_info_.block_size;
432        if (length - optional_reserve_bytes > (minimum_size_bytes / block_info_.block_size)) {
433            LOG("Space for reserve - OK\n");
434            length -= optional_reserve_blocks;
435        }
436    } else {
437        length = fbl::round_up(minimum_size_bytes, block_info_.block_size) / block_info_.block_size;
438    }
439    LOG("Final space in GPT - OK %zu @ %zu\n", length, start);
440
441    uint8_t guid[GPT_GUID_LEN];
442    if ((status = CreateGptPartition(name, type, start, length, guid)) != ZX_OK) {
443        return status;
444    }
445    LOG("Added partition, waiting for bind\n");
446
447    if ((status = OpenBlockPartition(guid, type, ZX_SEC(5), out_fd)) != ZX_OK) {
448        ERROR("Added partition, waiting for bind - NOT FOUND\n");
449        return status;
450    }
451    LOG("Added partition, waiting for bind - OK\n");
452    return ZX_OK;
453}
454
455zx_status_t GptDevicePartitioner::FindPartition(FilterCallback filter, gpt_partition_t** out,
456                                                fbl::unique_fd* out_fd) {
457    for (size_t i = 0; i < PARTITIONS_COUNT; i++) {
458        gpt_partition_t* p = gpt_->partitions[i];
459        if (!p) {
460            continue;
461        }
462
463        if (filter(*p)) {
464            LOG("Found partition in GPT, partition %zu\n", i);
465            if (out) {
466                *out = p;
467            }
468            if (out_fd) {
469                zx_status_t status;
470                if ((status = OpenBlockPartition(p->guid, p->type, ZX_SEC(5), out_fd)) != ZX_OK) {
471                    ERROR("Couldn't open partition\n");
472                    return status;
473                }
474            }
475            return ZX_OK;
476        }
477    }
478    return ZX_ERR_NOT_FOUND;
479}
480
481zx_status_t GptDevicePartitioner::FindPartition(FilterCallback filter,
482                                                fbl::unique_fd* out_fd) const {
483    for (size_t i = 0; i < PARTITIONS_COUNT; i++) {
484        const gpt_partition_t* p = gpt_->partitions[i];
485        if (!p) {
486            continue;
487        }
488
489        if (filter(*p)) {
490            LOG("Found partition in GPT, partition %zu\n", i);
491            if (out_fd) {
492                zx_status_t status;
493                if ((status = OpenBlockPartition(p->guid, p->type, ZX_SEC(5), out_fd)) != ZX_OK) {
494                    ERROR("Couldn't open partition\n");
495                    return status;
496                }
497            }
498            return ZX_OK;
499        }
500    }
501    return ZX_ERR_NOT_FOUND;
502}
503
504zx_status_t GptDevicePartitioner::WipePartitions(FilterCallback filter) {
505    bool modify = false;
506    for (size_t i = 0; i < PARTITIONS_COUNT; i++) {
507        const gpt_partition_t* p = gpt_->partitions[i];
508        if (!p) {
509            continue;
510        }
511        if (!filter(*p)) {
512            continue;
513        }
514
515        modify = true;
516
517        // Ignore the return status; wiping is a best-effort approach anyway.
518        WipeBlockPartition(p->guid, p->type);
519
520        if (gpt_partition_remove(gpt_, p->guid)) {
521            ERROR("Warning: Could not remove partition\n");
522        } else {
523            // If we successfully clear the partition, then all subsequent
524            // partitions get shifted down. If we just deleted partition 'i',
525            // we now need to look at partition 'i' again, since it's now
526            // occupied by what was in 'i+1'.
527            i--;
528        }
529    }
530    if (modify) {
531        gpt_device_sync(gpt_);
532        LOG("Immediate reboot strongly recommended\n");
533    }
534    ioctl_block_rr_part(fd_.get());
535    return ZX_OK;
536}
537
538/*====================================================*
539 *                 EFI SPECIFIC                       *
540 *====================================================*/
541
542zx_status_t EfiDevicePartitioner::Initialize(fbl::unique_ptr<DevicePartitioner>* partitioner) {
543    fbl::unique_ptr<GptDevicePartitioner> gpt;
544    zx_status_t status;
545    if ((status = GptDevicePartitioner::InitializeGpt(&gpt)) != ZX_OK) {
546        return status;
547    }
548    if (is_cros(gpt->GetGpt())) {
549        ERROR("Use CrOS Device Partitioner.");
550        return ZX_ERR_NOT_SUPPORTED;
551    }
552
553    LOG("Successfully intitialized EFI Device Partitioner\n");
554    *partitioner = fbl::move(WrapUnique(new EfiDevicePartitioner(fbl::move(gpt))));
555    return ZX_OK;
556}
557
558// Name used by previous Fuchsia Installer.
559constexpr char kOldEfiName[] = "EFI";
560
561// Name used for EFI partitions added by paver.
562constexpr char kEfiName[] = "EFI Gigaboot";
563
564zx_status_t EfiDevicePartitioner::AddPartition(Partition partition_type, fbl::unique_fd* out_fd) {
565    const char* name;
566    uint8_t type[GPT_GUID_LEN];
567    size_t minimum_size_bytes = 0;
568    size_t optional_reserve_bytes = 0;
569
570    switch (partition_type) {
571    case Partition::kEfi: {
572        const uint8_t efi_type[GPT_GUID_LEN] = GUID_EFI_VALUE;
573        memcpy(type, efi_type, GPT_GUID_LEN);
574        minimum_size_bytes = 1LU * (1 << 30);
575        name = kEfiName;
576        break;
577    }
578    case Partition::kFuchsiaVolumeManager: {
579        const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE;
580        memcpy(type, fvm_type, GPT_GUID_LEN);
581        minimum_size_bytes = 8LU * (1 << 30);
582        name = kFvmPartitionName;
583        break;
584    }
585    default:
586        ERROR("EFI partitioner cannot add unknown partition type\n");
587        return ZX_ERR_NOT_SUPPORTED;
588    }
589
590    return gpt_->AddPartition(name, type, minimum_size_bytes,
591                              optional_reserve_bytes, out_fd);
592}
593
594bool EfiDevicePartitioner::FilterZirconPartition(const block_info_t& info,
595                                                 const gpt_partition_t& part) {
596    const uint8_t efi_type[GPT_GUID_LEN] = GUID_EFI_VALUE;
597    char cstring_name[GPT_NAME_LEN];
598    utf16_to_cstring(cstring_name, reinterpret_cast<const uint16_t*>(part.name), GPT_NAME_LEN);
599    // Old EFI: Installed by the legacy Fuchsia installer, identified by
600    // large size and "EFI" label.
601    constexpr unsigned int k512MB = (1LU << 29);
602    const bool old_efi = strncmp(cstring_name, kOldEfiName, strlen(kOldEfiName)) == 0 &&
603                         ((part.last - part.first + 1) * info.block_size) > k512MB;
604    // Disk-paved EFI: Identified by "EFI Gigaboot" label.
605    const bool new_efi = strncmp(cstring_name, kEfiName, strlen(kEfiName)) == 0;
606    return memcmp(part.type, efi_type, GPT_GUID_LEN) == 0 && (old_efi || new_efi);
607}
608
609zx_status_t EfiDevicePartitioner::FindPartition(Partition partition_type,
610                                                fbl::unique_fd* out_fd) const {
611    block_info_t info;
612    zx_status_t status;
613    if ((status = gpt_->GetBlockInfo(&info)) != ZX_OK) {
614        ERROR("Unable to get block info\n");
615        return ZX_ERR_IO;
616    }
617
618    switch (partition_type) {
619    case Partition::kEfi: {
620        const auto filter = [&info](const gpt_partition_t& part) {
621            return FilterZirconPartition(info, part);
622        };
623        return gpt_->FindPartition(filter, out_fd);
624    }
625    case Partition::kFuchsiaVolumeManager:
626        return gpt_->FindPartition(FvmFilterCallback, out_fd);
627
628    default:
629        ERROR("EFI partitioner cannot find unknown partition type\n");
630        return ZX_ERR_NOT_SUPPORTED;
631    }
632}
633
634zx_status_t EfiDevicePartitioner::WipePartitions(const fbl::Vector<Partition>& partitions) {
635    const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE;
636    const uint8_t install_type[GPT_GUID_LEN] = GUID_INSTALL_VALUE;
637    const uint8_t system_type[GPT_GUID_LEN] = GUID_SYSTEM_VALUE;
638    const uint8_t blob_type[GPT_GUID_LEN] = GUID_BLOB_VALUE;
639    const uint8_t data_type[GPT_GUID_LEN] = GUID_DATA_VALUE;
640
641    block_info_t info;
642    zx_status_t status;
643    if ((status = gpt_->GetBlockInfo(&info)) != ZX_OK) {
644        ERROR("Unable to get block info\n");
645        return ZX_ERR_IO;
646    }
647
648    fbl::Vector<const uint8_t*> partition_list;
649    bool efi = false;
650    for (const Partition& partition_type : partitions) {
651        switch (partition_type) {
652        case Partition::kEfi: {
653            // Special case.
654            efi = true;
655            break;
656        }
657        case Partition::kKernelC:
658            break;
659        case Partition::kFuchsiaVolumeManager:
660            partition_list.push_back(fvm_type);
661            break;
662        case Partition::kInstallType:
663            partition_list.push_back(install_type);
664            break;
665        case Partition::kSystem:
666            partition_list.push_back(system_type);
667            break;
668        case Partition::kBlob:
669            partition_list.push_back(blob_type);
670            break;
671        case Partition::kData:
672            partition_list.push_back(data_type);
673            break;
674        default:
675            return ZX_ERR_NOT_SUPPORTED;
676        }
677    }
678
679    // Early return if nothing to wipe.
680    if (partition_list.is_empty() && !efi) {
681        return ZX_OK;
682    }
683
684    const auto filter = [&info, &partition_list, efi](const gpt_partition_t& part) {
685        for (const auto& type : partition_list) {
686            if (memcmp(part.type, type, GPT_GUID_LEN) == 0)
687                return true;
688        }
689        if (efi) {
690            return FilterZirconPartition(info, part);
691        }
692        return false;
693    };
694    return gpt_->WipePartitions(filter);
695}
696
697zx_status_t EfiDevicePartitioner::GetBlockSize(const fbl::unique_fd& device_fd,
698                                               uint32_t* block_size) const {
699    block_info_t info;
700    zx_status_t status = gpt_->GetBlockInfo(&info);
701    if (status == ZX_OK) {
702        *block_size = info.block_size;
703    }
704    return status;
705}
706
707/*====================================================*
708 *                CROS SPECIFIC                       *
709 *====================================================*/
710
711zx_status_t CrosDevicePartitioner::Initialize(fbl::unique_ptr<DevicePartitioner>* partitioner) {
712    fbl::unique_ptr<GptDevicePartitioner> gpt_partitioner;
713    zx_status_t status;
714    if ((status = GptDevicePartitioner::InitializeGpt(&gpt_partitioner)) != ZX_OK) {
715        return status;
716    }
717
718    gpt_device_t* gpt = gpt_partitioner->GetGpt();
719    if (!is_cros(gpt)) {
720        return ZX_ERR_NOT_FOUND;
721    }
722
723    block_info_t info;
724    gpt_partitioner->GetBlockInfo(&info);
725
726    if (!is_ready_to_pave(gpt, &info, SZ_ZX_PART)) {
727        if ((status = config_cros_for_fuchsia(gpt, &info, SZ_ZX_PART)) != ZX_OK) {
728            ERROR("Failed to configure CrOS for Fuchsia.\n");
729            return status;
730        }
731        gpt_device_sync(gpt);
732        ioctl_block_rr_part(gpt_partitioner->GetFd());
733    }
734
735    LOG("Successfully initialized CrOS Device Partitioner\n");
736    *partitioner = fbl::move(WrapUnique(new CrosDevicePartitioner(fbl::move(gpt_partitioner))));
737    return ZX_OK;
738}
739
740constexpr char kZirconAName[] = "ZIRCON-A";
741// TODO(raggi): near future - constexpr char kZirconBName[] = "ZIRCON-B";
742// TODO(raggi): near future - constexpr char kZirconRName[] = "ZIRCON-R";
743
744zx_status_t CrosDevicePartitioner::AddPartition(Partition partition_type,
745                                                fbl::unique_fd* out_fd) {
746    const char* name;
747    uint8_t type[GPT_GUID_LEN];
748    size_t minimum_size_bytes = 0;
749    size_t optional_reserve_bytes = 0;
750
751    switch (partition_type) {
752    case Partition::kKernelC: {
753        const uint8_t kernc_type[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE;
754        memcpy(type, kernc_type, GPT_GUID_LEN);
755        minimum_size_bytes = 64LU * (1 << 20);
756        name = kZirconAName;
757        break;
758    }
759    case Partition::kFuchsiaVolumeManager: {
760        const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE;
761        memcpy(type, fvm_type, GPT_GUID_LEN);
762        minimum_size_bytes = 8LU * (1 << 30);
763        name = kFvmPartitionName;
764        break;
765    }
766    default:
767        ERROR("Cros partitioner cannot add unknown partition type\n");
768        return ZX_ERR_NOT_SUPPORTED;
769    }
770    return gpt_->AddPartition(name, type, minimum_size_bytes,
771                              optional_reserve_bytes, out_fd);
772}
773
774zx_status_t CrosDevicePartitioner::FindPartition(Partition partition_type,
775                                                 fbl::unique_fd* out_fd) const {
776    switch (partition_type) {
777    case Partition::kKernelC: {
778        const auto filter = [](const gpt_partition_t& part) {
779            return KernelFilterCallback(part, kZirconAName);
780        };
781        return gpt_->FindPartition(filter, out_fd);
782    }
783    case Partition::kFuchsiaVolumeManager:
784        return gpt_->FindPartition(FvmFilterCallback, out_fd);
785
786    default:
787        ERROR("Cros partitioner cannot find unknown partition type\n");
788        return ZX_ERR_NOT_SUPPORTED;
789    }
790}
791
792zx_status_t CrosDevicePartitioner::FinalizePartition(Partition partition_type) {
793    // Special partition finalization is only necessary for Zircon partitions.
794    if (partition_type != Partition::kKernelC) {
795        return ZX_OK;
796    }
797
798    uint8_t top_priority = 0;
799
800    const uint8_t kern_type[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE;
801    constexpr char kPrefix[] = "ZIRCON-";
802    uint16_t zircon_prefix[strlen(kPrefix)*2];
803    cstring_to_utf16(&zircon_prefix[0], kPrefix, strlen(kPrefix));
804
805    for(size_t i = 0; i < PARTITIONS_COUNT; ++i) {
806        const gpt_partition_t* part = gpt_->GetGpt()->partitions[i];
807        if (part == NULL) {
808            continue;
809        }
810        if (memcmp(part->type, kern_type, GPT_GUID_LEN)) {
811            continue;
812        }
813        if (memcmp(part->name, zircon_prefix, strlen(kPrefix)*2)) {
814            const uint8_t priority = gpt_cros_attr_get_priority(part->flags);
815            if (priority > top_priority) {
816                top_priority = priority;
817            }
818        }
819    }
820
821    const auto filter_zircona = [](const gpt_partition_t& part) {
822        return KernelFilterCallback(part, kZirconAName);
823    };
824    zx_status_t status;
825    gpt_partition_t* partition;
826    if ((status = gpt_->FindPartition(filter_zircona, &partition, nullptr)) != ZX_OK) {
827        ERROR("Cannot find %s partition\n", kZirconAName);
828        return status;
829    }
830
831    // Priority for Zircon A set to higher priority than all other kernels.
832    if (top_priority == UINT8_MAX) {
833        ERROR("Cannot set CrOS partition priority higher than other kernels\n");
834        return ZX_ERR_OUT_OF_RANGE;
835    }
836
837    // TODO(raggi): when other (B/R) partitions are paved, set their priority
838    // appropriately as well.
839
840    if (gpt_cros_attr_set_priority(&partition->flags, ++top_priority) != 0) {
841        ERROR("Cannot set CrOS partition priority for ZIRCON-A\n");
842        return ZX_ERR_OUT_OF_RANGE;
843    }
844    // Successful set to 'true' to encourage the bootloader to
845    // use this partition.
846    gpt_cros_attr_set_successful(&partition->flags, true);
847    // Maximize the number of attempts to boot this partition before
848    // we fall back to a different kernel.
849    if (gpt_cros_attr_set_tries(&partition->flags, 15) != 0) {
850        ERROR("Cannot set CrOS partition 'tries' for KERN-C\n");
851        return ZX_ERR_OUT_OF_RANGE;
852    }
853    gpt_device_sync(gpt_->GetGpt());
854    return ZX_OK;
855}
856
857zx_status_t CrosDevicePartitioner::WipePartitions(const fbl::Vector<Partition>& partitions) {
858    const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE;
859    const uint8_t install_type[GPT_GUID_LEN] = GUID_INSTALL_VALUE;
860    const uint8_t system_type[GPT_GUID_LEN] = GUID_SYSTEM_VALUE;
861    const uint8_t blob_type[GPT_GUID_LEN] = GUID_BLOB_VALUE;
862    const uint8_t data_type[GPT_GUID_LEN] = GUID_DATA_VALUE;
863
864    // TODO(raggi): add logic here to cleanup the kernc, rootc, and a/b/r partitions.
865
866    fbl::Vector<const uint8_t*> partition_list;
867    for (const auto& partition_type : partitions) {
868        switch (partition_type) {
869        case Partition::kEfi:
870            continue;
871        case Partition::kFuchsiaVolumeManager:
872            partition_list.push_back(fvm_type);
873            break;
874        case Partition::kInstallType:
875            partition_list.push_back(install_type);
876            break;
877        case Partition::kSystem:
878            partition_list.push_back(system_type);
879            break;
880        case Partition::kBlob:
881            partition_list.push_back(blob_type);
882            break;
883        case Partition::kData:
884            partition_list.push_back(data_type);
885            break;
886        default:
887            return ZX_ERR_NOT_SUPPORTED;
888        }
889    }
890
891    auto filter = [&](const gpt_partition_t& part) {
892        for (const auto& type : partition_list) {
893            if (memcmp(part.type, type, GPT_GUID_LEN) == 0) {
894                return true;
895            }
896        }
897        return false;
898    };
899    return gpt_->WipePartitions(filter);
900}
901
902zx_status_t CrosDevicePartitioner::GetBlockSize(const fbl::unique_fd& device_fd,
903                                               uint32_t* block_size) const {
904    block_info_t info;
905    zx_status_t status = gpt_->GetBlockInfo(&info);
906    if (status == ZX_OK) {
907        *block_size = info.block_size;
908    }
909    return status;
910}
911
912/*====================================================*
913 *               FIXED PARTITION MAP                  *
914 *====================================================*/
915
916zx_status_t FixedDevicePartitioner::Initialize(fbl::unique_ptr<DevicePartitioner>* partitioner) {
917    if (HasSkipBlockDevice()) {
918        return ZX_ERR_NOT_SUPPORTED;
919    }
920    LOG("Successfully intitialized FixedDevicePartitioner Device Partitioner\n");
921    *partitioner = fbl::move(WrapUnique(new FixedDevicePartitioner));
922    return ZX_OK;
923}
924
925zx_status_t FixedDevicePartitioner::FindPartition(Partition partition_type,
926                                                  fbl::unique_fd* out_fd) const {
927    uint8_t type[GPT_GUID_LEN];
928
929    switch (partition_type) {
930    case Partition::kZirconA: {
931        const uint8_t zircon_a_type[GPT_GUID_LEN] = GUID_ZIRCON_A_VALUE;
932        memcpy(type, zircon_a_type, GPT_GUID_LEN);
933        break;
934    }
935    case Partition::kZirconB: {
936        const uint8_t zircon_b_type[GPT_GUID_LEN] = GUID_ZIRCON_B_VALUE;
937        memcpy(type, zircon_b_type, GPT_GUID_LEN);
938        break;
939    }
940    case Partition::kZirconR: {
941        const uint8_t zircon_r_type[GPT_GUID_LEN] = GUID_ZIRCON_R_VALUE;
942        memcpy(type, zircon_r_type, GPT_GUID_LEN);
943        break;
944    }
945    case Partition::kFuchsiaVolumeManager: {
946        const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE;
947        memcpy(type, fvm_type, GPT_GUID_LEN);
948        break;
949    }
950    default:
951        ERROR("partition_type is invalid!\n");
952        return ZX_ERR_NOT_SUPPORTED;
953    }
954
955    return OpenBlockPartition(nullptr, type, ZX_SEC(5), out_fd);
956}
957
958zx_status_t FixedDevicePartitioner::WipePartitions(const fbl::Vector<Partition>& partitions) {
959    const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE;
960    zx_status_t status;
961    for (const Partition& partition_type : partitions) {
962        switch (partition_type) {
963        case Partition::kFuchsiaVolumeManager:
964            if ((status = WipeBlockPartition(nullptr, fvm_type)) != ZX_OK) {
965                ERROR("Failed to wipe FVM.\n");
966            } else {
967                LOG("Wiped FVM successfully.\n");
968            }
969            break;
970        default:
971            // All non-FVM partitions are currently ignored on FixedDevices.
972            continue;
973        }
974    }
975    LOG("Immediate reboot strongly recommended\n");
976    return ZX_OK;
977}
978
979zx_status_t FixedDevicePartitioner::GetBlockSize(const fbl::unique_fd& device_fd,
980                                                 uint32_t* block_size) const {
981    ssize_t r;
982    block_info_t block_info;
983    if ((r = ioctl_block_get_info(device_fd.get(), &block_info) < 0)) {
984        return ZX_ERR_IO;
985    }
986    *block_size = block_info.block_size;
987    return ZX_OK;
988}
989
990/*====================================================*
991 *                SKIP BLOCK SPECIFIC                 *
992 *====================================================*/
993
994zx_status_t SkipBlockDevicePartitioner::Initialize(
995    fbl::unique_ptr<DevicePartitioner>* partitioner) {
996
997    if (!HasSkipBlockDevice()) {
998        return ZX_ERR_NOT_SUPPORTED;
999    }
1000    LOG("Successfully intitialized SkipBlockDevicePartitioner Device Partitioner\n");
1001    *partitioner = fbl::move(WrapUnique(new SkipBlockDevicePartitioner));
1002    return ZX_OK;
1003}
1004
1005zx_status_t SkipBlockDevicePartitioner::FindPartition(Partition partition_type,
1006                                                      fbl::unique_fd* out_fd) const {
1007    uint8_t type[GPT_GUID_LEN];
1008
1009    switch (partition_type) {
1010    case Partition::kBootloader: {
1011        const uint8_t bootloader_type[GPT_GUID_LEN] = GUID_BOOTLOADER_VALUE;
1012        memcpy(type, bootloader_type, GPT_GUID_LEN);
1013        break;
1014    }
1015    case Partition::kZirconA: {
1016        const uint8_t zircon_a_type[GPT_GUID_LEN] = GUID_ZIRCON_A_VALUE;
1017        memcpy(type, zircon_a_type, GPT_GUID_LEN);
1018        break;
1019    }
1020    case Partition::kZirconB: {
1021        const uint8_t zircon_b_type[GPT_GUID_LEN] = GUID_ZIRCON_B_VALUE;
1022        memcpy(type, zircon_b_type, GPT_GUID_LEN);
1023        break;
1024    }
1025    case Partition::kZirconR: {
1026        const uint8_t zircon_r_type[GPT_GUID_LEN] = GUID_ZIRCON_R_VALUE;
1027        memcpy(type, zircon_r_type, GPT_GUID_LEN);
1028        break;
1029    }
1030    case Partition::kFuchsiaVolumeManager: {
1031        const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE;
1032        memcpy(type, fvm_type, GPT_GUID_LEN);
1033        // FVM partition is managed so it should expose a normal block device.
1034        return OpenBlockPartition(nullptr, type, ZX_SEC(5), out_fd);
1035    }
1036    default:
1037        ERROR("partition_type is invalid!\n");
1038        return ZX_ERR_NOT_SUPPORTED;
1039    }
1040
1041    return OpenSkipBlockPartition(type, ZX_SEC(5), out_fd);
1042}
1043
1044zx_status_t SkipBlockDevicePartitioner::WipePartitions(const fbl::Vector<Partition>& partitions) {
1045    const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE;
1046    zx_status_t status;
1047    for (const Partition& partition_type : partitions) {
1048        switch (partition_type) {
1049        case Partition::kFuchsiaVolumeManager:
1050            if ((status = WipeBlockPartition(nullptr, fvm_type)) != ZX_OK) {
1051                ERROR("Failed to wipe FVM.\n");
1052            } else {
1053                LOG("Wiped FVM successfully.\n");
1054            }
1055            break;
1056        default:
1057            // All non-FVM partitions are currently ignored on SkipBlockDevices.
1058            continue;
1059        }
1060    }
1061    LOG("Immediate reboot strongly recommended\n");
1062    return ZX_OK;
1063}
1064
1065zx_status_t SkipBlockDevicePartitioner::GetBlockSize(const fbl::unique_fd& device_fd,
1066                                                     uint32_t* block_size) const {
1067    // Just in case we are trying to get info about FVM.
1068    ssize_t r;
1069    block_info_t block_info;
1070    if ((r = ioctl_block_get_info(device_fd.get(), &block_info) >= 0)) {
1071        *block_size = block_info.block_size;
1072        return ZX_OK;
1073    }
1074
1075    skip_block_partition_info_t info;
1076    if ((r = ioctl_skip_block_get_partition_info(device_fd.get(), &info) < 0)) {
1077        return ZX_ERR_IO;
1078    }
1079    *block_size = static_cast<uint32_t>(info.block_size_bytes);
1080
1081    return ZX_OK;
1082}
1083
1084} // namespace paver
1085