tele-watch.pl
changeset 2 3959408aa03d
parent 1 a54c42c041e6
child 3 9e4f1609eaf2
--- 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