Skip to content

Add ability to overwrite regular files and symbolic links in NIO FileSystem#3470

Closed
stepan-ulyanin wants to merge 58 commits intoapple:mainfrom
stepan-ulyanin:su/add-overwriting-file-copy
Closed

Add ability to overwrite regular files and symbolic links in NIO FileSystem#3470
stepan-ulyanin wants to merge 58 commits intoapple:mainfrom
stepan-ulyanin:su/add-overwriting-file-copy

Conversation

@stepan-ulyanin
Copy link
Copy Markdown
Contributor

@stepan-ulyanin stepan-ulyanin commented Dec 30, 2025

Adds a new parameter to the NIOFS FileSystem's copyItem that allows to atomically overwrite regular files and symbolic links.

Note that I opted in to not implement the overwrite for directories in this PR as it seems that there is no atomic way to overwrite directory trees on Posix systems - do we want to do it non-atomically?

Motivation:

Per #3403 it is not uncommon to want to copy an item and not care if the copied item overwrites an existing one.

Modifications:

  1. Adds a new overwriting: Bool parameter to NIOFS FileSystem's copyItem.
  2. Adds new syscall wrappers for TOCTOU-protected atomic operations:
    1. system_renameatx_np / Syscall.rename(from:relativeTo:to:relativeTo:options:) - Darwin directory-relative rename
    2. system_symlinkat / Syscall.symlinkat - create symlink relative to directory FD
    3. system_unlinkat / Syscall.unlinkat - unlink file relative to directory FD
  3. Implements atomic overwrite for both regular files and symbolic links.
    1. For regular files:
      1. On Darwin: a COPYFILE_UNLINK flag is added to copyfile(2)
      2. On Linux: an atomic rename is used - the file is copied to a temporary destination first and then atomically renamed, using renameat2(2) which overwrites the existing file.
    2. For symlinks:
      1. On Darwin: an atomic rename is used - the symlink is copied to a temporary destination first and then atomically renamed, using renameatx_np(2) which overwrites the existing symlink.
      2. On Linux: an atomic rename is used - the symlink is copied to a temporary destination first and then atomically renamed, using renameat2(2) which overwrites the existing symlink.
  4. Adds test cases to the integration test suite.
  5. Updates the _NIOFileSystem FileSystem's copyItem for backward compatibility.

Tests

Syscall tests:

  • test_symlinkat
  • test_unlinkat
  • test_renameatx_np

Integration tests:

  • testCopyFileOverwritingExistingFile
  • testCopyFileOverwritingNonExistingFile
  • testCopyFileOverwritingCleansUpTempFile
  • testCopyFileOverwritingPreservesPermissions
  • testCopySymlinkOverwritingExistingSymlink
  • testCopySymlinkOverwritingNonExistingSymlink
  • testCopySymlinkOverwritingCleansUpTempLink
  • testCopyFileOverwritingExistingSymlink
  • testCopySymlinkOverwritingExistingFile

Result:

Users will be able to copy a regular file or a symbolic link, overwriting an existing regular file or existing symbolic link.

@stepan-ulyanin stepan-ulyanin marked this pull request as ready for review December 30, 2025 06:51
@stepan-ulyanin stepan-ulyanin marked this pull request as draft December 30, 2025 06:59
@stepan-ulyanin stepan-ulyanin marked this pull request as ready for review January 24, 2026 01:46
@stepan-ulyanin stepan-ulyanin requested a review from Lukasa January 24, 2026 01:46
Copy link
Copy Markdown
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

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

Thanks for opening this! This is quite a large set of changes, can I suggest that you split this into (at least) two PRs. Maybe (1) the syscall changes, and (2) the file system changes?

One notable omission from a quick skim is the detailed error descriptions for the syscalls (take look at FileSystemError+Syscall.swift).

It might actually be easier to do this on _NIOFileSystem first and then bring over the changes to NIOFS as a 3rd PR but I'll leave that decision to you.

@stepan-ulyanin
Copy link
Copy Markdown
Contributor Author

Thanks @glbrntt, I think that is a good idea, let open smaller PR in the coming weeks

@stepan-ulyanin
Copy link
Copy Markdown
Contributor Author

Hey @glbrntt, here is the first PR, adding the system calls -> #3505

glbrntt pushed a commit that referenced this pull request Feb 9, 2026
…3505)

Adds three new system call wrappers for the `symlinkat`, `renameatx_np`,
and `unlinkat` system calls.

### Motivation:

Related to #3403 and
#3470. This PR adds syscall
wrappers needed to atomically overwrite existing files or symlinks at
the destination during copy operations. On Linux, atomic overwrites
require a "copy to temp file, then rename" strategy. We use the `*at`
family of syscalls (which operate relative to directory file
descriptors) to avoid TOCTOU race conditions.

### Modifications:

1. Adds three system call wrappers for the `symlinkat`, `renameatx_np`,
and `unlinkat` system calls.
2. Adds related tests
3. Updates the `FileSystemError` for `symlink` and `unlink` to take in
the system call name to allow for the `*at` names to be passed.
@stepan-ulyanin
Copy link
Copy Markdown
Contributor Author

stepan-ulyanin commented Feb 11, 2026

@glbrntt here is the PR for the overwriting: logic in NIOFS -> #3508. CI/CD is failing but it looks like the issue is unrelated to the changes. When we get this one in, I will add the same changes to the _NIOFileSystem for backward compatibility in a separate PR.

glbrntt added a commit that referenced this pull request Feb 18, 2026
…3508)

Adds capability to NIOFS to copy regular files and symlink, allowing to
overwrite the destination.

### Motivation:

Per #3403 and
#3470 we want to add
`replaceExisting: bool` to `FileSystem.copyItem`.

### Modifications:

1. Adds `replaceExisting: bool` parameter to
`FileSystemProtocol.copyItem`.
2. Adds `replaceExisting: bool` parameter to `FileSystem.copyItem` and
implementation for regular files and symbolic links.
3. Adds tests.

---------

Co-authored-by: George Barnett <gbarnett@apple.com>
@stepan-ulyanin stepan-ulyanin marked this pull request as draft February 20, 2026 02:48
@stepan-ulyanin
Copy link
Copy Markdown
Contributor Author

@glbrntt I ported the syscall changes to _NIOFileSystem in #3524.

glbrntt pushed a commit that referenced this pull request Mar 2, 2026
…to the `_NIOFileSystem` module (#3524)

Ports the added system calls from `NIOFS` (from
#3505) to `_NIOFileSystem`:

> Adds three new system call wrappers for the `symlinkat`,
`renameatx_np`, and `unlinkat` system calls.

### Motivation:

`_NIOFileSystem` module is around for backward compatibility - hence, we
are porting the new feature to it.

### Modifications:

Exactly same as in #3505 but
applied to the `_NIOFileSystem`:

> Related to #3403 and
#3470. This PR adds syscall
wrappers needed to atomically overwrite existing files or symlinks at
the destination during copy operations. On Linux, atomic overwrites
require a "copy to temp file, then rename" strategy. We use the `*at`
family of syscalls (which operate relative to directory file
descriptors) to avoid TOCTOU race conditions.
@stepan-ulyanin
Copy link
Copy Markdown
Contributor Author

Ok, we have added all of these changes in separate PRs, closing this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🆕 semver/minor Adds new public API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants