[disk-pave] Add support for updating kern-C's partition flags

Change-Id: I9af7d3986b0fa50342edaa24a6d8a30ceb255984
diff --git a/system/uapp/disk-pave/disk-pave.cpp b/system/uapp/disk-pave/disk-pave.cpp
index 80fd11d..098f214 100644
--- a/system/uapp/disk-pave/disk-pave.cpp
+++ b/system/uapp/disk-pave/disk-pave.cpp
@@ -51,9 +51,9 @@
 // FVM, not, for example, a GPT or MBR.
 //
 // |out| is true if |fd| is a VPartition, else false.
-zx_status_t fvm_is_vpartition(int fd, bool* out) {
+zx_status_t fvm_is_vpartition(const fbl::unique_fd& fd, bool* out) {
     char path[PATH_MAX];
-    ssize_t r = ioctl_device_get_topo_path(fd, path, sizeof(path));
+    ssize_t r = ioctl_device_get_topo_path(fd.get(), path, sizeof(path));
     if (r < 0) {
         return ZX_ERR_IO;
     }
@@ -80,15 +80,15 @@
             extent * sizeof(fvm::extent_descriptor_t));
 }
 
-zx_status_t register_fast_block_io(int fd, zx_handle_t vmo,
+zx_status_t register_fast_block_io(const fbl::unique_fd& fd, zx_handle_t vmo,
                                    txnid_t* txnid_out, vmoid_t* vmoid_out,
                                    fifo_client_t** client_out) {
     zx::fifo fifo;
-    if (ioctl_block_get_fifos(fd, fifo.reset_and_get_address()) < 0) {
+    if (ioctl_block_get_fifos(fd.get(), fifo.reset_and_get_address()) < 0) {
         fprintf(stderr, "[register_fast_block_io] Couldn't attach fifo to partition\n");
         return ZX_ERR_IO;
     }
-    if (ioctl_block_alloc_txn(fd, txnid_out) < 0) {
+    if (ioctl_block_alloc_txn(fd.get(), txnid_out) < 0) {
         fprintf(stderr, "[register_fast_block_io] Couldn't allocate transaction\n");
         return ZX_ERR_IO;
     }
@@ -99,7 +99,7 @@
         return ZX_ERR_IO;
     }
     zx_handle_t h = dup.release();
-    if (ioctl_block_attach_vmo(fd, &h, vmoid_out) < 0) {
+    if (ioctl_block_attach_vmo(fd.get(), &h, vmoid_out) < 0) {
         fprintf(stderr, "[register_fast_block_io] Couldn't attach VMO\n");
         return ZX_ERR_IO;
     }
@@ -113,7 +113,7 @@
 // Stream an FVM partition to disk.
 zx_status_t stream_fvm_partition(partition_info* part, MappedVmo* mvmo,
                                  fifo_client_t* client, size_t slice_size,
-                                 block_fifo_request_t* request, int src_fd) {
+                                 block_fifo_request_t* request, const fbl::unique_fd& src_fd) {
     const size_t vmo_cap = mvmo->GetSize();
     for (size_t e = 0; e < part->pd->extent_count; e++) {
         printf("[stream_fvm_partition] Writing extent %zu... \n", e);
@@ -125,7 +125,7 @@
         while (bytes_left > 0) {
             ssize_t r;
             size_t vmo_sz = 0;
-            while ((r = read(src_fd, &reinterpret_cast<uint8_t*>(mvmo->GetData())[vmo_sz],
+            while ((r = read(src_fd.get(), &reinterpret_cast<uint8_t*>(mvmo->GetData())[vmo_sz],
                              fbl::min(bytes_left, vmo_cap - vmo_sz))) > 0) {
                 vmo_sz += r;
                 bytes_left -= r;
@@ -183,14 +183,14 @@
 
 // Stream a raw (non-FVM) partition to disk.
 zx_status_t stream_partition(MappedVmo* mvmo, fifo_client_t* client,
-                             block_fifo_request_t* request, int src_fd) {
+                             block_fifo_request_t* request, const fbl::unique_fd& src_fd) {
     const size_t vmo_cap = mvmo->GetSize();
     size_t offset = 0;
 
     while (true) {
         ssize_t r;
         size_t vmo_sz = 0;
-        while ((r = read(src_fd, &reinterpret_cast<uint8_t*>(mvmo->GetData())[vmo_sz],
+        while ((r = read(src_fd.get(), &reinterpret_cast<uint8_t*>(mvmo->GetData())[vmo_sz],
                          vmo_cap - vmo_sz)) > 0) {
             vmo_sz += r;
             if (vmo_cap - vmo_sz == 0) {
@@ -264,14 +264,14 @@
 }
 
 // Returns |ZX_OK| if |part_fd| is a child of |fvm_fd|.
-zx_status_t fvm_partition_match(int fvm_fd, int part_fd) {
+zx_status_t fvm_partition_match(const fbl::unique_fd& fvm_fd, const fbl::unique_fd& part_fd) {
     char fvm_path[PATH_MAX];
     char part_path[PATH_MAX];
     ssize_t r;
-    if ((r = ioctl_device_get_topo_path(fvm_fd, fvm_path, sizeof(fvm_path))) < 0) {
+    if ((r = ioctl_device_get_topo_path(fvm_fd.get(), fvm_path, sizeof(fvm_path))) < 0) {
         fprintf(stderr, "[fvm_partition_match] Couldn't get topological path of FVM\n");
         return static_cast<zx_status_t>(r);
-    } else if ((r = ioctl_device_get_topo_path(part_fd, part_path, sizeof(part_path))) < 0) {
+    } else if ((r = ioctl_device_get_topo_path(part_fd.get(), part_path, sizeof(part_path))) < 0) {
         fprintf(stderr, "[fvm_partition_match] Couldn't get topological path of partition\n");
         return static_cast<zx_status_t>(r);
     }
@@ -357,10 +357,10 @@
 
         if (parts[p].old_part) {
             bool is_vpartition;
-            if (fvm_is_vpartition(parts[p].old_part.get(), &is_vpartition)) {
+            if (fvm_is_vpartition(parts[p].old_part, &is_vpartition)) {
                 fprintf(stderr, "[fvm_stream_partitions] Couldn't confirm old vpartition type\n");
                 return ZX_ERR_IO;
-            } else if (fvm_partition_match(fvm_fd.get(), parts[p].old_part.get()) != ZX_OK) {
+            } else if (fvm_partition_match(fvm_fd, parts[p].old_part) != ZX_OK) {
                 fprintf(stderr, "Streaming a partition type which also exists outside FVM\n");
                 fprintf(stderr, "Please run 'install-disk-image wipe' to clear your partitions\n");
                 return ZX_ERR_BAD_STATE;
@@ -451,7 +451,7 @@
         txnid_t txnid;
         vmoid_t vmoid;
         fifo_client_t* client;
-        zx_status_t status = register_fast_block_io(parts[p].new_part.get(),
+        zx_status_t status = register_fast_block_io(parts[p].new_part,
                                                     mvmo->GetVmo(), &txnid,
                                                     &vmoid, &client);
         if (status != ZX_OK) {
@@ -466,7 +466,7 @@
 
         printf("[fvm_stream_partitions] streaming partition %zu\n", p);
         status = stream_fvm_partition(&parts[p], mvmo.get(), client,
-                                      hdr.slice_size, &request, src_fd.get());
+                                      hdr.slice_size, &request, src_fd);
         printf("[fvm_stream_partitions] done streaming partition %zu\n", p);
         block_fifo_release_client(client);
         if (status != ZX_OK) {
@@ -600,17 +600,10 @@
 // Returns the |start_out| block and |length_out| blocks, indicating
 // how much space was found, on success. This may be larger than
 // the number of bytes requested.
-zx_status_t find_first_fit(const char* gpt_path, size_t bytes_requested,
-                           size_t* start_out, size_t* length_out) {
+zx_status_t find_first_fit(const gpt_device_t* gpt, const fbl::unique_fd& gpt_fd,
+                           size_t bytes_requested, size_t* start_out, size_t* length_out) {
     printf("[find_first_fit]\n");
     // Gather GPT-related information.
-    fbl::unique_fd gpt_fd;
-    gpt_device_t* gpt;
-    zx_status_t status;
-    if ((status = initialize_gpt(gpt_path, &gpt_fd, &gpt)) != ZX_OK) {
-        fprintf(stderr, "[find_first_fit] Cannot initialize GPT\n");
-        return status;
-    }
     block_info_t info;
     ssize_t rc = ioctl_block_get_info(gpt_fd.get(), &info);
     if (rc < 0) {
@@ -679,6 +672,7 @@
 using PartitionFilterCb = bool (*)(size_t gpt_index, const uint8_t type[GPT_GUID_LEN],
                                    const uint8_t name[GPT_NAME_LEN]);
 
+// Optional callback.
 // Returns "true" if a new partition should be created.
 // Only called if one doesn't already exist.
 //
@@ -686,74 +680,83 @@
 using PartitionCreateCb = bool (*)(uint8_t* type_out, uint64_t* size_bytes_out,
                                    const char** name_out);
 
-// Returns a file descriptor to an partition which can be paved,
-// allocating the partition if necessary.
-template <PartitionFilterCb filterCb, PartitionCreateCb createCb>
-zx_status_t partition_find_or_add(fbl::unique_fd *out_fd) {
-    printf("[partition_find_or_add]\n");
-    char gpt_path[PATH_MAX];
-    if (find_target_gpt(gpt_path)) {
-        return ZX_ERR_IO;
-    }
+// Optional callback.
+// Returns "true" if the partition has been updated.
+//
+// Allows the partition updater to modify attributes of the
+// partition (like flags) after writing it to disk.
+using PartitionFinalizeCb = bool (*)(gpt_partition_t* partition);
 
-    fbl::unique_fd gpt_fd;
-    gpt_device_t* gpt;
-    zx_status_t status;
-    if ((status = initialize_gpt(gpt_path, &gpt_fd, &gpt)) != ZX_OK) {
-        return status;
-    }
-
-    block_info_t info;
-    ssize_t rc = ioctl_block_get_info(gpt_fd.get(), &info);
-    if (rc < 0) {
-        fprintf(stderr, "[partition_find_or_add] Cannot acquire GPT info\n");
-        return static_cast<zx_status_t>(rc);
-    }
-
-    zx_status_t r = 0;
+// Returns a file descriptor to a partition which can be paved,
+// if one exists.
+template <PartitionFilterCb filterCb>
+zx_status_t partition_find(gpt_device_t* gpt, gpt_partition_t** out, fbl::unique_fd* out_fd) {
     for (size_t i = 0; i < PARTITIONS_COUNT; i++) {
         gpt_partition_t* p = gpt->partitions[i];
         if (!p) {
             continue;
         }
 
+        static_assert(filterCb != nullptr, "Filter callback required to find partition");
         if (filterCb(i, p->type, p->name)) {
-            printf("[partition_find_or_add] Found partition in GPT, partition %zu\n", i);
-            out_fd->reset(open_partition(p->guid, p->type, ZX_SEC(5), nullptr));
-            if (!*out_fd) {
-                fprintf(stderr, "[partition_find_or_add] Couldn't open partition\n");
-                return ZX_ERR_IO;
+            printf("[partition_find] Found partition in GPT, partition %zu\n", i);
+            if (out) {
+                *out = p;
+            }
+            if (out_fd) {
+                out_fd->reset(open_partition(p->guid, p->type, ZX_SEC(5), nullptr));
+                if (!*out_fd) {
+                    fprintf(stderr, "[partition_find] Couldn't open partition\n");
+                    return ZX_ERR_IO;
+                }
             }
             return ZX_OK;
         }
     }
+    return ZX_ERR_NOT_FOUND;
+}
 
+// Returns a file descriptor to a partition which can be paved,
+// creating it.
+// Assumes that the partition does not already exist.
+template <PartitionCreateCb createCb>
+zx_status_t partition_add(gpt_device_t* gpt, fbl::unique_fd gpt_fd, fbl::unique_fd *out_fd) {
     const char* name;
     uint8_t type[GPT_GUID_LEN];
     size_t minimumSizeBytes = 0;
+    static_assert(createCb != nullptr, "Create callback required to add partition");
     if (!createCb(type, &minimumSizeBytes, &name)) {
         return ZX_ERR_NOT_FOUND;
     }
 
     uint64_t start, length;
-    if ((r = find_first_fit(gpt_path, minimumSizeBytes, &start, &length)) != ZX_OK) {
-        fprintf(stderr, "[partition_find_or_add] Couldn't find fit\n");
+    zx_status_t r;
+    if ((r = find_first_fit(gpt, gpt_fd, minimumSizeBytes, &start, &length)) != ZX_OK) {
+        fprintf(stderr, "[partition_add] Couldn't find fit\n");
         return r;
     }
+
+    block_info_t info;
+    ssize_t rc = ioctl_block_get_info(gpt_fd.get(), &info);
+    if (rc < 0) {
+        fprintf(stderr, "[partition_add] Cannot acquire GPT info\n");
+        return static_cast<zx_status_t>(rc);
+    }
+
     length = (minimumSizeBytes + info.block_size - 1) / info.block_size;
     size_t sz;
     uint8_t guid[GPT_GUID_LEN];
     if ((r = zx_cprng_draw(guid, GPT_GUID_LEN, &sz)) != ZX_OK) {
-        fprintf(stderr, "[partition_find_or_add] Failed to get random GUID\n");
+        fprintf(stderr, "[partition_add] Failed to get random GUID\n");
         return r;
     } else if ((r = gpt_partition_add(gpt, name, type, guid, start, length, 0))) {
-        fprintf(stderr, "[partition_find_or_add] Failed to add partition\n");
+        fprintf(stderr, "[partition_add] Failed to add partition\n");
         return r;
     } else if ((r = gpt_device_sync(gpt))) {
-        fprintf(stderr, "[partition_find_or_add] Failed to sync GPT\n");
+        fprintf(stderr, "[partition_add] Failed to sync GPT\n");
         return r;
     } else if ((r = (int) ioctl_block_rr_part(gpt_fd.get())) < 0) {
-        fprintf(stderr, "[partition_find_or_add] Failed to rebind GPT\n");
+        fprintf(stderr, "[partition_add] Failed to rebind GPT\n");
         return r;
     }
     out_fd->reset(open_partition(guid, type, ZX_SEC(5), nullptr));
@@ -803,7 +806,7 @@
         }
     }
 
-    if ((r = find_first_fit(gpt_path, kMinimumFVMSizeBytes, &start, &length)) != ZX_OK) {
+    if ((r = find_first_fit(gpt, gpt_fd, kMinimumFVMSizeBytes, &start, &length)) != ZX_OK) {
         fprintf(stderr, "[fvm_add_to_gpt] Couldn't find space in GPT: %d\n", r);
         goto done;
     }
@@ -887,6 +890,19 @@
     return true;
 }
 
+bool kernc_finalize_cb(gpt_partition_t* partition) {
+    // Priority set to '3', making Kern C higher priority than
+    // the typical '1' and '2' reserved for Kern A and Kern B.
+    gpt_cros_attr_set_priority(&partition->flags, 3);
+    // Successful set to 'true' to encourage the bootloader to
+    // use this partition.
+    gpt_cros_attr_set_successful(&partition->flags, true);
+    // Maximize the number of attempts to boot this partition before
+    // we fall back to a different kernel.
+    gpt_cros_attr_set_tries(&partition->flags, 15);
+    return true;
+}
+
 } // namespace
 
 // Paves a sparse_file to the underlying disk, on top
@@ -916,16 +932,36 @@
 }
 
 // Paves an image onto the disk, within the GPT.
-template <PartitionFilterCb filterCb, PartitionCreateCb createCb>
+template <PartitionFilterCb filterCb, PartitionCreateCb createCb, PartitionFinalizeCb finalizeCb>
 zx_status_t partition_pave(fbl::unique_fd fd) {
     printf("[partition_pave]\n");
-    fbl::unique_fd part_fd;
+    char gpt_path[PATH_MAX];
+    if (find_target_gpt(gpt_path)) {
+        return ZX_ERR_IO;
+    }
+
+    fbl::unique_fd gpt_fd;
+    gpt_device_t* gpt;
     zx_status_t status;
-    if ((status = partition_find_or_add<filterCb, createCb>(&part_fd)) != ZX_OK) {
-        fprintf(stderr, "partition_pave: Cannot find suitable partition (or cannot make one)\n");
+    if ((status = initialize_gpt(gpt_path, &gpt_fd, &gpt)) != ZX_OK) {
         return status;
     }
 
+    fbl::unique_fd part_fd;
+    if ((status = partition_find<filterCb>(gpt, nullptr, &part_fd)) != ZX_OK) {
+        if (status != ZX_ERR_NOT_FOUND || (void*) createCb == nullptr) {
+            fprintf(stderr, "[partition_pave] Failure looking for partition: %d\n", status);
+            gpt_device_release(gpt);
+            return status;
+        }
+        if ((status = partition_add<createCb>(gpt, fbl::move(gpt_fd), &part_fd)) != ZX_OK) {
+            fprintf(stderr, "[partition_pave] Failure creating partition: %d\n", status);
+            gpt_device_release(gpt);
+            return status;
+        }
+    }
+    gpt_device_release(gpt);
+
     block_info_t info;
     if ((status = static_cast<zx_status_t>(ioctl_block_get_info(part_fd.get(), &info))) < 0) {
         fprintf(stderr, "[partition_pave] Couldn't get GPT block info\n");
@@ -942,7 +978,7 @@
     txnid_t txnid;
     vmoid_t vmoid;
     fifo_client_t* client;
-    status = register_fast_block_io(part_fd.get(), mvmo->GetVmo(), &txnid,
+    status = register_fast_block_io(part_fd, mvmo->GetVmo(), &txnid,
                                     &vmoid, &client);
     if (status != ZX_OK) {
         fprintf(stderr, "[partition_pave] Cannot register fast block I/O\n");
@@ -953,14 +989,30 @@
     request.txnid = txnid;
     request.vmoid = vmoid;
     request.opcode = BLOCKIO_WRITE;
-    status = stream_partition(mvmo.get(), client, &request, fd.get());
-
+    status = stream_partition(mvmo.get(), client, &request, fd);
     block_fifo_release_client(client);
     if (status != ZX_OK) {
+        fprintf(stderr, "[partition_pave] Failed to stream partition\n");
         return status;
     }
-    printf("[partition_pave] Completed successfully\n");
 
+    if ((void*) finalizeCb != nullptr) {
+        if ((status = initialize_gpt(gpt_path, &gpt_fd, &gpt)) != ZX_OK) {
+            fprintf(stderr, "[partition_pave] Cannot re-initialize GPT\n");
+            return status;
+        }
+        gpt_partition_t* partition;
+        if ((status = partition_find<filterCb>(gpt, &partition, nullptr)) != ZX_OK) {
+            fprintf(stderr, "[partition_pave] Cannot re-find partition\n");
+            return status;
+        }
+        if (finalizeCb(partition)) {
+            gpt_device_sync(gpt);
+        }
+        gpt_device_release(gpt);
+    }
+
+    printf("[partition_pave] Completed successfully\n");
     return ZX_OK;
 }
 
@@ -1070,10 +1122,13 @@
         }
     }
 
+    zx_status_t status;
     if (!strcmp(cmd, "install-efi")) {
-        return partition_pave<efi_filter_cb, efi_create_cb>(fbl::move(fd)) == ZX_OK ? 0 : -1;
+        status = partition_pave<efi_filter_cb, efi_create_cb, nullptr>(fbl::move(fd));
+        return status == ZX_OK ? 0 : -1;
     } else if (!strcmp(cmd, "install-kernc")) {
-        return partition_pave<kernc_filter_cb, kernc_create_cb>(fbl::move(fd)) == ZX_OK ? 0 : -1;
+        status = partition_pave<kernc_filter_cb, kernc_create_cb, kernc_finalize_cb>(fbl::move(fd));
+        return status == ZX_OK ? 0 : -1;
     } else if (!strcmp(cmd, "install-fvm")) {
         return fvm_pave(fbl::move(fd));
     } else if (!strcmp(cmd, "wipe")) {