Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(nextest)',
] }

# NOTE: This section will cause parsing issues on pre-edition2024 versions of
# Rust, but they are not needed for compilation of the main libpathrs binary.
# Our Makefile (via hack/with-crate-type.sh) will automatically remove this
# during builds, but if you are running cargo commands yourself you might need
# to work around this as well.
# MSRV(1.85): Remove the above note.
[workspace]
resolver = "2"
members = [
Expand Down
6 changes: 3 additions & 3 deletions contrib/bindings/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ RENAME_EXCHANGE = 0x2

with pathrs.Root("/path/to/rootfs") as root:
# symlink
root.symlink("foo", "bar") # foo -> bar
root.symlink("foo", "bar") # foo -> bar
# link
root.hardlink("a", "b") # a -> b
root.hardlink("a", "b") # a -> b
# rename(at2)
root.rename("foo", "b", flags=RENAME_EXCHANGE) # foo <-> b
root.rename("foo", "b", flags=RENAME_EXCHANGE) # foo <-> b
# open(O_CREAT)
with root.creat("newfile", "w+") as f:
f.write("Some contents.")
Expand Down
3 changes: 1 addition & 2 deletions contrib/bindings/python/pathrs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/python3
# SPDX-License-Identifier: MPL-2.0
#
# libpathrs: safe path resolution on Linux
Expand All @@ -13,7 +12,7 @@
import importlib.metadata

from . import _pathrs
from ._pathrs import * # noqa: F403 # We just re-export everything.
from ._pathrs import * # We just re-export everything.

# In order get pydoc to include the documentation for the re-exported code from
# _pathrs, we need to include all of the members in __all__. Rather than
Expand Down
55 changes: 28 additions & 27 deletions contrib/bindings/python/pathrs/_internal.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/python3
# SPDX-License-Identifier: MPL-2.0
#
# libpathrs: safe path resolution on Linux
Expand All @@ -9,19 +8,21 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

import io
import os
import sys
# TODO: Remove this once we only support Python >= 3.10.
from __future__ import annotations # PEP 604

import copy
import errno
import fcntl

import io
import os
import sys
import typing
from types import TracebackType
from typing import Any, Dict, IO, Optional, TextIO, Type, TypeVar, Union
from typing import IO, Any, ClassVar, TextIO, TypeAlias, TypeVar

# TODO: Remove this once we only support Python >= 3.11.
from typing_extensions import Self, TypeAlias
from typing_extensions import Self

from ._libpathrs_cffi import lib as libpathrs_so

Expand Down Expand Up @@ -52,7 +53,7 @@ def _pystr(cstr: CString) -> str:


def _cbuffer(size: int) -> CBuffer:
return ffi.new("char[%d]" % (size,))
return ffi.new(f"char[{size}]")


def _is_pathrs_err(ret: int) -> bool:
Expand All @@ -68,10 +69,10 @@ class PathrsError(Exception):
"""

message: str
errno: Optional[int]
strerror: Optional[str]
errno: int | None
strerror: str | None

def __init__(self, message: str, /, *, errno: Optional[int] = None):
def __init__(self, message: str, /, *, errno: int | None = None):
# Construct Exception.
super().__init__(message)

Expand All @@ -88,7 +89,7 @@ def __init__(self, message: str, /, *, errno: Optional[int] = None):
self.strerror = str(errno)

@classmethod
def _fetch(cls, err_id: int, /) -> Optional[Self]:
def _fetch(cls, err_id: int, /) -> Self | None:
if err_id >= 0:
return None

Expand All @@ -109,19 +110,19 @@ def __str__(self) -> str:
if self.errno is None:
return self.message
else:
return "%s (%s)" % (self.message, self.strerror)
return f"{self.message} ({self.strerror})"

def __repr__(self) -> str:
return "Error(%r, errno=%r)" % (self.message, self.errno)
return f"Error({self.message!r}, errno={self.errno!r})"

def pprint(self, out: TextIO = sys.stdout) -> None:
"Pretty-print the error to the given @out file."
# Basic error information.
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)
print(f"pathrs error [{self.strerror}]:", file=out)
print(f" {self.message}", file=out)


INTERNAL_ERROR = PathrsError("tried to fetch libpathrs error but no error found")
Expand All @@ -131,7 +132,7 @@ class FilenoFile(typing.Protocol):
def fileno(self) -> int: ...


FileLike = Union[FilenoFile, int]
FileLike = FilenoFile | int


def _fileno(file: FileLike) -> int:
Expand All @@ -151,7 +152,7 @@ def _clonefile(file: FileLike) -> int:
Fd = TypeVar("Fd", bound="WrappedFd")


class WrappedFd(object):
class WrappedFd:
"""
Represents a file descriptor that allows for manual lifetime management,
unlike os.fdopen() which are tracked by the GC with no way of "leaking" the
Expand All @@ -160,7 +161,7 @@ class WrappedFd(object):
pathrs will return WrappedFds for most operations that return an fd.
"""

_fd: Optional[int]
_fd: int | None

def __init__(self, file: FileLike, /):
"""
Expand Down Expand Up @@ -233,12 +234,12 @@ def fdopen(self, mode: str = "r") -> IO[Any]:
raise

@classmethod
def from_raw_fd(cls: Type[Fd], fd: int, /) -> Fd:
def from_raw_fd(cls, fd: int, /) -> Self:
"Shorthand for WrappedFd(fd)."
return cls(fd)

@classmethod
def from_file(cls: Type[Fd], file: FileLike, /) -> Fd:
def from_file(cls, file: FileLike, /) -> Self:
"Shorthand for WrappedFd(file)."
return cls(file)

Expand Down Expand Up @@ -288,7 +289,7 @@ def __copy__(self) -> Self:
# A "shallow copy" of a file is the same as a deep copy.
return copy.deepcopy(self)

def __deepcopy__(self, memo: Dict[int, Any]) -> Self:
def __deepcopy__(self, memo: dict[int, Any]) -> Self:
"Identical to WrappedFd.clone()"
return self.clone()

Expand All @@ -301,9 +302,9 @@ def __enter__(self) -> Self:

def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
exc_traceback: Optional[TracebackType],
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
exc_traceback: TracebackType | None,
) -> None:
self.close()

