Mansour
Mansour

Reputation: 1810

Implementing a lock using simple files

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.

Upvotes: 2

Views: 2595

Answers (4)

dayer
dayer

Reputation: 63

Note if you use the Greg solution could be a race condition between these two instructions if another program try to open it.

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

Upvotes: 0

pilcrow
pilcrow

Reputation: 58741

    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.

Upvotes: 0

Greg Bacon
Greg Bacon

Reputation: 139711

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

Upvotes: 7

Paul R
Paul R

Reputation: 213210

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.)

Upvotes: 0

Related Questions