root/usr.sbin/sysinst/disklabel.c
/*      $NetBSD: disklabel.c,v 1.53 2024/10/04 15:11:09 rillig Exp $    */

/*
 * Copyright 2018 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "defs.h"
#include "md.h"
#include <assert.h>
#include <util.h>
#include <paths.h>
#include <sys/ioctl.h>
#include <sys/param.h>

const struct disk_partitioning_scheme disklabel_parts;

/*************** disklabel ******************************************/
/* a disklabel based disk_partitions interface */
struct disklabel_disk_partitions {
        struct disk_partitions dp;
        struct disklabel l;
        daddr_t ptn_alignment, install_target;
        char last_mounted[MAXPARTITIONS][MOUNTLEN];
        uint fs_sub_type[MAXPARTITIONS], fs_opt3[MAXPARTITIONS];
};

/*
 * Maximum number of disklabel partitions the current kernel supports
 */
size_t dl_maxpart;

/* index into this array is the type code */
static struct part_type_desc dl_types[__arraycount(fstypenames)-1];

struct dl_custom_ptype {
        unsigned int type;
        char short_desc[6], description[30];
        struct part_type_desc desc;
};
struct dl_custom_ptype * dl_custom_ptypes;
size_t dl_custom_ptype_count;

static uint8_t dl_part_type_from_generic(const struct part_type_desc*);

static void
disklabel_init_default_alignment(struct disklabel_disk_partitions *parts,
    uint track)
{
        if (track == 0)
                track = MEG / parts->dp.bytes_per_sector;

        if (dl_maxpart == 0)
                dl_maxpart = getmaxpartitions();

#ifdef MD_DISKLABEL_SET_ALIGN_PRE
        if (MD_DISKLABEL_SET_ALIGN_PRE(parts->ptn_alignment, track))
                return;
#endif
        /* Use 1MB alignment for large (>128GB) disks */
        if (parts->dp.disk_size > HUGE_DISK_SIZE) {
                parts->ptn_alignment = 2048;
        } else if (parts->dp.disk_size > TINY_DISK_SIZE ||
            parts->dp.bytes_per_sector > 512) {
                parts->ptn_alignment = 64;
        } else {
                parts->ptn_alignment = 1;
        }
#ifdef MD_DISKLABEL_SET_ALIGN_POST
        MD_DISKLABEL_SET_ALIGN_POST(parts->ptn_alignment, track);
#endif
}

static bool
disklabel_change_geom(struct disk_partitions *arg, int ncyl, int nhead,
    int nsec)
{
        struct disklabel_disk_partitions *parts =
            (struct disklabel_disk_partitions*)arg;

        assert(parts->l.d_secsize != 0);
        assert(parts->l.d_nsectors != 0);
        assert(parts->l.d_ntracks != 0);
        assert(parts->l.d_ncylinders != 0);
        assert(parts->l.d_secpercyl != 0);

        disklabel_init_default_alignment(parts, nhead * nsec);
        if (ncyl*nhead*nsec <= TINY_DISK_SIZE)
                set_default_sizemult(arg->disk,
                    arg->bytes_per_sector, arg->bytes_per_sector);
        else
                set_default_sizemult(arg->disk, MEG,
                    arg->bytes_per_sector);

        return true;
}

static size_t
disklabel_cylinder_size(const struct disk_partitions *arg)
{
        const struct disklabel_disk_partitions *parts =
            (const struct disklabel_disk_partitions*)arg;

        return parts->l.d_secpercyl;
}

#ifdef NO_DISKLABEL_BOOT
static bool
disklabel_non_bootable(const char *disk)
{

        return false;
}
#endif

static struct disk_partitions *
disklabel_parts_new(const char *dev, daddr_t start, daddr_t len,
    bool is_boot_drive, struct disk_partitions *parent)
{
        struct disklabel_disk_partitions *parts;
        struct disk_geom geo;
        daddr_t total_size;

        if (!get_disk_geom(dev, &geo))
                return NULL;

        parts = calloc(1, sizeof(*parts));
        if (parts == NULL)
                return NULL;

        parts->install_target = -1;
        total_size = geo.dg_secperunit;
        if (len*(geo.dg_secsize/512) > disklabel_parts.size_limit)
                len = disklabel_parts.size_limit/(geo.dg_secsize/512);
        if (total_size*(geo.dg_secsize/512) > disklabel_parts.size_limit)
                total_size = disklabel_parts.size_limit/(geo.dg_secsize/512);

        parts->l.d_ncylinders = geo.dg_ncylinders;
        parts->l.d_ntracks = geo.dg_ntracks;
        parts->l.d_nsectors = geo.dg_nsectors;
        parts->l.d_secsize = geo.dg_secsize;
        parts->l.d_secpercyl = geo.dg_nsectors * geo.dg_ntracks;

        parts->dp.pscheme = &disklabel_parts;
        parts->dp.disk = strdup(dev);
        parts->dp.disk_start = start;
        parts->dp.disk_size = parts->dp.free_space = len;
        parts->dp.bytes_per_sector = parts->l.d_secsize;
        disklabel_init_default_alignment(parts, parts->l.d_secpercyl);
        parts->dp.parent = parent;

        strncpy(parts->l.d_packname, "fictious", sizeof parts->l.d_packname);

#if RAW_PART == 3
        if (parts->dp.parent != NULL) {
                parts->l.d_partitions[RAW_PART-1].p_fstype = FS_UNUSED;
                parts->l.d_partitions[RAW_PART-1].p_offset = start;
                parts->l.d_partitions[RAW_PART-1].p_size = len;
                parts->dp.num_part++;
        }
#endif
        parts->l.d_partitions[RAW_PART].p_fstype = FS_UNUSED;
        parts->l.d_partitions[RAW_PART].p_offset = 0;
        parts->l.d_partitions[RAW_PART].p_size = total_size;
        parts->dp.num_part++;

        parts->l.d_npartitions = RAW_PART+1;

        return &parts->dp;
}