Expand Down Expand Up @@ -349,9 +350,9 @@ def _convert_mode(mode: str) -> int:
class SingletonClass(type):
"""Metaclass used to create singleton classes."""

_instances: dict[type, Type[Any]] = {}
_instances: ClassVar[dict[type, type[Any]]] = {}

def __call__(cls, *args, **kwargs): # type: ignore[no-untyped-def] # TODO: Not clear what annotations to use, and mypy appears to be confused by metaclasses.
if cls not in cls._instances:
cls._instances[cls] = super(SingletonClass, cls).__call__(*args, **kwargs)
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
56 changes: 24 additions & 32 deletions contrib/bindings/python/pathrs/_libpathrs_cffi/lib.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

from typing import type_check_only, Union

# TODO: Remove this once we only support Python >= 3.10.
from typing_extensions import TypeAlias, Literal
from typing import Literal, TypeAlias, type_check_only

from .._pathrs import CBuffer, CString
from ..procfs import ProcfsBase
Expand All @@ -29,15 +27,15 @@ __PATHRS_MAX_ERR_VALUE: ErrorId

# TODO: We actually return Union[CError, cffi.FFI.NULL] but we can't express
# this using the typing stubs for CFFI...
def pathrs_errorinfo(err_id: Union[ErrorId, int]) -> CError: ...
def pathrs_errorinfo(err_id: ErrorId | int) -> CError: ...
def pathrs_errorinfo_free(err: CError) -> None: ...

# pathrs_version_info_t *
@type_check_only
class VersionInfo:
version_string: CString

def pathrs_version(info: VersionInfo, size: int) -> Union[ErrorId, int]: ...
def pathrs_version(info: VersionInfo, size: int) -> ErrorId | int: ...

# uint64_t
ProcfsOpenFlags: TypeAlias = int
Expand All @@ -58,70 +56,64 @@ __PATHRS_PROC_TYPE_PID: ProcfsBase
PATHRS_PROC_DEFAULT_ROOTFD: RawFd

