Ejemplo n.º 1
0
    def delete_snapshot(self, snapshot: Subvolume) -> None:
        logger = self._logger
        filesystem_path = snapshot.filesystem_path
        logical_path = snapshot.logical_path

        try:
            filesystem_path_str = str(filesystem_path)
            is_subvolume = filesystem_path.exists() and btrfsutil.is_subvolume(
                filesystem_path_str)

            if is_subvolume:
                root_dir_str = str(constants.ROOT_DIR)
                num_id = snapshot.num_id
                deleted_subvolumes = checked_cast(
                    list[int], btrfsutil.deleted_subvolumes(root_dir_str))

                if num_id not in deleted_subvolumes:
                    logger.info(f"Deleting the '{logical_path}' snapshot.")

                    btrfsutil.delete_subvolume(filesystem_path_str)
                else:
                    logger.warning(
                        f"The '{logical_path}' snapshot has already "
                        "been deleted but not yet cleaned up.")
            else:
                logger.warning(
                    f"The '{filesystem_path}' directory is not a subvolume.")
        except btrfsutil.BtrfsUtilError as e:
            logger.exception("btrfsutil call failed!")
            raise SubvolumeError(
                f"Could not delete the '{logical_path}' snapshot!") from e
Ejemplo n.º 2
0
    def test_create_snapshot(self):
        subvol = os.path.join(self.mountpoint, 'subvol')

        btrfsutil.create_subvolume(subvol)
        os.mkdir(os.path.join(subvol, 'dir'))

        for i, arg in enumerate(self.path_or_fd(subvol)):
            with self.subTest(type=type(arg)):
                snapshots_dir = os.path.join(self.mountpoint, 'snapshots{}'.format(i))
                os.mkdir(snapshots_dir)
                snapshot = os.path.join(snapshots_dir, 'snapshot')

                btrfsutil.create_snapshot(subvol, snapshot + '1')
                self.assertTrue(btrfsutil.is_subvolume(snapshot + '1'))
                self.assertTrue(os.path.exists(os.path.join(snapshot + '1', 'dir')))

                btrfsutil.create_snapshot(subvol, (snapshot + '2').encode())
                self.assertTrue(btrfsutil.is_subvolume(snapshot + '2'))
                self.assertTrue(os.path.exists(os.path.join(snapshot + '2', 'dir')))

                if HAVE_PATH_LIKE:
                    btrfsutil.create_snapshot(subvol, PurePath(snapshot + '3'))
                    self.assertTrue(btrfsutil.is_subvolume(snapshot + '3'))
                    self.assertTrue(os.path.exists(os.path.join(snapshot + '3', 'dir')))

        nested_subvol = os.path.join(subvol, 'nested')
        more_nested_subvol = os.path.join(nested_subvol, 'more_nested')
        btrfsutil.create_subvolume(nested_subvol)
        btrfsutil.create_subvolume(more_nested_subvol)
        os.mkdir(os.path.join(more_nested_subvol, 'nested_dir'))

        snapshot = os.path.join(self.mountpoint, 'snapshot')

        btrfsutil.create_snapshot(subvol, snapshot + '1')
        # Dummy subvolume.
        self.assertEqual(os.stat(os.path.join(snapshot + '1', 'nested')).st_ino, 2)
        self.assertFalse(os.path.exists(os.path.join(snapshot + '1', 'nested', 'more_nested')))

        btrfsutil.create_snapshot(subvol, snapshot + '2', recursive=True)
        self.assertTrue(os.path.exists(os.path.join(snapshot + '2', 'nested/more_nested/nested_dir')))

        transid = btrfsutil.create_snapshot(subvol, snapshot + '3', recursive=True, no_wait=True)
        self.assertTrue(os.path.exists(os.path.join(snapshot + '3', 'nested/more_nested/nested_dir')))
        self.assertGreater(transid, 0)

        btrfsutil.create_snapshot(subvol, snapshot + '4', read_only=True)
        self.assertTrue(btrfsutil.get_subvolume_read_only(snapshot + '4'))
