Skip to content

fs: implement preadv2/pwritev2 and renameat2 RENAME_NOREPLACE - #1429

Open
gburd wants to merge 1 commit into
cloudius-systems:masterfrom
gburd:pr/fs-syscalls
Open

fs: implement preadv2/pwritev2 and renameat2 RENAME_NOREPLACE#1429
gburd wants to merge 1 commit into
cloudius-systems:masterfrom
gburd:pr/fs-syscalls

Conversation

@gburd

@gburd gburd commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What

Three modern file syscalls that were missing or flag-rejecting.

preadv2 / pwritev2 implemented over the existing preadv/pwritev.
Supported RWF_* flags:

  • RWF_HIPRI is a scheduling hint and is ignored (the transfer is unchanged);
  • RWF_DSYNC / RWF_SYNC (pwritev2) fdatasync/fsync the fd after the write;
  • RWF_NOWAIT cannot be honored (OSv's file path can block) so it is rejected
    with EOPNOTSUPP. Faking EAGAIN (no data is actually pending) would make a
    retrying caller spin forever, so an honest "unsupported" is safer;
  • RWF_APPEND (pwritev2) is rejected with EOPNOTSUPP: a correct one-shot
    append must resolve the offset atomically under the file lock at write time,
    which needs a per-write append ioflag plumbed through the fops write path;
    a racy fstat()+pwritev() would let two concurrent appenders clobber each
    other. Callers needing append can open the fd with O_APPEND;
  • any other (unknown) flag returns EOPNOTSUPP.

renameat2 previously returned EINVAL for any flag. Now honors
RENAME_NOREPLACE via a pre-check (EEXIST if the destination exists - a benign
TOCTOU that plain rename also has, covering the common atomic-create-via-rename
pattern). RENAME_EXCHANGE and RENAME_WHITEOUT need filesystem-level support
OSv's VFS does not provide and are rejected with EINVAL; flags == 0 behaves
like renameat.

Wired SYS_preadv2/SYS_pwritev2/SYS_renameat2 (adding the x86-64 numbers
327/328/316; aarch64 already had them) with tracepoints, symbols exported. The
RWF_*/RENAME_* values are defined locally since musl 1.2.1 lacks them.

Testing

tests/tst-fs-syscalls.cc: pwritev2/preadv2 round-trip, RWF_APPEND,
RWF_DSYNC, RWF_NOWAIT (EOPNOTSUPP), unsupported RWF flag (EOPNOTSUPP), and
renameat2 RENAME_NOREPLACE (success when dest absent, EEXIST when present),
flags==0 replace, RENAME_EXCHANGE (EINVAL). Passes on OSv under KVM;
tst-mmap-file (30/30) unaffected.

Note

openat2 (needs struct open_how / RESOLVE_*) is left for a follow-up.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for three newer Linux filesystem syscalls in OSv’s libc/VFS layer (preadv2, pwritev2, renameat2), including syscall wiring, tracepoints, symbol exports, and a dedicated test to validate expected flag/error behavior.

Changes:

  • Implement preadv2()/pwritev2() wrappers over existing preadv()/pwritev() with support for selected RWF_* flags.
  • Implement renameat2() with RENAME_NOREPLACE and explicit rejection of unsupported RENAME_* flags.
  • Wire up syscall numbers/tracepoints/symbol exports and add a new test module (tst-fs-syscalls).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/tst-fs-syscalls.cc New test covering preadv2/pwritev2 and renameat2(RENAME_NOREPLACE) behavior.
fs/vfs/main.cc Implements preadv2/pwritev2 and renameat2 behavior in the VFS/libc layer.
syscalls/syscalls.cc.in Adds syscall stubs for renameat2, preadv2, pwritev2.
syscalls/syscall_tracepoints.cc.in Adds tracepoints for the new syscalls.
include/api/x64/bits/syscall.h Adds x86-64 syscall numbers for renameat2, preadv2, pwritev2.
linux.cc Declares the new syscall entry points for the Linux syscall layer.
exported_symbols/osv_libc.so.6.symbols Exports preadv2, pwritev2, renameat2 from osv_libc.
exported_symbols/osv_ld-musl.so.1.symbols Exports preadv2, pwritev2, renameat2 from the musl loader binary.
modules/tests/Makefile Adds tst-fs-syscalls.so to the tests image build.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread fs/vfs/main.cc Outdated
Comment thread fs/vfs/main.cc
Comment thread fs/vfs/main.cc Outdated
Comment thread tests/tst-fs-syscalls.cc Outdated

@nyh nyh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly good but I (and copilot) had some concerns and suggestions.

Comment thread fs/vfs/main.cc Outdated
Comment thread fs/vfs/main.cc Outdated
Comment thread fs/vfs/main.cc Outdated
Comment thread fs/vfs/main.cc Outdated
Comment thread fs/vfs/main.cc Outdated
Comment thread fs/vfs/main.cc Outdated
Comment thread tests/tst-fs-syscalls.cc Outdated
gburd added a commit to gburd/osv-1 that referenced this pull request Jul 13, 2026
Review feedback on cloudius-systems#1429:
- preadv2: reject write-only RWF flags (DSYNC/SYNC/APPEND) on reads, and reject
  RWF_NOWAIT with EOPNOTSUPP rather than faking EAGAIN. Returning EAGAIN for a
  request we can never satisfy non-blocking would make a retrying caller spin
  forever (nyh); rejecting is the honest answer. Reads now accept only RWF_HIPRI.
- pwritev2: propagate fsync()/fdatasync() failure. A caller that asked for
  RWF_SYNC/RWF_DSYNC must not be told the write is durable when the flush
  failed. Also propagate fstat() failure on RWF_APPEND.
- renameat2: collapse flag validation to a single '& ~RENAME_NOREPLACE' check;
  strengthen the FIXME documenting that RENAME_NOREPLACE and RWF_APPEND are
  best-effort NON-ATOMIC (TOCTOU) implementations that need real VFS-level
  atomic support before they can be relied on for concurrency.
- Move the RWF_*/RENAME_* defines and the three prototypes into a shared header
  include/api/osv/fs_flags.h used by both fs/vfs/main.cc and the test, so they
  cannot drift out of sync (nyh).
@gburd

gburd commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review (pushed):

  • preadv2: reads now accept only RWF_HIPRI; write-only flags (DSYNC/SYNC/APPEND) and RWF_NOWAIT are rejected with EOPNOTSUPP. You're right that faking EAGAIN for NOWAIT would make a retrying caller spin forever - rejecting is the honest answer.
  • pwritev2: now propagates fsync()/fdatasync() (and fstat-for-APPEND) failures instead of silently returning success.
  • renameat2: collapsed to a single flags & ~RENAME_NOREPLACE check, and strengthened the FIXME making explicit that RENAME_NOREPLACE (and pwritev2 RWF_APPEND) are best-effort NON-ATOMIC/TOCTOU implementations for non-racing callers that need real VFS-level atomic support before being relied on for concurrency.
  • shared header: RWF_/RENAME_ defines + the three prototypes now live in include/api/osv/fs_flags.h, included by both the implementation and the test so they can't drift.
    Re: RWF_APPEND via O_APPEND's mechanism - agreed that's the right long-term fix; noted as the FIXME upgrade path.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.

Comment thread fs/vfs/main.cc
Comment thread fs/vfs/main.cc
Comment thread fs/vfs/main.cc
Comment thread tests/tst-fs-syscalls.cc
Comment thread fs/vfs/main.cc
Comment thread include/api/osv/fs_flags.h
Comment thread include/api/osv/fs_flags.h

@nyh nyh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I approve, but there a few small things you might want to look again (like the copyright line, and a few comments from copilot), and please squash the PR into one patch with one coherent commit message without outdated information. Thanks.

Comment thread include/api/osv/fs_flags.h Outdated
Comment thread include/api/osv/fs_flags.h
@gburd

gburd commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the approve. Squashed into one coherent commit (966580c) with an up-to-date message, and addressed the remaining points:

  • RWF_APPEND: rather than ship the racy fstat()+pwritev() (two concurrent one-shot appenders would both write at the same previous EOF), I now reject it with EOPNOTSUPP. You're right that a correct one-shot append needs the offset resolved atomically under the file lock at write time (the IO_APPEND path vfs_file::write already uses for O_APPEND fds); plumbing a per-write append ioflag through the fops interface is out of scope for this PR, and an honest "not supported" beats silent data loss. Callers that need append open with O_APPEND, which OSv supports atomically. Documented the reasoning in the commit message and at the define.
  • RWF_NOWAIT: already rejected with EOPNOTSUPP (not faked EAGAIN), per your earlier comment.
  • pwritev2 RWF_SYNC/DSYNC: propagates fsync/fdatasync failure (returns -1) so a durability request that fails isn't reported as success.
  • preadv2 write-only flags: rejected (reads only accept RWF_HIPRI).
  • renameat2: single flags & ~RENAME_NOREPLACE -> EINVAL check (as you suggested); RENAME_NOREPLACE kept as best-effort with a FIXME noting the non-atomic TOCTOU.
  • RWF_/RENAME_ defines: moved to a shared header include/api/osv/fs_flags.h used by both the implementation and the test.
  • Copyright: the new files are "Copyright (C) 2026 Greg Burd".
  • Test on Linux: tst-fs-syscalls tolerates both OSv's EOPNOTSUPP and Linux's success for RWF_APPEND so it stays meaningful on both.

Rebuilt and the test passes.

@nyh nyh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the changes in one file are a mistake, perhaps some sort of merge accident?
Or maybe I'm missing something?

Comment thread libc/pthread.cc Outdated
@gburd

gburd commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Correction: my previous push of this branch had accidentally carried a spurious revert of the (since-merged) tgkill/rt_sigtimedwait (#1428) and pthread-timedlock (#1446) changes, because the branch base predated those merges. I've rebased onto current master and confirmed the diff now touches only the preadv2/pwritev2/renameat2 files (fs/vfs/main.cc, the shared fs_flags.h header, linux.cc syscall entries, exports, test) with no changes to linux.cc's rt_sigtimedwait, libc/signal.cc, or libc/pthread.cc. Rebuilt, tst-fs-syscalls passes.

@gburd
gburd force-pushed the pr/fs-syscalls branch 3 times, most recently from b397971 to 4053f0b Compare July 21, 2026 10:48
@gburd

gburd commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current master. The earlier concerns are addressed: the one-file change that looked like a merge accident was exactly that (an accidental revert of already-merged work) and has been corrected; RWF_APPEND returns EOPNOTSUPP rather than a racy fstat+pwritev; copyright/style nits fixed. Squashed to a single commit, MERGEABLE + CLEAN. Ready for re-review.

@gburd

gburd commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks -- good catches, and I think the current tip already resolves them (a couple were fixed in a push after your review; the rest below):

  1. pthread.cc "brought back older code from the dead" (line 460) -- you were exactly right, that was a merge accident. It's removed; the current tip has a zero diff against master for libc/pthread.cc.

  2. preadv2 EAGAIN (line 525) -- agreed, faking EAGAIN would make a retrying caller spin forever. I reject RWF_NOWAIT with EOPNOTSUPP instead (the read path can't satisfy true nowait), so a caller gets a clear "not supported" rather than a permanent EAGAIN.

  3. Atomic append (line 552) -- RWF_APPEND is rejected with EOPNOTSUPP and a comment saying callers who need append should open the fd with O_APPEND; I chose not to implement an incorrect (non-atomic) append at all, per your preference for ENOTSUPP over a wrong implementation.

  4. RENAME_EXCHANGE/WHITEOUT redundant checks (line 1204) -- simplified to exactly your suggestion: a single if (flags & ~(unsigned int)RENAME_NOREPLACE) { EINVAL }.

  5. RENAME_NOREPLACE atomicity (line 1212) -- kept as best-effort but now with an explicit FIXME that spells out the TOCTOU window and that a correct implementation needs VFS/filesystem-level atomic support (so it's honest about the guarantee it does not provide).

  6. Flag defines in a header (lines 510/1190/28) -- done: RWF_* and RENAME_* (plus the prototypes) now live in include/api/osv/fs_flags.h, and tests/tst-fs-syscalls.cc #includes that same header instead of redefining them, so the test and the source can't drift.

  7. Copyright line (fs_flags.h:2) -- it's Copyright (C) 2026 Greg Burd now, the standard new-file line.

On the api/osv vs api/uio.h placement: I kept a dedicated api/osv/fs_flags.h rather than extending api/uio.h, since these are OSv-provided defines that musl 1.2.1 lacks; happy to move them into uio.h if you'd rather they live where glibc puts them.

preadv2(2)/pwritev2(2): the RWF_* variants of preadv/pwritev.
- Shared RWF_*/RENAME_* definitions live in a new header
  include/api/osv/fs_flags.h so the implementation and tests agree.
- Reads reject any flag outside RWF_HIPRI (a schedulable hint we ignore).
  RWF_DSYNC/RWF_SYNC/RWF_APPEND are write-only, and RWF_NOWAIT cannot be
  honored on OSv's blocking read path, so it is rejected with EOPNOTSUPP
  rather than faked with EAGAIN (which would make a retrying caller spin).
- Writes honor RWF_DSYNC/RWF_SYNC by flushing after the write and PROPAGATE
  a fsync/fdatasync failure (a caller that asked for durability must not be
  told the write succeeded durably when it did not).
- RWF_APPEND is rejected with EOPNOTSUPP. A correct one-shot append must
  resolve the offset atomically under the file lock at write time (the
  mechanism O_APPEND already uses via IO_APPEND in vfs_file::write); doing it
  with fstat()+pwritev() is racy (two concurrent one-shot appenders both write
  at the same previous EOF and clobber each other), which is worse than an
  honest error. Callers that need append open the fd with O_APPEND, which OSv
  supports atomically.

renameat2(2): support RENAME_NOREPLACE; reject any other flag
(RENAME_EXCHANGE/RENAME_WHITEOUT need filesystem-level support OSv lacks) in a
single 'flags & ~RENAME_NOREPLACE -> EINVAL' check. RENAME_NOREPLACE is a
best-effort fstatat()+renameat() with a documented FIXME that it is not atomic
against a concurrent create (a correct version needs VFS-level support).

Wired into the syscall table, exported from libc/musl, and covered by
tst-fs-syscalls (which tolerates both OSv's EOPNOTSUPP and Linux's success for
RWF_APPEND so it stays meaningful on both).
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.

3 participants