# # Sbuild.pm: library for sbuild # Copyright © 2005 Ryan Murray # Copyright © 2005-2008 Roger Leigh . # ####################################################################### package Sbuild; use Sbuild::Sysconfig; use strict; use warnings; use POSIX; use FileHandle; use Filesys::Df qw(); use Time::Local; use IO::Zlib; use MIME::Base64; use Dpkg::Control; use Dpkg::Checksums; use POSIX qw(locale_h); BEGIN { use Exporter (); our (@ISA, @EXPORT); @ISA = qw(Exporter); @EXPORT = qw($debug_level $devnull binNMU_version parse_date isin copy dump_file check_packages help_text version_text usage_error send_mail debug debug2 df check_group_membership dsc_files dsc_pkgver shellescape strftime_c); } our $devnull; our $debug_level = 0; BEGIN { # A file representing /dev/null if (!open($devnull, '+<', '/dev/null')) { die "Cannot open /dev/null: $!\n";; } } sub binNMU_version ($$$); sub parse_date ($); sub isin ($@); sub copy ($); sub dump_file ($); sub check_packages ($$); sub help_text ($$); sub version_text ($); sub usage_error ($$); sub debug (@); sub debug2 (@); sub check_group_membership(); sub dsc_files ($); sub shellescape ($); sub strftime_c ($@); sub binNMU_version ($$$) { my $v = shift; my $binNMUver = shift; my $append_to_version = shift; my $ver = $v; if (defined($append_to_version) && $append_to_version) { $ver .= $append_to_version; } if (defined($binNMUver) && $binNMUver) { $ver .= "+b$binNMUver"; } return $ver; } my %monname = ('jan', 0, 'feb', 1, 'mar', 2, 'apr', 3, 'may', 4, 'jun', 5, 'jul', 6, 'aug', 7, 'sep', 8, 'oct', 9, 'nov', 10, 'dec', 11 ); sub parse_date ($) { my $text = shift; return 0 if !$text; die "Cannot parse date: $text\n" if $text !~ /^(\d{4}) (\w{3}) (\d+) (\d{2}):(\d{2}):(\d{2})$/; my ($year, $mon, $day, $hour, $min, $sec) = ($1, $2, $3, $4, $5, $6); $mon =~ y/A-Z/a-z/; die "Invalid month name $mon" if !exists $monname{$mon}; $mon = $monname{$mon}; return timegm($sec, $min, $hour, $day, $mon, $year); } sub isin ($@) { my $val = shift; return grep( $_ eq $val, @_ ); } sub copy ($) { my $r = shift; my $new; if (ref($r) eq "HASH") { $new = { }; foreach (keys %$r) { $new->{$_} = copy($r->{$_}); } } elsif (ref($r) eq "ARRAY") { my $i; $new = [ ]; for( $i = 0; $i < @$r; ++$i ) { $new->[$i] = copy($r->[$i]); } } elsif (!ref($r)) { $new = $r; } else { die "unknown ref type in copy\n"; } return $new; } sub dump_file ($) { my $file = shift; if (-r "$file" && open(SOURCES, "<$file")) { print " +------------------------------------------------------------------------\n"; while () { chomp; print " |$_\n"; } print " +------------------------------------------------------------------------\n"; close(SOURCES) or print "Failed to close $file\n"; } else { print "W: Failed to open $file\n"; } } # set and list saved package list (used by sbuild-checkpackages) sub check_packages ($$) { my $session = shift; my $mode = shift; my $package_checklist = $session->get_conf('PACKAGE_CHECKLIST'); my $chroot_dir = $session->get('Location'); my (@status, @ref, @install, @remove); my $pipe = $session->pipe_command({ COMMAND => ['dpkg-query', '--show', '--showformat=${Package} ${db:Status-Status}\n'] }); while (<$pipe>) { chomp; my @token = split / /, $_; my $pkgname = shift @token; my $state = shift @token; next if $state ne "installed"; push @status, $pkgname; } if (! close $pipe) { print STDERR "Error reading dpkg status file in chroot: $!\n"; return 1; } @status = sort @status; if (!@status) { print STDERR "dpkg status file is empty\n"; return 1; } if ($mode eq "set") { if (! open WREF, "> $chroot_dir/$package_checklist") { print STDERR "Can't write reference status file $chroot_dir/$package_checklist: $!\n"; return 1; } foreach (@status) { print WREF "$_\n"; } if (! close WREF) { print STDERR "Error writing reference status file: $!\n"; return 1; } } else { # "list" if (! open REF, "< $chroot_dir/$package_checklist") { print STDERR "Can't read reference status file $chroot_dir/$package_checklist: $!\n"; return 1; } while () { chomp; push @ref, $_; } if (! close REF) { print STDERR "Error reading reference status file: $!\n"; return 1; } @ref = sort @ref; if (!@ref) { print STDERR "Reference status file is empty\n"; return 1; } print "DELETE ADD\n"; print "--------------------------------------\n"; my $i = 0; my $j = 0; while ($i < scalar @status && $j < scalar @ref) { my $c = $status[$i] cmp $ref[$j]; if ($c < 0) { # In status, not reference; remove. print "$status[$i]\n"; $i++; } elsif ($c > 0) { # In reference, not status; install. print " $ref[$j]\n"; $j++; } else { # Identical; skip. $i++; $j++; } } # Print any remaining elements while ($i < scalar @status) { print "$status[$i]\n"; $i++; } while ($j < scalar @ref) { print " $ref[$j]\n"; $j++; } } } sub help_text ($$) { my $section = shift; my $page = shift; system('man', '--', $section, $page); exit 0; } sub version_text ($) { my $program = shift; print <<"EOF"; $program (Debian sbuild) $Sbuild::Sysconfig::version ($Sbuild::Sysconfig::release_date) Written by Roman Hodek, James Troup, Ben Collins, Ryan Murray, Rick Younie, Francesco Paolo Lovergine, Michael Banck, Roger Leigh and Andres Mejia. Copyright © 1998-2000 Roman Hodek © 1998-1999 James Troup © 2003-2006 Ryan Murray © 2001-2003 Rick Younie © 2003-2004 Francesco Paolo Lovergine © 2005 Michael Banck © 2005-2010 Roger Leigh © 2009-2010 Andres Mejia This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. EOF exit 0; } # Print an error message about incorrect command-line options sub usage_error ($$) { my $program = shift; my $message = shift; print STDERR "E: $message\n"; print STDERR "I: Run '$program --help' to list usage example and all available options\n"; exit 1; } sub send_mail ($$$$) { my $conf = shift; my $to = shift; my $subject = shift; my $file = shift; local( *MAIL, *F ); if (!open( F, "<$file" )) { warn "Cannot open $file for mailing: $!\n"; return 0; } local $SIG{'PIPE'} = 'IGNORE'; if (!open( MAIL, "|" . $conf->get('MAILPROG') . " -oem $to" )) { warn "Could not open pipe to " . $conf->get('MAILPROG') . ": $!\n"; close( F ); return 0; } print MAIL "From: " . $conf->get('MAILFROM') . "\n"; print MAIL "To: $to\n"; print MAIL "Subject: $subject\n"; print MAIL "Content-Type: text/plain; charset=UTF-8\n"; print MAIL "Content-Transfer-Encoding: 8bit\n"; print MAIL "\n"; while( ) { print MAIL "." if $_ eq ".\n"; print MAIL $_; } close( F ); if (!close( MAIL )) { warn $conf->get('MAILPROG') . " failed (exit status $?)\n"; return 0; } return 1; } # Note: split to stderr sub debug (@) { # TODO: Add debug level checking. if ($debug_level) { print STDERR "D: ", @_; } } sub debug2 (@) { # TODO: Add debug level checking. if ($debug_level && $debug_level >= 2) { print STDERR "D2: ", @_; } } sub df { my $dir = shift; my $stat = Filesys::Df::df($dir); return $stat->{bfree} if (defined($stat)); # This only happens if $dir was not a valid file or directory. return 0; } sub check_group_membership () { # Skip for root return if ($< == 0); my $user = getpwuid($<); my ($name,$passwd,$gid,$members) = getgrnam("sbuild"); if (!$gid) { die "Group sbuild does not exist"; } my $in_group = 0; my @groups = getgroups(); push @groups, getgid(); foreach (@groups) { ($name, $passwd, $gid, $members) = getgrgid($_); $in_group = 1 if defined($name) && $name eq 'sbuild'; } if (!$in_group) { print STDERR "User $user is not currently an effective member of group sbuild. Please run:\n"; print STDERR " sudo sbuild-adduser $user\n"; print STDERR "And then either log out and log in again or use `newgrp sbuild` to gain sbuild group privileges\n"; exit(1); } return; } sub dsc_files ($) { my $dsc = shift; debug("Parsing $dsc\n"); my $pdsc = Dpkg::Control->new(type => CTRL_PKG_SRC); $pdsc->set_options(allow_pgp => 1); if (!$pdsc->load($dsc)) { print STDERR "Could not parse $dsc\n"; return undef; } my $csums = Dpkg::Checksums->new(); $csums->add_from_control($pdsc, use_files_for_md5 => 1); return $csums->get_files(); } sub dsc_pkgver ($) { my $dsc = shift; debug("Parsing $dsc\n"); my $pdsc = Dpkg::Control->new(type => CTRL_PKG_SRC); $pdsc->set_options(allow_pgp => 1); if (!$pdsc->load($dsc)) { print STDERR "Could not parse $dsc\n"; return undef; } return ($pdsc->{'Source'}, $pdsc->{'Version'}); } # avoid dependency on String::ShellQuote by implementing the mechanism # from python's shlex.quote function sub shellescape ($) { my $string = shift; if (length $string == 0) { return "''"; } # search for occurrences of characters that are not safe # the 'a' regex modifier makes sure that \w only matches ASCII if ($string !~ m/[^\w@\%+=:,.\/-]/a) { return $string; } # wrap the string in single quotes and handle existing single quotes by # putting them outside of the single-quoted string $string =~ s/'/'"'"'/g; return "'$string'"; }; # this function uses strftime to format a timestamp as a string but makes sure # to use the C locale to do so instead of the system locale sub strftime_c ($@) { my $format = shift; my @time = @_; my $old_locale = setlocale(LC_TIME); setlocale(LC_TIME, "C.UTF-8"); my $ret = strftime $format, @time; setlocale(LC_TIME, $old_locale); return $ret; } 1;