All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog and this project adheres to Semantic Versioning.
- python bindings:
Root.creat
has had itsfilemode
andflags
arguments swapped to match the argument order ofopenat2
(andRoot.creat_raw
). This also now makesfilemode
have a default value of0o644
if unspecified. - Most of the C FFI functions have been renamed:
- Operations on a
Root
have been renamed to have apathrs_inroot_
prefix. pathrs_open_root
has been renamed topathrs_open_root
, to avoid confusion withpathrs_inroot_*
functions and clarify what it is opening.
- Operations on a
- python bindings:
Root.open
has been changed to be a wrapper ofpathrs_inroot_open
instead of being a wrapper around theRoot
constructor. - All C FFI functions that return a file descriptor now set
O_CLOEXEC
by default. Previously some functions that tookO_*
flags would only setO_CLOEXEC
if the user explicitly requested it, butO_CLOEXEC
is easy to unset on file descriptors and having it enabled is a more sane default.
- python bindings: add
Root.creat_raw
to create a new file and wrap it in a rawWrappedFd
(os opposed toRoot.creat
which returns anos.fdopen
). - Root: it is now possible to open a file in one shot without having to do an
intermediate
resolve
step withRoot::open_subpath
. This can be more efficient in some scenarios (especially with the openat2-based resolver or for C FFI users where function calls are expensive) as it saves one file descriptor allocation and extra function calls. - Error:
ErrorKind
is now exported, allowing you to programmatically handle errors returned by libpathrs. This interface may change in the future (in particular,ErrorKind::OsError
might change its representation oferrno
values). - capi: errors that are returned by libpathrs itself (such as invalid arguments
being passed by users) will now contain a
saved_errno
value that makes sense for the error type (soErrorKind::InvalidArgument
will result in anEINVAL
value forsaved_errno
). This will allow C users to have a nicer time handling errors programmatically.
- multiarch: we now build correctly on 32-bit architectures as well as
architectures that have unsigned char. We also have CI jobs that verify that
builds work on a fairly large number of architectures (all relevant tier-1
and tier-2-with-host-tools architectures). If there is an architecture you
would like us to add to the build matrix and it is well-supported by
rustc
, feel free to open an issue or PR! Handle::reopen
will now return an error if you attempt to reopen a handle to a symlink (such as one created withRoot::resolve_nofollow
). Previously, you would get various errors and unexpected behaviour. If you wish to make anO_PATH|O_NOFOLLOW
copy of a symlink handle, you can simply usetry_clone
(i.e.dup(2)
the file descriptor).Handle::reopen(O_NOFOLLOW)
will now return reasonable results. Previously, it would return-ELOOP
in most cases and in other cases it would return unexpected results because theO_NOFOLLOW
would have an effect on the magic-link used internally byHandle::reopen
.Root::mkdir_all
will no longer return-EEXIST
if another process tried to doRoot::mkdir_all
at the same time, instead the race winner's directory will be used by both processes. See opencontainers/runc#4543 for more details.
-
syscalls: switch to rustix for most of our syscall wrappers to simplify how much code we have for wrapper raw syscalls. This also lets us build on musl-based targets because musl doesn't support some of the syscalls we need.
There are some outstanding issues with rustix that make this switch a little uglier than necessary (rustix#1186, rustix#1187), but this is a net improvement overall.
0.1.3 - 2024-10-10
自動化って物は試しとすればいい物だ
- gha: our Rust crate and Python bindings are now uploaded automatically from a GitHub action when a tag is pushed.
- syscalls: the pretty-printing of
openat2
errors now gives a string description of the flags passed rather that just a hex value (to match other syscalls). - python bindings: restrict how our methods and functions can be called using
/
and*
to reduce the possibility of future breakages if we rename or re-order some of our arguments.
0.1.2 - 2024-10-09
蛇のように賢く、鳩のように素直でありなさい
- python bindings: add a minimal README for PyPI.
- python bindings: actually export
PROC_ROOT
. - python bindings: add type annotations and
py.typed
to allow for downstream users to get proper type annotations for the API.
0.1.1 - 2024-10-01
頒布と聞いたら蛇に睨まれた蛙になるよ
-
procfs: add support for operating on files in the
/proc
root (or other processes) withProcfsBase::ProcRoot
.While the cached file descriptor shouldn't leak into containers (container runtimes know to set
PR_SET_DUMPABLE
, and our cached file descriptor isO_CLOEXEC
), I felt a little uncomfortable about having a global unmasked procfs handle sitting around inlibpathrs
. So, in order to avoid making a file descriptor leak by alibpathrs
user catastrophic,libpathrs
will always try to use a "limited" procfs handle as the global cached handle (which is much safer to leak into a container) and for operations onProcfsBase::ProcRoot
, a temporary new "unrestricted" procfs handle is created just for that operartion. This is more expensive, but it avoids a potential leak turning into a breakout or other nightmare scenario. -
python bindings: The
cffi
build script is now a little easier to use for distributions that want to build the python bindings at the same time as the main library. After compiling the library, set thePATHRS_SRC_ROOT
environment variable to the root of thelibpathrs
source directory. This will instruct thecffi
build script (when called fromsetup.py
orpython3 -m build
) to link against the library built in the source directory rather than using system libraries. As long as you install the same library later, this should cause no issues.Standard wheel builds still work the same way, so users that want to link against the system libraries don't need to make any changes.
-
Root::mkdir_all
no longer does strict verification that directories created bymkdir_all
"look right" after opening each component. These checks didn't protect against any practical attack (since an attacker could just get us to use a directory by creating it beforeRoot::mkdir_all
and we would happily use it) and just resulted in spurious errors when dealing with complicated filesystem configurations (POSIX ACLs, weird filesystem-specific mount options). (#71) -
capi: Passing invalid
pathrs_proc_base_t
values topathrs_proc_*
will now return an error rather than resulting in Undefined Behaviour™.
0.1.0 - 2024-09-14
負けたくないことに理由って要る?
-
libpathrs now has an official MSRV of 1.63, which is verified by our CI. The MSRV was chosen because it's the Rust version in Debian stable and it has
io_safety
which is one of the last bits we absolutely need. -
libpathrs now has a "safe procfs resolver" implementation that verifies all of our operations on
/proc
are done safely (including usingfsopen(2)
oropen_tree(2)
to create a private/proc
to protect against race attacks).This is mainly motivated by issues like CVE-2019-16884 and CVE-2019-19921, where an attacker could configure a malicious mount table such that naively doing
/proc
operations could result in security issues. While there are limited things you can do in such a scenario, it is far more preferable to be able to detect these kinds of attacks and at least error out if there is a malicious/proc
.This is based on similar work I did in filepath-securejoin.
- This API is also exposed to users through the Rust and C FFI because this is something a fair number of system tools (such as container runtimes) need.
-
root: new
Root
methods:readlink
andresolve_nofollow
to allow users to operate on symlinks directly (though it is still unsafe to use the returned path for lookups!).remove_all
so that Go users can switch fromos.RemoveAll
(though Go'sos.RemoveAll
is safe against races since Go 1.21.11 and Go 1.22.4).mkdir_all
so that Go users can switch fromos.MkdirAll
. This is based on similar work done in filepath-securejoin.
-
root: The method for configuring the resolver has changed to be more akin to a getter-setter style. This allows for more ergonomic usage (see the
RootRef::with_resolver_flags
examples) and also lets us avoid exposing internal types needlessly.As part of this change, the ability to choose the resolver backend was removed (because the C API also no longer supports it). This will probably be re-added in the future, but for now it seems best to not add extra APIs that aren't necessary until someone asks.
-
opath resolver: We now emulate
fs.protected_symlinks
when resolving symlinks using the emulated opath resolver. This is only done iffs.protected_symlinks
is enabled on the system (to mirror the behaviour ofopenat2
). -
tests: Add a large number of integration tests, mainly based on the test suite in filepath-securejoin. This test suite tests all of the Rust code and the C FFI code from within Rust, giving us ~89% test coverage.
-
tests: Add some smoke tests using our bindings to ensure that you can actually build with them and run a basic
cat
program. In the future we will do proper e2e testing with all of the bindings. -
packaging: Add an autoconf-like
install.sh
script that generates apkg-config
specification for libpathrs. This should help distributions package libpathrs.
- Handling of
//
and trailing slashes has been fixed to better match what users expect and what the kernel does. - opath resolver: Use reference counting to avoid needlessly cloning files internally when doing lookups.
- Remove the
try_clone_hotfix
workaround, since the Rust stdlib patch was merged several years ago. - cffi: Building the C API is now optional, so Rust crates won't contain any of the C FFI code and we only build the C FFI crate types manually in the makefile. This also lets us remove some dependencies and other annoying things in the Rust crate (since those things are only needed for the C API).
- python bindings: Switch to setuptools to allow for a proper Python package
install. This also includes some reworking of the layout to avoid leaking
stuff to users that just do
import pathrs
.
-
cffi: Redesign the entire API to be file descriptor based, removing the need for complicated freeing logic and matching what most kernel APIs actually look like. While there is a risk that users would operate on file descriptors themselves, the benefits of a pure-fd-based API outweigh those issues (and languages with bindings like Python and Go can easily wrap the file descriptor to provide helper methods and avoid this mistake by users).
Aside from making C users happier, this makes writing bindings much simpler because every language has native support for handling the freeing of file objects (Go in particular has
*os.File
which would be too difficult to emulate outside of the stdlib because of it's uniqueClose
handling).- Unfortunately, this API change also removed some information from the C API
because it was too difficult to deal with:
- Backtraces are no longer provided to the C API. There is no plan to re-add them because they complicate the C API a fair bit and it turns out that it's basically impossible to graft backtraces to languages that have native backtrace support (Go and Python) so providing this information has no real benefit to anyone.
- The configuration API has been removed for now. In the future we will probably want to re-add it, but figuring out a nice API for this is left for a future (pre-1.0) release. In practice, the default settings are the best settings to use for most people anyway.
- Unfortunately, this API change also removed some information from the C API
because it was too difficult to deal with:
-
bindings: All of the bindings were rewritten to use the new API.
-
rust: Rework libpathrs to use the (stabilised in Rust 1.63)
io_safety
features. This lets us avoid possible "use after free" issues with file descriptors that were closed by accident.This required the addition of
HandleRef
andRootRef
to wrapBorrowedFd
(this is needed for the C API, but is almost certainly useful to other folks). Unfortunately we can't implementDeref
so all of the methods need to be duplicated for the new types. -
Split
Root::remove
intoRoot::remove_file
(unlink
) andRoot::remove_dir
(rmdir
) so we don't need to do the retry loop anymore. Some users care about what kind of inode they're removing, and if a user really wants to nuke a path they would want to useRoot::remove_all
anyway because the oldRoot::remove
would not remove non-empty directories. -
Switch from
snafu
tothiserror
for generating our error impls. One upshot of this change is that our errors are more opaque to Rust users. However, this change resulted in us removing backtraces from our errors (becausethiserror
only supportsstd::backtrace::Backtrace
which was stabilised after our MSRV, and even then it is somewhat limited until some more bits ofstd::backtrace::Backtrace
are stabilised). We do plan to re-add backtraces but they probably aren't strictly needed by most library users.In the worst case we could write our own handling of backtraces using the
backtrace
crate, but I'd like to see a user actually ask for that before sitting down to work on it.
0.0.2 - 2020-02-15
- bindings: Go bindings (thanks to Maxim Zhiburt for the initial version!).
- bindings: Add support for converting to/from file descriptors.
- Update to the newest
openat2
API (which now uses extensible structs).
- cffi: Make all objects thread-safe so multi-threaded programs don't hit data races.
- cffi: Major rework of the CPointer locking design to split the single Mutex (used for both the inner type and errors) into two separate locks. As the inner value is almost always read, this should massively reduce lock contention in multi-threaded programs.
- cffi:
pathrs_from_fd
now clones the passed file descriptor. Some GC'd languages are annoying to deal with when a file descriptor's ownership is meant to be transferred outside of the program.
0.0.1 - 2020-01-05
- docs: Fix rustdoc build errors.
0.0.0 - 2020-01-05 [YANKED]
Initial release.
(This release was yanked because the rust docs were broken.)
- Initial implementation of libpathrs, with most of the major functionality
we need implemented:
Root
:openat2
- andO_PATH
-based resolvers.resolve
create
andcreate_file
remove
rename
Handle
:reopen
- C FFI.
- Python bindings.