Treat directory symlinks as files instead of directories
[rersyncrecent.git] / bin / rrr-server
blobbac965b15ee505a41413fc033b5c24583d79969d
1 #!/usr/bin/perl
3 =head1 NAME
5 rrr-server - watch a tree and continuously update indexfiles
7 =head1 SYNOPSIS
9 rrr-server [options] principalfile
11 =head1 OPTIONS
13 =over 8
15 =cut
17 my @opt = <<'=back' =~ /B<--(\S+)>/g;
19 =item B<--help|h>
21 Prints a brief message and exists.
23 =item B<--verbose|v+>
25 More feedback.
27 =back
29 =head1 DESCRIPTION
31 After you have setup a tree watch it with inotify and keep it
32 uptodate. Depends on inotify which probably only exists on linux.
34 =head1 PREREQUISITE
36 Linux::Inotify2.
38 It is not declared as prerequisites of the F:R:M:Recent package
39 because server side handling is optional. XXX Todo: make server side
40 handling a separate package so we can declare Inotify2 as prereq.
42 =cut
44 use strict;
45 use warnings;
47 use File::Find qw(find);
48 use lib "/home/k/sources/rersyncrecent/lib";
49 use File::Rsync::Mirror::Recent;
50 use File::Spec;
51 use Getopt::Long;
52 use Pod::Usage qw(pod2usage);
53 use Time::HiRes qw(time);
55 our %Opt;
56 GetOptions(\%Opt,
57 @opt,
58 ) or pod2usage(1);
60 if ($Opt{help}) {
61 pod2usage(0);
64 if (@ARGV != 1) {
65 pod2usage(1);
68 sub my_inotify {
69 my($inotify, $directory) = @_;
70 unless ($inotify->watch
72 $directory,
73 IN_CLOSE_WRITE()
74 |IN_MOVED_FROM()
75 |IN_MOVED_TO()
76 |IN_CREATE()
77 |IN_DELETE()
78 |IN_DELETE_SELF()
79 |IN_MOVE_SELF()
80 )) {
81 # or die "watch creation failed: $!";
82 if ($!{ENOSPC}) {
83 die "Alert: ENOSPC reached, probably your system needs to increase the amount of inotify watches allowed per user via '/proc/sys/fs/inotify/max_user_watches'\n";
84 } else {
85 for my $err (qw(ENOENT EBADF EEXIST)) {
86 if ($!{$err}) {
87 warn "$err encountered on '$directory'. Giving up on this watch, trying to continue.\n" if $Opt{verbose};
88 return;
91 die "Alert: $!";
96 sub handle_file {
97 my($rf,$fullname,$type,$batch) = @_;
98 push @$batch, {path => $fullname, type => $type};
101 sub newdir {
102 my($inotify,$rf,$fullname,$batch) = @_;
103 return if -l $fullname;
104 my_inotify($inotify, $fullname);
105 # immediately inspect it, we certainly have missed the first
106 # events in this directory
107 opendir my $dh, $fullname or return;
108 for my $dirent (readdir $dh) {
109 next if $dirent eq "." || $dirent eq "..";
110 my $abs = File::Spec->catfile($fullname,$dirent);
111 if (-l $abs || -f _) {
112 warn "[..:..:..] Readdir_F $abs\n" if $Opt{verbose};
113 handle_file($rf,$abs,"new",$batch);
114 } elsif (-d $abs) {
115 warn "[..:..:..] Readdir_D $abs\n" if $Opt{verbose};
116 newdir($inotify,$rf,$abs,$batch);
121 sub handle_event {
122 my($inotify,$rf,$ev,$batch) = @_;
123 my @stringifiedmask;
124 for my $watch (
125 "IN_CREATE", "IN_CLOSE_WRITE", "IN_MOVED_TO", # new
126 "IN_DELETE", "IN_MOVED_FROM", # delete
127 "IN_DELETE_SELF", "IN_MOVE_SELF", # self
129 if ($ev->$watch()){
130 push @stringifiedmask, $watch;
131 # new directories must be added to the watches, deleted
132 # directories deleted; moved directories both
135 my $rootdir = $rf->localroot;
136 # warn sprintf "rootdir[$rootdir]time[%s]ev.w.name[%s]ev.name[%s]ev.fullname[%s]mask[%s]\n", time, $ev->w->name, $ev->name, $ev->fullname, join("|",@stringifiedmask);
137 my $ignore = 0;
138 if ($ev->w->name eq $rootdir) {
139 my $meta = $rf->meta_data;
140 my $ignore_rx = qr((?x: ^ \Q$meta->{filenameroot}\E - [0-9]*[smhdWMQYZ] \Q$meta->{serializer_suffix}\E ));
141 if ($ev->name =~ $ignore_rx) {
142 # warn sprintf "==> Ignoring object in rootdir looking like internal file: %s", $ev->name;
143 $ignore++;
146 unless ($ignore) {
147 my $fullname = $ev->fullname;
148 my($reportname) = $fullname =~ m{^\Q$rootdir\E/(.*)};
149 my $time = sprintf "%02d:%02d:%02d", (localtime)[2,1,0];
150 if (0) {
151 } elsif ($ev->IN_Q_OVERFLOW) {
152 $rf->_requires_fsck(1);
153 } elsif ($ev->IN_DELETE || $ev->IN_MOVED_FROM) {
154 # we don't know whether it was a directory, we simply pass
155 # it to $rf. $rf must be robust enough to swallow bogus
156 # deletes. Alternatively we could look into $rf whether
157 # this object is known, couldn't we?
158 handle_file($rf,$fullname,"delete",$batch);
159 warn "[$time] Deleteobj $reportname (@stringifiedmask)\n" if $Opt{verbose};
160 } elsif ($ev->IN_DELETE_SELF || $ev->IN_MOVE_SELF) {
161 $ev->w->cancel;
162 warn "[$time] Delwatcher $reportname (@stringifiedmask)\n" if $Opt{verbose};
163 } elsif (-l $fullname) {
164 handle_file($rf,$fullname,"new",$batch);
165 warn "[$time] Updatelink $reportname (@stringifiedmask)\n" if $Opt{verbose};
166 } elsif ($ev->IN_ISDIR) {
167 newdir($inotify,$rf,$fullname,$batch);
168 warn "[$time] Newwatcher $reportname (@stringifiedmask)\n" if $Opt{verbose};
169 } elsif (-f _) {
170 if ($ev->IN_CLOSE_WRITE || $ev->IN_MOVED_TO) {
171 handle_file($rf,$fullname,"new",$batch);
172 warn "[$time] Updatefile $reportname (@stringifiedmask)\n" if $Opt{verbose};
174 } else {
175 warn "[$time] Ignore $reportname (@stringifiedmask)\n" if $Opt{verbose};
180 sub init {
181 my($inotify, $rootdir) = @_;
182 foreach my $directory ( File::Find::Rule->new->directory->not( File::Find::Rule->new->symlink )->in($rootdir) ) {
183 my_inotify($inotify, $directory);
188 # Need also to verify that we watch all directories we encounter.
189 sub fsck {
190 my($rf) = @_;
191 0 == system $^X, "-Ilib", "bin/rrr-fsck", $rf->rfile, "--verbose" or die;
194 MAIN: {
195 my($principal) = @ARGV;
196 $principal = File::Spec->rel2abs($principal);
197 my $recc = File::Rsync::Mirror::Recent->new
198 (local => $principal);
199 my($rf) = $recc->principal_recentfile;
200 my $rootdir = $rf->localroot;
201 for my $req (qw(Linux::Inotify2 File::Find::Rule)) {
202 eval qq{ require $req; 1 };
203 if ($@) {
204 die "Failing on 'require $req': $@"
205 } else {
206 $req->import;
210 my $inotify = new Linux::Inotify2
211 or die "Unable to create new inotify object: $!";
213 init($inotify, $rootdir);
214 fsck($rf);
215 my $last_aggregate_call = 0;
217 while () {
218 my @events = $inotify->read;
219 unless ( @events > 0 ) {
220 print "Alert: inotify read error: $!";
221 last;
223 my @batch;
224 foreach my $event (@events) {
225 handle_event($inotify,$rf,$event,\@batch);
227 $rf->batch_update(\@batch) if @batch;
228 if (time > $last_aggregate_call + 60) { # arbitrary
229 $rf->aggregate;
230 $last_aggregate_call = time;
232 if ($rf->_requires_fsck) {
233 fsck($rf);
234 $rf->_requires_fsck(0);
239 __END__
242 # Local Variables:
243 # mode: cperl
244 # coding: utf-8
245 # cperl-indent-level: 4
246 # End: