HelenOS sources

root/uspace/srv/volsrv/volume.c

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

DEFINITIONS

This source file includes following definitions.
  1. vol_volume_new
  2. vol_volume_delete
  3. vol_volumes_create
  4. vol_volumes_merge_to
  5. vol_volumes_sync
  6. vol_volumes_destroy
  7. vol_volume_add_locked
  8. vol_volume_lookup_ref_locked
  9. vol_volume_lookup_ref
  10. vol_volume_find_by_id_ref_locked
  11. vol_volume_find_by_id_ref
  12. vol_volume_is_persist
  13. vol_volume_del_ref
  14. vol_volume_set_mountp
  15. vol_get_ids
  16. vol_volumes_load
  17. vol_volumes_save
  18. vol_volume_get_info

/*
 * 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 volsrv
 * @{
 */
/**
 * @file Volume handling
 * @brief
 *
 * Volumes are the file systems (or similar) contained in partitions.
 * Each vol_volume_t can be considered the configuration entry
 * for a volume. Each partition has an associated vol_volume_t.
 *
 * If there is any non-default configuration to be remembered for a
 * volume, the vol_volume_t structure is kept around even after the partition
 * is disassociated from it. Otherwise it is deleted once no longer
 * referenced.
 */

#include <adt/list.h>
#include <errno.h>
#include <fibril_synch.h>
#include <io/log.h>
#include <sif.h>
#include <stdbool.h>
#include <stdlib.h>
#include <str.h>

#include "volume.h"
#include "types/volume.h"

static void vol_volume_delete(vol_volume_t *);
static void vol_volume_add_locked(vol_volumes_t *, vol_volume_t *);
static errno_t vol_volume_lookup_ref_locked(vol_volumes_t *, const char *,
    vol_volume_t **);
static errno_t vol_volumes_load(sif_node_t *, vol_volumes_t *);
static errno_t vol_volumes_save(vol_volumes_t *, sif_node_t *);

/** Allocate new volume structure.
 *
 * @return Pointer to new volume structure
 */
static vol_volume_t *vol_volume_new(void)
{
        vol_volume_t *volume = calloc(1, sizeof(vol_volume_t));

        if (volume == NULL) {
                log_msg(LOG_DEFAULT, LVL_ERROR, "Failed allocating volume "
                    "structure. Out of memory.");
                return NULL;
        }

        volume->label = str_dup("");
        volume->mountp = str_dup("");

        if (volume->label == NULL || volume->mountp == NULL) {
                vol_volume_delete(volume);
                return NULL;
        }

        refcount_init(&volume->refcnt);
        link_initialize(&volume->lvolumes);
        volume->volumes = NULL;

        return volume;
}

/** Delete volume structure.
 *
 * @param volume Volume structure
 */
static void vol_volume_delete(vol_volume_t *volume)
{
        log_msg(LOG_DEFAULT, LVL_DEBUG, "Freeing volume %p", volume);

        free(volume->label);
        free(volume->mountp);
        free(volume);
}

/** Create list of volumes.
 *
 * @param cfg_path Path to file containing configuration repository in SIF
 * @param rvolumes Place to store pointer to list of volumes.
 * @return EOK on success, ENOMEM if out of memory
 */
errno_t vol_volumes_create(const char *cfg_path,
    vol_volumes_t **rvolumes)
{
        vol_volumes_t *volumes;
        sif_doc_t *doc = NULL;
        sif_node_t *node;
        sif_node_t *nvolumes;
        const char *ntype;
        errno_t rc;

        volumes = calloc(1, sizeof(vol_volumes_t));
        if (volumes == NULL)
                return ENOMEM;

        volumes->cfg_path = str_dup(cfg_path);
        if (volumes->cfg_path == NULL) {
                rc = ENOMEM;
                goto error;
        }

        fibril_mutex_initialize(&volumes->lock);
        list_initialize(&volumes->volumes);
        volumes->next_id = 1;

        /* Try opening existing repository */
        rc = sif_load(cfg_path, &doc);
        if (rc != EOK) {
                /* Failed to open existing, create new repository */
                rc = sif_new(&doc);
                if (rc != EOK)
                        goto error;

                /* Create 'volumes' node. */
                rc = sif_node_append_child(sif_get_root(doc), "volumes",
                    &nvolumes);
                if (rc != EOK)
                        goto error;

                rc = sif_save(doc, cfg_path);
                if (rc != EOK)
                        goto error;

                sif_delete(doc);
        } else {
                /*
                 * Loaded existing configuration. Find 'volumes' node, should
                 * be the first child of the root node.
                 */
                node = sif_node_first_child(sif_get_root(doc));

                /* Verify it's the correct node type */
                ntype = sif_node_get_type(node);
                if (str_cmp(ntype, "volumes") != 0) {
                        rc = EIO;
                        goto error;
                }

                rc = vol_volumes_load(node, volumes);
                if (rc != EOK)
                        goto error;

                sif_delete(doc);
        }

        *rvolumes = volumes;
        return EOK;
error:
        if (doc != NULL)
                (void) sif_delete(doc);
        if (volumes != NULL && volumes->cfg_path != NULL)
                free(volumes->cfg_path);
        if (volumes != NULL)
                free(volumes);

        return rc;
}

/** Merge list of volumes into new file.
 *
 * @param volumes List of volumes
 * @param cfg_path Path to file containing configuration repository in SIF
 * @return EOK on success, ENOMEM if out of memory
 */
errno_t vol_volumes_merge_to(vol_volumes_t *volumes, const char *cfg_path)
{
        sif_doc_t *doc = NULL;
        sif_node_t *node;
        const char *ntype;
        char *dcfg_path;
        errno_t rc;

        dcfg_path = str_dup(cfg_path);
        if (dcfg_path == NULL) {
                rc = ENOMEM;
                goto error;
        }

        free(volumes->cfg_path);
        volumes->cfg_path = dcfg_path;

        /* Try opening existing repository */
        rc = sif_load(cfg_path, &doc);
        if (rc != EOK) {
                /* Failed to open existing, create new repository */
                rc = vol_volumes_sync(volumes);
                if (rc != EOK)
                        goto error;
        } else {
                /*
                 * Loaded existing configuration. Find 'volumes' node, should
                 * be the first child of the root node.
                 */
                node = sif_node_first_child(sif_get_root(doc));

                /* Verify it's the correct node type */
                ntype = sif_node_get_type(node);
                if (str_cmp(ntype, "volumes") != 0) {
                        rc = EIO;
                        goto error;
                }

                rc = vol_volumes_load(node, volumes);
                if (rc != EOK)
                        goto error;

                sif_delete(doc);
        }

        return EOK;
error:
        if (doc != NULL)
                (void) sif_delete(doc);
        return rc;
}

/** Sync volume configuration to config file.
 *
 * @param volumes List of volumes
 * @return EOK on success, ENOMEM if out of memory
 */
errno_t vol_volumes_sync(vol_volumes_t *volumes)
{
        sif_doc_t *doc = NULL;
        errno_t rc;

        rc = sif_new(&doc);
        if (rc != EOK)
                goto error;

        rc = vol_volumes_save(volumes, sif_get_root(doc));
        if (rc != EOK)
                goto error;

        rc = sif_save(doc, volumes->cfg_path);
        if (rc != EOK)
                goto error;

        sif_delete(doc);
        return EOK;
error:
        if (doc != NULL)
                (void) sif_delete(doc);
        return rc;
}

/** Destroy list of volumes.
 *
 * @param volumes List of volumes or @c NULL
 */
void vol_volumes_destroy(vol_volumes_t *volumes)
{
        link_t *link;
        vol_volume_t *volume;

        if (volumes == NULL)
                return;

        link = list_first(&volumes->volumes);
        while (link != NULL) {
                volume = list_get_instance(link, vol_volume_t, lvolumes);

                list_remove(&volume->lvolumes);
                vol_volume_delete(volume);

                link = list_first(&volumes->volumes);
        }

        free(volumes->cfg_path);
        free(volumes);
}

/** Add new volume structure to list of volumes.
 *
 * @param volumes List of volumes
 * @param volume Volume structure
 */
static void vol_volume_add_locked(vol_volumes_t *volumes,
    vol_volume_t *volume)
{
        assert(fibril_mutex_is_locked(&volumes->lock));
        log_msg(LOG_DEFAULT, LVL_DEBUG, "vol_volume_add_locked(%p)", volume);

        volume->volumes = volumes;
        list_append(&volume->lvolumes, &volumes->volumes);
        volume->id.id = volumes->next_id;
        ++volumes->next_id;
}

/** Look up volume structure with locked volumes lock.
 *
 * If a matching existing volume is found, it is returned. Otherwise
 * a new volume structure is created.
 *
 * @param volumes List of volumes
 * @param label Volume label
 * @param rvolume Place to store pointer to volume structure (existing or new)
 *
 * @return EOK on success, ENOMEM if out of memory
 */
static errno_t vol_volume_lookup_ref_locked(vol_volumes_t *volumes,
    const char *label, vol_volume_t **rvolume)
{
        vol_volume_t *volume;

        assert(fibril_mutex_is_locked(&volumes->lock));

        list_foreach(volumes->volumes, lvolumes, vol_volume_t, volume) {
                if (str_cmp(volume->label, label) == 0 &&
                    str_size(label) > 0) {
                        /* Add reference */
                        refcount_up(&volume->refcnt);
                        *rvolume = volume;
                        return EOK;
                }
        }

        /* No existing volume found. Create a new one. */
        volume = vol_volume_new();
        if (volume == NULL)
                return ENOMEM;

        free(volume->label);
        volume->label = str_dup(label);

        if (volume->label == NULL) {
                vol_volume_delete(volume);
                return ENOMEM;
        }

        vol_volume_add_locked(volumes, volume);

        *rvolume = volume;
        return EOK;
}

/** Look up volume structure.
 *
 * If a matching existing volume is found, it is returned. Otherwise
 * a new volume structure is created.
 *
 * @param volumes List of volumes
 * @param label Volume label
 * @param rvolume Place to store pointer to volume structure (existing or new)
 *
 * @return EOK on success, ENOMEM if out of memory
 */
errno_t vol_volume_lookup_ref(vol_volumes_t *volumes, const char *label,
    vol_volume_t **rvolume)
{
        errno_t rc;

        fibril_mutex_lock(&volumes->lock);
        rc = vol_volume_lookup_ref_locked(volumes, label, rvolume);
        fibril_mutex_unlock(&volumes->lock);

        return rc;
}

/** Find volume structure by ID with locked volumes lock.
 * *
 * @param volumes List of volumes
 * @param vid Volume ID
 * @param rvolume Place to store pointer to volume structure (existing or new)
 *
 * @return EOK on success, ENOENT if not found
 */
static errno_t vol_volume_find_by_id_ref_locked(vol_volumes_t *volumes,
    volume_id_t vid, vol_volume_t **rvolume)
{
        assert(fibril_mutex_is_locked(&volumes->lock));

        list_foreach(volumes->volumes, lvolumes, vol_volume_t, volume) {
                log_msg(LOG_DEFAULT, LVL_DEBUG2,
                    "vol_volume_find_by_id_ref_locked(%zu==%zu)?",
                    volume->id.id, vid.id);
                if (volume->id.id == vid.id) {
                        log_msg(LOG_DEFAULT, LVL_DEBUG2,
                            "vol_volume_find_by_id_ref_locked: found");
                        /* Add reference */
                        refcount_up(&volume->refcnt);
                        *rvolume = volume;
                        return EOK;
                }
        }

        log_msg(LOG_DEFAULT, LVL_DEBUG2,
            "vol_volume_find_by_id_ref_locked: not found");
        return ENOENT;
}

/** Find volume by ID.
 *
 * @param volumes Volumes
 * @param vid Volume ID
 * @param rvolume Place to store pointer to volume, with reference count
 *                increased.
 * @return EOK on success or an error code
 */
errno_t vol_volume_find_by_id_ref(vol_volumes_t *volumes, volume_id_t vid,
    vol_volume_t **rvolume)
{
        errno_t rc;

        fibril_mutex_lock(&volumes->lock);
        rc = vol_volume_find_by_id_ref_locked(volumes, vid, rvolume);
        fibril_mutex_unlock(&volumes->lock);

        return rc;
}

/** Determine if volume has non-default settings that need to persist.
 *
 * @param volume Volume
 * @return @c true iff volume has settings that need to persist
 */
static bool vol_volume_is_persist(vol_volume_t *volume)
{
        return str_size(volume->mountp) > 0;
}

/** Delete reference to volume.
 *
 * @param volume Volume
 */
void vol_volume_del_ref(vol_volume_t *volume)
{
        if (refcount_down(&volume->refcnt)) {
                /* No more references. Check if volume is persistent. */
                list_remove(&volume->lvolumes);
                vol_volume_delete(volume);
        }
}

/** Set volume mount point.
 *
 * @param volume Volume
 * @param mountp Mount point
 *
 * @return EOK on success or error code
 */
errno_t vol_volume_set_mountp(vol_volume_t *volume, const char *mountp)
{
        char *mp;
        char *old_mp;
        bool was_persist;

        mp = str_dup(mountp);
        if (mp == NULL)
                return ENOMEM;

        was_persist = vol_volume_is_persist(volume);

        old_mp = volume->mountp;
        volume->mountp = mp;

        if (vol_volume_is_persist(volume)) {
                if (!was_persist) {
                        /*
                         * Volume is now persistent. Prevent it from being
                         * freed.
                         */
                        refcount_up(&volume->refcnt);
                }
        } else {
                if (was_persist) {
                        /*
                         * Volume is now non-persistent
                         * Allow volume to be freed.
                         */
                        vol_volume_del_ref(volume);
                }
        }

        vol_volumes_sync(volume->volumes);
        free(old_mp);
        return EOK;
}

