forked from Mirrors/btrfs-progs
libbtrfsutil: add btrfs_util_subvolume_path()
We can just walk up root backrefs with BTRFS_IOC_TREE_SEARCH and inode paths with BTRFS_IOC_INO_LOOKUP. Signed-off-by: Omar Sandoval <osandov@fb.com> Signed-off-by: David Sterba <dsterba@suse.com>master
parent
f676a8ad11
commit
8b87811f94
|
@ -147,6 +147,27 @@ enum btrfs_util_error btrfs_util_subvolume_id(const char *path,
|
||||||
*/
|
*/
|
||||||
enum btrfs_util_error btrfs_util_subvolume_id_fd(int fd, uint64_t *id_ret);
|
enum btrfs_util_error btrfs_util_subvolume_id_fd(int fd, uint64_t *id_ret);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* btrfs_util_subvolume_path() - Get the path of the subvolume with a given ID
|
||||||
|
* relative to the filesystem root.
|
||||||
|
* @path: Path on a Btrfs filesystem.
|
||||||
|
* @id: ID of subvolume to set as the default. If zero is given, the subvolume
|
||||||
|
* ID of @path is used.
|
||||||
|
* @path_ret: Returned path.
|
||||||
|
*
|
||||||
|
* This requires appropriate privilege (CAP_SYS_ADMIN).
|
||||||
|
*
|
||||||
|
* Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
|
||||||
|
*/
|
||||||
|
enum btrfs_util_error btrfs_util_subvolume_path(const char *path, uint64_t id,
|
||||||
|
char **path_ret);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* btrfs_util_subvolume_path_fd() - See btrfs_util_subvolume_path().
|
||||||
|
*/
|
||||||
|
enum btrfs_util_error btrfs_util_subvolume_path_fd(int fd, uint64_t id,
|
||||||
|
char **path_ret);
|
||||||
|
|
||||||
struct btrfs_util_qgroup_inherit;
|
struct btrfs_util_qgroup_inherit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -63,6 +63,7 @@ PyObject *start_sync(PyObject *self, PyObject *args, PyObject *kwds);
|
||||||
PyObject *wait_sync(PyObject *self, PyObject *args, PyObject *kwds);
|
PyObject *wait_sync(PyObject *self, PyObject *args, PyObject *kwds);
|
||||||
PyObject *is_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
|
PyObject *is_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
|
||||||
PyObject *subvolume_id(PyObject *self, PyObject *args, PyObject *kwds);
|
PyObject *subvolume_id(PyObject *self, PyObject *args, PyObject *kwds);
|
||||||
|
PyObject *subvolume_path(PyObject *self, PyObject *args, PyObject *kwds);
|
||||||
PyObject *create_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
|
PyObject *create_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
|
||||||
|
|
||||||
void add_module_constants(PyObject *m);
|
void add_module_constants(PyObject *m);
|
||||||
|
|
|
@ -165,6 +165,14 @@ static PyMethodDef btrfsutil_methods[] = {
|
||||||
"Get the ID of the subvolume containing a file.\n\n"
|
"Get the ID of the subvolume containing a file.\n\n"
|
||||||
"Arguments:\n"
|
"Arguments:\n"
|
||||||
"path -- string, bytes, path-like object, or open file descriptor"},
|
"path -- string, bytes, path-like object, or open file descriptor"},
|
||||||
|
{"subvolume_path", (PyCFunction)subvolume_path,
|
||||||
|
METH_VARARGS | METH_KEYWORDS,
|
||||||
|
"subvolume_path(path, id=0) -> int\n\n"
|
||||||
|
"Get the path of a subvolume relative to the filesystem root.\n\n"
|
||||||
|
"Arguments:\n"
|
||||||
|
"path -- string, bytes, path-like object, or open file descriptor\n"
|
||||||
|
"id -- if not zero, instead of returning the subvolume path of the\n"
|
||||||
|
"given path, return the path of the subvolume with this ID"},
|
||||||
{"create_subvolume", (PyCFunction)create_subvolume,
|
{"create_subvolume", (PyCFunction)create_subvolume,
|
||||||
METH_VARARGS | METH_KEYWORDS,
|
METH_VARARGS | METH_KEYWORDS,
|
||||||
"create_subvolume(path, async=False)\n\n"
|
"create_subvolume(path, async=False)\n\n"
|
||||||
|
|
|
@ -72,6 +72,36 @@ PyObject *subvolume_id(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
return PyLong_FromUnsignedLongLong(id);
|
return PyLong_FromUnsignedLongLong(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *subvolume_path(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
static char *keywords[] = {"path", "id", NULL};
|
||||||
|
struct path_arg path = {.allow_fd = true};
|
||||||
|
enum btrfs_util_error err;
|
||||||
|
uint64_t id = 0;
|
||||||
|
char *subvol_path;
|
||||||
|
PyObject *ret;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|K:subvolume_path",
|
||||||
|
keywords, &path_converter, &path, &id))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (path.path)
|
||||||
|
err = btrfs_util_subvolume_path(path.path, id, &subvol_path);
|
||||||
|
else
|
||||||
|
err = btrfs_util_subvolume_path_fd(path.fd, id, &subvol_path);
|
||||||
|
if (err) {
|
||||||
|
SetFromBtrfsUtilErrorWithPath(err, &path);
|
||||||
|
path_cleanup(&path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
path_cleanup(&path);
|
||||||
|
|
||||||
|
ret = PyUnicode_DecodeFSDefault(subvol_path);
|
||||||
|
free(subvol_path);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject *create_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};
|
||||||
|
|
|
@ -56,6 +56,37 @@ class TestSubvolume(BtrfsTestCase):
|
||||||
with self.subTest(type=type(arg)):
|
with self.subTest(type=type(arg)):
|
||||||
self.assertEqual(btrfsutil.subvolume_id(arg), 5)
|
self.assertEqual(btrfsutil.subvolume_id(arg), 5)
|
||||||
|
|
||||||
|
def test_subvolume_path(self):
|
||||||
|
btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'subvol1'))
|
||||||
|
os.mkdir(os.path.join(self.mountpoint, 'dir1'))
|
||||||
|
os.mkdir(os.path.join(self.mountpoint, 'dir1/dir2'))
|
||||||
|
btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'dir1/dir2/subvol2'))
|
||||||
|
btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'dir1/dir2/subvol2/subvol3'))
|
||||||
|
os.mkdir(os.path.join(self.mountpoint, 'subvol1/dir3'))
|
||||||
|
btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'subvol1/dir3/subvol4'))
|
||||||
|
|
||||||
|
for arg in self.path_or_fd(self.mountpoint):
|
||||||
|
with self.subTest(type=type(arg)):
|
||||||
|
self.assertEqual(btrfsutil.subvolume_path(arg), '')
|
||||||
|
self.assertEqual(btrfsutil.subvolume_path(arg, 5), '')
|
||||||
|
self.assertEqual(btrfsutil.subvolume_path(arg, 256), 'subvol1')
|
||||||
|
self.assertEqual(btrfsutil.subvolume_path(arg, 257), 'dir1/dir2/subvol2')
|
||||||
|
self.assertEqual(btrfsutil.subvolume_path(arg, 258), 'dir1/dir2/subvol2/subvol3')
|
||||||
|
self.assertEqual(btrfsutil.subvolume_path(arg, 259), 'subvol1/dir3/subvol4')
|
||||||
|
|
||||||
|
pwd = os.getcwd()
|
||||||
|
try:
|
||||||
|
os.chdir(self.mountpoint)
|
||||||
|
path = ''
|
||||||
|
for i in range(26):
|
||||||
|
name = chr(ord('a') + i) * 255
|
||||||
|
path = os.path.join(path, name)
|
||||||
|
btrfsutil.create_subvolume(name)
|
||||||
|
os.chdir(name)
|
||||||
|
self.assertEqual(btrfsutil.subvolume_path('.'), path)
|
||||||
|
finally:
|
||||||
|
os.chdir(pwd)
|
||||||
|
|
||||||
def test_create_subvolume(self):
|
def test_create_subvolume(self):
|
||||||
subvol = os.path.join(self.mountpoint, 'subvol')
|
subvol = os.path.join(self.mountpoint, 'subvol')
|
||||||
|
|
||||||
|
|
|
@ -126,6 +126,131 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_id_fd(int fd,
|
||||||
return BTRFS_UTIL_OK;
|
return BTRFS_UTIL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PUBLIC enum btrfs_util_error btrfs_util_subvolume_path(const char *path,
|
||||||
|
uint64_t id,
|
||||||
|
char **path_ret)
|
||||||
|
{
|
||||||
|
enum btrfs_util_error err;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY);
|
||||||
|
if (fd == -1)
|
||||||
|
return BTRFS_UTIL_ERROR_OPEN_FAILED;
|
||||||
|
|
||||||
|
err = btrfs_util_subvolume_path_fd(fd, id, path_ret);
|
||||||
|
SAVE_ERRNO_AND_CLOSE(fd);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUBLIC enum btrfs_util_error btrfs_util_subvolume_path_fd(int fd, uint64_t id,
|
||||||
|
char **path_ret)
|
||||||
|
{
|
||||||
|
char *path, *p;
|
||||||
|
size_t capacity = 4096;
|
||||||
|
|
||||||
|
if (id == 0) {
|
||||||
|
enum btrfs_util_error err;
|
||||||
|
|
||||||
|
err = btrfs_util_is_subvolume_fd(fd);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = btrfs_util_subvolume_id_fd(fd, &id);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = malloc(capacity);
|
||||||
|
if (!path)
|
||||||
|
return BTRFS_UTIL_ERROR_NO_MEMORY;
|
||||||
|
p = path + capacity - 1;
|
||||||
|
p[0] = '\0';
|
||||||
|
|
||||||
|
while (id != BTRFS_FS_TREE_OBJECTID) {
|
||||||
|
struct btrfs_ioctl_search_args search = {
|
||||||
|
.key = {
|
||||||
|
.tree_id = BTRFS_ROOT_TREE_OBJECTID,
|
||||||
|
.min_objectid = id,
|
||||||
|
.max_objectid = id,
|
||||||
|
.min_type = BTRFS_ROOT_BACKREF_KEY,
|
||||||
|
.max_type = BTRFS_ROOT_BACKREF_KEY,
|
||||||
|
.min_offset = 0,
|
||||||
|
.max_offset = UINT64_MAX,
|
||||||
|
.min_transid = 0,
|
||||||
|
.max_transid = UINT64_MAX,
|
||||||
|
.nr_items = 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
struct btrfs_ioctl_ino_lookup_args lookup;
|
||||||
|
const struct btrfs_ioctl_search_header *header;
|
||||||
|
const struct btrfs_root_ref *ref;
|
||||||
|
const char *name;
|
||||||
|
uint16_t name_len;
|
||||||
|
size_t lookup_len;
|
||||||
|
size_t total_len;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search);
|
||||||
|
if (ret == -1) {
|
||||||
|
free(path);
|
||||||
|
return BTRFS_UTIL_ERROR_SEARCH_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search.key.nr_items == 0) {
|
||||||
|
free(path);
|
||||||
|
errno = ENOENT;
|
||||||
|
return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
header = (struct btrfs_ioctl_search_header *)search.buf;
|
||||||
|
ref = (struct btrfs_root_ref *)(header + 1);
|
||||||
|
name = (char *)(ref + 1);
|
||||||
|
name_len = le16_to_cpu(ref->name_len);
|
||||||
|
|
||||||
|
id = header->offset;
|
||||||
|
|
||||||
|
lookup.treeid = id;
|
||||||
|
lookup.objectid = le64_to_cpu(ref->dirid);
|
||||||
|
ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &lookup);
|
||||||
|
if (ret == -1) {
|
||||||
|
free(path);
|
||||||
|
return BTRFS_UTIL_ERROR_SEARCH_FAILED;
|
||||||
|
}
|
||||||
|
lookup_len = strlen(lookup.name);
|
||||||
|
|
||||||
|
total_len = name_len + lookup_len + (id != BTRFS_FS_TREE_OBJECTID);
|
||||||
|
if (p - total_len < path) {
|
||||||
|
char *new_path, *new_p;
|
||||||
|
size_t new_capacity = capacity * 2;
|
||||||
|
|
||||||
|
new_path = malloc(new_capacity);
|
||||||
|
if (!new_path) {
|
||||||
|
free(path);
|
||||||
|
return BTRFS_UTIL_ERROR_NO_MEMORY;
|
||||||
|
}
|
||||||
|
new_p = new_path + new_capacity - (path + capacity - p);
|
||||||
|
memcpy(new_p, p, path + capacity - p);
|
||||||
|
free(path);
|
||||||
|
path = new_path;
|
||||||
|
p = new_p;
|
||||||
|
capacity = new_capacity;
|
||||||
|
}
|
||||||
|
p -= name_len;
|
||||||
|
memcpy(p, name, name_len);
|
||||||
|
p -= lookup_len;
|
||||||
|
memcpy(p, lookup.name, lookup_len);
|
||||||
|
if (id != BTRFS_FS_TREE_OBJECTID)
|
||||||
|
*--p = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p != path)
|
||||||
|
memmove(path, p, path + capacity - p);
|
||||||
|
|
||||||
|
*path_ret = path;
|
||||||
|
|
||||||
|
return BTRFS_UTIL_OK;
|
||||||
|
}
|
||||||
|
|
||||||
static enum btrfs_util_error openat_parent_and_name(int dirfd, const char *path,
|
static enum btrfs_util_error openat_parent_and_name(int dirfd, const char *path,
|
||||||
char *name, size_t name_len,
|
char *name, size_t name_len,
|
||||||
int *fd)
|
int *fd)
|
||||||
|
|
Loading…
Reference in New Issue