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.\" http://www.illumos.org/license/CDDL. 11.\" 12.\" Copyright (c) 2016, 2019 by Delphix. All Rights Reserved. 13.\" Copyright (c) 2019, 2020 by Christian Schwarz. All Rights Reserved. 14.\" Copyright 2020 Joyent, Inc. 15.\" Copyright (c) 2025, Rob Norris <robn@despairlabs.com> 16.\" 17.Dd June 5, 2025 18.Dt ZFS-PROGRAM 8 19.Os 20. 21.Sh NAME 22.Nm zfs-program 23.Nd execute ZFS channel programs 24.Sh SYNOPSIS 25.Nm zfs 26.Cm program 27.Op Fl jn 28.Op Fl t Ar instruction-limit 29.Op Fl m Ar memory-limit 30.Ar pool 31.Ar script 32.Op Ar script arguments 33. 34.Sh DESCRIPTION 35The ZFS channel program interface allows ZFS administrative operations to be 36run programmatically as a Lua script. 37The entire script is executed atomically, with no other administrative 38operations taking effect concurrently. 39A library of ZFS calls is made available to channel program scripts. 40Channel programs may only be run with root privileges. 41.Pp 42A modified version of the Lua 5.2 interpreter is used to run channel program 43scripts. 44The Lua 5.2 manual can be found at 45.Lk http://www.lua.org/manual/5.2/ 46.Pp 47The channel program given by 48.Ar script 49will be run on 50.Ar pool , 51and any attempts to access or modify other pools will cause an error. 52. 53.Sh OPTIONS 54.Bl -tag -width "-t" 55.It Fl j , -json 56Display channel program output in JSON format. 57When this flag is specified and standard output is empty - 58channel program encountered an error. 59The details of such an error will be printed to standard error in plain text. 60.It Fl n 61Executes a read-only channel program, which runs faster. 62The program cannot change on-disk state by calling functions from the 63zfs.sync submodule. 64The program can be used to gather information such as properties and 65determining if changes would succeed (zfs.check.*). 66Without this flag, all pending changes must be synced to disk before a 67channel program can complete. 68.It Fl t Ar instruction-limit 69Limit the number of Lua instructions to execute. 70If a channel program executes more than the specified number of instructions, 71it will be stopped and an error will be returned. 72The default limit is 10 million instructions, and it can be set to a maximum of 73100 million instructions. 74.It Fl m Ar memory-limit 75Memory limit, in bytes. 76If a channel program attempts to allocate more memory than the given limit, it 77will be stopped and an error returned. 78The default memory limit is 10 MiB, and can be set to a maximum of 100 MiB. 79.El 80.Pp 81All remaining argument strings will be passed directly to the Lua script as 82described in the 83.Sx LUA INTERFACE 84section below. 85. 86.Sh LUA INTERFACE 87A channel program can be invoked either from the command line, or via a library 88call to 89.Fn lzc_channel_program . 90. 91.Ss Arguments 92Arguments passed to the channel program are converted to a Lua table. 93If invoked from the command line, extra arguments to the Lua script will be 94accessible as an array stored in the argument table with the key 'argv': 95.Bd -literal -compact -offset indent 96args = ... 97argv = args["argv"] 98-- argv == {1="arg1", 2="arg2", ...} 99.Ed 100.Pp 101If invoked from the libzfs interface, an arbitrary argument list can be 102passed to the channel program, which is accessible via the same 103.Qq Li ... 104syntax in Lua: 105.Bd -literal -compact -offset indent 106args = ... 107-- args == {"foo"="bar", "baz"={...}, ...} 108.Ed 109.Pp 110Note that because Lua arrays are 1-indexed, arrays passed to Lua from the 111libzfs interface will have their indices incremented by 1. 112That is, the element 113in 114.Va arr[0] 115in a C array passed to a channel program will be stored in 116.Va arr[1] 117when accessed from Lua. 118. 119.Ss Return Values 120Lua return statements take the form: 121.Dl return ret0, ret1, ret2, ... 122.Pp 123Return statements returning multiple values are permitted internally in a 124channel program script, but attempting to return more than one value from the 125top level of the channel program is not permitted and will throw an error. 126However, tables containing multiple values can still be returned. 127If invoked from the command line, a return statement: 128.Bd -literal -compact -offset indent 129a = {foo="bar", baz=2} 130return a 131.Ed 132.Pp 133Will be output formatted as: 134.Bd -literal -compact -offset indent 135Channel program fully executed with return value: 136 return: 137 baz: 2 138 foo: 'bar' 139.Ed 140. 141.Ss Fatal Errors 142If the channel program encounters a fatal error while running, a non-zero exit 143status will be returned. 144If more information about the error is available, a singleton list will be 145returned detailing the error: 146.Dl error: \&"error string, including Lua stack trace" 147.Pp 148If a fatal error is returned, the channel program may have not executed at all, 149may have partially executed, or may have fully executed but failed to pass a 150return value back to userland. 151.Pp 152If the channel program exhausts an instruction or memory limit, a fatal error 153will be generated and the program will be stopped, leaving the program partially 154executed. 155No attempt is made to reverse or undo any operations already performed. 156Note that because both the instruction count and amount of memory used by a 157channel program are deterministic when run against the same inputs and 158filesystem state, as long as a channel program has run successfully once, you 159can guarantee that it will finish successfully against a similar size system. 160.Pp 161If a channel program attempts to return too large a value, the program will 162fully execute but exit with a nonzero status code and no return value. 163.Pp 164.Em Note : 165ZFS API functions do not generate Fatal Errors when correctly invoked, they 166return an error code and the channel program continues executing. 167See the 168.Sx ZFS API 169section below for function-specific details on error return codes. 170. 171.Ss Lua to C Value Conversion 172When invoking a channel program via the libzfs interface, it is necessary to 173translate arguments and return values from Lua values to their C equivalents, 174and vice-versa. 175.Pp 176There is a correspondence between nvlist values in C and Lua tables. 177A Lua table which is returned from the channel program will be recursively 178converted to an nvlist, with table values converted to their natural 179equivalents: 180.TS 181cw3 l c l . 182 string -> string 183 number -> int64 184 boolean -> boolean_value 185 nil -> boolean (no value) 186 table -> nvlist 187.TE 188.Pp 189Likewise, table keys are replaced by string equivalents as follows: 190.TS 191cw3 l c l . 192 string -> no change 193 number -> signed decimal string ("%lld") 194 boolean -> "true" | "false" 195.TE 196.Pp 197Any collision of table key strings (for example, the string "true" and a 198true boolean value) will cause a fatal error. 199.Pp 200Lua numbers are represented internally as signed 64-bit integers. 201. 202.Sh LUA STANDARD LIBRARY 203The following Lua built-in base library functions are available: 204.TS 205cw3 l l l l . 206 assert rawlen collectgarbage rawget 207 error rawset getmetatable select 208 ipairs setmetatable next tonumber 209 pairs tostring rawequal type 210.TE 211.Pp 212All functions in the 213.Em coroutine , 214.Em string , 215and 216.Em table 217built-in submodules are also available. 218A complete list and documentation of these modules is available in the Lua 219manual. 220.Pp 221The following functions base library functions have been disabled and are 222not available for use in channel programs: 223.TS 224cw3 l l l l l l . 225 dofile loadfile load pcall print xpcall 226.TE 227. 228.Sh ZFS API 229. 230.Ss Function Arguments 231Each API function takes a fixed set of required positional arguments and 232optional keyword arguments. 233For example, the destroy function takes a single positional string argument 234(the name of the dataset to destroy) and an optional "defer" keyword boolean 235argument. 236When using parentheses to specify the arguments to a Lua function, only 237positional arguments can be used: 238.Dl Sy zfs.sync.destroy Ns Pq \&"rpool@snap" 239.Pp 240To use keyword arguments, functions must be called with a single argument that 241is a Lua table containing entries mapping integers to positional arguments and 242strings to keyword arguments: 243.Dl Sy zfs.sync.destroy Ns Pq {1="rpool@snap", defer=true} 244.Pp 245The Lua language allows curly braces to be used in place of parenthesis as 246syntactic sugar for this calling convention: 247.Dl Sy zfs.sync.snapshot Ns {"rpool@snap", defer=true} 248. 249.Ss Function Return Values 250If an API function succeeds, it returns 0. 251If it fails, it returns an error code and the channel program continues 252executing. 253API functions do not generate Fatal Errors except in the case of an 254unrecoverable internal file system error. 255.Pp 256In addition to returning an error code, some functions also return extra 257details describing what caused the error. 258This extra description is given as a second return value, and will always be a 259Lua table, or Nil if no error details were returned. 260Different keys will exist in the error details table depending on the function 261and error case. 262Any such function may be called expecting a single return value: 263.Dl errno = Sy zfs.sync.promote Ns Pq dataset 264.Pp 265Or, the error details can be retrieved: 266.Bd -literal -compact -offset indent 267.No errno, details = Sy zfs.sync.promote Ns Pq dataset 268if (errno == EEXIST) then 269 assert(details ~= Nil) 270 list_of_conflicting_snapshots = details 271end 272.Ed 273.Pp 274The following global aliases for API function error return codes are defined 275for use in channel programs: 276.TS 277cw3 l l l l l l l . 278 EPERM ECHILD ENODEV ENOSPC ENOENT EAGAIN ENOTDIR 279 ESPIPE ESRCH ENOMEM EISDIR EROFS EINTR EACCES 280 EINVAL EMLINK EIO EFAULT ENFILE EPIPE ENXIO 281 ENOTBLK EMFILE EDOM E2BIG EBUSY ENOTTY ERANGE 282 ENOEXEC EEXIST ETXTBSY EDQUOT EBADF EXDEV EFBIG 283.TE 284. 285.Ss API Functions 286For detailed descriptions of the exact behavior of any ZFS administrative 287operations, see the main 288.Xr zfs 8 289manual page. 290.Bl -tag -width "xx" 291.It Fn zfs.debug msg 292Record a debug message in the zfs_dbgmsg log. 293A log of these messages can be printed via mdb's "::zfs_dbgmsg" command, or 294can be monitored live by running 295.Dl dtrace -n 'zfs-dbgmsg{trace(stringof(arg0))}' 296.Pp 297.Bl -tag -compact -width "property (string)" 298.It Ar msg Pq string 299Debug message to be printed. 300.El 301.It Fn zfs.exists dataset 302Returns true if the given dataset exists, or false if it doesn't. 303A fatal error will be thrown if the dataset is not in the target pool. 304That is, in a channel program running on rpool, 305.Sy zfs.exists Ns Pq \&"rpool/nonexistent_fs" 306returns false, but 307.Sy zfs.exists Ns Pq \&"somepool/fs_that_may_exist" 308will error. 309.Pp 310.Bl -tag -compact -width "property (string)" 311.It Ar dataset Pq string 312Dataset to check for existence. 313Must be in the target pool. 314.El 315.It Fn zfs.get_prop dataset property 316Returns two values. 317First, a string, number or table containing the property value for the given 318dataset. 319Second, a string containing the source of the property (i.e. the name of the 320dataset in which it was set or nil if it is readonly). 321Throws a Lua error if the dataset is invalid or the property doesn't exist. 322Note that Lua only supports int64 number types whereas ZFS number properties 323are uint64. 324This means very large values (like GUIDs) may wrap around and appear negative. 325.Pp 326.Bl -tag -compact -width "property (string)" 327.It Ar dataset Pq string 328Filesystem or snapshot path to retrieve properties from. 329.It Ar property Pq string 330Name of property to retrieve. 331All filesystem, snapshot and volume properties are supported except for 332.Sy mounted 333and 334.Sy iscsioptions . 335Also supports the 336.Sy written@ Ns Ar snap 337and 338.Sy written# Ns Ar bookmark 339properties and the 340.Ao Sy user Ns | Ns Sy group Ac Ns Ao Sy quota Ns | Ns Sy used Ac Ns Sy @ Ns Ar id 341properties, though the id must be in numeric form. 342.El 343.El 344.Bl -tag -width "xx" 345.It Sy zfs.sync submodule 346The sync submodule contains functions that modify the on-disk state. 347They are executed in "syncing context". 348.Pp 349The available sync submodule functions are as follows: 350.Bl -tag -width "xx" 351.It Fn zfs.sync.clone snapshot newdataset 352Create a new filesystem from a snapshot. 353Returns 0 if the filesystem was successfully created, 354and a nonzero error code otherwise. 355.Pp 356Note: Due to general limitations in channel programs, a filesystem created 357this way will not be mounted, regardless of the value of the 358.Sy mountpoint 359and 360.Sy canmount 361properties. 362This limitation may be removed in the future, 363so it is recommended that you set 364.Sy mountpoint Ns = Ns Sy none 365or 366.Sy canmount Ns = Ns Sy off 367or 368.Sy noauto 369to avoid surprises. 370.Pp 371.Bl -tag -compact -width "newbookmark (string)" 372.It Ar snapshot Pq string 373Name of the source snapshot to clone. 374.It Ar newdataset Pq string 375Name of the target dataset to create. 376.El 377.It Sy zfs.sync.destroy Ns Pq Ar dataset , Op Ar defer Ns = Ns Sy true Ns | Ns Sy false 378Destroy the given dataset. 379Returns 0 on successful destroy, or a nonzero error code if the dataset could 380not be destroyed (for example, if the dataset has any active children or 381clones). 382.Pp 383.Bl -tag -compact -width "newbookmark (string)" 384.It Ar dataset Pq string 385Filesystem or snapshot to be destroyed. 386.It Op Ar defer Pq boolean 387Valid only for destroying snapshots. 388If set to true, and the snapshot has holds or clones, allows the snapshot to be 389marked for deferred deletion rather than failing. 390.El 391.It Fn zfs.sync.inherit dataset property 392Clears the specified property in the given dataset, causing it to be inherited 393from an ancestor, or restored to the default if no ancestor property is set. 394The 395.Nm zfs Cm inherit Fl S 396option has not been implemented. 397Returns 0 on success, or a nonzero error code if the property could not be 398cleared. 399.Pp 400.Bl -tag -compact -width "newbookmark (string)" 401.It Ar dataset Pq string 402Filesystem or snapshot containing the property to clear. 403.It Ar property Pq string 404The property to clear. 405Allowed properties are the same as those for the 406.Nm zfs Cm inherit 407command. 408.El 409.It Fn zfs.sync.promote dataset 410Promote the given clone to a filesystem. 411Returns 0 on successful promotion, or a nonzero error code otherwise. 412If EEXIST is returned, the second return value will be an array of the clone's 413snapshots whose names collide with snapshots of the parent filesystem. 414.Pp 415.Bl -tag -compact -width "newbookmark (string)" 416.It Ar dataset Pq string 417Clone to be promoted. 418.El 419.It Fn zfs.sync.rollback filesystem 420Rollback to the previous snapshot for a dataset. 421Returns 0 on successful rollback, or a nonzero error code otherwise. 422Rollbacks can be performed on filesystems or zvols, but not on snapshots 423or mounted datasets. 424EBUSY is returned in the case where the filesystem is mounted. 425.Pp 426.Bl -tag -compact -width "newbookmark (string)" 427.It Ar filesystem Pq string 428Filesystem to rollback. 429.El 430.It Fn zfs.sync.set_prop dataset property value 431Sets the given property on a dataset. 432Currently only user properties are supported. 433Returns 0 if the property was set, or a nonzero error code otherwise. 434.Pp 435.Bl -tag -compact -width "newbookmark (string)" 436.It Ar dataset Pq string 437The dataset where the property will be set. 438.It Ar property Pq string 439The property to set. 440.It Ar value Pq string 441The value of the property to be set. 442.El 443.It Fn zfs.sync.snapshot dataset 444Create a snapshot of a filesystem. 445Returns 0 if the snapshot was successfully created, 446and a nonzero error code otherwise. 447.Pp 448Note: Taking a snapshot will fail on any pool older than legacy version 27. 449To enable taking snapshots from ZCP scripts, the pool must be upgraded. 450.Pp 451.Bl -tag -compact -width "newbookmark (string)" 452.It Ar dataset Pq string 453Name of snapshot to create. 454.El 455.It Fn zfs.sync.rename_snapshot dataset oldsnapname newsnapname 456Rename a snapshot of a filesystem or a volume. 457Returns 0 if the snapshot was successfully renamed, 458and a nonzero error code otherwise. 459.Pp 460.Bl -tag -compact -width "newbookmark (string)" 461.It Ar dataset Pq string 462Name of the snapshot's parent dataset. 463.It Ar oldsnapname Pq string 464Original name of the snapshot. 465.It Ar newsnapname Pq string 466New name of the snapshot. 467.El 468.It Fn zfs.sync.bookmark source newbookmark 469Create a bookmark of an existing source snapshot or bookmark. 470Returns 0 if the new bookmark was successfully created, 471and a nonzero error code otherwise. 472.Pp 473Note: Bookmarking requires the corresponding pool feature to be enabled. 474.Pp 475.Bl -tag -compact -width "newbookmark (string)" 476.It Ar source Pq string 477Full name of the existing snapshot or bookmark. 478.It Ar newbookmark Pq string 479Full name of the new bookmark. 480.El 481.El 482.It Sy zfs.check submodule 483For each function in the 484.Sy zfs.sync 485submodule, there is a corresponding 486.Sy zfs.check 487function which performs a "dry run" of the same operation. 488Each takes the same arguments as its 489.Sy zfs.sync 490counterpart and returns 0 if the operation would succeed, 491or a non-zero error code if it would fail, along with any other error details. 492That is, each has the same behavior as the corresponding sync function except 493for actually executing the requested change. 494For example, 495.Fn zfs.check.destroy \&"fs" 496returns 0 if 497.Fn zfs.sync.destroy \&"fs" 498would successfully destroy the dataset. 499.Pp 500The available 501.Sy zfs.check 502functions are: 503.Bl -tag -compact -width "xx" 504.It Fn zfs.check.clone snapshot newdataset 505.It Sy zfs.check.destroy Ns Pq Ar dataset , Op Ar defer Ns = Ns Sy true Ns | Ns Sy false 506.It Fn zfs.check.promote dataset 507.It Fn zfs.check.rollback filesystem 508.It Fn zfs.check.set_property dataset property value 509.It Fn zfs.check.snapshot dataset 510.El 511.It Sy zfs.list submodule 512The zfs.list submodule provides functions for iterating over datasets and 513properties. 514Rather than returning tables, these functions act as Lua iterators, and are 515generally used as follows: 516.Bd -literal -compact -offset indent 517.No for child in Fn zfs.list.children \&"rpool" No do 518 ... 519end 520.Ed 521.Pp 522The available 523.Sy zfs.list 524functions are: 525.Bl -tag -width "xx" 526.It Fn zfs.list.clones snapshot 527Iterate through all clones of the given snapshot. 528.Pp 529.Bl -tag -compact -width "snapshot (string)" 530.It Ar snapshot Pq string 531Must be a valid snapshot path in the current pool. 532.El 533.It Fn zfs.list.snapshots dataset 534Iterate through all snapshots of the given dataset. 535Each snapshot is returned as a string containing the full dataset name, 536e.g. "pool/fs@snap". 537.Pp 538.Bl -tag -compact -width "snapshot (string)" 539.It Ar dataset Pq string 540Must be a valid filesystem or volume. 541.El 542.It Fn zfs.list.children dataset 543Iterate through all direct children of the given dataset. 544Each child is returned as a string containing the full dataset name, 545e.g. "pool/fs/child". 546.Pp 547.Bl -tag -compact -width "snapshot (string)" 548.It Ar dataset Pq string 549Must be a valid filesystem or volume. 550.El 551.It Fn zfs.list.bookmarks dataset 552Iterate through all bookmarks of the given dataset. 553Each bookmark is returned as a string containing the full dataset name, 554e.g. "pool/fs#bookmark". 555.Pp 556.Bl -tag -compact -width "snapshot (string)" 557.It Ar dataset Pq string 558Must be a valid filesystem or volume. 559.El 560.It Fn zfs.list.holds snapshot 561Iterate through all user holds on the given snapshot. 562Each hold is returned 563as a pair of the hold's tag and the timestamp (in seconds since the epoch) at 564which it was created. 565.Pp 566.Bl -tag -compact -width "snapshot (string)" 567.It Ar snapshot Pq string 568Must be a valid snapshot. 569.El 570.It Fn zfs.list.properties dataset 571An alias for zfs.list.user_properties (see relevant entry). 572.Pp 573.Bl -tag -compact -width "snapshot (string)" 574.It Ar dataset Pq string 575Must be a valid filesystem, snapshot, or volume. 576.El 577.It Fn zfs.list.user_properties dataset 578Iterate through all user properties for the given dataset. 579For each step of the iteration, output the property name, its value, 580and its source. 581Throws a Lua error if the dataset is invalid. 582.Pp 583.Bl -tag -compact -width "snapshot (string)" 584.It Ar dataset Pq string 585Must be a valid filesystem, snapshot, or volume. 586.El 587.It Fn zfs.list.system_properties dataset 588Returns an array of strings, the names of the valid system (non-user defined) 589properties for the given dataset. 590Throws a Lua error if the dataset is invalid. 591.Pp 592.Bl -tag -compact -width "snapshot (string)" 593.It Ar dataset Pq string 594Must be a valid filesystem, snapshot or volume. 595.El 596.El 597.El 598. 599.Sh EXAMPLES 600. 601.Ss Example 1 602The following channel program recursively destroys a filesystem and all its 603snapshots and children in a naive manner. 604Note that this does not involve any error handling or reporting. 605.Bd -literal -offset indent 606function destroy_recursive(root) 607 for child in zfs.list.children(root) do 608 destroy_recursive(child) 609 end 610 for snap in zfs.list.snapshots(root) do 611 zfs.sync.destroy(snap) 612 end 613 zfs.sync.destroy(root) 614end 615destroy_recursive("pool/somefs") 616.Ed 617. 618.Ss Example 2 619A more verbose and robust version of the same channel program, which 620properly detects and reports errors, and also takes the dataset to destroy 621as a command line argument, would be as follows: 622.Bd -literal -offset indent 623succeeded = {} 624failed = {} 625 626function destroy_recursive(root) 627 for child in zfs.list.children(root) do 628 destroy_recursive(child) 629 end 630 for snap in zfs.list.snapshots(root) do 631 err = zfs.sync.destroy(snap) 632 if (err ~= 0) then 633 failed[snap] = err 634 else 635 succeeded[snap] = err 636 end 637 end 638 err = zfs.sync.destroy(root) 639 if (err ~= 0) then 640 failed[root] = err 641 else 642 succeeded[root] = err 643 end 644end 645 646args = ... 647argv = args["argv"] 648 649destroy_recursive(argv[1]) 650 651results = {} 652results["succeeded"] = succeeded 653results["failed"] = failed 654return results 655.Ed 656. 657.Ss Example 3 658The following function performs a forced promote operation by attempting to 659promote the given clone and destroying any conflicting snapshots. 660.Bd -literal -offset indent 661function force_promote(ds) 662 errno, details = zfs.check.promote(ds) 663 if (errno == EEXIST) then 664 assert(details ~= Nil) 665 for i, snap in ipairs(details) do 666 zfs.sync.destroy(ds .. "@" .. snap) 667 end 668 elseif (errno ~= 0) then 669 return errno 670 end 671 return zfs.sync.promote(ds) 672end 673.Ed 674