HelenOS sources

root/uspace/lib/fdisk/src/fdisk.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. fdisk_dev_info_delete
  2. fdisk_create
  3. fdisk_destroy
  4. fdisk_dev_list_get
  5. fdisk_dev_list_free
  6. fdisk_dev_first
  7. fdisk_dev_next
  8. fdisk_dev_info_get_svcid
  9. fdisk_dev_info_get_svcname
  10. fdisk_dev_info_capacity
  11. fdisk_part_add
  12. fdisk_part_remove
  13. fdisk_pri_part_insert_lists
  14. fdisk_log_part_insert_lists
  15. fdisk_dev_add_parts
  16. fdisk_dev_remove_parts
  17. fdisk_dev_open
  18. fdisk_dev_close
  19. fdisk_dev_erase
  20. fdisk_dev_get_flags
  21. fdisk_dev_get_svcname
  22. fdisk_dev_capacity
  23. fdisk_label_get_info
  24. fdisk_label_create
  25. fdisk_label_destroy
  26. fdisk_part_first
  27. fdisk_part_next
  28. fdisk_part_get_info
  29. fdisk_part_get_max_avail
  30. fdisk_part_get_tot_avail
  31. fdisk_part_create
  32. fdisk_part_destroy
  33. fdisk_part_set_mountp
  34. fdisk_pspec_init
  35. fdisk_part_get_free_idx
  36. fdisk_part_get_free_range
  37. fdisk_part_get_max_free_range
  38. fdisk_part_spec_prepare
  39. fdisk_update_dev_info
  40. fdisk_ba_align_up
  41. fdisk_ba_align_down
  42. fdisk_free_range_first
  43. fdisk_free_range_next
  44. fdisk_free_range_get
  45. fdisk_get_vollabel_support

/*
 * Copyright (c) 2024 Jiri Svoboda
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - 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.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.
 */

/** @addtogroup libfdisk
 * @{
 */
/**
 * @file Disk management library.
 */

#include <adt/list.h>
#include <capa.h>
#include <errno.h>
#include <fdisk.h>
#include <loc.h>
#include <macros.h>
#include <mem.h>
#include <stdlib.h>
#include <str.h>
#include <vbd.h>
#include <vol.h>

static errno_t fdisk_dev_add_parts(fdisk_dev_t *);
static void fdisk_dev_remove_parts(fdisk_dev_t *);
static errno_t fdisk_part_spec_prepare(fdisk_dev_t *, fdisk_part_spec_t *,
    vbd_part_spec_t *);
static void fdisk_pri_part_insert_lists(fdisk_dev_t *, fdisk_part_t *);
static void fdisk_log_part_insert_lists(fdisk_dev_t *, fdisk_part_t *);
static errno_t fdisk_update_dev_info(fdisk_dev_t *);
static uint64_t fdisk_ba_align_up(fdisk_dev_t *, uint64_t);
static uint64_t fdisk_ba_align_down(fdisk_dev_t *, uint64_t);
static errno_t fdisk_part_get_max_free_range(fdisk_dev_t *, fdisk_spc_t, aoff64_t *,
    aoff64_t *);
static void fdisk_free_range_first(fdisk_dev_t *, fdisk_spc_t, fdisk_free_range_t *);
static bool fdisk_free_range_next(fdisk_free_range_t *);
static bool fdisk_free_range_get(fdisk_free_range_t *, aoff64_t *, aoff64_t *);

static void fdisk_dev_info_delete(fdisk_dev_info_t *info)
{
        if (info == NULL)
                return;

        free(info->svcname);
        free(info);
}

errno_t fdisk_create(fdisk_t **rfdisk)
{
        fdisk_t *fdisk = NULL;
        errno_t rc;

        fdisk = calloc(1, sizeof(fdisk_t));
        if (fdisk == NULL) {
                rc = ENOMEM;
                goto error;
        }

        rc = vol_create(&fdisk->vol);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        rc = vbd_create(&fdisk->vbd);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        *rfdisk = fdisk;
        return EOK;
error:
        fdisk_destroy(fdisk);

        return rc;
}

void fdisk_destroy(fdisk_t *fdisk)
{
        if (fdisk == NULL)
                return;

        vol_destroy(fdisk->vol);
        vbd_destroy(fdisk->vbd);
        free(fdisk);
}

