--- a/tele-watch.pl Sat Feb 28 22:04:02 2009 +0100
+++ b/tele-watch.pl Sat Feb 28 23:42:42 2009 +0100
@@ -9,21 +9,28 @@
use Linux::Inotify2;
use Unix::Syslog qw(:macros :subs);
use Cwd qw(abs_path);
+use File::Temp qw(tempfile);
+use POSIX qw(setsid);
my $ME = basename $0;
-my $opt_block;
+my $opt_block = 1;
+my $opt_daemon = 1;
+my $opt_pidfile = "/var/run/$ME.pid";
sub writef($@);
+sub updatef($@);
sub readf($;$);
sub notice($;@);
sub timestamp();
+sub dir($);
openlog($ME, LOG_PID|LOG_PERROR, LOG_DAEMON);
MAIN: {
+ my @_ARGV = @ARGV;
my %TARGET;
- my %COOKIE;
+
END {
foreach (keys %TARGET) {
if (readf("$_/.watched") == $$) {
@@ -32,9 +39,11 @@
syslog(LOG_NOTICE, "cleaned $_/.watched");
}
}
+ unlink $opt_pidfile if $opt_pidfile and readf($opt_pidfile) == $$;
}
$SIG{INT} = sub { syslog(LOG_NOTICE, "got signal @_"); exit 0 };
+ $SIG{TERM} = $SIG{INT};
$SIG{__DIE__} = sub { die @_ if not defined $^S;
syslog(LOG_ERR, "%s", "@_"); exit $? };
$SIG{__WARN__} = sub { warn @_ if not defined $^S;
@@ -45,37 +54,68 @@
"h|help" => sub { pod2usage(-exitval => 0, -verbose => 1) },
"m|man" => sub { pod2usage(-exitval => 0, -verbose => 3) },
"block!" => \$opt_block,
+ "daemon!" => \$opt_daemon,
+ "pidfile=s" => \$opt_pidfile,
) and @ARGV or pod2usage();
foreach (@ARGV) {
my ($w, $t, $r) = split /:/;
die "too many \":\" in \"$_\"\n" if defined $r;
+ pod2usage() if not defined $w or not defined $t;
$w = abs_path($w);
$t = abs_path($t);
$TARGET{$w} = $t;
}
+ writef($opt_pidfile, $$) if $opt_pidfile;
+
# mark the directories as watched
foreach (keys %TARGET) {
- if (-f "$_/.watched") {
- my $pid = readf("$_/.watched");
- if (kill 0 => $pid) {
- die "$_ is watched by (running) process $pid\n";
- }
+ my $watcher = readf("$_/.watched");
+ if (defined $watcher and kill 0 => $watcher) {
+ die "$_ is watched by (running) process $watcher\n";
+ }
+ else {
+ unlink "$_/.watched";
}
system("chattr", "-i" => $_);
- writef(">$_/.watched", $$);
+ notice("watching $_");
+ writef("$_/.watched", $$);
+ }
+
+ $0 = "$ME @_ARGV";
+ chdir("/") or die "Can't chdir to /: $!\n";
+
+ if ($opt_daemon) {
+ open(STDIN, "</dev/null") or die "Can't redir STDIN: $!\n";
+ open(STDOUT, ">/dev/null") or die "Can't redir STDOUT: $!\n";
+ defined(my $pid = fork()) or die "Can't fork: $!\n";
+ if ($pid) {
+ %TARGET = ();
+ notice "child is $pid";
+ $opt_pidfile = "";
+ exit 0;
+ }
+ setsid();
+ open(STDERR, ">&STDOUT") or die "Can't dup stdout: $!\n";
+
+ updatef($opt_pidfile, $$) if $opt_pidfile;
+ foreach (keys %TARGET) {
+ updatef("$_/.watched", $$);
+ }
}
# now start the real watching
my $inotify = new Linux::Inotify2
or die "Can't get inotify object: $!\n";
+
foreach (keys %TARGET) {
$inotify->watch($_, IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM | IN_DELETE)
or die "Can't create watcher for \"$_\": $!\n";
}
+ my %COOKIE;
while () {
my @events = $inotify->read;
die "read error on notify: $!\n" if !@events;
@@ -88,7 +128,7 @@
if ($e->IN_CREATE) {
notice "new dir $fullname";
- foreach my $t (map { basename($_) } grep {-d} glob "$target/*") {
+ foreach my $t (map { basename($_) } grep {-d} dir "$target/") {
my $dir = "$target/$t/$e->{name}";
my $link = "$fullname/$t";
@@ -105,7 +145,7 @@
}
if ($e->IN_MOVED_FROM) {
- notice "$fullname moved from, set cookie";
+ notice "$fullname moves away, set cookie";
$COOKIE{$e->{cookie}} = $e->{name};
next EVENT;
}
@@ -124,7 +164,7 @@
# change the link targets
# find the links pointing to the $target/
- foreach my $link (grep {-l && readlink =~ /^$target\// } glob "$fullname/*") {
+ foreach my $link (grep {-l && readlink =~ /^$target\// } dir "$fullname/") {
my $x = readlink($link);
my ($t) = ($x =~ /^$target\/(.*)\/$from_base$/);
@@ -143,7 +183,7 @@
}
if ($e->IN_DELETE) {
- foreach my $dir (grep {-d} glob "$target/*") {
+ foreach my $dir (grep {-d} dir "$target/") {
-d "$dir/,old"
or mkdir "$dir/,old" => 0755
@@ -162,6 +202,12 @@
}
}
+sub dir($) {
+ my $base = shift;
+ opendir(my $dir, $base) or die "Can't open $base: $!\n";
+ return map { "$base/$_" } grep !/^(?:\.\.?)/, sort readdir $dir;
+}
+
sub timestamp() {
my @now = localtime;
return sprintf "%4d%02d%02d-%02d%02d%02d",
@@ -176,13 +222,25 @@
sub readf($;$) {
my $fn = shift;
my $rs = @_ ? shift : undef;
- open(my $fh, $fn) or die "Can't open $fn: $!\n";
+ open(my $fh, $fn) or return undef;
return <$fh>;
}
sub writef($@) {
my $fn = shift;
- open(my $fh, $fn) or die "Can't open $fn: $!\n";
+ my ($fh, $tmpfn) = tempfile(DIR => dirname($fn), UNLINK => 1)
+ or die "Can't get temp file name in dir "
+ . dirname($fn)
+ . ": $!\n";
+ print {$fh} @_;
+ close $fh;
+ link($tmpfn, $fn) or do die "Can't rename $tmpfn => $fn: $!\n";
+ unlink($tmpfn);
+}
+
+sub updatef($@) {
+ my $fn = shift;
+ open(my $fh, "+>$fn") or die "Can't open +>$fn: $!\n";
print {$fh} @_;
}
@@ -236,7 +294,17 @@
=item B<--[no]block>
If set, on exit the watched directories are blocked by C<chattr +i>.
-(default: off)
+(default: on)
+
+=item B<--[no]daemon>
+
+If set, the scripts daemonizes itself short after start. The pid gets
+written to the F<pidfile> (see pidfile option). (default: on)
+
+=item B<--pidfile>=I<pidfile>
+
+The name of the file holding the pid of the running process. (default:
+/var/run/tele-watch.pid)
=back