Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I would like to have a variant of the Commit method that fails on EEXIST #9

Open
niels-moller opened this issue Sep 18, 2023 · 5 comments

Comments

@niels-moller
Copy link

The usecase I have is atomic create of a file (with contents): I (i) want the file to appear at the given name only after it has been successfully written and synced to disk, but (ii) if some file with the given name already exists at commit time, I don't want to replace it, I want the commit operation to fail and leave the existing file untouched.

This could be implemented as a method CommitIfNotExists, using the link(2) syscall rather than rename(2), and explicitly removing the temporary file on success.

@dchest
Copy link
Owner

dchest commented Sep 18, 2023

I'm not sure using link is a good idea, but I think we can use renamex_np/renameat2 for both cases instead of rename, with different flags:

Darwin:

     The renamex_np() and renameatx_np() system calls are similar to their counterparts except
     that they take a flags argument.  Values for flags are constructed with below bits set:

           RENAME_SWAP
                   On file systems that support it (see getattrlist(2)
                   VOL_CAP_INT_RENAME_SWAP), it will cause the source and target to be
                   atomically swapped.  Source and target need not be of the same type, i.e. it
                   is possible to swap a file with a directory.  EINVAL is returned in case of
                   bitwise-inclusive OR with RENAME_EXCL.

           RENAME_EXCL
                   On file systems that support it (see getattrlist(2)
                   VOL_CAP_INT_RENAME_EXCL), it will cause EEXIST to be returned if the
                   destination already exists. EINVAL is returned in case of bitwise-inclusive
                   OR with RENAME_SWAP.

Linux:

   renameat2()
       renameat2() has an additional flags argument.  A renameat2() call with a zero flags ar‐
       gument is equivalent to renameat().

       The flags argument is a bit mask consisting of zero or more of the following flags:

       RENAME_EXCHANGE
              Atomically  exchange  oldpath and newpath.  Both pathnames must exist but may be
              of different types (e.g., one could be a non-empty directory  and  the  other  a
              symbolic link).

       RENAME_NOREPLACE
              Don't  overwrite  newpath of the rename.  Return an error if newpath already ex‐
              ists.

Not sure what to do on Windows, though.

Also, this will complicate the package a lot and needs tests on different kernel versions and filesystems.

@niels-moller
Copy link
Author

I suggested old-fashioned link(2) because I think it's portable to any posix system and ought to do the right thing on any filesystem supporting hard links at all. And I'd also expect it to fail in an obvious way if you try it on something like an MSDOS filesystem.

I'm afraid I'm not that familiar with Darwin or Windows, or with the details of Go stdlib glue to relevant syscalls.

I understand that appropriate unit tests will likely be more code than than the feature itself. I'll probably give implementation a try, unless you think it's a great idea and want to add it pretty soon yourself.

@niels-moller
Copy link
Author

Ping? I prepared a pull request some month ago, which seems to work fine for my usecase.

@dchest
Copy link
Owner

dchest commented Mar 4, 2024

Sorry, I'm currently busy with other things, so this package lacks maintenance. Please feel free to use/promote others to use your fork of this project for now.

@niels-moller
Copy link
Author

I see. For reference, this is what I have in the go.mod file in the project that needs this feature:

replace github.com/dchest/safefile => git.glasklar.is/sigsum/dependencies/safefile v1.0.0

If anyone else needs this, please reach out, since this fork is currently aimed only for our own use in the sigsum project.

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

No branches or pull requests

2 participants