diff mbox

[v2,04/27] libbtrfsutil: add btrfs_util_is_subvolume() and btrfs_util_subvolume_id()

Message ID 72c661cf7dc5cd27373ff0d19eb77cea14ce391c.1518720598.git.osandov@fb.com (mailing list archive)
State New, archived
Headers show

Commit Message

Omar Sandoval Feb. 15, 2018, 7:04 p.m. UTC
From: Omar Sandoval <osandov@fb.com>

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>
---
 Makefile                                    |   2 +-
 libbtrfsutil/btrfsutil.h                    |  33 ++++++++
 libbtrfsutil/python/btrfsutilpy.h           |   3 +
 libbtrfsutil/python/module.c                |  12 +++
 libbtrfsutil/python/setup.py                |   1 +
 libbtrfsutil/python/subvolume.c             |  73 ++++++++++++++++
 libbtrfsutil/python/tests/__init__.py       |  66 +++++++++++++++
 libbtrfsutil/python/tests/test_subvolume.py |  57 +++++++++++++
 libbtrfsutil/subvolume.c                    | 127 ++++++++++++++++++++++++++++
 9 files changed, 373 insertions(+), 1 deletion(-)
 create mode 100644 libbtrfsutil/python/subvolume.c
 create mode 100644 libbtrfsutil/python/tests/test_subvolume.py
 create mode 100644 libbtrfsutil/subvolume.c

Comments

