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#
16
17"""
18Tests for `libzfs_core` operations.
19
20These are mostly functional and conformance tests that validate
21that the operations produce expected effects or fail with expected
22exceptions.
23"""
24from __future__ import absolute_import, division, print_function
25
26import unittest
27import contextlib
28import errno
29import filecmp
30import os
31import platform
32import resource
33import shutil
34import stat
35import subprocess
36import sys
37import tempfile
38import time
39import uuid
40import itertools
41import zlib
42from .. import _libzfs_core as lzc
43from .. import exceptions as lzc_exc
44from .._nvlist import packed_nvlist_out
45
46
47def _print(*args):
48    for arg in args:
49        print(arg, end=' ')
50    print()
51
52
53@contextlib.contextmanager
54def suppress(exceptions=None):
55    try:
56        yield
57    except BaseException as e:
58        if exceptions is None or isinstance(e, exceptions):
59            pass
60        else:
61            raise
62
63
64@contextlib.contextmanager
65def _zfs_mount(fs):
66    mntdir = tempfile.mkdtemp()
67    if platform.system() == 'SunOS':
68        mount_cmd = ['mount', '-F', 'zfs', fs, mntdir]
69    else:
70        mount_cmd = ['mount', '-t', 'zfs', fs, mntdir]
71    unmount_cmd = ['umount', '-f', mntdir]
72
73    try:
74        subprocess.check_output(mount_cmd, stderr=subprocess.STDOUT)
75        try:
76            yield mntdir
77        finally:
78            with suppress():
79                subprocess.check_output(unmount_cmd, stderr=subprocess.STDOUT)
80    except subprocess.CalledProcessError as e:
81        print('failed to mount %s @ %s : %s' % (fs, mntdir, e.output))
82        raise
83    finally:
84        os.rmdir(mntdir)
85
86
87# XXX On illumos it is impossible to explicitly mount a snapshot.
88# So, either we need to implicitly mount it using .zfs/snapshot/
89# or we need to create a clone and mount it readonly (and discard
90# it afterwards).
91# At the moment the former approach is implemented.
92
93# This dictionary is used to keep track of mounted filesystems
94# (not snapshots), so that we do not try to mount a filesystem
95# more than once in the case more than one snapshot of the
96# filesystem is accessed from the same context or the filesystem
97# and its snapshot are accessed.
98_mnttab = {}
99
100
101@contextlib.contextmanager
102def _illumos_mount_fs(fs):
103    if fs in _mnttab:
104        yield _mnttab[fs]
105    else:
106        with _zfs_mount(fs) as mntdir:
107            _mnttab[fs] = mntdir
108            try:
109                yield mntdir
110            finally:
111                _mnttab.pop(fs, None)
112
113
114@contextlib.contextmanager
115def _illumos_mount_snap(fs):
116    (base, snap) = fs.split('@', 1)
117    with _illumos_mount_fs(base) as mntdir:
118        yield os.path.join(mntdir, '.zfs', 'snapshot', snap)
119
120
121@contextlib.contextmanager
122def _zfs_mount_illumos(fs):
123    if '@' not in fs:
124        with _illumos_mount_fs(fs) as mntdir:
125            yield mntdir
126    else:
127        with _illumos_mount_snap(fs) as mntdir:
128            yield mntdir
129
130
131if platform.system() == 'SunOS':
132    zfs_mount = _zfs_mount_illumos
133else:
134    zfs_mount = _zfs_mount
135
136
137@contextlib.contextmanager
138def cleanup_fd():
139    fd = os.open('/dev/zfs', os.O_EXCL)
140    try:
141        yield fd
142    finally:
143        os.close(fd)
144
145
146@contextlib.contextmanager
147def os_open(name, mode):
148    fd = os.open(name, mode)
149    try:
150        yield fd
151    finally:
152        os.close(fd)
153
154
155@contextlib.contextmanager
156def dev_null():
157    with tempfile.TemporaryFile(suffix='.zstream') as fd:
158        yield fd.fileno()
159
160
161@contextlib.contextmanager
162def dev_zero():
163    with os_open('/dev/zero', os.O_RDONLY) as fd:
164        yield fd
165
166
167@contextlib.contextmanager
168def temp_file_in_fs(fs):
169    with zfs_mount(fs) as mntdir:
170        with tempfile.NamedTemporaryFile(dir=mntdir) as f:
171            for i in range(1024):
172                f.write(b'x' * 1024)
173            f.flush()
174            yield f.name
175
176
177def make_snapshots(fs, before, modified, after):
178    def _maybe_snap(snap):
179        if snap is not None:
180            if not snap.startswith(fs):
181                snap = fs + b'@' + snap
182            lzc.lzc_snapshot([snap])
183        return snap
184
185    before = _maybe_snap(before)
186    with temp_file_in_fs(fs) as name:
187        modified = _maybe_snap(modified)
188    after = _maybe_snap(after)
189
190    return (name, (before, modified, after))
191
192
193@contextlib.contextmanager
194def streams(fs, first, second):
195    (filename, snaps) = make_snapshots(fs, None, first, second)
196    with tempfile.TemporaryFile(suffix='.zstream') as full:
197        lzc.lzc_send(snaps[1], None, full.fileno())
198        full.seek(0)
199        if snaps[2] is not None:
200            with tempfile.TemporaryFile(suffix='.zstream') as incremental:
201                lzc.lzc_send(snaps[2], snaps[1], incremental.fileno())
202                incremental.seek(0)
203                yield (filename, (full, incremental))
204        else:
205            yield (filename, (full, None))
206
207
208@contextlib.contextmanager
209def encrypted_filesystem():
210    fs = ZFSTest.pool.getFilesystem(b"encrypted")
211    name = fs.getName()
212    filename = None
213    key = os.urandom(lzc.WRAPPING_KEY_LEN)
214    with tempfile.NamedTemporaryFile() as f:
215        filename = "file://" + f.name
216        props = {
217            b"encryption": lzc.zio_encrypt.ZIO_CRYPT_AES_256_CCM,
218            b"keylocation": filename.encode(),
219            b"keyformat": lzc.zfs_keyformat.ZFS_KEYFORMAT_RAW,
220        }
221        lzc.lzc_create(name, 'zfs', props=props, key=key)
222    yield (name, key)
223
224
225def runtimeSkipIf(check_method, message):
226    def _decorator(f):
227        def _f(_self, *args, **kwargs):
228            if check_method(_self):
229                return _self.skipTest(message)
230            else:
231                return f(_self, *args, **kwargs)
232        _f.__name__ = f.__name__
233        return _f
234    return _decorator
235
236
237def skipIfFeatureAvailable(feature, message):
238    return runtimeSkipIf(
239        lambda _self: _self.__class__.pool.isPoolFeatureAvailable(feature),
240        message)
241
242
243def skipUnlessFeatureEnabled(feature, message):
244    return runtimeSkipIf(
245        lambda _self: not _self.__class__.pool.isPoolFeatureEnabled(feature),
246        message)
247
248
249def skipUnlessBookmarksSupported(f):
250    return skipUnlessFeatureEnabled(
251        'bookmarks', 'bookmarks are not enabled')(f)
252
253
254def snap_always_unmounted_before_destruction():
255    # Apparently OpenZFS automatically unmounts the snapshot
256    # only if it is mounted at its default .zfs/snapshot
257    # mountpoint under Linux.
258    return (
259        platform.system() != 'Linux', 'snapshot is not auto-unmounted')
260
261
262def illumos_bug_6379():
263    # zfs_ioc_hold() panics on a bad cleanup fd
264    return (
265        platform.system() == 'SunOS',
266        'see https://www.illumos.org/issues/6379')
267
268
269def needs_support(function):
270    return unittest.skipUnless(
271        lzc.is_supported(function),
272        '{} not available'.format(function.__name__))
273
274
275class ZFSTest(unittest.TestCase):
276    POOL_FILE_SIZE = 128 * 1024 * 1024
277    FILESYSTEMS = [b'fs1', b'fs2', b'fs1/fs']
278
279    pool = None
280    misc_pool = None
281    readonly_pool = None
282
283    @classmethod
284    def setUpClass(cls):
285        try:
286            cls.pool = _TempPool(filesystems=cls.FILESYSTEMS)
287            cls.misc_pool = _TempPool()
288            cls.readonly_pool = _TempPool(
289                filesystems=cls.FILESYSTEMS, readonly=True)
290            cls.pools = [cls.pool, cls.misc_pool, cls.readonly_pool]
291        except Exception:
292            cls._cleanUp()
293            raise
294
295    @classmethod
296    def tearDownClass(cls):
297        cls._cleanUp()
298
299    @classmethod
300    def _cleanUp(cls):
301        for pool in [cls.pool, cls.misc_pool, cls.readonly_pool]:
302            if pool is not None:
303                pool.cleanUp()
304
305    def setUp(self):
306        pass
307
308    def tearDown(self):
309        for pool in ZFSTest.pools:
310            pool.reset()
311
312    def assertExists(self, name):
313        self.assertTrue(
314            lzc.lzc_exists(name), 'ZFS dataset %s does not exist' % (name, ))
315
316    def assertNotExists(self, name):
317        self.assertFalse(
318            lzc.lzc_exists(name), 'ZFS dataset %s exists' % (name, ))
319
320    def test_exists(self):
321        self.assertExists(ZFSTest.pool.makeName())
322
323    def test_exists_in_ro_pool(self):
324        self.assertExists(ZFSTest.readonly_pool.makeName())
325
326    def test_exists_failure(self):
327        self.assertNotExists(ZFSTest.pool.makeName(b'nonexistent'))
328
329    def test_create_fs(self):
330        name = ZFSTest.pool.makeName(b"fs1/fs/test1")
331
332        lzc.lzc_create(name)
333        self.assertExists(name)
334
335    def test_create_zvol(self):
336        name = ZFSTest.pool.makeName(b"fs1/fs/zvol")
337        props = {b"volsize": 1024 * 1024}
338
339        lzc.lzc_create(name, ds_type='zvol', props=props)
340        self.assertExists(name)
341        # On Gentoo with ZFS 0.6.5.4 the volume is busy
342        # and can not be destroyed right after its creation.
343        # A reason for this is unknown at the moment.
344        # Because of that the post-test clean up could fail.
345        time.sleep(0.1)
346
347    def test_create_fs_with_prop(self):
348        name = ZFSTest.pool.makeName(b"fs1/fs/test2")
349        props = {b"atime": 0}
350
351        lzc.lzc_create(name, props=props)
352        self.assertExists(name)
353
354    def test_create_fs_wrong_ds_type(self):
355        name = ZFSTest.pool.makeName(b"fs1/fs/test1")
356
357        with self.assertRaises(lzc_exc.DatasetTypeInvalid):
358            lzc.lzc_create(name, ds_type='wrong')
359
360    def test_create_fs_below_zvol(self):
361        name = ZFSTest.pool.makeName(b"fs1/fs/zvol")
362        props = {b"volsize": 1024 * 1024}
363
364        lzc.lzc_create(name, ds_type='zvol', props=props)
365        with self.assertRaises(lzc_exc.WrongParent):
366            lzc.lzc_create(name + b'/fs')
367
368    def test_create_zvol_below_zvol(self):
369        name = ZFSTest.pool.makeName(b"fs1/fs/zvol")
370        props = {b"volsize": 1024 * 1024}
371
372        lzc.lzc_create(name, ds_type='zvol', props=props)
373        with self.assertRaises(lzc_exc.WrongParent):
374            lzc.lzc_create(name + b'/zvol', ds_type='zvol', props=props)
375
376    def test_create_fs_duplicate(self):
377        name = ZFSTest.pool.makeName(b"fs1/fs/test6")
378
379        lzc.lzc_create(name)
380
381        with self.assertRaises(lzc_exc.FilesystemExists):
382            lzc.lzc_create(name)
383
384    def test_create_fs_in_ro_pool(self):
385        name = ZFSTest.readonly_pool.makeName(b"fs")
386
387        with self.assertRaises(lzc_exc.ReadOnlyPool):
388            lzc.lzc_create(name)
389
390    def test_create_fs_without_parent(self):
391        name = ZFSTest.pool.makeName(b"fs1/nonexistent/test")
392
393        with self.assertRaises(lzc_exc.ParentNotFound):
394            lzc.lzc_create(name)
395        self.assertNotExists(name)
396
397    def test_create_fs_in_nonexistent_pool(self):
398        name = b"no-such-pool/fs"
399
400        with self.assertRaises(lzc_exc.ParentNotFound):
401            lzc.lzc_create(name)
402        self.assertNotExists(name)
403
404    def test_create_fs_with_invalid_prop(self):
405        name = ZFSTest.pool.makeName(b"fs1/fs/test3")
406        props = {b"BOGUS": 0}
407
408        with self.assertRaises(lzc_exc.PropertyInvalid):
409            lzc.lzc_create(name, 'zfs', props)
410        self.assertNotExists(name)
411
412    def test_create_fs_with_invalid_prop_type(self):
413        name = ZFSTest.pool.makeName(b"fs1/fs/test4")
414        props = {b"recordsize": b"128k"}
415
416        with self.assertRaises(lzc_exc.PropertyInvalid):
417            lzc.lzc_create(name, 'zfs', props)
418        self.assertNotExists(name)
419
420    def test_create_fs_with_invalid_prop_val(self):
421        name = ZFSTest.pool.makeName(b"fs1/fs/test5")
422        props = {b"atime": 20}
423
424        with self.assertRaises(lzc_exc.PropertyInvalid):
425            lzc.lzc_create(name, 'zfs', props)
426        self.assertNotExists(name)
427
428    def test_create_fs_with_invalid_name(self):
429        name = ZFSTest.pool.makeName(b"@badname")
430
431        with self.assertRaises(lzc_exc.NameInvalid):
432            lzc.lzc_create(name)
433        self.assertNotExists(name)
434
435    def test_create_fs_with_invalid_pool_name(self):
436        name = b"bad!pool/fs"
437
438        with self.assertRaises(lzc_exc.NameInvalid):
439            lzc.lzc_create(name)
440        self.assertNotExists(name)
441
442    def test_create_encrypted_fs(self):
443        fs = ZFSTest.pool.getFilesystem(b"encrypted")
444        name = fs.getName()
445        filename = None
446        with tempfile.NamedTemporaryFile() as f:
447            filename = "file://" + f.name
448            props = {
449                b"encryption": lzc.zio_encrypt.ZIO_CRYPT_AES_256_CCM,
450                b"keylocation": filename.encode(),
451                b"keyformat": lzc.zfs_keyformat.ZFS_KEYFORMAT_RAW,
452            }
453            key = os.urandom(lzc.WRAPPING_KEY_LEN)
454            lzc.lzc_create(name, 'zfs', props=props, key=key)
455        self.assertEqual(fs.getProperty("encryption"), b"aes-256-ccm")
456        self.assertEqual(fs.getProperty("encryptionroot"), name)
457        self.assertEqual(fs.getProperty("keylocation"), filename.encode())
458        self.assertEqual(fs.getProperty("keyformat"), b"raw")
459
460    def test_snapshot(self):
461        snapname = ZFSTest.pool.makeName(b"@snap")
462        snaps = [snapname]
463
464        lzc.lzc_snapshot(snaps)
465        self.assertExists(snapname)
466
467    def test_snapshot_empty_list(self):
468        lzc.lzc_snapshot([])
469
470    def test_snapshot_user_props(self):
471        snapname = ZFSTest.pool.makeName(b"@snap")
472        snaps = [snapname]
473        props = {b"user:foo": b"bar"}
474
475        lzc.lzc_snapshot(snaps, props)
476        self.assertExists(snapname)
477
478    def test_snapshot_invalid_props(self):
479        snapname = ZFSTest.pool.makeName(b"@snap")
480        snaps = [snapname]
481        props = {b"foo": b"bar"}
482
483        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
484            lzc.lzc_snapshot(snaps, props)
485
486        self.assertEqual(len(ctx.exception.errors), len(snaps))
487        for e in ctx.exception.errors:
488            self.assertIsInstance(e, lzc_exc.PropertyInvalid)
489        self.assertNotExists(snapname)
490
491    def test_snapshot_ro_pool(self):
492        snapname1 = ZFSTest.readonly_pool.makeName(b"@snap")
493        snapname2 = ZFSTest.readonly_pool.makeName(b"fs1@snap")
494        snaps = [snapname1, snapname2]
495
496        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
497            lzc.lzc_snapshot(snaps)
498
499        # NB: one common error is reported.
500        self.assertEqual(len(ctx.exception.errors), 1)
501        for e in ctx.exception.errors:
502            self.assertIsInstance(e, lzc_exc.ReadOnlyPool)
503        self.assertNotExists(snapname1)
504        self.assertNotExists(snapname2)
505
506    def test_snapshot_nonexistent_pool(self):
507        snapname = b"no-such-pool@snap"
508        snaps = [snapname]
509
510        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
511            lzc.lzc_snapshot(snaps)
512
513        self.assertEqual(len(ctx.exception.errors), 1)
514        for e in ctx.exception.errors:
515            self.assertIsInstance(e, lzc_exc.FilesystemNotFound)
516
517    def test_snapshot_nonexistent_fs(self):
518        snapname = ZFSTest.pool.makeName(b"nonexistent@snap")
519        snaps = [snapname]
520
521        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
522            lzc.lzc_snapshot(snaps)
523
524        self.assertEqual(len(ctx.exception.errors), 1)
525        for e in ctx.exception.errors:
526            self.assertIsInstance(e, lzc_exc.FilesystemNotFound)
527
528    def test_snapshot_nonexistent_and_existent_fs(self):
529        snapname1 = ZFSTest.pool.makeName(b"@snap")
530        snapname2 = ZFSTest.pool.makeName(b"nonexistent@snap")
531        snaps = [snapname1, snapname2]
532
533        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
534            lzc.lzc_snapshot(snaps)
535
536        self.assertEqual(len(ctx.exception.errors), 1)
537        for e in ctx.exception.errors:
538            self.assertIsInstance(e, lzc_exc.FilesystemNotFound)
539        self.assertNotExists(snapname1)
540        self.assertNotExists(snapname2)
541
542    def test_multiple_snapshots_nonexistent_fs(self):
543        snapname1 = ZFSTest.pool.makeName(b"nonexistent@snap1")
544        snapname2 = ZFSTest.pool.makeName(b"nonexistent@snap2")
545        snaps = [snapname1, snapname2]
546
547        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
548            lzc.lzc_snapshot(snaps)
549
550        # XXX two errors should be reported but alas
551        self.assertEqual(len(ctx.exception.errors), 1)
552        for e in ctx.exception.errors:
553            self.assertIsInstance(e, lzc_exc.DuplicateSnapshots)
554        self.assertNotExists(snapname1)
555        self.assertNotExists(snapname2)
556
557    def test_multiple_snapshots_multiple_nonexistent_fs(self):
558        snapname1 = ZFSTest.pool.makeName(b"nonexistent1@snap")
559        snapname2 = ZFSTest.pool.makeName(b"nonexistent2@snap")
560        snaps = [snapname1, snapname2]
561
562        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
563            lzc.lzc_snapshot(snaps)
564
565        self.assertEqual(len(ctx.exception.errors), 2)
566        for e in ctx.exception.errors:
567            self.assertIsInstance(e, lzc_exc.FilesystemNotFound)
568        self.assertNotExists(snapname1)
569        self.assertNotExists(snapname2)
570
571    def test_snapshot_already_exists(self):
572        snapname = ZFSTest.pool.makeName(b"@snap")
573        snaps = [snapname]
574
575        lzc.lzc_snapshot(snaps)
576
577        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
578            lzc.lzc_snapshot(snaps)
579
580        self.assertEqual(len(ctx.exception.errors), 1)
581        for e in ctx.exception.errors:
582            self.assertIsInstance(e, lzc_exc.SnapshotExists)
583
584    def test_multiple_snapshots_for_same_fs(self):
585        snapname1 = ZFSTest.pool.makeName(b"@snap1")
586        snapname2 = ZFSTest.pool.makeName(b"@snap2")
587        snaps = [snapname1, snapname2]
588
589        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
590            lzc.lzc_snapshot(snaps)
591
592        self.assertEqual(len(ctx.exception.errors), 1)
593        for e in ctx.exception.errors:
594            self.assertIsInstance(e, lzc_exc.DuplicateSnapshots)
595        self.assertNotExists(snapname1)
596        self.assertNotExists(snapname2)
597
598    def test_multiple_snapshots(self):
599        snapname1 = ZFSTest.pool.makeName(b"@snap")
600        snapname2 = ZFSTest.pool.makeName(b"fs1@snap")
601        snaps = [snapname1, snapname2]
602
603        lzc.lzc_snapshot(snaps)
604        self.assertExists(snapname1)
605        self.assertExists(snapname2)
606
607    def test_multiple_existing_snapshots(self):
608        snapname1 = ZFSTest.pool.makeName(b"@snap")
609        snapname2 = ZFSTest.pool.makeName(b"fs1@snap")
610        snaps = [snapname1, snapname2]
611
612        lzc.lzc_snapshot(snaps)
613
614        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
615            lzc.lzc_snapshot(snaps)
616
617        self.assertEqual(len(ctx.exception.errors), 2)
618        for e in ctx.exception.errors:
619            self.assertIsInstance(e, lzc_exc.SnapshotExists)
620
621    def test_multiple_new_and_existing_snapshots(self):
622        snapname1 = ZFSTest.pool.makeName(b"@snap")
623        snapname2 = ZFSTest.pool.makeName(b"fs1@snap")
624        snapname3 = ZFSTest.pool.makeName(b"fs2@snap")
625        snaps = [snapname1, snapname2]
626        more_snaps = snaps + [snapname3]
627
628        lzc.lzc_snapshot(snaps)
629
630        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
631            lzc.lzc_snapshot(more_snaps)
632
633        self.assertEqual(len(ctx.exception.errors), 2)
634        for e in ctx.exception.errors:
635            self.assertIsInstance(e, lzc_exc.SnapshotExists)
636        self.assertNotExists(snapname3)
637
638    def test_snapshot_multiple_errors(self):
639        snapname1 = ZFSTest.pool.makeName(b"@snap")
640        snapname2 = ZFSTest.pool.makeName(b"nonexistent@snap")
641        snapname3 = ZFSTest.pool.makeName(b"fs1@snap")
642        snaps = [snapname1]
643        more_snaps = [snapname1, snapname2, snapname3]
644
645        # create 'snapname1' snapshot
646        lzc.lzc_snapshot(snaps)
647
648        # attempt to create 3 snapshots:
649        # 1. duplicate snapshot name
650        # 2. refers to filesystem that doesn't exist
651        # 3. could have succeeded if not for 1 and 2
652        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
653            lzc.lzc_snapshot(more_snaps)
654
655        # It seems that FilesystemNotFound overrides the other error,
656        # but it doesn't have to.
657        self.assertGreater(len(ctx.exception.errors), 0)
658        for e in ctx.exception.errors:
659            self.assertIsInstance(
660                e, (lzc_exc.SnapshotExists, lzc_exc.FilesystemNotFound))
661        self.assertNotExists(snapname2)
662        self.assertNotExists(snapname3)
663
664    def test_snapshot_different_pools(self):
665        snapname1 = ZFSTest.pool.makeName(b"@snap")
666        snapname2 = ZFSTest.misc_pool.makeName(b"@snap")
667        snaps = [snapname1, snapname2]
668
669        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
670            lzc.lzc_snapshot(snaps)
671
672        # NB: one common error is reported.
673        self.assertEqual(len(ctx.exception.errors), 1)
674        for e in ctx.exception.errors:
675            self.assertIsInstance(e, lzc_exc.PoolsDiffer)
676        self.assertNotExists(snapname1)
677        self.assertNotExists(snapname2)
678
679    def test_snapshot_different_pools_ro_pool(self):
680        snapname1 = ZFSTest.pool.makeName(b"@snap")
681        snapname2 = ZFSTest.readonly_pool.makeName(b"@snap")
682        snaps = [snapname1, snapname2]
683
684        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
685            lzc.lzc_snapshot(snaps)
686
687        # NB: one common error is reported.
688        self.assertEqual(len(ctx.exception.errors), 1)
689        for e in ctx.exception.errors:
690            # NB: depending on whether the first attempted snapshot is
691            # for the read-only pool a different error is reported.
692            self.assertIsInstance(
693                e, (lzc_exc.PoolsDiffer, lzc_exc.ReadOnlyPool))
694        self.assertNotExists(snapname1)
695        self.assertNotExists(snapname2)
696
697    def test_snapshot_invalid_name(self):
698        snapname1 = ZFSTest.pool.makeName(b"@bad&name")
699        snapname2 = ZFSTest.pool.makeName(b"fs1@bad*name")
700        snapname3 = ZFSTest.pool.makeName(b"fs2@snap")
701        snaps = [snapname1, snapname2, snapname3]
702
703        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
704            lzc.lzc_snapshot(snaps)
705
706        # NB: one common error is reported.
707        self.assertEqual(len(ctx.exception.errors), 1)
708        for e in ctx.exception.errors:
709            self.assertIsInstance(e, lzc_exc.NameInvalid)
710            self.assertIsNone(e.name)
711
712    def test_snapshot_too_long_complete_name(self):
713        snapname1 = ZFSTest.pool.makeTooLongName(b"fs1@")
714        snapname2 = ZFSTest.pool.makeTooLongName(b"fs2@")
715        snapname3 = ZFSTest.pool.makeName(b"@snap")
716        snaps = [snapname1, snapname2, snapname3]
717
718        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
719            lzc.lzc_snapshot(snaps)
720
721        self.assertEqual(len(ctx.exception.errors), 2)
722        for e in ctx.exception.errors:
723            self.assertIsInstance(e, lzc_exc.NameTooLong)
724            self.assertIsNotNone(e.name)
725
726    def test_snapshot_too_long_snap_name(self):
727        snapname1 = ZFSTest.pool.makeTooLongComponent(b"fs1@")
728        snapname2 = ZFSTest.pool.makeTooLongComponent(b"fs2@")
729        snapname3 = ZFSTest.pool.makeName(b"@snap")
730        snaps = [snapname1, snapname2, snapname3]
731
732        with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
733            lzc.lzc_snapshot(snaps)
734
735        # NB: one common error is reported.
736        self.assertEqual(len(ctx.exception.errors), 1)
737        for e in ctx.exception.errors:
738            self.assertIsInstance(e, lzc_exc.NameTooLong)
739            self.assertIsNone(e.name)
740
741    def test_destroy_nonexistent_snapshot(self):
742        lzc.lzc_destroy_snaps([ZFSTest.pool.makeName(b"@nonexistent")], False)
743        lzc.lzc_destroy_snaps([ZFSTest.pool.makeName(b"@nonexistent")], True)
744
745    def test_destroy_snapshot_of_nonexistent_pool(self):
746        with self.assertRaises(lzc_exc.SnapshotDestructionFailure) as ctx:
747            lzc.lzc_destroy_snaps([b"no-such-pool@snap"], False)
748
749        for e in ctx.exception.errors:
750            self.assertIsInstance(e, lzc_exc.PoolNotFound)
751
752        with self.assertRaises(lzc_exc.SnapshotDestructionFailure) as ctx:
753            lzc.lzc_destroy_snaps([b"no-such-pool@snap"], True)
754
755        for e in ctx.exception.errors:
756            self.assertIsInstance(e, lzc_exc.PoolNotFound)
757
758    # NB: note the difference from the nonexistent pool test.
759    def test_destroy_snapshot_of_nonexistent_fs(self):
760        lzc.lzc_destroy_snaps(
761            [ZFSTest.pool.makeName(b"nonexistent@snap")], False)
762        lzc.lzc_destroy_snaps(
763            [ZFSTest.pool.makeName(b"nonexistent@snap")], True)
764
765    # Apparently the name is not checked for validity.
766    @unittest.expectedFailure
767    def test_destroy_invalid_snap_name(self):
768        with self.assertRaises(lzc_exc.SnapshotDestructionFailure):
769            lzc.lzc_destroy_snaps(
770                [ZFSTest.pool.makeName(b"@non$&*existent")], False)
771        with self.assertRaises(lzc_exc.SnapshotDestructionFailure):
772            lzc.lzc_destroy_snaps(
773                [ZFSTest.pool.makeName(b"@non$&*existent")], True)
774
775    # Apparently the full name is not checked for length.
776    @unittest.expectedFailure
777    def test_destroy_too_long_full_snap_name(self):
778        snapname1 = ZFSTest.pool.makeTooLongName(b"fs1@")
779        snaps = [snapname1]
780
781        with self.assertRaises(lzc_exc.SnapshotDestructionFailure):
782            lzc.lzc_destroy_snaps(snaps, False)
783        with self.assertRaises(lzc_exc.SnapshotDestructionFailure):
784            lzc.lzc_destroy_snaps(snaps, True)
785
786    def test_destroy_too_long_short_snap_name(self):
787        snapname1 = ZFSTest.pool.makeTooLongComponent(b"fs1@")
788        snapname2 = ZFSTest.pool.makeTooLongComponent(b"fs2@")
789        snapname3 = ZFSTest.pool.makeName(b"@snap")
790        snaps = [snapname1, snapname2, snapname3]
791
792        with self.assertRaises(lzc_exc.SnapshotDestructionFailure) as ctx:
793            lzc.lzc_destroy_snaps(snaps, False)
794
795        for e in ctx.exception.errors:
796            self.assertIsInstance(e, lzc_exc.NameTooLong)
797
798    @unittest.skipUnless(*snap_always_unmounted_before_destruction())
799    def test_destroy_mounted_snap(self):
800        snap = ZFSTest.pool.getRoot().getSnap()
801
802        lzc.lzc_snapshot([snap])
803        with zfs_mount(snap):
804            # the snapshot should be force-unmounted
805            lzc.lzc_destroy_snaps([snap], defer=False)
806            self.assertNotExists(snap)
807
808    def test_clone(self):
809        # NB: note the special name for the snapshot.
810        # Since currently we can not destroy filesystems,
811        # it would be impossible to destroy the snapshot,
812        # so no point in attempting to clean it up.
813        snapname = ZFSTest.pool.makeName(b"fs2@origin1")
814        name = ZFSTest.pool.makeName(b"fs1/fs/clone1")
815
816        lzc.lzc_snapshot([snapname])
817
818        lzc.lzc_clone(name, snapname)
819        self.assertExists(name)
820
821    def test_clone_nonexistent_snapshot(self):
822        snapname = ZFSTest.pool.makeName(b"fs2@nonexistent")
823        name = ZFSTest.pool.makeName(b"fs1/fs/clone2")
824
825        # XXX The error should be SnapshotNotFound
826        # but limitations of C interface do not allow
827        # to differentiate between the errors.
828        with self.assertRaises(lzc_exc.DatasetNotFound):
829            lzc.lzc_clone(name, snapname)
830        self.assertNotExists(name)
831
832    def test_clone_nonexistent_parent_fs(self):
833        snapname = ZFSTest.pool.makeName(b"fs2@origin3")
834        name = ZFSTest.pool.makeName(b"fs1/nonexistent/clone3")
835
836        lzc.lzc_snapshot([snapname])
837
838        with self.assertRaises(lzc_exc.DatasetNotFound):
839            lzc.lzc_clone(name, snapname)
840        self.assertNotExists(name)
841
842    def test_clone_to_nonexistent_pool(self):
843        snapname = ZFSTest.pool.makeName(b"fs2@snap")
844        name = b"no-such-pool/fs"
845
846        lzc.lzc_snapshot([snapname])
847
848        with self.assertRaises(lzc_exc.DatasetNotFound):
849            lzc.lzc_clone(name, snapname)
850        self.assertNotExists(name)
851
852    def test_clone_invalid_snap_name(self):
853        # Use a valid filesystem name of filesystem that
854        # exists as a snapshot name
855        snapname = ZFSTest.pool.makeName(b"fs1/fs")
856        name = ZFSTest.pool.makeName(b"fs2/clone")
857
858        with self.assertRaises(lzc_exc.SnapshotNameInvalid):
859            lzc.lzc_clone(name, snapname)
860        self.assertNotExists(name)
861
862    def test_clone_invalid_snap_name_2(self):
863        # Use a valid filesystem name of filesystem that
864        # doesn't exist as a snapshot name
865        snapname = ZFSTest.pool.makeName(b"fs1/nonexistent")
866        name = ZFSTest.pool.makeName(b"fs2/clone")
867
868        with self.assertRaises(lzc_exc.SnapshotNameInvalid):
869            lzc.lzc_clone(name, snapname)
870        self.assertNotExists(name)
871
872    def test_clone_invalid_name(self):
873        snapname = ZFSTest.pool.makeName(b"fs2@snap")
874        name = ZFSTest.pool.makeName(b"fs1/bad#name")
875
876        lzc.lzc_snapshot([snapname])
877
878        with self.assertRaises(lzc_exc.FilesystemNameInvalid):
879            lzc.lzc_clone(name, snapname)
880        self.assertNotExists(name)
881
882    def test_clone_invalid_pool_name(self):
883        snapname = ZFSTest.pool.makeName(b"fs2@snap")
884        name = b"bad!pool/fs1"
885
886        lzc.lzc_snapshot([snapname])
887
888        with self.assertRaises(lzc_exc.FilesystemNameInvalid):
889            lzc.lzc_clone(name, snapname)
890        self.assertNotExists(name)
891
892    def test_clone_across_pools(self):
893        snapname = ZFSTest.pool.makeName(b"fs2@snap")
894        name = ZFSTest.misc_pool.makeName(b"clone1")
895
896        lzc.lzc_snapshot([snapname])
897
898        with self.assertRaises(lzc_exc.PoolsDiffer):
899            lzc.lzc_clone(name, snapname)
900        self.assertNotExists(name)
901
902    def test_clone_across_pools_to_ro_pool(self):
903        snapname = ZFSTest.pool.makeName(b"fs2@snap")
904        name = ZFSTest.readonly_pool.makeName(b"fs1/clone1")
905
906        lzc.lzc_snapshot([snapname])
907
908        # it's legal to report either of the conditions
909        with self.assertRaises((lzc_exc.ReadOnlyPool, lzc_exc.PoolsDiffer)):
910            lzc.lzc_clone(name, snapname)
911        self.assertNotExists(name)
912
913    def test_destroy_cloned_fs(self):
914        snapname1 = ZFSTest.pool.makeName(b"fs2@origin4")
915        snapname2 = ZFSTest.pool.makeName(b"fs1@snap")
916        clonename = ZFSTest.pool.makeName(b"fs1/fs/clone4")
917        snaps = [snapname1, snapname2]
918
919        lzc.lzc_snapshot(snaps)
920        lzc.lzc_clone(clonename, snapname1)
921
922        with self.assertRaises(lzc_exc.SnapshotDestructionFailure) as ctx:
923            lzc.lzc_destroy_snaps(snaps, False)
924
925        self.assertEqual(len(ctx.exception.errors), 1)
926        for e in ctx.exception.errors:
927            self.assertIsInstance(e, lzc_exc.SnapshotIsCloned)
928        for snap in snaps:
929            self.assertExists(snap)
930
931    def test_deferred_destroy_cloned_fs(self):
932        snapname1 = ZFSTest.pool.makeName(b"fs2@origin5")
933        snapname2 = ZFSTest.pool.makeName(b"fs1@snap")
934        clonename = ZFSTest.pool.makeName(b"fs1/fs/clone5")
935        snaps = [snapname1, snapname2]
936
937        lzc.lzc_snapshot(snaps)
938        lzc.lzc_clone(clonename, snapname1)
939
940        lzc.lzc_destroy_snaps(snaps, defer=True)
941
942        self.assertExists(snapname1)
943        self.assertNotExists(snapname2)
944
945    def test_rollback(self):
946        name = ZFSTest.pool.makeName(b"fs1")
947        snapname = name + b"@snap"
948
949        lzc.lzc_snapshot([snapname])
950        ret = lzc.lzc_rollback(name)
951        self.assertEqual(ret, snapname)
952
953    def test_rollback_2(self):
954        name = ZFSTest.pool.makeName(b"fs1")
955        snapname1 = name + b"@snap1"
956        snapname2 = name + b"@snap2"
957
958        lzc.lzc_snapshot([snapname1])
959        lzc.lzc_snapshot([snapname2])
960        ret = lzc.lzc_rollback(name)
961        self.assertEqual(ret, snapname2)
962
963    def test_rollback_no_snaps(self):
964        name = ZFSTest.pool.makeName(b"fs1")
965
966        with self.assertRaises(lzc_exc.SnapshotNotFound):
967            lzc.lzc_rollback(name)
968
969    def test_rollback_non_existent_fs(self):
970        name = ZFSTest.pool.makeName(b"nonexistent")
971
972        with self.assertRaises(lzc_exc.FilesystemNotFound):
973            lzc.lzc_rollback(name)
974
975    def test_rollback_invalid_fs_name(self):
976        name = ZFSTest.pool.makeName(b"bad~name")
977
978        with self.assertRaises(lzc_exc.NameInvalid):
979            lzc.lzc_rollback(name)
980
981    def test_rollback_snap_name(self):
982        name = ZFSTest.pool.makeName(b"fs1@snap")
983
984        with self.assertRaises(lzc_exc.NameInvalid):
985            lzc.lzc_rollback(name)
986
987    def test_rollback_snap_name_2(self):
988        name = ZFSTest.pool.makeName(b"fs1@snap")
989
990        lzc.lzc_snapshot([name])
991        with self.assertRaises(lzc_exc.NameInvalid):
992            lzc.lzc_rollback(name)
993
994    def test_rollback_too_long_fs_name(self):
995        name = ZFSTest.pool.makeTooLongName()
996
997        with self.assertRaises(lzc_exc.NameTooLong):
998            lzc.lzc_rollback(name)
999
1000    def test_rollback_to_snap_name(self):
1001        name = ZFSTest.pool.makeName(b"fs1")
1002        snap = name + b"@snap"
1003
1004        lzc.lzc_snapshot([snap])
1005        lzc.lzc_rollback_to(name, snap)
1006
1007    def test_rollback_to_not_latest(self):
1008        fsname = ZFSTest.pool.makeName(b'fs1')
1009        snap1 = fsname + b"@snap1"
1010        snap2 = fsname + b"@snap2"
1011
1012        lzc.lzc_snapshot([snap1])
1013        lzc.lzc_snapshot([snap2])
1014        with self.assertRaises(lzc_exc.SnapshotNotLatest):
1015            lzc.lzc_rollback_to(fsname, fsname + b"@snap1")
1016
1017    @skipUnlessBookmarksSupported
1018    def test_bookmarks(self):
1019        snaps = [ZFSTest.pool.makeName(
1020            b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
1021        bmarks = [ZFSTest.pool.makeName(
1022            b'fs1#bmark1'), ZFSTest.pool.makeName(b'fs2#bmark1')]
1023        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1024
1025        lzc.lzc_snapshot(snaps)
1026        lzc.lzc_bookmark(bmark_dict)
1027
1028    @skipUnlessBookmarksSupported
1029    def test_bookmarks_2(self):
1030        snaps = [ZFSTest.pool.makeName(
1031            b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
1032        bmarks = [ZFSTest.pool.makeName(
1033            b'fs1#bmark1'), ZFSTest.pool.makeName(b'fs2#bmark1')]
1034        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1035        lzc.lzc_snapshot(snaps)
1036        lzc.lzc_bookmark(bmark_dict)
1037        lzc.lzc_destroy_snaps(snaps, defer=False)
1038
1039    @skipUnlessBookmarksSupported
1040    def test_bookmark_copying(self):
1041        snaps = [ZFSTest.pool.makeName(s) for s in [
1042            b'fs1@snap1', b'fs1@snap2', b'fs2@snap1']]
1043        bmarks = [ZFSTest.pool.makeName(x) for x in [
1044            b'fs1#bmark1', b'fs1#bmark2', b'fs2#bmark1']]
1045        bmarks_copies = [ZFSTest.pool.makeName(x) for x in [
1046            b'fs1#bmark1_copy', b'fs1#bmark2_copy', b'fs2#bmark1_copy']]
1047        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1048        bmark_copies_dict = {x: y for x, y in zip(bmarks_copies, bmarks)}
1049
1050        for snap in snaps:
1051            lzc.lzc_snapshot([snap])
1052        lzc.lzc_bookmark(bmark_dict)
1053
1054        lzc.lzc_bookmark(bmark_copies_dict)
1055        lzc.lzc_destroy_bookmarks(bmarks_copies)
1056
1057        lzc.lzc_destroy_bookmarks(bmarks)
1058        lzc.lzc_destroy_snaps(snaps, defer=False)
1059
1060    @skipUnlessBookmarksSupported
1061    def test_bookmarks_empty(self):
1062        lzc.lzc_bookmark({})
1063
1064    @skipUnlessBookmarksSupported
1065    def test_bookmarks_foreign_source(self):
1066        snaps = [ZFSTest.pool.makeName(b'fs1@snap1')]
1067        bmarks = [ZFSTest.pool.makeName(b'fs2#bmark1')]
1068        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1069
1070        lzc.lzc_snapshot(snaps)
1071        with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
1072            lzc.lzc_bookmark(bmark_dict)
1073
1074        for e in ctx.exception.errors:
1075            self.assertIsInstance(e, lzc_exc.BookmarkMismatch)
1076
1077    @skipUnlessBookmarksSupported
1078    def test_bookmarks_invalid_name(self):
1079        snaps = [ZFSTest.pool.makeName(b'fs1@snap1')]
1080        bmarks = [ZFSTest.pool.makeName(b'fs1#bmark!')]
1081        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1082
1083        lzc.lzc_snapshot(snaps)
1084        with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
1085            lzc.lzc_bookmark(bmark_dict)
1086
1087        for e in ctx.exception.errors:
1088            self.assertIsInstance(e, lzc_exc.NameInvalid)
1089
1090    @skipUnlessBookmarksSupported
1091    def test_bookmarks_invalid_name_2(self):
1092        snaps = [ZFSTest.pool.makeName(b'fs1@snap1')]
1093        bmarks = [ZFSTest.pool.makeName(b'fs1@bmark')]
1094        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1095
1096        lzc.lzc_snapshot(snaps)
1097        with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
1098            lzc.lzc_bookmark(bmark_dict)
1099
1100        for e in ctx.exception.errors:
1101            self.assertIsInstance(e, lzc_exc.NameInvalid)
1102
1103    @skipUnlessBookmarksSupported
1104    def test_bookmarks_too_long_name(self):
1105        snaps = [ZFSTest.pool.makeName(b'fs1@snap1')]
1106        bmarks = [ZFSTest.pool.makeTooLongName(b'fs1#')]
1107        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1108
1109        lzc.lzc_snapshot(snaps)
1110        with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
1111            lzc.lzc_bookmark(bmark_dict)
1112
1113        for e in ctx.exception.errors:
1114            self.assertIsInstance(e, lzc_exc.NameTooLong)
1115
1116    @skipUnlessBookmarksSupported
1117    def test_bookmarks_too_long_name_2(self):
1118        snaps = [ZFSTest.pool.makeName(b'fs1@snap1')]
1119        bmarks = [ZFSTest.pool.makeTooLongComponent(b'fs1#')]
1120        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1121
1122        lzc.lzc_snapshot(snaps)
1123        with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
1124            lzc.lzc_bookmark(bmark_dict)
1125
1126        for e in ctx.exception.errors:
1127            self.assertIsInstance(e, lzc_exc.NameTooLong)
1128
1129    @skipUnlessBookmarksSupported
1130    def test_bookmarks_foreign_sources(self):
1131        snaps = [ZFSTest.pool.makeName(
1132            b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
1133        bmarks = [ZFSTest.pool.makeName(
1134            b'fs2#bmark1'), ZFSTest.pool.makeName(b'fs1#bmark1')]
1135        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1136
1137        lzc.lzc_snapshot(snaps)
1138        with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
1139            lzc.lzc_bookmark(bmark_dict)
1140
1141        for e in ctx.exception.errors:
1142            self.assertIsInstance(e, lzc_exc.BookmarkMismatch)
1143
1144    @skipUnlessBookmarksSupported
1145    def test_bookmarks_partially_foreign_sources(self):
1146        snaps = [ZFSTest.pool.makeName(
1147            b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
1148        bmarks = [ZFSTest.pool.makeName(
1149            b'fs2#bmark'), ZFSTest.pool.makeName(b'fs2#bmark1')]
1150        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1151
1152        lzc.lzc_snapshot(snaps)
1153        with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
1154            lzc.lzc_bookmark(bmark_dict)
1155
1156        for e in ctx.exception.errors:
1157            self.assertIsInstance(e, lzc_exc.BookmarkMismatch)
1158
1159    @skipUnlessBookmarksSupported
1160    def test_bookmarks_cross_pool(self):
1161        snaps = [ZFSTest.pool.makeName(
1162            b'fs1@snap1'), ZFSTest.misc_pool.makeName(b'@snap1')]
1163        bmarks = [ZFSTest.pool.makeName(
1164            b'fs1#bmark1'), ZFSTest.misc_pool.makeName(b'#bmark1')]
1165        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1166
1167        lzc.lzc_snapshot(snaps[0:1])
1168        lzc.lzc_snapshot(snaps[1:2])
1169        with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
1170            lzc.lzc_bookmark(bmark_dict)
1171
1172        for e in ctx.exception.errors:
1173            self.assertIsInstance(e, lzc_exc.PoolsDiffer)
1174
1175    @skipUnlessBookmarksSupported
1176    def test_bookmarks_missing_snap(self):
1177        fss = [ZFSTest.pool.makeName(b'fs1'), ZFSTest.pool.makeName(b'fs2')]
1178        snaps = [ZFSTest.pool.makeName(
1179            b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
1180        bmarks = [ZFSTest.pool.makeName(
1181            b'fs1#bmark1'), ZFSTest.pool.makeName(b'fs2#bmark1')]
1182        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1183
1184        lzc.lzc_snapshot(snaps[0:1])  # only create fs1@snap1
1185
1186        with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
1187            lzc.lzc_bookmark(bmark_dict)
1188
1189        for e in ctx.exception.errors:
1190            self.assertIsInstance(e, lzc_exc.SnapshotNotFound)
1191
1192        # no new bookmarks are created if one or more sources do not exist
1193        for fs in fss:
1194            fsbmarks = lzc.lzc_get_bookmarks(fs)
1195            self.assertEqual(len(fsbmarks), 0)
1196
1197    @skipUnlessBookmarksSupported
1198    def test_bookmarks_missing_snaps(self):
1199        fss = [ZFSTest.pool.makeName(b'fs1'), ZFSTest.pool.makeName(b'fs2')]
1200        snaps = [ZFSTest.pool.makeName(
1201            b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
1202        bmarks = [ZFSTest.pool.makeName(
1203            b'fs1#bmark1'), ZFSTest.pool.makeName(b'fs2#bmark1')]
1204        bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
1205
1206        # do not create any snapshots
1207
1208        with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
1209            lzc.lzc_bookmark(bmark_dict)
1210
1211        for e in ctx.exception.errors:
1212            self.assertIsInstance(e, lzc_exc.SnapshotNotFound)
1213
1214        # no new bookmarks are created if one or more sources do not exist
1215        for fs in fss:
1216            fsbmarks = lzc.lzc_get_bookmarks(fs)
1217            self.assertEqual(len(fsbmarks), 0)
1218
1219    @skipUnlessBookmarksSupported
1220    def test_bookmarks_for_the_same_snap(self):
1221        snap = ZFSTest.pool.makeName(b'fs1@snap1')
1222        bmark1 = ZFSTest.pool.makeName(b'fs1#bmark1')
1223        bmark2 = ZFSTest.pool.makeName(b'fs1#bmark2')
1224        bmark_dict = {bmark1: snap, bmark2: snap}
1225
1226        lzc.lzc_snapshot([snap])
1227        lzc.lzc_bookmark(bmark_dict)
1228
1229    @skipUnlessBookmarksSupported
1230    def test_bookmarks_for_the_same_snap_2(self):
1231        snap = ZFSTest.pool.makeName(b'fs1@snap1')
1232        bmark1 = ZFSTest.pool.makeName(b'fs1#bmark1')
1233        bmark2 = ZFSTest.pool.makeName(b'fs1#bmark2')
1234        bmark_dict1 = {bmark1: snap}
1235        bmark_dict2 = {bmark2: snap}
1236
1237        lzc.lzc_snapshot([snap])
1238        lzc.lzc_bookmark(bmark_dict1)
1239        lzc.lzc_bookmark(bmark_dict2)
1240
1241    @skipUnlessBookmarksSupported
1242    def test_bookmarks_duplicate_name(self):
1243        snap1 = ZFSTest.pool.makeName(b'fs1@snap1')
1244        snap2 = ZFSTest.pool.makeName(b'fs1@snap2')
1245        bmark = ZFSTest.pool.makeName(b'fs1#bmark')
1246        bmark_dict1 = {bmark: snap1}
1247        bmark_dict2 = {bmark: snap2}
1248
1249        lzc.lzc_snapshot([snap1])
1250        lzc.lzc_snapshot([snap2])
1251        lzc.lzc_bookmark(bmark_dict1)
1252        with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
1253            lzc.lzc_bookmark(bmark_dict2)
1254
1255        for e in ctx.exception.errors:
1256            self.assertIsInstance(e, lzc_exc.BookmarkExists)
1257
1258    @skipUnlessBookmarksSupported
1259    def test_get_bookmarks(self):
1260        snap1 = ZFSTest.pool.makeName(b'fs1@snap1')
1261        snap2 = ZFSTest.pool.makeName(b'fs1@snap2')
1262        bmark = ZFSTest.pool.makeName(b'fs1#bmark')
1263        bmark1 = ZFSTest.pool.makeName(b'fs1#bmark1')
1264        bmark2 = ZFSTest.pool.makeName(b'fs1#bmark2')
1265        bmark_dict1 = {bmark1: snap1, bmark2: snap2}
1266        bmark_dict2 = {bmark: snap2}
1267
1268        lzc.lzc_snapshot([snap1])
1269        lzc.lzc_snapshot([snap2])
1270        lzc.lzc_bookmark(bmark_dict1)
1271        lzc.lzc_bookmark(bmark_dict2)
1272        lzc.lzc_destroy_snaps([snap1, snap2], defer=False)
1273
1274        bmarks = lzc.lzc_get_bookmarks(ZFSTest.pool.makeName(b'fs1'))
1275        self.assertEqual(len(bmarks), 3)
1276        for b in b'bmark', b'bmark1', b'bmark2':
1277            self.assertIn(b, bmarks)
1278            self.assertIsInstance(bmarks[b], dict)
1279            self.assertEqual(len(bmarks[b]), 0)
1280
1281        bmarks = lzc.lzc_get_bookmarks(ZFSTest.pool.makeName(b'fs1'),
1282                                       [b'guid', b'createtxg', b'creation'])
1283        self.assertEqual(len(bmarks), 3)
1284        for b in b'bmark', b'bmark1', b'bmark2':
1285            self.assertIn(b, bmarks)
1286            self.assertIsInstance(bmarks[b], dict)
1287            self.assertEqual(len(bmarks[b]), 3)
1288
1289    @skipUnlessBookmarksSupported
1290    def test_get_bookmarks_invalid_property(self):
1291        snap = ZFSTest.pool.makeName(b'fs1@snap')
1292        bmark = ZFSTest.pool.makeName(b'fs1#bmark')
1293        bmark_dict = {bmark: snap}
1294
1295        lzc.lzc_snapshot([snap])
1296        lzc.lzc_bookmark(bmark_dict)
1297
1298        bmarks = lzc.lzc_get_bookmarks(
1299            ZFSTest.pool.makeName(b'fs1'), [b'badprop'])
1300        self.assertEqual(len(bmarks), 1)
1301        for b in (b'bmark', ):
1302            self.assertIn(b, bmarks)
1303            self.assertIsInstance(bmarks[b], dict)
1304            self.assertEqual(len(bmarks[b]), 0)
1305
1306    @skipUnlessBookmarksSupported
1307    def test_get_bookmarks_nonexistent_fs(self):
1308        with self.assertRaises(lzc_exc.FilesystemNotFound):
1309            lzc.lzc_get_bookmarks(ZFSTest.pool.makeName(b'nonexistent'))
1310
1311    @skipUnlessBookmarksSupported
1312    def test_destroy_bookmarks(self):
1313        snap = ZFSTest.pool.makeName(b'fs1@snap')
1314        bmark = ZFSTest.pool.makeName(b'fs1#bmark')
1315        bmark_dict = {bmark: snap}
1316
1317        lzc.lzc_snapshot([snap])
1318        lzc.lzc_bookmark(bmark_dict)
1319
1320        lzc.lzc_destroy_bookmarks(
1321            [bmark, ZFSTest.pool.makeName(b'fs1#nonexistent')])
1322        bmarks = lzc.lzc_get_bookmarks(ZFSTest.pool.makeName(b'fs1'))
1323        self.assertEqual(len(bmarks), 0)
1324
1325    @skipUnlessBookmarksSupported
1326    def test_destroy_bookmarks_invalid_name(self):
1327        snap = ZFSTest.pool.makeName(b'fs1@snap')
1328        bmark = ZFSTest.pool.makeName(b'fs1#bmark')
1329        bmark_dict = {bmark: snap}
1330
1331        lzc.lzc_snapshot([snap])
1332        lzc.lzc_bookmark(bmark_dict)
1333
1334        with self.assertRaises(lzc_exc.BookmarkDestructionFailure) as ctx:
1335            lzc.lzc_destroy_bookmarks(
1336                [bmark, ZFSTest.pool.makeName(b'fs1/nonexistent')])
1337        for e in ctx.exception.errors:
1338            self.assertIsInstance(e, lzc_exc.NameInvalid)
1339
1340        bmarks = lzc.lzc_get_bookmarks(ZFSTest.pool.makeName(b'fs1'))
1341        self.assertEqual(len(bmarks), 1)
1342        self.assertIn(b'bmark', bmarks)
1343
1344    @skipUnlessBookmarksSupported
1345    def test_destroy_bookmark_nonexistent_fs(self):
1346        lzc.lzc_destroy_bookmarks(
1347            [ZFSTest.pool.makeName(b'nonexistent#bmark')])
1348
1349    @skipUnlessBookmarksSupported
1350    def test_destroy_bookmarks_empty(self):
1351        lzc.lzc_bookmark({})
1352
1353    def test_snaprange_space(self):
1354        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1355        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1356        snap3 = ZFSTest.pool.makeName(b"fs1@snap")
1357
1358        lzc.lzc_snapshot([snap1])
1359        lzc.lzc_snapshot([snap2])
1360        lzc.lzc_snapshot([snap3])
1361
1362        space = lzc.lzc_snaprange_space(snap1, snap2)
1363        self.assertIsInstance(space, (int, int))
1364        space = lzc.lzc_snaprange_space(snap2, snap3)
1365        self.assertIsInstance(space, (int, int))
1366        space = lzc.lzc_snaprange_space(snap1, snap3)
1367        self.assertIsInstance(space, (int, int))
1368
1369    def test_snaprange_space_2(self):
1370        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1371        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1372        snap3 = ZFSTest.pool.makeName(b"fs1@snap")
1373
1374        lzc.lzc_snapshot([snap1])
1375        with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
1376            with tempfile.NamedTemporaryFile(dir=mntdir) as f:
1377                for i in range(1024):
1378                    f.write(b'x' * 1024)
1379                f.flush()
1380                lzc.lzc_snapshot([snap2])
1381        lzc.lzc_snapshot([snap3])
1382
1383        space = lzc.lzc_snaprange_space(snap1, snap2)
1384        self.assertGreater(space, 1024 * 1024)
1385        space = lzc.lzc_snaprange_space(snap2, snap3)
1386        self.assertGreater(space, 1024 * 1024)
1387        space = lzc.lzc_snaprange_space(snap1, snap3)
1388        self.assertGreater(space, 1024 * 1024)
1389
1390    def test_snaprange_space_same_snap(self):
1391        snap = ZFSTest.pool.makeName(b"fs1@snap")
1392
1393        with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
1394            with tempfile.NamedTemporaryFile(dir=mntdir) as f:
1395                for i in range(1024):
1396                    f.write(b'x' * 1024)
1397                f.flush()
1398                lzc.lzc_snapshot([snap])
1399
1400        space = lzc.lzc_snaprange_space(snap, snap)
1401        self.assertGreater(space, 1024 * 1024)
1402        self.assertAlmostEqual(space, 1024 * 1024, delta=1024 * 1024 // 20)
1403
1404    def test_snaprange_space_wrong_order(self):
1405        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1406        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1407
1408        lzc.lzc_snapshot([snap1])
1409        lzc.lzc_snapshot([snap2])
1410
1411        with self.assertRaises(lzc_exc.SnapshotMismatch):
1412            lzc.lzc_snaprange_space(snap2, snap1)
1413
1414    def test_snaprange_space_unrelated(self):
1415        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1416        snap2 = ZFSTest.pool.makeName(b"fs2@snap2")
1417
1418        lzc.lzc_snapshot([snap1])
1419        lzc.lzc_snapshot([snap2])
1420
1421        with self.assertRaises(lzc_exc.SnapshotMismatch):
1422            lzc.lzc_snaprange_space(snap1, snap2)
1423
1424    def test_snaprange_space_across_pools(self):
1425        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1426        snap2 = ZFSTest.misc_pool.makeName(b"@snap2")
1427
1428        lzc.lzc_snapshot([snap1])
1429        lzc.lzc_snapshot([snap2])
1430
1431        with self.assertRaises(lzc_exc.PoolsDiffer):
1432            lzc.lzc_snaprange_space(snap1, snap2)
1433
1434    def test_snaprange_space_nonexistent(self):
1435        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1436        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1437
1438        lzc.lzc_snapshot([snap1])
1439
1440        with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
1441            lzc.lzc_snaprange_space(snap1, snap2)
1442        self.assertEqual(ctx.exception.name, snap2)
1443
1444        with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
1445            lzc.lzc_snaprange_space(snap2, snap1)
1446        self.assertEqual(ctx.exception.name, snap1)
1447
1448    def test_snaprange_space_invalid_name(self):
1449        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1450        snap2 = ZFSTest.pool.makeName(b"fs1@sn#p")
1451
1452        lzc.lzc_snapshot([snap1])
1453
1454        with self.assertRaises(lzc_exc.NameInvalid):
1455            lzc.lzc_snaprange_space(snap1, snap2)
1456
1457    def test_snaprange_space_not_snap(self):
1458        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1459        snap2 = ZFSTest.pool.makeName(b"fs1")
1460
1461        lzc.lzc_snapshot([snap1])
1462
1463        with self.assertRaises(lzc_exc.NameInvalid):
1464            lzc.lzc_snaprange_space(snap1, snap2)
1465        with self.assertRaises(lzc_exc.NameInvalid):
1466            lzc.lzc_snaprange_space(snap2, snap1)
1467
1468    def test_snaprange_space_not_snap_2(self):
1469        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1470        snap2 = ZFSTest.pool.makeName(b"fs1#bmark")
1471
1472        lzc.lzc_snapshot([snap1])
1473
1474        with self.assertRaises(lzc_exc.NameInvalid):
1475            lzc.lzc_snaprange_space(snap1, snap2)
1476        with self.assertRaises(lzc_exc.NameInvalid):
1477            lzc.lzc_snaprange_space(snap2, snap1)
1478
1479    def test_send_space(self):
1480        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1481        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1482        snap3 = ZFSTest.pool.makeName(b"fs1@snap")
1483
1484        lzc.lzc_snapshot([snap1])
1485        lzc.lzc_snapshot([snap2])
1486        lzc.lzc_snapshot([snap3])
1487
1488        space = lzc.lzc_send_space(snap2, snap1)
1489        self.assertIsInstance(space, (int, int))
1490        space = lzc.lzc_send_space(snap3, snap2)
1491        self.assertIsInstance(space, (int, int))
1492        space = lzc.lzc_send_space(snap3, snap1)
1493        self.assertIsInstance(space, (int, int))
1494        space = lzc.lzc_send_space(snap1)
1495        self.assertIsInstance(space, (int, int))
1496        space = lzc.lzc_send_space(snap2)
1497        self.assertIsInstance(space, (int, int))
1498        space = lzc.lzc_send_space(snap3)
1499        self.assertIsInstance(space, (int, int))
1500
1501    def test_send_space_2(self):
1502        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1503        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1504        snap3 = ZFSTest.pool.makeName(b"fs1@snap")
1505
1506        lzc.lzc_snapshot([snap1])
1507        with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
1508            with tempfile.NamedTemporaryFile(dir=mntdir) as f:
1509                for i in range(1024):
1510                    f.write(b'x' * 1024)
1511                f.flush()
1512                lzc.lzc_snapshot([snap2])
1513        lzc.lzc_snapshot([snap3])
1514
1515        space = lzc.lzc_send_space(snap2, snap1)
1516        self.assertGreater(space, 1024 * 1024)
1517
1518        space = lzc.lzc_send_space(snap3, snap2)
1519
1520        space = lzc.lzc_send_space(snap3, snap1)
1521
1522        space_empty = lzc.lzc_send_space(snap1)
1523
1524        space = lzc.lzc_send_space(snap2)
1525        self.assertGreater(space, 1024 * 1024)
1526
1527        space = lzc.lzc_send_space(snap3)
1528        self.assertEqual(space, space_empty)
1529
1530    def test_send_space_same_snap(self):
1531        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1532        lzc.lzc_snapshot([snap1])
1533        with self.assertRaises(lzc_exc.SnapshotMismatch):
1534            lzc.lzc_send_space(snap1, snap1)
1535
1536    def test_send_space_wrong_order(self):
1537        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1538        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1539
1540        lzc.lzc_snapshot([snap1])
1541        lzc.lzc_snapshot([snap2])
1542
1543        with self.assertRaises(lzc_exc.SnapshotMismatch):
1544            lzc.lzc_send_space(snap1, snap2)
1545
1546    def test_send_space_unrelated(self):
1547        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1548        snap2 = ZFSTest.pool.makeName(b"fs2@snap2")
1549
1550        lzc.lzc_snapshot([snap1])
1551        lzc.lzc_snapshot([snap2])
1552
1553        with self.assertRaises(lzc_exc.SnapshotMismatch):
1554            lzc.lzc_send_space(snap1, snap2)
1555
1556    def test_send_space_across_pools(self):
1557        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1558        snap2 = ZFSTest.misc_pool.makeName(b"@snap2")
1559
1560        lzc.lzc_snapshot([snap1])
1561        lzc.lzc_snapshot([snap2])
1562
1563        with self.assertRaises(lzc_exc.PoolsDiffer):
1564            lzc.lzc_send_space(snap1, snap2)
1565
1566    def test_send_space_nonexistent(self):
1567        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1568        snap2 = ZFSTest.pool.makeName(b"fs2@snap2")
1569
1570        lzc.lzc_snapshot([snap1])
1571
1572        with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
1573            lzc.lzc_send_space(snap1, snap2)
1574        self.assertEqual(ctx.exception.name, snap1)
1575
1576        with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
1577            lzc.lzc_send_space(snap2, snap1)
1578        self.assertEqual(ctx.exception.name, snap2)
1579
1580        with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
1581            lzc.lzc_send_space(snap2)
1582        self.assertEqual(ctx.exception.name, snap2)
1583
1584    def test_send_space_invalid_name(self):
1585        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1586        snap2 = ZFSTest.pool.makeName(b"fs1@sn!p")
1587
1588        lzc.lzc_snapshot([snap1])
1589
1590        with self.assertRaises(lzc_exc.NameInvalid) as ctx:
1591            lzc.lzc_send_space(snap2, snap1)
1592        self.assertEqual(ctx.exception.name, snap2)
1593        with self.assertRaises(lzc_exc.NameInvalid) as ctx:
1594            lzc.lzc_send_space(snap2)
1595        self.assertEqual(ctx.exception.name, snap2)
1596        with self.assertRaises(lzc_exc.NameInvalid) as ctx:
1597            lzc.lzc_send_space(snap1, snap2)
1598        self.assertEqual(ctx.exception.name, snap2)
1599
1600    def test_send_space_not_snap(self):
1601        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1602        snap2 = ZFSTest.pool.makeName(b"fs1")
1603
1604        lzc.lzc_snapshot([snap1])
1605
1606        with self.assertRaises(lzc_exc.NameInvalid):
1607            lzc.lzc_send_space(snap1, snap2)
1608        with self.assertRaises(lzc_exc.NameInvalid):
1609            lzc.lzc_send_space(snap2, snap1)
1610        with self.assertRaises(lzc_exc.NameInvalid):
1611            lzc.lzc_send_space(snap2)
1612
1613    def test_send_space_not_snap_2(self):
1614        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1615        snap2 = ZFSTest.pool.makeName(b"fs1#bmark")
1616
1617        lzc.lzc_snapshot([snap1])
1618
1619        with self.assertRaises(lzc_exc.NameInvalid):
1620            lzc.lzc_send_space(snap2, snap1)
1621        with self.assertRaises(lzc_exc.NameInvalid):
1622            lzc.lzc_send_space(snap2)
1623
1624    def test_send_full(self):
1625        snap = ZFSTest.pool.makeName(b"fs1@snap")
1626
1627        with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
1628            with tempfile.NamedTemporaryFile(dir=mntdir) as f:
1629                for i in range(1024):
1630                    f.write(b'x' * 1024)
1631                f.flush()
1632                lzc.lzc_snapshot([snap])
1633
1634        with tempfile.TemporaryFile(suffix='.zstream') as output:
1635            estimate = lzc.lzc_send_space(snap)
1636
1637            fd = output.fileno()
1638            lzc.lzc_send(snap, None, fd)
1639            st = os.fstat(fd)
1640            # 5%, arbitrary.
1641            self.assertAlmostEqual(st.st_size, estimate, delta=estimate // 20)
1642
1643    def test_send_incremental(self):
1644        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1645        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1646
1647        lzc.lzc_snapshot([snap1])
1648        with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
1649            with tempfile.NamedTemporaryFile(dir=mntdir) as f:
1650                for i in range(1024):
1651                    f.write(b'x' * 1024)
1652                f.flush()
1653                lzc.lzc_snapshot([snap2])
1654
1655        with tempfile.TemporaryFile(suffix='.zstream') as output:
1656            estimate = lzc.lzc_send_space(snap2, snap1)
1657
1658            fd = output.fileno()
1659            lzc.lzc_send(snap2, snap1, fd)
1660            st = os.fstat(fd)
1661            # 5%, arbitrary.
1662            self.assertAlmostEqual(st.st_size, estimate, delta=estimate // 20)
1663
1664    def test_send_flags(self):
1665        flags = ['embedded_data', 'large_blocks', 'compress', 'raw']
1666        snap = ZFSTest.pool.makeName(b"fs1@snap")
1667        lzc.lzc_snapshot([snap])
1668
1669        for c in range(len(flags)):
1670            for flag in itertools.permutations(flags, c + 1):
1671                with dev_null() as fd:
1672                    lzc.lzc_send(snap, None, fd, list(flag))
1673
1674    def test_send_unknown_flags(self):
1675        snap = ZFSTest.pool.makeName(b"fs1@snap")
1676        lzc.lzc_snapshot([snap])
1677        with dev_null() as fd:
1678            with self.assertRaises(lzc_exc.UnknownStreamFeature):
1679                lzc.lzc_send(snap, None, fd, ['embedded_data', 'UNKNOWN'])
1680
1681    def test_send_same_snap(self):
1682        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1683        lzc.lzc_snapshot([snap1])
1684        with tempfile.TemporaryFile(suffix='.zstream') as output:
1685            fd = output.fileno()
1686            with self.assertRaises(lzc_exc.SnapshotMismatch):
1687                lzc.lzc_send(snap1, snap1, fd)
1688
1689    def test_send_wrong_order(self):
1690        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1691        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1692
1693        lzc.lzc_snapshot([snap1])
1694        lzc.lzc_snapshot([snap2])
1695
1696        with tempfile.TemporaryFile(suffix='.zstream') as output:
1697            fd = output.fileno()
1698            with self.assertRaises(lzc_exc.SnapshotMismatch):
1699                lzc.lzc_send(snap1, snap2, fd)
1700
1701    def test_send_unrelated(self):
1702        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1703        snap2 = ZFSTest.pool.makeName(b"fs2@snap2")
1704
1705        lzc.lzc_snapshot([snap1])
1706        lzc.lzc_snapshot([snap2])
1707
1708        with tempfile.TemporaryFile(suffix='.zstream') as output:
1709            fd = output.fileno()
1710            with self.assertRaises(lzc_exc.SnapshotMismatch):
1711                lzc.lzc_send(snap1, snap2, fd)
1712
1713    def test_send_across_pools(self):
1714        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1715        snap2 = ZFSTest.misc_pool.makeName(b"@snap2")
1716
1717        lzc.lzc_snapshot([snap1])
1718        lzc.lzc_snapshot([snap2])
1719
1720        with tempfile.TemporaryFile(suffix='.zstream') as output:
1721            fd = output.fileno()
1722            with self.assertRaises(lzc_exc.PoolsDiffer):
1723                lzc.lzc_send(snap1, snap2, fd)
1724
1725    def test_send_nonexistent(self):
1726        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1727        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1728
1729        lzc.lzc_snapshot([snap1])
1730
1731        with tempfile.TemporaryFile(suffix='.zstream') as output:
1732            fd = output.fileno()
1733            with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
1734                lzc.lzc_send(snap1, snap2, fd)
1735            self.assertEqual(ctx.exception.name, snap1)
1736
1737            with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
1738                lzc.lzc_send(snap2, snap1, fd)
1739            self.assertEqual(ctx.exception.name, snap2)
1740
1741            with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
1742                lzc.lzc_send(snap2, None, fd)
1743            self.assertEqual(ctx.exception.name, snap2)
1744
1745    def test_send_invalid_name(self):
1746        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1747        snap2 = ZFSTest.pool.makeName(b"fs1@sn!p")
1748
1749        lzc.lzc_snapshot([snap1])
1750
1751        with tempfile.TemporaryFile(suffix='.zstream') as output:
1752            fd = output.fileno()
1753            with self.assertRaises(lzc_exc.NameInvalid) as ctx:
1754                lzc.lzc_send(snap2, snap1, fd)
1755            self.assertEqual(ctx.exception.name, snap2)
1756            with self.assertRaises(lzc_exc.NameInvalid) as ctx:
1757                lzc.lzc_send(snap2, None, fd)
1758            self.assertEqual(ctx.exception.name, snap2)
1759            with self.assertRaises(lzc_exc.NameInvalid) as ctx:
1760                lzc.lzc_send(snap1, snap2, fd)
1761            self.assertEqual(ctx.exception.name, snap2)
1762
1763    # XXX Although undocumented the API allows to create an incremental
1764    # or full stream for a filesystem as if a temporary unnamed snapshot
1765    # is taken at some time after the call is made and before the stream
1766    # starts being produced.
1767    def test_send_filesystem(self):
1768        snap = ZFSTest.pool.makeName(b"fs1@snap1")
1769        fs = ZFSTest.pool.makeName(b"fs1")
1770
1771        lzc.lzc_snapshot([snap])
1772
1773        with tempfile.TemporaryFile(suffix='.zstream') as output:
1774            fd = output.fileno()
1775            lzc.lzc_send(fs, snap, fd)
1776            lzc.lzc_send(fs, None, fd)
1777
1778    def test_send_from_filesystem(self):
1779        snap = ZFSTest.pool.makeName(b"fs1@snap1")
1780        fs = ZFSTest.pool.makeName(b"fs1")
1781
1782        lzc.lzc_snapshot([snap])
1783
1784        with tempfile.TemporaryFile(suffix='.zstream') as output:
1785            fd = output.fileno()
1786            with self.assertRaises(lzc_exc.NameInvalid):
1787                lzc.lzc_send(snap, fs, fd)
1788
1789    @skipUnlessBookmarksSupported
1790    def test_send_bookmark(self):
1791        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1792        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1793        bmark = ZFSTest.pool.makeName(b"fs1#bmark")
1794
1795        lzc.lzc_snapshot([snap1])
1796        lzc.lzc_snapshot([snap2])
1797        lzc.lzc_bookmark({bmark: snap2})
1798        lzc.lzc_destroy_snaps([snap2], defer=False)
1799
1800        with tempfile.TemporaryFile(suffix='.zstream') as output:
1801            fd = output.fileno()
1802            with self.assertRaises(lzc_exc.NameInvalid):
1803                lzc.lzc_send(bmark, snap1, fd)
1804            with self.assertRaises(lzc_exc.NameInvalid):
1805                lzc.lzc_send(bmark, None, fd)
1806
1807    @skipUnlessBookmarksSupported
1808    def test_send_from_bookmark(self):
1809        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
1810        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
1811        bmark = ZFSTest.pool.makeName(b"fs1#bmark")
1812
1813        lzc.lzc_snapshot([snap1])
1814        lzc.lzc_snapshot([snap2])
1815        lzc.lzc_bookmark({bmark: snap1})
1816        lzc.lzc_destroy_snaps([snap1], defer=False)
1817
1818        with tempfile.TemporaryFile(suffix='.zstream') as output:
1819            fd = output.fileno()
1820            lzc.lzc_send(snap2, bmark, fd)
1821
1822    def test_send_bad_fd(self):
1823        snap = ZFSTest.pool.makeName(b"fs1@snap")
1824        lzc.lzc_snapshot([snap])
1825
1826        with tempfile.TemporaryFile() as tmp:
1827            bad_fd = tmp.fileno()
1828
1829        with self.assertRaises(lzc_exc.StreamIOError) as ctx:
1830            lzc.lzc_send(snap, None, bad_fd)
1831        self.assertEqual(ctx.exception.errno, errno.EBADF)
1832
1833    def test_send_bad_fd_2(self):
1834        snap = ZFSTest.pool.makeName(b"fs1@snap")
1835        lzc.lzc_snapshot([snap])
1836
1837        with self.assertRaises(lzc_exc.StreamIOError) as ctx:
1838            lzc.lzc_send(snap, None, -2)
1839        self.assertEqual(ctx.exception.errno, errno.EBADF)
1840
1841    def test_send_bad_fd_3(self):
1842        snap = ZFSTest.pool.makeName(b"fs1@snap")
1843        lzc.lzc_snapshot([snap])
1844
1845        with tempfile.TemporaryFile() as tmp:
1846            bad_fd = tmp.fileno()
1847
1848        (soft, hard) = resource.getrlimit(resource.RLIMIT_NOFILE)
1849        bad_fd = hard + 1
1850        with self.assertRaises(lzc_exc.StreamIOError) as ctx:
1851            lzc.lzc_send(snap, None, bad_fd)
1852        self.assertEqual(ctx.exception.errno, errno.EBADF)
1853
1854    def test_send_to_broken_pipe(self):
1855        snap = ZFSTest.pool.makeName(b"fs1@snap")
1856        lzc.lzc_snapshot([snap])
1857
1858        if sys.version_info < (3, 0):
1859            proc = subprocess.Popen(['true'], stdin=subprocess.PIPE)
1860            proc.wait()
1861            with self.assertRaises(lzc_exc.StreamIOError) as ctx:
1862                lzc.lzc_send(snap, None, proc.stdin.fileno())
1863            self.assertEqual(ctx.exception.errno, errno.EPIPE)
1864        else:
1865            with subprocess.Popen(['true'], stdin=subprocess.PIPE) as proc:
1866                proc.wait()
1867                with self.assertRaises(lzc_exc.StreamIOError) as ctx:
1868                    lzc.lzc_send(snap, None, proc.stdin.fileno())
1869                self.assertEqual(ctx.exception.errno, errno.EPIPE)
1870
1871    def test_send_to_broken_pipe_2(self):
1872        snap = ZFSTest.pool.makeName(b"fs1@snap")
1873        with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
1874            with tempfile.NamedTemporaryFile(dir=mntdir) as f:
1875                for i in range(1024):
1876                    f.write(b'x' * 1024)
1877                f.flush()
1878                lzc.lzc_snapshot([snap])
1879
1880        if sys.version_info < (3, 0):
1881            p = subprocess.Popen(['sleep', '2'], stdin=subprocess.PIPE)
1882            with self.assertRaises(lzc_exc.StreamIOError) as ctx:
1883                lzc.lzc_send(snap, None, p.stdin.fileno())
1884            self.assertTrue(ctx.exception.errno == errno.EPIPE or
1885                            ctx.exception.errno == errno.EINTR)
1886        else:
1887            with subprocess.Popen(['sleep', '2'], stdin=subprocess.PIPE) as p:
1888                with self.assertRaises(lzc_exc.StreamIOError) as ctx:
1889                    lzc.lzc_send(snap, None, p.stdin.fileno())
1890                self.assertTrue(ctx.exception.errno == errno.EPIPE or
1891                                ctx.exception.errno == errno.EINTR)
1892
1893    def test_send_to_ro_file(self):
1894        snap = ZFSTest.pool.makeName(b"fs1@snap")
1895        lzc.lzc_snapshot([snap])
1896
1897        with tempfile.NamedTemporaryFile(
1898                suffix='.zstream', delete=False) as output:
1899            # tempfile always opens a temporary file in read-write mode
1900            # regardless of the specified mode, so we have to open it again.
1901            os.chmod(output.name, stat.S_IRUSR)
1902            fd = os.open(output.name, os.O_RDONLY)
1903            with self.assertRaises(lzc_exc.StreamIOError) as ctx:
1904                lzc.lzc_send(snap, None, fd)
1905            os.close(fd)
1906        self.assertEqual(ctx.exception.errno, errno.EBADF)
1907
1908    def test_recv_full(self):
1909        src = ZFSTest.pool.makeName(b"fs1@snap")
1910        dst = ZFSTest.pool.makeName(b"fs2/received-1@snap")
1911
1912        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")) as name:
1913            lzc.lzc_snapshot([src])
1914
1915        with tempfile.TemporaryFile(suffix='.zstream') as stream:
1916            lzc.lzc_send(src, None, stream.fileno())
1917            stream.seek(0)
1918            lzc.lzc_receive(dst, stream.fileno())
1919
1920        name = os.path.basename(name)
1921        with zfs_mount(src) as mnt1, zfs_mount(dst) as mnt2:
1922            self.assertTrue(
1923                filecmp.cmp(
1924                    os.path.join(mnt1, name), os.path.join(mnt2, name), False))
1925
1926    def test_recv_incremental(self):
1927        src1 = ZFSTest.pool.makeName(b"fs1@snap1")
1928        src2 = ZFSTest.pool.makeName(b"fs1@snap2")
1929        dst1 = ZFSTest.pool.makeName(b"fs2/received-2@snap1")
1930        dst2 = ZFSTest.pool.makeName(b"fs2/received-2@snap2")
1931
1932        lzc.lzc_snapshot([src1])
1933        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")) as name:
1934            lzc.lzc_snapshot([src2])
1935
1936        with tempfile.TemporaryFile(suffix='.zstream') as stream:
1937            lzc.lzc_send(src1, None, stream.fileno())
1938            stream.seek(0)
1939            lzc.lzc_receive(dst1, stream.fileno())
1940        with tempfile.TemporaryFile(suffix='.zstream') as stream:
1941            lzc.lzc_send(src2, src1, stream.fileno())
1942            stream.seek(0)
1943            lzc.lzc_receive(dst2, stream.fileno())
1944
1945        name = os.path.basename(name)
1946        with zfs_mount(src2) as mnt1, zfs_mount(dst2) as mnt2:
1947            self.assertTrue(
1948                filecmp.cmp(
1949                    os.path.join(mnt1, name), os.path.join(mnt2, name), False))
1950
1951    # This test case fails unless a patch from
1952    # https://clusterhq.atlassian.net/browse/ZFS-20
1953    # is applied to libzfs_core, otherwise it succeeds.
1954    @unittest.skip("fails with unpatched libzfs_core")
1955    def test_recv_without_explicit_snap_name(self):
1956        srcfs = ZFSTest.pool.makeName(b"fs1")
1957        src1 = srcfs + b"@snap1"
1958        src2 = srcfs + b"@snap2"
1959        dstfs = ZFSTest.pool.makeName(b"fs2/received-100")
1960        dst1 = dstfs + b'@snap1'
1961        dst2 = dstfs + b'@snap2'
1962
1963        with streams(srcfs, src1, src2) as (_, (full, incr)):
1964            lzc.lzc_receive(dstfs, full.fileno())
1965            lzc.lzc_receive(dstfs, incr.fileno())
1966        self.assertExists(dst1)
1967        self.assertExists(dst2)
1968
1969    def test_recv_clone(self):
1970        orig_src = ZFSTest.pool.makeName(b"fs2@send-origin")
1971        clone = ZFSTest.pool.makeName(b"fs1/fs/send-clone")
1972        clone_snap = clone + b"@snap"
1973        orig_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-origin@snap")
1974        clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone@snap")
1975
1976        lzc.lzc_snapshot([orig_src])
1977        with tempfile.TemporaryFile(suffix='.zstream') as stream:
1978            lzc.lzc_send(orig_src, None, stream.fileno())
1979            stream.seek(0)
1980            lzc.lzc_receive(orig_dst, stream.fileno())
1981
1982        lzc.lzc_clone(clone, orig_src)
1983        lzc.lzc_snapshot([clone_snap])
1984        with tempfile.TemporaryFile(suffix='.zstream') as stream:
1985            lzc.lzc_send(clone_snap, orig_src, stream.fileno())
1986            stream.seek(0)
1987            lzc.lzc_receive(clone_dst, stream.fileno(), origin=orig_dst)
1988
1989    def test_recv_full_already_existing_empty_fs(self):
1990        src = ZFSTest.pool.makeName(b"fs1@snap")
1991        dstfs = ZFSTest.pool.makeName(b"fs2/received-3")
1992        dst = dstfs + b'@snap'
1993
1994        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
1995            lzc.lzc_snapshot([src])
1996        lzc.lzc_create(dstfs)
1997        with tempfile.TemporaryFile(suffix='.zstream') as stream:
1998            lzc.lzc_send(src, None, stream.fileno())
1999            stream.seek(0)
2000            with self.assertRaises((
2001                    lzc_exc.DestinationModified, lzc_exc.DatasetExists)):
2002                lzc.lzc_receive(dst, stream.fileno())
2003
2004    def test_recv_full_into_root_empty_pool(self):
2005        empty_pool = None
2006        try:
2007            srcfs = ZFSTest.pool.makeName(b"fs1")
2008            empty_pool = _TempPool()
2009            dst = empty_pool.makeName(b'@snap')
2010
2011            with streams(srcfs, b"snap", None) as (_, (stream, _)):
2012                with self.assertRaises((
2013                        lzc_exc.DestinationModified, lzc_exc.DatasetExists)):
2014                    lzc.lzc_receive(dst, stream.fileno())
2015        finally:
2016            if empty_pool is not None:
2017                empty_pool.cleanUp()
2018
2019    def test_recv_full_into_ro_pool(self):
2020        srcfs = ZFSTest.pool.makeName(b"fs1")
2021        dst = ZFSTest.readonly_pool.makeName(b'fs2/received@snap')
2022
2023        with streams(srcfs, b"snap", None) as (_, (stream, _)):
2024            with self.assertRaises(lzc_exc.ReadOnlyPool):
2025                lzc.lzc_receive(dst, stream.fileno())
2026
2027    def test_recv_full_already_existing_modified_fs(self):
2028        src = ZFSTest.pool.makeName(b"fs1@snap")
2029        dstfs = ZFSTest.pool.makeName(b"fs2/received-5")
2030        dst = dstfs + b'@snap'
2031
2032        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
2033            lzc.lzc_snapshot([src])
2034        lzc.lzc_create(dstfs)
2035        with temp_file_in_fs(dstfs):
2036            with tempfile.TemporaryFile(suffix='.zstream') as stream:
2037                lzc.lzc_send(src, None, stream.fileno())
2038                stream.seek(0)
2039                with self.assertRaises((
2040                        lzc_exc.DestinationModified, lzc_exc.DatasetExists)):
2041                    lzc.lzc_receive(dst, stream.fileno())
2042
2043    def test_recv_full_already_existing_with_snapshots(self):
2044        src = ZFSTest.pool.makeName(b"fs1@snap")
2045        dstfs = ZFSTest.pool.makeName(b"fs2/received-4")
2046        dst = dstfs + b'@snap'
2047
2048        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
2049            lzc.lzc_snapshot([src])
2050        lzc.lzc_create(dstfs)
2051        lzc.lzc_snapshot([dstfs + b"@snap1"])
2052        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2053            lzc.lzc_send(src, None, stream.fileno())
2054            stream.seek(0)
2055            with self.assertRaises((
2056                    lzc_exc.StreamMismatch, lzc_exc.DatasetExists)):
2057                lzc.lzc_receive(dst, stream.fileno())
2058
2059    def test_recv_full_already_existing_snapshot(self):
2060        src = ZFSTest.pool.makeName(b"fs1@snap")
2061        dstfs = ZFSTest.pool.makeName(b"fs2/received-6")
2062        dst = dstfs + b'@snap'
2063
2064        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
2065            lzc.lzc_snapshot([src])
2066        lzc.lzc_create(dstfs)
2067        lzc.lzc_snapshot([dst])
2068        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2069            lzc.lzc_send(src, None, stream.fileno())
2070            stream.seek(0)
2071            with self.assertRaises(lzc_exc.DatasetExists):
2072                lzc.lzc_receive(dst, stream.fileno())
2073
2074    def test_recv_full_missing_parent_fs(self):
2075        src = ZFSTest.pool.makeName(b"fs1@snap")
2076        dst = ZFSTest.pool.makeName(b"fs2/nonexistent/fs@snap")
2077
2078        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
2079            lzc.lzc_snapshot([src])
2080        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2081            lzc.lzc_send(src, None, stream.fileno())
2082            stream.seek(0)
2083            with self.assertRaises(lzc_exc.DatasetNotFound):
2084                lzc.lzc_receive(dst, stream.fileno())
2085
2086    def test_recv_full_but_specify_origin(self):
2087        srcfs = ZFSTest.pool.makeName(b"fs1")
2088        src = srcfs + b"@snap"
2089        dstfs = ZFSTest.pool.makeName(b"fs2/received-30")
2090        dst = dstfs + b'@snap'
2091        origin1 = ZFSTest.pool.makeName(b"fs2@snap1")
2092        origin2 = ZFSTest.pool.makeName(b"fs2@snap2")
2093
2094        lzc.lzc_snapshot([origin1])
2095        with streams(srcfs, src, None) as (_, (stream, _)):
2096            lzc.lzc_receive(dst, stream.fileno(), origin=origin1)
2097            origin = ZFSTest.pool.getFilesystem(
2098                b"fs2/received-30").getProperty('origin')
2099            self.assertEqual(origin, origin1)
2100            stream.seek(0)
2101            # because origin snap does not exist can't receive as a clone of it
2102            with self.assertRaises((
2103                    lzc_exc.DatasetNotFound,
2104                    lzc_exc.BadStream)):
2105                lzc.lzc_receive(dst, stream.fileno(), origin=origin2)
2106
2107    def test_recv_full_existing_empty_fs_and_origin(self):
2108        srcfs = ZFSTest.pool.makeName(b"fs1")
2109        src = srcfs + b"@snap"
2110        dstfs = ZFSTest.pool.makeName(b"fs2/received-31")
2111        dst = dstfs + b'@snap'
2112        origin = dstfs + b'@dummy'
2113
2114        lzc.lzc_create(dstfs)
2115        with streams(srcfs, src, None) as (_, (stream, _)):
2116            # because the destination fs already exists and has no snaps
2117            with self.assertRaises((
2118                    lzc_exc.DestinationModified,
2119                    lzc_exc.DatasetExists,
2120                    lzc_exc.BadStream)):
2121                lzc.lzc_receive(dst, stream.fileno(), origin=origin)
2122            lzc.lzc_snapshot([origin])
2123            stream.seek(0)
2124            # because the destination fs already exists and has the snap
2125            with self.assertRaises((
2126                    lzc_exc.StreamMismatch,
2127                    lzc_exc.DatasetExists,
2128                    lzc_exc.BadStream)):
2129                lzc.lzc_receive(dst, stream.fileno(), origin=origin)
2130
2131    def test_recv_incremental_mounted_fs(self):
2132        srcfs = ZFSTest.pool.makeName(b"fs1")
2133        src1 = srcfs + b"@snap1"
2134        src2 = srcfs + b"@snap2"
2135        dstfs = ZFSTest.pool.makeName(b"fs2/received-7")
2136        dst1 = dstfs + b'@snap1'
2137        dst2 = dstfs + b'@snap2'
2138
2139        with streams(srcfs, src1, src2) as (_, (full, incr)):
2140            lzc.lzc_receive(dst1, full.fileno())
2141            with zfs_mount(dstfs):
2142                lzc.lzc_receive(dst2, incr.fileno())
2143
2144    def test_recv_incremental_modified_fs(self):
2145        srcfs = ZFSTest.pool.makeName(b"fs1")
2146        src1 = srcfs + b"@snap1"
2147        src2 = srcfs + b"@snap2"
2148        dstfs = ZFSTest.pool.makeName(b"fs2/received-15")
2149        dst1 = dstfs + b'@snap1'
2150        dst2 = dstfs + b'@snap2'
2151
2152        with streams(srcfs, src1, src2) as (_, (full, incr)):
2153            lzc.lzc_receive(dst1, full.fileno())
2154            with temp_file_in_fs(dstfs):
2155                with self.assertRaises(lzc_exc.DestinationModified):
2156                    lzc.lzc_receive(dst2, incr.fileno())
2157
2158    def test_recv_incremental_snapname_used(self):
2159        srcfs = ZFSTest.pool.makeName(b"fs1")
2160        src1 = srcfs + b"@snap1"
2161        src2 = srcfs + b"@snap2"
2162        dstfs = ZFSTest.pool.makeName(b"fs2/received-8")
2163        dst1 = dstfs + b'@snap1'
2164        dst2 = dstfs + b'@snap2'
2165
2166        with streams(srcfs, src1, src2) as (_, (full, incr)):
2167            lzc.lzc_receive(dst1, full.fileno())
2168            lzc.lzc_snapshot([dst2])
2169            with self.assertRaises(lzc_exc.DatasetExists):
2170                lzc.lzc_receive(dst2, incr.fileno())
2171
2172    def test_recv_incremental_more_recent_snap_with_no_changes(self):
2173        srcfs = ZFSTest.pool.makeName(b"fs1")
2174        src1 = srcfs + b"@snap1"
2175        src2 = srcfs + b"@snap2"
2176        dstfs = ZFSTest.pool.makeName(b"fs2/received-9")
2177        dst1 = dstfs + b'@snap1'
2178        dst2 = dstfs + b'@snap2'
2179        dst_snap = dstfs + b'@snap'
2180
2181        with streams(srcfs, src1, src2) as (_, (full, incr)):
2182            lzc.lzc_receive(dst1, full.fileno())
2183            lzc.lzc_snapshot([dst_snap])
2184            lzc.lzc_receive(dst2, incr.fileno())
2185
2186    def test_recv_incremental_non_clone_but_set_origin(self):
2187        srcfs = ZFSTest.pool.makeName(b"fs1")
2188        src1 = srcfs + b"@snap1"
2189        src2 = srcfs + b"@snap2"
2190        dstfs = ZFSTest.pool.makeName(b"fs2/received-20")
2191        dst1 = dstfs + b'@snap1'
2192        dst2 = dstfs + b'@snap2'
2193        dst_snap = dstfs + b'@snap'
2194
2195        with streams(srcfs, src1, src2) as (_, (full, incr)):
2196            lzc.lzc_receive(dst1, full.fileno())
2197            lzc.lzc_snapshot([dst_snap])
2198            # because cannot receive incremental and set origin on a non-clone
2199            with self.assertRaises(lzc_exc.BadStream):
2200                lzc.lzc_receive(dst2, incr.fileno(), origin=dst1)
2201
2202    def test_recv_incremental_non_clone_but_set_random_origin(self):
2203        srcfs = ZFSTest.pool.makeName(b"fs1")
2204        src1 = srcfs + b"@snap1"
2205        src2 = srcfs + b"@snap2"
2206        dstfs = ZFSTest.pool.makeName(b"fs2/received-21")
2207        dst1 = dstfs + b'@snap1'
2208        dst2 = dstfs + b'@snap2'
2209        dst_snap = dstfs + b'@snap'
2210
2211        with streams(srcfs, src1, src2) as (_, (full, incr)):
2212            lzc.lzc_receive(dst1, full.fileno())
2213            lzc.lzc_snapshot([dst_snap])
2214            # because origin snap does not exist can't receive as a clone of it
2215            with self.assertRaises((
2216                    lzc_exc.DatasetNotFound,
2217                    lzc_exc.BadStream)):
2218                lzc.lzc_receive(
2219                    dst2, incr.fileno(),
2220                    origin=ZFSTest.pool.makeName(b"fs2/fs@snap"))
2221
2222    def test_recv_incremental_more_recent_snap(self):
2223        srcfs = ZFSTest.pool.makeName(b"fs1")
2224        src1 = srcfs + b"@snap1"
2225        src2 = srcfs + b"@snap2"
2226        dstfs = ZFSTest.pool.makeName(b"fs2/received-10")
2227        dst1 = dstfs + b'@snap1'
2228        dst2 = dstfs + b'@snap2'
2229        dst_snap = dstfs + b'@snap'
2230
2231        with streams(srcfs, src1, src2) as (_, (full, incr)):
2232            lzc.lzc_receive(dst1, full.fileno())
2233            with temp_file_in_fs(dstfs):
2234                lzc.lzc_snapshot([dst_snap])
2235                with self.assertRaises(lzc_exc.DestinationModified):
2236                    lzc.lzc_receive(dst2, incr.fileno())
2237
2238    def test_recv_incremental_duplicate(self):
2239        srcfs = ZFSTest.pool.makeName(b"fs1")
2240        src1 = srcfs + b"@snap1"
2241        src2 = srcfs + b"@snap2"
2242        dstfs = ZFSTest.pool.makeName(b"fs2/received-11")
2243        dst1 = dstfs + b'@snap1'
2244        dst2 = dstfs + b'@snap2'
2245        dst_snap = dstfs + b'@snap'
2246
2247        with streams(srcfs, src1, src2) as (_, (full, incr)):
2248            lzc.lzc_receive(dst1, full.fileno())
2249            lzc.lzc_receive(dst2, incr.fileno())
2250            incr.seek(0)
2251            with self.assertRaises(lzc_exc.DestinationModified):
2252                lzc.lzc_receive(dst_snap, incr.fileno())
2253
2254    def test_recv_incremental_unrelated_fs(self):
2255        srcfs = ZFSTest.pool.makeName(b"fs1")
2256        src1 = srcfs + b"@snap1"
2257        src2 = srcfs + b"@snap2"
2258        dstfs = ZFSTest.pool.makeName(b"fs2/received-12")
2259        dst_snap = dstfs + b'@snap'
2260
2261        with streams(srcfs, src1, src2) as (_, (_, incr)):
2262            lzc.lzc_create(dstfs)
2263            with self.assertRaises(lzc_exc.StreamMismatch):
2264                lzc.lzc_receive(dst_snap, incr.fileno())
2265
2266    def test_recv_incremental_nonexistent_fs(self):
2267        srcfs = ZFSTest.pool.makeName(b"fs1")
2268        src1 = srcfs + b"@snap1"
2269        src2 = srcfs + b"@snap2"
2270        dstfs = ZFSTest.pool.makeName(b"fs2/received-13")
2271        dst_snap = dstfs + b'@snap'
2272
2273        with streams(srcfs, src1, src2) as (_, (_, incr)):
2274            with self.assertRaises(lzc_exc.DatasetNotFound):
2275                lzc.lzc_receive(dst_snap, incr.fileno())
2276
2277    def test_recv_incremental_same_fs(self):
2278        srcfs = ZFSTest.pool.makeName(b"fs1")
2279        src1 = srcfs + b"@snap1"
2280        src2 = srcfs + b"@snap2"
2281        src_snap = srcfs + b'@snap'
2282
2283        with streams(srcfs, src1, src2) as (_, (_, incr)):
2284            with self.assertRaises(lzc_exc.DestinationModified):
2285                lzc.lzc_receive(src_snap, incr.fileno())
2286
2287    def test_recv_clone_without_specifying_origin(self):
2288        orig_src = ZFSTest.pool.makeName(b"fs2@send-origin-2")
2289        clone = ZFSTest.pool.makeName(b"fs1/fs/send-clone-2")
2290        clone_snap = clone + b"@snap"
2291        orig_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-origin-2@snap")
2292        clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone-2@snap")
2293
2294        lzc.lzc_snapshot([orig_src])
2295        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2296            lzc.lzc_send(orig_src, None, stream.fileno())
2297            stream.seek(0)
2298            lzc.lzc_receive(orig_dst, stream.fileno())
2299
2300        lzc.lzc_clone(clone, orig_src)
2301        lzc.lzc_snapshot([clone_snap])
2302        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2303            lzc.lzc_send(clone_snap, orig_src, stream.fileno())
2304            stream.seek(0)
2305            with self.assertRaises(lzc_exc.BadStream):
2306                lzc.lzc_receive(clone_dst, stream.fileno())
2307
2308    def test_recv_clone_invalid_origin(self):
2309        orig_src = ZFSTest.pool.makeName(b"fs2@send-origin-3")
2310        clone = ZFSTest.pool.makeName(b"fs1/fs/send-clone-3")
2311        clone_snap = clone + b"@snap"
2312        orig_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-origin-3@snap")
2313        clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone-3@snap")
2314
2315        lzc.lzc_snapshot([orig_src])
2316        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2317            lzc.lzc_send(orig_src, None, stream.fileno())
2318            stream.seek(0)
2319            lzc.lzc_receive(orig_dst, stream.fileno())
2320
2321        lzc.lzc_clone(clone, orig_src)
2322        lzc.lzc_snapshot([clone_snap])
2323        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2324            lzc.lzc_send(clone_snap, orig_src, stream.fileno())
2325            stream.seek(0)
2326            with self.assertRaises(lzc_exc.NameInvalid):
2327                lzc.lzc_receive(
2328                    clone_dst, stream.fileno(),
2329                    origin=ZFSTest.pool.makeName(b"fs1/fs"))
2330
2331    def test_recv_clone_wrong_origin(self):
2332        orig_src = ZFSTest.pool.makeName(b"fs2@send-origin-4")
2333        clone = ZFSTest.pool.makeName(b"fs1/fs/send-clone-4")
2334        clone_snap = clone + b"@snap"
2335        orig_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-origin-4@snap")
2336        clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone-4@snap")
2337        wrong_origin = ZFSTest.pool.makeName(b"fs1/fs@snap")
2338
2339        lzc.lzc_snapshot([orig_src])
2340        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2341            lzc.lzc_send(orig_src, None, stream.fileno())
2342            stream.seek(0)
2343            lzc.lzc_receive(orig_dst, stream.fileno())
2344
2345        lzc.lzc_clone(clone, orig_src)
2346        lzc.lzc_snapshot([clone_snap])
2347        lzc.lzc_snapshot([wrong_origin])
2348        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2349            lzc.lzc_send(clone_snap, orig_src, stream.fileno())
2350            stream.seek(0)
2351            with self.assertRaises(lzc_exc.StreamMismatch):
2352                lzc.lzc_receive(
2353                    clone_dst, stream.fileno(), origin=wrong_origin)
2354
2355    def test_recv_clone_nonexistent_origin(self):
2356        orig_src = ZFSTest.pool.makeName(b"fs2@send-origin-5")
2357        clone = ZFSTest.pool.makeName(b"fs1/fs/send-clone-5")
2358        clone_snap = clone + b"@snap"
2359        orig_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-origin-5@snap")
2360        clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone-5@snap")
2361        wrong_origin = ZFSTest.pool.makeName(b"fs1/fs@snap")
2362
2363        lzc.lzc_snapshot([orig_src])
2364        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2365            lzc.lzc_send(orig_src, None, stream.fileno())
2366            stream.seek(0)
2367            lzc.lzc_receive(orig_dst, stream.fileno())
2368
2369        lzc.lzc_clone(clone, orig_src)
2370        lzc.lzc_snapshot([clone_snap])
2371        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2372            lzc.lzc_send(clone_snap, orig_src, stream.fileno())
2373            stream.seek(0)
2374            with self.assertRaises(lzc_exc.DatasetNotFound):
2375                lzc.lzc_receive(
2376                    clone_dst, stream.fileno(), origin=wrong_origin)
2377
2378    def test_force_recv_full_existing_fs(self):
2379        src = ZFSTest.pool.makeName(b"fs1@snap")
2380        dstfs = ZFSTest.pool.makeName(b"fs2/received-50")
2381        dst = dstfs + b'@snap'
2382
2383        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
2384            lzc.lzc_snapshot([src])
2385
2386        lzc.lzc_create(dstfs)
2387        with temp_file_in_fs(dstfs):
2388            pass  # enough to taint the fs
2389
2390        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2391            lzc.lzc_send(src, None, stream.fileno())
2392            stream.seek(0)
2393            lzc.lzc_receive(dst, stream.fileno(), force=True)
2394
2395    def test_force_recv_full_existing_modified_mounted_fs(self):
2396        src = ZFSTest.pool.makeName(b"fs1@snap")
2397        dstfs = ZFSTest.pool.makeName(b"fs2/received-53")
2398        dst = dstfs + b'@snap'
2399
2400        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
2401            lzc.lzc_snapshot([src])
2402
2403        lzc.lzc_create(dstfs)
2404
2405        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2406            lzc.lzc_send(src, None, stream.fileno())
2407            stream.seek(0)
2408            with zfs_mount(dstfs) as mntdir:
2409                f = tempfile.NamedTemporaryFile(dir=mntdir, delete=False)
2410                for i in range(1024):
2411                    f.write(b'x' * 1024)
2412                lzc.lzc_receive(dst, stream.fileno(), force=True)
2413                # The temporary file disappears and any access, even close(),
2414                # results in EIO.
2415                self.assertFalse(os.path.exists(f.name))
2416                with self.assertRaises(IOError):
2417                    f.close()
2418
2419    # This test-case expects the behavior that should be there,
2420    # at the moment it may fail with DatasetExists or StreamMismatch
2421    # depending on the implementation.
2422    def test_force_recv_full_already_existing_with_snapshots(self):
2423        src = ZFSTest.pool.makeName(b"fs1@snap")
2424        dstfs = ZFSTest.pool.makeName(b"fs2/received-51")
2425        dst = dstfs + b'@snap'
2426
2427        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
2428            lzc.lzc_snapshot([src])
2429
2430        lzc.lzc_create(dstfs)
2431        with temp_file_in_fs(dstfs):
2432            pass  # enough to taint the fs
2433        lzc.lzc_snapshot([dstfs + b"@snap1"])
2434
2435        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2436            lzc.lzc_send(src, None, stream.fileno())
2437            stream.seek(0)
2438            lzc.lzc_receive(dst, stream.fileno(), force=True)
2439
2440    def test_force_recv_full_already_existing_with_same_snap(self):
2441        src = ZFSTest.pool.makeName(b"fs1@snap")
2442        dstfs = ZFSTest.pool.makeName(b"fs2/received-52")
2443        dst = dstfs + b'@snap'
2444
2445        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
2446            lzc.lzc_snapshot([src])
2447
2448        lzc.lzc_create(dstfs)
2449        with temp_file_in_fs(dstfs):
2450            pass  # enough to taint the fs
2451        lzc.lzc_snapshot([dst])
2452
2453        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2454            lzc.lzc_send(src, None, stream.fileno())
2455            stream.seek(0)
2456            with self.assertRaises(lzc_exc.DatasetExists):
2457                lzc.lzc_receive(dst, stream.fileno(), force=True)
2458
2459    def test_force_recv_full_missing_parent_fs(self):
2460        src = ZFSTest.pool.makeName(b"fs1@snap")
2461        dst = ZFSTest.pool.makeName(b"fs2/nonexistent/fs@snap")
2462
2463        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
2464            lzc.lzc_snapshot([src])
2465        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2466            lzc.lzc_send(src, None, stream.fileno())
2467            stream.seek(0)
2468            with self.assertRaises(lzc_exc.DatasetNotFound):
2469                lzc.lzc_receive(dst, stream.fileno(), force=True)
2470
2471    def test_force_recv_incremental_modified_fs(self):
2472        srcfs = ZFSTest.pool.makeName(b"fs1")
2473        src1 = srcfs + b"@snap1"
2474        src2 = srcfs + b"@snap2"
2475        dstfs = ZFSTest.pool.makeName(b"fs2/received-60")
2476        dst1 = dstfs + b'@snap1'
2477        dst2 = dstfs + b'@snap2'
2478
2479        with streams(srcfs, src1, src2) as (_, (full, incr)):
2480            lzc.lzc_receive(dst1, full.fileno())
2481            with temp_file_in_fs(dstfs):
2482                pass  # enough to taint the fs
2483            lzc.lzc_receive(dst2, incr.fileno(), force=True)
2484
2485    def test_force_recv_incremental_modified_mounted_fs(self):
2486        srcfs = ZFSTest.pool.makeName(b"fs1")
2487        src1 = srcfs + b"@snap1"
2488        src2 = srcfs + b"@snap2"
2489        dstfs = ZFSTest.pool.makeName(b"fs2/received-64")
2490        dst1 = dstfs + b'@snap1'
2491        dst2 = dstfs + b'@snap2'
2492
2493        with streams(srcfs, src1, src2) as (_, (full, incr)):
2494            lzc.lzc_receive(dst1, full.fileno())
2495            with zfs_mount(dstfs) as mntdir:
2496                f = tempfile.NamedTemporaryFile(dir=mntdir, delete=False)
2497                for i in range(1024):
2498                    f.write(b'x' * 1024)
2499                lzc.lzc_receive(dst2, incr.fileno(), force=True)
2500                # The temporary file disappears and any access, even close(),
2501                # results in EIO.
2502                self.assertFalse(os.path.exists(f.name))
2503                with self.assertRaises(IOError):
2504                    f.close()
2505
2506    def test_force_recv_incremental_modified_fs_plus_later_snap(self):
2507        srcfs = ZFSTest.pool.makeName(b"fs1")
2508        src1 = srcfs + b"@snap1"
2509        src2 = srcfs + b"@snap2"
2510        dstfs = ZFSTest.pool.makeName(b"fs2/received-61")
2511        dst1 = dstfs + b'@snap1'
2512        dst2 = dstfs + b'@snap2'
2513        dst3 = dstfs + b'@snap'
2514
2515        with streams(srcfs, src1, src2) as (_, (full, incr)):
2516            lzc.lzc_receive(dst1, full.fileno())
2517            with temp_file_in_fs(dstfs):
2518                pass  # enough to taint the fs
2519            lzc.lzc_snapshot([dst3])
2520            lzc.lzc_receive(dst2, incr.fileno(), force=True)
2521        self.assertExists(dst1)
2522        self.assertExists(dst2)
2523        self.assertNotExists(dst3)
2524
2525    def test_force_recv_incremental_modified_fs_plus_same_name_snap(self):
2526        srcfs = ZFSTest.pool.makeName(b"fs1")
2527        src1 = srcfs + b"@snap1"
2528        src2 = srcfs + b"@snap2"
2529        dstfs = ZFSTest.pool.makeName(b"fs2/received-62")
2530        dst1 = dstfs + b'@snap1'
2531        dst2 = dstfs + b'@snap2'
2532
2533        with streams(srcfs, src1, src2) as (_, (full, incr)):
2534            lzc.lzc_receive(dst1, full.fileno())
2535            with temp_file_in_fs(dstfs):
2536                pass  # enough to taint the fs
2537            lzc.lzc_snapshot([dst2])
2538            with self.assertRaises(lzc_exc.DatasetExists):
2539                lzc.lzc_receive(dst2, incr.fileno(), force=True)
2540
2541    def test_force_recv_incremental_modified_fs_plus_held_snap(self):
2542        srcfs = ZFSTest.pool.makeName(b"fs1")
2543        src1 = srcfs + b"@snap1"
2544        src2 = srcfs + b"@snap2"
2545        dstfs = ZFSTest.pool.makeName(b"fs2/received-63")
2546        dst1 = dstfs + b'@snap1'
2547        dst2 = dstfs + b'@snap2'
2548        dst3 = dstfs + b'@snap'
2549
2550        with streams(srcfs, src1, src2) as (_, (full, incr)):
2551            lzc.lzc_receive(dst1, full.fileno())
2552            with temp_file_in_fs(dstfs):
2553                pass  # enough to taint the fs
2554            lzc.lzc_snapshot([dst3])
2555            with cleanup_fd() as cfd:
2556                lzc.lzc_hold({dst3: b'tag'}, cfd)
2557                with self.assertRaises(lzc_exc.DatasetBusy):
2558                    lzc.lzc_receive(dst2, incr.fileno(), force=True)
2559        self.assertExists(dst1)
2560        self.assertNotExists(dst2)
2561        self.assertExists(dst3)
2562
2563    def test_force_recv_incremental_modified_fs_plus_cloned_snap(self):
2564        srcfs = ZFSTest.pool.makeName(b"fs1")
2565        src1 = srcfs + b"@snap1"
2566        src2 = srcfs + b"@snap2"
2567        dstfs = ZFSTest.pool.makeName(b"fs2/received-70")
2568        dst1 = dstfs + b'@snap1'
2569        dst2 = dstfs + b'@snap2'
2570        dst3 = dstfs + b'@snap'
2571        cloned = ZFSTest.pool.makeName(b"fs2/received-cloned-70")
2572
2573        with streams(srcfs, src1, src2) as (_, (full, incr)):
2574            lzc.lzc_receive(dst1, full.fileno())
2575            with temp_file_in_fs(dstfs):
2576                pass  # enough to taint the fs
2577            lzc.lzc_snapshot([dst3])
2578            lzc.lzc_clone(cloned, dst3)
2579            with self.assertRaises(lzc_exc.DatasetExists):
2580                lzc.lzc_receive(dst2, incr.fileno(), force=True)
2581        self.assertExists(dst1)
2582        self.assertNotExists(dst2)
2583        self.assertExists(dst3)
2584
2585    def test_recv_incremental_into_cloned_fs(self):
2586        srcfs = ZFSTest.pool.makeName(b"fs1")
2587        src1 = srcfs + b"@snap1"
2588        src2 = srcfs + b"@snap2"
2589        dstfs = ZFSTest.pool.makeName(b"fs2/received-71")
2590        dst1 = dstfs + b'@snap1'
2591        cloned = ZFSTest.pool.makeName(b"fs2/received-cloned-71")
2592        dst2 = cloned + b'@snap'
2593
2594        with streams(srcfs, src1, src2) as (_, (full, incr)):
2595            lzc.lzc_receive(dst1, full.fileno())
2596            lzc.lzc_clone(cloned, dst1)
2597            # test both graceful and with-force attempts
2598            with self.assertRaises(lzc_exc.StreamMismatch):
2599                lzc.lzc_receive(dst2, incr.fileno())
2600            incr.seek(0)
2601            with self.assertRaises(lzc_exc.StreamMismatch):
2602                lzc.lzc_receive(dst2, incr.fileno(), force=True)
2603        self.assertExists(dst1)
2604        self.assertNotExists(dst2)
2605
2606    def test_recv_with_header_full(self):
2607        src = ZFSTest.pool.makeName(b"fs1@snap")
2608        dst = ZFSTest.pool.makeName(b"fs2/received")
2609
2610        with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")) as name:
2611            lzc.lzc_snapshot([src])
2612
2613        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2614            lzc.lzc_send(src, None, stream.fileno())
2615            stream.seek(0)
2616
2617            (header, c_header) = lzc.receive_header(stream.fileno())
2618            self.assertEqual(src, header['drr_toname'])
2619            snap = header['drr_toname'].split(b'@', 1)[1]
2620            lzc.lzc_receive_with_header(
2621                dst + b'@' + snap, stream.fileno(), c_header)
2622
2623        name = os.path.basename(name)
2624        with zfs_mount(src) as mnt1, zfs_mount(dst) as mnt2:
2625            self.assertTrue(
2626                filecmp.cmp(
2627                    os.path.join(mnt1, name), os.path.join(mnt2, name), False))
2628
2629    def test_recv_fs_below_zvol(self):
2630        send = ZFSTest.pool.makeName(b"fs1@snap")
2631        zvol = ZFSTest.pool.makeName(b"fs1/zvol")
2632        dest = zvol + b"/fs@snap"
2633        props = {b"volsize": 1024 * 1024}
2634
2635        lzc.lzc_snapshot([send])
2636        lzc.lzc_create(zvol, ds_type='zvol', props=props)
2637        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2638            lzc.lzc_send(send, None, stream.fileno())
2639            stream.seek(0)
2640            with self.assertRaises(lzc_exc.WrongParent):
2641                lzc.lzc_receive(dest, stream.fileno())
2642
2643    def test_recv_zvol_over_fs_with_children(self):
2644        parent = ZFSTest.pool.makeName(b"fs1")
2645        child = parent + b"subfs"
2646        zvol = ZFSTest.pool.makeName(b"fs1/zvol")
2647        send = zvol + b"@snap"
2648        props = {b"volsize": 1024 * 1024}
2649
2650        lzc.lzc_create(child)
2651        lzc.lzc_create(zvol, ds_type='zvol', props=props)
2652        lzc.lzc_snapshot([send])
2653        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2654            lzc.lzc_send(send, None, stream.fileno())
2655            stream.seek(0)
2656            with self.assertRaises(lzc_exc.WrongParent):
2657                lzc.lzc_receive(parent + b"@snap", stream.fileno(), force=True)
2658
2659    def test_recv_zvol_overwrite_rootds(self):
2660        zvol = ZFSTest.pool.makeName(b"fs1/zvol")
2661        snap = zvol + b"@snap"
2662        rootds = ZFSTest.pool.getRoot().getName()
2663        props = {b"volsize": 1024 * 1024}
2664
2665        lzc.lzc_create(zvol, ds_type='zvol', props=props)
2666        lzc.lzc_snapshot([snap])
2667        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2668            lzc.lzc_send(snap, None, stream.fileno())
2669            stream.seek(0)
2670            with self.assertRaises(lzc_exc.WrongParent):
2671                lzc.lzc_receive(rootds + b"@snap", stream.fileno(), force=True)
2672
2673    def test_send_full_across_clone_branch_point(self):
2674        origfs = ZFSTest.pool.makeName(b"fs2")
2675
2676        (_, (fromsnap, origsnap, _)) = make_snapshots(
2677            origfs, b"snap1", b"send-origin-20", None)
2678
2679        clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-20")
2680        lzc.lzc_clone(clonefs, origsnap)
2681
2682        (_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
2683
2684        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2685            lzc.lzc_send(tosnap, None, stream.fileno())
2686
2687    def test_send_incr_across_clone_branch_point(self):
2688        origfs = ZFSTest.pool.makeName(b"fs2")
2689
2690        (_, (fromsnap, origsnap, _)) = make_snapshots(
2691            origfs, b"snap1", b"send-origin-21", None)
2692
2693        clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-21")
2694        lzc.lzc_clone(clonefs, origsnap)
2695
2696        (_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
2697
2698        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2699            lzc.lzc_send(tosnap, fromsnap, stream.fileno())
2700
2701    def test_send_resume_token_full(self):
2702        src = ZFSTest.pool.makeName(b"fs1@snap")
2703        dstfs = ZFSTest.pool.getFilesystem(b"fs2/received")
2704        dst = dstfs.getSnap()
2705
2706        with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
2707            for i in range(1, 10):
2708                with tempfile.NamedTemporaryFile(dir=mntdir) as f:
2709                    f.write(b'x' * 1024 * i)
2710                    f.flush()
2711        lzc.lzc_snapshot([src])
2712
2713        with tempfile.NamedTemporaryFile(suffix='.zstream') as stream:
2714            lzc.lzc_send(src, None, stream.fileno())
2715            stream.seek(0)
2716            stream.truncate(1024 * 3)
2717            with self.assertRaises(lzc_exc.StreamTruncated):
2718                lzc.lzc_receive_resumable(dst, stream.fileno())
2719            # Resume token code from zfs_send_resume_token_to_nvlist()
2720            # XXX: if used more than twice move this code into an external func
2721            # format: <version>-<cksum>-<packed-size>-<compressed-payload>
2722            token = dstfs.getProperty("receive_resume_token")
2723            self.assertNotEqual(token, b'-')
2724            tokens = token.split(b'-')
2725            self.assertEqual(len(tokens), 4)
2726            version = tokens[0]
2727            packed_size = int(tokens[2], 16)
2728            compressed_nvs = tokens[3]
2729            # Validate resume token
2730            self.assertEqual(version, b'1')  # ZFS_SEND_RESUME_TOKEN_VERSION
2731            if sys.version_info < (3, 0):
2732                payload = (
2733                    zlib.decompress(str(bytearray.fromhex(compressed_nvs)))
2734                )
2735            else:
2736                payload = (
2737                    zlib.decompress(bytearray.fromhex(compressed_nvs.decode()))
2738                )
2739            self.assertEqual(len(payload), packed_size)
2740            # Unpack
2741            resume_values = packed_nvlist_out(payload, packed_size)
2742            resumeobj = resume_values.get(b'object')
2743            resumeoff = resume_values.get(b'offset')
2744            with tempfile.NamedTemporaryFile(suffix='.zstream') as rstream:
2745                lzc.lzc_send_resume(
2746                    src, None, rstream.fileno(), None, resumeobj, resumeoff)
2747                rstream.seek(0)
2748                lzc.lzc_receive_resumable(dst, rstream.fileno())
2749
2750    def test_send_resume_token_incremental(self):
2751        snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
2752        snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
2753        dstfs = ZFSTest.pool.getFilesystem(b"fs2/received")
2754        dst1 = dstfs.getSnap()
2755        dst2 = dstfs.getSnap()
2756
2757        lzc.lzc_snapshot([snap1])
2758        with tempfile.NamedTemporaryFile(suffix='.zstream') as stream:
2759            lzc.lzc_send(snap1, None, stream.fileno())
2760            stream.seek(0)
2761            lzc.lzc_receive(dst1, stream.fileno())
2762
2763        with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
2764            for i in range(1, 10):
2765                with tempfile.NamedTemporaryFile(dir=mntdir) as f:
2766                    f.write(b'x' * 1024 * i)
2767                    f.flush()
2768        lzc.lzc_snapshot([snap2])
2769
2770        with tempfile.NamedTemporaryFile(suffix='.zstream') as stream:
2771            lzc.lzc_send(snap2, snap1, stream.fileno())
2772            stream.seek(0)
2773            stream.truncate(1024 * 3)
2774            with self.assertRaises(lzc_exc.StreamTruncated):
2775                lzc.lzc_receive_resumable(dst2, stream.fileno())
2776            # Resume token code from zfs_send_resume_token_to_nvlist()
2777            # format: <version>-<cksum>-<packed-size>-<compressed-payload>
2778            token = dstfs.getProperty("receive_resume_token")
2779            self.assertNotEqual(token, '-')
2780            tokens = token.split(b'-')
2781            self.assertEqual(len(tokens), 4)
2782            version = tokens[0]
2783            packed_size = int(tokens[2], 16)
2784            compressed_nvs = tokens[3]
2785            # Validate resume token
2786            self.assertEqual(version, b'1')  # ZFS_SEND_RESUME_TOKEN_VERSION
2787            if sys.version_info < (3, 0):
2788                payload = (
2789                     zlib.decompress(str(bytearray.fromhex(compressed_nvs)))
2790                )
2791            else:
2792                payload = (
2793                    zlib.decompress(bytearray.fromhex(compressed_nvs.decode()))
2794                )
2795            self.assertEqual(len(payload), packed_size)
2796            # Unpack
2797            resume_values = packed_nvlist_out(payload, packed_size)
2798            resumeobj = resume_values.get(b'object')
2799            resumeoff = resume_values.get(b'offset')
2800            with tempfile.NamedTemporaryFile(suffix='.zstream') as rstream:
2801                lzc.lzc_send_resume(
2802                    snap2, snap1, rstream.fileno(), None, resumeobj, resumeoff)
2803                rstream.seek(0)
2804                lzc.lzc_receive_resumable(dst2, rstream.fileno())
2805
2806    def test_recv_full_across_clone_branch_point(self):
2807        origfs = ZFSTest.pool.makeName(b"fs2")
2808
2809        (_, (fromsnap, origsnap, _)) = make_snapshots(
2810            origfs, b"snap1", b"send-origin-30", None)
2811
2812        clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-30")
2813        lzc.lzc_clone(clonefs, origsnap)
2814
2815        (_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
2816
2817        recvfs = ZFSTest.pool.makeName(b"fs1/recv-clone-30")
2818        recvsnap = recvfs + b"@snap"
2819        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2820            lzc.lzc_send(tosnap, None, stream.fileno())
2821            stream.seek(0)
2822            lzc.lzc_receive(recvsnap, stream.fileno())
2823
2824    def test_recv_one(self):
2825        fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
2826        tosnap = ZFSTest.pool.makeName(b"recv@snap1")
2827
2828        lzc.lzc_snapshot([fromsnap])
2829        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2830            lzc.lzc_send(fromsnap, None, stream.fileno())
2831            stream.seek(0)
2832            (header, c_header) = lzc.receive_header(stream.fileno())
2833            lzc.lzc_receive_one(tosnap, stream.fileno(), c_header)
2834
2835    def test_recv_one_size(self):
2836        fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
2837        tosnap = ZFSTest.pool.makeName(b"recv@snap1")
2838
2839        lzc.lzc_snapshot([fromsnap])
2840        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2841            lzc.lzc_send(fromsnap, None, stream.fileno())
2842            size = os.fstat(stream.fileno()).st_size
2843            stream.seek(0)
2844            (header, c_header) = lzc.receive_header(stream.fileno())
2845            (read, _) = lzc.lzc_receive_one(tosnap, stream.fileno(), c_header)
2846            self.assertAlmostEqual(read, size, delta=read * 0.05)
2847
2848    def test_recv_one_props(self):
2849        fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
2850        fs = ZFSTest.pool.getFilesystem(b"recv")
2851        tosnap = fs.getName() + b"@snap1"
2852        props = {
2853            b"compression": 0x01,
2854            b"ns:prop": b"val"
2855        }
2856
2857        lzc.lzc_snapshot([fromsnap])
2858        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2859            lzc.lzc_send(fromsnap, None, stream.fileno())
2860            stream.seek(0)
2861            (header, c_header) = lzc.receive_header(stream.fileno())
2862            lzc.lzc_receive_one(tosnap, stream.fileno(), c_header, props=props)
2863            self.assertExists(tosnap)
2864            self.assertEqual(fs.getProperty("compression", "received"), b"on")
2865            self.assertEqual(fs.getProperty("ns:prop", "received"), b"val")
2866
2867    def test_recv_one_invalid_prop(self):
2868        fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
2869        fs = ZFSTest.pool.getFilesystem(b"recv")
2870        tosnap = fs.getName() + b"@snap1"
2871        props = {
2872            b"exec": 0xff,
2873            b"atime": 0x00
2874        }
2875
2876        lzc.lzc_snapshot([fromsnap])
2877        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2878            lzc.lzc_send(fromsnap, None, stream.fileno())
2879            stream.seek(0)
2880            (header, c_header) = lzc.receive_header(stream.fileno())
2881            with self.assertRaises(lzc_exc.ReceivePropertyFailure) as ctx:
2882                lzc.lzc_receive_one(
2883                    tosnap, stream.fileno(), c_header, props=props)
2884            self.assertExists(tosnap)
2885            self.assertEqual(fs.getProperty("atime", "received"), b"off")
2886            for e in ctx.exception.errors:
2887                self.assertIsInstance(e, lzc_exc.PropertyInvalid)
2888                self.assertEqual(e.name, b"exec")
2889
2890    def test_recv_with_cmdprops(self):
2891        fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
2892        fs = ZFSTest.pool.getFilesystem(b"recv")
2893        tosnap = fs.getName() + b"@snap1"
2894        props = {}
2895        cmdprops = {
2896            b"compression": 0x01,
2897            b"ns:prop": b"val"
2898        }
2899
2900        lzc.lzc_snapshot([fromsnap])
2901        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2902            lzc.lzc_send(fromsnap, None, stream.fileno())
2903            stream.seek(0)
2904            (header, c_header) = lzc.receive_header(stream.fileno())
2905            lzc.lzc_receive_with_cmdprops(
2906                tosnap, stream.fileno(), c_header, props=props,
2907                cmdprops=cmdprops)
2908            self.assertExists(tosnap)
2909            self.assertEqual(fs.getProperty("compression"), b"on")
2910            self.assertEqual(fs.getProperty("ns:prop"), b"val")
2911
2912    def test_recv_with_cmdprops_and_recvprops(self):
2913        fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
2914        fs = ZFSTest.pool.getFilesystem(b"recv")
2915        tosnap = fs.getName() + b"@snap1"
2916        props = {
2917            b"atime": 0x01,
2918            b"exec": 0x00,
2919            b"ns:prop": b"abc"
2920        }
2921        cmdprops = {
2922            b"compression": 0x01,
2923            b"ns:prop": b"def",
2924            b"exec": None,
2925        }
2926
2927        lzc.lzc_snapshot([fromsnap])
2928        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2929            lzc.lzc_send(fromsnap, None, stream.fileno())
2930            stream.seek(0)
2931            (header, c_header) = lzc.receive_header(stream.fileno())
2932            lzc.lzc_receive_with_cmdprops(
2933                tosnap, stream.fileno(), c_header, props=props,
2934                cmdprops=cmdprops)
2935            self.assertExists(tosnap)
2936            self.assertEqual(fs.getProperty("atime", True), b"on")
2937            self.assertEqual(fs.getProperty("exec", True), b"off")
2938            self.assertEqual(fs.getProperty("ns:prop", True), b"abc")
2939            self.assertEqual(fs.getProperty("compression"), b"on")
2940            self.assertEqual(fs.getProperty("ns:prop"), b"def")
2941            self.assertEqual(fs.getProperty("exec"), b"on")
2942
2943    def test_recv_incr_across_clone_branch_point_no_origin(self):
2944        origfs = ZFSTest.pool.makeName(b"fs2")
2945
2946        (_, (fromsnap, origsnap, _)) = make_snapshots(
2947            origfs, b"snap1", b"send-origin-32", None)
2948
2949        clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-32")
2950        lzc.lzc_clone(clonefs, origsnap)
2951
2952        (_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
2953
2954        recvfs = ZFSTest.pool.makeName(b"fs1/recv-clone-32")
2955        recvsnap1 = recvfs + b"@snap1"
2956        recvsnap2 = recvfs + b"@snap2"
2957        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2958            lzc.lzc_send(fromsnap, None, stream.fileno())
2959            stream.seek(0)
2960            lzc.lzc_receive(recvsnap1, stream.fileno())
2961        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2962            lzc.lzc_send(tosnap, fromsnap, stream.fileno())
2963            stream.seek(0)
2964            with self.assertRaises(lzc_exc.BadStream):
2965                lzc.lzc_receive(recvsnap2, stream.fileno())
2966
2967    def test_recv_incr_across_clone_branch_point(self):
2968        origfs = ZFSTest.pool.makeName(b"fs2")
2969
2970        (_, (fromsnap, origsnap, _)) = make_snapshots(
2971            origfs, b"snap1", b"send-origin-31", None)
2972
2973        clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-31")
2974        lzc.lzc_clone(clonefs, origsnap)
2975
2976        (_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
2977
2978        recvfs = ZFSTest.pool.makeName(b"fs1/recv-clone-31")
2979        recvsnap1 = recvfs + b"@snap1"
2980        recvsnap2 = recvfs + b"@snap2"
2981        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2982            lzc.lzc_send(fromsnap, None, stream.fileno())
2983            stream.seek(0)
2984            lzc.lzc_receive(recvsnap1, stream.fileno())
2985        with tempfile.TemporaryFile(suffix='.zstream') as stream:
2986            lzc.lzc_send(tosnap, fromsnap, stream.fileno())
2987            stream.seek(0)
2988            with self.assertRaises(lzc_exc.BadStream):
2989                lzc.lzc_receive(recvsnap2, stream.fileno(), origin=recvsnap1)
2990
2991    def test_recv_incr_across_clone_branch_point_new_fs(self):
2992        origfs = ZFSTest.pool.makeName(b"fs2")
2993
2994        (_, (fromsnap, origsnap, _)) = make_snapshots(
2995            origfs, b"snap1", b"send-origin-33", None)
2996
2997        clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-33")
2998        lzc.lzc_clone(clonefs, origsnap)
2999
3000        (_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
3001
3002        recvfs1 = ZFSTest.pool.makeName(b"fs1/recv-clone-33")
3003        recvsnap1 = recvfs1 + b"@snap"
3004        recvfs2 = ZFSTest.pool.makeName(b"fs1/recv-clone-33_2")
3005        recvsnap2 = recvfs2 + b"@snap"
3006        with tempfile.TemporaryFile(suffix='.zstream') as stream:
3007            lzc.lzc_send(fromsnap, None, stream.fileno())
3008            stream.seek(0)
3009            lzc.lzc_receive(recvsnap1, stream.fileno())
3010        with tempfile.TemporaryFile(suffix='.zstream') as stream:
3011            lzc.lzc_send(tosnap, fromsnap, stream.fileno())
3012            stream.seek(0)
3013            lzc.lzc_receive(recvsnap2, stream.fileno(), origin=recvsnap1)
3014
3015    def test_recv_bad_stream(self):
3016        dstfs = ZFSTest.pool.makeName(b"fs2/received")
3017        dst_snap = dstfs + b'@snap'
3018
3019        with dev_zero() as fd:
3020            with self.assertRaises(lzc_exc.BadStream):
3021                lzc.lzc_receive(dst_snap, fd)
3022
3023    @needs_support(lzc.lzc_promote)
3024    def test_promote(self):
3025        origfs = ZFSTest.pool.makeName(b"fs2")
3026        snap = b"@promote-snap-1"
3027        origsnap = origfs + snap
3028        lzc.lzc_snap([origsnap])
3029
3030        clonefs = ZFSTest.pool.makeName(b"fs1/fs/promote-clone-1")
3031        lzc.lzc_clone(clonefs, origsnap)
3032
3033        lzc.lzc_promote(clonefs)
3034        # the snapshot now should belong to the promoted fs
3035        self.assertExists(clonefs + snap)
3036
3037    @needs_support(lzc.lzc_promote)
3038    def test_promote_too_long_snapname(self):
3039        # origfs name must be shorter than clonefs name
3040        origfs = ZFSTest.pool.makeName(b"fs2")
3041        clonefs = ZFSTest.pool.makeName(b"fs1/fs/promote-clone-2")
3042        snapprefix = b"@promote-snap-2-"
3043        pad_len = 1 + lzc.MAXNAMELEN - len(clonefs) - len(snapprefix)
3044        snap = snapprefix + b'x' * pad_len
3045        origsnap = origfs + snap
3046
3047        lzc.lzc_snap([origsnap])
3048        lzc.lzc_clone(clonefs, origsnap)
3049
3050        # This may fail on older buggy systems.
3051        # See: https://www.illumos.org/issues/5909
3052        with self.assertRaises(lzc_exc.NameTooLong):
3053            lzc.lzc_promote(clonefs)
3054
3055    @needs_support(lzc.lzc_promote)
3056    def test_promote_not_cloned(self):
3057        fs = ZFSTest.pool.makeName(b"fs2")
3058        with self.assertRaises(lzc_exc.NotClone):
3059            lzc.lzc_promote(fs)
3060
3061    @unittest.skipIf(*illumos_bug_6379())
3062    def test_hold_bad_fd(self):
3063        snap = ZFSTest.pool.getRoot().getSnap()
3064        lzc.lzc_snapshot([snap])
3065
3066        with tempfile.TemporaryFile() as tmp:
3067            bad_fd = tmp.fileno()
3068
3069        with self.assertRaises(lzc_exc.BadHoldCleanupFD):
3070            lzc.lzc_hold({snap: b'tag'}, bad_fd)
3071
3072    @unittest.skipIf(*illumos_bug_6379())
3073    def test_hold_bad_fd_2(self):
3074        snap = ZFSTest.pool.getRoot().getSnap()
3075        lzc.lzc_snapshot([snap])
3076
3077        with self.assertRaises(lzc_exc.BadHoldCleanupFD):
3078            lzc.lzc_hold({snap: b'tag'}, -2)
3079
3080    @unittest.skipIf(*illumos_bug_6379())
3081    def test_hold_bad_fd_3(self):
3082        snap = ZFSTest.pool.getRoot().getSnap()
3083        lzc.lzc_snapshot([snap])
3084
3085        (soft, hard) = resource.getrlimit(resource.RLIMIT_NOFILE)
3086        bad_fd = hard + 1
3087        with self.assertRaises(lzc_exc.BadHoldCleanupFD):
3088            lzc.lzc_hold({snap: b'tag'}, bad_fd)
3089
3090    @unittest.skipIf(*illumos_bug_6379())
3091    def test_hold_wrong_fd(self):
3092        snap = ZFSTest.pool.getRoot().getSnap()
3093        lzc.lzc_snapshot([snap])
3094
3095        with tempfile.TemporaryFile() as tmp:
3096            fd = tmp.fileno()
3097            with self.assertRaises(lzc_exc.BadHoldCleanupFD):
3098                lzc.lzc_hold({snap: b'tag'}, fd)
3099
3100    def test_hold_fd(self):
3101        snap = ZFSTest.pool.getRoot().getSnap()
3102        lzc.lzc_snapshot([snap])
3103
3104        with cleanup_fd() as fd:
3105            lzc.lzc_hold({snap: b'tag'}, fd)
3106
3107    def test_hold_empty(self):
3108        with cleanup_fd() as fd:
3109            lzc.lzc_hold({}, fd)
3110
3111    def test_hold_empty_2(self):
3112        lzc.lzc_hold({})
3113
3114    def test_hold_vs_snap_destroy(self):
3115        snap = ZFSTest.pool.getRoot().getSnap()
3116        lzc.lzc_snapshot([snap])
3117
3118        with cleanup_fd() as fd:
3119            lzc.lzc_hold({snap: b'tag'}, fd)
3120
3121            with self.assertRaises(lzc_exc.SnapshotDestructionFailure) as ctx:
3122                lzc.lzc_destroy_snaps([snap], defer=False)
3123            for e in ctx.exception.errors:
3124                self.assertIsInstance(e, lzc_exc.SnapshotIsHeld)
3125
3126            lzc.lzc_destroy_snaps([snap], defer=True)
3127            self.assertExists(snap)
3128
3129        # after automatic hold cleanup and deferred destruction
3130        self.assertNotExists(snap)
3131
3132    def test_hold_many_tags(self):
3133        snap = ZFSTest.pool.getRoot().getSnap()
3134        lzc.lzc_snapshot([snap])
3135
3136        with cleanup_fd() as fd:
3137            lzc.lzc_hold({snap: b'tag1'}, fd)
3138            lzc.lzc_hold({snap: b'tag2'}, fd)
3139
3140    def test_hold_many_snaps(self):
3141        snap1 = ZFSTest.pool.getRoot().getSnap()
3142        snap2 = ZFSTest.pool.getRoot().getSnap()
3143        lzc.lzc_snapshot([snap1])
3144        lzc.lzc_snapshot([snap2])
3145
3146        with cleanup_fd() as fd:
3147            lzc.lzc_hold({snap1: b'tag', snap2: b'tag'}, fd)
3148
3149    def test_hold_many_with_one_missing(self):
3150        snap1 = ZFSTest.pool.getRoot().getSnap()
3151        snap2 = ZFSTest.pool.getRoot().getSnap()
3152        lzc.lzc_snapshot([snap1])
3153
3154        with cleanup_fd() as fd:
3155            missing = lzc.lzc_hold({snap1: b'tag', snap2: b'tag'}, fd)
3156        self.assertEqual(len(missing), 1)
3157        self.assertEqual(missing[0], snap2)
3158
3159    def test_hold_many_with_all_missing(self):
3160        snap1 = ZFSTest.pool.getRoot().getSnap()
3161        snap2 = ZFSTest.pool.getRoot().getSnap()
3162
3163        with cleanup_fd() as fd:
3164            missing = lzc.lzc_hold({snap1: b'tag', snap2: b'tag'}, fd)
3165        self.assertEqual(len(missing), 2)
3166        self.assertEqual(sorted(missing), sorted([snap1, snap2]))
3167
3168    def test_hold_missing_fs(self):
3169        # XXX skip pre-created filesystems
3170        ZFSTest.pool.getRoot().getFilesystem()
3171        ZFSTest.pool.getRoot().getFilesystem()
3172        ZFSTest.pool.getRoot().getFilesystem()
3173        ZFSTest.pool.getRoot().getFilesystem()
3174        ZFSTest.pool.getRoot().getFilesystem()
3175        snap = ZFSTest.pool.getRoot().getFilesystem().getSnap()
3176
3177        snaps = lzc.lzc_hold({snap: b'tag'})
3178        self.assertEqual([snap], snaps)
3179
3180    def test_hold_missing_fs_auto_cleanup(self):
3181        # XXX skip pre-created filesystems
3182        ZFSTest.pool.getRoot().getFilesystem()
3183        ZFSTest.pool.getRoot().getFilesystem()
3184        ZFSTest.pool.getRoot().getFilesystem()
3185        ZFSTest.pool.getRoot().getFilesystem()
3186        ZFSTest.pool.getRoot().getFilesystem()
3187        snap = ZFSTest.pool.getRoot().getFilesystem().getSnap()
3188
3189        with cleanup_fd() as fd:
3190            snaps = lzc.lzc_hold({snap: b'tag'}, fd)
3191            self.assertEqual([snap], snaps)
3192
3193    def test_hold_duplicate(self):
3194        snap = ZFSTest.pool.getRoot().getSnap()
3195        lzc.lzc_snapshot([snap])
3196
3197        with cleanup_fd() as fd:
3198            lzc.lzc_hold({snap: b'tag'}, fd)
3199            with self.assertRaises(lzc_exc.HoldFailure) as ctx:
3200                lzc.lzc_hold({snap: b'tag'}, fd)
3201        for e in ctx.exception.errors:
3202            self.assertIsInstance(e, lzc_exc.HoldExists)
3203
3204    def test_hold_across_pools(self):
3205        snap1 = ZFSTest.pool.getRoot().getSnap()
3206        snap2 = ZFSTest.misc_pool.getRoot().getSnap()
3207        lzc.lzc_snapshot([snap1])
3208        lzc.lzc_snapshot([snap2])
3209
3210        with cleanup_fd() as fd:
3211            with self.assertRaises(lzc_exc.HoldFailure) as ctx:
3212                lzc.lzc_hold({snap1: b'tag', snap2: b'tag'}, fd)
3213        for e in ctx.exception.errors:
3214            self.assertIsInstance(e, lzc_exc.PoolsDiffer)
3215
3216    def test_hold_too_long_tag(self):
3217        snap = ZFSTest.pool.getRoot().getSnap()
3218        tag = b't' * 256
3219        lzc.lzc_snapshot([snap])
3220
3221        with cleanup_fd() as fd:
3222            with self.assertRaises(lzc_exc.HoldFailure) as ctx:
3223                lzc.lzc_hold({snap: tag}, fd)
3224        for e in ctx.exception.errors:
3225            self.assertIsInstance(e, lzc_exc.NameTooLong)
3226            self.assertEqual(e.name, tag)
3227
3228    # Apparently the full snapshot name is not checked for length
3229    # and this snapshot is treated as simply missing.
3230    @unittest.expectedFailure
3231    def test_hold_too_long_snap_name(self):
3232        snap = ZFSTest.pool.getRoot().getTooLongSnap(False)
3233        with cleanup_fd() as fd:
3234            with self.assertRaises(lzc_exc.HoldFailure) as ctx:
3235                lzc.lzc_hold({snap: b'tag'}, fd)
3236        for e in ctx.exception.errors:
3237            self.assertIsInstance(e, lzc_exc.NameTooLong)
3238            self.assertEqual(e.name, snap)
3239
3240    def test_hold_too_long_snap_name_2(self):
3241        snap = ZFSTest.pool.getRoot().getTooLongSnap(True)
3242        with cleanup_fd() as fd:
3243            with self.assertRaises(lzc_exc.HoldFailure) as ctx:
3244                lzc.lzc_hold({snap: b'tag'}, fd)
3245        for e in ctx.exception.errors:
3246            self.assertIsInstance(e, lzc_exc.NameTooLong)
3247            self.assertEqual(e.name, snap)
3248
3249    def test_hold_invalid_snap_name(self):
3250        snap = ZFSTest.pool.getRoot().getSnap() + b'@bad'
3251        with cleanup_fd() as fd:
3252            with self.assertRaises(lzc_exc.HoldFailure) as ctx:
3253                lzc.lzc_hold({snap: b'tag'}, fd)
3254        for e in ctx.exception.errors:
3255            self.assertIsInstance(e, lzc_exc.NameInvalid)
3256            self.assertEqual(e.name, snap)
3257
3258    def test_hold_invalid_snap_name_2(self):
3259        snap = ZFSTest.pool.getRoot().getFilesystem().getName()
3260        with cleanup_fd() as fd:
3261            with self.assertRaises(lzc_exc.HoldFailure) as ctx:
3262                lzc.lzc_hold({snap: b'tag'}, fd)
3263        for e in ctx.exception.errors:
3264            self.assertIsInstance(e, lzc_exc.NameInvalid)
3265            self.assertEqual(e.name, snap)
3266
3267    def test_get_holds(self):
3268        snap = ZFSTest.pool.getRoot().getSnap()
3269        lzc.lzc_snapshot([snap])
3270
3271        with cleanup_fd() as fd:
3272            lzc.lzc_hold({snap: b'tag1'}, fd)
3273            lzc.lzc_hold({snap: b'tag2'}, fd)
3274
3275            holds = lzc.lzc_get_holds(snap)
3276            self.assertEqual(len(holds), 2)
3277            self.assertIn(b'tag1', holds)
3278            self.assertIn(b'tag2', holds)
3279            self.assertIsInstance(holds[b'tag1'], (int, int))
3280
3281    def test_get_holds_after_auto_cleanup(self):
3282        snap = ZFSTest.pool.getRoot().getSnap()
3283        lzc.lzc_snapshot([snap])
3284
3285        with cleanup_fd() as fd:
3286            lzc.lzc_hold({snap: b'tag1'}, fd)
3287            lzc.lzc_hold({snap: b'tag2'}, fd)
3288
3289        holds = lzc.lzc_get_holds(snap)
3290        self.assertEqual(len(holds), 0)
3291        self.assertIsInstance(holds, dict)
3292
3293    def test_get_holds_nonexistent_snap(self):
3294        snap = ZFSTest.pool.getRoot().getSnap()
3295        with self.assertRaises(lzc_exc.SnapshotNotFound):
3296            lzc.lzc_get_holds(snap)
3297
3298    def test_get_holds_too_long_snap_name(self):
3299        snap = ZFSTest.pool.getRoot().getTooLongSnap(False)
3300        with self.assertRaises(lzc_exc.NameTooLong):
3301            lzc.lzc_get_holds(snap)
3302
3303    def test_get_holds_too_long_snap_name_2(self):
3304        snap = ZFSTest.pool.getRoot().getTooLongSnap(True)
3305        with self.assertRaises(lzc_exc.NameTooLong):
3306            lzc.lzc_get_holds(snap)
3307
3308    def test_get_holds_invalid_snap_name(self):
3309        snap = ZFSTest.pool.getRoot().getSnap() + b'@bad'
3310        with self.assertRaises(lzc_exc.NameInvalid):
3311            lzc.lzc_get_holds(snap)
3312
3313    # A filesystem-like snapshot name is not recognized as
3314    # an invalid name.
3315    @unittest.expectedFailure
3316    def test_get_holds_invalid_snap_name_2(self):
3317        snap = ZFSTest.pool.getRoot().getFilesystem().getName()
3318        with self.assertRaises(lzc_exc.NameInvalid):
3319            lzc.lzc_get_holds(snap)
3320
3321    def test_release_hold(self):
3322        snap = ZFSTest.pool.getRoot().getSnap()
3323        lzc.lzc_snapshot([snap])
3324
3325        lzc.lzc_hold({snap: b'tag'})
3326        ret = lzc.lzc_release({snap: [b'tag']})
3327        self.assertEqual(len(ret), 0)
3328
3329    def test_release_hold_empty(self):
3330        ret = lzc.lzc_release({})
3331        self.assertEqual(len(ret), 0)
3332
3333    def test_release_hold_complex(self):
3334        snap1 = ZFSTest.pool.getRoot().getSnap()
3335        snap2 = ZFSTest.pool.getRoot().getSnap()
3336        snap3 = ZFSTest.pool.getRoot().getFilesystem().getSnap()
3337        lzc.lzc_snapshot([snap1])
3338        lzc.lzc_snapshot([snap2, snap3])
3339
3340        lzc.lzc_hold({snap1: b'tag1'})
3341        lzc.lzc_hold({snap1: b'tag2'})
3342        lzc.lzc_hold({snap2: b'tag'})
3343        lzc.lzc_hold({snap3: b'tag1'})
3344        lzc.lzc_hold({snap3: b'tag2'})
3345
3346        holds = lzc.lzc_get_holds(snap1)
3347        self.assertEqual(len(holds), 2)
3348        holds = lzc.lzc_get_holds(snap2)
3349        self.assertEqual(len(holds), 1)
3350        holds = lzc.lzc_get_holds(snap3)
3351        self.assertEqual(len(holds), 2)
3352
3353        release = {
3354            snap1: [b'tag1', b'tag2'],
3355            snap2: [b'tag'],
3356            snap3: [b'tag2'],
3357        }
3358        ret = lzc.lzc_release(release)
3359        self.assertEqual(len(ret), 0)
3360
3361        holds = lzc.lzc_get_holds(snap1)
3362        self.assertEqual(len(holds), 0)
3363        holds = lzc.lzc_get_holds(snap2)
3364        self.assertEqual(len(holds), 0)
3365        holds = lzc.lzc_get_holds(snap3)
3366        self.assertEqual(len(holds), 1)
3367
3368        ret = lzc.lzc_release({snap3: [b'tag1']})
3369        self.assertEqual(len(ret), 0)
3370        holds = lzc.lzc_get_holds(snap3)
3371        self.assertEqual(len(holds), 0)
3372
3373    def test_release_hold_before_auto_cleanup(self):
3374        snap = ZFSTest.pool.getRoot().getSnap()
3375        lzc.lzc_snapshot([snap])
3376
3377        with cleanup_fd() as fd:
3378            lzc.lzc_hold({snap: b'tag'}, fd)
3379            ret = lzc.lzc_release({snap: [b'tag']})
3380            self.assertEqual(len(ret), 0)
3381
3382    def test_release_hold_and_snap_destruction(self):
3383        snap = ZFSTest.pool.getRoot().getSnap()
3384        lzc.lzc_snapshot([snap])
3385
3386        with cleanup_fd() as fd:
3387            lzc.lzc_hold({snap: b'tag1'}, fd)
3388            lzc.lzc_hold({snap: b'tag2'}, fd)
3389
3390            lzc.lzc_destroy_snaps([snap], defer=True)
3391            self.assertExists(snap)
3392
3393            lzc.lzc_release({snap: [b'tag1']})
3394            self.assertExists(snap)
3395
3396            lzc.lzc_release({snap: [b'tag2']})
3397            self.assertNotExists(snap)
3398
3399    def test_release_hold_and_multiple_snap_destruction(self):
3400        snap = ZFSTest.pool.getRoot().getSnap()
3401        lzc.lzc_snapshot([snap])
3402
3403        with cleanup_fd() as fd:
3404            lzc.lzc_hold({snap: b'tag'}, fd)
3405
3406            lzc.lzc_destroy_snaps([snap], defer=True)
3407            self.assertExists(snap)
3408
3409            lzc.lzc_destroy_snaps([snap], defer=True)
3410            self.assertExists(snap)
3411
3412            lzc.lzc_release({snap: [b'tag']})
3413            self.assertNotExists(snap)
3414
3415    def test_release_hold_missing_tag(self):
3416        snap = ZFSTest.pool.getRoot().getSnap()
3417        lzc.lzc_snapshot([snap])
3418
3419        ret = lzc.lzc_release({snap: [b'tag']})
3420        self.assertEqual(len(ret), 1)
3421        self.assertEqual(ret[0], snap + b'#tag')
3422
3423    def test_release_hold_missing_snap(self):
3424        snap = ZFSTest.pool.getRoot().getSnap()
3425
3426        ret = lzc.lzc_release({snap: [b'tag']})
3427        self.assertEqual(len(ret), 1)
3428        self.assertEqual(ret[0], snap)
3429
3430    def test_release_hold_missing_snap_2(self):
3431        snap = ZFSTest.pool.getRoot().getSnap()
3432
3433        ret = lzc.lzc_release({snap: [b'tag', b'another']})
3434        self.assertEqual(len(ret), 1)
3435        self.assertEqual(ret[0], snap)
3436
3437    def test_release_hold_across_pools(self):
3438        snap1 = ZFSTest.pool.getRoot().getSnap()
3439        snap2 = ZFSTest.misc_pool.getRoot().getSnap()
3440        lzc.lzc_snapshot([snap1])
3441        lzc.lzc_snapshot([snap2])
3442
3443        with cleanup_fd() as fd:
3444            lzc.lzc_hold({snap1: b'tag'}, fd)
3445            lzc.lzc_hold({snap2: b'tag'}, fd)
3446            with self.assertRaises(lzc_exc.HoldReleaseFailure) as ctx:
3447                lzc.lzc_release({snap1: [b'tag'], snap2: [b'tag']})
3448        for e in ctx.exception.errors:
3449            self.assertIsInstance(e, lzc_exc.PoolsDiffer)
3450
3451    # Apparently the tag name is not verified,
3452    # only its existence is checked.
3453    @unittest.expectedFailure
3454    def test_release_hold_too_long_tag(self):
3455        snap = ZFSTest.pool.getRoot().getSnap()
3456        tag = b't' * 256
3457        lzc.lzc_snapshot([snap])
3458
3459        with self.assertRaises(lzc_exc.HoldReleaseFailure):
3460            lzc.lzc_release({snap: [tag]})
3461
3462    # Apparently the full snapshot name is not checked for length
3463    # and this snapshot is treated as simply missing.
3464    @unittest.expectedFailure
3465    def test_release_hold_too_long_snap_name(self):
3466        snap = ZFSTest.pool.getRoot().getTooLongSnap(False)
3467
3468        with self.assertRaises(lzc_exc.HoldReleaseFailure):
3469            lzc.lzc_release({snap: [b'tag']})
3470
3471    def test_release_hold_too_long_snap_name_2(self):
3472        snap = ZFSTest.pool.getRoot().getTooLongSnap(True)
3473        with self.assertRaises(lzc_exc.HoldReleaseFailure) as ctx:
3474            lzc.lzc_release({snap: [b'tag']})
3475        for e in ctx.exception.errors:
3476            self.assertIsInstance(e, lzc_exc.NameTooLong)
3477            self.assertEqual(e.name, snap)
3478
3479    def test_release_hold_invalid_snap_name(self):
3480        snap = ZFSTest.pool.getRoot().getSnap() + b'@bad'
3481        with self.assertRaises(lzc_exc.HoldReleaseFailure) as ctx:
3482            lzc.lzc_release({snap: [b'tag']})
3483        for e in ctx.exception.errors:
3484            self.assertIsInstance(e, lzc_exc.NameInvalid)
3485            self.assertEqual(e.name, snap)
3486
3487    def test_release_hold_invalid_snap_name_2(self):
3488        snap = ZFSTest.pool.getRoot().getFilesystem().getName()
3489        with self.assertRaises(lzc_exc.HoldReleaseFailure) as ctx:
3490            lzc.lzc_release({snap: [b'tag']})
3491        for e in ctx.exception.errors:
3492            self.assertIsInstance(e, lzc_exc.NameInvalid)
3493            self.assertEqual(e.name, snap)
3494
3495    def test_sync_missing_pool(self):
3496        pool = b"nonexistent"
3497        with self.assertRaises(lzc_exc.PoolNotFound):
3498            lzc.lzc_sync(pool)
3499
3500    def test_sync_pool_forced(self):
3501        pool = ZFSTest.pool.getRoot().getName()
3502        lzc.lzc_sync(pool, True)
3503
3504    def test_reopen_missing_pool(self):
3505        pool = b"nonexistent"
3506        with self.assertRaises(lzc_exc.PoolNotFound):
3507            lzc.lzc_reopen(pool)
3508
3509    def test_reopen_pool_no_restart(self):
3510        pool = ZFSTest.pool.getRoot().getName()
3511        lzc.lzc_reopen(pool, False)
3512
3513    def test_channel_program_missing_pool(self):
3514        pool = b"nonexistent"
3515        with self.assertRaises(lzc_exc.PoolNotFound):
3516            lzc.lzc_channel_program(pool, b"return {}")
3517
3518    def test_channel_program_timeout(self):
3519        pool = ZFSTest.pool.getRoot().getName()
3520        zcp = b"""
3521for i = 1,10000 do
3522    zfs.sync.snapshot('""" + pool + b"""@zcp' .. i)
3523end
3524"""
3525        with self.assertRaises(lzc_exc.ZCPTimeout):
3526            lzc.lzc_channel_program(pool, zcp, instrlimit=1)
3527
3528    def test_channel_program_memory_limit(self):
3529        pool = ZFSTest.pool.getRoot().getName()
3530        zcp = b"""
3531for i = 1,10000 do
3532    zfs.sync.snapshot('""" + pool + b"""@zcp' .. i)
3533end
3534"""
3535        with self.assertRaises(lzc_exc.ZCPSpaceError):
3536            lzc.lzc_channel_program(pool, zcp, memlimit=1)
3537
3538    def test_channel_program_invalid_limits(self):
3539        pool = ZFSTest.pool.getRoot().getName()
3540        zcp = b"""
3541return {}
3542"""
3543        with self.assertRaises(lzc_exc.ZCPLimitInvalid):
3544            lzc.lzc_channel_program(pool, zcp, instrlimit=0)
3545        with self.assertRaises(lzc_exc.ZCPLimitInvalid):
3546            lzc.lzc_channel_program(pool, zcp, memlimit=0)
3547
3548    def test_channel_program_syntax_error(self):
3549        pool = ZFSTest.pool.getRoot().getName()
3550        zcp = b"""
3551inv+val:id
3552"""
3553        with self.assertRaises(lzc_exc.ZCPSyntaxError) as ctx:
3554            lzc.lzc_channel_program(pool, zcp)
3555        self.assertTrue(b"syntax error" in ctx.exception.details)
3556
3557    def test_channel_program_sync_snapshot(self):
3558        pool = ZFSTest.pool.getRoot().getName()
3559        snapname = ZFSTest.pool.makeName(b"@zcp")
3560        zcp = b"""
3561zfs.sync.snapshot('""" + snapname + b"""')
3562"""
3563        lzc.lzc_channel_program(pool, zcp)
3564        self.assertExists(snapname)
3565
3566    def test_channel_program_runtime_error(self):
3567        pool = ZFSTest.pool.getRoot().getName()
3568
3569        # failing an assertion raises a runtime error
3570        with self.assertRaises(lzc_exc.ZCPRuntimeError) as ctx:
3571            lzc.lzc_channel_program(pool, b"assert(1 == 2)")
3572        self.assertTrue(
3573            b"assertion failed" in ctx.exception.details)
3574        # invoking the error() function raises a runtime error
3575        with self.assertRaises(lzc_exc.ZCPRuntimeError) as ctx:
3576            lzc.lzc_channel_program(pool, b"error()")
3577
3578    def test_channel_program_nosync_runtime_error(self):
3579        pool = ZFSTest.pool.getRoot().getName()
3580        zcp = b"""
3581zfs.sync.snapshot('""" + pool + b"""@zcp')
3582"""
3583        # lzc_channel_program_nosync() allows only "read-only" operations
3584        with self.assertRaises(lzc_exc.ZCPRuntimeError) as ctx:
3585            lzc.lzc_channel_program_nosync(pool, zcp)
3586        self.assertTrue(
3587            b"running functions from the zfs.sync" in ctx.exception.details)
3588
3589    def test_change_key_new(self):
3590        with encrypted_filesystem() as (fs, _):
3591            lzc.lzc_change_key(
3592                fs, 'new_key',
3593                props={b"keyformat": lzc.zfs_keyformat.ZFS_KEYFORMAT_RAW},
3594                key=os.urandom(lzc.WRAPPING_KEY_LEN))
3595
3596    def test_change_key_missing_fs(self):
3597        name = b"nonexistent"
3598
3599        with self.assertRaises(lzc_exc.FilesystemNotFound):
3600            lzc.lzc_change_key(
3601                name, 'new_key',
3602                props={b"keyformat": lzc.zfs_keyformat.ZFS_KEYFORMAT_RAW},
3603                key=os.urandom(lzc.WRAPPING_KEY_LEN))
3604
3605    def test_change_key_not_loaded(self):
3606        with encrypted_filesystem() as (fs, _):
3607            lzc.lzc_unload_key(fs)
3608            with self.assertRaises(lzc_exc.EncryptionKeyNotLoaded):
3609                lzc.lzc_change_key(
3610                    fs, 'new_key',
3611                    props={b"keyformat": lzc.zfs_keyformat.ZFS_KEYFORMAT_RAW},
3612                    key=os.urandom(lzc.WRAPPING_KEY_LEN))
3613
3614    def test_change_key_invalid_property(self):
3615        with encrypted_filesystem() as (fs, _):
3616            with self.assertRaises(lzc_exc.PropertyInvalid):
3617                lzc.lzc_change_key(fs, 'new_key', props={b"invalid": b"prop"})
3618
3619    def test_change_key_invalid_crypt_command(self):
3620        with encrypted_filesystem() as (fs, _):
3621            with self.assertRaises(lzc_exc.UnknownCryptCommand):
3622                lzc.lzc_change_key(fs, 'duplicate_key')
3623
3624    def test_load_key(self):
3625        with encrypted_filesystem() as (fs, key):
3626            lzc.lzc_unload_key(fs)
3627            lzc.lzc_load_key(fs, False, key)
3628
3629    def test_load_key_invalid(self):
3630        with encrypted_filesystem() as (fs, key):
3631            lzc.lzc_unload_key(fs)
3632            with self.assertRaises(lzc_exc.EncryptionKeyInvalid):
3633                lzc.lzc_load_key(fs, False, os.urandom(lzc.WRAPPING_KEY_LEN))
3634
3635    def test_load_key_already_loaded(self):
3636        with encrypted_filesystem() as (fs, key):
3637            lzc.lzc_unload_key(fs)
3638            lzc.lzc_load_key(fs, False, key)
3639            with self.assertRaises(lzc_exc.EncryptionKeyAlreadyLoaded):
3640                lzc.lzc_load_key(fs, False, key)
3641
3642    def test_load_key_missing_fs(self):
3643        name = b"nonexistent"
3644
3645        with self.assertRaises(lzc_exc.FilesystemNotFound):
3646            lzc.lzc_load_key(name, False, key=os.urandom(lzc.WRAPPING_KEY_LEN))
3647
3648    def test_unload_key(self):
3649        with encrypted_filesystem() as (fs, _):
3650            lzc.lzc_unload_key(fs)
3651
3652    def test_unload_key_missing_fs(self):
3653        name = b"nonexistent"
3654
3655        with self.assertRaises(lzc_exc.FilesystemNotFound):
3656            lzc.lzc_unload_key(name)
3657
3658    def test_unload_key_busy(self):
3659        with encrypted_filesystem() as (fs, _):
3660            with zfs_mount(fs):
3661                with self.assertRaises(lzc_exc.DatasetBusy):
3662                    lzc.lzc_unload_key(fs)
3663
3664    def test_unload_key_not_loaded(self):
3665        with encrypted_filesystem() as (fs, _):
3666            lzc.lzc_unload_key(fs)
3667            with self.assertRaises(lzc_exc.EncryptionKeyNotLoaded):
3668                lzc.lzc_unload_key(fs)
3669
3670    def test_checkpoint(self):
3671        pool = ZFSTest.pool.getRoot().getName()
3672
3673        lzc.lzc_pool_checkpoint(pool)
3674
3675    def test_checkpoint_missing_pool(self):
3676        pool = b"nonexistent"
3677
3678        with self.assertRaises(lzc_exc.PoolNotFound):
3679            lzc.lzc_pool_checkpoint(pool)
3680
3681    def test_checkpoint_already_exists(self):
3682        pool = ZFSTest.pool.getRoot().getName()
3683
3684        lzc.lzc_pool_checkpoint(pool)
3685        with self.assertRaises(lzc_exc.CheckpointExists):
3686            lzc.lzc_pool_checkpoint(pool)
3687
3688    def test_checkpoint_discard(self):
3689        pool = ZFSTest.pool.getRoot().getName()
3690
3691        lzc.lzc_pool_checkpoint(pool)
3692        lzc.lzc_pool_checkpoint_discard(pool)
3693
3694    def test_checkpoint_discard_missing_pool(self):
3695        pool = b"nonexistent"
3696
3697        with self.assertRaises(lzc_exc.PoolNotFound):
3698            lzc.lzc_pool_checkpoint_discard(pool)
3699
3700    def test_checkpoint_discard_missing_checkpoint(self):
3701        pool = ZFSTest.pool.getRoot().getName()
3702
3703        with self.assertRaises(lzc_exc.CheckpointNotFound):
3704            lzc.lzc_pool_checkpoint_discard(pool)
3705
3706    @needs_support(lzc.lzc_list_children)
3707    def test_list_children(self):
3708        name = ZFSTest.pool.makeName(b"fs1/fs")
3709        names = [ZFSTest.pool.makeName(b"fs1/fs/test1"),
3710                 ZFSTest.pool.makeName(b"fs1/fs/test2"),
3711                 ZFSTest.pool.makeName(b"fs1/fs/test3"), ]
3712        # and one snap to see that it is not listed
3713        snap = ZFSTest.pool.makeName(b"fs1/fs@test")
3714
3715        for fs in names:
3716            lzc.lzc_create(fs)
3717        lzc.lzc_snapshot([snap])
3718
3719        children = list(lzc.lzc_list_children(name))
3720        self.assertItemsEqual(children, names)
3721
3722    @needs_support(lzc.lzc_list_children)
3723    def test_list_children_nonexistent(self):
3724        fs = ZFSTest.pool.makeName(b"nonexistent")
3725
3726        with self.assertRaises(lzc_exc.DatasetNotFound):
3727            list(lzc.lzc_list_children(fs))
3728
3729    @needs_support(lzc.lzc_list_children)
3730    def test_list_children_of_snap(self):
3731        snap = ZFSTest.pool.makeName(b"@newsnap")
3732
3733        lzc.lzc_snapshot([snap])
3734        children = list(lzc.lzc_list_children(snap))
3735        self.assertEqual(children, [])
3736
3737    @needs_support(lzc.lzc_list_snaps)
3738    def test_list_snaps(self):
3739        name = ZFSTest.pool.makeName(b"fs1/fs")
3740        names = [ZFSTest.pool.makeName(b"fs1/fs@test1"),
3741                 ZFSTest.pool.makeName(b"fs1/fs@test2"),
3742                 ZFSTest.pool.makeName(b"fs1/fs@test3"), ]
3743        # and one filesystem to see that it is not listed
3744        fs = ZFSTest.pool.makeName(b"fs1/fs/test")
3745
3746        for snap in names:
3747            lzc.lzc_snapshot([snap])
3748        lzc.lzc_create(fs)
3749
3750        snaps = list(lzc.lzc_list_snaps(name))
3751        self.assertItemsEqual(snaps, names)
3752
3753    @needs_support(lzc.lzc_list_snaps)
3754    def test_list_snaps_nonexistent(self):
3755        fs = ZFSTest.pool.makeName(b"nonexistent")
3756
3757        with self.assertRaises(lzc_exc.DatasetNotFound):
3758            list(lzc.lzc_list_snaps(fs))
3759
3760    @needs_support(lzc.lzc_list_snaps)
3761    def test_list_snaps_of_snap(self):
3762        snap = ZFSTest.pool.makeName(b"@newsnap")
3763
3764        lzc.lzc_snapshot([snap])
3765        snaps = list(lzc.lzc_list_snaps(snap))
3766        self.assertEqual(snaps, [])
3767
3768    @needs_support(lzc.lzc_get_props)
3769    def test_get_fs_props(self):
3770        fs = ZFSTest.pool.makeName(b"new")
3771        props = {b"user:foo": b"bar"}
3772
3773        lzc.lzc_create(fs, props=props)
3774        actual_props = lzc.lzc_get_props(fs)
3775        self.assertDictContainsSubset(props, actual_props)
3776
3777    @needs_support(lzc.lzc_get_props)
3778    def test_get_fs_props_with_child(self):
3779        parent = ZFSTest.pool.makeName(b"parent")
3780        child = ZFSTest.pool.makeName(b"parent/child")
3781        parent_props = {b"user:foo": b"parent"}
3782        child_props = {b"user:foo": b"child"}
3783
3784        lzc.lzc_create(parent, props=parent_props)
3785        lzc.lzc_create(child, props=child_props)
3786        actual_parent_props = lzc.lzc_get_props(parent)
3787        actual_child_props = lzc.lzc_get_props(child)
3788        self.assertDictContainsSubset(parent_props, actual_parent_props)
3789        self.assertDictContainsSubset(child_props, actual_child_props)
3790
3791    @needs_support(lzc.lzc_get_props)
3792    def test_get_snap_props(self):
3793        snapname = ZFSTest.pool.makeName(b"@snap")
3794        snaps = [snapname]
3795        props = {b"user:foo": b"bar"}
3796
3797        lzc.lzc_snapshot(snaps, props)
3798        actual_props = lzc.lzc_get_props(snapname)
3799        self.assertDictContainsSubset(props, actual_props)
3800
3801    @needs_support(lzc.lzc_get_props)
3802    def test_get_props_nonexistent(self):
3803        fs = ZFSTest.pool.makeName(b"nonexistent")
3804
3805        with self.assertRaises(lzc_exc.DatasetNotFound):
3806            lzc.lzc_get_props(fs)
3807
3808    @needs_support(lzc.lzc_get_props)
3809    def test_get_mountpoint_none(self):
3810        '''
3811        If the *mountpoint* property is set to none, then its
3812        value is returned as `bytes` "none".
3813        Also, a child filesystem inherits that value.
3814        '''
3815        fs = ZFSTest.pool.makeName(b"new")
3816        child = ZFSTest.pool.makeName(b"new/child")
3817        props = {b"mountpoint": b"none"}
3818
3819        lzc.lzc_create(fs, props=props)
3820        lzc.lzc_create(child)
3821        actual_props = lzc.lzc_get_props(fs)
3822        self.assertDictContainsSubset(props, actual_props)
3823        # check that mountpoint value is correctly inherited
3824        child_props = lzc.lzc_get_props(child)
3825        self.assertDictContainsSubset(props, child_props)
3826
3827    @needs_support(lzc.lzc_get_props)
3828    def test_get_mountpoint_legacy(self):
3829        '''
3830        If the *mountpoint* property is set to legacy, then its
3831        value is returned as `bytes` "legacy".
3832        Also, a child filesystem inherits that value.
3833        '''
3834        fs = ZFSTest.pool.makeName(b"new")
3835        child = ZFSTest.pool.makeName(b"new/child")
3836        props = {b"mountpoint": b"legacy"}
3837
3838        lzc.lzc_create(fs, props=props)
3839        lzc.lzc_create(child)
3840        actual_props = lzc.lzc_get_props(fs)
3841        self.assertDictContainsSubset(props, actual_props)
3842        # check that mountpoint value is correctly inherited
3843        child_props = lzc.lzc_get_props(child)
3844        self.assertDictContainsSubset(props, child_props)
3845
3846    @needs_support(lzc.lzc_get_props)
3847    def test_get_mountpoint_path(self):
3848        '''
3849        If the *mountpoint* property is set to a path and the property
3850        is not explicitly set on a child filesystem, then its
3851        value is that of the parent filesystem with the child's
3852        name appended using the '/' separator.
3853        '''
3854        fs = ZFSTest.pool.makeName(b"new")
3855        child = ZFSTest.pool.makeName(b"new/child")
3856        props = {b"mountpoint": b"/mnt"}
3857
3858        lzc.lzc_create(fs, props=props)
3859        lzc.lzc_create(child)
3860        actual_props = lzc.lzc_get_props(fs)
3861        self.assertDictContainsSubset(props, actual_props)
3862        # check that mountpoint value is correctly inherited
3863        child_props = lzc.lzc_get_props(child)
3864        self.assertDictContainsSubset(
3865            {b"mountpoint": b"/mnt/child"}, child_props)
3866
3867    @needs_support(lzc.lzc_get_props)
3868    def test_get_snap_clones(self):
3869        fs = ZFSTest.pool.makeName(b"new")
3870        snap = ZFSTest.pool.makeName(b"@snap")
3871        clone1 = ZFSTest.pool.makeName(b"clone1")
3872        clone2 = ZFSTest.pool.makeName(b"clone2")
3873
3874        lzc.lzc_create(fs)
3875        lzc.lzc_snapshot([snap])
3876        lzc.lzc_clone(clone1, snap)
3877        lzc.lzc_clone(clone2, snap)
3878
3879        clones_prop = lzc.lzc_get_props(snap)["clones"]
3880        self.assertItemsEqual(clones_prop, [clone1, clone2])
3881
3882    @needs_support(lzc.lzc_rename)
3883    def test_rename(self):
3884        src = ZFSTest.pool.makeName(b"source")
3885        tgt = ZFSTest.pool.makeName(b"target")
3886
3887        lzc.lzc_create(src)
3888        lzc.lzc_rename(src, tgt)
3889        self.assertNotExists(src)
3890        self.assertExists(tgt)
3891
3892    @needs_support(lzc.lzc_rename)
3893    def test_rename_nonexistent(self):
3894        src = ZFSTest.pool.makeName(b"source")
3895        tgt = ZFSTest.pool.makeName(b"target")
3896
3897        with self.assertRaises(lzc_exc.FilesystemNotFound):
3898            lzc.lzc_rename(src, tgt)
3899
3900    @needs_support(lzc.lzc_rename)
3901    def test_rename_existing_target(self):
3902        src = ZFSTest.pool.makeName(b"source")
3903        tgt = ZFSTest.pool.makeName(b"target")
3904
3905        lzc.lzc_create(src)
3906        lzc.lzc_create(tgt)
3907        with self.assertRaises(lzc_exc.FilesystemExists):
3908            lzc.lzc_rename(src, tgt)
3909
3910    @needs_support(lzc.lzc_rename)
3911    def test_rename_nonexistent_target_parent(self):
3912        src = ZFSTest.pool.makeName(b"source")
3913        tgt = ZFSTest.pool.makeName(b"parent/target")
3914
3915        lzc.lzc_create(src)
3916        with self.assertRaises(lzc_exc.FilesystemNotFound):
3917            lzc.lzc_rename(src, tgt)
3918
3919    @needs_support(lzc.lzc_rename)
3920    def test_rename_parent_is_zvol(self):
3921        src = ZFSTest.pool.makeName(b"source")
3922        zvol = ZFSTest.pool.makeName(b"parent")
3923        tgt = zvol + b"/target"
3924        props = {b"volsize": 1024 * 1024}
3925
3926        lzc.lzc_create(src)
3927        lzc.lzc_create(zvol, ds_type='zvol', props=props)
3928        with self.assertRaises(lzc_exc.WrongParent):
3929            lzc.lzc_rename(src, tgt)
3930
3931    @needs_support(lzc.lzc_destroy)
3932    def test_destroy(self):
3933        fs = ZFSTest.pool.makeName(b"test-fs")
3934
3935        lzc.lzc_create(fs)
3936        lzc.lzc_destroy(fs)
3937        self.assertNotExists(fs)
3938
3939    @needs_support(lzc.lzc_destroy)
3940    def test_destroy_nonexistent(self):
3941        fs = ZFSTest.pool.makeName(b"test-fs")
3942
3943        with self.assertRaises(lzc_exc.FilesystemNotFound):
3944            lzc.lzc_destroy(fs)
3945
3946    @needs_support(lzc.lzc_inherit_prop)
3947    def test_inherit_prop(self):
3948        parent = ZFSTest.pool.makeName(b"parent")
3949        child = ZFSTest.pool.makeName(b"parent/child")
3950        the_prop = b"user:foo"
3951        parent_props = {the_prop: b"parent"}
3952        child_props = {the_prop: b"child"}
3953
3954        lzc.lzc_create(parent, props=parent_props)
3955        lzc.lzc_create(child, props=child_props)
3956        lzc.lzc_inherit_prop(child, the_prop)
3957        actual_props = lzc.lzc_get_props(child)
3958        self.assertDictContainsSubset(parent_props, actual_props)
3959
3960    @needs_support(lzc.lzc_inherit_prop)
3961    def test_inherit_missing_prop(self):
3962        parent = ZFSTest.pool.makeName(b"parent")
3963        child = ZFSTest.pool.makeName(b"parent/child")
3964        the_prop = "user:foo"
3965        child_props = {the_prop: "child"}
3966
3967        lzc.lzc_create(parent)
3968        lzc.lzc_create(child, props=child_props)
3969        lzc.lzc_inherit_prop(child, the_prop)
3970        actual_props = lzc.lzc_get_props(child)
3971        self.assertNotIn(the_prop, actual_props)
3972
3973    @needs_support(lzc.lzc_inherit_prop)
3974    def test_inherit_readonly_prop(self):
3975        parent = ZFSTest.pool.makeName(b"parent")
3976        child = ZFSTest.pool.makeName(b"parent/child")
3977        the_prop = b"createtxg"
3978
3979        lzc.lzc_create(parent)
3980        lzc.lzc_create(child)
3981        with self.assertRaises(lzc_exc.PropertyInvalid):
3982            lzc.lzc_inherit_prop(child, the_prop)
3983
3984    @needs_support(lzc.lzc_inherit_prop)
3985    def test_inherit_unknown_prop(self):
3986        parent = ZFSTest.pool.makeName(b"parent")
3987        child = ZFSTest.pool.makeName(b"parent/child")
3988        the_prop = b"nosuchprop"
3989
3990        lzc.lzc_create(parent)
3991        lzc.lzc_create(child)
3992        with self.assertRaises(lzc_exc.PropertyInvalid):
3993            lzc.lzc_inherit_prop(child, the_prop)
3994
3995    @needs_support(lzc.lzc_inherit_prop)
3996    def test_inherit_prop_on_snap(self):
3997        fs = ZFSTest.pool.makeName(b"new")
3998        snapname = ZFSTest.pool.makeName(b"new@snap")
3999        prop = b"user:foo"
4000        fs_val = b"fs"
4001        snap_val = b"snap"
4002
4003        lzc.lzc_create(fs, props={prop: fs_val})
4004        lzc.lzc_snapshot([snapname], props={prop: snap_val})
4005
4006        actual_props = lzc.lzc_get_props(snapname)
4007        self.assertDictContainsSubset({prop: snap_val}, actual_props)
4008
4009        lzc.lzc_inherit_prop(snapname, prop)
4010        actual_props = lzc.lzc_get_props(snapname)
4011        self.assertDictContainsSubset({prop: fs_val}, actual_props)
4012
4013    @needs_support(lzc.lzc_set_prop)
4014    def test_set_fs_prop(self):
4015        fs = ZFSTest.pool.makeName(b"new")
4016        prop = b"user:foo"
4017        val = b"bar"
4018
4019        lzc.lzc_create(fs)
4020        lzc.lzc_set_prop(fs, prop, val)
4021        actual_props = lzc.lzc_get_props(fs)
4022        self.assertDictContainsSubset({prop: val}, actual_props)
4023
4024    @needs_support(lzc.lzc_set_prop)
4025    def test_set_snap_prop(self):
4026        snapname = ZFSTest.pool.makeName(b"@snap")
4027        prop = b"user:foo"
4028        val = b"bar"
4029
4030        lzc.lzc_snapshot([snapname])
4031        lzc.lzc_set_prop(snapname, prop, val)
4032        actual_props = lzc.lzc_get_props(snapname)
4033        self.assertDictContainsSubset({prop: val}, actual_props)
4034
4035    @needs_support(lzc.lzc_set_prop)
4036    def test_set_prop_nonexistent(self):
4037        fs = ZFSTest.pool.makeName(b"nonexistent")
4038        prop = b"user:foo"
4039        val = b"bar"
4040
4041        with self.assertRaises(lzc_exc.DatasetNotFound):
4042            lzc.lzc_set_prop(fs, prop, val)
4043
4044    @needs_support(lzc.lzc_set_prop)
4045    def test_set_sys_prop(self):
4046        fs = ZFSTest.pool.makeName(b"new")
4047        prop = b"recordsize"
4048        val = 4096
4049
4050        lzc.lzc_create(fs)
4051        lzc.lzc_set_prop(fs, prop, val)
4052        actual_props = lzc.lzc_get_props(fs)
4053        self.assertDictContainsSubset({prop: val}, actual_props)
4054
4055    @needs_support(lzc.lzc_set_prop)
4056    def test_set_invalid_prop(self):
4057        fs = ZFSTest.pool.makeName(b"new")
4058        prop = b"nosuchprop"
4059        val = 0
4060
4061        lzc.lzc_create(fs)
4062        with self.assertRaises(lzc_exc.PropertyInvalid):
4063            lzc.lzc_set_prop(fs, prop, val)
4064
4065    @needs_support(lzc.lzc_set_prop)
4066    def test_set_invalid_value_prop(self):
4067        fs = ZFSTest.pool.makeName(b"new")
4068        prop = b"atime"
4069        val = 100
4070
4071        lzc.lzc_create(fs)
4072        with self.assertRaises(lzc_exc.PropertyInvalid):
4073            lzc.lzc_set_prop(fs, prop, val)
4074
4075    @needs_support(lzc.lzc_set_prop)
4076    def test_set_invalid_value_prop_2(self):
4077        fs = ZFSTest.pool.makeName(b"new")
4078        prop = b"readonly"
4079        val = 100
4080
4081        lzc.lzc_create(fs)
4082        with self.assertRaises(lzc_exc.PropertyInvalid):
4083            lzc.lzc_set_prop(fs, prop, val)
4084
4085    @needs_support(lzc.lzc_set_prop)
4086    def test_set_prop_too_small_quota(self):
4087        fs = ZFSTest.pool.makeName(b"new")
4088        prop = b"refquota"
4089        val = 1
4090
4091        lzc.lzc_create(fs)
4092        with self.assertRaises(lzc_exc.NoSpace):
4093            lzc.lzc_set_prop(fs, prop, val)
4094
4095    @needs_support(lzc.lzc_set_prop)
4096    def test_set_readonly_prop(self):
4097        fs = ZFSTest.pool.makeName(b"new")
4098        prop = b"creation"
4099        val = 0
4100
4101        lzc.lzc_create(fs)
4102        lzc.lzc_set_prop(fs, prop, val)
4103        actual_props = lzc.lzc_get_props(fs)
4104        # the change is silently ignored
4105        self.assertTrue(actual_props[prop] != val)
4106
4107
4108class _TempPool(object):
4109    SNAPSHOTS = [b'snap', b'snap1', b'snap2']
4110    BOOKMARKS = [b'bmark', b'bmark1', b'bmark2']
4111
4112    _cachefile_suffix = ".cachefile"
4113
4114    # XXX Whether to do a sloppy but much faster cleanup
4115    # or a proper but slower one.
4116    _recreate_pools = True
4117
4118    def __init__(self, size=128 * 1024 * 1024, readonly=False, filesystems=[]):
4119        self._filesystems = filesystems
4120        self._readonly = readonly
4121        if sys.version_info < (3, 0):
4122            self._pool_name = b'pool.' + bytes(uuid.uuid4())
4123        else:
4124            self._pool_name = b'pool.' + bytes(str(uuid.uuid4()),
4125                                               encoding='utf-8')
4126        self._root = _Filesystem(self._pool_name)
4127        (fd, self._pool_file_path) = tempfile.mkstemp(
4128            suffix='.zpool', prefix='tmp-')
4129        if readonly:
4130            cachefile = self._pool_file_path + _TempPool._cachefile_suffix
4131        else:
4132            cachefile = 'none'
4133        self._zpool_create = [
4134            'zpool', 'create', '-o', 'cachefile=' + cachefile,
4135            '-O', 'mountpoint=legacy', self._pool_name, self._pool_file_path]
4136        try:
4137            os.ftruncate(fd, size)
4138            os.close(fd)
4139
4140            subprocess.check_output(
4141                self._zpool_create, stderr=subprocess.STDOUT)
4142
4143            for fs in filesystems:
4144                lzc.lzc_create(self.makeName(fs))
4145
4146            self._bmarks_supported = self.isPoolFeatureEnabled('bookmarks')
4147
4148            if readonly:
4149                # To make a pool read-only it must exported and re-imported
4150                # with readonly option.
4151                # The most deterministic way to re-import the pool is by using
4152                # a cache file.
4153                # But the cache file has to be stashed away before the pool is
4154                # exported, because otherwise the pool is removed from the
4155                # cache.
4156                shutil.copyfile(cachefile, cachefile + '.tmp')
4157                subprocess.check_output(
4158                    ['zpool', 'export', '-f', self._pool_name],
4159                    stderr=subprocess.STDOUT)
4160                os.rename(cachefile + '.tmp', cachefile)
4161                subprocess.check_output(
4162                    ['zpool', 'import', '-f', '-N', '-c', cachefile,
4163                        '-o', 'readonly=on', self._pool_name],
4164                    stderr=subprocess.STDOUT)
4165                os.remove(cachefile)
4166
4167        except subprocess.CalledProcessError as e:
4168            self.cleanUp()
4169            if b'permission denied' in e.output:
4170                raise unittest.SkipTest(
4171                    'insufficient privileges to run libzfs_core tests')
4172            print('command failed: ', e.output)
4173            raise
4174        except Exception:
4175            self.cleanUp()
4176            raise
4177
4178    def reset(self):
4179        if self._readonly:
4180            return
4181
4182        if not self.__class__._recreate_pools:
4183            snaps = []
4184            for fs in [''] + self._filesystems:
4185                for snap in self.__class__.SNAPSHOTS:
4186                    snaps.append(self.makeName(fs + '@' + snap))
4187            self.getRoot().visitSnaps(lambda snap: snaps.append(snap))
4188            lzc.lzc_destroy_snaps(snaps, defer=False)
4189
4190            if self._bmarks_supported:
4191                bmarks = []
4192                for fs in [''] + self._filesystems:
4193                    for bmark in self.__class__.BOOKMARKS:
4194                        bmarks.append(self.makeName(fs + '#' + bmark))
4195                self.getRoot().visitBookmarks(
4196                    lambda bmark: bmarks.append(bmark))
4197                lzc.lzc_destroy_bookmarks(bmarks)
4198            self.getRoot().reset()
4199            return
4200
4201        # On the Buildbot builders this may fail with "pool is busy"
4202        # Retry 5 times before raising an error
4203        retry = 0
4204        while True:
4205            try:
4206                subprocess.check_output(
4207                    ['zpool', 'destroy', '-f', self._pool_name],
4208                    stderr=subprocess.STDOUT)
4209                subprocess.check_output(
4210                    self._zpool_create, stderr=subprocess.STDOUT)
4211                break
4212            except subprocess.CalledProcessError as e:
4213                if b'pool is busy' in e.output and retry < 5:
4214                    retry += 1
4215                    time.sleep(1)
4216                    continue
4217                else:
4218                    print('command failed: ', e.output)
4219                    raise
4220        for fs in self._filesystems:
4221            lzc.lzc_create(self.makeName(fs))
4222        self.getRoot().reset()
4223
4224    def cleanUp(self):
4225        try:
4226            subprocess.check_output(
4227                ['zpool', 'destroy', '-f', self._pool_name],
4228                stderr=subprocess.STDOUT)
4229        except Exception:
4230            pass
4231        try:
4232            os.remove(self._pool_file_path)
4233        except Exception:
4234            pass
4235        try:
4236            os.remove(self._pool_file_path + _TempPool._cachefile_suffix)
4237        except Exception:
4238            pass
4239        try:
4240            os.remove(
4241                self._pool_file_path + _TempPool._cachefile_suffix + '.tmp')
4242        except Exception:
4243            pass
4244
4245    def makeName(self, relative=None):
4246        if not relative:
4247            return self._pool_name
4248        if relative.startswith((b'@', b'#')):
4249            return self._pool_name + relative
4250        return self._pool_name + b'/' + relative
4251
4252    def makeTooLongName(self, prefix=None):
4253        if not prefix:
4254            prefix = b'x'
4255        prefix = self.makeName(prefix)
4256        pad_len = lzc.MAXNAMELEN + 1 - len(prefix)
4257        if pad_len > 0:
4258            return prefix + b'x' * pad_len
4259        else:
4260            return prefix
4261
4262    def makeTooLongComponent(self, prefix=None):
4263        padding = b'x' * (lzc.MAXNAMELEN + 1)
4264        if not prefix:
4265            prefix = padding
4266        else:
4267            prefix = prefix + padding
4268        return self.makeName(prefix)
4269
4270    def getRoot(self):
4271        return self._root
4272
4273    def getFilesystem(self, fsname):
4274        return _Filesystem(self._pool_name + b'/' + fsname)
4275
4276    def isPoolFeatureAvailable(self, feature):
4277        output = subprocess.check_output(
4278            ['zpool', 'get', '-H', 'feature@' + feature, self._pool_name])
4279        output = output.strip()
4280        return output != ''
4281
4282    def isPoolFeatureEnabled(self, feature):
4283        output = subprocess.check_output(
4284            ['zpool', 'get', '-H', 'feature@' + feature, self._pool_name])
4285        output = output.split()[2]
4286        return output in [b'active', b'enabled']
4287
4288
4289class _Filesystem(object):
4290
4291    def __init__(self, name):
4292        self._name = name
4293        self.reset()
4294
4295    def getName(self):
4296        return self._name
4297
4298    def reset(self):
4299        self._children = []
4300        self._fs_id = 0
4301        self._snap_id = 0
4302        self._bmark_id = 0
4303
4304    def getFilesystem(self):
4305        self._fs_id += 1
4306        fsname = self._name + b'/fs' + str(self._fs_id).encode()
4307        fs = _Filesystem(fsname)
4308        self._children.append(fs)
4309        return fs
4310
4311    def getProperty(self, propname, received=False):
4312        if received:
4313            output = subprocess.check_output(
4314                ['zfs', 'get', '-pH', '-o', 'received', propname, self._name])
4315        else:
4316            output = subprocess.check_output(
4317                ['zfs', 'get', '-pH', '-o', 'value', propname, self._name])
4318        return output.strip()
4319
4320    def _makeSnapName(self, i):
4321        return self._name + b'@snap' + str(i).encode()
4322
4323    def getSnap(self):
4324        self._snap_id += 1
4325        return self._makeSnapName(self._snap_id)
4326
4327    def _makeBookmarkName(self, i):
4328        return self._name + b'#bmark' + bytes(i)
4329
4330    def getBookmark(self):
4331        self._bmark_id += 1
4332        return self._makeBookmarkName(self._bmark_id)
4333
4334    def _makeTooLongName(self, too_long_component):
4335        if too_long_component:
4336            return b'x' * (lzc.MAXNAMELEN + 1)
4337
4338        # Note that another character is used for one of '/', '@', '#'.
4339        comp_len = lzc.MAXNAMELEN - len(self._name)
4340        if comp_len > 0:
4341            return b'x' * comp_len
4342        else:
4343            return b'x'
4344
4345    def getTooLongFilesystemName(self, too_long_component):
4346        return self._name + b'/' + self._makeTooLongName(too_long_component)
4347
4348    def getTooLongSnap(self, too_long_component):
4349        return self._name + b'@' + self._makeTooLongName(too_long_component)
4350
4351    def getTooLongBookmark(self, too_long_component):
4352        return self._name + b'#' + self._makeTooLongName(too_long_component)
4353
4354    def _visitFilesystems(self, visitor):
4355        for child in self._children:
4356            child._visitFilesystems(visitor)
4357        visitor(self)
4358
4359    def visitFilesystems(self, visitor):
4360        def _fsVisitor(fs):
4361            visitor(fs._name)
4362
4363        self._visitFilesystems(_fsVisitor)
4364
4365    def visitSnaps(self, visitor):
4366        def _snapVisitor(fs):
4367            for i in range(1, fs._snap_id + 1):
4368                visitor(fs._makeSnapName(i))
4369
4370        self._visitFilesystems(_snapVisitor)
4371
4372    def visitBookmarks(self, visitor):
4373        def _bmarkVisitor(fs):
4374            for i in range(1, fs._bmark_id + 1):
4375                visitor(fs._makeBookmarkName(i))
4376
4377        self._visitFilesystems(_bmarkVisitor)
4378
4379
4380# vim: softtabstop=4 tabstop=4 expandtab shiftwidth=4
4381