libbtrfsutil: add btrfs_util_is_subvolume() and btrfs_util_subvolume_id()

These are the most trivial helpers in the library and will be used to
implement several of the more involved functions.

Signed-off-by: Omar Sandoval <osandov@fb.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Omar Sandoval 2018-01-18 12:49:55 -08:00 committed by David Sterba
parent 1b2775bdb0
commit 92d4035074
7 changed files with 271 additions and 1 deletions

View File

@ -136,7 +136,7 @@ libbtrfsutil_minor := $(shell sed -rn 's/^\#define BTRFS_UTIL_VERSION_MINOR ([0-
libbtrfsutil_patch := $(shell sed -rn 's/^\#define BTRFS_UTIL_VERSION_PATCH ([0-9])+$$/\1/p' libbtrfsutil/btrfsutil.h)
libbtrfsutil_version := $(libbtrfsutil_major).$(libbtrfsutil_minor).$(libbtrfsutil_patch)
libbtrfsutil_objects = libbtrfsutil/errors.o libbtrfsutil/filesystem.o \
libbtrfsutil/qgroup.o
libbtrfsutil/subvolume.o libbtrfsutil/qgroup.o
convert_objects = convert/main.o convert/common.o convert/source-fs.o \
convert/source-ext2.o convert/source-reiserfs.o
mkfs_objects = mkfs/main.o mkfs/common.o mkfs/rootdir.o

View File

@ -61,6 +61,8 @@ void SetFromBtrfsUtilErrorWithPaths(enum btrfs_util_error err,
PyObject *filesystem_sync(PyObject *self, PyObject *args, PyObject *kwds);
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);
void add_module_constants(PyObject *m);

View File

@ -153,6 +153,18 @@ static PyMethodDef btrfsutil_methods[] = {
"path -- string, bytes, path-like object, or open file descriptor\n"
"transid -- int transaction ID to wait for, or zero for the current\n"
"transaction"},
{"is_subvolume", (PyCFunction)is_subvolume,
METH_VARARGS | METH_KEYWORDS,
"is_subvolume(path) -> bool\n\n"
"Get whether a file is a subvolume.\n\n"
"Arguments:\n"
"path -- string, bytes, path-like object, or open file descriptor"},
{"subvolume_id", (PyCFunction)subvolume_id,
METH_VARARGS | METH_KEYWORDS,
"subvolume_id(path) -> int\n\n"
"Get the ID of the subvolume containing a file.\n\n"
"Arguments:\n"
"path -- string, bytes, path-like object, or open file descriptor"},
{},
};

View File

@ -93,6 +93,7 @@ module = Extension(
'filesystem.c',
'module.c',
'qgroup.c',
'subvolume.c',
],
include_dirs=['..'],
library_dirs=['../..'],

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2018 Facebook
*
* This file is part of libbtrfsutil.
*
* libbtrfsutil is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libbtrfsutil is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
*/
#include "btrfsutilpy.h"
PyObject *is_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {"path", NULL};
struct path_arg path = {.allow_fd = true};
enum btrfs_util_error err;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:is_subvolume",
keywords, &path_converter, &path))
return NULL;
if (path.path)
err = btrfs_util_is_subvolume(path.path);
else
err = btrfs_util_is_subvolume_fd(path.fd);
if (err == BTRFS_UTIL_OK) {
path_cleanup(&path);
Py_RETURN_TRUE;
} else if (err == BTRFS_UTIL_ERROR_NOT_BTRFS ||
err == BTRFS_UTIL_ERROR_NOT_SUBVOLUME) {
path_cleanup(&path);
Py_RETURN_FALSE;
} else {
SetFromBtrfsUtilErrorWithPath(err, &path);
path_cleanup(&path);
return NULL;
}
}
PyObject *subvolume_id(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {"path", NULL};
struct path_arg path = {.allow_fd = true};
enum btrfs_util_error err;
uint64_t id;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:subvolume_id",
keywords, &path_converter, &path))
return NULL;
if (path.path)
err = btrfs_util_subvolume_id(path.path, &id);
else
err = btrfs_util_subvolume_id_fd(path.fd, &id);
if (err) {
SetFromBtrfsUtilErrorWithPath(err, &path);
path_cleanup(&path);
return NULL;
}
path_cleanup(&path);
return PyLong_FromUnsignedLongLong(id);
}

View File

