From 0a189087977f92eb4f21c0b83e5728b632728b4c Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Sat, 25 Apr 2026 23:03:44 +0900 Subject: [PATCH] capi: add pathrs_version() API Expose the libpathrs version (CARGO_PKG_VERSION) through the C API as a NUL-terminated static string. Wire it through the Go and Python bindings as pathrs.Version() and pathrs.library_version(), and surface it from the C/Go/Python example programs via their usage/--version output. Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Akihiro Suda --- contrib/bindings/python/pathrs/_pathrs.py | 16 ++++++++++ examples/c/cat.c | 1 + examples/c/cat_multithread.c | 1 + examples/go/cat.go | 9 ++++-- examples/python/sysctl.py | 7 +++++ .../internal/libpathrs/libpathrs_linux.go | 5 ++++ go-pathrs/version_linux.go | 29 +++++++++++++++++++ include/pathrs.h | 16 ++++++++++ src/capi/core.rs | 20 +++++++++++++ 9 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 go-pathrs/version_linux.go diff --git a/contrib/bindings/python/pathrs/_pathrs.py b/contrib/bindings/python/pathrs/_pathrs.py index 25cfdc3a..2e49271d 100644 --- a/contrib/bindings/python/pathrs/_pathrs.py +++ b/contrib/bindings/python/pathrs/_pathrs.py @@ -28,6 +28,7 @@ INTERNAL_ERROR, # CFFI helpers. _cstr, + _pystr, _cbuffer, ) from ._libpathrs_cffi import lib as libpathrs_so @@ -51,11 +52,26 @@ # Core api. "Root", "Handle", + "library_version", # Error api (re-export). "PathrsError", ] +def library_version() -> str: + """ + Return the version of the underlying libpathrs C library that this Python + binding is linked against. + + The returned string follows SemVer 2.0.0 (with an optional + "+" suffix for pre-release builds, e.g. "0.2.4+dev"). + + This is distinct from pathrs.__version__ which contains the version of the + Python pathrs package itself. + """ + return _pystr(libpathrs_so.pathrs_version()) + + class Handle(WrappedFd): "A handle to a filesystem object, usually resolved using Root.resolve()." diff --git a/examples/c/cat.c b/examples/c/cat.c index 9eb8f220..c035cafd 100644 --- a/examples/c/cat.c +++ b/examples/c/cat.c @@ -69,6 +69,7 @@ int open_in_root(const char *root_path, const char *unsafe_path) } void usage(void) { + printf("cat (libpathrs %s)\n", pathrs_version()); printf("usage: cat \n"); exit(1); } diff --git a/examples/c/cat_multithread.c b/examples/c/cat_multithread.c index cc10a63c..9a2de4a0 100644 --- a/examples/c/cat_multithread.c +++ b/examples/c/cat_multithread.c @@ -98,6 +98,7 @@ void *worker(void *_arg) { } void usage(void) { + printf("cat (libpathrs %s)\n", pathrs_version()); printf("usage: cat \n"); exit(1); } diff --git a/examples/go/cat.go b/examples/go/cat.go index 92c2e32d..2eeacd0e 100644 --- a/examples/go/cat.go +++ b/examples/go/cat.go @@ -28,10 +28,15 @@ import ( "cyphar.com/go-pathrs" ) +func usage() { + fmt.Fprintf(os.Stderr, "cat (libpathrs %s)\n", pathrs.Version()) + fmt.Fprintln(os.Stderr, "usage: cat ") + os.Exit(1) +} + func Main(args ...string) error { if len(args) != 2 { - fmt.Fprintln(os.Stderr, "usage: cat ") - os.Exit(1) + usage() } rootPath, unsafePath := args[0], args[1] diff --git a/examples/python/sysctl.py b/examples/python/sysctl.py index dfe2967f..3a5408cd 100755 --- a/examples/python/sysctl.py +++ b/examples/python/sysctl.py @@ -18,6 +18,7 @@ import sys sys.path.append(os.path.dirname(__file__) + "/../contrib/bindings/python") +import pathrs from pathrs import procfs from pathrs.procfs import ProcfsHandle @@ -65,6 +66,12 @@ def main(*args): prog="sysctl.py", description="A minimal implementation of sysctl(8) but using the libpathrs procfs API.", ) + parser.add_argument( + "-V", + "--version", + action="version", + version=f"sysctl.py (libpathrs {pathrs.library_version()})", + ) parser.add_argument( "-n", "--values", diff --git a/go-pathrs/internal/libpathrs/libpathrs_linux.go b/go-pathrs/internal/libpathrs/libpathrs_linux.go index d54497a5..1cc399d1 100644 --- a/go-pathrs/internal/libpathrs/libpathrs_linux.go +++ b/go-pathrs/internal/libpathrs/libpathrs_linux.go @@ -50,6 +50,11 @@ func fetchError(errID C.int) error { return err } +// Version wraps pathrs_version. +func Version() string { + return C.GoString(C.pathrs_version()) +} + // OpenRoot wraps pathrs_open_root. func OpenRoot(path string) (uintptr, error) { cPath := C.CString(path) diff --git a/go-pathrs/version_linux.go b/go-pathrs/version_linux.go new file mode 100644 index 00000000..f7777048 --- /dev/null +++ b/go-pathrs/version_linux.go @@ -0,0 +1,29 @@ +//go:build linux + +// SPDX-License-Identifier: MPL-2.0 +/* + * libpathrs: safe path resolution on Linux + * Copyright (C) 2019-2025 SUSE LLC + * Copyright (C) 2026 Aleksa Sarai + * + * This Source Code Form is subject to the terms of the Mozilla Public + * 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/. + */ + +package pathrs + +import ( + "cyphar.com/go-pathrs/internal/libpathrs" +) + +// Version returns the version string of the underlying libpathrs C library +// that go-pathrs is linked against. +// +// The returned string follows [SemVer 2.0.0] (with an optional +// "+" suffix for pre-release builds, e.g. "0.2.4+dev"). +// +// [SemVer 2.0.0]: https://semver.org/spec/v2.0.0.html +func Version() string { + return libpathrs.Version() +} diff --git a/include/pathrs.h b/include/pathrs.h index b6e81a7f..50750428 100644 --- a/include/pathrs.h +++ b/include/pathrs.h @@ -195,6 +195,22 @@ typedef struct __CBINDGEN_ALIGNED(8) { */ #define __PATHRS_MAX_ERR_VALUE -4096 +/** + * Get the libpathrs version as a static, NUL-terminated string. + * + * The returned string follows [SemVer 2.0.0][semver] (with an optional + * `+` suffix for pre-release builds, e.g. `0.2.4+dev`). + * + * # Return Value + * + * This function returns a pointer to a static, NUL-terminated string + * containing the libpathrs version. The returned pointer must not be freed, + * and remains valid for the lifetime of the program. + * + * [semver]: https://semver.org/spec/v2.0.0.html + */ +const char *pathrs_version(void); + /** * Open a root handle. * diff --git a/src/capi/core.rs b/src/capi/core.rs index 6167cddf..3c1c996c 100644 --- a/src/capi/core.rs +++ b/src/capi/core.rs @@ -49,6 +49,26 @@ use std::{ use libc::{c_char, c_int, c_uint, dev_t, size_t}; +/// Get the libpathrs version as a static, NUL-terminated string. +/// +/// The returned string follows [SemVer 2.0.0][semver] (with an optional +/// `+` suffix for pre-release builds, e.g. `0.2.4+dev`). +/// +/// # Return Value +/// +/// This function returns a pointer to a static, NUL-terminated string +/// containing the libpathrs version. The returned pointer must not be freed, +/// and remains valid for the lifetime of the program. +/// +/// [semver]: https://semver.org/spec/v2.0.0.html +#[no_mangle] +pub extern "C" fn pathrs_version() -> *const c_char { + concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char +} +utils::symver! { + fn pathrs_version <- (pathrs_version, version = "LIBPATHRS_0.2", default); +} + /// Open a root handle. /// /// The provided path must be an existing directory.