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