dqueued-watcher 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. #!/usr/bin/perl -w
  2. #
  3. # dqueued-watcher -- for regularily watching the queue daemon
  4. #
  5. # This script is intended to check periodically (e.g. started by cron) that
  6. # everything is ok with debianqueued. If the daemon isn't running, it notifies
  7. # the maintainer. It also checks if a new Debian keyring is available (in a
  8. # Debian mirror aera, f.i.) and then updates the keyring used by debianqueued.
  9. #
  10. # Copyright (C) 1997 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
  11. #
  12. # This program is free software. You can redistribute it and/or
  13. # modify it under the terms of the GNU General Public License as
  14. # published by the Free Software Foundation: either version 2 or
  15. # (at your option) any later version.
  16. # This program comes with ABSOLUTELY NO WARRANTY!
  17. #
  18. # $Id: dqueued-watcher,v 1.28 1999/07/08 09:43:22 ftplinux Exp $
  19. #
  20. # $Log: dqueued-watcher,v $
  21. # Revision 1.28 1999/07/08 09:43:22 ftplinux
  22. # Bumped release number to 0.9
  23. #
  24. # Revision 1.27 1999/07/07 11:58:22 ftplinux
  25. # Also update gpg keyring if $conf::gpg_keyring is set.
  26. #
  27. # Revision 1.26 1998/07/06 14:24:36 ftplinux
  28. # Some changes to handle debian-keyring.tar.gz files which expand to a
  29. # directory including a date.
  30. #
  31. # Revision 1.25 1998/05/14 14:21:45 ftplinux
  32. # Bumped release number to 0.8
  33. #
  34. # Revision 1.24 1998/03/30 12:31:05 ftplinux
  35. # Don't count "already reported" or "ignored for now" errors as .changes errors.
  36. # Also list files for several error types.
  37. # Also print out names of processed jobs.
  38. #
  39. # Revision 1.23 1998/03/30 11:27:37 ftplinux
  40. # If called with args, make summaries for the log files given.
  41. # make_summary: New arg $to_stdout, for printing report directly.
  42. #
  43. # Revision 1.22 1998/03/23 14:05:15 ftplinux
  44. # Bumped release number to 0.7
  45. #
  46. # Revision 1.21 1997/12/16 13:19:29 ftplinux
  47. # Bumped release number to 0.6
  48. #
  49. # Revision 1.20 1997/11/20 15:18:48 ftplinux
  50. # Bumped release number to 0.5
  51. #
  52. # Revision 1.19 1997/10/31 12:26:31 ftplinux
  53. # Again added new counters in make_summary: suspicious_files,
  54. # transient_changes_errs.
  55. # Extended tests for bad_changes.
  56. # Quotes in pattern seem not to work, replaced by '.'.
  57. #
  58. # Revision 1.18 1997/10/30 14:17:32 ftplinux
  59. # In make_summary, implemented some new counters for command files.
  60. #
  61. # Revision 1.17 1997/10/17 09:39:09 ftplinux
  62. # Fixed wrong args to plural_s
  63. #
  64. # Revision 1.16 1997/09/25 11:20:42 ftplinux
  65. # Bumped release number to 0.4
  66. #
  67. # Revision 1.15 1997/09/17 12:16:33 ftplinux
  68. # Added writing summaries to a file
  69. #
  70. # Revision 1.14 1997/09/16 11:39:29 ftplinux
  71. # In make_summary, initialize all counters to avoid warnings about uninited
  72. # values.
  73. #
  74. # Revision 1.13 1997/09/16 10:53:36 ftplinux
  75. # Made logging more verbose in queued and dqueued-watcher
  76. #
  77. # Revision 1.12 1997/08/18 13:07:15 ftplinux
  78. # Implemented summary mails
  79. #
  80. # Revision 1.11 1997/08/18 12:11:44 ftplinux
  81. # Replaced timegm by timelocal in parse_date; times in log file are
  82. # local times...
  83. #
  84. # Revision 1.10 1997/08/18 11:27:20 ftplinux
  85. # Revised age calculation of log file for rotating
  86. #
  87. # Revision 1.9 1997/08/12 09:54:40 ftplinux
  88. # Bumped release number
  89. #
  90. # Revision 1.8 1997/08/11 12:49:10 ftplinux
  91. # Implemented logfile rotating
  92. #
  93. # Revision 1.7 1997/07/28 13:20:38 ftplinux
  94. # Added release numner to startup message
  95. #
  96. # Revision 1.6 1997/07/25 10:23:04 ftplinux
  97. # Made SIGCHLD handling more portable between perl versions
  98. #
  99. # Revision 1.5 1997/07/09 10:13:55 ftplinux
  100. # Alternative implementation of status file as plain file (not FIFO), because
  101. # standard wu-ftpd doesn't allow retrieval of non-regular files. New config
  102. # option $statusdelay for this.
  103. #
  104. # Revision 1.4 1997/07/08 08:39:56 ftplinux
  105. # Need to remove -z from tar options if --use-compress-program
  106. #
  107. # Revision 1.3 1997/07/08 08:34:15 ftplinux
  108. # If dqueued-watcher runs as cron job, $PATH might not contain gzip. Use extra
  109. # --use-compress-program option to tar, and new config var $gzip.
  110. #
  111. # Revision 1.2 1997/07/03 13:05:57 ftplinux
  112. # Added some verbosity if stdin is a terminal
  113. #
  114. # Revision 1.1.1.1 1997/07/03 12:54:59 ftplinux
  115. # Import initial sources
  116. #
  117. #
  118. require 5.002;
  119. use strict;
  120. use POSIX;
  121. require "timelocal.pl";
  122. sub LINEWIDTH { 79 }
  123. my $batchmode = !(-t STDIN);
  124. $main::curr_year = (localtime)[5];
  125. do {
  126. my $version;
  127. ($version = 'Release: 0.9 $Revision: 1.28 $ $Date: 1999/07/08 09:43:22 $ $Author: ftplinux $') =~ s/\$ ?//g;
  128. print "dqueued-watcher $version\n" if !$batchmode;
  129. };
  130. package conf;
  131. ($conf::queued_dir = (($0 !~ m,^/,) ? POSIX::getcwd()."/" : "") . $0)
  132. =~ s,/[^/]+$,,;
  133. require "$conf::queued_dir/config";
  134. my # avoid spurious warnings about unused vars
  135. $junk = $conf::gzip;
  136. $junk = $conf::maintainer_mail;
  137. $junk = $conf::log_age;
  138. package main;
  139. # prototypes
  140. sub check_daemon();
  141. sub daemon_running();
  142. sub rotate_log();
  143. sub logf($);
  144. sub parse_date($);
  145. sub make_summary($$$);
  146. sub stimes($);
  147. sub plural_s($);
  148. sub format_list($@);
  149. sub mail($@);
  150. sub logger(@);
  151. sub format_time();
  152. # the main program:
  153. if (@ARGV) {
  154. # with arguments, make summaries (to stdout) for the logfiles given
  155. foreach (@ARGV) {
  156. make_summary( 1, undef, $_ );
  157. }
  158. }
  159. else {
  160. # without args, just do normal maintainance actions
  161. check_daemon();
  162. rotate_log();
  163. }
  164. exit 0;
  165. #
  166. # check if the daemon is running, notify maintainer if not
  167. #
  168. sub check_daemon() {
  169. my $daemon_down_text = "Daemon is not running\n";
  170. my( $line, $reported );
  171. if (daemon_running()) {
  172. print "Daemon is running\n" if !$batchmode;
  173. return;
  174. }
  175. print "Daemon is NOT running!\n" if !$batchmode;
  176. $reported = 0;
  177. if ($conf::statusfile && -f $conf::statusfile && ! -p _ &&
  178. open( STATUSFILE, "<$conf::statusfile" )) {
  179. $line = <STATUSFILE>;
  180. close( STATUSFILE );
  181. $reported = $line eq $daemon_down_text;
  182. }
  183. if (!$reported) {
  184. mail( "debianqueued down",
  185. "The Debian queue daemon isn't running!\n",
  186. "Please start it up again.\n" );
  187. logger( "Found that daemon is not running\n" );
  188. }
  189. # remove unnecessary pid file
  190. # also remove status FIFO, so opening it for reading won't block
  191. # forever
  192. unlink( $conf::pidfile, $conf::statusfile );
  193. # replace status FIFO by a file that tells the user the daemon is down
  194. if ($conf::statusfile) {
  195. open( STATUSFILE, ">$conf::statusfile" )
  196. or die "Can't open $conf::statusfile: $!\n";
  197. print STATUSFILE $daemon_down_text;
  198. close( STATUSFILE );
  199. }
  200. }
  201. #
  202. # check if daemon is running
  203. #
  204. sub daemon_running() {
  205. my $pid;
  206. local( *PIDFILE );
  207. if (open( PIDFILE, "<$conf::pidfile" )) {
  208. chomp( $pid = <PIDFILE> );
  209. close( PIDFILE );
  210. $main::daemon_pid = $pid, return 1 if $pid && kill( 0, $pid );
  211. }
  212. return 0;
  213. }
  214. #
  215. # check if new keyring is available, if yes extract it
  216. #
  217. sub rotate_log() {
  218. my( $first_date, $f1, $f2, $i );
  219. local( *F );
  220. return if !defined $main::daemon_pid || !-f $conf::logfile;
  221. open( F, "<$conf::logfile" ) or die "Can't open $conf::logfile: $!\n";
  222. while( <F> ) {
  223. last if $first_date = parse_date( $_ );
  224. }
  225. close( F );
  226. # Simply don't rotate if nothing couldn't be parsed as date -- probably
  227. # the file is empty.
  228. return if !$first_date;
  229. # assume year-wrap if $first_date is in the future
  230. $first_date -= 365*24*60*60 if $first_date > time;
  231. # don't rotate if first date too young
  232. return if time - $first_date < $conf::log_age*24*60*60;
  233. logger( "Logfile older than $conf::log_age days, rotating\n" );
  234. # remove oldest log
  235. $f1 = logf($conf::log_keep-1);
  236. if (-f $f1) {
  237. unlink( $f1 ) or warn "Can't remove $f1: $!\n";
  238. }
  239. # rename other logs
  240. for( $i = $conf::log_keep-2; $i > 0; --$i ) {
  241. $f1 = logf($i);
  242. $f2 = logf($i+1);
  243. if ($i == 0) {
  244. }
  245. if (-f $f1) {
  246. rename( $f1, $f2 ) or warn "Can't rename $f1 to $f2: $!\n";
  247. }
  248. }
  249. # compress newest log
  250. $f1 = "$conf::logfile.0";
  251. $f2 = "$conf::logfile.1.gz";
  252. if (-f $f1) {
  253. system $conf::gzip, "-9f", $f1
  254. and die "gzip failed on $f1 (status $?)\n";
  255. rename( "$f1.gz", $f2 ) or warn "Can't rename $f1.gz to $f2: $!\n";
  256. }
  257. # rename current log and signal the daemon to open a new logfile
  258. rename( $conf::logfile, $f1 );
  259. kill( 1, $main::daemon_pid );
  260. print "Rotated log files\n" if !$batchmode;
  261. make_summary( 0, $first_date, $f1 )
  262. if $conf::mail_summary || $conf::summary_file;
  263. }
  264. sub logf($) {
  265. my $num = shift;
  266. return sprintf( "$conf::logfile.%d.gz", $num );
  267. }
  268. sub parse_date($) {
  269. my $date = shift;
  270. my( $mon, $day, $hours, $mins, $month, $year, $secs );
  271. my %month_num = ( "jan", 0, "feb", 1, "mar", 2, "apr", 3, "may", 4,
  272. "jun", 5, "jul", 6, "aug", 7, "sep", 8, "oct", 9,
  273. "nov", 10, "dec", 11 );
  274. warn "Invalid date: $date\n", return 0
  275. unless $date =~ /^(\w\w\w)\s+(\d+)\s+(\d+):(\d+):(\d+)\s/;
  276. ($mon, $day, $hours, $mins, $secs) = ($1, $2, $3, $4, $5);
  277. $mon =~ tr/A-Z/a-z/;
  278. return 0 if !exists $month_num{$mon};
  279. $month = $month_num{$mon};
  280. return timelocal( $secs, $mins, $hours, $day, $month, $main::curr_year );
  281. }
  282. sub make_summary($$$) {
  283. my $to_stdout = shift;
  284. my $startdate = shift;
  285. my $file = shift;
  286. my( $starts, $statusd_starts, $suspicious_files, $transient_errs,
  287. $upl_failed, $success, $commands, $rm_cmds, $mv_cmds, $msg,
  288. $uploader );
  289. my( @pgp_fail, %transient_errs, @changes_errs, @removed_changes,
  290. @already_present, @del_stray, %uploaders, %cmd_uploaders );
  291. local( *F );
  292. if (!open( F, "<$file" )) {
  293. mail( "debianqueued summary failed",
  294. "Couldn't open $file to make summary of events." );
  295. return;
  296. }
  297. $starts = $statusd_starts = $suspicious_files = $transient_errs =
  298. $upl_failed = $success = $commands = $rm_cmds = $mv_cmds = 0;
  299. while( <F> ) {
  300. $startdate = parse_date( $_ ) if !$startdate;
  301. ++$starts if /daemon \(pid \d+\) started$/;
  302. ++$statusd_starts if /forked status daemon/;
  303. push( @pgp_fail, $1 )
  304. if /PGP signature check failed on (\S+)/;
  305. ++$suspicious_files if /found suspicious filename/;
  306. ++$transient_errs, ++$transient_errs{$1}
  307. if /(\S+) (doesn.t exist|is too small) \(ignored for now\)/;
  308. push( @changes_errs, $1 )
  309. if (!/\((already reported|ignored for now)\)/ &&
  310. (/(\S+) doesn.t exist/ || /(\S+) has incorrect (size|md5)/)) ||
  311. /(\S+) doesn.t contain a Maintainer: field/ ||
  312. /(\S+) isn.t signed with PGP/ ||
  313. /(\S+) doesn.t mention any files/;
  314. push( @removed_changes, $1 )
  315. if /(\S+) couldn.t be processed for \d+ hours and is now del/ ||
  316. /(\S+) couldn.t be uploaded for \d+ times/;
  317. push( @already_present, $1 )
  318. if /(\S+) is already present on master/;
  319. ++$upl_failed if /Upload to \S+ failed/;
  320. ++$success, push( @{$uploaders{$2}}, $1 )
  321. if /(\S+) processed successfully \(uploader (\S*)\)$/;
  322. push( @del_stray, $1 ) if /Deleted stray file (\S+)/;
  323. ++$commands if /processing .*\.commands$/;
  324. ++$rm_cmds if / > rm /;
  325. ++$mv_cmds if / > mv /;
  326. ++$cmd_uploaders{$1}
  327. if /\(command uploader (\S*)\)$/;
  328. }
  329. close( F );
  330. $msg .= "Queue Daemon Summary from " . localtime($startdate) . " to " .
  331. localtime(time) . ":\n\n";
  332. $msg .= "Daemon started ".stimes($starts)."\n"
  333. if $starts;
  334. $msg .= "Status daemon restarted ".stimes($statusd_starts-$starts)."\n"
  335. if $statusd_starts > $starts;
  336. $msg .= @pgp_fail." job".plural_s(@pgp_fail)." failed PGP check:\n" .
  337. format_list(2,@pgp_fail)
  338. if @pgp_fail;
  339. $msg .= "$suspicious_files file".plural_s($suspicious_files)." with ".
  340. "suspicious names found\n"
  341. if $suspicious_files;
  342. $msg .= "Detected ".$transient_errs." transient error".
  343. plural_s($transient_errs)." in .changes files:\n".
  344. format_list(2,keys %transient_errs)
  345. if $transient_errs;
  346. $msg .= "Detected ".@changes_errs." error".plural_s(@changes_errs).
  347. " in .changes files:\n".format_list(2,@changes_errs)
  348. if @changes_errs;
  349. $msg .= @removed_changes." job".plural_s(@removed_changes).
  350. " removed due to persistent errors:\n".
  351. format_list(2,@removed_changes)
  352. if @removed_changes;
  353. $msg .= @already_present." job".plural_s(@already_present).
  354. " were already present on master:\n".format_list(2,@already_present)
  355. if @already_present;
  356. $msg .= @del_stray." stray file".plural_s(@del_stray)." deleted:\n".
  357. format_list(2,@del_stray)
  358. if @del_stray;
  359. $msg .= "$commands command file".plural_s($commands)." processed\n"
  360. if $commands;
  361. $msg .= " ($rm_cmds rm, $mv_cmds mv commands)\n"
  362. if $rm_cmds || $mv_cmds;
  363. $msg .= "$success job".plural_s($success)." processed successfully\n";
  364. if ($success) {
  365. $msg .= "\nPeople who used the queue:\n";
  366. foreach $uploader ( keys %uploaders ) {
  367. $msg .= " $uploader (".@{$uploaders{$uploader}}."):\n".
  368. format_list(4,@{$uploaders{$uploader}});
  369. }
  370. }
  371. if (%cmd_uploaders) {
  372. $msg .= "\nPeople who used command files:\n";
  373. foreach $uploader ( keys %cmd_uploaders ) {
  374. $msg .= " $uploader ($cmd_uploaders{$uploader})\n";
  375. }
  376. }
  377. if ($to_stdout) {
  378. print $msg;
  379. }
  380. else {
  381. if ($conf::mail_summary) {
  382. mail( "debianqueued summary", $msg );
  383. }
  384. if ($conf::summary_file) {
  385. local( *F );
  386. open( F, ">>$conf::summary_file" ) or
  387. die "Cannot open $conf::summary_file for appending: $!\n";
  388. print F "\n", "-"x78, "\n", $msg;
  389. close( F );
  390. }
  391. }
  392. }
  393. sub stimes($) {
  394. my $num = shift;
  395. return $num == 1 ? "once" : "$num times";
  396. }
  397. sub plural_s($) {
  398. my $num = shift;
  399. return $num == 1 ? "" : "s";
  400. }
  401. sub format_list($@) {
  402. my $indent = shift;
  403. my( $i, $pos, $ret, $item, $len );
  404. $ret = " " x $indent; $pos += $indent;
  405. while( $item = shift ) {
  406. $len = length($item);
  407. $item .= ", ", $len += 2 if @_;
  408. if ($pos+$len > LINEWIDTH) {
  409. $ret .= "\n" . " "x$indent;
  410. $pos = $indent;
  411. }
  412. $ret .= $item;
  413. $pos += $len;
  414. }
  415. $ret .= "\n";
  416. return $ret;
  417. }
  418. #
  419. # send mail to maintainer
  420. #
  421. sub mail($@) {
  422. my $subject = shift;
  423. local( *MAIL );
  424. open( MAIL, "|$conf::mail -s '$subject' '$conf::maintainer_mail'" )
  425. or (warn( "Could not open pipe to $conf::mail: $!\n" ), return);
  426. print MAIL @_;
  427. print MAIL "\nGreetings,\n\n\tYour Debian queue daemon watcher\n";
  428. close( MAIL )
  429. or warn( "$conf::mail failed (exit status $?)\n" );
  430. }
  431. #
  432. # log something to logfile
  433. #
  434. sub logger(@) {
  435. my $now = format_time();
  436. local( *LOG );
  437. if (!open( LOG, ">>$conf::logfile" )) {
  438. warn( "Can't open $conf::logfile\n" );
  439. return;
  440. }
  441. print LOG "$now dqueued-watcher: ", @_;
  442. close( LOG );
  443. }
  444. #
  445. # return current time as string
  446. #
  447. sub format_time() {
  448. my $t;
  449. # omit weekday and year for brevity
  450. ($t = localtime) =~ /^\w+\s(.*)\s\d+$/;
  451. return $1;
  452. }
  453. # Local Variables:
  454. # tab-width: 4
  455. # fill-column: 78
  456. # End: