xref: /freebsd/sys/contrib/openzfs/module/zfs/zcp_iter.c (revision 61145dc2b94f12f6a47344fb9aac702321880e43)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * CDDL HEADER START
4  *
5  * This file and its contents are supplied under the terms of the
6  * Common Development and Distribution License ("CDDL"), version 1.0.
7  * You may only use this file in accordance with the terms of version
8  * 1.0 of the CDDL.
9  *
10  * A full copy of the text of the CDDL should have accompanied this
11  * source.  A copy of the CDDL is also available via the Internet at
12  * http://www.illumos.org/license/CDDL.
13  *
14  * CDDL HEADER END
15  */
16 
17 /*
18  * Copyright (c) 2016, 2018 by Delphix. All rights reserved.
19  */
20 
21 #include <sys/lua/lua.h>
22 #include <sys/lua/lauxlib.h>
23 
24 #include <sys/dmu.h>
25 #include <sys/dsl_prop.h>
26 #include <sys/dsl_synctask.h>
27 #include <sys/dsl_bookmark.h>
28 #include <sys/dsl_dataset.h>
29 #include <sys/dsl_pool.h>
30 #include <sys/dmu_tx.h>
31 #include <sys/dmu_objset.h>
32 #include <sys/zap.h>
33 #include <sys/dsl_dir.h>
34 #include <sys/zcp_prop.h>
35 
36 #include <sys/zcp.h>
37 
38 #include "zfs_comutil.h"
39 
40 typedef int (zcp_list_func_t)(lua_State *);
41 typedef struct zcp_list_info {
42 	const char *name;
43 	zcp_list_func_t *func;
44 	zcp_list_func_t *gc;
45 	const zcp_arg_t pargs[4];
46 	const zcp_arg_t kwargs[2];
47 } zcp_list_info_t;
48 
49 static int
zcp_clones_iter(lua_State * state)50 zcp_clones_iter(lua_State *state)
51 {
52 	int err;
53 	char clonename[ZFS_MAX_DATASET_NAME_LEN];
54 	uint64_t dsobj = lua_tonumber(state, lua_upvalueindex(1));
55 	uint64_t cursor = lua_tonumber(state, lua_upvalueindex(2));
56 	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
57 	dsl_dataset_t *ds, *clone;
58 	zap_attribute_t *za;
59 	zap_cursor_t zc;
60 
61 	err = dsl_dataset_hold_obj(dp, dsobj, FTAG, &ds);
62 	if (err == ENOENT) {
63 		return (0);
64 	} else if (err != 0) {
65 		return (luaL_error(state,
66 		    "unexpected error %d from dsl_dataset_hold_obj(dsobj)",
67 		    err));
68 	}
69 
70 	if (dsl_dataset_phys(ds)->ds_next_clones_obj == 0) {
71 		dsl_dataset_rele(ds, FTAG);
72 		return (0);
73 	}
74 
75 	zap_cursor_init_serialized(&zc, dp->dp_meta_objset,
76 	    dsl_dataset_phys(ds)->ds_next_clones_obj, cursor);
77 	dsl_dataset_rele(ds, FTAG);
78 
79 	za = zap_attribute_alloc();
80 	err = zap_cursor_retrieve(&zc, za);
81 	if (err != 0) {
82 		zap_cursor_fini(&zc);
83 		zap_attribute_free(za);
84 		if (err != ENOENT) {
85 			return (luaL_error(state,
86 			    "unexpected error %d from zap_cursor_retrieve()",
87 			    err));
88 		}
89 		return (0);
90 	}
91 	zap_cursor_advance(&zc);
92 	cursor = zap_cursor_serialize(&zc);
93 	zap_cursor_fini(&zc);
94 
95 	err = dsl_dataset_hold_obj(dp, za->za_first_integer, FTAG, &clone);
96 	zap_attribute_free(za);
97 	if (err != 0) {
98 		return (luaL_error(state,
99 		    "unexpected error %d from "
100 		    "dsl_dataset_hold_obj(za_first_integer)", err));
101 	}
102 
103 	dsl_dir_name(clone->ds_dir, clonename);
104 	dsl_dataset_rele(clone, FTAG);
105 
106 	lua_pushnumber(state, cursor);
107 	lua_replace(state, lua_upvalueindex(2));
108 
109 	(void) lua_pushstring(state, clonename);
110 	return (1);
111 }
112 
113 static int zcp_clones_list(lua_State *);
114 static const zcp_list_info_t zcp_clones_list_info = {
115 	.name = "clones",
116 	.func = zcp_clones_list,
117 	.gc = NULL,
118 	.pargs = {
119 	    { .za_name = "snapshot", .za_lua_type = LUA_TSTRING },
120 	    {NULL, 0}
121 	},
122 	.kwargs = {
123 	    {NULL, 0}
124 	}
125 };
126 
127 static int
zcp_clones_list(lua_State * state)128 zcp_clones_list(lua_State *state)
129 {
130 	const char *snapname = lua_tostring(state, 1);
131 	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
132 
133 	/*
134 	 * zcp_dataset_hold will either successfully return the requested
135 	 * dataset or throw a lua error and longjmp out of the zfs.list.clones
136 	 * call without returning.
137 	 */
138 	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, snapname, FTAG);
139 	if (ds == NULL)
140 		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
141 	boolean_t issnap = ds->ds_is_snapshot;
142 	uint64_t cursor = 0;
143 	uint64_t dsobj = ds->ds_object;
144 	dsl_dataset_rele(ds, FTAG);
145 
146 	if (!issnap) {
147 		return (zcp_argerror(state, 1, "%s is not a snapshot",
148 		    snapname));
149 	}
150 
151 	lua_pushnumber(state, dsobj);
152 	lua_pushnumber(state, cursor);
153 	lua_pushcclosure(state, &zcp_clones_iter, 2);
154 	return (1);
155 }
156 
157 static int
zcp_snapshots_iter(lua_State * state)158 zcp_snapshots_iter(lua_State *state)
159 {
160 	int err;
161 	char snapname[ZFS_MAX_DATASET_NAME_LEN];
162 	uint64_t dsobj = lua_tonumber(state, lua_upvalueindex(1));
163 	uint64_t cursor = lua_tonumber(state, lua_upvalueindex(2));
164 	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
165 	dsl_dataset_t *ds;
166 	objset_t *os;
167 	char *p;
168 
169 	err = dsl_dataset_hold_obj(dp, dsobj, FTAG, &ds);
170 	if (err != 0) {
171 		return (luaL_error(state,
172 		    "unexpected error %d from dsl_dataset_hold_obj(dsobj)",
173 		    err));
174 	}
175 
176 	dsl_dataset_name(ds, snapname);
177 	VERIFY3U(sizeof (snapname), >,
178 	    strlcat(snapname, "@", sizeof (snapname)));
179 
180 	p = strchr(snapname, '\0');
181 	VERIFY0(dmu_objset_from_ds(ds, &os));
182 	err = dmu_snapshot_list_next(os,
183 	    sizeof (snapname) - (p - snapname), p, NULL, &cursor, NULL);
184 	dsl_dataset_rele(ds, FTAG);
185 
186 	if (err == ENOENT) {
187 		return (0);
188 	} else if (err != 0) {
189 		return (luaL_error(state,
190 		    "unexpected error %d from dmu_snapshot_list_next()", err));
191 	}
192 
193 	lua_pushnumber(state, cursor);
194 	lua_replace(state, lua_upvalueindex(2));
195 
196 	(void) lua_pushstring(state, snapname);
197 	return (1);
198 }
199 
200 static int zcp_snapshots_list(lua_State *);
201 static const zcp_list_info_t zcp_snapshots_list_info = {
202 	.name = "snapshots",
203 	.func = zcp_snapshots_list,
204 	.gc = NULL,
205 	.pargs = {
206 	    { .za_name = "filesystem | volume", .za_lua_type = LUA_TSTRING },
207 	    {NULL, 0}
208 	},
209 	.kwargs = {
210 	    {NULL, 0}
211 	}
212 };
213 
214 static int
zcp_snapshots_list(lua_State * state)215 zcp_snapshots_list(lua_State *state)
216 {
217 	const char *fsname = lua_tostring(state, 1);
218 	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
219 	boolean_t issnap;
220 	uint64_t dsobj;
221 
222 	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, fsname, FTAG);
223 	if (ds == NULL)
224 		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
225 	issnap = ds->ds_is_snapshot;
226 	dsobj = ds->ds_object;
227 	dsl_dataset_rele(ds, FTAG);
228 
229 	if (issnap) {
230 		return (zcp_argerror(state, 1,
231 		    "argument %s cannot be a snapshot", fsname));
232 	}
233 
234 	lua_pushnumber(state, dsobj);
235 	lua_pushnumber(state, 0);
236 	lua_pushcclosure(state, &zcp_snapshots_iter, 2);
237 	return (1);
238 }
239 
240 static int
zcp_children_iter(lua_State * state)241 zcp_children_iter(lua_State *state)
242 {
243 	int err;
244 	char childname[ZFS_MAX_DATASET_NAME_LEN];
245 	uint64_t dsobj = lua_tonumber(state, lua_upvalueindex(1));
246 	uint64_t cursor = lua_tonumber(state, lua_upvalueindex(2));
247 	zcp_run_info_t *ri = zcp_run_info(state);
248 	dsl_pool_t *dp = ri->zri_pool;
249 	dsl_dataset_t *ds;
250 	objset_t *os;
251 	char *p;
252 
253 	err = dsl_dataset_hold_obj(dp, dsobj, FTAG, &ds);
254 	if (err != 0) {
255 		return (luaL_error(state,
256 		    "unexpected error %d from dsl_dataset_hold_obj(dsobj)",
257 		    err));
258 	}
259 
260 	dsl_dataset_name(ds, childname);
261 	VERIFY3U(sizeof (childname), >,
262 	    strlcat(childname, "/", sizeof (childname)));
263 	p = strchr(childname, '\0');
264 
265 	VERIFY0(dmu_objset_from_ds(ds, &os));
266 	do {
267 		err = dmu_dir_list_next(os,
268 		    sizeof (childname) - (p - childname), p, NULL, &cursor);
269 	} while (err == 0 && zfs_dataset_name_hidden(childname));
270 	dsl_dataset_rele(ds, FTAG);
271 
272 	if (err == ENOENT) {
273 		return (0);
274 	} else if (err != 0) {
275 		return (luaL_error(state,
276 		    "unexpected error %d from dmu_dir_list_next()",
277 		    err));
278 	}
279 
280 	lua_pushnumber(state, cursor);
281 	lua_replace(state, lua_upvalueindex(2));
282 
283 	(void) lua_pushstring(state, childname);
284 	return (1);
285 }
286 
287 static int zcp_children_list(lua_State *);
288 static const zcp_list_info_t zcp_children_list_info = {
289 	.name = "children",
290 	.func = zcp_children_list,
291 	.gc = NULL,
292 	.pargs = {
293 	    { .za_name = "filesystem | volume", .za_lua_type = LUA_TSTRING },
294 	    {NULL, 0}
295 	},
296 	.kwargs = {
297 	    {NULL, 0}
298 	}
299 };
300 
301 static int
zcp_children_list(lua_State * state)302 zcp_children_list(lua_State *state)
303 {
304 	const char *fsname = lua_tostring(state, 1);
305 	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
306 	boolean_t issnap;
307 	uint64_t dsobj;
308 
309 	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, fsname, FTAG);
310 	if (ds == NULL)
311 		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
312 
313 	issnap = ds->ds_is_snapshot;
314 	dsobj = ds->ds_object;
315 	dsl_dataset_rele(ds, FTAG);
316 
317 	if (issnap) {
318 		return (zcp_argerror(state, 1,
319 		    "argument %s cannot be a snapshot", fsname));
320 	}
321 
322 	lua_pushnumber(state, dsobj);
323 	lua_pushnumber(state, 0);
324 	lua_pushcclosure(state, &zcp_children_iter, 2);
325 	return (1);
326 }
327 
328 static int
zcp_user_props_list_gc(lua_State * state)329 zcp_user_props_list_gc(lua_State *state)
330 {
331 	nvlist_t **props = lua_touserdata(state, 1);
332 	if (*props != NULL)
333 		fnvlist_free(*props);
334 	return (0);
335 }
336 
337 static int
zcp_user_props_iter(lua_State * state)338 zcp_user_props_iter(lua_State *state)
339 {
340 	const char *source, *val;
341 	nvlist_t *nvprop;
342 	nvlist_t **props = lua_touserdata(state, lua_upvalueindex(1));
343 	nvpair_t *pair = lua_touserdata(state, lua_upvalueindex(2));
344 
345 	do {
346 		pair = nvlist_next_nvpair(*props, pair);
347 		if (pair == NULL) {
348 			fnvlist_free(*props);
349 			*props = NULL;
350 			return (0);
351 		}
352 	} while (!zfs_prop_user(nvpair_name(pair)));
353 
354 	lua_pushlightuserdata(state, pair);
355 	lua_replace(state, lua_upvalueindex(2));
356 
357 	nvprop = fnvpair_value_nvlist(pair);
358 	val = fnvlist_lookup_string(nvprop, ZPROP_VALUE);
359 	source = fnvlist_lookup_string(nvprop, ZPROP_SOURCE);
360 
361 	(void) lua_pushstring(state, nvpair_name(pair));
362 	(void) lua_pushstring(state, val);
363 	(void) lua_pushstring(state, source);
364 	return (3);
365 }
366 
367 static int zcp_user_props_list(lua_State *);
368 static const zcp_list_info_t zcp_user_props_list_info = {
369 	.name = "user_properties",
370 	.func = zcp_user_props_list,
371 	.gc = zcp_user_props_list_gc,
372 	.pargs = {
373 	    { .za_name = "filesystem | snapshot | volume",
374 	    .za_lua_type = LUA_TSTRING },
375 	    {NULL, 0}
376 	},
377 	.kwargs = {
378 	    {NULL, 0}
379 	}
380 };
381 
382 /*
383  * 'properties' was the initial name for 'user_properties' seen
384  * above. 'user_properties' is a better name as it distinguishes
385  * these properties from 'system_properties' which are different.
386  * In order to avoid breaking compatibility between different
387  * versions of ZFS, we declare 'properties' as an alias for
388  * 'user_properties'.
389  */
390 static const zcp_list_info_t zcp_props_list_info = {
391 	.name = "properties",
392 	.func = zcp_user_props_list,
393 	.gc = zcp_user_props_list_gc,
394 	.pargs = {
395 	    { .za_name = "filesystem | snapshot | volume",
396 	    .za_lua_type = LUA_TSTRING },
397 	    {NULL, 0}
398 	},
399 	.kwargs = {
400 	    {NULL, 0}
401 	}
402 };
403 
404 static int
zcp_user_props_list(lua_State * state)405 zcp_user_props_list(lua_State *state)
406 {
407 	const char *dsname = lua_tostring(state, 1);
408 	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
409 	objset_t *os;
410 	nvlist_t **props = lua_newuserdata(state, sizeof (nvlist_t *));
411 
412 	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, dsname, FTAG);
413 	if (ds == NULL)
414 		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
415 	VERIFY0(dmu_objset_from_ds(ds, &os));
416 	VERIFY0(dsl_prop_get_all(os, props));
417 	dsl_dataset_rele(ds, FTAG);
418 
419 	/*
420 	 * Set the metatable for the properties list to free it on
421 	 * completion.
422 	 */
423 	luaL_getmetatable(state, zcp_user_props_list_info.name);
424 	(void) lua_setmetatable(state, -2);
425 
426 	lua_pushlightuserdata(state, NULL);
427 	lua_pushcclosure(state, &zcp_user_props_iter, 2);
428 	return (1);
429 }
430 
431 
432 /*
433  * Populate nv with all valid system properties and their values for the given
434  * dataset.
435  */
436 static void
zcp_dataset_system_props(dsl_dataset_t * ds,nvlist_t * nv)437 zcp_dataset_system_props(dsl_dataset_t *ds, nvlist_t *nv)
438 {
439 	for (int prop = ZFS_PROP_TYPE; prop < ZFS_NUM_PROPS; prop++) {
440 		/* Do not display hidden props */
441 		if (!zfs_prop_visible(prop))
442 			continue;
443 		/* Do not display props not valid for this dataset */
444 		if (!prop_valid_for_ds(ds, prop))
445 			continue;
446 		fnvlist_add_boolean(nv, zfs_prop_to_name(prop));
447 	}
448 }
449 
450 static int zcp_system_props_list(lua_State *);
451 static const zcp_list_info_t zcp_system_props_list_info = {
452 	.name = "system_properties",
453 	.func = zcp_system_props_list,
454 	.pargs = {
455 	    { .za_name = "dataset", .za_lua_type = LUA_TSTRING },
456 	    {NULL, 0}
457 	},
458 	.kwargs = {
459 	    {NULL, 0}
460 	}
461 };
462 
463 /*
464  * Get a list of all visible system properties and their values for a given
465  * dataset. Returned on the stack as a Lua table.
466  */
467 static int
zcp_system_props_list(lua_State * state)468 zcp_system_props_list(lua_State *state)
469 {
470 	int error;
471 	char errbuf[128];
472 	const char *dataset_name;
473 	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
474 	const zcp_list_info_t *libinfo = &zcp_system_props_list_info;
475 	zcp_parse_args(state, libinfo->name, libinfo->pargs, libinfo->kwargs);
476 	dataset_name = lua_tostring(state, 1);
477 	nvlist_t *nv = fnvlist_alloc();
478 
479 	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, dataset_name, FTAG);
480 	if (ds == NULL)
481 		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
482 
483 	/* Get the names of all valid system properties for this dataset */
484 	zcp_dataset_system_props(ds, nv);
485 	dsl_dataset_rele(ds, FTAG);
486 
487 	/* push list as lua table */
488 	error = zcp_nvlist_to_lua(state, nv, errbuf, sizeof (errbuf));
489 	nvlist_free(nv);
490 	if (error != 0) {
491 		return (luaL_error(state,
492 		    "Error returning nvlist: %s", errbuf));
493 	}
494 	return (1);
495 }
496 
497 static int
zcp_bookmarks_iter(lua_State * state)498 zcp_bookmarks_iter(lua_State *state)
499 {
500 	char ds_name[ZFS_MAX_DATASET_NAME_LEN];
501 	char bookmark_name[ZFS_MAX_DATASET_NAME_LEN];
502 	uint64_t dsobj = lua_tonumber(state, lua_upvalueindex(1));
503 	uint64_t cursor = lua_tonumber(state, lua_upvalueindex(2));
504 	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
505 	dsl_dataset_t *ds;
506 	zap_attribute_t *za;
507 	zap_cursor_t zc;
508 
509 	int err = dsl_dataset_hold_obj(dp, dsobj, FTAG, &ds);
510 	if (err == ENOENT) {
511 		return (0);
512 	} else if (err != 0) {
513 		return (luaL_error(state,
514 		    "unexpected error %d from dsl_dataset_hold_obj(dsobj)",
515 		    err));
516 	}
517 
518 	if (!dsl_dataset_is_zapified(ds)) {
519 		dsl_dataset_rele(ds, FTAG);
520 		return (0);
521 	}
522 
523 	err = zap_lookup(dp->dp_meta_objset, ds->ds_object,
524 	    DS_FIELD_BOOKMARK_NAMES, sizeof (ds->ds_bookmarks_obj), 1,
525 	    &ds->ds_bookmarks_obj);
526 	if (err != 0 && err != ENOENT) {
527 		dsl_dataset_rele(ds, FTAG);
528 		return (luaL_error(state,
529 		    "unexpected error %d from zap_lookup()", err));
530 	}
531 	if (ds->ds_bookmarks_obj == 0) {
532 		dsl_dataset_rele(ds, FTAG);
533 		return (0);
534 	}
535 
536 	/* Store the dataset's name so we can append the bookmark's name */
537 	dsl_dataset_name(ds, ds_name);
538 
539 	zap_cursor_init_serialized(&zc, ds->ds_dir->dd_pool->dp_meta_objset,
540 	    ds->ds_bookmarks_obj, cursor);
541 	dsl_dataset_rele(ds, FTAG);
542 
543 	za = zap_attribute_alloc();
544 	err = zap_cursor_retrieve(&zc, za);
545 	if (err != 0) {
546 		zap_cursor_fini(&zc);
547 		zap_attribute_free(za);
548 		if (err != ENOENT) {
549 			return (luaL_error(state,
550 			    "unexpected error %d from zap_cursor_retrieve()",
551 			    err));
552 		}
553 		return (0);
554 	}
555 	zap_cursor_advance(&zc);
556 	cursor = zap_cursor_serialize(&zc);
557 	zap_cursor_fini(&zc);
558 
559 	/* Create the full "pool/fs#bookmark" string to return */
560 	int n = snprintf(bookmark_name, ZFS_MAX_DATASET_NAME_LEN, "%s#%s",
561 	    ds_name, za->za_name);
562 	zap_attribute_free(za);
563 	if (n >= ZFS_MAX_DATASET_NAME_LEN) {
564 		return (luaL_error(state,
565 		    "unexpected error %d from snprintf()", ENAMETOOLONG));
566 	}
567 
568 	lua_pushnumber(state, cursor);
569 	lua_replace(state, lua_upvalueindex(2));
570 
571 	(void) lua_pushstring(state, bookmark_name);
572 	return (1);
573 }
574 
575 static int zcp_bookmarks_list(lua_State *);
576 static const zcp_list_info_t zcp_bookmarks_list_info = {
577 	.name = "bookmarks",
578 	.func = zcp_bookmarks_list,
579 	.pargs = {
580 	    { .za_name = "dataset", .za_lua_type = LUA_TSTRING },
581 	    {NULL, 0}
582 	},
583 	.kwargs = {
584 	    {NULL, 0}
585 	}
586 };
587 
588 static int
zcp_bookmarks_list(lua_State * state)589 zcp_bookmarks_list(lua_State *state)
590 {
591 	const char *dsname = lua_tostring(state, 1);
592 	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
593 
594 	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, dsname, FTAG);
595 	if (ds == NULL)
596 		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
597 
598 	boolean_t issnap = ds->ds_is_snapshot;
599 	uint64_t dsobj = ds->ds_object;
600 	uint64_t cursor = 0;
601 	dsl_dataset_rele(ds, FTAG);
602 
603 	if (issnap) {
604 		return (zcp_argerror(state, 1, "%s is a snapshot", dsname));
605 	}
606 
607 	lua_pushnumber(state, dsobj);
608 	lua_pushnumber(state, cursor);
609 	lua_pushcclosure(state, &zcp_bookmarks_iter, 2);
610 	return (1);
611 }
612 
613 static int
zcp_holds_iter(lua_State * state)614 zcp_holds_iter(lua_State *state)
615 {
616 	uint64_t dsobj = lua_tonumber(state, lua_upvalueindex(1));
617 	uint64_t cursor = lua_tonumber(state, lua_upvalueindex(2));
618 	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
619 	dsl_dataset_t *ds;
620 	zap_attribute_t *za;
621 	zap_cursor_t zc;
622 
623 	int err = dsl_dataset_hold_obj(dp, dsobj, FTAG, &ds);
624 	if (err == ENOENT) {
625 		return (0);
626 	} else if (err != 0) {
627 		return (luaL_error(state,
628 		    "unexpected error %d from dsl_dataset_hold_obj(dsobj)",
629 		    err));
630 	}
631 
632 	if (dsl_dataset_phys(ds)->ds_userrefs_obj == 0) {
633 		dsl_dataset_rele(ds, FTAG);
634 		return (0);
635 	}
636 
637 	zap_cursor_init_serialized(&zc, ds->ds_dir->dd_pool->dp_meta_objset,
638 	    dsl_dataset_phys(ds)->ds_userrefs_obj, cursor);
639 	dsl_dataset_rele(ds, FTAG);
640 
641 	za = zap_attribute_alloc();
642 	err = zap_cursor_retrieve(&zc, za);
643 	if (err != 0) {
644 		zap_cursor_fini(&zc);
645 		zap_attribute_free(za);
646 		if (err != ENOENT) {
647 			return (luaL_error(state,
648 			    "unexpected error %d from zap_cursor_retrieve()",
649 			    err));
650 		}
651 		return (0);
652 	}
653 	zap_cursor_advance(&zc);
654 	cursor = zap_cursor_serialize(&zc);
655 	zap_cursor_fini(&zc);
656 
657 	lua_pushnumber(state, cursor);
658 	lua_replace(state, lua_upvalueindex(2));
659 
660 	(void) lua_pushstring(state, za->za_name);
661 	(void) lua_pushnumber(state, za->za_first_integer);
662 	zap_attribute_free(za);
663 	return (2);
664 }
665 
666 static int zcp_holds_list(lua_State *);
667 static const zcp_list_info_t zcp_holds_list_info = {
668 	.name = "holds",
669 	.func = zcp_holds_list,
670 	.gc = NULL,
671 	.pargs = {
672 	    { .za_name = "snapshot", .za_lua_type = LUA_TSTRING },
673 	    {NULL, 0}
674 	},
675 	.kwargs = {
676 	    {NULL, 0}
677 	}
678 };
679 
680 /*
681  * Iterate over all the holds for a given dataset. Each iteration returns
682  * a hold's tag and its timestamp as an integer.
683  */
684 static int
zcp_holds_list(lua_State * state)685 zcp_holds_list(lua_State *state)
686 {
687 	const char *snapname = lua_tostring(state, 1);
688 	dsl_pool_t *dp = zcp_run_info(state)->zri_pool;
689 
690 	dsl_dataset_t *ds = zcp_dataset_hold(state, dp, snapname, FTAG);
691 	if (ds == NULL)
692 		return (1); /* not reached; zcp_dataset_hold() longjmp'd */
693 
694 	boolean_t issnap = ds->ds_is_snapshot;
695 	uint64_t dsobj = ds->ds_object;
696 	uint64_t cursor = 0;
697 	dsl_dataset_rele(ds, FTAG);
698 
699 	if (!issnap) {
700 		return (zcp_argerror(state, 1, "%s is not a snapshot",
701 		    snapname));
702 	}
703 
704 	lua_pushnumber(state, dsobj);
705 	lua_pushnumber(state, cursor);
706 	lua_pushcclosure(state, &zcp_holds_iter, 2);
707 	return (1);
708 }
709 
710 static int
zcp_list_func(lua_State * state)711 zcp_list_func(lua_State *state)
712 {
713 	zcp_list_info_t *info = lua_touserdata(state, lua_upvalueindex(1));
714 
715 	zcp_parse_args(state, info->name, info->pargs, info->kwargs);
716 
717 	return (info->func(state));
718 }
719 
720 int
zcp_load_list_lib(lua_State * state)721 zcp_load_list_lib(lua_State *state)
722 {
723 	const zcp_list_info_t *zcp_list_funcs[] = {
724 		&zcp_children_list_info,
725 		&zcp_snapshots_list_info,
726 		&zcp_user_props_list_info,
727 		&zcp_props_list_info,
728 		&zcp_clones_list_info,
729 		&zcp_system_props_list_info,
730 		&zcp_bookmarks_list_info,
731 		&zcp_holds_list_info,
732 		NULL
733 	};
734 
735 	lua_newtable(state);
736 
737 	for (int i = 0; zcp_list_funcs[i] != NULL; i++) {
738 		const zcp_list_info_t *info = zcp_list_funcs[i];
739 
740 		if (info->gc != NULL) {
741 			/*
742 			 * If the function requires garbage collection, create
743 			 * a metatable with its name and register the __gc
744 			 * function.
745 			 */
746 			(void) luaL_newmetatable(state, info->name);
747 			(void) lua_pushstring(state, "__gc");
748 			lua_pushcfunction(state, info->gc);
749 			lua_settable(state, -3);
750 			lua_pop(state, 1);
751 		}
752 
753 		lua_pushlightuserdata(state, (void *)(uintptr_t)info);
754 		lua_pushcclosure(state, &zcp_list_func, 1);
755 		lua_setfield(state, -2, info->name);
756 	}
757 
758 	return (1);
759 }
760