David Sterba Feb. 21, 2018, 11:43 a.m. UTC | #1
On Thu, Feb 15, 2018 at 11:04:49AM -0800, Omar Sandoval wrote:
> --- /dev/null
> +++ b/libbtrfsutil/subvolume.c
> @@ -0,0 +1,127 @@
> +/*
> + * 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/btrfs.h>
> +#include <linux/btrfs_tree.h>

How is this supposed to work? This pulls the system-wide includes, the
travis-ci environment has an old kernel and btrfs_tree.h does not exist
there. The missing defines are provided by libbtrfs' btrfs/ctree.h.

We can add a configure-time check to detect the availability of the
headers, but I'm not sure if this is right. As libbtrfsutil/subvolume.c
is built internally it should use the includes from the btrfs-progs git
itself, no?

Ie. the #ifdef BTRFS_FLAT_INCLUDES way is not necessary for non-exported
sources.

> +#include <linux/magic.h>
> +
> +#include "btrfsutil_internal.h"

All the include magic could be easily hidden inside this internal header
so we won't need to include anything from the linux/* namespace
directly.

I'll keep this patch merged and unmodified, as it works on my machine,
so any fix shall be an incremental on top of devel.
--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Sterba Feb. 21, 2018, 1:02 p.m. UTC | #2
On Wed, Feb 21, 2018 at 12:43:07PM +0100, David Sterba wrote:
> On Thu, Feb 15, 2018 at 11:04:49AM -0800, Omar Sandoval wrote:
> > --- /dev/null
> > +++ b/libbtrfsutil/subvolume.c
> > @@ -0,0 +1,127 @@
> > +/*
> > + * 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/btrfs.h>
> > +#include <linux/btrfs_tree.h>
> 
> How is this supposed to work? This pulls the system-wide includes, the
> travis-ci environment has an old kernel and btrfs_tree.h does not exist
> there. The missing defines are provided by libbtrfs' btrfs/ctree.h.
> 
> We can add a configure-time check to detect the availability of the
> headers, but I'm not sure if this is right. As libbtrfsutil/subvolume.c
> is built internally it should use the includes from the btrfs-progs git
> itself, no?

Oh yeah, the fun has begun.  This will need to be sorted first. ctree.h
unconditionally pulls kerncompat.h, so it cannot be used instead of the
linux/btrfs*.h headers, there are warnings about redefined endianity
conversion macros.

We can split the headers by type, so ctree.h does not contain
everything, or copy defines and types to libbtrfsutil header so it is
completely independent of the git or system headers. Or something else.

I can now make it compile without including btrfs_tree.h and copying
parts of ctree.h until it compiles. The type u8 also has to be __u8.
--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Omar Sandoval Feb. 21, 2018, 6:13 p.m. UTC | #3
On Wed, Feb 21, 2018 at 02:02:02PM +0100, David Sterba wrote:
> On Wed, Feb 21, 2018 at 12:43:07PM +0100, David Sterba wrote:
> > On Thu, Feb 15, 2018 at 11:04:49AM -0800, Omar Sandoval wrote:
> > > --- /dev/null
> > > +++ b/libbtrfsutil/subvolume.c
> > > @@ -0,0 +1,127 @@
> > > +/*
> > > + * 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/btrfs.h>
> > > +#include <linux/btrfs_tree.h>
> > 
> > How is this supposed to work? This pulls the system-wide includes, the
> > travis-ci environment has an old kernel and btrfs_tree.h does not exist
> > there. The missing defines are provided by libbtrfs' btrfs/ctree.h.
> > 
> > We can add a configure-time check to detect the availability of the
> > headers, but I'm not sure if this is right. As libbtrfsutil/subvolume.c
> > is built internally it should use the includes from the btrfs-progs git
> > itself, no?

Dang it, yes, since the lib started out as its own repo I used the
system headers, but when I ported it to btrfs-progs I forgot to sort
this out.

> Oh yeah, the fun has begun.  This will need to be sorted first. ctree.h
> unconditionally pulls kerncompat.h, so it cannot be used instead of the
> linux/btrfs*.h headers, there are warnings about redefined endianity
> conversion macros.
> 
> We can split the headers by type, so ctree.h does not contain
> everything, or copy defines and types to libbtrfsutil header so it is
> completely independent of the git or system headers. Or something else.
> 
> I can now make it compile without including btrfs_tree.h and copying
> parts of ctree.h until it compiles. The type u8 also has to be __u8.

I'll figure something out and send a patch, thanks.
--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Makefile b/Makefile
index 95ee9678..58b3eca4 100644
--- a/Makefile
+++ b/Makefile
@@ -135,7 +135,7 @@  libbtrfsutil_major := $(shell sed -rn 's/^\#define BTRFS_UTIL_VERSION_MAJOR ([0-
 libbtrfsutil_minor := $(shell sed -rn 's/^\#define BTRFS_UTIL_VERSION_MINOR ([0-9])+$$/\1/p' libbtrfsutil/btrfsutil.h)
 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_objects = libbtrfsutil/errors.o libbtrfsutil/subvolume.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
diff --git a/libbtrfsutil/btrfsutil.h b/libbtrfsutil/btrfsutil.h
index 867418f2..ca36f695 100644
--- a/libbtrfsutil/btrfsutil.h
+++ b/libbtrfsutil/btrfsutil.h
@@ -20,6 +20,8 @@ 
 #ifndef BTRFS_UTIL_H
 #define BTRFS_UTIL_H
 
+#include <stdint.h>
+
 #define BTRFS_UTIL_VERSION_MAJOR 1
 #define BTRFS_UTIL_VERSION_MINOR 0
 #define BTRFS_UTIL_VERSION_PATCH 0
@@ -69,6 +71,37 @@  enum btrfs_util_error {
  */
 const char *btrfs_util_strerror(enum btrfs_util_error err);
 
+/**
+ * btrfs_util_is_subvolume() - Return whether a given path is a Btrfs subvolume.
+ * @path: Path to check.
+ *
+ * Return: %BTRFS_UTIL_OK if @path is a Btrfs subvolume,
+ * %BTRFS_UTIL_ERROR_NOT_BTRFS if @path is not on a Btrfs filesystem,
+ * %BTRFS_UTIL_ERROR_NOT_SUBVOLUME if @path is not a subvolume, non-zero error
+ * code on any other failure.
+ */
+enum btrfs_util_error btrfs_util_is_subvolume(const char *path);
+
+/**
+ * btrfs_util_is_subvolume_fd() - See btrfs_util_is_subvolume().
+ */
+enum btrfs_util_error btrfs_util_is_subvolume_fd(int fd);
+
+/**
+ * btrfs_util_subvolume_id() - Get the ID of the subvolume containing a path.
+ * @path: Path on a Btrfs filesystem.
+ * @id_ret: Returned subvolume ID.
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_subvolume_id(const char *path,
+					      uint64_t *id_ret);
+
+/**
+ * btrfs_util_subvolume_id_fd() - See btrfs_util_subvolume_id().
+ */
+enum btrfs_util_error btrfs_util_subvolume_id_fd(int fd, uint64_t *id_ret);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libbtrfsutil/python/btrfsutilpy.h b/libbtrfsutil/python/btrfsutilpy.h
index 6d82f7e1..9a04fda7 100644
--- a/libbtrfsutil/python/btrfsutilpy.h
+++ b/libbtrfsutil/python/btrfsutilpy.h
@@ -52,6 +52,9 @@  void SetFromBtrfsUtilErrorWithPaths(enum btrfs_util_error err,
 				    struct path_arg *path1,
 				    struct path_arg *path2);
 
