Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Atomic open of non-existing file in Perl

I want to write something to a file which name is in variable $filename. I don't want to overwrite it, so I check first if it exists and then open it:

#stage1
if(-e $filename)
{
    print "file $filename exists, not overwriting\n";
    exit 1;
}

#stage2
open(OUTFILE, ">", $filename) or die $!;

But this is not atomic. Theoretically someone can create this file between stage1 and stage2. Is there some variant of open command that will do these both things in atomic way, so it will fail to open a file for writing if the file exists?

like image 326
nuoritoveri Avatar asked Dec 19 '12 13:12

nuoritoveri


2 Answers

Here is an atomic way of opening files:

#!/usr/bin/env perl
use strict;
use warnings qw(all);

use Fcntl qw(:DEFAULT :flock);

my $filename = 'test';
my $fh;

# this is "atomic open" part
unless (sysopen($fh, $filename, O_CREAT | O_EXCL | O_WRONLY)) {
    print "file $filename exists, not overwriting\n";
    exit 1;
}

# flock() isn't required for "atomic open" per se
# but useful in real world usage like log appending
flock($fh, LOCK_EX);

# use the handle as you wish
print $fh scalar localtime;
print $fh "\n";

# unlock & close
flock($fh, LOCK_UN);
close $fh;

Debug session:

stas@Stanislaws-MacBook-Pro:~/stackoverflow$ cat test
Wed Dec 19 12:10:37 2012
stas@Stanislaws-MacBook-Pro:~/stackoverflow$ perl sysopen.pl 
file test exists, not overwriting
stas@Stanislaws-MacBook-Pro:~/stackoverflow$ cat test
Wed Dec 19 12:10:37 2012
like image 71
creaktive Avatar answered Oct 02 '22 01:10

creaktive


If you're concerned about multiple Perl scripts modifying the same file, just use the flock() function in each one to lock the file you're interested in.

If you're worried about external processes, which you probably don't have control over, you can use the sysopen() function. According to the Programming Perl book (which I highly recommend, by the way):

To fix this problem of overwriting, you’ll need to use sysopen, which provides individual controls over whether to create a new file or clobber an existing one. And we’ll ditch that –e file existence test since it serves no useful purpose here and only increases our exposure to race conditions.

They also provide this sample block of code:

use Fcntl qw/O_WRONLY O_CREAT O_EXCL/;
open(FH, "<", $file)
    || sysopen(FH, $file, O_WRONLY | O_CREAT | O_EXCL)
    || die "can't create new file $file: $!";

In this example, they first pull in a few constants (to be used in the sysopen call). Next, they try to open the file with open, and if that fails, they then try sysopen. They continue on to say:

Now even if the file somehow springs into existence between when open fails and when sysopen tries to open a new file for writing, no harm is done, because with the flags provided, sysopen will refuse to open a file that already exists.

So, to make things clear for your situation, remove the file test completely (no more stage 1), and only do the open operation using code similar to the block above. Problem solved!

like image 43
Jonah Bishop Avatar answered Oct 02 '22 01:10

Jonah Bishop