/** Get list of volume IDs.
 *
 * Get the list of IDs of all persistent volumes (volume configuration
 * entries).
 *
 * @param volumes Volumes
 * @param id_buf Buffer to hold the IDs
 * @param buf_size Buffer size in bytes
 * @param act_size Place to store actual number bytes needed
 * @return EOK on success or an error code
 */
errno_t vol_get_ids(vol_volumes_t *volumes, volume_id_t *id_buf,
    size_t buf_size, size_t *act_size)
{
        size_t act_cnt;
        size_t buf_cnt;

        fibril_mutex_lock(&volumes->lock);

        buf_cnt = buf_size / sizeof(volume_id_t);

        act_cnt = 0;
        list_foreach(volumes->volumes, lvolumes, vol_volume_t, volume) {
                if (vol_volume_is_persist(volume))
                        ++act_cnt;
        }
        *act_size = act_cnt * sizeof(volume_id_t);

        if (buf_size % sizeof(volume_id_t) != 0) {
                fibril_mutex_unlock(&volumes->lock);
                return EINVAL;
        }

        size_t pos = 0;
        list_foreach(volumes->volumes, lvolumes, vol_volume_t, volume) {
                if (vol_volume_is_persist(volume)) {
                        if (pos < buf_cnt)
                                id_buf[pos].id = volume->id.id;
                        pos++;
                }
        }

        fibril_mutex_unlock(&volumes->lock);
        return EOK;
}

/** Load volumes from SIF document.
 *
 * @param nvolumes Volumes node
 * @param volumes Volumes object
 *
 * @return EOK on success or error code
 */
static errno_t vol_volumes_load(sif_node_t *nvolumes, vol_volumes_t *volumes)
{
        sif_node_t *nvolume;
        vol_volume_t *volume = NULL;
        const char *label;
        const char *mountp;
        errno_t rc;

        nvolume = sif_node_first_child(nvolumes);
        while (nvolume != NULL) {
                if (str_cmp(sif_node_get_type(nvolume), "volume") != 0) {
                        rc = EIO;
                        goto error;
                }

                volume = vol_volume_new();
                if (volume == NULL) {
                        rc = ENOMEM;
                        goto error;
                }

                label = sif_node_get_attr(nvolume, "label");
                mountp = sif_node_get_attr(nvolume, "mountp");

                if (label == NULL || mountp == NULL) {
                        rc = EIO;
                        goto error;
                }

                free(volume->label);
                free(volume->mountp);

                volume->label = str_dup(label);
                volume->mountp = str_dup(mountp);

                fibril_mutex_lock(&volumes->lock);
                vol_volume_add_locked(volumes, volume);
                fibril_mutex_unlock(&volumes->lock);
                nvolume = sif_node_next_child(nvolume);
        }

        return EOK;
error:
        if (volume != NULL)
                vol_volume_delete(volume);
        return rc;
}

/** Save volumes to SIF document.
 *
 * @param volumes List of volumes
 * @param rnode Configuration root node
 * @return EOK on success, ENOMEM if out of memory
 */
errno_t vol_volumes_save(vol_volumes_t *volumes, sif_node_t *rnode)
{
        sif_node_t *nvolumes;
        sif_node_t *node;
        link_t *link;
        vol_volume_t *volume;
        errno_t rc;

        /* Create 'volumes' node. */
        rc = sif_node_append_child(rnode, "volumes", &nvolumes);
        if (rc != EOK)
                goto error;

        link = list_first(&volumes->volumes);
        while (link != NULL) {
                volume = list_get_instance(link, vol_volume_t, lvolumes);

                if (vol_volume_is_persist(volume)) {
                        /* Create 'volume' node. */
                        rc = sif_node_append_child(nvolumes, "volume", &node);
                        if (rc != EOK)
                                goto error;

                        rc = sif_node_set_attr(node, "label", volume->label);
                        if (rc != EOK)
                                goto error;

                        rc = sif_node_set_attr(node, "mountp", volume->mountp);
                        if (rc != EOK)
                                goto error;
                }

                link = list_next(&volume->lvolumes, &volumes->volumes);
        }

        return EOK;
error:
        return rc;
}

/** Get volume information.
 *
 * @param volume Volume
 * @param vinfo Volume information structure to safe info to
 * @return EOK on success or an error code
 */
errno_t vol_volume_get_info(vol_volume_t *volume, vol_info_t *vinfo)
{
        vinfo->id = volume->id;
        str_cpy(vinfo->label, sizeof(vinfo->label), volume->label);
        str_cpy(vinfo->path, sizeof(vinfo->path), volume->mountp);
        return EOK;
}

/** @}
 */

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