-
Notifications
You must be signed in to change notification settings - Fork 24
/
libzfs.pyx
4773 lines (3801 loc) · 159 KB
/
libzfs.pyx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# encoding: utf-8
# cython: language_level=3, c_string_type=unicode, c_string_encoding=default
import os
import stat
import enum
import errno
import itertools
import tempfile
import logging
import time
import threading
cimport libzfs
cimport zfs
cimport nvpair
from datetime import datetime
from libc.errno cimport errno
from libc.string cimport memset, strncpy
from libc.stdlib cimport realloc
import errno as py_errno
import urllib.parse
GLOBAL_CONTEXT_LOCK = threading.Lock()
logger = logging.getLogger(__name__)
include "config.pxi"
include "nvpair.pxi"
include "converter.pxi"
class DatasetType(enum.IntEnum):
FILESYSTEM = zfs.ZFS_TYPE_FILESYSTEM
VOLUME = zfs.ZFS_TYPE_VOLUME
SNAPSHOT = zfs.ZFS_TYPE_SNAPSHOT
BOOKMARK = zfs.ZFS_TYPE_BOOKMARK
class UserquotaProp(enum.IntEnum):
USERUSED = zfs.ZFS_PROP_USERUSED
USERQUOTA = zfs.ZFS_PROP_USERQUOTA
GROUPUSED = zfs.ZFS_PROP_GROUPUSED
GROUPQUOTA = zfs.ZFS_PROP_GROUPQUOTA
IF HAVE_SPA_FEATURE_USEROBJ_ACCOUNTING:
USEROBJUSED = zfs.ZFS_PROP_USEROBJUSED
USEROBJQUOTA = zfs.ZFS_PROP_USEROBJQUOTA
GROUPOBJUSED = zfs.ZFS_PROP_GROUPOBJUSED
GROUPOBJQUOTA = zfs.ZFS_PROP_GROUPOBJQUOTA
IF HAVE_SPA_FEATURE_PROJECT_QUOTA:
PROJECTUSED = zfs.ZFS_PROP_PROJECTUSED
PROJECTQUOTA = zfs.ZFS_PROP_PROJECTQUOTA
PROJECTOBJUSED = zfs.ZFS_PROP_PROJECTOBJUSED
PROJECTOBJQUOTA = zfs.ZFS_PROP_PROJECTOBJQUOTA
class Error(enum.IntEnum):
SUCCESS = libzfs.EZFS_SUCCESS
NOMEM = libzfs.EZFS_NOMEM
BADPROP = libzfs.EZFS_BADPROP
PROPREADONLY = libzfs.EZFS_PROPREADONLY
PROPTYPE = libzfs.EZFS_PROPTYPE
PROPNONINHERIT = libzfs.EZFS_PROPNONINHERIT
PROPSPACE = libzfs.EZFS_PROPSPACE
BADTYPE = libzfs.EZFS_BADTYPE
BUSY = libzfs.EZFS_BUSY
EXISTS = libzfs.EZFS_EXISTS
NOENT = libzfs.EZFS_NOENT
BADSTREAM = libzfs.EZFS_BADSTREAM
DSREADONLY = libzfs.EZFS_DSREADONLY
VOLTOOBIG = libzfs.EZFS_VOLTOOBIG
INVALIDNAME = libzfs.EZFS_INVALIDNAME
BADRESTORE = libzfs.EZFS_BADRESTORE
BADBACKUP = libzfs.EZFS_BADBACKUP
BADTARGET = libzfs.EZFS_BADTARGET
NODEVICE = libzfs.EZFS_NODEVICE
BADDEV = libzfs.EZFS_BADDEV
NOREPLICAS = libzfs.EZFS_NOREPLICAS
RESILVERING = libzfs.EZFS_RESILVERING
BADVERSION = libzfs.EZFS_BADVERSION
POOLUNAVAIL = libzfs.EZFS_POOLUNAVAIL
DEVOVERFLOW = libzfs.EZFS_DEVOVERFLOW
BADPATH = libzfs.EZFS_BADPATH
CROSSTARGET = libzfs.EZFS_CROSSTARGET
ZONED = libzfs.EZFS_ZONED
MOUNTFAILED = libzfs.EZFS_MOUNTFAILED
UMOUNTFAILED = libzfs.EZFS_UMOUNTFAILED
UNSHARENFSFAILED = libzfs.EZFS_UNSHARENFSFAILED
SHARENFSFAILED = libzfs.EZFS_SHARENFSFAILED
PERM = libzfs.EZFS_PERM
NOSPC = libzfs.EZFS_NOSPC
FAULT = libzfs.EZFS_FAULT
IO = libzfs.EZFS_IO
INTR = libzfs.EZFS_INTR
ISSPARE = libzfs.EZFS_ISSPARE
INVALCONFIG = libzfs.EZFS_INVALCONFIG
RECURSIVE = libzfs.EZFS_RECURSIVE
NOHISTORY = libzfs.EZFS_NOHISTORY
POOLPROPS = libzfs.EZFS_POOLPROPS
POOL_NOTSUP = libzfs.EZFS_POOL_NOTSUP
INVALARG = libzfs.EZFS_POOL_INVALARG
NAMETOOLONG = libzfs.EZFS_NAMETOOLONG
OPENFAILED = libzfs.EZFS_OPENFAILED
NOCAP = libzfs.EZFS_NOCAP
LABELFAILED = libzfs.EZFS_LABELFAILED
BADWHO = libzfs.EZFS_BADWHO
BADPERM = libzfs.EZFS_BADPERM
BADPERMSET = libzfs.EZFS_BADPERMSET
NODELEGATION = libzfs.EZFS_NODELEGATION
UNSHARESMBFAILED = libzfs.EZFS_UNSHARESMBFAILED
SHARESMBFAILED = libzfs.EZFS_SHARESMBFAILED
BADCACHE = libzfs.EZFS_BADCACHE
ISL2CACHE = libzfs.EZFS_ISL2CACHE
VDEVNOTSUP = libzfs.EZFS_VDEVNOTSUP
NOTSUP = libzfs.EZFS_NOTSUP
SPARE = libzfs.EZFS_ACTIVE_SPARE
LOGS = libzfs.EZFS_UNPLAYED_LOGS
RELE = libzfs.EZFS_REFTAG_RELE
HOLD = libzfs.EZFS_REFTAG_HOLD
TAGTOOLONG = libzfs.EZFS_TAGTOOLONG
PIPEFAILED = libzfs.EZFS_PIPEFAILED
THREADCREATEFAILED = libzfs.EZFS_THREADCREATEFAILED
ONLINE = libzfs.EZFS_POSTSPLIT_ONLINE
SCRUBBING = libzfs.EZFS_SCRUBBING
SCRUB = libzfs.EZFS_NO_SCRUB
DIFF = libzfs.EZFS_DIFF
DIFFDATA = libzfs.EZFS_DIFFDATA
POOLREADONLY = libzfs.EZFS_POOLREADONLY
UNKNOWN = libzfs.EZFS_UNKNOWN
IF HAVE_ZFS_ENCRYPTION:
CRYPTO_FAILED = libzfs.EZFS_CRYPTOFAILED
class LpcError(enum.IntEnum):
IF HAVE_ZPOOL_SEARCH_IMPORT_LIBZUTIL and HAVE_ZPOOL_SEARCH_IMPORT_PARAMS == 2:
SUCCESS = libzfs.LPC_SUCCESS
BADCACHE = libzfs.LPC_BADCACHE
BADPATH = libzfs.LPC_BADPATH
NOMEM = libzfs.LPC_NOMEM
EACCESS = libzfs.LPC_EACCESS
UNKNOWN = libzfs.LPC_UNKNOWN
class PropertySource(enum.IntEnum):
NONE = zfs.ZPROP_SRC_NONE
DEFAULT = zfs.ZPROP_SRC_DEFAULT
TEMPORARY = zfs.ZPROP_SRC_TEMPORARY
LOCAL = zfs.ZPROP_SRC_LOCAL
INHERITED = zfs.ZPROP_SRC_INHERITED
RECEIVED = zfs.ZPROP_SRC_RECEIVED
class VDevState(enum.IntEnum):
UNKNOWN = zfs.VDEV_STATE_UNKNOWN
CLOSED = zfs.VDEV_STATE_CLOSED
OFFLINE = zfs.VDEV_STATE_OFFLINE
REMOVED = zfs.VDEV_STATE_REMOVED
CANT_OPEN = zfs.VDEV_STATE_CANT_OPEN
FAULTED = zfs.VDEV_STATE_FAULTED
DEGRADED = zfs.VDEV_STATE_DEGRADED
HEALTHY = zfs.VDEV_STATE_HEALTHY
class VDevAuxState(enum.IntEnum):
NONE = zfs.VDEV_AUX_NONE
OPEN_FAILED = zfs.VDEV_AUX_OPEN_FAILED
CORRUPT_DATA = zfs.VDEV_AUX_CORRUPT_DATA
NO_REPLICAS = zfs.VDEV_AUX_NO_REPLICAS
BAD_GUID_SUM = zfs.VDEV_AUX_BAD_GUID_SUM
TOO_SMALL = zfs.VDEV_AUX_TOO_SMALL
BAD_LABEL = zfs.VDEV_AUX_BAD_LABEL
VERSION_NEWER = zfs.VDEV_AUX_VERSION_NEWER
VERSION_OLDER = zfs.VDEV_AUX_VERSION_OLDER
UNSUP_FEAT = zfs.VDEV_AUX_UNSUP_FEAT
SPARED = zfs.VDEV_AUX_SPARED
ERR_EXCEEDED = zfs.VDEV_AUX_ERR_EXCEEDED
IO_FAILURE = zfs.VDEV_AUX_IO_FAILURE
BAD_LOG = zfs.VDEV_AUX_BAD_LOG
EXTERNAL = zfs.VDEV_AUX_EXTERNAL
SPLIT_POOL = zfs.VDEV_AUX_SPLIT_POOL
IF HAVE_VDEV_AUX_ASHIFT_TOO_BIG:
ASHIFT_TOO_BIG = zfs.VDEV_AUX_ASHIFT_TOO_BIG
class PoolState(enum.IntEnum):
ACTIVE = zfs.POOL_STATE_ACTIVE
EXPORTED = zfs.POOL_STATE_EXPORTED
DESTROYED = zfs.POOL_STATE_DESTROYED
SPARE = zfs.POOL_STATE_SPARE
L2CACHE = zfs.POOL_STATE_L2CACHE
UNINITIALIZED = zfs.POOL_STATE_UNINITIALIZED
UNAVAIL = zfs.POOL_STATE_UNAVAIL
POTENTIALLY_ACTIVE = zfs.POOL_STATE_POTENTIALLY_ACTIVE
class ScanFunction(enum.IntEnum):
NONE = zfs.POOL_SCAN_NONE
SCRUB = zfs.POOL_SCAN_SCRUB
RESILVER = zfs.POOL_SCAN_RESILVER
class PoolStatus(enum.IntEnum):
CORRUPT_CACHE = libzfs.ZPOOL_STATUS_CORRUPT_CACHE
MISSING_DEV_R = libzfs.ZPOOL_STATUS_MISSING_DEV_R
MISSING_DEV_NR = libzfs.ZPOOL_STATUS_MISSING_DEV_NR
CORRUPT_LABEL_R = libzfs.ZPOOL_STATUS_CORRUPT_LABEL_R
CORRUPT_LABEL_NR = libzfs.ZPOOL_STATUS_CORRUPT_LABEL_NR
BAD_GUID_SUM = libzfs.ZPOOL_STATUS_BAD_GUID_SUM
CORRUPT_POOL = libzfs.ZPOOL_STATUS_CORRUPT_POOL
CORRUPT_DATA = libzfs.ZPOOL_STATUS_CORRUPT_DATA
FAILING_DEV = libzfs.ZPOOL_STATUS_FAILING_DEV
VERSION_NEWER = libzfs.ZPOOL_STATUS_VERSION_NEWER
HOSTID_MISMATCH = libzfs.ZPOOL_STATUS_HOSTID_MISMATCH
HOSTID_ACTIVE = libzfs.ZPOOL_STATUS_HOSTID_ACTIVE
HOSTID_REQUIRED = libzfs.ZPOOL_STATUS_HOSTID_REQUIRED
IO_FAILURE_WAIT = libzfs.ZPOOL_STATUS_IO_FAILURE_WAIT
IO_FAILURE_CONTINUE = libzfs.ZPOOL_STATUS_IO_FAILURE_CONTINUE
IO_FAILURE_MMP = libzfs.ZPOOL_STATUS_IO_FAILURE_MMP
BAD_LOG = libzfs.ZPOOL_STATUS_BAD_LOG
IF HAVE_ZPOOL_STATUS_ERRATA:
ERRATA = libzfs.ZPOOL_STATUS_ERRATA
UNSUP_FEAT_READ = libzfs.ZPOOL_STATUS_UNSUP_FEAT_READ
UNSUP_FEAT_WRITE = libzfs.ZPOOL_STATUS_UNSUP_FEAT_WRITE
FAULTED_DEV_R = libzfs.ZPOOL_STATUS_FAULTED_DEV_R
FAULTED_DEV_NR = libzfs.ZPOOL_STATUS_FAULTED_DEV_NR
VERSION_OLDER = libzfs.ZPOOL_STATUS_VERSION_OLDER
FEAT_DISABLED = libzfs.ZPOOL_STATUS_FEAT_DISABLED
RESILVERING = libzfs.ZPOOL_STATUS_RESILVERING
OFFLINE_DEV = libzfs.ZPOOL_STATUS_OFFLINE_DEV
REMOVED_DEV = libzfs.ZPOOL_STATUS_REMOVED_DEV
IF HAVE_ZPOOL_STATUS_REBUILDING:
REBUILDING = libzfs.ZPOOL_STATUS_REBUILDING
IF HAVE_ZPOOL_STATUS_REBUILD_SCRUB:
REBUILD_SCRUB = libzfs.ZPOOL_STATUS_REBUILD_SCRUB
NON_NATIVE_ASHIFT = libzfs.ZPOOL_STATUS_NON_NATIVE_ASHIFT
IF HAVE_ZPOOL_STATUS_COMPATIBILITY_ERR:
COMPATIBILITY_ERR = libzfs.ZPOOL_STATUS_COMPATIBILITY_ERR
IF HAVE_ZPOOL_STATUS_INCOMPATIBLE_FEAT:
INCOMPATIBLE_FEAT = libzfs.ZPOOL_STATUS_INCOMPATIBLE_FEAT
OK = libzfs.ZPOOL_STATUS_OK
class ScanState(enum.IntEnum):
NONE = zfs.DSS_NONE
SCANNING = zfs.DSS_SCANNING
FINISHED = zfs.DSS_FINISHED
CANCELED = zfs.DSS_CANCELED
class ZIOType(enum.IntEnum):
NONE = zfs.ZIO_TYPE_NULL
READ = zfs.ZIO_TYPE_READ
WRITE = zfs.ZIO_TYPE_WRITE
FREE = zfs.ZIO_TYPE_FREE
CLAIM = zfs.ZIO_TYPE_CLAIM
IOCTL = zfs.ZIO_TYPE_IOCTL
IF HAVE_LZC_WAIT:
class ZpoolWaitActivity(enum.IntEnum):
DISCARD = zfs.ZPOOL_WAIT_CKPT_DISCARD
FREE = zfs.ZPOOL_WAIT_FREE
INITIALIZE = zfs.ZPOOL_WAIT_INITIALIZE
REPLACE = zfs.ZPOOL_WAIT_REPLACE
REMOVE = zfs.ZPOOL_WAIT_REMOVE
RESILVER = zfs.ZPOOL_WAIT_RESILVER
SCRUB = zfs.ZPOOL_WAIT_SCRUB
TRIM = zfs.ZPOOL_WAIT_TRIM
NUM_ACTIVITIES = zfs.ZPOOL_WAIT_NUM_ACTIVITIES
class FeatureState(enum.Enum):
DISABLED = 0
ENABLED = 1
ACTIVE = 2
class SendFlag(enum.Enum):
IF HAVE_SENDFLAGS_T_VERBOSITY:
VERBOSITY = 0
ELSE:
VERBOSE = 0
REPLICATE = 1
DOALL = 2
FROMORIGIN = 3
PROPS = 4
DRYRUN = 5
PARSABLE = 6
PROGRESS = 7
LARGEBLOCK = 8
EMBED_DATA = 9
IF HAVE_SENDFLAGS_T_COMPRESS:
COMPRESS = 10
IF HAVE_SENDFLAGS_T_RAW:
RAW = 11
IF HAVE_SENDFLAGS_T_BACKUP:
BACKUP = 12
IF HAVE_SENDFLAGS_T_HOLDS:
HOLDS = 13
IF HAVE_SENDFLAGS_T_SAVED:
SAVED = 14
IF HAVE_SENDFLAGS_T_PROGRESSASTITLE:
PROGRESSASTITLE = 15
IF HAVE_SENDFLAGS_T_DEDUP:
DEDUP = 16
class DiffRecordType(enum.Enum):
ADD = '+'
REMOVE = '-'
MODIFY = 'M'
RENAME = 'R'
class DiffFileType(enum.Enum):
BLOCK = 'B'
CHAR = 'C'
FILE = 'F'
DIRECTORY = '/'
SYMLINK = '@'
SOCKET = '='
IF HAVE_ZFS_MAX_DATASET_NAME_LEN:
cdef enum:
MAX_DATASET_NAME_LEN = zfs.ZFS_MAX_DATASET_NAME_LEN
ELSE:
cdef enum:
MAX_DATASET_NAME_LEN = libzfs.ZFS_MAXNAMELEN
cdef struct iter_state:
uintptr_t *array
size_t length
size_t alloc
cdef struct prop_iter_state:
zfs.zfs_type_t type
void *props
def validate_dataset_name(name):
return validate_zfs_resource_name(name, zfs.ZFS_TYPE_FILESYSTEM | zfs.ZFS_TYPE_VOLUME)
def validate_snapshot_name(name):
return validate_zfs_resource_name(name, zfs.ZFS_TYPE_SNAPSHOT)
def validate_pool_name(name):
return validate_zfs_resource_name(name, zfs.ZFS_TYPE_POOL)
cdef validate_zfs_resource_name(str name, int r_type):
cdef const char *c_name = name
cdef int ret
with nogil:
ret = libzfs.zfs_name_valid(c_name, <zfs.zfs_type_t>r_type)
return bool(ret)
def validate_draid_configuration(children, draid_parity, draid_spare_disks, draid_data_disks):
draid_data_disks = zfs.VDEV_DRAID_MAX_CHILDREN if draid_data_disks is None else draid_data_disks
# Validation added from
# https://github.com/truenas/zfs/blob/2bb9ef45772886bffcc93b59cfd62594f478cc83/cmd/zpool/zpool_vdev.c#L1369
if draid_data_disks == zfs.VDEV_DRAID_MAX_CHILDREN:
if children > draid_spare_disks + draid_parity:
draid_data_disks = min(children - draid_spare_disks - draid_parity, 8)
else:
raise ZFSException(
py_errno.EINVAL,
f'Request number of distributed spares {draid_spare_disks} and parity level {draid_parity}\n'
'leaves no disks available for data'
)
if draid_data_disks == 0 or (draid_data_disks + draid_parity) > (children - draid_spare_disks):
raise ZFSException(
py_errno.EINVAL,
f'Requested number of dRAID data disks per group {draid_data_disks} is too high, at'
f' most {children - draid_spare_disks - draid_parity} disks are available for data'
)
if draid_parity == 0 or draid_parity > zfs.VDEV_DRAID_MAXPARITY:
raise ZFSException(
py_errno.EINVAL,
f'Invalid dRAID parity level {draid_parity}; must be between 1 and {zfs.VDEV_DRAID_MAXPARITY}'
)
if draid_spare_disks > 100 or draid_spare_disks > (children - (draid_data_disks + draid_parity)):
raise ZFSException(
py_errno.EINVAL,
f'Invalid number of dRAID spares {draid_spare_disks}. Additional disks would be required'
)
if children < (draid_data_disks + draid_parity + draid_spare_disks):
raise ZFSException(
py_errno.EINVAL,
f'{children} disks were provided, but at least '
f'{draid_data_disks + draid_parity + draid_spare_disks} disks are required for this config'
)
if children > zfs.VDEV_DRAID_MAX_CHILDREN:
raise ZFSException(
py_errno.EINVAL,
f'{children} disks were provided, but dRAID only supports up to {zfs.VDEV_DRAID_MAX_CHILDREN} disks'
)
return draid_data_disks
def update_draid_config(nvlist, children, draid_parity=1, draid_spare_disks=0, draid_data_disks=None):
draid_data_disks = validate_draid_configuration(children, draid_parity, draid_spare_disks, draid_data_disks)
ngroups = 1
while (ngroups * (draid_data_disks + draid_parity)) % (children - draid_spare_disks) != 0:
ngroups += 1
# Store the basic dRAID configuration.
nvlist[zfs.ZPOOL_CONFIG_NPARITY] = draid_parity
nvlist[zfs.ZPOOL_CONFIG_DRAID_NDATA] = draid_data_disks
nvlist[zfs.ZPOOL_CONFIG_DRAID_NSPARES] = draid_spare_disks
nvlist[zfs.ZPOOL_CONFIG_DRAID_NGROUPS] = ngroups
class DiffRecord(object):
def __init__(self, raw):
timestamp, cmd, typ, rest = raw.split(maxsplit=3)
paths = rest.split('->', maxsplit=2)
self.raw = raw
self.timestamp = datetime.utcfromtimestamp(float(timestamp))
self.cmd = DiffRecordType(cmd)
self.type = DiffFileType(typ)
self.path = paths[0].strip()
if self.cmd == DiffRecordType.RENAME:
self.oldpath = paths[1].strip()
def __str__(self):
return self.raw
def __repr__(self):
return str(self)
def asdict(self):
return {
'timestamp': self.timestamp,
'cmd': self.cmd.name,
'type': self.type.name,
'path': self.path,
'oldpath': getattr(self, 'oldpath', None)
}
IF HAVE_LZC_SEND_FLAG_EMBED_DATA:
class SendFlags(enum.IntEnum):
EMBED_DATA = libzfs.LZC_SEND_FLAG_EMBED_DATA
class ZFSInvalidCachefileException(OSError):
pass
class ZFSException(RuntimeError):
def __init__(self, code, message):
super(ZFSException, self).__init__(message)
self.code = code
def __reduce__(self):
return (self.__class__, (self.code, *self.args))
class ZFSVdevStatsException(ZFSException):
def __init__(self, code, message='Failed to fetch ZFS Vdev Stats'):
super(ZFSVdevStatsException, self).__init__(code, message)
class ZFSPoolRaidzExpandStatsException(ZFSException):
def __init__(self, code, message='Failed to retrieve ZFS pool scan stats'):
super(ZFSPoolRaidzExpandStatsException, self).__init__(code, message)
class ZFSPoolScanStatsException(ZFSException):
def __init__(self, code, message='Failed to retrieve ZFS pool scan stats'):
super(ZFSPoolScanStatsException, self).__init__(code, message)
cdef class ZFS(object):
cdef libzfs.libzfs_handle_t* handle
cdef boolean_t mnttab_cache_enable
cdef int history
cdef char *history_prefix
proptypes = {}
def __cinit__(self, history=True, history_prefix='py-libzfs:', mnttab_cache=True):
cdef zfs.zfs_type_t c_type
cdef prop_iter_state iter
self.mnttab_cache_enable=mnttab_cache
with nogil:
self.handle = libzfs.libzfs_init()
if isinstance(history, bool):
self.history = history
else:
raise ZFSException(Error.BADTYPE, 'history is a boolean parameter')
if self.history:
if isinstance(history_prefix, str):
self.history_prefix = history_prefix
else:
raise ZFSException(Error.BADTYPE, 'history_prefix is a string parameter')
for t in DatasetType.__members__.values():
proptypes = []
c_type = <zfs.zfs_type_t>t
iter.type = c_type
iter.props = <void *>proptypes
with nogil:
libzfs.zprop_iter(self.__iterate_props, <void*>&iter, True, True, c_type)
props = self.proptypes.setdefault(t, [])
if set(proptypes) != set(props):
self.proptypes[t] = proptypes
def __enter__(self):
GLOBAL_CONTEXT_LOCK.acquire()
return self
def __exit__(self, exc_type, value, traceback):
self.__libzfs_fini()
GLOBAL_CONTEXT_LOCK.release()
if exc_type is not None:
raise
def __libzfs_fini(self):
if self.handle:
with nogil:
libzfs.libzfs_fini(self.handle)
self.handle = NULL
def __dealloc__(self):
ZFS.__libzfs_fini(self)
def asdict(self):
return [p.asdict() for p in self.pools]
IF HAVE_ZPOOL_EVENTS_NEXT:
def zpool_events(self, blocking=True, skip_existing_events=False):
if skip_existing_events:
existing_events = len(list(self.zpool_events(blocking=False, skip_existing_events=False)))
event_count = -1
zevent_fd = os.open(zfs.ZFS_DEV, os.O_RDWR)
try:
event = True
while event:
event = self.zpool_events_single(zevent_fd, blocking)
event_count += 1
if skip_existing_events and event_count < existing_events:
continue
if event:
yield event
finally:
os.close(zevent_fd)
def zpool_events_single(self, zfs_dev_fd, blocking=True):
cdef nvpair.nvlist_t *nvl
cdef NVList py_nvl
cdef int zevent_fd, ret, dropped
cdef int block_flag = 0 if blocking else 1
zevent_fd = zfs_dev_fd
with nogil:
ret = libzfs.zpool_events_next(self.handle, &nvl, &dropped, block_flag, zevent_fd)
if ret != 0 or (nvl == NULL and block_flag == 0):
raise self.get_error()
if nvl == NULL:
# This is okay when non blocking behavior is desired
return None
else:
retval = {'dropped': dropped, **dict(NVList(<uintptr_t>nvl))}
with nogil:
nvpair.nvlist_free(nvl)
return retval
@staticmethod
cdef int __iterate_props(int proptype, void *arg) nogil:
cdef prop_iter_state *iter
cdef boolean_t ret = False
iter = <prop_iter_state *>arg
IF HAVE_ZFS_PROP_VALID_FOR_TYPE == 3:
ret = zfs.zfs_prop_valid_for_type(proptype, iter.type, ret)
ELSE:
ret = zfs.zfs_prop_valid_for_type(proptype, iter.type)
if not ret:
return zfs.ZPROP_CONT
with gil:
proptypes = <object>iter.props
proptypes.append(proptype)
return zfs.ZPROP_CONT
@staticmethod
cdef int __iterate_pools(libzfs.zpool_handle_t *handle, void *arg) nogil:
cdef iter_state *iter
cdef iter_state new
iter = <iter_state *>arg
if iter.length == iter.alloc:
new.alloc = iter.alloc + 32
new.array = <uintptr_t *>realloc(iter.array, new.alloc * sizeof(uintptr_t))
if not new.array:
free(iter.array)
raise MemoryError()
iter.alloc = new.alloc
iter.array = new.array
iter.array[iter.length] = <uintptr_t>handle
iter.length += 1
@staticmethod
cdef int __iterate_filesystems(libzfs.zfs_handle_t *zhp, int flags, libzfs.zfs_iter_f func, void *data) nogil:
IF HAVE_ZFS_ITER_FILESYSTEMS == 4:
return libzfs.zfs_iter_filesystems(zhp, flags, func, data)
ELSE:
# flags are ignored on older zfs
return libzfs.zfs_iter_filesystems(zhp, func, data)
@staticmethod
cdef int __iterate_snapspec(libzfs.zfs_handle_t *zhp, int flags, const char *spec_orig, libzfs.zfs_iter_f func, void *arg) nogil:
IF HAVE_ZFS_ITER_SNAPSPEC == 5:
return libzfs.zfs_iter_snapspec(zhp, flags, spec_orig, func, arg)
ELSE:
# flags are ignored on older zfs
return libzfs.zfs_iter_snapspec(zhp, spec_orig, func, arg)
@staticmethod
cdef int __iterate_dependents(libzfs.zfs_handle_t *zhp, int flags, boolean_t allowrecursion, libzfs.zfs_iter_f func, void *data) nogil:
IF HAVE_ZFS_ITER_DEPENDENTS == 5:
return libzfs.zfs_iter_dependents(zhp, flags, allowrecursion, func, data)
ELSE:
# flags are ignored on older zfs
return libzfs.zfs_iter_dependents(zhp, allowrecursion, func, data)
@staticmethod
cdef int __iterate_bookmarks(libzfs.zfs_handle_t *zhp, int flags, libzfs.zfs_iter_f func, void *data) nogil:
IF HAVE_ZFS_ITER_BOOKMARKS == 4:
return libzfs.zfs_iter_bookmarks(zhp, flags, func, data)
ELSE:
# flags are ignored on older zfs
return libzfs.zfs_iter_bookmarks(zhp, func, data)
cdef object get_error(self):
description = (<bytes>libzfs.libzfs_error_description(self.handle)).decode('utf-8', 'backslashreplace')
error_action = (<bytes>libzfs.libzfs_error_action(self.handle)).decode('utf-8', 'backslashreplace')
if error_action:
description = f'{error_action}: {description}'
return ZFSException(Error(libzfs.libzfs_errno(self.handle)), description)
cdef ZFSVdev make_vdev_tree(self, topology, props=None):
cdef ZFSVdev root
root = ZFSVdev(self, zfs.VDEV_TYPE_ROOT)
root.children = topology.get('data', [])
ashift_value = (props or {}).get(zfs.ZPOOL_CONFIG_ASHIFT)
if ashift_value and not isinstance(ashift_value, int):
ashift_value = None
def add_properties_to_vdev(vdev):
cdef char vpath[zfs.MAXPATHLEN + 1]
cdef boolean_t whole_disk
IF IS_OPENZFS:
# Each leaf vdev is supposed to have the wholedisk
# and ashift properties in its nvlist
if vdev.type != 'disk':
for child in vdev.children:
add_properties_to_vdev(child)
else:
strncpy(vpath, vdev.path, zfs.MAXPATHLEN)
with nogil:
whole_disk = zfs_dev_is_whole_disk(vpath)
(<ZFSVdev>vdev).set_whole_disk(whole_disk)
if ashift_value:
(<ZFSVdev>vdev).set_ashift(ashift_value)
return vdev
root = <ZFSVdev>add_properties_to_vdev(root)
if 'cache' in topology:
root.nvlist[zfs.ZPOOL_CONFIG_L2CACHE] = [
(<ZFSVdev>add_properties_to_vdev(<ZFSVdev>i)).nvlist for i in topology['cache']
]
if 'spare' in topology:
root.nvlist[zfs.ZPOOL_CONFIG_SPARES] = [
(<ZFSVdev>add_properties_to_vdev(<ZFSVdev>i)).nvlist for i in topology['spare']
]
if 'log' in topology:
for i in topology['log']:
vdev = <ZFSVdev>i
vdev.nvlist[zfs.ZPOOL_CONFIG_IS_LOG] = 1L
IF HAVE_ZPOOL_CONFIG_ALLOCATION_BIAS:
vdev.nvlist[zfs.ZPOOL_CONFIG_ALLOCATION_BIAS] = zfs.VDEV_ALLOC_BIAS_LOG
root.add_child_vdev((<ZFSVdev>add_properties_to_vdev(vdev)))
if 'draid' in topology:
children = len(topology['draid'])
for draid in topology['draid']:
vdev = <ZFSVdev>draid['disk']
update_draid_config(vdev.nvlist, **draid['parameters'])
root.add_child_vdev((<ZFSVdev>add_properties_to_vdev(vdev)))
IF HAVE_ZPOOL_CONFIG_ALLOCATION_BIAS:
if 'special' in topology:
for i in topology['special']:
vdev = <ZFSVdev>i
vdev.nvlist[zfs.ZPOOL_CONFIG_IS_LOG] = False
vdev.nvlist[zfs.ZPOOL_CONFIG_ALLOCATION_BIAS] = zfs.VDEV_ALLOC_BIAS_SPECIAL
root.add_child_vdev((<ZFSVdev>add_properties_to_vdev(vdev)))
if 'dedup' in topology:
for i in topology['dedup']:
vdev = <ZFSVdev>i
vdev.nvlist[zfs.ZPOOL_CONFIG_IS_LOG] = False
vdev.nvlist[zfs.ZPOOL_CONFIG_ALLOCATION_BIAS] = zfs.VDEV_ALLOC_BIAS_DEDUP
root.add_child_vdev((<ZFSVdev>add_properties_to_vdev(vdev)))
return root
@staticmethod
cdef int __dataset_handles(libzfs.zfs_handle_t* handle, void *arg) nogil:
cdef int prop_id
cdef char csrcstr[MAX_DATASET_NAME_LEN + 1]
cdef char crawvalue[libzfs.ZFS_MAXPROPLEN + 1]
cdef char cvalue[libzfs.ZFS_MAXPROPLEN + 1]
cdef zfs.zprop_source_t csource
cdef const char *name
cdef zfs.zfs_type_t typ
cdef boolean_t retrieve_children
cdef nvpair.nvlist_t *nvlist
name = libzfs.zfs_get_name(handle)
typ = libzfs.zfs_get_type(handle)
nvlist = libzfs.zfs_get_user_props(handle)
with gil:
dataset_type = DatasetType(typ)
data_list = <object> arg
configuration_data = data_list[0]
retrieve_children = configuration_data['retrieve_children']
data = data_list[1]
children = []
child_data = [configuration_data, {}]
properties = {}
for key, value in NVList(<uintptr_t>nvlist).items() if configuration_data['user_props'] else []:
src = 'NONE'
if value.get('source'):
src = value.pop('source')
if src == name:
src = PropertySource.LOCAL.name
elif src == '$recvd':
src = PropertySource.RECEIVED.name
else:
src = PropertySource.INHERITED.name
properties[key] = {
'value': value.get('value'),
'rawvalue': value.get('value'),
'source': src,
'parsed': value.get('value')
}
for prop_name, prop_id in configuration_data['props'].get(dataset_type, {}).items():
csource = zfs.ZPROP_SRC_NONE
with nogil:
strncpy(cvalue, '', libzfs.ZFS_MAXPROPLEN + 1)
strncpy(crawvalue, '', libzfs.ZFS_MAXPROPLEN + 1)
strncpy(csrcstr, '', MAX_DATASET_NAME_LEN + 1)
if libzfs.zfs_prop_get(
handle, prop_id, cvalue, libzfs.ZFS_MAXPROPLEN,
&csource, csrcstr, MAX_DATASET_NAME_LEN, False
) != 0:
csource = zfs.ZPROP_SRC_NONE
libzfs.zfs_prop_get(
handle, prop_id, crawvalue, libzfs.ZFS_MAXPROPLEN,
NULL, NULL, 0, True
)
properties[prop_name] = {
'parsed': parse_zfs_prop(prop_name, crawvalue),
'rawvalue': crawvalue,
'value': cvalue,
'source': PropertySource(<int>csource).name,
'source_info': str(csrcstr) if csource == zfs.ZPROP_SRC_INHERITED else None
}
if retrieve_children:
ZFS.__iterate_filesystems(handle, 0, ZFS.__dataset_handles, <void*>child_data)
with gil:
data[name] = {}
child_data = child_data[1]
encryption_dict = {}
IF HAVE_ZFS_ENCRYPTION:
if 'encryption' in properties:
encryption_dict['encrypted'] = properties['encryption']['value'] != 'off'
if 'encryptionroot' in properties:
encryption_dict['encryption_root'] = properties['encryptionroot']['value'] or None
if 'keystatus' in properties:
encryption_dict['key_loaded'] = properties['keystatus']['value'] == 'available'
data[name].update({
'properties': properties,
'id': name,
'type': dataset_type.name,
'name': name,
'pool': configuration_data['pool'],
**encryption_dict,
})
if retrieve_children:
data[name]['children'] = list(child_data.values())
if configuration_data['snapshots'] or configuration_data['snapshots_recursive']:
snap_props = ['name']
if configuration_data['snapshot_props'] is None:
# We will retrieve all properties of snapshot in this case
snap_props = None
else:
snap_props.extend(configuration_data['snapshot_props'])
snap_list = ZFS._snapshots_snaplist_arg(
snap_props, False, False, configuration_data['snapshots_recursive'], False, 0, 0
)
snap_list[0]['pool'] = configuration_data['pool']
ZFS.__datasets_snapshots(handle, <void*>snap_list)
data[name]['snapshots'] = snap_list[1:]
libzfs.zfs_close(handle)
def datasets_serialized(
self, props=None, user_props=True, datasets=None, snapshots=False, retrieve_children=True,
snapshots_recursive=False, snapshot_props=None,
):
cdef libzfs.zfs_handle_t* handle
cdef const char *c_name
cdef int prop_id
prop_mapping = {}
datasets = datasets or [p.name for p in self.pools]
# If props is None, we include all properties, if it's an empty list, no property is retrieved
for dataset_type in [DatasetType.FILESYSTEM, DatasetType.VOLUME] if props is None or len(props) else []:
prop_mapping[dataset_type] = {}
for prop_id in ZFS.proptypes[dataset_type]:
with nogil:
prop_name = libzfs.zfs_prop_to_name(prop_id)
if props is None or prop_name in props:
prop_mapping[dataset_type][prop_name] = prop_id
for ds_name in datasets:
c_name = handle = NULL
c_name = ds_name
dataset = [
{
'pool': ds_name.split('/', 1)[0],
'props': prop_mapping,
'user_props': user_props,
'snapshots': snapshots,
'snapshots_recursive': bool(snapshots_recursive),
'retrieve_children': retrieve_children,
'snapshot_props': snapshot_props,
},
{}
]
with nogil:
handle = libzfs.zfs_open(self.handle, c_name, zfs.ZFS_TYPE_FILESYSTEM | zfs.ZFS_TYPE_VOLUME)
if handle == NULL:
# It just means that the dataset in question does not exist
# and it's okay to continue checking the next one
continue
else:
ZFS.__dataset_handles(handle, <void*>dataset)
if len(dataset) > 1:
yield dataset[1][ds_name]
@staticmethod
cdef int __retrieve_mountable_datasets_handles(libzfs.zfs_handle_t* handle, void *arg) nogil:
cdef libzfs.get_all_cb_t *cb = <libzfs.get_all_cb_t*>arg
if libzfs.zfs_get_type(handle) != zfs.ZFS_TYPE_FILESYSTEM:
libzfs.zfs_close(handle)
return 0
if libzfs.zfs_prop_get_int(handle, zfs.ZFS_PROP_CANMOUNT) == zfs.ZFS_CANMOUNT_NOAUTO:
libzfs.zfs_close(handle)
return 0
IF HAVE_ZFS_ENCRYPTION:
if libzfs.zfs_prop_get_int(handle, zfs.ZFS_PROP_KEYSTATUS) == zfs.ZFS_KEYSTATUS_UNAVAILABLE:
libzfs.zfs_close(handle)
return 0
IF HAVE_ZFS_SEND_RESUME_TOKEN_TO_NVLIST:
if (
libzfs.zfs_prop_get_int(handle, zfs.ZFS_PROP_INCONSISTENT) and libzfs.zfs_prop_get(
handle, zfs.ZFS_PROP_RECEIVE_RESUME_TOKEN, NULL, 0, NULL, NULL, 0, True
) == 0
):
libzfs.zfs_close(handle)
return 0
libzfs.libzfs_add_handle(cb, handle)
ZFS.__iterate_filesystems(handle, 0, ZFS.__retrieve_mountable_datasets_handles, cb)
@staticmethod
cdef int mount_dataset(libzfs.zfs_handle_t *zhp, void *arg) nogil:
cdef int ret
cdef nvpair.nvlist_t* mount_data = <nvpair.nvlist_t*>arg
IF HAVE_ZFS_ENCRYPTION:
if libzfs.zfs_prop_get_int(zhp, zfs.ZFS_PROP_KEYSTATUS) == zfs.ZFS_KEYSTATUS_UNAVAILABLE:
return 0
ret = libzfs.zfs_mount(zhp, NULL, 0)
if ret != 0:
nvpair.nvlist_add_boolean(mount_data, libzfs.zfs_get_name(zhp))
return ret
@staticmethod
cdef int share_one_dataset(libzfs.zfs_handle_t *zhp, void *arg) nogil:
cdef int ret
IF HAVE_ZFS_SHARE == 1:
ret = libzfs.zfs_share(zhp)
ELSE:
ret = libzfs.zfs_share(zhp, NULL)
if ret != 0:
with gil:
mount_results = <object> arg
mount_results['failed_share'].append(libzfs.zfs_get_name(zhp))
return ret
def run(self):
self.zpool_enable_datasets('pool', False)
IF HAVE_ZFS_FOREACH_MOUNTPOINT:
cdef int zpool_enable_datasets(self, str name, int enable_shares) nogil:
cdef libzfs.zfs_handle_t* handle
cdef const char *c_name
cdef libzfs.get_all_cb_t cb
with gil:
mount_data = NVList(otherdict={})
mount_results = {'failed_mount': [], 'failed_share': []}
c_name = name
cb = libzfs.get_all_cb_t(cb_alloc=0, cb_used=0, cb_handles=NULL)
handle = libzfs.zfs_open(self.handle, c_name, zfs.ZFS_TYPE_FILESYSTEM)
if handle == NULL:
free(cb.cb_handles)
raise self.get_error()
# Gathering all handles first
ZFS.__retrieve_mountable_datasets_handles(handle, &cb)
# Mount all datasets
libzfs.zfs_foreach_mountpoint(
self.handle, cb.cb_handles, cb.cb_used, ZFS.mount_dataset, <void*>mount_data.handle, True
)
# Share all datasets
if enable_shares:
libzfs.zfs_foreach_mountpoint(
self.handle, cb.cb_handles, cb.cb_used, ZFS.share_one_dataset, <void*>mount_results, False
)
IF HAVE_ZFS_SHARE == 2:
with gil:
if not mount_results['failed_share']:
with nogil:
libzfs.zfs_commit_shares(NULL)
# Free all handles
for i in range(cb.cb_used):
libzfs.zfs_close(cb.cb_handles[i])
free(cb.cb_handles)
with gil:
mount_results['failed_mount'] = mount_data.keys()
if mount_results['failed_mount'] or mount_results['failed_share']: