1#!/usr/bin/perl 2 3# mass-bug: mass-file a bug report against a list of packages 4# For options, see the usage message below. 5# 6# Copyright 2006 by Joey Hess <joeyh@debian.org> 7# 8# This program is free software; you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation; either version 2 of the License, or 11# (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program. If not, see <https://www.gnu.org/licenses/>. 20 21=head1 NAME 22 23mass-bug - mass-file a bug report against a list of packages 24 25=head1 SYNOPSIS 26 27B<mass-bug> [I<options>] B<--subject=">I<bug subject>B<"> I<template package-list> 28 29=head1 DESCRIPTION 30 31mass-bug assists in filing a mass bug report in the Debian BTS on a set of 32packages. For each package in the package-list file (which should list one 33package per line together with an optional version number separated 34from the package name by an underscore), it fills out the template, adds 35BTS pseudo-headers, and either displays or sends the bug report. 36 37Warning: Some care has been taken to avoid unpleasant and common mistakes, 38but this is still a power tool that can generate massive amounts of bug 39report mails. Use it with care, and read the documentation in the 40Developer's Reference about mass filing of bug reports first. 41 42=head1 TEMPLATE 43 44The template file is the body of the message that will be sent for each bug 45report, excluding the BTS pseudo-headers. In the template, #PACKAGE# is 46replaced with the name of the package. If a version was specified for 47the package, #VERSION# will be replaced by that version. 48 49The components of the version number may be specified using #EPOCH#, 50#UPSTREAM_VERSION# and #REVISION#. #EPOCH# includes the trailing colon and 51#REVISION# the leading dash so that #EPOCH#UPSTREAM_VERSION##REVISION# is 52always the same as #VERSION#. 53 54Note that text in the template will be automatically word-wrapped to 70 55columns, up to the start of a signature (indicated by S<'-- '> at the 56start of a line on its own). This is another reason to avoid including 57BTS pseudo-headers in your template. 58 59=head1 OPTIONS 60 61B<mass-bug> examines the B<devscripts> configuration files as described 62below. Command line options override the configuration file settings, 63though. 64 65=over 4 66 67=item B<--severity=>(B<wishlist>|B<minor>|B<normal>|B<important>|B<serious>|B<grave>|B<critical>) 68 69Specify the severity with which bugs should be filed. Default 70is B<normal>. 71 72=item B<--display> 73 74Fill out the templates for each package and display them all for 75verification. This is the default behavior. 76 77=item B<--send> 78 79Actually send the bug reports. 80 81=item B<--subject=">I<bug subject>B<"> 82 83Specify the subject of the bug report. The subject will be automatically 84prefixed with the name of the package that the bug is filed against. 85 86=item B<--tags> 87 88Set the BTS pseudo-header for tags. 89 90=item B<--user> 91 92Set the BTS pseudo-header for a usertags' user. 93 94=item B<--usertags> 95 96Set the BTS pseudo-header for usertags. 97 98=item B<--control=>I<COMMAND> 99 100Add a BTS control command. This option may be repeated to add multiple 101control commands. For example, if you are mass-bug-filing "please stop 102depending on this deprecated package", and bug 123456 represents removal 103of the deprecated package, you could use: 104 105 mass-bug --control='block 123456 by -1' ... 106 107=item B<--source> 108 109Specify that package names refer to source packages rather than binary 110packages. 111 112=item B<--sendmail=>I<SENDMAILCMD> 113 114Specify the B<sendmail> command. The command will be split on white 115space and will not be passed to a shell. Default is F</usr/sbin/sendmail>. 116 117=item B<--no-wrap> 118 119Do not wrap the template to lines of 70 characters. 120 121=item B<--no-conf>, B<--noconf> 122 123Do not read any configuration files. This can only be used as the 124first option given on the command-line. 125 126=item B<--help> 127 128Provide a usage message. 129 130=item B<--version> 131 132Display version information. 133 134=back 135 136=head1 ENVIRONMENT 137 138B<DEBEMAIL> and B<EMAIL> can be set in the environment to control the email 139address that the bugs are sent from. 140 141=head1 CONFIGURATION VARIABLES 142 143The two configuration files F</etc/devscripts.conf> and 144F<~/.devscripts> are sourced by a shell in that order to set 145configuration variables. Command line options can be used to override 146configuration file settings. Environment variable settings are 147ignored for this purpose. The currently recognised variables are: 148 149=over 4 150 151=item B<BTS_SENDMAIL_COMMAND> 152 153If this is set, specifies a B<sendmail> command to use instead of 154F</usr/sbin/sendmail>. Same as the B<--sendmail> command line option. 155 156=back 157 158=cut 159 160use strict; 161use warnings; 162use Getopt::Long qw(:config bundling permute no_getopt_compat); 163use Text::Wrap; 164use File::Basename; 165use POSIX qw(locale_h strftime); 166 167setlocale(LC_TIME, "C"); # so that strftime is locale independent 168 169my $progname = basename($0); 170$Text::Wrap::columns = 70; 171my $submission_email = "maintonly\@bugs.debian.org"; 172my $sendmailcmd = '/usr/sbin/sendmail'; 173my $modified_conf_msg; 174my %versions; 175 176sub usageerror { 177 die 178"Usage: $progname [options] --subject=\"bug subject\" <template> <package-list>\n"; 179} 180 181sub usage { 182 print <<"EOT"; 183Usage: 184 $progname [options] --subject="bug subject" <template> <package-list> 185 186Valid options are: 187 --display Display the messages but don\'t send them 188 --send Actually send the mass bug reports to the BTS 189 --subject="bug subject" 190 Text for email subject line (will be prefixed 191 with "package: ") 192 --severity=(wishlist|minor|normal|important|serious|grave|critical) 193 Specify the severity of the bugs to be filed 194 (default "normal") 195 196 --tags=tags Set the BTS pseudo-header for tags. 197 --user=user Set the BTS pseudo-header for a usertags' user 198 --usertags=usertags Set the BTS pseudo-header for usertags 199 --control="COMMAND" Add an arbitrary BTS control command (repeatable) 200 --source Specify that package names refer to source packages 201 202 --sendmail=cmd Sendmail command to use (default /usr/sbin/sendmail) 203 --no-wrap Don't wrap the template to 70 chars. 204 --no-conf, --noconf Don\'t read devscripts config files; 205 must be the first option given 206 --help Display this message 207 --version Display version and copyright info 208 209 <template> File containing email template; #PACKAGE# will 210 be replaced by the package name and #VERSION# 211 with the corresponding version (or a blank 212 string if the version was not specified) 213 <package-list> File containing list of packages, one per line 214 in the format package(_version) 215 216 Ensure that you read the Developer\'s Reference on mass-filing bugs before 217 using this script! 218 219Default settings modified by devscripts configuration files: 220$modified_conf_msg 221EOT 222} 223 224sub version () { 225 print <<"EOF"; 226This is $progname, from the Debian devscripts package, version ###VERSION### 227This code is copyright 2006 by Joey Hess, all rights reserved. 228This program comes with ABSOLUTELY NO WARRANTY. 229You are free to redistribute this code under the terms of the 230GNU General Public License, version 2 or later. 231EOF 232} 233 234# Next, read read configuration files and then command line 235# The next stuff is boilerplate 236 237if (@ARGV and $ARGV[0] =~ /^--no-?conf$/) { 238 $modified_conf_msg = " (no configuration files read)"; 239 shift; 240} else { 241 my @config_files = ('/etc/devscripts.conf', '~/.devscripts'); 242 my %config_vars = ('BTS_SENDMAIL_COMMAND' => '/usr/sbin/sendmail',); 243 my %config_default = %config_vars; 244 245 my $shell_cmd; 246 # Set defaults 247 foreach my $var (keys %config_vars) { 248 $shell_cmd .= qq[$var="$config_vars{$var}";\n]; 249 } 250 $shell_cmd .= 'for file in ' . join(" ", @config_files) . "; do\n"; 251 $shell_cmd .= '[ -f $file ] && . $file; done;' . "\n"; 252 # Read back values 253 foreach my $var (keys %config_vars) { $shell_cmd .= "echo \$$var;\n" } 254 my $shell_out = `/bin/bash -c '$shell_cmd'`; 255 @config_vars{ keys %config_vars } = split /\n/, $shell_out, -1; 256 257 # Check validity 258 $config_vars{'BTS_SENDMAIL_COMMAND'} =~ /./ 259 or $config_vars{'BTS_SENDMAIL_COMMAND'} = '/usr/sbin/sendmail'; 260 261 if ($config_vars{'BTS_SENDMAIL_COMMAND'} ne '/usr/sbin/sendmail') { 262 my $cmd = (split ' ', $config_vars{'BTS_SENDMAIL_COMMAND'})[0]; 263 unless ($cmd =~ /^~?[A-Za-z0-9_\-\+\.\/]*$/) { 264 warn 265"BTS_SENDMAIL_COMMAND contained funny characters: $cmd\nReverting to default value /usr/sbin/sendmail\n"; 266 $config_vars{'BTS_SENDMAIL_COMMAND'} = '/usr/sbin/sendmail'; 267 } elsif (system("command -v $cmd >/dev/null 2>&1") != 0) { 268 warn 269"BTS_SENDMAIL_COMMAND $cmd could not be executed.\nReverting to default value /usr/sbin/sendmail\n"; 270 $config_vars{'BTS_SENDMAIL_COMMAND'} = '/usr/sbin/sendmail'; 271 } 272 } 273 274 foreach my $var (sort keys %config_vars) { 275 if ($config_vars{$var} ne $config_default{$var}) { 276 $modified_conf_msg .= " $var=$config_vars{$var}\n"; 277 } 278 } 279 $modified_conf_msg ||= " (none)\n"; 280 chomp $modified_conf_msg; 281 282 $sendmailcmd = $config_vars{'BTS_SENDMAIL_COMMAND'}; 283} 284 285sub gen_subject { 286 my $subject = shift; 287 my $package = shift; 288 289 return "$package\: $subject"; 290} 291 292sub gen_bug { 293 my $template_text = shift; 294 my $package = shift; 295 my $severity = shift; 296 my $tags = shift; 297 my $user = shift; 298 my $usertags = shift; 299 my $nowrap = shift; 300 my $type = shift; 301 my $control = shift; 302 my $version = ""; 303 my $bugtext; 304 305 $version = $versions{$package} || ""; 306 307 my ($epoch, $upstream, $revision) 308 = ($version =~ /^(\d+:)?(.+?)(-[^-]+)?$/); 309 $epoch ||= ""; 310 $revision ||= ""; 311 312 $template_text =~ s/#PACKAGE#/$package/g; 313 $template_text =~ s/#VERSION#/$version/g; 314 $template_text =~ s/#EPOCH#/$epoch/g; 315 $template_text =~ s/#UPSTREAM_VERSION#/$upstream/g; 316 $template_text =~ s/#REVISION#/$revision/g; 317 318 $version = "Version: $version\n" if $version; 319 320 unless ($nowrap) { 321 if ($template_text =~ /\A(.*?)(^-- $)(.*)/ms) 322 { # there's a sig involved 323 my ($presig, $sig) = ($1, $2 . $3); 324 $template_text = fill("", "", $presig) . "\n" . $sig; 325 } else { 326 $template_text = fill("", "", $template_text); 327 } 328 } 329 if (defined $control) { 330 $control = join '', map { "Control: $_\n" } @$control; 331 } else { 332 $control = ''; 333 } 334 $bugtext = "$type: $package\n$version" 335 . "Severity: $severity\n$tags$user$usertags$control\n$template_text"; 336 return $bugtext; 337} 338 339sub div { 340 print +("-" x 79) . "\n"; 341} 342 343sub mailbts { 344 my ($subject, $body, $to, $from) = @_; 345 346 if (defined $from) { 347 my $date = strftime "%a, %d %b %Y %T %z", localtime; 348 349 my $pid = open(MAIL, "|-"); 350 if (!defined $pid) { 351 die "$progname: Couldn't fork: $!\n"; 352 } 353 $SIG{'PIPE'} = sub { die "$progname: pipe for $sendmailcmd broke\n"; }; 354 if ($pid) { 355 # parent 356 print MAIL <<"EOM"; 357From: $from 358To: $to 359Subject: $subject 360Date: $date 361X-Generator: mass-bug from devscripts ###VERSION### 362 363$body 364EOM 365 close MAIL or die "$progname: sendmail error: $!\n"; 366 } else { 367 # child 368 exec(split(' ', $sendmailcmd), "-t") 369 or die "$progname: error running sendmail: $!\n"; 370 } 371 } else { # No $from 372 unless (system("command -v mail >/dev/null 2>&1") == 0) { 373 die 374"$progname: You need to either specify an email address (say using DEBEMAIL)\n or have the mailx/mailutils package installed to send mail!\n"; 375 } 376 my $pid = open(MAIL, "|-"); 377 if (!defined $pid) { 378 die "$progname: Couldn't fork: $!\n"; 379 } 380 $SIG{'PIPE'} = sub { die "$progname: pipe for mail broke\n"; }; 381 if ($pid) { 382 # parent 383 print MAIL $body; 384 close MAIL or die "$progname: error running mail: $!\n"; 385 } else { 386 # child 387 exec("mail", "-s", $subject, $to) 388 or die "$progname: error running mail: $!\n"; 389 } 390 } 391} 392 393my $mode = "display"; 394my $subject; 395my $severity = "normal"; 396my $tags = ""; 397my $user = ""; 398my $usertags = ""; 399my @control = (); 400my $type = "Package"; 401my $opt_sendmail; 402my $nowrap = ""; 403 404if ( 405 !GetOptions( 406 "display" => sub { $mode = "display" }, 407 "send" => sub { $mode = "send" }, 408 "subject=s" => \$subject, 409 "severity=s" => \$severity, 410 "tags=s" => \$tags, 411 "user=s" => \$user, 412 "usertags=s" => \$usertags, 413 "control=s" => \@control, 414 "source" => sub { $type = "Source"; }, 415 "sendmail=s" => \$opt_sendmail, 416 "help" => sub { usage(); exit 0; }, 417 "version" => sub { version(); exit 0; }, 418 "no-wrap" => sub { $nowrap = 1; }, 419 'noconf|no-conf' => 420 sub { die '--noconf must come first on the command line' }, 421 ) 422) { 423 usageerror(); 424} 425 426if (!defined $subject || !length $subject) { 427 print STDERR 428 "$progname: You must specify a subject for the bug reports.\n"; 429 usageerror(); 430} 431 432unless ( 433 $severity =~ /^(wishlist|minor|normal|important|serious|grave|critical)$/) 434{ 435 print STDERR 436"$progname: Severity must be one of wishlist, minor, normal, important, serious, grave or critical.\n"; 437 usageerror(); 438} 439 440if (@ARGV != 2) { 441 usageerror(); 442} 443 444if ($tags) { 445 $tags = "Tags: $tags\n"; 446} 447 448if ($user) { 449 $user = "User: $user\n"; 450} 451 452if ($usertags) { 453 $usertags = "Usertags: $usertags\n"; 454} 455 456if ($opt_sendmail) { 457 if ( $opt_sendmail ne '/usr/sbin/sendmail' 458 and $opt_sendmail ne $sendmailcmd) { 459 my $cmd = (split ' ', $opt_sendmail)[0]; 460 unless ($cmd =~ /^~?[A-Za-z0-9_\-\+\.\/]*$/) { 461 warn 462"--sendmail command contained funny characters: $cmd\nReverting to default value $sendmailcmd\n"; 463 undef $opt_sendmail; 464 } elsif (system("command -v $cmd >/dev/null 2>&1") != 0) { 465 warn 466"--sendmail command $cmd could not be executed.\nReverting to default value $sendmailcmd\n"; 467 undef $opt_sendmail; 468 } 469 } 470} 471$sendmailcmd = $opt_sendmail if $opt_sendmail; 472 473my $template = shift; 474my $package_list = shift; 475 476my $template_text; 477open(T, "$template") || die "$progname: error reading $template: $!\n"; 478{ 479 local $/ = undef; 480 $template_text = <T>; 481} 482close T; 483if (!length $template_text) { 484 die "$progname: empty template\n"; 485} 486 487my @packages; 488open(L, "$package_list") || die "$progname: error reading $package_list: $!\n"; 489while (<L>) { 490 chomp; 491 if (!/^([-+\.a-z0-9]+)(?:_(.*))?$/) { 492 die "\"$_\" does not look like the name of a Debian package\n"; 493 } 494 push @packages, $1; 495 $versions{$1} = $2 if $2; 496} 497close L; 498 499# Uses variables from above. 500sub showsample { 501 my $package = shift; 502 503 print "To: $submission_email\n"; 504 print "Subject: " . gen_subject($subject, $package) . "\n"; 505 print "\n"; 506 print gen_bug( 507 $template_text, $package, $severity, $tags, $user, 508 $usertags, $nowrap, $type, \@control 509 ) . "\n"; 510} 511 512if ($mode eq 'display') { 513 print "Displaying all " . scalar(@packages) . " bug reports..\n"; 514 print "Run again with --send switch to send the bug reports.\n"; 515 div(); 516 foreach my $package (@packages) { 517 showsample($package); 518 div(); 519 } 520} elsif ($mode eq 'send') { 521 my $from; 522 $from ||= $ENV{'DEBEMAIL'}; 523 $from ||= $ENV{'EMAIL'}; 524 525 print "Preparing to send " 526 . scalar(@packages) 527 . " bug reports like this one:\n"; 528 div(); 529 showsample($packages[0]); 530 div(); 531 $| = 1; 532 print 533"Are you sure that you have read the Developer's Reference on mass-filing\nbug reports, have checked this case out on debian-devel, and really want to\nsend out these " 534 . scalar(@packages) 535 . " bug reports? [yes/no] "; 536 my $ans = <STDIN>; 537 538 unless ($ans =~ /^yes$/i) { 539 print "OK, aborting.\n"; 540 exit 0; 541 } 542 print "OK, going ahead then...\n"; 543 foreach my $package (@packages) { 544 print "Sending bug for $package ...\n"; 545 mailbts( 546 gen_subject($subject, $package), 547 gen_bug( 548 $template_text, $package, $severity, 549 $tags, $user, $usertags, 550 $nowrap, $type, \@control, 551 ), 552 $submission_email, 553 $from 554 ); 555 } 556 print "All bugs sent.\n"; 557} 558 559=head1 COPYRIGHT 560 561This program is Copyright (C) 2006 by Joey Hess <joeyh@debian.org>. 562 563It is licensed under the terms of the GPL, either version 2 of the 564License, or (at your option) any later version. 565 566=head1 AUTHOR 567 568Joey Hess <joeyh@debian.org> 569 570=cut 571