Beispiel #1
0
    def _getRoots(self):
        for (header, buf) in self._walkTree(BTRFS_ROOT_TREE_OBJECTID):
            if header.type == objectTypeKeys['BTRFS_ROOT_BACKREF_KEY']:
                data = Structure(
                    (btrfs_root_ref, 'ref'),
                    (t.char, 'name', header.len - btrfs_root_ref.size),
                ).read(buf)

                info = data.ref
                name = data.name

                directory = self.INO_LOOKUP(treeid=header.offset,
                                            objectid=info.dirid)

                logger.debug("%s: %s %s", name, pretty(info),
                             pretty(directory))

                self.volumes[header.objectid]._addLink(
                    header.offset,
                    info.dirid,
                    info.sequence,
                    directory.name,
                    name,
                )
            elif header.type == objectTypeKeys['BTRFS_ROOT_ITEM_KEY']:
                if header.len == btrfs_root_item.size:
                    info = btrfs_root_item.read(buf)
                elif header.len == btrfs_root_item_v0.size:
                    info = btrfs_root_item_v0.read(buf)
                else:
                    assert False, header.len

                if ((header.objectid >= BTRFS_FIRST_FREE_OBJECTID
                     and header.objectid <= BTRFS_LAST_FREE_OBJECTID)
                        or header.objectid == BTRFS_FS_TREE_OBJECTID):
                    assert header.objectid not in self.volumes, header.objectid
                    self.volumes[header.objectid] = _Volume(
                        self,
                        header.objectid,
                        header.offset,
                        info,
                    )
Beispiel #2
0
# import crc32c  # This provides *slow* btrfs crc32c
import crcmod.predefined  # This provides fast compiled extension
crc32c = crcmod.predefined.mkPredefinedCrcFun("crc-32c")

import logging
import struct

logger = logging.getLogger(__name__)
# logger.setLevel('DEBUG')

BTRFS_SEND_STREAM_MAGIC = "btrfs-stream\0"
BTRFS_SEND_STREAM_VERSION = 1

btrfs_stream_header = Structure(
    (t.char, 'magic', len(BTRFS_SEND_STREAM_MAGIC)),
    (t.le32, 'version'),
    packed=True
)

btrfs_cmd_header = Structure(
    # /* len excluding the header */
    (t.le32, 'len'),
    (t.le16, 'cmd'),
    # /* crc including the header with zero crc field */
    (t.le32, 'crc'),
    packed=True
)

