Skip to content

[posix] E: Add libposix pathconf/fpathconf stubs#23

Open
esaurez wants to merge 1 commit into
devfrom
feat/libposix-pathconf-stubs
Open

[posix] E: Add libposix pathconf/fpathconf stubs#23
esaurez wants to merge 1 commit into
devfrom
feat/libposix-pathconf-stubs

Conversation

@esaurez

@esaurez esaurez commented May 28, 2026

Copy link
Copy Markdown
Owner

Summary

Adds minimal extern "C" stubs for pathconf(2) and fpathconf(2) to libposix's src/libs/posix/src/dummy.rs. Both stubs return -1 with errno = ENOSYS, matching the convention already used by every other "not implemented" stub in that module (popen, wordexp, glob, ftw, realpath, ...).

Why this is required

libstdc++'s std::filesystem code emits a strong undefined reference to pathconf from at least two compilation units:

  • libstdc++-v3/src/c++17/cow-fs_ops.ccstd::filesystem::current_path(std::error_code&)
  • libstdc++-v3/src/c++17/fs_ops.ccstd::filesystem::current_path[abi:cxx11](std::error_code&)

std::filesystem::current_path() calls pathconf("/", _PC_PATH_MAX) to size the buffer it passes to getcwd(). Without a definition for pathconf anywhere in the link, any executable that links libstdc++ (CPython, any C++ user binary that touches std::filesystem) fails with undefined reference to 'pathconf'. Concretely, this blocks ./z build of nanvix/cpython:

i686-nanvix-ld: libstdc++.a(cow-fs_ops.o): in function `std::filesystem::current_path(std::error_code&)':
cow-fs_ops.cc:(.text.../current_pathERSt10error_code+0x21): undefined reference to `pathconf'
collect2: error: ld returned 1 exit status
configure: error: C compiler cannot create executables

There is no existing implementation of either function in libposix today (verified by grep -r 'fn pathconf' src/libs/posix).

What changed

Just src/libs/posix/src/dummy.rs: two new public extern "C" functions in the "Standalone Functions" block, with full doc comments (Description / Parameters / Returns / Notes / Safety) and the standard #[trace_libcall] attribute. No other files touched. 79 insertions, 0 deletions.

pub unsafe extern "C" fn pathconf(_path: *const c_char, _name: c_int) -> c_long {
    ::syslog::debug!("pathconf(): not implemented");
    *__errno_location() = ErrorCode::InvalidSysCall.get();
    -1
}

pub unsafe extern "C" fn fpathconf(_fd: c_int, _name: c_int) -> c_long {
    ::syslog::debug!("fpathconf(): not implemented");
    *__errno_location() = ErrorCode::InvalidSysCall.get();
    -1
}

Why this stub is sufficient for libstdc++ today

libstdc++'s std::filesystem code path is defensive: it checks pathconf(...) == -1 and falls back to a compile-time PATH_MAX default (getcwd is called with a fixed 1024-byte buffer that grows on ERANGE). It does not look at errno. So returning -1 with any errno value, including the ENOSYS we use here, is sufficient to unblock the link without any observable behaviour change vs. "the system advertises no path-conf limit".

Future work (not in this PR)

A real implementation should return concrete, selector-aware values for the well-defined _PC_* selectors and only set errno = EINVAL for genuinely unrecognised selectors, following the musl src/conf/pathconf.c pattern:

Selector Suggested value Source
_PC_PATH_MAX 4096 nanvix <limits.h>
_PC_NAME_MAX 255 nanvix <limits.h>
_PC_LINK_MAX 1 (no hard links today) nanvix VFS state
_PC_PIPE_BUF 4096 nanvix pipe buffer size
_PC_NO_TRUNC 1 nanvix never truncates names silently
... (others) -1 with errno unchanged per POSIX "no determinate limit"

For now the stub returns -1 with errno = ENOSYS for every selector. The doc comment calls this out as a known limitation and points to the future-work direction. Users that rely on real values can follow up separately.

Validation

  • In-tree CI checks pass locally: .\z.ps1 build -- format-check rust-lint-check spellcheck is green; pre-commit hook passes.
  • End-to-end: against a Nanvix tree with these stubs applied, CPython 3.12 links against libstdc++ (previously failed on pathconf strong UND), the resulting python.elf runs hello.py, and import numpy; numpy.array(...).sum() via dlopen() of numpy 1.26.4's extension .sos produces NUMPY_TEST_OK on the Nanvix microvm.

Compatibility

  • No public API change beyond adding two new C ABI symbols.
  • No change to behaviour for any existing caller (the symbols were not defined before).
  • The ENOSYS errno mirrors every other "not implemented" stub in dummy.rs for consistency. A future PR can refine to selector-aware values as described above without breaking this contract.

POSIX defines `pathconf(2)` / `fpathconf(2)` as queryable per-path
configurable system limits.  libstdc++'s `std::filesystem` code
(`cow-fs_ops.cc`, `fs_ops.cc`) emits strong undefined references to
`pathconf` -- specifically, `std::filesystem::current_path()` calls it
to size the buffer for `getcwd()`.  When the consumer of libstdc++
(e.g. CPython linking against `libstdc++.a`) tries to link, those
references go unresolved and the link fails with "undefined reference
to `pathconf'".

This patch adds minimal `extern "C"` stubs for both functions to
`src/libs/posix/src/dummy.rs`, matching the convention used by the
other "not implemented" stubs in that module:

  - `pathconf(path, name) -> c_long` always returns `-1` with
    `errno = ENOSYS` (via `ErrorCode::InvalidSysCall`).
  - `fpathconf(fd, name) -> c_long` always returns `-1` with
    `errno = ENOSYS` (via `ErrorCode::InvalidSysCall`).

This is sufficient to satisfy the libstdc++ link.  libstdc++'s
filesystem code checks `pathconf(...) == -1` and falls back to a
compile-time `PATH_MAX` default, so observable behaviour is unchanged
from "limit not advertised".

A future implementation should return real, selector-aware values
(`_PC_PATH_MAX = 4096`, `_PC_NAME_MAX = 255`, `_PC_LINK_MAX = 1`,
etc.) and only set `errno = EINVAL` for genuinely unrecognised
selectors, following the musl `src/conf/pathconf.c` pattern.

Validated end-to-end: CPython 3.12 + numpy 1.26.4 link cleanly against
a libstdc++.a that previously failed on `pathconf`, run `hello.py`,
and produce `NUMPY_TEST_OK` on the Nanvix microvm.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ppenna pushed a commit to nanvix/cpython that referenced this pull request Jun 8, 2026
Updates `Makefile.nanvix` so that `python.elf` correctly serves as the
"main module" against which extension `.so`s (numpy, ssl, lxml, future
pip-installed wheels, ...) resolve their C and C++ runtime symbols at
dlopen() time. This is the consumer-side companion to the Nanvix
loader's STB_WEAK support (esaurez/nanvix#22) and is gated on the new
libposix `pathconf` / `fpathconf` stubs (esaurez/nanvix#23) for the
configure conftest to even produce an executable.

Three coordinated link-flag changes to the `CONFIGURE_ENV` block:

  1. `LIBS` segment 1 -- new `--whole-archive ... --no-whole-archive`
     block ahead of the existing `--start-group`. Forces every object
     from libposix, libc, libm, libstdc++, and libgcc into python.elf
     so the runtime symbols extension `.so`s depend on are embedded
     (and re-exported via `-Wl,--export-dynamic`, already present).
     Without this, the static linker drops unreferenced objects
     (e.g. `fscanf`, `longjmp`, `strtold_l` for numpy; `operator
     new/delete[]`, `__cxa_*`, `_Unwind_*`, `std::type_info` vtables
     for any C++ extension) and subsequent dlopen() of those `.so`s
     fails with "symbol not found".

  2. `LIBS` segment 2 -- the existing `--start-group` is trimmed to
     just the external add-on libraries (sqlite3, ssl, crypto, z, bz2,
     lzma, ffi). It no longer re-lists libposix / libc / libm: those
     archives are already fully included by segment 1, so the external
     libs can resolve their references against the already-embedded
     objects.

  3. Two new top-level Makefile vars `LIBSTDCXX := -lstdc++` and
     `LIBGCC := -lgcc`. The GCC driver resolves them against its built-
     in search paths (libgcc lives under a versioned `lib/gcc/i686-
     nanvix/<gcc-version>/` directory, which would be fragile to
     hardcode). Defined once at top level because the `-l` form is
     identical between the docker and host build paths.

`LDFLAGS` is unchanged. The existing `-Wl,--allow-multiple-definition`
flag is kept and the surrounding comment is expanded to honestly
enumerate the duplicate-symbol categories the flag is masking (newlib
long-double math helpers, libposix/libc env+isatty overlaps, libc/libm
math helper overlaps, libgcc internal `__x86.get_pc_thunk.*`
duplicates, etc.) -- the set is large and toolchain-build-version-
dependent, and is the only practical workaround until the contributing
upstreams are fixed.

`.nanvix/config.py::configure_env()` -- an unused helper that mirrors
`Makefile.nanvix`'s `CONFIGURE_ENV` -- is kept in sync (same
`--whole-archive` LIBS, same LDFLAGS) and gains a docstring calling
out the dead-code status. A separate small cleanup PR can delete the
helper entirely.

Validated end-to-end on the Nanvix microvm: CPython 3.12 + numpy 1.26.4
runs `import numpy`, `np.arange`, `np.dot`, `reshape`, `flatten`,
broadcasting, all producing `NUMPY_TEST_OK`. Hello.py and the existing
single-process / multi-process / standalone modes are unaffected by
the change because the linker flags are not mode-conditional.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ppenna pushed a commit to nanvix/cpython that referenced this pull request Jun 10, 2026
Updates `Makefile.nanvix` so that `python.elf` correctly serves as the
"main module" against which extension `.so`s (numpy, ssl, lxml, future
pip-installed wheels, ...) resolve their C and C++ runtime symbols at
dlopen() time. This is the consumer-side companion to the Nanvix
loader's STB_WEAK support (esaurez/nanvix#22) and is gated on the new
libposix `pathconf` / `fpathconf` stubs (esaurez/nanvix#23) for the
configure conftest to even produce an executable.

Three coordinated link-flag changes to the `CONFIGURE_ENV` block:

  1. `LIBS` segment 1 -- new `--whole-archive ... --no-whole-archive`
     block ahead of the existing `--start-group`. Forces every object
     from libposix, libc, libm, libstdc++, and libgcc into python.elf
     so the runtime symbols extension `.so`s depend on are embedded
     (and re-exported via `-Wl,--export-dynamic`, already present).
     Without this, the static linker drops unreferenced objects
     (e.g. `fscanf`, `longjmp`, `strtold_l` for numpy; `operator
     new/delete[]`, `__cxa_*`, `_Unwind_*`, `std::type_info` vtables
     for any C++ extension) and subsequent dlopen() of those `.so`s
     fails with "symbol not found".

  2. `LIBS` segment 2 -- the existing `--start-group` is trimmed to
     just the external add-on libraries (sqlite3, ssl, crypto, z, bz2,
     lzma, ffi). It no longer re-lists libposix / libc / libm: those
     archives are already fully included by segment 1, so the external
     libs can resolve their references against the already-embedded
     objects.

  3. Two new top-level Makefile vars `LIBSTDCXX := -lstdc++` and
     `LIBGCC := -lgcc`. The GCC driver resolves them against its built-
     in search paths (libgcc lives under a versioned `lib/gcc/i686-
     nanvix/<gcc-version>/` directory, which would be fragile to
     hardcode). Defined once at top level because the `-l` form is
     identical between the docker and host build paths.

`LDFLAGS` is unchanged. The existing `-Wl,--allow-multiple-definition`
flag is kept and the surrounding comment is expanded to honestly
enumerate the duplicate-symbol categories the flag is masking (newlib
long-double math helpers, libposix/libc env+isatty overlaps, libc/libm
math helper overlaps, libgcc internal `__x86.get_pc_thunk.*`
duplicates, etc.) -- the set is large and toolchain-build-version-
dependent, and is the only practical workaround until the contributing
upstreams are fixed.

`.nanvix/config.py::configure_env()` -- an unused helper that mirrors
`Makefile.nanvix`'s `CONFIGURE_ENV` -- is kept in sync (same
`--whole-archive` LIBS, same LDFLAGS) and gains a docstring calling
out the dead-code status. A separate small cleanup PR can delete the
helper entirely.

Validated end-to-end on the Nanvix microvm: CPython 3.12 + numpy 1.26.4
runs `import numpy`, `np.arange`, `np.dot`, `reshape`, `flatten`,
broadcasting, all producing `NUMPY_TEST_OK`. Hello.py and the existing
single-process / multi-process / standalone modes are unaffected by
the change because the linker flags are not mode-conditional.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant