Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 23d89499

Von Moritz Bunkus vor etwa 14 Jahren hinzugefügt

  • ID 23d894990d46c57b3d354b080ca8a373dbeeb756
  • Vorgänger ee71ba33
  • Nachfolger 149d2f33

Perl-Module für Daemons

Unterschiede anzeigen:

modules/fallback/Daemon/Generic.pm
# Copyright (C) 2006, David Muir Sharnoff <perl@dave.sharnoff.org>
package Daemon::Generic;
use strict;
use warnings;
require Exporter;
require POSIX;
use Getopt::Long;
use File::Slurp;
use File::Flock;
our @ISA = qw(Exporter);
our @EXPORT = qw(newdaemon);
our $VERSION = 0.71;
our $force_quit_delay = 15;
our $package = __PACKAGE__;
our $caller;
sub newdaemon
{
my (%args) = @_;
my $pkg = $caller || caller() || 'main';
my $foo = bless {}, $pkg;
unless ($foo->isa($package)) {
no strict qw(refs);
my $isa = \@{"${pkg}::ISA"};
unshift(@$isa, $package);
}
bless $foo, 'This::Package::Does::Not::Exist';
undef $foo;
new($pkg, %args);
}
sub new
{
my ($pkg, %args) = @_;
if ($pkg eq __PACKAGE__) {
$pkg = caller() || 'main';
}
srand(time ^ ($$ << 5))
unless $args{no_srand};
my $av0 = $0;
$av0 =~ s!/!/.!g;
my $self = {
gd_args => \%args,
gd_pidfile => $args{pidfile},
gd_logpriority => $args{logpriority},
gd_progname => $args{progname}
? $args{progname}
: $0,
gd_pidbase => $args{pidbase}
? $args{pidbase}
: ($args{progname}
? "/var/run/$args{progname}"
: "/var/run/$av0"),
gd_foreground => $args{foreground} || 0,
configfile => $args{configfile}
? $args{configfile}
: ($args{progname}
? "/etc/$args{progname}.conf"
: "/etc/$av0"),
debug => $args{debug} || 0,
};
bless $self, $pkg;
$self->gd_getopt;
$self->gd_parse_argv;
my $do = $self->{do} = $ARGV[0];
$self->gd_help if $do eq 'help';
$self->gd_version if $do eq 'version';
$self->gd_install if $do eq 'install';
$self->gd_uninstall if $do eq 'uninstall';
$self->gd_pidfile unless $self->{gd_pidfile};
my %newconfig = $self->gd_preconfig;
$self->{gd_pidfile} = $newconfig{pidfile} if $newconfig{pidfile};
print "Configuration looks okay\n" if $do eq 'check';
my $pidfile = $self->{gd_pidfile};
my $killed = 0;
my $locked = 0;
if (-e $pidfile) {
if ($locked = lock($pidfile, undef, 'nonblocking')) {
# old process is dead
if ($do eq 'status') {
print "$0 dead\n";
exit 1;
}
} else {
sleep(2) if -M $pidfile < 2/86400;
my $oldpid = read_file($pidfile);
chomp($oldpid);
if ($oldpid) {
if ($do eq 'stop' or $do eq 'restart') {
$killed = $self->gd_kill($oldpid);
$locked = lock($pidfile);
if ($do eq 'stop') {
unlink($pidfile);
exit;
}
} elsif ($do eq 'reload') {
if (kill(1,$oldpid)) {
print "Requested reconfiguration\n";
exit;
} else {
print "Kill failed: $!\n";
}
} elsif ($do eq 'status') {
if (kill(0,$oldpid)) {
print "$0 running - pid $oldpid\n";
$self->gd_check($pidfile, $oldpid);
exit 0;
} else {
print "$0 dead\n";
exit 1;
}
} elsif ($do eq 'check') {
if (kill(0,$oldpid)) {
print "$0 running - pid $oldpid\n";
$self->gd_check($pidfile, $oldpid);
exit;
}
} elsif ($do eq 'start') {
print "\u$self->{gd_progname} is already running (pid $oldpid)\n";
exit; # according to LSB, this is no error
}
} else {
$self->gd_error("Pid file $pidfile is invalid but locked, exiting\n");
}
}
} else {
$locked = lock($pidfile, undef, 'nonblocking')
or die "Could not lock pid file $pidfile: $!";
}
if ($do eq 'reload' || $do eq 'stop' || $do eq 'check' || ($do eq 'restart' && ! $killed)) {
print "No $0 running\n";
}
if ($do eq 'stop') {
unlink($pidfile);
exit;
}
if ($do eq 'status') {
print "Unused\n";
exit 3;
}
if ($do eq 'check') {
$self->gd_check($pidfile);
exit
}
unless ($do eq 'reload' || $do eq 'restart' || $do eq 'start') {
$self->gd_other_cmd($do, $locked);
}
unless ($self->{gd_foreground}) {
$self->gd_daemonize;
}
$locked or lock($pidfile, undef, 'nonblocking')
or die "Could not lock PID file $pidfile: $!";
write_file($pidfile, "$$\n");
print STDERR "Starting up...\n";
$self->gd_postconfig(%newconfig);
$self->gd_setup_signals;
$self->gd_run;
unlink($pidfile);
exit(0);
}
sub gd_check {}
sub gd_more_opt { return() }
sub gd_getopt
{
my $self = shift;
Getopt::Long::Configure("auto_version");
GetOptions(
'configfile=s' => \$self->{configfile},
'foreground!' => \$self->{gd_foreground},
'debug!' => \$self->{debug},
$self->{gd_args}{options}
? %{$self->{gd_args}{options}}
: (),
$self->gd_more_opt(),
) or exit($self->gd_usage());
if (@ARGV < ($self->{gd_args}{minimum_args} || 1)) {
exit($self->gd_usage());
}
if (@ARGV > ($self->{gd_args}{maximum_args} || 1)) {
exit($self->gd_usage());
}
}
sub gd_parse_argv { }
sub gd_help
{
my $self = shift;
exit($self->gd_usage($self->{gd_args}));
}
sub gd_version
{
my $self = shift;
no strict qw(refs);
my $v = $self->{gd_args}{version}
|| ${ref($self)."::VERSION"}
|| $::VERSION
|| $main::VERSION
|| "?";
print "$self->{gd_progname} - version $v\n";;
exit;
}
sub gd_pidfile
{
my $self = shift;
my $x = $self->{configfile};
$x =~ s!/!.!g;
$self->{gd_pidfile} = "$self->{gd_pidbase}$x.pid";
}
sub gd_other_cmd
{
my $self = shift;
$self->gd_usage;
exit(1);
}
sub gd_redirect_output
{
my $self = shift;
return if $self->{gd_foreground};
my $logname = $self->gd_logname;
my $p = $self->{gd_logpriority} ? "-p $self->{gd_logpriority}" : "";
open(STDERR, "|logger $p -t '$logname'") or (print "could not open stderr: $!" && exit(1));
close(STDOUT);
open(STDOUT, ">&STDERR") or die "redirect STDOUT -> STDERR: $!";
close(STDIN);
}
sub gd_daemonize
{
my $self = shift;
print "Starting $self->{gd_progname} server\n";
$self->gd_redirect_output();
my $pid;
POSIX::_exit(0) if $pid = fork;
die "Could not fork: $!" unless defined $pid;
POSIX::_exit(0) if $pid = fork;
die "Could not fork: $!" unless defined $pid;
POSIX::setsid();
select(STDERR);
$| = 1;
print "Sucessfully daemonized\n";
}
sub gd_logname
{
my $self = shift;
return $self->{gd_progname}."[$$]";
}
sub gd_reconfig_event
{
my $self = shift;
print STDERR "Reconfiguration requested\n";
$self->gd_postconfig($self->gd_preconfig());
}
sub gd_quit_event
{
my $self = shift;
print STDERR "Quitting...\n";
exit(0);
}
sub gd_setup_signals
{
my $self = shift;
$SIG{INT} = sub { $self->gd_quit_event() };
$SIG{HUP} = sub { $self->gd_reconfig_event() };
}
sub gd_run { die "must defined gd_run()" }
sub gd_error
{
my $self = shift;
my $e = shift;
my $do = $self->{do};
if ($do && $do eq 'stop') {
warn $e;
} else {
die $e;
}
}
sub gd_flags_more { return () }
sub gd_flags
{
my $self = shift;
return (
'-c file' => "Specify configuration file (instead of $self->{configfile})",
'-f' => "Run in the foreground (don't detach)",
$self->gd_flags_more
);
}
sub gd_commands_more { return () }
sub gd_commands
{
my $self = shift;
return (
start => "Starts a new $self->{gd_progname} if there isn't one running already",
stop => "Stops a running $self->{gd_progname}",
reload => "Causes a running $self->{gd_progname} to reload it's config file. Starts a new one if none is running.",
restart => "Stops a running $self->{gd_progname} if one is running. Starts a new one.",
$self->gd_commands_more(),
($self->gd_can_install()
? ('install' => "Setup $self->{gd_progname} to run automatically after reboot")
: ()),
($self->gd_can_uninstall()
? ('uninstall' => "Do not run $self->{gd_progname} after reboots")
: ()),
check => "Check the configuration file and report the daemon state",
help => "Display this usage info",
version => "Display the version of $self->{gd_progname}",
)
}
sub gd_positional_more { return() }
sub gd_alts
{
my $offset = shift;
my @results;
for (my $i = $offset; $i <= $#_; $i += 2) {
push(@results, $_[$i]);
}
return @results;
}
sub gd_usage
{
my $self = shift;
require Text::Wrap;
import Text::Wrap;
my $col = 15;
my @flags = $self->gd_flags;
my @commands = $self->gd_commands;
my @positional = $self->gd_positional_more;
my $summary = "Usage: $self->{gd_progname} ";
my $details = '';
for my $i (gd_alts(0, @flags)) {
$summary .= "[ $i ] ";
}
$summary .= "{ ";
$summary .= join(" | ", gd_alts(0, @commands));
$summary .= " } ";
$summary .= join(" ", gd_alts(0, @positional));
my (@all) = (@flags, @commands, @positional);
while (@all) {
my ($key, $desc) = splice(@all, 0, 2);
local($Text::Wrap::columns) = 79;
$details .= wrap(
sprintf(" %-${col}s ", $key),
" " x ($col + 2),
$desc);
$details .= "\n";
}
print "$summary\n$details";
return 0;
}
sub gd_install_pre {}
sub gd_install_post {}
sub gd_can_install
{
my $self = shift;
require File::Basename;
my $basename = File::Basename::basename($0);
if (
-x "/usr/sbin/update-rc.d"
&&
-x $0
&&
$0 !~ m{^(?:/usr|/var)?/tmp/}
&&
eval { symlink("",""); 1 }
&&
-d "/etc/init.d"
&&
! -e "/etc/init.d/$basename"
) {
return sub {
$self->gd_install_pre("update-rc.d");
require Cwd;
my $abs_path = Cwd::abs_path($0);
symlink($abs_path, "/etc/init.d/$basename")
or die "Install failed: symlink /etc/init.d/$basename -> $abs_path: $!\n";
print "+ /usr/sbin/update-rc.d $basename defaults\n";
system("/usr/sbin/update-rc.d", $basename, "defaults");
my $exit = $? >> 8;
$self->gd_install_post("update-rc.d");
exit($exit) if $exit;
};
}
return 0;
}
sub gd_install
{
my $self = shift;
my $ifunc = $self->gd_can_install();
die "Install command not supported\n" unless $ifunc;
&$ifunc($self);
exit(0);
}
sub gd_uninstall_pre {}
sub gd_uninstall_post {}
sub gd_can_uninstall
{
my $self = shift;
require File::Basename;
my $basename = File::Basename::basename($0);
require Cwd;
my $abs_path = Cwd::abs_path($0) || 'no abs path';
my $link = readlink("/etc/init.d/$basename") || 'no link';
if (
$link eq $abs_path
&&
-x "/usr/sbin/update-rc.d"
) {
return sub {
$self->gd_uninstall_pre("update-rc.d");
unlink("/etc/init.d/$basename");
print "+ /usr/sbin/update-rc.d $basename remove\n";
system("/usr/sbin/update-rc.d", $basename, "remove");
my $exit = $? >> 8;
$self->gd_uninstall_post("update-rc.d");
exit($exit) if $exit;
}
}
return 0;
}
sub gd_uninstall
{
my $self = shift;
my $ufunc = $self->gd_can_uninstall();
die "Cannot uninstall\n" unless $ufunc;
&$ufunc($self);
exit(0);
}
sub gd_kill
{
my ($self, $pid) = @_;
my $talkmore = 0;
my $killed = 0;
if (kill(0, $pid)) {
$killed = 1;
kill(2,$pid);
print "Killing $pid\n";
my $t = time;
sleep(1) if kill(0, $pid);
if ($force_quit_delay && kill(0, $pid)) {
print "Waiting for $pid to die...\n";
$talkmore = 1;
while(kill(0, $pid) && time - $t < $force_quit_delay) {
sleep(1);
}
}
if (kill(15, $pid)) {
print "Killing $pid with -TERM...\n";
if ($force_quit_delay) {
while(kill(0, $pid) && time - $t < $force_quit_delay * 2) {
sleep(1);
}
} else {
sleep(1) if kill(0, $pid);
}
}
if (kill(9, $pid)) {
print "Killing $pid with -KILL...\n";
my $k9 = time;
my $max = $force_quit_delay * 4;
$max = 60 if $max < 60;
while(kill(0, $pid)) {
if (time - $k9 > $max) {
print "Giving up on $pid ever dying.\n";
exit(1);
}
print "Waiting for $pid to die...\n";
sleep(1);
}
}
print "Process $pid is gone\n" if $talkmore;
} else {
print "Process $pid no longer running\n";
}
return $killed;
}
sub gd_preconfig { die "gd_preconfig() must be redefined"; }
sub gd_postconfig { }
1;
modules/fallback/Daemon/Generic/Event.pm
# Copyright (C) 2006, David Muir Sharnoff <muir@idiom.com>
package Daemon::Generic::Event;
use strict;
use warnings;
require Daemon::Generic;
require Event;
require Exporter;
our @ISA = qw(Daemon::Generic Exporter);
our @EXPORT = @Daemon::Generic::EXPORT;
our $VERSION = 0.3;
sub newdaemon
{
local($Daemon::Generic::caller) = caller() || 'main';
local($Daemon::Generic::package) = __PACKAGE__;
Daemon::Generic::newdaemon(@_);
}
sub gd_setup_signals
{
my $self = shift;
my $reload_event = Event->signal(
signal => 'HUP',
desc => 'reload on SIGHUP',
prio => 6,
cb => sub {
$self->gd_reconfig_event;
$self->{gd_timer}->cancel()
if $self->{gd_timer};
$self->gd_setup_timer();
},
);
my $quit_event = Event->signal(
signal => 'INT',
cb => sub { $self->gd_quit_event; },
);
}
sub gd_setup_timer
{
my $self = shift;
if ($self->can('gd_run_body')) {
my $interval = ($self->can('gd_interval') && $self->gd_interval()) || 1;
$self->{gd_timer} = Event->timer(
cb => [ $self, 'gd_run_body' ],
interval => $interval,
hard => 0,
);
}
}
sub gd_run
{
my $self = shift;
$self->gd_setup_timer();
Event::loop();
}
sub gd_quit_event
{
my $self = shift;
print STDERR "Quitting...\n";
Event::unloop_all();
}
1;
=head1 NAME
Daemon::Generic::Event - Generic daemon framework with Event.pm
=head1 SYNOPSIS
use Daemon::Generic::Event;
@ISA = qw(Daemon::Generic::Event);
sub gd_preconfig {
# stuff
}
=head1 DESCRIPTION
Daemon::Generic::Event is a subclass of L<Daemon::Generic> that
predefines some methods:
=over 15
=item gd_run()
Setup a periodic callback to C<gd_run_body()> if there is a C<gd_run_body()>.
Call C<Event::loop()>.
=item gd_setup_signals()
Bind SIGHUP to call C<gd_reconfig_event()>.
Bind SIGINT to call C<gd_quit_event()>.
=back
To use Daemon::Generic::Event, you have to provide a C<gd_preconfig()>
method. It can be empty if you have a C<gd_run_body()>.
Set up your own events in C<gd_preconfig()> and C<gd_postconfig()>.
If you have a C<gd_run_body()> method, it will be called once per
second or every C<gd_interval()> seconds if you have a C<gd_interval()>
method. Unlike in L<Daemon::Generic::While1>, C<gd_run_body()> should
not include a call to C<sleep()>.
=head1 THANK THE AUTHOR
If you need high-speed internet services (T1, T3, OC3 etc), please
send me your request-for-quote. I have access to very good pricing:
you'll save money and get a great service.
=head1 LICENSE
Copyright(C) 2006 David Muir Sharnoff <muir@idiom.com>.
This module may be used and distributed on the same terms
as Perl itself.
modules/fallback/Daemon/Generic/While1.pm
# Copyright (C) 2006, David Muir Sharnoff <muir@idiom.com>
package Daemon::Generic::While1;
use strict;
use warnings;
use Carp;
require Daemon::Generic;
require POSIX;
require Exporter;
our @ISA = qw(Daemon::Generic Exporter);
our @EXPORT = @Daemon::Generic::EXPORT;
our $VERSION = 0.3;
sub newdaemon
{
local($Daemon::Generic::caller) = caller() || 'main';
local($Daemon::Generic::package) = __PACKAGE__;
Daemon::Generic::newdaemon(@_);
}
sub gd_setup_signals
{
my ($self) = @_;
$SIG{HUP} = sub {
$self->{gd_sighup} = time;
};
my $child;
$SIG{INT} = sub {
$self->{gd_sigint} = time;
#
# We'll be getting a SIGTERM in a bit if we're not dead, so let's use it.
#
$SIG{TERM} = sub {
$self->gd_quit_event();
kill(15, $child) if $child; # if we're still alive, let's stay that way
};
};
}
sub gd_sleep
{
my ($self, $period) = @_;
croak "Sleep period must be defined" unless defined $period;
my $hires;
if ($period*1000 != int($period*1000)) {
$hires = 1;
require Time::HiRes;
import Time::HiRes qw(time sleep);
}
my $t = time;
while (time - $t < $period) {
return if $self->{gd_sigint};
return if $self->{gd_sighup};
if ($hires) {
my $p = (time - $t < 1)
? time - $t
: 1;
sleep($p);
} else {
sleep(1);
}
}
}
sub gd_run
{
my ($self) = @_;
while(1) {
if ($self->{gd_sigint}) {
$self->{gd_sigint} = 0;
$self->gd_quit_event();
}
if ($self->{gd_sighup}) {
$self->{gd_sighup} = 0;
$self->gd_reconfig_event();
}
$self->gd_run_body();
}
}
sub gd_reconfig_event
{
my $self = shift;
print STDERR "Reconfiguration requested\n";
$self->gd_postconfig($self->gd_preconfig());
}
sub gd_quit_event
{
print STDERR "Quitting...\n";
exit(0);
}
sub gd_run_body { die "must override gd_run_body()" }
1;
=head1 NAME
Daemon::Generic::While1 - Daemon framework with default while(1) loop
=head1 SYNOPSIS
@ISA = qw(Daemon::Generic::While1);
sub gd_run_body {
# stuff
}
=head1 DESCRIPTION
This is a slight variation on L<Daemon::Generic>: a default
C<gd_run()> provided. It has a while(1) loop that calls
C<gd_run_body()> over and over. It checks for reconifg and
and terminate events and only actions them between calls
to C<gd_run_body()>.
Terminate events will be forced through after
C<$Daemon::Generic::force_quit_delay> seconds if
C<gd_run_body()> doesn't return quickly enough.
=head1 SUBCLASS METHODS REQUIRD
The following method is required to be overridden to subclass
Daemon::Generic::While1:
=over 15
=item gd_run_body()
This method will be called over and over. This method should
include a call to C<sleep(1)> (or a bit more). Reconfig events
will not interrupt it. Quit events will only interrupt it
after 15 seconds.
=back
=head1 ADDITIONAL METHODS
The following additional methods are available for your use
(as compared to L<Daemon::Generic>):
=over 15
=item gd_sleep($period)
This will sleep for C<$period> seconds but in one-second
intervals so that if a SIGINT or SIGHUP arrives the sleep
period can end more quickly.
Using this makes it safe for C<gd_run_body()> to sleep for
longer than C<$Daemon::Generic::force_quit_delay> seconds
at a time.
=back
=head1 ADDITIONAL MEMBER DATA
The following additional bits of member data are defined:
=over 15
=item gd_sigint
The time at which an (unprocessed) SIGINT was recevied.
=item gd_sighup
The time at which an (unprocessed) SIGHUP was recevied.
=back
=head1 THANK THE AUTHOR
If you need high-speed internet services (T1, T3, OC3 etc), please
send me your request-for-quote. I have access to very good pricing:
you'll save money and get a great service.
=head1 LICENSE
Copyright(C) 2006 David Muir Sharnoff <muir@idiom.com>.
This module may be used and distributed on the same terms
as Perl itself.
modules/fallback/File/Flock.pm
# Copyright (C) 1996, 1998 David Muir Sharnoff
package File::Flock;
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(lock unlock lock_rename);
use Carp;
use POSIX qw(EAGAIN EACCES EWOULDBLOCK ENOENT EEXIST O_EXCL O_CREAT O_RDWR);
use Fcntl qw(LOCK_SH LOCK_EX LOCK_NB LOCK_UN);
use IO::File;
use vars qw($VERSION $debug $av0debug);
BEGIN {
$VERSION = 2008.01;
$debug = 0;
$av0debug = 0;
}
use strict;
no strict qw(refs);
my %locks; # did we create the file?
my %lockHandle;
my %shared;
my %pid;
my %rm;
sub new
{
my ($pkg, $file, $shared, $nonblocking) = @_;
&lock($file, $shared, $nonblocking) or return undef;
return bless \$file, $pkg;
}
sub DESTROY
{
my ($this) = @_;
unlock($$this);
}
sub lock
{
my ($file, $shared, $nonblocking) = @_;
my $f = new IO::File;
my $created = 0;
my $previous = exists $locks{$file};
# the file may be springing in and out of existance...
OPEN:
for(;;) {
if (-e $file) {
unless (sysopen($f, $file, O_RDWR)) {
redo OPEN if $! == ENOENT;
croak "open $file: $!";
}
} else {
unless (sysopen($f, $file, O_CREAT|O_EXCL|O_RDWR)) {
redo OPEN if $! == EEXIST;
croak "open >$file: $!";
}
print STDERR " {$$ " if $debug; # }
$created = 1;
}
last;
}
$locks{$file} = $created || $locks{$file} || 0;
$shared{$file} = $shared;
$pid{$file} = $$;
$lockHandle{$file} = $f;
my $flags;
$flags = $shared ? LOCK_SH : LOCK_EX;
$flags |= LOCK_NB
if $nonblocking;
local($0) = "$0 - locking $file" if $av0debug && ! $nonblocking;
my $r = flock($f, $flags);
print STDERR " ($$ " if $debug and $r;
if ($r) {
# let's check to make sure the file wasn't
# removed on us!
my $ifile = (stat($file))[1];
my $ihandle;
eval { $ihandle = (stat($f))[1] };
croak $@ if $@;
return 1 if defined $ifile
and defined $ihandle
and $ifile == $ihandle;
# oh well, try again
flock($f, LOCK_UN);
close($f);
return File::Flock::lock($file);
}
return 1 if $r;
if ($nonblocking and
(($! == EAGAIN)
or ($! == EACCES)
or ($! == EWOULDBLOCK)))
{
if (! $previous) {
delete $locks{$file};
delete $lockHandle{$file};
delete $shared{$file};
delete $pid{$file};
}
if ($created) {
# oops, a bad thing just happened.
# We don't want to block, but we made the file.
&background_remove($f, $file);
}
close($f);
return 0;
}
croak "flock $f $flags: $!";
}
#
# get a lock on a file and remove it if it's empty. This is to
# remove files that were created just so that they could be locked.
#
# To do this without blocking, defer any files that are locked to the
# the END block.
#
sub background_remove
{
my ($f, $file) = @_;
if (flock($f, LOCK_EX|LOCK_NB)) {
unlink($file)
if -s $file == 0;
flock($f, LOCK_UN);
return 1;
} else {
$rm{$file} = 1
unless exists $rm{$file};
return 0;
}
}
sub unlock
{
my ($file) = @_;
if (ref $file eq 'File::Flock') {
bless $file, 'UNIVERSAL'; # avoid destructor later
$file = $$file;
}
croak "no lock on $file" unless exists $locks{$file};
my $created = $locks{$file};
my $unlocked = 0;
my $size = -s $file;
if ($created && defined($size) && $size == 0) {
if ($shared{$file}) {
$unlocked =
&background_remove($lockHandle{$file}, $file);
} else {
# {
print STDERR " $$} " if $debug;
unlink($file)
or croak "unlink $file: $!";
}
}
delete $locks{$file};
delete $pid{$file};
my $f = $lockHandle{$file};
delete $lockHandle{$file};
return 0 unless defined $f;
print STDERR " $$) " if $debug;
$unlocked or flock($f, LOCK_UN)
or croak "flock $file UN: $!";
close($f);
return 1;
}
sub lock_rename
{
my ($oldfile, $newfile) = @_;
if (exists $locks{$newfile}) {
unlock $newfile;
}
delete $locks{$newfile};
delete $shared{$newfile};
delete $pid{$newfile};
delete $lockHandle{$newfile};
delete $rm{$newfile};
$locks{$newfile} = $locks{$oldfile} if exists $locks{$oldfile};
$shared{$newfile} = $shared{$oldfile} if exists $shared{$oldfile};
$pid{$newfile} = $pid{$oldfile} if exists $pid{$oldfile};
$lockHandle{$newfile} = $lockHandle{$oldfile} if exists $lockHandle{$oldfile};
$rm{$newfile} = $rm{$oldfile} if exists $rm{$oldfile};
delete $locks{$oldfile};
delete $shared{$oldfile};
delete $pid{$oldfile};
delete $lockHandle{$oldfile};
delete $rm{$oldfile};
}
#
# Unlock any files that are still locked and remove any files
# that were created just so that they could be locked.
#
END {
my $f;
for $f (keys %locks) {
&unlock($f)
if $pid{$f} == $$;
}
my %bgrm;
for my $file (keys %rm) {
my $f = new IO::File;
if (sysopen($f, $file, O_RDWR)) {
if (flock($f, LOCK_EX|LOCK_NB)) {
unlink($file)
if -s $file == 0;
flock($f, LOCK_UN);
} else {
$bgrm{$file} = 1;
}
close($f);
}
}
if (%bgrm) {
my $ppid = fork;
croak "cannot fork" unless defined $ppid;
my $pppid = $$;
my $b0 = $0;
$0 = "$b0: waiting for child ($ppid) to fork()";
unless ($ppid) {
my $pid = fork;
croak "cannot fork" unless defined $pid;
unless ($pid) {
for my $file (keys %bgrm) {
my $f = new IO::File;
if (sysopen($f, $file, O_RDWR)) {
if (flock($f, LOCK_EX)) {
unlink($file)
if -s $file == 0;
flock($f, LOCK_UN);
}
close($f);
}
}
print STDERR " $pppid] $pppid)" if $debug;
}
kill(9, $$); # exit w/o END or anything else
}
waitpid($ppid, 0);
kill(9, $$); # exit w/o END or anything else
}
}
1;
__DATA__
=head1 NAME
File::Flock - file locking with flock
=head1 SYNOPSIS
use File::Flock;
lock($filename);
lock($filename, 'shared');
lock($filename, undef, 'nonblocking');
lock($filename, 'shared', 'nonblocking');
unlock($filename);
my $lock = new File::Flock '/somefile';
lock_rename($oldfilename, $newfilename)
=head1 DESCRIPTION
Lock files using the flock() call. If the file to be locked does not
exist, then the file is created. If the file was created then it will
be removed when it is unlocked assuming it's still an empty file.
Locks can be created by new'ing a B<File::Flock> object. Such locks
are automatically removed when the object goes out of scope. The
B<unlock()> method may also be used.
B<lock_rename()> is used to tell File::Flock when a file has been
renamed (and thus the internal locking data that is stored based
on the filename should be moved to a new name). B<unlock()> the
new name rather than the original name.
=head1 LICENSE
File::Flock may be used/modified/distibuted on the same terms
as perl itself.
=head1 AUTHOR
David Muir Sharnoff <muir@idiom.org>

Auch abrufbar als: Unified diff