# procfs API
def pathrs_procfs_open(how: ProcfsOpenHow, size: int) -> Union[RawFd, ErrorId]: ...
def pathrs_procfs_open(how: ProcfsOpenHow, size: int) -> RawFd | ErrorId: ...
def pathrs_proc_open(
base: ProcfsBase, path: CString, flags: int
) -> Union[RawFd, ErrorId]: ...
) -> RawFd | ErrorId: ...
def pathrs_proc_openat(
proc_root_fd: RawFd, base: ProcfsBase, path: CString, flags: int
) -> Union[RawFd, ErrorId]: ...
) -> RawFd | ErrorId: ...
def pathrs_proc_readlink(
base: ProcfsBase, path: CString, linkbuf: CBuffer, linkbuf_size: int
) -> Union[int, ErrorId]: ...
) -> int | ErrorId: ...
def pathrs_proc_readlinkat(
proc_root_fd: RawFd,
base: ProcfsBase,
path: CString,
linkbuf: CBuffer,
linkbuf_size: int,
) -> Union[int, ErrorId]: ...
) -> int | ErrorId: ...

# core API
def pathrs_open_root(path: CString) -> Union[RawFd, ErrorId]: ...
def pathrs_reopen(fd: RawFd, flags: int) -> Union[RawFd, ErrorId]: ...
def pathrs_inroot_resolve(rootfd: RawFd, path: CString) -> Union[RawFd, ErrorId]: ...
def pathrs_inroot_resolve_nofollow(
rootfd: RawFd, path: CString
) -> Union[RawFd, ErrorId]: ...
def pathrs_inroot_open(
rootfd: RawFd, path: CString, flags: int
) -> Union[RawFd, ErrorId]: ...
def pathrs_open_root(path: CString) -> RawFd | ErrorId: ...
def pathrs_reopen(fd: RawFd, flags: int) -> RawFd | ErrorId: ...
def pathrs_inroot_resolve(rootfd: RawFd, path: CString) -> RawFd | ErrorId: ...
def pathrs_inroot_resolve_nofollow(rootfd: RawFd, path: CString) -> RawFd | ErrorId: ...
def pathrs_inroot_open(rootfd: RawFd, path: CString, flags: int) -> RawFd | ErrorId: ...
def pathrs_inroot_creat(
rootfd: RawFd, path: CString, flags: int, filemode: int
) -> Union[RawFd, ErrorId]: ...
) -> RawFd | ErrorId: ...
def pathrs_inroot_rename(
old_rootfd: RawFd,
old_path: CString,
new_rootfd: RawFd,
new_path: CString,
flags: int,
) -> Union[Literal[0], ErrorId]: ...
def pathrs_inroot_rmdir(rootfd: RawFd, path: CString) -> Union[Literal[0], ErrorId]: ...
def pathrs_inroot_unlink(
rootfd: RawFd, path: CString
) -> Union[Literal[0], ErrorId]: ...
def pathrs_inroot_remove_all(rootfd: RawFd, path: CString) -> Union[RawFd, ErrorId]: ...
) -> Literal[0] | ErrorId: ...
def pathrs_inroot_rmdir(rootfd: RawFd, path: CString) -> Literal[0] | ErrorId: ...
def pathrs_inroot_unlink(rootfd: RawFd, path: CString) -> Literal[0] | ErrorId: ...
def pathrs_inroot_remove_all(rootfd: RawFd, path: CString) -> RawFd | ErrorId: ...
def pathrs_inroot_mkdir(
rootfd: RawFd, path: CString, mode: int
) -> Union[Literal[0], ErrorId]: ...
) -> Literal[0] | ErrorId: ...
def pathrs_inroot_mkdir_all(
rootfd: RawFd, path: CString, mode: int
) -> Union[Literal[0], ErrorId]: ...
) -> Literal[0] | ErrorId: ...
def pathrs_inroot_mknod(
rootfd: RawFd, path: CString, mode: int, dev: int
) -> Union[Literal[0], ErrorId]: ...
) -> Literal[0] | ErrorId: ...
def pathrs_inroot_hardlink(
old_rootfd: RawFd,
old_path: CString,
new_rootfd: RawFd,
new_path: CString,
flags: int,
) -> Union[Literal[0], ErrorId]: ...
) -> Literal[0] | ErrorId: ...
def pathrs_inroot_symlink(
target: CString,
rootfd: RawFd,
linkpath: CString,
) -> Union[Literal[0], ErrorId]: ...
) -> Literal[0] | ErrorId: ...
def pathrs_inroot_readlink(
rootfd: RawFd, path: CString, linkbuf: CBuffer, linkbuf_size: int
) -> Union[int, ErrorId]: ...
) -> int | ErrorId: ...
Loading
Loading