xref: /freebsd/sys/contrib/openzfs/module/zfs/zcp_synctask.c (revision 3a8960711f4319f9b894ea2453c89065ee1b3a10)
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, 2017 by Delphix. All rights reserved.
19  * Copyright (c) 2019, 2020 by Christian Schwarz. All rights reserved.
20  * Copyright 2020 Joyent, Inc.
21  * Copyright (c) 2025, Rob Norris <robn@despairlabs.com>
22  */
23 
24 #include <sys/lua/lua.h>
25 #include <sys/lua/lauxlib.h>
26 
27 #include <sys/zcp.h>
28 #include <sys/zcp_set.h>
29 #include <sys/dsl_dir.h>
30 #include <sys/dsl_pool.h>
31 #include <sys/dsl_prop.h>
32 #include <sys/dsl_synctask.h>
33 #include <sys/dsl_dataset.h>
34 #include <sys/dsl_bookmark.h>
35 #include <sys/dsl_destroy.h>
36 #include <sys/dmu_objset.h>
37 #include <sys/zfs_znode.h>
38 #include <sys/zfeature.h>
39 #include <sys/metaslab.h>
40 
41 #define	DST_AVG_BLKSHIFT 14
42 
43 typedef struct zcp_inherit_prop_arg {
44 	lua_State		*zipa_state;
45 	const char		*zipa_prop;
46 	dsl_props_set_arg_t	zipa_dpsa;
47 } zcp_inherit_prop_arg_t;
48 
49 typedef int (zcp_synctask_func_t)(lua_State *, boolean_t, nvlist_t *);
50 typedef struct zcp_synctask_info {
51 	const char *name;
52 	zcp_synctask_func_t *func;
53 	const zcp_arg_t pargs[4];
54 	const zcp_arg_t kwargs[2];
55 	zfs_space_check_t space_check;
56 	int blocks_modified;
57 } zcp_synctask_info_t;
58 
59 static void
zcp_synctask_cleanup(void * arg)60 zcp_synctask_cleanup(void *arg)
61 {
62 	fnvlist_free(arg);
63 }
64 
65 /*
66  * Generic synctask interface for channel program syncfuncs.
67  *
68  * To perform some action in syncing context, we'd generally call
69  * dsl_sync_task(), but since the Lua script is already running inside a
70  * synctask we need to leave out some actions (such as acquiring the config
71  * rwlock and performing space checks).
72  *
73  * If 'sync' is false, executes a dry run and returns the error code.
74  *
75  * If we are not running in syncing context and we are not doing a dry run
76  * (meaning we are running a zfs.sync function in open-context) then we
77  * return a Lua error.
78  *
79  * This function also handles common fatal error cases for channel program
80  * library functions. If a fatal error occurs, err_dsname will be the dataset
81  * name reported in error messages, if supplied.
82  */
83 static int
zcp_sync_task(lua_State * state,dsl_checkfunc_t * checkfunc,dsl_syncfunc_t * syncfunc,void * arg,boolean_t sync,const char * err_dsname)84 zcp_sync_task(lua_State *state, dsl_checkfunc_t *checkfunc,
85     dsl_syncfunc_t *syncfunc, void *arg, boolean_t sync, const char *err_dsname)
86 {
87 	int err;
88 	zcp_run_info_t *ri = zcp_run_info(state);
89 
90 	err = checkfunc(arg, ri->zri_tx);
91 	if (!sync)
92 		return (err);
93 
94 	if (!ri->zri_sync) {
95 		return (luaL_error(state, "running functions from the zfs.sync "
96 		    "submodule requires passing sync=TRUE to "
97 		    "lzc_channel_program() (i.e. do not specify the \"-n\" "
98 		    "command line argument)"));
99 	}
100 
101 	if (err == 0) {
102 		syncfunc(arg, ri->zri_tx);
103 	} else if (err == EIO) {
104 		if (err_dsname != NULL) {
105 			return (luaL_error(state,
106 			    "I/O error while accessing dataset '%s'",
107 			    err_dsname));
108 		} else {
109 			return (luaL_error(state,
110 			    "I/O error while accessing dataset."));
111 		}
112 	}
113 
114 	return (err);
115 }
116 
117 static int zcp_synctask_clone(lua_State *, boolean_t, nvlist_t *);
118 static const zcp_synctask_info_t zcp_synctask_clone_info = {
119 	.name = "clone",
120 	.func = zcp_synctask_clone,
121 	.pargs = {
122 	    {.za_name = "snapshot", .za_lua_type = LUA_TSTRING },
123 	    {.za_name = "newdataset", .za_lua_type = LUA_TSTRING },
124 	    {NULL, 0}
125 	},
126 	.kwargs = {
127 	    {NULL, 0}
128 	},
129 	.space_check = ZFS_SPACE_CHECK_NORMAL,
130 	.blocks_modified = 3
131 };
132 static int
zcp_synctask_clone(lua_State * state,boolean_t sync,nvlist_t * err_details)133 zcp_synctask_clone(lua_State *state, boolean_t sync, nvlist_t *err_details)
134 {
135 	(void) err_details;
136 	int err;
137 
138 	zcp_run_info_t *ri = zcp_run_info(state);
139 	dsl_dataset_clone_arg_t ddca = {
140 		.ddca_origin = lua_tostring(state, 1),
141 		.ddca_clone = lua_tostring(state, 2),
142 		.ddca_cred = ri->zri_cred,
143 	};
144 
145 	err = zcp_sync_task(state, dsl_dataset_clone_check,
146 	    dsl_dataset_clone_sync, &ddca, sync, ddca.ddca_origin);
147 
148 	if (err == 0)
149 		/*
150 		 * If the new dataset is a zvol, it will need a device
151 		 * node. Put it on the list to be considered in open context.
152 		 * We don't have to check the type here; if it's not a zvol,
153 		 * or has volmode=none, it will be ignored.
154 		 */
155 		fnvlist_add_boolean(ri->zri_new_zvols, ddca.ddca_clone);
156 
157 	return (err);
158 }
159 
160 static int zcp_synctask_destroy(lua_State *, boolean_t, nvlist_t *);
161 static const zcp_synctask_info_t zcp_synctask_destroy_info = {
162 	.name = "destroy",
163 	.func = zcp_synctask_destroy,
164 	.pargs = {
165 	    {.za_name = "filesystem | snapshot", .za_lua_type = LUA_TSTRING },
166 	    {NULL, 0}
167 	},
168 	.kwargs = {
169 	    {.za_name = "defer", .za_lua_type = LUA_TBOOLEAN },
170 	    {NULL, 0}
171 	},
172 	.space_check = ZFS_SPACE_CHECK_DESTROY,
173 	.blocks_modified = 0
174 };
175 
176 static int
zcp_synctask_destroy(lua_State * state,boolean_t sync,nvlist_t * err_details)177 zcp_synctask_destroy(lua_State *state, boolean_t sync, nvlist_t *err_details)
178 {
179 	(void) err_details;
180 	int err;
181 	const char *dsname = lua_tostring(state, 1);
182 
183 	boolean_t issnap = (strchr(dsname, '@') != NULL);
184 
185 	if (!issnap && !lua_isnil(state, 2)) {
186 		return (luaL_error(state,
187 		    "'deferred' kwarg only supported for snapshots: %s",
188 		    dsname));
189 	}
190 
191 	if (issnap) {
192 		dsl_destroy_snapshot_arg_t ddsa = { 0 };
193 		ddsa.ddsa_name = dsname;
194 		if (!lua_isnil(state, 2)) {
195 			ddsa.ddsa_defer = lua_toboolean(state, 2);
196 		} else {
197 			ddsa.ddsa_defer = B_FALSE;
198 		}
199 
200 		err = zcp_sync_task(state, dsl_destroy_snapshot_check,
201 		    dsl_destroy_snapshot_sync, &ddsa, sync, dsname);
202 	} else {
203 		dsl_destroy_head_arg_t ddha = { 0 };
204 		ddha.ddha_name = dsname;
205 
206 		err = zcp_sync_task(state, dsl_destroy_head_check,
207 		    dsl_destroy_head_sync, &ddha, sync, dsname);
208 	}
209 
210 	return (err);
211 }
212 
213 static int zcp_synctask_promote(lua_State *, boolean_t, nvlist_t *);
214 static const zcp_synctask_info_t zcp_synctask_promote_info = {
215 	.name = "promote",
216 	.func = zcp_synctask_promote,
217 	.pargs = {
218 	    {.za_name = "clone", .za_lua_type = LUA_TSTRING },
219 	    {NULL, 0}
220 	},
221 	.kwargs = {
222 	    {NULL, 0}
223 	},
224 	.space_check = ZFS_SPACE_CHECK_RESERVED,
225 	.blocks_modified = 3
226 };
227 
228 static int
zcp_synctask_promote(lua_State * state,boolean_t sync,nvlist_t * err_details)229 zcp_synctask_promote(lua_State *state, boolean_t sync, nvlist_t *err_details)
230 {
231 	int err;
232 	dsl_dataset_promote_arg_t ddpa = { 0 };
233 	const char *dsname = lua_tostring(state, 1);
234 	zcp_run_info_t *ri = zcp_run_info(state);
235 
236 	ddpa.ddpa_clonename = dsname;
237 	ddpa.err_ds = err_details;
238 	ddpa.cr = ri->zri_cred;
239 
240 	/*
241 	 * If there was a snapshot name conflict, then err_ds will be filled
242 	 * with a list of conflicting snapshot names.
243 	 */
244 	err = zcp_sync_task(state, dsl_dataset_promote_check,
245 	    dsl_dataset_promote_sync, &ddpa, sync, dsname);
246 
247 	return (err);
248 }
249 
250 static int zcp_synctask_rollback(lua_State *, boolean_t, nvlist_t *err_details);
251 static const zcp_synctask_info_t zcp_synctask_rollback_info = {
252 	.name = "rollback",
253 	.func = zcp_synctask_rollback,
254 	.space_check = ZFS_SPACE_CHECK_RESERVED,
255 	.blocks_modified = 1,
256 	.pargs = {
257 	    {.za_name = "filesystem", .za_lua_type = LUA_TSTRING },
258 	    {0, 0}
259 	},
260 	.kwargs = {
261 	    {0, 0}
262 	}
263 };
264 
265 static int
zcp_synctask_rollback(lua_State * state,boolean_t sync,nvlist_t * err_details)266 zcp_synctask_rollback(lua_State *state, boolean_t sync, nvlist_t *err_details)
267 {
268 	int err;
269 	const char *dsname = lua_tostring(state, 1);
270 	dsl_dataset_rollback_arg_t ddra = { 0 };
271 
272 	ddra.ddra_fsname = dsname;
273 	ddra.ddra_result = err_details;
274 
275 	err = zcp_sync_task(state, dsl_dataset_rollback_check,
276 	    dsl_dataset_rollback_sync, &ddra, sync, dsname);
277 
278 	return (err);
279 }
280 
281 static int zcp_synctask_snapshot(lua_State *, boolean_t, nvlist_t *);
282 static const zcp_synctask_info_t zcp_synctask_snapshot_info = {
283 	.name = "snapshot",
284 	.func = zcp_synctask_snapshot,
285 	.pargs = {
286 	    {.za_name = "filesystem@snapname | volume@snapname",
287 	    .za_lua_type = LUA_TSTRING },
288 	    {NULL, 0}
289 	},
290 	.kwargs = {
291 	    {NULL, 0}
292 	},
293 	.space_check = ZFS_SPACE_CHECK_NORMAL,
294 	.blocks_modified = 3
295 };
296 
297 static int
zcp_synctask_snapshot(lua_State * state,boolean_t sync,nvlist_t * err_details)298 zcp_synctask_snapshot(lua_State *state, boolean_t sync, nvlist_t *err_details)
299 {
300 	(void) err_details;
301 	int err;
302 	dsl_dataset_snapshot_arg_t ddsa = { 0 };
303 	const char *dsname = lua_tostring(state, 1);
304 	zcp_run_info_t *ri = zcp_run_info(state);
305 
306 	/*
307 	 * On old pools, the ZIL must not be active when a snapshot is created,
308 	 * but we can't suspend the ZIL because we're already in syncing
309 	 * context.
310 	 */
311 	if (spa_version(ri->zri_pool->dp_spa) < SPA_VERSION_FAST_SNAP) {
312 		return (SET_ERROR(ENOTSUP));
313 	}
314 
315 	/*
316 	 * We only allow for a single snapshot rather than a list, so the
317 	 * error list output is unnecessary.
318 	 */
319 	ddsa.ddsa_errors = NULL;
320 	ddsa.ddsa_props = NULL;
321 	ddsa.ddsa_cr = ri->zri_cred;
322 	ddsa.ddsa_snaps = fnvlist_alloc();
323 	fnvlist_add_boolean(ddsa.ddsa_snaps, dsname);
324 
325 	zcp_cleanup_handler_t *zch = zcp_register_cleanup(state,
326 	    zcp_synctask_cleanup, ddsa.ddsa_snaps);
327 
328 	err = zcp_sync_task(state, dsl_dataset_snapshot_check,
329 	    dsl_dataset_snapshot_sync, &ddsa, sync, dsname);
330 
331 	if (err == 0) {
332 		/*
333 		 * We may need to create a new device minor node for this
334 		 * dataset (if it is a zvol and the "snapdev" property is set).
335 		 * Save it in the nvlist so that it can be processed in open
336 		 * context.
337 		 */
338 		fnvlist_add_boolean(ri->zri_new_zvols, dsname);
339 	}
340 
341 	zcp_deregister_cleanup(state, zch);
342 	fnvlist_free(ddsa.ddsa_snaps);
343 
344 	return (err);
345 }
346 
347 static int zcp_synctask_rename_snapshot(lua_State *, boolean_t, nvlist_t *);
348 static const zcp_synctask_info_t zcp_synctask_rename_snapshot_info = {
349 	.name = "rename_snapshot",
350 	.func = zcp_synctask_rename_snapshot,
351 	.pargs = {
352 	    {.za_name = "filesystem | volume", .za_lua_type = LUA_TSTRING },
353 	    {.za_name = "oldsnapname", .za_lua_type = LUA_TSTRING },
354 	    {.za_name = "newsnapname", .za_lua_type = LUA_TSTRING },
355 	    {NULL, 0}
356 	},
357 	.space_check = ZFS_SPACE_CHECK_RESERVED,
358 	.blocks_modified = 1
359 };
360 
361 static int
zcp_synctask_rename_snapshot(lua_State * state,boolean_t sync,nvlist_t * err_details)362 zcp_synctask_rename_snapshot(lua_State *state, boolean_t sync,
363     nvlist_t *err_details)
364 {
365 	(void) err_details;
366 	int err;
367 	const char *fsname = lua_tostring(state, 1);
368 	const char *oldsnapname = lua_tostring(state, 2);
369 	const char *newsnapname = lua_tostring(state, 3);
370 
371 	struct dsl_dataset_rename_snapshot_arg ddrsa = { 0 };
372 	ddrsa.ddrsa_fsname = fsname;
373 	ddrsa.ddrsa_oldsnapname = oldsnapname;
374 	ddrsa.ddrsa_newsnapname = newsnapname;
375 	ddrsa.ddrsa_recursive = B_FALSE;
376 
377 	err = zcp_sync_task(state, dsl_dataset_rename_snapshot_check,
378 	    dsl_dataset_rename_snapshot_sync, &ddrsa, sync, NULL);
379 
380 	return (err);
381 }
382 
383 static int zcp_synctask_inherit_prop(lua_State *, boolean_t,
384     nvlist_t *err_details);
385 static const zcp_synctask_info_t zcp_synctask_inherit_prop_info = {
386 	.name = "inherit",
387 	.func = zcp_synctask_inherit_prop,
388 	.space_check = ZFS_SPACE_CHECK_RESERVED,
389 	.blocks_modified = 2, /* 2 * numprops */
390 	.pargs = {
391 		{ .za_name = "dataset", .za_lua_type = LUA_TSTRING },
392 		{ .za_name = "property", .za_lua_type = LUA_TSTRING },
393 		{ NULL, 0 }
394 	},
395 	.kwargs = {
396 		{ NULL, 0 }
397 	},
398 };
399 
400 static int
zcp_synctask_inherit_prop_check(void * arg,dmu_tx_t * tx)401 zcp_synctask_inherit_prop_check(void *arg, dmu_tx_t *tx)
402 {
403 	zcp_inherit_prop_arg_t *args = arg;
404 	zfs_prop_t prop = zfs_name_to_prop(args->zipa_prop);
405 
406 	if (prop == ZPROP_USERPROP) {
407 		if (zfs_prop_user(args->zipa_prop))
408 			return (0);
409 
410 		return (EINVAL);
411 	}
412 
413 	if (zfs_prop_readonly(prop))
414 		return (EINVAL);
415 
416 	if (!zfs_prop_inheritable(prop))
417 		return (EINVAL);
418 
419 	return (dsl_props_set_check(&args->zipa_dpsa, tx));
420 }
421 
422 static void
zcp_synctask_inherit_prop_sync(void * arg,dmu_tx_t * tx)423 zcp_synctask_inherit_prop_sync(void *arg, dmu_tx_t *tx)
424 {
425 	zcp_inherit_prop_arg_t *args = arg;
426 	dsl_props_set_arg_t *dpsa = &args->zipa_dpsa;
427 
428 	dsl_props_set_sync(dpsa, tx);
429 }
430 
431 static int
zcp_synctask_inherit_prop(lua_State * state,boolean_t sync,nvlist_t * err_details)432 zcp_synctask_inherit_prop(lua_State *state, boolean_t sync,
433     nvlist_t *err_details)
434 {
435 	(void) err_details;
436 	int err;
437 	zcp_inherit_prop_arg_t zipa = { 0 };
438 	dsl_props_set_arg_t *dpsa = &zipa.zipa_dpsa;
439 
440 	const char *dsname = lua_tostring(state, 1);
441 	const char *prop = lua_tostring(state, 2);
442 
443 	zipa.zipa_state = state;
444 	zipa.zipa_prop = prop;
445 	dpsa->dpsa_dsname = dsname;
446 	dpsa->dpsa_source = ZPROP_SRC_INHERITED;
447 	dpsa->dpsa_props = fnvlist_alloc();
448 	fnvlist_add_boolean(dpsa->dpsa_props, prop);
449 
450 	zcp_cleanup_handler_t *zch = zcp_register_cleanup(state,
451 	    zcp_synctask_cleanup, dpsa->dpsa_props);
452 
453 	err = zcp_sync_task(state, zcp_synctask_inherit_prop_check,
454 	    zcp_synctask_inherit_prop_sync, &zipa, sync, dsname);
455 
456 	zcp_deregister_cleanup(state, zch);
457 	fnvlist_free(dpsa->dpsa_props);
458 
459 	return (err);
460 }
461 
462 static int zcp_synctask_bookmark(lua_State *, boolean_t, nvlist_t *);
463 static const zcp_synctask_info_t zcp_synctask_bookmark_info = {
464 	.name = "bookmark",
465 	.func = zcp_synctask_bookmark,
466 	.pargs = {
467 	    {.za_name = "snapshot | bookmark", .za_lua_type = LUA_TSTRING },
468 	    {.za_name = "bookmark", .za_lua_type = LUA_TSTRING },
469 	    {NULL, 0}
470 	},
471 	.kwargs = {
472 	    {NULL, 0}
473 	},
474 	.space_check = ZFS_SPACE_CHECK_NORMAL,
475 	.blocks_modified = 1,
476 };
477 
478 static int
zcp_synctask_bookmark(lua_State * state,boolean_t sync,nvlist_t * err_details)479 zcp_synctask_bookmark(lua_State *state, boolean_t sync, nvlist_t *err_details)
480 {
481 	(void) err_details;
482 	int err;
483 	const char *source = lua_tostring(state, 1);
484 	const char *new = lua_tostring(state, 2);
485 
486 	nvlist_t *bmarks = fnvlist_alloc();
487 	fnvlist_add_string(bmarks, new, source);
488 
489 	zcp_cleanup_handler_t *zch = zcp_register_cleanup(state,
490 	    zcp_synctask_cleanup, bmarks);
491 
492 	dsl_bookmark_create_arg_t dbca = {
493 		.dbca_bmarks = bmarks,
494 		.dbca_errors = NULL,
495 	};
496 	err = zcp_sync_task(state, dsl_bookmark_create_check,
497 	    dsl_bookmark_create_sync, &dbca, sync, source);
498 
499 	zcp_deregister_cleanup(state, zch);
500 	fnvlist_free(bmarks);
501 
502 	return (err);
503 }
504 
505 static int zcp_synctask_set_prop(lua_State *, boolean_t, nvlist_t *err_details);
506 static const zcp_synctask_info_t zcp_synctask_set_prop_info = {
507 	.name = "set_prop",
508 	.func = zcp_synctask_set_prop,
509 	.space_check = ZFS_SPACE_CHECK_RESERVED,
510 	.blocks_modified = 2,
511 	.pargs = {
512 		{ .za_name = "dataset", .za_lua_type = LUA_TSTRING },
513 		{ .za_name = "property", .za_lua_type =  LUA_TSTRING },
514 		{ .za_name = "value", .za_lua_type =  LUA_TSTRING },
515 		{ NULL, 0 }
516 	},
517 	.kwargs = {
518 		{ NULL, 0 }
519 	}
520 };
521 
522 static int
zcp_synctask_set_prop(lua_State * state,boolean_t sync,nvlist_t * err_details)523 zcp_synctask_set_prop(lua_State *state, boolean_t sync, nvlist_t *err_details)
524 {
525 	(void) err_details;
526 	int err;
527 	zcp_set_prop_arg_t args = { 0 };
528 
529 	const char *dsname = lua_tostring(state, 1);
530 	const char *prop = lua_tostring(state, 2);
531 	const char *val = lua_tostring(state, 3);
532 
533 	args.state = state;
534 	args.dsname = dsname;
535 	args.prop = prop;
536 	args.val = val;
537 
538 	err = zcp_sync_task(state, zcp_set_prop_check, zcp_set_prop_sync,
539 	    &args, sync, dsname);
540 
541 	return (err);
542 }
543 
544 static int
zcp_synctask_wrapper(lua_State * state)545 zcp_synctask_wrapper(lua_State *state)
546 {
547 	int err;
548 	zcp_cleanup_handler_t *zch;
549 	int num_ret = 1;
550 	nvlist_t *err_details = fnvlist_alloc();
551 
552 	/*
553 	 * Make sure err_details is properly freed, even if a fatal error is
554 	 * thrown during the synctask.
555 	 */
556 	zch = zcp_register_cleanup(state, zcp_synctask_cleanup, err_details);
557 
558 	zcp_synctask_info_t *info = lua_touserdata(state, lua_upvalueindex(1));
559 	boolean_t sync = lua_toboolean(state, lua_upvalueindex(2));
560 
561 	zcp_run_info_t *ri = zcp_run_info(state);
562 	dsl_pool_t *dp = ri->zri_pool;
563 
564 	/* MOS space is triple-dittoed, so we multiply by 3. */
565 	uint64_t funcspace =
566 	    ((uint64_t)info->blocks_modified << DST_AVG_BLKSHIFT) * 3;
567 
568 	zcp_parse_args(state, info->name, info->pargs, info->kwargs);
569 
570 	err = 0;
571 	if (info->space_check != ZFS_SPACE_CHECK_NONE) {
572 		uint64_t quota = dsl_pool_unreserved_space(dp,
573 		    info->space_check);
574 		uint64_t used = dsl_dir_phys(dp->dp_root_dir)->dd_used_bytes +
575 		    ri->zri_space_used;
576 
577 		if (used + funcspace > quota) {
578 			err = SET_ERROR(ENOSPC);
579 		}
580 	}
581 
582 	if (err == 0) {
583 		err = info->func(state, sync, err_details);
584 	}
585 
586 	if (err == 0) {
587 		ri->zri_space_used += funcspace;
588 	}
589 
590 	lua_pushnumber(state, (lua_Number)err);
591 	if (fnvlist_num_pairs(err_details) > 0) {
592 		(void) zcp_nvlist_to_lua(state, err_details, NULL, 0);
593 		num_ret++;
594 	}
595 
596 	zcp_deregister_cleanup(state, zch);
597 	fnvlist_free(err_details);
598 
599 	return (num_ret);
600 }
601 
602 int
zcp_load_synctask_lib(lua_State * state,boolean_t sync)603 zcp_load_synctask_lib(lua_State *state, boolean_t sync)
604 {
605 	const zcp_synctask_info_t *zcp_synctask_funcs[] = {
606 		&zcp_synctask_destroy_info,
607 		&zcp_synctask_clone_info,
608 		&zcp_synctask_promote_info,
609 		&zcp_synctask_rollback_info,
610 		&zcp_synctask_snapshot_info,
611 		&zcp_synctask_rename_snapshot_info,
612 		&zcp_synctask_inherit_prop_info,
613 		&zcp_synctask_bookmark_info,
614 		&zcp_synctask_set_prop_info,
615 		NULL
616 	};
617 
618 	lua_newtable(state);
619 
620 	for (int i = 0; zcp_synctask_funcs[i] != NULL; i++) {
621 		const zcp_synctask_info_t *info = zcp_synctask_funcs[i];
622 		lua_pushlightuserdata(state, (void *)(uintptr_t)info);
623 		lua_pushboolean(state, sync);
624 		lua_pushcclosure(state, &zcp_synctask_wrapper, 2);
625 		lua_setfield(state, -2, info->name);
626 	}
627 
628 	return (1);
629 }
630