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]

Reply via email to