Ejemplo n.º 3
0
    def test_create_snapshot(self):
        subvol = os.path.join(self.mountpoint, 'subvol')

        btrfsutil.create_subvolume(subvol)
        os.mkdir(os.path.join(subvol, 'dir'))

        for i, arg in enumerate(self.path_or_fd(subvol)):
            with self.subTest(type=type(arg)):
                snapshots_dir = os.path.join(self.mountpoint, 'snapshots{}'.format(i))
                os.mkdir(snapshots_dir)
                snapshot = os.path.join(snapshots_dir, 'snapshot')

                btrfsutil.create_snapshot(subvol, snapshot + '1')
                self.assertTrue(btrfsutil.is_subvolume(snapshot + '1'))
                self.assertTrue(os.path.exists(os.path.join(snapshot + '1', 'dir')))

                btrfsutil.create_snapshot(subvol, (snapshot + '2').encode())
                self.assertTrue(btrfsutil.is_subvolume(snapshot + '2'))
                self.assertTrue(os.path.exists(os.path.join(snapshot + '2', 'dir')))

                if HAVE_PATH_LIKE:
                    btrfsutil.create_snapshot(subvol, PurePath(snapshot + '3'))
                    self.assertTrue(btrfsutil.is_subvolume(snapshot + '3'))
                    self.assertTrue(os.path.exists(os.path.join(snapshot + '3', 'dir')))

        nested_subvol = os.path.join(subvol, 'nested')
        more_nested_subvol = os.path.join(nested_subvol, 'more_nested')
        btrfsutil.create_subvolume(nested_subvol)
        btrfsutil.create_subvolume(more_nested_subvol)
        os.mkdir(os.path.join(more_nested_subvol, 'nested_dir'))

        snapshot = os.path.join(self.mountpoint, 'snapshot')

        btrfsutil.create_snapshot(subvol, snapshot + '1')
        # Dummy subvolume.
        self.assertEqual(os.stat(os.path.join(snapshot + '1', 'nested')).st_ino, 2)
        self.assertFalse(os.path.exists(os.path.join(snapshot + '1', 'nested', 'more_nested')))

        btrfsutil.create_snapshot(subvol, snapshot + '2', recursive=True)
        self.assertTrue(os.path.exists(os.path.join(snapshot + '2', 'nested/more_nested/nested_dir')))

        transid = btrfsutil.create_snapshot(subvol, snapshot + '3', recursive=True, async_=True)
        self.assertTrue(os.path.exists(os.path.join(snapshot + '3', 'nested/more_nested/nested_dir')))
        self.assertGreater(transid, 0)

        btrfsutil.create_snapshot(subvol, snapshot + '4', read_only=True)
        self.assertTrue(btrfsutil.get_subvolume_read_only(snapshot + '4'))
Ejemplo n.º 4
0
    def test_is_subvolume(self):
        dir = os.path.join(self.mountpoint, 'foo')
        os.mkdir(dir)

        for arg in self.path_or_fd(self.mountpoint):
            with self.subTest(type=type(arg)):
                self.assertTrue(btrfsutil.is_subvolume(arg))
        for arg in self.path_or_fd(dir):
            with self.subTest(type=type(arg)):
                self.assertFalse(btrfsutil.is_subvolume(arg))

        with self.assertRaises(btrfsutil.BtrfsUtilError) as e:
            btrfsutil.is_subvolume(os.path.join(self.mountpoint, 'bar'))
        # This is a bit of an implementation detail, but really this is testing
        # that the exception is initialized correctly.
        self.assertEqual(e.exception.btrfsutilerror, btrfsutil.ERROR_STATFS_FAILED)
        self.assertEqual(e.exception.errno, errno.ENOENT)
Ejemplo n.º 5
0
    def test_is_subvolume(self):
        dir = os.path.join(self.mountpoint, 'foo')
        os.mkdir(dir)

        for arg in self.path_or_fd(self.mountpoint):
            with self.subTest(type=type(arg)):
                self.assertTrue(btrfsutil.is_subvolume(arg))
        for arg in self.path_or_fd(dir):
            with self.subTest(type=type(arg)):
                self.assertFalse(btrfsutil.is_subvolume(arg))

        with self.assertRaises(btrfsutil.BtrfsUtilError) as e:
            btrfsutil.is_subvolume(os.path.join(self.mountpoint, 'bar'))
        # This is a bit of an implementation detail, but really this is testing
        # that the exception is initialized correctly.
        self.assertEqual(e.exception.btrfsutilerror, btrfsutil.ERROR_STATFS_FAILED)
        self.assertEqual(e.exception.errno, errno.ENOENT)
Ejemplo n.º 6
0
    def _create_writable_snapshot_from(self, source: Subvolume) -> Subvolume:
        logger = self._logger
        snapshot_manipulation = self.package_config.snapshot_manipulation
        destination_directory = snapshot_manipulation.destination_directory

        if not destination_directory.exists():
            directory_permissions = constants.SNAPSHOTS_ROOT_DIR_PERMISSIONS
            octal_permissions = "{0:o}".format(directory_permissions)

            try:
                logger.info(
                    f"Creating the '{destination_directory}' destination "
                    f"directory with {octal_permissions} permissions.")

                destination_directory.mkdir(mode=directory_permissions,
                                            parents=True)
            except OSError as e:
                logger.exception("Path.mkdir() call failed!")
                raise SubvolumeError(
                    f"Could not create the '{destination_directory}' destination directory!"
                ) from e

        destination = source.to_destination(destination_directory)
        source_logical_path = source.logical_path
        snapshot_directory = destination.filesystem_path

        try:
            logger.info(
                "Creating a new writable snapshot from the read-only "
                f"'{source_logical_path}' snapshot at '{snapshot_directory}'.")

            snapshot_directory_str = str(snapshot_directory)
            is_subvolume = snapshot_directory.exists(
            ) and btrfsutil.is_subvolume(snapshot_directory_str)

            if not is_subvolume:
                source_filesystem_path_str = str(source.filesystem_path)

                btrfsutil.create_snapshot(source_filesystem_path_str,
                                          snapshot_directory_str,
                                          read_only=False)
            else:
                logger.warning(
                    f"The '{snapshot_directory}' directory is already a subvolume."
                )
        except btrfsutil.BtrfsUtilError as e:
            logger.exception("btrfsutil call failed!")
            raise SubvolumeError(
                f"Could not create a new writable snapshot at '{snapshot_directory}'!"
            ) from e

        writable_snapshot = self.get_subvolume_from(snapshot_directory)

        return none_throws(writable_snapshot).as_newly_created_from(source)
Ejemplo n.º 7
0
    def test_create_subvolume(self):
        subvol = os.path.join(self.mountpoint, 'subvol')

        btrfsutil.create_subvolume(subvol + '1')
        self.assertTrue(btrfsutil.is_subvolume(subvol + '1'))
        btrfsutil.create_subvolume((subvol + '2').encode())
        self.assertTrue(btrfsutil.is_subvolume(subvol + '2'))
        if HAVE_PATH_LIKE:
            btrfsutil.create_subvolume(PurePath(subvol + '3'))
            self.assertTrue(btrfsutil.is_subvolume(subvol + '3'))

        pwd = os.getcwd()
        try:
            os.chdir(self.mountpoint)
            btrfsutil.create_subvolume('subvol4')
            self.assertTrue(btrfsutil.is_subvolume('subvol4'))
        finally:
            os.chdir(pwd)

        btrfsutil.create_subvolume(subvol + '5/')
        self.assertTrue(btrfsutil.is_subvolume(subvol + '5'))

        btrfsutil.create_subvolume(subvol + '6//')
        self.assertTrue(btrfsutil.is_subvolume(subvol + '6'))

        transid = btrfsutil.create_subvolume(subvol + '7', no_wait=True)
        self.assertTrue(btrfsutil.is_subvolume(subvol + '7'))
        self.assertGreater(transid, 0)

        # Test creating subvolumes under '/' in a chroot.
        pid = os.fork()
        if pid == 0:
            try:
                os.chroot(self.mountpoint)
                os.chdir('/')
                btrfsutil.create_subvolume('/subvol8')
                self.assertTrue(btrfsutil.is_subvolume('/subvol8'))
                with self.assertRaises(btrfsutil.BtrfsUtilError):
                    btrfsutil.create_subvolume('/')
                os._exit(0)
            except Exception:
                traceback.print_exc()
                os._exit(1)
        wstatus = os.waitpid(pid, 0)[1]
        self.assertTrue(os.WIFEXITED(wstatus))
        self.assertEqual(os.WEXITSTATUS(wstatus), 0)