errno_t fdisk_dev_list_get(fdisk_t *fdisk, fdisk_dev_list_t **rdevlist)
{
        fdisk_dev_list_t *devlist = NULL;
        fdisk_dev_info_t *info;
        service_id_t *svcs = NULL;
        size_t count, i;
        errno_t rc;

        devlist = calloc(1, sizeof(fdisk_dev_list_t));
        if (devlist == NULL)
                return ENOMEM;

        devlist->fdisk = fdisk;
        list_initialize(&devlist->devinfos);

        rc = vbd_get_disks(fdisk->vbd, &svcs, &count);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        for (i = 0; i < count; i++) {
                info = calloc(1, sizeof(fdisk_dev_info_t));
                if (info == NULL) {
                        rc = ENOMEM;
                        goto error;
                }

                info->svcid = svcs[i];
                info->devlist = devlist;
                list_append(&info->ldevlist, &devlist->devinfos);
        }

        *rdevlist = devlist;
        free(svcs);
        return EOK;
error:
        free(svcs);
        fdisk_dev_list_free(devlist);
        return rc;
}

void fdisk_dev_list_free(fdisk_dev_list_t *devlist)
{
        fdisk_dev_info_t *info;

        if (devlist == NULL)
                return;

        while (!list_empty(&devlist->devinfos)) {
                info = list_get_instance(list_first(
                    &devlist->devinfos), fdisk_dev_info_t,
                    ldevlist);

                list_remove(&info->ldevlist);
                fdisk_dev_info_delete(info);
        }

        free(devlist);
}

fdisk_dev_info_t *fdisk_dev_first(fdisk_dev_list_t *devlist)
{
        if (list_empty(&devlist->devinfos))
                return NULL;

        return list_get_instance(list_first(&devlist->devinfos),
            fdisk_dev_info_t, ldevlist);
}

fdisk_dev_info_t *fdisk_dev_next(fdisk_dev_info_t *devinfo)
{
        link_t *lnext;

        lnext = list_next(&devinfo->ldevlist,
            &devinfo->devlist->devinfos);
        if (lnext == NULL)
                return NULL;

        return list_get_instance(lnext, fdisk_dev_info_t,
            ldevlist);
}

void fdisk_dev_info_get_svcid(fdisk_dev_info_t *info, service_id_t *rsid)
{
        *rsid = info->svcid;
}

errno_t fdisk_dev_info_get_svcname(fdisk_dev_info_t *info, char **rname)
{
        char *name;
        errno_t rc;

        if (info->svcname == NULL) {
                rc = loc_service_get_name(info->svcid,
                    &info->svcname);
                if (rc != EOK)
                        return rc;
        }

        name = str_dup(info->svcname);
        if (name == NULL)
                return ENOMEM;

        *rname = name;
        return EOK;
}

errno_t fdisk_dev_info_capacity(fdisk_dev_info_t *info, capa_spec_t *capa)
{
        vbd_disk_info_t vinfo;
        errno_t rc;

        rc = vbd_disk_info(info->devlist->fdisk->vbd, info->svcid, &vinfo);
        if (rc != EOK)
                return EIO;

        capa_from_blocks(vinfo.nblocks, vinfo.block_size, capa);
        return EOK;
}

/** Add partition to our inventory. */
static errno_t fdisk_part_add(fdisk_dev_t *dev, vbd_part_id_t partid,
    fdisk_part_t **rpart)
{
        fdisk_part_t *part;
        vbd_part_info_t pinfo;
        vol_part_info_t vpinfo;
        errno_t rc;

        part = calloc(1, sizeof(fdisk_part_t));
        if (part == NULL)
                return ENOMEM;

        rc = vbd_part_get_info(dev->fdisk->vbd, partid, &pinfo);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        if (pinfo.svc_id != 0) {
                /*
                 * Normally vol service discovers the partition asynchronously.
                 * Here we need to make sure the partition is already known to it.
                 */
                rc = vol_part_add(dev->fdisk->vol, pinfo.svc_id);
                if (rc != EOK && rc != EEXIST) {
                        rc = EIO;
                        goto error;
                }

                rc = vol_part_info(dev->fdisk->vol, pinfo.svc_id, &vpinfo);
                if (rc != EOK) {
                        rc = EIO;
                        goto error;
                }

                part->pcnt = vpinfo.pcnt;
                part->fstype = vpinfo.fstype;
                part->label = str_dup(vpinfo.label);
        }

        part->dev = dev;
        part->index = pinfo.index;
        part->block0 = pinfo.block0;
        part->nblocks = pinfo.nblocks;
        part->pkind = pinfo.pkind;
        part->svc_id = pinfo.svc_id;

        switch (part->pkind) {
        case lpk_primary:
        case lpk_extended:
                fdisk_pri_part_insert_lists(dev, part);
                break;
        case lpk_logical:
                fdisk_log_part_insert_lists(dev, part);
                break;
        }

        list_append(&part->lparts, &dev->parts);

        if (part->pkind == lpk_extended)
                dev->ext_part = part;

        capa_from_blocks(part->nblocks, dev->dinfo.block_size,
            &part->capacity);
        part->part_id = partid;

        if (rpart != NULL)
                *rpart = part;
        return EOK;
error:
        if (part != NULL)
                free(part->label);
        free(part);
        return rc;
}

/** Remove partition from our inventory. */
static void fdisk_part_remove(fdisk_part_t *part)
{
        list_remove(&part->lparts);
        if (link_used(&part->lpri_ba))
                list_remove(&part->lpri_ba);
        if (link_used(&part->lpri_idx))
                list_remove(&part->lpri_idx);
        if (link_used(&part->llog_ba))
                list_remove(&part->llog_ba);

        free(part->label);
        free(part);
}

static void fdisk_pri_part_insert_lists(fdisk_dev_t *dev, fdisk_part_t *part)
{
        link_t *link;
        fdisk_part_t *p;

        /* Insert to list by block address */
        link = list_first(&dev->pri_ba);
        while (link != NULL) {
                p = list_get_instance(link, fdisk_part_t, lpri_ba);
                if (p->block0 > part->block0) {
                        list_insert_before(&part->lpri_ba, &p->lpri_ba);
                        break;
                }

                link = list_next(link, &dev->pri_ba);
        }

        if (link == NULL)
                list_append(&part->lpri_ba, &dev->pri_ba);

        /* Insert to list by index */
        link = list_first(&dev->pri_idx);
        while (link != NULL) {
                p = list_get_instance(link, fdisk_part_t, lpri_idx);
                if (p->index > part->index) {
                        list_insert_before(&part->lpri_idx, &p->lpri_idx);
                        break;
                }

                link = list_next(link, &dev->pri_idx);
        }

        if (link == NULL)
                list_append(&part->lpri_idx, &dev->pri_idx);
}

static void fdisk_log_part_insert_lists(fdisk_dev_t *dev, fdisk_part_t *part)
{
        link_t *link;
        fdisk_part_t *p;

        /* Insert to list by block address */
        link = list_first(&dev->log_ba);
        while (link != NULL) {
                p = list_get_instance(link, fdisk_part_t, llog_ba);
                if (p->block0 > part->block0) {
                        list_insert_before(&part->llog_ba, &p->llog_ba);
                        break;
                }

                link = list_next(link, &dev->log_ba);
        }

        if (link == NULL)
                list_append(&part->llog_ba, &dev->log_ba);
}

static errno_t fdisk_dev_add_parts(fdisk_dev_t *dev)
{
        service_id_t *psids = NULL;
        size_t nparts, i;
        errno_t rc;

        rc = fdisk_update_dev_info(dev);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        rc = vbd_label_get_parts(dev->fdisk->vbd, dev->sid, &psids, &nparts);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        for (i = 0; i < nparts; i++) {
                rc = fdisk_part_add(dev, psids[i], NULL);
                if (rc != EOK) {
                        goto error;
                }
        }

        free(psids);
        return EOK;
error:
        fdisk_dev_remove_parts(dev);
        return rc;
}

static void fdisk_dev_remove_parts(fdisk_dev_t *dev)
{
        fdisk_part_t *part;

        part = fdisk_part_first(dev);
        while (part != NULL) {
                fdisk_part_remove(part);
                part = fdisk_part_first(dev);
        }
}

errno_t fdisk_dev_open(fdisk_t *fdisk, service_id_t sid, fdisk_dev_t **rdev)
{
        fdisk_dev_t *dev = NULL;
        service_id_t *psids = NULL;
        size_t nparts, i;
        errno_t rc;

        dev = calloc(1, sizeof(fdisk_dev_t));
        if (dev == NULL)
                return ENOMEM;

        dev->fdisk = fdisk;
        dev->sid = sid;
        list_initialize(&dev->parts);
        list_initialize(&dev->pri_idx);
        list_initialize(&dev->pri_ba);
        list_initialize(&dev->log_ba);

        rc = fdisk_update_dev_info(dev);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        rc = vbd_label_get_parts(fdisk->vbd, sid, &psids, &nparts);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        for (i = 0; i < nparts; i++) {
                rc = fdisk_part_add(dev, psids[i], NULL);
                if (rc != EOK) {
                        goto error;
                }
        }

        free(psids);
        *rdev = dev;
        return EOK;
error:
        fdisk_dev_close(dev);
        return rc;
}

void fdisk_dev_close(fdisk_dev_t *dev)
{
        if (dev == NULL)
                return;

        fdisk_dev_remove_parts(dev);
        free(dev);
}

/** Erase contents of unlabeled disk. */
errno_t fdisk_dev_erase(fdisk_dev_t *dev)
{
        fdisk_part_t *part;
        errno_t rc;

        if (dev->dinfo.ltype != lt_none)
                return EINVAL;

        part = fdisk_part_first(dev);
        assert(part != NULL);
        if (part->pcnt == vpc_empty)
                return EINVAL;

        rc = vol_part_empty(dev->fdisk->vol, part->svc_id);
        if (rc != EOK) {
                return rc;
        }

        part->pcnt = vpc_empty;
        return EOK;
}

void fdisk_dev_get_flags(fdisk_dev_t *dev, fdisk_dev_flags_t *rflags)
{
        fdisk_dev_flags_t flags;
        fdisk_part_t *part;

        flags = 0;

        /* fdf_can_create_label */
        if (dev->dinfo.ltype == lt_none) {
                part = fdisk_part_first(dev);
                assert(part != NULL);
                if (part->pcnt == vpc_empty)
                        flags |= fdf_can_create_label;
                else
                        flags |= fdf_can_erase_dev;
        } else {
                flags |= fdf_can_delete_label;
        }

        *rflags = flags;
}

errno_t fdisk_dev_get_svcname(fdisk_dev_t *dev, char **rname)
{
        char *name;
        errno_t rc;

        rc = loc_service_get_name(dev->sid, &name);
        if (rc != EOK)
                return rc;

        *rname = name;
        return EOK;
}

errno_t fdisk_dev_capacity(fdisk_dev_t *dev, capa_spec_t *capa)
{
        capa_from_blocks(dev->dinfo.nblocks, dev->dinfo.block_size, capa);
        return EOK;
}

errno_t fdisk_label_get_info(fdisk_dev_t *dev, fdisk_label_info_t *info)
{
        vbd_disk_info_t vinfo;
        uint64_t b0, nb;
        uint64_t hdrb;
        errno_t rc;

        rc = vbd_disk_info(dev->fdisk->vbd, dev->sid, &vinfo);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        info->ltype = vinfo.ltype;
        info->flags = vinfo.flags;

        if ((info->flags & lf_can_create_pri) != 0 ||
            (info->flags & lf_can_create_ext) != 0) {
                /* Verify there is enough space to create partition */

                rc = fdisk_part_get_max_free_range(dev, spc_pri, &b0, &nb);
                if (rc != EOK)
                        info->flags &= ~(lf_can_create_pri | lf_can_create_ext);
        }

        if ((info->flags & lf_can_create_log) != 0) {
                /* Verify there is enough space to create logical partition */
                hdrb = max(1, dev->align);
                rc = fdisk_part_get_max_free_range(dev, spc_log, &b0, &nb);
                if (rc != EOK || nb <= hdrb)
                        info->flags &= ~lf_can_create_log;
        }

        return EOK;
error:
        return rc;
}

errno_t fdisk_label_create(fdisk_dev_t *dev, label_type_t ltype)
{
        fdisk_part_t *part;
        errno_t rc;

        /* Disk must not contain a label. */
        if (dev->dinfo.ltype != lt_none)
                return EEXIST;

        /* Dummy partition spanning entire disk must be considered empty */
        part = fdisk_part_first(dev);
        assert(part != NULL);
        if (part->pcnt != vpc_empty)
                return EEXIST;

        /* Remove dummy partition */
        fdisk_dev_remove_parts(dev);

        rc = vbd_label_create(dev->fdisk->vbd, dev->sid, ltype);
        if (rc != EOK) {
                /* Re-add dummy partition */
                (void) fdisk_dev_add_parts(dev);
                return rc;
        }

        rc = fdisk_update_dev_info(dev);
        if (rc != EOK)
                return rc;

        return EOK;
}

