FelipeMdeO opened a new pull request, #19210:
URL: https://github.com/apache/nuttx/pull/19210
## Summary
The NuttX VFS has no hard-link support, yet `link()` was implemented as a
thin
alias of `symlink()`:
```c
#ifdef CONFIG_PSEUDOFS_SOFTLINKS
int link(FAR const char *path1, FAR const char *path2)
{
return symlink(path1, path2);
}
#endif
```
This is wrong in two independent ways, and fixing it correctly is *why the
change is not a one-liner*.
**1. Incorrect POSIX semantics.** `link()` is defined to create a *hard*
link.
Silently creating a *symbolic* link instead is surprising and breaks callers
that rely on the distinction. In particular, the common portability pattern
of
probing for hard-link support — call `link()` and check for failure — was
told
the operation *succeeded*, having quietly created a symlink it never asked
for.
The correct answer for a filesystem with no hard links is `-1` / `ENOSYS`.
**2. The hard-link API was gated behind an unrelated feature.** Returning
`ENOSYS` from the function body alone fixes nothing, because the whole of
`fs_link.c` was compiled only under `CONFIG_PSEUDOFS_SOFTLINKS`. When soft
links
are disabled, `link()` did not exist at all — so editing its body changes
nothing for those configurations; `link()` simply remains an undefined
symbol.
Hard links and soft links are independent concepts, so gating `link()` (hard
link) on `CONFIG_PSEUDOFS_SOFTLINKS` (soft links) is a category error. Making
`link()` report `ENOSYS` *regardless of soft-link configuration* therefore
requires decoupling it from that option.
That decoupling is a single logical change, but NuttX expresses the same
build
condition in four parallel places, so the same `CONFIG_PSEUDOFS_SOFTLINKS`
guard had to be removed from each:
| File | Role |
| --- | --- |
| `fs/vfs/fs_link.c` | the `#ifdef` wrapping the function itself |
| `fs/vfs/Make.defs` | Make build |
| `fs/vfs/CMakeLists.txt` | CMake build |
| `syscall/syscall.csv` | syscall stub generation |
These are four copies of one condition, not four independent changes; leaving
the guard in any of them would make the tree inconsistent (e.g. compiled but
absent from the syscall table, or present in CMake but not in Make).
`fs_symlink.c` and `fs_readlink.c` remain under `CONFIG_PSEUDOFS_SOFTLINKS`,
as
they should — soft-link support is untouched (see Impact).
**This was already a latent inconsistency, not just a cosmetic issue.**
`libs/libc/unistd/lib_linkat.c` is built *unconditionally* and is
implemented on
top of `link()`:
```c
ret = link(oldfullpath, newfullpath); /* lib_linkat.c */
```
So the tree already assumed `link()` is always available — directly
contradicting the `CONFIG_PSEUDOFS_SOFTLINKS` guard. Without soft links,
`linkat()` referenced a `link()` that was never built. After this change
`fs_link.c` is always compiled, `link()` always returns `-1`/`ENOSYS`, and
`linkat()` has a consistent, always-present backend.
```c
int link(FAR const char *path1, FAR const char *path2)
{
(void)path1;
(void)path2;
set_errno(ENOSYS);
return ERROR;
}
```
## Impact
**Soft-link support (`CONFIG_PSEUDOFS_SOFTLINKS`) is fully preserved.** This
change does not touch soft links: `symlink()`/`readlink()` remain gated by
the
option and behave exactly as before. The two features are orthogonal —
`CONFIG_PSEUDOFS_SOFTLINKS` provides *symbolic* links via `symlink()`, while
`link()` is the *hard*-link API. Enabling soft links should not, and now does
not, change the behavior of the hard-link API. The only difference for a
`CONFIG_PSEUDOFS_SOFTLINKS=y` build is that `link()` no longer silently
creates
a symlink:
| Call | `SOFTLINKS=y`, before | `SOFTLINKS=y`, after |
| --- | --- | --- |
| `symlink()` | creates a symlink | creates a symlink (unchanged) |
| `readlink()` | reads a symlink | reads a symlink (unchanged) |
| `link()` | silently created a *symlink* | `-1` / `ENOSYS` |
A caller that wants a symbolic link must use `symlink()` (the correct API,
already available when the option is enabled); a caller of `link()` wants a
*hard* link, which NuttX supports in no configuration, so `ENOSYS` is the
correct result with or without soft links. No in-tree caller relied on the
old
aliasing.
Other effects:
- **Configurations without soft links:** `link()` now exists and returns
`ENOSYS` instead of being an undefined reference, and `linkat()` behaves
consistently in every configuration.
- **Build:** `fs_link.c` is now always compiled — a tiny `ENOSYS` stub, no
new
dependencies and no Kconfig changes. No size impact beyond a few bytes.
- **API / docs:** `link()` is still declared in `<unistd.h>`; only its
runtime
behavior changes, and it is now standards-compliant for a filesystem
without
hard-link support.
- **Rejected alternative:** simply enabling `CONFIG_PSEUDOFS_SOFTLINKS` in a
board defconfig would pull in the entire symlink/readlink machinery just to
make a hard-link call report "unsupported", keeps the incorrect coupling,
and
hides a VFS-wide issue behind a per-board workaround instead of fixing it
in
the place the behavior actually lives.
## Testing
Host: <Ubuntu 24.04 x86_64>
Board: esp32c3-devkit, configuration without `CONFIG_PSEUDOFS_SOFTLINKS`
Built and exercised on the esp32c3-devkit: `link()` returns `-1` with
`errno == ENOSYS`, and `linkat()` resolves consistently in a build with soft
links disabled.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]