]> git.proxmox.com Git - mirror_zfs.git/commitdiff
ZVOLs should not be allowed to have children
authorloli10K <loli10K@users.noreply.github.com>
Fri, 8 Feb 2019 23:44:15 +0000 (00:44 +0100)
committerMatthew Ahrens <mahrens@delphix.com>
Fri, 8 Feb 2019 23:44:15 +0000 (15:44 -0800)
zfs create, receive and rename can bypass this hierarchy rule. Update
both userland and kernel module to prevent this issue and use pyzfs
unit tests to exercise the ioctls directly.

Note: this commit slightly changes zfs_ioc_create() ABI. This allow to
differentiate a generic error (EINVAL) from the specific case where we
tried to create a dataset below a ZVOL (ZFS_ERR_WRONG_PARENT).

Reviewed-by: Paul Dagnelie <pcd@delphix.com>
Reviewed-by: Matt Ahrens <mahrens@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Tom Caputi <tcaputi@datto.com>
Signed-off-by: loli10K <ezomori.nozomu@gmail.com>
17 files changed:
contrib/pyzfs/libzfs_core/_constants.py
contrib/pyzfs/libzfs_core/_error_translation.py
contrib/pyzfs/libzfs_core/_libzfs_core.py
contrib/pyzfs/libzfs_core/exceptions.py
contrib/pyzfs/libzfs_core/test/test_libzfs_core.py
include/libzfs.h
include/sys/fs/zfs.h
lib/libzfs/libzfs_dataset.c
lib/libzfs/libzfs_sendrecv.c
lib/libzfs/libzfs_util.c
module/zfs/dmu_objset.c
module/zfs/dmu_recv.c
module/zfs/dsl_dir.c
module/zfs/zfs_ioctl.c
tests/runfiles/linux.run
tests/zfs-tests/tests/functional/zvol/zvol_misc/Makefile.am
tests/zfs-tests/tests/functional/zvol/zvol_misc/zvol_misc_hierarchy.ksh [new file with mode: 0755]

index 917feee015ae1bf56581b191073c1dd680ed8584..55de55d422865eeab5a58b73c901e0724b014a16 100644 (file)
@@ -65,6 +65,7 @@ ZFS_ERR_DISCARDING_CHECKPOINT = 1025
 ZFS_ERR_NO_CHECKPOINT = 1026
 ZFS_ERR_DEVRM_IN_PROGRESS = 1027
 ZFS_ERR_VDEV_TOO_BIG = 1028
+ZFS_ERR_WRONG_PARENT = 1033
 
 
 # vim: softtabstop=4 tabstop=4 expandtab shiftwidth=4
index b5f4bebce1527ddcaecbe5db16d8d0d77337c624..b888fd7255bbe194f8e9920b1e9d70af25f10c7b 100644 (file)
@@ -38,7 +38,8 @@ from ._constants import (
     ZFS_ERR_DISCARDING_CHECKPOINT,
     ZFS_ERR_NO_CHECKPOINT,
     ZFS_ERR_DEVRM_IN_PROGRESS,
-    ZFS_ERR_VDEV_TOO_BIG
+    ZFS_ERR_VDEV_TOO_BIG,
+    ZFS_ERR_WRONG_PARENT
 )
 
 
@@ -46,13 +47,14 @@ def lzc_create_translate_error(ret, name, ds_type, props):
     if ret == 0:
         return
     if ret == errno.EINVAL:
-        # XXX: should raise lzc_exc.WrongParent if parent is ZVOL
         _validate_fs_name(name)
         raise lzc_exc.PropertyInvalid(name)
     if ret == errno.EEXIST:
         raise lzc_exc.FilesystemExists(name)
     if ret == errno.ENOENT:
         raise lzc_exc.ParentNotFound(name)
+    if ret == ZFS_ERR_WRONG_PARENT:
+        raise lzc_exc.WrongParent(_fs_name(name))
     raise _generic_exception(ret, name, "Failed to create filesystem")
 
 
