root/usr/src/cmd/ndmpd/ndmp/ndmpd_chkpnt.c
/*
 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2013, 2015 by Delphix. All rights reserved.
 * Copyright (c) 2013 Steven Hartland. All rights reserved.
 * Copyright (c) 2016 Martin Matuska. All rights reserved.
 */

/*
 * BSD 3 Clause License
 *
 * Copyright (c) 2007, The Storage Networking Industry Association.
 *
 * 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.
 *
 *      - Neither the name of The Storage Networking Industry Association (SNIA)
 *        nor the names of its contributors may be used to endorse or promote
 *        products derived from this software without specific prior written
 *        permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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 <stdio.h>
#include <string.h>
#include "ndmpd.h"
#include <libzfs.h>

typedef struct snap_param {
        char *snp_name;
        boolean_t snp_found;
} snap_param_t;

static int cleanup_fd = -1;

/*
 * ndmp_has_backup
 *
 * Call backup function which looks for backup snapshot.
 * This is a callback function used with zfs_iter_snapshots.
 *
 * Parameters:
 *   zhp (input) - ZFS handle pointer
 *   data (output) - 0 - no backup snapshot
 *                   1 - has backup snapshot
 *
 * Returns:
 *   0: on success
 *  -1: otherwise
 */
static int
ndmp_has_backup(zfs_handle_t *zhp, void *data)
{
        const char *name;
        snap_param_t *chp = (snap_param_t *)data;

        name = zfs_get_name(zhp);
        if (name == NULL ||
            strstr(name, chp->snp_name) == NULL) {
                zfs_close(zhp);
                return (-1);
        }

        chp->snp_found = 1;
        zfs_close(zhp);

        return (0);
}

/*
 * ndmp_has_backup_snapshot
 *
 * Returns TRUE if the volume has an active backup snapshot, otherwise,
 * returns FALSE.
 *
 * Parameters:
 *   volname (input) - name of the volume
 *
 * Returns:
 *   0: on success
 *  -1: otherwise
 */
static int
ndmp_has_backup_snapshot(char *volname, char *jobname)
{
        zfs_handle_t *zhp;
        snap_param_t snp;
        char chname[ZFS_MAX_DATASET_NAME_LEN];

        (void) mutex_lock(&zlib_mtx);
        if ((zhp = zfs_open(zlibh, volname, ZFS_TYPE_DATASET)) == 0) {
                NDMP_LOG(LOG_ERR, "Cannot open snapshot %s.", volname);
                (void) mutex_unlock(&zlib_mtx);
                return (-1);
        }

        snp.snp_found = 0;
        (void) snprintf(chname, ZFS_MAX_DATASET_NAME_LEN, "@%s", jobname);
        snp.snp_name = chname;

        (void) zfs_iter_snapshots(zhp, B_FALSE, ndmp_has_backup, &snp);
        zfs_close(zhp);
        (void) mutex_unlock(&zlib_mtx);

        return (snp.snp_found);
}

/*
 * ndmp_create_snapshot
 *
 * This function will parse the path to get the real volume name.
 * It will then create a snapshot based on volume and job name.
 * This function should be called before the NDMP backup is started.
 *
 * Parameters:
 *   vol_name (input) - name of the volume
 *
 * Returns:
 *   0: on success
 *   -1: otherwise
 */
int
ndmp_create_snapshot(char *vol_name, char *jname)
{
        char vol[ZFS_MAX_DATASET_NAME_LEN];

        if (vol_name == 0 ||
            get_zfsvolname(vol, sizeof (vol), vol_name) == -1)
                return (0);

        /*
         * If there is an old snapshot left from the previous
         * backup it could be stale one and it must be
         * removed before using it.
         */
        if (ndmp_has_backup_snapshot(vol, jname))
                (void) snapshot_destroy(vol, jname, B_FALSE, B_TRUE, NULL);

        return (snapshot_create(vol, jname, B_FALSE, B_TRUE));
}

/*
 * ndmp_remove_snapshot
 *
 * This function will parse the path to get the real volume name.
 * It will then remove the snapshot for that volume and job name.
 * This function should be called after NDMP backup is finished.
 *
 * Parameters:
 *   vol_name (input) - name of the volume
 *
 * Returns:
 *   0: on success
 *   -1: otherwise
 */
int
ndmp_remove_snapshot(char *vol_name, char *jname)
{
        char vol[ZFS_MAX_DATASET_NAME_LEN];

        if (vol_name == 0 ||
            get_zfsvolname(vol, sizeof (vol), vol_name) == -1)
                return (0);

        return (snapshot_destroy(vol, jname, B_FALSE, B_TRUE, NULL));
}

/*
 * Put a hold on snapshot
 */
int
snapshot_hold(char *volname, char *snapname, char *jname, boolean_t recursive)
{
        zfs_handle_t *zhp;
        char *p;

        if ((zhp = zfs_open(zlibh, volname, ZFS_TYPE_DATASET)) == 0) {
                NDMP_LOG(LOG_ERR, "Cannot open volume %s.", volname);
                return (-1);
        }

        if (cleanup_fd == -1 && (cleanup_fd = open(ZFS_DEV,
            O_RDWR|O_EXCL)) < 0) {
                NDMP_LOG(LOG_ERR, "Cannot open dev %d", errno);
                zfs_close(zhp);
                return (-1);
        }

        p = strchr(snapname, '@') + 1;
        if (zfs_hold(zhp, p, jname, recursive, cleanup_fd) != 0) {
                NDMP_LOG(LOG_ERR, "Cannot hold snapshot %s", p);
                zfs_close(zhp);
                return (-1);
        }
        zfs_close(zhp);
        return (0);
}

int
snapshot_release(char *volname, char *snapname, char *jname,
    boolean_t recursive)
{
        zfs_handle_t *zhp;
        char *p;
        int rv = 0;

        if ((zhp = zfs_open(zlibh, volname, ZFS_TYPE_DATASET)) == 0) {
                NDMP_LOG(LOG_ERR, "Cannot open volume %s", volname);
                return (-1);
        }

        p = strchr(snapname, '@') + 1;
        if (zfs_release(zhp, p, jname, recursive) != 0) {
                NDMP_LOG(LOG_DEBUG, "Cannot release snapshot %s", p);
                rv = -1;
        }
        if (cleanup_fd != -1) {
                (void) close(cleanup_fd);
                cleanup_fd = -1;
        }
        zfs_close(zhp);
        return (rv);
}

/*
 * Create a snapshot on the volume
 */
int
snapshot_create(char *volname, char *jname, boolean_t recursive,
    boolean_t hold)
{
        char snapname[ZFS_MAX_DATASET_NAME_LEN];
        int rv;

        if (!volname || !*volname)
                return (-1);

        (void) snprintf(snapname, ZFS_MAX_DATASET_NAME_LEN,
            "%s@%s", volname, jname);

        (void) mutex_lock(&zlib_mtx);
        if ((rv = zfs_snapshot(zlibh, snapname, recursive, NULL))
            == -1) {
                if (errno == EEXIST) {
                        (void) mutex_unlock(&zlib_mtx);
                        return (0);
                }
                NDMP_LOG(LOG_DEBUG,
                    "snapshot_create: %s failed (err=%d): %s",
                    snapname, errno, libzfs_error_description(zlibh));
                (void) mutex_unlock(&zlib_mtx);
                return (rv);
        }
        if (hold && snapshot_hold(volname, snapname, jname, recursive) != 0) {
                NDMP_LOG(LOG_DEBUG,
                    "snapshot_create: %s hold failed (err=%d): %s",
                    snapname, errno, libzfs_error_description(zlibh));
                (void) mutex_unlock(&zlib_mtx);
                return (-1);
        }

        (void) mutex_unlock(&zlib_mtx);
        return (0);
}

/*
 * Remove and release the backup snapshot
 */
int
snapshot_destroy(char *volname, char *jname, boolean_t recursive,
    boolean_t hold, int *zfs_err)
{
        char snapname[ZFS_MAX_DATASET_NAME_LEN];
        zfs_handle_t *zhp;
        zfs_type_t ztype;
        char *namep;
        int err;

        if (zfs_err)
                *zfs_err = 0;

        if (!volname || !*volname)
                return (-1);

        if (recursive) {
                ztype = ZFS_TYPE_VOLUME | ZFS_TYPE_FILESYSTEM;
                namep = volname;
        } else {
                (void) snprintf(snapname, ZFS_MAX_DATASET_NAME_LEN,
                    "%s@%s", volname, jname);
                namep = snapname;
                ztype = ZFS_TYPE_SNAPSHOT;
        }

        (void) mutex_lock(&zlib_mtx);
        if (hold &&
            snapshot_release(volname, namep, jname, recursive) != 0) {
                NDMP_LOG(LOG_DEBUG,
                    "snapshot_destroy: %s release failed (err=%d): %s",
                    namep, errno, libzfs_error_description(zlibh));
                (void) mutex_unlock(&zlib_mtx);
                return (-1);
        }

        if ((zhp = zfs_open(zlibh, namep, ztype)) == NULL) {
                NDMP_LOG(LOG_DEBUG, "snapshot_destroy: open %s failed",
                    namep);
                (void) mutex_unlock(&zlib_mtx);
                return (-1);
        }

        if (recursive) {
                err = zfs_destroy_snaps(zhp, jname, B_TRUE);
        } else {
                err = zfs_destroy(zhp, B_TRUE);
        }

        if (err) {
                NDMP_LOG(LOG_ERR, "%s (recursive destroy: %d): %d; %s; %s",
                    namep,
                    recursive,
                    libzfs_errno(zlibh),
                    libzfs_error_action(zlibh),
                    libzfs_error_description(zlibh));

                if (zfs_err)
                        *zfs_err = err;
        }

        zfs_close(zhp);
        (void) mutex_unlock(&zlib_mtx);

        return (0);
}