]>
Commit | Line | Data |
---|---|---|
85ce3f4f | 1 | # |
2 | # Copyright 2015 ClusterHQ | |
3 | # | |
4 | # Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | # you may not use this file except in compliance with the License. | |
6 | # You may obtain a copy of the License at | |
7 | # | |
8 | # http://www.apache.org/licenses/LICENSE-2.0 | |
9 | # | |
10 | # Unless required by applicable law or agreed to in writing, software | |
11 | # distributed under the License is distributed on an "AS IS" BASIS, | |
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | # See the License for the specific language governing permissions and | |
14 | # limitations under the License. | |
15 | # | |
6abf9225 AG |
16 | |
17 | """ | |
18 | Python wrappers for libzfs_core interfaces. | |
19 | ||
20 | As a rule, there is a Python function for each C function. | |
21 | The signatures of the Python functions generally follow those of the | |
22 | functions, but the argument types are natural to Python. | |
23 | nvlists are wrapped as dictionaries or lists depending on their usage. | |
24 | Some parameters have default values depending on typical use for | |
25 | increased convenience. Output parameters are not used and return values | |
26 | are directly returned. Error conditions are signalled by exceptions | |
27 | rather than by integer error codes. | |
28 | """ | |
9de8c0cd | 29 | from __future__ import absolute_import, division, print_function |
6abf9225 AG |
30 | |
31 | import errno | |
32 | import functools | |
33 | import fcntl | |
34 | import os | |
35 | import struct | |
36 | import threading | |
37 | from . import exceptions | |
38 | from . import _error_translation as errors | |
39 | from .bindings import libzfs_core | |
85ce3f4f | 40 | from ._constants import ( # noqa: F401 |
41 | MAXNAMELEN, | |
42 | ZCP_DEFAULT_INSTRLIMIT, | |
43 | ZCP_DEFAULT_MEMLIMIT, | |
44 | WRAPPING_KEY_LEN, | |
45 | zfs_key_location, | |
46 | zfs_keyformat, | |
47 | zio_encrypt | |
48 | ) | |
49 | from .ctypes import ( | |
50 | int32_t, | |
51 | uint64_t | |
52 | ) | |
6abf9225 AG |
53 | from ._nvlist import nvlist_in, nvlist_out |
54 | ||
55 | ||
85ce3f4f | 56 | def _uncommitted(depends_on=None): |
57 | ''' | |
58 | Mark an API function as being an uncommitted extension that might not be | |
59 | available. | |
60 | ||
61 | :param function depends_on: the function that would be checked instead of | |
62 | a decorated function. For example, if the decorated function uses | |
63 | another uncommitted function. | |
64 | ||
65 | This decorator transforms a decorated function to raise | |
66 | :exc:`NotImplementedError` if the C libzfs_core library does not provide | |
67 | a function with the same name as the decorated function. | |
68 | ||
69 | The optional `depends_on` parameter can be provided if the decorated | |
70 | function does not directly call the C function but instead calls another | |
71 | Python function that follows the typical convention. | |
72 | One example is :func:`lzc_list_snaps` that calls :func:`lzc_list` that | |
73 | calls ``lzc_list`` in libzfs_core. | |
74 | ||
75 | This decorator is implemented using :func:`is_supported`. | |
76 | ''' | |
77 | def _uncommitted_decorator(func, depends_on=depends_on): | |
78 | @functools.wraps(func) | |
79 | def _f(*args, **kwargs): | |
80 | if not is_supported(_f): | |
81 | raise NotImplementedError(func.__name__) | |
82 | return func(*args, **kwargs) | |
83 | if depends_on is not None: | |
84 | _f._check_func = depends_on | |
85 | return _f | |
86 | return _uncommitted_decorator | |
87 | ||
88 | ||
89 | def lzc_create(name, ds_type='zfs', props=None, key=None): | |
6abf9225 AG |
90 | ''' |
91 | Create a ZFS filesystem or a ZFS volume ("zvol"). | |
92 | ||
93 | :param bytes name: a name of the dataset to be created. | |
85ce3f4f | 94 | :param str ds_type: the type of the dataset to be created, |
95 | currently supported types are "zfs" (the default) for a filesystem and | |
96 | "zvol" for a volume. | |
97 | :param props: a `dict` of ZFS dataset property name-value pairs | |
98 | (empty by default). | |
6abf9225 | 99 | :type props: dict of bytes:Any |
85ce3f4f | 100 | :param key: dataset encryption key data (empty by default). |
101 | :type key: bytes | |
6abf9225 AG |
102 | |
103 | :raises FilesystemExists: if a dataset with the given name already exists. | |
85ce3f4f | 104 | :raises ParentNotFound: if a parent dataset of the requested dataset does |
105 | not exist. | |
106 | :raises PropertyInvalid: if one or more of the specified properties is | |
107 | invalid or has an invalid type or value. | |
6abf9225 AG |
108 | :raises NameInvalid: if the name is not a valid dataset name. |
109 | :raises NameTooLong: if the name is too long. | |
85ce3f4f | 110 | :raises WrongParent: if the parent dataset of the requested dataset is not |
111 | a filesystem (e.g. ZVOL) | |
6abf9225 AG |
112 | ''' |
113 | if props is None: | |
114 | props = {} | |
85ce3f4f | 115 | if key is None: |
e5fb1dc5 | 116 | key = b"" |
85ce3f4f | 117 | else: |
118 | key = bytes(key) | |
6abf9225 AG |
119 | if ds_type == 'zfs': |
120 | ds_type = _lib.DMU_OST_ZFS | |
121 | elif ds_type == 'zvol': | |
122 | ds_type = _lib.DMU_OST_ZVOL | |
123 | else: | |
124 | raise exceptions.DatasetTypeInvalid(ds_type) | |
125 | nvlist = nvlist_in(props) | |
85ce3f4f | 126 | ret = _lib.lzc_create(name, ds_type, nvlist, key, len(key)) |
6abf9225 AG |
127 | errors.lzc_create_translate_error(ret, name, ds_type, props) |
128 | ||
129 | ||
130 | def lzc_clone(name, origin, props=None): | |
131 | ''' | |
132 | Clone a ZFS filesystem or a ZFS volume ("zvol") from a given snapshot. | |
133 | ||
134 | :param bytes name: a name of the dataset to be created. | |
135 | :param bytes origin: a name of the origin snapshot. | |
85ce3f4f | 136 | :param props: a `dict` of ZFS dataset property name-value pairs |
137 | (empty by default). | |
6abf9225 AG |
138 | :type props: dict of bytes:Any |
139 | ||
140 | :raises FilesystemExists: if a dataset with the given name already exists. | |
85ce3f4f | 141 | :raises DatasetNotFound: if either a parent dataset of the requested |
142 | dataset or the origin snapshot does not exist. | |
143 | :raises PropertyInvalid: if one or more of the specified properties is | |
144 | invalid or has an invalid type or value. | |
6abf9225 AG |
145 | :raises FilesystemNameInvalid: if the name is not a valid dataset name. |
146 | :raises SnapshotNameInvalid: if the origin is not a valid snapshot name. | |
147 | :raises NameTooLong: if the name or the origin name is too long. | |
148 | :raises PoolsDiffer: if the clone and the origin have different pool names. | |
149 | ||
150 | .. note:: | |
151 | Because of a deficiency of the underlying C interface | |
85ce3f4f | 152 | :exc:`.DatasetNotFound` can mean that either a parent filesystem of |
153 | the target or the origin snapshot does not exist. | |
6abf9225 | 154 | It is currently impossible to distinguish between the cases. |
85ce3f4f | 155 | :func:`lzc_hold` can be used to check that the snapshot exists and |
156 | ensure that it is not destroyed before cloning. | |
6abf9225 AG |
157 | ''' |
158 | if props is None: | |
159 | props = {} | |
160 | nvlist = nvlist_in(props) | |
161 | ret = _lib.lzc_clone(name, origin, nvlist) | |
162 | errors.lzc_clone_translate_error(ret, name, origin, props) | |
163 | ||
164 | ||
165 | def lzc_rollback(name): | |
166 | ''' | |
167 | Roll back a filesystem or volume to its most recent snapshot. | |
168 | ||
169 | Note that the latest snapshot may change if a new one is concurrently | |
170 | created or the current one is destroyed. lzc_rollback_to can be used | |
171 | to roll back to a specific latest snapshot. | |
172 | ||
173 | :param bytes name: a name of the dataset to be rolled back. | |
174 | :return: a name of the most recent snapshot. | |
175 | :rtype: bytes | |
176 | ||
177 | :raises FilesystemNotFound: if the dataset does not exist. | |
178 | :raises SnapshotNotFound: if the dataset does not have any snapshots. | |
179 | :raises NameInvalid: if the dataset name is invalid. | |
180 | :raises NameTooLong: if the dataset name is too long. | |
181 | ''' | |
182 | # Account for terminating NUL in C strings. | |
183 | snapnamep = _ffi.new('char[]', MAXNAMELEN + 1) | |
184 | ret = _lib.lzc_rollback(name, snapnamep, MAXNAMELEN + 1) | |
185 | errors.lzc_rollback_translate_error(ret, name) | |
186 | return _ffi.string(snapnamep) | |
187 | ||
85ce3f4f | 188 | |
6abf9225 AG |
189 | def lzc_rollback_to(name, snap): |
190 | ''' | |
191 | Roll back this filesystem or volume to the specified snapshot, if possible. | |
192 | ||
193 | :param bytes name: a name of the dataset to be rolled back. | |
194 | :param bytes snap: a name of the snapshot to be rolled back. | |
195 | ||
196 | :raises FilesystemNotFound: if the dataset does not exist. | |
197 | :raises SnapshotNotFound: if the dataset does not have any snapshots. | |
198 | :raises NameInvalid: if the dataset name is invalid. | |
199 | :raises NameTooLong: if the dataset name is too long. | |
200 | :raises SnapshotNotLatest: if the snapshot is not the latest. | |
201 | ''' | |
202 | ret = _lib.lzc_rollback_to(name, snap) | |
203 | errors.lzc_rollback_to_translate_error(ret, name, snap) | |
204 | ||
85ce3f4f | 205 | |
6abf9225 AG |
206 | def lzc_snapshot(snaps, props=None): |
207 | ''' | |
208 | Create snapshots. | |
209 | ||
210 | All snapshots must be in the same pool. | |
211 | ||
212 | Optionally snapshot properties can be set on all snapshots. | |
213 | Currently only user properties (prefixed with "user:") are supported. | |
214 | ||
215 | Either all snapshots are successfully created or none are created if | |
216 | an exception is raised. | |
217 | ||
218 | :param snaps: a list of names of snapshots to be created. | |
219 | :type snaps: list of bytes | |
85ce3f4f | 220 | :param props: a `dict` of ZFS dataset property name-value pairs |
221 | (empty by default). | |
6abf9225 AG |
222 | :type props: dict of bytes:bytes |
223 | ||
224 | :raises SnapshotFailure: if one or more snapshots could not be created. | |
225 | ||
226 | .. note:: | |
227 | :exc:`.SnapshotFailure` is a compound exception that provides at least | |
228 | one detailed error object in :attr:`SnapshotFailure.errors` `list`. | |
229 | ||
230 | .. warning:: | |
231 | The underlying implementation reports an individual, per-snapshot error | |
232 | only for :exc:`.SnapshotExists` condition and *sometimes* for | |
233 | :exc:`.NameTooLong`. | |
234 | In all other cases a single error is reported without connection to any | |
235 | specific snapshot name(s). | |
236 | ||
237 | This has the following implications: | |
238 | ||
85ce3f4f | 239 | * if multiple error conditions are encountered only one of them is |
240 | reported | |
6abf9225 AG |
241 | |
242 | * unless only one snapshot is requested then it is impossible to tell | |
243 | how many snapshots are problematic and what they are | |
244 | ||
245 | * only if there are no other error conditions :exc:`.SnapshotExists` | |
246 | is reported for all affected snapshots | |
247 | ||
248 | * :exc:`.NameTooLong` can behave either in the same way as | |
249 | :exc:`.SnapshotExists` or as all other exceptions. | |
85ce3f4f | 250 | The former is the case where the full snapshot name exceeds the |
251 | maximum allowed length but the short snapshot name (after '@') is | |
252 | within the limit. | |
6abf9225 AG |
253 | The latter is the case when the short name alone exceeds the maximum |
254 | allowed length. | |
255 | ''' | |
256 | snaps_dict = {name: None for name in snaps} | |
257 | errlist = {} | |
258 | snaps_nvlist = nvlist_in(snaps_dict) | |
259 | if props is None: | |
260 | props = {} | |
261 | props_nvlist = nvlist_in(props) | |
262 | with nvlist_out(errlist) as errlist_nvlist: | |
263 | ret = _lib.lzc_snapshot(snaps_nvlist, props_nvlist, errlist_nvlist) | |
264 | errors.lzc_snapshot_translate_errors(ret, errlist, snaps, props) | |
265 | ||
266 | ||
267 | lzc_snap = lzc_snapshot | |
268 | ||
269 | ||
270 | def lzc_destroy_snaps(snaps, defer): | |
271 | ''' | |
272 | Destroy snapshots. | |
273 | ||
274 | They must all be in the same pool. | |
275 | Snapshots that do not exist will be silently ignored. | |
276 | ||
277 | If 'defer' is not set, and a snapshot has user holds or clones, the | |
278 | destroy operation will fail and none of the snapshots will be | |
279 | destroyed. | |
280 | ||
281 | If 'defer' is set, and a snapshot has user holds or clones, it will be | |
282 | marked for deferred destruction, and will be destroyed when the last hold | |
283 | or clone is removed/destroyed. | |
284 | ||
285 | The operation succeeds if all snapshots were destroyed (or marked for | |
286 | later destruction if 'defer' is set) or didn't exist to begin with. | |
287 | ||
288 | :param snaps: a list of names of snapshots to be destroyed. | |
289 | :type snaps: list of bytes | |
290 | :param bool defer: whether to mark busy snapshots for deferred destruction | |
85ce3f4f | 291 | rather than immediately failing. |
6abf9225 | 292 | |
85ce3f4f | 293 | :raises SnapshotDestructionFailure: if one or more snapshots could not be |
294 | created. | |
6abf9225 AG |
295 | |
296 | .. note:: | |
85ce3f4f | 297 | :exc:`.SnapshotDestructionFailure` is a compound exception that |
298 | provides at least one detailed error object in | |
299 | :attr:`SnapshotDestructionFailure.errors` `list`. | |
6abf9225 AG |
300 | |
301 | Typical error is :exc:`SnapshotIsCloned` if `defer` is `False`. | |
85ce3f4f | 302 | The snapshot names are validated quite loosely and invalid names are |
303 | typically ignored as nonexisiting snapshots. | |
6abf9225 | 304 | |
85ce3f4f | 305 | A snapshot name referring to a filesystem that doesn't exist is |
306 | ignored. | |
6abf9225 AG |
307 | However, non-existent pool name causes :exc:`PoolNotFound`. |
308 | ''' | |
309 | snaps_dict = {name: None for name in snaps} | |
310 | errlist = {} | |
311 | snaps_nvlist = nvlist_in(snaps_dict) | |
312 | with nvlist_out(errlist) as errlist_nvlist: | |
313 | ret = _lib.lzc_destroy_snaps(snaps_nvlist, defer, errlist_nvlist) | |
314 | errors.lzc_destroy_snaps_translate_errors(ret, errlist, snaps, defer) | |
315 | ||
316 | ||
317 | def lzc_bookmark(bookmarks): | |
318 | ''' | |
319 | Create bookmarks. | |
320 | ||
85ce3f4f | 321 | :param bookmarks: a dict that maps names of wanted bookmarks to names of |
322 | existing snapshots. | |
6abf9225 | 323 | :type bookmarks: dict of bytes to bytes |
85ce3f4f | 324 | :raises BookmarkFailure: if any of the bookmarks can not be created for any |
325 | reason. | |
6abf9225 | 326 | |
85ce3f4f | 327 | The bookmarks `dict` maps from name of the bookmark |
328 | (e.g. :file:`{pool}/{fs}#{bmark}`) to the name of the snapshot | |
329 | (e.g. :file:`{pool}/{fs}@{snap}`). All the bookmarks and snapshots must | |
330 | be in the same pool. | |
6abf9225 AG |
331 | ''' |
332 | errlist = {} | |
333 | nvlist = nvlist_in(bookmarks) | |
334 | with nvlist_out(errlist) as errlist_nvlist: | |
335 | ret = _lib.lzc_bookmark(nvlist, errlist_nvlist) | |
336 | errors.lzc_bookmark_translate_errors(ret, errlist, bookmarks) | |
337 | ||
338 | ||
339 | def lzc_get_bookmarks(fsname, props=None): | |
340 | ''' | |
341 | Retrieve a listing of bookmarks for the given file system. | |
342 | ||
343 | :param bytes fsname: a name of the filesystem. | |
85ce3f4f | 344 | :param props: a `list` of properties that will be returned for each |
345 | bookmark. | |
6abf9225 AG |
346 | :type props: list of bytes |
347 | :return: a `dict` that maps the bookmarks' short names to their properties. | |
348 | :rtype: dict of bytes:dict | |
349 | ||
350 | :raises FilesystemNotFound: if the filesystem is not found. | |
351 | ||
352 | The following are valid properties on bookmarks: | |
353 | ||
354 | guid : integer | |
355 | globally unique identifier of the snapshot the bookmark refers to | |
356 | createtxg : integer | |
357 | txg when the snapshot the bookmark refers to was created | |
358 | creation : integer | |
359 | timestamp when the snapshot the bookmark refers to was created | |
360 | ||
361 | Any other properties passed in ``props`` are ignored without reporting | |
362 | any error. | |
363 | Values in the returned dictionary map the names of the requested properties | |
364 | to their respective values. | |
365 | ''' | |
366 | bmarks = {} | |
367 | if props is None: | |
368 | props = [] | |
369 | props_dict = {name: None for name in props} | |
370 | nvlist = nvlist_in(props_dict) | |
371 | with nvlist_out(bmarks) as bmarks_nvlist: | |
372 | ret = _lib.lzc_get_bookmarks(fsname, nvlist, bmarks_nvlist) | |
373 | errors.lzc_get_bookmarks_translate_error(ret, fsname, props) | |
374 | return bmarks | |
375 | ||
376 | ||
377 | def lzc_destroy_bookmarks(bookmarks): | |
378 | ''' | |
379 | Destroy bookmarks. | |
380 | ||
85ce3f4f | 381 | :param bookmarks: a list of the bookmarks to be destroyed. The bookmarks |
382 | are specified as :file:`{fs}#{bmark}`. | |
6abf9225 AG |
383 | :type bookmarks: list of bytes |
384 | ||
85ce3f4f | 385 | :raises BookmarkDestructionFailure: if any of the bookmarks may not be |
386 | destroyed. | |
6abf9225 AG |
387 | |
388 | The bookmarks must all be in the same pool. | |
389 | Bookmarks that do not exist will be silently ignored. | |
390 | This also includes the case where the filesystem component of the bookmark | |
391 | name does not exist. | |
392 | However, an invalid bookmark name will cause :exc:`.NameInvalid` error | |
393 | reported in :attr:`SnapshotDestructionFailure.errors`. | |
394 | ||
395 | Either all bookmarks that existed are destroyed or an exception is raised. | |
396 | ''' | |
397 | errlist = {} | |
398 | bmarks_dict = {name: None for name in bookmarks} | |
399 | nvlist = nvlist_in(bmarks_dict) | |
400 | with nvlist_out(errlist) as errlist_nvlist: | |
401 | ret = _lib.lzc_destroy_bookmarks(nvlist, errlist_nvlist) | |
402 | errors.lzc_destroy_bookmarks_translate_errors(ret, errlist, bookmarks) | |
403 | ||
404 | ||
405 | def lzc_snaprange_space(firstsnap, lastsnap): | |
406 | ''' | |
85ce3f4f | 407 | Calculate a size of data referenced by snapshots in the inclusive range |
408 | between the ``firstsnap`` and the ``lastsnap`` and not shared with any | |
409 | other datasets. | |
6abf9225 AG |
410 | |
411 | :param bytes firstsnap: the name of the first snapshot in the range. | |
412 | :param bytes lastsnap: the name of the last snapshot in the range. | |
413 | :return: the calculated stream size, in bytes. | |
414 | :rtype: `int` or `long` | |
415 | ||
416 | :raises SnapshotNotFound: if either of the snapshots does not exist. | |
417 | :raises NameInvalid: if the name of either snapshot is invalid. | |
418 | :raises NameTooLong: if the name of either snapshot is too long. | |
85ce3f4f | 419 | :raises SnapshotMismatch: if ``fromsnap`` is not an ancestor snapshot of |
420 | ``snapname``. | |
6abf9225 AG |
421 | :raises PoolsDiffer: if the snapshots belong to different pools. |
422 | ||
423 | ``lzc_snaprange_space`` calculates total size of blocks that exist | |
85ce3f4f | 424 | because they are referenced only by one or more snapshots in the given |
425 | range but no other dataset. | |
426 | In other words, this is the set of blocks that were born after the snap | |
427 | before firstsnap, and died before the snap after the last snap. | |
428 | Yet another interpretation is that the result of ``lzc_snaprange_space`` | |
429 | is the size of the space that would be freed if the snapshots in the range | |
430 | are destroyed. | |
431 | ||
432 | If the same snapshot is given as both the ``firstsnap`` and the | |
433 | ``lastsnap``. | |
6abf9225 AG |
434 | In that case ``lzc_snaprange_space`` calculates space used by the snapshot. |
435 | ''' | |
436 | valp = _ffi.new('uint64_t *') | |
437 | ret = _lib.lzc_snaprange_space(firstsnap, lastsnap, valp) | |
438 | errors.lzc_snaprange_space_translate_error(ret, firstsnap, lastsnap) | |
439 | return int(valp[0]) | |
440 | ||
441 | ||
442 | def lzc_hold(holds, fd=None): | |
443 | ''' | |
444 | Create *user holds* on snapshots. If there is a hold on a snapshot, | |
85ce3f4f | 445 | the snapshot can not be destroyed. (However, it can be marked for |
446 | deletion by :func:`lzc_destroy_snaps` ( ``defer`` = `True` ).) | |
6abf9225 | 447 | |
85ce3f4f | 448 | :param holds: the dictionary of names of the snapshots to hold mapped to |
449 | the hold names. | |
6abf9225 AG |
450 | :type holds: dict of bytes : bytes |
451 | :type fd: int or None | |
85ce3f4f | 452 | :param fd: if not None then it must be the result of :func:`os.open` |
453 | called as ``os.open("/dev/zfs", O_EXCL)``. | |
6abf9225 AG |
454 | :type fd: int or None |
455 | :return: a list of the snapshots that do not exist. | |
456 | :rtype: list of bytes | |
457 | ||
85ce3f4f | 458 | :raises HoldFailure: if a hold was impossible on one or more of the |
459 | snapshots. | |
460 | :raises BadHoldCleanupFD: if ``fd`` is not a valid file descriptor | |
461 | associated with :file:`/dev/zfs`. | |
6abf9225 AG |
462 | |
463 | The snapshots must all be in the same pool. | |
464 | ||
465 | If ``fd`` is not None, then when the ``fd`` is closed (including on process | |
466 | termination), the holds will be released. If the system is shut down | |
467 | uncleanly, the holds will be released when the pool is next opened | |
468 | or imported. | |
469 | ||
470 | Holds for snapshots which don't exist will be skipped and have an entry | |
471 | added to the return value, but will not cause an overall failure. | |
85ce3f4f | 472 | No exceptions is raised if all holds, for snapshots that existed, were |
473 | succesfully created. | |
474 | Otherwise :exc:`.HoldFailure` exception is raised and no holds will be | |
475 | created. | |
476 | :attr:`.HoldFailure.errors` may contain a single element for an error that | |
477 | is not specific to any hold / snapshot, or it may contain one or more | |
478 | elements detailing specific error per each affected hold. | |
6abf9225 AG |
479 | ''' |
480 | errlist = {} | |
481 | if fd is None: | |
482 | fd = -1 | |
483 | nvlist = nvlist_in(holds) | |
484 | with nvlist_out(errlist) as errlist_nvlist: | |
485 | ret = _lib.lzc_hold(nvlist, fd, errlist_nvlist) | |
486 | errors.lzc_hold_translate_errors(ret, errlist, holds, fd) | |
487 | # If there is no error (no exception raised by _handleErrList), but errlist | |
488 | # is not empty, then it contains missing snapshots. | |
9de8c0cd AR |
489 | assert all(errlist[x] == errno.ENOENT for x in errlist) |
490 | return list(errlist.keys()) | |
6abf9225 AG |
491 | |
492 | ||
493 | def lzc_release(holds): | |
494 | ''' | |
495 | Release *user holds* on snapshots. | |
496 | ||
497 | If the snapshot has been marked for | |
498 | deferred destroy (by lzc_destroy_snaps(defer=B_TRUE)), it does not have | |
499 | any clones, and all the user holds are removed, then the snapshot will be | |
500 | destroyed. | |
501 | ||
502 | The snapshots must all be in the same pool. | |
503 | ||
504 | :param holds: a ``dict`` where keys are snapshot names and values are | |
85ce3f4f | 505 | lists of hold tags to remove. |
6abf9225 | 506 | :type holds: dict of bytes : list of bytes |
85ce3f4f | 507 | :return: a list of any snapshots that do not exist and of any tags that do |
508 | not exist for existing snapshots. | |
509 | Such tags are qualified with a corresponding snapshot name using the | |
510 | following format :file:`{pool}/{fs}@{snap}#{tag}` | |
6abf9225 AG |
511 | :rtype: list of bytes |
512 | ||
85ce3f4f | 513 | :raises HoldReleaseFailure: if one or more existing holds could not be |
514 | released. | |
6abf9225 AG |
515 | |
516 | Holds which failed to release because they didn't exist will have an entry | |
517 | added to errlist, but will not cause an overall failure. | |
518 | ||
519 | This call is success if ``holds`` was empty or all holds that | |
520 | existed, were successfully removed. | |
521 | Otherwise an exception will be raised. | |
522 | ''' | |
523 | errlist = {} | |
524 | holds_dict = {} | |
9de8c0cd AR |
525 | for snap in holds: |
526 | hold_list = holds[snap] | |
6abf9225 AG |
527 | if not isinstance(hold_list, list): |
528 | raise TypeError('holds must be in a list') | |
529 | holds_dict[snap] = {hold: None for hold in hold_list} | |
530 | nvlist = nvlist_in(holds_dict) | |
531 | with nvlist_out(errlist) as errlist_nvlist: | |
532 | ret = _lib.lzc_release(nvlist, errlist_nvlist) | |
533 | errors.lzc_release_translate_errors(ret, errlist, holds) | |
534 | # If there is no error (no exception raised by _handleErrList), but errlist | |
535 | # is not empty, then it contains missing snapshots and tags. | |
9de8c0cd AR |
536 | assert all(errlist[x] == errno.ENOENT for x in errlist) |
537 | return list(errlist.keys()) | |
6abf9225 AG |
538 | |
539 | ||
540 | def lzc_get_holds(snapname): | |
541 | ''' | |
542 | Retrieve list of *user holds* on the specified snapshot. | |
543 | ||
544 | :param bytes snapname: the name of the snapshot. | |
545 | :return: holds on the snapshot along with their creation times | |
85ce3f4f | 546 | in seconds since the epoch |
6abf9225 AG |
547 | :rtype: dict of bytes : int |
548 | ''' | |
549 | holds = {} | |
550 | with nvlist_out(holds) as nvlist: | |
551 | ret = _lib.lzc_get_holds(snapname, nvlist) | |
552 | errors.lzc_get_holds_translate_error(ret, snapname) | |
553 | return holds | |
554 | ||
555 | ||
556 | def lzc_send(snapname, fromsnap, fd, flags=None): | |
557 | ''' | |
558 | Generate a zfs send stream for the specified snapshot and write it to | |
559 | the specified file descriptor. | |
560 | ||
561 | :param bytes snapname: the name of the snapshot to send. | |
562 | :param fromsnap: if not None the name of the starting snapshot | |
85ce3f4f | 563 | for the incremental stream. |
6abf9225 AG |
564 | :type fromsnap: bytes or None |
565 | :param int fd: the file descriptor to write the send stream to. | |
85ce3f4f | 566 | :param flags: the flags that control what enhanced features can be used in |
567 | the stream. | |
6abf9225 AG |
568 | :type flags: list of bytes |
569 | ||
85ce3f4f | 570 | :raises SnapshotNotFound: if either the starting snapshot is not `None` and |
571 | does not exist, or if the ending snapshot does not exist. | |
6abf9225 AG |
572 | :raises NameInvalid: if the name of either snapshot is invalid. |
573 | :raises NameTooLong: if the name of either snapshot is too long. | |
85ce3f4f | 574 | :raises SnapshotMismatch: if ``fromsnap`` is not an ancestor snapshot of |
575 | ``snapname``. | |
6abf9225 AG |
576 | :raises PoolsDiffer: if the snapshots belong to different pools. |
577 | :raises IOError: if an input / output error occurs while writing to ``fd``. | |
85ce3f4f | 578 | :raises UnknownStreamFeature: if the ``flags`` contain an unknown flag |
579 | name. | |
6abf9225 AG |
580 | |
581 | If ``fromsnap`` is None, a full (non-incremental) stream will be sent. | |
582 | If ``fromsnap`` is not None, it must be the full name of a snapshot or | |
85ce3f4f | 583 | bookmark to send an incremental from, e.g. |
584 | :file:`{pool}/{fs}@{earlier_snap}` or :file:`{pool}/{fs}#{earlier_bmark}`. | |
6abf9225 | 585 | |
85ce3f4f | 586 | The specified snapshot or bookmark must represent an earlier point in the |
587 | history of ``snapname``. | |
588 | It can be an earlier snapshot in the same filesystem or zvol as | |
589 | ``snapname``, or it can be the origin of ``snapname``'s filesystem, or an | |
590 | earlier snapshot in the origin, etc. | |
591 | ``fromsnap`` must be strictly an earlier snapshot, specifying the same | |
592 | snapshot as both ``fromsnap`` and ``snapname`` is an error. | |
6abf9225 AG |
593 | |
594 | If ``flags`` contains *"large_blocks"*, the stream is permitted | |
85ce3f4f | 595 | to contain ``DRR_WRITE`` records with ``drr_length`` > 128K, |
596 | and ``DRR_OBJECT`` records with ``drr_blksz`` > 128K. | |
6abf9225 AG |
597 | |
598 | If ``flags`` contains *"embedded_data"*, the stream is permitted | |
599 | to contain ``DRR_WRITE_EMBEDDED`` records with | |
600 | ``drr_etype`` == ``BP_EMBEDDED_TYPE_DATA``, | |
601 | which the receiving system must support (as indicated by support | |
602 | for the *embedded_data* feature). | |
603 | ||
85ce3f4f | 604 | If ``flags`` contains *"compress"*, the stream is generated by using |
605 | compressed WRITE records for blocks which are compressed on disk and | |
606 | in memory. If the *lz4_compress* feature is active on the sending | |
607 | system, then the receiving system must have that feature enabled as well. | |
608 | ||
609 | If ``flags`` contains *"raw"*, the stream is generated, for encrypted | |
610 | datasets, by sending data exactly as it exists on disk. This allows | |
611 | backups to be taken even if encryption keys are not currently loaded. | |
612 | ||
6abf9225 AG |
613 | .. note:: |
614 | ``lzc_send`` can actually accept a filesystem name as the ``snapname``. | |
615 | In that case ``lzc_send`` acts as if a temporary snapshot was created | |
85ce3f4f | 616 | after the start of the call and before the stream starts being |
617 | produced. | |
6abf9225 AG |
618 | |
619 | .. note:: | |
85ce3f4f | 620 | ``lzc_send`` does not return until all of the stream is written to |
621 | ``fd``. | |
6abf9225 AG |
622 | |
623 | .. note:: | |
624 | ``lzc_send`` does *not* close ``fd`` upon returning. | |
625 | ''' | |
626 | if fromsnap is not None: | |
627 | c_fromsnap = fromsnap | |
628 | else: | |
629 | c_fromsnap = _ffi.NULL | |
630 | c_flags = 0 | |
631 | if flags is None: | |
632 | flags = [] | |
633 | for flag in flags: | |
634 | c_flag = { | |
85ce3f4f | 635 | 'embedded_data': _lib.LZC_SEND_FLAG_EMBED_DATA, |
636 | 'large_blocks': _lib.LZC_SEND_FLAG_LARGE_BLOCK, | |
637 | 'compress': _lib.LZC_SEND_FLAG_COMPRESS, | |
638 | 'raw': _lib.LZC_SEND_FLAG_RAW, | |
6abf9225 AG |
639 | }.get(flag) |
640 | if c_flag is None: | |
641 | raise exceptions.UnknownStreamFeature(flag) | |
642 | c_flags |= c_flag | |
643 | ||
644 | ret = _lib.lzc_send(snapname, c_fromsnap, fd, c_flags) | |
645 | errors.lzc_send_translate_error(ret, snapname, fromsnap, fd, flags) | |
646 | ||
647 | ||
648 | def lzc_send_space(snapname, fromsnap=None, flags=None): | |
649 | ''' | |
650 | Estimate size of a full or incremental backup stream | |
651 | given the optional starting snapshot and the ending snapshot. | |
652 | ||
85ce3f4f | 653 | :param bytes snapname: the name of the snapshot for which the estimate |
654 | should be done. | |
6abf9225 | 655 | :param fromsnap: the optional starting snapshot name. |
85ce3f4f | 656 | If not `None` then an incremental stream size is estimated, otherwise |
657 | a full stream is esimated. | |
6abf9225 AG |
658 | :type fromsnap: `bytes` or `None` |
659 | :param flags: the flags that control what enhanced features can be used | |
85ce3f4f | 660 | in the stream. |
6abf9225 AG |
661 | :type flags: list of bytes |
662 | ||
663 | :return: the estimated stream size, in bytes. | |
664 | :rtype: `int` or `long` | |
665 | ||
85ce3f4f | 666 | :raises SnapshotNotFound: if either the starting snapshot is not `None` and |
667 | does not exist, or if the ending snapshot does not exist. | |
6abf9225 AG |
668 | :raises NameInvalid: if the name of either snapshot is invalid. |
669 | :raises NameTooLong: if the name of either snapshot is too long. | |
85ce3f4f | 670 | :raises SnapshotMismatch: if ``fromsnap`` is not an ancestor snapshot of |
671 | ``snapname``. | |
6abf9225 AG |
672 | :raises PoolsDiffer: if the snapshots belong to different pools. |
673 | ||
674 | ``fromsnap``, if not ``None``, must be strictly an earlier snapshot, | |
85ce3f4f | 675 | specifying the same snapshot as both ``fromsnap`` and ``snapname`` is an |
676 | error. | |
6abf9225 AG |
677 | ''' |
678 | if fromsnap is not None: | |
679 | c_fromsnap = fromsnap | |
680 | else: | |
681 | c_fromsnap = _ffi.NULL | |
682 | c_flags = 0 | |
683 | if flags is None: | |
684 | flags = [] | |
685 | for flag in flags: | |
686 | c_flag = { | |
85ce3f4f | 687 | 'embedded_data': _lib.LZC_SEND_FLAG_EMBED_DATA, |
688 | 'large_blocks': _lib.LZC_SEND_FLAG_LARGE_BLOCK, | |
689 | 'compress': _lib.LZC_SEND_FLAG_COMPRESS, | |
690 | 'raw': _lib.LZC_SEND_FLAG_RAW, | |
6abf9225 AG |
691 | }.get(flag) |
692 | if c_flag is None: | |
693 | raise exceptions.UnknownStreamFeature(flag) | |
694 | c_flags |= c_flag | |
695 | valp = _ffi.new('uint64_t *') | |
696 | ||
697 | ret = _lib.lzc_send_space(snapname, c_fromsnap, c_flags, valp) | |
698 | errors.lzc_send_space_translate_error(ret, snapname, fromsnap) | |
699 | return int(valp[0]) | |
700 | ||
701 | ||
702 | def lzc_receive(snapname, fd, force=False, raw=False, origin=None, props=None): | |
703 | ''' | |
704 | Receive from the specified ``fd``, creating the specified snapshot. | |
705 | ||
706 | :param bytes snapname: the name of the snapshot to create. | |
707 | :param int fd: the file descriptor from which to read the stream. | |
708 | :param bool force: whether to roll back or destroy the target filesystem | |
85ce3f4f | 709 | if that is required to receive the stream. |
6abf9225 | 710 | :param bool raw: whether this is a "raw" stream. |
85ce3f4f | 711 | :param origin: the optional origin snapshot name if the stream is for a |
712 | clone. | |
6abf9225 | 713 | :type origin: bytes or None |
85ce3f4f | 714 | :param props: the properties to set on the snapshot as *received* |
715 | properties. | |
6abf9225 AG |
716 | :type props: dict of bytes : Any |
717 | ||
85ce3f4f | 718 | :raises IOError: if an input / output error occurs while reading from the |
719 | ``fd``. | |
6abf9225 | 720 | :raises DatasetExists: if the snapshot named ``snapname`` already exists. |
85ce3f4f | 721 | :raises DatasetExists: if the stream is a full stream and the destination |
722 | filesystem already exists. | |
723 | :raises DatasetExists: if ``force`` is `True` but the destination | |
724 | filesystem could not be rolled back to a matching snapshot because a | |
725 | newer snapshot exists and it is an origin of a cloned filesystem. | |
6abf9225 | 726 | :raises StreamMismatch: if an incremental stream is received and the latest |
85ce3f4f | 727 | snapshot of the destination filesystem does not match the source |
728 | snapshot of the stream. | |
6abf9225 | 729 | :raises StreamMismatch: if a full stream is received and the destination |
85ce3f4f | 730 | filesystem already exists and it has at least one snapshot, and |
731 | ``force`` is `False`. | |
732 | :raises StreamMismatch: if an incremental clone stream is received but the | |
733 | specified ``origin`` is not the actual received origin. | |
734 | :raises DestinationModified: if an incremental stream is received and the | |
735 | destination filesystem has been modified since the last snapshot and | |
736 | ``force`` is `False`. | |
737 | :raises DestinationModified: if a full stream is received and the | |
738 | destination filesystem already exists and it does not have any | |
739 | snapshots, and ``force`` is `False`. | |
740 | :raises DatasetNotFound: if the destination filesystem and its parent do | |
741 | not exist. | |
742 | :raises DatasetNotFound: if the ``origin`` is not `None` and does not | |
743 | exist. | |
744 | :raises DatasetBusy: if ``force`` is `True` but the destination filesystem | |
745 | could not be rolled back to a matching snapshot because a newer | |
746 | snapshot is held and could not be destroyed. | |
6abf9225 | 747 | :raises DatasetBusy: if another receive operation is being performed on the |
85ce3f4f | 748 | destination filesystem. |
749 | :raises BadStream: if the stream is corrupt or it is not recognized or it | |
750 | is a compound stream or it is a clone stream, but ``origin`` is `None`. | |
751 | :raises BadStream: if a clone stream is received and the destination | |
752 | filesystem already exists. | |
6abf9225 | 753 | :raises StreamFeatureNotSupported: if the stream has a feature that is not |
85ce3f4f | 754 | supported on this side. |
6abf9225 AG |
755 | :raises NameInvalid: if the name of either snapshot is invalid. |
756 | :raises NameTooLong: if the name of either snapshot is too long. | |
d8d418ff | 757 | :raises WrongParent: if the parent dataset of the received destination is |
758 | not a filesystem (e.g. ZVOL) | |
6abf9225 AG |
759 | |
760 | .. note:: | |
761 | The ``origin`` is ignored if the actual stream is an incremental stream | |
762 | that is not a clone stream and the destination filesystem exists. | |
763 | If the stream is a full stream and the destination filesystem does not | |
85ce3f4f | 764 | exist then the ``origin`` is checked for existence: if it does not |
765 | exist :exc:`.DatasetNotFound` is raised, otherwise | |
766 | :exc:`.StreamMismatch` is raised, because that snapshot can not have | |
767 | any relation to the stream. | |
6abf9225 AG |
768 | |
769 | .. note:: | |
85ce3f4f | 770 | If ``force`` is `True` and the stream is incremental then the |
771 | destination filesystem is rolled back to a matching source snapshot if | |
772 | necessary. Intermediate snapshots are destroyed in that case. | |
6abf9225 AG |
773 | |
774 | However, none of the existing snapshots may have the same name as | |
775 | ``snapname`` even if such a snapshot were to be destroyed. | |
85ce3f4f | 776 | The existing ``snapname`` snapshot always causes |
777 | :exc:`.SnapshotExists` to be raised. | |
6abf9225 | 778 | |
85ce3f4f | 779 | If ``force`` is `True` and the stream is a full stream then the |
780 | destination filesystem is replaced with the received filesystem unless | |
781 | the former has any snapshots. This prevents the destination filesystem | |
782 | from being rolled back / replaced. | |
6abf9225 AG |
783 | |
784 | .. note:: | |
785 | This interface does not work on dedup'd streams | |
786 | (those with ``DMU_BACKUP_FEATURE_DEDUP``). | |
787 | ||
788 | .. note:: | |
85ce3f4f | 789 | ``lzc_receive`` does not return until all of the stream is read from |
790 | ``fd`` and applied to the pool. | |
6abf9225 AG |
791 | |
792 | .. note:: | |
793 | ``lzc_receive`` does *not* close ``fd`` upon returning. | |
794 | ''' | |
795 | ||
796 | if origin is not None: | |
797 | c_origin = origin | |
798 | else: | |
799 | c_origin = _ffi.NULL | |
800 | if props is None: | |
801 | props = {} | |
802 | nvlist = nvlist_in(props) | |
803 | ret = _lib.lzc_receive(snapname, nvlist, c_origin, force, raw, fd) | |
85ce3f4f | 804 | errors.lzc_receive_translate_errors( |
805 | ret, snapname, fd, force, raw, False, False, origin, None | |
806 | ) | |
6abf9225 AG |
807 | |
808 | ||
809 | lzc_recv = lzc_receive | |
810 | ||
811 | ||
85ce3f4f | 812 | def lzc_exists(name): |
813 | ''' | |
814 | Check if a dataset (a filesystem, or a volume, or a snapshot) | |
815 | with the given name exists. | |
816 | ||
817 | :param bytes name: the dataset name to check. | |
818 | :return: `True` if the dataset exists, `False` otherwise. | |
819 | :rtype: bool | |
820 | ||
821 | .. note:: | |
822 | ``lzc_exists`` can not be used to check for existence of bookmarks. | |
823 | ''' | |
824 | ret = _lib.lzc_exists(name) | |
825 | return bool(ret) | |
826 | ||
827 | ||
828 | @_uncommitted() | |
829 | def lzc_change_key(fsname, crypt_cmd, props=None, key=None): | |
830 | ''' | |
831 | Change encryption key on the specified dataset. | |
832 | ||
833 | :param bytes fsname: the name of the dataset. | |
834 | :param str crypt_cmd: the encryption "command" to be executed, currently | |
835 | supported values are "new_key", "inherit", "force_new_key" and | |
836 | "force_inherit". | |
837 | :param props: a `dict` of encryption-related property name-value pairs; | |
838 | only "keyformat", "keylocation" and "pbkdf2iters" are supported | |
839 | (empty by default). | |
840 | :type props: dict of bytes:Any | |
841 | :param key: dataset encryption key data (empty by default). | |
842 | :type key: bytes | |
843 | ||
844 | :raises PropertyInvalid: if ``props`` contains invalid values. | |
845 | :raises FilesystemNotFound: if the dataset does not exist. | |
846 | :raises UnknownCryptCommand: if ``crypt_cmd`` is invalid. | |
847 | :raises EncryptionKeyNotLoaded: if the encryption key is not currently | |
848 | loaded and therefore cannot be changed. | |
849 | ''' | |
850 | if props is None: | |
851 | props = {} | |
852 | if key is None: | |
e5fb1dc5 | 853 | key = b"" |
85ce3f4f | 854 | else: |
855 | key = bytes(key) | |
856 | cmd = { | |
857 | 'new_key': _lib.DCP_CMD_NEW_KEY, | |
858 | 'inherit': _lib.DCP_CMD_INHERIT, | |
859 | 'force_new_key': _lib.DCP_CMD_FORCE_NEW_KEY, | |
860 | 'force_inherit': _lib.DCP_CMD_FORCE_INHERIT, | |
861 | }.get(crypt_cmd) | |
862 | if cmd is None: | |
863 | raise exceptions.UnknownCryptCommand(crypt_cmd) | |
864 | nvlist = nvlist_in(props) | |
865 | ret = _lib.lzc_change_key(fsname, cmd, nvlist, key, len(key)) | |
866 | errors.lzc_change_key_translate_error(ret, fsname) | |
867 | ||
868 | ||
869 | @_uncommitted() | |
870 | def lzc_load_key(fsname, noop, key): | |
871 | ''' | |
872 | Load or verify encryption key on the specified dataset. | |
873 | ||
874 | :param bytes fsname: the name of the dataset. | |
875 | :param bool noop: if `True` the encryption key will only be verified, | |
876 | not loaded. | |
877 | :param key: dataset encryption key data. | |
878 | :type key: bytes | |
879 | ||
880 | :raises FilesystemNotFound: if the dataset does not exist. | |
881 | :raises EncryptionKeyAlreadyLoaded: if the encryption key is already | |
882 | loaded. | |
883 | :raises EncryptionKeyInvalid: if the encryption key provided is incorrect. | |
884 | ''' | |
885 | ret = _lib.lzc_load_key(fsname, noop, key, len(key)) | |
886 | errors.lzc_load_key_translate_error(ret, fsname, noop) | |
887 | ||
888 | ||
889 | @_uncommitted() | |
890 | def lzc_unload_key(fsname): | |
891 | ''' | |
892 | Unload encryption key from the specified dataset. | |
893 | ||
894 | :param bytes fsname: the name of the dataset. | |
895 | ||
896 | :raises FilesystemNotFound: if the dataset does not exist. | |
897 | :raises DatasetBusy: if the encryption key is still being used. This | |
898 | usually occurs when the dataset is mounted. | |
899 | :raises EncryptionKeyNotLoaded: if the encryption key is not currently | |
900 | loaded. | |
901 | ''' | |
902 | ret = _lib.lzc_unload_key(fsname) | |
903 | errors.lzc_unload_key_translate_error(ret, fsname) | |
904 | ||
905 | ||
906 | def lzc_channel_program( | |
907 | poolname, program, instrlimit=ZCP_DEFAULT_INSTRLIMIT, | |
908 | memlimit=ZCP_DEFAULT_MEMLIMIT, params=None | |
909 | ): | |
910 | ''' | |
911 | Executes a script as a ZFS channel program on pool ``poolname``. | |
912 | ||
913 | :param bytes poolname: the name of the pool. | |
914 | :param bytes program: channel program text. | |
915 | :param int instrlimit: execution time limit, in milliseconds. | |
916 | :param int memlimit: execution memory limit, in bytes. | |
917 | :param bytes params: a `list` of parameters passed to the channel program | |
918 | (empty by default). | |
919 | :type params: dict of bytes:Any | |
920 | :return: a dictionary of result values procuced by the channel program, | |
921 | if any. | |
922 | :rtype: dict | |
923 | ||
924 | :raises PoolNotFound: if the pool does not exist. | |
925 | :raises ZCPLimitInvalid: if either instruction or memory limit are invalid. | |
926 | :raises ZCPSyntaxError: if the channel program contains syntax errors. | |
927 | :raises ZCPTimeout: if the channel program took too long to execute. | |
928 | :raises ZCPSpaceError: if the channel program exhausted the memory limit. | |
929 | :raises ZCPMemoryError: if the channel program return value was too large. | |
930 | :raises ZCPPermissionError: if the user lacks the permission to run the | |
931 | channel program. Channel programs must be run as root. | |
932 | :raises ZCPRuntimeError: if the channel program encountered a runtime | |
933 | error. | |
934 | ''' | |
935 | output = {} | |
e5fb1dc5 | 936 | params_nv = nvlist_in({b"argv": params}) |
85ce3f4f | 937 | with nvlist_out(output) as outnvl: |
938 | ret = _lib.lzc_channel_program( | |
939 | poolname, program, instrlimit, memlimit, params_nv, outnvl) | |
940 | errors.lzc_channel_program_translate_error( | |
e5fb1dc5 BB |
941 | ret, poolname, output.get(b"error")) |
942 | return output.get(b"return") | |
85ce3f4f | 943 | |
944 | ||
945 | def lzc_channel_program_nosync( | |
946 | poolname, program, instrlimit=ZCP_DEFAULT_INSTRLIMIT, | |
947 | memlimit=ZCP_DEFAULT_MEMLIMIT, params=None | |
948 | ): | |
949 | ''' | |
950 | Executes a script as a read-only ZFS channel program on pool ``poolname``. | |
951 | A read-only channel program works programmatically the same way as a | |
952 | normal channel program executed with | |
953 | :func:`lzc_channel_program`. The only difference is it runs exclusively in | |
954 | open-context and therefore can return faster. | |
955 | The downside to that, is that the program cannot change on-disk state by | |
956 | calling functions from the zfs.sync submodule. | |
957 | ||
958 | :param bytes poolname: the name of the pool. | |
959 | :param bytes program: channel program text. | |
960 | :param int instrlimit: execution time limit, in milliseconds. | |
961 | :param int memlimit: execution memory limit, in bytes. | |
962 | :param bytes params: a `list` of parameters passed to the channel program | |
963 | (empty by default). | |
964 | :type params: dict of bytes:Any | |
965 | :return: a dictionary of result values procuced by the channel program, | |
966 | if any. | |
967 | :rtype: dict | |
968 | ||
969 | :raises PoolNotFound: if the pool does not exist. | |
970 | :raises ZCPLimitInvalid: if either instruction or memory limit are invalid. | |
971 | :raises ZCPSyntaxError: if the channel program contains syntax errors. | |
972 | :raises ZCPTimeout: if the channel program took too long to execute. | |
973 | :raises ZCPSpaceError: if the channel program exhausted the memory limit. | |
974 | :raises ZCPMemoryError: if the channel program return value was too large. | |
975 | :raises ZCPPermissionError: if the user lacks the permission to run the | |
976 | channel program. Channel programs must be run as root. | |
977 | :raises ZCPRuntimeError: if the channel program encountered a runtime | |
978 | error. | |
979 | ''' | |
980 | output = {} | |
e5fb1dc5 | 981 | params_nv = nvlist_in({b"argv": params}) |
85ce3f4f | 982 | with nvlist_out(output) as outnvl: |
983 | ret = _lib.lzc_channel_program_nosync( | |
984 | poolname, program, instrlimit, memlimit, params_nv, outnvl) | |
985 | errors.lzc_channel_program_translate_error( | |
e5fb1dc5 BB |
986 | ret, poolname, output.get(b"error")) |
987 | return output.get(b"return") | |
85ce3f4f | 988 | |
989 | ||
990 | def lzc_receive_resumable( | |
991 | snapname, fd, force=False, raw=False, origin=None, props=None | |
992 | ): | |
993 | ''' | |
994 | Like :func:`lzc_receive`, but if the receive fails due to premature stream | |
995 | termination, the intermediate state will be preserved on disk. In this | |
996 | case, ECKSUM will be returned. The receive may subsequently be resumed | |
997 | with a resuming send stream generated by lzc_send_resume(). | |
998 | ||
999 | :param bytes snapname: the name of the snapshot to create. | |
1000 | :param int fd: the file descriptor from which to read the stream. | |
1001 | :param bool force: whether to roll back or destroy the target filesystem | |
1002 | if that is required to receive the stream. | |
1003 | :param bool raw: whether this is a "raw" stream. | |
1004 | :param origin: the optional origin snapshot name if the stream is for a | |
1005 | clone. | |
1006 | :type origin: bytes or None | |
1007 | :param props: the properties to set on the snapshot as *received* | |
1008 | properties. | |
1009 | :type props: dict of bytes : Any | |
1010 | ||
1011 | :raises IOError: if an input / output error occurs while reading from the | |
1012 | ``fd``. | |
1013 | :raises DatasetExists: if the snapshot named ``snapname`` already exists. | |
1014 | :raises DatasetExists: if the stream is a full stream and the destination | |
1015 | filesystem already exists. | |
1016 | :raises DatasetExists: if ``force`` is `True` but the destination | |
1017 | filesystem could not be rolled back to a matching snapshot because a | |
1018 | newer snapshot exists and it is an origin of a cloned filesystem. | |
1019 | :raises StreamMismatch: if an incremental stream is received and the latest | |
1020 | snapshot of the destination filesystem does not match the source | |
1021 | snapshot of the stream. | |
1022 | :raises StreamMismatch: if a full stream is received and the destination | |
1023 | filesystem already exists and it has at least one snapshot, and | |
1024 | ``force`` is `False`. | |
1025 | :raises StreamMismatch: if an incremental clone stream is received but the | |
1026 | specified ``origin`` is not the actual received origin. | |
1027 | :raises DestinationModified: if an incremental stream is received and the | |
1028 | destination filesystem has been modified since the last snapshot and | |
1029 | ``force`` is `False`. | |
1030 | :raises DestinationModified: if a full stream is received and the | |
1031 | destination filesystem already exists and it does not have any | |
1032 | snapshots, and ``force`` is `False`. | |
1033 | :raises DatasetNotFound: if the destination filesystem and its parent do | |
1034 | not exist. | |
1035 | :raises DatasetNotFound: if the ``origin`` is not `None` and does not | |
1036 | exist. | |
1037 | :raises DatasetBusy: if ``force`` is `True` but the destination filesystem | |
1038 | could not be rolled back to a matching snapshot because a newer | |
1039 | snapshot is held and could not be destroyed. | |
1040 | :raises DatasetBusy: if another receive operation is being performed on the | |
1041 | destination filesystem. | |
1042 | :raises BadStream: if the stream is corrupt or it is not recognized or it | |
1043 | is a compound stream or it is a clone stream, but ``origin`` is `None`. | |
1044 | :raises BadStream: if a clone stream is received and the destination | |
1045 | filesystem already exists. | |
1046 | :raises StreamFeatureNotSupported: if the stream has a feature that is not | |
1047 | supported on this side. | |
1048 | :raises NameInvalid: if the name of either snapshot is invalid. | |
1049 | :raises NameTooLong: if the name of either snapshot is too long. | |
1050 | ''' | |
1051 | ||
1052 | if origin is not None: | |
1053 | c_origin = origin | |
1054 | else: | |
1055 | c_origin = _ffi.NULL | |
1056 | if props is None: | |
1057 | props = {} | |
1058 | nvlist = nvlist_in(props) | |
1059 | ret = _lib.lzc_receive_resumable( | |
1060 | snapname, nvlist, c_origin, force, raw, fd) | |
1061 | errors.lzc_receive_translate_errors( | |
1062 | ret, snapname, fd, force, raw, False, False, origin, None) | |
1063 | ||
1064 | ||
1065 | def lzc_receive_with_header( | |
1066 | snapname, fd, begin_record, force=False, resumable=False, raw=False, | |
1067 | origin=None, props=None | |
1068 | ): | |
6abf9225 AG |
1069 | ''' |
1070 | Like :func:`lzc_receive`, but allows the caller to read the begin record | |
1071 | and then to pass it in. | |
1072 | ||
1073 | That could be useful if the caller wants to derive, for example, | |
1074 | the snapname or the origin parameters based on the information contained in | |
1075 | the begin record. | |
85ce3f4f | 1076 | :func:`receive_header` can be used to receive the begin record from the |
1077 | file descriptor. | |
6abf9225 AG |
1078 | |
1079 | :param bytes snapname: the name of the snapshot to create. | |
1080 | :param int fd: the file descriptor from which to read the stream. | |
85ce3f4f | 1081 | :param begin_record: the stream's begin record. |
1082 | :type begin_record: ``cffi`` `CData` representing the dmu_replay_record_t | |
1083 | structure. | |
6abf9225 | 1084 | :param bool force: whether to roll back or destroy the target filesystem |
85ce3f4f | 1085 | if that is required to receive the stream. |
1086 | :param bool resumable: whether this stream should be treated as resumable. | |
1087 | If the receive fails due to premature stream termination, the | |
1088 | intermediate state will be preserved on disk and may subsequently be | |
1089 | resumed with :func:`lzc_send_resume`. | |
1090 | :param bool raw: whether this is a "raw" stream. | |
1091 | :param origin: the optional origin snapshot name if the stream is for a | |
1092 | clone. | |
6abf9225 | 1093 | :type origin: bytes or None |
85ce3f4f | 1094 | :param props: the properties to set on the snapshot as *received* |
1095 | properties. | |
6abf9225 AG |
1096 | :type props: dict of bytes : Any |
1097 | ||
85ce3f4f | 1098 | :raises IOError: if an input / output error occurs while reading from the |
1099 | ``fd``. | |
6abf9225 | 1100 | :raises DatasetExists: if the snapshot named ``snapname`` already exists. |
85ce3f4f | 1101 | :raises DatasetExists: if the stream is a full stream and the destination |
1102 | filesystem already exists. | |
1103 | :raises DatasetExists: if ``force`` is `True` but the destination | |
1104 | filesystem could not be rolled back to a matching snapshot because a | |
1105 | newer snapshot exists and it is an origin of a cloned filesystem. | |
6abf9225 | 1106 | :raises StreamMismatch: if an incremental stream is received and the latest |
85ce3f4f | 1107 | snapshot of the destination filesystem does not match the source |
1108 | snapshot of the stream. | |
6abf9225 | 1109 | :raises StreamMismatch: if a full stream is received and the destination |
85ce3f4f | 1110 | filesystem already exists and it has at least one snapshot, and |
1111 | ``force`` is `False`. | |
1112 | :raises StreamMismatch: if an incremental clone stream is received but the | |
1113 | specified ``origin`` is not the actual received origin. | |
1114 | :raises DestinationModified: if an incremental stream is received and the | |
1115 | destination filesystem has been modified since the last snapshot and | |
1116 | ``force`` is `False`. | |
1117 | :raises DestinationModified: if a full stream is received and the | |
1118 | destination filesystem already exists and it does not have any | |
1119 | snapshots, and ``force`` is `False`. | |
1120 | :raises DatasetNotFound: if the destination filesystem and its parent do | |
1121 | not exist. | |
1122 | :raises DatasetNotFound: if the ``origin`` is not `None` and does not | |
1123 | exist. | |
1124 | :raises DatasetBusy: if ``force`` is `True` but the destination filesystem | |
1125 | could not be rolled back to a matching snapshot because a newer | |
1126 | snapshot is held and could not be destroyed. | |
6abf9225 | 1127 | :raises DatasetBusy: if another receive operation is being performed on the |
85ce3f4f | 1128 | destination filesystem. |
1129 | :raises BadStream: if the stream is corrupt or it is not recognized or it | |
1130 | is a compound stream or it is a clone stream, but ``origin`` is `None`. | |
1131 | :raises BadStream: if a clone stream is received and the destination | |
1132 | filesystem already exists. | |
6abf9225 | 1133 | :raises StreamFeatureNotSupported: if the stream has a feature that is not |
85ce3f4f | 1134 | supported on this side. |
6abf9225 AG |
1135 | :raises NameInvalid: if the name of either snapshot is invalid. |
1136 | :raises NameTooLong: if the name of either snapshot is too long. | |
1137 | ''' | |
1138 | ||
1139 | if origin is not None: | |
1140 | c_origin = origin | |
1141 | else: | |
1142 | c_origin = _ffi.NULL | |
1143 | if props is None: | |
1144 | props = {} | |
1145 | nvlist = nvlist_in(props) | |
85ce3f4f | 1146 | ret = _lib.lzc_receive_with_header( |
1147 | snapname, nvlist, c_origin, force, resumable, raw, fd, begin_record) | |
1148 | errors.lzc_receive_translate_errors( | |
1149 | ret, snapname, fd, force, raw, False, False, origin, None) | |
6abf9225 AG |
1150 | |
1151 | ||
1152 | def receive_header(fd): | |
1153 | ''' | |
85ce3f4f | 1154 | Read the begin record of the ZFS backup stream from the given file |
1155 | descriptor. | |
6abf9225 AG |
1156 | |
1157 | This is a helper function for :func:`lzc_receive_with_header`. | |
1158 | ||
1159 | :param int fd: the file descriptor from which to read the stream. | |
85ce3f4f | 1160 | :return: a tuple with two elements where the first one is a Python `dict` |
1161 | representing the fields of the begin record and the second one is an | |
1162 | opaque object suitable for passing to :func:`lzc_receive_with_header`. | |
1163 | :raises IOError: if an input / output error occurs while reading from the | |
1164 | ``fd``. | |
6abf9225 AG |
1165 | |
1166 | At present the following fields can be of interest in the header: | |
1167 | ||
1168 | drr_toname : bytes | |
1169 | the name of the snapshot for which the stream has been created | |
1170 | drr_toguid : integer | |
1171 | the GUID of the snapshot for which the stream has been created | |
1172 | drr_fromguid : integer | |
85ce3f4f | 1173 | the GUID of the starting snapshot in the case the stream is |
1174 | incremental, zero otherwise | |
6abf9225 AG |
1175 | drr_flags : integer |
1176 | the flags describing the stream's properties | |
1177 | drr_type : integer | |
1178 | the type of the dataset for which the stream has been created | |
1179 | (volume, filesystem) | |
1180 | ''' | |
85ce3f4f | 1181 | # read sizeof(dmu_replay_record_t) bytes directly into the memort backing |
1182 | # 'record' | |
6abf9225 AG |
1183 | record = _ffi.new("dmu_replay_record_t *") |
1184 | _ffi.buffer(record)[:] = os.read(fd, _ffi.sizeof(record[0])) | |
1185 | # get drr_begin member and its representation as a Pythn dict | |
1186 | drr_begin = record.drr_u.drr_begin | |
1187 | header = {} | |
1188 | for field, descr in _ffi.typeof(drr_begin).fields: | |
1189 | if descr.type.kind == 'primitive': | |
1190 | header[field] = getattr(drr_begin, field) | |
1191 | elif descr.type.kind == 'enum': | |
1192 | header[field] = getattr(drr_begin, field) | |
1193 | elif descr.type.kind == 'array' and descr.type.item.cname == 'char': | |
1194 | header[field] = _ffi.string(getattr(drr_begin, field)) | |
1195 | else: | |
85ce3f4f | 1196 | raise TypeError( |
1197 | 'Unexpected field type in drr_begin: ' + str(descr.type)) | |
6abf9225 AG |
1198 | return (header, record) |
1199 | ||
1200 | ||
85ce3f4f | 1201 | @_uncommitted() |
1202 | def lzc_receive_one( | |
1203 | snapname, fd, begin_record, force=False, resumable=False, raw=False, | |
1204 | origin=None, props=None, cleanup_fd=-1, action_handle=0 | |
1205 | ): | |
6abf9225 | 1206 | ''' |
85ce3f4f | 1207 | Like :func:`lzc_receive`, but allows the caller to pass all supported |
1208 | arguments and retrieve all values returned. The only additional input | |
1209 | parameter is 'cleanup_fd' which is used to set a cleanup-on-exit file | |
1210 | descriptor. | |
1211 | ||
1212 | :param bytes snapname: the name of the snapshot to create. | |
1213 | :param int fd: the file descriptor from which to read the stream. | |
1214 | :param begin_record: the stream's begin record. | |
1215 | :type begin_record: ``cffi`` `CData` representing the dmu_replay_record_t | |
1216 | structure. | |
1217 | :param bool force: whether to roll back or destroy the target filesystem | |
1218 | if that is required to receive the stream. | |
1219 | :param bool resumable: whether this stream should be treated as resumable. | |
1220 | If the receive fails due to premature stream termination, the | |
1221 | intermediate state will be preserved on disk and may subsequently be | |
1222 | resumed with :func:`lzc_send_resume`. | |
1223 | :param bool raw: whether this is a "raw" stream. | |
1224 | :param origin: the optional origin snapshot name if the stream is for a | |
1225 | clone. | |
1226 | :type origin: bytes or None | |
1227 | :param props: the properties to set on the snapshot as *received* | |
1228 | properties. | |
1229 | :type props: dict of bytes : Any | |
1230 | :param int cleanup_fd: file descriptor used to set a cleanup-on-exit file | |
1231 | descriptor. | |
1232 | :param int action_handle: variable used to pass the handle for guid/ds | |
1233 | mapping: this should be set to zero on first call and will contain an | |
1234 | updated handle on success, which should be passed in subsequent calls. | |
1235 | ||
1236 | :return: a tuple with two elements where the first one is the number of | |
1237 | bytes read from the file descriptor and the second one is the | |
1238 | action_handle return value. | |
1239 | ||
1240 | :raises IOError: if an input / output error occurs while reading from the | |
1241 | ``fd``. | |
1242 | :raises DatasetExists: if the snapshot named ``snapname`` already exists. | |
1243 | :raises DatasetExists: if the stream is a full stream and the destination | |
1244 | filesystem already exists. | |
1245 | :raises DatasetExists: if ``force`` is `True` but the destination | |
1246 | filesystem could not be rolled back to a matching snapshot because a | |
1247 | newer snapshot exists and it is an origin of a cloned filesystem. | |
1248 | :raises StreamMismatch: if an incremental stream is received and the latest | |
1249 | snapshot of the destination filesystem does not match the source | |
1250 | snapshot of the stream. | |
1251 | :raises StreamMismatch: if a full stream is received and the destination | |
1252 | filesystem already exists and it has at least one snapshot, and | |
1253 | ``force`` is `False`. | |
1254 | :raises StreamMismatch: if an incremental clone stream is received but the | |
1255 | specified ``origin`` is not the actual received origin. | |
1256 | :raises DestinationModified: if an incremental stream is received and the | |
1257 | destination filesystem has been modified since the last snapshot and | |
1258 | ``force`` is `False`. | |
1259 | :raises DestinationModified: if a full stream is received and the | |
1260 | destination filesystem already exists and it does not have any | |
1261 | snapshots, and ``force`` is `False`. | |
1262 | :raises DatasetNotFound: if the destination filesystem and its parent do | |
1263 | not exist. | |
1264 | :raises DatasetNotFound: if the ``origin`` is not `None` and does not | |
1265 | exist. | |
1266 | :raises DatasetBusy: if ``force`` is `True` but the destination filesystem | |
1267 | could not be rolled back to a matching snapshot because a newer | |
1268 | snapshot is held and could not be destroyed. | |
1269 | :raises DatasetBusy: if another receive operation is being performed on the | |
1270 | destination filesystem. | |
1271 | :raises BadStream: if the stream is corrupt or it is not recognized or it | |
1272 | is a compound stream or it is a clone stream, but ``origin`` is `None`. | |
1273 | :raises BadStream: if a clone stream is received and the destination | |
1274 | filesystem already exists. | |
1275 | :raises StreamFeatureNotSupported: if the stream has a feature that is not | |
1276 | supported on this side. | |
1277 | :raises ReceivePropertyFailure: if one or more of the specified properties | |
1278 | is invalid or has an invalid type or value. | |
1279 | :raises NameInvalid: if the name of either snapshot is invalid. | |
1280 | :raises NameTooLong: if the name of either snapshot is too long. | |
1281 | ''' | |
1282 | ||
1283 | if origin is not None: | |
1284 | c_origin = origin | |
1285 | else: | |
1286 | c_origin = _ffi.NULL | |
1287 | if action_handle is not None: | |
1288 | c_action_handle = _ffi.new("uint64_t *") | |
1289 | else: | |
1290 | c_action_handle = _ffi.NULL | |
1291 | c_read_bytes = _ffi.new("uint64_t *") | |
1292 | c_errflags = _ffi.new("uint64_t *") | |
1293 | if props is None: | |
1294 | props = {} | |
1295 | nvlist = nvlist_in(props) | |
1296 | properrs = {} | |
1297 | with nvlist_out(properrs) as c_errors: | |
1298 | ret = _lib.lzc_receive_one( | |
1299 | snapname, nvlist, c_origin, force, resumable, raw, fd, | |
1300 | begin_record, cleanup_fd, c_read_bytes, c_errflags, | |
1301 | c_action_handle, c_errors) | |
1302 | errors.lzc_receive_translate_errors( | |
1303 | ret, snapname, fd, force, raw, False, False, origin, properrs) | |
1304 | return (int(c_read_bytes[0]), action_handle) | |
1305 | ||
1306 | ||
1307 | @_uncommitted() | |
1308 | def lzc_receive_with_cmdprops( | |
1309 | snapname, fd, begin_record, force=False, resumable=False, raw=False, | |
d9c460a0 TC |
1310 | origin=None, props=None, cmdprops=None, key=None, cleanup_fd=-1, |
1311 | action_handle=0 | |
85ce3f4f | 1312 | ): |
1313 | ''' | |
1314 | Like :func:`lzc_receive_one`, but allows the caller to pass an additional | |
1315 | 'cmdprops' argument. The 'cmdprops' nvlist contains both override | |
1316 | ('zfs receive -o') and exclude ('zfs receive -x') properties. | |
1317 | ||
1318 | :param bytes snapname: the name of the snapshot to create. | |
1319 | :param int fd: the file descriptor from which to read the stream. | |
1320 | :param begin_record: the stream's begin record. | |
1321 | :type begin_record: ``cffi`` `CData` representing the dmu_replay_record_t | |
1322 | structure. | |
1323 | :param bool force: whether to roll back or destroy the target filesystem | |
1324 | if that is required to receive the stream. | |
1325 | :param bool resumable: whether this stream should be treated as resumable. | |
1326 | If the receive fails due to premature stream termination, the | |
1327 | intermediate state will be preserved on disk and may subsequently be | |
1328 | resumed with :func:`lzc_send_resume`. | |
1329 | :param bool raw: whether this is a "raw" stream. | |
1330 | :param origin: the optional origin snapshot name if the stream is for a | |
1331 | clone. | |
1332 | :type origin: bytes or None | |
1333 | :param props: the properties to set on the snapshot as *received* | |
1334 | properties. | |
1335 | :type props: dict of bytes : Any | |
1336 | :param cmdprops: the properties to set on the snapshot as local overrides | |
1337 | to *received* properties. `bool` values are forcefully inherited while | |
1338 | every other value is set locally as if the command "zfs set" was | |
1339 | invoked immediately before the receive. | |
1340 | :type cmdprops: dict of bytes : Any | |
d9c460a0 TC |
1341 | :param key: raw bytes representing user's wrapping key |
1342 | :type key: bytes | |
85ce3f4f | 1343 | :param int cleanup_fd: file descriptor used to set a cleanup-on-exit file |
1344 | descriptor. | |
1345 | :param int action_handle: variable used to pass the handle for guid/ds | |
1346 | mapping: this should be set to zero on first call and will contain an | |
1347 | updated handle on success, it should be passed in subsequent calls. | |
1348 | ||
1349 | :return: a tuple with two elements where the first one is the number of | |
1350 | bytes read from the file descriptor and the second one is the | |
1351 | action_handle return value. | |
1352 | ||
1353 | :raises IOError: if an input / output error occurs while reading from the | |
1354 | ``fd``. | |
1355 | :raises DatasetExists: if the snapshot named ``snapname`` already exists. | |
1356 | :raises DatasetExists: if the stream is a full stream and the destination | |
1357 | filesystem already exists. | |
1358 | :raises DatasetExists: if ``force`` is `True` but the destination | |
1359 | filesystem could not be rolled back to a matching snapshot because a | |
1360 | newer snapshot exists and it is an origin of a cloned filesystem. | |
1361 | :raises StreamMismatch: if an incremental stream is received and the latest | |
1362 | snapshot of the destination filesystem does not match the source | |
1363 | snapshot of the stream. | |
1364 | :raises StreamMismatch: if a full stream is received and the destination | |
1365 | filesystem already exists and it has at least one snapshot, and | |
1366 | ``force`` is `False`. | |
1367 | :raises StreamMismatch: if an incremental clone stream is received but the | |
1368 | specified ``origin`` is not the actual received origin. | |
1369 | :raises DestinationModified: if an incremental stream is received and the | |
1370 | destination filesystem has been modified since the last snapshot and | |
1371 | ``force`` is `False`. | |
1372 | :raises DestinationModified: if a full stream is received and the | |
1373 | destination filesystem already exists and it does not have any | |
1374 | snapshots, and ``force`` is `False`. | |
1375 | :raises DatasetNotFound: if the destination filesystem and its parent do | |
1376 | not exist. | |
1377 | :raises DatasetNotFound: if the ``origin`` is not `None` and does not | |
1378 | exist. | |
1379 | :raises DatasetBusy: if ``force`` is `True` but the destination filesystem | |
1380 | could not be rolled back to a matching snapshot because a newer | |
1381 | snapshot is held and could not be destroyed. | |
1382 | :raises DatasetBusy: if another receive operation is being performed on the | |
1383 | destination filesystem. | |
1384 | :raises BadStream: if the stream is corrupt or it is not recognized or it | |
1385 | is a compound stream or it is a clone stream, but ``origin`` is `None`. | |
1386 | :raises BadStream: if a clone stream is received and the destination | |
1387 | filesystem already exists. | |
1388 | :raises StreamFeatureNotSupported: if the stream has a feature that is not | |
1389 | supported on this side. | |
1390 | :raises ReceivePropertyFailure: if one or more of the specified properties | |
1391 | is invalid or has an invalid type or value. | |
1392 | :raises NameInvalid: if the name of either snapshot is invalid. | |
1393 | :raises NameTooLong: if the name of either snapshot is too long. | |
1394 | ''' | |
1395 | ||
1396 | if origin is not None: | |
1397 | c_origin = origin | |
1398 | else: | |
1399 | c_origin = _ffi.NULL | |
1400 | if action_handle is not None: | |
1401 | c_action_handle = _ffi.new("uint64_t *") | |
1402 | else: | |
1403 | c_action_handle = _ffi.NULL | |
1404 | c_read_bytes = _ffi.new("uint64_t *") | |
1405 | c_errflags = _ffi.new("uint64_t *") | |
1406 | if props is None: | |
1407 | props = {} | |
1408 | if cmdprops is None: | |
1409 | cmdprops = {} | |
d9c460a0 | 1410 | if key is None: |
e5fb1dc5 | 1411 | key = b"" |
d9c460a0 TC |
1412 | else: |
1413 | key = bytes(key) | |
1414 | ||
85ce3f4f | 1415 | nvlist = nvlist_in(props) |
1416 | cmdnvlist = nvlist_in(cmdprops) | |
1417 | properrs = {} | |
1418 | with nvlist_out(properrs) as c_errors: | |
1419 | ret = _lib.lzc_receive_with_cmdprops( | |
d9c460a0 TC |
1420 | snapname, nvlist, cmdnvlist, key, len(key), c_origin, |
1421 | force, resumable, raw, fd, begin_record, cleanup_fd, c_read_bytes, | |
1422 | c_errflags, c_action_handle, c_errors) | |
85ce3f4f | 1423 | errors.lzc_receive_translate_errors( |
1424 | ret, snapname, fd, force, raw, False, False, origin, properrs) | |
1425 | return (int(c_read_bytes[0]), action_handle) | |
6abf9225 | 1426 | |
85ce3f4f | 1427 | |
1428 | @_uncommitted() | |
1429 | def lzc_reopen(poolname, restart=True): | |
1430 | ''' | |
1431 | Reopen a pool | |
1432 | ||
1433 | :param bytes poolname: the name of the pool. | |
1434 | :param bool restart: whether to restart an in-progress scrub operation. | |
1435 | ||
1436 | :raises PoolNotFound: if the pool does not exist. | |
1437 | ''' | |
1438 | ret = _lib.lzc_reopen(poolname, restart) | |
1439 | errors.lzc_reopen_translate_error(ret, poolname) | |
1440 | ||
1441 | ||
1442 | def lzc_send_resume( | |
1443 | snapname, fromsnap, fd, flags=None, resumeobj=0, resumeoff=0 | |
1444 | ): | |
1445 | ''' | |
1446 | Resume a previously interrupted send operation generating a zfs send stream | |
1447 | for the specified snapshot and writing it to the specified file descriptor. | |
1448 | ||
1449 | :param bytes snapname: the name of the snapshot to send. | |
1450 | :param fromsnap: if not None the name of the starting snapshot | |
1451 | for the incremental stream. | |
1452 | :type fromsnap: bytes or None | |
1453 | :param int fd: the file descriptor to write the send stream to. | |
1454 | :param flags: the flags that control what enhanced features can be used in | |
1455 | the stream. | |
1456 | :type flags: list of bytes | |
1457 | :param int resumeobj: the object number where this send stream should | |
1458 | resume from. | |
1459 | :param int resumeoff: the offset where this send stream should resume from. | |
1460 | ||
1461 | :raises SnapshotNotFound: if either the starting snapshot is not `None` and | |
1462 | does not exist, or if the ending snapshot does not exist. | |
1463 | :raises NameInvalid: if the name of either snapshot is invalid. | |
1464 | :raises NameTooLong: if the name of either snapshot is too long. | |
1465 | :raises SnapshotMismatch: if ``fromsnap`` is not an ancestor snapshot of | |
1466 | ``snapname``. | |
1467 | :raises PoolsDiffer: if the snapshots belong to different pools. | |
1468 | :raises IOError: if an input / output error occurs while writing to ``fd``. | |
1469 | :raises UnknownStreamFeature: if the ``flags`` contain an unknown flag | |
1470 | name. | |
6abf9225 AG |
1471 | |
1472 | .. note:: | |
85ce3f4f | 1473 | See :func:`lzc_send` for more information. |
6abf9225 | 1474 | ''' |
85ce3f4f | 1475 | if fromsnap is not None: |
1476 | c_fromsnap = fromsnap | |
1477 | else: | |
1478 | c_fromsnap = _ffi.NULL | |
1479 | c_flags = 0 | |
1480 | if flags is None: | |
1481 | flags = [] | |
1482 | for flag in flags: | |
1483 | c_flag = { | |
1484 | 'embedded_data': _lib.LZC_SEND_FLAG_EMBED_DATA, | |
1485 | 'large_blocks': _lib.LZC_SEND_FLAG_LARGE_BLOCK, | |
1486 | 'compress': _lib.LZC_SEND_FLAG_COMPRESS, | |
1487 | 'raw': _lib.LZC_SEND_FLAG_RAW, | |
1488 | }.get(flag) | |
1489 | if c_flag is None: | |
1490 | raise exceptions.UnknownStreamFeature(flag) | |
1491 | c_flags |= c_flag | |
1492 | ||
1493 | ret = _lib.lzc_send_resume( | |
1494 | snapname, c_fromsnap, fd, c_flags, uint64_t(resumeobj), | |
1495 | uint64_t(resumeoff)) | |
1496 | errors.lzc_send_translate_error(ret, snapname, fromsnap, fd, flags) | |
1497 | ||
1498 | ||
1499 | @_uncommitted() | |
1500 | def lzc_sync(poolname, force=False): | |
1501 | ''' | |
1502 | Forces all in-core dirty data to be written to the primary pool storage | |
1503 | and not the ZIL. | |
1504 | ||
1505 | :param bytes poolname: the name of the pool. | |
1506 | :param bool force: whether to force uberblock update even if there is no | |
1507 | dirty data. | |
1508 | ||
1509 | :raises PoolNotFound: if the pool does not exist. | |
1510 | ||
1511 | .. note:: | |
1512 | This method signature is different from its C libzfs_core counterpart: | |
1513 | `innvl` has been replaced by the `force` boolean and `outnvl` has been | |
1514 | conveniently removed since it's not used. | |
1515 | ''' | |
e5fb1dc5 | 1516 | innvl = nvlist_in({b"force": force}) |
85ce3f4f | 1517 | with nvlist_out({}) as outnvl: |
1518 | ret = _lib.lzc_sync(poolname, innvl, outnvl) | |
1519 | errors.lzc_sync_translate_error(ret, poolname) | |
6abf9225 AG |
1520 | |
1521 | ||
1522 | def is_supported(func): | |
1523 | ''' | |
1524 | Check whether C *libzfs_core* provides implementation required | |
1525 | for the given Python wrapper. | |
1526 | ||
1527 | If `is_supported` returns ``False`` for the function, then | |
1528 | calling the function would result in :exc:`NotImplementedError`. | |
1529 | ||
1530 | :param function func: the function to check. | |
1531 | :return bool: whether the function can be used. | |
1532 | ''' | |
1533 | fname = func.__name__ | |
1534 | if fname not in globals(): | |
1535 | raise ValueError(fname + ' is not from libzfs_core') | |
1536 | if not callable(func): | |
1537 | raise ValueError(fname + ' is not a function') | |
1538 | if not fname.startswith("lzc_"): | |
1539 | raise ValueError(fname + ' is not a libzfs_core API function') | |
1540 | check_func = getattr(func, "_check_func", None) | |
1541 | if check_func is not None: | |
1542 | return is_supported(check_func) | |
1543 | return getattr(_lib, fname, None) is not None | |
1544 | ||
1545 | ||
6abf9225 AG |
1546 | @_uncommitted() |
1547 | def lzc_promote(name): | |
1548 | ''' | |
1549 | Promotes the ZFS dataset. | |
1550 | ||
1551 | :param bytes name: the name of the dataset to promote. | |
1552 | :raises NameInvalid: if the dataset name is invalid. | |
1553 | :raises NameTooLong: if the dataset name is too long. | |
85ce3f4f | 1554 | :raises NameTooLong: if the dataset's origin has a snapshot that, if |
1555 | transferred to the dataset, would get a too long name. | |
6abf9225 AG |
1556 | :raises NotClone: if the dataset is not a clone. |
1557 | :raises FilesystemNotFound: if the dataset does not exist. | |
85ce3f4f | 1558 | :raises SnapshotExists: if the dataset already has a snapshot with the same |
1559 | name as one of the origin's snapshots. | |
6abf9225 AG |
1560 | ''' |
1561 | ret = _lib.lzc_promote(name, _ffi.NULL, _ffi.NULL) | |
1562 | errors.lzc_promote_translate_error(ret, name) | |
1563 | ||
1564 | ||
85ce3f4f | 1565 | @_uncommitted() |
1566 | def lzc_remap(name): | |
1567 | ''' | |
1568 | Remaps the ZFS dataset. | |
1569 | ||
1570 | :param bytes name: the name of the dataset to remap. | |
1571 | :raises NameInvalid: if the dataset name is invalid. | |
1572 | :raises NameTooLong: if the dataset name is too long. | |
1573 | :raises DatasetNotFound: if the dataset does not exist. | |
1574 | :raises FeatureNotSupported: if the pool containing the dataset does not | |
1575 | have the *obsolete_counts* feature enabled. | |
1576 | ''' | |
1577 | ret = _lib.lzc_remap(name) | |
1578 | errors.lzc_remap_translate_error(ret, name) | |
1579 | ||
1580 | ||
c962fd6c | 1581 | @_uncommitted() |
1582 | def lzc_pool_checkpoint(name): | |
1583 | ''' | |
1584 | Creates a checkpoint for the specified pool. | |
1585 | ||
1586 | :param bytes name: the name of the pool to create a checkpoint for. | |
1587 | :raises CheckpointExists: if the pool already has a checkpoint. | |
1588 | :raises CheckpointDiscarding: if ZFS is in the middle of discarding a | |
1589 | checkpoint for this pool. | |
1590 | :raises DeviceRemovalRunning: if a vdev is currently being removed. | |
1591 | :raises DeviceTooBig: if one or more top-level vdevs exceed the maximum | |
1592 | vdev size. | |
1593 | ''' | |
1594 | ret = _lib.lzc_pool_checkpoint(name) | |
1595 | errors.lzc_pool_checkpoint_translate_error(ret, name) | |
1596 | ||
1597 | ||
1598 | @_uncommitted() | |
1599 | def lzc_pool_checkpoint_discard(name): | |
1600 | ''' | |
1601 | Discard the checkpoint from the specified pool. | |
1602 | ||
1603 | :param bytes name: the name of the pool to discard the checkpoint from. | |
1604 | :raises CheckpointNotFound: if pool does not have a checkpoint. | |
1605 | :raises CheckpointDiscarding: if ZFS is in the middle of discarding a | |
1606 | checkpoint for this pool. | |
1607 | ''' | |
1608 | ret = _lib.lzc_pool_checkpoint_discard(name) | |
1609 | errors.lzc_pool_checkpoint_discard_translate_error(ret, name) | |
1610 | ||
1611 | ||
6abf9225 AG |
1612 | def lzc_rename(source, target): |
1613 | ''' | |
1614 | Rename the ZFS dataset. | |
1615 | ||
1616 | :param source name: the current name of the dataset to rename. | |
1617 | :param target name: the new name of the dataset. | |
1618 | :raises NameInvalid: if either the source or target name is invalid. | |
1619 | :raises NameTooLong: if either the source or target name is too long. | |
85ce3f4f | 1620 | :raises NameTooLong: if a snapshot of the source would get a too long name |
1621 | after renaming. | |
6abf9225 AG |
1622 | :raises FilesystemNotFound: if the source does not exist. |
1623 | :raises FilesystemNotFound: if the target's parent does not exist. | |
1624 | :raises FilesystemExists: if the target already exists. | |
1625 | :raises PoolsDiffer: if the source and target belong to different pools. | |
d8d418ff | 1626 | :raises WrongParent: if the "new" parent dataset is not a filesystem |
1627 | (e.g. ZVOL) | |
6abf9225 | 1628 | ''' |
dc1c630b | 1629 | ret = _lib.lzc_rename(source, target) |
6abf9225 AG |
1630 | errors.lzc_rename_translate_error(ret, source, target) |
1631 | ||
1632 | ||
dc1c630b | 1633 | def lzc_destroy(name): |
6abf9225 AG |
1634 | ''' |
1635 | Destroy the ZFS dataset. | |
1636 | ||
1637 | :param bytes name: the name of the dataset to destroy. | |
1638 | :raises NameInvalid: if the dataset name is invalid. | |
1639 | :raises NameTooLong: if the dataset name is too long. | |
1640 | :raises FilesystemNotFound: if the dataset does not exist. | |
1641 | ''' | |
dc1c630b | 1642 | ret = _lib.lzc_destroy(name) |
6abf9225 AG |
1643 | errors.lzc_destroy_translate_error(ret, name) |
1644 | ||
1645 | ||
6abf9225 AG |
1646 | @_uncommitted() |
1647 | def lzc_inherit(name, prop): | |
1648 | ''' | |
1649 | Inherit properties from a parent dataset of the given ZFS dataset. | |
1650 | ||
1651 | :param bytes name: the name of the dataset. | |
1652 | :param bytes prop: the name of the property to inherit. | |
1653 | :raises NameInvalid: if the dataset name is invalid. | |
1654 | :raises NameTooLong: if the dataset name is too long. | |
1655 | :raises DatasetNotFound: if the dataset does not exist. | |
85ce3f4f | 1656 | :raises PropertyInvalid: if one or more of the specified properties is |
1657 | invalid or has an invalid type or value. | |
6abf9225 AG |
1658 | |
1659 | Inheriting a property actually resets it to its default value | |
1660 | or removes it if it's a user property, so that the property could be | |
1661 | inherited if it's inheritable. If the property is not inheritable | |
1662 | then it would just have its default value. | |
1663 | ||
1664 | This function can be used on snapshots to inherit user defined properties. | |
1665 | ''' | |
1666 | ret = _lib.lzc_inherit(name, prop, _ffi.NULL) | |
1667 | errors.lzc_inherit_prop_translate_error(ret, name, prop) | |
1668 | ||
1669 | ||
1670 | # As the extended API is not committed yet, the names of the new interfaces | |
1671 | # are not settled down yet. | |
1672 | # lzc_inherit_prop makes it clearer what is to be inherited. | |
1673 | lzc_inherit_prop = lzc_inherit | |
1674 | ||
1675 | ||
1676 | @_uncommitted() | |
1677 | def lzc_set_props(name, prop, val): | |
1678 | ''' | |
1679 | Set properties of the ZFS dataset. | |
1680 | ||
1681 | :param bytes name: the name of the dataset. | |
1682 | :param bytes prop: the name of the property. | |
1683 | :param Any val: the value of the property. | |
1684 | :raises NameInvalid: if the dataset name is invalid. | |
1685 | :raises NameTooLong: if the dataset name is too long. | |
1686 | :raises DatasetNotFound: if the dataset does not exist. | |
85ce3f4f | 1687 | :raises NoSpace: if the property controls a quota and the values is too |
1688 | small for that quota. | |
1689 | :raises PropertyInvalid: if one or more of the specified properties is | |
1690 | invalid or has an invalid type or value. | |
6abf9225 AG |
1691 | |
1692 | This function can be used on snapshots to set user defined properties. | |
1693 | ||
1694 | .. note:: | |
1695 | An attempt to set a readonly / statistic property is ignored | |
1696 | without reporting any error. | |
1697 | ''' | |
1698 | props = {prop: val} | |
1699 | props_nv = nvlist_in(props) | |
1700 | ret = _lib.lzc_set_props(name, props_nv, _ffi.NULL, _ffi.NULL) | |
1701 | errors.lzc_set_prop_translate_error(ret, name, prop, val) | |
1702 | ||
1703 | ||
1704 | # As the extended API is not committed yet, the names of the new interfaces | |
1705 | # are not settled down yet. | |
1706 | # It's not clear if atomically setting multiple properties is an achievable | |
1707 | # goal and an interface acting on mutiple entities must do so atomically | |
1708 | # by convention. | |
1709 | # Being able to set a single property at a time is sufficient for ClusterHQ. | |
1710 | lzc_set_prop = lzc_set_props | |
1711 | ||
1712 | ||
1713 | @_uncommitted() | |
1714 | def lzc_list(name, options): | |
1715 | ''' | |
1716 | List subordinate elements of the given dataset. | |
1717 | ||
85ce3f4f | 1718 | This function can be used to list child datasets and snapshots of the given |
1719 | dataset. The listed elements can be filtered by their type and by their | |
1720 | depth relative to the starting dataset. | |
6abf9225 | 1721 | |
85ce3f4f | 1722 | :param bytes name: the name of the dataset to be listed, could be a |
1723 | snapshot or a dataset. | |
1724 | :param options: a `dict` of the options that control the listing behavior. | |
6abf9225 | 1725 | :type options: dict of bytes:Any |
85ce3f4f | 1726 | :return: a pair of file descriptors the first of which can be used to read |
1727 | the listing. | |
6abf9225 AG |
1728 | :rtype: tuple of (int, int) |
1729 | :raises DatasetNotFound: if the dataset does not exist. | |
1730 | ||
1731 | Two options are currently available: | |
1732 | ||
1733 | recurse : integer or None | |
85ce3f4f | 1734 | specifies depth of the recursive listing. If ``None`` the depth is not |
1735 | limited. | |
1736 | Absence of this option means that only the given dataset is listed. | |
6abf9225 AG |
1737 | |
1738 | type : dict of bytes:None | |
1739 | specifies dataset types to include into the listing. | |
1740 | Currently allowed keys are "filesystem", "volume", "snapshot". | |
1741 | Absence of this option implies all types. | |
1742 | ||
1743 | The first of the returned file descriptors can be used to | |
1744 | read the listing in a binary encounded format. The data is | |
1745 | a series of variable sized records each starting with a fixed | |
1746 | size header, the header is followed by a serialized ``nvlist``. | |
1747 | Each record describes a single element and contains the element's | |
1748 | name as well as its properties. | |
1749 | The file descriptor must be closed after reading from it. | |
1750 | ||
1751 | The second file descriptor represents a pipe end to which the | |
1752 | kernel driver is writing information. It should not be closed | |
1753 | until all interesting information has been read and it must | |
1754 | be explicitly closed afterwards. | |
1755 | ''' | |
1756 | (rfd, wfd) = os.pipe() | |
1757 | fcntl.fcntl(rfd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) | |
1758 | fcntl.fcntl(wfd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) | |
1759 | options = options.copy() | |
1760 | options['fd'] = int32_t(wfd) | |
1761 | opts_nv = nvlist_in(options) | |
1762 | ret = _lib.lzc_list(name, opts_nv) | |
1763 | if ret == errno.ESRCH: | |
1764 | return (None, None) | |
1765 | errors.lzc_list_translate_error(ret, name, options) | |
1766 | return (rfd, wfd) | |
1767 | ||
1768 | ||
1769 | # Description of the binary format used to pass data from the kernel. | |
1770 | _PIPE_RECORD_FORMAT = 'IBBBB' | |
1771 | _PIPE_RECORD_SIZE = struct.calcsize(_PIPE_RECORD_FORMAT) | |
1772 | ||
1773 | ||
1774 | def _list(name, recurse=None, types=None): | |
1775 | ''' | |
1776 | A wrapper for :func:`lzc_list` that hides details of working | |
1777 | with the file descriptors and provides data in an easy to | |
1778 | consume format. | |
1779 | ||
85ce3f4f | 1780 | :param bytes name: the name of the dataset to be listed, could be a |
1781 | snapshot, a volume or a filesystem. | |
1782 | :param recurse: specifies depth of the recursive listing. If ``None`` the | |
1783 | depth is not limited. | |
6abf9225 | 1784 | :param types: specifies dataset types to include into the listing. |
85ce3f4f | 1785 | Currently allowed keys are "filesystem", "volume", "snapshot". ``None`` |
1786 | is equivalent to specifying the type of the dataset named by `name`. | |
6abf9225 AG |
1787 | :type types: list of bytes or None |
1788 | :type recurse: integer or None | |
85ce3f4f | 1789 | :return: a list of dictionaries each describing a single listed element. |
6abf9225 AG |
1790 | :rtype: list of dict |
1791 | ''' | |
1792 | options = {} | |
1793 | ||
1794 | # Convert types to a dict suitable for mapping to an nvlist. | |
1795 | if types is not None: | |
1796 | types = {x: None for x in types} | |
1797 | options['type'] = types | |
1798 | if recurse is None or recurse > 0: | |
1799 | options['recurse'] = recurse | |
1800 | ||
1801 | # Note that other_fd is used by the kernel side to write | |
1802 | # the data, so we have to keep that descriptor open until | |
1803 | # we are done. | |
1804 | # Also, we have to explicitly close the descriptor as the | |
1805 | # kernel doesn't do that. | |
1806 | (fd, other_fd) = lzc_list(name, options) | |
1807 | if fd is None: | |
1808 | return | |
1809 | ||
1810 | try: | |
1811 | while True: | |
1812 | record_bytes = os.read(fd, _PIPE_RECORD_SIZE) | |
1813 | if not record_bytes: | |
1814 | break | |
1815 | (size, _, err, _, _) = struct.unpack( | |
1816 | _PIPE_RECORD_FORMAT, record_bytes) | |
1817 | if err == errno.ESRCH: | |
1818 | break | |
1819 | errors.lzc_list_translate_error(err, name, options) | |
1820 | if size == 0: | |
1821 | break | |
1822 | data_bytes = os.read(fd, size) | |
1823 | result = {} | |
1824 | with nvlist_out(result) as nvp: | |
1825 | ret = _lib.nvlist_unpack(data_bytes, size, nvp, 0) | |
1826 | if ret != 0: | |
85ce3f4f | 1827 | raise exceptions.ZFSGenericError( |
1828 | ret, None, "Failed to unpack list data") | |
6abf9225 AG |
1829 | yield result |
1830 | finally: | |
1831 | os.close(other_fd) | |
1832 | os.close(fd) | |
1833 | ||
1834 | ||
1835 | @_uncommitted(lzc_list) | |
1836 | def lzc_get_props(name): | |
1837 | ''' | |
1838 | Get properties of the ZFS dataset. | |
1839 | ||
1840 | :param bytes name: the name of the dataset. | |
1841 | :raises DatasetNotFound: if the dataset does not exist. | |
1842 | :raises NameInvalid: if the dataset name is invalid. | |
1843 | :raises NameTooLong: if the dataset name is too long. | |
1844 | :return: a dictionary mapping the property names to their values. | |
1845 | :rtype: dict of bytes:Any | |
1846 | ||
1847 | .. note:: | |
85ce3f4f | 1848 | The value of ``clones`` property is a `list` of clone names as byte |
1849 | strings. | |
6abf9225 AG |
1850 | |
1851 | .. warning:: | |
1852 | The returned dictionary does not contain entries for properties | |
1853 | with default values. One exception is the ``mountpoint`` property | |
1854 | for which the default value is derived from the dataset name. | |
1855 | ''' | |
1856 | result = next(_list(name, recurse=0)) | |
1857 | is_snapshot = result['dmu_objset_stats']['dds_is_snapshot'] | |
1858 | result = result['properties'] | |
1859 | # In most cases the source of the property is uninteresting and the | |
1860 | # value alone is sufficient. One exception is the 'mountpoint' | |
1861 | # property the final value of which is not the same as the inherited | |
1862 | # value. | |
1863 | mountpoint = result.get('mountpoint') | |
1864 | if mountpoint is not None: | |
1865 | mountpoint_src = mountpoint['source'] | |
1866 | mountpoint_val = mountpoint['value'] | |
1867 | # 'source' is the name of the dataset that has 'mountpoint' set | |
1868 | # to a non-default value and from which the current dataset inherits | |
1869 | # the property. 'source' can be the current dataset if its | |
1870 | # 'mountpoint' is explicitly set. | |
1871 | # 'source' can also be a special value like '$recvd', that case | |
1872 | # is equivalent to the property being set on the current dataset. | |
1873 | # Note that a normal mountpoint value should start with '/' | |
1874 | # unlike the special values "none" and "legacy". | |
85ce3f4f | 1875 | if (mountpoint_val.startswith('/') and |
1876 | not mountpoint_src.startswith('$')): | |
6abf9225 AG |
1877 | mountpoint_val = mountpoint_val + name[len(mountpoint_src):] |
1878 | elif not is_snapshot: | |
1879 | mountpoint_val = '/' + name | |
1880 | else: | |
1881 | mountpoint_val = None | |
9de8c0cd | 1882 | result = {k: result[k]['value'] for k in result} |
6abf9225 | 1883 | if 'clones' in result: |
9de8c0cd | 1884 | result['clones'] = list(result['clones'].keys()) |
6abf9225 AG |
1885 | if mountpoint_val is not None: |
1886 | result['mountpoint'] = mountpoint_val | |
1887 | return result | |
1888 | ||
1889 | ||
1890 | @_uncommitted(lzc_list) | |
1891 | def lzc_list_children(name): | |
1892 | ''' | |
1893 | List the children of the ZFS dataset. | |
1894 | ||
1895 | :param bytes name: the name of the dataset. | |
1896 | :return: an iterator that produces the names of the children. | |
1897 | :raises NameInvalid: if the dataset name is invalid. | |
1898 | :raises NameTooLong: if the dataset name is too long. | |
1899 | :raises DatasetNotFound: if the dataset does not exist. | |
1900 | ||
1901 | .. warning:: | |
1902 | If the dataset does not exist, then the returned iterator would produce | |
1903 | no results and no error is reported. | |
1904 | That case is indistinguishable from the dataset having no children. | |
1905 | ||
1906 | An attempt to list children of a snapshot is silently ignored as well. | |
1907 | ''' | |
1908 | children = [] | |
1909 | for entry in _list(name, recurse=1, types=['filesystem', 'volume']): | |
1910 | child = entry['name'] | |
1911 | if child != name: | |
1912 | children.append(child) | |
1913 | ||
1914 | return iter(children) | |
1915 | ||
1916 | ||
1917 | @_uncommitted(lzc_list) | |
1918 | def lzc_list_snaps(name): | |
1919 | ''' | |
1920 | List the snapshots of the ZFS dataset. | |
1921 | ||
1922 | :param bytes name: the name of the dataset. | |
1923 | :return: an iterator that produces the names of the snapshots. | |
1924 | :raises NameInvalid: if the dataset name is invalid. | |
1925 | :raises NameTooLong: if the dataset name is too long. | |
1926 | :raises DatasetNotFound: if the dataset does not exist. | |
1927 | ||
1928 | .. warning:: | |
1929 | If the dataset does not exist, then the returned iterator would produce | |
1930 | no results and no error is reported. | |
1931 | That case is indistinguishable from the dataset having no snapshots. | |
1932 | ||
1933 | An attempt to list snapshots of a snapshot is silently ignored as well. | |
1934 | ''' | |
1935 | snaps = [] | |
1936 | for entry in _list(name, recurse=1, types=['snapshot']): | |
1937 | snap = entry['name'] | |
1938 | if snap != name: | |
1939 | snaps.append(snap) | |
1940 | ||
1941 | return iter(snaps) | |
1942 | ||
1943 | ||
1944 | # TODO: a better way to init and uninit the library | |
1945 | def _initialize(): | |
1946 | class LazyInit(object): | |
1947 | ||
1948 | def __init__(self, lib): | |
1949 | self._lib = lib | |
1950 | self._inited = False | |
1951 | self._lock = threading.Lock() | |
1952 | ||
1953 | def __getattr__(self, name): | |
1954 | if not self._inited: | |
1955 | with self._lock: | |
1956 | if not self._inited: | |
1957 | ret = self._lib.libzfs_core_init() | |
1958 | if ret != 0: | |
1959 | raise exceptions.ZFSInitializationFailed(ret) | |
1960 | self._inited = True | |
1961 | return getattr(self._lib, name) | |
1962 | ||
1963 | return LazyInit(libzfs_core.lib) | |
1964 | ||
85ce3f4f | 1965 | |
6abf9225 AG |
1966 | _ffi = libzfs_core.ffi |
1967 | _lib = _initialize() | |
1968 | ||
1969 | ||
1970 | # vim: softtabstop=4 tabstop=4 expandtab shiftwidth=4 |