]> git.proxmox.com Git - mirror_zfs.git/blob - contrib/pyzfs/libzfs_core/_libzfs_core.py
Import pyzfs source code from ClusterHQ
[mirror_zfs.git] / contrib / pyzfs / libzfs_core / _libzfs_core.py
1 # Copyright 2015 ClusterHQ. See LICENSE file for details.
2
3 """
4 Python wrappers for libzfs_core interfaces.
5
6 As a rule, there is a Python function for each C function.
7 The signatures of the Python functions generally follow those of the
8 functions, but the argument types are natural to Python.
9 nvlists are wrapped as dictionaries or lists depending on their usage.
10 Some parameters have default values depending on typical use for
11 increased convenience. Output parameters are not used and return values
12 are directly returned. Error conditions are signalled by exceptions
13 rather than by integer error codes.
14 """
15
16 import errno
17 import functools
18 import fcntl
19 import os
20 import struct
21 import threading
22 from . import exceptions
23 from . import _error_translation as errors
24 from .bindings import libzfs_core
25 from ._constants import MAXNAMELEN
26 from .ctypes import int32_t
27 from ._nvlist import nvlist_in, nvlist_out
28
29
30 def lzc_create(name, ds_type='zfs', props=None):
31 '''
32 Create a ZFS filesystem or a ZFS volume ("zvol").
33
34 :param bytes name: a name of the dataset to be created.
35 :param str ds_type: the type of the dataset to be create, currently supported
36 types are "zfs" (the default) for a filesystem
37 and "zvol" for a volume.
38 :param props: a `dict` of ZFS dataset property name-value pairs (empty by default).
39 :type props: dict of bytes:Any
40
41 :raises FilesystemExists: if a dataset with the given name already exists.
42 :raises ParentNotFound: if a parent dataset of the requested dataset does not exist.
43 :raises PropertyInvalid: if one or more of the specified properties is invalid
44 or has an invalid type or value.
45 :raises NameInvalid: if the name is not a valid dataset name.
46 :raises NameTooLong: if the name is too long.
47 '''
48 if props is None:
49 props = {}
50 if ds_type == 'zfs':
51 ds_type = _lib.DMU_OST_ZFS
52 elif ds_type == 'zvol':
53 ds_type = _lib.DMU_OST_ZVOL
54 else:
55 raise exceptions.DatasetTypeInvalid(ds_type)
56 nvlist = nvlist_in(props)
57 ret = _lib.lzc_create(name, ds_type, nvlist)
58 errors.lzc_create_translate_error(ret, name, ds_type, props)
59
60
61 def lzc_clone(name, origin, props=None):
62 '''
63 Clone a ZFS filesystem or a ZFS volume ("zvol") from a given snapshot.
64
65 :param bytes name: a name of the dataset to be created.
66 :param bytes origin: a name of the origin snapshot.
67 :param props: a `dict` of ZFS dataset property name-value pairs (empty by default).
68 :type props: dict of bytes:Any
69
70 :raises FilesystemExists: if a dataset with the given name already exists.
71 :raises DatasetNotFound: if either a parent dataset of the requested dataset
72 or the origin snapshot does not exist.
73 :raises PropertyInvalid: if one or more of the specified properties is invalid
74 or has an invalid type or value.
75 :raises FilesystemNameInvalid: if the name is not a valid dataset name.
76 :raises SnapshotNameInvalid: if the origin is not a valid snapshot name.
77 :raises NameTooLong: if the name or the origin name is too long.
78 :raises PoolsDiffer: if the clone and the origin have different pool names.
79
80 .. note::
81 Because of a deficiency of the underlying C interface
82 :exc:`.DatasetNotFound` can mean that either a parent filesystem of the target
83 or the origin snapshot does not exist.
84 It is currently impossible to distinguish between the cases.
85 :func:`lzc_hold` can be used to check that the snapshot exists and ensure that
86 it is not destroyed before cloning.
87 '''
88 if props is None:
89 props = {}
90 nvlist = nvlist_in(props)
91 ret = _lib.lzc_clone(name, origin, nvlist)
92 errors.lzc_clone_translate_error(ret, name, origin, props)
93
94
95 def lzc_rollback(name):
96 '''
97 Roll back a filesystem or volume to its most recent snapshot.
98
99 Note that the latest snapshot may change if a new one is concurrently
100 created or the current one is destroyed. lzc_rollback_to can be used
101 to roll back to a specific latest snapshot.
102
103 :param bytes name: a name of the dataset to be rolled back.
104 :return: a name of the most recent snapshot.
105 :rtype: bytes
106
107 :raises FilesystemNotFound: if the dataset does not exist.
108 :raises SnapshotNotFound: if the dataset does not have any snapshots.
109 :raises NameInvalid: if the dataset name is invalid.
110 :raises NameTooLong: if the dataset name is too long.
111 '''
112 # Account for terminating NUL in C strings.
113 snapnamep = _ffi.new('char[]', MAXNAMELEN + 1)
114 ret = _lib.lzc_rollback(name, snapnamep, MAXNAMELEN + 1)
115 errors.lzc_rollback_translate_error(ret, name)
116 return _ffi.string(snapnamep)
117
118 def lzc_rollback_to(name, snap):
119 '''
120 Roll back this filesystem or volume to the specified snapshot, if possible.
121
122 :param bytes name: a name of the dataset to be rolled back.
123 :param bytes snap: a name of the snapshot to be rolled back.
124
125 :raises FilesystemNotFound: if the dataset does not exist.
126 :raises SnapshotNotFound: if the dataset does not have any snapshots.
127 :raises NameInvalid: if the dataset name is invalid.
128 :raises NameTooLong: if the dataset name is too long.
129 :raises SnapshotNotLatest: if the snapshot is not the latest.
130 '''
131 ret = _lib.lzc_rollback_to(name, snap)
132 errors.lzc_rollback_to_translate_error(ret, name, snap)
133
134 def lzc_snapshot(snaps, props=None):
135 '''
136 Create snapshots.
137
138 All snapshots must be in the same pool.
139
140 Optionally snapshot properties can be set on all snapshots.
141 Currently only user properties (prefixed with "user:") are supported.
142
143 Either all snapshots are successfully created or none are created if
144 an exception is raised.
145
146 :param snaps: a list of names of snapshots to be created.
147 :type snaps: list of bytes
148 :param props: a `dict` of ZFS dataset property name-value pairs (empty by default).
149 :type props: dict of bytes:bytes
150
151 :raises SnapshotFailure: if one or more snapshots could not be created.
152
153 .. note::
154 :exc:`.SnapshotFailure` is a compound exception that provides at least
155 one detailed error object in :attr:`SnapshotFailure.errors` `list`.
156
157 .. warning::
158 The underlying implementation reports an individual, per-snapshot error
159 only for :exc:`.SnapshotExists` condition and *sometimes* for
160 :exc:`.NameTooLong`.
161 In all other cases a single error is reported without connection to any
162 specific snapshot name(s).
163
164 This has the following implications:
165
166 * if multiple error conditions are encountered only one of them is reported
167
168 * unless only one snapshot is requested then it is impossible to tell
169 how many snapshots are problematic and what they are
170
171 * only if there are no other error conditions :exc:`.SnapshotExists`
172 is reported for all affected snapshots
173
174 * :exc:`.NameTooLong` can behave either in the same way as
175 :exc:`.SnapshotExists` or as all other exceptions.
176 The former is the case where the full snapshot name exceeds the maximum
177 allowed length but the short snapshot name (after '@') is within
178 the limit.
179 The latter is the case when the short name alone exceeds the maximum
180 allowed length.
181 '''
182 snaps_dict = {name: None for name in snaps}
183 errlist = {}
184 snaps_nvlist = nvlist_in(snaps_dict)
185 if props is None:
186 props = {}
187 props_nvlist = nvlist_in(props)
188 with nvlist_out(errlist) as errlist_nvlist:
189 ret = _lib.lzc_snapshot(snaps_nvlist, props_nvlist, errlist_nvlist)
190 errors.lzc_snapshot_translate_errors(ret, errlist, snaps, props)
191
192
193 lzc_snap = lzc_snapshot
194
195
196 def lzc_destroy_snaps(snaps, defer):
197 '''
198 Destroy snapshots.
199
200 They must all be in the same pool.
201 Snapshots that do not exist will be silently ignored.
202
203 If 'defer' is not set, and a snapshot has user holds or clones, the
204 destroy operation will fail and none of the snapshots will be
205 destroyed.
206
207 If 'defer' is set, and a snapshot has user holds or clones, it will be
208 marked for deferred destruction, and will be destroyed when the last hold
209 or clone is removed/destroyed.
210
211 The operation succeeds if all snapshots were destroyed (or marked for
212 later destruction if 'defer' is set) or didn't exist to begin with.
213
214 :param snaps: a list of names of snapshots to be destroyed.
215 :type snaps: list of bytes
216 :param bool defer: whether to mark busy snapshots for deferred destruction
217 rather than immediately failing.
218
219 :raises SnapshotDestructionFailure: if one or more snapshots could not be created.
220
221 .. note::
222 :exc:`.SnapshotDestructionFailure` is a compound exception that provides at least
223 one detailed error object in :attr:`SnapshotDestructionFailure.errors` `list`.
224
225 Typical error is :exc:`SnapshotIsCloned` if `defer` is `False`.
226 The snapshot names are validated quite loosely and invalid names are typically
227 ignored as nonexisiting snapshots.
228
229 A snapshot name referring to a filesystem that doesn't exist is ignored.
230 However, non-existent pool name causes :exc:`PoolNotFound`.
231 '''
232 snaps_dict = {name: None for name in snaps}
233 errlist = {}
234 snaps_nvlist = nvlist_in(snaps_dict)
235 with nvlist_out(errlist) as errlist_nvlist:
236 ret = _lib.lzc_destroy_snaps(snaps_nvlist, defer, errlist_nvlist)
237 errors.lzc_destroy_snaps_translate_errors(ret, errlist, snaps, defer)
238
239
240 def lzc_bookmark(bookmarks):
241 '''
242 Create bookmarks.
243
244 :param bookmarks: a dict that maps names of wanted bookmarks to names of existing snapshots.
245 :type bookmarks: dict of bytes to bytes
246
247 :raises BookmarkFailure: if any of the bookmarks can not be created for any reason.
248
249 The bookmarks `dict` maps from name of the bookmark (e.g. :file:`{pool}/{fs}#{bmark}`) to
250 the name of the snapshot (e.g. :file:`{pool}/{fs}@{snap}`). All the bookmarks and
251 snapshots must be in the same pool.
252 '''
253 errlist = {}
254 nvlist = nvlist_in(bookmarks)
255 with nvlist_out(errlist) as errlist_nvlist:
256 ret = _lib.lzc_bookmark(nvlist, errlist_nvlist)
257 errors.lzc_bookmark_translate_errors(ret, errlist, bookmarks)
258
259
260 def lzc_get_bookmarks(fsname, props=None):
261 '''
262 Retrieve a listing of bookmarks for the given file system.
263
264 :param bytes fsname: a name of the filesystem.
265 :param props: a `list` of properties that will be returned for each bookmark.
266 :type props: list of bytes
267 :return: a `dict` that maps the bookmarks' short names to their properties.
268 :rtype: dict of bytes:dict
269
270 :raises FilesystemNotFound: if the filesystem is not found.
271
272 The following are valid properties on bookmarks:
273
274 guid : integer
275 globally unique identifier of the snapshot the bookmark refers to
276 createtxg : integer
277 txg when the snapshot the bookmark refers to was created
278 creation : integer
279 timestamp when the snapshot the bookmark refers to was created
280
281 Any other properties passed in ``props`` are ignored without reporting
282 any error.
283 Values in the returned dictionary map the names of the requested properties
284 to their respective values.
285 '''
286 bmarks = {}
287 if props is None:
288 props = []
289 props_dict = {name: None for name in props}
290 nvlist = nvlist_in(props_dict)
291 with nvlist_out(bmarks) as bmarks_nvlist:
292 ret = _lib.lzc_get_bookmarks(fsname, nvlist, bmarks_nvlist)
293 errors.lzc_get_bookmarks_translate_error(ret, fsname, props)
294 return bmarks
295
296
297 def lzc_destroy_bookmarks(bookmarks):
298 '''
299 Destroy bookmarks.
300
301 :param bookmarks: a list of the bookmarks to be destroyed.
302 The bookmarks are specified as :file:`{fs}#{bmark}`.
303 :type bookmarks: list of bytes
304
305 :raises BookmarkDestructionFailure: if any of the bookmarks may not be destroyed.
306
307 The bookmarks must all be in the same pool.
308 Bookmarks that do not exist will be silently ignored.
309 This also includes the case where the filesystem component of the bookmark
310 name does not exist.
311 However, an invalid bookmark name will cause :exc:`.NameInvalid` error
312 reported in :attr:`SnapshotDestructionFailure.errors`.
313
314 Either all bookmarks that existed are destroyed or an exception is raised.
315 '''
316 errlist = {}
317 bmarks_dict = {name: None for name in bookmarks}
318 nvlist = nvlist_in(bmarks_dict)
319 with nvlist_out(errlist) as errlist_nvlist:
320 ret = _lib.lzc_destroy_bookmarks(nvlist, errlist_nvlist)
321 errors.lzc_destroy_bookmarks_translate_errors(ret, errlist, bookmarks)
322
323
324 def lzc_snaprange_space(firstsnap, lastsnap):
325 '''
326 Calculate a size of data referenced by snapshots in the inclusive range between
327 the ``firstsnap`` and the ``lastsnap`` and not shared with any other datasets.
328
329 :param bytes firstsnap: the name of the first snapshot in the range.
330 :param bytes lastsnap: the name of the last snapshot in the range.
331 :return: the calculated stream size, in bytes.
332 :rtype: `int` or `long`
333
334 :raises SnapshotNotFound: if either of the snapshots does not exist.
335 :raises NameInvalid: if the name of either snapshot is invalid.
336 :raises NameTooLong: if the name of either snapshot is too long.
337 :raises SnapshotMismatch: if ``fromsnap`` is not an ancestor snapshot of ``snapname``.
338 :raises PoolsDiffer: if the snapshots belong to different pools.
339
340 ``lzc_snaprange_space`` calculates total size of blocks that exist
341 because they are referenced only by one or more snapshots in the given range
342 but no other dataset.
343 In other words, this is the set of blocks that were born after the snap before
344 firstsnap, and died before the snap after the last snap.
345 Yet another interpretation is that the result of ``lzc_snaprange_space`` is the size
346 of the space that would be freed if the snapshots in the range are destroyed.
347
348 If the same snapshot is given as both the ``firstsnap`` and the ``lastsnap``.
349 In that case ``lzc_snaprange_space`` calculates space used by the snapshot.
350 '''
351 valp = _ffi.new('uint64_t *')
352 ret = _lib.lzc_snaprange_space(firstsnap, lastsnap, valp)
353 errors.lzc_snaprange_space_translate_error(ret, firstsnap, lastsnap)
354 return int(valp[0])
355
356
357 def lzc_hold(holds, fd=None):
358 '''
359 Create *user holds* on snapshots. If there is a hold on a snapshot,
360 the snapshot can not be destroyed. (However, it can be marked for deletion
361 by :func:`lzc_destroy_snaps` ( ``defer`` = `True` ).)
362
363 :param holds: the dictionary of names of the snapshots to hold mapped to the hold names.
364 :type holds: dict of bytes : bytes
365 :type fd: int or None
366 :param fd: if not None then it must be the result of :func:`os.open` called as ``os.open("/dev/zfs", O_EXCL)``.
367 :type fd: int or None
368 :return: a list of the snapshots that do not exist.
369 :rtype: list of bytes
370
371 :raises HoldFailure: if a hold was impossible on one or more of the snapshots.
372 :raises BadHoldCleanupFD: if ``fd`` is not a valid file descriptor associated with :file:`/dev/zfs`.
373
374 The snapshots must all be in the same pool.
375
376 If ``fd`` is not None, then when the ``fd`` is closed (including on process
377 termination), the holds will be released. If the system is shut down
378 uncleanly, the holds will be released when the pool is next opened
379 or imported.
380
381 Holds for snapshots which don't exist will be skipped and have an entry
382 added to the return value, but will not cause an overall failure.
383 No exceptions is raised if all holds, for snapshots that existed, were succesfully created.
384 Otherwise :exc:`.HoldFailure` exception is raised and no holds will be created.
385 :attr:`.HoldFailure.errors` may contain a single element for an error that is not
386 specific to any hold / snapshot, or it may contain one or more elements
387 detailing specific error per each affected hold.
388 '''
389 errlist = {}
390 if fd is None:
391 fd = -1
392 nvlist = nvlist_in(holds)
393 with nvlist_out(errlist) as errlist_nvlist:
394 ret = _lib.lzc_hold(nvlist, fd, errlist_nvlist)
395 errors.lzc_hold_translate_errors(ret, errlist, holds, fd)
396 # If there is no error (no exception raised by _handleErrList), but errlist
397 # is not empty, then it contains missing snapshots.
398 assert all(x == errno.ENOENT for x in errlist.itervalues())
399 return errlist.keys()
400
401
402 def lzc_release(holds):
403 '''
404 Release *user holds* on snapshots.
405
406 If the snapshot has been marked for
407 deferred destroy (by lzc_destroy_snaps(defer=B_TRUE)), it does not have
408 any clones, and all the user holds are removed, then the snapshot will be
409 destroyed.
410
411 The snapshots must all be in the same pool.
412
413 :param holds: a ``dict`` where keys are snapshot names and values are
414 lists of hold tags to remove.
415 :type holds: dict of bytes : list of bytes
416 :return: a list of any snapshots that do not exist and of any tags that do not
417 exist for existing snapshots.
418 Such tags are qualified with a corresponding snapshot name
419 using the following format :file:`{pool}/{fs}@{snap}#{tag}`
420 :rtype: list of bytes
421
422 :raises HoldReleaseFailure: if one or more existing holds could not be released.
423
424 Holds which failed to release because they didn't exist will have an entry
425 added to errlist, but will not cause an overall failure.
426
427 This call is success if ``holds`` was empty or all holds that
428 existed, were successfully removed.
429 Otherwise an exception will be raised.
430 '''
431 errlist = {}
432 holds_dict = {}
433 for snap, hold_list in holds.iteritems():
434 if not isinstance(hold_list, list):
435 raise TypeError('holds must be in a list')
436 holds_dict[snap] = {hold: None for hold in hold_list}
437 nvlist = nvlist_in(holds_dict)
438 with nvlist_out(errlist) as errlist_nvlist:
439 ret = _lib.lzc_release(nvlist, errlist_nvlist)
440 errors.lzc_release_translate_errors(ret, errlist, holds)
441 # If there is no error (no exception raised by _handleErrList), but errlist
442 # is not empty, then it contains missing snapshots and tags.
443 assert all(x == errno.ENOENT for x in errlist.itervalues())
444 return errlist.keys()
445
446
447 def lzc_get_holds(snapname):
448 '''
449 Retrieve list of *user holds* on the specified snapshot.
450
451 :param bytes snapname: the name of the snapshot.
452 :return: holds on the snapshot along with their creation times
453 in seconds since the epoch
454 :rtype: dict of bytes : int
455 '''
456 holds = {}
457 with nvlist_out(holds) as nvlist:
458 ret = _lib.lzc_get_holds(snapname, nvlist)
459 errors.lzc_get_holds_translate_error(ret, snapname)
460 return holds
461
462
463 def lzc_send(snapname, fromsnap, fd, flags=None):
464 '''
465 Generate a zfs send stream for the specified snapshot and write it to
466 the specified file descriptor.
467
468 :param bytes snapname: the name of the snapshot to send.
469 :param fromsnap: if not None the name of the starting snapshot
470 for the incremental stream.
471 :type fromsnap: bytes or None
472 :param int fd: the file descriptor to write the send stream to.
473 :param flags: the flags that control what enhanced features can be used
474 in the stream.
475 :type flags: list of bytes
476
477 :raises SnapshotNotFound: if either the starting snapshot is not `None` and does not exist,
478 or if the ending snapshot does not exist.
479 :raises NameInvalid: if the name of either snapshot is invalid.
480 :raises NameTooLong: if the name of either snapshot is too long.
481 :raises SnapshotMismatch: if ``fromsnap`` is not an ancestor snapshot of ``snapname``.
482 :raises PoolsDiffer: if the snapshots belong to different pools.
483 :raises IOError: if an input / output error occurs while writing to ``fd``.
484 :raises UnknownStreamFeature: if the ``flags`` contain an unknown flag name.
485
486 If ``fromsnap`` is None, a full (non-incremental) stream will be sent.
487 If ``fromsnap`` is not None, it must be the full name of a snapshot or
488 bookmark to send an incremental from, e.g. :file:`{pool}/{fs}@{earlier_snap}`
489 or :file:`{pool}/{fs}#{earlier_bmark}`.
490
491 The specified snapshot or bookmark must represent an earlier point in the history
492 of ``snapname``.
493 It can be an earlier snapshot in the same filesystem or zvol as ``snapname``,
494 or it can be the origin of ``snapname``'s filesystem, or an earlier
495 snapshot in the origin, etc.
496 ``fromsnap`` must be strictly an earlier snapshot, specifying the same snapshot
497 as both ``fromsnap`` and ``snapname`` is an error.
498
499 If ``flags`` contains *"large_blocks"*, the stream is permitted
500 to contain ``DRR_WRITE`` records with ``drr_length`` > 128K, and ``DRR_OBJECT``
501 records with ``drr_blksz`` > 128K.
502
503 If ``flags`` contains *"embedded_data"*, the stream is permitted
504 to contain ``DRR_WRITE_EMBEDDED`` records with
505 ``drr_etype`` == ``BP_EMBEDDED_TYPE_DATA``,
506 which the receiving system must support (as indicated by support
507 for the *embedded_data* feature).
508
509 .. note::
510 ``lzc_send`` can actually accept a filesystem name as the ``snapname``.
511 In that case ``lzc_send`` acts as if a temporary snapshot was created
512 after the start of the call and before the stream starts being produced.
513
514 .. note::
515 ``lzc_send`` does not return until all of the stream is written to ``fd``.
516
517 .. note::
518 ``lzc_send`` does *not* close ``fd`` upon returning.
519 '''
520 if fromsnap is not None:
521 c_fromsnap = fromsnap
522 else:
523 c_fromsnap = _ffi.NULL
524 c_flags = 0
525 if flags is None:
526 flags = []
527 for flag in flags:
528 c_flag = {
529 'embedded_data': _lib.LZC_SEND_FLAG_EMBED_DATA,
530 'large_blocks': _lib.LZC_SEND_FLAG_LARGE_BLOCK,
531 }.get(flag)
532 if c_flag is None:
533 raise exceptions.UnknownStreamFeature(flag)
534 c_flags |= c_flag
535
536 ret = _lib.lzc_send(snapname, c_fromsnap, fd, c_flags)
537 errors.lzc_send_translate_error(ret, snapname, fromsnap, fd, flags)
538
539
540 def lzc_send_space(snapname, fromsnap=None, flags=None):
541 '''
542 Estimate size of a full or incremental backup stream
543 given the optional starting snapshot and the ending snapshot.
544
545 :param bytes snapname: the name of the snapshot for which the estimate should be done.
546 :param fromsnap: the optional starting snapshot name.
547 If not `None` then an incremental stream size is estimated,
548 otherwise a full stream is esimated.
549 :type fromsnap: `bytes` or `None`
550 :param flags: the flags that control what enhanced features can be used
551 in the stream.
552 :type flags: list of bytes
553
554 :return: the estimated stream size, in bytes.
555 :rtype: `int` or `long`
556
557 :raises SnapshotNotFound: if either the starting snapshot is not `None` and does not exist,
558 or if the ending snapshot does not exist.
559 :raises NameInvalid: if the name of either snapshot is invalid.
560 :raises NameTooLong: if the name of either snapshot is too long.
561 :raises SnapshotMismatch: if ``fromsnap`` is not an ancestor snapshot of ``snapname``.
562 :raises PoolsDiffer: if the snapshots belong to different pools.
563
564 ``fromsnap``, if not ``None``, must be strictly an earlier snapshot,
565 specifying the same snapshot as both ``fromsnap`` and ``snapname`` is an error.
566 '''
567 if fromsnap is not None:
568 c_fromsnap = fromsnap
569 else:
570 c_fromsnap = _ffi.NULL
571 c_flags = 0
572 if flags is None:
573 flags = []
574 for flag in flags:
575 c_flag = {
576 'embedded_data': _lib.LZC_SEND_FLAG_EMBED_DATA,
577 'large_blocks': _lib.LZC_SEND_FLAG_LARGE_BLOCK,
578 }.get(flag)
579 if c_flag is None:
580 raise exceptions.UnknownStreamFeature(flag)
581 c_flags |= c_flag
582 valp = _ffi.new('uint64_t *')
583
584 ret = _lib.lzc_send_space(snapname, c_fromsnap, c_flags, valp)
585 errors.lzc_send_space_translate_error(ret, snapname, fromsnap)
586 return int(valp[0])
587
588
589 def lzc_receive(snapname, fd, force=False, raw=False, origin=None, props=None):
590 '''
591 Receive from the specified ``fd``, creating the specified snapshot.
592
593 :param bytes snapname: the name of the snapshot to create.
594 :param int fd: the file descriptor from which to read the stream.
595 :param bool force: whether to roll back or destroy the target filesystem
596 if that is required to receive the stream.
597 :param bool raw: whether this is a "raw" stream.
598 :param origin: the optional origin snapshot name if the stream is for a clone.
599 :type origin: bytes or None
600 :param props: the properties to set on the snapshot as *received* properties.
601 :type props: dict of bytes : Any
602
603 :raises IOError: if an input / output error occurs while reading from the ``fd``.
604 :raises DatasetExists: if the snapshot named ``snapname`` already exists.
605 :raises DatasetExists: if the stream is a full stream and the destination filesystem already exists.
606 :raises DatasetExists: if ``force`` is `True` but the destination filesystem could not
607 be rolled back to a matching snapshot because a newer snapshot
608 exists and it is an origin of a cloned filesystem.
609 :raises StreamMismatch: if an incremental stream is received and the latest
610 snapshot of the destination filesystem does not match
611 the source snapshot of the stream.
612 :raises StreamMismatch: if a full stream is received and the destination
613 filesystem already exists and it has at least one snapshot,
614 and ``force`` is `False`.
615 :raises StreamMismatch: if an incremental clone stream is received but the specified
616 ``origin`` is not the actual received origin.
617 :raises DestinationModified: if an incremental stream is received and the destination
618 filesystem has been modified since the last snapshot
619 and ``force`` is `False`.
620 :raises DestinationModified: if a full stream is received and the destination
621 filesystem already exists and it does not have any
622 snapshots, and ``force`` is `False`.
623 :raises DatasetNotFound: if the destination filesystem and its parent do not exist.
624 :raises DatasetNotFound: if the ``origin`` is not `None` and does not exist.
625 :raises DatasetBusy: if ``force`` is `True` but the destination filesystem could not
626 be rolled back to a matching snapshot because a newer snapshot
627 is held and could not be destroyed.
628 :raises DatasetBusy: if another receive operation is being performed on the
629 destination filesystem.
630 :raises BadStream: if the stream is corrupt or it is not recognized or it is
631 a compound stream or it is a clone stream, but ``origin``
632 is `None`.
633 :raises BadStream: if a clone stream is received and the destination filesystem
634 already exists.
635 :raises StreamFeatureNotSupported: if the stream has a feature that is not
636 supported on this side.
637 :raises PropertyInvalid: if one or more of the specified properties is invalid
638 or has an invalid type or value.
639 :raises NameInvalid: if the name of either snapshot is invalid.
640 :raises NameTooLong: if the name of either snapshot is too long.
641
642 .. note::
643 The ``origin`` is ignored if the actual stream is an incremental stream
644 that is not a clone stream and the destination filesystem exists.
645 If the stream is a full stream and the destination filesystem does not
646 exist then the ``origin`` is checked for existence: if it does not exist
647 :exc:`.DatasetNotFound` is raised, otherwise :exc:`.StreamMismatch` is
648 raised, because that snapshot can not have any relation to the stream.
649
650 .. note::
651 If ``force`` is `True` and the stream is incremental then the destination
652 filesystem is rolled back to a matching source snapshot if necessary.
653 Intermediate snapshots are destroyed in that case.
654
655 However, none of the existing snapshots may have the same name as
656 ``snapname`` even if such a snapshot were to be destroyed.
657 The existing ``snapname`` snapshot always causes :exc:`.SnapshotExists`
658 to be raised.
659
660 If ``force`` is `True` and the stream is a full stream then the destination
661 filesystem is replaced with the received filesystem unless the former
662 has any snapshots. This prevents the destination filesystem from being
663 rolled back / replaced.
664
665 .. note::
666 This interface does not work on dedup'd streams
667 (those with ``DMU_BACKUP_FEATURE_DEDUP``).
668
669 .. note::
670 ``lzc_receive`` does not return until all of the stream is read from ``fd``
671 and applied to the pool.
672
673 .. note::
674 ``lzc_receive`` does *not* close ``fd`` upon returning.
675 '''
676
677 if origin is not None:
678 c_origin = origin
679 else:
680 c_origin = _ffi.NULL
681 if props is None:
682 props = {}
683 nvlist = nvlist_in(props)
684 ret = _lib.lzc_receive(snapname, nvlist, c_origin, force, raw, fd)
685 errors.lzc_receive_translate_error(ret, snapname, fd, force, origin, props)
686
687
688 lzc_recv = lzc_receive
689
690
691 def lzc_receive_with_header(snapname, fd, header, force=False, origin=None, props=None):
692 '''
693 Like :func:`lzc_receive`, but allows the caller to read the begin record
694 and then to pass it in.
695
696 That could be useful if the caller wants to derive, for example,
697 the snapname or the origin parameters based on the information contained in
698 the begin record.
699 :func:`receive_header` can be used to receive the begin record from the file
700 descriptor.
701
702 :param bytes snapname: the name of the snapshot to create.
703 :param int fd: the file descriptor from which to read the stream.
704 :param header: the stream's begin header.
705 :type header: ``cffi`` `CData` representing the header structure.
706 :param bool force: whether to roll back or destroy the target filesystem
707 if that is required to receive the stream.
708 :param origin: the optional origin snapshot name if the stream is for a clone.
709 :type origin: bytes or None
710 :param props: the properties to set on the snapshot as *received* properties.
711 :type props: dict of bytes : Any
712
713 :raises IOError: if an input / output error occurs while reading from the ``fd``.
714 :raises DatasetExists: if the snapshot named ``snapname`` already exists.
715 :raises DatasetExists: if the stream is a full stream and the destination filesystem already exists.
716 :raises DatasetExists: if ``force`` is `True` but the destination filesystem could not
717 be rolled back to a matching snapshot because a newer snapshot
718 exists and it is an origin of a cloned filesystem.
719 :raises StreamMismatch: if an incremental stream is received and the latest
720 snapshot of the destination filesystem does not match
721 the source snapshot of the stream.
722 :raises StreamMismatch: if a full stream is received and the destination
723 filesystem already exists and it has at least one snapshot,
724 and ``force`` is `False`.
725 :raises StreamMismatch: if an incremental clone stream is received but the specified
726 ``origin`` is not the actual received origin.
727 :raises DestinationModified: if an incremental stream is received and the destination
728 filesystem has been modified since the last snapshot
729 and ``force`` is `False`.
730 :raises DestinationModified: if a full stream is received and the destination
731 filesystem already exists and it does not have any
732 snapshots, and ``force`` is `False`.
733 :raises DatasetNotFound: if the destination filesystem and its parent do not exist.
734 :raises DatasetNotFound: if the ``origin`` is not `None` and does not exist.
735 :raises DatasetBusy: if ``force`` is `True` but the destination filesystem could not
736 be rolled back to a matching snapshot because a newer snapshot
737 is held and could not be destroyed.
738 :raises DatasetBusy: if another receive operation is being performed on the
739 destination filesystem.
740 :raises BadStream: if the stream is corrupt or it is not recognized or it is
741 a compound stream or it is a clone stream, but ``origin``
742 is `None`.
743 :raises BadStream: if a clone stream is received and the destination filesystem
744 already exists.
745 :raises StreamFeatureNotSupported: if the stream has a feature that is not
746 supported on this side.
747 :raises PropertyInvalid: if one or more of the specified properties is invalid
748 or has an invalid type or value.
749 :raises NameInvalid: if the name of either snapshot is invalid.
750 :raises NameTooLong: if the name of either snapshot is too long.
751 '''
752
753 if origin is not None:
754 c_origin = origin
755 else:
756 c_origin = _ffi.NULL
757 if props is None:
758 props = {}
759 nvlist = nvlist_in(props)
760 ret = _lib.lzc_receive_with_header(snapname, nvlist, c_origin, force,
761 False, fd, header)
762 errors.lzc_receive_translate_error(ret, snapname, fd, force, origin, props)
763
764
765 def receive_header(fd):
766 '''
767 Read the begin record of the ZFS backup stream from the given file descriptor.
768
769 This is a helper function for :func:`lzc_receive_with_header`.
770
771 :param int fd: the file descriptor from which to read the stream.
772 :return: a tuple with two elements where the first one is a Python `dict` representing
773 the fields of the begin record and the second one is an opaque object
774 suitable for passing to :func:`lzc_receive_with_header`.
775 :raises IOError: if an input / output error occurs while reading from the ``fd``.
776
777 At present the following fields can be of interest in the header:
778
779 drr_toname : bytes
780 the name of the snapshot for which the stream has been created
781 drr_toguid : integer
782 the GUID of the snapshot for which the stream has been created
783 drr_fromguid : integer
784 the GUID of the starting snapshot in the case the stream is incremental,
785 zero otherwise
786 drr_flags : integer
787 the flags describing the stream's properties
788 drr_type : integer
789 the type of the dataset for which the stream has been created
790 (volume, filesystem)
791 '''
792 # read sizeof(dmu_replay_record_t) bytes directly into the memort backing 'record'
793 record = _ffi.new("dmu_replay_record_t *")
794 _ffi.buffer(record)[:] = os.read(fd, _ffi.sizeof(record[0]))
795 # get drr_begin member and its representation as a Pythn dict
796 drr_begin = record.drr_u.drr_begin
797 header = {}
798 for field, descr in _ffi.typeof(drr_begin).fields:
799 if descr.type.kind == 'primitive':
800 header[field] = getattr(drr_begin, field)
801 elif descr.type.kind == 'enum':
802 header[field] = getattr(drr_begin, field)
803 elif descr.type.kind == 'array' and descr.type.item.cname == 'char':
804 header[field] = _ffi.string(getattr(drr_begin, field))
805 else:
806 raise TypeError('Unexpected field type in drr_begin: ' + str(descr.type))
807 return (header, record)
808
809
810 def lzc_exists(name):
811 '''
812 Check if a dataset (a filesystem, or a volume, or a snapshot)
813 with the given name exists.
814
815 :param bytes name: the dataset name to check.
816 :return: `True` if the dataset exists, `False` otherwise.
817 :rtype: bool
818
819 .. note::
820 ``lzc_exists`` can not be used to check for existence of bookmarks.
821 '''
822 ret = _lib.lzc_exists(name)
823 return bool(ret)
824
825
826 def is_supported(func):
827 '''
828 Check whether C *libzfs_core* provides implementation required
829 for the given Python wrapper.
830
831 If `is_supported` returns ``False`` for the function, then
832 calling the function would result in :exc:`NotImplementedError`.
833
834 :param function func: the function to check.
835 :return bool: whether the function can be used.
836 '''
837 fname = func.__name__
838 if fname not in globals():
839 raise ValueError(fname + ' is not from libzfs_core')
840 if not callable(func):
841 raise ValueError(fname + ' is not a function')
842 if not fname.startswith("lzc_"):
843 raise ValueError(fname + ' is not a libzfs_core API function')
844 check_func = getattr(func, "_check_func", None)
845 if check_func is not None:
846 return is_supported(check_func)
847 return getattr(_lib, fname, None) is not None
848
849
850 def _uncommitted(depends_on=None):
851 '''
852 Mark an API function as being an uncommitted extension that might not be
853 available.
854
855 :param function depends_on: the function that would be checked
856 instead of a decorated function.
857 For example, if the decorated function uses
858 another uncommitted function.
859
860 This decorator transforms a decorated function to raise
861 :exc:`NotImplementedError` if the C libzfs_core library does not provide
862 a function with the same name as the decorated function.
863
864 The optional `depends_on` parameter can be provided if the decorated
865 function does not directly call the C function but instead calls another
866 Python function that follows the typical convention.
867 One example is :func:`lzc_list_snaps` that calls :func:`lzc_list` that
868 calls ``lzc_list`` in libzfs_core.
869
870 This decorator is implemented using :func:`is_supported`.
871 '''
872 def _uncommitted_decorator(func, depends_on=depends_on):
873 @functools.wraps(func)
874 def _f(*args, **kwargs):
875 if not is_supported(_f):
876 raise NotImplementedError(func.__name__)
877 return func(*args, **kwargs)
878 if depends_on is not None:
879 _f._check_func = depends_on
880 return _f
881 return _uncommitted_decorator
882
883
884 @_uncommitted()
885 def lzc_promote(name):
886 '''
887 Promotes the ZFS dataset.
888
889 :param bytes name: the name of the dataset to promote.
890 :raises NameInvalid: if the dataset name is invalid.
891 :raises NameTooLong: if the dataset name is too long.
892 :raises NameTooLong: if the dataset's origin has a snapshot that,
893 if transferred to the dataset, would get
894 a too long name.
895 :raises NotClone: if the dataset is not a clone.
896 :raises FilesystemNotFound: if the dataset does not exist.
897 :raises SnapshotExists: if the dataset already has a snapshot with
898 the same name as one of the origin's snapshots.
899 '''
900 ret = _lib.lzc_promote(name, _ffi.NULL, _ffi.NULL)
901 errors.lzc_promote_translate_error(ret, name)
902
903
904 @_uncommitted()
905 def lzc_rename(source, target):
906 '''
907 Rename the ZFS dataset.
908
909 :param source name: the current name of the dataset to rename.
910 :param target name: the new name of the dataset.
911 :raises NameInvalid: if either the source or target name is invalid.
912 :raises NameTooLong: if either the source or target name is too long.
913 :raises NameTooLong: if a snapshot of the source would get a too long
914 name after renaming.
915 :raises FilesystemNotFound: if the source does not exist.
916 :raises FilesystemNotFound: if the target's parent does not exist.
917 :raises FilesystemExists: if the target already exists.
918 :raises PoolsDiffer: if the source and target belong to different pools.
919 '''
920 ret = _lib.lzc_rename(source, target, _ffi.NULL, _ffi.NULL)
921 errors.lzc_rename_translate_error(ret, source, target)
922
923
924 @_uncommitted()
925 def lzc_destroy_one(name):
926 '''
927 Destroy the ZFS dataset.
928
929 :param bytes name: the name of the dataset to destroy.
930 :raises NameInvalid: if the dataset name is invalid.
931 :raises NameTooLong: if the dataset name is too long.
932 :raises FilesystemNotFound: if the dataset does not exist.
933 '''
934 ret = _lib.lzc_destroy_one(name, _ffi.NULL)
935 errors.lzc_destroy_translate_error(ret, name)
936
937
938 # As the extended API is not committed yet, the names of the new interfaces
939 # are not settled down yet.
940 # lzc_destroy() might make more sense as we do not have lzc_create_one().
941 lzc_destroy = lzc_destroy_one
942
943
944 @_uncommitted()
945 def lzc_inherit(name, prop):
946 '''
947 Inherit properties from a parent dataset of the given ZFS dataset.
948
949 :param bytes name: the name of the dataset.
950 :param bytes prop: the name of the property to inherit.
951 :raises NameInvalid: if the dataset name is invalid.
952 :raises NameTooLong: if the dataset name is too long.
953 :raises DatasetNotFound: if the dataset does not exist.
954 :raises PropertyInvalid: if one or more of the specified properties is invalid
955 or has an invalid type or value.
956
957 Inheriting a property actually resets it to its default value
958 or removes it if it's a user property, so that the property could be
959 inherited if it's inheritable. If the property is not inheritable
960 then it would just have its default value.
961
962 This function can be used on snapshots to inherit user defined properties.
963 '''
964 ret = _lib.lzc_inherit(name, prop, _ffi.NULL)
965 errors.lzc_inherit_prop_translate_error(ret, name, prop)
966
967
968 # As the extended API is not committed yet, the names of the new interfaces
969 # are not settled down yet.
970 # lzc_inherit_prop makes it clearer what is to be inherited.
971 lzc_inherit_prop = lzc_inherit
972
973
974 @_uncommitted()
975 def lzc_set_props(name, prop, val):
976 '''
977 Set properties of the ZFS dataset.
978
979 :param bytes name: the name of the dataset.
980 :param bytes prop: the name of the property.
981 :param Any val: the value of the property.
982 :raises NameInvalid: if the dataset name is invalid.
983 :raises NameTooLong: if the dataset name is too long.
984 :raises DatasetNotFound: if the dataset does not exist.
985 :raises NoSpace: if the property controls a quota and the values is
986 too small for that quota.
987 :raises PropertyInvalid: if one or more of the specified properties is invalid
988 or has an invalid type or value.
989
990 This function can be used on snapshots to set user defined properties.
991
992 .. note::
993 An attempt to set a readonly / statistic property is ignored
994 without reporting any error.
995 '''
996 props = {prop: val}
997 props_nv = nvlist_in(props)
998 ret = _lib.lzc_set_props(name, props_nv, _ffi.NULL, _ffi.NULL)
999 errors.lzc_set_prop_translate_error(ret, name, prop, val)
1000
1001
1002 # As the extended API is not committed yet, the names of the new interfaces
1003 # are not settled down yet.
1004 # It's not clear if atomically setting multiple properties is an achievable
1005 # goal and an interface acting on mutiple entities must do so atomically
1006 # by convention.
1007 # Being able to set a single property at a time is sufficient for ClusterHQ.
1008 lzc_set_prop = lzc_set_props
1009
1010
1011 @_uncommitted()
1012 def lzc_list(name, options):
1013 '''
1014 List subordinate elements of the given dataset.
1015
1016 This function can be used to list child datasets and snapshots
1017 of the given dataset. The listed elements can be filtered by
1018 their type and by their depth relative to the starting dataset.
1019
1020 :param bytes name: the name of the dataset to be listed, could
1021 be a snapshot or a dataset.
1022 :param options: a `dict` of the options that control the listing
1023 behavior.
1024 :type options: dict of bytes:Any
1025 :return: a pair of file descriptors the first of which can be
1026 used to read the listing.
1027 :rtype: tuple of (int, int)
1028 :raises DatasetNotFound: if the dataset does not exist.
1029
1030 Two options are currently available:
1031
1032 recurse : integer or None
1033 specifies depth of the recursive listing. If ``None`` the
1034 depth is not limited.
1035 Absence of this option means that only the given dataset
1036 is listed.
1037
1038 type : dict of bytes:None
1039 specifies dataset types to include into the listing.
1040 Currently allowed keys are "filesystem", "volume", "snapshot".
1041 Absence of this option implies all types.
1042
1043 The first of the returned file descriptors can be used to
1044 read the listing in a binary encounded format. The data is
1045 a series of variable sized records each starting with a fixed
1046 size header, the header is followed by a serialized ``nvlist``.
1047 Each record describes a single element and contains the element's
1048 name as well as its properties.
1049 The file descriptor must be closed after reading from it.
1050
1051 The second file descriptor represents a pipe end to which the
1052 kernel driver is writing information. It should not be closed
1053 until all interesting information has been read and it must
1054 be explicitly closed afterwards.
1055 '''
1056 (rfd, wfd) = os.pipe()
1057 fcntl.fcntl(rfd, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
1058 fcntl.fcntl(wfd, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
1059 options = options.copy()
1060 options['fd'] = int32_t(wfd)
1061 opts_nv = nvlist_in(options)
1062 ret = _lib.lzc_list(name, opts_nv)
1063 if ret == errno.ESRCH:
1064 return (None, None)
1065 errors.lzc_list_translate_error(ret, name, options)
1066 return (rfd, wfd)
1067
1068
1069 # Description of the binary format used to pass data from the kernel.
1070 _PIPE_RECORD_FORMAT = 'IBBBB'
1071 _PIPE_RECORD_SIZE = struct.calcsize(_PIPE_RECORD_FORMAT)
1072
1073
1074 def _list(name, recurse=None, types=None):
1075 '''
1076 A wrapper for :func:`lzc_list` that hides details of working
1077 with the file descriptors and provides data in an easy to
1078 consume format.
1079
1080 :param bytes name: the name of the dataset to be listed, could
1081 be a snapshot, a volume or a filesystem.
1082 :param recurse: specifies depth of the recursive listing.
1083 If ``None`` the depth is not limited.
1084 :param types: specifies dataset types to include into the listing.
1085 Currently allowed keys are "filesystem", "volume", "snapshot".
1086 ``None`` is equivalent to specifying the type of the dataset
1087 named by `name`.
1088 :type types: list of bytes or None
1089 :type recurse: integer or None
1090 :return: a list of dictionaries each describing a single listed
1091 element.
1092 :rtype: list of dict
1093 '''
1094 options = {}
1095
1096 # Convert types to a dict suitable for mapping to an nvlist.
1097 if types is not None:
1098 types = {x: None for x in types}
1099 options['type'] = types
1100 if recurse is None or recurse > 0:
1101 options['recurse'] = recurse
1102
1103 # Note that other_fd is used by the kernel side to write
1104 # the data, so we have to keep that descriptor open until
1105 # we are done.
1106 # Also, we have to explicitly close the descriptor as the
1107 # kernel doesn't do that.
1108 (fd, other_fd) = lzc_list(name, options)
1109 if fd is None:
1110 return
1111
1112 try:
1113 while True:
1114 record_bytes = os.read(fd, _PIPE_RECORD_SIZE)
1115 if not record_bytes:
1116 break
1117 (size, _, err, _, _) = struct.unpack(
1118 _PIPE_RECORD_FORMAT, record_bytes)
1119 if err == errno.ESRCH:
1120 break
1121 errors.lzc_list_translate_error(err, name, options)
1122 if size == 0:
1123 break
1124 data_bytes = os.read(fd, size)
1125 result = {}
1126 with nvlist_out(result) as nvp:
1127 ret = _lib.nvlist_unpack(data_bytes, size, nvp, 0)
1128 if ret != 0:
1129 raise exceptions.ZFSGenericError(ret, None,
1130 "Failed to unpack list data")
1131 yield result
1132 finally:
1133 os.close(other_fd)
1134 os.close(fd)
1135
1136
1137 @_uncommitted(lzc_list)
1138 def lzc_get_props(name):
1139 '''
1140 Get properties of the ZFS dataset.
1141
1142 :param bytes name: the name of the dataset.
1143 :raises DatasetNotFound: if the dataset does not exist.
1144 :raises NameInvalid: if the dataset name is invalid.
1145 :raises NameTooLong: if the dataset name is too long.
1146 :return: a dictionary mapping the property names to their values.
1147 :rtype: dict of bytes:Any
1148
1149 .. note::
1150 The value of ``clones`` property is a `list` of clone names
1151 as byte strings.
1152
1153 .. warning::
1154 The returned dictionary does not contain entries for properties
1155 with default values. One exception is the ``mountpoint`` property
1156 for which the default value is derived from the dataset name.
1157 '''
1158 result = next(_list(name, recurse=0))
1159 is_snapshot = result['dmu_objset_stats']['dds_is_snapshot']
1160 result = result['properties']
1161 # In most cases the source of the property is uninteresting and the
1162 # value alone is sufficient. One exception is the 'mountpoint'
1163 # property the final value of which is not the same as the inherited
1164 # value.
1165 mountpoint = result.get('mountpoint')
1166 if mountpoint is not None:
1167 mountpoint_src = mountpoint['source']
1168 mountpoint_val = mountpoint['value']
1169 # 'source' is the name of the dataset that has 'mountpoint' set
1170 # to a non-default value and from which the current dataset inherits
1171 # the property. 'source' can be the current dataset if its
1172 # 'mountpoint' is explicitly set.
1173 # 'source' can also be a special value like '$recvd', that case
1174 # is equivalent to the property being set on the current dataset.
1175 # Note that a normal mountpoint value should start with '/'
1176 # unlike the special values "none" and "legacy".
1177 if mountpoint_val.startswith('/') and not mountpoint_src.startswith('$'):
1178 mountpoint_val = mountpoint_val + name[len(mountpoint_src):]
1179 elif not is_snapshot:
1180 mountpoint_val = '/' + name
1181 else:
1182 mountpoint_val = None
1183 result = {k: v['value'] for k, v in result.iteritems()}
1184 if 'clones' in result:
1185 result['clones'] = result['clones'].keys()
1186 if mountpoint_val is not None:
1187 result['mountpoint'] = mountpoint_val
1188 return result
1189
1190
1191 @_uncommitted(lzc_list)
1192 def lzc_list_children(name):
1193 '''
1194 List the children of the ZFS dataset.
1195
1196 :param bytes name: the name of the dataset.
1197 :return: an iterator that produces the names of the children.
1198 :raises NameInvalid: if the dataset name is invalid.
1199 :raises NameTooLong: if the dataset name is too long.
1200 :raises DatasetNotFound: if the dataset does not exist.
1201
1202 .. warning::
1203 If the dataset does not exist, then the returned iterator would produce
1204 no results and no error is reported.
1205 That case is indistinguishable from the dataset having no children.
1206
1207 An attempt to list children of a snapshot is silently ignored as well.
1208 '''
1209 children = []
1210 for entry in _list(name, recurse=1, types=['filesystem', 'volume']):
1211 child = entry['name']
1212 if child != name:
1213 children.append(child)
1214
1215 return iter(children)
1216
1217
1218 @_uncommitted(lzc_list)
1219 def lzc_list_snaps(name):
1220 '''
1221 List the snapshots of the ZFS dataset.
1222
1223 :param bytes name: the name of the dataset.
1224 :return: an iterator that produces the names of the snapshots.
1225 :raises NameInvalid: if the dataset name is invalid.
1226 :raises NameTooLong: if the dataset name is too long.
1227 :raises DatasetNotFound: if the dataset does not exist.
1228
1229 .. warning::
1230 If the dataset does not exist, then the returned iterator would produce
1231 no results and no error is reported.
1232 That case is indistinguishable from the dataset having no snapshots.
1233
1234 An attempt to list snapshots of a snapshot is silently ignored as well.
1235 '''
1236 snaps = []
1237 for entry in _list(name, recurse=1, types=['snapshot']):
1238 snap = entry['name']
1239 if snap != name:
1240 snaps.append(snap)
1241
1242 return iter(snaps)
1243
1244
1245 # TODO: a better way to init and uninit the library
1246 def _initialize():
1247 class LazyInit(object):
1248
1249 def __init__(self, lib):
1250 self._lib = lib
1251 self._inited = False
1252 self._lock = threading.Lock()
1253
1254 def __getattr__(self, name):
1255 if not self._inited:
1256 with self._lock:
1257 if not self._inited:
1258 ret = self._lib.libzfs_core_init()
1259 if ret != 0:
1260 raise exceptions.ZFSInitializationFailed(ret)
1261 self._inited = True
1262 return getattr(self._lib, name)
1263
1264 return LazyInit(libzfs_core.lib)
1265
1266 _ffi = libzfs_core.ffi
1267 _lib = _initialize()
1268
1269
1270 # vim: softtabstop=4 tabstop=4 expandtab shiftwidth=4