Path: blob/main/sys/contrib/openzfs/cmd/zfs/zfs_iter.c
48288 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) 2012 Pawel Jakub Dawidek <[email protected]>.25* Copyright 2013 Nexenta Systems, Inc. All rights reserved.26* Copyright (c) 2013 by Delphix. All rights reserved.27*/2829#include <libintl.h>30#include <libuutil.h>31#include <stddef.h>32#include <stdio.h>33#include <stdlib.h>34#include <string.h>3536#include <libzfs.h>3738#include "zfs_util.h"39#include "zfs_iter.h"4041/*42* This is a private interface used to gather up all the datasets specified on43* the command line so that we can iterate over them in order.44*45* First, we iterate over all filesystems, gathering them together into an46* AVL tree. We report errors for any explicitly specified datasets47* that we couldn't open.48*49* When finished, we have an AVL tree of ZFS handles. We go through and execute50* the provided callback for each one, passing whatever data the user supplied.51*/5253typedef struct zfs_node {54zfs_handle_t *zn_handle;55uu_avl_node_t zn_avlnode;56} zfs_node_t;5758typedef struct callback_data {59uu_avl_t *cb_avl;60int cb_flags;61zfs_type_t cb_types;62zfs_sort_column_t *cb_sortcol;63zprop_list_t **cb_proplist;64int cb_depth_limit;65int cb_depth;66uint8_t cb_props_table[ZFS_NUM_PROPS];67} callback_data_t;6869uu_avl_pool_t *avl_pool;7071/*72* Include snaps if they were requested or if this a zfs list where types73* were not specified and the "listsnapshots" property is set on this pool.74*/75static boolean_t76zfs_include_snapshots(zfs_handle_t *zhp, callback_data_t *cb)77{78zpool_handle_t *zph;7980if ((cb->cb_flags & ZFS_ITER_PROP_LISTSNAPS) == 0)81return (cb->cb_types & ZFS_TYPE_SNAPSHOT);8283zph = zfs_get_pool_handle(zhp);84return (zpool_get_prop_int(zph, ZPOOL_PROP_LISTSNAPS, NULL));85}8687/*88* Called for each dataset. If the object is of an appropriate type,89* add it to the avl tree and recurse over any children as necessary.90*/91static int92zfs_callback(zfs_handle_t *zhp, void *data)93{94callback_data_t *cb = data;95boolean_t should_close = B_TRUE;96boolean_t include_snaps = zfs_include_snapshots(zhp, cb);97boolean_t include_bmarks = (cb->cb_types & ZFS_TYPE_BOOKMARK);9899if ((zfs_get_type(zhp) & cb->cb_types) ||100((zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT) && include_snaps)) {101uu_avl_index_t idx;102zfs_node_t *node = safe_malloc(sizeof (zfs_node_t));103104node->zn_handle = zhp;105uu_avl_node_init(node, &node->zn_avlnode, avl_pool);106if (uu_avl_find(cb->cb_avl, node, cb->cb_sortcol,107&idx) == NULL) {108if (cb->cb_proplist) {109if ((*cb->cb_proplist) &&110!(*cb->cb_proplist)->pl_all)111zfs_prune_proplist(zhp,112cb->cb_props_table);113114if (zfs_expand_proplist(zhp, cb->cb_proplist,115(cb->cb_flags & ZFS_ITER_RECVD_PROPS),116(cb->cb_flags & ZFS_ITER_LITERAL_PROPS))117!= 0) {118free(node);119return (-1);120}121}122uu_avl_insert(cb->cb_avl, node, idx);123should_close = B_FALSE;124} else {125free(node);126}127}128129/*130* Recurse if necessary.131*/132if (cb->cb_flags & ZFS_ITER_RECURSE &&133((cb->cb_flags & ZFS_ITER_DEPTH_LIMIT) == 0 ||134cb->cb_depth < cb->cb_depth_limit)) {135cb->cb_depth++;136137/*138* If we are not looking for filesystems, we don't need to139* recurse into filesystems when we are at our depth limit.140*/141if ((cb->cb_depth < cb->cb_depth_limit ||142(cb->cb_flags & ZFS_ITER_DEPTH_LIMIT) == 0 ||143(cb->cb_types &144(ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME))) &&145zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) {146(void) zfs_iter_filesystems_v2(zhp, cb->cb_flags,147zfs_callback, data);148}149150if (((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT |151ZFS_TYPE_BOOKMARK)) == 0) && include_snaps) {152(void) zfs_iter_snapshots_v2(zhp, cb->cb_flags,153zfs_callback, data, 0, 0);154}155156if (((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT |157ZFS_TYPE_BOOKMARK)) == 0) && include_bmarks) {158(void) zfs_iter_bookmarks_v2(zhp, cb->cb_flags,159zfs_callback, data);160}161162cb->cb_depth--;163}164165if (should_close)166zfs_close(zhp);167168return (0);169}170171int172zfs_add_sort_column(zfs_sort_column_t **sc, const char *name,173boolean_t reverse)174{175zfs_sort_column_t *col;176zfs_prop_t prop;177178if ((prop = zfs_name_to_prop(name)) == ZPROP_USERPROP &&179!zfs_prop_user(name))180return (-1);181182col = safe_malloc(sizeof (zfs_sort_column_t));183184col->sc_prop = prop;185col->sc_reverse = reverse;186if (prop == ZPROP_USERPROP) {187col->sc_user_prop = safe_malloc(strlen(name) + 1);188(void) strcpy(col->sc_user_prop, name);189}190191if (*sc == NULL) {192col->sc_last = col;193*sc = col;194} else {195(*sc)->sc_last->sc_next = col;196(*sc)->sc_last = col;197}198199return (0);200}201202void203zfs_free_sort_columns(zfs_sort_column_t *sc)204{205zfs_sort_column_t *col;206207while (sc != NULL) {208col = sc->sc_next;209free(sc->sc_user_prop);210free(sc);211sc = col;212}213}214215/*216* Return true if all of the properties to be sorted are populated by217* dsl_dataset_fast_stat(). Note that sc == NULL (no sort) means we218* don't need any extra properties, so returns true.219*/220boolean_t221zfs_sort_only_by_fast(const zfs_sort_column_t *sc)222{223while (sc != NULL) {224switch (sc->sc_prop) {225case ZFS_PROP_NAME:226case ZFS_PROP_GUID:227case ZFS_PROP_CREATETXG:228case ZFS_PROP_NUMCLONES:229case ZFS_PROP_INCONSISTENT:230case ZFS_PROP_REDACTED:231case ZFS_PROP_ORIGIN:232break;233default:234return (B_FALSE);235}236sc = sc->sc_next;237}238239return (B_TRUE);240}241242boolean_t243zfs_list_only_by_fast(const zprop_list_t *p)244{245if (p == NULL) {246/* NULL means 'all' so we can't use simple mode */247return (B_FALSE);248}249250while (p != NULL) {251switch (p->pl_prop) {252case ZFS_PROP_NAME:253case ZFS_PROP_GUID:254case ZFS_PROP_CREATETXG:255case ZFS_PROP_NUMCLONES:256case ZFS_PROP_INCONSISTENT:257case ZFS_PROP_REDACTED:258case ZFS_PROP_ORIGIN:259break;260default:261return (B_FALSE);262}263p = p->pl_next;264}265266return (B_TRUE);267}268269static int270zfs_compare(const void *larg, const void *rarg)271{272zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle;273zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle;274const char *lname = zfs_get_name(l);275const char *rname = zfs_get_name(r);276char *lat, *rat;277uint64_t lcreate, rcreate;278int ret;279280lat = (char *)strchr(lname, '@');281rat = (char *)strchr(rname, '@');282283if (lat != NULL)284*lat = '\0';285if (rat != NULL)286*rat = '\0';287288ret = strcmp(lname, rname);289if (ret == 0 && (lat != NULL || rat != NULL)) {290/*291* If we're comparing a dataset to one of its snapshots, we292* always make the full dataset first.293*/294if (lat == NULL) {295ret = -1;296} else if (rat == NULL) {297ret = 1;298} else {299/*300* If we have two snapshots from the same dataset, then301* we want to sort them according to creation time. We302* use the hidden CREATETXG property to get an absolute303* ordering of snapshots.304*/305lcreate = zfs_prop_get_int(l, ZFS_PROP_CREATETXG);306rcreate = zfs_prop_get_int(r, ZFS_PROP_CREATETXG);307308/*309* Both lcreate and rcreate being 0 means we don't have310* properties and we should compare full name.311*/312if (lcreate == 0 && rcreate == 0)313ret = strcmp(lat + 1, rat + 1);314else if (lcreate < rcreate)315ret = -1;316else if (lcreate > rcreate)317ret = 1;318}319}320321if (lat != NULL)322*lat = '@';323if (rat != NULL)324*rat = '@';325326return (ret);327}328329/*330* Sort datasets by specified columns.331*332* o Numeric types sort in ascending order.333* o String types sort in alphabetical order.334* o Types inappropriate for a row sort that row to the literal335* bottom, regardless of the specified ordering.336*337* If no sort columns are specified, or two datasets compare equally338* across all specified columns, they are sorted alphabetically by name339* with snapshots grouped under their parents.340*/341static int342zfs_sort(const void *larg, const void *rarg, void *data)343{344zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle;345zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle;346zfs_sort_column_t *sc = (zfs_sort_column_t *)data;347zfs_sort_column_t *psc;348349for (psc = sc; psc != NULL; psc = psc->sc_next) {350char lbuf[ZFS_MAXPROPLEN], rbuf[ZFS_MAXPROPLEN];351const char *lstr, *rstr;352uint64_t lnum = 0, rnum = 0;353boolean_t lvalid, rvalid;354int ret = 0;355356/*357* We group the checks below the generic code. If 'lstr' and358* 'rstr' are non-NULL, then we do a string based comparison.359* Otherwise, we compare 'lnum' and 'rnum'.360*/361lstr = rstr = NULL;362if (psc->sc_prop == ZPROP_USERPROP) {363nvlist_t *luser, *ruser;364nvlist_t *lval, *rval;365366luser = zfs_get_user_props(l);367ruser = zfs_get_user_props(r);368369lvalid = (nvlist_lookup_nvlist(luser,370psc->sc_user_prop, &lval) == 0);371rvalid = (nvlist_lookup_nvlist(ruser,372psc->sc_user_prop, &rval) == 0);373374if (lvalid)375verify(nvlist_lookup_string(lval,376ZPROP_VALUE, &lstr) == 0);377if (rvalid)378verify(nvlist_lookup_string(rval,379ZPROP_VALUE, &rstr) == 0);380} else if (psc->sc_prop == ZFS_PROP_NAME) {381lvalid = rvalid = B_TRUE;382383(void) strlcpy(lbuf, zfs_get_name(l), sizeof (lbuf));384(void) strlcpy(rbuf, zfs_get_name(r), sizeof (rbuf));385386lstr = lbuf;387rstr = rbuf;388} else if (zfs_prop_is_string(psc->sc_prop)) {389lvalid = (zfs_prop_get(l, psc->sc_prop, lbuf,390sizeof (lbuf), NULL, NULL, 0, B_TRUE) == 0);391rvalid = (zfs_prop_get(r, psc->sc_prop, rbuf,392sizeof (rbuf), NULL, NULL, 0, B_TRUE) == 0);393394lstr = lbuf;395rstr = rbuf;396} else {397lvalid = zfs_prop_valid_for_type(psc->sc_prop,398zfs_get_type(l), B_FALSE);399rvalid = zfs_prop_valid_for_type(psc->sc_prop,400zfs_get_type(r), B_FALSE);401402if (lvalid)403lnum = zfs_prop_get_int(l, psc->sc_prop);404if (rvalid)405rnum = zfs_prop_get_int(r, psc->sc_prop);406}407408if (!lvalid && !rvalid)409continue;410else if (!lvalid)411return (1);412else if (!rvalid)413return (-1);414415if (lstr)416ret = strcmp(lstr, rstr);417else if (lnum < rnum)418ret = -1;419else if (lnum > rnum)420ret = 1;421422if (ret != 0) {423if (psc->sc_reverse == B_TRUE)424ret = (ret < 0) ? 1 : -1;425return (ret);426}427}428429return (zfs_compare(larg, rarg));430}431432int433zfs_for_each(int argc, char **argv, int flags, zfs_type_t types,434zfs_sort_column_t *sortcol, zprop_list_t **proplist, int limit,435zfs_iter_f callback, void *data)436{437callback_data_t cb = {0};438int ret = 0;439zfs_node_t *node;440uu_avl_walk_t *walk;441442avl_pool = uu_avl_pool_create("zfs_pool", sizeof (zfs_node_t),443offsetof(zfs_node_t, zn_avlnode), zfs_sort, UU_DEFAULT);444445if (avl_pool == NULL)446nomem();447448cb.cb_sortcol = sortcol;449cb.cb_flags = flags;450cb.cb_proplist = proplist;451cb.cb_types = types;452cb.cb_depth_limit = limit;453/*454* If cb_proplist is provided then in the zfs_handles created we455* retain only those properties listed in cb_proplist and sortcol.456* The rest are pruned. So, the caller should make sure that no other457* properties other than those listed in cb_proplist/sortcol are458* accessed.459*460* If cb_proplist is NULL then we retain all the properties. We461* always retain the zoned property, which some other properties462* need (userquota & friends), and the createtxg property, which463* we need to sort snapshots.464*/465if (cb.cb_proplist && *cb.cb_proplist) {466zprop_list_t *p = *cb.cb_proplist;467468while (p) {469if (p->pl_prop >= ZFS_PROP_TYPE &&470p->pl_prop < ZFS_NUM_PROPS) {471cb.cb_props_table[p->pl_prop] = B_TRUE;472}473p = p->pl_next;474}475476while (sortcol) {477if (sortcol->sc_prop >= ZFS_PROP_TYPE &&478sortcol->sc_prop < ZFS_NUM_PROPS) {479cb.cb_props_table[sortcol->sc_prop] = B_TRUE;480}481sortcol = sortcol->sc_next;482}483484cb.cb_props_table[ZFS_PROP_ZONED] = B_TRUE;485cb.cb_props_table[ZFS_PROP_CREATETXG] = B_TRUE;486} else {487(void) memset(cb.cb_props_table, B_TRUE,488sizeof (cb.cb_props_table));489}490491if ((cb.cb_avl = uu_avl_create(avl_pool, NULL, UU_DEFAULT)) == NULL)492nomem();493494if (argc == 0) {495/*496* If given no arguments, iterate over all datasets.497*/498cb.cb_flags |= ZFS_ITER_RECURSE;499ret = zfs_iter_root(g_zfs, zfs_callback, &cb);500} else {501zfs_handle_t *zhp = NULL;502zfs_type_t argtype = types;503504/*505* If we're recursive, then we always allow filesystems as506* arguments. If we also are interested in snapshots or507* bookmarks, then we can take volumes as well.508*/509if (flags & ZFS_ITER_RECURSE) {510argtype |= ZFS_TYPE_FILESYSTEM;511if (types & (ZFS_TYPE_SNAPSHOT | ZFS_TYPE_BOOKMARK))512argtype |= ZFS_TYPE_VOLUME;513}514515for (int i = 0; i < argc; i++) {516if (flags & ZFS_ITER_ARGS_CAN_BE_PATHS) {517zhp = zfs_path_to_zhandle(g_zfs, argv[i],518argtype);519} else {520zhp = zfs_open(g_zfs, argv[i], argtype);521}522if (zhp != NULL)523ret |= zfs_callback(zhp, &cb);524else525ret = 1;526}527}528529/*530* At this point we've got our AVL tree full of zfs handles, so iterate531* over each one and execute the real user callback.532*/533for (node = uu_avl_first(cb.cb_avl); node != NULL;534node = uu_avl_next(cb.cb_avl, node))535ret |= callback(node->zn_handle, data);536537/*538* Finally, clean up the AVL tree.539*/540if ((walk = uu_avl_walk_start(cb.cb_avl, UU_WALK_ROBUST)) == NULL)541nomem();542543while ((node = uu_avl_walk_next(walk)) != NULL) {544uu_avl_remove(cb.cb_avl, node);545zfs_close(node->zn_handle);546free(node);547}548549uu_avl_walk_end(walk);550uu_avl_destroy(cb.cb_avl);551uu_avl_pool_destroy(avl_pool);552553return (ret);554}555556557