@@ -444,6 +446,8 @@ def lzc_receive_translate_errors(
         raise lzc_exc.SuspendedPool(_pool_name(snapname))
     if ret == errno.EBADE:  # ECKSUM
         raise lzc_exc.BadStream()
+    if ret == ZFS_ERR_WRONG_PARENT:
+        raise lzc_exc.WrongParent(_fs_name(snapname))
 
     raise lzc_exc.StreamIOError(ret)
 
@@ -596,6 +600,8 @@ def lzc_rename_translate_error(ret, source, target):
         raise lzc_exc.FilesystemExists(target)
     if ret == errno.ENOENT:
         raise lzc_exc.FilesystemNotFound(source)
+    if ret == ZFS_ERR_WRONG_PARENT:
+        raise lzc_exc.WrongParent(target)
     raise _generic_exception(ret, source, "Failed to rename dataset")
 
 
index 589926ba876fe584332cd59fc1c75ba53307d4d9..5c8a1f5e690a82149bffb8240ce4138864bd2da9 100644 (file)
@@ -754,6 +754,8 @@ def lzc_receive(snapname, fd, force=False, raw=False, origin=None, props=None):
         supported on this side.
     :raises NameInvalid: if the name of either snapshot is invalid.
     :raises NameTooLong: if the name of either snapshot is too long.
+    :raises WrongParent: if the parent dataset of the received destination is
+        not a filesystem (e.g. ZVOL)
 
     .. note::
         The ``origin`` is ignored if the actual stream is an incremental stream
@@ -1621,6 +1623,8 @@ def lzc_rename(source, target):
     :raises FilesystemNotFound: if the target's parent does not exist.
     :raises FilesystemExists: if the target already exists.
     :raises PoolsDiffer: if the source and target belong to different pools.
+    :raises WrongParent: if the "new" parent dataset is not a filesystem
+        (e.g. ZVOL)
     '''
     ret = _lib.lzc_rename(source, target)
     errors.lzc_rename_translate_error(ret, source, target)
index c54459ec8b4745413fe79ad0e34d7b653495b618..f465cd3d9309b97cbe5c1c817f354b3eddfd7075 100644 (file)
@@ -25,7 +25,8 @@ from ._constants import (
     ZFS_ERR_DISCARDING_CHECKPOINT,
     ZFS_ERR_NO_CHECKPOINT,
     ZFS_ERR_DEVRM_IN_PROGRESS,
-    ZFS_ERR_VDEV_TOO_BIG
+    ZFS_ERR_VDEV_TOO_BIG,
+    ZFS_ERR_WRONG_PARENT
 )
 
 
@@ -140,7 +141,7 @@ class ParentNotFound(ZFSError):
 
 
 class WrongParent(ZFSError):
-    errno = errno.EINVAL
+    errno = ZFS_ERR_WRONG_PARENT
     message = "Parent dataset is not a filesystem"
 
     def __init__(self, name):
index 97fd36ce7236533b18898a8dc14dbc9007fb22c0..25f20a4aeebc7376d12d0d90ee032df5c36c986a 100644 (file)
@@ -193,11 +193,11 @@ def make_snapshots(fs, before, modified, after):
 @contextlib.contextmanager
 def streams(fs, first, second):
     (filename, snaps) = make_snapshots(fs, None, first, second)
-    with tempfile.TemporaryFile(suffix='.ztream') as full:
+    with tempfile.TemporaryFile(suffix='.zstream') as full:
         lzc.lzc_send(snaps[1], None, full.fileno())
         full.seek(0)
         if snaps[2] is not None:
-            with tempfile.TemporaryFile(suffix='.ztream') as incremental:
+            with tempfile.TemporaryFile(suffix='.zstream') as incremental:
                 lzc.lzc_send(snaps[2], snaps[1], incremental.fileno())
                 incremental.seek(0)
                 yield (filename, (full, incremental))
@@ -357,8 +357,6 @@ class ZFSTest(unittest.TestCase):
         with self.assertRaises(lzc_exc.DatasetTypeInvalid):
             lzc.lzc_create(name, ds_type='wrong')
 
-    # XXX: we should have a way to raise lzc_exc.WrongParent from lzc_create()
-    @unittest.expectedFailure
     def test_create_fs_below_zvol(self):
         name = ZFSTest.pool.makeName(b"fs1/fs/zvol")
         props = {b"volsize": 1024 * 1024}
@@ -367,6 +365,14 @@ class ZFSTest(unittest.TestCase):
         with self.assertRaises(lzc_exc.WrongParent):
             lzc.lzc_create(name + b'/fs')
 
+    def test_create_zvol_below_zvol(self):
+        name = ZFSTest.pool.makeName(b"fs1/fs/zvol")
+        props = {b"volsize": 1024 * 1024}
+
+        lzc.lzc_create(name, ds_type='zvol', props=props)
+        with self.assertRaises(lzc_exc.WrongParent):
+            lzc.lzc_create(name + b'/zvol', ds_type='zvol', props=props)
+
     def test_create_fs_duplicate(self):
         name = ZFSTest.pool.makeName(b"fs1/fs/test6")
 
@@ -1590,7 +1596,7 @@ class ZFSTest(unittest.TestCase):
                 f.flush()
                 lzc.lzc_snapshot([snap])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             estimate = lzc.lzc_send_space(snap)
 
             fd = output.fileno()
@@ -1611,7 +1617,7 @@ class ZFSTest(unittest.TestCase):
                 f.flush()
                 lzc.lzc_snapshot([snap2])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             estimate = lzc.lzc_send_space(snap2, snap1)
 
             fd = output.fileno()
@@ -1640,7 +1646,7 @@ class ZFSTest(unittest.TestCase):
     def test_send_same_snap(self):
         snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
         lzc.lzc_snapshot([snap1])
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             fd = output.fileno()
             with self.assertRaises(lzc_exc.SnapshotMismatch):
                 lzc.lzc_send(snap1, snap1, fd)
@@ -1652,7 +1658,7 @@ class ZFSTest(unittest.TestCase):
         lzc.lzc_snapshot([snap1])
         lzc.lzc_snapshot([snap2])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             fd = output.fileno()
             with self.assertRaises(lzc_exc.SnapshotMismatch):
                 lzc.lzc_send(snap1, snap2, fd)
@@ -1664,7 +1670,7 @@ class ZFSTest(unittest.TestCase):
         lzc.lzc_snapshot([snap1])
         lzc.lzc_snapshot([snap2])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             fd = output.fileno()
             with self.assertRaises(lzc_exc.SnapshotMismatch):
                 lzc.lzc_send(snap1, snap2, fd)
@@ -1676,7 +1682,7 @@ class ZFSTest(unittest.TestCase):
         lzc.lzc_snapshot([snap1])
         lzc.lzc_snapshot([snap2])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             fd = output.fileno()
             with self.assertRaises(lzc_exc.PoolsDiffer):
                 lzc.lzc_send(snap1, snap2, fd)
@@ -1687,7 +1693,7 @@ class ZFSTest(unittest.TestCase):
 
         lzc.lzc_snapshot([snap1])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             fd = output.fileno()
             with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
                 lzc.lzc_send(snap1, snap2, fd)
@@ -1707,7 +1713,7 @@ class ZFSTest(unittest.TestCase):
 
         lzc.lzc_snapshot([snap1])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             fd = output.fileno()
             with self.assertRaises(lzc_exc.NameInvalid) as ctx:
                 lzc.lzc_send(snap2, snap1, fd)
@@ -1729,7 +1735,7 @@ class ZFSTest(unittest.TestCase):
 
         lzc.lzc_snapshot([snap])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             fd = output.fileno()
             lzc.lzc_send(fs, snap, fd)
             lzc.lzc_send(fs, None, fd)
@@ -1740,7 +1746,7 @@ class ZFSTest(unittest.TestCase):
 
         lzc.lzc_snapshot([snap])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             fd = output.fileno()
             with self.assertRaises(lzc_exc.NameInvalid):
                 lzc.lzc_send(snap, fs, fd)
@@ -1756,7 +1762,7 @@ class ZFSTest(unittest.TestCase):
         lzc.lzc_bookmark({bmark: snap2})
         lzc.lzc_destroy_snaps([snap2], defer=False)
 
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             fd = output.fileno()
             with self.assertRaises(lzc_exc.NameInvalid):
                 lzc.lzc_send(bmark, snap1, fd)
@@ -1774,7 +1780,7 @@ class ZFSTest(unittest.TestCase):
         lzc.lzc_bookmark({bmark: snap1})
         lzc.lzc_destroy_snaps([snap1], defer=False)
 
-        with tempfile.TemporaryFile(suffix='.ztream') as output:
+        with tempfile.TemporaryFile(suffix='.zstream') as output:
             fd = output.fileno()
             lzc.lzc_send(snap2, bmark, fd)
 
@@ -1854,7 +1860,7 @@ class ZFSTest(unittest.TestCase):
         lzc.lzc_snapshot([snap])
 
         with tempfile.NamedTemporaryFile(
-                suffix='.ztream', delete=False) as output:
+                suffix='.zstream', delete=False) as output:
             # tempfile always opens a temporary file in read-write mode
             # regardless of the specified mode, so we have to open it again.
             os.chmod(output.name, stat.S_IRUSR)
@@ -1871,7 +1877,7 @@ class ZFSTest(unittest.TestCase):
         with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")) as name:
             lzc.lzc_snapshot([src])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(dst, stream.fileno())
@@ -1892,11 +1898,11 @@ class ZFSTest(unittest.TestCase):
         with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")) as name:
             lzc.lzc_snapshot([src2])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src1, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(dst1, stream.fileno())
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src2, src1, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(dst2, stream.fileno())
@@ -1933,14 +1939,14 @@ class ZFSTest(unittest.TestCase):
         clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone@snap")
 
         lzc.lzc_snapshot([orig_src])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(orig_src, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(orig_dst, stream.fileno())
 
         lzc.lzc_clone(clone, orig_src)
         lzc.lzc_snapshot([clone_snap])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(clone_snap, orig_src, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(clone_dst, stream.fileno(), origin=orig_dst)
@@ -1953,7 +1959,7 @@ class ZFSTest(unittest.TestCase):
         with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
             lzc.lzc_snapshot([src])
         lzc.lzc_create(dstfs)
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
             with self.assertRaises((
@@ -1992,7 +1998,7 @@ class ZFSTest(unittest.TestCase):
             lzc.lzc_snapshot([src])
         lzc.lzc_create(dstfs)
         with temp_file_in_fs(dstfs):
-            with tempfile.TemporaryFile(suffix='.ztream') as stream:
+            with tempfile.TemporaryFile(suffix='.zstream') as stream:
                 lzc.lzc_send(src, None, stream.fileno())
                 stream.seek(0)
                 with self.assertRaises((
@@ -2008,7 +2014,7 @@ class ZFSTest(unittest.TestCase):
             lzc.lzc_snapshot([src])
         lzc.lzc_create(dstfs)
         lzc.lzc_snapshot([dstfs + b"@snap1"])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
             with self.assertRaises((
@@ -2024,7 +2030,7 @@ class ZFSTest(unittest.TestCase):
             lzc.lzc_snapshot([src])
         lzc.lzc_create(dstfs)
         lzc.lzc_snapshot([dst])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
             with self.assertRaises(lzc_exc.DatasetExists):
@@ -2036,7 +2042,7 @@ class ZFSTest(unittest.TestCase):
 
         with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
             lzc.lzc_snapshot([src])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
             with self.assertRaises(lzc_exc.DatasetNotFound):
@@ -2251,14 +2257,14 @@ class ZFSTest(unittest.TestCase):
         clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone-2@snap")
 
         lzc.lzc_snapshot([orig_src])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(orig_src, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(orig_dst, stream.fileno())
 
         lzc.lzc_clone(clone, orig_src)
         lzc.lzc_snapshot([clone_snap])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(clone_snap, orig_src, stream.fileno())
             stream.seek(0)
             with self.assertRaises(lzc_exc.BadStream):
@@ -2272,14 +2278,14 @@ class ZFSTest(unittest.TestCase):
         clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone-3@snap")
 
         lzc.lzc_snapshot([orig_src])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(orig_src, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(orig_dst, stream.fileno())
 
         lzc.lzc_clone(clone, orig_src)
         lzc.lzc_snapshot([clone_snap])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(clone_snap, orig_src, stream.fileno())
             stream.seek(0)
             with self.assertRaises(lzc_exc.NameInvalid):
@@ -2296,7 +2302,7 @@ class ZFSTest(unittest.TestCase):
         wrong_origin = ZFSTest.pool.makeName(b"fs1/fs@snap")
 
         lzc.lzc_snapshot([orig_src])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(orig_src, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(orig_dst, stream.fileno())
@@ -2304,7 +2310,7 @@ class ZFSTest(unittest.TestCase):
         lzc.lzc_clone(clone, orig_src)
         lzc.lzc_snapshot([clone_snap])
         lzc.lzc_snapshot([wrong_origin])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(clone_snap, orig_src, stream.fileno())
             stream.seek(0)
             with self.assertRaises(lzc_exc.StreamMismatch):
@@ -2320,14 +2326,14 @@ class ZFSTest(unittest.TestCase):
         wrong_origin = ZFSTest.pool.makeName(b"fs1/fs@snap")
 
         lzc.lzc_snapshot([orig_src])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(orig_src, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(orig_dst, stream.fileno())
 
         lzc.lzc_clone(clone, orig_src)
         lzc.lzc_snapshot([clone_snap])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(clone_snap, orig_src, stream.fileno())
             stream.seek(0)
             with self.assertRaises(lzc_exc.DatasetNotFound):
@@ -2346,7 +2352,7 @@ class ZFSTest(unittest.TestCase):
         with temp_file_in_fs(dstfs):
             pass  # enough to taint the fs
 
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(dst, stream.fileno(), force=True)
@@ -2361,7 +2367,7 @@ class ZFSTest(unittest.TestCase):
 
         lzc.lzc_create(dstfs)
 
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
             with zfs_mount(dstfs) as mntdir:
@@ -2391,7 +2397,7 @@ class ZFSTest(unittest.TestCase):
             pass  # enough to taint the fs
         lzc.lzc_snapshot([dstfs + b"@snap1"])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(dst, stream.fileno(), force=True)
@@ -2409,7 +2415,7 @@ class ZFSTest(unittest.TestCase):
             pass  # enough to taint the fs
         lzc.lzc_snapshot([dst])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
             with self.assertRaises(lzc_exc.DatasetExists):
@@ -2421,7 +2427,7 @@ class ZFSTest(unittest.TestCase):
 
         with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
             lzc.lzc_snapshot([src])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
             with self.assertRaises(lzc_exc.DatasetNotFound):
@@ -2569,7 +2575,7 @@ class ZFSTest(unittest.TestCase):
         with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")) as name:
             lzc.lzc_snapshot([src])
 
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
 
@@ -2585,6 +2591,50 @@ class ZFSTest(unittest.TestCase):
                 filecmp.cmp(
                     os.path.join(mnt1, name), os.path.join(mnt2, name), False))
 
+    def test_recv_fs_below_zvol(self):
+        send = ZFSTest.pool.makeName(b"fs1@snap")
+        zvol = ZFSTest.pool.makeName(b"fs1/zvol")
+        dest = zvol + b"/fs@snap"
+        props = {b"volsize": 1024 * 1024}
+
+        lzc.lzc_snapshot([send])
+        lzc.lzc_create(zvol, ds_type='zvol', props=props)
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
+            lzc.lzc_send(send, None, stream.fileno())
+            stream.seek(0)
+            with self.assertRaises(lzc_exc.WrongParent):
+                lzc.lzc_receive(dest, stream.fileno())
+
+    def test_recv_zvol_over_fs_with_children(self):
+        parent = ZFSTest.pool.makeName(b"fs1")
+        child = parent + b"subfs"
+        zvol = ZFSTest.pool.makeName(b"fs1/zvol")
+        send = zvol + b"@snap"
+        props = {b"volsize": 1024 * 1024}
+
+        lzc.lzc_create(child)
+        lzc.lzc_create(zvol, ds_type='zvol', props=props)
+        lzc.lzc_snapshot([send])
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
+            lzc.lzc_send(send, None, stream.fileno())
+            stream.seek(0)
+            with self.assertRaises(lzc_exc.WrongParent):
+                lzc.lzc_receive(parent + b"@snap", stream.fileno(), force=True)
+
+    def test_recv_zvol_overwrite_rootds(self):
+        zvol = ZFSTest.pool.makeName(b"fs1/zvol")
+        snap = zvol + b"@snap"
+        rootds = ZFSTest.pool.getRoot().getName()
+        props = {b"volsize": 1024 * 1024}
+
+        lzc.lzc_create(zvol, ds_type='zvol', props=props)
+        lzc.lzc_snapshot([snap])
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
+            lzc.lzc_send(snap, None, stream.fileno())
+            stream.seek(0)
+            with self.assertRaises(lzc_exc.WrongParent):
+                lzc.lzc_receive(rootds + b"@snap", stream.fileno(), force=True)
+
     def test_send_full_across_clone_branch_point(self):
         origfs = ZFSTest.pool.makeName(b"fs2")
 
@@ -2596,7 +2646,7 @@ class ZFSTest(unittest.TestCase):
 
         (_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
 
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(tosnap, None, stream.fileno())
 
     def test_send_incr_across_clone_branch_point(self):
@@ -2610,7 +2660,7 @@ class ZFSTest(unittest.TestCase):
 
         (_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
 
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(tosnap, fromsnap, stream.fileno())
 
     def test_send_resume_token_full(self):
@@ -2625,7 +2675,7 @@ class ZFSTest(unittest.TestCase):
                     f.flush()
         lzc.lzc_snapshot([src])
 
-        with tempfile.NamedTemporaryFile(suffix='.ztream') as stream:
+        with tempfile.NamedTemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(src, None, stream.fileno())
             stream.seek(0)
             stream.truncate(1024 * 3)
@@ -2656,7 +2706,7 @@ class ZFSTest(unittest.TestCase):
             resume_values = packed_nvlist_out(payload, packed_size)
             resumeobj = resume_values.get(b'object')
             resumeoff = resume_values.get(b'offset')
-            with tempfile.NamedTemporaryFile(suffix='.ztream') as rstream:
+            with tempfile.NamedTemporaryFile(suffix='.zstream') as rstream:
                 lzc.lzc_send_resume(
                     src, None, rstream.fileno(), None, resumeobj, resumeoff)
                 rstream.seek(0)
@@ -2670,7 +2720,7 @@ class ZFSTest(unittest.TestCase):
         dst2 = dstfs.getSnap()
 
         lzc.lzc_snapshot([snap1])
-        with tempfile.NamedTemporaryFile(suffix='.ztream') as stream:
+        with tempfile.NamedTemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(snap1, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(dst1, stream.fileno())
@@ -2682,7 +2732,7 @@ class ZFSTest(unittest.TestCase):
                     f.flush()
         lzc.lzc_snapshot([snap2])
 
-        with tempfile.NamedTemporaryFile(suffix='.ztream') as stream:
+        with tempfile.NamedTemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(snap2, snap1, stream.fileno())
             stream.seek(0)
             stream.truncate(1024 * 3)
@@ -2712,7 +2762,7 @@ class ZFSTest(unittest.TestCase):
             resume_values = packed_nvlist_out(payload, packed_size)
             resumeobj = resume_values.get(b'object')
             resumeoff = resume_values.get(b'offset')
-            with tempfile.NamedTemporaryFile(suffix='.ztream') as rstream:
+            with tempfile.NamedTemporaryFile(suffix='.zstream') as rstream:
                 lzc.lzc_send_resume(
                     snap2, snap1, rstream.fileno(), None, resumeobj, resumeoff)
                 rstream.seek(0)
@@ -2731,7 +2781,7 @@ class ZFSTest(unittest.TestCase):
 
         recvfs = ZFSTest.pool.makeName(b"fs1/recv-clone-30")
         recvsnap = recvfs + b"@snap"
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(tosnap, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(recvsnap, stream.fileno())
@@ -2741,7 +2791,7 @@ class ZFSTest(unittest.TestCase):
         tosnap = ZFSTest.pool.makeName(b"recv@snap1")
 
         lzc.lzc_snapshot([fromsnap])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(fromsnap, None, stream.fileno())
             stream.seek(0)
             (header, c_header) = lzc.receive_header(stream.fileno())
@@ -2752,7 +2802,7 @@ class ZFSTest(unittest.TestCase):
         tosnap = ZFSTest.pool.makeName(b"recv@snap1")
 
         lzc.lzc_snapshot([fromsnap])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(fromsnap, None, stream.fileno())
             size = os.fstat(stream.fileno()).st_size
             stream.seek(0)
@@ -2770,7 +2820,7 @@ class ZFSTest(unittest.TestCase):
         }
 
         lzc.lzc_snapshot([fromsnap])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(fromsnap, None, stream.fileno())
             stream.seek(0)
             (header, c_header) = lzc.receive_header(stream.fileno())
@@ -2789,7 +2839,7 @@ class ZFSTest(unittest.TestCase):
         }
 
         lzc.lzc_snapshot([fromsnap])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(fromsnap, None, stream.fileno())
             stream.seek(0)
             (header, c_header) = lzc.receive_header(stream.fileno())
@@ -2813,7 +2863,7 @@ class ZFSTest(unittest.TestCase):
         }
 
         lzc.lzc_snapshot([fromsnap])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(fromsnap, None, stream.fileno())
             stream.seek(0)
             (header, c_header) = lzc.receive_header(stream.fileno())
@@ -2840,7 +2890,7 @@ class ZFSTest(unittest.TestCase):
         }
 
         lzc.lzc_snapshot([fromsnap])
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(fromsnap, None, stream.fileno())
             stream.seek(0)
             (header, c_header) = lzc.receive_header(stream.fileno())
@@ -2869,11 +2919,11 @@ class ZFSTest(unittest.TestCase):
         recvfs = ZFSTest.pool.makeName(b"fs1/recv-clone-32")
         recvsnap1 = recvfs + b"@snap1"
         recvsnap2 = recvfs + b"@snap2"
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(fromsnap, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(recvsnap1, stream.fileno())
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(tosnap, fromsnap, stream.fileno())
             stream.seek(0)
             with self.assertRaises(lzc_exc.BadStream):
@@ -2893,11 +2943,11 @@ class ZFSTest(unittest.TestCase):
         recvfs = ZFSTest.pool.makeName(b"fs1/recv-clone-31")
         recvsnap1 = recvfs + b"@snap1"
         recvsnap2 = recvfs + b"@snap2"
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(fromsnap, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(recvsnap1, stream.fileno())
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(tosnap, fromsnap, stream.fileno())
             stream.seek(0)
             with self.assertRaises(lzc_exc.BadStream):
@@ -2918,11 +2968,11 @@ class ZFSTest(unittest.TestCase):
         recvsnap1 = recvfs1 + b"@snap"
         recvfs2 = ZFSTest.pool.makeName(b"fs1/recv-clone-33_2")
         recvsnap2 = recvfs2 + b"@snap"
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(fromsnap, None, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(recvsnap1, stream.fileno())
-        with tempfile.TemporaryFile(suffix='.ztream') as stream:
+        with tempfile.TemporaryFile(suffix='.zstream') as stream:
             lzc.lzc_send(tosnap, fromsnap, stream.fileno())
             stream.seek(0)
             lzc.lzc_receive(recvsnap2, stream.fileno(), origin=recvsnap1)
@@ -3856,6 +3906,18 @@ zfs.sync.snapshot('""" + pool + b"""@zcp')
         with self.assertRaises(lzc_exc.FilesystemNotFound):
             lzc.lzc_rename(src, tgt)
 
+    @needs_support(lzc.lzc_rename)
+    def test_rename_parent_is_zvol(self):
+        src = ZFSTest.pool.makeName(b"source")
+        zvol = ZFSTest.pool.makeName(b"parent")
+        tgt = zvol + b"/target"
+        props = {b"volsize": 1024 * 1024}
+
+        lzc.lzc_create(src)
+        lzc.lzc_create(zvol, ds_type='zvol', props=props)
+        with self.assertRaises(lzc_exc.WrongParent):
+            lzc.lzc_rename(src, tgt)
+
     @needs_support(lzc.lzc_destroy)
     def test_destroy(self):
         fs = ZFSTest.pool.makeName(b"test-fs")
index 85b0bc0ddb771303c62a4814dee6d3ad012649b4..72d956b41f328e61e8286b895f319c7152457ee9 100644 (file)
@@ -142,6 +142,7 @@ typedef enum zfs_error {
        EZFS_TOOMANY,           /* argument list too long */
        EZFS_INITIALIZING,      /* currently initializing */
        EZFS_NO_INITIALIZE,     /* no active initialize */
+       EZFS_WRONG_PARENT,      /* invalid parent dataset (e.g ZVOL) */
        EZFS_UNKNOWN
 } zfs_error_t;
 
index 945853739b7cb70954f36bd1cc5657a2dcacaf70..395d2e27f20f1b0afc4c815f61392dc2e6bd8c69 100644 (file)
@@ -1256,6 +1256,7 @@ typedef enum {
        ZFS_ERR_IOC_ARG_UNAVAIL,
        ZFS_ERR_IOC_ARG_REQUIRED,
        ZFS_ERR_IOC_ARG_BADTYPE,
+       ZFS_ERR_WRONG_PARENT,
 } zfs_errno_t;
 
 /*
index 6445c9d7acf3509ce73d8aeed83eaeda279314d5..be86e5692b0df923608381506a851d3f932cc72d 100644 (file)
@@ -3809,11 +3809,6 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
                            "no such parent '%s'"), parent);
                        return (zfs_error(hdl, EZFS_NOENT, errbuf));
 
-               case EINVAL:
-                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                           "parent '%s' is not a filesystem"), parent);
-                       return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
-
                case ENOTSUP:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "pool must be upgraded to set this "
index ae24454d7c792977884598b5709613fb1a3e9a43..1d8292101dd62a63bdd334024aed8adf9a44c6ef 100644 (file)
@@ -28,7 +28,7 @@
  * Copyright (c) 2013 Steven Hartland. All rights reserved.
  * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved.
  * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
- * Copyright (c) 2017, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
+ * Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
  */
 
 #include <assert.h>
@@ -3912,6 +3912,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                 *  - we are resuming a failed receive.
                 */
                if (stream_wantsnewfs) {
+                       boolean_t is_volume = drrb->drr_type == DMU_OST_ZVOL;
                        if (!flags->force) {
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "destination '%s' exists\n"
@@ -3928,6 +3929,24 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                                err = zfs_error(hdl, EZFS_EXISTS, errbuf);
                                goto out;
                        }
+                       if (is_volume && strrchr(name, '/') == NULL) {
+                               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                                   "destination %s is the root dataset\n"
+                                   "cannot overwrite with a ZVOL"),
+                                   name);
+                               err = zfs_error(hdl, EZFS_EXISTS, errbuf);
+                               goto out;
+                       }
+                       if (is_volume &&
+                           ioctl(hdl->libzfs_fd, ZFS_IOC_DATASET_LIST_NEXT,
+                           &zc) == 0) {
+                               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                                   "destination has children (eg. %s)\n"
+                                   "cannot overwrite with a ZVOL"),
+                                   zc.zc_name);
+                               err = zfs_error(hdl, EZFS_WRONG_PARENT, errbuf);
+                               goto out;
+                       }
                }
 
                if ((zhp = zfs_open(hdl, name,
@@ -4051,6 +4070,20 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                        goto out;
                }
 
+               /* validate parent */
+               zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
+               if (zhp == NULL) {
+                       err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+                       goto out;
+               }
+               if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) {
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "parent '%s' is not a filesystem"), name);
+                       err = zfs_error(hdl, EZFS_WRONG_PARENT, errbuf);
+                       zfs_close(zhp);
+                       goto out;
+               }
+
                /*
                 * It is invalid to receive a properties stream that was
                 * unencrypted on the send side as a child of an encrypted
@@ -4068,23 +4101,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                    zfs_prop_to_name(ZFS_PROP_ENCRYPTION))) {
                        uint64_t crypt;
 
-                       zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
-                       if (zhp == NULL) {
-                               err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
-                               goto out;
-                       }
-
                        crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
-                       zfs_close(zhp);
 
                        if (crypt != ZIO_CRYPT_OFF) {
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "parent '%s' must not be encrypted to "
                                    "receive unenecrypted property"), name);
                                err = zfs_error(hdl, EZFS_BADPROP, errbuf);
+                               zfs_close(zhp);
                                goto out;
                        }
                }
+               zfs_close(zhp);
 
                newfs = B_TRUE;
                *cp = '/';
index d7401cdf408a0379d9e5499ccf14142889c2a787..4ed88588092271061336b8d210fc37911cf674e7 100644 (file)
@@ -290,6 +290,8 @@ libzfs_error_description(libzfs_handle_t *hdl)
        case EZFS_NO_INITIALIZE:
                return (dgettext(TEXT_DOMAIN, "there is no active "
                    "initialization"));
+       case EZFS_WRONG_PARENT:
+               return (dgettext(TEXT_DOMAIN, "invalid parent dataset"));
        case EZFS_UNKNOWN:
                return (dgettext(TEXT_DOMAIN, "unknown error"));
        default:
@@ -471,6 +473,9 @@ zfs_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
        case ZFS_ERR_IOC_ARG_BADTYPE:
                zfs_verror(hdl, EZFS_IOC_NOTSUPPORTED, fmt, ap);
                break;
+       case ZFS_ERR_WRONG_PARENT:
+               zfs_verror(hdl, EZFS_WRONG_PARENT, fmt, ap);
+               break;
        default:
                zfs_error_aux(hdl, strerror(error));
                zfs_verror(hdl, EZFS_UNKNOWN, fmt, ap);
index a8304c18df28dbf7c400cd6e13263d13c3b9cbbb..fa80e3a6181a3284b98d7d6b0facfcc64977c4cf 100644 (file)
@@ -29,6 +29,7 @@
  * Copyright (c) 2016 Actifio, Inc. All rights reserved.
  * Copyright 2017 Nexenta Systems, Inc.
  * Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
+ * Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
  */
 
 /* Portions Copyright 2010 Robert Milkowski */
@@ -1118,6 +1119,8 @@ dmu_objset_create_check(void *arg, dmu_tx_t *tx)
        dmu_objset_create_arg_t *doca = arg;
        dsl_pool_t *dp = dmu_tx_pool(tx);
        dsl_dir_t *pdd;
+       dsl_dataset_t *parentds;
+       objset_t *parentos;
        const char *tail;
        int error;
 
@@ -1146,7 +1149,30 @@ dmu_objset_create_check(void *arg, dmu_tx_t *tx)
 
        error = dsl_fs_ss_limit_check(pdd, 1, ZFS_PROP_FILESYSTEM_LIMIT, NULL,
            doca->doca_cred);
+       if (error != 0) {
+               dsl_dir_rele(pdd, FTAG);
+               return (error);
+       }
 
+       /* can't create below anything but filesystems (eg. no ZVOLs) */
+       error = dsl_dataset_hold_obj(pdd->dd_pool,
+           dsl_dir_phys(pdd)->dd_head_dataset_obj, FTAG, &parentds);
+       if (error != 0) {
+               dsl_dir_rele(pdd, FTAG);
+               return (error);
+       }
+       error = dmu_objset_from_ds(parentds, &parentos);
+       if (error != 0) {
+               dsl_dataset_rele(parentds, FTAG);
+               dsl_dir_rele(pdd, FTAG);
+               return (error);
+       }
+       if (dmu_objset_type(parentos) != DMU_OST_ZFS) {
+               dsl_dataset_rele(parentds, FTAG);
+               dsl_dir_rele(pdd, FTAG);
+               return (SET_ERROR(ZFS_ERR_WRONG_PARENT));
+       }
+       dsl_dataset_rele(parentds, FTAG);
        dsl_dir_rele(pdd, FTAG);
 
        return (error);
index 4ac6f2f17f1447ce6b606cf13aadb1c35486a913..e05b5ad821f0ec81299a057babd16addd88a43a1 100644 (file)
@@ -26,6 +26,7 @@
  * Copyright 2014 HybridCluster. All rights reserved.
  * Copyright 2016 RackTop Systems.
  * Copyright (c) 2016 Actifio, Inc. All rights reserved.
+ * Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
  */
 
 #include <sys/dmu.h>
@@ -79,6 +80,7 @@ recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds,
     uint64_t fromguid, uint64_t featureflags)
 {
        uint64_t val;
+       uint64_t children;
        int error;
        dsl_pool_t *dp = ds->ds_dir->dd_pool;
        boolean_t encrypted = ds->ds_dir->dd_crypto_obj != 0;
@@ -99,6 +101,15 @@ recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds,
        if (error != ENOENT)
                return (error == 0 ? EEXIST : error);
 
+       /* must not have children if receiving a ZVOL */
+       error = zap_count(dp->dp_meta_objset,
+           dsl_dir_phys(ds->ds_dir)->dd_child_dir_zapobj, &children);
+       if (error != 0)
+               return (error);
+       if (drba->drba_cookie->drc_drrb->drr_type != DMU_OST_ZFS &&
+           children > 0)
+               return (SET_ERROR(ZFS_ERR_WRONG_PARENT));
+
        /*
         * Check snapshot limit before receiving. We'll recheck again at the
         * end, but might as well abort before receiving if we're already over
@@ -283,6 +294,7 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx)
        } else if (error == ENOENT) {
                /* target fs does not exist; must be a full backup or clone */
                char buf[ZFS_MAX_DATASET_NAME_LEN];
+               objset_t *os;
 
                /*
                 * If it's a non-clone incremental, we are missing the
@@ -352,6 +364,17 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx)
                        return (error);
                }
 
+               /* can't recv below anything but filesystems (eg. no ZVOLs) */
+               error = dmu_objset_from_ds(ds, &os);
+               if (error != 0) {
+                       dsl_dataset_rele_flags(ds, dsflags, FTAG);
+                       return (error);
+               }
+               if (dmu_objset_type(os) != DMU_OST_ZFS) {
+                       dsl_dataset_rele_flags(ds, dsflags, FTAG);
+                       return (SET_ERROR(ZFS_ERR_WRONG_PARENT));
+               }
+
                if (drba->drba_origin != NULL) {
                        dsl_dataset_t *origin;
 
@@ -381,6 +404,7 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx)
                        dsl_dataset_rele_flags(origin,
                            dsflags, FTAG);
                }
+
                dsl_dataset_rele_flags(ds, dsflags, FTAG);
                error = 0;
        }
index 5b6ce0420dea631d7543beaf43937390f868f303..b3b677fb85f52a1088115396c1403a041d271b12 100644 (file)
@@ -25,6 +25,7 @@
  * Copyright (c) 2014 Joyent, Inc. All rights reserved.
  * Copyright (c) 2014 Spectra Logic Corporation, All rights reserved.
  * Copyright (c) 2016 Actifio, Inc. All rights reserved.
+ * Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
  */
 
 #include <sys/dmu.h>
@@ -1888,6 +1889,8 @@ dsl_dir_rename_check(void *arg, dmu_tx_t *tx)
        dsl_pool_t *dp = dmu_tx_pool(tx);
        dsl_dir_t *dd, *newparent;
        dsl_valid_rename_arg_t dvra;
+       dsl_dataset_t *parentds;
+       objset_t *parentos;
        const char *mynewname;
        int error;
 
@@ -1918,6 +1921,29 @@ dsl_dir_rename_check(void *arg, dmu_tx_t *tx)
                return (SET_ERROR(EEXIST));
        }
 
+       /* can't rename below anything but filesystems (eg. no ZVOLs) */
+       error = dsl_dataset_hold_obj(newparent->dd_pool,
+           dsl_dir_phys(newparent)->dd_head_dataset_obj, FTAG, &parentds);
+       if (error != 0) {
+               dsl_dir_rele(newparent, FTAG);
+               dsl_dir_rele(dd, FTAG);
+               return (error);
+       }
+       error = dmu_objset_from_ds(parentds, &parentos);
+       if (error != 0) {
+               dsl_dataset_rele(parentds, FTAG);
+               dsl_dir_rele(newparent, FTAG);
+               dsl_dir_rele(dd, FTAG);
+               return (error);
+       }
+       if (dmu_objset_type(parentos) != DMU_OST_ZFS) {
+               dsl_dataset_rele(parentds, FTAG);
+               dsl_dir_rele(newparent, FTAG);
+               dsl_dir_rele(dd, FTAG);
+               return (SET_ERROR(ZFS_ERR_WRONG_PARENT));
+       }
+       dsl_dataset_rele(parentds, FTAG);
+
        ASSERT3U(strnlen(ddra->ddra_newname, ZFS_MAX_DATASET_NAME_LEN),
            <, ZFS_MAX_DATASET_NAME_LEN);
        ASSERT3U(strnlen(ddra->ddra_oldname, ZFS_MAX_DATASET_NAME_LEN),
index 0dfa016845a33a4cf49d99a0ad70eb56def705c2..f4aea57d445ba4df77f803e0cdb1cb082ee8a462 100644 (file)
@@ -33,7 +33,7 @@
  * Copyright (c) 2014 Integros [integros.com]
  * Copyright 2016 Toomas Soome <tsoome@me.com>
  * Copyright (c) 2016 Actifio, Inc. All rights reserved.
- * Copyright (c) 2017, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
+ * Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
  * Copyright (c) 2017 Datto Inc. All rights reserved.
  * Copyright 2017 RackTop Systems.
  * Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
@@ -3082,8 +3082,9 @@ zfs_fill_zplprops_impl(objset_t *os, uint64_t zplver,
 
        ASSERT(zplprops != NULL);
 
+       /* parent dataset must be a filesystem */
        if (os != NULL && os->os_phys->os_type != DMU_OST_ZFS)
-               return (SET_ERROR(EINVAL));
+               return (SET_ERROR(ZFS_ERR_WRONG_PARENT));
 
        /*
         * Pull out creator prop choices, if any.
@@ -3162,15 +3163,11 @@ zfs_fill_zplprops(const char *dataset, nvlist_t *createprops,
        uint64_t zplver = ZPL_VERSION;
        objset_t *os = NULL;
        char parentname[ZFS_MAX_DATASET_NAME_LEN];
-       char *cp;
        spa_t *spa;
        uint64_t spa_vers;
        int error;
 
-       (void) strlcpy(parentname, dataset, sizeof (parentname));
-       cp = strrchr(parentname, '/');
-       ASSERT(cp != NULL);
-       cp[0] = '\0';
+       zfs_get_parent(dataset, parentname, sizeof (parentname));
 
        if ((error = spa_open(dataset, &spa, FTAG)) != 0)
                return (error);
index d3ecf6274770cbbcb8716354c5aade32160afcf8..8ab5e7033702f6ad2f07e7332f395f3e08296919 100644 (file)
@@ -887,7 +887,8 @@ tags = ['functional', 'zvol', 'zvol_cli']
 [tests/functional/zvol/zvol_misc]
 tests = ['zvol_misc_001_neg', 'zvol_misc_002_pos', 'zvol_misc_003_neg',
     'zvol_misc_004_pos', 'zvol_misc_005_neg', 'zvol_misc_006_pos',
-    'zvol_misc_snapdev', 'zvol_misc_volmode', 'zvol_misc_zil']
+    'zvol_misc_hierarchy', 'zvol_misc_snapdev', 'zvol_misc_volmode',
+    'zvol_misc_zil']
 tags = ['functional', 'zvol', 'zvol_misc']
 
 [tests/functional/zvol/zvol_swap]
index a2c95a3ebb72dd7a6f99a0d9772c5aac62faaff2..57ffbd565e66a9405ed1012367543732318c7059 100644 (file)
@@ -8,6 +8,7 @@ dist_pkgdata_SCRIPTS = \
        zvol_misc_004_pos.ksh \
        zvol_misc_005_neg.ksh \
        zvol_misc_006_pos.ksh \
+       zvol_misc_hierarchy.ksh \
        zvol_misc_snapdev.ksh \
        zvol_misc_volmode.ksh \
        zvol_misc_zil.ksh
diff --git a/tests/zfs-tests/tests/functional/zvol/zvol_misc/zvol_misc_hierarchy.ksh b/tests/zfs-tests/tests/functional/zvol/zvol_misc/zvol_misc_hierarchy.ksh
new file mode 100755 (executable)
index 0000000..1431f0b
--- /dev/null
@@ -0,0 +1,93 @@
+#!/bin/ksh -p
+#
+# CDDL HEADER START
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2018, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
+#
+
+. $STF_SUITE/include/libtest.shlib
+
+#
+# DESCRIPTION:
+# ZVOLs cannot have children datasets: verify zfs commands respect this
+# hierarchy rule.
+#
+# STRATEGY:
+# 1. Create a filesystem and a ZVOL
+# 2. Verify 'zfs recv' will not (force-)receive a ZVOL over the root dataset
+# 3. Verify 'zfs recv' cannot receive a ZVOL overwriting datasets with children
+# 4. Verify 'zfs recv' cannot receive datasets below a ZVOL
+# 5. Verify 'zfs create' cannot create datasets under a ZVOL
+# 6. Verify 'zfs rename' cannot move datasets under a ZVOL
+#
+
+verify_runnable "both"
+
+function cleanup
+{
+       destroy_pool "$poolname"
+       log_must rm -f "$vdevfile" "$streamfile_fs" "$streamfile_zvol"
+}
+
+log_assert "ZVOLs cannot have children datasets: verify zfs commands respect "\
+       "this hierarchy rule"
+log_onexit cleanup
+
+poolname="$TESTPOOL-zvol_hierarchy"
+vdevfile="$TEST_BASE_DIR/vdevfile.$$"
+streamfile_fs="$TEST_BASE_DIR/streamfile_fs.$$"
+streamfile_zvol="$TEST_BASE_DIR/streamfile_zvol.$$"
+
+# 1. Create filesystems and ZVOLs
+# NOTE: set "mountpoint=none" just to speed up the test process
+log_must truncate -s $MINVDEVSIZE "$vdevfile"
+log_must zpool create -O mountpoint=none "$poolname" "$vdevfile"
+log_must zfs create "$poolname/sendfs"
+log_must zfs create -V 1M -s "$poolname/sendvol"
+log_must zfs snapshot "$poolname/sendfs@snap"
+log_must zfs snapshot "$poolname/sendvol@snap"
+log_must eval "zfs send $poolname/sendfs@snap > $streamfile_fs"
+log_must eval "zfs send $poolname/sendvol@snap > $streamfile_zvol"
+
+# 2. Verify 'zfs recv' will not (force-)receive a ZVOL over the root dataset
+log_mustnot eval "zfs receive -F $poolname < $streamfile_zvol"
+
+# 3. Verify 'zfs recv' cannot receive a ZVOL overwriting datasets with children
+log_must zfs create "$poolname/fs"
+log_must zfs create "$poolname/fs/subfs"
+log_mustnot eval "zfs receive -F $poolname/fs < $streamfile_zvol"
+log_must zfs destroy "$poolname/fs/subfs"
+log_must eval "zfs receive -F $poolname/fs < $streamfile_zvol"
+
+# 4. Verify 'zfs recv' cannot receive datasets below a ZVOL
+log_must zfs create -V 1M -s "$poolname/volume"
+log_mustnot eval "zfs receive $poolname/volume/subfs < $streamfile_fs"
+log_mustnot eval "zfs receive $poolname/volume/subvol < $streamfile_zvol"
+
+# 5. Verify 'zfs create' cannot create datasets under a ZVOL
+log_must zfs create -V 1M -s "$poolname/createvol"
+log_mustnot zfs create "$poolname/createvol/fs"
+log_mustnot zfs create -V 1M -s "$poolname/createvol/vol"
+
+# 6. Verify 'zfs rename' cannot move datasets under a ZVOL
+log_must zfs create "$poolname/movefs"
+log_must zfs create -V 1M -s "$poolname/movevol"
+log_must zfs create -V 1M -s "$poolname/renamevol"
+log_mustnot zfs rename "$poolname/fs" "$poolname/renamevol/fs"
+log_mustnot zfs rename "$poolname/vol" "$poolname/renamevol/vol"
+
+log_pass "ZVOLs cannot have children datasets and zfs commands enforce this "\
+       "rule"