@ -0,0 +1,57 @@
# Copyright (C) 2018 Facebook
#
# This file is part of libbtrfsutil.
#
# libbtrfsutil is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# libbtrfsutil is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# 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 fcntl
import errno
import os
import os.path
from pathlib import PurePath
import traceback
import btrfsutil
from tests import BtrfsTestCase
class TestSubvolume(BtrfsTestCase):
def test_is_subvolume(self):
dir = os.path.join(self.mountpoint, 'foo')
os.mkdir(dir)
for arg in self.path_or_fd(self.mountpoint):
with self.subTest(type=type(arg)):
self.assertTrue(btrfsutil.is_subvolume(arg))
for arg in self.path_or_fd(dir):
with self.subTest(type=type(arg)):
self.assertFalse(btrfsutil.is_subvolume(arg))
with self.assertRaises(btrfsutil.BtrfsUtilError) as e:
btrfsutil.is_subvolume(os.path.join(self.mountpoint, 'bar'))
# This is a bit of an implementation detail, but really this is testing
# that the exception is initialized correctly.
self.assertEqual(e.exception.btrfsutilerror, btrfsutil.ERROR_STATFS_FAILED)
self.assertEqual(e.exception.errno, errno.ENOENT)
def test_subvolume_id(self):
dir = os.path.join(self.mountpoint, 'foo')
os.mkdir(dir)
for arg in self.path_or_fd(self.mountpoint):
with self.subTest(type=type(arg)):
self.assertEqual(btrfsutil.subvolume_id(arg), 5)
for arg in self.path_or_fd(dir):
with self.subTest(type=type(arg)):
self.assertEqual(btrfsutil.subvolume_id(arg), 5)

125
libbtrfsutil/subvolume.c Normal file
View File

@ -0,0 +1,125 @@
/*
* Copyright (C) 2018 Facebook
*
* This file is part of libbtrfsutil.
*
* libbtrfsutil is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libbtrfsutil is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/vfs.h>
#include <linux/magic.h>
#include "btrfsutil_internal.h"
/*
* This intentionally duplicates btrfs_util_is_subvolume_fd() instead of opening
* a file descriptor and calling it, because fstat() and fstatfs() don't accept
* file descriptors opened with O_PATH on old kernels (before v3.6 and before
* v3.12, respectively), but stat() and statfs() can be called on a path that
* the user doesn't have read or write permissions to.
*/
PUBLIC enum btrfs_util_error btrfs_util_is_subvolume(const char *path)
{
struct statfs sfs;
struct stat st;
int ret;
ret = statfs(path, &sfs);
if (ret == -1)
return BTRFS_UTIL_ERROR_STATFS_FAILED;
if (sfs.f_type != BTRFS_SUPER_MAGIC) {
errno = EINVAL;
return BTRFS_UTIL_ERROR_NOT_BTRFS;
}
ret = stat(path, &st);
if (ret == -1)
return BTRFS_UTIL_ERROR_STAT_FAILED;
if (st.st_ino != BTRFS_FIRST_FREE_OBJECTID || !S_ISDIR(st.st_mode)) {
errno = EINVAL;
return BTRFS_UTIL_ERROR_NOT_SUBVOLUME;
}
return BTRFS_UTIL_OK;
}
PUBLIC enum btrfs_util_error btrfs_util_is_subvolume_fd(int fd)
{
struct statfs sfs;
struct stat st;
int ret;
ret = fstatfs(fd, &sfs);
if (ret == -1)
return BTRFS_UTIL_ERROR_STATFS_FAILED;
if (sfs.f_type != BTRFS_SUPER_MAGIC) {
errno = EINVAL;
return BTRFS_UTIL_ERROR_NOT_BTRFS;
}
ret = fstat(fd, &st);
if (ret == -1)
return BTRFS_UTIL_ERROR_STAT_FAILED;
if (st.st_ino != BTRFS_FIRST_FREE_OBJECTID || !S_ISDIR(st.st_mode)) {
errno = EINVAL;
return BTRFS_UTIL_ERROR_NOT_SUBVOLUME;
}
return BTRFS_UTIL_OK;
}
PUBLIC enum btrfs_util_error btrfs_util_subvolume_id(const char *path,
uint64_t *id_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_id_fd(fd, id_ret);
SAVE_ERRNO_AND_CLOSE(fd);
return err;
}
PUBLIC enum btrfs_util_error btrfs_util_subvolume_id_fd(int fd,
uint64_t *id_ret)
{
struct btrfs_ioctl_ino_lookup_args args = {
.treeid = 0,
.objectid = BTRFS_FIRST_FREE_OBJECTID,
};
int ret;
ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args);
if (ret == -1) {
close(fd);
return BTRFS_UTIL_ERROR_INO_LOOKUP_FAILED;
}
*id_ret = args.treeid;
return BTRFS_UTIL_OK;
}