diff --git a/contrib/bindings/python/.gitignore b/contrib/bindings/python/.gitignore index b0194920..a7719469 100644 --- a/contrib/bindings/python/.gitignore +++ b/contrib/bindings/python/.gitignore @@ -1,2 +1,2 @@ -/__pycache__/ -/_pathrs* +/dist/ +/*.egg-info/ diff --git a/contrib/bindings/python/pathrs/.gitignore b/contrib/bindings/python/pathrs/.gitignore new file mode 100644 index 00000000..eea96bf6 --- /dev/null +++ b/contrib/bindings/python/pathrs/.gitignore @@ -0,0 +1,2 @@ +/__pycache__/ +/_libpathrs_cffi* diff --git a/contrib/bindings/python/pathrs/__init__.py b/contrib/bindings/python/pathrs/__init__.py new file mode 100644 index 00000000..f11e495b --- /dev/null +++ b/contrib/bindings/python/pathrs/__init__.py @@ -0,0 +1,21 @@ +#!/usr/bin/python3 +# libpathrs: safe path resolution on Linux +# Copyright (C) 2019-2024 Aleksa Sarai +# Copyright (C) 2019-2024 SUSE LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ._pathrs import * + +# TODO: Figure out a way to keep this version up-to-date with Cargo.toml. +__version__ = "0.0.2" diff --git a/contrib/bindings/python/pathrs.py b/contrib/bindings/python/pathrs/_pathrs.py similarity index 96% rename from contrib/bindings/python/pathrs.py rename to contrib/bindings/python/pathrs/_pathrs.py index f7357445..c691fb3f 100644 --- a/contrib/bindings/python/pathrs.py +++ b/contrib/bindings/python/pathrs/_pathrs.py @@ -22,7 +22,7 @@ import copy import fcntl -from _pathrs import ffi, lib as libpathrs_so +from ._libpathrs_cffi import ffi, lib as libpathrs_so __all__ = [ # core api @@ -102,7 +102,7 @@ def pprint(self, out=sys.stdout): INTERNAL_ERROR = Error("tried to fetch libpathrs error but no error found") -def fileno(file): +def _fileno(file): if isinstance(file, int): # file is a plain fd return file @@ -110,14 +110,13 @@ def fileno(file): # Assume there is a fileno method. return file.fileno() - -def clonefile(file): +def _clonefile(file): return fcntl.fcntl(fileno(file), fcntl.F_DUPFD_CLOEXEC) class WrappedFd(object): def __init__(self, file): - fd = fileno(file) + fd = _fileno(file) if isinstance(file, io.IOBase): # If this is a regular open file, we need to make a copy because # you cannot leak files and so the GC might close it from @@ -161,7 +160,7 @@ def close(self): def clone(self): if self.isclosed(): raise ValueError("cannot clone closed file") - return self.__class__(clonefile(self)) + return self.__class__(_clonefile(self)) def __copy__(self): # A "shallow copy" of a file is the same as a deep copy. @@ -175,7 +174,7 @@ def __del__(self): # XXX: This is _super_ ugly but so is the one in CPython. -def convert_mode(mode): +def _convert_mode(mode): mode = set(mode) flags = os.O_CLOEXEC @@ -216,7 +215,7 @@ def convert_mode(mode): PROC_THREAD_SELF = libpathrs_so.PATHRS_PROC_THREAD_SELF def proc_open(base, path, mode="r", extra_flags=0): - flags = convert_mode(mode) | extra_flags + flags = _convert_mode(mode) | extra_flags return proc_open_raw(base, path, flags).fdopen(mode) def proc_open_raw(base, path, flags): @@ -256,7 +255,7 @@ def from_file(cls, file): return cls(file) def reopen(self, mode="r", extra_flags=0): - flags = convert_mode(mode) | extra_flags + flags = _convert_mode(mode) | extra_flags return self.reopen_raw(flags).fdopen(mode) def reopen_raw(self, flags): @@ -315,7 +314,7 @@ def readlink(self, path): def creat(self, path, filemode, mode="r", extra_flags=0): path = _cstr(path) - flags = convert_mode(mode) | extra_flags + flags = _convert_mode(mode) | extra_flags fd = libpathrs_so.pathrs_creat(self.fileno(), path, flags, filemode) if fd < 0: raise Error._fetch(fd) or INTERNAL_ERROR diff --git a/contrib/bindings/python/pathrs_build.py b/contrib/bindings/python/pathrs/pathrs_build.py similarity index 81% rename from contrib/bindings/python/pathrs_build.py rename to contrib/bindings/python/pathrs/pathrs_build.py index bf8f6980..87359acf 100755 --- a/contrib/bindings/python/pathrs_build.py +++ b/contrib/bindings/python/pathrs/pathrs_build.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 # libpathrs: safe path resolution on Linux -# Copyright (C) 2019-2021 Aleksa Sarai -# Copyright (C) 2019-2021 SUSE LLC +# Copyright (C) 2019-2024 Aleksa Sarai +# Copyright (C) 2019-2024 SUSE LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,11 +19,10 @@ # build of libpathrs, and can be redistributed alongside the pathrs.py wrapping # library). It's much better than the ABI-mode of CFFI. -# TODO: Make this work properly with setuptools -- this might take some work. - import re import os import sys + import cffi def load_hdr(ffi, hdr_path): @@ -42,25 +41,24 @@ def load_hdr(ffi, hdr_path): # Load the header. ffi.cdef(hdr) -def compile_module(**kwargs): +def create_ffibuilder(**kwargs): ffibuilder = cffi.FFI() ffibuilder.cdef("typedef uint32_t dev_t;") # We need to use cdef to tell cffi what functions we need to FFI to. But we # don't need the structs (I hope). - for include_dir in kwargs["include_dirs"]: + for include_dir in kwargs.get("include_dirs", []): pathrs_hdr = os.path.join(include_dir, "pathrs.h") if os.path.exists(pathrs_hdr): load_hdr(ffibuilder, pathrs_hdr) # Add a source and link to libpathrs. - ffibuilder.set_source("_pathrs", "#include ", + ffibuilder.set_source("_libpathrs_cffi", "#include ", libraries=["pathrs"], **kwargs) - # Compile the cffi module. - ffibuilder.compile(verbose=True) + return ffibuilder -def main(): +def find_ffibuilder(): # Figure out where the libpathrs source dir is. ROOT_DIR = None candidate = os.path.dirname(sys.path[0] or os.getcwd()) @@ -91,8 +89,16 @@ def main(): lib_paths = [os.path.dirname(path) for path in lib_paths] # Compile the libpathrs module. - compile_module(include_dirs=[os.path.join(ROOT_DIR, "include")], - library_dirs=lib_paths) + return create_ffibuilder(include_dirs=[os.path.join(ROOT_DIR, "include")], + library_dirs=lib_paths) if __name__ == "__main__": - main() + # Compile the cffi module if running outside of setuptools. + ffibuilder = find_ffibuilder() + ffibuilder.compile(verbose=True) +else: + # Use the system libraries if running inside setuptools. + ffibuilder = create_ffibuilder(include_dirs=[ + "/usr/include", + "/usr/local/include" + ]) diff --git a/contrib/bindings/python/pyproject.toml b/contrib/bindings/python/pyproject.toml new file mode 100644 index 00000000..15df9b2a --- /dev/null +++ b/contrib/bindings/python/pyproject.toml @@ -0,0 +1,58 @@ +#!/usr/bin/python3 +# libpathrs: safe path resolution on Linux +# Copyright (C) 2019-2024 Aleksa Sarai +# Copyright (C) 2019-2024 SUSE LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[build-system] +requires = [ + "setuptools>=68", + "wheel", + "cffi>=1.10.0" +] +build-backend = "setuptools.build_meta" + +[project] +name = "libpathrs" +# TODO: Figure out a way to keep this version up-to-date with Cargo.toml. +version = "0.0.2" +description = "Python bindings for libpathrs, a safe path resolution library for Linux." +keywords = ["libpathrs", "pathrs"] +license = { file = "COPYING.APACHE-2.0" } +authors = [ + {name = "Aleksa Sarai", email = "cyphar@cyphar.com"}, +] +maintainers = [ + {name = "Aleksa Sarai", email = "cyphar@cyphar.com"}, +] +classifiers = [ + "Topic :: Security", + "Topic :: System :: Filesystems", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Libraries :: Python Modules", +] + +requires-python = ">= 3.8" +dependencies = [ + "cffi>=1.10.0" +] + +[project.urls] +Homepage = "https://github.com/openSUSE/libpathrs" +Repository = "https://github.com/openSUSE/libpathrs" diff --git a/contrib/bindings/python/setup.py b/contrib/bindings/python/setup.py new file mode 100755 index 00000000..6dfa618a --- /dev/null +++ b/contrib/bindings/python/setup.py @@ -0,0 +1,24 @@ +#!/usr/bin/python3 +# libpathrs: safe path resolution on Linux +# Copyright (C) 2019-2024 Aleksa Sarai +# Copyright (C) 2019-2024 SUSE LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import setuptools + +setuptools.setup( + ext_package="pathrs", + platforms=["Linux"], + cffi_modules=["pathrs/pathrs_build.py:ffibuilder"], +)