static struct disk_partitions *
disklabel_parts_read(const char *disk, daddr_t start, daddr_t len, size_t bps,
    const struct disk_partitioning_scheme *scheme)
{
        int fd;
        char diskpath[MAXPATHLEN];
        uint flags;
        bool have_own_label = false;

        /* read partitions */

        struct disklabel_disk_partitions *parts = calloc(1, sizeof(*parts));
        if (parts == NULL)
                return NULL;
        parts->install_target = -1;

        fd = opendisk(disk, O_RDONLY, diskpath, sizeof(diskpath), 0);
        if (fd == -1) {
                free(parts);
                return NULL;
        }

        /*
         * We should actually try to read the label inside the start/len
         * boundary, but for simplicity just rely on the kernel and
         * instead verify a FS_UNUSED partition at RAW_PART-1 (if
         * RAW_PART > 'c') is within the given limits.
         */
        if (ioctl(fd, DIOCGDINFO, &parts->l) < 0) {
                free(parts);
                close(fd);
                return NULL;
        }
#if RAW_PART == 3
        if (parts->l.d_partitions[RAW_PART-1].p_fstype == FS_UNUSED) {
                daddr_t dlstart = parts->l.d_partitions[RAW_PART-1].p_offset;
                daddr_t dlend = start +
                    parts->l.d_partitions[RAW_PART-1].p_size;

                if (dlstart < start || dlend > (start+len)) {
                        /*
                         * Kernel assumes different outer partition
                         * (probably not yet written back to disk)
                         * so this label is invalid.
                         */
                        free(parts);
                        close(fd);
                        return NULL;
                }
        }
#endif

        if (len > disklabel_parts.size_limit)
                len = disklabel_parts.size_limit;
        parts->dp.pscheme = scheme;
        parts->dp.disk = strdup(disk);
        parts->dp.disk_start = start;
        parts->dp.disk_size = parts->dp.free_space = len;
        parts->l.d_secsize = bps;
        parts->dp.bytes_per_sector = bps;
        disklabel_init_default_alignment(parts, parts->l.d_secpercyl);

        for (int part = 0; part < parts->l.d_npartitions; part++) {
                if (parts->l.d_partitions[part].p_fstype == FS_UNUSED
                    && parts->l.d_partitions[part].p_size == 0)
                        continue;

                parts->dp.num_part++;
                if (parts->l.d_partitions[part].p_fstype == FS_UNUSED)
                        continue;

                flags = 0;
                if (parts->l.d_partitions[part].p_fstype == FS_MSDOS)
                        flags = GLM_MAYBE_FAT32;
                else if (parts->l.d_partitions[part].p_fstype == FS_BSDFFS) {
                        flags = GLM_LIKELY_FFS;
                        if (parts->install_target < 0)
                                parts->install_target =
                                    parts->l.d_partitions[part].p_offset;
                }
                if (flags != 0) {
                        uint fs_type, fs_sub_type;
                        const char *lm = get_last_mounted(fd,
                            parts->l.d_partitions[part].p_offset,
                            &fs_type, &fs_sub_type, flags);
                        if (lm != NULL && *lm != 0) {
                                strlcpy(parts->last_mounted[part], lm,
                                    sizeof(parts->last_mounted[part]));
                                if (parts->l.d_partitions[part].p_fstype ==
                                    fs_type)
                                        parts->fs_sub_type[part] = fs_sub_type;
                                canonicalize_last_mounted(
                                    parts->last_mounted[part]);
                        }
                }

                if (parts->l.d_partitions[part].p_size > parts->dp.free_space)
                        parts->dp.free_space = 0;
                else
                        parts->dp.free_space -=
                            parts->l.d_partitions[part].p_size;
        }
        close(fd);

        /*
         * Verify we really have a disklabel on the target disk.
         */
        if (run_program(RUN_SILENT | RUN_ERROR_OK,
            "disklabel -r %s", disk) == 0) {
                have_own_label = true;
        }
#ifdef DISKLABEL_NO_ONDISK_VERIFY
        else {
                /*
                 * disklabel(8) with -r checks a native disklabel at
                 * LABELOFFSET sector, but several ports don't have
                 * a native label and use emulated one translated from
                 * port specific MD disk partition information.
                 * Unfortunately, there is no MI way to check whether
                 * the disk has a native BSD disklabel by readdisklabel(9)
                 * via DIOCGDINFO.  So check if returned label looks
                 * defaults set by readdisklabel(9) per MD way.
                 */
                have_own_label = !md_disklabel_is_default(&parts->l);
        }
#endif

        if (!have_own_label) {
                bool found_real_part = false;

                if (parts->l.d_npartitions <= RAW_PART ||
                    parts->l.d_partitions[RAW_PART].p_size == 0)
                        goto no_valid_label;

                /*
                 * Check if kernel translation gave us "something" besides
                 * the raw or the whole-disk partition.
                 * If not: report missing disklabel.
                 */
                for (int part = 0; part < parts->l.d_npartitions; part++) {
                        if (parts->l.d_partitions[part].p_fstype == FS_UNUSED)
                                continue;
                        if (/* part == 0 && */  /* PR kern/54882 */
                            parts->l.d_partitions[part].p_offset ==
                             parts->l.d_partitions[RAW_PART].p_offset &&
                            parts->l.d_partitions[part].p_size ==
                             parts->l.d_partitions[RAW_PART].p_size)
                                continue;
                        if (part == RAW_PART)
                                continue;
                        found_real_part = true;
                        break;
                }
                if (!found_real_part) {
                        /* no partition there yet */
no_valid_label:
                        free(parts);
                        return NULL;
                }
        }

        return &parts->dp;
}

/*
 * Escape a string for usage as a tag name in a capfile(5),
 * we really know there is enough space in the destination buffer...
 */
static void
escape_capfile(char *dest, const char *src, size_t len)
{
        while (*src && len > 0) {
                if (*src == ':')
                        *dest++ = ' ';
                else
                        *dest++ = *src;
                src++;
                len--;
        }
        *dest = 0;
}

static bool
disklabel_write_to_disk(struct disk_partitions *arg)
{
        struct disklabel_disk_partitions *parts =
            (struct disklabel_disk_partitions*)arg;
        FILE *f;
        char fname[PATH_MAX], packname[sizeof(parts->l.d_packname)+1],
            disktype[sizeof(parts->l.d_typename)+1];
        int i, rv = 0;
        const char *disk = parts->dp.disk, *s;
        const struct partition *lp;
        char *d;
        size_t n;

        assert(parts->l.d_secsize != 0);
        assert(parts->l.d_nsectors != 0);
        assert(parts->l.d_ntracks != 0);
        assert(parts->l.d_ncylinders != 0);
        assert(parts->l.d_secpercyl != 0);

        /* make sure we have a 0 terminated packname */
        strlcpy(packname, parts->l.d_packname, sizeof packname);
        if (packname[0] == 0)
                strcpy(packname, "fictious");

        /* fill typename with disk name prefix, if not already set */
        if (strlen(parts->l.d_typename) == 0) {
                for (n = 0, d = parts->l.d_typename, s = disk;
                    *s && n < sizeof(parts->l.d_typename); d++, s++, n++) {
                        if (isdigit((unsigned char)*s))
                                break;
                        *d = *s;
                }
        }

        /* we need a valid disk type name, so enforce an arbitrary if
         * above did not yield a usable one */
        if (strlen(parts->l.d_typename) == 0)
                strncpy(parts->l.d_typename, "SCSI",
                    sizeof(parts->l.d_typename));
        escape_capfile(disktype, parts->l.d_typename,
            sizeof(parts->l.d_typename));

        sprintf(fname, "/tmp/disklabel.%u", getpid());
        f = fopen(fname, "w");
        if (f == NULL)
                return false;

        lp = parts->l.d_partitions;
        scripting_fprintf(NULL, "cat <<EOF >%s\n", fname);
        scripting_fprintf(f, "%s|NetBSD installation generated:\\\n",
            disktype);
        scripting_fprintf(f, "\t:nc#%d:nt#%d:ns#%d:\\\n",
            parts->l.d_ncylinders, parts->l.d_ntracks, parts->l.d_nsectors);
        scripting_fprintf(f, "\t:sc#%d:su#%" PRIu32 ":\\\n",
            parts->l.d_secpercyl, lp[RAW_PART].p_offset+lp[RAW_PART].p_size);
        scripting_fprintf(f, "\t:se#%d:\\\n", parts->l.d_secsize);

        for (i = 0; i < parts->l.d_npartitions; i++) {
                scripting_fprintf(f, "\t:p%c#%" PRIu32 ":o%c#%" PRIu32
                    ":t%c=%s:", 'a'+i, (uint32_t)lp[i].p_size,
                    'a'+i, (uint32_t)lp[i].p_offset, 'a'+i,
                    fstypenames[lp[i].p_fstype]);
                if (lp[i].p_fstype == FS_BSDLFS ||
                    lp[i].p_fstype == FS_BSDFFS)
                        scripting_fprintf (f, "b%c#%" PRIu32 ":f%c#%" PRIu32
                            ":", 'a'+i,
                            (uint32_t)(lp[i].p_fsize *
                            lp[i].p_frag),
                            'a'+i, (uint32_t)lp[i].p_fsize);

                if (i < parts->l.d_npartitions - 1)
                        scripting_fprintf(f, "\\\n");
                else
                        scripting_fprintf(f, "\n");
        }
        scripting_fprintf(NULL, "EOF\n");

        fclose(f);

        /*
         * Label a disk using an MD-specific string DISKLABEL_CMD for
         * to invoke disklabel.
         * if MD code does not define DISKLABEL_CMD, this is a no-op.
         *
         * i386 port uses "/sbin/disklabel -w -r", just like i386
         * miniroot scripts, though this may leave a bogus incore label.
         *
         * Sun ports should use DISKLABEL_CMD "/sbin/disklabel -w"
         * to get incore to ondisk inode translation for the Sun proms.
         */
#ifdef DISKLABEL_CMD
        /* disklabel the disk */
        rv = run_program(0, "%s -f %s %s '%s' '%s'",
            DISKLABEL_CMD, fname, disk, disktype, packname);
#endif

        unlink(fname);

        return rv == 0;
}

static bool
disklabel_delete_all(struct disk_partitions *arg)
{
        struct disklabel_disk_partitions *parts =
            (struct disklabel_disk_partitions*)arg;
        daddr_t total_size = parts->l.d_partitions[RAW_PART].p_size;

        memset(&parts->l.d_partitions, 0, sizeof(parts->l.d_partitions));
        parts->dp.num_part = 0;

#if RAW_PART == 3
        if (parts->dp.parent != NULL) {
                parts->l.d_partitions[RAW_PART-1].p_fstype = FS_UNUSED;
                parts->l.d_partitions[RAW_PART-1].p_offset =
                    parts->dp.disk_start;
                parts->l.d_partitions[RAW_PART-1].p_size = parts->dp.disk_size;
                parts->dp.num_part++;
        }
#endif
        parts->l.d_partitions[RAW_PART].p_fstype = FS_UNUSED;
        parts->l.d_partitions[RAW_PART].p_offset = 0;
        parts->l.d_partitions[RAW_PART].p_size = total_size;
        parts->dp.num_part++;

        parts->l.d_npartitions = RAW_PART+1;
        return true;
}

static bool
disklabel_delete(struct disk_partitions *arg, part_id id,
    const char **err_msg)
{
        struct disklabel_disk_partitions *parts =
            (struct disklabel_disk_partitions*)arg;
        part_id ndx;

        ndx = 0;
        for (int part = 0; part < parts->l.d_npartitions; part++) {
                if (parts->l.d_partitions[part].p_fstype == FS_UNUSED
                    && parts->l.d_partitions[part].p_size == 0)
                        continue;

                if (ndx == id) {
                        if (part == RAW_PART
#if RAW_PART == 3
                                || (part == RAW_PART-1 &&
                                    parts->dp.parent != NULL)
#endif
                                                ) {
                                if (err_msg)
                                        *err_msg = msg_string(
                                            MSG_part_not_deletable);
                                return false;
                        }
                        if (parts->install_target ==
                            parts->l.d_partitions[part].p_offset)
                                parts->install_target = -1;
                        parts->dp.free_space +=
                            parts->l.d_partitions[part].p_size;
                        parts->l.d_partitions[part].p_size = 0;
                        parts->l.d_partitions[part].p_offset = 0;
                        parts->l.d_partitions[part].p_fstype = FS_UNUSED;
                        parts->dp.num_part--;
                        return true;
                }
                ndx++;
        }

        if (err_msg)
                *err_msg = INTERNAL_ERROR;
        return false;
}

static bool
disklabel_delete_range(struct disk_partitions *arg, daddr_t r_start,
    daddr_t r_size)
{
        struct disklabel_disk_partitions *parts =
            (struct disklabel_disk_partitions*)arg;

        for (int part = 0; part < parts->l.d_npartitions; part++) {
                if (parts->l.d_partitions[part].p_fstype == FS_UNUSED
                    && parts->l.d_partitions[part].p_size == 0)
                        continue;

                if (part == RAW_PART)
                        continue;

                daddr_t start = parts->l.d_partitions[part].p_offset;
                daddr_t end = start + parts->l.d_partitions[part].p_size;

#if RAW_PART == 3
                if (parts->dp.parent != NULL &&
                    part == RAW_PART - 1 && start == r_start &&
                    r_start + r_size == end)
                        continue;
#endif

                if ((start >= r_start && start <= r_start+r_size) ||
                    (end >= r_start && end <= r_start+r_size)) {
                        if (start == parts->install_target)
                                parts->install_target  = -1;
                        if (parts->dp.num_part > 1)
                                parts->dp.num_part--;
                        parts->dp.free_space +=
                            parts->l.d_partitions[part].p_size;
                        parts->l.d_partitions[part].p_fstype = FS_UNUSED;
                        parts->l.d_partitions[part].p_size = 0;
                }
        }

        return true;
}

static void
dl_init_types(void)
{
        for (size_t i = 0; i < __arraycount(dl_types); i++) {
                if (fstypenames[i] == NULL)
                        break;
                dl_types[i].short_desc =
                dl_types[i].description = getfslabelname(i, 0);
                enum part_type pt;
                switch (i) {
                case FS_UNUSED: pt = PT_undef; break;
                case FS_BSDFFS:
                case FS_RAID:
                case FS_BSDLFS:
                case FS_CGD:
                                pt = PT_root; break;
                case FS_SWAP:   pt = PT_swap; break;
                case FS_MSDOS:  pt = PT_FAT; break;
                case FS_EX2FS:  pt = PT_EXT2; break;
                case FS_SYSVBFS:
                                pt = PT_SYSVBFS; break;
                default:        pt = PT_unknown; break;
                }
                dl_types[i].generic_ptype = pt;
        }
}

static uint8_t
dl_part_type_from_generic(const struct part_type_desc *gent)
{

        if (dl_types[0].description == NULL)
                dl_init_types();
        for (size_t i = 0; i < __arraycount(dl_types); i++)
                if (gent == &dl_types[i])
                        return (uint8_t)i;

        for (size_t i = 0; i < dl_custom_ptype_count; i++)
                if (gent == &dl_custom_ptypes[i].desc)
                        return dl_custom_ptypes[i].type;

        return 0;
}

static size_t
disklabel_type_count(void)
{
        return __arraycount(dl_types) + dl_custom_ptype_count;
}

static const struct part_type_desc *
disklabel_get_type(size_t ndx)
{
        if (dl_types[0].description == NULL)
                dl_init_types();

        if (ndx < __arraycount(dl_types))
                return &dl_types[ndx];

        ndx -= __arraycount(dl_types);
        if (ndx >= dl_custom_ptype_count)
                return NULL;

        return &dl_custom_ptypes[ndx].desc;
}

static const struct part_type_desc *
disklabel_find_type(uint type, bool create_if_unknown)
{
        if (dl_types[0].description == NULL)
                dl_init_types();

        if (type < __arraycount(dl_types))
                return &dl_types[type];

        for (size_t i = 0; i < dl_custom_ptype_count; i++)
                if (dl_custom_ptypes[i].type == type)
                        return &dl_custom_ptypes[i].desc;

        if (create_if_unknown) {
                struct dl_custom_ptype *nt;

                nt = realloc(dl_custom_ptypes, dl_custom_ptype_count+1);
                if (nt == NULL)
                        return NULL;
                dl_custom_ptypes = nt;
                nt = dl_custom_ptypes + dl_custom_ptype_count;
                dl_custom_ptype_count++;
                memset(nt, 0, sizeof(*nt));
                nt->type = type;
                snprintf(nt->short_desc, sizeof(nt->short_desc), "%u", type);
                nt->short_desc[sizeof(nt->short_desc)-1] = 0;
                snprintf(nt->description, sizeof(nt->description),
                    "%s (%u)", msg_string(MSG_custom_type), type);
                nt->description[sizeof(nt->description)-1] = 0;
                nt->desc.generic_ptype = PT_unknown;
                nt->desc.short_desc = nt->short_desc;
                nt->desc.description = nt->description;
                return &nt->desc;
        }

        return NULL;
}

static const struct part_type_desc *
disklabel_create_custom_part_type(const char *custom, const char **err_msg)
{
        char *endp;
        unsigned long fstype;

        fstype = strtoul(custom, &endp, 10);
        if (*endp != 0) {
                if (err_msg)
                        *err_msg = msg_string(MSG_dl_type_invalid);
                return NULL;
        }

        return disklabel_find_type(fstype, true);
}

static const struct part_type_desc *
disklabel_get_fs_part_type(enum part_type pt, unsigned fstype, unsigned subtype)
{
        return disklabel_find_type(fstype, false);
}

static const struct part_type_desc *
disklabel_create_unknown_part_type(void)
{
        return disklabel_find_type(FS_OTHER, false);
}

static const struct part_type_desc *
disklabel_get_generic_type(enum part_type pt)
{
        size_t nt;

        if (dl_types[0].description == NULL)
                dl_init_types();

        switch (pt) {
        case PT_root:   nt = FS_BSDFFS; break;
        case PT_swap:   nt = FS_SWAP; break;
        case PT_FAT:
        case PT_EFI_SYSTEM:
                        nt = FS_MSDOS; break;
        case PT_EXT2:   nt = FS_EX2FS; break;
        case PT_SYSVBFS:
                        nt = FS_SYSVBFS; break;
        default:        nt = FS_UNUSED; break;
        }

        return disklabel_get_type(nt);
}

static bool
disklabel_get_default_fstype(const struct part_type_desc *nat_type,
    unsigned *fstype, unsigned *fs_sub_type)
{

        *fstype = dl_part_type_from_generic(nat_type);
#ifdef DEFAULT_UFS2
        if (*fstype == FS_BSDFFS)
                *fs_sub_type = 2;
        else
#endif
                *fs_sub_type = 0;
        return true;
}

static bool
disklabel_get_part_info(const struct disk_partitions *arg, part_id id,
    struct disk_part_info *info)
{
        const struct disklabel_disk_partitions *parts =
            (const struct disklabel_disk_partitions*)arg;
        part_id ndx;

        if (dl_types[0].description == NULL)
                dl_init_types();

        ndx = 0;
        for (int part = 0; part < parts->l.d_npartitions; part++) {
                if (parts->l.d_partitions[part].p_fstype == FS_UNUSED
                    && parts->l.d_partitions[part].p_size == 0)
                        continue;

                if (ndx == id) {
                        memset(info, 0, sizeof(*info));
                        info->start = parts->l.d_partitions[part].p_offset;
                        info->size = parts->l.d_partitions[part].p_size;
                        info->nat_type = disklabel_find_type(
                            parts->l.d_partitions[part].p_fstype, true);
                        if (parts->last_mounted[part][0] != 0)
                                info->last_mounted = parts->last_mounted[part];
                        info->fs_type = parts->l.d_partitions[part].p_fstype;
                        info->fs_sub_type = parts->fs_sub_type[part];
                        info->fs_opt2 = parts->l.d_partitions[part].p_fsize;
                        info->fs_opt1 = info->fs_opt2 *
                            parts->l.d_partitions[part].p_frag;
                        info->fs_opt3 = parts->fs_opt3[part];
                        if (part == RAW_PART &&
                            parts->l.d_partitions[part].p_fstype == FS_UNUSED)
                                info->flags |=
                                    PTI_PSCHEME_INTERNAL|PTI_RAW_PART;
                        if (info->start == parts->install_target &&
                            parts->l.d_partitions[part].p_fstype != FS_UNUSED)
                                info->flags |= PTI_INSTALL_TARGET;
#if RAW_PART == 3
                        if (part == (RAW_PART-1) && parts->dp.parent != NULL &&
                            parts->l.d_partitions[part].p_fstype == FS_UNUSED)
                                info->flags |=
                                    PTI_PSCHEME_INTERNAL|PTI_WHOLE_DISK;
#endif
                        return true;
                }

                ndx++;
                if (ndx > parts->dp.num_part || ndx > id)
                        break;
        }

        return false;
}

