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