From b0ad0448d7544a13783e055ccadcd950d6b847a1 Mon Sep 17 00:00:00 2001 From: Steve Peters Date: Wed, 19 Dec 2007 15:19:41 +0000 Subject: [PATCH] Upgrade to File-Temp-0.19 p4raw-id: //depot/perl@32652 --- MANIFEST | 2 + lib/File/Temp.pm | 230 +++++++++++++++++++++++++++++++++++++-------- lib/File/Temp/t/fork.t | 90 ++++++++++++++++++ lib/File/Temp/t/lock.t | 52 ++++++++++ lib/File/Temp/t/object.t | 19 +++- lib/File/Temp/t/seekable.t | 12 ++- 6 files changed, 361 insertions(+), 44 deletions(-) create mode 100644 lib/File/Temp/t/fork.t create mode 100644 lib/File/Temp/t/lock.t diff --git a/MANIFEST b/MANIFEST index 9d2e2d8..eb69ed6 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1892,6 +1892,8 @@ lib/File/stat.pm By-name interface to Perl's builtin stat lib/File/stat.t See if File::stat works lib/File/Temp.pm create safe temporary files and file handles lib/File/Temp/t/cmp.t See if File::Temp works +lib/File/Temp/t/fork.t See if File::Temp works +lib/File/Temp/t/lock.t See if File::Temp works lib/File/Temp/t/mktemp.t See if File::Temp works lib/File/Temp/t/object.t See if File::Temp works lib/File/Temp/t/posix.t See if File::Temp works diff --git a/lib/File/Temp.pm b/lib/File/Temp.pm index b933963..bd6c5f9 100644 --- a/lib/File/Temp.pm +++ b/lib/File/Temp.pm @@ -52,7 +52,9 @@ The C<_can_do_level> method should be modified accordingly. ($fh, $filename) = tempfile( $template, DIR => $dir); ($fh, $filename) = tempfile( $template, SUFFIX => '.dat'); + ($fh, $filename) = tempfile( $template, TMPDIR => 1 ); + binmode( $fh, ":utf8" ); $dir = tempdir( CLEANUP => 1 ); ($fh, $filename) = tempfile( DIR => $dir ); @@ -63,13 +65,13 @@ Object interface: use File::Temp (); use File::Temp qw/ :seekable /; - $fh = new File::Temp(); + $fh = File::Temp->new(); $fname = $fh->filename; - $fh = new File::Temp(TEMPLATE => $template); + $fh = File::Temp->new(TEMPLATE => $template); $fname = $fh->filename; - $tmp = new File::Temp( UNLINK => 0, SUFFIX => '.dat' ); + $tmp = File::Temp->new( UNLINK => 0, SUFFIX => '.dat' ); print $tmp "Some data\n"; print "Filename is $tmp\n"; $tmp->seek( 0, SEEK_END ); @@ -130,6 +132,8 @@ but should be used with caution since they return only a filename that was valid when function was called, so cannot guarantee that the file will not exist by the time the caller opens the filename. +Filehandles returned by these functions support the seekable methods. + =cut # 5.6.0 gives us S_IWOTH, S_IWGRP, our and auto-vivifying filehandls @@ -149,7 +153,7 @@ require VMS::Stdio if $^O eq 'VMS'; # us that Carp::Heavy won't load rather than an error telling us we # have run out of file handles. We either preload croak() or we # switch the calls to croak from _gettemp() to use die. -require Carp::Heavy; +eval { require Carp::Heavy; }; # Need the Symbol package if we are running older perl require Symbol if $] < 5.006; @@ -199,7 +203,7 @@ Exporter::export_tags('POSIX','mktemp','seekable'); # Version number -$VERSION = '0.18'; +$VERSION = '0.19'; # This is a list of characters that can be used in random filenames @@ -229,9 +233,10 @@ use constant HIGH => 2; # us an optimisation when many temporary files are requested my $OPENFLAGS = O_CREAT | O_EXCL | O_RDWR; +my $LOCKFLAG; unless ($^O eq 'MacOS') { - for my $oflag (qw/ NOFOLLOW BINARY LARGEFILE EXLOCK NOINHERIT /) { + for my $oflag (qw/ NOFOLLOW BINARY LARGEFILE NOINHERIT /) { my ($bit, $func) = (0, "Fcntl::O_" . $oflag); no strict 'refs'; $OPENFLAGS |= $bit if eval { @@ -243,6 +248,12 @@ unless ($^O eq 'MacOS') { 1; }; } + # Special case O_EXLOCK + $LOCKFLAG = eval { + local $SIG{__DIE__} = sub {}; + local $SIG{__WARN__} = sub {}; + &Fcntl::O_EXLOCK(); + }; } # On some systems the O_TEMPORARY flag can be used to tell the OS @@ -256,6 +267,7 @@ my $OPENTEMPFLAGS = $OPENFLAGS; unless ($^O eq 'MacOS') { for my $oflag (qw/ TEMPORARY /) { my ($bit, $func) = (0, "Fcntl::O_" . $oflag); + local($@); no strict 'refs'; $OPENTEMPFLAGS |= $bit if eval { # Make sure that redefined die handlers do not cause problems @@ -268,6 +280,9 @@ unless ($^O eq 'MacOS') { } } +# Private hash tracking which files have been created by each process id via the OO interface +my %FILES_CREATED_BY_OBJECT; + # INTERNAL ROUTINES - not to be used outside of package # Generic routine for getting a temporary filename @@ -292,6 +307,7 @@ unless ($^O eq 'MacOS') { # the file as soon as it is closed. Usually indicates # use of the O_TEMPORARY flag to sysopen. # Usually irrelevant on unix +# "use_exlock" => Indicates that O_EXLOCK should be used. Default is true. # Optionally a reference to a scalar can be passed into the function # On error this will be used to store the reason for the error @@ -328,6 +344,7 @@ sub _gettemp { "mkdir" => 0, "suffixlen" => 0, "unlink_on_close" => 0, + "use_exlock" => 1, "ErrStr" => \$tempErrStr, ); @@ -437,6 +454,10 @@ sub _gettemp { # not a file -- no point returning a name that includes a directory # that does not exist or is not writable + unless (-e $parent) { + ${$options{ErrStr}} = "Parent directory ($parent) does not exist"; + return (); + } unless (-d $parent) { ${$options{ErrStr}} = "Parent directory ($parent) is not a directory"; return (); @@ -493,6 +514,7 @@ sub _gettemp { my $flags = ( ($options{"unlink_on_close"} && !$KEEP_ALL) ? $OPENTEMPFLAGS : $OPENFLAGS ); + $flags |= $LOCKFLAG if (defined $LOCKFLAG && $options{use_exlock}); $open_success = sysopen($fh, $path, $flags, 0600); } if ( $open_success ) { @@ -587,22 +609,6 @@ sub _gettemp { } -# Internal routine to return a random character from the -# character list. Does not do an srand() since rand() -# will do one automatically - -# No arguments. Return value is the random character - -# No longer called since _replace_XX runs a few percent faster if -# I inline the code. This is important if we are creating thousands of -# temporary files. - -sub _randchar { - - $CHARS[ int( rand( $#CHARS ) ) ]; - -} - # Internal routine to replace the XXXX... with random characters # This has to be done by _gettemp() every time it fails to # open a temp file/dir @@ -623,11 +629,12 @@ sub _replace_XX { # and suffixlen=0 returns nothing if used in the substr directly # Alternatively, could simply set $ignore to length($path)-1 # Don't want to always use substr when not required though. + my $end = ( $] >= 5.006 ? "\\z" : "\\Z" ); if ($ignore) { - substr($path, 0, - $ignore) =~ s/X(?=X*\z)/$CHARS[ int( rand( $#CHARS ) ) ]/ge; + substr($path, 0, - $ignore) =~ s/X(?=X*$end)/$CHARS[ int( rand( @CHARS ) ) ]/ge; } else { - $path =~ s/X(?=X*\z)/$CHARS[ int( rand( $#CHARS ) ) ]/ge; + $path =~ s/X(?=X*$end)/$CHARS[ int( rand( @CHARS ) ) ]/ge; } return $path; } @@ -678,7 +685,7 @@ sub _is_safe { # UID is in [4] if ($info[4] > File::Temp->top_system_uid() && $info[4] != $>) { - Carp::cluck(sprintf "uid=$info[4] topuid=%s euid=$< path='$path'", + Carp::cluck(sprintf "uid=$info[4] topuid=%s euid=$> path='$path'", File::Temp->top_system_uid()); $$err_ref = "Directory owned neither by root nor the current user" @@ -733,6 +740,7 @@ sub _is_verysafe { # Should Get the value of _PC_CHOWN_RESTRICTED if it is defined # and If it is not there do the extensive test + local($@); my $chown_restricted; $chown_restricted = &POSIX::_PC_CHOWN_RESTRICTED() if eval { &POSIX::_PC_CHOWN_RESTRICTED(); 1}; @@ -974,7 +982,7 @@ available. Create a temporary file object. - my $tmp = new File::Temp(); + my $tmp = File::Temp->new(); by default the object is constructed as if C was called without options, but with the additional behaviour @@ -982,11 +990,11 @@ that the temporary file is removed by the object destructor if UNLINK is set to true (the default). Supported arguments are the same as for C: UNLINK -(defaulting to true), DIR and SUFFIX. Additionally, the filename +(defaulting to true), DIR, EXLOCK and SUFFIX. Additionally, the filename template is specified using the TEMPLATE option. The OPEN option is not supported (the file is always opened). - $tmp = new File::Temp( TEMPLATE => 'tempXXXXX', + $tmp = File::Temp->new( TEMPLATE => 'tempXXXXX', DIR => 'mydir', SUFFIX => '.dat'); @@ -1024,6 +1032,9 @@ sub new { # Store the filename in the scalar slot ${*$fh} = $path; + # Cache the filename by pid so that the destructor can decide whether to remove it + $FILES_CREATED_BY_OBJECT{$$}{$path} = 1; + # Store unlink information in hash slot (plus other constructor info) %{*$fh} = %args; @@ -1036,9 +1047,48 @@ sub new { return $fh; } +=item B + +Create a temporary directory using an object oriented interface. + + $dir = File::Temp->newdir(); + +By default the directory is deleted when the object goes out of scope. + +Supports the same options as the C function. Note that directories +created with this method default to CLEANUP => 1. + + $dir = File::Temp->newdir( $template, %options ); + +=cut + +sub newdir { + my $self = shift; + + # need to handle args as in tempdir because we have to force CLEANUP + # default without passing CLEANUP to tempdir + my $template = (scalar(@_) % 2 == 1 ? shift(@_) : undef ); + my %options = @_; + my $cleanup = (exists $options{CLEANUP} ? $options{CLEANUP} : 1 ); + + delete $options{CLEANUP}; + + my $tempdir; + if (defined $template) { + $tempdir = tempdir( $template, %options ); + } else { + $tempdir = tempdir( %options ); + } + return bless { DIRNAME => $tempdir, + CLEANUP => $cleanup, + LAUNCHPID => $$, + }, "File::Temp::Dir"; +} + =item B -Return the name of the temporary file associated with this object. +Return the name of the temporary file associated with this object +(if the object was created using the "new" constructor). $filename = $tmp->filename; @@ -1057,6 +1107,15 @@ sub STRINGIFY { return $self->filename; } +=item B + +Return the name of the temporary directory associated with this +object (if the object was created using the "newdir" constructor). + + $dirname = $tmpdir->dirname; + +This method is called automatically when the object is used in string context. + =item B Control whether the file is unlinked when the object goes out of scope. @@ -1085,7 +1144,15 @@ if UNLINK is not specified). No error is given if the unlink fails. -If the global variable $KEEP_ALL is true, the file will not be removed. +If the object has been passed to a child process during a fork, the +file will be deleted when the object goes out of scope in the parent. + +For a temporary directory object the directory will be removed +unless the CLEANUP argument was used in the constructor (and set to +false) or C was modified after creation. + +If the global variable $KEEP_ALL is true, the file or directory +will not be removed. =cut @@ -1094,6 +1161,9 @@ sub DESTROY { if (${*$self}{UNLINK} && !$KEEP_ALL) { print "# ---------> Unlinking $self\n" if $DEBUG; + # only delete if this process created it + return unless exists $FILES_CREATED_BY_OBJECT{$$}{$self->filename}; + # The unlink1 may fail if the file has been closed # by the caller. This leaves us with the decision # of whether to refuse to remove the file or simply @@ -1145,6 +1215,12 @@ But see the WARNING at the end. Translates the template as before except that a directory name is specified. + ($fh, $filename) = tempfile($template, TMPDIR => 1); + +Equivalent to specifying a DIR of "File::Spec->tmpdir", writing the file +into the same temporary directory as would be used if no template was +specified at all. + ($fh, $filename) = tempfile($template, UNLINK => 1); Return the filename and filehandle as before except that the file is @@ -1163,7 +1239,7 @@ automatically generated. This temporary file is placed in tmpdir() (L) unless a directory is specified explicitly with the DIR option. - $fh = tempfile( $template, DIR => $dir ); + $fh = tempfile( DIR => $dir ); If called in scalar context, only the filehandle is returned and the file will automatically be deleted when closed on operating systems @@ -1186,6 +1262,16 @@ if warnings are turned on. Consider using the tmpnam() and mktemp() functions described elsewhere in this document if opening the file is not required. +If the operating system supports it (for example BSD derived systems), the +filehandle will be opened with O_EXLOCK (open with exclusive file lock). +This can sometimes cause problems if the intention is to pass the filename +to another system that expects to take an exclusive lock itself (such as +DBD::SQLite) whilst ensuring that the tempfile is not reused. In this +situation the "EXLOCK" option can be passed to tempfile. By default EXLOCK +will be true (this retains compatibility with earlier releases). + + ($fh, $filename) = tempfile($template, EXLOCK => 0); + Options can be combined as required. Will croak() if there is an error. @@ -1199,11 +1285,13 @@ sub tempfile { # Default options my %options = ( - "DIR" => undef, # Directory prefix + "DIR" => undef, # Directory prefix "SUFFIX" => '', # Template suffix "UNLINK" => 0, # Do not unlink file on exit "OPEN" => 1, # Open file - ); + "TMPDIR" => 0, # Place tempfile in tempdir if template specified + "EXLOCK" => 1, # Open file with O_EXLOCK + ); # Check to see whether we have an odd or even number of arguments my $template = (scalar(@_) % 2 == 1 ? shift(@_) : undef); @@ -1234,10 +1322,15 @@ sub tempfile { # First generate a template if not defined and prefix the directory # If no template must prefix the temp directory if (defined $template) { + # End up with current directory if neither DIR not TMPDIR are set if ($options{"DIR"}) { $template = File::Spec->catfile($options{"DIR"}, $template); + } elsif ($options{TMPDIR}) { + + $template = File::Spec->catfile(File::Spec->tmpdir, $template ); + } } else { @@ -1278,6 +1371,7 @@ sub tempfile { "unlink_on_close" => $unlink_on_close, "suffixlen" => length($options{'SUFFIX'}), "ErrStr" => \$errstr, + "use_exlock" => $options{EXLOCK}, ) ); # Set up an exit handler that can do whatever is right for the @@ -1312,7 +1406,15 @@ sub tempfile { =item B -This is the recommended interface for creation of temporary directories. +This is the recommended interface for creation of temporary +directories. By default the directory will not be removed on exit +(that is, it won't be temporary; this behaviour can not be changed +because of issues with backwards compatibility). To enable removal +either use the CLEANUP option which will trigger removal on program +exit, or consider using the "newdir" method in the object interface which +will allow the directory to be cleaned up when the object goes out of +scope. + The behaviour of the function depends on the arguments: $tempdir = tempdir(); @@ -2045,11 +2147,10 @@ Options are: =item STANDARD -Do the basic security measures to ensure the directory exists and -is writable, that the umask() is fixed before opening of the file, -that temporary files are opened only if they do not already exist, and -that possible race conditions are avoided. Finally the L -function is used to remove files safely. +Do the basic security measures to ensure the directory exists and is +writable, that temporary files are opened only if they do not already +exist, and that possible race conditions are avoided. Finally the +L function is used to remove files safely. =item MEDIUM @@ -2237,9 +2338,12 @@ themselves to give up if they exceed the number of retry attempts. =head2 BINMODE The file returned by File::Temp will have been opened in binary mode -if such a mode is available. If that is not correct, use the binmode() +if such a mode is available. If that is not correct, use the C function to change the mode of the filehandle. +Note that you can modify the encoding of a file opened by File::Temp +also by using C. + =head1 HISTORY Originally began life in May 1999 as an XS interface to the system @@ -2256,10 +2360,14 @@ L, L, L, L See L and L, L for different implementations of temporary file handling. +See L for an alternative object-oriented wrapper for +the C function. + =head1 AUTHOR Tim Jenness Etjenness@cpan.orgE +Copyright (C) 2007 Tim Jenness. Copyright (C) 1999-2007 Tim Jenness and the UK Particle Physics and Astronomy Research Council. All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the same @@ -2272,4 +2380,46 @@ security enhancements. =cut +package File::Temp::Dir; + +use File::Path qw/ rmtree /; +use strict; +use overload '""' => "STRINGIFY", fallback => 1; + +# private class specifically to support tempdir objects +# created by File::Temp->newdir + +# ostensibly the same method interface as File::Temp but without +# inheriting all the IO::Seekable methods and other cruft + +# Read-only - returns the name of the temp directory + +sub dirname { + my $self = shift; + return $self->{DIRNAME}; +} + +sub STRINGIFY { + my $self = shift; + return $self->dirname; +} + +sub unlink_on_destroy { + my $self = shift; + if (@_) { + $self->{CLEANUP} = shift; + } + return $self->{CLEANUP}; +} + +sub DESTROY { + my $self = shift; + if ($self->unlink_on_destroy && + $$ == $self->{LAUNCHPID} && !$File::Temp::KEEP_ALL) { + rmtree($self->{DIRNAME}, $File::Temp::DEBUG, 0) + if -d $self->{DIRNAME}; + } +} + + 1; diff --git a/lib/File/Temp/t/fork.t b/lib/File/Temp/t/fork.t new file mode 100644 index 0000000..2589c22 --- /dev/null +++ b/lib/File/Temp/t/fork.t @@ -0,0 +1,90 @@ +#!/usr/bin/perl +$| = 1; + +# Note that because fork loses test count we do not use Test::More + +use strict; + +BEGIN { print "1..8\n"; } + +use File::Temp; + +# OO interface + +my $file = File::Temp->new(CLEANUP=>1); + +myok( 1, -f $file->filename, "OO File exists" ); + +my $children = 2; +for my $i (1 .. $children) { + my $pid = fork; + die "Can't fork: $!" unless defined $pid; + if ($pid) { + # parent process + next; + } else { + # in a child we can't keep the count properly so we do it manually + # make sure that child 1 dies first + srand(); + my $time = (($i-1) * 5) +int(rand(5)); + print "# child $i sleeping for $time seconds\n"; + sleep($time); + my $count = $i + 1; + myok( $count, -f $file->filename(), "OO file present in child $i" ); + print "# child $i exiting\n"; + exit; + } +} + +while ($children) { + wait; + $children--; +} + + + +myok( 4, -f $file->filename(), "OO File exists in parent" ); + +# non-OO interface + +my ($fh, $filename) = File::Temp::tempfile( CLEANUP => 1 ); + +myok( 5, -f $filename, "non-OO File exists" ); + +$children = 2; +for my $i (1 .. $children) { + my $pid = fork; + die "Can't fork: $!" unless defined $pid; + if ($pid) { + # parent process + next; + } else { + srand(); + my $time = (($i-1) * 5) +int(rand(5)); + print "# child $i sleeping for $time seconds\n"; + sleep($time); + my $count = 5 + $i; + myok( $count, -f $filename, "non-OO File present in child $i" ); + print "# child $i exiting\n"; + exit; + } +} + +while ($children) { + wait; + $children--; +} +myok(8, -f $filename, "non-OO File exists in parent" ); + + +# Local ok sub handles explicit number +sub myok { + my ($count, $test, $msg) = @_; + + if ($test) { + print "ok $count - $msg\n"; + } else { + print "not ok $count - $msg\n"; + } + return $test; +} diff --git a/lib/File/Temp/t/lock.t b/lib/File/Temp/t/lock.t new file mode 100644 index 0000000..fae86f2 --- /dev/null +++ b/lib/File/Temp/t/lock.t @@ -0,0 +1,52 @@ +#!perl -w +# Test O_EXLOCK + +use Test::More; +use strict; +use Fcntl; + +BEGIN {use_ok( "File::Temp" ); } + +# see if we have O_EXLOCK +eval { &Fcntl::O_EXLOCK; }; +if ($@) { + plan skip_all => 'Do not seem to have O_EXLOCK'; +} else { + plan tests => 3; +} + +# Get a tempfile with O_EXLOCK +my $fh = new File::Temp(); +ok( -e "$fh", "temp file is present" ); + +# try to open it with a lock +my $flags = O_CREAT | O_RDWR | O_EXLOCK; + +my $timeout = 5; +my $status; +eval { + local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required + alarm $timeout; + $status = sysopen(my $newfh, "$fh", $flags, 0600); + alarm 0; +}; +if ($@) { + die unless $@ eq "alarm\n"; # propagate unexpected errors + # timed out +} +ok( !$status, "File $fh is locked" ); + +# Now get a tempfile with locking disabled +$fh = new File::Temp( EXLOCK => 0 ); + +eval { + local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required + alarm $timeout; + $status = sysopen(my $newfh, "$fh", $flags, 0600); + alarm 0; +}; +if ($@) { + die unless $@ eq "alarm\n"; # propagate unexpected errors + # timed out +} +ok( $status, "File $fh is not locked"); diff --git a/lib/File/Temp/t/object.t b/lib/File/Temp/t/object.t index c485d63..8cf3785 100644 --- a/lib/File/Temp/t/object.t +++ b/lib/File/Temp/t/object.t @@ -2,7 +2,7 @@ # Test for File::Temp - OO interface use strict; -use Test::More tests => 26; +use Test::More tests => 30; use File::Spec; # Will need to check that all files were unlinked correctly @@ -44,7 +44,22 @@ ok( (-f "$fh"), "File $fh still exists after close" ); # Check again at exit push(@files, "$fh"); -# TEMPDIR test +# OO tempdir +my $tdir = File::Temp->newdir(); +my $dirname = "$tdir"; # Stringify overload +ok( -d $dirname, "Directory $tdir exists"); +undef $tdir; +ok( !-d $dirname, "Directory should now be gone"); + +# Quick basic tempfile test +my $qfh = File::Temp->new(); +my $qfname = "$qfh"; +ok (-f $qfname, "temp file exists"); +undef $qfh; +ok( !-f $qfname, "temp file now gone"); + + +# TEMPDIR test as somewhere to put the temp files # Create temp directory in current dir my $template = 'tmpdirXXXXXX'; print "# Template: $template\n"; diff --git a/lib/File/Temp/t/seekable.t b/lib/File/Temp/t/seekable.t index 8432a1d..69346d0 100644 --- a/lib/File/Temp/t/seekable.t +++ b/lib/File/Temp/t/seekable.t @@ -6,7 +6,7 @@ # change 'tests => 1' to 'tests => last_test_to_print'; -use Test::More tests => 7; +use Test::More tests => 10; BEGIN { use_ok('File::Temp') }; ######################### @@ -21,7 +21,11 @@ isa_ok( $tmp, 'IO::Handle' ); isa_ok( $tmp, 'IO::Seekable' ); # make sure the seek method is available... -ok( File::Temp->can('seek'), 'tmp can seek' ); +# Note that we need a reasonably modern IO::Seekable +SKIP: { + skip "IO::Seekable is too old", 1 if IO::Seekable->VERSION <= 1.06; + ok( File::Temp->can('seek'), 'tmp can seek' ); +} # make sure IO::Handle methods are still there... ok( File::Temp->can('print'), 'tmp can print' ); @@ -30,3 +34,7 @@ ok( File::Temp->can('print'), 'tmp can print' ); $c = scalar @File::Temp::EXPORT; $l = join ' ', @File::Temp::EXPORT; ok( $c == 9, "really exporting $c: $l" ); + +ok(defined eval { SEEK_SET() }, 'SEEK_SET defined by File::Temp') or diag $@; +ok(defined eval { SEEK_END() }, 'SEEK_END defined by File::Temp') or diag $@; +ok(defined eval { SEEK_CUR() }, 'SEEK_CUR defined by File::Temp') or diag $@; -- 2.7.4