+PyObject *is_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *subvolume_id(PyObject *self, PyObject *args, PyObject *kwds);
+
 void add_module_constants(PyObject *m);
 
 #endif /* BTRFSUTILPY_H */
diff --git a/libbtrfsutil/python/module.c b/libbtrfsutil/python/module.c
index d7398808..d492cbc7 100644
--- a/libbtrfsutil/python/module.c
+++ b/libbtrfsutil/python/module.c
@@ -132,6 +132,18 @@  void path_cleanup(struct path_arg *path)
 }
 
 static PyMethodDef btrfsutil_methods[] = {
+	{"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"},
 	{},
 };
 
diff --git a/libbtrfsutil/python/setup.py b/libbtrfsutil/python/setup.py
index cd8a6048..57249829 100755
--- a/libbtrfsutil/python/setup.py
+++ b/libbtrfsutil/python/setup.py
@@ -91,6 +91,7 @@  module = Extension(
         'constants.c',
         'error.c',
         'module.c',
+        'subvolume.c',
     ],
     include_dirs=['..'],
     library_dirs=['../..'],
diff --git a/libbtrfsutil/python/subvolume.c b/libbtrfsutil/python/subvolume.c
new file mode 100644
index 00000000..4aab06a5
--- /dev/null
+++ b/libbtrfsutil/python/subvolume.c
@@ -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);
+}
diff --git a/libbtrfsutil/python/tests/__init__.py b/libbtrfsutil/python/tests/__init__.py
index e69de29b..d2c6ff28 100644
--- a/libbtrfsutil/python/tests/__init__.py
+++ b/libbtrfsutil/python/tests/__init__.py
@@ -0,0 +1,66 @@ 
+# 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 os
+from pathlib import PurePath
+import subprocess
+import tempfile
+import unittest
+
+
+HAVE_PATH_LIKE = hasattr(PurePath, '__fspath__')
+
+
+@unittest.skipIf(os.geteuid() != 0, 'must be run as root')
+class BtrfsTestCase(unittest.TestCase):
+    def setUp(self):
+        self.mountpoint = tempfile.mkdtemp()
+        try:
+            with tempfile.NamedTemporaryFile(delete=False) as f:
+                os.truncate(f.fileno(), 1024 * 1024 * 1024)
+                self.image = f.name
+        except Exception as e:
+            os.rmdir(self.mountpoint)
+            raise e
+
+        try:
+            subprocess.check_call(['mkfs.btrfs', '-q', self.image])
+            subprocess.check_call(['mount', '-o', 'loop', '--', self.image, self.mountpoint])
+        except Exception as e:
+            os.remove(self.image)
+            os.rmdir(self.mountpoint)
+            raise e
+
+    def tearDown(self):
+        try:
+            subprocess.check_call(['umount', self.mountpoint])
+        finally:
+            os.remove(self.image)
+            os.rmdir(self.mountpoint)
+
+    @staticmethod
+    def path_or_fd(path, open_flags=os.O_RDONLY):
+        yield path
+        yield path.encode()
+        if HAVE_PATH_LIKE:
+            yield PurePath(path)
+        fd = os.open(path, open_flags)
+        try:
+            yield fd
+        finally:
+            os.close(fd)
+
diff --git a/libbtrfsutil/python/tests/test_subvolume.py b/libbtrfsutil/python/tests/test_subvolume.py
new file mode 100644
index 00000000..44b1d7f0
--- /dev/null
+++ b/libbtrfsutil/python/tests/test_subvolume.py
@@ -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)
diff --git a/libbtrfsutil/subvolume.c b/libbtrfsutil/subvolume.c
new file mode 100644
index 00000000..04988c14
--- /dev/null
+++ b/libbtrfsutil/subvolume.c
@@ -0,0 +1,127 @@ 
+/*
+ * 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/btrfs.h>
+#include <linux/btrfs_tree.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;
+}