diff --git a/lib/Ubic.pm b/lib/Ubic.pm index 137cec0..804ea84 100644 --- a/lib/Ubic.pm +++ b/lib/Ubic.pm @@ -50,6 +50,7 @@ use Ubic::Multiservice::Dir; use Ubic::AccessGuard; use Ubic::Credentials; use Ubic::Persistent; +use Ubic::AtomicFile; use Ubic::SingletonLock; use Ubic::Settings; @@ -727,12 +728,9 @@ sub forked_call { }; try { - open my $fh, '>', "$tmp_file.tmp" or die "Can't write to '$tmp_file.tmp: $!"; - print {$fh} freeze($result); - close $fh or die "Can't close $tmp_file.tmp: $!"; + Ubic::AtomicFile::store( freeze($result) => $tmp_file ); STDOUT->flush; STDERR->flush; - rename "$tmp_file.tmp", $tmp_file; POSIX::_exit(0); # don't allow to lock to be released - this process was forked from unknown environment, don't want to run unknown destructors } catch { diff --git a/lib/Ubic/Admin/Setup.pm b/lib/Ubic/Admin/Setup.pm index d131669..7d36c2b 100644 --- a/lib/Ubic/Admin/Setup.pm +++ b/lib/Ubic/Admin/Setup.pm @@ -23,6 +23,7 @@ use Getopt::Long 2.33; use Carp; use IPC::Open3; +use Ubic::AtomicFile; use Ubic::Settings; use Ubic::Settings::ConfigFile; @@ -299,9 +300,7 @@ sub setup { print "Installing ubic.$name service...\n"; my $file = "$service_dir/ubic/$name"; - open my $fh, '>', $file or die "Can't write to '$file': $!"; - print {$fh} $content or die "Can't write to '$file': $!"; - close $fh or die "Can't close '$file': $!"; + Ubic::AtomicFile::store($content => $file); }; $add_service->( diff --git a/lib/Ubic/AtomicFile.pm b/lib/Ubic/AtomicFile.pm new file mode 100644 index 0000000..35e6dd0 --- /dev/null +++ b/lib/Ubic/AtomicFile.pm @@ -0,0 +1,40 @@ +package Ubic::AtomicFile; + +use strict; +use warnings; + +# ABSTRACT: atomic file operations + +=head1 SYNOPSIS + + use Ubic::AtomicFile; + + Ubic::AtomicFile::store("blah\n" => "/var/lib/blah"); + +=head1 FUNCTIONS + +=over + +=item B + +Store C<$data> into C<$file> atomically. Temporary C<$file.new> will be created and then renamed to C<$file>. + +=cut +sub store($$) { + my ($data, $file) = @_; + + my $new_file = "$file.new"; + + # here is an interesting link explaining why we need to do it this way: + # https://bugs.launchpad.net/ubuntu/+source/linux/+bug/317781/comments/54 + open my $fh, '>', $new_file or die "Can't open '$new_file' for writing: $!"; + print {$fh} $data or die "Can't print to '$new_file': $!"; + $fh->flush or die "Can't flush '$new_file': $!"; + close $fh or die "Can't close '$new_file': $!"; + rename $new_file => $file or die "Can't rename '$new_file' to '$file': $!"; +} +=back + +=cut + +1; diff --git a/lib/Ubic/Daemon/PidState.pm b/lib/Ubic/Daemon/PidState.pm index 9e8cfef..cf38748 100644 --- a/lib/Ubic/Daemon/PidState.pm +++ b/lib/Ubic/Daemon/PidState.pm @@ -13,6 +13,7 @@ use warnings; use Params::Validate qw(:all); use Ubic::Lockf; +use Ubic::AtomicFile; use overload '""' => sub { my $self = shift; @@ -176,13 +177,13 @@ sub write { my ($pid, $guid) = @$params{qw/ pid guid /}; my $self_pid = $$; - open my $fh, '>', "$dir/pid.new" or die "Can't write '$dir/pid.new': $!"; - print {$fh} "pid $self_pid\n"; - print {$fh} "guid $guid\n"; - print {$fh} "daemon $pid\n"; - $fh->flush; - close $fh or die "Can't close '$dir/pid.new': $!"; - rename "$dir/pid.new" => "$dir/pid" or die "Can't commit pidfile $dir: $!"; + + Ubic::AtomicFile::store( + "pid $self_pid\n". + "guid $guid\n". + "daemon $pid\n" + => "$dir/pid" + ); } =back diff --git a/lib/Ubic/Persistent.pm b/lib/Ubic/Persistent.pm index 933c901..9d0d01c 100644 --- a/lib/Ubic/Persistent.pm +++ b/lib/Ubic/Persistent.pm @@ -22,6 +22,7 @@ use warnings; use JSON; use Ubic::Lockf; +use Ubic::AtomicFile; { # JSON.pm v2 incompatibility with v1 is really, really annoying. @@ -92,11 +93,8 @@ Write data back on disk. sub commit { my $self = shift; my $fname = $meta->{$self}{fname}; - open my $tmp_fh, '>', "$fname.new" or die "Can't write '$fname.new': $!"; - print {$tmp_fh} objToJson({ %$self }); - close $tmp_fh or die "Can't write to '$fname.new': $!"; - rename "$fname.new" => $fname or die "Can't rename '$fname.new' to '$fname': $!"; + Ubic::AtomicFile::store(objToJson({ %$self }) => $fname); } sub DESTROY {