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, )
# 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 */
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)
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)