1ecd6cf80Smarks /*
2ecd6cf80Smarks * CDDL HEADER START
3ecd6cf80Smarks *
4ecd6cf80Smarks * The contents of this file are subject to the terms of the
5ecd6cf80Smarks * Common Development and Distribution License (the "License").
6ecd6cf80Smarks * You may not use this file except in compliance with the License.
7ecd6cf80Smarks *
8ecd6cf80Smarks * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9ecd6cf80Smarks * or http://www.opensolaris.org/os/licensing.
10ecd6cf80Smarks * See the License for the specific language governing permissions
11ecd6cf80Smarks * and limitations under the License.
12ecd6cf80Smarks *
13ecd6cf80Smarks * When distributing Covered Code, include this CDDL HEADER in each
14ecd6cf80Smarks * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15ecd6cf80Smarks * If applicable, add the following below this CDDL HEADER, with the
16ecd6cf80Smarks * fields enclosed by brackets "[]" replaced with your own identifying
17ecd6cf80Smarks * information: Portions Copyright [yyyy] [name of copyright owner]
18ecd6cf80Smarks *
19ecd6cf80Smarks * CDDL HEADER END
20ecd6cf80Smarks */
21ecd6cf80Smarks /*
2299d5e173STim Haley * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
239adfa60dSMatthew Ahrens * Copyright (c) 2011, 2015 by Delphix. All rights reserved.
24ecd6cf80Smarks */
25ecd6cf80Smarks
26ecd6cf80Smarks /*
27ecd6cf80Smarks * DSL permissions are stored in a two level zap attribute
28ecd6cf80Smarks * mechanism. The first level identifies the "class" of
29ecd6cf80Smarks * entry. The class is identified by the first 2 letters of
30ecd6cf80Smarks * the attribute. The second letter "l" or "d" identifies whether
31ecd6cf80Smarks * it is a local or descendent permission. The first letter
32ecd6cf80Smarks * identifies the type of entry.
33ecd6cf80Smarks *
34da6c28aaSamw * ul$<id> identifies permissions granted locally for this userid.
35ecd6cf80Smarks * ud$<id> identifies permissions granted on descendent datasets for
36ecd6cf80Smarks * this userid.
37ecd6cf80Smarks * Ul$<id> identifies permission sets granted locally for this userid.
38ecd6cf80Smarks * Ud$<id> identifies permission sets granted on descendent datasets for
39ecd6cf80Smarks * this userid.
40ecd6cf80Smarks * gl$<id> identifies permissions granted locally for this groupid.
41ecd6cf80Smarks * gd$<id> identifies permissions granted on descendent datasets for
42ecd6cf80Smarks * this groupid.
43ecd6cf80Smarks * Gl$<id> identifies permission sets granted locally for this groupid.
44ecd6cf80Smarks * Gd$<id> identifies permission sets granted on descendent datasets for
45ecd6cf80Smarks * this groupid.
46ecd6cf80Smarks * el$ identifies permissions granted locally for everyone.
47ecd6cf80Smarks * ed$ identifies permissions granted on descendent datasets
48ecd6cf80Smarks * for everyone.
49ecd6cf80Smarks * El$ identifies permission sets granted locally for everyone.
50ecd6cf80Smarks * Ed$ identifies permission sets granted to descendent datasets for
51ecd6cf80Smarks * everyone.
52ecd6cf80Smarks * c-$ identifies permission to create at dataset creation time.
53ecd6cf80Smarks * C-$ identifies permission sets to grant locally at dataset creation
54ecd6cf80Smarks * time.
55ecd6cf80Smarks * s-$@<name> permissions defined in specified set @<name>
56ecd6cf80Smarks * S-$@<name> Sets defined in named set @<name>
57ecd6cf80Smarks *
58da6c28aaSamw * Each of the above entities points to another zap attribute that contains one
59ecd6cf80Smarks * attribute for each allowed permission, such as create, destroy,...
60ecd6cf80Smarks * All of the "upper" case class types will specify permission set names
61ecd6cf80Smarks * rather than permissions.
62ecd6cf80Smarks *
63ecd6cf80Smarks * Basically it looks something like this:
64ecd6cf80Smarks * ul$12 -> ZAP OBJ -> permissions...
65ecd6cf80Smarks *
66ecd6cf80Smarks * The ZAP OBJ is referred to as the jump object.
67ecd6cf80Smarks */
68ecd6cf80Smarks
69ecd6cf80Smarks #include <sys/dmu.h>
70ecd6cf80Smarks #include <sys/dmu_objset.h>
71ecd6cf80Smarks #include <sys/dmu_tx.h>
72ecd6cf80Smarks #include <sys/dsl_dataset.h>
73ecd6cf80Smarks #include <sys/dsl_dir.h>
74ecd6cf80Smarks #include <sys/dsl_prop.h>
75ecd6cf80Smarks #include <sys/dsl_synctask.h>
76ecd6cf80Smarks #include <sys/dsl_deleg.h>
77ecd6cf80Smarks #include <sys/spa.h>
78ecd6cf80Smarks #include <sys/zap.h>
79ecd6cf80Smarks #include <sys/fs/zfs.h>
80ecd6cf80Smarks #include <sys/cred.h>
81ecd6cf80Smarks #include <sys/sunddi.h>
82ecd6cf80Smarks
83ecd6cf80Smarks #include "zfs_deleg.h"
84ecd6cf80Smarks
85ecd6cf80Smarks /*
86ecd6cf80Smarks * Validate that user is allowed to delegate specified permissions.
87ecd6cf80Smarks *
8891ebeef5Sahrens * In order to delegate "create" you must have "create"
89ecd6cf80Smarks * and "allow".
90ecd6cf80Smarks */
91ecd6cf80Smarks int
dsl_deleg_can_allow(char * ddname,nvlist_t * nvp,cred_t * cr)92ecd6cf80Smarks dsl_deleg_can_allow(char *ddname, nvlist_t *nvp, cred_t *cr)
93ecd6cf80Smarks {
94ecd6cf80Smarks nvpair_t *whopair = NULL;
9591ebeef5Sahrens int error;
96ecd6cf80Smarks
9791ebeef5Sahrens if ((error = dsl_deleg_access(ddname, ZFS_DELEG_PERM_ALLOW, cr)) != 0)
98ecd6cf80Smarks return (error);
99ecd6cf80Smarks
100ecd6cf80Smarks while (whopair = nvlist_next_nvpair(nvp, whopair)) {
101ecd6cf80Smarks nvlist_t *perms;
102ecd6cf80Smarks nvpair_t *permpair = NULL;
103ecd6cf80Smarks
104ecd6cf80Smarks VERIFY(nvpair_value_nvlist(whopair, &perms) == 0);
105ecd6cf80Smarks
106ecd6cf80Smarks while (permpair = nvlist_next_nvpair(perms, permpair)) {
107ecd6cf80Smarks const char *perm = nvpair_name(permpair);
108ecd6cf80Smarks
109ecd6cf80Smarks if (strcmp(perm, ZFS_DELEG_PERM_ALLOW) == 0)
110be6fd75aSMatthew Ahrens return (SET_ERROR(EPERM));
111ecd6cf80Smarks
11291ebeef5Sahrens if ((error = dsl_deleg_access(ddname, perm, cr)) != 0)
113ecd6cf80Smarks return (error);
114ecd6cf80Smarks }
115ecd6cf80Smarks }
11691ebeef5Sahrens return (0);
117ecd6cf80Smarks }
118ecd6cf80Smarks
119ecd6cf80Smarks /*
120ecd6cf80Smarks * Validate that user is allowed to unallow specified permissions. They
121ecd6cf80Smarks * must have the 'allow' permission, and even then can only unallow
122ecd6cf80Smarks * perms for their uid.
123ecd6cf80Smarks */
124ecd6cf80Smarks int
dsl_deleg_can_unallow(char * ddname,nvlist_t * nvp,cred_t * cr)125ecd6cf80Smarks dsl_deleg_can_unallow(char *ddname, nvlist_t *nvp, cred_t *cr)
126ecd6cf80Smarks {
127ecd6cf80Smarks nvpair_t *whopair = NULL;
128ecd6cf80Smarks int error;
12991ebeef5Sahrens char idstr[32];
130ecd6cf80Smarks
13191ebeef5Sahrens if ((error = dsl_deleg_access(ddname, ZFS_DELEG_PERM_ALLOW, cr)) != 0)
132ecd6cf80Smarks return (error);
133ecd6cf80Smarks
13491ebeef5Sahrens (void) snprintf(idstr, sizeof (idstr), "%lld",
13591ebeef5Sahrens (longlong_t)crgetuid(cr));
13691ebeef5Sahrens
137ecd6cf80Smarks while (whopair = nvlist_next_nvpair(nvp, whopair)) {
138ecd6cf80Smarks zfs_deleg_who_type_t type = nvpair_name(whopair)[0];
139ecd6cf80Smarks
140ecd6cf80Smarks if (type != ZFS_DELEG_USER &&
141ecd6cf80Smarks type != ZFS_DELEG_USER_SETS)
142be6fd75aSMatthew Ahrens return (SET_ERROR(EPERM));
143ecd6cf80Smarks
144ecd6cf80Smarks if (strcmp(idstr, &nvpair_name(whopair)[3]) != 0)
145be6fd75aSMatthew Ahrens return (SET_ERROR(EPERM));
146ecd6cf80Smarks }
147ecd6cf80Smarks return (0);
148ecd6cf80Smarks }
149ecd6cf80Smarks
1503b2aab18SMatthew Ahrens typedef struct dsl_deleg_arg {
1513b2aab18SMatthew Ahrens const char *dda_name;
1523b2aab18SMatthew Ahrens nvlist_t *dda_nvlist;
1533b2aab18SMatthew Ahrens } dsl_deleg_arg_t;
154ecd6cf80Smarks
1553b2aab18SMatthew Ahrens static void
dsl_deleg_set_sync(void * arg,dmu_tx_t * tx)1563b2aab18SMatthew Ahrens dsl_deleg_set_sync(void *arg, dmu_tx_t *tx)
1573b2aab18SMatthew Ahrens {
1583b2aab18SMatthew Ahrens dsl_deleg_arg_t *dda = arg;
1593b2aab18SMatthew Ahrens dsl_dir_t *dd;
1603b2aab18SMatthew Ahrens dsl_pool_t *dp = dmu_tx_pool(tx);
1613b2aab18SMatthew Ahrens objset_t *mos = dp->dp_meta_objset;
1623b2aab18SMatthew Ahrens nvpair_t *whopair = NULL;
1633b2aab18SMatthew Ahrens uint64_t zapobj;
1643b2aab18SMatthew Ahrens
1653b2aab18SMatthew Ahrens VERIFY0(dsl_dir_hold(dp, dda->dda_name, FTAG, &dd, NULL));
1663b2aab18SMatthew Ahrens
167c1379625SJustin T. Gibbs zapobj = dsl_dir_phys(dd)->dd_deleg_zapobj;
168ecd6cf80Smarks if (zapobj == 0) {
169ecd6cf80Smarks dmu_buf_will_dirty(dd->dd_dbuf, tx);
170c1379625SJustin T. Gibbs zapobj = dsl_dir_phys(dd)->dd_deleg_zapobj = zap_create(mos,
171ecd6cf80Smarks DMU_OT_DSL_PERMS, DMU_OT_NONE, 0, tx);
172ecd6cf80Smarks }
173ecd6cf80Smarks
1743b2aab18SMatthew Ahrens while (whopair = nvlist_next_nvpair(dda->dda_nvlist, whopair)) {
1753cb34c60Sahrens const char *whokey = nvpair_name(whopair);
1763cb34c60Sahrens nvlist_t *perms;
1773cb34c60Sahrens nvpair_t *permpair = NULL;
1783cb34c60Sahrens uint64_t jumpobj;
1793cb34c60Sahrens
1803b2aab18SMatthew Ahrens perms = fnvpair_value_nvlist(whopair);
1813cb34c60Sahrens
1823cb34c60Sahrens if (zap_lookup(mos, zapobj, whokey, 8, 1, &jumpobj) != 0) {
183ad135b5dSChristopher Siden jumpobj = zap_create_link(mos, DMU_OT_DSL_PERMS,
184ad135b5dSChristopher Siden zapobj, whokey, tx);
1853cb34c60Sahrens }
1863cb34c60Sahrens
1873cb34c60Sahrens while (permpair = nvlist_next_nvpair(perms, permpair)) {
1883cb34c60Sahrens const char *perm = nvpair_name(permpair);
1893cb34c60Sahrens uint64_t n = 0;
1903cb34c60Sahrens
1913cb34c60Sahrens VERIFY(zap_update(mos, jumpobj,
1923cb34c60Sahrens perm, 8, 1, &n, tx) == 0);
1934445fffbSMatthew Ahrens spa_history_log_internal_dd(dd, "permission update", tx,
1944445fffbSMatthew Ahrens "%s %s", whokey, perm);
1953cb34c60Sahrens }
1963cb34c60Sahrens }
1973b2aab18SMatthew Ahrens dsl_dir_rele(dd, FTAG);
1983cb34c60Sahrens }
1993cb34c60Sahrens
2003cb34c60Sahrens static void
dsl_deleg_unset_sync(void * arg,dmu_tx_t * tx)2013b2aab18SMatthew Ahrens dsl_deleg_unset_sync(void *arg, dmu_tx_t *tx)
2023cb34c60Sahrens {
2033b2aab18SMatthew Ahrens dsl_deleg_arg_t *dda = arg;
2043b2aab18SMatthew Ahrens dsl_dir_t *dd;
2053b2aab18SMatthew Ahrens dsl_pool_t *dp = dmu_tx_pool(tx);
2063b2aab18SMatthew Ahrens objset_t *mos = dp->dp_meta_objset;
2073cb34c60Sahrens nvpair_t *whopair = NULL;
2083b2aab18SMatthew Ahrens uint64_t zapobj;
2093cb34c60Sahrens
2103b2aab18SMatthew Ahrens VERIFY0(dsl_dir_hold(dp, dda->dda_name, FTAG, &dd, NULL));
211c1379625SJustin T. Gibbs zapobj = dsl_dir_phys(dd)->dd_deleg_zapobj;
2123b2aab18SMatthew Ahrens if (zapobj == 0) {
2133b2aab18SMatthew Ahrens dsl_dir_rele(dd, FTAG);
2143cb34c60Sahrens return;
2153b2aab18SMatthew Ahrens }
2163cb34c60Sahrens
2173b2aab18SMatthew Ahrens while (whopair = nvlist_next_nvpair(dda->dda_nvlist, whopair)) {
218ecd6cf80Smarks const char *whokey = nvpair_name(whopair);
219ecd6cf80Smarks nvlist_t *perms;
220ecd6cf80Smarks nvpair_t *permpair = NULL;
221ecd6cf80Smarks uint64_t jumpobj;
222ecd6cf80Smarks
223ecd6cf80Smarks if (nvpair_value_nvlist(whopair, &perms) != 0) {
224ecd6cf80Smarks if (zap_lookup(mos, zapobj, whokey, 8,
225ecd6cf80Smarks 1, &jumpobj) == 0) {
226ecd6cf80Smarks (void) zap_remove(mos, zapobj, whokey, tx);
227ecd6cf80Smarks VERIFY(0 == zap_destroy(mos, jumpobj, tx));
228ecd6cf80Smarks }
2294445fffbSMatthew Ahrens spa_history_log_internal_dd(dd, "permission who remove",
2304445fffbSMatthew Ahrens tx, "%s", whokey);
231ecd6cf80Smarks continue;
232ecd6cf80Smarks }
233ecd6cf80Smarks
2343cb34c60Sahrens if (zap_lookup(mos, zapobj, whokey, 8, 1, &jumpobj) != 0)
235ecd6cf80Smarks continue;
236ecd6cf80Smarks
237ecd6cf80Smarks while (permpair = nvlist_next_nvpair(perms, permpair)) {
238ecd6cf80Smarks const char *perm = nvpair_name(permpair);
239ecd6cf80Smarks uint64_t n = 0;
240ecd6cf80Smarks
241ecd6cf80Smarks (void) zap_remove(mos, jumpobj, perm, tx);
2423cb34c60Sahrens if (zap_count(mos, jumpobj, &n) == 0 && n == 0) {
243ecd6cf80Smarks (void) zap_remove(mos, zapobj,
244ecd6cf80Smarks whokey, tx);
245ecd6cf80Smarks VERIFY(0 == zap_destroy(mos,
246ecd6cf80Smarks jumpobj, tx));
247ecd6cf80Smarks }
2484445fffbSMatthew Ahrens spa_history_log_internal_dd(dd, "permission remove", tx,
2494445fffbSMatthew Ahrens "%s %s", whokey, perm);
250ecd6cf80Smarks }
251ecd6cf80Smarks }
2523b2aab18SMatthew Ahrens dsl_dir_rele(dd, FTAG);
2533b2aab18SMatthew Ahrens }
2543b2aab18SMatthew Ahrens
2553b2aab18SMatthew Ahrens static int
dsl_deleg_check(void * arg,dmu_tx_t * tx)2563b2aab18SMatthew Ahrens dsl_deleg_check(void *arg, dmu_tx_t *tx)
2573b2aab18SMatthew Ahrens {
2583b2aab18SMatthew Ahrens dsl_deleg_arg_t *dda = arg;
2593b2aab18SMatthew Ahrens dsl_dir_t *dd;
2603b2aab18SMatthew Ahrens int error;
2613b2aab18SMatthew Ahrens
2623b2aab18SMatthew Ahrens if (spa_version(dmu_tx_pool(tx)->dp_spa) <
2633b2aab18SMatthew Ahrens SPA_VERSION_DELEGATED_PERMS) {
264be6fd75aSMatthew Ahrens return (SET_ERROR(ENOTSUP));
2653b2aab18SMatthew Ahrens }
2663b2aab18SMatthew Ahrens
2673b2aab18SMatthew Ahrens error = dsl_dir_hold(dmu_tx_pool(tx), dda->dda_name, FTAG, &dd, NULL);
2683b2aab18SMatthew Ahrens if (error == 0)
2693b2aab18SMatthew Ahrens dsl_dir_rele(dd, FTAG);
2703b2aab18SMatthew Ahrens return (error);
271ecd6cf80Smarks }
272ecd6cf80Smarks
273ecd6cf80Smarks int
dsl_deleg_set(const char * ddname,nvlist_t * nvp,boolean_t unset)274ecd6cf80Smarks dsl_deleg_set(const char *ddname, nvlist_t *nvp, boolean_t unset)
275ecd6cf80Smarks {
2763b2aab18SMatthew Ahrens dsl_deleg_arg_t dda;
277ecd6cf80Smarks
2783b2aab18SMatthew Ahrens /* nvp must already have been verified to be valid */
279ecd6cf80Smarks
2803b2aab18SMatthew Ahrens dda.dda_name = ddname;
2813b2aab18SMatthew Ahrens dda.dda_nvlist = nvp;
282ecd6cf80Smarks
2833b2aab18SMatthew Ahrens return (dsl_sync_task(ddname, dsl_deleg_check,
2843cb34c60Sahrens unset ? dsl_deleg_unset_sync : dsl_deleg_set_sync,
2857d46dc6cSMatthew Ahrens &dda, fnvlist_num_pairs(nvp), ZFS_SPACE_CHECK_RESERVED));
286ecd6cf80Smarks }
287ecd6cf80Smarks
288ecd6cf80Smarks /*
289ecd6cf80Smarks * Find all 'allow' permissions from a given point and then continue
290ecd6cf80Smarks * traversing up to the root.
291ecd6cf80Smarks *
292ecd6cf80Smarks * This function constructs an nvlist of nvlists.
293ecd6cf80Smarks * each setpoint is an nvlist composed of an nvlist of an nvlist
294ecd6cf80Smarks * of the individual * users/groups/everyone/create
295ecd6cf80Smarks * permissions.
296ecd6cf80Smarks *
297ecd6cf80Smarks * The nvlist will look like this.
298ecd6cf80Smarks *
299ecd6cf80Smarks * { source fsname -> { whokeys { permissions,...}, ...}}
300ecd6cf80Smarks *
301ecd6cf80Smarks * The fsname nvpairs will be arranged in a bottom up order. For example,
302ecd6cf80Smarks * if we have the following structure a/b/c then the nvpairs for the fsnames
303ecd6cf80Smarks * will be ordered a/b/c, a/b, a.
304ecd6cf80Smarks */
305ecd6cf80Smarks int
dsl_deleg_get(const char * ddname,nvlist_t ** nvp)306ecd6cf80Smarks dsl_deleg_get(const char *ddname, nvlist_t **nvp)
307ecd6cf80Smarks {
308ecd6cf80Smarks dsl_dir_t *dd, *startdd;
309ecd6cf80Smarks dsl_pool_t *dp;
310ecd6cf80Smarks int error;
311ecd6cf80Smarks objset_t *mos;
312ecd6cf80Smarks
3133b2aab18SMatthew Ahrens error = dsl_pool_hold(ddname, FTAG, &dp);
3143b2aab18SMatthew Ahrens if (error != 0)
315ecd6cf80Smarks return (error);
316ecd6cf80Smarks
3173b2aab18SMatthew Ahrens error = dsl_dir_hold(dp, ddname, FTAG, &startdd, NULL);
3183b2aab18SMatthew Ahrens if (error != 0) {
3193b2aab18SMatthew Ahrens dsl_pool_rele(dp, FTAG);
3203b2aab18SMatthew Ahrens return (error);
3213b2aab18SMatthew Ahrens }
3223b2aab18SMatthew Ahrens
323ecd6cf80Smarks dp = startdd->dd_pool;
324ecd6cf80Smarks mos = dp->dp_meta_objset;
325ecd6cf80Smarks
326ecd6cf80Smarks VERIFY(nvlist_alloc(nvp, NV_UNIQUE_NAME, KM_SLEEP) == 0);
327ecd6cf80Smarks
328ecd6cf80Smarks for (dd = startdd; dd != NULL; dd = dd->dd_parent) {
329ecd6cf80Smarks zap_cursor_t basezc;
330ecd6cf80Smarks zap_attribute_t baseza;
331ecd6cf80Smarks nvlist_t *sp_nvp;
332ecd6cf80Smarks uint64_t n;
3339adfa60dSMatthew Ahrens char source[ZFS_MAX_DATASET_NAME_LEN];
334ecd6cf80Smarks
335c1379625SJustin T. Gibbs if (dsl_dir_phys(dd)->dd_deleg_zapobj == 0 ||
336c1379625SJustin T. Gibbs zap_count(mos,
337c1379625SJustin T. Gibbs dsl_dir_phys(dd)->dd_deleg_zapobj, &n) != 0 || n == 0)
338ecd6cf80Smarks continue;
339ecd6cf80Smarks
3403b2aab18SMatthew Ahrens sp_nvp = fnvlist_alloc();
341ecd6cf80Smarks for (zap_cursor_init(&basezc, mos,
342c1379625SJustin T. Gibbs dsl_dir_phys(dd)->dd_deleg_zapobj);
343ecd6cf80Smarks zap_cursor_retrieve(&basezc, &baseza) == 0;
344ecd6cf80Smarks zap_cursor_advance(&basezc)) {
345ecd6cf80Smarks zap_cursor_t zc;
346ecd6cf80Smarks zap_attribute_t za;
347ecd6cf80Smarks nvlist_t *perms_nvp;
348ecd6cf80Smarks
349ecd6cf80Smarks ASSERT(baseza.za_integer_length == 8);
350ecd6cf80Smarks ASSERT(baseza.za_num_integers == 1);
351ecd6cf80Smarks
3523b2aab18SMatthew Ahrens perms_nvp = fnvlist_alloc();
353ecd6cf80Smarks for (zap_cursor_init(&zc, mos, baseza.za_first_integer);
354ecd6cf80Smarks zap_cursor_retrieve(&zc, &za) == 0;
355ecd6cf80Smarks zap_cursor_advance(&zc)) {
3563b2aab18SMatthew Ahrens fnvlist_add_boolean(perms_nvp, za.za_name);
357ecd6cf80Smarks }
358ecd6cf80Smarks zap_cursor_fini(&zc);
3593b2aab18SMatthew Ahrens fnvlist_add_nvlist(sp_nvp, baseza.za_name, perms_nvp);
3603b2aab18SMatthew Ahrens fnvlist_free(perms_nvp);
361ecd6cf80Smarks }
362ecd6cf80Smarks
363ecd6cf80Smarks zap_cursor_fini(&basezc);
364ecd6cf80Smarks
365ecd6cf80Smarks dsl_dir_name(dd, source);
3663b2aab18SMatthew Ahrens fnvlist_add_nvlist(*nvp, source, sp_nvp);
367ecd6cf80Smarks nvlist_free(sp_nvp);
368ecd6cf80Smarks }
369ecd6cf80Smarks
3703b2aab18SMatthew Ahrens dsl_dir_rele(startdd, FTAG);
3713b2aab18SMatthew Ahrens dsl_pool_rele(dp, FTAG);
372ecd6cf80Smarks return (0);
373ecd6cf80Smarks }
374ecd6cf80Smarks
375ecd6cf80Smarks /*
376ecd6cf80Smarks * Routines for dsl_deleg_access() -- access checking.
377ecd6cf80Smarks */
378ecd6cf80Smarks typedef struct perm_set {
379ecd6cf80Smarks avl_node_t p_node;
380ecd6cf80Smarks boolean_t p_matched;
38191ebeef5Sahrens char p_setname[ZFS_MAX_DELEG_NAME];
382ecd6cf80Smarks } perm_set_t;
383ecd6cf80Smarks
384ecd6cf80Smarks static int
perm_set_compare(const void * arg1,const void * arg2)385ecd6cf80Smarks perm_set_compare(const void *arg1, const void *arg2)
386ecd6cf80Smarks {
387c4ab0d3fSGvozden Neskovic const perm_set_t *node1 = (const perm_set_t *)arg1;
388c4ab0d3fSGvozden Neskovic const perm_set_t *node2 = (const perm_set_t *)arg2;
389ecd6cf80Smarks int val;
390ecd6cf80Smarks
391ecd6cf80Smarks val = strcmp(node1->p_setname, node2->p_setname);
392c4ab0d3fSGvozden Neskovic
393*4d7988d6SPaul Dagnelie return (TREE_ISIGN(val));
394ecd6cf80Smarks }
395ecd6cf80Smarks
396ecd6cf80Smarks /*
397ecd6cf80Smarks * Determine whether a specified permission exists.
398ecd6cf80Smarks *
399ecd6cf80Smarks * First the base attribute has to be retrieved. i.e. ul$12
400ecd6cf80Smarks * Once the base object has been retrieved the actual permission
401ecd6cf80Smarks * is lookup up in the zap object the base object points to.
402ecd6cf80Smarks *
403ecd6cf80Smarks * Return 0 if permission exists, ENOENT if there is no whokey, EPERM if
404ecd6cf80Smarks * there is no perm in that jumpobj.
405ecd6cf80Smarks */
406ecd6cf80Smarks static int
dsl_check_access(objset_t * mos,uint64_t zapobj,char type,char checkflag,void * valp,const char * perm)407ecd6cf80Smarks dsl_check_access(objset_t *mos, uint64_t zapobj,
408ecd6cf80Smarks char type, char checkflag, void *valp, const char *perm)
409ecd6cf80Smarks {
410ecd6cf80Smarks int error;
411ecd6cf80Smarks uint64_t jumpobj, zero;
412ecd6cf80Smarks char whokey[ZFS_MAX_DELEG_NAME];
413ecd6cf80Smarks
414ecd6cf80Smarks zfs_deleg_whokey(whokey, type, checkflag, valp);
415ecd6cf80Smarks error = zap_lookup(mos, zapobj, whokey, 8, 1, &jumpobj);
416ecd6cf80Smarks if (error == 0) {
417ecd6cf80Smarks error = zap_lookup(mos, jumpobj, perm, 8, 1, &zero);
418ecd6cf80Smarks if (error == ENOENT)
419be6fd75aSMatthew Ahrens error = SET_ERROR(EPERM);
420ecd6cf80Smarks }
421ecd6cf80Smarks return (error);
422ecd6cf80Smarks }
423ecd6cf80Smarks
424ecd6cf80Smarks /*
425ecd6cf80Smarks * check a specified user/group for a requested permission
426ecd6cf80Smarks */
427ecd6cf80Smarks static int
dsl_check_user_access(objset_t * mos,uint64_t zapobj,const char * perm,int checkflag,cred_t * cr)42891ebeef5Sahrens dsl_check_user_access(objset_t *mos, uint64_t zapobj, const char *perm,
429ecd6cf80Smarks int checkflag, cred_t *cr)
430ecd6cf80Smarks {
431ecd6cf80Smarks const gid_t *gids;
432ecd6cf80Smarks int ngids;
433ecd6cf80Smarks int i;
434ecd6cf80Smarks uint64_t id;
435ecd6cf80Smarks
436ecd6cf80Smarks /* check for user */
437ecd6cf80Smarks id = crgetuid(cr);
43891ebeef5Sahrens if (dsl_check_access(mos, zapobj,
439ecd6cf80Smarks ZFS_DELEG_USER, checkflag, &id, perm) == 0)
440ecd6cf80Smarks return (0);
441ecd6cf80Smarks
442ecd6cf80Smarks /* check for users primary group */
443ecd6cf80Smarks id = crgetgid(cr);
44491ebeef5Sahrens if (dsl_check_access(mos, zapobj,
445ecd6cf80Smarks ZFS_DELEG_GROUP, checkflag, &id, perm) == 0)
446ecd6cf80Smarks return (0);
447ecd6cf80Smarks
448ecd6cf80Smarks /* check for everyone entry */
449ecd6cf80Smarks id = -1;
45091ebeef5Sahrens if (dsl_check_access(mos, zapobj,
451ecd6cf80Smarks ZFS_DELEG_EVERYONE, checkflag, &id, perm) == 0)
452ecd6cf80Smarks return (0);
453ecd6cf80Smarks
454ecd6cf80Smarks /* check each supplemental group user is a member of */
455ecd6cf80Smarks ngids = crgetngroups(cr);
456ecd6cf80Smarks gids = crgetgroups(cr);
457ecd6cf80Smarks for (i = 0; i != ngids; i++) {
458ecd6cf80Smarks id = gids[i];
45991ebeef5Sahrens if (dsl_check_access(mos, zapobj,
460ecd6cf80Smarks ZFS_DELEG_GROUP, checkflag, &id, perm) == 0)
461ecd6cf80Smarks return (0);
462ecd6cf80Smarks }
463ecd6cf80Smarks
464be6fd75aSMatthew Ahrens return (SET_ERROR(EPERM));
465ecd6cf80Smarks }
466ecd6cf80Smarks
467ecd6cf80Smarks /*
468ecd6cf80Smarks * Iterate over the sets specified in the specified zapobj
469ecd6cf80Smarks * and load them into the permsets avl tree.
470ecd6cf80Smarks */
471ecd6cf80Smarks static int
dsl_load_sets(objset_t * mos,uint64_t zapobj,char type,char checkflag,void * valp,avl_tree_t * avl)472ecd6cf80Smarks dsl_load_sets(objset_t *mos, uint64_t zapobj,
473ecd6cf80Smarks char type, char checkflag, void *valp, avl_tree_t *avl)
474ecd6cf80Smarks {
475ecd6cf80Smarks zap_cursor_t zc;
476ecd6cf80Smarks zap_attribute_t za;
477ecd6cf80Smarks perm_set_t *permnode;
478ecd6cf80Smarks avl_index_t idx;
479ecd6cf80Smarks uint64_t jumpobj;
480ecd6cf80Smarks int error;
481ecd6cf80Smarks char whokey[ZFS_MAX_DELEG_NAME];
482ecd6cf80Smarks
483ecd6cf80Smarks zfs_deleg_whokey(whokey, type, checkflag, valp);
484ecd6cf80Smarks
485ecd6cf80Smarks error = zap_lookup(mos, zapobj, whokey, 8, 1, &jumpobj);
486ecd6cf80Smarks if (error != 0)
487ecd6cf80Smarks return (error);
488ecd6cf80Smarks
489ecd6cf80Smarks for (zap_cursor_init(&zc, mos, jumpobj);
490ecd6cf80Smarks zap_cursor_retrieve(&zc, &za) == 0;
491ecd6cf80Smarks zap_cursor_advance(&zc)) {
492ecd6cf80Smarks permnode = kmem_alloc(sizeof (perm_set_t), KM_SLEEP);
493ecd6cf80Smarks (void) strlcpy(permnode->p_setname, za.za_name,
494ecd6cf80Smarks sizeof (permnode->p_setname));
495ecd6cf80Smarks permnode->p_matched = B_FALSE;
496ecd6cf80Smarks
497ecd6cf80Smarks if (avl_find(avl, permnode, &idx) == NULL) {
498ecd6cf80Smarks avl_insert(avl, permnode, idx);
499ecd6cf80Smarks } else {
500ecd6cf80Smarks kmem_free(permnode, sizeof (perm_set_t));
501ecd6cf80Smarks }
502ecd6cf80Smarks }
503ecd6cf80Smarks zap_cursor_fini(&zc);
504ecd6cf80Smarks return (0);
505ecd6cf80Smarks }
506ecd6cf80Smarks
507ecd6cf80Smarks /*
508ecd6cf80Smarks * Load all permissions user based on cred belongs to.
509ecd6cf80Smarks */
510ecd6cf80Smarks static void
dsl_load_user_sets(objset_t * mos,uint64_t zapobj,avl_tree_t * avl,char checkflag,cred_t * cr)511ecd6cf80Smarks dsl_load_user_sets(objset_t *mos, uint64_t zapobj, avl_tree_t *avl,
512ecd6cf80Smarks char checkflag, cred_t *cr)
513ecd6cf80Smarks {
514ecd6cf80Smarks const gid_t *gids;
515ecd6cf80Smarks int ngids, i;
516ecd6cf80Smarks uint64_t id;
517ecd6cf80Smarks
518ecd6cf80Smarks id = crgetuid(cr);
519ecd6cf80Smarks (void) dsl_load_sets(mos, zapobj,
520ecd6cf80Smarks ZFS_DELEG_USER_SETS, checkflag, &id, avl);
521ecd6cf80Smarks
522ecd6cf80Smarks id = crgetgid(cr);
523ecd6cf80Smarks (void) dsl_load_sets(mos, zapobj,
524ecd6cf80Smarks ZFS_DELEG_GROUP_SETS, checkflag, &id, avl);
525ecd6cf80Smarks
526ecd6cf80Smarks (void) dsl_load_sets(mos, zapobj,
527ecd6cf80Smarks ZFS_DELEG_EVERYONE_SETS, checkflag, NULL, avl);
528ecd6cf80Smarks
529ecd6cf80Smarks ngids = crgetngroups(cr);
530ecd6cf80Smarks gids = crgetgroups(cr);
531ecd6cf80Smarks for (i = 0; i != ngids; i++) {
532ecd6cf80Smarks id = gids[i];
533ecd6cf80Smarks (void) dsl_load_sets(mos, zapobj,
534ecd6cf80Smarks ZFS_DELEG_GROUP_SETS, checkflag, &id, avl);
535ecd6cf80Smarks }
536ecd6cf80Smarks }
537ecd6cf80Smarks
538ecd6cf80Smarks /*
5394445fffbSMatthew Ahrens * Check if user has requested permission.
540ecd6cf80Smarks */
541ecd6cf80Smarks int
dsl_deleg_access_impl(dsl_dataset_t * ds,const char * perm,cred_t * cr)5424445fffbSMatthew Ahrens dsl_deleg_access_impl(dsl_dataset_t *ds, const char *perm, cred_t *cr)
543ecd6cf80Smarks {
544bb0ade09Sahrens dsl_dir_t *dd;
545ecd6cf80Smarks dsl_pool_t *dp;
546ecd6cf80Smarks void *cookie;
547ecd6cf80Smarks int error;
5488b33e213SWilliam Gorrell char checkflag;
549ecd6cf80Smarks objset_t *mos;
550ecd6cf80Smarks avl_tree_t permsets;
551ecd6cf80Smarks perm_set_t *setnode;
552ecd6cf80Smarks
553bb0ade09Sahrens dp = ds->ds_dir->dd_pool;
554ecd6cf80Smarks mos = dp->dp_meta_objset;
555ecd6cf80Smarks
556a7f53a56SChris Kirby if (dsl_delegation_on(mos) == B_FALSE)
557be6fd75aSMatthew Ahrens return (SET_ERROR(ECANCELED));
558ecd6cf80Smarks
559ecd6cf80Smarks if (spa_version(dmu_objset_spa(dp->dp_meta_objset)) <
560a7f53a56SChris Kirby SPA_VERSION_DELEGATED_PERMS)
561be6fd75aSMatthew Ahrens return (SET_ERROR(EPERM));
562ecd6cf80Smarks
563bc9014e6SJustin Gibbs if (ds->ds_is_snapshot) {
5648b33e213SWilliam Gorrell /*
5658b33e213SWilliam Gorrell * Snapshots are treated as descendents only,
5668b33e213SWilliam Gorrell * local permissions do not apply.
5678b33e213SWilliam Gorrell */
5688b33e213SWilliam Gorrell checkflag = ZFS_DELEG_DESCENDENT;
5698b33e213SWilliam Gorrell } else {
5708b33e213SWilliam Gorrell checkflag = ZFS_DELEG_LOCAL;
5718b33e213SWilliam Gorrell }
5728b33e213SWilliam Gorrell
573ecd6cf80Smarks avl_create(&permsets, perm_set_compare, sizeof (perm_set_t),
574ecd6cf80Smarks offsetof(perm_set_t, p_node));
575ecd6cf80Smarks
5763b2aab18SMatthew Ahrens ASSERT(dsl_pool_config_held(dp));
577bb0ade09Sahrens for (dd = ds->ds_dir; dd != NULL; dd = dd->dd_parent,
578ecd6cf80Smarks checkflag = ZFS_DELEG_DESCENDENT) {
579ecd6cf80Smarks uint64_t zapobj;
580ecd6cf80Smarks boolean_t expanded;
581ecd6cf80Smarks
582ecd6cf80Smarks /*
583ecd6cf80Smarks * If not in global zone then make sure
584ecd6cf80Smarks * the zoned property is set
585ecd6cf80Smarks */
586ecd6cf80Smarks if (!INGLOBALZONE(curproc)) {
587ecd6cf80Smarks uint64_t zoned;
588ecd6cf80Smarks
589bb0ade09Sahrens if (dsl_prop_get_dd(dd,
590ecd6cf80Smarks zfs_prop_to_name(ZFS_PROP_ZONED),
59192241e0bSTom Erickson 8, 1, &zoned, NULL, B_FALSE) != 0)
592ecd6cf80Smarks break;
593ecd6cf80Smarks if (!zoned)
594ecd6cf80Smarks break;
595ecd6cf80Smarks }
596c1379625SJustin T. Gibbs zapobj = dsl_dir_phys(dd)->dd_deleg_zapobj;
597ecd6cf80Smarks
598ecd6cf80Smarks if (zapobj == 0)
599ecd6cf80Smarks continue;
600ecd6cf80Smarks
601ecd6cf80Smarks dsl_load_user_sets(mos, zapobj, &permsets, checkflag, cr);
602ecd6cf80Smarks again:
603ecd6cf80Smarks expanded = B_FALSE;
604ecd6cf80Smarks for (setnode = avl_first(&permsets); setnode;
605ecd6cf80Smarks setnode = AVL_NEXT(&permsets, setnode)) {
606ecd6cf80Smarks if (setnode->p_matched == B_TRUE)
607ecd6cf80Smarks continue;
608ecd6cf80Smarks
609ecd6cf80Smarks /* See if this set directly grants this permission */
610ecd6cf80Smarks error = dsl_check_access(mos, zapobj,
611ecd6cf80Smarks ZFS_DELEG_NAMED_SET, 0, setnode->p_setname, perm);
612ecd6cf80Smarks if (error == 0)
613ecd6cf80Smarks goto success;
614ecd6cf80Smarks if (error == EPERM)
615ecd6cf80Smarks setnode->p_matched = B_TRUE;
616ecd6cf80Smarks
617ecd6cf80Smarks /* See if this set includes other sets */
618ecd6cf80Smarks error = dsl_load_sets(mos, zapobj,
619ecd6cf80Smarks ZFS_DELEG_NAMED_SET_SETS, 0,
620ecd6cf80Smarks setnode->p_setname, &permsets);
621ecd6cf80Smarks if (error == 0)
622ecd6cf80Smarks setnode->p_matched = expanded = B_TRUE;
623ecd6cf80Smarks }
624ecd6cf80Smarks /*
625ecd6cf80Smarks * If we expanded any sets, that will define more sets,
626ecd6cf80Smarks * which we need to check.
627ecd6cf80Smarks */
628ecd6cf80Smarks if (expanded)
629ecd6cf80Smarks goto again;
630ecd6cf80Smarks
631ecd6cf80Smarks error = dsl_check_user_access(mos, zapobj, perm, checkflag, cr);
632ecd6cf80Smarks if (error == 0)
633ecd6cf80Smarks goto success;
634ecd6cf80Smarks }
635be6fd75aSMatthew Ahrens error = SET_ERROR(EPERM);
636ecd6cf80Smarks success:
637ecd6cf80Smarks
638ecd6cf80Smarks cookie = NULL;
63991ebeef5Sahrens while ((setnode = avl_destroy_nodes(&permsets, &cookie)) != NULL)
640ecd6cf80Smarks kmem_free(setnode, sizeof (perm_set_t));
641ecd6cf80Smarks
642ecd6cf80Smarks return (error);
643ecd6cf80Smarks }
644ecd6cf80Smarks
645a7f53a56SChris Kirby int
dsl_deleg_access(const char * dsname,const char * perm,cred_t * cr)646a7f53a56SChris Kirby dsl_deleg_access(const char *dsname, const char *perm, cred_t *cr)
647a7f53a56SChris Kirby {
6483b2aab18SMatthew Ahrens dsl_pool_t *dp;
649a7f53a56SChris Kirby dsl_dataset_t *ds;
650a7f53a56SChris Kirby int error;
651a7f53a56SChris Kirby
6523b2aab18SMatthew Ahrens error = dsl_pool_hold(dsname, FTAG, &dp);
6533b2aab18SMatthew Ahrens if (error != 0)
654a7f53a56SChris Kirby return (error);
6553b2aab18SMatthew Ahrens error = dsl_dataset_hold(dp, dsname, FTAG, &ds);
6563b2aab18SMatthew Ahrens if (error == 0) {
6574445fffbSMatthew Ahrens error = dsl_deleg_access_impl(ds, perm, cr);
658a7f53a56SChris Kirby dsl_dataset_rele(ds, FTAG);
6593b2aab18SMatthew Ahrens }
6603b2aab18SMatthew Ahrens dsl_pool_rele(dp, FTAG);
661a7f53a56SChris Kirby
662a7f53a56SChris Kirby return (error);
663a7f53a56SChris Kirby }
664a7f53a56SChris Kirby
665ecd6cf80Smarks /*
666ecd6cf80Smarks * Other routines.
667ecd6cf80Smarks */
668ecd6cf80Smarks
669ecd6cf80Smarks static void
copy_create_perms(dsl_dir_t * dd,uint64_t pzapobj,boolean_t dosets,uint64_t uid,dmu_tx_t * tx)67091ebeef5Sahrens copy_create_perms(dsl_dir_t *dd, uint64_t pzapobj,
671ecd6cf80Smarks boolean_t dosets, uint64_t uid, dmu_tx_t *tx)
672ecd6cf80Smarks {
67391ebeef5Sahrens objset_t *mos = dd->dd_pool->dp_meta_objset;
674ecd6cf80Smarks uint64_t jumpobj, pjumpobj;
675c1379625SJustin T. Gibbs uint64_t zapobj = dsl_dir_phys(dd)->dd_deleg_zapobj;
676ecd6cf80Smarks zap_cursor_t zc;
677ecd6cf80Smarks zap_attribute_t za;
678ecd6cf80Smarks char whokey[ZFS_MAX_DELEG_NAME];
679ecd6cf80Smarks
680ecd6cf80Smarks zfs_deleg_whokey(whokey,
681ecd6cf80Smarks dosets ? ZFS_DELEG_CREATE_SETS : ZFS_DELEG_CREATE,
682ecd6cf80Smarks ZFS_DELEG_LOCAL, NULL);
68391ebeef5Sahrens if (zap_lookup(mos, pzapobj, whokey, 8, 1, &pjumpobj) != 0)
684ecd6cf80Smarks return;
685ecd6cf80Smarks
686ecd6cf80Smarks if (zapobj == 0) {
687ecd6cf80Smarks dmu_buf_will_dirty(dd->dd_dbuf, tx);
688c1379625SJustin T. Gibbs zapobj = dsl_dir_phys(dd)->dd_deleg_zapobj = zap_create(mos,
689ecd6cf80Smarks DMU_OT_DSL_PERMS, DMU_OT_NONE, 0, tx);
690ecd6cf80Smarks }
691ecd6cf80Smarks
69291ebeef5Sahrens zfs_deleg_whokey(whokey,
69391ebeef5Sahrens dosets ? ZFS_DELEG_USER_SETS : ZFS_DELEG_USER,
69491ebeef5Sahrens ZFS_DELEG_LOCAL, &uid);
695ecd6cf80Smarks if (zap_lookup(mos, zapobj, whokey, 8, 1, &jumpobj) == ENOENT) {
696ecd6cf80Smarks jumpobj = zap_create(mos, DMU_OT_DSL_PERMS, DMU_OT_NONE, 0, tx);
697ecd6cf80Smarks VERIFY(zap_add(mos, zapobj, whokey, 8, 1, &jumpobj, tx) == 0);
698ecd6cf80Smarks }
699ecd6cf80Smarks
700ecd6cf80Smarks for (zap_cursor_init(&zc, mos, pjumpobj);
701ecd6cf80Smarks zap_cursor_retrieve(&zc, &za) == 0;
702ecd6cf80Smarks zap_cursor_advance(&zc)) {
70391ebeef5Sahrens uint64_t zero = 0;
704ecd6cf80Smarks ASSERT(za.za_integer_length == 8 && za.za_num_integers == 1);
705ecd6cf80Smarks
706ecd6cf80Smarks VERIFY(zap_update(mos, jumpobj, za.za_name,
707ecd6cf80Smarks 8, 1, &zero, tx) == 0);
708ecd6cf80Smarks }
709ecd6cf80Smarks zap_cursor_fini(&zc);
710ecd6cf80Smarks }
711ecd6cf80Smarks
712ecd6cf80Smarks /*
713ecd6cf80Smarks * set all create time permission on new dataset.
714ecd6cf80Smarks */
715ecd6cf80Smarks void
dsl_deleg_set_create_perms(dsl_dir_t * sdd,dmu_tx_t * tx,cred_t * cr)716ecd6cf80Smarks dsl_deleg_set_create_perms(dsl_dir_t *sdd, dmu_tx_t *tx, cred_t *cr)
717ecd6cf80Smarks {
718ecd6cf80Smarks dsl_dir_t *dd;
71991ebeef5Sahrens uint64_t uid = crgetuid(cr);
720ecd6cf80Smarks
721ecd6cf80Smarks if (spa_version(dmu_objset_spa(sdd->dd_pool->dp_meta_objset)) <
722990b4856Slling SPA_VERSION_DELEGATED_PERMS)
723ecd6cf80Smarks return;
724ecd6cf80Smarks
725ecd6cf80Smarks for (dd = sdd->dd_parent; dd != NULL; dd = dd->dd_parent) {
726c1379625SJustin T. Gibbs uint64_t pzapobj = dsl_dir_phys(dd)->dd_deleg_zapobj;
727ecd6cf80Smarks
72891ebeef5Sahrens if (pzapobj == 0)
729ecd6cf80Smarks continue;
730ecd6cf80Smarks
73191ebeef5Sahrens copy_create_perms(sdd, pzapobj, B_FALSE, uid, tx);
73291ebeef5Sahrens copy_create_perms(sdd, pzapobj, B_TRUE, uid, tx);
733ecd6cf80Smarks }
734ecd6cf80Smarks }
735ecd6cf80Smarks
736ecd6cf80Smarks int
dsl_deleg_destroy(objset_t * mos,uint64_t zapobj,dmu_tx_t * tx)737ecd6cf80Smarks dsl_deleg_destroy(objset_t *mos, uint64_t zapobj, dmu_tx_t *tx)
738ecd6cf80Smarks {
739ecd6cf80Smarks zap_cursor_t zc;
740ecd6cf80Smarks zap_attribute_t za;
741ecd6cf80Smarks
742ecd6cf80Smarks if (zapobj == 0)
743ecd6cf80Smarks return (0);
744ecd6cf80Smarks
745ecd6cf80Smarks for (zap_cursor_init(&zc, mos, zapobj);
746ecd6cf80Smarks zap_cursor_retrieve(&zc, &za) == 0;
747ecd6cf80Smarks zap_cursor_advance(&zc)) {
748ecd6cf80Smarks ASSERT(za.za_integer_length == 8 && za.za_num_integers == 1);
749ecd6cf80Smarks VERIFY(0 == zap_destroy(mos, za.za_first_integer, tx));
750ecd6cf80Smarks }
751ecd6cf80Smarks zap_cursor_fini(&zc);
752ecd6cf80Smarks VERIFY(0 == zap_destroy(mos, zapobj, tx));
753ecd6cf80Smarks return (0);
754ecd6cf80Smarks }
755ecd6cf80Smarks
756ecd6cf80Smarks boolean_t
dsl_delegation_on(objset_t * os)757ecd6cf80Smarks dsl_delegation_on(objset_t *os)
758ecd6cf80Smarks {
759b24ab676SJeff Bonwick return (!!spa_delegation(os->os_spa));
760ecd6cf80Smarks }
761