Path: blob/main/sys/contrib/openzfs/lib/libzfs/libzfs_iter.c
48378 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*/2122/*23* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.24* Copyright (c) 2013, 2019 by Delphix. All rights reserved.25* Copyright 2014 Nexenta Systems, Inc. All rights reserved.26* Copyright (c) 2019 Datto Inc.27*/2829#include <stdio.h>30#include <stdlib.h>31#include <string.h>32#include <unistd.h>33#include <stddef.h>34#include <libintl.h>35#include <libzfs.h>36#include <libzutil.h>37#include <sys/mntent.h>3839#include "libzfs_impl.h"4041static int42zfs_iter_clones(zfs_handle_t *zhp, int flags __maybe_unused, zfs_iter_f func,43void *data)44{45nvlist_t *nvl = zfs_get_clones_nvl(zhp);46nvpair_t *pair;4748if (nvl == NULL)49return (0);5051for (pair = nvlist_next_nvpair(nvl, NULL); pair != NULL;52pair = nvlist_next_nvpair(nvl, pair)) {53zfs_handle_t *clone = zfs_open(zhp->zfs_hdl, nvpair_name(pair),54ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);55if (clone != NULL) {56int err = func(clone, data);57if (err != 0)58return (err);59}60}61return (0);62}6364static int65zfs_do_list_ioctl(zfs_handle_t *zhp, int arg, zfs_cmd_t *zc)66{67int rc;68uint64_t orig_cookie;6970orig_cookie = zc->zc_cookie;71top:72(void) strlcpy(zc->zc_name, zhp->zfs_name, sizeof (zc->zc_name));73zc->zc_objset_stats.dds_creation_txg = 0;74rc = zfs_ioctl(zhp->zfs_hdl, arg, zc);7576if (rc == -1) {77switch (errno) {78case ENOMEM:79/* expand nvlist memory and try again */80zcmd_expand_dst_nvlist(zhp->zfs_hdl, zc);81zc->zc_cookie = orig_cookie;82goto top;83/*84* An errno value of ESRCH indicates normal completion.85* If ENOENT is returned, then the underlying dataset86* has been removed since we obtained the handle.87*/88case ESRCH:89case ENOENT:90rc = 1;91break;92default:93rc = zfs_standard_error(zhp->zfs_hdl, errno,94dgettext(TEXT_DOMAIN,95"cannot iterate filesystems"));96break;97}98}99return (rc);100}101102/*103* Iterate over all child filesystems104*/105int106zfs_iter_filesystems(zfs_handle_t *zhp, zfs_iter_f func, void *data)107{108return (zfs_iter_filesystems_v2(zhp, 0, func, data));109}110111int112zfs_iter_filesystems_v2(zfs_handle_t *zhp, int flags, zfs_iter_f func,113void *data)114{115zfs_cmd_t zc = {"\0"};116zfs_handle_t *nzhp;117int ret;118119if (zhp->zfs_type != ZFS_TYPE_FILESYSTEM)120return (0);121122zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0);123124if ((flags & ZFS_ITER_SIMPLE) == ZFS_ITER_SIMPLE)125zc.zc_simple = B_TRUE;126127while ((ret = zfs_do_list_ioctl(zhp, ZFS_IOC_DATASET_LIST_NEXT,128&zc)) == 0) {129if (zc.zc_simple)130nzhp = make_dataset_simple_handle_zc(zhp, &zc);131else132nzhp = make_dataset_handle_zc(zhp->zfs_hdl, &zc);133/*134* Silently ignore errors, as the only plausible explanation is135* that the pool has since been removed.136*/137if (nzhp == NULL)138continue;139140if ((ret = func(nzhp, data)) != 0) {141zcmd_free_nvlists(&zc);142return (ret);143}144}145zcmd_free_nvlists(&zc);146return ((ret < 0) ? ret : 0);147}148149/*150* Iterate over all snapshots151*/152int153zfs_iter_snapshots(zfs_handle_t *zhp, boolean_t simple, zfs_iter_f func,154void *data, uint64_t min_txg, uint64_t max_txg)155{156return (zfs_iter_snapshots_v2(zhp, simple ? ZFS_ITER_SIMPLE : 0, func,157data, min_txg, max_txg));158}159160int161zfs_iter_snapshots_v2(zfs_handle_t *zhp, int flags, zfs_iter_f func,162void *data, uint64_t min_txg, uint64_t max_txg)163{164zfs_cmd_t zc = {"\0"};165zfs_handle_t *nzhp;166int ret;167nvlist_t *range_nvl = NULL;168169if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT ||170zhp->zfs_type == ZFS_TYPE_BOOKMARK)171return (0);172173zc.zc_simple = (flags & ZFS_ITER_SIMPLE) != 0;174175zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0);176177if (min_txg != 0) {178range_nvl = fnvlist_alloc();179fnvlist_add_uint64(range_nvl, SNAP_ITER_MIN_TXG, min_txg);180}181if (max_txg != 0) {182if (range_nvl == NULL)183range_nvl = fnvlist_alloc();184fnvlist_add_uint64(range_nvl, SNAP_ITER_MAX_TXG, max_txg);185}186187if (range_nvl != NULL)188zcmd_write_src_nvlist(zhp->zfs_hdl, &zc, range_nvl);189190while ((ret = zfs_do_list_ioctl(zhp, ZFS_IOC_SNAPSHOT_LIST_NEXT,191&zc)) == 0) {192193if (zc.zc_simple)194nzhp = make_dataset_simple_handle_zc(zhp, &zc);195else196nzhp = make_dataset_handle_zc(zhp->zfs_hdl, &zc);197if (nzhp == NULL)198continue;199200if ((ret = func(nzhp, data)) != 0) {201zcmd_free_nvlists(&zc);202fnvlist_free(range_nvl);203return (ret);204}205}206zcmd_free_nvlists(&zc);207fnvlist_free(range_nvl);208return ((ret < 0) ? ret : 0);209}210211/*212* Iterate over all bookmarks213*/214int215zfs_iter_bookmarks(zfs_handle_t *zhp, zfs_iter_f func, void *data)216{217return (zfs_iter_bookmarks_v2(zhp, 0, func, data));218}219220int221zfs_iter_bookmarks_v2(zfs_handle_t *zhp, int flags __maybe_unused,222zfs_iter_f func, void *data)223{224zfs_handle_t *nzhp;225nvlist_t *props = NULL;226nvlist_t *bmarks = NULL;227int err;228nvpair_t *pair;229230if ((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT | ZFS_TYPE_BOOKMARK)) != 0)231return (0);232233/* Setup the requested properties nvlist. */234props = fnvlist_alloc();235for (zfs_prop_t p = 0; p < ZFS_NUM_PROPS; p++) {236if (zfs_prop_valid_for_type(p, ZFS_TYPE_BOOKMARK, B_FALSE)) {237fnvlist_add_boolean(props, zfs_prop_to_name(p));238}239}240fnvlist_add_boolean(props, "redact_complete");241242if ((err = lzc_get_bookmarks(zhp->zfs_name, props, &bmarks)) != 0)243goto out;244245for (pair = nvlist_next_nvpair(bmarks, NULL);246pair != NULL; pair = nvlist_next_nvpair(bmarks, pair)) {247char name[ZFS_MAX_DATASET_NAME_LEN];248const char *bmark_name;249nvlist_t *bmark_props;250251bmark_name = nvpair_name(pair);252bmark_props = fnvpair_value_nvlist(pair);253254if (snprintf(name, sizeof (name), "%s#%s", zhp->zfs_name,255bmark_name) >= sizeof (name)) {256err = EINVAL;257goto out;258}259260nzhp = make_bookmark_handle(zhp, name, bmark_props);261if (nzhp == NULL)262continue;263264if ((err = func(nzhp, data)) != 0)265goto out;266}267268out:269fnvlist_free(props);270fnvlist_free(bmarks);271272return (err);273}274275/*276* Routines for dealing with the sorted snapshot functionality277*/278typedef struct zfs_node {279zfs_handle_t *zn_handle;280avl_node_t zn_avlnode;281} zfs_node_t;282283static int284zfs_sort_snaps(zfs_handle_t *zhp, void *data)285{286avl_tree_t *avl = data;287zfs_node_t *node;288zfs_node_t search;289290search.zn_handle = zhp;291node = avl_find(avl, &search, NULL);292if (node) {293/*294* If this snapshot was renamed while we were creating the295* AVL tree, it's possible that we already inserted it under296* its old name. Remove the old handle before adding the new297* one.298*/299zfs_close(node->zn_handle);300avl_remove(avl, node);301free(node);302}303304node = zfs_alloc(zhp->zfs_hdl, sizeof (zfs_node_t));305node->zn_handle = zhp;306avl_add(avl, node);307308return (0);309}310311static int312zfs_snapshot_compare(const void *larg, const void *rarg)313{314zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle;315zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle;316uint64_t lcreate, rcreate;317318/*319* Sort them according to creation time. We use the hidden320* CREATETXG property to get an absolute ordering of snapshots.321*/322lcreate = zfs_prop_get_int(l, ZFS_PROP_CREATETXG);323rcreate = zfs_prop_get_int(r, ZFS_PROP_CREATETXG);324325return (TREE_CMP(lcreate, rcreate));326}327328int329zfs_iter_snapshots_sorted(zfs_handle_t *zhp, zfs_iter_f callback,330void *data, uint64_t min_txg, uint64_t max_txg)331{332return (zfs_iter_snapshots_sorted_v2(zhp, 0, callback, data,333min_txg, max_txg));334}335336int337zfs_iter_snapshots_sorted_v2(zfs_handle_t *zhp, int flags, zfs_iter_f callback,338void *data, uint64_t min_txg, uint64_t max_txg)339{340int ret = 0;341zfs_node_t *node;342avl_tree_t avl;343void *cookie = NULL;344345avl_create(&avl, zfs_snapshot_compare,346sizeof (zfs_node_t), offsetof(zfs_node_t, zn_avlnode));347348ret = zfs_iter_snapshots_v2(zhp, flags, zfs_sort_snaps, &avl, min_txg,349max_txg);350351for (node = avl_first(&avl); node != NULL; node = AVL_NEXT(&avl, node))352ret |= callback(node->zn_handle, data);353354while ((node = avl_destroy_nodes(&avl, &cookie)) != NULL)355free(node);356357avl_destroy(&avl);358359return (ret);360}361362typedef struct {363char *ssa_first;364char *ssa_last;365boolean_t ssa_seenfirst;366boolean_t ssa_seenlast;367zfs_iter_f ssa_func;368void *ssa_arg;369} snapspec_arg_t;370371static int372snapspec_cb(zfs_handle_t *zhp, void *arg)373{374snapspec_arg_t *ssa = arg;375const char *shortsnapname;376int err = 0;377378if (ssa->ssa_seenlast)379return (0);380381shortsnapname = strchr(zfs_get_name(zhp), '@') + 1;382if (!ssa->ssa_seenfirst && strcmp(shortsnapname, ssa->ssa_first) == 0)383ssa->ssa_seenfirst = B_TRUE;384if (strcmp(shortsnapname, ssa->ssa_last) == 0)385ssa->ssa_seenlast = B_TRUE;386387if (ssa->ssa_seenfirst) {388err = ssa->ssa_func(zhp, ssa->ssa_arg);389} else {390zfs_close(zhp);391}392393return (err);394}395396/*397* spec is a string like "A,B%C,D"398*399* <snaps>, where <snaps> can be:400* <snap> (single snapshot)401* <snap>%<snap> (range of snapshots, inclusive)402* %<snap> (range of snapshots, starting with earliest)403* <snap>% (range of snapshots, ending with last)404* % (all snapshots)405* <snaps>[,...] (comma separated list of the above)406*407* If a snapshot can not be opened, continue trying to open the others, but408* return ENOENT at the end.409*/410int411zfs_iter_snapspec(zfs_handle_t *fs_zhp, const char *spec_orig,412zfs_iter_f func, void *arg)413{414return (zfs_iter_snapspec_v2(fs_zhp, 0, spec_orig, func, arg));415}416417int418zfs_iter_snapspec_v2(zfs_handle_t *fs_zhp, int flags, const char *spec_orig,419zfs_iter_f func, void *arg)420{421char *buf, *comma_separated, *cp;422int err = 0;423int ret = 0;424425buf = zfs_strdup(fs_zhp->zfs_hdl, spec_orig);426cp = buf;427428while ((comma_separated = strsep(&cp, ",")) != NULL) {429char *pct = strchr(comma_separated, '%');430if (pct != NULL) {431snapspec_arg_t ssa = { 0 };432ssa.ssa_func = func;433ssa.ssa_arg = arg;434435if (pct == comma_separated)436ssa.ssa_seenfirst = B_TRUE;437else438ssa.ssa_first = comma_separated;439*pct = '\0';440ssa.ssa_last = pct + 1;441442/*443* If there is a lastname specified, make sure it444* exists.445*/446if (ssa.ssa_last[0] != '\0') {447char snapname[ZFS_MAX_DATASET_NAME_LEN];448(void) snprintf(snapname, sizeof (snapname),449"%s@%s", zfs_get_name(fs_zhp),450ssa.ssa_last);451if (!zfs_dataset_exists(fs_zhp->zfs_hdl,452snapname, ZFS_TYPE_SNAPSHOT)) {453ret = ENOENT;454continue;455}456}457458err = zfs_iter_snapshots_sorted_v2(fs_zhp, flags,459snapspec_cb, &ssa, 0, 0);460if (ret == 0)461ret = err;462if (ret == 0 && (!ssa.ssa_seenfirst ||463(ssa.ssa_last[0] != '\0' && !ssa.ssa_seenlast))) {464ret = ENOENT;465}466} else {467char snapname[ZFS_MAX_DATASET_NAME_LEN];468zfs_handle_t *snap_zhp;469(void) snprintf(snapname, sizeof (snapname), "%s@%s",470zfs_get_name(fs_zhp), comma_separated);471snap_zhp = make_dataset_handle(fs_zhp->zfs_hdl,472snapname);473if (snap_zhp == NULL) {474ret = ENOENT;475continue;476}477err = func(snap_zhp, arg);478if (ret == 0)479ret = err;480}481}482483free(buf);484return (ret);485}486487/*488* Iterate over all children, snapshots and filesystems489* Process snapshots before filesystems because they are nearer the input490* handle: this is extremely important when used with zfs_iter_f functions491* looking for data, following the logic that we would like to find it as soon492* and as close as possible.493*/494int495zfs_iter_children(zfs_handle_t *zhp, zfs_iter_f func, void *data)496{497return (zfs_iter_children_v2(zhp, 0, func, data));498}499500int501zfs_iter_children_v2(zfs_handle_t *zhp, int flags, zfs_iter_f func, void *data)502{503int ret;504505if ((ret = zfs_iter_snapshots_v2(zhp, flags, func, data, 0, 0)) != 0)506return (ret);507508return (zfs_iter_filesystems_v2(zhp, flags, func, data));509}510511512typedef struct iter_stack_frame {513struct iter_stack_frame *next;514zfs_handle_t *zhp;515} iter_stack_frame_t;516517typedef struct iter_dependents_arg {518boolean_t first;519int flags;520boolean_t allowrecursion;521iter_stack_frame_t *stack;522zfs_iter_f func;523void *data;524} iter_dependents_arg_t;525526static int527iter_dependents_cb(zfs_handle_t *zhp, void *arg)528{529iter_dependents_arg_t *ida = arg;530int err = 0;531boolean_t first = ida->first;532ida->first = B_FALSE;533534if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) {535err = zfs_iter_clones(zhp, ida->flags, iter_dependents_cb, ida);536} else if (zhp->zfs_type != ZFS_TYPE_BOOKMARK) {537iter_stack_frame_t isf;538iter_stack_frame_t *f;539540/*541* check if there is a cycle by seeing if this fs is already542* on the stack.543*/544for (f = ida->stack; f != NULL; f = f->next) {545if (f->zhp->zfs_dmustats.dds_guid ==546zhp->zfs_dmustats.dds_guid) {547if (ida->allowrecursion) {548zfs_close(zhp);549return (0);550} else {551zfs_error_aux(zhp->zfs_hdl,552dgettext(TEXT_DOMAIN,553"recursive dependency at '%s'"),554zfs_get_name(zhp));555err = zfs_error(zhp->zfs_hdl,556EZFS_RECURSIVE,557dgettext(TEXT_DOMAIN,558"cannot determine dependent "559"datasets"));560zfs_close(zhp);561return (err);562}563}564}565566isf.zhp = zhp;567isf.next = ida->stack;568ida->stack = &isf;569err = zfs_iter_filesystems_v2(zhp, ida->flags,570iter_dependents_cb, ida);571if (err == 0)572err = zfs_iter_snapshots_sorted_v2(zhp, ida->flags,573iter_dependents_cb, ida, 0, 0);574ida->stack = isf.next;575}576577if (!first && err == 0)578err = ida->func(zhp, ida->data);579else580zfs_close(zhp);581582return (err);583}584585int586zfs_iter_dependents(zfs_handle_t *zhp, boolean_t allowrecursion,587zfs_iter_f func, void *data)588{589return (zfs_iter_dependents_v2(zhp, 0, allowrecursion, func, data));590}591592int593zfs_iter_dependents_v2(zfs_handle_t *zhp, int flags, boolean_t allowrecursion,594zfs_iter_f func, void *data)595{596iter_dependents_arg_t ida;597ida.flags = flags;598ida.allowrecursion = allowrecursion;599ida.stack = NULL;600ida.func = func;601ida.data = data;602ida.first = B_TRUE;603return (iter_dependents_cb(zfs_handle_dup(zhp), &ida));604}605606/*607* Iterate over mounted children of the specified dataset608*/609int610zfs_iter_mounted(zfs_handle_t *zhp, zfs_iter_f func, void *data)611{612char mnt_prop[ZFS_MAXPROPLEN];613struct mnttab entry;614zfs_handle_t *mtab_zhp;615size_t namelen = strlen(zhp->zfs_name);616FILE *mnttab;617int err = 0;618619if ((mnttab = fopen(MNTTAB, "re")) == NULL)620return (ENOENT);621622while (err == 0 && getmntent(mnttab, &entry) == 0) {623/* Ignore non-ZFS entries */624if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0)625continue;626627/* Ignore datasets not within the provided dataset */628if (strncmp(entry.mnt_special, zhp->zfs_name, namelen) != 0 ||629entry.mnt_special[namelen] != '/')630continue;631632/* Skip snapshot of any child dataset */633if (strchr(entry.mnt_special, '@') != NULL)634continue;635636if ((mtab_zhp = zfs_open(zhp->zfs_hdl, entry.mnt_special,637ZFS_TYPE_FILESYSTEM)) == NULL)638continue;639640/* Ignore legacy mounts as they are user managed */641verify(zfs_prop_get(mtab_zhp, ZFS_PROP_MOUNTPOINT, mnt_prop,642sizeof (mnt_prop), NULL, NULL, 0, B_FALSE) == 0);643if (strcmp(mnt_prop, "legacy") == 0) {644zfs_close(mtab_zhp);645continue;646}647648err = func(mtab_zhp, data);649}650651fclose(mnttab);652653return (err);654}655656657