btrfs_tlv_header = Structure(
    (t.le16, 'tlv_type'),
    # /* len excluding the header */
Beispiel #3
0
class FileSystem(ioctl.Device):
    """ Mounted file system descriptor for ioctl actions. """
    def __init__(self, path):
        """ Initialize. """
        super(FileSystem, self).__init__(path)

        self.defaultID = None
        self.devices = []
        self.volumes = {}
        self.mounts = {}

    @property
    def subvolumes(self):
        """ Subvolumes contained in this mount. """
        self.SYNC()
        self._getDevices()
        self._getRoots()
        self._getMounts()
        self._getUsage()

        volumes = self.volumes.values()
        volumes.sort(key=(lambda v: v.fullPath))
        return volumes

    def _rescanSizes(self, force=True):
        """ Zero and recalculate quota sizes to subvolume sizes will be correct. """
        status = self.QUOTA_CTL(cmd=BTRFS_QUOTA_CTL_ENABLE).status
        logger.debug("CTL Status: %s", hex(status))

        status = self.QUOTA_RESCAN_STATUS()
        logger.debug("RESCAN Status: %s", status)

        if not status.flags:
            if not force:
                return
            self.QUOTA_RESCAN()

        logger.warn("Waiting for btrfs quota usage scan...")
        self.QUOTA_RESCAN_WAIT()

    def _getDevices(self):
        if self.devices:
            return

        fs = self.FS_INFO()
        for i in xrange(1, fs.max_id + 1):
            try:
                dev = self.DEV_INFO(devid=i)
            except IOError as error:
                if error.errno == 19:
                    continue
                raise
            self.devices.append(dev.path)

    def _getMounts(self):

        # This is a "fake" device, created separately for each subvol
        # See https://bugzilla.redhat.com/show_bug.cgi?id=711881
        # myDevice = os.stat(self.path).st_dev
        # myDevice = (os.major(myDevice), os.minor(myDevice))

        if self.mounts:
            return

        if self.defaultID is None:
            logger.warn("Default subvolume not identified")
            defaultSubvol = ""
        else:
            defaultSubvol = self.volumes[self.defaultID].fullPath.rstrip("/")

        logger.debug("Default subvolume: %s", defaultSubvol)

        with open("/proc/self/mountinfo") as mtab:
            for line in mtab:
                # (dev, path, fs, opts, freq, passNum) = line.split()  # /etc/mtab

                (left, _, right) = line.partition(" - ")
                (mountID, parentID, devIDs, subvol, path,
                 mountOpts) = left.split()[:6]
                (fs, dev, superOpts) = right.split()

                if fs != "btrfs":
                    continue
                if dev not in self.devices:
                    logger.debug(
                        "%s device (%s) not in %s devices (%s)",
                        path,
                        dev,
                        self.path,
                        self.devices,
                    )
                    continue

                self.mounts[subvol] = path
                logger.debug("%s: %s", subvol, path)

    Key = collections.namedtuple('Key', ('objectid', 'type', 'offset'))

    Key.first = Key(
        objectid=0,
        type=0,
        offset=0,
    )

    Key.last = Key(
        objectid=BTRFS_LAST_FREE_OBJECTID,
        type=t.max_u32,
        offset=t.max_u64,
    )

    Key.next = (
        lambda key: FileSystem.Key(key.objectid, key.type, key.offset + 1))

    def _walkTree(self, treeid):
        key = FileSystem.Key.first

        while True:
            # Returned objects seem to be monotonically increasing in (objectid, type, offset)
            # min and max values are *not* filters.
            result = self.TREE_SEARCH(key=dict(
                tree_id=treeid,
                min_type=key.type,
                max_type=FileSystem.Key.last.type,
                min_objectid=key.objectid,
                max_objectid=FileSystem.Key.last.objectid,
                min_offset=key.offset,
                max_offset=FileSystem.Key.last.offset,
                min_transid=0,
                max_transid=t.max_u64,
                nr_items=4096,
            ), )
            # logger.debug("Search key result: \n%s", pretty(result.key))

            buf = ioctl.Buffer(result.buf)
            results = result.key.nr_items

            # logger.debug("Reading %d nodes from %d bytes", results, buf.len)
            if results == 0:
                break

            for _ in xrange(results):
                # assert buf.len >= btrfs_ioctl_search_header.size, buf.len
                data = buf.read(btrfs_ioctl_search_header)

                # logger.debug("Object %d: %s", i, pretty(data))

                # assert buf.len >= data.len, (buf.len, data.len)
                yield (data, buf.readBuffer(data.len))

                key = FileSystem.Key(data.objectid, data.type,
                                     data.offset).next()

    def _getRoots(self):
        for (header, buf) in self._walkTree(BTRFS_ROOT_TREE_OBJECTID):
            if header.type == objectTypeKeys['BTRFS_ROOT_BACKREF_KEY']:
                info = buf.read(btrfs_root_ref)
                name = buf.readView(info.name_len).tobytes()

                directory = self.INO_LOOKUP(treeid=header.offset,
                                            objectid=info.dirid)

                logger.debug("%s: %s %s", name, pretty(info),
                             pretty(directory))

                self.volumes[header.objectid]._addLink(
                    header.offset,
                    info.dirid,
                    info.sequence,
                    directory.name,
                    name,
                )
            elif header.type == objectTypeKeys['BTRFS_ROOT_ITEM_KEY']:
                if header.len == btrfs_root_item.size:
                    info = buf.read(btrfs_root_item)
                elif header.len == btrfs_root_item_v0.size:
                    info = buf.read(btrfs_root_item_v0)
                else:
                    assert False, header.len

                if ((header.objectid >= BTRFS_FIRST_FREE_OBJECTID
                     and header.objectid <= BTRFS_LAST_FREE_OBJECTID)
                        or header.objectid == BTRFS_FS_TREE_OBJECTID):
                    assert header.objectid not in self.volumes, header.objectid
                    self.volumes[header.objectid] = _Volume(
                        self,
                        header.objectid,
                        header.offset,
                        info,
                    )
            elif header.type == objectTypeKeys['BTRFS_DIR_ITEM_KEY']:
                info = buf.read(btrfs_dir_item)
                name = buf.readView(info.name_len).tobytes()
                if name == "default":
                    self.defaultID = info.location.objectid
                logger.debug("Found dir '%s' is %d", name, self.defaultID)

    def _getUsage(self):
        try:
            self._rescanSizes(False)
            self._unsafeGetUsage()
        except (IOError, _BtrfsError) as error:
            logger.warn("%s", error)
            self._rescanSizes()
            self._unsafeGetUsage()

    def _unsafeGetUsage(self):
        for (header, buf) in self._walkTree(BTRFS_QUOTA_TREE_OBJECTID):
            # logger.debug("%s %s", objectTypeNames[header.type], header)

            if header.type == objectTypeKeys['BTRFS_QGROUP_INFO_KEY']:
                data = btrfs_qgroup_info_item.read(buf)
                try:
                    vol = self.volumes[header.offset]
                    vol.totalSize = data.referenced
                    vol.exclusiveSize = data.exclusive

                    if (data.referenced < 0 or data.exclusive < 0
                            or data.referenced < data.exclusive):
                        raise _BtrfsError(
                            "Btrfs returned corrupt size of %s (%s exclusive) for %s"
                            % (
                                humanize(vol.totalSize or -1),
                                humanize(vol.exclusiveSize or -1),
                                vol.fullPath,
                            ))

                except KeyError:
                    pass

            elif header.type == objectTypeKeys['BTRFS_QGROUP_LIMIT_KEY']:
                data = btrfs_qgroup_limit_item.read(buf)
            elif header.type == objectTypeKeys['BTRFS_QGROUP_RELATION_KEY']:
                data = None
            else:
                data = None

            # logger.debug('%s', pretty(data))

    def _getDevInfo(self):
        return self.DEV_INFO(devid=1, uuid="")

    def _getFSInfo(self):
        return self.FS_INFO()

    volid_struct = Structure((t.u64, 'id'))

    SYNC = Control.IO(8)
    TREE_SEARCH = Control.IOWR(17, btrfs_ioctl_search_args)
    INO_LOOKUP = Control.IOWR(18, btrfs_ioctl_ino_lookup_args)
    DEFAULT_SUBVOL = Control.IOW(19, volid_struct)
    DEV_INFO = Control.IOWR(30, btrfs_ioctl_dev_info_args)
    FS_INFO = Control.IOR(31, btrfs_ioctl_fs_info_args)
    QUOTA_CTL = Control.IOWR(40, btrfs_ioctl_quota_ctl_args)
    QUOTA_RESCAN = Control.IOW(44, btrfs_ioctl_quota_rescan_args)
    QUOTA_RESCAN_STATUS = Control.IOR(45, btrfs_ioctl_quota_rescan_args)
    QUOTA_RESCAN_WAIT = Control.IO(46)
Beispiel #4
0
    if u is None:
        return ''

    return "".join(u.split('-')).decode('hex')


BTRFS_DEVICE_PATH_NAME_MAX = 1024
BTRFS_SUBVOL_CREATE_ASYNC = (1 << 0)
BTRFS_SUBVOL_RDONLY = (1 << 1)
BTRFS_SUBVOL_QGROUP_INHERIT = (1 << 2)
BTRFS_FSID_SIZE = 16
BTRFS_UUID_SIZE = 16

BTRFS_INO_LOOKUP_PATH_MAX = 4080
btrfs_ioctl_ino_lookup_args = Structure(
    (t.u64, 'treeid'), (t.u64, 'objectid'),
    (t.char, 'name', BTRFS_INO_LOOKUP_PATH_MAX, t.readString, t.writeString),
    packed=True)

btrfs_ioctl_search_key = Structure(
    (t.u64, 'tree_id'), (t.u64, 'min_objectid'), (t.u64, 'max_objectid'),
    (t.u64, 'min_offset'), (t.u64, 'max_offset'), (t.u64, 'min_transid'),
    (t.u64, 'max_transid'), (t.u32, 'min_type'), (t.u32, 'max_type'),
    (t.u32, 'nr_items'), (t.u32, 'unused'), (t.u64, 'unused1'),
    (t.u64, 'unused2'), (t.u64, 'unused3'), (t.u64, 'unused4'),
    packed=True)

btrfs_ioctl_search_header = Structure((t.u64, 'transid'), (t.u64, 'objectid'),
                                      (t.u64, 'offset'), (t.u32, 'type'),
                                      (t.u32, 'len'),
                                      packed=True)