| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include <sys/file.h> |
| |
| #include "alloc-util.h" |
| #include "extract-word.h" |
| #include "gpt.h" |
| #include "id128-util.h" |
| #include "parse-util.h" |
| #include "stdio-util.h" |
| #include "string-util.h" |
| #include "sysupdate-partition.h" |
| |
| void partition_info_destroy(PartitionInfo *p) { |
| assert(p); |
| |
| p->label = mfree(p->label); |
| p->device = mfree(p->device); |
| } |
| |
| static int fdisk_partition_get_attrs_as_uint64( |
| struct fdisk_partition *pa, |
| uint64_t *ret) { |
| |
| uint64_t flags = 0; |
| const char *a; |
| int r; |
| |
| assert(pa); |
| assert(ret); |
| |
| /* Retrieve current flags as uint64_t mask */ |
| |
| a = fdisk_partition_get_attrs(pa); |
| if (!a) { |
| *ret = 0; |
| return 0; |
| } |
| |
| for (;;) { |
| _cleanup_free_ char *word = NULL; |
| |
| r = extract_first_word(&a, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS); |
| if (r < 0) |
| return r; |
| if (r == 0) |
| break; |
| |
| if (streq(word, "RequiredPartition")) |
| flags |= SD_GPT_FLAG_REQUIRED_PARTITION; |
| else if (streq(word, "NoBlockIOProtocol")) |
| flags |= SD_GPT_FLAG_NO_BLOCK_IO_PROTOCOL; |
| else if (streq(word, "LegacyBIOSBootable")) |
| flags |= SD_GPT_FLAG_LEGACY_BIOS_BOOTABLE; |
| else { |
| const char *e; |
| unsigned u; |
| |
| /* Drop "GUID" prefix if specified */ |
| e = startswith(word, "GUID:") ?: word; |
| |
| if (safe_atou(e, &u) < 0) { |
| log_debug("Unknown partition flag '%s', ignoring.", word); |
| continue; |
| } |
| |
| if (u >= sizeof(flags)*8) { /* partition flags on GPT are 64bit. Let's ignore any further |
| bits should libfdisk report them */ |
| log_debug("Partition flag above bit 63 (%s), ignoring.", word); |
| continue; |
| } |
| |
| flags |= UINT64_C(1) << u; |
| } |
| } |
| |
| *ret = flags; |
| return 0; |
| } |
| |
| static int fdisk_partition_set_attrs_as_uint64( |
| struct fdisk_partition *pa, |
| uint64_t flags) { |
| |
| _cleanup_free_ char *attrs = NULL; |
| int r; |
| |
| assert(pa); |
| |
| for (unsigned i = 0; i < sizeof(flags) * 8; i++) { |
| if (!FLAGS_SET(flags, UINT64_C(1) << i)) |
| continue; |
| |
| r = strextendf_with_separator(&attrs, ",", "%u", i); |
| if (r < 0) |
| return r; |
| } |
| |
| return fdisk_partition_set_attrs(pa, strempty(attrs)); |
| } |
| |
| int read_partition_info( |
| struct fdisk_context *c, |
| struct fdisk_table *t, |
| size_t i, |
| PartitionInfo *ret) { |
| |
| _cleanup_free_ char *label_copy = NULL, *device = NULL; |
| const char *label; |
| struct fdisk_partition *p; |
| uint64_t start, size, flags; |
| sd_id128_t ptid, id; |
| GptPartitionType type; |
| size_t partno; |
| int r; |
| |
| assert(c); |
| assert(t); |
| assert(ret); |
| |
| p = fdisk_table_get_partition(t, i); |
| if (!p) |
| return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m"); |
| |
| if (fdisk_partition_is_used(p) <= 0) { |
| *ret = (PartitionInfo) PARTITION_INFO_NULL; |
| return 0; /* not found! */ |
| } |
| |
| if (fdisk_partition_has_partno(p) <= 0 || |
| fdisk_partition_has_start(p) <= 0 || |
| fdisk_partition_has_size(p) <= 0) |
| return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a number, position or size."); |
| |
| partno = fdisk_partition_get_partno(p); |
| |
| start = fdisk_partition_get_start(p); |
| assert(start <= UINT64_MAX / 512U); |
| start *= 512U; |
| |
| size = fdisk_partition_get_size(p); |
| assert(size <= UINT64_MAX / 512U); |
| size *= 512U; |
| |
| label = fdisk_partition_get_name(p); |
| if (!label) |
| return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a label."); |
| |
| r = fdisk_partition_get_type_as_id128(p, &ptid); |
| if (r < 0) |
| return log_error_errno(r, "Failed to read partition type UUID: %m"); |
| |
| r = fdisk_partition_get_uuid_as_id128(p, &id); |
| if (r < 0) |
| return log_error_errno(r, "Failed to read partition UUID: %m"); |
| |
| r = fdisk_partition_get_attrs_as_uint64(p, &flags); |
| if (r < 0) |
| return log_error_errno(r, "Failed to get partition flags: %m"); |
| |
| r = fdisk_partition_to_string(p, c, FDISK_FIELD_DEVICE, &device); |
| if (r != 0) |
| return log_error_errno(r, "Failed to get partition device name: %m"); |
| |
| label_copy = strdup(label); |
| if (!label_copy) |
| return log_oom(); |
| |
| type = gpt_partition_type_from_uuid(ptid); |
| |
| *ret = (PartitionInfo) { |
| .partno = partno, |
| .start = start, |
| .size = size, |
| .flags = flags, |
| .type = ptid, |
| .uuid = id, |
| .label = TAKE_PTR(label_copy), |
| .device = TAKE_PTR(device), |
| .no_auto = FLAGS_SET(flags, SD_GPT_FLAG_NO_AUTO) && gpt_partition_type_knows_no_auto(type), |
| .read_only = FLAGS_SET(flags, SD_GPT_FLAG_READ_ONLY) && gpt_partition_type_knows_read_only(type), |
| .growfs = FLAGS_SET(flags, SD_GPT_FLAG_GROWFS) && gpt_partition_type_knows_growfs(type), |
| }; |
| |
| return 1; /* found! */ |
| } |
| |
| int find_suitable_partition( |
| const char *device, |
| uint64_t space, |
| sd_id128_t *partition_type, |
| PartitionInfo *ret) { |
| |
| _cleanup_(partition_info_destroy) PartitionInfo smallest = PARTITION_INFO_NULL; |
| _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; |
| _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL; |
| size_t n_partitions; |
| int r; |
| |
| assert(device); |
| assert(ret); |
| |
| c = fdisk_new_context(); |
| if (!c) |
| return log_oom(); |
| |
| r = fdisk_assign_device(c, device, /* readonly= */ true); |
| if (r < 0) |
| return log_error_errno(r, "Failed to open device '%s': %m", device); |
| |
| if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) |
| return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device); |
| |
| r = fdisk_get_partitions(c, &t); |
| if (r < 0) |
| return log_error_errno(r, "Failed to acquire partition table: %m"); |
| |
| n_partitions = fdisk_table_get_nents(t); |
| for (size_t i = 0; i < n_partitions; i++) { |
| _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL; |
| |
| r = read_partition_info(c, t, i, &pinfo); |
| if (r < 0) |
| return r; |
| if (r == 0) /* not assigned */ |
| continue; |
| |
| /* Filter out non-matching partition types */ |
| if (partition_type && !sd_id128_equal(pinfo.type, *partition_type)) |
| continue; |
| |
| if (!streq_ptr(pinfo.label, "_empty")) /* used */ |
| continue; |
| |
| if (space != UINT64_MAX && pinfo.size < space) /* too small */ |
| continue; |
| |
| if (smallest.partno != SIZE_MAX && smallest.size <= pinfo.size) /* already found smaller */ |
| continue; |
| |
| smallest = pinfo; |
| pinfo = (PartitionInfo) PARTITION_INFO_NULL; |
| } |
| |
| if (smallest.partno == SIZE_MAX) |
| return log_error_errno(SYNTHETIC_ERRNO(ENOSPC), "No available partition of a suitable size found."); |
| |
| *ret = smallest; |
| smallest = (PartitionInfo) PARTITION_INFO_NULL; |
| |
| return 0; |
| } |
| |
| int patch_partition( |
| const char *device, |
| const PartitionInfo *info, |
| PartitionChange change) { |
| |
| _cleanup_(fdisk_unref_partitionp) struct fdisk_partition *pa = NULL; |
| _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; |
| bool tweak_no_auto, tweak_read_only, tweak_growfs; |
| GptPartitionType type; |
| int r, fd; |
| |
| assert(device); |
| assert(info); |
| assert(change <= _PARTITION_CHANGE_MAX); |
| |
| if (change == 0) /* Nothing to do */ |
| return 0; |
| |
| c = fdisk_new_context(); |
| if (!c) |
| return log_oom(); |
| |
| r = fdisk_assign_device(c, device, /* readonly= */ false); |
| if (r < 0) |
| return log_error_errno(r, "Failed to open device '%s': %m", device); |
| |
| assert_se((fd = fdisk_get_devfd(c)) >= 0); |
| |
| /* Make sure udev doesn't read the device while we make changes (this lock is released automatically |
| * by the kernel when the fd is closed, i.e. when the fdisk context is freed, hence no explicit |
| * unlock by us here anywhere.) */ |
| if (flock(fd, LOCK_EX) < 0) |
| return log_error_errno(errno, "Failed to lock block device '%s': %m", device); |
| |
| if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) |
| return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device); |
| |
| r = fdisk_get_partition(c, info->partno, &pa); |
| if (r < 0) |
| return log_error_errno(r, "Failed to read partition %zu of GPT label of '%s': %m", info->partno, device); |
| |
| if (change & PARTITION_LABEL) { |
| r = fdisk_partition_set_name(pa, info->label); |
| if (r < 0) |
| return log_error_errno(r, "Failed to update partition label: %m"); |
| } |
| |
| if (change & PARTITION_UUID) { |
| r = fdisk_partition_set_uuid(pa, SD_ID128_TO_UUID_STRING(info->uuid)); |
| if (r < 0) |
| return log_error_errno(r, "Failed to update partition UUID: %m"); |
| } |
| |
| type = gpt_partition_type_from_uuid(info->type); |
| |
| /* Tweak the read-only flag, but only if supported by the partition type */ |
| tweak_no_auto = |
| FLAGS_SET(change, PARTITION_NO_AUTO) && |
| gpt_partition_type_knows_no_auto(type); |
| tweak_read_only = |
| FLAGS_SET(change, PARTITION_READ_ONLY) && |
| gpt_partition_type_knows_read_only(type); |
| tweak_growfs = |
| FLAGS_SET(change, PARTITION_GROWFS) && |
| gpt_partition_type_knows_growfs(type); |
| |
| if (change & PARTITION_FLAGS) { |
| uint64_t flags; |
| |
| /* Update the full flags parameter, and import the read-only flag into it */ |
| |
| flags = info->flags; |
| if (tweak_no_auto) |
| SET_FLAG(flags, SD_GPT_FLAG_NO_AUTO, info->no_auto); |
| if (tweak_read_only) |
| SET_FLAG(flags, SD_GPT_FLAG_READ_ONLY, info->read_only); |
| if (tweak_growfs) |
| SET_FLAG(flags, SD_GPT_FLAG_GROWFS, info->growfs); |
| |
| r = fdisk_partition_set_attrs_as_uint64(pa, flags); |
| if (r < 0) |
| return log_error_errno(r, "Failed to update partition flags: %m"); |
| |
| } else if (tweak_no_auto || tweak_read_only || tweak_growfs) { |
| uint64_t old_flags, new_flags; |
| |
| /* So we aren't supposed to update the full flags parameter, but we are supposed to update |
| * the RO flag of it. */ |
| |
| r = fdisk_partition_get_attrs_as_uint64(pa, &old_flags); |
| if (r < 0) |
| return log_error_errno(r, "Failed to get old partition flags: %m"); |
| |
| new_flags = old_flags; |
| if (tweak_no_auto) |
| SET_FLAG(new_flags, SD_GPT_FLAG_NO_AUTO, info->no_auto); |
| if (tweak_read_only) |
| SET_FLAG(new_flags, SD_GPT_FLAG_READ_ONLY, info->read_only); |
| if (tweak_growfs) |
| SET_FLAG(new_flags, SD_GPT_FLAG_GROWFS, info->growfs); |
| |
| if (new_flags != old_flags) { |
| r = fdisk_partition_set_attrs_as_uint64(pa, new_flags); |
| if (r < 0) |
| return log_error_errno(r, "Failed to update partition flags: %m"); |
| } |
| } |
| |
| r = fdisk_set_partition(c, info->partno, pa); |
| if (r < 0) |
| return log_error_errno(r, "Failed to update partition: %m"); |
| |
| r = fdisk_write_disklabel(c); |
| if (r < 0) |
| return log_error_errno(r, "Failed to write updated partition table: %m"); |
| |
| return 0; |
| } |