Print this page
2882 implement libzfs_core
2883 changing "canmount" property to "on" should not always remount dataset
2900 "zfs snapshot" should be able to create multiple, arbitrary snapshots at once
Reviewed by: George Wilson <george.wilson@delphix.com>
Reviewed by: Chris Siden <christopher.siden@delphix.com>
Reviewed by: Garrett D'Amore <garrett@damore.org>
Reviewed by: Bill Pijewski <wdp@joyent.com>
Reviewed by: Dan Kruchinin <dan.kruchinin@gmail.com>

*** 18,27 **** --- 18,28 ---- * * CDDL HEADER END */ /* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012 by Delphix. All rights reserved. */ /* Portions Copyright 2010 Robert Milkowski */ #include <sys/cred.h>
*** 697,730 **** { dsl_dir_t *dd = arg1; spa_t *spa = dd->dd_pool->dp_spa; struct oscarg *oa = arg2; uint64_t obj; ASSERT(dmu_tx_is_syncing(tx)); obj = dsl_dataset_create_sync(dd, oa->lastname, oa->clone_origin, oa->flags, oa->cr, tx); ! if (oa->clone_origin == NULL) { ! dsl_pool_t *dp = dd->dd_pool; ! dsl_dataset_t *ds; ! blkptr_t *bp; ! objset_t *os; ! ! VERIFY3U(0, ==, dsl_dataset_hold_obj(dp, obj, FTAG, &ds)); bp = dsl_dataset_get_blkptr(ds); ! ASSERT(BP_IS_HOLE(bp)); ! ! os = dmu_objset_create_impl(spa, ds, bp, oa->type, tx); if (oa->userfunc) oa->userfunc(os, oa->userarg, oa->cr, tx); - dsl_dataset_rele(ds, FTAG); } ! spa_history_log_internal(LOG_DS_CREATE, spa, tx, "dataset = %llu", obj); } int dmu_objset_create(const char *name, dmu_objset_type_t type, uint64_t flags, void (*func)(objset_t *os, void *arg, cred_t *cr, dmu_tx_t *tx), void *arg) --- 698,734 ---- { dsl_dir_t *dd = arg1; spa_t *spa = dd->dd_pool->dp_spa; struct oscarg *oa = arg2; uint64_t obj; + dsl_dataset_t *ds; + blkptr_t *bp; ASSERT(dmu_tx_is_syncing(tx)); obj = dsl_dataset_create_sync(dd, oa->lastname, oa->clone_origin, oa->flags, oa->cr, tx); ! VERIFY3U(0, ==, dsl_dataset_hold_obj(dd->dd_pool, obj, FTAG, &ds)); bp = dsl_dataset_get_blkptr(ds); ! if (BP_IS_HOLE(bp)) { ! objset_t *os = ! dmu_objset_create_impl(spa, ds, bp, oa->type, tx); if (oa->userfunc) oa->userfunc(os, oa->userarg, oa->cr, tx); } ! if (oa->clone_origin == NULL) { ! spa_history_log_internal_ds(ds, "create", tx, ""); ! } else { ! char namebuf[MAXNAMELEN]; ! dsl_dataset_name(oa->clone_origin, namebuf); ! spa_history_log_internal_ds(ds, "clone", tx, ! "origin=%s (%llu)", namebuf, oa->clone_origin->ds_object); ! } ! dsl_dataset_rele(ds, FTAG); } int dmu_objset_create(const char *name, dmu_objset_type_t type, uint64_t flags, void (*func)(objset_t *os, void *arg, cred_t *cr, dmu_tx_t *tx), void *arg)
*** 797,834 **** } return (error); } ! struct snaparg { ! dsl_sync_task_group_t *dstg; ! char *snapname; ! char *htag; ! char failed[MAXPATHLEN]; ! boolean_t recursive; ! boolean_t needsuspend; ! boolean_t temporary; ! nvlist_t *props; ! struct dsl_ds_holdarg *ha; /* only needed in the temporary case */ ! dsl_dataset_t *newds; ! }; static int snapshot_check(void *arg1, void *arg2, dmu_tx_t *tx) { objset_t *os = arg1; ! struct snaparg *sn = arg2; int error; /* The props have already been checked by zfs_check_userprops(). */ error = dsl_dataset_snapshot_check(os->os_dsl_dataset, ! sn->snapname, tx); if (error) return (error); ! if (sn->temporary) { /* * Ideally we would just call * dsl_dataset_user_hold_check() and * dsl_dataset_destroy_check() here. However the * dataset we want to hold and destroy is the snapshot --- 801,844 ---- } return (error); } ! typedef struct snapallarg { ! dsl_sync_task_group_t *saa_dstg; ! boolean_t saa_needsuspend; ! nvlist_t *saa_props; ! ! /* the following are used only if 'temporary' is set: */ ! boolean_t saa_temporary; ! const char *saa_htag; ! struct dsl_ds_holdarg *saa_ha; ! dsl_dataset_t *saa_newds; ! } snapallarg_t; ! ! typedef struct snaponearg { ! const char *soa_longname; /* long snap name */ ! const char *soa_snapname; /* short snap name */ ! snapallarg_t *soa_saa; ! } snaponearg_t; static int snapshot_check(void *arg1, void *arg2, dmu_tx_t *tx) { objset_t *os = arg1; ! snaponearg_t *soa = arg2; ! snapallarg_t *saa = soa->soa_saa; int error; /* The props have already been checked by zfs_check_userprops(). */ error = dsl_dataset_snapshot_check(os->os_dsl_dataset, ! soa->soa_snapname, tx); if (error) return (error); ! if (saa->saa_temporary) { /* * Ideally we would just call * dsl_dataset_user_hold_check() and * dsl_dataset_destroy_check() here. However the * dataset we want to hold and destroy is the snapshot
*** 842,1017 **** return (ENOTSUP); /* * Not checking number of tags because the tag will be * unique, as it will be the only tag. */ ! if (strlen(sn->htag) + MAX_TAG_PREFIX_LEN >= MAXNAMELEN) return (E2BIG); ! sn->ha = kmem_alloc(sizeof (struct dsl_ds_holdarg), KM_SLEEP); ! sn->ha->temphold = B_TRUE; ! sn->ha->htag = sn->htag; } return (error); } static void snapshot_sync(void *arg1, void *arg2, dmu_tx_t *tx) { objset_t *os = arg1; dsl_dataset_t *ds = os->os_dsl_dataset; ! struct snaparg *sn = arg2; ! dsl_dataset_snapshot_sync(ds, sn->snapname, tx); ! if (sn->props) { dsl_props_arg_t pa; ! pa.pa_props = sn->props; pa.pa_source = ZPROP_SRC_LOCAL; dsl_props_set_sync(ds->ds_prev, &pa, tx); } ! if (sn->temporary) { struct dsl_ds_destroyarg da; ! dsl_dataset_user_hold_sync(ds->ds_prev, sn->ha, tx); ! kmem_free(sn->ha, sizeof (struct dsl_ds_holdarg)); ! sn->ha = NULL; ! sn->newds = ds->ds_prev; da.ds = ds->ds_prev; da.defer = B_TRUE; dsl_dataset_destroy_sync(&da, FTAG, tx); } } static int ! dmu_objset_snapshot_one(const char *name, void *arg) { ! struct snaparg *sn = arg; objset_t *os; int err; - char *cp; - - /* - * If the objset starts with a '%', then ignore it unless it was - * explicitly named (ie, not recursive). These hidden datasets - * are always inconsistent, and by not opening them here, we can - * avoid a race with dsl_dir_destroy_check(). - */ - cp = strrchr(name, '/'); - if (cp && cp[1] == '%' && sn->recursive) - return (0); ! (void) strcpy(sn->failed, name); ! /* ! * Check permissions if we are doing a recursive snapshot. The ! * permission checks for the starting dataset have already been ! * performed in zfs_secpolicy_snapshot() ! */ ! if (sn->recursive && (err = zfs_secpolicy_snapshot_perms(name, CRED()))) ! return (err); ! ! err = dmu_objset_hold(name, sn, &os); if (err != 0) return (err); /* * If the objset is in an inconsistent state (eg, in the process ! * of being destroyed), don't snapshot it. As with %hidden ! * datasets, we return EBUSY if this name was explicitly ! * requested (ie, not recursive), and otherwise ignore it. */ if (os->os_dsl_dataset->ds_phys->ds_flags & DS_FLAG_INCONSISTENT) { ! dmu_objset_rele(os, sn); ! return (sn->recursive ? 0 : EBUSY); } ! if (sn->needsuspend) { err = zil_suspend(dmu_objset_zil(os)); if (err) { ! dmu_objset_rele(os, sn); return (err); } } ! dsl_sync_task_create(sn->dstg, snapshot_check, snapshot_sync, ! os, sn, 3); return (0); } int ! dmu_objset_snapshot(char *fsname, char *snapname, char *tag, ! nvlist_t *props, boolean_t recursive, boolean_t temporary, int cleanup_fd) { dsl_sync_task_t *dst; ! struct snaparg sn; spa_t *spa; ! minor_t minor; int err; ! (void) strcpy(sn.failed, fsname); ! err = spa_open(fsname, &spa, FTAG); if (err) return (err); - if (temporary) { if (cleanup_fd < 0) { spa_close(spa, FTAG); return (EINVAL); } if ((err = zfs_onexit_fd_hold(cleanup_fd, &minor)) != 0) { spa_close(spa, FTAG); return (err); } - } ! sn.dstg = dsl_sync_task_group_create(spa_get_dsl(spa)); ! sn.snapname = snapname; ! sn.htag = tag; ! sn.props = props; ! sn.recursive = recursive; ! sn.needsuspend = (spa_version(spa) < SPA_VERSION_FAST_SNAP); ! sn.temporary = temporary; ! sn.ha = NULL; ! sn.newds = NULL; ! ! if (recursive) { ! err = dmu_objset_find(fsname, ! dmu_objset_snapshot_one, &sn, DS_FIND_CHILDREN); ! } else { ! err = dmu_objset_snapshot_one(fsname, &sn); ! } if (err == 0) ! err = dsl_sync_task_group_wait(sn.dstg); ! for (dst = list_head(&sn.dstg->dstg_tasks); dst; ! dst = list_next(&sn.dstg->dstg_tasks, dst)) { objset_t *os = dst->dst_arg1; ! dsl_dataset_t *ds = os->os_dsl_dataset; ! if (dst->dst_err) { ! dsl_dataset_name(ds, sn.failed); ! } else if (temporary) { ! dsl_register_onexit_hold_cleanup(sn.newds, tag, minor); ! } ! if (sn.needsuspend) zil_resume(dmu_objset_zil(os)); ! dmu_objset_rele(os, &sn); } - if (err) - (void) strcpy(fsname, sn.failed); - if (temporary) zfs_onexit_fd_rele(cleanup_fd); ! dsl_sync_task_group_destroy(sn.dstg); spa_close(spa, FTAG); return (err); } static void dmu_objset_sync_dnodes(list_t *list, list_t *newlist, dmu_tx_t *tx) { dnode_t *dn; --- 852,1078 ---- return (ENOTSUP); /* * Not checking number of tags because the tag will be * unique, as it will be the only tag. */ ! if (strlen(saa->saa_htag) + MAX_TAG_PREFIX_LEN >= MAXNAMELEN) return (E2BIG); ! saa->saa_ha = kmem_alloc(sizeof (struct dsl_ds_holdarg), ! KM_SLEEP); ! saa->saa_ha->temphold = B_TRUE; ! saa->saa_ha->htag = saa->saa_htag; } return (error); } static void snapshot_sync(void *arg1, void *arg2, dmu_tx_t *tx) { objset_t *os = arg1; dsl_dataset_t *ds = os->os_dsl_dataset; ! snaponearg_t *soa = arg2; ! snapallarg_t *saa = soa->soa_saa; ! dsl_dataset_snapshot_sync(ds, soa->soa_snapname, tx); ! if (saa->saa_props != NULL) { dsl_props_arg_t pa; ! pa.pa_props = saa->saa_props; pa.pa_source = ZPROP_SRC_LOCAL; dsl_props_set_sync(ds->ds_prev, &pa, tx); } ! if (saa->saa_temporary) { struct dsl_ds_destroyarg da; ! dsl_dataset_user_hold_sync(ds->ds_prev, saa->saa_ha, tx); ! kmem_free(saa->saa_ha, sizeof (struct dsl_ds_holdarg)); ! saa->saa_ha = NULL; ! saa->saa_newds = ds->ds_prev; da.ds = ds->ds_prev; da.defer = B_TRUE; dsl_dataset_destroy_sync(&da, FTAG, tx); } } static int ! snapshot_one_impl(const char *snapname, void *arg) { ! char fsname[MAXPATHLEN]; ! snapallarg_t *saa = arg; ! snaponearg_t *soa; objset_t *os; int err; ! (void) strlcpy(fsname, snapname, sizeof (fsname)); ! strchr(fsname, '@')[0] = '\0'; ! err = dmu_objset_hold(fsname, saa, &os); if (err != 0) return (err); /* * If the objset is in an inconsistent state (eg, in the process ! * of being destroyed), don't snapshot it. */ if (os->os_dsl_dataset->ds_phys->ds_flags & DS_FLAG_INCONSISTENT) { ! dmu_objset_rele(os, saa); ! return (EBUSY); } ! if (saa->saa_needsuspend) { err = zil_suspend(dmu_objset_zil(os)); if (err) { ! dmu_objset_rele(os, saa); return (err); } } ! ! soa = kmem_zalloc(sizeof (*soa), KM_SLEEP); ! soa->soa_saa = saa; ! soa->soa_longname = snapname; ! soa->soa_snapname = strchr(snapname, '@') + 1; ! ! dsl_sync_task_create(saa->saa_dstg, snapshot_check, snapshot_sync, ! os, soa, 3); return (0); } + /* + * The snapshots must all be in the same pool. + */ int ! dmu_objset_snapshot(nvlist_t *snaps, nvlist_t *props, nvlist_t *errors) { dsl_sync_task_t *dst; ! snapallarg_t saa = { 0 }; spa_t *spa; ! int rv = 0; ! int err; ! nvpair_t *pair; ! ! pair = nvlist_next_nvpair(snaps, NULL); ! if (pair == NULL) ! return (0); ! ! err = spa_open(nvpair_name(pair), &spa, FTAG); ! if (err) ! return (err); ! saa.saa_dstg = dsl_sync_task_group_create(spa_get_dsl(spa)); ! saa.saa_props = props; ! saa.saa_needsuspend = (spa_version(spa) < SPA_VERSION_FAST_SNAP); ! ! for (pair = nvlist_next_nvpair(snaps, NULL); pair != NULL; ! pair = nvlist_next_nvpair(snaps, pair)) { ! err = snapshot_one_impl(nvpair_name(pair), &saa); ! if (err != 0) { ! if (errors != NULL) { ! fnvlist_add_int32(errors, ! nvpair_name(pair), err); ! } ! rv = err; ! } ! } ! ! /* ! * If any call to snapshot_one_impl() failed, don't execute the ! * sync task. The error handling code below will clean up the ! * snaponearg_t from any successful calls to ! * snapshot_one_impl(). ! */ ! if (rv == 0) ! err = dsl_sync_task_group_wait(saa.saa_dstg); ! if (err != 0) ! rv = err; ! ! for (dst = list_head(&saa.saa_dstg->dstg_tasks); dst; ! dst = list_next(&saa.saa_dstg->dstg_tasks, dst)) { ! objset_t *os = dst->dst_arg1; ! snaponearg_t *soa = dst->dst_arg2; ! if (dst->dst_err != 0) { ! if (errors != NULL) { ! fnvlist_add_int32(errors, ! soa->soa_longname, dst->dst_err); ! } ! rv = dst->dst_err; ! } ! ! if (saa.saa_needsuspend) ! zil_resume(dmu_objset_zil(os)); ! dmu_objset_rele(os, &saa); ! kmem_free(soa, sizeof (*soa)); ! } ! ! dsl_sync_task_group_destroy(saa.saa_dstg); ! spa_close(spa, FTAG); ! return (rv); ! } ! ! int ! dmu_objset_snapshot_one(const char *fsname, const char *snapname) ! { int err; + char *longsnap = kmem_asprintf("%s@%s", fsname, snapname); + nvlist_t *snaps = fnvlist_alloc(); ! fnvlist_add_boolean(snaps, longsnap); ! err = dmu_objset_snapshot(snaps, NULL, NULL); ! fnvlist_free(snaps); ! strfree(longsnap); ! return (err); ! } ! int ! dmu_objset_snapshot_tmp(const char *snapname, const char *tag, int cleanup_fd) ! { ! dsl_sync_task_t *dst; ! snapallarg_t saa = { 0 }; ! spa_t *spa; ! minor_t minor; ! int err; ! ! err = spa_open(snapname, &spa, FTAG); if (err) return (err); + saa.saa_dstg = dsl_sync_task_group_create(spa_get_dsl(spa)); + saa.saa_htag = tag; + saa.saa_needsuspend = (spa_version(spa) < SPA_VERSION_FAST_SNAP); + saa.saa_temporary = B_TRUE; if (cleanup_fd < 0) { spa_close(spa, FTAG); return (EINVAL); } if ((err = zfs_onexit_fd_hold(cleanup_fd, &minor)) != 0) { spa_close(spa, FTAG); return (err); } ! err = snapshot_one_impl(snapname, &saa); if (err == 0) ! err = dsl_sync_task_group_wait(saa.saa_dstg); ! for (dst = list_head(&saa.saa_dstg->dstg_tasks); dst; ! dst = list_next(&saa.saa_dstg->dstg_tasks, dst)) { objset_t *os = dst->dst_arg1; ! dsl_register_onexit_hold_cleanup(saa.saa_newds, tag, minor); ! if (saa.saa_needsuspend) zil_resume(dmu_objset_zil(os)); ! dmu_objset_rele(os, &saa); } zfs_onexit_fd_rele(cleanup_fd); ! dsl_sync_task_group_destroy(saa.saa_dstg); spa_close(spa, FTAG); return (err); } + static void dmu_objset_sync_dnodes(list_t *list, list_t *newlist, dmu_tx_t *tx) { dnode_t *dn;