views:

154

answers:

3

I would like to know if there is any way a file can be moved only if the destination does not exist - in other words, move only if it does not lead to overwriting.

mv --update

seemed first to be the solution, however, if the timestamp of the source path is newer than the destination, move will overwrite it and all attempts to circumvent this by modifying the timestamp before the move will fail.

I need this behaviour to implement a simple file based lock where existence of a 'lock' file indicates that the lock is acquired.

I use perl for this task, so if perl has this functionality, it would be as helpful. However, I need to ensure that the move operation is atomic.

A: 

mv -n should do what you want.

From the man page:

 -n      Do not overwrite an existing file.  (The -n option overrides any previous -f or -i options.)
Paul R
Note that not all versions of `mv` have this option. Mac OS X (BSD-based) seems to have it but the GNU version on RedHat doesn't.
David Gelhar
Maybe your version of RedHat's coreutils are out of date? I'm not at a GNU/Linux box at the moment, but it's in the documentation. http://www.gnu.org/software/coreutils/manual/html_node/mv-invocation.html
cikkle
@David: thanks for pointing that out - it seems that `-n` is not available everywhere - some Linux (and cygwin) users may need an alternative solution, although nothing obvious springs to mind.
Paul R
@cikkle running RHEL5.4, which has coreutils 5.97. According to git, http://git.savannah.gnu.org/cgit/coreutils.git/commit/?id=d01338eb3d30e5634f1b4d4179c229f54eea0b44 the change adding "-n" was committed in January '09, probably too new for redhat to have moved it into their "stable" release.
David Gelhar
ye - I don't have -n on my prod environment.
mikeY
+6  A: 

But what will you do while someone else has the lock? Quit and try later? Busy-wait?

If you don't need synchronization, then a good bet is sysopen with the O_EXCL and O_CREAT flags set, which will create the file only if it doesn't exist.

use Fcntl qw/ :DEFAULT /;

# ...

sysopen my $fh, $LOCKFILE, O_EXCL|O_CREAT
  or die "$0: sysopen: $!";

But note the following caveat from the Linux open(2) manual page:

O_EXCL is only supported on NFS when using NFSv3 or later on kernel 2.6 or later. In environments where NFS O_EXCL support is not provided, programs that rely on it for performing locking tasks will contain a race condition. Portable programs that want to perform atomic file locking using a lockfile, and need to avoid reliance on NFS support for O_EXCL, can create a unique file on the same file system (e.g., incorporating hostname and PID), and use link(2) to make a link to the lockfile. If link(2) returns 0, the lock is successful. Otherwise, use stat(2) on the unique file to check if its link count has increased to 2, in which case the lock is also successful.

“I’d rather have a network filesystem than NFS,” as the saying goes, so keep your coordinating processes on the same machine if you can.

You might consider using flock as in the code below:

#! /usr/bin/perl

use warnings;
use strict;

use Fcntl qw/ :DEFAULT :flock /;

my $LOCKFILE = "/tmp/mylock";

sub acquire_lock {
  sysopen my $fh, $LOCKFILE, O_RDWR|O_CREAT or die "$0: open: $!";
  flock $fh, LOCK_EX                        or die "$0: flock: $!";
  $fh;
}

sub work {
  for (1 .. 2) {
    my $fh = acquire_lock;
    print "$0: $$ has lock\n";
    sleep rand 3;
    close $fh or warn "$0: [$$] close: $!";
  }
  exit;
}

For a demo, the code below forks five children that take turns acquiring the lock:

my $KIDS = 5;
my %pids;
for (1 .. $KIDS) {
  my $pid = fork;
  die "$0: fork: $!" unless defined $pid;

  $pid ? ++$pids{$pid} : work;
}

while (my $pid = wait) {
  last if $pid == -1;
  warn "$0: unknown child $pid" unless delete $pids{$pid};
}

warn "$0: still alive: " .
     join(", " => sort { $a <=> $b } keys %pids) .
     "\n"
  if keys %pids;

Sample output:

./kidlock: 26644 has lock
./kidlock: 26645 has lock
./kidlock: 26646 has lock
./kidlock: 26645 has lock
./kidlock: 26648 has lock
./kidlock: 26646 has lock
./kidlock: 26647 has lock
./kidlock: 26647 has lock
./kidlock: 26644 has lock
./kidlock: 26648 has lock
Greg Bacon
Thanks -- learned a lot from this one!
FM
Thanks a lot for the detailed response. This would be suitable if the working code releases the lock before quitting. However in my case, the process that acquires the lock, exits and is later run to release it. In between, other processes may need to acquire the lock, in which case they simply fail (no busy-wait). Think of this as a stateless setup like http.
mikeY
@mikeY In that case, I suggest using `sysopen` with `O_EXCL|O_CREAT` because it will fail if the lockfile exists and succeed only when no one else has created it.
Greg Bacon
+1 Solid technique.
pilcrow
A: 

    perldoc -frename

On *NIX systems, rename is atomic, and satisfies your question/requirements as literally posed. As a matter of practice for lockfiles, however, I often use the O_EXCL|O_CREAT approach suggested in @gbacon's answer.

pilcrow