HEX
Server: LiteSpeed
System: Linux CentOS-79-64-minimal 3.10.0-1160.119.1.el7.x86_64 #1 SMP Tue Jun 4 14:43:51 UTC 2024 x86_64
User: vishn3436 (5293)
PHP: 8.0.15
Disabled: NONE
Upload Files
File: //usr/src/dattobd-0.11.2/ioctl_handlers.c
// SPDX-License-Identifier: GPL-2.0-only

/*
 * Copyright (C) 2022 Datto Inc.
 */

#include "ioctl_handlers.h"

#include "blkdev.h"
#include "dattobd.h"
#include "hints.h"
#include "includes.h"
#include "logging.h"
#include "module_control.h"
#include "snap_device.h"
#include "tracer.h"
#include "tracer_helper.h"
#include "userspace_copy_helpers.h"

#ifdef HAVE_UAPI_MOUNT_H
#include <uapi/linux/mount.h>
#endif

#define verify_minor_available(minor) __verify_minor(minor, 0)
#define verify_minor_in_use_not_busy(minor) __verify_minor(minor, 1)
#define verify_minor_in_use(minor) __verify_minor(minor, 2)

#define ioctl_setup_snap(minor, bdev_path, cow_path, fallocated_space,         \
                         cache_size)                                           \
        __ioctl_setup(minor, bdev_path, cow_path, fallocated_space,            \
                      cache_size, 1, 0)
#define ioctl_reload_snap(minor, bdev_path, cow_path, cache_size)              \
        __ioctl_setup(minor, bdev_path, cow_path, 0, cache_size, 1, 1)
#define ioctl_reload_inc(minor, bdev_path, cow_path, cache_size)               \
        __ioctl_setup(minor, bdev_path, cow_path, 0, cache_size, 0, 1)

struct mutex ioctl_mutex;

/************************IOCTL HANDLER FUNCTIONS************************/

/**
 * __verify_minor() - Verify the supplied minor device number according to the
 *                    requested mode.
 *
 * @minor: the minor number to check.
 * @mode: what to verify:
 * * 0: the minor is not in allocated.
 * * 1: the minor is allocated and is not busy.
 * * 2: the minor is allocated whether busy or not.
 *
 * Return:
 * * 0 - successfully validated.
 * * 1 - a negative errno otherwise.
 */
static int __verify_minor(unsigned int minor, int mode)
{
        // check minor number is within range
        if (minor >= dattobd_max_snap_devices) {
                LOG_ERROR(-EINVAL, "minor number specified is out of range");
                return -EINVAL;
        }

        // check if the device is in use
        if (mode == 0) {
                if (snap_devices[minor]) {
                        LOG_ERROR(-EBUSY, "device specified already exists");
                        return -EBUSY;
                }
        } else {
                if (!snap_devices[minor]) {
                        LOG_ERROR(-ENOENT, "device specified does not exist");
                        return -ENOENT;
                }

                // check that the device is not busy if we care
                if (mode == 1 && atomic_read(&snap_devices[minor]->sd_refs)) {
                        LOG_ERROR(-EBUSY, "device specified is busy");
                        return -EBUSY;
                }
        }

        return 0;
}

/**
 * __verify_bdev_writable() - Determines if the block device is writable.
 *
 * @bdev_path: the path to the block device.
 * @out: the result
 *
 * Return:
 * * 0 - successful, @out contains a boolean value indicating whether the bdev
 * is writable.
 * * !0 - errno indicating the error.
 */
int __verify_bdev_writable(const char *bdev_path, int *out)
{
        int writable = 0;
        struct block_device *bdev;
        struct super_block *sb;

        // open the base block device
        bdev = blkdev_get_by_path(bdev_path, FMODE_READ, NULL);

        if (IS_ERR(bdev)) {
                *out = 0;
                return PTR_ERR(bdev);
        }

        sb = dattobd_get_super(bdev);
        if (sb) {
                writable = !(sb->s_flags & MS_RDONLY);
                dattobd_drop_super(sb);
        }

        dattobd_blkdev_put(bdev);
        *out = writable;
        return 0;
}

/**
 * __ioctl_setup() - Sets up for tracking for mounted or unmounted in
 * reload/setup mode as appropriate for the current mount state.
 * block devices
 *
 * @minor: An unallocated device minor number.
 * @bdev_path: The path to the block device.
 * @cow_path: The path to the cow file.
 * @fallocated_space: The specific amount of space to use if non-zero,
 *                    default otherwise.
 * @cache_size: The specific amount of RAM to use for cache, default otherwise.
 * @is_snap: snapshot or incremental.
 * @is_reload: is a reload or a new setup.
 *
 * Return:
 * * 0 - successfully set up.
 * * !0 - errno indicating the error.
 */
int __ioctl_setup(unsigned int minor, const char *bdev_path,
                  const char *cow_path, unsigned long fallocated_space,
                  unsigned long cache_size, int is_snap, int is_reload)
{
        int ret, is_mounted;
        struct snap_device *dev = NULL;

        LOG_DEBUG("received %s %s ioctl - %u : %s : %s",
                  (is_reload) ? "reload" : "setup", (is_snap) ? "snap" : "inc",
                  minor, bdev_path, cow_path);

        // verify that the minor number is valid
        ret = verify_minor_available(minor);
        if (ret)
                goto error;

        // check if block device is mounted
        ret = __verify_bdev_writable(bdev_path, &is_mounted);
        if (ret)
                goto error;

        // check that reload / setup command matches current mount state
        if (is_mounted && is_reload) {
                ret = -EINVAL;
                LOG_ERROR(ret, "illegal to perform reload while mounted");
                goto error;
        } else if (!is_mounted && !is_reload) {
                ret = -EINVAL;
                LOG_ERROR(ret, "illegal to perform setup while unmounted");
                goto error;
        }

        // allocate the tracing struct
        ret = tracer_alloc(&dev);
        if (ret)
                goto error;

        // route to the appropriate setup function
        if (is_snap) {
                if (is_mounted)
                        ret = tracer_setup_active_snap(dev, minor, bdev_path,
                                                       cow_path,
                                                       fallocated_space,
                                                       cache_size);
                else
                        ret = tracer_setup_unverified_snap(
                                dev, minor, bdev_path, cow_path, cache_size);
        } else {
                if (!is_mounted)
                        ret = tracer_setup_unverified_inc(dev, minor, bdev_path,
                                                          cow_path, cache_size);
                else {
                        ret = -EINVAL;
                        LOG_ERROR(ret,
                                  "illegal to setup as active incremental");
                        goto error;
                }
        }

        if (ret)
                goto error;

        return 0;

error:
        LOG_ERROR(ret, "error during setup ioctl handler");
        if (dev)
                kfree(dev);
        return ret;
}

/**
 * ioctl_destroy() - Tears down an allocated minor device as long as it is not
 *                   referenced(busy).
 *
 * @minor: An allocated device minor number.
 *
 * Return:
 * * 0 - successful.
 * * !0 - errno indicating the error.
 */
int ioctl_destroy(unsigned int minor)
{
        int ret;
        struct snap_device *dev;

        LOG_DEBUG("received destroy ioctl - %u", minor);

        // verify that the minor number is valid
        ret = verify_minor_in_use_not_busy(minor);
        if (ret) {
                LOG_ERROR(ret, "error during destroy ioctl handler");
                return ret;
        }

        dev = snap_devices[minor];
        tracer_destroy(snap_devices[minor]);
        kfree(dev);

        return 0;
}

/**
 * ioctl_transition_inc() - Transitions the snapshot device to incremental
 *                          tracking.
 *
 * @minor: An allocated device minor number.
 *
 * Return:
 * * 0 - successful.
 * * !0 - errno indicating the error.
 */
int ioctl_transition_inc(unsigned int minor)
{
        int ret;
        struct snap_device *dev;

        LOG_DEBUG("received transition inc ioctl - %u", minor);

        // verify that the minor number is valid
        ret = verify_minor_in_use_not_busy(minor);
        if (ret)
                goto error;

        dev = snap_devices[minor];

        // check that the device is not in the fail state
        if (tracer_read_fail_state(dev)) {
                ret = -EINVAL;
                LOG_ERROR(ret, "device specified is in the fail state");
                goto error;
        }

        // check that tracer is in active snapshot state
        if (!test_bit(SNAPSHOT, &dev->sd_state) ||
            !test_bit(ACTIVE, &dev->sd_state)) {
                ret = -EINVAL;
                LOG_ERROR(ret,
                          "device specified is not in active snapshot mode");
                goto error;
        }

        ret = tracer_active_snap_to_inc(dev);
        if (ret)
                goto error;

        return 0;

error:
        LOG_ERROR(ret, "error during transition to incremental ioctl handler");
        return ret;
}

/**
 * ioctl_transition_snap() - Transitions from active incremental mode to
 *                           snapshot mode.
 *
 * @minor: An allocated device minor number.
 * @cow_path: The path to the cow file.
 * @fallocated_space: The specific amount of space to use if non-zero,
 *                    default otherwise.
 *
 * As a result COW data will be used during snapshotting to preserve snapshot
 * data while the live volume might change.
 *
 * Return:
 * * 0 - successful.
 * * !0 - errno indicating the error.
 */
int ioctl_transition_snap(unsigned int minor, const char *cow_path,
                          unsigned long fallocated_space)
{
        int ret;
        struct snap_device *dev;

        LOG_DEBUG("received transition snap ioctl - %u : %s", minor, cow_path);

        // verify that the minor number is valid
        ret = verify_minor_in_use_not_busy(minor);
        if (ret)
                goto error;

        dev = snap_devices[minor];

        // check that the device is not in the fail state
        if (tracer_read_fail_state(dev)) {
                ret = -EINVAL;
                LOG_ERROR(ret, "device specified is in the fail state");
                goto error;
        }

        // check that tracer is in active incremental state
        if (test_bit(SNAPSHOT, &dev->sd_state) ||
            !test_bit(ACTIVE, &dev->sd_state)) {
                ret = -EINVAL;
                LOG_ERROR(ret,
                          "device specified is not in active incremental mode");
                goto error;
        }

        ret = tracer_active_inc_to_snap(dev, cow_path, fallocated_space);
        if (ret)
                goto error;

        return 0;

error:
        LOG_ERROR(ret, "error during transition to snapshot ioctl handler");
        return ret;
}

/**
 * ioctl_reconfigure() - Reconfigures the cache size to match the supplied
 *                       value.
 * @minor: An allocated device minor number.
 * @cache_size: The specific amount of RAM to use for cache, default otherwise.
 *
 * Return:
 * * 0 - successful.
 * * !0 - errno indicating the error.
 */
int ioctl_reconfigure(unsigned int minor, unsigned long cache_size)
{
        int ret;
        struct snap_device *dev;

        LOG_DEBUG("received reconfigure ioctl - %u : %lu", minor, cache_size);

        // verify that the minor number is valid
        ret = verify_minor_in_use_not_busy(minor);
        if (ret)
                goto error;

        dev = snap_devices[minor];

        // check that the device is not in the fail state
        if (tracer_read_fail_state(dev)) {
                ret = -EINVAL;
                LOG_ERROR(ret, "device specified is in the fail state");
                goto error;
        }

        tracer_reconfigure(dev, cache_size);

        return 0;

error:
        LOG_ERROR(ret, "error during reconfigure ioctl handler");
        return ret;
}

/**
 * ioctl_dattobd_info() - Stores relevant, current &struct snap_device state
 *                        in @info.
 *
 * @info: A @struct dattobd_info object pointer.
 *
 * Return:
 * * 0 - successful.
 * * !0 - errno indicating the error.
 */
int ioctl_dattobd_info(struct dattobd_info *info)
{
        int ret;
        struct snap_device *dev;

        LOG_DEBUG("received dattobd info ioctl - %u", info->minor);

        // verify that the minor number is valid
        ret = verify_minor_in_use(info->minor);
        if (ret)
                goto error;

        dev = snap_devices[info->minor];

        tracer_dattobd_info(dev, info);

        return 0;

error:
        LOG_ERROR(ret, "error during reconfigure ioctl handler");
        return ret;
}

/**
 * get_free_minor() - Determine the next available device minor number.
 *
 * Return: The next available minor number or an errno indicating the error.
 */
int get_free_minor(void)
{
        struct snap_device *dev;
        int i;

        tracer_for_each_full(dev, i)
        {
                if (!dev)
                        return i;
        }

        return -ENOENT;
}

/**
 * ctrl_ioctl() - Dispatches the supplied IOCTL command to the appropriate
 *                handler function(s).
 *
 * @filp: The &struct file object pointer associated with the misc driver
 *        that handles the IOCTL interface.
 * @cmd: The IOCTL command.
 * @arg: A user space argument supplied with the IOCTL call.
 *
 * Return:
 * * 0 - successful.
 * * !0 - errno indicating the error.
 */
