Perl での secure な一時ファイルの取り扱い

Perl で (というか Unix で) ファイルを open した後に unlink しても write, read は出来る。というテスト。

#!/usr/local/bin/perl

use strict;
use warnings;
use IO::File;

my $file = "/var/tmp/test.$$";
print "file = $file\n";

die "$file is symlink\n"     if -l $file;
die "$file already exists\n" if -e _;
umask(066);

# my $result = open my $fh, "+>" . $file;
# my $result = open my $fh, "+>", $file;
my $result = sysopen my $fh, $file, O_RDWR|O_CREAT|O_EXCL;
print "sysopen : $result\n";

$result = unlink $file;
print "unlink  : $result\n";

$result = print $fh "hoge\n";
print "print   : $result\n";

$result = seek $fh, 0, SEEK_SET;
print "seek    : $result\n";

$result = read $fh, my $str, 4096;
print "read    : $result\n";
print "str     : $str";

close $fh;

実行すると

% ./test.pl
file = /var/tmp/test.1545
sysopen : 1
unlink  : 1
print   : 1
seek    : 1
read    : 5
str     : hoge

こんな風になる。
高い Security Level を要求されるプログラム (Unix 上の) だと、一時ファイルは open してからすぐ unlink して他のプロセスからはアクセス出来ないようにするのが基本だよね。とか言われて、そんなことは全く知らなかったのでテストしてみた次第です。
CPAN module で同じようなこと出来ないのかな。と思って調べたら、標準添付の File::Temp がイケル。

#!/usr/local/bin/perl

use strict;
use warnings;
use File::Temp qw /tempfile/;
File::Temp->safe_level( File::Temp::HIGH );
use Fcntl ':seek';

my $fh = tempfile();

my $result = print $fh "hoge\n";
print "print  : $result\n";

$result = seek $fh, 0, SEEK_SET;
print "seek   : $result\n";

$result = read $fh, my $str, 4096;
print "read   : $result\n";
print "str    : $str";

close $fh;

実行。

% ./test.pl
print  : 1
seek   : 1
read   : 5
str    : hoge

ただ File::Temp にはちょっと癖があって、tempfile() を filename を求めない形で呼び出さないといけない。要するに、

# ok
my $fh = tempfile();

# ng
my( $fh, $filename ) = tempfile();

# also ng
my $fh       = new File::Temp;
my $filename = $fh->filename;

な感じ。これがなぜか source を抜粋して解説してみましょうか。

source from File::Temp::tempfile
# Return
if (wantarray()) {

    if ($options{'OPEN'}) {
        return ($fh, $path);
    } else {
        return (undef, $path);
    }

} else {

    # Unlink the file. It is up to unlink0 to decide what to do with
    # this (whether to unlink now or to defer until later)
    unlink0($fh, $path) or croak "Error unlinking file $path using unlink0";

    # Return just the filehandle.
    return $fh;
}

File::Temp::tempfile() は wantarray な形で呼び出すと unlink0 (unlink の OS の違いを吸収してる subroutine) を呼び出さないようになってる。よって、

my( $fh, $filename ) = tempfile();

は NG。
それと OO な表記だけど、new の中の tempfile() の呼び出しは常に

source from File::Temp::new
# Open the file and retain file handle and file name
my ($fh, $path) = tempfile( @template, %args );

なのでこれも NG。
他にも File::Temp::_can_unlink_opened_file を見ると、open 後に unlink 出来る OS がわかるようだ。最近の Unix 系の OS は問題ないな。
書き方によって微妙に動作が違う仕様ってどうなんだろう?特に OO で書くと(別にその後で filename を呼び出さなくても)一時ファイルが残るなんて、微妙な仕様だな...。