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);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -63,6 +63,7 @@ PyObject *start_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 *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);
|
||||
|
||||
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"
|
||||
"Arguments:\n"
|
||||
"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,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"create_subvolume(path, async=False)\n\n"
|
||||
|
|
|
@ -72,6 +72,36 @@ PyObject *subvolume_id(PyObject *self, PyObject *args, PyObject *kwds)
|
|||
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)
|
||||
{
|
||||
static char *keywords[] = {"path", "async", "qgroup_inherit", NULL};
|
||||
|
|
|
@ -56,6 +56,37 @@ class TestSubvolume(BtrfsTestCase):
|
|||
with self.subTest(type=type(arg)):
|
||||
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):
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
char *name, size_t name_len,
|
||||
int *fd)
|
||||
|
|
Loading…
Reference in New Issue