import re
import os
import sys
from _pathrs import ffi, lib as libpathrs_so
__all__ = ["Root", "Handle", "Error"]
DEFAULT_RESOLVER = {
"kernel": libpathrs_so.PATHRS_KERNEL_RESOLVER,
"emulated": libpathrs_so.PATHRS_EMULATED_RESOLVER,
}.get(os.environ.get("PATHRS_RESOLVER"))
def _cstr(pystr):
return ffi.new("char[]", pystr.encode("utf8"))
def _pystr(cstr):
return ffi.string(cstr).decode("utf8")
def _pyptr(cptr):
return int(ffi.cast("uintptr_t", cptr))
def _into_fd(typ, inner):
fd = libpathrs_so.pathrs_into_fd(typ, inner)
if fd < 0:
raise Error.fetch(typ, inner)
return fd
def _from_fd(typ, fd):
inner = libpathrs_so.pathrs_from_fd(typ, fd)
if inner == ffi.NULL:
raise Error("pathrs_handle allocation failed")
err = Error.fetch(typ, inner)
if err:
raise err
return inner
class Error(Exception):
def __init__(self, message, *_, errno=None, backtrace=None):
super().__init__(message)
self.message = message
self.errno = errno
self.backtrace = backtrace
self.strerror = None
if self.errno is not None:
try:
self.strerror = os.strerror(errno)
except ValueError:
self.strerror = str(errno)
@classmethod
def fetch(cls, typ, obj):
err = libpathrs_so.pathrs_error(typ, obj)
if err == ffi.NULL:
return None
description = _pystr(err.description)
errno = err.saved_errno or None
backtrace = Backtrace(err.backtrace) or None
libpathrs_so.pathrs_free(libpathrs_so.PATHRS_ERROR, err)
del err
return cls(description, backtrace=backtrace, errno=errno)
def __str__(self):
if self.errno is None:
return self.message
else:
return "%s (%s)" % (self.message, self.strerror)
def __repr__(self):
return "Error(%r, errno=%r)" % (self.message, self.errno)
def pprint(self, out=sys.stdout):
if self.errno is None:
print("pathrs error:", file=out)
else:
print("pathrs error [%s]:" % (self.strerror,), file=out)
print(" %s" % (self.message,), file=out)
if self.backtrace:
print("rust backtrace:", file=out)
for entry in self.backtrace:
print(" %s" % (entry,), file=out)
if entry.symbol.file is not None:
print(" in file '%s':%d" % (entry.symbol.file, entry.symbol.lineno), file=out)
CLOSED_OBJECT = Error("cannot operate on closed libpathrs object")
class BacktraceSymbol(object):
def __init__(self, c_entry):
self.address = _pyptr(c_entry.symbol_address)
self.name = None
if c_entry.symbol_name != ffi.NULL:
self.name = _pystr(c_entry.symbol_name)
self.file = None
self.lineno = None
if c_entry.symbol_file != ffi.NULL:
self.file = _pystr(c_entry.symbol_file)
self.lineno = c_entry.symbol_lineno
def __str__(self):
string = "<0x%x>" % (self.address,)
if self.name is not None:
string = "'%s'@%s" % (self.name, string)
return string
class BacktraceEntry(object):
def __init__(self, c_entry):
self.ip = _pyptr(c_entry.ip)
self.symbol = BacktraceSymbol(c_entry)
def __str__(self):
return "%s+0x%x" % (self.symbol, self.ip - self.symbol.address)
class Backtrace(list):
def __init__(self, c_backtrace):
super().__init__()
if c_backtrace == ffi.NULL:
return
for idx in range(c_backtrace.length):
c_entry = c_backtrace.head[idx]
self.append(BacktraceEntry(c_entry))
class Handle(object):
def __init__(self, handle):
self._type = libpathrs_so.PATHRS_HANDLE
self._inner = handle
def _error(self):
return Error.fetch(self._type, self._inner)
def __del__(self):
if self._inner is not None:
libpathrs_so.pathrs_free(self._type, self._inner)
def __copy__(self):
return self.__deepcopy__({})
def __deepcopy__(self, memo):
new_inner = libpathrs_so.pathrs_duplicate(self._type, self._inner)
if new_inner == ffi.NULL:
raise self._error() or CLOSED_OBJECT
cls = self.__class__
new = cls.__new__(cls)
new._type = self._type
new._inner = new_inner
return new
@classmethod
def from_raw_fd(cls, fd):
inner = _from_fd(libpathrs_so.PATHRS_HANDLE, fd)
return cls(inner)
def into_raw_fd(self):
return _into_fd(self._type, self._inner)
@staticmethod
def _convert_mode(mode):
mode = set(mode)
flags = os.O_CLOEXEC
if "x" in mode:
raise ValueError("pathrs doesn't support mode='x', use creat()")
if len(mode & {"r", "w", "a"}) > 1:
raise ValueError("must have exactly one of read/write/append mode")
read = False
write = False
if "+" in mode:
read = True
write = True
if "r" in mode:
read = True
if "w" in mode:
write = True
flags |= os.O_TRUNC
if "a" in mode:
write = True
flags |= os.O_APPEND
if read and write:
flags |= os.O_RDWR
elif write:
flags |= os.O_WRONLY
else:
flags |= os.O_RDONLY
return flags
def reopen(self, mode="r", extra_flags=0):
flags = self._convert_mode(mode) | extra_flags
fd = libpathrs_so.pathrs_reopen(self._inner, flags)
if fd < 0:
raise self._error() or CLOSED_OBJECT
try:
return os.fdopen(fd, mode)
except Exception as e:
os.close(fd)
raise
class Root(object):
def __init__(self, path, resolver=DEFAULT_RESOLVER):
path = _cstr(path)
self._type = libpathrs_so.PATHRS_ROOT
self._inner = libpathrs_so.pathrs_open(path)
if self._inner == ffi.NULL:
raise Error("pathrs_root allocation failed")
err = self._error()
if err:
raise err
if resolver is not None:
new_config = ffi.new("pathrs_config_root_t *")
new_config.resolver = resolver
err = libpathrs_so.pathrs_configure(self._type, self._inner, ffi.NULL, new_config, ffi.sizeof(new_config))
if err:
raise self._error() or CLOSED_OBJECT
def _error(self):
return Error.fetch(self._type, self._inner)
def __del__(self):
if self._inner is not None:
libpathrs_so.pathrs_free(self._type, self._inner)
def __copy__(self):
return self.__deepcopy__({})
def __deepcopy__(self, memo):
new_inner = libpathrs_so.pathrs_duplicate(self._type, self._inner)
if new_inner == ffi.NULL:
raise self._error() or CLOSED_OBJECT
cls = self.__class__
new = cls.__new__(cls)
new._type = self._type
new._inner = new_inner
return new
@classmethod
def from_raw_fd(cls, fd):
inner = _from_fd(libpathrs_so.PATHRS_ROOT, fd)
return cls(inner)
def into_raw_fd(self):
return _into_fd(self._type, self._inner)
def resolve(self, path):
path = _cstr(path)
handle = libpathrs_so.pathrs_resolve(self._inner, path)
if handle == ffi.NULL:
raise self._error() or CLOSED_OBJECT
return Handle(handle)
def rename(self, src, dst, flags=0):
src = _cstr(src)
dst = _cstr(dst)
err = libpathrs_so.pathrs_rename(self._inner, src, dst, flags)
if err < 0:
raise self._error() or CLOSED_OBJECT
def creat(self, path, mode):
path = _cstr(path)
handle = libpathrs_so.pathrs_creat(self._inner, path, mode)
if handle == ffi.NULL:
raise self._error() or CLOSED_OBJECT
return Handle(handle)
def mkdir(self, path, mode):
path = _cstr(path)
err = libpathrs_so.pathrs_mkdir(self._inner, path, mode)
if err < 0:
raise self._error() or CLOSED_OBJECT
def mknod(self, path, mode, dev):
path = _cstr(path)
err = libpathrs_so.pathrs_mknod(self._inner, path, mode, dev)
if err < 0:
raise self._error() or CLOSED_OBJECT
def hardlink(self, path, target):
path = _cstr(path)
target = _cstr(target)
err = libpathrs_so.pathrs_hardlink(self._inner, path, target)
if err < 0:
raise self._error() or CLOSED_OBJECT
def symlink(self, path, target):
path = _cstr(path)
target = _cstr(target)
err = libpathrs_so.pathrs_symlink(self._inner, path, target)
if err < 0:
raise self._error() or CLOSED_OBJECT