3 # debianqueued -- daemon for managing Debian upload queues
5 # Copyright (C) 1997 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
6 # Copyright (C) 2001-2007 Ryan Murray <rmurray@debian.org>
7 # Copyright (C) 2008 Thomas Viehmann <tv@beamnet.de>
9 # This program is free software. You can redistribute it and/or
10 # modify it under the terms of the GNU General Public License as
11 # published by the Free Software Foundation: either version 2 or
12 # (at your option) any later version.
13 # This program comes with ABSOLUTELY NO WARRANTY!
19 use POSIX qw( strftime sys_stat_h sys_wait_h signal_h );
22 use Socket qw( PF_INET AF_INET SOCK_STREAM );
25 # ---------------------------------------------------------------------------
27 # ---------------------------------------------------------------------------
30 ( $conf::queued_dir = ( ( $0 !~ m,^/, ) ? POSIX::getcwd() . "/" : "" ) . $0 )
32 require "$conf::queued_dir/config";
33 my $junk = $conf::debug; # avoid spurious warnings about unused vars
34 $junk = $conf::ssh_key_file;
35 $junk = $conf::stray_remove_timeout;
36 $junk = $conf::problem_report_timeout;
37 $junk = $conf::queue_delay;
38 $junk = $conf::keep_files;
39 $junk = $conf::valid_files;
40 $junk = $conf::max_upload_retries;
41 $junk = $conf::upload_delay_1;
42 $junk = $conf::upload_delay_2;
46 $junk = $conf::check_md5sum;
50 $junk = $conf::ftpdebug;
51 $junk = $conf::ftptimeout;
52 $junk = $conf::no_changes_timeout;
53 $junk = @conf::nonus_packages;
54 $junk = @conf::test_binaries;
55 $junk = @conf::maintainer_mail;
56 $junk = @conf::targetdir_delayed;
57 $junk = $conf::mail ||= '/usr/sbin/sendmail';
58 $conf::target = "localhost" if $conf::upload_method eq "copy";
62 ( $main::progname = $0 ) =~ s,.*/,,;
66 # extract -r and -k args
68 if ( @ARGV == 1 && $ARGV[0] =~ /^-[rk]$/ ) {
69 $main::arg = ( $ARGV[0] eq '-k' ) ? "kill" : "restart";
73 # test for another instance of the queued already running
74 my ( $pid, $delayed_dirs, $adelayedcore );
75 if ( open( PIDFILE, "<$conf::pidfile" ) ) {
76 chomp( $pid = <PIDFILE> );
80 # remove stale pid file
81 unlink($conf::pidfile);
82 } elsif ($main::arg) {
84 print "Killing running daemon (pid $pid) ...";
87 while ( kill( 0, $pid ) && $cnt-- > 0 ) {
91 if ( kill( 0, $pid ) ) {
92 print " failed!\nProcess $pid still running.\n";
96 if ( -e "$conf::incoming/core" ) {
97 unlink("$conf::incoming/core");
98 print "(Removed core file)\n";
100 for ( $delayed_dirs = 0 ;
101 $delayed_dirs <= $conf::max_delayed ;
105 sprintf( "$conf::incoming_delayed/core", $delayed_dirs );
106 if ( -e $adelayedcore ) {
107 unlink($adelayedcore);
108 print "(Removed core file)\n";
110 } ## end for ( $delayed_dirs = 0...
111 exit 0 if $main::arg eq "kill";
113 die "Another $main::progname is already running (pid $pid)\n"
114 if $pid && kill( 0, $pid );
116 } elsif ( $main::arg eq "kill" ) {
117 die "No daemon running\n";
118 } elsif ( $main::arg eq "restart" ) {
119 print "(No daemon running; starting anyway)\n";
122 # if started without arguments (initial invocation), then fork
125 # now go to background
126 die "$main::progname: fork failed: $!\n"
127 unless defined( $pid = fork );
130 # parent: wait for signal from child (SIGCHLD or SIGUSR1) and exit
131 my $sigset = POSIX::SigSet->new();
133 $SIG{"CHLD"} = sub { };
134 $SIG{"USR1"} = sub { };
135 POSIX::sigsuspend($sigset);
136 waitpid( $pid, WNOHANG );
137 if ( kill( 0, $pid ) ) {
138 print "Daemon started in background (pid $pid)\n";
147 if ( $conf::upload_method eq "ssh" ) {
149 # exec an ssh-agent that starts us again
150 # force shell to be /bin/sh, ssh-agent may base its decision
151 # whether to use a fd or a Unix socket on the shell...
152 $ENV{"SHELL"} = "/bin/sh";
153 exec $conf::ssh_agent, $0, "startup", getppid();
154 die "$main::progname: Could not exec $conf::ssh_agent: $!\n";
157 # no need to exec, just set up @ARGV as expected below
158 @ARGV = ( "startup", getppid() );
160 } ## end else [ if ($pid)
161 } ## end if ( !@ARGV )
162 die "Please start without any arguments.\n"
163 if @ARGV != 2 || $ARGV[0] ne "startup";
164 my $parent_pid = $ARGV[1];
169 'Release: 0.9 $Revision: 1.51 $ $Date: 1999/07/08 09:43:21 $ $Author: ftplinux $'
171 print "debianqueued $version\n";
174 # check if all programs exist
176 foreach $prg ( $conf::gpg, $conf::ssh, $conf::scp, $conf::ssh_agent,
177 $conf::ssh_add, $conf::md5sum, $conf::mail, $conf::mkfifo )
179 die "Required program $prg doesn't exist or isn't executable\n"
182 # check for correct upload method
183 die "Bad upload method '$conf::upload_method'.\n"
184 if $conf::upload_method ne "ssh"
185 && $conf::upload_method ne "ftp"
186 && $conf::upload_method ne "copy";
187 die "No keyrings\n" if !@conf::keyrings;
189 } ## end foreach $prg ( $conf::gpg, ...
190 die "statusfile path must be absolute."
191 if $conf::statusfile !~ m,^/,;
192 die "upload and target queue paths must be absolute."
193 if $conf::incoming !~ m,^/,
194 || $conf::incoming_delayed !~ m,^/,
195 || $conf::targetdir !~ m,^/,
196 || $conf::targetdir_delayed !~ m,^/,;
198 # ---------------------------------------------------------------------------
200 # ---------------------------------------------------------------------------
205 sub get_filelist_from_known_good_changes($);
206 sub age_delayed_queues();
207 sub process_changes($\@);
208 sub process_commands($);
209 sub age_delayed_queues();
210 sub is_on_target($\@);
211 sub copy_to_target(@);
214 sub check_incoming_writable();
216 sub write_status_file();
217 sub print_status($$$$$$);
218 sub format_status_num(\$$);
219 sub format_status_str(\$$);
231 sub check_incoming_writable();
234 sub is_debian_file($);
235 sub get_maintainer($);
236 sub debian_file_stem($);
242 sub try_to_get_mail_addr($$);
246 sub unblock_signals();
249 sub restart_statusd();
252 $ENV{"PATH"} = "/bin:/usr/bin";
253 $ENV{"IFS"} = "" if defined( $ENV{"IFS"} && $ENV{"IFS"} ne "" );
266 sub ST_CTIME() { 10 }
268 # fixed lengths of data items passed over status pipe
269 sub STATNUM_LEN() { 30 }
270 sub STATSTR_LEN() { 128 }
272 # init list of signals
273 defined $Config{sig_name}
274 or die "$main::progname: No signal list defined!\n";
277 foreach $name ( split( ' ', $Config{sig_name} ) ) {
278 $main::signo{$name} = $i++;
281 @main::fatal_signals = qw( INT QUIT ILL TRAP ABRT BUS FPE USR2 SEGV PIPE
282 TERM XCPU XFSZ PWR );
284 $main::block_sigset = POSIX::SigSet->new;
285 $main::block_sigset->addset( $main::signo{"INT"} );
286 $main::block_sigset->addset( $main::signo{"TERM"} );
288 # some constant net stuff
289 $main::tcp_proto = ( getprotobyname('tcp') )[2]
290 or die "Cannot get protocol number for 'tcp'\n";
291 my $used_service = ( $conf::upload_method eq "ssh" ) ? "ssh" : "ftp";
292 $main::echo_port = ( getservbyname( $used_service, 'tcp' ) )[2]
293 or die "Cannot get port number for service '$used_service'\n";
295 # clear queue of stored mails
296 @main::stored_mails = ();
298 # run ssh-add to bring the key into the agent (will use stdin/stdout)
299 if ( $conf::upload_method eq "ssh" ) {
300 system "$conf::ssh_add $conf::ssh_key_file"
301 and die "$main::progname: Running $conf::ssh_add failed "
302 . "(exit status ", $? >> 8, ")\n";
305 # change to queue dir
306 chdir($conf::incoming)
307 or die "$main::progname: cannot cd to $conf::incoming: $!\n";
309 # needed before /dev/null redirects, some system send a SIGHUP when loosing
310 # the controlling tty
311 $SIG{"HUP"} = "IGNORE";
313 # open logfile, make it unbuffered
314 open( LOG, ">>$conf::logfile" )
315 or die "Cannot open my logfile $conf::logfile: $!\n";
316 chmod( 0644, $conf::logfile )
317 or die "Cannot set modes of $conf::logfile: $!\n";
318 select( ( select(LOG), $| = 1 )[0] );
321 $SIG{"HUP"} = \&close_log;
323 # redirect stdin, ... to /dev/null
324 open( STDIN, "</dev/null" )
325 or die "$main::progname: Can't redirect stdin to /dev/null: $!\n";
326 open( STDOUT, ">&LOG" )
327 or die "$main::progname: Can't redirect stdout to $conf::logfile: $!\n";
328 open( STDERR, ">&LOG" )
329 or die "$main::progname: Can't redirect stderr to $conf::logfile: $!\n";
331 # ok, from this point usually no "die" anymore, stderr is gone!
332 msg( "log", "daemon (pid $$) started\n" );
334 # initialize variables used by send_status before launching the status daemon
336 format_status_num( $main::next_run, time + 10 );
337 format_status_str( $main::current_changes, "" );
339 $main::incoming_writable = 1; # assume this for now
341 # start the daemon watching the 'status' FIFO
342 if ( $conf::statusfile && $conf::statusdelay == 0 ) {
343 $main::statusd_pid = fork_statusd();
344 $SIG{"CHLD"} = \&kid_died; # watch out for dead status daemon
345 # SIGUSR1 triggers status info
346 $SIG{"USR1"} = \&send_status;
347 } ## end if ( $conf::statusfile...
348 $main::maind_pid = $$;
351 kill( $main::signo{"ABRT"}, $$ )
352 if defined $main::signo{"ABRT"};
356 open( PIDFILE, ">$conf::pidfile" )
357 or msg( "log", "Can't open $conf::pidfile: $!\n" );
358 printf PIDFILE "%5d\n", $$;
360 chmod( 0644, $conf::pidfile )
361 or die "Cannot set modes of $conf::pidfile: $!\n";
363 # other signals will just log an error and exit
364 foreach (@main::fatal_signals) {
365 $SIG{$_} = \&fatal_signal;
368 # send signal to user-started process that we're ready and it can exit
369 kill( $main::signo{"USR1"}, $parent_pid );
371 # ---------------------------------------------------------------------------
373 # ---------------------------------------------------------------------------
375 # default to classical incoming/target
376 $main::current_incoming = $conf::incoming;
377 $main::current_targetdir = $conf::targetdir;
380 write_status_file() if $conf::statusdelay;
383 # ping target only if there is the possibility that we'll contact it (but
384 # also don't wait too long).
385 my @have_changes = <*.changes *.commands>;
386 for ( my $delayed_dirs = 0 ;
387 $delayed_dirs <= $conf::max_delayed ;
390 my $adelayeddir = sprintf( "$conf::incoming_delayed", $delayed_dirs );
391 push( @have_changes, <$adelayeddir/*.changes> );
392 } ## end for ( my $delayed_dirs ...
394 if @have_changes || ( time - $main::last_ping_time ) > 8 * 60 * 60;
396 if ( @have_changes && $main::target_up ) {
397 check_incoming_writable if !$main::incoming_writable;
398 check_dir() if $main::incoming_writable;
401 write_status_file() if $conf::statusdelay;
403 if ( $conf::upload_method eq "copy" ) {
404 age_delayed_queues();
407 # sleep() returns if we received a signal (SIGUSR1 for status FIFO), so
408 # calculate the end time once and wait for it being reached.
409 format_status_num( $main::next_run, time + $conf::queue_delay );
411 while ( ( $delta = calc_delta() ) > 0 ) {
412 debug("mainloop sleeping $delta secs");
415 # check if statusd died, if using status FIFO, or update status file
416 if ($conf::statusdelay) {
421 } ## end while ( ( $delta = calc_delta...
427 $delta = $main::next_run - time;
428 $delta = $conf::statusdelay
429 if $conf::statusdelay && $conf::statusdelay < $delta;
431 } ## end sub calc_delta()
433 # ---------------------------------------------------------------------------
434 # main working functions
435 # ---------------------------------------------------------------------------
438 # main function for checking the incoming dir
441 my ( @files, @changes, @keep_files, @this_keep_files, @stats, $file,
444 debug("starting checkdir");
446 write_status_file() if $conf::statusdelay;
448 # test if needed binaries are available; this is if they're on maybe
449 # slow-mounted NFS filesystems
450 foreach (@conf::test_binaries) {
453 # maybe the mount succeeds now
456 msg( "log", "binary test failed for $_; delaying queue run\n" );
458 } ## end foreach (@conf::test_binaries)
460 for ( $adelay = -1 ; $adelay <= $conf::max_delayed ; $adelay++ ) {
461 if ( $adelay == -1 ) {
462 $main::current_incoming = $conf::incoming;
463 $main::current_incoming_short = "";
464 $main::current_targetdir = $conf::targetdir;
466 $main::current_incoming = sprintf( $conf::incoming_delayed, $adelay );
467 $main::current_incoming_short = sprintf( "DELAYED/%d-day", $adelay );
468 $main::current_targetdir = sprintf( $conf::targetdir_delayed, $adelay );
471 # need to clear directory specific variables
473 undef(@this_keep_files);
475 chdir($main::current_incoming)
479 "Cannot change to dir "
480 . "${main::current_incoming_short}: $!\n"
485 # look for *.commands files but not in delayed queues
486 if ( $adelay == -1 ) {
487 foreach $file (<*.commands>) {
490 process_commands($file);
493 write_status_file() if $conf::statusdelay;
495 } ## end foreach $file (<*.commands>)
496 } ## end if ( $adelay == -1 )
500 "log", "Cannot open dir ${main::current_incoming_short}: $!\n"
504 @files = readdir(INC);
507 # process all .changes files found
508 @changes = grep /\.changes$/, @files;
509 push( @keep_files, @changes ); # .changes files aren't stray
510 foreach $file (@changes) {
513 # wrap in an eval to allow jumpbacks to here with die in case
516 eval { process_changes( $file, @this_keep_files ); };
518 msg( "log,mail", $@ ) if $@;
520 write_status_file() if $conf::statusdelay;
522 # files which are ok in conjunction with this .changes
523 debug("$file tells to keep @this_keep_files");
524 push( @keep_files, @this_keep_files );
527 # break out of this loop if the incoming dir has become unwritable
528 goto end_run if !$main::incoming_writable;
529 } ## end foreach $file (@changes)
530 ftp_close() if $conf::upload_method eq "ftp";
532 # find files which aren't related to any .changes
533 foreach $file (@files) {
535 # filter out files we never want to delete
536 next if !-f $file || # may have disappeared in the meantime
539 || ( grep { $_ eq $file } @keep_files )
540 || $file =~ /$conf::keep_files/;
542 # Delete such files if they're older than
543 # $stray_remove_timeout; they could be part of an
544 # yet-incomplete upload, with the .changes still missing.
545 # Cannot send any notification, since owner unknown.
546 next if !( @stats = stat($file) );
547 my $age = time - $stats[ST_MTIME];
548 my ( $maint, $pattern, @job_files );
549 if ( $file =~ /^junk-for-writable-test/
550 || $file !~ m,$conf::valid_files,
551 || $age >= $conf::stray_remove_timeout )
554 "Deleted stray file ${main::current_incoming_short}/$file\n" )
557 $age > $conf::no_changes_timeout
558 && is_debian_file($file)
561 # not already reported
562 !( $stats[ST_MODE] & S_ISGID )
563 && ( $pattern = debian_file_stem($file) )
564 && ( @job_files = glob($pattern) )
567 # If a .changes is in the list, it has the same stem as the
568 # found file (probably a .orig.tar.gz). Don't report in this
570 !( grep( /\.changes$/, @job_files ) )
573 $maint = get_maintainer($file);
575 # Don't send a mail if this looks like the recompilation of a
576 # package for a non-i386 arch. For those, the maintainer field is
578 if ( !grep( /(\.dsc|_(i386|all)\.deb)$/, @job_files ) ) {
579 msg( "log", "Found an upload without .changes and with no ",
582 "Not sending a report, because probably ",
583 "recompilation job\n" );
586 $main::mail_addr = $maint;
587 $main::mail_addr = $1 if $main::mail_addr =~ /<([^>]*)>/;
588 $main::mail_subject =
589 "Incomplete upload found in " . "Debian upload queue";
592 "Probably you are the uploader of the following "
595 msg( "mail", "the Debian upload queue directory:\n " );
596 msg( "mail", join( "\n ", @job_files ), "\n" );
599 "This looks like an upload, but a .changes file "
600 . "is missing, so the job\n"
602 msg( "mail", "cannot be processed.\n\n" );
605 "If no .changes file arrives within ",
606 print_time( $conf::stray_remove_timeout - $age ),
607 ", the files will be deleted.\n\n"
611 "If you didn't upload those files, please just "
612 . "ignore this message.\n"
617 "Sending problem report for an upload without a "
620 msg( "log", "Maintainer: $maint\n" );
624 "Found an upload without .changes, but can't "
625 . "find a maintainer address\n"
627 } ## end else [ if ( !grep( /(\.dsc|_(i386|all)\.deb)$/...
628 msg( "log", "Files: @job_files\n" );
630 # remember we already have sent a mail regarding this file
631 foreach (@job_files) {
633 next if !@st; # file may have disappeared in the meantime
634 chmod +( $st[ST_MODE] |= S_ISGID ), $_;
638 "found stray file ${main::current_incoming_short}/$file, deleting in ",
639 print_time( $conf::stray_remove_timeout - $age )
641 } ## end else [ if ( $file =~ /^junk-for-writable-test/...
642 } ## end foreach $file (@files)
643 } ## end for ( $adelay = -1 ; $adelay...
644 chdir($conf::incoming);
648 write_status_file() if $conf::statusdelay;
649 } ## end sub check_dir()
651 sub get_filelist_from_known_good_changes($) {
657 # parse the .changes file
658 open( CHANGES, "<$changes" )
659 or die "$changes: $!\n";
660 outer_loop: while (<CHANGES>) {
663 redo outer_loop if !/^\s/;
664 my @field = split(/\s+/);
667 # forbid shell meta chars in the name, we pass it to a
668 # subshell several times...
669 $field[5] =~ /^([a-zA-Z0-9.+_:@=%-][~a-zA-Z0-9.+_:@=%-]*)/;
670 if ( $1 ne $field[5] ) {
671 msg( "log", "found suspicious filename $field[5]\n" );
674 push( @filenames, $field[5] );
675 } ## end while (<CHANGES>)
676 } ## end if (/^Files:/i)
677 } ## end while (<CHANGES>)
680 } ## end sub get_filelist_from_known_good_changes($)
683 # process one .changes file
685 sub process_changes($\@) {
687 my $keep_list = shift;
689 $pgplines, @files, @filenames, @changes_stats,
690 $failure_file, $retries, $last_retry, $upload_time,
691 $file, $do_report, $ls_l, $problems_reported,
692 $errs, $pkgname, $signator
697 format_status_str( $main::current_changes,
698 "$main::current_incoming_short/$changes" );
700 write_status_file() if $conf::statusdelay;
703 msg( "log", "processing ${main::current_incoming_short}/$changes\n" );
705 # parse the .changes file
706 open( CHANGES, "<$changes" )
707 or die "Cannot open ${main::current_incoming_short}/$changes: $!\n";
709 $main::mail_addr = "";
711 outer_loop: while (<CHANGES>) {
712 if (/^---+(BEGIN|END) PGP .*---+$/) {
714 } elsif (/^Maintainer:\s*/i) {
715 chomp( $main::mail_addr = $' );
716 $main::mail_addr = $1 if $main::mail_addr =~ /<([^>]*)>/;
717 } elsif (/^Source:\s*/i) {
718 chomp( $pkgname = $' );
719 $pkgname =~ s/\s+$//;
720 $main::packages{$pkgname}++;
721 } elsif (/^Files:/i) {
723 redo outer_loop if !/^\s/;
724 my @field = split(/\s+/);
727 # forbid shell meta chars in the name, we pass it to a
728 # subshell several times...
729 $field[5] =~ /^([a-zA-Z0-9.+_:@=%-][~a-zA-Z0-9.+_:@=%-]*)/;
730 if ( $1 ne $field[5] ) {
731 msg( "log", "found suspicious filename $field[5]\n" );
734 "File '$field[5]' mentioned in $main::current_incoming_short/$changes\n",
735 "has bad characters in its name. Removed.\n"
739 } ## end if ( $1 ne $field[5] )
748 push( @filenames, $field[5] );
749 debug( "includes file $field[5], size $field[2], ", "md5 $field[1]" );
750 } ## end while (<CHANGES>)
751 } ## end elsif (/^Files:/i)
752 } ## end while (<CHANGES>)
755 # tell check_dir that the files mentioned in this .changes aren't stray,
756 # we know about them somehow
757 @$keep_list = @filenames;
759 # some consistency checks
760 if ( !$main::mail_addr ) {
762 "$main::current_incoming_short/$changes doesn't contain a Maintainer: field; "
763 . "cannot process\n" );
764 goto remove_only_changes;
765 } ## end if ( !$main::mail_addr)
766 if ( $main::mail_addr !~ /^(buildd_\S+-\S+|\S+\@\S+\.\S+)/ ) {
768 # doesn't look like a mail address, maybe only the name
769 my ( $new_addr, @addr_list );
770 if ( $new_addr = try_to_get_mail_addr( $main::mail_addr, \@addr_list ) ) {
772 # substitute (unique) found addr, but give a warning
775 "(The Maintainer: field didn't contain a proper "
780 "Looking for `$main::mail_addr' in the Debian "
781 . "keyring gave your address\n"
783 msg( "mail", "as unique result, so I used this.)\n" );
785 "Substituted $new_addr for malformed " . "$main::mail_addr\n" );
786 $main::mail_addr = $new_addr;
789 # not found or not unique: hold the job and inform queue maintainer
790 my $old_addr = $main::mail_addr;
791 $main::mail_addr = $conf::maintainer_mail;
794 "The job ${main::current_incoming_short}/$changes doesn't have a correct email\n"
796 msg( "mail", "address in the Maintainer: field:\n" );
797 msg( "mail", " $old_addr\n" );
798 msg( "mail", "A check for this in the Debian keyring gave:\n" );
801 ? " " . join( ", ", @addr_list ) . "\n"
803 msg( "mail", "Please fix this manually\n" );
806 "Bad Maintainer: field in ${main::current_incoming_short}/$changes: $old_addr\n"
808 goto remove_only_changes;
809 } ## end else [ if ( $new_addr = try_to_get_mail_addr...
810 } ## end if ( $main::mail_addr ...
811 if ( $pgplines < 3 ) {
814 "$main::current_incoming_short/$changes isn't signed with PGP/GnuPG\n"
816 msg( "log", "(uploader $main::mail_addr)\n" );
817 goto remove_only_changes;
818 } ## end if ( $pgplines < 3 )
821 "$main::current_incoming_short/$changes doesn't mention any files\n" );
822 msg( "log", "(uploader $main::mail_addr)\n" );
823 goto remove_only_changes;
824 } ## end if ( !@files )
826 # check for packages that shouldn't be processed
827 if ( grep( $_ eq $pkgname, @conf::nonus_packages ) ) {
830 "$pkgname is a package that must be uploaded "
831 . "to nonus.debian.org\n"
833 msg( "log,mail", "instead of target.\n" );
835 "Job rejected and removed all files belonging " . "to it:\n" );
836 msg( "log,mail", " ", join( ", ", @filenames ), "\n" );
837 rm( $changes, @filenames );
839 } ## end if ( grep( $_ eq $pkgname...
841 $failure_file = $changes . ".failures";
842 $retries = $last_retry = 0;
843 if ( -f $failure_file ) {
844 open( FAILS, "<$failure_file" )
845 or die "Cannot open $main::current_incoming_short/$failure_file: $!\n";
848 ( $retries, $last_retry ) = ( $1, $2 )
849 if $line =~ /^(\d+)\s+(\d+)$/;
850 push( @$keep_list, $failure_file );
851 } ## end if ( -f $failure_file )
853 # run PGP on the file to check the signature
854 if ( !( $signator = pgp_check($changes) ) ) {
857 "$main::current_incoming_short/$changes has bad PGP/GnuPG signature!\n"
859 msg( "log", "(uploader $main::mail_addr)\n" );
863 "Removing $main::current_incoming_short/$changes, but keeping its associated ",
868 # Set SGID bit on associated files, so that the test for Debian files
869 # without a .changes doesn't consider them.
870 foreach (@filenames) {
872 next if !@st; # file may have disappeared in the meantime
873 chmod +( $st[ST_MODE] |= S_ISGID ), $_;
876 } elsif ( $signator eq "LOCAL ERROR" ) {
878 # An error has appened when starting pgp... Don't process the file,
879 # but also don't delete it
881 "Can't PGP/GnuPG check $main::current_incoming_short/$changes -- don't process it for now"
884 } ## end elsif ( $signator eq "LOCAL ERROR")
886 die "Cannot stat ${main::current_incoming_short}/$changes (??): $!\n"
887 if !( @changes_stats = stat($changes) );
889 # Make $upload_time the maximum of all modification times of files
890 # related to this .changes (and the .changes it self). This is the
891 # last time something changes to these files.
892 $upload_time = $changes_stats[ST_MTIME];
895 next if !( @stats = stat( $file->{"name"} ) );
896 $file->{"stats"} = \@stats;
897 $upload_time = $stats[ST_MTIME] if $stats[ST_MTIME] > $upload_time;
898 } ## end for $file (@files)
900 $do_report = ( time - $upload_time ) > $conf::problem_report_timeout;
901 $problems_reported = $changes_stats[ST_MODE] & S_ISGID;
903 # if any of the files is newer than the .changes' ctime (the time
904 # we sent a report and set the sticky bit), send new problem reports
905 if ( $problems_reported && $changes_stats[ST_CTIME] < $upload_time ) {
906 $problems_reported = 0;
907 chmod +( $changes_stats[ST_MODE] &= ~S_ISGID ), $changes;
908 debug("upload_time>changes-ctime => resetting problems reported");
910 debug("do_report=$do_report problems_reported=$problems_reported");
912 # now check all files for correct size and md5 sum
914 my $filename = $file->{"name"};
915 if ( !defined( $file->{"stats"} ) ) {
917 # could be an upload that isn't complete yet, be quiet,
918 # but don't process the file;
919 msg( "log,mail", "$filename doesn't exist\n" )
920 if $do_report && !$problems_reported;
921 msg( "log", "$filename doesn't exist (ignored for now)\n" )
923 msg( "log", "$filename doesn't exist (already reported)\n" )
924 if $problems_reported;
926 } elsif ( $file->{"stats"}->[ST_SIZE] < $file->{"size"}
930 # could be an upload that isn't complete yet, be quiet,
931 # but don't process the file
932 msg( "log", "$filename is too small (ignored for now)\n" );
934 } elsif ( $file->{"stats"}->[ST_SIZE] != $file->{"size"} ) {
935 msg( "log,mail", "$filename has incorrect size; deleting it\n" );
938 } elsif ( md5sum($filename) ne $file->{"md5"} ) {
940 "$filename has incorrect md5 checksum; ",
944 } ## end elsif ( md5sum($filename)...
945 } ## end for $file (@files)
948 if ( ( time - $upload_time ) > $conf::bad_changes_timeout ) {
950 # if a .changes fails for a really long time (several days
951 # or so), remove it and all associated files
954 "$main::current_incoming_short/$changes couldn't be processed for ",
955 int( $conf::bad_changes_timeout / ( 60 * 60 ) ),
956 " hours and is now deleted\n"
958 msg( "log,mail", "All files it mentions are also removed:\n" );
959 msg( "log,mail", " ", join( ", ", @filenames ), "\n" );
960 rm( $changes, @filenames, $failure_file );
961 } elsif ( $do_report && !$problems_reported ) {
963 # otherwise, send a problem report, if not done already
966 "Due to the errors above, the .changes file couldn't ",
968 "Please fix the problems for the upload to happen.\n"
971 # remember we already have sent a mail regarding this file
972 debug("Sending problem report mail and setting SGID bit");
973 my $mode = $changes_stats[ST_MODE] |= S_ISGID;
974 msg( "log", "chmod failed: $!" )
975 if ( chmod( $mode, $changes ) != 1 );
976 } ## end elsif ( $do_report && !$problems_reported)
983 # if this upload already failed earlier, wait until the delay requirement
986 && ( time - $last_retry ) <
987 ( $retries == 1 ? $conf::upload_delay_1 : $conf::upload_delay_2 ) )
989 msg( "log", "delaying retry of upload\n" );
991 } ## end if ( $retries > 0 && (...
993 if ( $conf::upload_method eq "ftp" ) {
994 return if !ftp_open();
997 # check if the job is already present on target
998 # (moved to here, to avoid bothering target as long as there are errors in
1000 if ( $ls_l = is_on_target( $changes, @filenames ) ) {
1003 "$main::current_incoming_short/$changes is already present on target host:\n"
1005 msg( "log,mail", "$ls_l\n" );
1007 "Either you already uploaded it, or someone else ",
1009 msg( "log,mail", "Job $changes removed.\n" );
1010 rm( $changes, @filenames, $failure_file );
1012 } ## end if ( $ls_l = is_on_target...
1014 # clear sgid bit before upload, scp would copy it to target. We don't need
1015 # it anymore, we know there are no problems if we come here. Also change
1016 # mode of files to 644 if this should be done locally.
1017 $changes_stats[ST_MODE] &= ~S_ISGID;
1018 if ( !$conf::chmod_on_target ) {
1019 $changes_stats[ST_MODE] &= ~0777;
1020 $changes_stats[ST_MODE] |= 0644;
1022 chmod +( $changes_stats[ST_MODE] ), $changes;
1024 # try uploading to target
1025 if ( !copy_to_target( $changes, @filenames ) ) {
1027 # if the upload failed, increment the retry counter and remember the
1028 # current time; both things are written to the .failures file. Don't
1029 # increment the fail counter if the error was due to incoming
1031 return if !$main::incoming_writable;
1032 if ( ++$retries >= $conf::max_upload_retries ) {
1034 "$changes couldn't be uploaded for $retries times now.\n" );
1036 "Giving up and removing it and its associated files:\n" );
1037 msg( "log,mail", " ", join( ", ", @filenames ), "\n" );
1038 rm( $changes, @filenames, $failure_file );
1041 if ( open( FAILS, ">$failure_file" ) ) {
1042 print FAILS "$retries $last_retry\n";
1044 chmod( 0600, $failure_file )
1045 or die "Cannot set modes of $failure_file: $!\n";
1046 } ## end if ( open( FAILS, ">$failure_file"...
1047 push( @$keep_list, $failure_file );
1048 debug("now $retries failed uploads");
1051 "The upload will be retried in ",
1054 ? $conf::upload_delay_1
1055 : $conf::upload_delay_2
1059 } ## end else [ if ( ++$retries >= $conf::max_upload_retries)
1061 } ## end if ( !copy_to_target( ...
1063 # If the files were uploaded ok, remove them
1064 rm( $changes, @filenames, $failure_file );
1066 msg( "mail", "$changes uploaded successfully to $conf::target\n" );
1067 msg( "mail", "along with the files:\n ", join( "\n ", @filenames ),
1070 "$changes processed successfully (uploader $main::mail_addr)\n" );
1072 # Check for files that have the same stem as the .changes (and weren't
1073 # mentioned there) and delete them. It happens often enough that people
1074 # upload a .orig.tar.gz where it isn't needed and also not in the
1075 # .changes. Explicitly deleting it (and not waiting for the
1076 # $stray_remove_timeout) reduces clutter in the queue dir and maybe also
1077 # educates uploaders :-)
1079 # my $pattern = debian_file_stem( $changes );
1080 # my $spattern = substr( $pattern, 0, -1 ); # strip off '*' at end
1081 # my @other_files = glob($pattern);
1082 # filter out files that have a Debian revision at all and a different
1083 # revision. Those belong to a different upload.
1084 # if ($changes =~ /^\Q$spattern\E-([\d.+-]+)/) {
1085 # my $this_rev = $1;
1086 # @other_files = grep( !/^\Q$spattern\E-([\d.+-]+)/ || $1 eq $this_rev,
1089 # Also do not remove those files if a .changes is among them. Then there
1090 # is probably a second upload for another version or another architecture.
1091 # if (@other_files && !grep( /\.changes$/, @other_files )) {
1092 # rm( @other_files );
1093 # msg( "mail", "\nThe following file(s) seemed to belong to the same ".
1094 # "upload, but weren't listed\n" );
1095 # msg( "mail", "in the .changes file:\n " );
1096 # msg( "mail", join( "\n ", @other_files ), "\n" );
1097 # msg( "mail", "They have been deleted.\n" );
1098 # msg( "log", "Deleted files in upload not in $changes: @other_files\n" );
1100 } ## end sub process_changes($\@)
1103 # process one .commands file
1105 sub process_commands($) {
1106 my $commands = shift;
1107 my ( @cmds, $cmd, $pgplines, $signator );
1109 my ( @files, $file, @removed, $target_delay );
1111 format_status_str( $main::current_changes, $commands );
1113 write_status_file() if $conf::statusdelay;
1115 msg( "log", "processing $main::current_incoming_short/$commands\n" );
1117 # parse the .commands file
1118 if ( !open( COMMANDS, "<$commands" ) ) {
1119 msg( "log", "Cannot open $main::current_incoming_short/$commands: $!\n" );
1123 $main::mail_addr = "";
1125 outer_loop: while (<COMMANDS>) {
1126 if (/^---+(BEGIN|END) PGP .*---+$/) {
1128 } elsif (/^Uploader:\s*/i) {
1129 chomp( $main::mail_addr = $' );
1130 $main::mail_addr = $1 if $main::mail_addr =~ /<([^>]*)>/;
1131 } elsif (/^Commands:/i) {
1134 s/^\s*(.*)\s*$/$1/; # delete whitespace at both ends
1137 debug("includes cmd $_");
1139 last outer_loop if !defined( $_ = scalar(<COMMANDS>) );
1141 redo outer_loop if !/^\s/ || /^$/;
1142 } ## end for ( ; ; )
1143 } ## end elsif (/^Commands:/i)
1144 } ## end while (<COMMANDS>)
1147 # some consistency checks
1148 if ( !$main::mail_addr || $main::mail_addr !~ /^\S+\@\S+\.\S+/ ) {
1150 "$main::current_incoming_short/$commands contains no or bad Uploader: field: "
1151 . "$main::mail_addr\n" );
1153 "cannot process $main::current_incoming_short/$commands\n" );
1154 $main::mail_addr = "";
1156 } ## end if ( !$main::mail_addr...
1157 msg( "log", "(command uploader $main::mail_addr)\n" );
1159 if ( $pgplines < 3 ) {
1162 "$main::current_incoming_short/$commands isn't signed with PGP/GnuPG\n"
1166 "or the uploaded file is broken. Make sure to transfer in binary mode\n"
1168 msg( "mail", "or better yet - use dcut for commands files\n" );
1170 } ## end if ( $pgplines < 3 )
1172 # run PGP on the file to check the signature
1173 if ( !( $signator = pgp_check($commands) ) ) {
1176 "$main::current_incoming_short/$commands has bad PGP/GnuPG signature!\n"
1179 msg( "log,mail", "Removing $main::current_incoming_short/$commands\n" );
1182 } elsif ( $signator eq "LOCAL ERROR" ) {
1184 # An error has appened when starting pgp... Don't process the file,
1185 # but also don't delete it
1187 "Can't PGP/GnuPG check $main::current_incoming_short/$commands -- don't process it for now"
1190 } ## end elsif ( $signator eq "LOCAL ERROR")
1191 msg( "log", "(PGP/GnuPG signature by $signator)\n" );
1193 # now process commands
1196 "Log of processing your commands file $main::current_incoming_short/$commands:\n\n"
1198 foreach $cmd (@cmds) {
1199 my @word = split( /\s+/, $cmd );
1200 msg( "mail,log", "> @word\n" );
1201 my $selecteddelayed = -1;
1204 if ( $word[0] eq "rm" ) {
1205 foreach ( @word[ 1 .. $#word ] ) {
1207 if (m,^DELAYED/([0-9]+)-day/,) {
1208 $selecteddelayed = $1;
1209 s,^DELAYED/[0-9]+-day/,,;
1211 if ( $origword eq "--searchdirs" ) {
1212 $selecteddelayed = -2;
1216 "$_: filename may not contain slashes except for DELAYED/#-day/ prefixes\n"
1220 # process wildcards but also plain names
1222 my $pat = quotemeta($_);
1223 $pat =~ s/\\\*/.*/g;
1224 $pat =~ s/\\\?/.?/g;
1225 $pat =~ s/\\([][])/$1/g;
1227 if ( $selecteddelayed < 0 ) { # scanning or explicitly incoming
1228 opendir( DIR, "." );
1229 push( @thesefiles, grep /^$pat$/, readdir(DIR) );
1232 if ( $selecteddelayed >= 0 ) {
1233 my $dir = sprintf( $conf::incoming_delayed, $selecteddelayed );
1234 opendir( DIR, $dir );
1236 map ( "$dir/$_", grep /^$pat$/, readdir(DIR) ) );
1238 } elsif ( $selecteddelayed == -2 ) {
1239 for ( my ($adelay) = 0 ;
1240 ( !@thesefiles ) && $adelay <= $conf::max_delayed ;
1243 my $dir = sprintf( $conf::incoming_delayed, $adelay );
1244 opendir( DIR, $dir );
1246 map ( "$dir/$_", grep /^$pat$/, readdir(DIR) ) );
1248 } ## end for ( my ($adelay) = 0 ...
1249 } ## end elsif ( $selecteddelayed ...
1250 push( @files, @thesefiles );
1251 if ( !@thesefiles ) {
1252 msg( "mail,log", "$origword did not match anything\n" );
1254 } ## end else [ if ( $origword eq "--searchdirs")
1255 } ## end foreach ( @word[ 1 .. $#word...
1257 msg( "mail,log", "No files to delete\n" );
1260 foreach $file (@files) {
1262 msg( "mail,log", "$file: no such file\n" );
1263 } elsif ( $file =~ /$conf::keep_files/ ) {
1264 msg( "mail,log", "$file is protected, cannot " . "remove\n" );
1265 } elsif ( !unlink($file) ) {
1266 msg( "mail,log", "$file: rm: $!\n" );
1268 $file =~ s,$conf::incoming/?,,;
1269 push( @removed, $file );
1271 } ## end foreach $file (@files)
1272 msg( "mail,log", "Files removed: @removed\n" ) if @removed;
1273 } ## end else [ if ( !@files )
1274 } elsif ( $word[0] eq "reschedule" ) {
1276 msg( "mail,log", "Wrong number of arguments\n" );
1277 } elsif ( $conf::upload_method ne "copy" ) {
1278 msg( "mail,log", "reschedule not available\n" );
1279 } elsif ( $word[1] =~ m,/, || $word[1] !~ m/\.changes/ ) {
1282 "$word[1]: filename may not contain slashes and must be .changes\n"
1284 } elsif ( !( ($target_delay) = $word[2] =~ m,^([0-9]+)-day$, )
1285 || $target_delay > $conf::max_delayed )
1289 "$word[2]: rescheduling target must be #-day with # between 0 and $conf::max_delayed (in particular, no '/' allowed)\n"
1291 } elsif ( $word[1] =~ /$conf::keep_files/ ) {
1292 msg( "mail,log", "$word[1] is protected, cannot do stuff with it\n" );
1296 $adelay <= $conf::max_delayed
1298 sprintf( "$conf::targetdir_delayed", $adelay ) . "/$word[1]" ) ;
1301 } ## end for ( $adelay = 0 ; $adelay...
1302 if ( $adelay > $conf::max_delayed ) {
1303 msg( "mail,log", "$word[1] not found\n" );
1304 } elsif ( $adelay == $target_delay ) {
1305 msg( "mail,log", "$word[1] already is in $word[2]\n" );
1308 my ($dir) = sprintf( "$conf::targetdir_delayed", $adelay );
1310 sprintf( "$conf::targetdir_delayed", $target_delay );
1311 push( @thesefiles, $word[1] );
1313 get_filelist_from_known_good_changes("$dir/$word[1]") );
1314 for my $afile (@thesefiles) {
1315 if ( $afile =~ m/\.changes$/ ) {
1316 utime undef, undef, ("$dir/$afile");
1318 if ( !rename "$dir/$afile", "$target_dir/$afile" ) {
1319 msg( "mail,log", "rename: $!\n" );
1321 msg( "mail,log", "$afile moved to $target_delay-day\n" );
1323 } ## end for my $afile (@thesefiles)
1324 } ## end else [ if ( $adelay > $conf::max_delayed)
1325 } ## end else [ if ( @word != 3 )
1326 } elsif ( $word[0] eq "cancel" ) {
1328 msg( "mail,log", "Wrong number of arguments\n" );
1329 } elsif ( $conf::upload_method ne "copy" ) {
1330 msg( "mail,log", "cancel not available\n" );
1332 $word[1] !~ m,^[a-zA-Z0-9.+_:@=%-][~a-zA-Z0-9.+_:@=%-]*\.changes$, )
1335 "argument to cancel must be one .changes filename without path\n" );
1336 } ## end elsif ( $word[1] !~ ...
1338 for ( my ($adelay) = 0 ; $adelay <= $conf::max_delayed ; $adelay++ ) {
1339 my ($dir) = sprintf( "$conf::targetdir_delayed", $adelay );
1340 if ( -f "$dir/$word[1]" ) {
1342 push( @files, "$word[1]" );
1344 get_filelist_from_known_good_changes("$dir/$word[1]") );
1345 foreach $file (@files) {
1346 if ( !-f "$dir/$file" ) {
1347 msg( "mail,log", "$dir/$file: no such file\n" );
1348 } elsif ( "$dir/$file" =~ /$conf::keep_files/ ) {
1350 "$dir/$file is protected, cannot " . "remove\n" );
1351 } elsif ( !unlink("$dir/$file") ) {
1352 msg( "mail,log", "$dir/$file: rm: $!\n" );
1354 push( @removed, $file );
1356 } ## end foreach $file (@files)
1357 msg( "mail,log", "Files removed from $adelay-day: @removed\n" )
1359 } ## end if ( -f "$dir/$word[1]")
1360 } ## end for ( my ($adelay) = 0 ...
1362 msg( "mail,log", "No upload found: $word[1]\n" );
1365 msg( "mail,log", "unknown command $word[0]\n" );
1367 } ## end foreach $cmd (@cmds)
1370 "-- End of $main::current_incoming_short/$commands processing\n" );
1371 } ## end sub process_commands($)
1373 sub age_delayed_queues() {
1374 for ( my ($adelay) = 0 ; $adelay <= $conf::max_delayed ; $adelay++ ) {
1375 my ($dir) = sprintf( "$conf::targetdir_delayed", $adelay );
1377 if ( $adelay == 0 ) {
1378 $target_dir = $conf::targetdir;
1380 $target_dir = sprintf( "$conf::targetdir_delayed", $adelay - 1 );
1382 for my $achanges (<$dir/*.changes>) {
1383 my $mtime = ( stat($achanges) )[9];
1384 if ( $mtime + 24 * 60 * 60 <= time || $adelay == 0 ) {
1385 utime undef, undef, ($achanges);
1386 my @thesefiles = ( $achanges =~ m,.*/([^/]*), );
1387 push( @thesefiles, get_filelist_from_known_good_changes($achanges) );
1388 for my $afile (@thesefiles) {
1389 if ( !rename "$dir/$afile", "$target_dir/$afile" ) {
1390 msg( "log", "rename: $!\n" );
1392 msg( "log", "$afile moved to $target_dir\n" );
1394 } ## end for my $afile (@thesefiles)
1395 } ## end if ( $mtime + 24 * 60 ...
1396 } ## end for my $achanges (<$dir/*.changes>)
1397 } ## end for ( my ($adelay) = 0 ...
1398 } ## end sub age_delayed_queues()
1401 # check if a file is already on target
1403 sub is_on_target($\@) {
1405 my $filelist = shift;
1409 if ( $conf::upload_method eq "ssh" ) {
1410 ( $msg, $stat ) = ssh_cmd("ls -l $file");
1411 } elsif ( $conf::upload_method eq "ftp" ) {
1413 ( $msg, $err ) = ftp_cmd( "dir", $file );
1419 $msg = "ls: no such file\n";
1422 $msg = join( "\n", @$msg );
1425 my @allfiles = ($file);
1426 push( @allfiles, @$filelist );
1428 $msg = "no such file";
1429 for my $afile (@allfiles) {
1430 if ( -f "$conf::targetdir/$afile" ) {
1434 } ## end for my $afile (@allfiles)
1435 for ( my ($adelay) = 0 ;
1436 $adelay <= $conf::max_delayed && $stat ;
1439 for my $afile (@allfiles) {
1441 -f ( sprintf( "$conf::targetdir_delayed", $adelay ) . "/$afile" ) )
1444 $msg = sprintf( "%d-day", $adelay ) . "/$afile";
1445 } ## end if ( -f ( sprintf( "$conf::targetdir_delayed"...
1446 } ## end for my $afile (@allfiles)
1447 } ## end for ( my ($adelay) = 0 ...
1448 } ## end else [ if ( $conf::upload_method...
1450 debug("exit status: $stat, output was: $msg");
1452 return "" if $stat && $msg =~ /no such file/i; # file not present
1453 msg( "log", "strange ls -l output on target:\n", $msg ), return ""
1454 if $stat || $@; # some other error, but still try to upload
1456 # ls -l returned 0 -> file already there
1457 $msg =~ s/\s\s+/ /g; # make multiple spaces into one, to save space
1459 } ## end sub is_on_target($\@)
1462 # copy a list of files to target
1464 sub copy_to_target(@) {
1466 my ( @md5sum, @expected_files, $sum, $name, $msgs, $stat );
1469 write_status_file() if $conf::statusdelay;
1472 if ( $conf::upload_method eq "ssh" ) {
1473 ( $msgs, $stat ) = scp_cmd(@files);
1475 } elsif ( $conf::upload_method eq "ftp" ) {
1477 if ( !$main::FTP_chan->cwd($main::current_targetdir) ) {
1479 "Can't cd to $main::current_targetdir on $conf::target\n" );
1482 foreach $file (@files) {
1483 ( $rv, $msgs ) = ftp_cmd( "put", $file );
1488 local_cmd( "$conf::cp @files $main::current_targetdir", 'NOCD' );
1492 # check md5sums or sizes on target against our own
1493 my $have_md5sums = 1;
1494 if ($conf::check_md5sum) {
1495 if ( $conf::upload_method eq "ssh" ) {
1496 ( $msgs, $stat ) = ssh_cmd("md5sum @files");
1498 @md5sum = split( "\n", $msgs );
1499 } elsif ( $conf::upload_method eq "ftp" ) {
1500 my ( $rv, $err, $file );
1501 foreach $file (@files) {
1502 ( $rv, $err ) = ftp_cmd( "quot", "site", "md5sum", $file );
1504 next if ftp_code() == 550; # file not found
1505 if ( ftp_code() == 500 ) { # unimplemented
1507 goto get_sizes_instead;
1512 chomp( my $t = ftp_response() );
1513 push( @md5sum, $t );
1514 } ## end foreach $file (@files)
1515 if ( !$have_md5sums ) {
1517 foreach $file (@files) {
1518 ( $rv, $err ) = ftp_cmd( "size", $file );
1520 next if ftp_code() == 550; # file not found
1524 push( @md5sum, "$rv $file" );
1525 } ## end foreach $file (@files)
1526 } ## end if ( !$have_md5sums )
1528 ( $msgs, $stat ) = local_cmd("$conf::md5sum @files");
1530 @md5sum = split( "\n", $msgs );
1533 @expected_files = @files;
1536 ( $sum, $name ) = split;
1537 next if !grep { $_ eq $name } @files; # a file we didn't upload??
1538 next if $sum eq "md5sum:"; # looks like an error message
1539 if ( ( $have_md5sums && $sum ne md5sum($name) )
1540 || ( !$have_md5sums && $sum != ( -s $name ) ) )
1544 "Upload of $name to $conf::target failed ",
1545 "(" . ( $have_md5sums ? "md5sum" : "size" ) . " mismatch)\n"
1548 } ## end if ( ( $have_md5sums &&...
1550 # seen that file, remove it from expect list
1551 @expected_files = map { $_ eq $name ? () : $_ } @expected_files;
1552 } ## end foreach (@md5sum)
1553 if (@expected_files) {
1554 msg( "log,mail", "Failed to upload the files\n" );
1555 msg( "log,mail", " ", join( ", ", @expected_files ), "\n" );
1556 msg( "log,mail", "(Not present on target after upload)\n" );
1558 } ## end if (@expected_files)
1559 } ## end if ($conf::check_md5sum)
1561 if ($conf::chmod_on_target) {
1563 # change file's mode explicitly to 644 on target
1564 if ( $conf::upload_method eq "ssh" ) {
1565 ( $msgs, $stat ) = ssh_cmd("chmod 644 @files");
1567 } elsif ( $conf::upload_method eq "ftp" ) {
1569 foreach $file (@files) {
1570 ( $rv, $msgs ) = ftp_cmd( "quot", "site", "chmod", "644", $file );
1571 msg( "log", "Can't chmod $file on target:\n$msgs" )
1574 } ## end foreach $file (@files)
1576 ( $msgs, $stat ) = local_cmd("$conf::chmod 644 @files");
1579 } ## end if ($conf::chmod_on_target)
1582 write_status_file() if $conf::statusdelay;
1587 "Upload to $conf::target failed",
1588 $? ? ", last exit status " . sprintf( "%s", $? >> 8 ) : "", "\n" );
1589 msg( "log,mail", "Error messages:\n", $msgs )
1592 # If "permission denied" was among the errors, test if the incoming is
1594 if ( $msgs =~ /(permission denied|read-?only file)/i ) {
1595 if ( !check_incoming_writable() ) {
1596 msg( "log,mail", "(The incoming directory seems to be ",
1599 } ## end if ( $msgs =~ /(permission denied|read-?only file)/i)
1601 # remove bad files or an incomplete upload on target
1602 if ( $conf::upload_method eq "ssh" ) {
1603 ssh_cmd("rm -f @files");
1604 } elsif ( $conf::upload_method eq "ftp" ) {
1606 foreach $file (@files) {
1608 ( $rv, $err ) = ftp_cmd( "delete", $file );
1609 msg( "log", "Can't delete $file on target:\n$err" )
1611 } ## end foreach $file (@files)
1613 my @tfiles = map { "$main::current_targetdir/$_" } @files;
1614 debug("executing unlink(@tfiles)");
1618 write_status_file() if $conf::statusdelay;
1620 } ## end sub copy_to_target(@)
1623 # check if a file is correctly signed with PGP
1634 if ( -x $conf::gpg ) {
1635 debug( "executing $conf::gpg --no-options --batch "
1636 . "--no-default-keyring --always-trust "
1638 . join( " --keyring ", @conf::keyrings )
1639 . " --verify '$file'" );
1642 "$conf::gpg --no-options --batch "
1643 . "--no-default-keyring --always-trust "
1645 . join( " --keyring ", @conf::keyrings )
1646 . " --verify '$file'"
1651 msg( "log", "Can't open pipe to $conf::gpg: $!\n" );
1652 return "LOCAL ERROR";
1653 } ## end if ( !open( PIPE, "$conf::gpg --no-options --batch "...
1654 $output .= $_ while (<PIPE>);
1657 } ## end if ( -x $conf::gpg )
1660 msg( "log,mail", "GnuPG signature check failed on $file\n" );
1661 msg( "mail", $output );
1662 msg( "log,mail", "(Exit status ", $stat >> 8, ")\n" );
1666 $output =~ /^(gpg: )?good signature from (user )?"(.*)"\.?$/im;
1667 ( $signator = $3 ) ||= "unknown signator";
1669 debug("GnuPG signature ok (by $signator)");
1672 } ## end sub pgp_check($)
1674 # ---------------------------------------------------------------------------
1676 # ---------------------------------------------------------------------------
1679 # fork a subprocess that watches the 'status' FIFO
1681 # that process blocks until someone opens the FIFO, then sends a
1682 # signal (SIGUSR1) to the main process, expects
1684 sub fork_statusd() {
1690 $statusd_pid = open( STATUSD, "|-" );
1691 die "cannot fork: $!\n" if !defined($statusd_pid);
1693 # parent just returns
1695 msg( "log", "forked status daemon (pid $statusd_pid)\n" );
1696 return $statusd_pid;
1699 # child: the status FIFO daemon
1701 # ignore SIGPIPE here, in case some closes the FIFO without completely
1703 $SIG{"PIPE"} = "IGNORE";
1705 # also ignore SIGCLD, we don't want to inherit the restart-statusd handler
1707 $SIG{"CHLD"} = "DEFAULT";
1709 rm($conf::statusfile);
1710 $errs = `$conf::mkfifo $conf::statusfile`;
1711 die "$main::progname: cannot create named pipe $conf::statusfile: $errs"
1713 chmod( 0644, $conf::statusfile )
1714 or die "Cannot set modes of $conf::statusfile: $!\n";
1716 # close log file, so that log rotating works
1722 my ( $status, $mup, $incw, $ds, $next_run, $last_ping, $currch, $l );
1724 # open the FIFO for writing; this blocks until someone (probably ftpd)
1725 # opens it for reading
1726 open( STATFIFO, ">$conf::statusfile" )
1727 or die "Cannot open $conf::statusfile\n";
1730 # tell main daemon to send us status infos
1731 kill( $main::signo{"USR1"}, $main_pid );
1733 # get the infos from stdin; must loop until enough bytes received!
1734 my $expect_len = 3 + 2 * STATNUM_LEN + STATSTR_LEN;
1735 for ( $status = "" ; ( $l = length($status) ) < $expect_len ; ) {
1736 sysread( STDIN, $status, $expect_len - $l, $l );
1739 # disassemble the status byte stream
1745 [ next_run => STATNUM_LEN ],
1746 [ last_ping => STATNUM_LEN ],
1747 [ currch => STATSTR_LEN ]
1750 eval "\$$_->[0] = substr( \$status, $pos, $_->[1] );";
1752 } ## end foreach ( [ mup => 1 ], [ incw...
1753 $currch =~ s/\n+//g;
1755 print_status( $mup, $incw, $ds, $next_run, $last_ping, $currch );
1758 # This sleep is necessary so that we can't reopen the FIFO
1759 # immediately, in case the reader hasn't closed it yet if we get to
1760 # the open again. Is there a better solution for this??
1763 } ## end sub fork_statusd()
1766 # update the status file, in case we use a plain file and not a FIFO
1768 sub write_status_file() {
1770 return if !$conf::statusfile;
1772 open( STATFILE, ">$conf::statusfile" )
1773 or ( msg( "log", "Could not open $conf::statusfile: $!\n" ), return );
1774 my $oldsel = select(STATFILE);
1777 $main::target_up, $main::incoming_writable,
1778 $main::dstat, $main::next_run,
1779 $main::last_ping_time, $main::current_changes
1784 } ## end sub write_status_file()
1786 sub print_status($$$$$$) {
1790 my $next_run = shift;
1791 my $last_ping = shift;
1796 ( $version = 'Release: 0.9 $Revision: 1.51 $' ) =~ s/\$ ?//g;
1797 print "debianqueued $version\n";
1799 $approx = $conf::statusdelay ? "approx. " : "";
1801 if ( $mup eq "0" ) {
1802 print "$conf::target is down, queue pausing\n";
1804 } elsif ( $conf::upload_method ne "copy" ) {
1805 print "$conf::target seems to be up, last ping $approx",
1806 print_time( time - $last_ping ), " ago\n";
1809 if ( $incw eq "0" ) {
1810 print "The incoming directory is not writable, queue pausing\n";
1815 print "Next queue check in $approx", print_time( $next_run - time ), "\n";
1817 } elsif ( $ds eq "c" ) {
1818 print "Checking queue directory\n";
1819 } elsif ( $ds eq "u" ) {
1820 print "Uploading to $conf::target\n";
1822 print "Bad status data from daemon: \"$mup$incw$ds\"\n";
1826 print "Current job is $currch\n" if $currch;
1827 } ## end sub print_status($$$$$$)
1830 # format a number for sending to statusd (fixed length STATNUM_LEN)
1832 sub format_status_num(\$$) {
1836 $$varref = sprintf "%" . STATNUM_LEN . "d", $num;
1837 } ## end sub format_status_num(\$$)
1840 # format a string for sending to statusd (fixed length STATSTR_LEN)
1842 sub format_status_str(\$$) {
1846 $$varref = substr( $str, 0, STATSTR_LEN );
1847 $$varref .= "\n" x ( STATSTR_LEN - length($$varref) );
1848 } ## end sub format_status_str(\$$)
1851 # send a status string to the status daemon
1853 # Avoid all operations that could call malloc() here! Most libc
1854 # implementations aren't reentrant, so we may not call it from a
1855 # signal handler. So use only already-defined variables.
1858 local $! = 0; # preserve errno
1860 # re-setup handler, in case we have broken SysV signals
1861 $SIG{"USR1"} = \&send_status;
1863 syswrite( STATUSD, $main::target_up, 1 );
1864 syswrite( STATUSD, $main::incoming_writable, 1 );
1865 syswrite( STATUSD, $main::dstat, 1 );
1866 syswrite( STATUSD, $main::next_run, STATNUM_LEN );
1867 syswrite( STATUSD, $main::last_ping_time, STATNUM_LEN );
1868 syswrite( STATUSD, $main::current_changes, STATSTR_LEN );
1869 } ## end sub send_status()
1871 # ---------------------------------------------------------------------------
1873 # ---------------------------------------------------------------------------
1876 # open FTP connection to target host if not already open
1880 if ($main::FTP_chan) {
1882 # is already open, but might have timed out; test with a cwd
1883 return $main::FTP_chan
1884 if $main::FTP_chan->cwd($main::current_targetdir);
1886 # cwd didn't work, channel is closed, try to reopen it
1887 $main::FTP_chan = undef;
1888 } ## end if ($main::FTP_chan)
1895 Debug => $conf::ftpdebug,
1896 Timeout => $conf::ftptimeout,
1902 msg( "log,mail", "Cannot open FTP server $conf::target\n" );
1904 } ## end if ( !( $main::FTP_chan...
1905 if ( !$main::FTP_chan->login() ) {
1906 msg( "log,mail", "Anonymous login on FTP server $conf::target failed\n" );
1909 if ( !$main::FTP_chan->binary() ) {
1910 msg( "log,mail", "Can't set binary FTP mode on $conf::target\n" );
1913 if ( !$main::FTP_chan->cwd($main::current_targetdir) ) {
1915 "Can't cd to $main::current_targetdir on $conf::target\n" );
1918 debug("opened FTP channel to $conf::target");
1922 $main::FTP_chan = undef;
1924 } ## end sub ftp_open()
1929 my $direct_resp_cmd = ( $cmd eq "quot" );
1931 debug( "executing FTP::$cmd(" . join( ", ", @_ ) . ")" );
1932 $SIG{"ALRM"} = sub { die "timeout in FTP::$cmd\n" };
1933 alarm($conf::remote_timeout);
1934 eval { $rv = $main::FTP_chan->$cmd(@_); };
1937 $rv = ( ftp_code() =~ /^2/ ) ? 1 : 0 if $direct_resp_cmd;
1942 $err = ftp_response();
1944 return ( $rv, $err );
1945 } ## end sub ftp_cmd($@)
1948 if ($main::FTP_chan) {
1949 $main::FTP_chan->quit();
1950 $main::FTP_chan = undef;
1953 } ## end sub ftp_close()
1955 sub ftp_response() {
1956 return join( '', @{ ${*$main::FTP_chan}{'net_cmd_resp'} } );
1960 return ${*$main::FTP_chan}{'net_cmd_code'};
1964 my $code = ftp_code();
1965 return ( $code =~ /^[45]/ ) ? 1 : 0;
1968 # ---------------------------------------------------------------------------
1970 # ---------------------------------------------------------------------------
1976 my $ecmd = "$conf::ssh $conf::ssh_options $conf::target "
1977 . "-l $conf::targetlogin \'cd $main::current_targetdir; $cmd\'";
1978 debug("executing $ecmd");
1979 $SIG{"ALRM"} = sub { die "timeout in ssh command\n" };
1980 alarm($conf::remote_timeout);
1981 eval { $msg = `$ecmd 2>&1`; };
1989 return ( $msg, $stat );
1990 } ## end sub ssh_cmd($)
1995 my $ecmd = "$conf::scp $conf::ssh_options @_ "
1996 . "$conf::targetlogin\@$conf::target:$main::current_targetdir";
1997 debug("executing $ecmd");
1998 $SIG{"ALRM"} = sub { die "timeout in scp\n" };
1999 alarm($conf::remote_timeout);
2000 eval { $msg = `$ecmd 2>&1`; };
2008 return ( $msg, $stat );
2009 } ## end sub scp_cmd(@)
2011 sub local_cmd($;$) {
2016 my $ecmd = ( $nocd ? "" : "cd $main::current_targetdir; " ) . $cmd;
2017 debug("executing $ecmd");
2018 $msg = `($ecmd) 2>&1`;
2020 return ( $msg, $stat );
2022 } ## end sub local_cmd($;$)
2025 # check if target is alive (code stolen from Net::Ping.pm)
2027 sub check_alive(;$) {
2028 my $timeout = shift;
2029 my ( $saddr, $ret, $target_ip );
2032 if ( $conf::upload_method eq "copy" ) {
2033 format_status_num( $main::last_ping_time, time );
2034 $main::target_up = 1;
2040 if ( !( $target_ip = ( gethostbyname($conf::target) )[4] ) ) {
2041 msg( "log", "Cannot get IP address of $conf::target\n" );
2045 $saddr = pack( 'S n a4 x8', AF_INET, $main::echo_port, $target_ip );
2046 $SIG{'ALRM'} = sub { die };
2049 $ret = $main::tcp_proto; # avoid warnings about unused variable
2052 return unless socket( PINGSOCK, PF_INET, SOCK_STREAM, $main::tcp_proto );
2053 return unless connect( PINGSOCK, $saddr );
2058 msg( "log", "pinging $conf::target: " . ( $ret ? "ok" : "down" ) . "\n" );
2060 $main::target_up = $ret ? "1" : "0";
2061 format_status_num( $main::last_ping_time, time );
2062 write_status_file() if $conf::statusdelay;
2063 } ## end sub check_alive(;$)
2066 # check if incoming dir on target is writable
2068 sub check_incoming_writable() {
2069 my $testfile = ".debianqueued-testfile";
2072 if ( $conf::upload_method eq "ssh" ) {
2074 ssh_cmd( "rm -f $testfile; touch $testfile; " . "rm -f $testfile" );
2075 } elsif ( $conf::upload_method eq "ftp" ) {
2076 my $file = "junk-for-writable-test-" . format_time();
2077 $file =~ s/[ :.]/-/g;
2079 open( F, ">$file" );
2082 ( $rv, $msg ) = ftp_cmd( "put", $file );
2084 $msg = "" if !defined $msg;
2086 ftp_cmd( "delete", $file );
2087 } elsif ( $conf::upload_method eq "copy" ) {
2089 local_cmd( "rm -f $testfile; touch $testfile; " . "rm -f $testfile" );
2092 debug("exit status: $stat, output was: $msg");
2096 # change incoming_writable only if ssh didn't return an error
2097 $main::incoming_writable =
2098 ( $msg =~ /(permission denied|read-?only file|cannot create)/i )
2102 debug("local error, keeping old status");
2104 debug("incoming_writable = $main::incoming_writable");
2105 write_status_file() if $conf::statusdelay;
2106 return $main::incoming_writable;
2107 } ## end sub check_incoming_writable()
2110 # remove a list of files, log failing ones
2116 ( unlink $_ and ++$done )
2118 or msg( "log", "Could not delete $_: $!\n" );
2124 # get md5 checksum of a file
2130 chomp( $line = `$conf::md5sum $file` );
2131 debug( "md5sum($file): ",
2132 $? ? "exit status $?"
2133 : $line =~ /^(\S+)/ ? $1
2135 return $? ? "" : $line =~ /^(\S+)/ ? $1 : "";
2136 } ## end sub md5sum($)
2139 # check if a file probably belongs to a Debian upload
2141 sub is_debian_file($) {
2143 return $file =~ /\.(deb|dsc|(diff|tar)\.gz)$/
2144 && $file !~ /\.orig\.tar\.gz/;
2148 # try to extract maintainer email address from some a non-.changes file
2149 # return "" if not possible
2151 sub get_maintainer($) {
2153 my $maintainer = "";
2156 if ( $file =~ /\.diff\.gz$/ ) {
2159 open( F, "$conf::gzip -dc '$file' 2>/dev/null |" ) or return "";
2162 # look for header line of a file */debian/control
2163 last if m,^\+\+\+\s+[^/]+/debian/control(\s+|$),;
2166 last if /^---/; # end of control file patch, no Maintainer: found
2167 # inside control file patch look for Maintainer: field
2168 $maintainer = $1, last if /^\+Maintainer:\s*(.*)$/i;
2170 while (<F>) { } # read to end of file to avoid broken pipe
2171 close(F) or return "";
2172 } elsif ( $file =~ /\.(deb|dsc|tar\.gz)$/ ) {
2173 if ( $file =~ /\.deb$/ && $conf::ar ) {
2175 # extract control.tar.gz from .deb with ar, then let tar extract
2176 # the control file itself
2178 "($conf::ar p '$file' control.tar.gz | "
2179 . "$conf::tar -xOf - "
2180 . "--use-compress-program $conf::gzip "
2181 . "control) 2>/dev/null |"
2183 } elsif ( $file =~ /\.dsc$/ ) {
2185 # just do a plain grep
2186 debug("get_maint: .dsc, no cmd");
2187 open( F, "<$file" ) or return "";
2188 } elsif ( $file =~ /\.tar\.gz$/ ) {
2190 # let tar extract a file */debian/control
2192 "$conf::tar -xOf '$file' "
2193 . "--use-compress-program $conf::gzip "
2194 . "\\*/debian/control 2>&1 |"
2200 $maintainer = $1, last if /^Maintainer:\s*(.*)$/i;
2202 close(F) or return "";
2203 } ## end elsif ( $file =~ /\.(deb|dsc|tar\.gz)$/)
2206 } ## end sub get_maintainer($)
2209 # return a pattern that matches all files that probably belong to one job
2211 sub debian_file_stem($) {
2213 my ( $pkg, $version );
2216 $file =~ s,\.(deb|dsc|changes|(orig\.)?tar\.gz|diff\.gz)$,,;
2218 # if not is *_* (name_version), can't derive a stem and return just
2220 return $file if !( $file =~ /^([^_]+)_([^_]+)/ );
2221 ( $pkg, $version ) = ( $1, $2 );
2223 # strip Debian revision from version
2224 $version =~ s/^(.*)-[\d.+-]+$/$1/;
2226 return "${pkg}_${version}*";
2227 } ## end sub debian_file_stem($)
2230 # output a messages to several destinations
2232 # first arg is a comma-separated list of destinations; valid are "log"
2233 # and "mail"; rest is stuff to be printed, just as with print
2236 my @dest = split( ',', shift );
2238 if ( grep /log/, @dest ) {
2239 my $now = format_time();
2240 print LOG "$now ", @_;
2243 if ( grep /mail/, @dest ) {
2244 $main::mail_text .= join( '', @_ );
2246 } ## end sub msg($@)
2249 # print a debug messages, if $debug is true
2252 return if !$conf::debug;
2253 my $now = format_time();
2254 print LOG "$now DEBUG ", @_, "\n";
2258 # intialize the "mail" destination of msg() (this clears text,
2259 # address, subject, ...)
2264 $main::mail_addr = "";
2265 $main::mail_text = "";
2266 %main::packages = ();
2267 $main::mail_subject = $file ? "Processing of $file" : "";
2268 } ## end sub init_mail(;$)
2271 # finalize mail to be sent from msg(): check if something present, and
2276 debug("No mail for $main::mail_addr")
2277 if $main::mail_addr && !$main::mail_text;
2278 return unless $main::mail_addr && $main::mail_text;
2280 if ( !send_mail( $main::mail_addr, $main::mail_subject, $main::mail_text ) )
2283 # store this mail in memory so it isn't lost if executing sendmail
2286 @main::stored_mails,
2288 addr => $main::mail_addr,
2289 subject => $main::mail_subject,
2290 text => $main::mail_text
2293 } ## end if ( !send_mail( $main::mail_addr...
2296 # try to send out stored mails
2298 while ( $mailref = shift(@main::stored_mails) ) {
2300 !send_mail( $mailref->{'addr'}, $mailref->{'subject'},
2301 $mailref->{'text'} )
2304 unshift( @main::stored_mails, $mailref );
2306 } ## end if ( !send_mail( $mailref...
2307 } ## end while ( $mailref = shift(...
2308 } ## end sub finish_mail()
2313 sub send_mail($$$) {
2315 my $subject = shift;
2319 keys %main::packages ? join( ' ', keys %main::packages ) : "";
2323 unless ( defined($Email::Send::Sendmail::SENDMAIL) ) {
2324 $Email::Send::Sendmail::SENDMAIL = $conf::mail;
2327 my $date = sprintf "%s",
2328 strftime( "%a, %d %b %Y %T %z", ( localtime(time) ) );
2329 my $message = <<__MESSAGE__;
2331 From: Archive Administrator <dak\@ftp-master.debian.org>
2337 if ( length $package ) {
2338 $message .= "X-Debian-Package: $package\n";
2341 $message .= "\n$text";
2342 $message .= "\nGreetings,\n\n\tYour Debian queue daemon\n";
2344 my $mail = Email::Send->new;
2345 for (qw[Sendmail SMTP]) {
2346 $mail->mailer($_) and last if $mail->mailer_available($_);
2349 my $ret = $mail->send($message);
2350 if ( $ret && $ret !~ /Message sent|success/ ) {
2355 } ## end sub send_mail($$$)
2358 # try to find a mail address for a name in the keyrings
2360 sub try_to_get_mail_addr($$) {
2362 my $listref = shift;
2366 "$conf::gpg --no-options --batch --no-default-keyring "
2367 . "--always-trust --keyring "
2368 . join( " --keyring ", @conf::keyrings )
2372 if ( /^pub / && / $name / ) {
2374 push( @$listref, $1 );
2376 } ## end while (<F>)
2379 return ( @$listref >= 1 ) ? $listref->[0] : "";
2380 } ## end sub try_to_get_mail_addr($$)
2383 # return current time as string
2388 # omit weekday and year for brevity
2389 ( $t = localtime ) =~ /^\w+\s(.*)\s\d+$/;
2391 } ## end sub format_time()
2395 my $hours = int( $secs / ( 60 * 60 ) );
2397 $secs -= $hours * 60 * 60;
2398 return sprintf "%d:%02d:%02d", $hours, int( $secs / 60 ), $secs % 60;
2399 } ## end sub print_time($)
2402 # block some signals during queue processing
2404 # This is just to avoid data inconsistency or uploads being aborted in the
2405 # middle. Only "soft" signals are blocked, i.e. SIGINT and SIGTERM, try harder
2406 # ones if you really want to kill the daemon at once.
2408 sub block_signals() {
2409 POSIX::sigprocmask( SIG_BLOCK, $main::block_sigset );
2412 sub unblock_signals() {
2413 POSIX::sigprocmask( SIG_UNBLOCK, $main::block_sigset );
2417 # process SIGHUP: close log file and reopen it (for logfile cycling)
2424 open( LOG, ">>$conf::logfile" )
2425 or die "Cannot open my logfile $conf::logfile: $!\n";
2426 chmod( 0644, $conf::logfile )
2427 or msg( "log", "Cannot set modes of $conf::logfile: $!\n" );
2428 select( ( select(LOG), $| = 1 )[0] );
2430 open( STDOUT, ">&LOG" )
2432 "$main::progname: Can't redirect stdout to " . "$conf::logfile: $!\n" );
2433 open( STDERR, ">&LOG" )
2435 "$main::progname: Can't redirect stderr to " . "$conf::logfile: $!\n" );
2436 msg( "log", "Restart after SIGHUP\n" );
2437 } ## end sub close_log($)
2440 # process SIGCHLD: check if it was our statusd process
2445 # reap statusd, so that it's no zombie when we try to kill(0) it
2446 waitpid( $main::statusd_pid, WNOHANG );
2448 # Uncomment the following line if your Perl uses unreliable System V signal
2449 # (i.e. if handlers reset to default if the signal is delivered).
2450 # (Unfortunately, the re-setup can't be done in any case, since on some
2451 # systems this will cause the SIGCHLD to be delivered again if there are
2452 # still unreaped children :-(( )
2454 # $SIG{"CHLD"} = \&kid_died; # resetup handler for SysV
2455 } ## end sub kid_died($)
2457 sub restart_statusd() {
2459 # restart statusd if it died
2460 if ( !kill( 0, $main::statusd_pid ) ) {
2461 close(STATUSD); # close out pipe end
2462 $main::statusd_pid = fork_statusd();
2464 } ## end sub restart_statusd()
2467 # process a fatal signal: cleanup and exit
2469 sub fatal_signal($) {
2470 my $signame = shift;
2473 # avoid recursions of fatal_signal in case of BSD signals
2474 foreach $sig (qw( ILL ABRT BUS FPE SEGV PIPE )) {
2475 $SIG{$sig} = "DEFAULT";
2478 if ( $$ == $main::maind_pid ) {
2480 # only the main daemon should do this
2481 kill( $main::signo{"TERM"}, $main::statusd_pid )
2482 if defined $main::statusd_pid;
2483 unlink( $conf::statusfile, $conf::pidfile );
2484 } ## end if ( $$ == $main::maind_pid)
2485 msg( "log", "Caught SIG$signame -- exiting (pid $$)\n" );
2487 } ## end sub fatal_signal($)