Ejemplo n.º 8
0
    def test_create_subvolume(self):
        subvol = os.path.join(self.mountpoint, 'subvol')

        btrfsutil.create_subvolume(subvol + '1')
        self.assertTrue(btrfsutil.is_subvolume(subvol + '1'))
        btrfsutil.create_subvolume((subvol + '2').encode())
        self.assertTrue(btrfsutil.is_subvolume(subvol + '2'))
        if HAVE_PATH_LIKE:
            btrfsutil.create_subvolume(PurePath(subvol + '3'))
            self.assertTrue(btrfsutil.is_subvolume(subvol + '3'))

        pwd = os.getcwd()
        try:
            os.chdir(self.mountpoint)
            btrfsutil.create_subvolume('subvol4')
            self.assertTrue(btrfsutil.is_subvolume('subvol4'))
        finally:
            os.chdir(pwd)

        btrfsutil.create_subvolume(subvol + '5/')
        self.assertTrue(btrfsutil.is_subvolume(subvol + '5'))

        btrfsutil.create_subvolume(subvol + '6//')
        self.assertTrue(btrfsutil.is_subvolume(subvol + '6'))

        transid = btrfsutil.create_subvolume(subvol + '7', async=True)
        self.assertTrue(btrfsutil.is_subvolume(subvol + '7'))
        self.assertGreater(transid, 0)

        # Test creating subvolumes under '/' in a chroot.
        pid = os.fork()
        if pid == 0:
            try:
                os.chroot(self.mountpoint)
                os.chdir('/')
                btrfsutil.create_subvolume('/subvol8')
                self.assertTrue(btrfsutil.is_subvolume('/subvol8'))
                with self.assertRaises(btrfsutil.BtrfsUtilError):
                    btrfsutil.create_subvolume('/')
                os._exit(0)
            except Exception:
                traceback.print_exc()
                os._exit(1)
        wstatus = os.waitpid(pid, 0)[1]
        self.assertTrue(os.WIFEXITED(wstatus))
        self.assertEqual(os.WEXITSTATUS(wstatus), 0)
Ejemplo n.º 9
0
def scan_dir(p: Path, filter_func=None):
    """Scans Path p for btrfs snapshots, returns the paths of all found snapshots as a list or None if no snapshots were found.
    Snapshots can be filtered by passing a function as filter_func that takes a path and returns a boolean expression.
    If such a function is present, only those snapshots where the filter function returns true will be returned in the snapshot list."""
    snapshots = list()
    for f in p.iterdir():
        if btrfsutil.is_subvolume(f):
            if filter_func is not None:
                if filter_func(f):
                    snapshots.append(f)
            else:
                snapshots.append(f)
    if len(snapshots) < 1:
        return None
    return snapshots
Ejemplo n.º 10
0
    def get_subvolume_from(self, filesystem_path: Path) -> Optional[Subvolume]:
        logger = self._logger

        if not filesystem_path.exists():
            raise SubvolumeError(
                f"The '{filesystem_path}' path does not exist!")

        if not filesystem_path.is_dir():
            raise SubvolumeError(
                f"The '{filesystem_path}' path does not represent a directory!"
            )

        try:
            filesystem_path_str = str(filesystem_path)

            if btrfsutil.is_subvolume(filesystem_path_str):
                subvolume_id = btrfsutil.subvolume_id(filesystem_path_str)
                subvolume_path = btrfsutil.subvolume_path(
                    filesystem_path_str, subvolume_id)
                subvolume_read_only = btrfsutil.get_subvolume_read_only(
                    filesystem_path_str)
                subvolume_info = btrfsutil.subvolume_info(
                    filesystem_path_str, subvolume_id)
                self_uuid = default_if_none(
                    try_convert_bytes_to_uuid(subvolume_info.uuid),
                    constants.EMPTY_UUID,
                )
                parent_uuid = default_if_none(
                    try_convert_bytes_to_uuid(subvolume_info.parent_uuid),
                    constants.EMPTY_UUID,
                )

                return Subvolume(
                    filesystem_path,
                    subvolume_path,
                    datetime.fromtimestamp(subvolume_info.otime),
                    UuidRelation(self_uuid, parent_uuid),
                    NumIdRelation(subvolume_info.id, subvolume_info.parent_id),
                    subvolume_read_only,
                )
        except btrfsutil.BtrfsUtilError as e:
            logger.exception("btrfsutil call failed!")
            raise SubvolumeError(
                f"Could not initialize the subvolume for '{filesystem_path}'!"
            ) from e

        return None