ZFS-PROGRAM(8) | System Manager's Manual | ZFS-PROGRAM(8) |
zfs-program
—
execute ZFS channel programs
zfs |
program [-jn ]
[-t instruction-limit]
[-m memory-limit]
pool script
[script arguments] |
The ZFS channel program interface allows ZFS administrative operations to be run programmatically as a Lua script. The entire script is executed atomically, with no other administrative operations taking effect concurrently. A library of ZFS calls is made available to channel program scripts. Channel programs may only be run with root privileges.
A modified version of the Lua 5.2 interpreter is used to run channel program scripts. The Lua 5.2 manual can be found at http://www.lua.org/manual/5.2/
The channel program given by script will be run on pool, and any attempts to access or modify other pools will cause an error.
-j
-n
-t
instruction-limit-m
memory-limitAll remaining argument strings will be passed directly to the Lua script as described in the LUA INTERFACE section below.
A channel program can be invoked either from the command line, or
via a library call to
lzc_channel_program
().
Arguments passed to the channel program are converted to a Lua table. If invoked from the command line, extra arguments to the Lua script will be accessible as an array stored in the argument table with the key 'argv':
args = ... argv = args["argv"] -- argv == {1="arg1", 2="arg2", ...}
If invoked from the libzfs interface, an arbitrary argument list
can be passed to the channel program, which is accessible via the same
"...
" syntax in Lua:
args = ... -- args == {"foo"="bar", "baz"={...}, ...}
Note that because Lua arrays are 1-indexed, arrays passed to Lua from the libzfs interface will have their indices incremented by 1. That is, the element in arr[0] in a C array passed to a channel program will be stored in arr[1] when accessed from Lua.
Lua return statements take the form:
return ret0, ret1, ret2,
...
Return statements returning multiple values are permitted internally in a channel program script, but attempting to return more than one value from the top level of the channel program is not permitted and will throw an error. However, tables containing multiple values can still be returned. If invoked from the command line, a return statement:
a = {foo="bar", baz=2} return a
Will be output formatted as:
Channel program fully executed with return value: return: baz: 2 foo: 'bar'
If the channel program encounters a fatal error while running, a non-zero exit status will be returned. If more information about the error is available, a singleton list will be returned detailing the error:
error: "error string, including
Lua stack trace"
If a fatal error is returned, the channel program may have not executed at all, may have partially executed, or may have fully executed but failed to pass a return value back to userland.
If the channel program exhausts an instruction or memory limit, a fatal error will be generated and the program will be stopped, leaving the program partially executed. No attempt is made to reverse or undo any operations already performed. Note that because both the instruction count and amount of memory used by a channel program are deterministic when run against the same inputs and filesystem state, as long as a channel program has run successfully once, you can guarantee that it will finish successfully against a similar size system.
If a channel program attempts to return too large a value, the program will fully execute but exit with a nonzero status code and no return value.
Note: ZFS API functions do not generate Fatal Errors when correctly invoked, they return an error code and the channel program continues executing. See the ZFS API section below for function-specific details on error return codes.
When invoking a channel program via the libzfs interface, it is necessary to translate arguments and return values from Lua values to their C equivalents, and vice-versa.
There is a correspondence between nvlist values in C and Lua tables. A Lua table which is returned from the channel program will be recursively converted to an nvlist, with table values converted to their natural equivalents:
string | -> | string | |
number | -> | int64 | |
boolean | -> | boolean_value | |
nil | -> | boolean (no value) | |
table | -> | nvlist |
Likewise, table keys are replaced by string equivalents as follows:
string | -> | no change | |
number | -> | signed decimal string ("%lld") | |
boolean | -> | "true" | "false" |
Any collision of table key strings (for example, the string "true" and a true boolean value) will cause a fatal error.
Lua numbers are represented internally as signed 64-bit integers.
The following Lua built-in base library functions are available:
assert | rawlen | collectgarbage | rawget | |
error | rawset | getmetatable | select | |
ipairs | setmetatable | next | tonumber | |
pairs | tostring | rawequal | type |
All functions in the coroutine, string, and table built-in submodules are also available. A complete list and documentation of these modules is available in the Lua manual.
The following functions base library functions have been disabled and are not available for use in channel programs:
dofile | loadfile | load | pcall | xpcall |
Each API function takes a fixed set of required positional arguments and optional keyword arguments. For example, the destroy function takes a single positional string argument (the name of the dataset to destroy) and an optional "defer" keyword boolean argument. When using parentheses to specify the arguments to a Lua function, only positional arguments can be used:
zfs.sync.destroy("rpool@snap")
To use keyword arguments, functions must be called with a single argument that is a Lua table containing entries mapping integers to positional arguments and strings to keyword arguments:
zfs.sync.destroy({1="rpool@snap",
defer=true})
The Lua language allows curly braces to be used in place of parenthesis as syntactic sugar for this calling convention:
zfs.sync.snapshot{"rpool@snap",
defer=true}
If an API function succeeds, it returns 0. If it fails, it returns an error code and the channel program continues executing. API functions do not generate Fatal Errors except in the case of an unrecoverable internal file system error.
In addition to returning an error code, some functions also return extra details describing what caused the error. This extra description is given as a second return value, and will always be a Lua table, or Nil if no error details were returned. Different keys will exist in the error details table depending on the function and error case. Any such function may be called expecting a single return value:
errno =
zfs.sync.promote(dataset)
Or, the error details can be retrieved:
errno, details = zfs.sync.promote(dataset)
if (errno == EEXIST) then
assert(details ~= Nil)
list_of_conflicting_snapshots = details
end
The following global aliases for API function error return codes are defined for use in channel programs:
EPERM | ECHILD | ENODEV | ENOSPC | ENOENT | EAGAIN | ENOTDIR | |
ESPIPE | ESRCH | ENOMEM | EISDIR | EROFS | EINTR | EACCES | |
EINVAL | EMLINK | EIO | EFAULT | ENFILE | EPIPE | ENXIO | |
ENOTBLK | EMFILE | EDOM | E2BIG | EBUSY | ENOTTY | ERANGE | |
ENOEXEC | EEXIST | ETXTBSY | EDQUOT | EBADF | EXDEV | EFBIG |
For detailed descriptions of the exact behavior of any ZFS administrative operations, see the main zfs(8) manual page.
zfs.debug
(msg)dtrace -n
'zfs-dbgmsg{trace(stringof(arg0))}'
zfs.exists
(dataset)zfs.get_prop
(dataset,
property)The available sync submodule functions are as follows:
zfs.sync.inherit
(dataset,
property)zfs
inherit
-S
option has
not been implemented. Returns 0 on success, or a nonzero error code if
the property could not be cleared.
zfs
inherit
command.zfs.sync.promote
(dataset)zfs.sync.rollback
(filesystem)zfs.sync.set_prop
(dataset,
property, value)zfs.sync.snapshot
(dataset)Note: Taking a snapshot will fail on any pool older than legacy version 27. To enable taking snapshots from ZCP scripts, the pool must be upgraded.
zfs.sync.rename_snapshot
(dataset,
oldsnapname,
newsnapname)zfs.sync.bookmark
(source,
newbookmark)Note: Bookmarking requires the corresponding pool feature to be enabled.
zfs.check.destroy
("fs")
returns 0 if
zfs.sync.destroy
("fs")
would successfully destroy the dataset.
The available zfs.check functions are:
zfs.check.promote
(dataset)zfs.check.rollback
(filesystem)zfs.check.set_property
(dataset,
property, value)zfs.check.snapshot
(dataset)for child in zfs.list.children
("rpool") do
...
end
The available zfs.list functions are:
zfs.list.clones
(snapshot)zfs.list.snapshots
(dataset)zfs.list.children
(dataset)zfs.list.bookmarks
(dataset)zfs.list.holds
(snapshot)zfs.list.properties
(dataset)zfs.list.user_properties
(dataset)zfs.list.system_properties
(dataset)The following channel program recursively destroys a filesystem and all its snapshots and children in a naive manner. Note that this does not involve any error handling or reporting.
function destroy_recursive(root) for child in zfs.list.children(root) do destroy_recursive(child) end for snap in zfs.list.snapshots(root) do zfs.sync.destroy(snap) end zfs.sync.destroy(root) end destroy_recursive("pool/somefs")
A more verbose and robust version of the same channel program, which properly detects and reports errors, and also takes the dataset to destroy as a command line argument, would be as follows:
succeeded = {} failed = {} function destroy_recursive(root) for child in zfs.list.children(root) do destroy_recursive(child) end for snap in zfs.list.snapshots(root) do err = zfs.sync.destroy(snap) if (err ~= 0) then failed[snap] = err else succeeded[snap] = err end end err = zfs.sync.destroy(root) if (err ~= 0) then failed[root] = err else succeeded[root] = err end end args = ... argv = args["argv"] destroy_recursive(argv[1]) results = {} results["succeeded"] = succeeded results["failed"] = failed return results
The following function performs a forced promote operation by attempting to promote the given clone and destroying any conflicting snapshots.
function force_promote(ds) errno, details = zfs.check.promote(ds) if (errno == EEXIST) then assert(details ~= Nil) for i, snap in ipairs(details) do zfs.sync.destroy(ds .. "@" .. snap) end elseif (errno ~= 0) then return errno end return zfs.sync.promote(ds) end
May 27, 2021 | OpenZFS |