libbtrfsutil: relax the privileges of subvolume_info()

Attempt to use the BTRFS_IOC_GET_SUBVOL_INFO ioctl (added in kernel
4.18) for subvolume_info() if not root. Also, rename
get_subvolume_info_root() -> get_subvolume_info_privileged() for
consistency with further changes.

This is based on a patch from Misono Tomohiro.

Signed-off-by: Omar Sandoval <osandov@fb.com>
Signed-off-by: David Sterba <dsterba@suse.com>
master
Omar Sandoval 2018-11-13 23:47:02 -08:00 committed by David Sterba
parent 39ac43a2a4
commit bfe2dc3796
4 changed files with 89 additions and 12 deletions

View File

@ -63,6 +63,7 @@ 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,
};
/**
@ -266,7 +267,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.
*/

View File

@ -45,6 +45,8 @@ 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",
};
PUBLIC const char *btrfs_util_strerror(enum btrfs_util_error err)

View File

@ -23,7 +23,12 @@ from pathlib import PurePath
import traceback
import btrfsutil
from tests import BtrfsTestCase, HAVE_PATH_LIKE
from tests import (
BtrfsTestCase,
drop_privs,
HAVE_PATH_LIKE,
skipUnlessHaveNobody,
)
class TestSubvolume(BtrfsTestCase):
@ -87,7 +92,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 +105,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 +114,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 +134,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):

View File

@ -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;
}
static enum btrfs_util_error get_subvolume_info_root(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 = {
@ -383,6 +388,45 @@ static enum btrfs_util_error get_subvolume_info_root(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)
{
@ -393,6 +437,9 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
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;
@ -404,7 +451,7 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
}
return get_subvolume_info_root(fd, id, subvol);
return get_subvolume_info_privileged(fd, id, subvol);
}
PUBLIC enum btrfs_util_error btrfs_util_get_subvolume_read_only_fd(int fd,