diff mbox

[v2,03/27] libbtrfsutil: add Python bindings

Message ID 818e5d95ac69282a1e8c193bf92671f4b9982728.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>

The C libbtrfsutil library isn't very useful for scripting, so we also
want bindings for Python. Writing unit tests in Python is also much
easier than doing so in C. Only Python 3 is supported; if someone really
wants Python 2 support, they can write their own bindings. This commit
is just the scaffolding.

Signed-off-by: Omar Sandoval <osandov@fb.com>
---
 INSTALL                               |   4 +
 Makefile                              |  36 ++++++
 Makefile.inc.in                       |   2 +
 configure.ac                          |  15 +++
 libbtrfsutil/README.md                |   5 +-
 libbtrfsutil/python/.gitignore        |   7 ++
 libbtrfsutil/python/btrfsutilpy.h     |  57 ++++++++++
 libbtrfsutil/python/error.c           | 202 ++++++++++++++++++++++++++++++++++
 libbtrfsutil/python/module.c          | 166 ++++++++++++++++++++++++++++
 libbtrfsutil/python/setup.py          | 108 ++++++++++++++++++
 libbtrfsutil/python/tests/__init__.py |   0
 11 files changed, 601 insertions(+), 1 deletion(-)
 create mode 100644 libbtrfsutil/python/.gitignore
 create mode 100644 libbtrfsutil/python/btrfsutilpy.h
 create mode 100644 libbtrfsutil/python/error.c
 create mode 100644 libbtrfsutil/python/module.c
 create mode 100755 libbtrfsutil/python/setup.py
 create mode 100644 libbtrfsutil/python/tests/__init__.py

diff --git a/libbtrfsutil/python/tests/__init__.py b/libbtrfsutil/python/tests/__init__.py
new file mode 100644
index 00000000..e69de29b

Comments

David Sterba Feb. 21, 2018, 1:47 p.m. UTC | #1
On Thu, Feb 15, 2018 at 11:04:48AM -0800, Omar Sandoval wrote:
> +setup(
> +    name='btrfsutil',
> +    version=get_version(),
> +    description='Library for managing Btrfs filesystems',
> +    url='https://github.com/kdave/btrfs-progs',
> +    license='GPLv3',

LGPLv3?

> +    cmdclass={'build_ext': my_build_ext},
> +    ext_modules=[module],
> +)
--
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:08 p.m. UTC | #2
On Wed, Feb 21, 2018 at 02:47:54PM +0100, David Sterba wrote:
> On Thu, Feb 15, 2018 at 11:04:48AM -0800, Omar Sandoval wrote:
> > +setup(
> > +    name='btrfsutil',
> > +    version=get_version(),
> > +    description='Library for managing Btrfs filesystems',
> > +    url='https://github.com/kdave/btrfs-progs',
> > +    license='GPLv3',
> 
> LGPLv3?

Yes, that was a typo. I'll send an incremental patch.
--
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
Misono Tomohiro Feb. 22, 2018, 1:44 a.m. UTC | #3
On 2018/02/16 4:04, Omar Sandoval wrote:
> From: Omar Sandoval <osandov@fb.com>
> 
> The C libbtrfsutil library isn't very useful for scripting, so we also
> want bindings for Python. Writing unit tests in Python is also much
> easier than doing so in C. Only Python 3 is supported; if someone really
> wants Python 2 support, they can write their own bindings. This commit
> is just the scaffolding.
> 
> Signed-off-by: Omar Sandoval <osandov@fb.com>
> ---
>  INSTALL                               |   4 +
>  Makefile                              |  36 ++++++
>  Makefile.inc.in                       |   2 +
>  configure.ac                          |  15 +++
>  libbtrfsutil/README.md                |   5 +-
>  libbtrfsutil/python/.gitignore        |   7 ++
>  libbtrfsutil/python/btrfsutilpy.h     |  57 ++++++++++
>  libbtrfsutil/python/error.c           | 202 ++++++++++++++++++++++++++++++++++
>  libbtrfsutil/python/module.c          | 166 ++++++++++++++++++++++++++++
>  libbtrfsutil/python/setup.py          | 108 ++++++++++++++++++
>  libbtrfsutil/python/tests/__init__.py |   0
>  11 files changed, 601 insertions(+), 1 deletion(-)
>  create mode 100644 libbtrfsutil/python/.gitignore
>  create mode 100644 libbtrfsutil/python/btrfsutilpy.h
>  create mode 100644 libbtrfsutil/python/error.c
>  create mode 100644 libbtrfsutil/python/module.c
>  create mode 100755 libbtrfsutil/python/setup.py
>  create mode 100644 libbtrfsutil/python/tests/__init__.py
> 
> diff --git a/INSTALL b/INSTALL
> index 819b92ea..24d6e24f 100644
> --- a/INSTALL
> +++ b/INSTALL
> @@ -41,6 +41,10 @@ To build from the released tarballs:
>      $ make
>      $ make install
>  
> +To install the libbtrfsutil Python bindings:
> +
> +    $ make install_python
> +
>  You may disable building some parts like documentation, btrfs-convert or
>  backtrace support. See ./configure --help for more.
>  
> diff --git a/Makefile b/Makefile
> index 7fb70d06..95ee9678 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -154,8 +154,10 @@ endif
>  
>  ifeq ($(BUILD_VERBOSE),1)
>    Q =
> +  SETUP_PY_Q =
>  else
>    Q = @
> +  SETUP_PY_Q = -q
>  endif
>  
>  ifeq ("$(origin D)", "command line")
> @@ -302,6 +304,9 @@ endif
>  		$($(subst -,_,btrfs-$(@:%/$(notdir $@)=%)-cflags))
>  
>  all: $(progs) $(libs) $(lib_links) $(BUILDDIRS)
> +ifeq ($(PYTHON_BINDINGS),1)
> +all: libbtrfsutil_python
> +endif
>  $(SUBDIRS): $(BUILDDIRS)
>  $(BUILDDIRS):
>  	@echo "Making all in $(patsubst build-%,%,$@)"
> @@ -345,6 +350,16 @@ test-inst: all
>  
>  test: test-fsck test-mkfs test-convert test-misc test-fuzz test-cli
>  
> +ifeq ($(PYTHON_BINDINGS),1)
> +test-libbtrfsutil: libbtrfsutil_python
> +	$(Q)cd libbtrfsutil/python; \
> +		LD_LIBRARY_PATH=../.. $(PYTHON) -m unittest discover -v tests
> +
> +.PHONY: test-libbtrfsutil
> +
> +test: test-libbtrfsutil
> +endif
> +
>  #
>  # NOTE: For static compiles, you need to have all the required libs
>  # 	static equivalent available
> @@ -395,6 +410,15 @@ libbtrfsutil.so.$(libbtrfsutil_major) libbtrfsutil.so: libbtrfsutil.so.$(libbtrf
>  	@echo "    [LN]     $@"
>  	$(Q)$(LN_S) -f $< $@
>  
> +ifeq ($(PYTHON_BINDINGS),1)
> +libbtrfsutil_python: libbtrfsutil.so libbtrfsutil/btrfsutil.h
> +	@echo "    [PY]     libbtrfsutil"
> +	$(Q)cd libbtrfsutil/python; \
> +		CFLAGS= LDFLAGS= $(PYTHON) setup.py $(SETUP_PY_Q) build_ext -i build
> +
> +.PHONY: libbtrfsutil_python
> +endif
> +
>  # keep intermediate files from the below implicit rules around
>  .PRECIOUS: $(addsuffix .o,$(progs))
>  
> @@ -578,6 +602,10 @@ clean: $(CLEANDIRS)
>  	      $(libs) $(lib_links) \
>  	      $(progs_static) $(progs_extra) \
>  	      libbtrfsutil/*.o libbtrfsutil/*.o.d
> +ifeq ($(PYTHON_BINDINGS),1)
> +	$(Q)cd libbtrfsutil/python; \
> +		$(PYTHON) setup.py $(SETUP_PY_Q) clean -a
> +endif
>  
>  clean-doc:
>  	@echo "Cleaning Documentation"
> @@ -613,6 +641,14 @@ ifneq ($(udevdir),)
>  	$(INSTALL) -m644 $(udev_rules) $(DESTDIR)$(udevruledir)
>  endif
>  
> +ifeq ($(PYTHON_BINDINGS),1)
> +install_python: libbtrfsutil_python
> +	$(Q)cd libbtrfsutil/python; \
> +		$(PYTHON) setup.py install --skip-build $(if $(DESTDIR),--root $(DESTDIR)) --prefix $(prefix)
> +
> +.PHONY: install_python
> +endif
> +
>  install-static: $(progs_static) $(INSTALLDIRS)
>  	$(INSTALL) -m755 -d $(DESTDIR)$(bindir)
>  	$(INSTALL) $(progs_static) $(DESTDIR)$(bindir)
> diff --git a/Makefile.inc.in b/Makefile.inc.in
> index b53bef80..159d38ed 100644
> --- a/Makefile.inc.in
> +++ b/Makefile.inc.in
> @@ -14,6 +14,8 @@ DISABLE_BTRFSCONVERT = @DISABLE_BTRFSCONVERT@
>  BTRFSCONVERT_EXT2 = @BTRFSCONVERT_EXT2@
>  BTRFSCONVERT_REISERFS = @BTRFSCONVERT_REISERFS@
>  BTRFSRESTORE_ZSTD = @BTRFSRESTORE_ZSTD@
> +PYTHON_BINDINGS = @PYTHON_BINDINGS@
> +PYTHON = @PYTHON@
>  
>  SUBST_CFLAGS = @CFLAGS@
>  SUBST_LDFLAGS = @LDFLAGS@
> diff --git a/configure.ac b/configure.ac
> index 3afcdb47..50d36e4f 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -195,6 +195,19 @@ fi
>  AS_IF([test "x$enable_zstd" = xyes], [BTRFSRESTORE_ZSTD=1], [BTRFSRESTORE_ZSTD=0])
>  AC_SUBST(BTRFSRESTORE_ZSTD)
>  
> +AC_ARG_ENABLE([python],
> +	AS_HELP_STRING([--disable-python], [do not build libbtrfsutil Python bindings]),
> +	[], [enable_python=yes]
> +)
> +
> +if test "x$enable_python" = xyes; then
> +	AM_PATH_PYTHON([3.4])
> +fi

I'm not familiar with autoconf, but python3-devel package is also needed for Python.h for Fedora
and can we check the dependency too?

> +AS_IF([test "x$enable_python" = xyes], [PYTHON_BINDINGS=1], [PYTHON_BINDINGS=0])
> +AC_SUBST(PYTHON_BINDINGS)
> +AC_SUBST(PYTHON)
> +
>  # udev v190 introduced the btrfs builtin and a udev rule to use it.
>  # Our udev rule gives us the friendly dm names but isn't required (or valid)
>  # on earlier releases.
> @@ -249,6 +262,8 @@ AC_MSG_RESULT([
>  	backtrace support:  ${enable_backtrace}
>  	btrfs-convert:      ${enable_convert} ${convertfs:+($convertfs)}
>  	btrfs-restore zstd: ${enable_zstd}
> +	Python bindings:    ${enable_python}
> +	Python interpreter: ${PYTHON}
>  
>  	Type 'make' to compile.
>  ])
> diff --git a/libbtrfsutil/README.md b/libbtrfsutil/README.md
> index ee4c6a1d..0c8eba44 100644
> --- a/libbtrfsutil/README.md
> +++ b/libbtrfsutil/README.md
> @@ -3,7 +3,8 @@ libbtrfsutil
>  
>  libbtrfsutil is a library for managing Btrfs filesystems. It is licensed under
>  the LGPL. libbtrfsutil provides interfaces for a subset of the operations
> -offered by the `btrfs` command line utility.
> +offered by the `btrfs` command line utility. It also includes official Python
> +bindings (Python 3 only).
>  
>  Development
>  -----------
> @@ -33,3 +34,5 @@ A few guidelines:
>    type specific to `libbtrfsutil`)
>  * Preserve API and ABI compatability at all times (i.e., we don't want to bump
>    the library major version if we don't have to)
> +* Include Python bindings for all interfaces
> +* Write tests for all interfaces
> diff --git a/libbtrfsutil/python/.gitignore b/libbtrfsutil/python/.gitignore
> new file mode 100644
> index 00000000..d050ff7c
> --- /dev/null
> +++ b/libbtrfsutil/python/.gitignore
> @@ -0,0 +1,7 @@
> +__pycache__
> +*.pyc
> +/btrfsutil.egg-info
> +/btrfsutil*.so
> +/build
> +/constants.c
> +/dist
> diff --git a/libbtrfsutil/python/btrfsutilpy.h b/libbtrfsutil/python/btrfsutilpy.h
> new file mode 100644
> index 00000000..6d82f7e1
> --- /dev/null
> +++ b/libbtrfsutil/python/btrfsutilpy.h
> @@ -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/>.
> + */
> +
> +#ifndef BTRFSUTILPY_H
> +#define BTRFSUTILPY_H
> +
> +#define PY_SSIZE_T_CLEAN
> +
> +#include <stdbool.h>
> +#include <stddef.h>
> +#include <Python.h>
> +#include "structmember.h"
> +
> +#include <btrfsutil.h>
> +
> +extern PyTypeObject BtrfsUtilError_type;
> +
> +/*
> + * Helpers for path arguments based on posixmodule.c in CPython.
> + */
> +struct path_arg {
> +	bool allow_fd;
> +	char *path;
> +	int fd;
> +	Py_ssize_t length;
> +	PyObject *object;
> +	PyObject *cleanup;
> +};
> +int path_converter(PyObject *o, void *p);
> +void path_cleanup(struct path_arg *path);
> +
> +void SetFromBtrfsUtilError(enum btrfs_util_error err);
> +void SetFromBtrfsUtilErrorWithPath(enum btrfs_util_error err,
> +				   struct path_arg *path);
> +void SetFromBtrfsUtilErrorWithPaths(enum btrfs_util_error err,
> +				    struct path_arg *path1,
> +				    struct path_arg *path2);
> +
> +void add_module_constants(PyObject *m);
> +
> +#endif /* BTRFSUTILPY_H */
> diff --git a/libbtrfsutil/python/error.c b/libbtrfsutil/python/error.c
> new file mode 100644
> index 00000000..0876c9b4
> --- /dev/null
> +++ b/libbtrfsutil/python/error.c
> @@ -0,0 +1,202 @@
> +/*
> + * 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"
> +
> +typedef struct {
> +	PyOSErrorObject os_error;
> +	PyObject *btrfsutilerror;
> +} BtrfsUtilError;
> +
> +void SetFromBtrfsUtilError(enum btrfs_util_error err)
> +{
> +	SetFromBtrfsUtilErrorWithPaths(err, NULL, NULL);
> +}
> +
> +void SetFromBtrfsUtilErrorWithPath(enum btrfs_util_error err,
> +				   struct path_arg *path1)
> +{
> +	SetFromBtrfsUtilErrorWithPaths(err, path1, NULL);
> +}
> +
> +void SetFromBtrfsUtilErrorWithPaths(enum btrfs_util_error err,
> +				    struct path_arg *path1,
> +				    struct path_arg *path2)
> +{
> +	PyObject *strobj, *args, *exc;
> +	int i = errno;
> +	const char *str1 = btrfs_util_strerror(err), *str2 = strerror(i);
> +
> +	if (str1 && str2 && strcmp(str1, str2) != 0) {
> +		strobj = PyUnicode_FromFormat("%s: %s", str1, str2);
> +	} else if (str1) {
> +		strobj = PyUnicode_FromString(str1);
> +	} else if (str2) {
> +		strobj = PyUnicode_FromString(str2);
> +	} else {
> +		Py_INCREF(Py_None);
> +		strobj = Py_None;
> +	}
> +	if (strobj == NULL)
> +		return;
> +
> +	args = Py_BuildValue("iOOOOi", i, strobj,
> +			     path1 ? path1->object : Py_None, Py_None,
> +			     path2 ? path2->object : Py_None, (int)err);
> +	Py_DECREF(strobj);
> +	if (args == NULL)
> +		return;
> +
> +	exc = PyObject_CallObject((PyObject *)&BtrfsUtilError_type, args);
> +	Py_DECREF(args);
> +	if (exc == NULL)
> +		return;
> +
> +	PyErr_SetObject((PyObject *)&BtrfsUtilError_type, exc);
> +	Py_DECREF(exc);
> +}
> +
> +static int BtrfsUtilError_clear(BtrfsUtilError *self)
> +{
> +	Py_CLEAR(self->btrfsutilerror);
> +	return Py_TYPE(self)->tp_base->tp_clear((PyObject *)self);
> +}
> +
> +static void BtrfsUtilError_dealloc(BtrfsUtilError *self)
> +{
> +	PyObject_GC_UnTrack(self);
> +	BtrfsUtilError_clear(self);
> +	Py_TYPE(self)->tp_free((PyObject *)self);
> +}
> +
> +static int BtrfsUtilError_traverse(BtrfsUtilError *self, visitproc visit,
> +				   void *arg)
> +{
> +	Py_VISIT(self->btrfsutilerror);
> +	return Py_TYPE(self)->tp_base->tp_traverse((PyObject *)self, visit, arg);
> +}
> +
> +static PyObject *BtrfsUtilError_new(PyTypeObject *type, PyObject *args,
> +				    PyObject *kwds)
> +{
> +	BtrfsUtilError *self;
> +	PyObject *oserror_args = args;
> +
> +	if (PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 6) {
> +		oserror_args = PyTuple_GetSlice(args, 0, 5);
> +		if (oserror_args == NULL)
> +			return NULL;
> +	}
> +
> +	self = (BtrfsUtilError *)type->tp_base->tp_new(type, oserror_args,
> +						       kwds);
> +	if (oserror_args != args)
> +		Py_DECREF(oserror_args);
> +	if (self == NULL)
> +		return NULL;
> +
> +	if (PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 6) {
> +		self->btrfsutilerror = PyTuple_GET_ITEM(args, 5);
> +		Py_INCREF(self->btrfsutilerror);
> +	}
> +
> +	return (PyObject *)self;
> +}
> +
> +static PyObject *BtrfsUtilError_str(BtrfsUtilError *self)
> +{
> +#define OR_NONE(x) ((x) ? (x) : Py_None)
> +	if (self->btrfsutilerror) {
> +		if (self->os_error.filename) {
> +			if (self->os_error.filename2) {
> +				return PyUnicode_FromFormat("[BtrfsUtilError %S Errno %S] %S: %R -> %R",
> +							    OR_NONE(self->btrfsutilerror),
> +							    OR_NONE(self->os_error.myerrno),
> +							    OR_NONE(self->os_error.strerror),
> +							    self->os_error.filename,
> +							    self->os_error.filename2);
> +			} else {
> +				return PyUnicode_FromFormat("[BtrfsUtilError %S Errno %S] %S: %R",
> +							    OR_NONE(self->btrfsutilerror),
> +							    OR_NONE(self->os_error.myerrno),
> +							    OR_NONE(self->os_error.strerror),
> +							    self->os_error.filename);
> +			}
> +		}
> +		if (self->os_error.myerrno && self->os_error.strerror) {
> +			return PyUnicode_FromFormat("[BtrfsUtilError %S Errno %S] %S",
> +						    self->btrfsutilerror,
> +						    self->os_error.myerrno,
> +						    self->os_error.strerror);
> +		}
> +	}
> +	return Py_TYPE(self)->tp_base->tp_str((PyObject *)self);
> +#undef OR_NONE
> +}
> +
> +static PyMemberDef BtrfsUtilError_members[] = {
> +	{"btrfsutilerror", T_OBJECT,
> +	 offsetof(BtrfsUtilError, btrfsutilerror), 0,
> +	 "btrfsutil error code"},
> +	{},
> +};
> +
> +#define BtrfsUtilError_DOC	\
> +	"Btrfs operation error."
> +
> +PyTypeObject BtrfsUtilError_type = {
> +	PyVarObject_HEAD_INIT(NULL, 0)
> +	"btrfsutil.BtrfsUtilError",			/* tp_name */
> +	sizeof(BtrfsUtilError),				/* tp_basicsize */
> +	0,						/* tp_itemsize */
> +	(destructor)BtrfsUtilError_dealloc,		/* tp_dealloc */
> +	NULL,						/* tp_print */
> +	NULL,						/* tp_getattr */
> +	NULL,						/* tp_setattr */
> +	NULL,						/* tp_as_async */
> +	NULL,						/* tp_repr */
> +	NULL,						/* tp_as_number */
> +	NULL,						/* tp_as_sequence */
> +	NULL,						/* tp_as_mapping */
> +	NULL,						/* tp_hash  */
> +	NULL,						/* tp_call */
> +	(reprfunc)BtrfsUtilError_str,			/* tp_str */
> +	NULL,						/* tp_getattro */
> +	NULL,						/* tp_setattro */
> +	NULL,						/* tp_as_buffer */
> +	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,	/* tp_flags */
> +	BtrfsUtilError_DOC,				/* tp_doc */
> +	(traverseproc)BtrfsUtilError_traverse,		/* tp_traverse */
> +	(inquiry)BtrfsUtilError_clear,			/* tp_clear */
> +	NULL,						/* tp_richcompare */
> +	0,						/* tp_weaklistoffset */
> +	NULL,						/* tp_iter */
> +	NULL,						/* tp_iternext */
> +	NULL,						/* tp_methods */
> +	BtrfsUtilError_members,				/* tp_members */
> +	NULL,						/* tp_getset */
> +	NULL,						/* tp_base */
> +	NULL,						/* tp_dict */
> +	NULL,						/* tp_descr_get */
> +	NULL,						/* tp_descr_set */
> +	offsetof(BtrfsUtilError, os_error.dict),	/* tp_dictoffset */
> +	NULL,						/* tp_init */
> +	NULL,						/* tp_alloc */
> +	BtrfsUtilError_new,				/* tp_new */
> +};
> diff --git a/libbtrfsutil/python/module.c b/libbtrfsutil/python/module.c
> new file mode 100644
> index 00000000..d7398808
> --- /dev/null
> +++ b/libbtrfsutil/python/module.c
> @@ -0,0 +1,166 @@
> +/*
> + * 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"
> +
> +static int fd_converter(PyObject *o, void *p)
> +{
> +	int *fd = p;
> +	long tmp;
> +	int overflow;
> +
> +	tmp = PyLong_AsLongAndOverflow(o, &overflow);
> +	if (tmp == -1 && PyErr_Occurred())
> +		return 0;
> +	if (overflow > 0 || tmp > INT_MAX) {
> +		PyErr_SetString(PyExc_OverflowError,
> +				"fd is greater than maximum");
> +		return 0;
> +	}
> +	if (overflow < 0 || tmp < 0) {
> +		PyErr_SetString(PyExc_ValueError, "fd is negative");
> +		return 0;
> +	}
> +	*fd = tmp;
> +	return 1;
> +}
> +
> +int path_converter(PyObject *o, void *p)
> +{
> +	struct path_arg *path = p;
> +	int is_index, is_bytes, is_unicode;
> +	PyObject *bytes = NULL;
> +	Py_ssize_t length = 0;
> +	char *tmp;
> +
> +	if (o == NULL) {
> +		path_cleanup(p);
> +		return 1;
> +	}
> +
> +	path->object = path->cleanup = NULL;
> +	Py_INCREF(o);
> +
> +	path->fd = -1;
> +
> +	is_index = path->allow_fd && PyIndex_Check(o);
> +	is_bytes = PyBytes_Check(o);
> +	is_unicode = PyUnicode_Check(o);
> +
> +	if (!is_index && !is_bytes && !is_unicode) {
> +		_Py_IDENTIFIER(__fspath__);
> +		PyObject *func;
> +
> +		func = _PyObject_LookupSpecial(o, &PyId___fspath__);
> +		if (func == NULL)
> +			goto err_format;
> +		Py_DECREF(o);
> +		o = PyObject_CallFunctionObjArgs(func, NULL);
> +		Py_DECREF(func);
> +		if (o == NULL)
> +			return 0;
> +		is_bytes = PyBytes_Check(o);
> +		is_unicode = PyUnicode_Check(o);
> +	}
> +
> +	if (is_unicode) {
> +		if (!PyUnicode_FSConverter(o, &bytes))
> +			goto err;
> +	} else if (is_bytes) {
> +		bytes = o;
> +		Py_INCREF(bytes);
> +	} else if (is_index) {
> +		if (!fd_converter(o, &path->fd))
> +			goto err;
> +		path->path = NULL;
> +		goto out;
> +	} else {
> +err_format:
> +		PyErr_Format(PyExc_TypeError, "expected %s, not %s",
> +			     path->allow_fd ? "string, bytes, os.PathLike, or integer" :
> +			     "string, bytes, or os.PathLike",
> +			     Py_TYPE(o)->tp_name);
> +		goto err;
> +	}
> +
> +	length = PyBytes_GET_SIZE(bytes);
> +	tmp = PyBytes_AS_STRING(bytes);
> +	if ((size_t)length != strlen(tmp)) {
> +		PyErr_SetString(PyExc_TypeError,
> +				"path has embedded nul character");
> +		goto err;
> +	}
> +
> +	path->path = tmp;
> +	if (bytes == o)
> +		Py_DECREF(bytes);
> +	else
> +		path->cleanup = bytes;
> +	path->fd = -1;
> +
> +out:
> +	path->length = length;
> +	path->object = o;
> +	return Py_CLEANUP_SUPPORTED;
> +
> +err:
> +	Py_XDECREF(o);
> +	Py_XDECREF(bytes);
> +	return 0;
> +}
> +
> +void path_cleanup(struct path_arg *path)
> +{
> +	Py_CLEAR(path->object);
> +	Py_CLEAR(path->cleanup);
> +}
> +
> +static PyMethodDef btrfsutil_methods[] = {
> +	{},
> +};
> +
> +static struct PyModuleDef btrfsutilmodule = {
> +	PyModuleDef_HEAD_INIT,
> +	"btrfsutil",
> +	"Library for managing Btrfs filesystems",
> +	-1,
> +	btrfsutil_methods,
> +};
> +
> +PyMODINIT_FUNC
> +PyInit_btrfsutil(void)
> +{
> +	PyObject *m;
> +
> +	BtrfsUtilError_type.tp_base = (PyTypeObject *)PyExc_OSError;
> +	if (PyType_Ready(&BtrfsUtilError_type) < 0)
> +		return NULL;
> +
> +	m = PyModule_Create(&btrfsutilmodule);
> +	if (!m)
> +		return NULL;
> +
> +	Py_INCREF(&BtrfsUtilError_type);
> +	PyModule_AddObject(m, "BtrfsUtilError",
> +			   (PyObject *)&BtrfsUtilError_type);
> +
> +	add_module_constants(m);
> +
> +	return m;
> +}
> diff --git a/libbtrfsutil/python/setup.py b/libbtrfsutil/python/setup.py
> new file mode 100755
> index 00000000..cd8a6048
> --- /dev/null
> +++ b/libbtrfsutil/python/setup.py
> @@ -0,0 +1,108 @@
> +#!/usr/bin/env python3
> +
> +# 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 re
> +import os
> +import os.path
> +from setuptools import setup, Extension
> +from setuptools.command.build_ext import build_ext
> +import subprocess
> +
> +
> +def get_version():
> +    with open('../btrfsutil.h', 'r') as f:
> +        btrfsutil_h = f.read()
> +    major = re.search(r'^#define BTRFS_UTIL_VERSION_MAJOR ([0-9]+)$',
> +                      btrfsutil_h, flags=re.MULTILINE).group(1)
> +    minor = re.search(r'^#define BTRFS_UTIL_VERSION_MINOR ([0-9]+)$',
> +                      btrfsutil_h, flags=re.MULTILINE).group(1)
> +    patch = re.search(r'^#define BTRFS_UTIL_VERSION_PATCH ([0-9]+)$',
> +                      btrfsutil_h, flags=re.MULTILINE).group(1)
> +    return major + '.' + minor + '.' + patch
> +
> +
> +def out_of_date(dependencies, target):
> +    dependency_mtimes = [os.path.getmtime(dependency) for dependency in dependencies]
> +    try:
> +        target_mtime = os.path.getmtime(target)
> +    except OSError:
> +        return True
> +    return any(dependency_mtime >= target_mtime for dependency_mtime in dependency_mtimes)
> +
> +
> +def gen_constants():
> +    with open('../btrfsutil.h', 'r') as f:
> +        btrfsutil_h = f.read()
> +
> +    constants = re.findall(
> +        r'^\s*(BTRFS_UTIL_ERROR_[a-zA-Z0-9_]+)',
> +        btrfsutil_h, flags=re.MULTILINE)
> +
> +    with open('constants.c', 'w') as f:
> +        f.write("""\
> +#include <btrfsutil.h>
> +#include "btrfsutilpy.h"
> +
> +void add_module_constants(PyObject *m)
> +{
> +""")
> +        for constant in constants:
> +            assert constant.startswith('BTRFS_UTIL_')
> +            name = constant[len('BTRFS_UTIL_'):]
> +            f.write('\tPyModule_AddIntConstant(m, "{}", {});\n'.format(name, constant))
> +        f.write("""\
> +}
> +""")
> +
> +
> +class my_build_ext(build_ext):
> +    def run(self):
> +        if out_of_date(['../btrfsutil.h'], 'constants.c'):
> +            try:
> +                gen_constants()
> +            except Exception as e:
> +                try:
> +                    os.remove('constants.c')
> +                except OSError:
> +                    pass
> +                raise e
> +        super().run()
> +
> +
> +module = Extension(
> +    name='btrfsutil',
> +    sources=[
> +        'constants.c',
> +        'error.c',
> +        'module.c',
> +    ],
> +    include_dirs=['..'],
> +    library_dirs=['../..'],
> +    libraries=['btrfsutil'],
> +)
> +
> +setup(
> +    name='btrfsutil',
> +    version=get_version(),
> +    description='Library for managing Btrfs filesystems',
> +    url='https://github.com/kdave/btrfs-progs',
> +    license='GPLv3',
> +    cmdclass={'build_ext': my_build_ext},
> +    ext_modules=[module],
> +)
> diff --git a/libbtrfsutil/python/tests/__init__.py b/libbtrfsutil/python/tests/__init__.py
> new file mode 100644
> index 00000000..e69de29b
> 

--
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/INSTALL b/INSTALL
index 819b92ea..24d6e24f 100644
--- a/INSTALL
+++ b/INSTALL
@@ -41,6 +41,10 @@  To build from the released tarballs:
     $ make
     $ make install
 
+To install the libbtrfsutil Python bindings:
+
+    $ make install_python
+
 You may disable building some parts like documentation, btrfs-convert or
 backtrace support. See ./configure --help for more.
 
diff --git a/Makefile b/Makefile
index 7fb70d06..95ee9678 100644
--- a/Makefile
+++ b/Makefile
@@ -154,8 +154,10 @@  endif
 
 ifeq ($(BUILD_VERBOSE),1)
   Q =
+  SETUP_PY_Q =
 else
   Q = @
+  SETUP_PY_Q = -q
 endif
 
 ifeq ("$(origin D)", "command line")
@@ -302,6 +304,9 @@  endif
 		$($(subst -,_,btrfs-$(@:%/$(notdir $@)=%)-cflags))
 
 all: $(progs) $(libs) $(lib_links) $(BUILDDIRS)
+ifeq ($(PYTHON_BINDINGS),1)
+all: libbtrfsutil_python
+endif
 $(SUBDIRS): $(BUILDDIRS)
 $(BUILDDIRS):
 	@echo "Making all in $(patsubst build-%,%,$@)"
@@ -345,6 +350,16 @@  test-inst: all
 
 test: test-fsck test-mkfs test-convert test-misc test-fuzz test-cli
 
+ifeq ($(PYTHON_BINDINGS),1)
+test-libbtrfsutil: libbtrfsutil_python
+	$(Q)cd libbtrfsutil/python; \
+		LD_LIBRARY_PATH=../.. $(PYTHON) -m unittest discover -v tests
+
+.PHONY: test-libbtrfsutil
+
+test: test-libbtrfsutil
+endif
+
 #
 # NOTE: For static compiles, you need to have all the required libs
 # 	static equivalent available
@@ -395,6 +410,15 @@  libbtrfsutil.so.$(libbtrfsutil_major) libbtrfsutil.so: libbtrfsutil.so.$(libbtrf
 	@echo "    [LN]     $@"
 	$(Q)$(LN_S) -f $< $@
 
+ifeq ($(PYTHON_BINDINGS),1)
+libbtrfsutil_python: libbtrfsutil.so libbtrfsutil/btrfsutil.h
+	@echo "    [PY]     libbtrfsutil"
+	$(Q)cd libbtrfsutil/python; \
+		CFLAGS= LDFLAGS= $(PYTHON) setup.py $(SETUP_PY_Q) build_ext -i build
+
+.PHONY: libbtrfsutil_python
+endif
+
 # keep intermediate files from the below implicit rules around
 .PRECIOUS: $(addsuffix .o,$(progs))
 
@@ -578,6 +602,10 @@  clean: $(CLEANDIRS)
 	      $(libs) $(lib_links) \
 	      $(progs_static) $(progs_extra) \
 	      libbtrfsutil/*.o libbtrfsutil/*.o.d
+ifeq ($(PYTHON_BINDINGS),1)
+	$(Q)cd libbtrfsutil/python; \
+		$(PYTHON) setup.py $(SETUP_PY_Q) clean -a
+endif
 
 clean-doc:
 	@echo "Cleaning Documentation"
@@ -613,6 +641,14 @@  ifneq ($(udevdir),)
 	$(INSTALL) -m644 $(udev_rules) $(DESTDIR)$(udevruledir)
 endif
 
+ifeq ($(PYTHON_BINDINGS),1)
+install_python: libbtrfsutil_python
+	$(Q)cd libbtrfsutil/python; \
+		$(PYTHON) setup.py install --skip-build $(if $(DESTDIR),--root $(DESTDIR)) --prefix $(prefix)
+
+.PHONY: install_python
+endif
+
 install-static: $(progs_static) $(INSTALLDIRS)
 	$(INSTALL) -m755 -d $(DESTDIR)$(bindir)
 	$(INSTALL) $(progs_static) $(DESTDIR)$(bindir)
diff --git a/Makefile.inc.in b/Makefile.inc.in
index b53bef80..159d38ed 100644
--- a/Makefile.inc.in
+++ b/Makefile.inc.in
@@ -14,6 +14,8 @@  DISABLE_BTRFSCONVERT = @DISABLE_BTRFSCONVERT@
 BTRFSCONVERT_EXT2 = @BTRFSCONVERT_EXT2@
 BTRFSCONVERT_REISERFS = @BTRFSCONVERT_REISERFS@
 BTRFSRESTORE_ZSTD = @BTRFSRESTORE_ZSTD@
+PYTHON_BINDINGS = @PYTHON_BINDINGS@
+PYTHON = @PYTHON@
 
 SUBST_CFLAGS = @CFLAGS@
 SUBST_LDFLAGS = @LDFLAGS@
diff --git a/configure.ac b/configure.ac
index 3afcdb47..50d36e4f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -195,6 +195,19 @@  fi
 AS_IF([test "x$enable_zstd" = xyes], [BTRFSRESTORE_ZSTD=1], [BTRFSRESTORE_ZSTD=0])
 AC_SUBST(BTRFSRESTORE_ZSTD)
 
+AC_ARG_ENABLE([python],
+	AS_HELP_STRING([--disable-python], [do not build libbtrfsutil Python bindings]),
+	[], [enable_python=yes]
+)
+
+if test "x$enable_python" = xyes; then
+	AM_PATH_PYTHON([3.4])
+fi
+
+AS_IF([test "x$enable_python" = xyes], [PYTHON_BINDINGS=1], [PYTHON_BINDINGS=0])
+AC_SUBST(PYTHON_BINDINGS)
+AC_SUBST(PYTHON)
+
 # udev v190 introduced the btrfs builtin and a udev rule to use it.
 # Our udev rule gives us the friendly dm names but isn't required (or valid)
 # on earlier releases.
@@ -249,6 +262,8 @@  AC_MSG_RESULT([
 	backtrace support:  ${enable_backtrace}
 	btrfs-convert:      ${enable_convert} ${convertfs:+($convertfs)}
 	btrfs-restore zstd: ${enable_zstd}
+	Python bindings:    ${enable_python}
+	Python interpreter: ${PYTHON}
 
 	Type 'make' to compile.
 ])
diff --git a/libbtrfsutil/README.md b/libbtrfsutil/README.md
index ee4c6a1d..0c8eba44 100644
--- a/libbtrfsutil/README.md
+++ b/libbtrfsutil/README.md
@@ -3,7 +3,8 @@  libbtrfsutil
 
 libbtrfsutil is a library for managing Btrfs filesystems. It is licensed under
 the LGPL. libbtrfsutil provides interfaces for a subset of the operations
-offered by the `btrfs` command line utility.
+offered by the `btrfs` command line utility. It also includes official Python
+bindings (Python 3 only).
 
 Development
 -----------
@@ -33,3 +34,5 @@  A few guidelines:
   type specific to `libbtrfsutil`)
 * Preserve API and ABI compatability at all times (i.e., we don't want to bump
   the library major version if we don't have to)
+* Include Python bindings for all interfaces
+* Write tests for all interfaces
diff --git a/libbtrfsutil/python/.gitignore b/libbtrfsutil/python/.gitignore
new file mode 100644
index 00000000..d050ff7c
--- /dev/null
+++ b/libbtrfsutil/python/.gitignore
@@ -0,0 +1,7 @@ 
+__pycache__
+*.pyc
+/btrfsutil.egg-info
+/btrfsutil*.so
+/build
+/constants.c
+/dist
diff --git a/libbtrfsutil/python/btrfsutilpy.h b/libbtrfsutil/python/btrfsutilpy.h
new file mode 100644
index 00000000..6d82f7e1
--- /dev/null
+++ b/libbtrfsutil/python/btrfsutilpy.h
@@ -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/>.
+ */
+
+#ifndef BTRFSUTILPY_H
+#define BTRFSUTILPY_H
+
+#define PY_SSIZE_T_CLEAN
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <Python.h>
+#include "structmember.h"
+
+#include <btrfsutil.h>
+
+extern PyTypeObject BtrfsUtilError_type;
+
+/*
+ * Helpers for path arguments based on posixmodule.c in CPython.
+ */
+struct path_arg {
+	bool allow_fd;
+	char *path;
+	int fd;
+	Py_ssize_t length;
+	PyObject *object;
+	PyObject *cleanup;
+};
+int path_converter(PyObject *o, void *p);
+void path_cleanup(struct path_arg *path);
+
+void SetFromBtrfsUtilError(enum btrfs_util_error err);
+void SetFromBtrfsUtilErrorWithPath(enum btrfs_util_error err,
+				   struct path_arg *path);
+void SetFromBtrfsUtilErrorWithPaths(enum btrfs_util_error err,
+				    struct path_arg *path1,
+				    struct path_arg *path2);
+
+void add_module_constants(PyObject *m);
+
+#endif /* BTRFSUTILPY_H */
diff --git a/libbtrfsutil/python/error.c b/libbtrfsutil/python/error.c
new file mode 100644
index 00000000..0876c9b4
--- /dev/null
+++ b/libbtrfsutil/python/error.c
@@ -0,0 +1,202 @@ 
+/*
+ * 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"
+
+typedef struct {
+	PyOSErrorObject os_error;
+	PyObject *btrfsutilerror;
+} BtrfsUtilError;
+
+void SetFromBtrfsUtilError(enum btrfs_util_error err)
+{
+	SetFromBtrfsUtilErrorWithPaths(err, NULL, NULL);
+}
+
+void SetFromBtrfsUtilErrorWithPath(enum btrfs_util_error err,
+				   struct path_arg *path1)
+{
+	SetFromBtrfsUtilErrorWithPaths(err, path1, NULL);
+}
+
+void SetFromBtrfsUtilErrorWithPaths(enum btrfs_util_error err,
+				    struct path_arg *path1,
+				    struct path_arg *path2)
+{
+	PyObject *strobj, *args, *exc;
+	int i = errno;
+	const char *str1 = btrfs_util_strerror(err), *str2 = strerror(i);
+
+	if (str1 && str2 && strcmp(str1, str2) != 0) {
+		strobj = PyUnicode_FromFormat("%s: %s", str1, str2);
+	} else if (str1) {
+		strobj = PyUnicode_FromString(str1);
+	} else if (str2) {
+		strobj = PyUnicode_FromString(str2);
+	} else {
+		Py_INCREF(Py_None);
+		strobj = Py_None;
+	}
+	if (strobj == NULL)
+		return;
+
+	args = Py_BuildValue("iOOOOi", i, strobj,
+			     path1 ? path1->object : Py_None, Py_None,
+			     path2 ? path2->object : Py_None, (int)err);
+	Py_DECREF(strobj);
+	if (args == NULL)
+		return;
+
+	exc = PyObject_CallObject((PyObject *)&BtrfsUtilError_type, args);
+	Py_DECREF(args);
+	if (exc == NULL)
+		return;
+
+	PyErr_SetObject((PyObject *)&BtrfsUtilError_type, exc);
+	Py_DECREF(exc);
+}
+
+static int BtrfsUtilError_clear(BtrfsUtilError *self)
+{
+	Py_CLEAR(self->btrfsutilerror);
+	return Py_TYPE(self)->tp_base->tp_clear((PyObject *)self);
+}
+
+static void BtrfsUtilError_dealloc(BtrfsUtilError *self)
+{
+	PyObject_GC_UnTrack(self);
+	BtrfsUtilError_clear(self);
+	Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static int BtrfsUtilError_traverse(BtrfsUtilError *self, visitproc visit,
+				   void *arg)
+{
+	Py_VISIT(self->btrfsutilerror);
+	return Py_TYPE(self)->tp_base->tp_traverse((PyObject *)self, visit, arg);
+}
+
+static PyObject *BtrfsUtilError_new(PyTypeObject *type, PyObject *args,
+				    PyObject *kwds)
+{
+	BtrfsUtilError *self;
+	PyObject *oserror_args = args;
+
+	if (PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 6) {
+		oserror_args = PyTuple_GetSlice(args, 0, 5);
+		if (oserror_args == NULL)
+			return NULL;
+	}
+
+	self = (BtrfsUtilError *)type->tp_base->tp_new(type, oserror_args,
+						       kwds);
+	if (oserror_args != args)
+		Py_DECREF(oserror_args);
+	if (self == NULL)
+		return NULL;
+
+	if (PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 6) {
+		self->btrfsutilerror = PyTuple_GET_ITEM(args, 5);
+		Py_INCREF(self->btrfsutilerror);
+	}
+
+	return (PyObject *)self;
+}
+
+static PyObject *BtrfsUtilError_str(BtrfsUtilError *self)
+{
+#define OR_NONE(x) ((x) ? (x) : Py_None)
+	if (self->btrfsutilerror) {
+		if (self->os_error.filename) {
+			if (self->os_error.filename2) {
+				return PyUnicode_FromFormat("[BtrfsUtilError %S Errno %S] %S: %R -> %R",
+							    OR_NONE(self->btrfsutilerror),
+							    OR_NONE(self->os_error.myerrno),
+							    OR_NONE(self->os_error.strerror),
+							    self->os_error.filename,
+							    self->os_error.filename2);
+			} else {
+				return PyUnicode_FromFormat("[BtrfsUtilError %S Errno %S] %S: %R",
+							    OR_NONE(self->btrfsutilerror),
+							    OR_NONE(self->os_error.myerrno),
+							    OR_NONE(self->os_error.strerror),
+							    self->os_error.filename);
+			}
+		}
+		if (self->os_error.myerrno && self->os_error.strerror) {
+			return PyUnicode_FromFormat("[BtrfsUtilError %S Errno %S] %S",
+						    self->btrfsutilerror,
+						    self->os_error.myerrno,
+						    self->os_error.strerror);
+		}
+	}
+	return Py_TYPE(self)->tp_base->tp_str((PyObject *)self);
+#undef OR_NONE
+}
+
+static PyMemberDef BtrfsUtilError_members[] = {
+	{"btrfsutilerror", T_OBJECT,
+	 offsetof(BtrfsUtilError, btrfsutilerror), 0,
+	 "btrfsutil error code"},
+	{},
+};
+
+#define BtrfsUtilError_DOC	\
+	"Btrfs operation error."
+
+PyTypeObject BtrfsUtilError_type = {
+	PyVarObject_HEAD_INIT(NULL, 0)
+	"btrfsutil.BtrfsUtilError",			/* tp_name */
+	sizeof(BtrfsUtilError),				/* tp_basicsize */
+	0,						/* tp_itemsize */
+	(destructor)BtrfsUtilError_dealloc,		/* tp_dealloc */
+	NULL,						/* tp_print */
+	NULL,						/* tp_getattr */
+	NULL,						/* tp_setattr */
+	NULL,						/* tp_as_async */
+	NULL,						/* tp_repr */
+	NULL,						/* tp_as_number */
+	NULL,						/* tp_as_sequence */
+	NULL,						/* tp_as_mapping */
+	NULL,						/* tp_hash  */
+	NULL,						/* tp_call */
+	(reprfunc)BtrfsUtilError_str,			/* tp_str */
+	NULL,						/* tp_getattro */
+	NULL,						/* tp_setattro */
+	NULL,						/* tp_as_buffer */
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,	/* tp_flags */
+	BtrfsUtilError_DOC,				/* tp_doc */
+	(traverseproc)BtrfsUtilError_traverse,		/* tp_traverse */
+	(inquiry)BtrfsUtilError_clear,			/* tp_clear */
+	NULL,						/* tp_richcompare */
+	0,						/* tp_weaklistoffset */
+	NULL,						/* tp_iter */
+	NULL,						/* tp_iternext */
+	NULL,						/* tp_methods */
+	BtrfsUtilError_members,				/* tp_members */
+	NULL,						/* tp_getset */
+	NULL,						/* tp_base */
+	NULL,						/* tp_dict */
+	NULL,						/* tp_descr_get */
+	NULL,						/* tp_descr_set */
+	offsetof(BtrfsUtilError, os_error.dict),	/* tp_dictoffset */
+	NULL,						/* tp_init */
+	NULL,						/* tp_alloc */
+	BtrfsUtilError_new,				/* tp_new */
+};
diff --git a/libbtrfsutil/python/module.c b/libbtrfsutil/python/module.c
new file mode 100644
index 00000000..d7398808
--- /dev/null
+++ b/libbtrfsutil/python/module.c
@@ -0,0 +1,166 @@ 
+/*
+ * 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"
+
+static int fd_converter(PyObject *o, void *p)
+{
+	int *fd = p;
+	long tmp;
+	int overflow;
+
+	tmp = PyLong_AsLongAndOverflow(o, &overflow);
+	if (tmp == -1 && PyErr_Occurred())
+		return 0;
+	if (overflow > 0 || tmp > INT_MAX) {
+		PyErr_SetString(PyExc_OverflowError,
+				"fd is greater than maximum");
+		return 0;
+	}
+	if (overflow < 0 || tmp < 0) {
+		PyErr_SetString(PyExc_ValueError, "fd is negative");
+		return 0;
+	}
+	*fd = tmp;
+	return 1;
+}
+
+int path_converter(PyObject *o, void *p)
+{
+	struct path_arg *path = p;
+	int is_index, is_bytes, is_unicode;
+	PyObject *bytes = NULL;
+	Py_ssize_t length = 0;
+	char *tmp;
+
+	if (o == NULL) {
+		path_cleanup(p);
+		return 1;
+	}
+
+	path->object = path->cleanup = NULL;
+	Py_INCREF(o);
+
+	path->fd = -1;
+
+	is_index = path->allow_fd && PyIndex_Check(o);
+	is_bytes = PyBytes_Check(o);
+	is_unicode = PyUnicode_Check(o);
+
+	if (!is_index && !is_bytes && !is_unicode) {
+		_Py_IDENTIFIER(__fspath__);
+		PyObject *func;
+
+		func = _PyObject_LookupSpecial(o, &PyId___fspath__);
+		if (func == NULL)
+			goto err_format;
+		Py_DECREF(o);
+		o = PyObject_CallFunctionObjArgs(func, NULL);
+		Py_DECREF(func);
+		if (o == NULL)
+			return 0;
+		is_bytes = PyBytes_Check(o);
+		is_unicode = PyUnicode_Check(o);
+	}
+
+	if (is_unicode) {
+		if (!PyUnicode_FSConverter(o, &bytes))
+			goto err;
+	} else if (is_bytes) {
+		bytes = o;
+		Py_INCREF(bytes);
+	} else if (is_index) {
+		if (!fd_converter(o, &path->fd))
+			goto err;
+		path->path = NULL;
+		goto out;
+	} else {
+err_format:
+		PyErr_Format(PyExc_TypeError, "expected %s, not %s",
+			     path->allow_fd ? "string, bytes, os.PathLike, or integer" :
+			     "string, bytes, or os.PathLike",
+			     Py_TYPE(o)->tp_name);
+		goto err;
+	}
+
+	length = PyBytes_GET_SIZE(bytes);
+	tmp = PyBytes_AS_STRING(bytes);
+	if ((size_t)length != strlen(tmp)) {
+		PyErr_SetString(PyExc_TypeError,
+				"path has embedded nul character");
+		goto err;
+	}
+
+	path->path = tmp;
+	if (bytes == o)
+		Py_DECREF(bytes);
+	else
+		path->cleanup = bytes;
+	path->fd = -1;
+
+out:
+	path->length = length;
+	path->object = o;
+	return Py_CLEANUP_SUPPORTED;
+
+err:
+	Py_XDECREF(o);
+	Py_XDECREF(bytes);
+	return 0;
+}
+
+void path_cleanup(struct path_arg *path)
+{
+	Py_CLEAR(path->object);
+	Py_CLEAR(path->cleanup);
+}
+
+static PyMethodDef btrfsutil_methods[] = {
+	{},
+};
+
+static struct PyModuleDef btrfsutilmodule = {
+	PyModuleDef_HEAD_INIT,
+	"btrfsutil",
+	"Library for managing Btrfs filesystems",
+	-1,
+	btrfsutil_methods,
+};
+
+PyMODINIT_FUNC
+PyInit_btrfsutil(void)
+{
+	PyObject *m;
+
+	BtrfsUtilError_type.tp_base = (PyTypeObject *)PyExc_OSError;
+	if (PyType_Ready(&BtrfsUtilError_type) < 0)
+		return NULL;
+
+	m = PyModule_Create(&btrfsutilmodule);
+	if (!m)
+		return NULL;
+
+	Py_INCREF(&BtrfsUtilError_type);
+	PyModule_AddObject(m, "BtrfsUtilError",
+			   (PyObject *)&BtrfsUtilError_type);
+
+	add_module_constants(m);
+
+	return m;
+}
diff --git a/libbtrfsutil/python/setup.py b/libbtrfsutil/python/setup.py
new file mode 100755
index 00000000..cd8a6048
--- /dev/null
+++ b/libbtrfsutil/python/setup.py
@@ -0,0 +1,108 @@ 
+#!/usr/bin/env python3
+
+# 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 re
+import os
+import os.path
+from setuptools import setup, Extension
+from setuptools.command.build_ext import build_ext
+import subprocess
+
+
+def get_version():
+    with open('../btrfsutil.h', 'r') as f:
+        btrfsutil_h = f.read()
+    major = re.search(r'^#define BTRFS_UTIL_VERSION_MAJOR ([0-9]+)$',
+                      btrfsutil_h, flags=re.MULTILINE).group(1)
+    minor = re.search(r'^#define BTRFS_UTIL_VERSION_MINOR ([0-9]+)$',
+                      btrfsutil_h, flags=re.MULTILINE).group(1)
+    patch = re.search(r'^#define BTRFS_UTIL_VERSION_PATCH ([0-9]+)$',
+                      btrfsutil_h, flags=re.MULTILINE).group(1)
+    return major + '.' + minor + '.' + patch
+
+
+def out_of_date(dependencies, target):
+    dependency_mtimes = [os.path.getmtime(dependency) for dependency in dependencies]
+    try:
+        target_mtime = os.path.getmtime(target)
+    except OSError:
+        return True
+    return any(dependency_mtime >= target_mtime for dependency_mtime in dependency_mtimes)
+
+
+def gen_constants():
+    with open('../btrfsutil.h', 'r') as f:
+        btrfsutil_h = f.read()
+
+    constants = re.findall(
+        r'^\s*(BTRFS_UTIL_ERROR_[a-zA-Z0-9_]+)',
+        btrfsutil_h, flags=re.MULTILINE)
+
+    with open('constants.c', 'w') as f:
+        f.write("""\
+#include <btrfsutil.h>
+#include "btrfsutilpy.h"
+
+void add_module_constants(PyObject *m)
+{
+""")
+        for constant in constants:
+            assert constant.startswith('BTRFS_UTIL_')
+            name = constant[len('BTRFS_UTIL_'):]
+            f.write('\tPyModule_AddIntConstant(m, "{}", {});\n'.format(name, constant))
+        f.write("""\
+}
+""")
+
+
+class my_build_ext(build_ext):
+    def run(self):
+        if out_of_date(['../btrfsutil.h'], 'constants.c'):
+            try:
+                gen_constants()
+            except Exception as e:
+                try:
+                    os.remove('constants.c')
+                except OSError:
+                    pass
+                raise e
+        super().run()
+
+
+module = Extension(
+    name='btrfsutil',
+    sources=[
+        'constants.c',
+        'error.c',
+        'module.c',
+    ],
+    include_dirs=['..'],
+    library_dirs=['../..'],
+    libraries=['btrfsutil'],
+)
+
+setup(
+    name='btrfsutil',
+    version=get_version(),
+    description='Library for managing Btrfs filesystems',
+    url='https://github.com/kdave/btrfs-progs',
+    license='GPLv3',
+    cmdclass={'build_ext': my_build_ext},
+    ext_modules=[module],
+)