long ctrl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
        int ret, idx;
        char *bdev_path = NULL;
        char *cow_path = NULL;
        struct dattobd_info *info = NULL;
        unsigned int minor = 0;
        unsigned long fallocated_space = 0, cache_size = 0;

        LOG_DEBUG("ioctl command received: %d", cmd);
        mutex_lock(&ioctl_mutex);

        switch (cmd) {
        case IOCTL_SETUP_SNAP:
                // get params from user space
                ret = get_setup_params((struct setup_params __user *)arg,
                                       &minor, &bdev_path, &cow_path,
                                       &fallocated_space, &cache_size);
                if (ret)
                        break;

                ret = ioctl_setup_snap(minor, bdev_path, cow_path,
                                       fallocated_space, cache_size);
                if (ret)
                        break;

                break;
        case IOCTL_RELOAD_SNAP:
                // get params from user space
                ret = get_reload_params((struct reload_params __user *)arg,
                                        &minor, &bdev_path, &cow_path,
                                        &cache_size);
                if (ret)
                        break;

                ret = ioctl_reload_snap(minor, bdev_path, cow_path, cache_size);
                if (ret)
                        break;

                break;
        case IOCTL_RELOAD_INC:
                // get params from user space
                ret = get_reload_params((struct reload_params __user *)arg,
                                        &minor, &bdev_path, &cow_path,
                                        &cache_size);
                if (ret)
                        break;

                ret = ioctl_reload_inc(minor, bdev_path, cow_path, cache_size);
                if (ret)
                        break;

                break;
        case IOCTL_DESTROY:
                // get minor from user space
                ret = get_user(minor, (unsigned int __user *)arg);
                if (ret) {
                        LOG_ERROR(ret,
                                  "error copying minor number from user space");
                        break;
                }

                ret = ioctl_destroy(minor);
                if (ret)
                        break;

                break;
        case IOCTL_TRANSITION_INC:
                // get minor from user space
                ret = get_user(minor, (unsigned int __user *)arg);
                if (ret) {
                        LOG_ERROR(ret,
                                  "error copying minor number from user space");
                        break;
                }

                ret = ioctl_transition_inc(minor);
                if (ret)
                        break;

                break;
        case IOCTL_TRANSITION_SNAP:
                // get params from user space
                ret = get_transition_snap_params(
                        (struct transition_snap_params __user *)arg, &minor,
                        &cow_path, &fallocated_space);
                if (ret)
                        break;

                ret = ioctl_transition_snap(minor, cow_path, fallocated_space);
                if (ret)
                        break;

                break;
        case IOCTL_RECONFIGURE:
                // get params from user space
                ret = get_reconfigure_params(
                        (struct reconfigure_params __user *)arg, &minor,
                        &cache_size);
                if (ret)
                        break;

                ret = ioctl_reconfigure(minor, cache_size);
                if (ret)
                        break;

                break;
        case IOCTL_DATTOBD_INFO:
                // get params from user space
                info = kmalloc(sizeof(struct dattobd_info), GFP_KERNEL);
                if (!info) {
                        ret = -ENOMEM;
                        LOG_ERROR(ret,
                                  "error allocating memory for dattobd info");
                        break;
                }

                ret = copy_from_user(info, (struct dattobd_info __user *)arg,
                                     sizeof(struct dattobd_info));
                if (ret) {
                        ret = -EFAULT;
                        LOG_ERROR(ret, "error copying dattobd info struct from "
                                       "user space");
                        break;
                }

                ret = ioctl_dattobd_info(info);
                if (ret)
                        break;

                ret = copy_to_user((struct dattobd_info __user *)arg, info,
                                   sizeof(struct dattobd_info));
                if (ret) {
                        ret = -EFAULT;
                        LOG_ERROR(
                                ret,
                                "error copying dattobd info struct to user space");
                        break;
                }

                break;
        case IOCTL_GET_FREE:
                idx = get_free_minor();
                if (idx < 0) {
                        ret = idx;
                        LOG_ERROR(ret, "no free devices");
                        break;
                }

                ret = copy_to_user((int __user *)arg, &idx, sizeof(idx));
                if (ret) {
                        ret = -EFAULT;
                        LOG_ERROR(ret, "error copying minor to user space");
                        break;
                }

                break;
        default:
                ret = -EINVAL;
                LOG_ERROR(ret, "invalid ioctl called");
                break;
        }

        LOG_DEBUG("minor range = %u - %u", lowest_minor, highest_minor);
        mutex_unlock(&ioctl_mutex);

        if (bdev_path)
                kfree(bdev_path);
        if (cow_path)
                kfree(cow_path);
        if (info)
                kfree(info);

        return ret;
}