3

It seems mv -T is a GNU extension to mv.

Is there a robust way (race-free, portable, and otherwise without "gotchas") to do the equivalent of mv -T dir1 dir2?

To be clear:

  • I DO NOT want this to ever result in dir2/dir1. If dir2 exists, I want the command to fail. If dir1 is moved at all, it must become dir2.

  • I DO NOT want to move out every child one-by-one. I want to move the directory itself.

  • I DO want to avoid race conditions. It's trivial to test if dir2 exists first, but then it might be created after the check but before the move.

user541686
  • 3,033
  • 5
  • 28
  • 43
  • I don't think it's possible to satisfy "If `dir2` exists, I want the command to fail" portably; POSIX *requires* `rename` to delete an empty dir2 and succeed. – Michael Homer Feb 26 '19 at 03:37
  • The spec text: "If the *old* argument points to the pathname of a directory, the *new* argument shall not point to the pathname of a file that is not a directory. If the directory named by the *new* argument exists, it shall be removed and *old* renamed to *new*. [...] If *new* names an existing directory, it shall be required to be an empty directory.". That also applies to `renameat`. I don't believe there can be any portable method to rename a file that does not go through that path. Given that, which of your requirements can you relax? What sorts of race condition are you concerned with? – Michael Homer Feb 26 '19 at 03:41
  • Related: [mv: Move file only if destination does not exist](https://unix.stackexchange.com/questions/248544/mv-move-file-only-if-destination-does-not-exist) – Freddy Feb 26 '19 at 03:51
  • @MichaelHomer: Interesting, thanks. The race condition I'm trying to avoid is `dir1` landing inside `dir2`, which can happen if multiple processes try to run this command at once. However, I guess it would be fine if an empty `dir2` were deleted. Would that help? Also, if there's still no single POSIX solution, I'd be interested in knowing if there are even platform-specific solution that I can call. E.g. is there a solution that works on Mac, let alone other platforms? I haven't found any. – user541686 Feb 26 '19 at 03:54
  • The `renameat2` system call takes a flag argument and one such flag is `RENAME_NOREPLACE` which causes it to: "Don't overwrite newpath of the rename. Return an error if newpath already exists." ... But that doesn't really satisfy "portable", so not sure it solves your problem... – filbranden Feb 26 '19 at 04:47
  • @filbranden: It's Linux-only, right? In which case I already have `mv -T`... – user541686 Feb 26 '19 at 04:58
  • Yes, Linux only, available on kernel 3.15+ and not exposed by glibc... – filbranden Feb 26 '19 at 05:05
  • @filbranden: Wait so `mv -T` is actually prone to a race too? Or does it use another technique? – user541686 Feb 26 '19 at 05:06
  • `mv -T` [uses `renameatu`](https://git.savannah.gnu.org/cgit/coreutils.git/tree/src/copy.c#n1876), which [has a race condition](https://sourceware.org/git/?p=glibc.git;a=blob;f=NEWS;h=0a3b6c7a5a16d4c757d5768b1b4d83de7143bbdd;hb=HEAD#l367) ([by design](https://git.savannah.gnu.org/cgit/gnulib.git/tree/lib/renameatu.c#n64) in order not to fail eagerly), yes. I don't think it's possible not to have one at all, but you can minimise the risk (for some systems at least). If the issue is when "multiple processes try to run this command at once", can't standard locking in "this command" do the job? – Michael Homer Feb 26 '19 at 06:22
  • @MichaelHomer: I see. Lockfiles in... a Bash script? What if the process dies before the lockfile is removed? Nobody is going to be watching over the system 24/7. – user541686 Feb 26 '19 at 06:38
  • Your `dir2` is in effect a lock file: if `mkdir dir2` succeeds, that means you uniquely, atomically created it, and then `rename` can do its job of replacing it. The problem becomes accessing `rename`, which I don't think you can do from Bash, but luckily this isn't a [tag:bash] question. [This question](https://unix.stackexchange.com/q/436305/73093) does list some ways of running library functions from the shell, though. There are still *other* race conditions: someone could make a file inside the directory in between, or rename a parent directory, or change a symlink... – Michael Homer Feb 26 '19 at 07:17
  • Ultimately, returning to my second comment, I think you need to specify very clearly *exactly what* your requirements are: what sorts of race conditions, which programs or processes might be involved, what your implementation languages or constraints are, what you consider a success or a failure, etc. At the moment there are lots of not-quite-there possibilities and no way of winnowing them down. – Michael Homer Feb 26 '19 at 07:19

0 Answers0