errno_t fdisk_label_destroy(fdisk_dev_t *dev)
{
        fdisk_part_t *part;
        fdisk_dev_flags_t dflags;
        errno_t rc;

        part = fdisk_part_first(dev);
        while (part != NULL) {
                rc = fdisk_part_destroy(part);
                if (rc != EOK)
                        return EIO;
                part = fdisk_part_first(dev);
        }

        rc = vbd_label_delete(dev->fdisk->vbd, dev->sid);
        if (rc != EOK)
                return EIO;

        rc = fdisk_dev_add_parts(dev);
        if (rc != EOK)
                return rc;

        /* Make sure device is considered empty */
        fdisk_dev_get_flags(dev, &dflags);
        if ((dflags & fdf_can_erase_dev) != 0) {
                rc = fdisk_dev_erase(dev);
                if (rc != EOK)
                        return rc;
        }

        return EOK;
}

fdisk_part_t *fdisk_part_first(fdisk_dev_t *dev)
{
        link_t *link;

        link = list_first(&dev->parts);
        if (link == NULL)
                return NULL;

        return list_get_instance(link, fdisk_part_t, lparts);
}

fdisk_part_t *fdisk_part_next(fdisk_part_t *part)
{
        link_t *link;

        link = list_next(&part->lparts, &part->dev->parts);
        if (link == NULL)
                return NULL;

        return list_get_instance(link, fdisk_part_t, lparts);
}

errno_t fdisk_part_get_info(fdisk_part_t *part, fdisk_part_info_t *info)
{
        info->capacity = part->capacity;
        info->pcnt = part->pcnt;
        info->fstype = part->fstype;
        info->pkind = part->pkind;
        info->label = part->label;
        info->svc_id = part->svc_id;
        return EOK;
}

/** Get size of largest free block. */
errno_t fdisk_part_get_max_avail(fdisk_dev_t *dev, fdisk_spc_t spc,
    capa_spec_t *capa)
{
        errno_t rc;
        uint64_t b0;
        uint64_t nb;
        aoff64_t hdrb;

        rc = fdisk_part_get_max_free_range(dev, spc, &b0, &nb);
        if (rc != EOK)
                return rc;

        /* For logical partitions we need to subtract header size */
        if (spc == spc_log) {
                hdrb = max(1, dev->align);
                if (nb <= hdrb)
                        return ENOSPC;
                nb -= hdrb;
        }

        capa_from_blocks(nb, dev->dinfo.block_size, capa);
        return EOK;
}

/** Get total free space capacity. */
errno_t fdisk_part_get_tot_avail(fdisk_dev_t *dev, fdisk_spc_t spc,
    capa_spec_t *capa)
{
        fdisk_free_range_t fr;
        uint64_t hdrb;
        uint64_t b0;
        uint64_t nb;
        uint64_t totb;

        if (spc == spc_log)
                hdrb = max(1, dev->align);
        else
                hdrb = 0;

        totb = 0;
        fdisk_free_range_first(dev, spc, &fr);
        do {
                if (fdisk_free_range_get(&fr, &b0, &nb)) {
                        if (nb > hdrb)
                                totb += nb - hdrb;
                }
        } while (fdisk_free_range_next(&fr));

        capa_from_blocks(totb, dev->dinfo.block_size, capa);
        return EOK;
}

/** Create partition.
 *
 * Create new partition based on a specification.
 *
 * @param dev Fdisk device
 * @param pspec Partition specification
 * @param rpart Place to store pointer to new partition
 *
 * @return EOK on success or error code
 */
errno_t fdisk_part_create(fdisk_dev_t *dev, fdisk_part_spec_t *pspec,
    fdisk_part_t **rpart)
{
        fdisk_part_t *part = NULL;
        vbd_part_spec_t vpspec;
        vbd_part_id_t partid = 0;
        vol_part_info_t vpinfo;
        const char *label;
        const char *mountp;
        errno_t rc;

        label = pspec->label != NULL ? pspec->label : "";
        mountp = pspec->mountp != NULL ? pspec->mountp : "";

        rc = fdisk_part_spec_prepare(dev, pspec, &vpspec);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        rc = vbd_part_create(dev->fdisk->vbd, dev->sid, &vpspec, &partid);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        rc = fdisk_part_add(dev, partid, &part);
        if (rc != EOK) {
                rc = EIO;
                goto error;
        }

        if (part->svc_id != 0) {
                rc = vol_part_mkfs(dev->fdisk->vol, part->svc_id, pspec->fstype,
                    label, mountp);
                if (rc != EOK && rc != ENOTSUP) {
                        rc = EIO;
                        goto error;
                }

                /* Get the real label value */
                rc = vol_part_info(dev->fdisk->vol, part->svc_id, &vpinfo);
                if (rc != EOK) {
                        rc = EIO;
                        goto error;
                }

                part->pcnt = vpinfo.pcnt;
                part->fstype = vpinfo.fstype;
                part->label = str_dup(vpinfo.label);

                if (part->label == NULL) {
                        rc = EIO;
                        goto error;
                }
        }

        if (rpart != NULL)
                *rpart = part;
        return EOK;
error:
        /* Try rolling back */
        if (part != NULL)
                fdisk_part_remove(part);
        if (partid != 0)
                (void) vbd_part_delete(dev->fdisk->vbd, partid);
        return rc;
}

/** Destroy partition.
 *
 * @param part Partition
 * @return EOK on success or error code
 */
errno_t fdisk_part_destroy(fdisk_part_t *part)
{
        errno_t rc;

        rc = vol_part_eject(part->dev->fdisk->vol, part->svc_id);
        if (rc != EOK)
                return EIO;

        rc = vbd_part_delete(part->dev->fdisk->vbd, part->part_id);
        if (rc != EOK)
                return EIO;

        fdisk_part_remove(part);
        return EOK;
}

/** Set partition mount point.
 *
 * @param part Fdisk partition
 * @param mountp Mount point
 *
 * @return EOK on success or error code
 */
errno_t fdisk_part_set_mountp(fdisk_part_t *part, const char *mountp)
{
        return vol_part_set_mountp(part->dev->fdisk->vol,
            part->svc_id, mountp);
}

void fdisk_pspec_init(fdisk_part_spec_t *pspec)
{
        memset(pspec, 0, sizeof(fdisk_part_spec_t));
}

/** Get free partition index. */
static errno_t fdisk_part_get_free_idx(fdisk_dev_t *dev, int *rindex)
{
        link_t *link;
        fdisk_part_t *part;
        int nidx;

        link = list_first(&dev->pri_idx);
        nidx = 1;
        while (link != NULL) {
                part = list_get_instance(link, fdisk_part_t, lpri_idx);
                if (part->index > nidx)
                        break;
                nidx = part->index + 1;
                link = list_next(link, &dev->pri_idx);
        }

        if (nidx > 4 /* XXXX actual number of slots */) {
                return ELIMIT;
        }

        *rindex = nidx;
        return EOK;
}

/** Get free range of blocks.
 *
 * Get free range of blocks of at least the specified size (first fit).
 */
static errno_t fdisk_part_get_free_range(fdisk_dev_t *dev, aoff64_t nblocks,
    fdisk_spc_t spc, aoff64_t *rblock0, aoff64_t *rnblocks)
{
        fdisk_free_range_t fr;
        uint64_t b0;
        uint64_t nb;

        fdisk_free_range_first(dev, spc, &fr);
        do {
                if (fdisk_free_range_get(&fr, &b0, &nb)) {
                        if (nb >= nblocks) {
                                *rblock0 = b0;
                                *rnblocks = nb;
                                return EOK;
                        }
                }
        } while (fdisk_free_range_next(&fr));

        /* No conforming free range found */
        return ENOSPC;
}

/** Get largest free range of blocks.
 *
 * Get free range of blocks of the maximum size.
 */
static errno_t fdisk_part_get_max_free_range(fdisk_dev_t *dev, fdisk_spc_t spc,
    aoff64_t *rblock0, aoff64_t *rnblocks)
{
        fdisk_free_range_t fr;
        uint64_t b0;
        uint64_t nb;
        uint64_t best_b0;
        uint64_t best_nb;

        best_b0 = best_nb = 0;
        fdisk_free_range_first(dev, spc, &fr);
        do {
                if (fdisk_free_range_get(&fr, &b0, &nb)) {
                        if (nb > best_nb) {
                                best_b0 = b0;
                                best_nb = nb;
                        }
                }
        } while (fdisk_free_range_next(&fr));

        if (best_nb == 0)
                return ENOSPC;

        *rblock0 = best_b0;
        *rnblocks = best_nb;
        return EOK;
}

/** Prepare new partition specification for VBD. */
static errno_t fdisk_part_spec_prepare(fdisk_dev_t *dev, fdisk_part_spec_t *pspec,
    vbd_part_spec_t *vpspec)
{
        aoff64_t nom_blocks;
        aoff64_t min_blocks;
        aoff64_t max_blocks;
        aoff64_t act_blocks;
        aoff64_t fblock0;
        aoff64_t fnblocks;
        aoff64_t hdrb;
        label_pcnt_t pcnt;
        fdisk_spc_t spc;
        int index;
        errno_t rc;

        rc = capa_to_blocks(&pspec->capacity, cv_nom, dev->dinfo.block_size,
            &nom_blocks);
        if (rc != EOK)
                return rc;

        rc = capa_to_blocks(&pspec->capacity, cv_min, dev->dinfo.block_size,
            &min_blocks);
        if (rc != EOK)
                return rc;

        rc = capa_to_blocks(&pspec->capacity, cv_max, dev->dinfo.block_size,
            &max_blocks);
        if (rc != EOK)
                return rc;

        nom_blocks = fdisk_ba_align_up(dev, nom_blocks);
        min_blocks = fdisk_ba_align_up(dev, min_blocks);
        max_blocks = fdisk_ba_align_up(dev, max_blocks);

        pcnt = LPC_LIMIT;

        switch (pspec->fstype) {
        case fs_exfat:
                pcnt = lpc_exfat;
                break;
        case fs_fat:
                pcnt = lpc_fat32; /* XXX Detect FAT12/16 vs FAT32 */
                break;
        case fs_minix:
                pcnt = lpc_minix;
                break;
        case fs_ext4:
                pcnt = lpc_ext4;
                break;
        case fs_cdfs:
                return EINVAL; /* You cannot create an ISO partition */
        }

        if (pcnt == LPC_LIMIT)
                return EINVAL;

        if (pspec->pkind == lpk_logical) {
                hdrb = max(1, dev->align);
                spc = spc_log;
        } else {
                hdrb = 0;
                spc = spc_pri;
        }

        rc = fdisk_part_get_free_range(dev, hdrb + nom_blocks, spc,
            &fblock0, &fnblocks);

        if (rc == EOK) {
                /*
                 * If the size of the free range would still give the same capacity
                 * when rounded, allocate entire range. Otherwise allocate exactly
                 * what we were asked for.
                 */
                if (fnblocks <= max_blocks) {
                        act_blocks = fnblocks;
                } else {
                        act_blocks = hdrb + nom_blocks;
                }
        } else {
                assert(rc == ENOSPC);

                /*
                 * There is no free range that can contain exactly the requested
                 * capacity. Try to allocate at least such number of blocks
                 * that would still fullfill the request within the limits
                 * of the precision with witch the capacity was specified
                 * (i.e. when rounded up).
                 */
                rc = fdisk_part_get_free_range(dev, hdrb + min_blocks, spc,
                    &fblock0, &fnblocks);
                if (rc != EOK)
                        return rc;

                assert(fnblocks < hdrb + nom_blocks);
                act_blocks = fnblocks;
        }

        if (pspec->pkind != lpk_logical) {
                rc = fdisk_part_get_free_idx(dev, &index);
                if (rc != EOK)
                        return EIO;
        } else {
                index = 0;
        }

        memset(vpspec, 0, sizeof(vbd_part_spec_t));
        vpspec->index = index;
        vpspec->hdr_blocks = hdrb;
        vpspec->block0 = fblock0 + hdrb;
        vpspec->nblocks = act_blocks - hdrb;
        vpspec->pkind = pspec->pkind;

        if (pspec->pkind != lpk_extended) {
                rc = vbd_suggest_ptype(dev->fdisk->vbd, dev->sid, pcnt,
                    &vpspec->ptype);
                if (rc != EOK)
                        return EIO;
        }

        return EOK;
}

