summaryrefslogtreecommitdiff
path: root/libbtrfsutil
diff options
context:
space:
mode:
Diffstat (limited to 'libbtrfsutil')
-rw-r--r--libbtrfsutil/README.md424
-rw-r--r--libbtrfsutil/btrfs.h101
-rw-r--r--libbtrfsutil/btrfs_tree.h4
-rw-r--r--libbtrfsutil/btrfsutil.h21
-rw-r--r--libbtrfsutil/errors.c8
-rw-r--r--libbtrfsutil/python/module.c17
-rw-r--r--libbtrfsutil/python/subvolume.c6
-rw-r--r--libbtrfsutil/python/tests/__init__.py66
-rw-r--r--libbtrfsutil/python/tests/test_subvolume.py215
-rw-r--r--libbtrfsutil/stubs.c1
-rw-r--r--libbtrfsutil/subvolume.c446
11 files changed, 1155 insertions, 154 deletions
diff --git a/libbtrfsutil/README.md b/libbtrfsutil/README.md
index 0c8eba44..8abb426d 100644
--- a/libbtrfsutil/README.md
+++ b/libbtrfsutil/README.md
@@ -6,6 +6,425 @@ the LGPL. libbtrfsutil provides interfaces for a subset of the operations
offered by the `btrfs` command line utility. It also includes official Python
bindings (Python 3 only).
+API Overview
+------------
+
+This section provides an overview of the interfaces available in libbtrfsutil
+as well as example usages. Detailed documentation for the C API can be found in
+[`btrfsutil.h`](btrfsutil.h). Detailed documentation for the Python bindings is
+available with `pydoc3 btrfsutil` or in the interpreter:
+
+```
+>>> import btrfsutil
+>>> help(btrfsutil)
+```
+
+Many functions in the C API have a variant taking a path and a variant taking a
+file descriptor. The latter has the same name as the former with an `_fd`
+suffix. The Python bindings for these functions can take a path, a file object,
+or a file descriptor.
+
+Error handling is omitted from most of these examples for brevity. Please
+handle errors in production code.
+
+### Error Handling
+
+In the C API, all functions that can return an error return an `enum
+btrfs_util_error` and set `errno`. `BTRFS_UTIL_OK` (zero) is returned on
+success. `btrfs_util_strerror()` converts an error code to a string
+description suitable for human-friendly error reporting.
+
+```c
+enum btrfs_util_err err;
+
+err = btrfs_util_sync("/");
+if (err)
+ fprintf("stderr, %s: %m\n", btrfs_util_strerror(err));
+```
+
+In the Python bindings, functions may raise a `BtrfsUtilError`, which is a
+subclass of `OSError` with an added `btrfsutilerror` error code member. Error
+codes are available as `ERROR_*` constants.
+
+```python
+try:
+ btrfsutil.sync('/')
+except btrfsutil.BtrfsUtilError as e:
+ print(e, file=sys.stderr)
+```
+
+### Filesystem Operations
+
+There are several operations which act on the entire filesystem.
+
+#### Sync
+
+Btrfs can commit all caches for a specific filesystem to disk.
+
+`btrfs_util_sync()` forces a sync on the filesystem containing the given file
+and waits for it to complete.
+
+`btrfs_wait_sync()` waits for a previously started transaction to complete. The
+transaction is specified by ID, which may be zero to indicate the current
+transaction.
+
+`btrfs_start_sync()` asynchronously starts a sync and returns a transaction ID
+which can then be passed to `btrfs_wait_sync()`.
+
+```c
+uint64_t transid;
+btrfs_util_sync("/");
+btrfs_util_start_sync("/", &transid);
+btrfs_util_wait_sync("/", &transid);
+btrfs_util_wait_sync("/", 0);
+```
+
+```python
+btrfsutil.sync('/')
+transid = btrfsutil.start_sync('/')
+btrfsutil.wait_sync('/', transid)
+btrfsutil.wait_sync('/') # equivalent to wait_sync('/', 0)
+```
+
+All of these functions have `_fd` variants.
+
+The equivalent `btrfs-progs` command is `btrfs filesystem sync`.
+
+### Subvolume Operations
+
+Functions which take a file and a subvolume ID can be used in two ways. If zero
+is given as the subvolume ID, then the given file is used as the subvolume.
+Otherwise, the given file can be any file in the filesystem, and the subvolume
+with the given ID is used.
+
+#### Subvolume Information
+
+`btrfs_util_is_subvolume()` returns whether a given file is a subvolume.
+
+`btrfs_util_subvolume_id()` returns the ID of the subvolume containing the
+given file.
+
+```c
+enum btrfs_util_error err;
+err = btrfs_util_is_subvolume("/subvol");
+if (!err)
+ printf("Subvolume\n");
+else if (err == BTRFS_UTIL_ERROR_NOT_BTRFS || err == BTRFS_UTIL_ERROR_NOT_SUBVOLUME)
+ printf("Not subvolume\n");
+uint64_t id;
+btrfs_util_subvolume_id("/subvol", &id);
+```
+
+```python
+if btrfsutil.is_subvolume('/subvol'):
+ print('Subvolume')
+else:
+ print('Not subvolume')
+id_ = btrfsutil.subvolume_id('/subvol')
+```
+
+`btrfs_util_subvolume_path()` returns the path of the subvolume with the given
+ID relative to the filesystem root. This requires `CAP_SYS_ADMIN`. The path
+must be freed with `free()`.
+
+```c
+char *path;
+btrfs_util_subvolume_path("/", 256, &path);
+free(path);
+btrfs_util_subvolume_path("/subvol", 0, &path);
+free(path);
+```
+
+```python
+path = btrfsutil.subvolume_path('/', 256)
+path = btrfsutil.subvolume_path('/subvol') # equivalent to subvolume_path('/subvol', 0)
+```
+
+`btrfs_util_subvolume_info()` returns information (including ID, parent ID,
+UUID) about a subvolume. In the C API, this is returned as a `struct
+btrfs_util_subvolume_info`. The Python bindings use a `SubvolumeInfo` object.
+
+This requires `CAP_SYS_ADMIN` unless the given subvolume ID is zero and the
+kernel supports the `BTRFS_IOC_GET_SUBVOL_INFO` ioctl (added in 4.18).
+
+The equivalent `btrfs-progs` command is `btrfs subvolume show`.
+
+```c
+struct btrfs_util_subvolume_info info;
+btrfs_util_subvolume_info("/", 256, &info);
+btrfs_util_subvolume_info("/subvol", 0, &info);
+```
+
+```python
+info = btrfsutil.subvolume_info('/', 256)
+info = btrfsutil.subvolume_info('/subvol') # equivalent to subvolume_info('/subvol', 0)
+```
+
+All of these functions have `_fd` variants.
+
+#### Enumeration
+
+An iterator interface is provided for enumerating subvolumes on a filesystem.
+In the C API, a `struct btrfs_util_subvolume_iterator` is initialized by
+`btrfs_util_create_subvolume_iterator()`, which takes a top subvolume to
+enumerate under and flags. Currently, the only flag is to specify post-order
+traversal instead of the default pre-order. This function has an `_fd` variant.
+
+`btrfs_util_destroy_subvolume_iterator()` must be called to free a previously
+created `struct btrfs_util_subvolume_iterator`.
+
+`btrfs_util_subvolume_iterator_fd()` returns the file descriptor opened by
+`btrfs_util_create_subvolume_iterator()` which can be used for other functions.
+
+`btrfs_util_subvolume_iterator_next()` returns the path (relative to the top
+subvolume that the iterator was created with) and ID of the next subvolume.
+`btrfs_util_subvolume_iterator_next_info()` returns a `struct
+btrfs_subvolume_info` instead of the ID. It is slightly more efficient than
+doing separate `btrfs_util_subvolume_iterator_next()` and
+`btrfs_util_subvolume_info()` calls if the subvolume information is needed. The
+path returned by these functions must be freed with `free()`. When there are no
+more subvolumes, they return `BTRFS_UTIL_ERROR_STOP_ITERATION`.
+
+```c
+struct btrfs_util_subvolume_iterator *iter;
+enum btrfs_util_error err;
+char *path;
+uint64_t id;
+struct btrfs_util_subvolume_info info;
+
+btrfs_util_create_subvolume_iterator("/", 256, 0, &iter);
+/*
+ * This is just an example use-case for btrfs_util_subvolume_iterator_fd(). It
+ * is not necessary.
+ */
+btrfs_util_sync_fd(btrfs_util_subvolume_iterator_fd(iter));
+while (!(err = btrfs_util_subvolume_iterator_next(iter, &path, &id))) {
+ printf("%" PRIu64 " %s\n", id, path);
+ free(path);
+}
+btrfs_util_destroy_subvolume_iterator(iter);
+
+btrfs_util_create_subvolume_iterator("/subvol", 0,
+ BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER,
+ &iter);
+while (!(err = btrfs_util_subvolume_iterator_next_info(iter, &path, &info))) {
+ printf("%" PRIu64 " %" PRIu64 " %s\n", info.id, info.parent_id, path);
+ free(path);
+}
+btrfs_util_destroy_subvolume_iterator(iter);
+```
+
+The Python bindings provide this interface as an iterable `SubvolumeIterator`
+class. It should be used as a context manager to ensure that the underlying
+file descriptor is closed. Alternatively, it has a `close()` method for closing
+explicitly. It also has a `fileno()` method to get the underlying file
+descriptor.
+
+```python
+with btrfsutil.SubvolumeIterator('/', 256) as it:
+ # This is just an example use-case for fileno(). It is not necessary.
+ btrfsutil.sync(it.fileno())
+ for path, id_ in it:
+ print(id_, path)
+
+it = btrfsutil.SubvolumeIterator('/subvol', info=True, post_order=True)
+try:
+ for path, info in it:
+ print(info.id, info.parent_id, path)
+finally:
+ it.close()
+```
+
+This interface requires `CAP_SYS_ADMIN` unless the given top subvolume ID is
+zero and the kernel supports the `BTRFS_IOC_GET_SUBVOL_ROOTREF` and
+`BTRFS_IOC_INO_LOOKUP_USER` ioctls (added in 4.18). In the unprivileged case,
+subvolumes which cannot be accessed are skipped.
+
+The equivalent `btrfs-progs` command is `btrfs subvolume list`.
+
+#### Creation
+
+`btrfs_util_create_subvolume()` creates a new subvolume at the given path. The
+subvolume can be created asynchronously and inherit from quota groups
+(qgroups).
+
+Qgroups to inherit are specified with a `struct btrfs_util_qgroup_inherit`,
+which is created by `btrfs_util_create_qgroup_inherit()` and freed by
+`btrfs_util_destroy_qgroup_inherit()`. Qgroups are added with
+`btrfs_util_qgroup_inherit_add_group()`. The list of added groups can be
+retrieved with `btrfs_util_qgroup_inherit_get_groups()`; note that the returned
+array does not need to be freed and is no longer valid when the `struct
+btrfs_util_qgroup_inherit` is freed.
+
+The Python bindings provide a `QgroupInherit` class. It has an `add_group()`
+method and a `groups` member, which is a list of ints.
+
+```c
+btrfs_util_create_subvolume("/subvol2", 0, NULL, NULL);
+
+uint64_t async_transid;
+btrfs_util_create_subvolume("/subvol2", 0, &async_transid, NULL);
+btrfs_util_wait_sync("/", async_transid);
+
+struct btrfs_util_qgroup_inherit *qgroups;
+btrfs_util_create_qgroup_inherit(0, &qgroups);
+btrfs_util_qgroup_inherit_add_group(&qgroups, 256);
+btrfs_util_create_subvolume("/subvol2", 0, NULL, qgroups);
+btrfs_util_destroy_qgroup_inherit(qgroups);
+```
+
+```python
+btrfsutil.create_subvolume('/subvol2')
+
+async_transid = btrfsutil.create_subvolume('/subvol2', async_=True)
+btrfsutil.wait_sync('/', async_transid)
+
+qgroups = btrfsutil.QgroupInherit()
+qgroups.add_group(256)
+btrfsutil.create_subvolume('/subvol2', qgroup_inherit=qgroups)
+```
+
+The C API has an `_fd` variant which takes a name and a file descriptor
+referring to the parent directory.
+
+The equivalent `btrfs-progs` command is `btrfs subvolume create`.
+
+#### Snapshotting
+
+Snapshots are created with `btrfs_util_create_snapshot()`, which takes a source
+path, a destination path, and flags. It can also be asynchronous and inherit
+from quota groups; see [subvolume creation](#Creation).
+
+Snapshot creation can be recursive, in which case subvolumes underneath the
+subvolume being snapshotted will also be snapshotted onto the same location in
+the new snapshot (note that this is implemented in userspace non-atomically and
+has the same capability requirements as a [subvolume iterator](#Enumeration)).
+The newly created snapshot can also be read-only, but not if doing a recursive
+snapshot.
+
+```c
+btrfs_util_create_snapshot("/subvol", "/snapshot", 0, NULL, NULL);
+btrfs_util_create_snapshot("/nested_subvol", "/nested_snapshot",
+ BTRFS_UTIL_CREATE_SNAPSHOT_RECURSIVE, NULL, NULL);
+btrfs_util_create_snapshot("/subvol", "/rosnapshot",
+ BTRFS_UTIL_CREATE_SNAPSHOT_READ_ONLY, NULL, NULL);
+```
+
+```python
+btrfsutil.create_snapshot('/subvol', '/snapshot')
+btrfsutil.create_snapshot('/nested_subvol', '/nested_snapshot', recursive=True)
+btrfsutil.create_snapshot('/subvol', '/rosnapshot', read_only=True)
+```
+
+The C API has two `_fd` variants. `btrfs_util_create_snapshot_fd()` takes the
+source subvolume as a file descriptor. `btrfs_util_create_snapshot_fd2()` takes
+the source subvolume as a file descriptor and the destination as a name and
+parent file descriptor.
+
+The equivalent `btrfs-progs` command is `btrfs subvolume snapshot`.
+
+#### Deletion
+
+`btrfs_util_delete_subvolume()` takes a subvolume to delete and flags. This
+requires `CAP_SYS_ADMIN` if the filesystem was not mounted with
+`user_subvol_rm_allowed`. Deletion may be recursive, in which case all
+subvolumes beneath the given subvolume are deleted before the given subvolume
+is deleted. This is implemented in user-space non-atomically and has the same
+capability requirements as a [subvolume iterator](#Enumeration).
+
+```c
+btrfs_util_delete_subvolume("/subvol", 0);
+btrfs_util_delete_subvolume("/nested_subvol",
+ BTRFS_UTIL_DELETE_SUBVOLUME_RECURSIVE);
+```
+
+```python
+btrfsutil.delete_subvolume('/subvol')
+btrfsutil.delete_subvolume('/nested_subvol', recursive=True)
+```
+
+The C API has an `_fd` variant which takes a name and a file descriptor
+referring to the parent directory.
+
+The equivalent `btrfs-progs` command is `btrfs subvolume delete`.
+
+#### Deleted Subvolumes
+
+Btrfs lazily cleans up deleted subvolumes. `btrfs_util_deleted_subvolumes()`
+returns an array of subvolume IDs which have been deleted but not yet cleaned
+up. The returned array should be freed with `free()`.
+```c
+uint64_t *ids;
+size_t n; /* Number of returned IDs. */
+btrfs_util_deleted_subvolumes("/", &ids, &n);
+free(ids);
+```
+
+The Python binding returns a list of ints.
+
+```python
+ids = btrfsutil.deleted_subvolumes('/')
+```
+
+This function also has an `_fd` variant. It requires `CAP_SYS_ADMIN`.
+
+The closest `btrfs-progs` command is `btrfs subvolume sync`, which waits for
+deleted subvolumes to be cleaned up.
+
+#### Read-Only Flag
+
+Subvolumes can be set to read-only. `btrfs_util_get_subvolume_read_only()`
+returns whether a subvolume is read-only.
+`btrfs_util_set_subvolume_read_only()` sets the read-only flag to the desired
+value.
+
+```c
+bool read_only;
+btrfs_util_get_subvolume_read_only("/subvol", &read_only);
+btrfs_util_set_subvolume_read_only("/subvol", true);
+btrfs_util_set_subvolume_read_only("/subvol", false);
+```
+
+```python
+read_only = btrfsutil.get_subvolume_read_only('/subvol')
+btrfsutil.set_subvolume_read_only('/subvol', True)
+btrfsutil.set_subvolume_read_only('/subvol', False)
+```
+
+Both of these functions have `_fd` variants.
+
+The equivalent `btrfs-progs` commands are `btrfs property get` and `btrfs
+property set` with the `ro` property.
+
+#### Default Subvolume
+
+The default subvolume of a filesystem is the subvolume which is mounted when no
+`subvol` or `subvolid` mount option is passed.
+
+`btrfs_util_get_default_subvolume()` gets the ID of the default subvolume for
+the filesystem containing the given file.
+
+`btrfs_util_set_default_subvolume()` sets the default subvolume.
+
+```c
+uint64_t id;
+btrfs_util_get_default_subvolume("/", &id);
+btrfs_util_set_default_subvolume("/", 256);
+btrfs_util_set_default_subvolume("/subvol", 0);
+```
+
+```python
+id = btrfsutil.get_default_subvolume('/')
+btrfsutil.set_default_subvolume('/', 256)
+btrfsutil.set_default_subvolume('/subvol') # equivalent to set_default_subvolume('/subvol', 0)
+```
+
+Both of these functions have an `_fd` variant. They both require
+`CAP_SYS_ADMIN`.
+
+The equivalent `btrfs-progs` commands are `btrfs subvolume get-default` and
+`btrfs subvolume set-default`.
+
Development
-----------
@@ -24,7 +443,8 @@ release of btrfs-progs).
A few guidelines:
-* All interfaces must be documented in `btrfsutil.h` using the kernel-doc style
+* All interfaces must be documented in this README and in `btrfsutil.h` using
+ the kernel-doc style
* Error codes should be specific about what _exactly_ failed
* Functions should have a path and an fd variant whenever possible
* Spell out terms in function names, etc. rather than abbreviating whenever
@@ -32,7 +452,7 @@ A few guidelines:
* Don't require the Btrfs UAPI headers for any interfaces (e.g., instead of
directly exposing a type from `linux/btrfs_tree.h`, abstract it away in a
type specific to `libbtrfsutil`)
-* Preserve API and ABI compatability at all times (i.e., we don't want to bump
+* Preserve API and ABI compatibility at all times (i.e., we don't want to bump
the library major version if we don't have to)
* Include Python bindings for all interfaces
* Write tests for all interfaces
diff --git a/libbtrfsutil/btrfs.h b/libbtrfsutil/btrfs.h
index c293f6bf..944d5013 100644
--- a/libbtrfsutil/btrfs.h
+++ b/libbtrfsutil/btrfs.h
@@ -134,7 +134,7 @@ struct btrfs_scrub_progress {
__u64 tree_bytes_scrubbed; /* # of tree bytes scrubbed */
__u64 read_errors; /* # of read errors encountered (EIO) */
__u64 csum_errors; /* # of failed csum checks */
- __u64 verify_errors; /* # of occurences, where the metadata
+ __u64 verify_errors; /* # of occurrences, where the metadata
* of a tree block did not match the
* expected values, like generation or
* logical */
@@ -154,7 +154,7 @@ struct btrfs_scrub_progress {
__u64 last_physical; /* last physical address scrubbed. In
* case a scrub was aborted, this can
* be used to restart the scrub */
- __u64 unverified_errors; /* # of occurences where a read for a
+ __u64 unverified_errors; /* # of occurrences where a read for a
* full (64k) bio failed, but the re-
* check succeeded for each 4k piece.
* Intermittent error. */
@@ -421,6 +421,21 @@ struct btrfs_ioctl_ino_lookup_args {
char name[BTRFS_INO_LOOKUP_PATH_MAX];
};
+#define BTRFS_INO_LOOKUP_USER_PATH_MAX (4080 - BTRFS_VOL_NAME_MAX - 1)
+struct btrfs_ioctl_ino_lookup_user_args {
+ /* in, inode number containing the subvolume of 'subvolid' */
+ __u64 dirid;
+ /* in */
+ __u64 treeid;
+ /* out, name of the subvolume of 'treeid' */
+ char name[BTRFS_VOL_NAME_MAX + 1];
+ /*
+ * out, constructed path from the directory with which the ioctl is
+ * called to dirid
+ */
+ char path[BTRFS_INO_LOOKUP_USER_PATH_MAX];
+};
+
/* Search criteria for the btrfs SEARCH ioctl family. */
struct btrfs_ioctl_search_key {
/*
@@ -720,6 +735,82 @@ struct btrfs_ioctl_send_args {
__u64 reserved[4]; /* in */
};
+/*
+ * Information about a fs tree root.
+ *
+ * All items are filled by the ioctl
+ */
+struct btrfs_ioctl_get_subvol_info_args {
+ /* Id of this subvolume */
+ __u64 treeid;
+
+ /* Name of this subvolume, used to get the real name at mount point */
+ char name[BTRFS_VOL_NAME_MAX + 1];
+
+ /*
+ * Id of the subvolume which contains this subvolume.
+ * Zero for top-level subvolume or a deleted subvolume.
+ */
+ __u64 parent_id;
+
+ /*
+ * Inode number of the directory which contains this subvolume.
+ * Zero for top-level subvolume or a deleted subvolume
+ */
+ __u64 dirid;
+
+ /* Latest transaction id of this subvolume */
+ __u64 generation;
+
+ /* Flags of this subvolume */
+ __u64 flags;
+
+ /* UUID of this subvolume */
+ __u8 uuid[BTRFS_UUID_SIZE];
+
+ /*
+ * UUID of the subvolume of which this subvolume is a snapshot.
+ * All zero for a non-snapshot subvolume.
+ */
+ __u8 parent_uuid[BTRFS_UUID_SIZE];
+
+ /*
+ * UUID of the subvolume from which this subvolume was received.
+ * All zero for non-received subvolume.
+ */
+ __u8 received_uuid[BTRFS_UUID_SIZE];
+
+ /* Transaction id indicating when change/create/send/receive happened */
+ __u64 ctransid;
+ __u64 otransid;
+ __u64 stransid;
+ __u64 rtransid;
+ /* Time corresponding to c/o/s/rtransid */
+ struct btrfs_ioctl_timespec ctime;
+ struct btrfs_ioctl_timespec otime;
+ struct btrfs_ioctl_timespec stime;
+ struct btrfs_ioctl_timespec rtime;
+
+ /* Must be zero */
+ __u64 reserved[8];
+};
+
+#define BTRFS_MAX_ROOTREF_BUFFER_NUM 255
+struct btrfs_ioctl_get_subvol_rootref_args {
+ /* in/out, minimum id of rootref's treeid to be searched */
+ __u64 min_treeid;
+
+ /* out */
+ struct {
+ __u64 treeid;
+ __u64 dirid;
+ } rootref[BTRFS_MAX_ROOTREF_BUFFER_NUM];
+
+ /* out, number of found items */
+ __u8 num_items;
+ __u8 align[7];
+};
+
/* Error codes as returned by the kernel */
enum btrfs_err_code {
BTRFS_ERROR_DEV_RAID1_MIN_NOT_MET = 1,
@@ -836,5 +927,11 @@ enum btrfs_err_code {
struct btrfs_ioctl_feature_flags[3])
#define BTRFS_IOC_RM_DEV_V2 _IOW(BTRFS_IOCTL_MAGIC, 58, \
struct btrfs_ioctl_vol_args_v2)
+#define BTRFS_IOC_GET_SUBVOL_INFO _IOR(BTRFS_IOCTL_MAGIC, 60, \
+ struct btrfs_ioctl_get_subvol_info_args)
+#define BTRFS_IOC_GET_SUBVOL_ROOTREF _IOWR(BTRFS_IOCTL_MAGIC, 61, \
+ struct btrfs_ioctl_get_subvol_rootref_args)
+#define BTRFS_IOC_INO_LOOKUP_USER _IOWR(BTRFS_IOCTL_MAGIC, 62, \
+ struct btrfs_ioctl_ino_lookup_user_args)
#endif /* _LINUX_BTRFS_H */
diff --git a/libbtrfsutil/btrfs_tree.h b/libbtrfsutil/btrfs_tree.h
index f2ac0267..2af7205c 100644
--- a/libbtrfsutil/btrfs_tree.h
+++ b/libbtrfsutil/btrfs_tree.h
@@ -54,7 +54,7 @@
/* for storing balance parameters in the root tree */
#define BTRFS_BALANCE_OBJECTID -4ULL
-/* orhpan objectid for tracking unlinked/truncated files */
+/* orphan objectid for tracking unlinked/truncated files */
#define BTRFS_ORPHAN_OBJECTID -5ULL
/* does write ahead logging to speed up fsyncs */
@@ -270,7 +270,7 @@
#define BTRFS_PERSISTENT_ITEM_KEY 249
/*
- * Persistantly stores the device replace state in the device tree.
+ * Persistently stores the device replace state in the device tree.
* The key is built like this: (0, BTRFS_DEV_REPLACE_KEY, 0).
*/
#define BTRFS_DEV_REPLACE_KEY 250
diff --git a/libbtrfsutil/btrfsutil.h b/libbtrfsutil/btrfsutil.h
index 6d655f49..ad4f043e 100644
--- a/libbtrfsutil/btrfsutil.h
+++ b/libbtrfsutil/btrfsutil.h
@@ -26,7 +26,7 @@
#include <sys/time.h>
#define BTRFS_UTIL_VERSION_MAJOR 1
-#define BTRFS_UTIL_VERSION_MINOR 0
+#define BTRFS_UTIL_VERSION_MINOR 1
#define BTRFS_UTIL_VERSION_PATCH 0
#ifdef __cplusplus
@@ -63,6 +63,10 @@ enum btrfs_util_error {
BTRFS_UTIL_ERROR_SYNC_FAILED,
BTRFS_UTIL_ERROR_START_SYNC_FAILED,
BTRFS_UTIL_ERROR_WAIT_SYNC_FAILED,
+ BTRFS_UTIL_ERROR_GET_SUBVOL_INFO_FAILED,
+ BTRFS_UTIL_ERROR_GET_SUBVOL_ROOTREF_FAILED,
+ BTRFS_UTIL_ERROR_INO_LOOKUP_USER_FAILED,
+ BTRFS_UTIL_ERROR_FS_INFO_FAILED,
};
/**
@@ -266,7 +270,8 @@ struct btrfs_util_subvolume_info {
* to check whether the subvolume exists; %BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND
* will be returned if it does not.
*
- * This requires appropriate privilege (CAP_SYS_ADMIN).
+ * This requires appropriate privilege (CAP_SYS_ADMIN) unless @id is zero and
+ * the kernel supports BTRFS_IOC_GET_SUBVOL_INFO (kernel >= 4.18).
*
* Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
*/
@@ -505,6 +510,12 @@ struct btrfs_util_subvolume_iterator;
* @flags: Bitmask of BTRFS_UTIL_SUBVOLUME_ITERATOR_* flags.
* @ret: Returned iterator.
*
+ * Subvolume iterators require appropriate privilege (CAP_SYS_ADMIN) unless @top
+ * is zero and the kernel supports BTRFS_IOC_GET_SUBVOL_ROOTREF and
+ * BTRFS_IOC_INO_LOOKUP_USER (kernel >= 4.18). In this case, subvolumes which
+ * cannot be accessed (e.g., due to permissions or other mounts) will be
+ * skipped.
+ *
* The returned iterator must be freed with
* btrfs_util_destroy_subvolume_iterator().
*
@@ -553,7 +564,8 @@ int btrfs_util_subvolume_iterator_fd(const struct btrfs_util_subvolume_iterator
* Must be freed with free().
* @id_ret: Returned subvolume ID. May be %NULL.
*
- * This requires appropriate privilege (CAP_SYS_ADMIN).
+ * This requires appropriate privilege (CAP_SYS_ADMIN) for kernels < 4.18. See
+ * btrfs_util_create_subvolume_iterator().
*
* Return: %BTRFS_UTIL_OK on success, %BTRFS_UTIL_ERROR_STOP_ITERATION if there
* are no more subvolumes, non-zero error code on failure.
@@ -572,7 +584,8 @@ enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_util_subvo
* This convenience function basically combines
* btrfs_util_subvolume_iterator_next() and btrfs_util_subvolume_info().
*
- * This requires appropriate privilege (CAP_SYS_ADMIN).
+ * This requires appropriate privilege (CAP_SYS_ADMIN) for kernels < 4.18. See
+ * btrfs_util_create_subvolume_iterator().
*
* Return: See btrfs_util_subvolume_iterator_next().
*/
diff --git a/libbtrfsutil/errors.c b/libbtrfsutil/errors.c
index 634edc65..d39b38d0 100644
--- a/libbtrfsutil/errors.c
+++ b/libbtrfsutil/errors.c
@@ -45,6 +45,14 @@ static const char * const error_messages[] = {
[BTRFS_UTIL_ERROR_SYNC_FAILED] = "Could not sync filesystem",
[BTRFS_UTIL_ERROR_START_SYNC_FAILED] = "Could not start filesystem sync",
[BTRFS_UTIL_ERROR_WAIT_SYNC_FAILED] = "Could not wait for filesystem sync",
+ [BTRFS_UTIL_ERROR_GET_SUBVOL_INFO_FAILED] =
+ "Could not get subvolume information with BTRFS_IOC_GET_SUBVOL_INFO",
+ [BTRFS_UTIL_ERROR_GET_SUBVOL_ROOTREF_FAILED] =
+ "Could not get rootref information with BTRFS_IOC_GET_SUBVOL_ROOTREF",
+ [BTRFS_UTIL_ERROR_INO_LOOKUP_USER_FAILED] =
+ "Could not resolve subvolume path with BTRFS_IOC_INO_LOOKUP_USER",
+ [BTRFS_UTIL_ERROR_FS_INFO_FAILED] =
+ "Could not get filesystem information",
};
PUBLIC const char *btrfs_util_strerror(enum btrfs_util_error err)
diff --git a/libbtrfsutil/python/module.c b/libbtrfsutil/python/module.c
index 2dbdc7be..f8260c84 100644
--- a/libbtrfsutil/python/module.c
+++ b/libbtrfsutil/python/module.c
@@ -233,23 +233,28 @@ static PyMethodDef btrfsutil_methods[] = {
"this ID instead of the given path"},
{"create_subvolume", (PyCFunction)create_subvolume,
METH_VARARGS | METH_KEYWORDS,
- "create_subvolume(path, async=False)\n\n"
+ "create_subvolume(path, async_=False, qgroup_inherit=None)\n\n"
"Create a new subvolume.\n\n"
"Arguments:\n"
"path -- string, bytes, or path-like object\n"
- "async -- create the subvolume without waiting for it to commit to\n"
- "disk and return the transaction ID"},
+ "async_ -- create the subvolume without waiting for it to commit to\n"
+ "disk and return the transaction ID\n"
+ "qgroup_inherit -- optional QgroupInherit object of qgroups to\n"
+ "inherit from"},
{"create_snapshot", (PyCFunction)create_snapshot,
METH_VARARGS | METH_KEYWORDS,
- "create_snapshot(source, path, recursive=False, read_only=False, async=False)\n\n"
+ "create_snapshot(source, path, recursive=False, read_only=False,\n"
+ " async_=False, qgroup_inherit=None)\n\n"
"Create a new snapshot.\n\n"
"Arguments:\n"
"source -- string, bytes, path-like object, or open file descriptor\n"
"path -- string, bytes, or path-like object\n"
"recursive -- also snapshot child subvolumes\n"
"read_only -- create a read-only snapshot\n"
- "async -- create the subvolume without waiting for it to commit to\n"
- "disk and return the transaction ID"},
+ "async_ -- create the subvolume without waiting for it to commit to\n"
+ "disk and return the transaction ID\n"
+ "qgroup_inherit -- optional QgroupInherit object of qgroups to\n"
+ "inherit from"},
{"delete_subvolume", (PyCFunction)delete_subvolume,
METH_VARARGS | METH_KEYWORDS,
"delete_subvolume(path, recursive=False)\n\n"
diff --git a/libbtrfsutil/python/subvolume.c b/libbtrfsutil/python/subvolume.c
index 069e606b..0f893b91 100644
--- a/libbtrfsutil/python/subvolume.c
+++ b/libbtrfsutil/python/subvolume.c
@@ -322,7 +322,7 @@ PyObject *set_default_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
PyObject *create_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
{
- static char *keywords[] = {"path", "async", "qgroup_inherit", NULL};
+ static char *keywords[] = {"path", "async_", "qgroup_inherit", NULL};
struct path_arg path = {.allow_fd = false};
enum btrfs_util_error err;
int async = 0;
@@ -352,7 +352,7 @@ PyObject *create_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
PyObject *create_snapshot(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {
- "source", "path", "recursive", "read_only", "async",
+ "source", "path", "recursive", "read_only", "async_",
"qgroup_inherit", NULL,
};
struct path_arg src = {.allow_fd = true}, dst = {.allow_fd = false};
@@ -525,7 +525,7 @@ static int SubvolumeIterator_init(SubvolumeIterator *self, PyObject *args,
static char *keywords[] = {"path", "top", "info", "post_order", NULL};
struct path_arg path = {.allow_fd = true};
enum btrfs_util_error err;
- unsigned long long top = 5;
+ unsigned long long top = 0;
int info = 0;
int post_order = 0;
int flags = 0;
diff --git a/libbtrfsutil/python/tests/__init__.py b/libbtrfsutil/python/tests/__init__.py
index 35550e0a..9fd6f6de 100644
--- a/libbtrfsutil/python/tests/__init__.py
+++ b/libbtrfsutil/python/tests/__init__.py
@@ -15,26 +15,60 @@
# You should have received a copy of the GNU Lesser General Public License
# along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+import contextlib
import os
from pathlib import PurePath
+import pwd
import subprocess
import tempfile
import unittest
HAVE_PATH_LIKE = hasattr(PurePath, '__fspath__')
+try:
+ NOBODY_UID = pwd.getpwnam('nobody').pw_uid
+ skipUnlessHaveNobody = lambda func: func
+except KeyError:
+ NOBODY_UID = None
+ skipUnlessHaveNobody = unittest.skip('must have nobody user')
+
+
+@contextlib.contextmanager
+def drop_privs():
+ try:
+ os.seteuid(NOBODY_UID)
+ yield
+ finally:
+ os.seteuid(0)
+
+
+@contextlib.contextmanager
+def regain_privs():
+ uid = os.geteuid()
+ if uid:
+ try:
+ os.seteuid(0)
+ yield
+ finally:
+ os.seteuid(uid)
+ else:
+ yield
@unittest.skipIf(os.geteuid() != 0, 'must be run as root')
class BtrfsTestCase(unittest.TestCase):
- def setUp(self):
- self.mountpoint = tempfile.mkdtemp()
+ def __init__(self, *args, **kwds):
+ super().__init__(*args, **kwds)
+ self._mountpoints = []
+
+ def mount_btrfs(self):
+ mountpoint = tempfile.mkdtemp()
try:
with tempfile.NamedTemporaryFile(delete=False) as f:
os.truncate(f.fileno(), 1024 * 1024 * 1024)
- self.image = f.name
+ image = f.name
except Exception as e:
- os.rmdir(self.mountpoint)
+ os.rmdir(mountpoint)
raise e
if os.path.exists('../../mkfs.btrfs'):
@@ -42,19 +76,24 @@ class BtrfsTestCase(unittest.TestCase):
else:
mkfs = 'mkfs.btrfs'
try:
- subprocess.check_call([mkfs, '-q', self.image])
- subprocess.check_call(['mount', '-o', 'loop', '--', self.image, self.mountpoint])
+ subprocess.check_call([mkfs, '-q', image])
+ subprocess.check_call(['mount', '-o', 'loop', '--', image, mountpoint])
except Exception as e:
- os.remove(self.image)
- os.rmdir(self.mountpoint)
+ os.rmdir(mountpoint)
+ os.remove(image)
raise e
+ self._mountpoints.append((mountpoint, image))
+ return mountpoint, image
+
+ def setUp(self):
+ self.mountpoint, self.image = self.mount_btrfs()
+
def tearDown(self):
- try:
- subprocess.check_call(['umount', self.mountpoint])
- finally:
- os.remove(self.image)
- os.rmdir(self.mountpoint)
+ for mountpoint, image in self._mountpoints:
+ subprocess.call(['umount', '-R', mountpoint])
+ os.rmdir(mountpoint)
+ os.remove(image)
@staticmethod
def path_or_fd(path, open_flags=os.O_RDONLY):
@@ -67,4 +106,3 @@ class BtrfsTestCase(unittest.TestCase):
yield fd
finally:
os.close(fd)
-
diff --git a/libbtrfsutil/python/tests/test_subvolume.py b/libbtrfsutil/python/tests/test_subvolume.py
index 93396cba..99ec97bc 100644
--- a/libbtrfsutil/python/tests/test_subvolume.py
+++ b/libbtrfsutil/python/tests/test_subvolume.py
@@ -20,10 +20,18 @@ import errno
import os
import os.path
from pathlib import PurePath
+import subprocess
import traceback
import btrfsutil
-from tests import BtrfsTestCase, HAVE_PATH_LIKE
+from tests import (
+ BtrfsTestCase,
+ drop_privs,
+ HAVE_PATH_LIKE,
+ NOBODY_UID,
+ regain_privs,
+ skipUnlessHaveNobody,
+)
class TestSubvolume(BtrfsTestCase):
@@ -87,7 +95,7 @@ class TestSubvolume(BtrfsTestCase):
finally:
os.chdir(pwd)
- def test_subvolume_info(self):
+ def _test_subvolume_info(self, subvol, snapshot):
for arg in self.path_or_fd(self.mountpoint):
with self.subTest(type=type(arg)):
info = btrfsutil.subvolume_info(arg)
@@ -100,7 +108,7 @@ class TestSubvolume(BtrfsTestCase):
self.assertEqual(info.parent_uuid, bytes(16))
self.assertEqual(info.received_uuid, bytes(16))
self.assertNotEqual(info.generation, 0)
- self.assertEqual(info.ctransid, 0)
+ self.assertGreaterEqual(info.ctransid, 0)
self.assertEqual(info.otransid, 0)
self.assertEqual(info.stransid, 0)
self.assertEqual(info.rtransid, 0)
@@ -109,9 +117,6 @@ class TestSubvolume(BtrfsTestCase):
self.assertEqual(info.stime, 0)
self.assertEqual(info.rtime, 0)
- subvol = os.path.join(self.mountpoint, 'subvol')
- btrfsutil.create_subvolume(subvol)
-
info = btrfsutil.subvolume_info(subvol)
self.assertEqual(info.id, 256)
self.assertEqual(info.parent_id, 5)
@@ -132,19 +137,43 @@ class TestSubvolume(BtrfsTestCase):
self.assertEqual(info.rtime, 0)
subvol_uuid = info.uuid
- snapshot = os.path.join(self.mountpoint, 'snapshot')
- btrfsutil.create_snapshot(subvol, snapshot)
info = btrfsutil.subvolume_info(snapshot)
self.assertEqual(info.parent_uuid, subvol_uuid)
# TODO: test received_uuid, stransid, rtransid, stime, and rtime
+ def test_subvolume_info(self):
+ subvol = os.path.join(self.mountpoint, 'subvol')
+ btrfsutil.create_subvolume(subvol)
+ snapshot = os.path.join(self.mountpoint, 'snapshot')
+ btrfsutil.create_snapshot(subvol, snapshot)
+
+ self._test_subvolume_info(subvol, snapshot)
+
for arg in self.path_or_fd(self.mountpoint):
with self.subTest(type=type(arg)):
with self.assertRaises(btrfsutil.BtrfsUtilError) as e:
# BTRFS_EXTENT_TREE_OBJECTID
btrfsutil.subvolume_info(arg, 2)
+ self.assertEqual(e.exception.btrfsutilerror,
+ btrfsutil.ERROR_SUBVOLUME_NOT_FOUND)
+
+ @skipUnlessHaveNobody
+ def test_subvolume_info_unprivileged(self):
+ subvol = os.path.join(self.mountpoint, 'subvol')
+ btrfsutil.create_subvolume(subvol)
+ snapshot = os.path.join(self.mountpoint, 'snapshot')
+ btrfsutil.create_snapshot(subvol, snapshot)
+
+ with drop_privs():
+ try:
+ self._test_subvolume_info(subvol, snapshot)
+ except OSError as e:
+ if e.errno == errno.ENOTTY:
+ self.skipTest('BTRFS_IOC_GET_SUBVOL_INFO is not available')
+ else:
+ raise
def test_read_only(self):
for arg in self.path_or_fd(self.mountpoint):
@@ -202,7 +231,7 @@ class TestSubvolume(BtrfsTestCase):
btrfsutil.create_subvolume(subvol + '6//')
self.assertTrue(btrfsutil.is_subvolume(subvol + '6'))
- transid = btrfsutil.create_subvolume(subvol + '7', async=True)
+ transid = btrfsutil.create_subvolume(subvol + '7', async_=True)
self.assertTrue(btrfsutil.is_subvolume(subvol + '7'))
self.assertGreater(transid, 0)
@@ -265,7 +294,7 @@ class TestSubvolume(BtrfsTestCase):
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)
+ 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)
@@ -328,58 +357,136 @@ class TestSubvolume(BtrfsTestCase):
with self.subTest(type=type(arg)):
self.assertEqual(btrfsutil.deleted_subvolumes(arg), [256])
- def test_subvolume_iterator(self):
- pwd = os.getcwd()
- try:
- os.chdir(self.mountpoint)
- btrfsutil.create_subvolume('foo')
+ def _test_subvolume_iterator(self):
+ btrfsutil.create_subvolume('foo')
- path, subvol = next(btrfsutil.SubvolumeIterator('.', info=True))
+ with btrfsutil.SubvolumeIterator('.', info=True) as it:
+ path, subvol = next(it)
self.assertEqual(path, 'foo')
self.assertIsInstance(subvol, btrfsutil.SubvolumeInfo)
self.assertEqual(subvol.id, 256)
self.assertEqual(subvol.parent_id, 5)
-
- btrfsutil.create_subvolume('foo/bar')
- btrfsutil.create_subvolume('foo/bar/baz')
-
- subvols = [
- ('foo', 256),
- ('foo/bar', 257),
- ('foo/bar/baz', 258),
- ]
-
- for arg in self.path_or_fd('.'):
- with self.subTest(type=type(arg)):
- self.assertEqual(list(btrfsutil.SubvolumeIterator(arg)), subvols)
- self.assertEqual(list(btrfsutil.SubvolumeIterator('.', top=0)), subvols)
-
- self.assertEqual(list(btrfsutil.SubvolumeIterator('.', post_order=True)),
+ self.assertRaises(StopIteration, next, it)
+
+ btrfsutil.create_subvolume('foo/bar')
+ btrfsutil.create_subvolume('foo/bar/baz')
+
+ subvols = [
+ ('foo', 256),
+ ('foo/bar', 257),
+ ('foo/bar/baz', 258),
+ ]
+
+ for arg in self.path_or_fd('.'):
+ with self.subTest(type=type(arg)), btrfsutil.SubvolumeIterator(arg) as it:
+ self.assertEqual(list(it), subvols)
+ with btrfsutil.SubvolumeIterator('.', top=0) as it:
+ self.assertEqual(list(it), subvols)
+ if os.geteuid() == 0:
+ with btrfsutil.SubvolumeIterator('foo', top=5) as it:
+ self.assertEqual(list(it), subvols)
+
+ with btrfsutil.SubvolumeIterator('.', post_order=True) as it:
+ self.assertEqual(list(it),
[('foo/bar/baz', 258),
('foo/bar', 257),
('foo', 256)])
- subvols = [
- ('bar', 257),
- ('bar/baz', 258),
- ]
-
- self.assertEqual(list(btrfsutil.SubvolumeIterator('.', top=256)), subvols)
- self.assertEqual(list(btrfsutil.SubvolumeIterator('foo', top=0)), subvols)
-
- os.rename('foo/bar/baz', 'baz')
- self.assertEqual(sorted(btrfsutil.SubvolumeIterator('.')),
- [('baz', 258),
- ('foo', 256),
- ('foo/bar', 257)])
-
- with btrfsutil.SubvolumeIterator('.') as it:
- self.assertGreaterEqual(it.fileno(), 0)
- it.close()
- with self.assertRaises(ValueError):
- next(iter(it))
- with self.assertRaises(ValueError):
- it.fileno()
- it.close()
+ subvols = [
+ ('bar', 257),
+ ('bar/baz', 258),
+ ]
+
+ if os.geteuid() == 0:
+ with btrfsutil.SubvolumeIterator('.', top=256) as it:
+ self.assertEqual(list(it), subvols)
+ with btrfsutil.SubvolumeIterator('foo') as it:
+ self.assertEqual(list(it), subvols)
+ with btrfsutil.SubvolumeIterator('foo', top=0) as it:
+ self.assertEqual(list(it), subvols)
+
+ os.rename('foo/bar/baz', 'baz')
+ os.mkdir('dir')
+ btrfsutil.create_subvolume('dir/qux')
+ os.mkdir('dir/qux/dir2')
+ btrfsutil.create_subvolume('dir/qux/dir2/quux')
+
+ subvols = [
+ ('baz', 258),
+ ('dir/qux', 259),
+ ('dir/qux/dir2/quux', 260),
+ ('foo', 256),
+ ('foo/bar', 257),
+ ]
+
+ # Test various corner cases of the unprivileged implementation
+ # where we can't access the subvolume.
+ if os.geteuid() != 0:
+ with regain_privs():
+ # We don't have permission to traverse the path.
+ os.mkdir('directory_perms', 0o700)
+ btrfsutil.create_subvolume('directory_perms/subvol')
+
+ # We don't have permission to resolve the subvolume path.
+ os.mkdir('subvol_perms', 0o755)
+ btrfsutil.create_subvolume('subvol_perms/subvol')
+ os.chmod('subvol_perms/subvol', 0o700)
+
+ # The path doesn't exist.
+ os.mkdir('enoent', 0o755)
+ btrfsutil.create_subvolume('enoent/subvol')
+ subprocess.check_call(['mount', '-t', 'tmpfs', 'tmpfs', 'enoent'])
+
+ # The path exists but it's not a subvolume.
+ os.mkdir('notsubvol', 0o755)
+ btrfsutil.create_subvolume('notsubvol/subvol')
+ subprocess.check_call(['mount', '-t', 'tmpfs', 'tmpfs', 'notsubvol'])
+ os.mkdir('notsubvol/subvol')
+
+ # The path exists and is a subvolume, but on a different
+ # filesystem.
+ os.mkdir('wrongfs', 0o755)
+ btrfsutil.create_subvolume('wrongfs/subvol')
+ other_mountpoint, _ = self.mount_btrfs()
+ subprocess.check_call(['mount', '--bind', '--',
+ other_mountpoint, 'wrongfs/subvol'])
+
+ # The path exists and is a subvolume on the same
+ # filesystem, but not the right one.
+ os.mkdir('wrongsubvol', 0o755)
+ btrfsutil.create_subvolume('wrongsubvol/subvol')
+ subprocess.check_call(['mount', '--bind', 'baz', 'wrongsubvol/subvol'])
+
+
+ with btrfsutil.SubvolumeIterator('.') as it:
+ self.assertEqual(sorted(it), subvols)
+ with btrfsutil.SubvolumeIterator('.', post_order=True) as it:
+ self.assertEqual(sorted(it), subvols)
+
+ with btrfsutil.SubvolumeIterator('.') as it:
+ self.assertGreaterEqual(it.fileno(), 0)
+ it.close()
+ with self.assertRaises(ValueError):
+ next(iter(it))
+ with self.assertRaises(ValueError):
+ it.fileno()
+ it.close()
+
+ def test_subvolume_iterator(self):
+ pwd = os.getcwd()
+ try:
+ os.chdir(self.mountpoint)
+ self._test_subvolume_iterator()
+ finally:
+ os.chdir(pwd)
+
+ @skipUnlessHaveNobody
+ def test_subvolume_iterator_unprivileged(self):
+ os.chown(self.mountpoint, NOBODY_UID, -1)
+ pwd = os.getcwd()
+ try:
+ os.chdir(self.mountpoint)
+ with drop_privs():
+ self._test_subvolume_iterator()
finally:
os.chdir(pwd)
diff --git a/libbtrfsutil/stubs.c b/libbtrfsutil/stubs.c
index 9b9e037f..c530e408 100644
--- a/libbtrfsutil/stubs.c
+++ b/libbtrfsutil/stubs.c
@@ -19,6 +19,7 @@
#include <stdlib.h>
#include <errno.h>
+#include "stubs.h"
void *reallocarray(void *ptr, size_t nmemb, size_t size)
{
diff --git a/libbtrfsutil/subvolume.c b/libbtrfsutil/subvolume.c
index 867b3e10..60ab9f9d 100644
--- a/libbtrfsutil/subvolume.c
+++ b/libbtrfsutil/subvolume.c
@@ -31,6 +31,11 @@
#include "btrfsutil_internal.h"
+static bool is_root(void)
+{
+ return geteuid() == 0;
+}
+
/*
* This intentionally duplicates btrfs_util_is_subvolume_fd() instead of opening
* a file descriptor and calling it, because fstat() and fstatfs() don't accept
@@ -295,8 +300,8 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_info(const char *path,
return err;
}
-PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
- struct btrfs_util_subvolume_info *subvol)
+static enum btrfs_util_error get_subvolume_info_privileged(int fd, uint64_t id,
+ struct btrfs_util_subvolume_info *subvol)
{
struct btrfs_ioctl_search_args search = {
.key = {
@@ -310,27 +315,10 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
.nr_items = 0,
},
};
- enum btrfs_util_error err;
size_t items_pos = 0, buf_off = 0;
bool need_root_item = true, need_root_backref = true;
int ret;
- if (id == 0) {
- err = btrfs_util_is_subvolume_fd(fd);
- if (err)
- return err;
-
- err = btrfs_util_subvolume_id_fd(fd, &id);
- if (err)
- return err;
- }
-
- if ((id < BTRFS_FIRST_FREE_OBJECTID && id != BTRFS_FS_TREE_OBJECTID) ||
- id > BTRFS_LAST_FREE_OBJECTID) {
- errno = ENOENT;
- return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
- }
-
search.key.min_objectid = search.key.max_objectid = id;
if (subvol) {
@@ -400,6 +388,72 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
return BTRFS_UTIL_OK;
}
+static enum btrfs_util_error get_subvolume_info_unprivileged(int fd,
+ struct btrfs_util_subvolume_info *subvol)
+{
+ struct btrfs_ioctl_get_subvol_info_args info;
+ int ret;
+
+ ret = ioctl(fd, BTRFS_IOC_GET_SUBVOL_INFO, &info);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_GET_SUBVOL_INFO_FAILED;
+
+ subvol->id = info.treeid;
+ subvol->parent_id = info.parent_id;
+ subvol->dir_id = info.dirid;
+ subvol->flags = info.flags;
+ subvol->generation = info.generation;
+
+ memcpy(subvol->uuid, info.uuid, sizeof(subvol->uuid));
+ memcpy(subvol->parent_uuid, info.parent_uuid,
+ sizeof(subvol->parent_uuid));
+ memcpy(subvol->received_uuid, info.received_uuid,
+ sizeof(subvol->received_uuid));
+
+ subvol->ctransid = info.ctransid;
+ subvol->otransid = info.otransid;
+ subvol->stransid = info.stransid;
+ subvol->rtransid = info.rtransid;
+
+ subvol->ctime.tv_sec = info.ctime.sec;
+ subvol->ctime.tv_nsec = info.ctime.nsec;
+ subvol->otime.tv_sec = info.otime.sec;
+ subvol->otime.tv_nsec = info.otime.nsec;
+ subvol->stime.tv_sec = info.stime.sec;
+ subvol->stime.tv_nsec = info.stime.nsec;
+ subvol->rtime.tv_sec = info.rtime.sec;
+ subvol->rtime.tv_nsec = info.rtime.nsec;
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
+ struct btrfs_util_subvolume_info *subvol)
+{
+ enum btrfs_util_error err;
+
+ if (id == 0) {
+ err = btrfs_util_is_subvolume_fd(fd);
+ if (err)
+ return err;
+
+ if (!is_root())
+ return get_subvolume_info_unprivileged(fd, subvol);
+
+ err = btrfs_util_subvolume_id_fd(fd, &id);
+ if (err)
+ return err;
+ }
+
+ if ((id < BTRFS_FIRST_FREE_OBJECTID && id != BTRFS_FS_TREE_OBJECTID) ||
+ id > BTRFS_LAST_FREE_OBJECTID) {
+ errno = ENOENT;
+ return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
+ }
+
+ return get_subvolume_info_privileged(fd, id, subvol);
+}
+
PUBLIC enum btrfs_util_error btrfs_util_get_subvolume_read_only_fd(int fd,
bool *read_only_ret)
{
@@ -695,13 +749,28 @@ PUBLIC enum btrfs_util_error btrfs_util_create_subvolume_fd(int parent_fd,
#define BTRFS_UTIL_SUBVOLUME_ITERATOR_CLOSE_FD (1 << 30)
struct search_stack_entry {
- struct btrfs_ioctl_search_args search;
- size_t items_pos, buf_off;
+ union {
+ /* Used for subvolume_iterator_next_tree_search(). */
+ struct {
+ struct btrfs_ioctl_search_args search;
+ size_t buf_off;
+ };
+ /* Used for subvolume_iterator_next_unprivileged(). */
+ struct {
+ uint64_t id;
+ struct btrfs_ioctl_get_subvol_rootref_args rootref_args;
+ };
+ };
+ /* Used for both. */
+ size_t items_pos;
size_t path_len;
};
struct btrfs_util_subvolume_iterator {
+ bool use_tree_search;
int fd;
+ /* cur_fd is only used for subvolume_iterator_next_unprivileged(). */
+ int cur_fd;
int flags;
struct search_stack_entry *search_stack;
@@ -712,6 +781,58 @@ struct btrfs_util_subvolume_iterator {
size_t cur_path_capacity;
};
+static struct search_stack_entry *top_search_stack_entry(struct btrfs_util_subvolume_iterator *iter)
+{
+ return &iter->search_stack[iter->search_stack_len - 1];
+}
+
+/*
+ * Check that a path that we opened is the subvolume which we expect. It may not
+ * be if there is another filesystem mounted over a parent directory or the
+ * subvolume itself.
+ */
+static enum btrfs_util_error check_expected_subvolume(int fd, int parent_fd,
+ uint64_t tree_id)
+{
+ struct btrfs_ioctl_fs_info_args parent_fs_info, fs_info;
+ enum btrfs_util_error err;
+ uint64_t id;
+ int ret;
+
+ /* Make sure it's a subvolume. */
+ err = btrfs_util_is_subvolume_fd(fd);
+ if (err == BTRFS_UTIL_ERROR_NOT_BTRFS ||
+ err == BTRFS_UTIL_ERROR_NOT_SUBVOLUME) {
+ errno = ENOENT;
+ return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
+ } else if (err) {
+ return err;
+ }
+
+ /* Make sure it's on the same filesystem. */
+ ret = ioctl(parent_fd, BTRFS_IOC_FS_INFO, &parent_fs_info);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_FS_INFO_FAILED;
+ ret = ioctl(fd, BTRFS_IOC_FS_INFO, &fs_info);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_FS_INFO_FAILED;
+ if (memcmp(parent_fs_info.fsid, fs_info.fsid, sizeof(fs_info.fsid)) != 0) {
+ errno = ENOENT;
+ return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
+ }
+
+ /* Make sure it's the subvolume that we expected. */
+ err = btrfs_util_subvolume_id_fd(fd, &id);
+ if (err)
+ return err;
+ if (id != tree_id) {
+ errno = ENOENT;
+ return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
+ }
+
+ return BTRFS_UTIL_OK;
+}
+
static enum btrfs_util_error append_to_search_stack(struct btrfs_util_subvolume_iterator *iter,
uint64_t tree_id,
size_t path_len)
@@ -732,24 +853,84 @@ static enum btrfs_util_error append_to_search_stack(struct btrfs_util_subvolume_
iter->search_stack = new_search_stack;
}
- entry = &iter->search_stack[iter->search_stack_len++];
+ entry = &iter->search_stack[iter->search_stack_len];
- memset(&entry->search, 0, sizeof(entry->search));
- entry->search.key.tree_id = BTRFS_ROOT_TREE_OBJECTID;
- entry->search.key.min_objectid = tree_id;
- entry->search.key.max_objectid = tree_id;
- entry->search.key.min_type = BTRFS_ROOT_REF_KEY;
- entry->search.key.max_type = BTRFS_ROOT_REF_KEY;
- entry->search.key.min_offset = 0;
- entry->search.key.max_offset = UINT64_MAX;
- entry->search.key.min_transid = 0;
- entry->search.key.max_transid = UINT64_MAX;
- entry->search.key.nr_items = 0;
+ memset(entry, 0, sizeof(*entry));
+ entry->path_len = path_len;
+ if (iter->use_tree_search) {
+ entry->search.key.tree_id = BTRFS_ROOT_TREE_OBJECTID;
+ entry->search.key.min_objectid = tree_id;
+ entry->search.key.max_objectid = tree_id;
+ entry->search.key.min_type = BTRFS_ROOT_REF_KEY;
+ entry->search.key.max_type = BTRFS_ROOT_REF_KEY;
+ entry->search.key.min_offset = 0;
+ entry->search.key.max_offset = UINT64_MAX;
+ entry->search.key.min_transid = 0;
+ entry->search.key.max_transid = UINT64_MAX;
+ entry->search.key.nr_items = 0;
+ } else {
+ entry->id = tree_id;
- entry->items_pos = 0;
- entry->buf_off = 0;
+ if (iter->search_stack_len) {
+ struct search_stack_entry *top;
+ enum btrfs_util_error err;
+ char *path;
+ int fd;
- entry->path_len = path_len;
+ top = top_search_stack_entry(iter);
+ path = &iter->cur_path[top->path_len];
+ if (*path == '/')
+ path++;
+ fd = openat(iter->cur_fd, path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = check_expected_subvolume(fd, iter->cur_fd,
+ tree_id);
+ if (err) {
+ close(fd);
+ return err;
+ }
+
+ close(iter->cur_fd);
+ iter->cur_fd = fd;
+ }
+ }
+
+ iter->search_stack_len++;
+
+ return BTRFS_UTIL_OK;
+}
+
+static enum btrfs_util_error pop_search_stack(struct btrfs_util_subvolume_iterator *iter)
+{
+ struct search_stack_entry *top, *parent;
+ int fd, parent_fd;
+ size_t i;
+
+ if (iter->use_tree_search || iter->search_stack_len == 1) {
+ iter->search_stack_len--;
+ return BTRFS_UTIL_OK;
+ }
+
+ top = top_search_stack_entry(iter);
+ iter->search_stack_len--;
+ parent = top_search_stack_entry(iter);
+
+ fd = iter->cur_fd;
+ for (i = parent->path_len; i < top->path_len; i++) {
+ if (i == 0 || iter->cur_path[i] == '/') {
+ parent_fd = openat(fd, "..", O_RDONLY);
+ if (fd != iter->cur_fd)
+ SAVE_ERRNO_AND_CLOSE(fd);
+ if (parent_fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+ fd = parent_fd;
+ }
+ }
+ if (iter->cur_fd != iter->fd)
+ close(iter->cur_fd);
+ iter->cur_fd = fd;
return BTRFS_UTIL_OK;
}
@@ -782,12 +963,14 @@ PUBLIC enum btrfs_util_error btrfs_util_create_subvolume_iterator_fd(int fd,
{
struct btrfs_util_subvolume_iterator *iter;
enum btrfs_util_error err;
+ bool use_tree_search;
if (flags & ~BTRFS_UTIL_SUBVOLUME_ITERATOR_MASK) {
errno = EINVAL;
return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
}
+ use_tree_search = top != 0 || is_root();
if (top == 0) {
err = btrfs_util_is_subvolume_fd(fd);
if (err)
@@ -803,7 +986,9 @@ PUBLIC enum btrfs_util_error btrfs_util_create_subvolume_iterator_fd(int fd,
return BTRFS_UTIL_ERROR_NO_MEMORY;
iter->fd = fd;
+ iter->cur_fd = fd;
iter->flags = flags;
+ iter->use_tree_search = use_tree_search;
iter->search_stack_len = 0;
iter->search_stack_capacity = 4;
@@ -1112,6 +1297,8 @@ PUBLIC void btrfs_util_destroy_subvolume_iterator(struct btrfs_util_subvolume_it
if (iter) {
free(iter->cur_path);
free(iter->search_stack);
+ if (iter->cur_fd != iter->fd)
+ SAVE_ERRNO_AND_CLOSE(iter->cur_fd);
if (iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_CLOSE_FD)
SAVE_ERRNO_AND_CLOSE(iter->fd);
free(iter);
@@ -1123,32 +1310,14 @@ PUBLIC int btrfs_util_subvolume_iterator_fd(const struct btrfs_util_subvolume_it
return iter->fd;
}
-static struct search_stack_entry *top_search_stack_entry(struct btrfs_util_subvolume_iterator *iter)
-{
- return &iter->search_stack[iter->search_stack_len - 1];
-}
-
static enum btrfs_util_error build_subvol_path(struct btrfs_util_subvolume_iterator *iter,
- const struct btrfs_ioctl_search_header *header,
- const struct btrfs_root_ref *ref,
- const char *name,
+ const char *name, size_t name_len,
+ const char *dir, size_t dir_len,
size_t *path_len_ret)
{
- struct btrfs_ioctl_ino_lookup_args lookup = {
- .treeid = header->objectid,
- .objectid = le64_to_cpu(ref->dirid),
- };
struct search_stack_entry *top = top_search_stack_entry(iter);
- size_t dir_len, name_len, path_len;
+ size_t path_len;
char *p;
- int ret;
-
- ret = ioctl(iter->fd, BTRFS_IOC_INO_LOOKUP, &lookup);
- if (ret == -1)
- return BTRFS_UTIL_ERROR_INO_LOOKUP_FAILED;
-
- dir_len = strlen(lookup.name);
- name_len = le16_to_cpu(ref->name_len);
path_len = top->path_len;
/*
@@ -1166,33 +1335,75 @@ static enum btrfs_util_error build_subvol_path(struct btrfs_util_subvolume_itera
path_len++;
path_len += name_len;
- if (path_len > iter->cur_path_capacity) {
- char *tmp = realloc(iter->cur_path, path_len);
+ /* We need one extra character for the NUL terminator. */
+ if (path_len + 1 > iter->cur_path_capacity) {
+ char *tmp = realloc(iter->cur_path, path_len + 1);
if (!tmp)
return BTRFS_UTIL_ERROR_NO_MEMORY;
iter->cur_path = tmp;
- iter->cur_path_capacity = path_len;
+ iter->cur_path_capacity = path_len + 1;
}
p = iter->cur_path + top->path_len;
if (top->path_len && dir_len)
*p++ = '/';
- memcpy(p, lookup.name, dir_len);
+ memcpy(p, dir, dir_len);
p += dir_len;
if (top->path_len && !dir_len && name_len)
*p++ = '/';
memcpy(p, name, name_len);
p += name_len;
+ *p = '\0';
*path_len_ret = path_len;
return BTRFS_UTIL_OK;
}
-PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_util_subvolume_iterator *iter,
- char **path_ret,
- uint64_t *id_ret)
+static enum btrfs_util_error build_subvol_path_privileged(struct btrfs_util_subvolume_iterator *iter,
+ const struct btrfs_ioctl_search_header *header,
+ const struct btrfs_root_ref *ref,
+ const char *name,
+ size_t *path_len_ret)
+{
+ struct btrfs_ioctl_ino_lookup_args lookup = {
+ .treeid = header->objectid,
+ .objectid = le64_to_cpu(ref->dirid),
+ };
+ int ret;
+
+ ret = ioctl(iter->fd, BTRFS_IOC_INO_LOOKUP, &lookup);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_INO_LOOKUP_FAILED;
+
+ return build_subvol_path(iter, name, le16_to_cpu(ref->name_len),
+ lookup.name, strlen(lookup.name),
+ path_len_ret);
+}
+
+static enum btrfs_util_error build_subvol_path_unprivileged(struct btrfs_util_subvolume_iterator *iter,
+ uint64_t treeid,
+ uint64_t dirid,
+ size_t *path_len_ret)
+{
+ struct btrfs_ioctl_ino_lookup_user_args args = {
+ .treeid = treeid,
+ .dirid = dirid,
+ };
+ int ret;
+
+ ret = ioctl(iter->cur_fd, BTRFS_IOC_INO_LOOKUP_USER, &args);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_INO_LOOKUP_USER_FAILED;
+
+ return build_subvol_path(iter, args.name, strlen(args.name),
+ args.path, strlen(args.path), path_len_ret);
+}
+
+static enum btrfs_util_error subvolume_iterator_next_tree_search(struct btrfs_util_subvolume_iterator *iter,
+ char **path_ret,
+ uint64_t *id_ret)
{
struct search_stack_entry *top;
const struct btrfs_ioctl_search_header *header;
@@ -1219,7 +1430,10 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_uti
top->buf_off = 0;
if (top->search.key.nr_items == 0) {
- iter->search_stack_len--;
+ /*
+ * This never fails for use_tree_search.
+ */
+ pop_search_stack(iter);
if ((iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER) &&
iter->search_stack_len)
goto out;
@@ -1239,7 +1453,8 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_uti
ref = (struct btrfs_root_ref *)(header + 1);
name = (const char *)(ref + 1);
- err = build_subvol_path(iter, header, ref, name, &path_len);
+ err = build_subvol_path_privileged(iter, header, ref, name,
+ &path_len);
if (err)
return err;
@@ -1266,6 +1481,100 @@ out:
return BTRFS_UTIL_OK;
}
+static enum btrfs_util_error subvolume_iterator_next_unprivileged(struct btrfs_util_subvolume_iterator *iter,
+ char **path_ret,
+ uint64_t *id_ret)
+{
+ struct search_stack_entry *top;
+ uint64_t treeid, dirid;
+ enum btrfs_util_error err;
+ size_t path_len;
+ int ret;
+
+ for (;;) {
+ for (;;) {
+ if (iter->search_stack_len == 0)
+ return BTRFS_UTIL_ERROR_STOP_ITERATION;
+
+ top = top_search_stack_entry(iter);
+ if (top->items_pos < top->rootref_args.num_items) {
+ break;
+ } else {
+ ret = ioctl(iter->cur_fd,
+ BTRFS_IOC_GET_SUBVOL_ROOTREF,
+ &top->rootref_args);
+ if (ret == -1 && errno != EOVERFLOW)
+ return BTRFS_UTIL_ERROR_GET_SUBVOL_ROOTREF_FAILED;
+ top->items_pos = 0;
+
+ if (top->rootref_args.num_items == 0) {
+ err = pop_search_stack(iter);
+ if (err)
+ return err;
+ if ((iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER) &&
+ iter->search_stack_len)
+ goto out;
+ }
+ }
+ }
+
+ treeid = top->rootref_args.rootref[top->items_pos].treeid;
+ dirid = top->rootref_args.rootref[top->items_pos].dirid;
+ top->items_pos++;
+ err = build_subvol_path_unprivileged(iter, treeid, dirid,
+ &path_len);
+ if (err) {
+ /* Skip the subvolume if we can't access it. */
+ if (errno == EACCES)
+ continue;
+ return err;
+ }
+
+ err = append_to_search_stack(iter, treeid, path_len);
+ if (err) {
+ /*
+ * Skip the subvolume if it does not exist (which can
+ * happen if there is another filesystem mounted over a
+ * parent directory) or we don't have permission to
+ * access it.
+ */
+ if (errno == ENOENT || errno == EACCES)
+ continue;
+ return err;
+ }
+
+ if (!(iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER)) {
+ top = top_search_stack_entry(iter);
+ goto out;
+ }
+ }
+
+out:
+ if (path_ret) {
+ *path_ret = malloc(top->path_len + 1);
+ if (!*path_ret)
+ return BTRFS_UTIL_ERROR_NO_MEMORY;
+ memcpy(*path_ret, iter->cur_path, top->path_len);
+ (*path_ret)[top->path_len] = '\0';
+ }
+ if (id_ret)
+ *id_ret = top->id;
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_util_subvolume_iterator *iter,
+ char **path_ret,
+ uint64_t *id_ret)
+{
+ if (iter->use_tree_search) {
+ return subvolume_iterator_next_tree_search(iter, path_ret,
+ id_ret);
+ } else {
+ return subvolume_iterator_next_unprivileged(iter, path_ret,
+ id_ret);
+ }
+}
+
PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next_info(struct btrfs_util_subvolume_iterator *iter,
char **path_ret,
struct btrfs_util_subvolume_info *subvol)
@@ -1277,7 +1586,10 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next_info(struct btrf
if (err)
return err;
- return btrfs_util_subvolume_info_fd(iter->fd, id, subvol);
+ if (iter->use_tree_search)
+ return btrfs_util_subvolume_info_fd(iter->fd, id, subvol);
+ else
+ return btrfs_util_subvolume_info_fd(iter->cur_fd, 0, subvol);
}
PUBLIC enum btrfs_util_error btrfs_util_deleted_subvolumes(const char *path,