static bool
disklabel_set_part_info(struct disk_partitions *arg, part_id id,
    const struct disk_part_info *info, const char **err_msg)
{
        struct disklabel_disk_partitions *parts =
            (struct disklabel_disk_partitions*)arg;
        part_id ndx;
        bool was_inst_target;

        if (dl_types[0].description == NULL)
                dl_init_types();

        ndx = 0;
        for (int part = 0; part < parts->l.d_npartitions; part++) {
                if (parts->l.d_partitions[part].p_fstype == FS_UNUSED
                    && parts->l.d_partitions[part].p_size == 0)
                        continue;

                if (ndx == id) {
                        was_inst_target = parts->l.d_partitions[part].p_offset
                            == parts->install_target;
                        parts->l.d_partitions[part].p_offset = info->start;
                        if (part != RAW_PART
#if RAW_PART == 3
                                && (part != RAW_PART-1 ||
                                    parts->dp.parent == NULL)
#endif
                                                        ) {
                                parts->dp.free_space +=
                                    parts->l.d_partitions[part].p_size -
                                    info->size;
                        }
                        parts->l.d_partitions[part].p_size = info->size;
                        parts->l.d_partitions[part].p_fstype =
                            dl_part_type_from_generic(info->nat_type);
                        parts->l.d_partitions[part].p_fsize = info->fs_opt2;
                        if (info->fs_opt2 != 0)
                                parts->l.d_partitions[part].p_frag =
                                    info->fs_opt1 / info->fs_opt2;
                        else
                                parts->l.d_partitions[part].p_frag = 0;
                        parts->fs_opt3[part] = info->fs_opt3;
                        if (info->last_mounted != NULL &&
                            info->last_mounted != parts->last_mounted[part])
                                strlcpy(parts->last_mounted[part],
                                    info->last_mounted,
                                    sizeof(parts->last_mounted[part]));
                        if (info->flags & PTI_INSTALL_TARGET)
                                parts->install_target = info->start;
                        else if (was_inst_target)
                                parts->install_target = -1;
                        assert(info->fs_type == 0 || info->fs_type ==
                            parts->l.d_partitions[part].p_fstype);
                        if (info->fs_sub_type != 0)
                                parts->fs_sub_type[part] = info->fs_sub_type;
                        return true;
                }

                ndx++;
                if (ndx > parts->dp.num_part || ndx > id)
                        break;
        }

        return false;
}

static size_t
disklabel_get_free_spaces_internal(const struct
    disklabel_disk_partitions *parts,
    struct disk_part_free_space *result, size_t max_num_result,
    daddr_t min_space_size, daddr_t align, daddr_t start, daddr_t ignore)
{
        size_t cnt = 0, i;
        daddr_t s, e, from, size, end_of_disk;

        if (start < parts->dp.disk_start)
                start = parts->dp.disk_start;
        if (min_space_size < 1)
                min_space_size = 1;
        if (align > 1 && (start % align) != 0)
                start = max(roundup(start, align), align);
        end_of_disk = parts->dp.disk_start + parts->dp.disk_size;
        from = start;
        while (from < end_of_disk && cnt < max_num_result) {
again:
                size = parts->dp.disk_start + parts->dp.disk_size - from;
                start = from;
                for (i = 0; i < parts->l.d_npartitions; i++) {
                        if (i == RAW_PART)
                                continue;
                        if (parts->l.d_partitions[i].p_fstype == FS_UNUSED)
                                continue;
                        if (parts->l.d_partitions[i].p_size == 0)
                                continue;

                        s = parts->l.d_partitions[i].p_offset;
                        e = parts->l.d_partitions[i].p_size + s;
                        if (s == ignore)
                                continue;
                        if (e < from)
                                continue;
                        if (s <= from && e > from) {
                                if (e - 1 >= end_of_disk)
                                        return cnt;

                                from = e + 1;
                                if (align > 1) {
                                        from = max(roundup(from, align), align);
                                        if (from >= end_of_disk) {
                                                size = 0;
                                                break;
                                        }
                                }
                                goto again;
                        }
                        if (s > from && s - from < size) {
                                size = s - from;
                        }
                }
                if (size >= min_space_size) {
                        result->start = start;
                        result->size = size;
                        result++;
                        cnt++;
                }
                from += size + 1;
                if (align > 1)
                        from = max(roundup(from, align), align);
        }

        return cnt;
}

static bool
disklabel_can_add_partition(const struct disk_partitions *arg)
{
        const struct disklabel_disk_partitions *parts =
            (const struct disklabel_disk_partitions*)arg;
        struct disk_part_free_space space;
        int i;

        if (dl_maxpart == 0)
                dl_maxpart = getmaxpartitions();
        if (parts->dp.free_space < parts->ptn_alignment)
                return false;
        if (parts->dp.num_part >= dl_maxpart)
                return false;
        if (disklabel_get_free_spaces_internal(parts, &space, 1,
            parts->ptn_alignment, parts->ptn_alignment, 0, -1) < 1)
                return false;
        if (parts->l.d_npartitions < dl_maxpart)
                return true;
        for (i = 0; i < parts->l.d_npartitions; i++) {
                if (i == RAW_PART)
                        continue;
#if RAW_PART == 3
                if (i == RAW_PART-1 && parts->dp.parent != NULL)
                        continue;
#endif
                if (parts->l.d_partitions[i].p_fstype == FS_UNUSED)
                        return true;
        }
        return false;
}

static bool
disklabel_get_disk_pack_name(const struct disk_partitions *arg,
    char *buf, size_t len)
{
        const struct disklabel_disk_partitions *parts =
            (const struct disklabel_disk_partitions*)arg;

        strlcpy(buf, parts->l.d_packname, min(len,
            sizeof(parts->l.d_packname)+1));
        return true;
}

static bool
disklabel_set_disk_pack_name(struct disk_partitions *arg, const char *pack)
{
        struct disklabel_disk_partitions *parts =
            (struct disklabel_disk_partitions*)arg;

        strncpy(parts->l.d_packname, pack, sizeof(parts->l.d_packname));
        return true;
}

static bool
disklabel_get_part_device(const struct disk_partitions *arg,
    part_id ptn, char *devname, size_t max_devname_len, int *part,
    enum dev_name_usage which_name, bool with_path, bool life)
{
        const struct disklabel_disk_partitions *parts =
            (const struct disklabel_disk_partitions*)arg;
        part_id id;
        int part_index;
        char pname;

        if (ptn >= parts->l.d_npartitions)
                return false;

        for (id = part_index = 0; part_index < parts->l.d_npartitions;
            part_index++) {
                if (parts->l.d_partitions[part_index].p_fstype == FS_UNUSED &&
                    parts->l.d_partitions[part_index].p_size == 0)
                        continue;
                if (id == ptn)
                        break;
                id++;
                if (id > ptn)
                        return false;
        }

        if (part != 0)
                *part = part_index;

        pname = 'a'+ part_index;

        switch (which_name) {
        case parent_device_only:
                strlcpy(devname, arg->disk, max_devname_len);
                return true;
        case logical_name:
        case plain_name:
                if (with_path)
                        snprintf(devname, max_devname_len, _PATH_DEV "%s%c",
                            arg->disk, pname);
                else
                        snprintf(devname, max_devname_len, "%s%c",
                            arg->disk, pname);
                return true;
        case raw_dev_name:
                if (with_path)
                        snprintf(devname, max_devname_len, _PATH_DEV "r%s%c",
                            arg->disk, pname);
                else
                        snprintf(devname, max_devname_len, "r%s%c",
                            arg->disk, pname);
                return true;
        }

        return false;
}

/*
 * If the requested partition file system type internally skips
 * the disk label sector, we can allow it to start at the beginning
 * of the disk. In most cases though we have to move the partition
 * to start past the label sector.
 */
static bool
need_to_skip_past_label(const struct disk_part_info *info)
{
        switch (info->fs_type) {
        case FS_BSDFFS:
        case FS_RAID:
                return false;
        }

        return true;
}

static part_id
disklabel_add_partition(struct disk_partitions *arg,
    const struct disk_part_info *info, const char **err_msg)
{
        struct disklabel_disk_partitions *parts =
            (struct disklabel_disk_partitions*)arg;
        int i, part = -1;
        part_id new_id;
        struct disk_part_free_space space;
        struct disk_part_info data = *info;

        if (disklabel_get_free_spaces_internal(parts, &space, 1, 1, 1,
            data.start, -1) < 1) {
                if (err_msg)
                        *err_msg = msg_string(MSG_No_free_space);
                return NO_PART;
        }
        if (space.start <= (parts->dp.disk_start + LABELSECTOR) &&
            need_to_skip_past_label(&data)) {
                daddr_t new_start = roundup(parts->dp.disk_start + LABELSECTOR,
                    parts->ptn_alignment);
                daddr_t off = new_start - space.start;
                space.start += off;
                space.size -= off;
        }
        if (data.size > space.size)
                data.size = space.size;
        daddr_t dend = data.start+data.size;
        if (space.start > data.start)
                data.start = space.start;
        if (space.start + space.size < dend)
                data.size = space.start+space.size-data.start;

        if (dl_maxpart == 0)
                dl_maxpart = getmaxpartitions();

        for (new_id = 0, i = 0; i < parts->l.d_npartitions; i++) {
                if (parts->l.d_partitions[i].p_size > 0)
                        new_id++;
                if (data.nat_type->generic_ptype != PT_root &&
                    data.nat_type->generic_ptype != PT_swap && i < RAW_PART)
                        continue;
                if (i == 0 && data.nat_type->generic_ptype != PT_root)
                        continue;
                if (i == 1 && data.nat_type->generic_ptype != PT_swap)
                        continue;
                if (i == RAW_PART)
                        continue;
#if RAW_PART == 3
                if (i == RAW_PART-1 && parts->dp.parent != NULL)
                        continue;
#endif
                if (parts->l.d_partitions[i].p_size > 0)
                        continue;
#ifdef  MD_DISKLABEL_PART_INDEX_CHECK
                if (!MD_DISKLABEL_PART_INDEX_CHECK(&parts->l, i, info))
                        continue;
#endif
                part = i;
                break;
        }

        if (part < 0) {
                if (parts->l.d_npartitions >= dl_maxpart) {
                        if (err_msg)
                                *err_msg =
                                    msg_string(MSG_err_too_many_partitions);
                        return NO_PART;
                }

                part = parts->l.d_npartitions++;
        }
        parts->l.d_partitions[part].p_offset = data.start;
        parts->l.d_partitions[part].p_size = data.size;
        parts->l.d_partitions[part].p_fstype =
             dl_part_type_from_generic(data.nat_type);
        parts->l.d_partitions[part].p_fsize = info->fs_opt2;
        if (info->fs_opt2 != 0)
                parts->l.d_partitions[part].p_frag =
                    info->fs_opt1 / info->fs_opt2;
        else
                parts->l.d_partitions[part].p_frag = 0;
        if (data.last_mounted && data.last_mounted[0])
                strlcpy(parts->last_mounted[part], data.last_mounted,
                    sizeof(parts->last_mounted[part]));
        else
                parts->last_mounted[part][0] = 0;
        parts->fs_sub_type[part] = data.fs_sub_type;
        parts->dp.num_part++;
        if (data.size <= parts->dp.free_space)
                parts->dp.free_space -= data.size;
        else
                parts->dp.free_space = 0;

        return new_id;
}

static part_id
disklabel_add_outer_partition(struct disk_partitions *arg,
    const struct disk_part_info *info, const char **err_msg)
{
        struct disklabel_disk_partitions *parts =
            (struct disklabel_disk_partitions*)arg;
        int i, part = -1;
        part_id new_id;

        if (dl_maxpart == 0)
                dl_maxpart = getmaxpartitions();

        for (new_id = 0, i = 0; i < parts->l.d_npartitions; i++) {
                if (parts->l.d_partitions[i].p_size > 0)
                        new_id++;
                if (info->nat_type->generic_ptype != PT_root &&
                    info->nat_type->generic_ptype != PT_swap && i < RAW_PART)
                        continue;
                if (i == 0 && info->nat_type->generic_ptype != PT_root)
                        continue;
                if (i == 1 && info->nat_type->generic_ptype != PT_swap)
                        continue;
                if (i == RAW_PART)
                        continue;
#if RAW_PART == 3
                if (i == RAW_PART-1 && parts->dp.parent != NULL)
                        continue;
#endif
                if (parts->l.d_partitions[i].p_size > 0)
                        continue;
                part = i;
                break;
        }

        if (part < 0) {
                if (parts->l.d_npartitions >= dl_maxpart) {
                        if (err_msg)
                                *err_msg =
                                    msg_string(MSG_err_too_many_partitions);
                        return NO_PART;
                }

                part = parts->l.d_npartitions++;
        }
        parts->l.d_partitions[part].p_offset = info->start;
        parts->l.d_partitions[part].p_size = info->size;
        parts->l.d_partitions[part].p_fstype =
             dl_part_type_from_generic(info->nat_type);
        parts->l.d_partitions[part].p_fsize = info->fs_opt2;
        if (info->fs_opt2 != 0)
                parts->l.d_partitions[part].p_frag =
                    info->fs_opt1 / info->fs_opt2;
        else
                parts->l.d_partitions[part].p_frag = 0;
        if (info->last_mounted && info->last_mounted[0])
                strlcpy(parts->last_mounted[part], info->last_mounted,
                    sizeof(parts->last_mounted[part]));
        else
                parts->last_mounted[part][0] = 0;
        parts->fs_sub_type[part] = info->fs_sub_type;
        parts->dp.num_part++;

        return new_id;
}

static size_t
disklabel_get_free_spaces(const struct disk_partitions *arg,
    struct disk_part_free_space *result, size_t max_num_result,
    daddr_t min_space_size, daddr_t align, daddr_t start, daddr_t ignore)
{
        const struct disklabel_disk_partitions *parts =
            (const struct disklabel_disk_partitions*)arg;

        return disklabel_get_free_spaces_internal(parts, result,
            max_num_result, min_space_size, align, start, ignore);
}

static daddr_t
disklabel_max_free_space_at(const struct disk_partitions *arg, daddr_t start)
{
        const struct disklabel_disk_partitions *parts =
            (const struct disklabel_disk_partitions*)arg;
        struct disk_part_free_space space;

        if (disklabel_get_free_spaces_internal(parts, &space, 1, 1, 0,
            start, start) == 1)
                return space.size;

        return 0;
}

static daddr_t
disklabel_get_alignment(const struct disk_partitions *arg)
{
        const struct disklabel_disk_partitions *parts =
            (const struct disklabel_disk_partitions*)arg;

        return parts->ptn_alignment;
}

static part_id
disklabel_find_by_name(struct disk_partitions *arg, const char *name)
{
        const struct disklabel_disk_partitions *parts =
            (const struct disklabel_disk_partitions*)arg;
        const char *sl;
        char part;
        ptrdiff_t n;
        part_id pno, id, i;

        sl = strrchr(name, '/');
        if (sl == NULL)
                return NO_PART;
        n = sl - name;
        if (strncmp(name, parts->l.d_packname, n) != 0)
                return NO_PART;
        part = name[n+1];
        if (part < 'a')
                return NO_PART;
        pno = part - 'a';
        if (pno >= parts->l.d_npartitions)
                return NO_PART;
        if (parts->l.d_partitions[pno].p_fstype == FS_UNUSED)
                return NO_PART;
        for (id = 0, i = 0; i < pno; i++)
                if (parts->l.d_partitions[i].p_fstype != FS_UNUSED ||
                    parts->l.d_partitions[i].p_size != 0)
                        id++;
        return id;
}

static void
disklabel_free(struct disk_partitions *arg)
{

        assert(arg != NULL);
        free(__UNCONST(arg->disk));
        free(arg);
}

static void
disklabel_destroy_part_scheme(struct disk_partitions *arg)
{

        run_program(RUN_SILENT, "disklabel -D %s", arg->disk);
        free(arg);
}

const struct disk_partitioning_scheme
disklabel_parts = {
        .name = MSG_parttype_disklabel,
        .short_name = MSG_parttype_disklabel_short,
        .new_type_prompt = MSG_dl_get_custom_fstype,
        .size_limit = (daddr_t)UINT32_MAX,
        .write_to_disk = disklabel_write_to_disk,
        .read_from_disk = disklabel_parts_read,
        .create_new_for_disk = disklabel_parts_new,
#ifdef NO_DISKLABEL_BOOT
        .have_boot_support = disklabel_non_bootable,
#endif
        .change_disk_geom = disklabel_change_geom,
        .get_cylinder_size = disklabel_cylinder_size,
        .find_by_name = disklabel_find_by_name,
        .get_disk_pack_name = disklabel_get_disk_pack_name,
        .set_disk_pack_name = disklabel_set_disk_pack_name,
        .delete_all_partitions = disklabel_delete_all,
        .delete_partitions_in_range = disklabel_delete_range,
        .delete_partition = disklabel_delete,
        .get_part_types_count = disklabel_type_count,
        .get_part_type = disklabel_get_type,
        .get_generic_part_type = disklabel_get_generic_type,
        .get_fs_part_type = disklabel_get_fs_part_type,
        .get_default_fstype = disklabel_get_default_fstype,
        .create_custom_part_type = disklabel_create_custom_part_type,
        .create_unknown_part_type = disklabel_create_unknown_part_type,
        .get_part_alignment = disklabel_get_alignment,
        .adapt_foreign_part_info = generic_adapt_foreign_part_info,
        .get_part_info = disklabel_get_part_info,
        .can_add_partition = disklabel_can_add_partition,
        .set_part_info = disklabel_set_part_info,
        .add_partition = disklabel_add_partition,
        .add_outer_partition = disklabel_add_outer_partition,
        .max_free_space_at = disklabel_max_free_space_at,
        .get_free_spaces = disklabel_get_free_spaces,
        .get_part_device = disklabel_get_part_device,
        .free = disklabel_free,
        .destroy_part_scheme = disklabel_destroy_part_scheme,
};