diff options
author | Holger Hoffstätte <holger@applied-asynchrony.com> | 2023-06-13 16:03:40 +0200 |
---|---|---|
committer | Sam James <sam@gentoo.org> | 2023-06-14 03:51:04 +0100 |
commit | 5c08f76951bd55ac4aea016ec515886acc8e1f76 (patch) | |
tree | c77e2e525f103f8f4049bb4a703df6019bfdaf0a /dev-python/btrfs | |
parent | app-backup/duplicity: add pypi upstream metadata (diff) | |
download | gentoo-5c08f76951bd55ac4aea016ec515886acc8e1f76.tar.gz gentoo-5c08f76951bd55ac4aea016ec515886acc8e1f76.tar.bz2 gentoo-5c08f76951bd55ac4aea016ec515886acc8e1f76.zip |
dev-python/btrfs: new package, add v13 + support for block-group-tree
Closes: https://bugs.gentoo.org/908452
Signed-off-by: Holger Hoffstätte <holger@applied-asynchrony.com>
Closes: https://github.com/gentoo/gentoo/pull/31416
Signed-off-by: Sam James <sam@gentoo.org>
Diffstat (limited to 'dev-python/btrfs')
9 files changed, 448 insertions, 0 deletions
diff --git a/dev-python/btrfs/Manifest b/dev-python/btrfs/Manifest new file mode 100644 index 000000000000..8c584ff7f6ff --- /dev/null +++ b/dev-python/btrfs/Manifest @@ -0,0 +1 @@ +DIST btrfs-13.gh.tar.gz 93110 BLAKE2B 286e5d1d9aa66ce5072f2fe144b2da6bbcde65dd90eb21efe1eb69b9aaa3230d76e8b8baffcfd269ce5d53e1004b5fc6c5546125bc1a5c26413ba8abc66f068a SHA512 5117b9c6542fe191eccbed56a15bb5d8eadc2d3c0edd986a3e1a33d0b63f58c3db5e7f6d234c43a83d1cc74eea3e106c5882a92926c3087601edc36e6672721f diff --git a/dev-python/btrfs/btrfs-13.ebuild b/dev-python/btrfs/btrfs-13.ebuild new file mode 100644 index 000000000000..4d688620e0b6 --- /dev/null +++ b/dev-python/btrfs/btrfs-13.ebuild @@ -0,0 +1,39 @@ +# Copyright 2023 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +EAPI=8 + +DISTUTILS_USE_PEP517=setuptools +PYTHON_COMPAT=( python3_{10..12} ) + +inherit distutils-r1 + +DESCRIPTION="Python module to inspect btrfs filesystems" +HOMEPAGE="https://github.com/knorrie/python-btrfs" +SRC_URI="https://github.com/knorrie/python-${PN}/archive/v${PV}.tar.gz -> ${P}.gh.tar.gz" + +LICENSE="LGPL-3" +SLOT="0" +KEYWORDS="~amd64" +IUSE="examples" + +S="${WORKDIR}/python-${P}" + +PATCHES=( + "${FILESDIR}"/${PV}-001-docs-do-not-monkey-patch-for-sphinx-4.patch + "${FILESDIR}"/${PV}-002-show_file_csum-fix-vaddr-computation.patch + "${FILESDIR}"/${PV}-003-ioctl-fix-documentation-error-in-FeatureFlags.patch + "${FILESDIR}"/${PV}-004-add-Block-Group-Tree.patch + "${FILESDIR}"/${PV}-005-ctree-FileSystem-add-block_groups-function.patch + "${FILESDIR}"/${PV}-006-btrfs-search-metadata-use-FileSystem-block_groups.patch +) + +python_install_all() { + if use examples; then + # skip symlink meant for development + rm examples/btrfs + dodoc -r examples + fi + + distutils-r1_python_install_all +} diff --git a/dev-python/btrfs/files/13-001-docs-do-not-monkey-patch-for-sphinx-4.patch b/dev-python/btrfs/files/13-001-docs-do-not-monkey-patch-for-sphinx-4.patch new file mode 100644 index 000000000000..f74d1502aa66 --- /dev/null +++ b/dev-python/btrfs/files/13-001-docs-do-not-monkey-patch-for-sphinx-4.patch @@ -0,0 +1,146 @@ + +Patch from: +https://github.com/knorrie/python-btrfs/commit/892bc3d8882d62bf91df5de9a11569b6cffec3cd + +From 892bc3d8882d62bf91df5de9a11569b6cffec3cd Mon Sep 17 00:00:00 2001 +From: Hans van Kranenburg <hans@knorrie.org> +Date: Sun, 8 Aug 2021 16:40:41 +0200 +Subject: [PATCH] docs: don't monkey patch for sphinx >= 4 + +There's a really long existing bug in sphinx that causes it to generate +cross references in places where it really should not: + https://github.com/sphinx-doc/sphinx/issues/2549 + +In docs/source/conf.py there's a monkey patch for this, from line 363 +and further. + +It looks like a fix for this was implemented in v4: + https://github.com/sphinx-doc/sphinx/pull/8638/commits + + -$ git tag --contains 918086b5590763663c1627578085e528f1358384 + v4.0.0 + [...] + +The function that is replaced while applying the workaround has been +changed recently, causing breakage (TypeError: patched_make_field() got +an unexpected keyword argument 'inliner'). + + -$ git tag --contains 4534d2d1a5755c8cbc9ef4327eab7e34a85a7de8 + v4.1.0 + [...] + +So, combining this information, it seems that when the major version +number of the sphinx lib being used is >= 4, the monkey patch part +should be skipped. + +Fixes: https://github.com/knorrie/python-btrfs/issues/31 +--- a/docs/source/conf.py ++++ b/docs/source/conf.py +@@ -359,54 +359,56 @@ def get_version(): + + autodoc_member_order = 'bysource' + +- +-from docutils import nodes +-from sphinx.util.docfields import TypedField +-from sphinx import addnodes +- +- +-def patched_make_field(self, +- types, # type: Dict[unicode, List[nodes.Node]] +- domain, # type: unicode +- items, # type: Tuple +- env=None, # type: BuildEnvironment +- ): +- # type: (...) -> nodes.field +- def handle_item(fieldarg, content): +- # type: (unicode, unicode) -> nodes.paragraph +- par = nodes.paragraph() +- # Adding the next line, and taking out the one after should prevent +- # ivars from getting incorrect cross-references. +- par += addnodes.literal_strong('', fieldarg) +- #par.extend(self.make_xrefs(self.rolename, domain, fieldarg, +- # addnodes.literal_strong, env=env)) +- if fieldarg in types: +- par += nodes.Text(' (') +- # NOTE: using .pop() here to prevent a single type node to be +- # inserted twice into the doctree, which leads to +- # inconsistencies later when references are resolved +- fieldtype = types.pop(fieldarg) +- if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text): +- typename = u''.join(n.astext() for n in fieldtype) +- par.extend(self.make_xrefs(self.typerolename, domain, typename, +- addnodes.literal_emphasis, env=env)) +- else: +- par += fieldtype +- par += nodes.Text(')') +- par += nodes.Text(' -- ') +- par += content +- return par +- +- fieldname = nodes.field_name('', self.label) +- if len(items) == 1 and self.can_collapse: +- fieldarg, content = items[0] +- bodynode = handle_item(fieldarg, content) +- else: +- bodynode = self.list_type() +- for fieldarg, content in items: +- bodynode += nodes.list_item('', handle_item(fieldarg, content)) +- fieldbody = nodes.field_body('', bodynode) +- return nodes.field('', fieldname, fieldbody) +- +- +-TypedField.make_field = patched_make_field ++import sphinx ++ ++if int(sphinx.__version__.split('.')[0]) < 4: ++ from docutils import nodes ++ from sphinx.util.docfields import TypedField ++ from sphinx import addnodes ++ ++ ++ def patched_make_field(self, ++ types, # type: Dict[unicode, List[nodes.Node]] ++ domain, # type: unicode ++ items, # type: Tuple ++ env=None, # type: BuildEnvironment ++ ): ++ # type: (...) -> nodes.field ++ def handle_item(fieldarg, content): ++ # type: (unicode, unicode) -> nodes.paragraph ++ par = nodes.paragraph() ++ # Adding the next line, and taking out the one after should prevent ++ # ivars from getting incorrect cross-references. ++ par += addnodes.literal_strong('', fieldarg) ++ #par.extend(self.make_xrefs(self.rolename, domain, fieldarg, ++ # addnodes.literal_strong, env=env)) ++ if fieldarg in types: ++ par += nodes.Text(' (') ++ # NOTE: using .pop() here to prevent a single type node to be ++ # inserted twice into the doctree, which leads to ++ # inconsistencies later when references are resolved ++ fieldtype = types.pop(fieldarg) ++ if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text): ++ typename = u''.join(n.astext() for n in fieldtype) ++ par.extend(self.make_xrefs(self.typerolename, domain, typename, ++ addnodes.literal_emphasis, env=env)) ++ else: ++ par += fieldtype ++ par += nodes.Text(')') ++ par += nodes.Text(' -- ') ++ par += content ++ return par ++ ++ fieldname = nodes.field_name('', self.label) ++ if len(items) == 1 and self.can_collapse: ++ fieldarg, content = items[0] ++ bodynode = handle_item(fieldarg, content) ++ else: ++ bodynode = self.list_type() ++ for fieldarg, content in items: ++ bodynode += nodes.list_item('', handle_item(fieldarg, content)) ++ fieldbody = nodes.field_body('', bodynode) ++ return nodes.field('', fieldname, fieldbody) ++ ++ ++ TypedField.make_field = patched_make_field diff --git a/dev-python/btrfs/files/13-002-show_file_csum-fix-vaddr-computation.patch b/dev-python/btrfs/files/13-002-show_file_csum-fix-vaddr-computation.patch new file mode 100644 index 000000000000..dcf81c7566de --- /dev/null +++ b/dev-python/btrfs/files/13-002-show_file_csum-fix-vaddr-computation.patch @@ -0,0 +1,27 @@ + +Patch from: +https://github.com/knorrie/python-btrfs/commit/8ebe99f77b5e4da38d8e9322bb5a7c4688c98b4a + +From 8ebe99f77b5e4da38d8e9322bb5a7c4688c98b4a Mon Sep 17 00:00:00 2001 +From: Cebtenzzre <cebtenzzre@gmail.com> +Date: Sun, 28 Nov 2021 14:46:39 -0500 +Subject: [PATCH] examples/show_file_csum: Fix vaddr computation + +extent.logical_offset is relative to the start of the file, whereas +extent.offset is relative to the start of the extent data on disk. +--- a/examples/show_file_csum.py ++++ b/examples/show_file_csum.py +@@ -90,11 +90,11 @@ def first_regular_file_extent(inum, tree): + "inside a data extent at vaddr {}.".format( + extent.logical_offset, extent.num_bytes, extent.offset, extent.disk_bytenr)) + +-vaddr = extent.disk_bytenr + extent.logical_offset ++vaddr = extent.disk_bytenr + extent.offset + + wraprint("Now, we first look up the checksum value for one block ({} bytes) " + "of data at vaddr {} ({} + {}).".format( +- fs.sectorsize, vaddr, extent.disk_bytenr, extent.logical_offset)) ++ fs.sectorsize, vaddr, extent.disk_bytenr, extent.offset)) + wraprint("If we're lucky, the checksum tree has a key at {}. " + "If not, we have to try searching back a bit to find the csum object that " + "holds information about our data block. Searching back is done in a very clumsy " diff --git a/dev-python/btrfs/files/13-003-ioctl-fix-documentation-error-in-FeatureFlags.patch b/dev-python/btrfs/files/13-003-ioctl-fix-documentation-error-in-FeatureFlags.patch new file mode 100644 index 000000000000..24403089bd70 --- /dev/null +++ b/dev-python/btrfs/files/13-003-ioctl-fix-documentation-error-in-FeatureFlags.patch @@ -0,0 +1,21 @@ + +Patch from: +https://github.com/knorrie/python-btrfs/commit/9f1698ce16b6ae15bf7b3f9f414e9f08dd052b79 + +From 9f1698ce16b6ae15bf7b3f9f414e9f08dd052b79 Mon Sep 17 00:00:00 2001 +From: Hans van Kranenburg <hans@knorrie.org> +Date: Sun, 21 May 2023 17:44:15 +0200 +Subject: [PATCH] ioctl: Fix documentation error in FeatureFlags + +The free_space_tree flag is located in compat_ro_flags. +--- a/btrfs/ioctl.py ++++ b/btrfs/ioctl.py +@@ -1410,7 +1410,7 @@ class FeatureFlags(object): + incompat_flags: mixed_backref|default_subvol|compress_lzo|big_metadata|extended_iref + >>> features.incompat_flags & btrfs.ioctl.FEATURE_INCOMPAT_MIXED_GROUPS + 0 +- >>> features.incompat_flags & btrfs.ioctl.FEATURE_COMPAT_RO_FREE_SPACE_TREE ++ >>> features.compat_ro_flags & btrfs.ioctl.FEATURE_COMPAT_RO_FREE_SPACE_TREE + 1 + + .. note:: diff --git a/dev-python/btrfs/files/13-004-add-Block-Group-Tree.patch b/dev-python/btrfs/files/13-004-add-Block-Group-Tree.patch new file mode 100644 index 000000000000..8e21581237bc --- /dev/null +++ b/dev-python/btrfs/files/13-004-add-Block-Group-Tree.patch @@ -0,0 +1,110 @@ + +Patch from: +https://github.com/knorrie/python-btrfs/commit/7d8dca5bf1211843d8fd5c02b118afddaa53bee8 + +From 7d8dca5bf1211843d8fd5c02b118afddaa53bee8 Mon Sep 17 00:00:00 2001 +From: Hans van Kranenburg <hans@knorrie.org> +Date: Sun, 21 May 2023 17:46:06 +0200 +Subject: [PATCH] WIP ctree,ioctl,utils: Add Block Group Tree + +When the block_group_tree feature is enabled on a filesystem, we have to +look up Block Group metadata items in the new Block Group Tree, instead +of the Extent Tree, where they always were located before. + +WIP: We don't want to call the get_features ioctl *every* time we look +up 1 block group object, but we also cannot just cache the result of it, +since some feature flags can change while the fs is mounted. + +So, this needs some extra work in the features department first to make +this more nice. +--- a/btrfs/ctree.py ++++ b/btrfs/ctree.py +@@ -91,6 +91,7 @@ def _struct_format(s): + QUOTA_TREE_OBJECTID = 8 #: Quota tree + UUID_TREE_OBJECTID = 9 #: Subvolume UUID tree + FREE_SPACE_TREE_OBJECTID = 10 #: Free space tree ++BLOCK_GROUP_TREE_OBJECTID = 11 #: Block group tree + + DEV_STATS_OBJECTID = 0 #: Object ID of device statistics in the Device tree. + BALANCE_OBJECTID = ULL(-4) #: Object ID to store balance status. (-4) +@@ -346,6 +347,7 @@ def _qgroup_objectid(level, subvid): + QUOTA_TREE_OBJECTID: 'QUOTA_TREE', + UUID_TREE_OBJECTID: 'UUID_TREE', + FREE_SPACE_TREE_OBJECTID: 'FREE_SPACE_TREE', ++ BLOCK_GROUP_TREE_OBJECTID: 'BLOCK_GROUP_TREE', + BALANCE_OBJECTID: 'BALANCE', + ORPHAN_OBJECTID: 'ORPHAN', + TREE_LOG_OBJECTID: 'TREE_LOG', +@@ -765,6 +767,11 @@ def __init__(self, path): + self.fsid = _fs_info.fsid + self.nodesize = _fs_info.nodesize + self.sectorsize = _fs_info.sectorsize ++ # TEMP cached feature flag for block_group_tree TEMP ++ _features = self.features() ++ self._block_group_tree = self.features().compat_ro_flags & \ ++ btrfs.ioctl.FEATURE_COMPAT_RO_BLOCK_GROUP_TREE != 0 ++ # TEMP cached feature flag for block_group_tree TEMP + + def __enter__(self): + return self +@@ -870,7 +877,10 @@ def block_group(self, vaddr, length=None): + :raises: :class:`ItemNotFoundError` if no Block Group Item can be found + at the address. + """ +- tree = EXTENT_TREE_OBJECTID ++ if not self._block_group_tree: ++ tree = EXTENT_TREE_OBJECTID ++ else: ++ tree = BLOCK_GROUP_TREE_OBJECTID + min_offset = length if length is not None else 0 + max_offset = length if length is not None else ULLONG_MAX + min_key = Key(vaddr, BLOCK_GROUP_ITEM_KEY, min_offset) +@@ -1240,11 +1250,14 @@ class BlockGroupItem(ItemData): + The `Block Group` has a 1 to 1 relationship with a `Chunk` and tracks some + usage information about a range of virtual address space. + +- * Tree: `EXTENT_TREE_OBJECTID` (2) ++ * Tree: `EXTENT_TREE_OBJECTID` (2) or `BLOCK_GROUP_TREE_OBJECTID` (11) + * Key objectid: Virtual address. + * Key type: `BLOCK_GROUP_ITEM_KEY` (192) + * Key offset: Block Group length. + ++ If the block_group_tree feature is enabled on the filesystem, these items ++ can be found inside the Block Group Tree instead of the Extent Tree. ++ + :ivar int vaddr: Virtual address where the Bock Group starts (taken from + the objectid field of the item key). + :ivar int length: Block Group length in bytes (taken from the offset field +--- a/btrfs/ioctl.py ++++ b/btrfs/ioctl.py +@@ -1325,10 +1325,12 @@ def _compat_flags_str(flags): + + FEATURE_COMPAT_RO_FREE_SPACE_TREE = 1 << 0 + FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID = 1 << 1 ++FEATURE_COMPAT_RO_BLOCK_GROUP_TREE = 1 << 3 + + _feature_compat_ro_str_map = { + FEATURE_COMPAT_RO_FREE_SPACE_TREE: 'free_space_tree', + FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID: 'free_space_tree_valid', ++ FEATURE_COMPAT_RO_BLOCK_GROUP_TREE : 'block_group_tree', + } + + +@@ -1383,6 +1385,7 @@ class FeatureFlags(object): + + - FEATURE_COMPAT_RO_FREE_SPACE_TREE + - FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID ++ - FEATURE_COMPAT_RO_BLOCK_GROUP_TREE + + Known incompat_flags (available as attribute of this module) are: + +--- a/btrfs/utils.py ++++ b/btrfs/utils.py +@@ -436,6 +436,7 @@ def embedded_text_for_str(text): + 'quota': btrfs.ctree.QUOTA_TREE_OBJECTID, + 'uuid': btrfs.ctree.UUID_TREE_OBJECTID, + 'free_space': btrfs.ctree.FREE_SPACE_TREE_OBJECTID, ++ 'block_group': btrfs.ctree.BLOCK_GROUP_TREE_OBJECTID, + 'tree_log': btrfs.ctree.TREE_LOG_OBJECTID, + 'tree_log_fixup': btrfs.ctree.TREE_LOG_FIXUP_OBJECTID, + 'tree_reloc': btrfs.ctree.TREE_RELOC_OBJECTID, diff --git a/dev-python/btrfs/files/13-005-ctree-FileSystem-add-block_groups-function.patch b/dev-python/btrfs/files/13-005-ctree-FileSystem-add-block_groups-function.patch new file mode 100644 index 000000000000..a6aae613a59d --- /dev/null +++ b/dev-python/btrfs/files/13-005-ctree-FileSystem-add-block_groups-function.patch @@ -0,0 +1,58 @@ + +Patch from: +https://github.com/knorrie/python-btrfs/commit/be867c10e30b7d8e4d3cfd939a433cc19f362966 + +From be867c10e30b7d8e4d3cfd939a433cc19f362966 Mon Sep 17 00:00:00 2001 +From: Hans van Kranenburg <hans@knorrie.org> +Date: Sun, 21 May 2023 18:40:28 +0200 +Subject: [PATCH] WIP ctree: FileSystem: add block_groups function + +In the past, I did not really want to add this helper function, since +it's not just translating some function parameters to another function +call, but, to get all Block Group objects, we needed to search the Chunk +tree and get all of them individually. So, to make it more explicit to +the user of the library that it was a bit weird inefficient process, I +let the user do that little dance. + +Now, with the new Block Group Tree, we can actually just to a cheap +lookup of a Block Group range! So, well, let's add the convenience +function now, and let it handle both the old and new case. + +Note that the difference in behaviour between error handling for looking +up a range or a single items stays the same. block_groups(...) will +return an iterator which has no objects to produce, and block_group(...) +will throw the ItemNotFoundError. +--- a/btrfs/ctree.py ++++ b/btrfs/ctree.py +@@ -866,6 +866,31 @@ def dev_extents(self, min_devid=1, max_devid=ULLONG_MAX): + for header, data in btrfs.ioctl.search_v2(self.fd, tree, min_key, max_key): + yield DevExtent(header, data) + ++ def block_groups(self, min_vaddr=0, max_vaddr=ULLONG_MAX, nr_items=None): ++ """ ++ :param int min_vaddr: Lowest virtual address to search for. ++ :param int max_vaddr: Highest virtual address to search for. ++ :param int nr_items: Maximum amount of items to return. Defaults to no limit. ++ :returns: Block Group items from the Extent Tree or Block Group Tree ++ :rtype: Iterator[:class:`~btrfs.ctree.BlockGroupItem`] ++ """ ++ if not self._block_group_tree: ++ for chunk in self.chunks(min_vaddr, max_vaddr, nr_items): ++ try: ++ yield self.block_group(chunk.vaddr, chunk.length) ++ except btrfs.ctree.ItemNotFoundError: ++ # This is simply to prevent the program from aborting when a block ++ # group is removed in between doing the chunks lookup and the block ++ # group item lookup. ++ pass ++ else: ++ tree = BLOCK_GROUP_TREE_OBJECTID ++ min_key = Key(min_vaddr, BLOCK_GROUP_ITEM_KEY, 0) ++ max_key = Key(max_vaddr, BLOCK_GROUP_ITEM_KEY, ULLONG_MAX) ++ for header, data in btrfs.ioctl.search_v2(self.fd, tree, min_key, max_key, ++ nr_items=nr_items): ++ yield BlockGroupItem(header, data) ++ + def block_group(self, vaddr, length=None): + """ + :param int vaddr: Starting virtual address of the block group. diff --git a/dev-python/btrfs/files/13-006-btrfs-search-metadata-use-FileSystem-block_groups.patch b/dev-python/btrfs/files/13-006-btrfs-search-metadata-use-FileSystem-block_groups.patch new file mode 100644 index 000000000000..286282f73822 --- /dev/null +++ b/dev-python/btrfs/files/13-006-btrfs-search-metadata-use-FileSystem-block_groups.patch @@ -0,0 +1,30 @@ + +Patch from: +https://github.com/knorrie/python-btrfs/commit/59c8daca3a05f86001b1dc36b2ab2e5961bf7bc0 + +From 59c8daca3a05f86001b1dc36b2ab2e5961bf7bc0 Mon Sep 17 00:00:00 2001 +From: Hans van Kranenburg <hans@knorrie.org> +Date: Sun, 21 May 2023 18:48:55 +0200 +Subject: [PATCH] btrfs-search-metadata: use FileSystem block_groups + +Now that we have a helper for looking up Block Groups, let's use it, so +that it can deal with Block Groups in either Extent or Block Group Tree +automagically. +--- a/bin/btrfs-search-metadata ++++ b/bin/btrfs-search-metadata +@@ -37,14 +37,7 @@ def get_chunks(fs, **kwargs): + + + def get_block_groups(fs, **kwargs): +- for chunk in fs.chunks(): +- try: +- yield fs.block_group(chunk.vaddr, chunk.length) +- except btrfs.ctree.ItemNotFoundError: +- # This is simply to prevent the program from aborting when a block +- # group is removed in between doing the chunks lookup and the block +- # group item lookup. +- pass ++ return fs.block_groups() + + + def get_dev_extents(fs, **kwargs): diff --git a/dev-python/btrfs/metadata.xml b/dev-python/btrfs/metadata.xml new file mode 100644 index 000000000000..0167c381ec56 --- /dev/null +++ b/dev-python/btrfs/metadata.xml @@ -0,0 +1,16 @@ +<?xml version='1.0' encoding='UTF-8'?> +<!DOCTYPE pkgmetadata SYSTEM "https://www.gentoo.org/dtd/metadata.dtd"> +<pkgmetadata> + <maintainer type="person" proxied="yes"> + <email>holger@applied-asynchrony.com</email> + <name>Holger Hoffstätte</name> + </maintainer> + <maintainer type="project" proxied="proxy"> + <email>proxy-maint@gentoo.org</email> + <name>Proxy Maintainers</name> + </maintainer> + <upstream> + <remote-id type="github">knorrie/python-btrfs</remote-id> + <remote-id type="pypi">btrfs</remote-id> + </upstream> +</pkgmetadata> |