Path: blob/main/sys/contrib/openzfs/module/zfs/dsl_userhold.c
48383 views
// SPDX-License-Identifier: CDDL-1.01/*2* CDDL HEADER START3*4* The contents of this file are subject to the terms of the5* Common Development and Distribution License (the "License").6* You may not use this file except in compliance with the License.7*8* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE9* or https://opensource.org/licenses/CDDL-1.0.10* See the License for the specific language governing permissions11* and limitations under the License.12*13* When distributing Covered Code, include this CDDL HEADER in each14* file and include the License file at usr/src/OPENSOLARIS.LICENSE.15* If applicable, add the following below this CDDL HEADER, with the16* fields enclosed by brackets "[]" replaced with your own identifying17* information: Portions Copyright [yyyy] [name of copyright owner]18*19* CDDL HEADER END20*/21/*22* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.23* Copyright (c) 2012, 2017 by Delphix. All rights reserved.24* Copyright (c) 2013 Steven Hartland. All rights reserved.25*/2627#include <sys/zfs_context.h>28#include <sys/dsl_userhold.h>29#include <sys/dsl_dataset.h>30#include <sys/dsl_destroy.h>31#include <sys/dsl_synctask.h>32#include <sys/dmu_tx.h>33#include <sys/zfs_onexit.h>34#include <sys/dsl_pool.h>35#include <sys/dsl_dir.h>36#include <sys/zfs_ioctl.h>37#include <sys/zap.h>3839typedef struct dsl_dataset_user_hold_arg {40nvlist_t *dduha_holds;41nvlist_t *dduha_chkholds;42nvlist_t *dduha_errlist;43minor_t dduha_minor;44} dsl_dataset_user_hold_arg_t;4546/*47* If you add new checks here, you may need to add additional checks to the48* "temporary" case in snapshot_check() in dmu_objset.c.49*/50int51dsl_dataset_user_hold_check_one(dsl_dataset_t *ds, const char *htag,52boolean_t temphold, dmu_tx_t *tx)53{54dsl_pool_t *dp = dmu_tx_pool(tx);55objset_t *mos = dp->dp_meta_objset;56int error = 0;5758ASSERT(dsl_pool_config_held(dp));5960if (strlen(htag) > MAXNAMELEN)61return (SET_ERROR(E2BIG));62/* Tempholds have a more restricted length */63if (temphold && strlen(htag) + MAX_TAG_PREFIX_LEN >= MAXNAMELEN)64return (SET_ERROR(E2BIG));6566/* tags must be unique (if ds already exists) */67if (ds != NULL && dsl_dataset_phys(ds)->ds_userrefs_obj != 0) {68uint64_t value;6970error = zap_lookup(mos, dsl_dataset_phys(ds)->ds_userrefs_obj,71htag, 8, 1, &value);72if (error == 0)73error = SET_ERROR(EEXIST);74else if (error == ENOENT)75error = 0;76}7778return (error);79}8081static int82dsl_dataset_user_hold_check(void *arg, dmu_tx_t *tx)83{84dsl_dataset_user_hold_arg_t *dduha = arg;85dsl_pool_t *dp = dmu_tx_pool(tx);86nvlist_t *tmp_holds;8788if (spa_version(dp->dp_spa) < SPA_VERSION_USERREFS)89return (SET_ERROR(ENOTSUP));9091if (!dmu_tx_is_syncing(tx))92return (0);9394/*95* Ensure the list has no duplicates by copying name/values from96* non-unique dduha_holds to unique tmp_holds, and comparing counts.97*/98tmp_holds = fnvlist_alloc();99for (nvpair_t *pair = nvlist_next_nvpair(dduha->dduha_holds, NULL);100pair != NULL; pair = nvlist_next_nvpair(dduha->dduha_holds, pair)) {101size_t len = strlen(nvpair_name(pair)) +102strlen(fnvpair_value_string(pair));103char *nameval = kmem_zalloc(len + 2, KM_SLEEP);104(void) strlcpy(nameval, nvpair_name(pair), len + 2);105(void) strlcat(nameval, "@", len + 2);106(void) strlcat(nameval, fnvpair_value_string(pair), len + 2);107fnvlist_add_string(tmp_holds, nameval, "");108kmem_free(nameval, len + 2);109}110size_t tmp_count = fnvlist_num_pairs(tmp_holds);111fnvlist_free(tmp_holds);112if (tmp_count != fnvlist_num_pairs(dduha->dduha_holds))113return (SET_ERROR(EEXIST));114for (nvpair_t *pair = nvlist_next_nvpair(dduha->dduha_holds, NULL);115pair != NULL; pair = nvlist_next_nvpair(dduha->dduha_holds, pair)) {116dsl_dataset_t *ds;117int error = 0;118const char *htag, *name;119120/* must be a snapshot */121name = nvpair_name(pair);122if (strchr(name, '@') == NULL)123error = SET_ERROR(EINVAL);124125if (error == 0)126error = nvpair_value_string(pair, &htag);127128if (error == 0)129error = dsl_dataset_hold(dp, name, FTAG, &ds);130131if (error == 0) {132error = dsl_dataset_user_hold_check_one(ds, htag,133dduha->dduha_minor != 0, tx);134dsl_dataset_rele(ds, FTAG);135}136137if (error == 0) {138fnvlist_add_string(dduha->dduha_chkholds, name, htag);139} else {140/*141* We register ENOENT errors so they can be correctly142* reported if needed, such as when all holds fail.143*/144fnvlist_add_int32(dduha->dduha_errlist, name, error);145if (error != ENOENT)146return (error);147}148}149150return (0);151}152153154static void155dsl_dataset_user_hold_sync_one_impl(nvlist_t *tmpholds, dsl_dataset_t *ds,156const char *htag, minor_t minor, uint64_t now, dmu_tx_t *tx)157{158dsl_pool_t *dp = ds->ds_dir->dd_pool;159objset_t *mos = dp->dp_meta_objset;160uint64_t zapobj;161162ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));163164if (dsl_dataset_phys(ds)->ds_userrefs_obj == 0) {165/*166* This is the first user hold for this dataset. Create167* the userrefs zap object.168*/169dmu_buf_will_dirty(ds->ds_dbuf, tx);170zapobj = dsl_dataset_phys(ds)->ds_userrefs_obj =171zap_create(mos, DMU_OT_USERREFS, DMU_OT_NONE, 0, tx);172} else {173zapobj = dsl_dataset_phys(ds)->ds_userrefs_obj;174}175ds->ds_userrefs++;176177VERIFY0(zap_add(mos, zapobj, htag, 8, 1, &now, tx));178179if (minor != 0) {180char name[MAXNAMELEN];181nvlist_t *tags;182183VERIFY0(dsl_pool_user_hold(dp, ds->ds_object,184htag, now, tx));185(void) snprintf(name, sizeof (name), "%llx",186(u_longlong_t)ds->ds_object);187188if (nvlist_lookup_nvlist(tmpholds, name, &tags) != 0) {189tags = fnvlist_alloc();190fnvlist_add_boolean(tags, htag);191fnvlist_add_nvlist(tmpholds, name, tags);192fnvlist_free(tags);193} else {194fnvlist_add_boolean(tags, htag);195}196}197198spa_history_log_internal_ds(ds, "hold", tx,199"tag=%s temp=%d refs=%llu",200htag, minor != 0, (u_longlong_t)ds->ds_userrefs);201}202203typedef struct zfs_hold_cleanup_arg {204char zhca_spaname[ZFS_MAX_DATASET_NAME_LEN];205uint64_t zhca_spa_load_guid;206nvlist_t *zhca_holds;207} zfs_hold_cleanup_arg_t;208209static void210dsl_dataset_user_release_onexit(void *arg)211{212zfs_hold_cleanup_arg_t *ca = arg;213spa_t *spa;214int error;215216error = spa_open(ca->zhca_spaname, &spa, FTAG);217if (error != 0) {218zfs_dbgmsg("couldn't release holds on pool=%s "219"because pool is no longer loaded",220ca->zhca_spaname);221return;222}223if (spa_load_guid(spa) != ca->zhca_spa_load_guid) {224zfs_dbgmsg("couldn't release holds on pool=%s "225"because pool is no longer loaded (guid doesn't match)",226ca->zhca_spaname);227spa_close(spa, FTAG);228return;229}230231(void) dsl_dataset_user_release_tmp(spa_get_dsl(spa), ca->zhca_holds);232fnvlist_free(ca->zhca_holds);233kmem_free(ca, sizeof (zfs_hold_cleanup_arg_t));234spa_close(spa, FTAG);235}236237static void238dsl_onexit_hold_cleanup(spa_t *spa, nvlist_t *holds, minor_t minor)239{240zfs_hold_cleanup_arg_t *ca;241242if (minor == 0 || nvlist_empty(holds)) {243fnvlist_free(holds);244return;245}246247ASSERT(spa != NULL);248ca = kmem_alloc(sizeof (*ca), KM_SLEEP);249250(void) strlcpy(ca->zhca_spaname, spa_name(spa),251sizeof (ca->zhca_spaname));252ca->zhca_spa_load_guid = spa_load_guid(spa);253ca->zhca_holds = holds;254VERIFY0(zfs_onexit_add_cb(minor,255dsl_dataset_user_release_onexit, ca, NULL));256}257258void259dsl_dataset_user_hold_sync_one(dsl_dataset_t *ds, const char *htag,260minor_t minor, uint64_t now, dmu_tx_t *tx)261{262nvlist_t *tmpholds;263264if (minor != 0)265tmpholds = fnvlist_alloc();266else267tmpholds = NULL;268dsl_dataset_user_hold_sync_one_impl(tmpholds, ds, htag, minor, now, tx);269dsl_onexit_hold_cleanup(dsl_dataset_get_spa(ds), tmpholds, minor);270}271272static void273dsl_dataset_user_hold_sync(void *arg, dmu_tx_t *tx)274{275dsl_dataset_user_hold_arg_t *dduha = arg;276dsl_pool_t *dp = dmu_tx_pool(tx);277nvlist_t *tmpholds;278uint64_t now = gethrestime_sec();279280if (dduha->dduha_minor != 0)281tmpholds = fnvlist_alloc();282else283tmpholds = NULL;284for (nvpair_t *pair = nvlist_next_nvpair(dduha->dduha_chkholds, NULL);285pair != NULL;286pair = nvlist_next_nvpair(dduha->dduha_chkholds, pair)) {287dsl_dataset_t *ds;288289VERIFY0(dsl_dataset_hold(dp, nvpair_name(pair), FTAG, &ds));290dsl_dataset_user_hold_sync_one_impl(tmpholds, ds,291fnvpair_value_string(pair), dduha->dduha_minor, now, tx);292dsl_dataset_rele(ds, FTAG);293}294dsl_onexit_hold_cleanup(dp->dp_spa, tmpholds, dduha->dduha_minor);295}296297/*298* The full semantics of this function are described in the comment above299* lzc_hold().300*301* To summarize:302* holds is nvl of snapname -> holdname303* errlist will be filled in with snapname -> error304*305* The snapshots must all be in the same pool.306*307* Holds for snapshots that don't exist will be skipped.308*309* If none of the snapshots for requested holds exist then ENOENT will be310* returned.311*312* If cleanup_minor is not 0, the holds will be temporary, which will be cleaned313* up when the process exits.314*315* On success all the holds, for snapshots that existed, will be created and 0316* will be returned.317*318* On failure no holds will be created, the errlist will be filled in,319* and an errno will returned.320*321* In all cases the errlist will contain entries for holds where the snapshot322* didn't exist.323*/324int325dsl_dataset_user_hold(nvlist_t *holds, minor_t cleanup_minor, nvlist_t *errlist)326{327dsl_dataset_user_hold_arg_t dduha;328nvpair_t *pair;329int ret;330331pair = nvlist_next_nvpair(holds, NULL);332if (pair == NULL)333return (0);334335dduha.dduha_holds = holds;336/* chkholds can have non-unique name */337VERIFY0(nvlist_alloc(&dduha.dduha_chkholds, 0, KM_SLEEP));338dduha.dduha_errlist = errlist;339dduha.dduha_minor = cleanup_minor;340341ret = dsl_sync_task(nvpair_name(pair), dsl_dataset_user_hold_check,342dsl_dataset_user_hold_sync, &dduha,343fnvlist_num_pairs(holds), ZFS_SPACE_CHECK_RESERVED);344fnvlist_free(dduha.dduha_chkholds);345346return (ret);347}348349typedef int (dsl_holdfunc_t)(dsl_pool_t *dp, const char *name, const void *tag,350dsl_dataset_t **dsp);351352typedef struct dsl_dataset_user_release_arg {353dsl_holdfunc_t *ddura_holdfunc;354nvlist_t *ddura_holds;355nvlist_t *ddura_todelete;356nvlist_t *ddura_errlist;357nvlist_t *ddura_chkholds;358} dsl_dataset_user_release_arg_t;359360/* Place a dataset hold on the snapshot identified by passed dsobj string */361static int362dsl_dataset_hold_obj_string(dsl_pool_t *dp, const char *dsobj, const void *tag,363dsl_dataset_t **dsp)364{365return (dsl_dataset_hold_obj(dp, zfs_strtonum(dsobj, NULL), tag, dsp));366}367368static int369dsl_dataset_user_release_check_one(dsl_dataset_user_release_arg_t *ddura,370dsl_dataset_t *ds, nvlist_t *holds, const char *snapname)371{372uint64_t zapobj;373nvlist_t *holds_found;374objset_t *mos;375int numholds;376377if (!ds->ds_is_snapshot)378return (SET_ERROR(EINVAL));379380if (nvlist_empty(holds))381return (0);382383numholds = 0;384mos = ds->ds_dir->dd_pool->dp_meta_objset;385zapobj = dsl_dataset_phys(ds)->ds_userrefs_obj;386VERIFY0(nvlist_alloc(&holds_found, NV_UNIQUE_NAME, KM_SLEEP));387388for (nvpair_t *pair = nvlist_next_nvpair(holds, NULL); pair != NULL;389pair = nvlist_next_nvpair(holds, pair)) {390uint64_t tmp;391int error;392const char *holdname = nvpair_name(pair);393394if (zapobj != 0)395error = zap_lookup(mos, zapobj, holdname, 8, 1, &tmp);396else397error = SET_ERROR(ENOENT);398399/*400* Non-existent holds are put on the errlist, but don't401* cause an overall failure.402*/403if (error == ENOENT) {404if (ddura->ddura_errlist != NULL) {405char *errtag = kmem_asprintf("%s#%s",406snapname, holdname);407fnvlist_add_int32(ddura->ddura_errlist, errtag,408ENOENT);409kmem_strfree(errtag);410}411continue;412}413414if (error != 0) {415fnvlist_free(holds_found);416return (error);417}418419fnvlist_add_boolean(holds_found, holdname);420numholds++;421}422423if (DS_IS_DEFER_DESTROY(ds) &&424dsl_dataset_phys(ds)->ds_num_children == 1 &&425ds->ds_userrefs == numholds) {426/* we need to destroy the snapshot as well */427if (dsl_dataset_long_held(ds)) {428fnvlist_free(holds_found);429return (SET_ERROR(EBUSY));430}431fnvlist_add_boolean(ddura->ddura_todelete, snapname);432}433434if (numholds != 0) {435fnvlist_add_nvlist(ddura->ddura_chkholds, snapname,436holds_found);437}438fnvlist_free(holds_found);439440return (0);441}442443static int444dsl_dataset_user_release_check(void *arg, dmu_tx_t *tx)445{446dsl_dataset_user_release_arg_t *ddura;447dsl_holdfunc_t *holdfunc;448dsl_pool_t *dp;449450if (!dmu_tx_is_syncing(tx))451return (0);452453dp = dmu_tx_pool(tx);454455ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));456457ddura = arg;458holdfunc = ddura->ddura_holdfunc;459460for (nvpair_t *pair = nvlist_next_nvpair(ddura->ddura_holds, NULL);461pair != NULL; pair = nvlist_next_nvpair(ddura->ddura_holds, pair)) {462int error;463dsl_dataset_t *ds;464nvlist_t *holds;465const char *snapname = nvpair_name(pair);466467error = nvpair_value_nvlist(pair, &holds);468if (error != 0)469error = (SET_ERROR(EINVAL));470else471error = holdfunc(dp, snapname, FTAG, &ds);472if (error == 0) {473error = dsl_dataset_user_release_check_one(ddura, ds,474holds, snapname);475dsl_dataset_rele(ds, FTAG);476}477if (error != 0) {478if (ddura->ddura_errlist != NULL) {479fnvlist_add_int32(ddura->ddura_errlist,480snapname, error);481}482/*483* Non-existent snapshots are put on the errlist,484* but don't cause an overall failure.485*/486if (error != ENOENT)487return (error);488}489}490491return (0);492}493494static void495dsl_dataset_user_release_sync_one(dsl_dataset_t *ds, nvlist_t *holds,496dmu_tx_t *tx)497{498dsl_pool_t *dp = ds->ds_dir->dd_pool;499objset_t *mos = dp->dp_meta_objset;500501for (nvpair_t *pair = nvlist_next_nvpair(holds, NULL); pair != NULL;502pair = nvlist_next_nvpair(holds, pair)) {503int error;504const char *holdname = nvpair_name(pair);505506/* Remove temporary hold if one exists. */507error = dsl_pool_user_release(dp, ds->ds_object, holdname, tx);508VERIFY(error == 0 || error == ENOENT);509510VERIFY0(zap_remove(mos, dsl_dataset_phys(ds)->ds_userrefs_obj,511holdname, tx));512ds->ds_userrefs--;513514spa_history_log_internal_ds(ds, "release", tx,515"tag=%s refs=%lld", holdname, (longlong_t)ds->ds_userrefs);516}517}518519static void520dsl_dataset_user_release_sync(void *arg, dmu_tx_t *tx)521{522dsl_dataset_user_release_arg_t *ddura = arg;523dsl_holdfunc_t *holdfunc = ddura->ddura_holdfunc;524dsl_pool_t *dp = dmu_tx_pool(tx);525526ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));527528for (nvpair_t *pair = nvlist_next_nvpair(ddura->ddura_chkholds, NULL);529pair != NULL; pair = nvlist_next_nvpair(ddura->ddura_chkholds,530pair)) {531dsl_dataset_t *ds;532const char *name = nvpair_name(pair);533534VERIFY0(holdfunc(dp, name, FTAG, &ds));535536dsl_dataset_user_release_sync_one(ds,537fnvpair_value_nvlist(pair), tx);538if (nvlist_exists(ddura->ddura_todelete, name)) {539ASSERT(ds->ds_userrefs == 0 &&540dsl_dataset_phys(ds)->ds_num_children == 1 &&541DS_IS_DEFER_DESTROY(ds));542dsl_destroy_snapshot_sync_impl(ds, B_FALSE, tx);543}544dsl_dataset_rele(ds, FTAG);545}546}547548/*549* The full semantics of this function are described in the comment above550* lzc_release().551*552* To summarize:553* Releases holds specified in the nvl holds.554*555* holds is nvl of snapname -> { holdname, ... }556* errlist will be filled in with snapname -> error557*558* If tmpdp is not NULL the names for holds should be the dsobj's of snapshots,559* otherwise they should be the names of snapshots.560*561* As a release may cause snapshots to be destroyed this tries to ensure they562* aren't mounted.563*564* The release of non-existent holds are skipped.565*566* At least one hold must have been released for the this function to succeed567* and return 0.568*/569static int570dsl_dataset_user_release_impl(nvlist_t *holds, nvlist_t *errlist,571dsl_pool_t *tmpdp)572{573dsl_dataset_user_release_arg_t ddura;574nvpair_t *pair;575const char *pool;576int error;577578pair = nvlist_next_nvpair(holds, NULL);579if (pair == NULL)580return (0);581582/*583* The release may cause snapshots to be destroyed; make sure they584* are not mounted.585*/586if (tmpdp != NULL) {587/* Temporary holds are specified by dsobj string. */588ddura.ddura_holdfunc = dsl_dataset_hold_obj_string;589pool = spa_name(tmpdp->dp_spa);590#ifdef _KERNEL591for (pair = nvlist_next_nvpair(holds, NULL); pair != NULL;592pair = nvlist_next_nvpair(holds, pair)) {593dsl_dataset_t *ds;594595dsl_pool_config_enter(tmpdp, FTAG);596error = dsl_dataset_hold_obj_string(tmpdp,597nvpair_name(pair), FTAG, &ds);598if (error == 0) {599char name[ZFS_MAX_DATASET_NAME_LEN];600dsl_dataset_name(ds, name);601dsl_pool_config_exit(tmpdp, FTAG);602dsl_dataset_rele(ds, FTAG);603(void) zfs_unmount_snap(name);604} else {605dsl_pool_config_exit(tmpdp, FTAG);606}607}608#endif609} else {610/* Non-temporary holds are specified by name. */611ddura.ddura_holdfunc = dsl_dataset_hold;612pool = nvpair_name(pair);613#ifdef _KERNEL614for (pair = nvlist_next_nvpair(holds, NULL); pair != NULL;615pair = nvlist_next_nvpair(holds, pair)) {616(void) zfs_unmount_snap(nvpair_name(pair));617}618#endif619}620621ddura.ddura_holds = holds;622ddura.ddura_errlist = errlist;623VERIFY0(nvlist_alloc(&ddura.ddura_todelete, NV_UNIQUE_NAME,624KM_SLEEP));625VERIFY0(nvlist_alloc(&ddura.ddura_chkholds, NV_UNIQUE_NAME,626KM_SLEEP));627628error = dsl_sync_task(pool, dsl_dataset_user_release_check,629dsl_dataset_user_release_sync, &ddura, 0,630ZFS_SPACE_CHECK_EXTRA_RESERVED);631fnvlist_free(ddura.ddura_todelete);632fnvlist_free(ddura.ddura_chkholds);633634return (error);635}636637/*638* holds is nvl of snapname -> { holdname, ... }639* errlist will be filled in with snapname -> error640*/641int642dsl_dataset_user_release(nvlist_t *holds, nvlist_t *errlist)643{644return (dsl_dataset_user_release_impl(holds, errlist, NULL));645}646647/*648* holds is nvl of snapdsobj -> { holdname, ... }649*/650void651dsl_dataset_user_release_tmp(struct dsl_pool *dp, nvlist_t *holds)652{653ASSERT(dp != NULL);654(void) dsl_dataset_user_release_impl(holds, NULL, dp);655}656657int658dsl_dataset_get_holds(const char *dsname, nvlist_t *nvl)659{660dsl_pool_t *dp;661dsl_dataset_t *ds;662int err;663664err = dsl_pool_hold(dsname, FTAG, &dp);665if (err != 0)666return (err);667err = dsl_dataset_hold(dp, dsname, FTAG, &ds);668if (err != 0) {669dsl_pool_rele(dp, FTAG);670return (err);671}672673if (dsl_dataset_phys(ds)->ds_userrefs_obj != 0) {674zap_attribute_t *za;675zap_cursor_t zc;676677za = zap_attribute_alloc();678for (zap_cursor_init(&zc, ds->ds_dir->dd_pool->dp_meta_objset,679dsl_dataset_phys(ds)->ds_userrefs_obj);680zap_cursor_retrieve(&zc, za) == 0;681zap_cursor_advance(&zc)) {682fnvlist_add_uint64(nvl, za->za_name,683za->za_first_integer);684}685zap_cursor_fini(&zc);686zap_attribute_free(za);687}688dsl_dataset_rele(ds, FTAG);689dsl_pool_rele(dp, FTAG);690return (0);691}692693694