source: trunk/soft/scripts/transfert @ 42

Last change on this file since 42 was 42, checked in by thauvin, 19 years ago
  • update doc
  • Property cvs2svn:cvs-rev set to 1.9
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 10.3 KB
Line 
1#!/usr/bin/perl
2
3# $Id$
4
5=head1 NAME
6
7transfert - A script to transfert data using rsync
8
9=head1 VERSION
10
11CVS: $Revision$
12
13=cut
14
15use strict;
16use warnings;
17use Getopt::Long;
18use Config::IniFiles;
19use Mail::Sendmail;
20use POSIX qw(strftime);
21use Pod::Usage;
22use Digest::MD5;
23
24# 0 .. 6 => debug, info, report, warn, error, die
25my $verbose = 1;
26
27GetOptions(
28    'config|c=s' => \my $configfile,
29    'v' => sub { $verbose-- },
30    'd|debug' => \my $debug,
31    'nomail' => \my $nomail,
32    'mailto' => \my $mailto,
33    'logfile=s' => \my $logfile,
34    'help|h' => sub { pod2usage(-exitval => 0) },
35) or pod2usage();
36
37=head1 SYNOPSIS
38
39transfert -c <configfile> [...]
40
41    config|c <configfile>   use this config file
42    v                       increase verbosity
43    nomail                  do not sent any mail
44    logfile <logfilefmt>    write log into this file
45    help|h                  print this message
46    debug|d                 debug mode
47
48=head1 OPTIONS
49
50=head1 CONFIGURATION FILE
51
52=head2 Description
53
54The configuration is an INI file like. Each section describe
55a path to transfert. The section named 'default' is used to store
56global or default values.
57
58Example:
59
60    [default]
61
62    [musique]
63    source=/source/
64    dest=host:/dest
65    mailto=user@domain
66
67=head2 Globals values
68
69Following paramater should be set into [default] section:
70
71=over
72
73=item * smtp Mail host to use to send mail, default is 'localhost'
74
75=item * mailfrom the mail adress set into mail as from, only the form
76user@domain should appear
77
78=item * mailheader a message to copy at the beggining of each mail
79
80=item * mailfooter a message to copy at the end of each mail
81
82=item * logfile redirect log into this file.
83
84logfile is a strftime format. See L<strftime> man page, or L<POSIX>
85perl module documentation.
86
87=head2 Per transfert parameters
88
89All this paramter can be set into [default] section
90
91=item * source the source file to transfert.
92
93This paramater is passed directly to rsync, so can be a glob.
94
95=item * destination the destination where file should be copied
96
97=item * cmd the command to use to trnsfert files
98
99=item * mailto the list of coma separate mail address which will receive
100transfert log
101
102=item * postrun a command to run for each file, %s is replaced by the path
103of the file
104
105=back
106   
107=cut
108
109$verbose = 0 if ($debug);
110my $maillog; # mail => { level =>, msg => @}
111my $logcallback = undef;
112my $loghandle = \*STDERR;
113
114if (!$configfile) {
115    loging(6, "No config file given");
116    exit(1);
117}
118
119loging(0, "Reading config '%s'", $configfile);
120my $config = new Config::IniFiles(
121            -file => $configfile,
122            -default => 'default',
123            -allowcontinue => 1
124);
125
126if (!($config && -f $configfile && -r _)) {
127    loging(6, "Can't open %s", $configfile);
128    exit(1);
129}
130
131$logfile ||= $config->val('default', 'logfile');
132if ($logfile) {
133    loging(0, "Openning log file %s (%s)", strftime($logfile, gmtime), $logfile);
134    if (!open($loghandle, '> ' . strftime($logfile, gmtime))) {
135        $loghandle = \*STDERR;
136        loging(5, "Can't open log file %s (%s)", strftime($logfile, gmtime), $logfile);
137    }
138}
139
140
141foreach my $entry ($config->Sections) {
142    $entry eq 'default' and next;
143    loging(0, "Starting section '%s'", $entry);
144    my @mail = split(/\s*,\s*/, $config->val($entry, 'mailto'));
145    loging(0, "mail to '%s' in section '%s'", $config->val($entry, 'mailto') || "", $entry);
146    loging(0, "after filter, mail sent to %s in section '%s'",
147        join(", ", map { "'$_'" } @mail), $entry);
148   
149    $logcallback = sub {
150        my ($level, $msg) = @_;
151        foreach (@mail) {
152            push(@{$maillog->{$_}}, "[$level] $msg");
153        }         
154    } unless($nomail);
155   
156    transfert($entry);
157    $logcallback = undef;
158    send_mail() unless($nomail);
159   
160}
161
162loging(0, "End of transfert, exiting");
163close($loghandle);
164exit(0);
165
166#################
167# MAIN LOOP END #
168#################
169
170sub loging {
171    my ($level, $fmt, @val) = @_;
172    my $msg = sprintf($fmt, @val);
173    if($level >= 0 && $level >= $verbose) {
174        if ($logcallback) {
175            $logcallback->($level, $msg);
176        }
177        if (!$logcallback || $debug) {
178            print $loghandle "[$level] $msg\n";
179        }
180    }
181    $msg
182}
183
184=head1 DATA TRANSFERT
185
186=head2 Finding files
187
188The scripts look for L<source> parameter, if it is a directory, the script
189perform a glob using 'source/*', otherwise, it perform a glob using directly
190the source parameter
191
192If the source parameter is not defined, the script log an error and skip the
193section.
194
195The second required parameter is the destination of files, L<dest> parameter
196in config files. It should be accepted by the L<cmd> program that will
197transfert data, the form "user@host:/path" is generally use as the default
198program is rsync.
199
200For each files found according L<source> parameter:
201
202- we look if a .md5sum exists (using file name as prefix) and if it is more
203recent, if yes, we assume file has been already transfered.
204
205- a .md5sum is create, this files contains some comment and the md5sum of
206the data file, this .md5sum can be used by md5sum -c to check data file
207integrity
208
209- both data file and its md5sum file are tranfered, if tranfer failed, the
210md5sum file is deleted
211
212- L<postrun> command is run for both data file and its md5sum file if tranfert
213is succefully completed.
214
215=cut
216
217sub transfert {
218    my ($entry) = @_;
219    my $source = $config->val($entry, 'source') or do {
220        loging(5, "No source defined for %s", $entry);
221        return 0;
222    };
223
224    my $dest = $config->val($entry, 'dest') or do {
225        loging(5, "No destination defined for %s", $entry);
226        return 0;
227    };
228
229    my @files = -d $source ? glob("$source/*") : glob($source);
230    my @datafiles;
231   
232    foreach my $f (@files) {
233        if ($f =~ /.md5sum$/) {
234            loging(0, "%s is md5sum file, skipping", $f);
235            next;
236        }
237       
238        if (! -f $f) {
239            loging(0, "%s is not a regular file, skipping", $f);
240            next;
241        }
242        my ($fsize, $ftime);
243        {
244            my @st = stat($f);
245            $fsize = $st[7];
246            $ftime = $st[9];
247        }
248       
249        # Does the transfert need
250        if (-f "$f.md5sum") {
251            if ((stat("$f.md5sum"))[9] > $ftime) {
252                loging(0, "%s exists and is newer, assume transfert is not need", "$f.md5sum");
253                next;
254            }
255        }
256       
257        # Creating md5sum file
258        if(open(my $md5h, "> $f.md5sum")) {
259            if (open(my $fh, "< $f")) {
260                loging(0, "Creating %s md5 file", "$f.md5sum");
261                binmode($fh);
262                my $md5 = new Digest::MD5;
263                $md5->addfile($fh);
264                printf $md5h 
265                        "# %s\n# %s (%d octets)\n# %s\n%s  %s\n",
266                        q$Id$,
267                        $f, $fsize,
268                        scalar(localtime),
269                        $md5->hexdigest,
270                        ($f =~ m!(?:.*/)?(.*)$!)[0];
271                close($fh);
272            } else {
273                login(5, "Can't open %s for reading", $f);
274            }
275            close($md5h);
276        } else {
277            login(5, "Can't open %s for writting", "$f.md5sum");
278        }
279
280        my $error = 0;
281        foreach my $filetotranfert (grep { -f $_ } ("$f.md5sum", $f)) {
282            my $cmd = sprintf(
283                "%s %s %s",
284                $config->val($entry, 'cmd', 'rsync -e ssh'),
285                $filetotranfert,
286                $dest,
287            );
288            loging(0, "running `%s'", $cmd);
289            if (system($cmd)) {
290                loging(4, "tranfert failed for %s, exit code: %d", $filetotranfert, $? >> 8);
291                unlink("$f.md5sum"); # ensure transfert will be retried
292                $error++;
293            }
294        }
295       
296        if ($error) {
297            loging(5, "Failed to transfert %s", $f);
298            next;
299        } else {
300            loging(1, "%s (%d) transfer ok", $f, $fsize);
301            push(@datafiles, { file => $f, size => $fsize });
302        }
303
304        # No error running action:
305        if (my $postrun = $config->val($entry, 'postrun')) {
306            loging(0, "found postrun: %s for %s", $postrun || '(unset)', $entry);
307            foreach my $filetotranfert (grep { -f $_ } ("$f.md5sum", $f)) {
308                my $fpostrun = sprintf($postrun, $filetotranfert);
309                loging(0, "Running `%s'", $fpostrun);
310                my $postrunres = system($fpostrun);
311                loging(0, "Postrun exit: %d", $postrunres);
312            }
313        }
314    }
315
316    my $totalsize = 0;
317    $totalsize += $_->{size} foreach (@datafiles);
318    loging(2, "%s: %d file transmit, %d octets", $entry, scalar(@datafiles), $totalsize);
319   
320    return 0;
321}
322
323
324sub send_mail {
325    foreach $mailto (keys %${maillog}) {
326        my %mail = (
327            To => $mailto,
328            Subject => "Data transfert report",
329            'X-Mailer' => "Data transfert",
330            'X-OSBDATA-HOST' => "",
331            Smtp => $config->val('default', 'smtp', 'localhost'),
332            From => 'Data transfert bot <' . $config->val(
333                'default', 
334                'mailfrom',
335                'olivier.thauvin@aerov.jussieu.fr') .'>',
336
337        );
338        $mail{message} = "";
339        $mail{message} .= $config->val('default', 'mailheader', '');
340        $mail{message} .= "$_\n" foreach (@{$maillog->{$mailto}});
341        $mail{message} .= $config->val('default', 'mailfooter', '');
342        my $hostname = `hostname`; chomp($hostname);
343        $mail{message} .= sprintf(
344            "\n-- \nMail sent by transfert bot on %s\n%s\n",
345            $hostname,
346            q$Id$,
347        );
348       
349        sendmail(%mail) or loging(5, "Can't sent mail to %s: %s", $mailto, $Mail::Sendmail::error);
350    }
351}
352
353__END__
354
355=head1 AUTHOR
356
357Olivier Thauvin <olivier.thauvin@aerov.jussieu.fr>
358
359=head1 CHANGELOG
360
361    $Log$
362    Revision 1.9  2005/11/22 07:48:49  thauvin
363    - update doc
364
365    Revision 1.8  2005/11/21 19:11:49  thauvin
366    - add signature in mail
367
368    Revision 1.7  2005/11/21 18:44:07  thauvin
369    - put relative path to file for md5sum
370
371    Revision 1.6  2005/11/21 18:04:46  thauvin
372    - rework transfert, create md5sum file
373
374    Revision 1.5  2005/11/18 18:40:27  thauvin
375    - add postrun doc
376
377    Revision 1.4  2005/11/18 18:29:08  thauvin
378    - add postrun function
379
380    Revision 1.3  2005/11/08 18:40:43  thauvin
381    - add documentation
382
383    Revision 1.2  2005/11/08 17:17:29  thauvin
384    - add logfile support
385
Note: See TracBrowser for help on using the repository browser.