static errno_t fdisk_update_dev_info(fdisk_dev_t *dev)
{
        errno_t rc;
        size_t align_bytes;
        uint64_t avail_cap;

        rc = vbd_disk_info(dev->fdisk->vbd, dev->sid, &dev->dinfo);
        if (rc != EOK)
                return EIO;

        /* Capacity available for partition in bytes */
        avail_cap = dev->dinfo.anblocks * dev->dinfo.block_size;

        /* Determine optimum alignment */
        align_bytes = 1024 * 1024; /* 1 MiB */
        while (align_bytes > 1 && avail_cap / align_bytes < 256) {
                align_bytes = align_bytes / 16;
        }

        dev->align = align_bytes / dev->dinfo.block_size;
        if (dev->align < 1)
                dev->align = 1;
        return EOK;
}

static uint64_t fdisk_ba_align_up(fdisk_dev_t *dev, uint64_t ba)
{
        return ((ba + dev->align - 1) / dev->align) * dev->align;
}

static uint64_t fdisk_ba_align_down(fdisk_dev_t *dev, uint64_t ba)
{
        return ba - (ba % dev->align);
}

static void fdisk_free_range_first(fdisk_dev_t *dev, fdisk_spc_t spc,
    fdisk_free_range_t *fr)
{
        link_t *link;

        fr->dev = dev;
        fr->spc = spc;

        if (fr->spc == spc_pri) {
                /* Primary partitions */
                fr->b0 = fdisk_ba_align_up(fr->dev, fr->dev->dinfo.ablock0);

                link = list_first(&dev->pri_ba);
                if (link != NULL)
                        fr->npart = list_get_instance(link, fdisk_part_t, lpri_ba);
                else
                        fr->npart = NULL;
        } else { /* fr->spc == spc_log */
                /* Logical partitions */
                fr->b0 = fdisk_ba_align_up(fr->dev, fr->dev->ext_part->block0);

                link = list_first(&dev->log_ba);
                if (link != NULL)
                        fr->npart = list_get_instance(link, fdisk_part_t, llog_ba);
                else
                        fr->npart = NULL;
        }
}

static bool fdisk_free_range_next(fdisk_free_range_t *fr)
{
        link_t *link;

        if (fr->npart == NULL)
                return false;

        fr->b0 = fdisk_ba_align_up(fr->dev, fr->npart->block0 +
            fr->npart->nblocks);

        if (fr->spc == spc_pri) {
                /* Primary partitions */
                link = list_next(&fr->npart->lpri_ba, &fr->dev->pri_ba);
                if (link != NULL)
                        fr->npart = list_get_instance(link, fdisk_part_t, lpri_ba);
                else
                        fr->npart = NULL;
        } else { /* fr->spc == spc_log */
                /* Logical partitions */
                link = list_next(&fr->npart->llog_ba, &fr->dev->log_ba);
                if (link != NULL)
                        fr->npart = list_get_instance(link, fdisk_part_t, llog_ba);
                else
                        fr->npart = NULL;
        }

        return true;
}

static bool fdisk_free_range_get(fdisk_free_range_t *fr,
    aoff64_t *b0, aoff64_t *nb)
{
        aoff64_t b1;

        if (fr->npart != NULL) {
                b1 = fdisk_ba_align_down(fr->dev, fr->npart->block0);
        } else {
                if (fr->spc == spc_pri) {
                        b1 = fdisk_ba_align_down(fr->dev,
                            fr->dev->dinfo.ablock0 + fr->dev->dinfo.anblocks);
                } else { /* fr->spc == spc_log */
                        b1 = fdisk_ba_align_down(fr->dev,
                            fr->dev->ext_part->block0 + fr->dev->ext_part->nblocks);
                }
        }

        if (b1 < fr->b0)
                return false;

        *b0 = fr->b0;
        *nb = b1 - fr->b0;

        return true;
}

/** Get volume label support. */
errno_t fdisk_get_vollabel_support(fdisk_dev_t *dev, vol_fstype_t fstype,
    vol_label_supp_t *vlsupp)
{
        return vol_part_get_lsupp(dev->fdisk->vol, fstype, vlsupp);
}

/** @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub