diff options
Diffstat (limited to 'bin')
36 files changed, 4981 insertions, 0 deletions
diff --git a/bin/Makefile.am b/bin/Makefile.am new file mode 100644 index 0000000..e4fcb69 --- /dev/null +++ b/bin/Makefile.am @@ -0,0 +1,99 @@ +# sbuild Makefile template +# +# +# Copyright © 2004-2008 Roger Leigh <rleigh@debian.org> +# +# sbuild is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# sbuild is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +##################################################################### + +include $(top_srcdir)/scripts/global.mk + +sbuilddatadir = $(SBUILD_DATA_DIR) +aptsolverdir = $(prefix)/lib/apt/solvers + +bin_SCRIPTS = \ + sbuild \ + sbuild-abort \ + sbuild-apt \ + sbuild-checkpackages \ + sbuild-createchroot \ + sbuild-debian-developer-setup \ + sbuild-update \ + sbuild-upgrade \ + sbuild-distupgrade \ + sbuild-clean \ + sbuild-qemu \ + sbuild-qemu-boot \ + sbuild-qemu-create \ + sbuild-qemu-create-modscript \ + sbuild-qemu-update \ + sbuild-shell \ + sbuild-hold \ + sbuild-unhold \ + buildd \ + buildd-mail \ + buildd-uploader \ + buildd-vlog \ + buildd-update-chroots \ + buildd-watcher + +sbin_SCRIPTS = \ + sbuild-adduser \ + sbuild-destroychroot + +sbuilddata_SCRIPTS = \ + dobuildlog + +doc_DATA = \ + README.bins + +aptsolver_SCRIPTS = \ + sbuild-cross-resolver + +EXTRA_DIST = \ + $(bin_SCRIPTS) \ + $(sbin_SCRIPTS) \ + $(sbuilddata_SCRIPTS) \ + $(doc_DATA) \ + $(aptsolver_SCRIPTS) \ + buildd-make-chroot \ + check-old-builds \ + finish-build \ + sbuild-debuild \ + setup_system \ + wb-ssh-wrapper + +install-exec-hook: +# Additional directories + $(MKDIR_P) "$(DESTDIR)$(sbuilddatadir)" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/buildd" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/buildd/.ssh" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/buildd/build" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/buildd/build-trees" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/buildd/logs" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/buildd/mqueue" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/buildd/old-logs" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/buildd/stats/graphs" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/buildd/upload" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/buildd/upload-security" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/sbuild" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/sbuild/build" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/sbuild/apt-keys" + +# Links for compatibility. + ln -sf "$(bindir)/buildd-mail" "$(DESTDIR)$(bindir)/buildd-mail-wrapper" + ln -sf "$(bindir)/sbuild-abort" "$(DESTDIR)$(bindir)/buildd-abort" + diff --git a/bin/README.bins b/bin/README.bins new file mode 100644 index 0000000..eeda008 --- /dev/null +++ b/bin/README.bins @@ -0,0 +1,36 @@ + dobuildlog + +A shell script for mutt & vi to handle the build logs that +sbuild mails to you. The "bug" option is generally useful as it +massages the build log a little so you can easily file a bug. +The other options are for communicating with a build daemon. + +From the script: +# craft a bug report or fail/success reply to a buildd log mail +# using vim, mutt and optionally quintuple-agent: +# mutt +# 'f'orward the message +# (may require autoedit & edit_headers .muttrc settings) +# vim +# map <F3> :%!~buildd/bin/dobuildlog agpg<CR> +# map <S-F3> :%!~buildd/bin/dobuildlog gpg<CR> +# map <F4> :%!~buildd/bin/dobuildlog bug<CR> + +You'll have to change these to your own settings: +SIGNOPTS='--clearsign --default-key younie@debian.org' +FROM="$EMAIL" # "Your Name <your@addr.ess>" +ARCH=m68k # for the bug report log link + +Please see the comments in the script. + + ------------------------------------------ +<PLUG> +ppack in the searchscripts package can be useful, + +ppack -r unstable -O[q] # shows orphans in the unstable chroot +ppack -r un -I[q] '*' # shows installed packages in unstable +ppack -r un -a # shows anomalies (holds, misconfigured pkgs) + +ppack -r st -Lb pkg # urlified a package listing -> browser + - this is useful for browsing an installed packages contents. Try + "ppack -Lb sbuild" although it may be a little late for that now. :-) diff --git a/bin/buildd b/bin/buildd new file mode 100755 index 0000000..41fbf09 --- /dev/null +++ b/bin/buildd @@ -0,0 +1,93 @@ +#!/usr/bin/perl +# +# buildd: daemon to automatically build packages +# Copyright © 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> +# Copyright © 2009 Roger Leigh <rleigh@debian.org> +# Copyright © 2005 Ryan Murray <rmurray@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +use Buildd::Conf qw(); +use Buildd::Daemon; +use Sbuild::OptionsBase; + +sub shutdown_fast ($); +sub reread_config ($); +sub reopen_log ($); + +my $conf = Buildd::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::OptionsBase->new($conf, "buildd", "1"); +exit 1 if !defined($options); +my $daemon = Buildd::Daemon->new($conf); +exit 1 if !defined($daemon); + +# Global signal handling +foreach (qw(QUIT ILL TRAP ABRT BUS FPE USR2 SEGV PIPE XCPU XFSZ)) { + $SIG{$_} = \&shutdown_fast; +} +$SIG{'HUP'} = \&reopen_log; +$SIG{'USR1'} = \&reread_config; +$SIG{'INT'} = \&shutdown; +$SIG{'TERM'} = \&shutdown; + +exit $daemon->run(); + +sub shutdown_fast ($) { + my $signame = shift; + $daemon->log("buildd ($$) killed by SIG$signame\n") + if defined($daemon); + unlink( $conf->get('PIDFILE') ); + exit 1; +} + +sub shutdown ($) { + my $signame = shift; + + if ($daemon) { + $daemon->shutdown($signame); + } + exit 1; +} + +sub reread_config ($) { + my $signame = shift; + + $daemon->log("buildd ($$) received SIG$signame -- rereading configuration\n") + if defined($daemon); + + $Buildd::Conf::reread_config = 1; +} + +sub reopen_log ($) { + my $signame = shift; + + $daemon->log("buildd ($$) received SIG$signame -- reopening logfile\n") + if defined($daemon); + + $daemon->reopen_log(); +} + +END { + unlink( $conf->get('PIDFILE') ) + if (defined($conf) && + defined($daemon) && + $daemon->get('Daemon')); +} diff --git a/bin/buildd-mail b/bin/buildd-mail new file mode 100755 index 0000000..a45424e --- /dev/null +++ b/bin/buildd-mail @@ -0,0 +1,42 @@ +#!/usr/bin/perl +# +# buildd-mail: mail answer processor for buildd +# Copyright © 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> +# Copyright © 2009 Roger Leigh <rleigh@debian.org> +# Copyright © 2005 Ryan Murray <rmurray@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +use Buildd::Conf qw(); +use Buildd::Mail; +use Sbuild::OptionsBase; + +my $conf = Buildd::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::OptionsBase->new($conf, "buildd-mail", "1"); +exit 1 if !defined($options); +my $mail = Buildd::Mail->new($conf); +exit 1 if !defined($mail); + +my $status = $mail->run(); + +$mail->close_log(); + +exit $status; diff --git a/bin/buildd-make-chroot b/bin/buildd-make-chroot new file mode 100755 index 0000000..6062968 --- /dev/null +++ b/bin/buildd-make-chroot @@ -0,0 +1,97 @@ +#!/bin/sh -e +# +# Script that uses debootstrap 0.3.2+ to build a build-essential +# chroot for buildd use. +# Copyright © 2005 Ryan Murray <rmurray@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +# user suite target <mirror> +if [ "$#" -lt "3" ]; then + echo "usage: buildd-make-chroot user suite target <mirror>" + exit 1 +fi + +if [ "$#" -gt "4" ]; then + echo "usage: buildd-make-chroot user suite target <mirror>" + exit 1 +fi + +USER=$1 +SUITE=$2 +if echo "$3" | grep -Eq '^/'; then + TARGET="$3" +else + TARGET="`pwd`/$3" +fi +if [ "$#" -gt "3" ]; then + MIRROR=$4 +else + MIRROR=http://incoming.debian.org/debian +fi +debootstrap --variant=buildd --include=sudo,fakeroot,build-essential $SUITE $TARGET $MIRROR +hostname=`hostname` +echo 127.0.0.1 $hostname localhost > $TARGET/etc/hosts +echo "# put any local/close mirrors at the top of the file" > $TARGET/etc/apt/sources.list +if [ "$#" -gt "3" ]; then + echo "deb $4 $SUITE main contrib" >> $TARGET/etc/apt/sources.list +fi +echo "deb http://incoming.debian.org/debian-debian buildd-$SUITE main contrib" >> $TARGET/etc/apt/sources.list +echo "deb-src http://incoming.debian.org/debian-debian buildd-$SUITE main contrib" >> $TARGET/etc/apt/sources.list +case "$2" in + sid) + ;; + woody) + echo "deb http://non-us.debian.org/debian-non-US $SUITE/non-US main contrib" >> $TARGET/etc/apt/sources.list + echo "deb-src http://non-us.debian.org/debian-non-US $SUITE/non-US main contrib" >> $TARGET/etc/apt/sources.list + echo "deb http://$hostname:PASSWORD@security-master.debian.org/debian-security $SUITE/updates main contrib" >> $TARGET/etc/apt/sources.list + echo "deb-src http://$hostname:PASSWORD@security-master.debian.org/debian-security $SUITE/updates main contrib" >> $TARGET/etc/apt/sources.list + echo "deb http://$hostname:PASSWORD@security-master.debian.org/buildd $SUITE/" >> $TARGET/etc/apt/sources.list + echo "deb-src http://$hostname:PASSWORD@security-master.debian.org/buildd $SUITE/" >> $TARGET/etc/apt/sources.list + echo "deb http://incoming.debian.org/debian $SUITE-proposed-updates main contrib" >> $TARGET/etc/apt/sources.list + echo "deb-src http://incoming.debian.org/debian $SUITE-proposed-updates main contrib" >> $TARGET/etc/apt/sources.list + ;; + sarge) + echo "deb http://incoming.debian.org/debian $SUITE-proposed-updates main contrib" >> $TARGET/etc/apt/sources.list + echo "deb-src http://incoming.debian.org/debian $SUITE-proposed-updates main contrib" >> $TARGET/etc/apt/sources.list + echo "deb http://$hostname:PASSWORD@security-master.debian.org/debian-security $SUITE/updates main contrib" >> $TARGET/etc/apt/sources.list + echo "deb-src http://$hostname:PASSWORD@security-master.debian.org/debian-security $SUITE/updates main contrib" >> $TARGET/etc/apt/sources.list + echo "deb http://$hostname:PASSWORD@security-master.debian.org/buildd $SUITE/" >> $TARGET/etc/apt/sources.list + echo "deb-src http://$hostname:PASSWORD@security-master.debian.org/buildd $SUITE/" >> $TARGET/etc/apt/sources.list + ;; + etch) + echo "deb http://incoming.debian.org/debian $SUITE-proposed-updates main contrib" >> $TARGET/etc/apt/sources.list + echo "deb-src http://incoming.debian.org/debian $SUITE-proposed-updates main contrib" >> $TARGET/etc/apt/sources.list + echo "deb http://$hostname:PASSWORD@security-master.debian.org/debian-security $SUITE/updates main contrib" >> $TARGET/etc/apt/sources.list + echo "deb-src http://$hostname:PASSWORD@security-master.debian.org/debian-security $SUITE/updates main contrib" >> $TARGET/etc/apt/sources.list + echo "deb http://$hostname:PASSWORD@security-master.debian.org/buildd $SUITE/" >> $TARGET/etc/apt/sources.list + echo "deb-src http://$hostname:PASSWORD@security-master.debian.org/buildd $SUITE/" >> $TARGET/etc/apt/sources.list + ;; +esac +getent passwd $USER | sed -re 's/^([^:]+):x/\1:*/' -e 's/:[^:]+:([^:]+)$/:\/nonexistent:\1/' >> $TARGET/etc/passwd +getent group $USER | sed -re 's/^([^:]+):x/\1:*/' >> $TARGET/etc/group +echo $USER ALL=NOPASSWD: ALL >> $TARGET/etc/sudoers +mkdir -p $TARGET/var/lib/sbuild//srcdep-lock $TARGET/build/$USER +chown -R $USER:$USER $TARGET/var/lib/sbuild $TARGET/build/$USER +chmod -R 02775 $TARGET/var/lib/sbuild +echo include /etc/ld.so.conf.d/*.conf >> $TARGET/etc/ld.so.conf +(cd $TARGET/dev ; ./MAKEDEV fd) +sudo chroot $TARGET dpkg -P debconf-i18n debconf liblocale-gettext-perl libtext-charwidth-perl libtext-iconv-perl libtext-wrapi18n-perl procps makedev +echo "Successfully setup chroot for a buildd" +echo Possible commands to append to fstab: +echo echo $SUITE-proc $TARGET/proc proc defaults 0 0 \>\> /etc/fstab +echo echo $SUITE-devpts $TARGET/dev/pts devpts defaults,gid=5,mode=600 0 0 \>\> /etc/fstab diff --git a/bin/buildd-update-chroots b/bin/buildd-update-chroots new file mode 100755 index 0000000..b8d0166 --- /dev/null +++ b/bin/buildd-update-chroots @@ -0,0 +1,42 @@ +#!/bin/sh +# +# Copyright © 2005 Ryan Murray <rmurray@debian.org> +# Copyright © 2009 Thibaut VARÈNE <varenet@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +cleanup() { + rm -f ~/NO-DAEMON-PLEASE +} + +touch ~/NO-DAEMON-PLEASE +trap cleanup 0 + +touch ~/EXIT-DAEMON-PLEASE +echo -n Waiting for sbuild and buildd to exit... +while [ -f ~/EXIT-DAEMON-PLEASE ]; do + sleep 10 +done +echo . + +schroot -a -u root -d /root -- apt-get update +echo Upgrading chroots: +schroot -a -u root -d /root -- apt-get dist-upgrade -y +echo Cleaning chroots: +schroot -a -u root -d /root -- apt-get autoremove -y +schroot -a -u root -d /root -- debfoster -f + diff --git a/bin/buildd-uploader b/bin/buildd-uploader new file mode 100755 index 0000000..de172f0 --- /dev/null +++ b/bin/buildd-uploader @@ -0,0 +1,51 @@ +#!/usr/bin/perl +# +# buildd-uploader: upload finished packages for buildd +# Copyright © 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> +# Copyright © 2009 Roger Leigh <rleigh@debian.org> +# Copyright © 2005 Ryan Murray <rmurray@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +use Buildd qw(unlock_file); +use Buildd::Conf qw(); +use Buildd::Uploader; +use Sbuild::OptionsBase; + +my $conf = Buildd::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::OptionsBase->new($conf, "buildd-uploader", "1"); +exit 1 if !defined($options); +my $uploader = Buildd::Uploader->new($conf); +exit 1 if !defined($uploader); + +my $status = $uploader->run(); + +$uploader->close_log(); + +exit $status; + +END { + unlock_file($conf->get('HOME') . "/buildd-uploader") + if (defined($conf) && + defined($uploader) && + defined($uploader->get('Uploader Lock')) && + $uploader->get('Uploader Lock')); +} diff --git a/bin/buildd-vlog b/bin/buildd-vlog new file mode 100755 index 0000000..3b83efa --- /dev/null +++ b/bin/buildd-vlog @@ -0,0 +1,132 @@ +#!/usr/bin/perl +# +# buildd-vlog: little utility to watch the logs written by sbuild +# Copyright © 1999 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> +# Copyright © 2009 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; +use POSIX; + +use Buildd; +use Buildd::Conf qw(); +use Sbuild::OptionsBase; + +my $conf = Buildd::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::OptionsBase->new($conf, "buildd-vlog", "1"); +exit 1 if !defined($options); + +my $logpath = $conf->get('HOME') . '/logs'; + +sub read_progress (); +sub newest_log ($); +sub tail ($); + +while( 1 ) { + + my $curr_pkg = read_progress(); + if (!$curr_pkg) { + print "No build-progress -- waiting\n"; + do { + sleep 5; + } while (!($curr_pkg = read_progress())); + } + + print("package '$curr_pkg'\n"); + + my $logf = newest_log( "$logpath/${curr_pkg}*" ); + + if ($logf eq "") { + sleep(1); + next; + } + + print "\n\n", "="x78, "\n$logf:\n\n"; + + tail( $logf ); +} + +sub read_progress () { + my $f = $conf->get('HOME') . '/build/build-progress'; + my $p = ""; + my $state = ""; + + open( F, "<$f" ) || return ""; + while( <F> ) { + s/_[0-9]+:/_/; + ($p,$state) = ($1,$2) if /^(\S+): (\S+)$/; + } + return "" + if ($state ne "building"); + close( F ); + return $p; +} + +sub newest_log ($) { + my $pattern = shift; + + my @f = glob( $pattern ); + my $maxtime = 0; + my $f = ""; + my @s; + + foreach (@f) { + @s = stat( $_ ); + warn "Cannot stat $_: $!", next if !@s; + if ($s[9] > $maxtime) { + $maxtime = $s[9]; + $f = $_; + } + } + return $f; +} + +sub tail ($) { + my $f = shift; + + my @s = stat( $f ); + if (!@s) { + warn "Cannot stat $f: $!\n"; + return; + } + my $size = $s[7]; + + if (!open( F, "<$f" )) { + warn "Cannot open $f: $!\n"; + return; + } + if ($size > 3*1024) { + seek( F, -3*1024, SEEK_END ); + my $junk = <F>; # throw away first incomplete line + print $size+3*1024, " bytes skipped...\n"; + } + + while( 1 ) { + while( <F> ) { + print $_; + if (/^Build needed \d\d:\d\d:\d\d/) { + close( F ); + sleep( 1 ); + return; + } + } + sleep( 2 ); + } +} diff --git a/bin/buildd-watcher b/bin/buildd-watcher new file mode 100755 index 0000000..5d6a110 --- /dev/null +++ b/bin/buildd-watcher @@ -0,0 +1,42 @@ +#!/usr/bin/perl +# +# buildd-watcher: +# Copyright © 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> +# Copyright © 2009 Roger Leigh <rleigh@debian.org> +# Copyright © 2005 Ryan Murray <rmurray@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +use Buildd::Conf qw(); +use Buildd::Watcher; +use Sbuild::OptionsBase; + +my $conf = Buildd::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::OptionsBase->new($conf, "buildd-watcher", "1"); +exit 1 if !defined($options); +my $watcher = Buildd::Watcher->new($conf); +exit 1 if !defined($watcher); + +my $status = $watcher->run(); + +$watcher->close_log(); + +exit $status; diff --git a/bin/check-old-builds b/bin/check-old-builds new file mode 100755 index 0000000..12306f7 --- /dev/null +++ b/bin/check-old-builds @@ -0,0 +1,153 @@ +#!/usr/bin/perl +# +# check-old-build: check for packages which are in Building for extended time +# Copyright © 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; +use Time::Local; + +my $HOME = $ENV{'HOME'} +or die "HOME not defined in environment!\n"; + +sub check (@); +sub notify_mail (@); +sub parse_date ($); + +my $reported_file = "$HOME/lib/reported-old-builds"; +my $list_cmd = "wanna-build --list=building -v"; +my $report_days = 10; +my $mailprog = "/usr/sbin/sendmail"; +chomp( my $mailname = `cat /etc/mailname` || `hostname` ); +my $sender = $ENV{'LOGNAME'} || (getpwuid($<))[0]; + +my ($pkg, $builder, $date); +my %reported; +my %seen; +my $now = time; +my $report_time = $report_days * 24*60*60; + +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 ); + +if (open( F, "<$reported_file" )) { + while( <F> ) { + next if !/^(\S+)\s+(\S+)\s+(\d+)$/; + $reported{$2}->{$1} = $3; + } + close( F ); +} + +my $dist; +foreach $dist (qw(stable frozen unstable)) { + open( PIPE, "$list_cmd --dist=$dist 2>&1 |" ) + or die "Cannot spawn $list_cmd: $!\n"; + while( <PIPE> ) { + next if /^wanna-build Revision/ || /^Total \d+ package/; + if (/^Database for \S+ doesn't exist/i) { + last; + } + elsif (m,^\S*/(\S+) by (\S+) \[.*\]$,) { + ($pkg, $builder) = ($1, $2); + $seen{$dist}->{$pkg} = 1; + } + elsif (/^\s+Previous state was \S+ until (.*)$/) { + $date = parse_date($1); + check( $dist, $pkg, $builder, $date ); + } + elsif (/^Database locked by \S+ -- please wait/ || /^\s/) { + # ignore + } + else { + warn "Unexpected output from $list_cmd line $.:\n$_"; + } + } + close( PIPE ); +} + +open( F, ">$reported_file" ) + or die "Cannot open $reported_file for writing: $!\n"; +foreach $dist (qw(stable frozen unstable)) { + foreach (keys %{$reported{$dist}}) { + print F "$_ $dist $reported{$dist}->{$_}\n" + if $seen{$dist}->{$_}; + } +} +close( F ); + +exit 0; + +sub check (@) { + my ($dist, $pkg, $builder, $bdate) = @_; + my $date = (exists $reported{$dist}->{$pkg}) ? + $reported{$dist}->{$pkg} : $bdate; + + if ($now - $date > $report_time) { + notify_mail( $dist, $pkg, $builder, $bdate ); + $reported{$dist}->{$pkg} = $now; + } +} + +sub notify_mail (@) { + my ($dist, $pkg, $to, $_date) = @_; + my $date = localtime($date); + local( *MAIL ); + + local $SIG{'PIPE'} = 'IGNORE'; + open( MAIL, "| $mailprog -oem $to\@$mailname" ) + or die "Can't open pipe to $mailprog: $!\n"; + print MAIL <<"EOF"; +From: $sender\@$mailname +To: $to\@$mailname +Subject: Old build of $pkg (dist=$dist) + +The package $pkg has been taken by you for +building in distribution $dist at $date. +This is some time ago now, so it could be you have forgotten the build. +Can you please check this and --if this is the case-- give back the package +or finish it? +If you did not call wanna-build --uploaded, it might also be the case +that the package is not yet installed in the archive. + +(This is an automated message.) +EOF + close( MAIL ); +} + +sub parse_date ($) { + my $text = shift; + + 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 timelocal($sec, $min, $hour, $day, $mon, $year); +} diff --git a/bin/dobuildlog b/bin/dobuildlog new file mode 100755 index 0000000..793daf2 --- /dev/null +++ b/bin/dobuildlog @@ -0,0 +1,132 @@ +#!/bin/bash -e +# +# Copyright © 2002 Rick Younie <rick@def.debian.net> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +# +# craft a bug report or fail/success reply to a buildd log mail +# using vim, mutt and optionally quintuple-agent: +# mutt +# 'f'orward the message +# (may require autoedit & edit_headers .muttrc settings) +# vim +# map <F3> :%!~buildd/bin/dobuildlog agpg<CR> +# map <S-F3> :%!~buildd/bin/dobuildlog gpg<CR> +# map <F4> :%!~buildd/bin/dobuildlog bug<CR> + +# these require setting by the user +SIGNOPTS='--clearsign --default-key younie@debian.org' +FROM="$EMAIL" # "Your Name <your@addr.ess>" +ARCH=m68k # for the bug report log link + +print_header () { + echo "From: $FROM" + sed -n ' + /^-----/,/^Automatic/ { + s/From: /To: /p + s/^Subject: Log/Subject: Re: Log/p + }' + echo +} + +fail_options () { + cat << EOF +failed + this one takes a comment, + multi-line, indenting optional +dep-wait + - usage: dep-wait some-package (>= version), another-package (>> version) +giveback +manual +newvers +not-for-us +purge + - purges the source tree from the chroot +retry +upload-rem + + +EOF +} + +success_fail () { + STATUS=$(sed -n '/^-----/,/^Automatic/ s/^Subject: Log for \([^ ]*\) build .*/\1/p') + + case "$STATUS" in + successful ) + print_header + sed -n '/\.changes:$/,$ { + /^Format: /,/^$/p + }' |$SIGNPRG 2>/dev/null + ;; + failed ) + print_header + fail_options + sed -n '/^Automatic build of/,$p' + ;; + * ) + echo "..this doesn't appear to be a buildd success/fail message" + exit 1 + ;; + esac +} + +bug_report () { + PKG=$1 + VERS=$2 + + cat << EOF +From: $FROM +To: submit@bugs.debian.org +Subject: $PKG_VERS: fails to build + +Package: $PKG +Version: $VERS +Severity: serious + +Hi, + + +EOF + + sed -n '/^Automatic build of/,/^Build needed/ s/^/| /p' + cat <<EOF + + +The $ARCH build logs for $PKG can be found at + http://buildd.debian.org/build.php?arch=$ARCH&pkg=$PKG + + +EOF +} + +case "$1" in + gpg | agpg ) + SIGNPRG="$1 $SIGNOPTS" + success_fail + exit 0 + ;; + bug ) + PKG_VERS=$(sed -n '/^-----/,/^Automatic/ s/^Subject: Log for \([^ ]*\) build of \([^ ]*\) .*/\2/p') + bug_report $(echo "$PKG_VERS" |sed 's/_/ /') + ;; + * ) + echo "Usage: $(basename $0) gpg|agpg|bug" + exit 1 + ;; +esac diff --git a/bin/finish-build b/bin/finish-build new file mode 100755 index 0000000..1c63d72 --- /dev/null +++ b/bin/finish-build @@ -0,0 +1,123 @@ +#!/bin/sh +# +# finish-build: finishes a manually fixed build by running binary-arch +# if necessary and generating a .changes file +# Copyright © 1999 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +set -e + +# TODO: Convert to perl and read configuration directly. + +if [ ! -f debian/rules ]; then + echo "This directory doesn't seem to contain a Debian source tree" 1>&2 + exit 1 +fi + +logpath=$(cat /etc/sbuild.conf /etc/sbuild.conf.local $HOME/.sbuildrc | \ + sed -n '/^\$log_dir/s/.*"\(.*\)".*/\1/p' | tail -1) +logpath=`eval echo $logpath` +if [ -z "$logpath" ]; then + logpath=$HOME/logs +fi + +maintname=$(cat /etc/sbuild.conf /etc/sbuild.conf.local $HOME/.sbuildrc | \ + sed -n '/^\$maintainer_name/s/.*"\(.*\)".*/\1/p' | tail -1) +maintname=$(echo $maintname | sed 's/\\@/@/g') +if [ -z "$maintname" ]; then + echo "Can't extract \$maintainer_name variable from sbuild config" 1>&2 + exit 1 +fi + +mailto=$(cat /etc/sbuild.conf /etc/sbuild.conf.local $HOME/.sbuildrc | \ + sed -n '/^\$mailto/s/.*"\(.*\)".*/\1/p' | tail -1) +mailto=$(echo $mailto | sed 's/\\@/@/g') +if [ -z "$mailto" ]; then + echo "Can't extract \$mailto variable from sbuild config" 1>&2 + exit 1 +fi + +setvar () { + if [ "x$2" = x ]; then + echo "$0: unable to determine $3" + exit 1 + else + eval "$1='$2'" + fi +} + +opt_b=0 +while [ $# -ge 1 ]; do + case "$1" in + -b) opt_b=1;; + *) echo "Unknown option $1" 1>&2; exit 1;; + esac + shift +done + +tmpf=/tmp/finish-build.$$ +dpkg-parsechangelog > $tmpf +setvar package "`sed -n 's/^Source: //p' $tmpf`" "source package" +setvar version "`sed -n 's/^Version: //p' $tmpf`" "source version" +setvar arch "`dpkg --print-architecture`" "build architecture" +rm -f $tmpf +sversion=`echo "$version" | perl -pe 's/^\d+://'` +changes=${package}_${sversion}_${arch}.changes +logpat=${package}_${version} + +lastlog=`(cd $logpath; ls -1t ${logpat}_* | head -1) 2>/dev/null` +if [ -z "$lastlog" ]; then + echo "No log file found (pattern ${logpat}_*)" 1>&2 + exit 1 +else + echo " Log file is $lastlog" +fi + +do_binarch=0 +if [ ! -f debian/files ]; then + echo " debian/files missing -- running binary-arch" + do_binarch=1 +elif [ $opt_b = 1 ]; then + do_binarch=1 +fi + +if [ $do_binarch = 1 ]; then + echo " sudo debian/rules binary-arch" + sudo debian/rules binary-arch 2>&1 | tee -a $logpath/$lastlog +fi + +if [ ! -s ../$changes ]; then + echo " Generating .changes file:" + dpkg-genchanges -B -m"$maintname" > ../$changes +fi + +if [ ! -f debian/files ]; then + echo "debian/files not found" 1>&2 + exit 1 +fi +files="`cut -d' ' -f1 debian/files`" +if [ -z "$files" ]; then + echo "No files list" 1>&2 + exit 1 +fi + +(cat $logpath/$lastlog; + for i in $files; do echo; echo "$i:"; dpkg --info ../$i; done; + for i in $files; do echo; echo "$i:"; dpkg --contents ../$i; done; + echo; echo "$changes:"; cat ../$changes + ) | mail -s "Log for successful build of $logpat (dist=unstable)" $mailto diff --git a/bin/sbuild b/bin/sbuild new file mode 100755 index 0000000..4f0db29 --- /dev/null +++ b/bin/sbuild @@ -0,0 +1,395 @@ +#!/usr/bin/perl +# +# sbuild: build packages, obeying source dependencies +# Copyright © 1998-2000 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> +# Copyright © 2005 Ryan Murray <rmurray@debian.org> +# Copyright © 2005-2009 Roger Leigh <rleigh@debian.org +# Copyright © 2008 Timothy G Abbott <tabbott@mit.edu> +# Copyright © 2008 Simon McVittie <smcv@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +package main; + +use strict; +use warnings; + +use Cwd qw(:DEFAULT abs_path); +use File::Basename qw(basename dirname); +use File::Spec; +use POSIX; +use Data::Dumper; +use Dpkg::Control; +use Sbuild qw(isin check_group_membership $debug_level dsc_files debug); +use Sbuild::Conf qw(); +use Sbuild::Sysconfig qw(%programs); +use Sbuild::Options; +use Sbuild::Build; +use Sbuild::Exception; +use Sbuild::Utility qw(check_url download); + +sub main (); +sub create_source_package ($); +sub download_source_package ($); +sub write_jobs_file (); +sub status_trigger ($$); +sub shutdown ($); +sub dump_main_state (); + +my $conf = Sbuild::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::Options->new($conf, "sbuild", "1"); +exit 1 if !defined($options); +check_group_membership() if $conf->get('CHROOT_MODE') eq 'schroot'; + +if (!$conf->get('MAINTAINER_NAME') && + ($conf->get('BIN_NMU') || $conf->get('APPEND_TO_VERSION'))) { + die "A maintainer name, uploader name or key ID must be specified in .sbuildrc,\nor use -m, -e or -k, when performing a binNMU or appending a version suffix\n"; +} + +# default umask for Debian +# see dpkg source: scripts/Dpkg/Vendor/Debian.pm +umask(0022); + +# Job state +my $job = undef; + +main(); + +sub main () { + $SIG{'INT'} = \&main::shutdown; + $SIG{'TERM'} = \&main::shutdown; + $SIG{'ALRM'} = \&main::shutdown; + $SIG{'PIPE'} = \&main::shutdown; + + # If no arguments are supplied, assume we want to process the current dir. + push @ARGV, '.' unless (@ARGV); + + die "Only one build is permitted\n" + if (@ARGV != 1); + + # Create and run job + my $status = eval { + my $jobname = $ARGV[0]; + my $source_dir = 0; + + if (-e $jobname) { + # $jobname should be an absolute path, so that the %SBUILD_DSC + # escape also is absolute. This is important for `dgit sbuild`. + # See Debian bug #801436 for details. On the other hand, the + # last component of the path must not have any possible symlinks + # resolved so that a symlink ending in .dsc is not turned + # into a path that does not end in .dsc. See Debian bug #1012856 + # for details. Thus, we call File::Spec->rel2abs instead of + # Cwd::abs_path because the latter behaves like `realpath` and + # resolves symlinks while the former does not. + $jobname = File::Spec->rel2abs($jobname); + } + + if (-d $jobname) { + $jobname = create_source_package($jobname); + if ($jobname eq '.') { + chdir('..') or Sbuild::Exception::Build->throw( + error => "Failed to change directory", + failstage => "change-build-dir"); + $conf->_set_default('BUILD_DIR', cwd()); + } + $source_dir = 1; + } elsif (($jobname =~ m/\.dsc$/) && # Use apt to download + check_url($jobname)) { + # Valid URL + $jobname = download_source_package($jobname); + } + + + # Check after source package build (which might set dist) + my $dist = $conf->get('DISTRIBUTION'); + if (!defined($dist) || !$dist) { + print STDERR "No distribution defined\n"; + exit(1); + } + + print "Selected distribution " . $conf->get('DISTRIBUTION') . "\n" + if $conf->get('DEBUG'); + print "Selected chroot " . $conf->get('CHROOT') . "\n" + if $conf->get('DEBUG') and defined $conf->get('CHROOT'); + print "Selected host architecture " . $conf->get('HOST_ARCH') . "\n" + if $conf->get('DEBUG' && defined($conf->get('HOST_ARCH'))); + print "Selected build architecture " . $conf->get('BUILD_ARCH') . "\n" + if $conf->get('DEBUG' && defined($conf->get('BUILD_ARCH'))); + print "Selected build profiles " . $conf->get('BUILD_PROFILES') . "\n" + if $conf->get('DEBUG' && defined($conf->get('BUILD_PROFILES'))); + + $job = Sbuild::Build->new($jobname, $conf); + $job->set('Pkg Status Trigger', \&status_trigger); + write_jobs_file(); # Will now update on trigger. + + # Run job. + $job->run(); + + dump_main_state() if $conf->get('DEBUG'); + }; + + my $e; + if ($e = Exception::Class->caught('Sbuild::Exception::Build')) { + print STDERR "E: $e\n"; + print STDERR "I: " . $e->info . "\n" + if ($e->info); + if ($debug_level) { + #dump_main_state(); + #print STDERR $e->trace->as_string, "\n"; + } + } elsif (!defined($e)) { + print STDERR "E: $@\n" if $@; + } + + unlink($conf->get('JOB_FILE')) + if $conf->get('BATCH_MODE'); + + # Until buildd parses status info from sbuild output, skipped must + # be treated as a failure. + if (defined($job)) { + if ($job->get_status() eq "successful" || + ($conf->get('SBUILD_MODE') ne "buildd" && + $job->get_status() eq "skipped")) { + exit 0; + } elsif ($job->get_status() eq "attempted") { + exit 2; + } elsif ($job->get_status() eq "given-back") { + #Probably needs a give back: + exit 3; + } + # Unknown status - probably needs a give back, but needs to be + # reported to the admin as failure: + exit 1; + } + debug("Error main(): $@") if $@; + exit 1; +} + +sub create_source_package ($) { + my $dsc = shift; + + open(my $pipe, '-|', 'dpkg-parsechangelog', + '-l' . $dsc . '/debian/changelog') + or Sbuild::Exception::Build->throw( + error => "Could not parse $dsc/debian/changelog: $!", + failstage => "pack-source"); + + my $pclog = Dpkg::Control->new(type => CTRL_CHANGELOG); + if (!$pclog->parse($pipe, 'dpkg-parsechangelog')) { + Sbuild::Exception::Build->throw( + error => "Could not parse $dsc/debian/changelog: $!", + failstage => "pack-source"); + } + + $pipe->close or Sbuild::Exception::Build->throw( + error => "dpkg-parsechangelog failed (exit status $?)", + failstage => "pack-source"); + + my $package = $pclog->{'Source'}; + my $version = $pclog->{'Version'}; + + if (!defined($package) || !defined($version)) { + Sbuild::Exception::Build->throw( + error => "Missing Source or Version in $dsc/debian/changelog", + failstage => "pack-source"); + } + + my $dist = $pclog->{'Distribution'}; + my $pver = Dpkg::Version->new($version, check => 1); + unless (defined $pver) { + Sbuild::Exception::Build->throw( + error => "Bad version $version in $dsc/debian/changelog", + failstage => "pack-source"); + } + + my ($uversion, $dversion); + $uversion = $pver->version(); + $dversion = "-" . $pver->revision(); + $dversion = "" if $pver->{'no_revision'}; + + if (!defined($conf->get('DISTRIBUTION')) || + !$conf->get('DISTRIBUTION')) { + $conf->set('DISTRIBUTION', $dist); + } + + my $dir = getcwd(); + my $origdir=$dir; + my $origdsc=$dsc; + # Note: need to support cases when invoked from a subdirectory + # of the build directory, i.e. $dsc/foo -> $dsc/.. in addition + # to $dsc -> $dsc/.. as below. + # We won't attempt to build the source package from the source + # directory so the source package files will go to the parent dir. + my $dscdir = abs_path("$dsc/.."); + if (index($dir, $dsc, 0) == 0) { + $conf->_set_default('BUILD_DIR', $dscdir); + } + + $dsc = "${dscdir}/${package}_${uversion}${dversion}.dsc"; + + $dir = $origdsc; + + chdir($dir) or Sbuild::Exception::Build->throw( + error => "Failed to change directory", + failstage => "pack-source"); + my @dpkg_source_before = ($conf->get('DPKG_SOURCE'), '--before-build'); + push @dpkg_source_before, @{$conf->get('DPKG_SOURCE_OPTIONS')} + if ($conf->get('DPKG_SOURCE_OPTIONS')); + push @dpkg_source_before, '.'; + system(@dpkg_source_before); + if ($?) { + Sbuild::Exception::Build->throw( + error => "Failed to run dpkg-source --before-build " . getcwd(), + failstage => "pack-source"); + } + if ($conf->get('CLEAN_SOURCE')) { + system($conf->get('FAKEROOT'), 'debian/rules', 'clean'); + if ($?) { + Sbuild::Exception::Build->throw( + error => "Failed to clean source directory $dir ($dsc)", + failstage => "pack-source"); + } + } + my @dpkg_source_command = ($conf->get('DPKG_SOURCE'), '-b'); + push @dpkg_source_command, @{$conf->get('DPKG_SOURCE_OPTIONS')} + if ($conf->get('DPKG_SOURCE_OPTIONS')); + push @dpkg_source_command, '.'; + system(@dpkg_source_command); + if ($?) { + Sbuild::Exception::Build->throw( + error => "Failed to package source directory " . getcwd(), + failstage => "pack-source"); + } + my @dpkg_source_after = ($conf->get('DPKG_SOURCE'), '--after-build'); + push @dpkg_source_after, @{$conf->get('DPKG_SOURCE_OPTIONS')} + if ($conf->get('DPKG_SOURCE_OPTIONS')); + push @dpkg_source_after, '.'; + system(@dpkg_source_after); + if ($?) { + Sbuild::Exception::Build->throw( + error => "Failed to run dpkg-source --after-build " . getcwd(), + failstage => "pack-source"); + } + chdir($origdir) or Sbuild::Exception::Build->throw( + error => "Failed to change directory", + failstage => "pack-source"); + + return $dsc; +} + +sub download_source_package ($) { + my $dsc = shift; + + my $srcdir = dirname($dsc); + my $dscbase = basename($dsc); + + my @fetched; + + # Work with a .dsc file. + # $file is the name of the downloaded dsc file written in a tempfile. + my $file; + $file = download($dsc, $dscbase) or + Sbuild::Exception::Build->throw( + error => "Could not download $dsc", + failstage => "download-source"); + push(@fetched, $dscbase); + + my @cwd_files = dsc_files($file); + + foreach (@cwd_files) { + my $subfile = download("$srcdir/$_", $_); + if (!$subfile) { + # Remove downloaded sources + foreach my $rm (@fetched) { + unlink($rm); + } + Sbuild::Exception::Build->throw( + error => "Could not download $srcdir/$_", + failstage => "download-source"); + } + push(@fetched, $_); + } + + return $file; +} + +# only called from main loop, but depends on job state. +sub write_jobs_file () { + if ($conf->get('BATCH_MODE')) { + + my $file = $conf->get('JOB_FILE'); + local( *F ); + + return if !open( F, ">$file" ); + if (defined($job)) { + print F $job->get('Package_OVersion') . ": " . + $job->get_status() . "\n"; + } + close( F ); + } +} + +sub status_trigger ($$) { + my $build = shift; + my $status = shift; + + write_jobs_file(); + + # Rewrite status if we need to give back or mark attempted + # following failure. Note that this must follow the above + # function calls because set_status will recursively trigger. + if ($status eq "failed" && + isin($build->get('Pkg Fail Stage'), + qw(fetch-src install-core install-essential install-deps + unpack check-unpacked-version check-space hack-binNMU + install-deps-env apt-get-clean apt-get-update + apt-get-upgrade apt-get-distupgrade))) { + $build->set_status('given-back'); + } elsif ($status eq "failed" && + isin ($build->get('Pkg Fail Stage'), + qw(build arch-check))) { + $build->set_status('attempted'); + } +} + +sub shutdown ($) { + my $signame = shift; + + $SIG{'INT'} = 'IGNORE'; + $SIG{'QUIT'} = 'IGNORE'; + $SIG{'TERM'} = 'IGNORE'; + $SIG{'ALRM'} = 'IGNORE'; + $SIG{'PIPE'} = 'IGNORE'; + + if (defined($job)) { + $job->request_abort("Received $signame signal"); + } else { + exit(1); + } + + $SIG{'INT'} = \&main::shutdown; + $SIG{'TERM'} = \&main::shutdown; + $SIG{'ALRM'} = \&main::shutdown; + $SIG{'PIPE'} = \&main::shutdown; +} + +sub dump_main_state () { + print STDERR Data::Dumper->Dump([$job], + [qw($job)] ); +} diff --git a/bin/sbuild-abort b/bin/sbuild-abort new file mode 100755 index 0000000..556188e --- /dev/null +++ b/bin/sbuild-abort @@ -0,0 +1,78 @@ +#!/usr/bin/perl +# +# Abort the current build. +# Copyright © 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> +# Copyright © 2003 Ryan Murray <rmurray@debian.org> +# Copyright © 2008 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +use Getopt::Long; +use Sbuild qw(help_text version_text usage_error check_group_membership); +use Sbuild::Conf qw(); +use Sbuild::OptionsBase; + +my $conf = Sbuild::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::OptionsBase->new($conf, "sbuild-abort", "1"); +exit 1 if !defined($options); +check_group_membership(); + +my $buildcount = 0; +my $linecount = 0; +my $header = ""; +my @detail = (); + +open (PIPE, "/bin/ps xjww |") or die "Can't run /bin/ps: $!\n"; +while(<PIPE>) { + chomp; + if ($linecount == 0) { + $header = $_; + } elsif (m/\/usr\/bin\/perl \/usr\/bin\/dpkg-buildpackage/) { + push @detail, $_; + $buildcount++; + } + $linecount++; +} +close (PIPE) or die "Can't close /bin/ps pipe: $!\n"; + +if ($buildcount == 0) { + print STDERR "E: No dpkg-buildpackage process found\n"; + exit 1; +} elsif ($buildcount > 1) { + print STDERR "E: More than one dpkg-buildpackage process found:\n"; + + print "I: $header\n"; + foreach (@detail) { + print "I: $_\n"; + } + exit 1; +} + +# Get PGID from saved ps output. +my @fields = split(/[[:space:]]+/, $detail[0]); +die "Error parsing /bin/ps output" if (@fields < 1); +my $pgid = $fields[2]; + +# Kill process group. +print "I: Killing process group $pgid\n"; +kill("TERM", -$pgid) or die "Error killing PGID $pgid: $!\n"; + +exit 0; diff --git a/bin/sbuild-adduser b/bin/sbuild-adduser new file mode 100755 index 0000000..033a4ed --- /dev/null +++ b/bin/sbuild-adduser @@ -0,0 +1,82 @@ +#!/usr/bin/perl -w +# +# Copyright © 2006-2008 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +package main; + +use Getopt::Long; +use Sbuild qw(help_text version_text usage_error); +use Sbuild::Conf qw(); +use Sbuild::OptionsBase; + +my $conf = Sbuild::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::OptionsBase->new($conf, "sbuild-adduser", "8"); +exit 1 if !defined($options); + +usage_error("sbuild-adduser", "Incorrect number of options") if (@ARGV < 1); + +my $status = 0; + +foreach (@ARGV) { + my $user = getpwnam($_); + + if (defined $user) { + $status += system(qw(/usr/sbin/adduser --), $_, 'sbuild'); + } else { + print STDERR "W: User \"$_\" does not exist\n"; + $status++; + } +} + +if ($status == 0) { + print STDOUT <<EOF; + +# Setup tasks for sudo users: + +# BUILD +# HOME directory in chroot, user:sbuild, 0770 perms, from +# passwd/group copying to chroot, filtered +# Maybe source 50sbuild, or move into common location. + +Next, copy the example sbuildrc file to the home directory of each user and +set the variables for your system: + +EOF + + foreach (@ARGV) { + my $home = (getpwnam($_))[7]; + print STDERR " cp /usr/share/doc/sbuild/examples/example.sbuildrc $home/.sbuildrc\n"; + } + print STDOUT <<EOF; + +Now try a build: + + cd /path/to/source + sbuild-update -ud <distribution> + (or "sbuild-apt <distribution> apt-get -f install" + first if the chroot is broken) + sbuild -d <distribution> <package>_<version> +EOF +} + +exit ($status ? 1:0); diff --git a/bin/sbuild-apt b/bin/sbuild-apt new file mode 100755 index 0000000..879f438 --- /dev/null +++ b/bin/sbuild-apt @@ -0,0 +1,69 @@ +#!/usr/bin/perl -w +# +# Copyright © 2006-2008 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +use Getopt::Long; +use Sbuild qw(help_text version_text usage_error check_group_membership); +use Sbuild::Utility qw(setup cleanup); +use Sbuild::Conf qw(); +use Sbuild::OptionsBase; +use Sbuild::AptResolver; +use Sbuild::ChrootRoot; + +my $conf = Sbuild::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::OptionsBase->new($conf, "sbuild-apt", "1"); +exit 1 if !defined($options); +check_group_membership(); + +usage_error("sbuild-apt", "Incorrect number of options") if (@ARGV < 2); + +my $chroot = shift @ARGV; +my $command = shift @ARGV; + +if ($command eq "apt-get") { + $command = $conf->get('APT_GET'); +} elsif ($command eq "apt-cache") { + $command = $conf->get('APT_CACHE'); +} else { + usage_error("sbuild-apt", + "Bad command $command. Allowed commands: apt-get or apt-cache\n"); +} + +my $session = setup('source', $chroot, $conf) or die "Chroot setup failed"; +my $host = Sbuild::ChrootRoot->new($conf); +$host->begin_session() or die "Chroot setup (host) failed"; + +my $resolver = Sbuild::AptResolver->new($conf, $session, $host); +$resolver->setup(); + +$resolver->run_apt_command( + { COMMAND => [$command, '-oAPT::Get::Assume-Yes=true', @ARGV], + ENV => {'DEBIAN_FRONTEND' => 'noninteractive'}, + USER => 'root', + PRIORITY => 1, + DIR => '/' }); +my $status = $? >> 8; + +cleanup($conf); + +exit $status; diff --git a/bin/sbuild-checkpackages b/bin/sbuild-checkpackages new file mode 100755 index 0000000..68c696c --- /dev/null +++ b/bin/sbuild-checkpackages @@ -0,0 +1,80 @@ +#!/usr/bin/perl -w +# check the package list in a chroot against a reference list. +# +# Copyright © 2006-2008 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +our $mode = undef; + +package Options; + +use Sbuild::OptionsBase; +use Sbuild::Conf qw(); + +BEGIN { + use Exporter (); + our (@ISA, @EXPORT); + + @ISA = qw(Exporter Sbuild::OptionsBase); + + @EXPORT = qw(); +} + +sub set_options { + my $self = shift; + + $self->add_options( + "l|list" => sub { $mode = "list"; }, + "s|set" => sub { $mode = "set"; }); +} + +package main; + +use locale; +use POSIX qw(locale_h); +use Getopt::Long; +use Sbuild qw(help_text version_text usage_error check_packages check_group_membership); +use Sbuild::Conf qw(); +use Sbuild::Utility qw(setup cleanup shutdown); + +my $conf = Sbuild::Conf::new(); +exit 1 if !defined($conf); +my $options = Options->new($conf, "sbuild-checkpackages", "1"); +exit 1 if !defined($options); +check_group_membership(); + +usage_error("sbuild-checkpackages", "--list or --set must be specified") + if (!defined($mode)); + +usage_error("sbuild-checkpackages", "A chroot must be specified") + if (@ARGV != 1); + +my $chroot = $ARGV[0]; + +setlocale(LC_COLLATE, "POSIX"); +$ENV{'LC_COLLATE'} = "POSIX"; + +my $session = setup('source', $chroot, $conf) or die "Chroot setup failed"; + +check_packages($session, $mode); + +cleanup($conf); +exit 0; diff --git a/bin/sbuild-clean b/bin/sbuild-clean new file mode 100755 index 0000000..b69346b --- /dev/null +++ b/bin/sbuild-clean @@ -0,0 +1,26 @@ +#!/usr/bin/perl -w +# +# Copyright © 2005-2009 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +print "$0 is deprecated. sbuild-clean functionality has been merged with "; +print "sbuild-update. Use sbuild-update instead.\n"; +exec("sbuild-update", "--clean", @ARGV) or die "Can't run sbuild-update: $!"; diff --git a/bin/sbuild-createchroot b/bin/sbuild-createchroot new file mode 100755 index 0000000..07e5912 --- /dev/null +++ b/bin/sbuild-createchroot @@ -0,0 +1,813 @@ +#!/usr/bin/perl +# +# Run debootstrap and add a few other files needed to create a working +# sbuild chroot. +# Copyright © 2004 Francesco P. Lovergine <frankie@debian.org>. +# Copyright © 2007-2010 Roger Leigh <rleigh@debian.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +umask 0022; + +use English; + +use Sbuild::AptResolver; + +package Conf; + +sub setup { + my $conf = shift; + + my $keyring = ''; + $keyring = '/etc/apt/trusted.gpg' + if -f '/etc/apt/trusted.gpg'; + + my %createchroot_keys = ( + 'CHROOT_PREFIX' => { + DEFAULT => undef + }, + 'CHROOT_SUFFIX' => { + DEFAULT => '-sbuild' + }, + 'FOREIGN' => { + DEFAULT => 0 + }, + 'INCLUDE' => { + DEFAULT => '' + }, + 'EXCLUDE' => { + DEFAULT => '' + }, + 'COMPONENTS' => { + DEFAULT => 'main' + }, + 'RESOLVE_DEPS' => { + DEFAULT => 1 + }, + 'KEEP_DEBOOTSTRAP_DIR' => { + DEFAULT => 0 + }, + 'DEBOOTSTRAP' => { + DEFAULT => 'debootstrap' + }, + 'KEYRING' => { + DEFAULT => undef + }, + 'SETUP_ONLY' => { + DEFAULT => 0 + }, + 'MAKE_SBUILD_TARBALL' => { + DEFAULT => '' + }, + 'KEEP_SBUILD_CHROOT_DIR' => { + DEFAULT => 0 + }, + 'DEB_SRC' => { + DEFAULT => 1 + }, + 'ALIASES' => { + DEFAULT => [] + }, + 'EXTRA_REPOSITORIES' => { + DEFAULT => [] + }, + 'COMMAND_PREFIX' => { + DEFAULT => '' + }, + 'CHROOT_MODE' => { + DEFAULT => 'schroot' + }, + 'MERGED_USR' => { + DEFAULT => 'auto' + }, + ); + + $conf->set_allowed_keys(\%createchroot_keys); +} + +package Options; + +use Sbuild::OptionsBase; +use Sbuild::Conf qw(); + +BEGIN { + use Exporter (); + our (@ISA, @EXPORT); + + @ISA = qw(Exporter Sbuild::OptionsBase); + + @EXPORT = qw(); +} + +sub set_options { + my $self = shift; + + $self->add_options( + "chroot-mode=s" => sub { + $self->set_conf('CHROOT_MODE', $_[1]); + }, + "chroot-prefix=s" => sub { + $self->set_conf('CHROOT_PREFIX', $_[1]); + }, + "chroot-suffix=s" => sub { + $self->set_conf('CHROOT_SUFFIX', $_[1]); + }, + "arch=s" => sub { + $self->set_conf('BUILD_ARCH', $_[1]); + }, + "foreign" => sub { + $self->set_conf('FOREIGN', 1); + }, + "resolve-deps" => sub { + $self->set_conf('RESOLVE_DEPS', 1) + }, + "no-resolve-deps" => sub { + $self->set_conf('RESOLVE_DEPS', 0) + }, + "keep-debootstrap-dir" => sub { + $self->set_conf('KEEP_DEBOOTSTRAP_DIR', 1) + }, + "debootstrap=s" => sub { + $self->set_conf('DEBOOTSTRAP', $_[1]) + }, + "exclude=s" => sub { + $self->set_conf('EXCLUDE', $_[1]); + }, + "include=s" => sub { + $self->set_conf('INCLUDE', $_[1]); + }, + "components=s" => sub { + $self->set_conf('COMPONENTS', $_[1]); + }, + "keyring=s" => sub { + $self->set_conf('KEYRING', $_[1]); + }, + "setup-only" => sub { + $self->set_conf('SETUP_ONLY', 1); + }, + "make-sbuild-tarball=s" => sub { + $self->set_conf('MAKE_SBUILD_TARBALL', $_[1]); + }, + "keep-sbuild-chroot-dir" => sub { + $self->set_conf('KEEP_SBUILD_CHROOT_DIR', 1); + }, + "no-deb-src" => sub { + $self->set_conf('DEB_SRC', 0); + }, + "alias=s" => sub { + push @{$self->get_conf('ALIASES')}, $_[1]; + }, + "extra-repository=s" => sub { + push @{$self->get_conf('EXTRA_REPOSITORIES')}, $_[1]; + }, + "command-prefix=s" => sub { + $self->set_conf('COMMAND_PREFIX', $_[1]); + }, + "merged-usr" => sub { + $self->set_conf('MERGED_USR', 1) + }, + "auto-merged-usr" => sub { + $self->set_conf('MERGED_USR', 'auto') + }, + "no-merged-usr" => sub { + $self->set_conf('MERGED_USR', 0) + }); +} + +package main; + +use POSIX; +use Getopt::Long qw(:config no_ignore_case auto_abbrev gnu_getopt); +use Sbuild qw(dump_file help_text version_text usage_error check_packages); +use Sbuild::ChrootPlain; +use Sbuild::ChrootUnshare; +use Sbuild::ChrootRoot; +use Sbuild::Sysconfig; +use Sbuild::Conf qw(); +use Sbuild::Utility; +use File::Basename qw(dirname); +use File::Path qw(mkpath rmtree); +use File::Temp qw(tempfile); +use File::Copy; +use Cwd qw(abs_path); +use IPC::Open3; +use File::Spec; + +sub add_items ($@); +sub makedir ($$); + +my %personalities = ( + 'armel:arm64' => 'linux32', + 'armhf:arm64' => 'linux32', + 'i386:amd64' => 'linux32', + 'mipsel:mips64el' => 'linux32', + 'powerpc:ppc64' => 'linux32', +); + +my $conf = Sbuild::Conf::new(); +Conf::setup($conf); +exit 1 if !defined($conf); +my $options = Options->new($conf, "sbuild-createchroot", "8"); +exit 1 if !defined($options); + + +usage_error("sbuild-createchroot", + "Incorrect number of options") if (@ARGV <2 || @ARGV >4); + +if ($conf->get('CHROOT_MODE') eq 'unshare' and !$conf->get('MAKE_SBUILD_TARBALL')) { + usage_error("sbuild-createchroot", + "--chroot-mode=unshare requires --make-sbuild-tarball to be set"); +} + +if ($conf->get('CHROOT_MODE') eq 'unshare' and $conf->get('SETUP_ONLY')) { + usage_error("sbuild-createchroot", + "--chroot-mode=unshare is incompatible with --setup-only") +} + +if ($conf->get('CHROOT_MODE') eq 'unshare' and scalar @{$conf->get('ALIASES')} > 0) { + usage_error("sbuild-createchroot", + "--chroot-mode=unshare is incompatible with --alias") +} + +if ($conf->get('CHROOT_MODE') ne 'schroot' and $conf->get('COMMAND_PREFIX')) { + usage_error("sbuild-createchroot", + "--command-prefix requires --chroot-mode=schroot") +} + +if ($conf->get('MAKE_SBUILD_TARBALL') and -e $conf->get('MAKE_SBUILD_TARBALL')) { + print STDERR "E: tarball already exists: ". $conf->get('MAKE_SBUILD_TARBALL') . "\n"; + exit 1; +} + +# Make sure fakeroot and build-essential are installed +$conf->set('INCLUDE', add_items($conf->get('INCLUDE'), + "fakeroot", + "build-essential")); + +# Deal with SUITE-VARIANT +my $suite = $ARGV[0]; + +# check if schroot name is already in use + +my $chrootname; +if (defined $conf->get('CHROOT_PREFIX') && $conf->get('CHROOT_PREFIX') ne "") { + $chrootname = $conf->get('CHROOT_PREFIX') +} else { + $chrootname = $suite +} +$chrootname .= "-" . $conf->get('BUILD_ARCH') . $conf->get('CHROOT_SUFFIX'); + +if ($conf->get('CHROOT_MODE') eq 'schroot') { + # We redirect stderr to /dev/null because otherwise schroot might print + # warnings on stderr which throws off autopkgtest + open(NULL, ">", File::Spec->devnull); + my $pid = open3(my $in = '', \*PH, \*NULL, 'schroot', '-l', '--all-source-chroots'); + while (my $line = <PH>) { + $line ne "source:$chrootname\n" or die "chroot with name $chrootname already exists"; + } + waitpid($pid, 0); + if (($? >> 8) != 0) { + die "schroot exited with non-zero exit status"; + } +} + +my $target = $ARGV[1]; +if (-e $target) { + if (!-d $target) { + die "$target exists and is not a directory"; + } + chmod 0755, $target or die "cannot chmod $target"; + # only check if the directory is empty if the --setup-only option is not + # given because that option needs an already populated directory + if (!$conf->get('SETUP_ONLY')) { + # check if the directory is empty or contains nothing more than an + # empty lost+found directory. The latter exists on freshly created + # ext3 and ext4 partitions. + # rationale for requiring an empty directory: https://bugs.debian.org/833525 + opendir(my $dh, $target) or die "Can't opendir($target): $!"; + while (my $entry = readdir $dh) { + # skip the "." and ".." entries + next if $entry eq "."; + next if $entry eq ".."; + # if the entry is a directory named "lost+found" then skip it + # if it's empty + if ($entry eq "lost+found" and -d "$target/$entry") { + opendir(my $dh2, "$target/$entry"); + # Attempt reading the directory thrice. If the third time + # succeeds, then it has more entries than just "." and ".." + # and must thus not be empty. + readdir $dh2; + readdir $dh2; + # rationale for requiring an empty directory: + # https://bugs.debian.org/833525 + if (readdir $dh2) { + die "$target contains a non-empty lost+found directory"; + } + closedir($dh2); + } else { + die "$target is not empty"; + } + } + closedir($dh); + } +} else { + # Create the target directory in advance so abs_path (which is buggy) + # won't fail. Remove if abs_path is replaced by something better. + makedir($target, 0755); +} +$target = abs_path($target); +my $script = undef; +my $mirror = "http://deb.debian.org/debian"; + +$mirror = $ARGV[2] if $#ARGV >= 2; +$script = $ARGV[3] if $#ARGV == 3; + +if ($conf->get('VERBOSE')) { + print "I: SUITE: $suite\n"; + print "I: TARGET: $target\n"; + print "I: MIRROR: $mirror\n"; + print "I: SCRIPT: $script\n" if (defined($script)); +} + +my @args = ("--arch=" . $conf->get('BUILD_ARCH'), + "--variant=buildd"); +push @args, "--verbose" if $conf->get('VERBOSE'); +push @args, "--foreign" if $conf->get('FOREIGN'); +push @args, "--keep-debootstrap-dir" if $conf->get('KEEP_DEBOOTSTRAP_DIR'); +push @args, "--include=" . $conf->get('INCLUDE') if $conf->get('INCLUDE'); +push @args, "--exclude=" . $conf->get('EXCLUDE') if $conf->get('EXCLUDE'); +push @args, "--components=" . $conf->get('COMPONENTS') + if $conf->get('COMPONENTS'); +push @args, "--keyring=" . $conf->get('KEYRING') if $conf->get('KEYRING'); +push @args, "--no-check-gpg" if defined $conf->get('KEYRING') && $conf->get('KEYRING') eq ""; +push @args, $conf->get('RESOLVE_DEPS') ? + "--resolve-deps" : "--no-resolve-deps"; +if ($conf->get('MERGED_USR') ne 'auto') { + push @args, $conf->get('MERGED_USR') ? + "--merged-usr" : "--no-merged-usr"; +} +push @args, "$suite", "$target", "$mirror"; +push @args, "$script" if $script; + +# Set the path to debootstrap +my $debootstrap = $conf->get('DEBOOTSTRAP'); + +# Get the name of the debootstrap binary +my $debootstrap_bin = $debootstrap; +$debootstrap_bin =~ s/^.*\///s; + +if ($conf->get('VERBOSE')) { + print "I: Running $debootstrap_bin " . join(' ',@args) . "\n"; +} + +my @idmap; +if ($conf->get('CHROOT_MODE') eq 'unshare') { + @idmap = read_subuid_subgid; + # sanity check + if (scalar(@idmap) != 2 || $idmap[0][0] ne 'u' || $idmap[1][0] ne 'g') { + printf STDERR "invalid idmap\n"; + return 0; + } +} + +# Run debootstrap with specified options. +if (!$conf->get('SETUP_ONLY')) { + if ($conf->get('CHROOT_MODE') eq 'unshare') { + if(!test_unshare) { + print STDERR "E: unable to to unshare\n"; + exit 1; + } + + makedir($target, 0755); + + my $outer_gid = $REAL_GROUP_ID+0; + system(get_unshare_cmd({ + IDMAP => [['u', '0', $REAL_USER_ID, '1'], + ['g', '0', $outer_gid, '1'], + ['u', '1', $idmap[0][2], '1'], + ['g', '1', $idmap[1][2], '1'], + ] + }), 'chown', '1:1', $target); + my @cmd = ('env', 'PATH=/usr/sbin:/usr/bin:/sbin:/bin', + get_unshare_cmd({UNSHARE_FLAGS => CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWIPC, FORK => 1, IDMAP => \@idmap}), 'sh', '-c', " + rootdir=\"\$1\"; shift; + hostname sbuild; + mkdir -p \"\$rootdir/dev\"; + for f in null zero full random urandom tty; do + touch \"\$rootdir/dev/\$f\"; + chmod -rwx \"\$rootdir/dev/\$f\"; + mount -o bind \"/dev/\$f\" \"\$rootdir/dev/\$f\"; + done; + mkdir -p \"\$rootdir/sys\"; + mount -o rbind /sys \"\$rootdir/sys\"; + mkdir -p \"\$rootdir/proc\"; + mount -t proc proc \"\$rootdir/proc\"; + mkdir -p \"\$rootdir/fakebin\"; + ln -sf /bin/true \"\$rootdir/fakebin/mknod\"; + ln -sf /bin/true \"\$rootdir/fakebin/mount\"; + export PATH=\"\$rootdir/fakebin:/fakebin:\$PATH\" + \"\$@\"; + exit_status=\$?; + rm \"\$rootdir/fakebin/mknod\" \"\$rootdir/fakebin/mount\"; + rm -d \"\$rootdir/fakebin\"; + exit \$exit_status; + ", '--', $target, $debootstrap, @args + ); + !system(@cmd) or die "E: Error running @cmd"; + } else { + if ($REAL_USER_ID == 0) { + chown(0, 0, $target) or die "cannot chown $target"; + } + !system($debootstrap, @args) or die "E: Error running $debootstrap_bin"; + } +} + +if (!($conf->get('SETUP_ONLY') && $conf->get('MAKE_SBUILD_TARBALL'))) { + my $sources_list = ""; + + # Add deb-src to /etc/apt/sources.list. + if ($conf->get('DEB_SRC')) { + my $comps = join(' ',split(/,/,$conf->get('COMPONENTS'))); + $sources_list .= "deb-src $mirror $suite $comps\n"; + } + + # Add extra repositories to /etc/apt/sources.list + for my $repo (@{$conf->get('EXTRA_REPOSITORIES')}) { + $sources_list .= "$repo\n"; + } + + my $passwd_sbuild = `getent passwd sbuild`; + my $group_sbuild = `getent group sbuild`; + + my $setup_script = <<"EOF"; +open (my \$passwd_fd, ">>", "\$target/etc/passwd") or die "cannot open /etc/passwd"; +print \$passwd_fd \$passwd_sbuild; +close(\$passwd_fd); +open (my \$group_fd, ">>", "\$target/etc/group") or die "cannot open /etc/group"; +print \$group_fd \$group_sbuild; +close(\$group_fd); + + +# Set up minimal /etc/hosts if it didn't exist yet. Normally, the package +# netbase would create the file. +my \$hosts = "\${target}/etc/hosts"; +if (! -e \$hosts) { + open(HOSTS, ">\$hosts") + or die "Can't open \$hosts for writing"; + # write the default content that would be created by the netbase package + print HOSTS <<"EOF2"; +127.0.0.1 localhost +::1 localhost ip6-localhost ip6-loopback +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters + +EOF2 + close HOSTS or die "Can't close \$hosts"; + + # Display /etc/hosts. + print "I: Configured /etc/hosts:\n"; + dump_file("\$hosts"); +} + +# Set up minimal /usr/sbin/policy-rc.d. +my \$policy_rc_d = "\${target}/usr/sbin/policy-rc.d"; +open(POLICY_RC_D, ">\$policy_rc_d") + or die "Can't open \$policy_rc_d for writing"; +print POLICY_RC_D <<"EOF2"; +#!/bin/sh +echo "All runlevel operations denied by policy" >&2 +exit 101 +EOF2 + +close POLICY_RC_D or die "Can't close \$policy_rc_d"; + +my (undef, undef, \$uid, undef) = getpwnam('root'); +chown(\$uid, -1, \$policy_rc_d) == 1 + or die "E: Failed to set root: ownership on \$policy_rc_d"; +chmod(0775, \$policy_rc_d) == 1 + or die "E: Failed to set 0755 permissions on \$policy_rc_d"; + +# Display /usr/sbin/policy-rc.d. +print "I: Configured /usr/sbin/policy-rc.d:\n"; +dump_file("\$policy_rc_d"); +EOF + + if ($conf->get('DEB_SRC') || scalar @{$conf->get('EXTRA_REPOSITORIES')} > 0) { + $setup_script .= <<"EOF"; +my \$sources = "\${target}/etc/apt/sources.list"; +open(SOURCES, ">>\$sources") + or die "E: Can't open \$sources for writing"; + +print SOURCES \$sources_list; +close SOURCES or die "E: Can't close \$sources"; +EOF + } + + $setup_script .= <<"EOF"; +# Display /etc/apt/sources.list. +print "I: Configured APT /etc/apt/sources.list:\n"; +dump_file("\${target}/etc/apt/sources.list"); +print "I: Please add any additional APT sources to \${target}/etc/apt/sources.list\n"; +EOF + if ($conf->get('CHROOT_MODE') eq 'unshare') { + my $group_sbuild = `getent group sbuild`; + $setup_script = <<"EOF"; +use strict; +use warnings; +use Sbuild qw(dump_file); +my \$target = \$ARGV[0]; +my \$passwd_sbuild = \$ARGV[1]; +my \$group_sbuild = \$ARGV[2]; +my \$sources_list = \$ARGV[3]; +$setup_script +EOF + !system(get_unshare_cmd( + {IDMAP => \@idmap}), 'perl', '-e', $setup_script, $target, $passwd_sbuild, $group_sbuild, $sources_list + ) or die "E: failed running setup script"; + } else { + eval $setup_script; + if ($@) { + die "E: failed running setup script: $@\n"; + } + } +} + +if ($conf->get('CHROOT_MODE') eq 'schroot') { + # Write out schroot chroot configuration. + + my $arch = $conf->get('BUILD_ARCH'); + my $config_entry = <<"EOF"; +[$chrootname] +description=Debian $suite/$arch autobuilder +groups=root,sbuild +root-groups=root,sbuild +profile=sbuild +EOF + + # Determine the schroot chroot configuration to use. + if ($conf->get('MAKE_SBUILD_TARBALL')) { + my $tarball = $conf->get('MAKE_SBUILD_TARBALL'); + + # Default to using tar gzip compression if unable to determine compression + # mode via file extension. + if ($tarball !~ /\.(tgz|tbz|tlz|txz|tar(\.(gz|bz2|lz|xz))?)$/) { + print "I: Renaming sbuild tarball '$tarball' to '$tarball.tar.gz'\n"; + $tarball .= ".tar.gz"; + $conf->set('MAKE_SBUILD_TARBALL', $tarball); + } + + $config_entry .= <<"EOF"; +type=file +file=$tarball +EOF + } else { + # Determine whether system has overlayfs capability + my $uniontype = "none"; + if (lc("$^O") =~ /linux/ && -e '/sbin/modprobe') { + my $ret = system(qw(/sbin/modprobe overlay)); + if ($ret == 0 && open(FILE, "/proc/filesystems")) { + if (grep {/\soverlay$/} <FILE>) { + $uniontype = "overlay"; + } + close(FILE); + } + } + + $config_entry .= <<"EOF"; +type=directory +directory=$target +union-type=$uniontype +EOF + } + + if (scalar @{$conf->get('ALIASES')} > 0) { + my $aliases = join ',', @{$conf->get('ALIASES')}; + $config_entry .= "aliases=$aliases\n"; + } + + if ($conf->get('COMMAND_PREFIX') ne '') { + $config_entry .= "command-prefix=" . $conf->get('COMMAND_PREFIX') . "\n"; + } + + if (-d "/etc/schroot/chroot.d") { + # TODO: Don't hardcode path + my $SCHROOT_CONF = + new File::Temp( TEMPLATE => "$chrootname-XXXXXX", + DIR => "/etc/schroot/chroot.d", + UNLINK => 0) + or die "Can't open schroot configuration file: $!\n"; + + print $SCHROOT_CONF "$config_entry"; + + my ($personality, $personality_message); + # Detect whether personality might be needed. + if ($conf->get('ARCH') ne $conf->get('BUILD_ARCH')) { + # Take care of the known case(s). + my $key = $conf->get('BUILD_ARCH') . ':' . $conf->get('ARCH'); + if (exists $personalities{$key}) { + $personality = $personalities{$key}; + $personality_message = + "I: Added personality=$personality automatically " . + "(" . $conf->get('BUILD_ARCH') . " on " . $conf->get('ARCH') . ").\n"; + } else { + $personality_message = + "W: The selected architecture and the current architecture do not match\n" . + "W: (" . $conf->get('BUILD_ARCH') . " versus " . $conf->get('ARCH') . ").\n" . + "I: You probably need to add a personality option (see schroot(1)).\n" . + "I: You may want to report your use case to the sbuild developers so that\n" . + "I: the appropriate option gets automatically added in the future.\n\n"; + } + } + + # Add personality if detected. + print $SCHROOT_CONF "personality=$personality\n" if $personality; + + # Needed to display file below. + $SCHROOT_CONF->flush(); + + # Display schroot configuration. + print "I: schroot chroot configuration written to $SCHROOT_CONF.\n"; + chmod 0644, "$SCHROOT_CONF"; + dump_file("$SCHROOT_CONF"); + print "I: Please rename and modify this file as required.\n"; + print $personality_message if $personality_message; + } +} + +if ($conf->get('CHROOT_MODE') eq 'schroot' || $conf->get('CHROOT_MODE') eq 'sudo') { + if (! -d "$Sbuild::Sysconfig::paths{'SBUILD_SYSCONF_DIR'}/chroot") { + makedir("$Sbuild::Sysconfig::paths{'SBUILD_SYSCONF_DIR'}/chroot", 0775); + } + + # Populate /etc/sbuild/chroot with a symlink to be able to use the chroot in + # sudo mode for directory based chroots + my $chrootlink = "$Sbuild::Sysconfig::paths{'SBUILD_SYSCONF_DIR'}/chroot/$chrootname"; + if ((defined $chrootlink) && (! $conf->get('MAKE_SBUILD_TARBALL'))) { + if (! -e $chrootlink) { + if (symlink($target, $chrootlink)) { + print "I: sudo chroot configuration linked as $Sbuild::Sysconfig::paths{'SBUILD_SYSCONF_DIR'}/chroot/$chrootname.\n"; + } else { + print STDERR "E: Failed to symlink $target to $chrootlink: $!\n"; + } + } else { + print "W: Not creating symlink $target to $chrootlink: file already exists\n"; + + } + } +} + +if (!$conf->get('SETUP_ONLY') || !$conf->get('MAKE_SBUILD_TARBALL')) { + # FIXME: also update packages with the unshare backend + if ($conf->get('ARCH') eq $conf->get('HOST_ARCH') && $conf->get('CHROOT_MODE') ne 'unshare') { + my $session = Sbuild::ChrootPlain->new($conf, $target); + my $host = Sbuild::ChrootRoot->new($conf); + if (defined($session)) { + $session->set('Log Stream', \*STDOUT); + + if (!$session->begin_session() || !$host->begin_session()) { + print STDERR "E: Error creating chroot session: skipping apt update\n"; + } else { + my $resolver = Sbuild::AptResolver->new($conf, $session, $host); + $resolver->setup(); + + print "I: Setting reference package list.\n"; + check_packages($session, "set"); + + print "I: Updating chroot.\n"; + my $status = $resolver->update(); + print "W: Failed to update APT package lists\n" + if ($status); + + $status = $resolver->distupgrade(); + print "W: Failed to upgrade chroot\n" + if ($status); + + $status = $resolver->clean(); + print "W: Failed to clean up downloaded packages\n" + if ($status); + + $resolver->cleanup(); + $session->end_session(); + $session = undef; + } + } + } elsif ($conf->get('ARCH') ne $conf->get('HOST_ARCH')) { + print "W: The selected architecture and the current architecture do not match\n"; + print "W: (" . $conf->get('BUILD_ARCH') . " versus " . $conf->get('ARCH') . ").\n"; + print "W: Not automatically updating APT package lists.\n"; + print "I: Run \"apt-get update\" and \"apt-get dist-upgrade\" prior to use.\n"; + print "I: Run \"sbuild-checkpackages --set\" to set reference package list.\n"; + } +} + +# This block makes the tarball chroot if one has been requested and delete +# the sbuild chroot directory created, unless it's been requested to keep the +# directory. +if ($conf->get('MAKE_SBUILD_TARBALL') && !$conf->get('SETUP_ONLY')) { + my ($tmpfh, $tmpfile) = tempfile("XXXXXX"); + my @program_list = ("/bin/tar", "-c", "-C", $target); + push @program_list, get_tar_compress_options($conf->get('MAKE_SBUILD_TARBALL')); + if ($conf->get('CHROOT_MODE') ne 'unshare') { + push @program_list, '-f', $tmpfile; + } + push @program_list, './'; + + print "I: Creating tarball...\n"; + if ($conf->get('CHROOT_MODE') eq 'unshare') { + open(my $in, '-|', get_unshare_cmd( + {IDMAP => \@idmap}), @program_list + ) // die "could not exec tar"; + if (copy($in, $tmpfile) != 1 ) { + die "unable to copy: $!\n"; + } + close($in) or die "Could not create chroot tarball: $?\n"; + } else { + system(@program_list) == 0 or die "Could not create chroot tarball: $?\n"; + } + + makedir(dirname($conf->get('MAKE_SBUILD_TARBALL')), 0755); + move("$tmpfile", $conf->get('MAKE_SBUILD_TARBALL')) or die "cannot mv to $conf->get('MAKE_SBUILD_TARBALL'): $!"; + chmod 0644, $conf->get('MAKE_SBUILD_TARBALL'); + + print "I: Done creating " . $conf->get('MAKE_SBUILD_TARBALL') . "\n"; + + if (! $conf->get('KEEP_SBUILD_CHROOT_DIR')) { + if ($conf->get('CHROOT_MODE') eq 'unshare') { + # this looks like a recipe for disaster, but since we execute "rm -rf" with + # lxc-usernsexec, we only have permission to delete the files that were + # created with the fake root user + system(get_unshare_cmd({IDMAP => \@idmap}), 'rm', '-rf', $target); + die "Unable to remove $target" if -e $target; + } else { + rmtree("$target"); + } + print "I: chroot $target has been removed.\n"; + } else { + print "I: chroot $target has been kept.\n"; + } +} + +print "I: Successfully set up $suite chroot.\n"; +if ($conf->get('CHROOT_MODE') eq 'schroot') { + print "I: Run \"sbuild-adduser\" to add new sbuild users.\n"; +} + +exit 0; + +# Add items to the start of a comma-separated list, and remove the +# items from later in the list if they were already in the list. +sub add_items ($@) { + my $items = shift; + my @add = @_; + + my $ret = ''; + my %values; + + foreach (@_) { + $values{$_} = ''; + $ret .= "$_," + } + + # Only add if not already used, to eliminate duplicates. + foreach (split (/,/,$items)) { + $ret .= "$_," if (!defined($values{$_})); + } + + # Remove trailing comma. + $ret =~ s/,$//; + + return $ret; +} + +sub makedir ($$) { + my $dir = shift; + my $perms = shift; + + mkpath($dir, + { mode => $perms, + verbose => 1, + error => \my $error + }); + + for my $diag (@$error) { + my ($file, $message) = each %$diag; + print "E: Can't make directory $file: $message\n"; + } +} diff --git a/bin/sbuild-cross-resolver b/bin/sbuild-cross-resolver new file mode 100755 index 0000000..110fd53 --- /dev/null +++ b/bin/sbuild-cross-resolver @@ -0,0 +1,77 @@ +#!/usr/bin/perl +# +# This script is in the public domain +# +# Author: Johannes Schauer Marin Rodrigues <josch@mister-muffin.de> +# +# Thin layer around /usr/lib/apt/solvers/apt which removes M-A:foreign and +# Essential:yes packages that are not arch:all and not arch:native from the +# EDSP before handing it to the apt solver. This is useful for resolving cross +# build dependencies as it makes sure that M-A:foreign packages and +# Essential:yes packages in the solution must come from the build architecture. + +use strict; +use warnings; + +if (! -e '/usr/lib/apt/solvers/apt') { + printf STDOUT 'Error: ERR_NO_SOLVER\n'; + printf STDOUT 'Message: The external apt solver doesn\'t exist. You must install the apt-utils package.\n'; + exit 1; +} + +my $buffer = ''; +my $architecture = undef; +my $essential = 0; +my $multiarch = 'no'; +my $build_arch; +sub keep { + if ( $multiarch ne 'foreign' and !$essential ) { + return 1; + } + if ( !defined $architecture ) { + print STDOUT 'Error: ERR_NO_ARCH\n'; + print STDOUT 'Message: package without architecture\n'; + exit 1; + } + if ( $architecture eq 'all' or $architecture eq $build_arch ) { + return 1; + } + return 0; +} +open my $fh, '|-', '/usr/lib/apt/solvers/apt'; +my $first_stanza = 1; +while ( my $line = <STDIN> ) { + $buffer .= $line; + if ( $line eq "\n" ) { + if ($first_stanza) { + if (! defined $architecture) { + print STDOUT 'ERROR: ERR_NO_ARCH'; + print STDOUT 'Message: no Architecture field in first stanza'; + exit 1; + } + $build_arch = $architecture; + $first_stanza = 0; + } + if (keep) { + print $fh $buffer; + } + $buffer = ''; + $architecture = undef; + $essential = 0; + $multiarch = 'no'; + next; + } + if ( $line =~ /^Essential: yes\n$/ ) { + $essential = 1; + } + if ( $line =~ /^Multi-Arch: (.*)\n$/ ) { + $multiarch = $1; + } + if ( $line =~ /^Architecture: (.*)\n$/ ) { + $architecture = $1; + } +} +if (keep) { + print $fh $buffer; +} +close $fh; diff --git a/bin/sbuild-debian-developer-setup b/bin/sbuild-debian-developer-setup new file mode 100755 index 0000000..fa9fb51 --- /dev/null +++ b/bin/sbuild-debian-developer-setup @@ -0,0 +1,77 @@ +#!/usr/bin/perl +# +# Set up sbuild so that packages for Debian unstable can be built and +# maintenance is done automatically via a daily update cronjob. +# Copyright © 2017 Michael Stapelberg <stapelberg@debian.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; +use v5.10; +use Getopt::Long; +use Sbuild qw(help_text); + +my $dist = "debian"; +my $suite = "unstable"; +chomp(my $arch = `dpkg --print-architecture`); + +my @options = ( + 'distribution=s' => \$dist, + 'suite=s' => \$suite, + 'arch=s' => \$arch, + 'help' => sub { help_text(1, "sbuild-debian-developer-setup") }, + ); +GetOptions(@options); + +if (!defined($ENV{SUDO_USER})) { + die "Please run sudo $0"; +} + +system("adduser", "--", $ENV{SUDO_USER}, "sbuild") == 0 + or die "adduser failed: $?"; + +sub chroot_exists { + system("schroot -i -c chroot:$suite-$arch-sbuild >/dev/null 2>&1") == 0 +} + +if (!chroot_exists()) { + my @aliases = (); + if ( $suite eq "unstable" || $suite eq "sid" ) { + @aliases = ( "--alias=UNRELEASED", "--alias=UNRELEASED-$arch-sbuild" ); + if ( $suite eq "unstable" ) { + push @aliases, "--alias=sid"; + } + if ( $suite eq "sid" ) { + push @aliases, "--alias=unstable"; + } + } + system("sbuild-createchroot", + "--command-prefix=eatmydata", + "--include=eatmydata", + @aliases, + "$suite", + "/srv/chroot/$suite-$arch-sbuild", + "http://localhost:3142/deb.debian.org/debian") == 0 + or die "sbuild-createchroot failed: $!"; +} else { + say "chroot $suite-$arch-sbuild already exists"; +} + +say "Your current user is now part of the sbuild group (no need to run sbuild-adduser) and a chroot environment exists in /srv/chroot/$suite-$arch-sbuild"; + +say "Now run `newgrp sbuild', or log out and log in again."; diff --git a/bin/sbuild-debuild b/bin/sbuild-debuild new file mode 100644 index 0000000..ca15a77 --- /dev/null +++ b/bin/sbuild-debuild @@ -0,0 +1,391 @@ +#!/usr/bin/perl -w +# +# Copyright © 2005-2009 Roger Leigh <rleigh@debian.org> +# Copyright © 2009 Andres Mejia <mcitadel@gmail.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +package Conf; + +sub setup { + my $conf = shift; + + my %update_keys = ( + 'DPKG_BUILDPACKAGE_OPTS' => { + DEFAULT => ['-S', + '-us', + '-uc', + ] + }, + 'DPKG_BUILDPACKAGE_EXTRA_OPTS' => { + DEFAULT => [] + }, + 'SBUILD_OPTS' => { + DEFAULT => [] + }, + 'SBUILD_EXTRA_OPTS' => { + DEFAULT => [] + }, + 'LINTIAN_OPTS' => { + DEFAULT => [] + }, + 'LINTIAN_EXTRA_OPTS' => { + DEFAULT => [] + }, + 'NO_LINTIAN' => { + DEFAULT => 0 + }, + 'PRE_DPKG_BUILDPACKAGE_COMMANDS' => { + DEFAULT => [] + }, + 'PRE_SBUILD_COMMANDS' => { + DEFAULT => [] + }, + 'PRE_LINTIAN_COMMANDS' => { + DEFAULT => [] + }, + 'PRE_EXIT_COMMANDS' => { + DEFAULT => [] + }, + ); + + $conf->set_allowed_keys(\%update_keys); +} + +# This subroutine is used to read a different set of config files for +# sbuild-debuild +sub read_sbuild_debuild_config { + my $self = shift; + + # Our HOME environment variable. + my $HOME = $self->get('HOME'); + + # Variables are undefined, so config will default to DEFAULT if unset. + my $dpkg_buildpackage_opts = undef; + my $dpkg_buildpackage_extra_opts = undef; + my $sbuild_opts = undef; + my $sbuild_extra_opts = undef; + my $lintian_opts = undef; + my $lintian_extra_opts = undef; + my $no_lintian = undef; + my $pre_dpkg_buildpackage_commands = undef; + my $pre_sbuild_commands = undef; + my $pre_lintian_commands = undef; + my $pre_exit_commands = undef; + + foreach ("/etc/sbuild/sbuild-debuild.conf", "$HOME/.sbuild-debuildrc") { + if (-r $_) { + my $e = eval `cat "$_"`; + if (!defined($e)) { + print STDERR "E: $_: Errors found in configuration file:\n$@"; + exit(1); + } + } + } + + $self->set('DPKG_BUILDPACKAGE_OPTS', $dpkg_buildpackage_opts) + if ($dpkg_buildpackage_opts); + push(@{$self->get('DPKG_BUILDPACKAGE_OPTS')}, + @{$dpkg_buildpackage_extra_opts}) if ($dpkg_buildpackage_extra_opts); + $self->set('SBUILD_OPTS', $sbuild_opts) + if ($sbuild_opts); + push(@{$self->get('SBUILD_OPTS')}, @{$sbuild_extra_opts}) + if ($sbuild_extra_opts); + $self->set('LINTIAN_OPTS', $lintian_opts) + if ($lintian_opts); + push(@{$self->get('LINTIAN_OPTS')}, @{$lintian_extra_opts}) + if ($lintian_extra_opts); + $self->set('NO_LINTIAN', $no_lintian) + if ($no_lintian); + $self->set('PRE_DPKG_BUILDPACKAGE_COMMANDS', + $pre_dpkg_buildpackage_commands) if ($pre_dpkg_buildpackage_commands); + $self->set('PRE_SBUILD_COMMANDS', $pre_sbuild_commands) + if ($pre_sbuild_commands); + $self->set('PRE_LINTIAN_COMMANDS', $pre_lintian_commands) + if ($pre_lintian_commands); + $self->set('PRE_EXIT_COMMANDS', $pre_exit_commands) + if ($pre_exit_commands); +} + +package Options; + +use Sbuild::OptionsBase; +use Sbuild::Conf; + +BEGIN { + use Exporter (); + our (@ISA, @EXPORT); + + @ISA = qw(Exporter Sbuild::OptionsBase); + + @EXPORT = qw(); +} + +sub set_options { + my $self = shift; + + $self->add_options( + "dpkg-buildpackage-opts=s" => sub { + my @opt_values = split(/\s+/,$_[1]); + $self->set_conf('DPKG_BUILDPACKAGE_OPTS', \@opt_values); + }, + "dpkg-buildpackage-extra-opts=s" => sub { + my @opt_values = split(/\s+/,$_[1]); + push(@{$self->get_conf('DPKG_BUILDPACKAGE_OPTS')}, + @opt_values); + }, + "sbuild-opts=s" => sub { + my @opt_values = split(/\s+/,$_[1]); + $self->set_conf('SBUILD_OPTS', \@opt_values); + }, + "sbuild-extra-opts=s" => sub { + my @opt_values = split(/\s+/,$_[1]); + push(@{$self->get_conf('SBUILD_OPTS')}, + @opt_values); + }, + "lintian-opts=s" => sub { + my @opt_values = split(/\s+/,$_[1]); + $self->set_conf('LINTIAN_OPTS', \@opt_values); + }, + "lintian-extra-opts=s" => sub { + my @opt_values = split(/\s+/,$_[1]); + push(@{$self->get_conf('LINTIAN_OPTS')}, + @opt_values); + }, + "no-lintian" => sub { + $self->set_conf('NO_LINTIAN', 1); + }, + "pre-dpkg-buildpackage-commands=s" => sub { + push(@{$self->get_conf('PRE_DPKG_BUILDPACKAGE_COMMANDS')}, $_[1]); + }, + "pre-sbuild-commands=s" => sub { + push(@{$self->get_conf('PRE_SBUILD_COMMANDS')}, $_[1]); + }, + "pre-lintian-commands=s" => sub { + push(@{$self->get_conf('PRE_LINTIAN_COMMANDS')}, $_[1]); + }, + "pre-exit-commands=s" => sub { + push(@{$self->get_conf('PRE_EXIT_COMMANDS')}, $_[1]); + }, + ); +} + +package main; + +use Getopt::Long; +use Cwd; +use File::Basename; +use Sbuild qw(help_text version_text usage_error check_group_membership); + +my $conf = Sbuild::Conf::new(); +Conf::setup($conf); +exit 1 if !defined($conf); +$conf->read_sbuild_debuild_config(); +my $options = Options->new($conf, "sbuild-debuild", "1"); +exit 1 if !defined($options); +check_group_membership(); + +# This subroutine determines the architecture we're building for. +sub detect_arch { + # Detect if we're building for another architecture + my @values = grep {/--arch=.+/} @{$conf->get('SBUILD_OPTS')}; + my $arch_opt = $values[0]; + $arch_opt =~ s/--arch=// if ($arch_opt); + + # Determine the arch using dpkg-architecture + my $dpkg_arch_command = '/usr/bin/dpkg-architecture -qDEB_HOST_ARCH'; + $dpkg_arch_command .= " -a$arch_opt" if ($arch_opt); + + # Grab the architecture and return it. We discard output from STDERR + # to suppress the "Specified GNU system ... does not match" warning that + # may occur from dpkg-architecture + my $arch = qx($dpkg_arch_command 2>/dev/null); + chomp $arch; + return $arch; +} + +# This subroutine determines the package and version we're building for +sub detect_package_and_version { + my ($build_input) = @_; + + # chdir into package directories, but not dsc files + chdir($build_input) unless ($build_input =~ /.*\.dsc$/); + + my $output; + if ($build_input =~ m/.*\.dsc$/) { + # Read the dsc file directly. + open($output, '<', $build_input); + } else { + # Grab the output from dpkg-parsechangelog + my $dpkg_parsechangelog = '/usr/bin/dpkg-parsechangelog'; + open($output, '-|', $dpkg_parsechangelog); + } + + # Grab the package and version info + my ($package, $version); + while (<$output>) { + $package = $1 if ($_ =~ /^Source: (.*)$/); + $version = $1 if ($_ =~ /^Version: (.*)$/); + last if (($package) and ($version)); + } + return ($package, $version); +} + +# This subroutine strips the epoch from a Debian package version. +sub strip_epoch { + my ($version) = @_; + $version =~ s/^[^:]*?://; + return $version; +} + +# This subroutine processes the array external ommands to run at the various +# stages of sbuild-debuild's run +sub process_commands { + my ($commands, $dsc, $source_changes, $bin_changes) = @_; + + # Determine which set of commands to run based on the string $commands + my @commands; + if ($commands eq "pre_dpkg_buildpackage_commands") { + @commands = @{$conf->get('PRE_DPKG_BUILDPACKAGE_COMMANDS')}; + } elsif ($commands eq "pre_sbuild_commands") { + @commands = @{$conf->get('PRE_SBUILD_COMMANDS')}; + } elsif ($commands eq "pre_lintian_commands") { + @commands = @{$conf->get('PRE_LINTIAN_COMMANDS')}; + } elsif ($commands eq "pre_exit_commands") { + @commands = @{$conf->get('PRE_EXIT_COMMANDS')}; + } + + # Run each command, substituting the various @SBUILD_DEBUILD_*@ options from + # the commands to run with the appropriate subsitutions. + my $returnval = 1; + foreach my $command (@commands) { + $command =~ s/\@SBUILD_DEBUILD_DSC@/$dsc/; + $command =~ s/\@SBUILD_DEBUILD_SOURCE_CHANGES@/$source_changes/; + $command =~ s/\@SBUILD_DEBUILD_BIN_CHANGES@/$bin_changes/; + my @args = split(/\s+/, $command); + print "Running $command\n"; + system(@args); + if (($? >> 8) != 0) { + print "Failed to run command ($command): $?"; + $returnval = 0; + } + } + return $returnval; +} + +# Subroutine to determine various files we'll be working with. +sub detect_files { + my ($build_input) = @_; + + # Determine the dsc and changes files that we'll use + my ($package, $version) = detect_package_and_version($build_input); + $version = strip_epoch($version); + my $arch = detect_arch(); + my ($dsc, $dirname); + if ($build_input =~ /.*\.dsc$/) { + $dsc = Cwd::abs_path("$build_input"); + $dirname = Cwd::abs_path(dirname($build_input)); + } else { + $dirname = Cwd::abs_path("$build_input/.."); + $dsc = "$dirname/" . $package . "_" . "$version.dsc"; + } + my $source_changes = "$dirname/" . $package . "_$version" . + "_source.changes"; + my $bin_changes = "$dirname/" . $package . "_$version" . "_$arch.changes"; + + return ($dsc, $source_changes, $bin_changes); +} + +# Process a debianized package directory or .dsc file. +sub process_package { + my ($build_input) = @_; + + # We use this to determine if there was a problem processing the external + # commands. + my $returnval = 1; + + # Save the current directory we're in. + my $currentdir = getcwd(); + + # Determine the dsc and changes files that we'll use + my ($dsc, $source_changes, $bin_changes) = detect_files($build_input); + + print "Processing pre dpkg-buildpackage commands.\n"; + $returnval = 0 unless process_commands("pre_dpkg_buildpackage_commands", + $dsc, $source_changes, $bin_changes); + + if ($build_input !~ /.*\.dsc$/) { + chdir($build_input); + print "Running dpkg-buildpackage.\n"; + system('/usr/bin/dpkg-buildpackage', + @{$conf->get('DPKG_BUILDPACKAGE_OPTS')}); + if (($? >> 8) != 0) { + print "Running dpkg-buildpckage failed: $?"; + chdir($currentdir); + return 0; + } + } + + print "Processing pre sbuild commands.\n"; + $returnval = 0 unless process_commands("pre_sbuild_commands", $dsc, + $source_changes, $bin_changes); + + chdir(dirname($dsc)); + print "Running sbuild.\n"; + system('/usr/bin/sbuild', @{$conf->get('SBUILD_OPTS')}, $dsc); + if (($? >> 8) != 0) { + print "Running sbuild failed: $?"; + chdir($currentdir); + return 0; + } + + print "Processing pre lintian commands.\n"; + $returnval = 0 unless process_commands("pre_lintian_commands", $dsc, + $source_changes, $bin_changes); + + if ((!$conf->get('NO_LINTIAN')) && (-x '/usr/bin/lintian')) { + print "Running lintian.\n"; + system('/usr/bin/lintian', @{$conf->get('LINTIAN_OPTS')}, '--', $bin_changes); + if (($? >> 8) != 0) { + print "Running lintian failed: $?"; + chdir($currentdir); + return 0; + } + } + + print "Processing commands run before exiting.\n"; + $returnval = 0 unless process_commands("pre_exit_commands", $dsc, + $source_changes, $bin_changes); + + # Go back to the directory we were in earlier + chdir($currentdir); + return $returnval; +} + +# Process each package directory and .dsc file. +my $status = 0; +if (!@ARGV) { + $status = 1 unless process_package(getcwd()); +} else { + foreach my $arg (@ARGV) { + $status = 1 unless process_package($arg); + } +} +exit $status; diff --git a/bin/sbuild-destroychroot b/bin/sbuild-destroychroot new file mode 100755 index 0000000..264c2ba --- /dev/null +++ b/bin/sbuild-destroychroot @@ -0,0 +1,181 @@ +#!/usr/bin/perl +# +# Destroy a chroot created by sbuild-createchroot +# +# Copyright © 2016 Johannes Schauer Marin Rodrigues <josch@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +package Conf; + +sub setup { + my $conf = shift; + + my %destroychroot_keys = ( + 'CHROOT_SUFFIX' => { + DEFAULT => '-sbuild' + }, + ); + + $conf->set_allowed_keys(\%destroychroot_keys); +} + + +package Options; + +use Sbuild::OptionsBase; +use Sbuild::Conf qw(); + +BEGIN { + use Exporter (); + our (@ISA, @EXPORT); + + @ISA = qw(Exporter Sbuild::OptionsBase); + + @EXPORT = qw(); +} + +sub set_options { + my $self = shift; + + $self->add_options( + "chroot-suffix=s" => sub { + $self->set_conf('CHROOT_SUFFIX', $_[1]); + }, + "arch=s" => sub { + $self->set_conf('BUILD_ARCH', $_[1]); + }, + ); +} + +package main; + +use POSIX; +use Getopt::Long qw(:config no_ignore_case auto_abbrev gnu_getopt); +use Sbuild qw(usage_error); +use Sbuild::Utility; +use Sbuild::ChrootInfoSchroot; + +my $conf = Sbuild::Conf::new(); +Conf::setup($conf); +exit 1 if !defined($conf); +my $options = Options->new($conf, "sbuild-destroychroot", "8"); +exit 1 if !defined($options); + +usage_error("sbuild-destroychroot", + "Incorrect number of options") if (scalar @ARGV != 1); + +my $chroot = Sbuild::Utility::get_dist($ARGV[0]); + +my $chroot_info = Sbuild::ChrootInfoSchroot->new($conf); +my $session = $chroot_info->create("source", + $chroot, + undef, # TODO: Add --chroot option + $conf->get('BUILD_ARCH')); + +if (!defined $session) { + die "Error creating chroot info\n"; +} + +my $chroot_id = $session->get('Chroot ID'); + +$chroot_id =~ s/^source://; + +opendir my $dir, "/etc/schroot/chroot.d" or die "Cannot open /etc/schroot/chroot.d: $!"; +my @files = readdir $dir; +closedir $dir; + +my $config_path; +foreach my $file (@files) { + my $ininame = "/etc/schroot/chroot.d/$file"; + -f $ininame || next; + open F, $ininame or die "cannot open $ininame\n"; + my $firstline = <F>; + chomp $firstline; + close F; + $firstline eq "[$chroot_id]" || next; + $config_path = $ininame; + last; +} + +if (! defined $config_path) { + die "Cannot find configuration file for $chroot_id in /etc/schroot/chroot.d\n"; +} + +my $chroot_type; +my $chroot_path; +open F, $config_path or die "cannot open $config_path\n"; +while (<F>) { + if (/^type=(.*)/) { + $chroot_type = $1; + } + if (/^directory=(.*)/) { + $chroot_path = $1; + } + if (/^file=(.*)/) { + $chroot_path = $1; + } +} +close F; + +if (! defined $chroot_type) { + die "type key missing from config\n"; +} + +if (! defined $chroot_path) { + die "directory or file key missing from config\n"; +} + +if ($chroot_type ne "file" && $chroot_type ne "directory") { + die "unknown chroot type: $chroot_type\n"; +} + +print "Before deleting the chroot, make sure that it is not in use anymore.\n"; +print "Specifically, make sure that no open schroot session is using it\n"; +print "anymore by running:\n"; +print "\n"; +print " schroot --all-sessions --list\n"; +print "\n"; +if ($chroot_type eq "directory") { + print "Make sure that no other process is using the chroot directory anymore, \n"; + print "for example by running:\n"; + print "\n"; + print " lsof $chroot_path\n"; + print "\n"; + print "Delete the chroot, for example by running:\n"; + print "\n"; + print " rm --recursive --one-file-system $chroot_path\n"; + print "\n"; + if (-e "$Sbuild::Sysconfig::paths{'SBUILD_SYSCONF_DIR'}/chroot/$chroot") { + print "Delete the chroot link, for example by running:\n"; + print "\n"; + print " rm $Sbuild::Sysconfig::paths{'SBUILD_SYSCONF_DIR'}/chroot/$chroot\n"; + print "\n"; + } +} else { + print "Delete the tarball, for example by running:\n"; + print "\n"; + print " rm $chroot_path\n"; + print "\n"; +} +print "Finally, delete the schroot configuration file, for example by running:\n"; +print "\n"; +print " rm $config_path\n"; +print "\n"; diff --git a/bin/sbuild-distupgrade b/bin/sbuild-distupgrade new file mode 100755 index 0000000..4eef909 --- /dev/null +++ b/bin/sbuild-distupgrade @@ -0,0 +1,26 @@ +#!/usr/bin/perl -w +# +# Copyright © 2006-2008 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +print "$0 is deprecated. Use sbuild-update --dist-upgrade directly instead.\n"; +exec("sbuild-update", "--dist-upgrade", @ARGV) or + die "Can't run sbuild-update: $!"; diff --git a/bin/sbuild-hold b/bin/sbuild-hold new file mode 100755 index 0000000..1d14a04 --- /dev/null +++ b/bin/sbuild-hold @@ -0,0 +1,58 @@ +#!/usr/bin/perl -w +# changes the dpkg status of a package in a chroot to "hold" +# +# Copyright © 2006,2008 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +use Getopt::Long; +use Sbuild qw(help_text version_text usage_error check_group_membership); +use Sbuild::Utility qw(setup cleanup shutdown); +use Sbuild::ChrootSetup qw(hold_packages list_packages); +use Sbuild::Conf qw(); +use Sbuild::OptionsBase; + +my $conf = Sbuild::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::OptionsBase->new($conf, "sbuild-hold", "1"); +exit 1 if !defined($options); +check_group_membership(); + +usage_error("sbuild-hold", "Incorrect number of options") if (@ARGV < 2); + +my $chroot = Sbuild::Utility::get_dist($ARGV[0]); + +my $session = setup('source', $ARGV[0], $conf) or die "Chroot setup failed for $chroot chroot"; + +print STDOUT "Holding packages in $chroot chroot:"; +shift @ARGV; +foreach (@ARGV) { + print STDOUT " $_"; +} +print STDOUT ".\n\n"; + +my $status = hold_packages($session, $conf, @ARGV); +$status >>= 8; + +list_packages($session, $conf, @ARGV); + +cleanup($conf); + +exit $status; diff --git a/bin/sbuild-qemu b/bin/sbuild-qemu new file mode 100755 index 0000000..52ab6ff --- /dev/null +++ b/bin/sbuild-qemu @@ -0,0 +1,190 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright © 2020-2024 Christian Kastner <ckk@debian.org> +# 2024 Johannes Schauer Marin Rodrigues <josch@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + + +import argparse +import os +import subprocess +import sys + + +DEB_ARCH_TO_QEMU = { + 'amd64': 'x86_64', + 'arm64': 'aarch64', + 'armhf': 'arm', + 'i386': 'i386', + 'ppc64el': 'ppc64le', +} + +IMAGEDIR = os.environ.get( + 'IMAGEDIR', + os.path.join(os.path.expanduser('~'), '.cache', 'sbuild'), +) + +DEFAULT_ARCH = subprocess.check_output( + ['dpkg', '--print-architecture'], + text=True, +).strip() + + +def main(): + # init options + parser = argparse.ArgumentParser( + description="Build Debian packages with sbuild(1) using QEMU images", + epilog="All options other than the ones described below are passed on " + "through to sbuild(1), though the options --dist, --arch, and " + "--build are peeked at when looking for images. The image will " + "be started in snapshot mode, so the image is never modified. " + "Multiple processes can use the same image concurrently. The " + "architectures currently supported by sbuild-qemu are: " + f"{', '.join(DEB_ARCH_TO_QEMU.keys())}.", + ) + parser.add_argument( + '--image', + action='store', + help="QEMU image file to use for building. If not specified, " + "sbuild-qemu will look for an image with the name " + "DIST-autopkgtest-ARCH.img, where DIST is taken from --dist " + "if present, and ARCH is taken from --arch or --build if " + "present. Otherwise, DIST defaults to 'unstable', and ARCH to " + "the host architecture. sbuild-qemu will first look in the " + "current directory for such an image, and then in the directory " + "$IMAGEDIR. A suitable image can be created with " + "qemu-sbuild-create(1).", + ) + parser.add_argument( + '--ram-size', + metavar='MiB', + action='store', + default=2048, + help=f"VM memory size in MB. Default: 2048", + ) + parser.add_argument( + '--cpus', + metavar='CPUs', + action='store', + default=2, + help="VM CPU count. Default: 2", + ) + parser.add_argument( + '--overlay-dir', + action='store', + default='/tmp', + help="Directory for the temporary image overlay instead of " + "autopkgtest's default of /tmp (or $TMPDIR).", + ) + parser.add_argument( + '--noexec', + action='store_true', + help="Don't actually do anything. Just print the sbuild(1) command " + "string that would be executed, and then exit.", + ) + parser.add_argument( + '--autopkgtest-debug', + action='store_true', + help="Enable debug output for the autopkgtest-virt-qemu(1) driver.", + ) + parser.add_argument( + '--boot', + choices=['auto', 'bios', 'efi', 'ieee1275', 'none'], + default='auto', + help="How to boot the image. Default is BIOS on amd64 and i386, EFI " + "on arm64 and armhf, and IEEE1275 on ppc64el.", + ) + parsed_args, unparsed_args = parser.parse_known_args() + + # These aren't options for us specifically, but we use them for guessing + # image locations + peeker = argparse.ArgumentParser() + peeker.add_argument( + '--dist', + action='store', + default='unstable', + ) + peeker.add_argument( + '--arch', + action='store', + default=DEFAULT_ARCH, + ) + peeker.add_argument( + '--build', + action='store', + ) + peeked_args, _ = peeker.parse_known_args(unparsed_args) + + build_arch = peeked_args.build or peeked_args.arch + try: + qemu_arch = DEB_ARCH_TO_QEMU[build_arch] + except KeyError: + print(f"Unsupported architecture: {build_arch}", file=sys.stderr) + print("Supported architectures are: ", file=sys.stderr, end="") + print(f"{', '.join(DEB_ARCH_TO_QEMU.keys())}", file=sys.stderr) + sys.exit(1) + + if parsed_args.image: + if os.path.exists(os.path.abspath(parsed_args.image)): + image = parsed_args.image + else: + image = os.path.join(IMAGEDIR, parsed_args.image) + else: + guessed_name = f'{peeked_args.dist}-autopkgtest-{build_arch}.img' + if os.path.exists(os.path.abspath(guessed_name)): + images = os.path.abspath(guessed_name) + else: + image = os.path.join( + IMAGEDIR, + f'{peeked_args.dist}-autopkgtest-{build_arch}.img', + ) + + if not os.path.exists(image): + print(f"File {image} does not exist.", file=sys.stderr) + sys.exit(1) + + args = [ + 'sbuild', + '--dist', peeked_args.dist, + '--purge-build=never', + '--purge-deps=never', + '--chroot-mode=autopkgtest', + '--autopkgtest-virt-server=qemu', + '--autopkgtest-virt-server-opt', f'--overlay-dir={parsed_args.overlay_dir}', + '--autopkgtest-virt-server-opt', f'--qemu-architecture={qemu_arch}', + '--autopkgtest-virt-server-opt', f'--ram-size={parsed_args.ram_size}', + '--autopkgtest-virt-server-opt', f'--cpus={parsed_args.cpus}', + '--autopkgtest-virt-server-opt', f'--boot={parsed_args.boot}', + '--autopkgtest-virt-server-opt', image, + # Worarkound -- dose can hang stuff in a qemu VM + '--bd-uninstallable-explainer', 'apt', + ] + if parsed_args.autopkgtest_debug: + args += ['--autopkgtest-virt-server-opt', '--debug'] + + # Pass on the remaining (before peeking) arguments to sbuild + args += unparsed_args + + print(' '.join(str(a) for a in args)) + if not parsed_args.noexec: + os.execvp(args[0], args) + + +if __name__ == '__main__': + main() diff --git a/bin/sbuild-qemu-boot b/bin/sbuild-qemu-boot new file mode 100755 index 0000000..ae2ef1d --- /dev/null +++ b/bin/sbuild-qemu-boot @@ -0,0 +1,291 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright © 2020-2024 Christian Kastner <ckk@debian.org> +# 2021 Simon McVittie <smcv@debian.org> +# 2024 Johannes Schauer Marin Rodrigues <josch@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + + +# Note that there is significant overlap between this program and +# sbuild-qemu-update. Both are in their developmental stages and I'd prefer to +# wait and see where this goes before refactoring them. --ckk + + +import argparse +import datetime +import os +import subprocess +import sys + + +SUPPORTED_ARCHS = [ + 'amd64', + 'arm64', + 'armhf', + 'i386', + 'ppc64el', +] + +IMAGEDIR = os.environ.get( + 'IMAGEDIR', + os.path.join(os.path.expanduser('~'), '.cache', 'sbuild'), +) + + +def make_snapshot(image): + iso_stamp = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S') + run = subprocess.run( + ['qemu-img', 'snapshot', '-l', image], + capture_output=True + ) + tags = [t.split()[1].decode('utf-8') for t in run.stdout.splitlines()[2:]] + + if iso_stamp in tags: + print( + f"Error: snapshot for {iso_stamp} already exists.", + file=sys.stderr + ) + return False + + run = subprocess.run(['qemu-img', 'snapshot', '-c', iso_stamp, image]) + return True if run.returncode == 0 else False + + +def get_qemu_base_args(image, guest_arch=None, boot="auto"): + host_arch = subprocess.check_output( + ['dpkg', '--print-architecture'], + text=True, + ).strip() + + if not guest_arch: + # This assumes that images are named foo-bar-ARCH.img + root, _ = os.path.splitext(os.path.basename(image)) + components = root.split('-') + for c in reversed(components): + if c in SUPPORTED_ARCHS: + guest_arch = c + break + if not guest_arch: + print( + f"Could not guess guest architecture, please use --arch", + file=sys.stderr, + ) + return + else: + if not guest_arch in SUPPORTED_ARCHS: + print(f"Unsupported architecture: {guest_arch}", file=sys.stderr) + print("Supported architectures are: ", file=sys.stderr, end="") + print(f"{', '.join(SUPPORTED_ARCHS)}", file=sys.stderr) + return + + if guest_arch == 'amd64' : + argv = ['qemu-system-x86_64'] + if host_arch == 'amd64': + argv.append('-enable-kvm') + elif guest_arch == 'i386': + argv = ['qemu-system-i386', '-machine', 'q35'] + if host_arch in ['amd64', 'i386']: + argv.append('-enable-kvm') + elif guest_arch == 'ppc64el': + argv = ['qemu-system-ppc64le'] + if host_arch == 'ppc64el': + argv.append('-enable-kvm') + elif guest_arch == 'arm64': + argv = [ + 'qemu-system-aarch64', + '-machine', 'virt', + '-drive', 'if=pflash,format=raw,unit=0,read-only=on,' + 'file=/usr/share/AAVMF/AAVMF_CODE.fd', + ] + if host_arch == 'arm64': + argv.extend(['-cpu', 'host', '-enable-kvm']) + else: + argv.extend(['-cpu', 'cortex-a53']) + elif guest_arch == 'armhf': + if host_arch == 'arm64': + argv = [ + 'qemu-system-aarch64', + '-cpu', 'host,aarch64=off', + '-enable-kvm' + ] + else: + argv = ['qemu-system-arm'] + argv.extend([ + '-machine', 'virt', + '-drive', 'if=pflash,format=raw,unit=0,read-only=on,' + 'file=/usr/share/AAVMF/AAVMF32_CODE.fd', + ]) + + if boot == "auto": + match guest_arch: + case 'amd64'|'i386': + boot = "bios" + case 'arm64'|'armhf': + boot = "efi" + case 'ppc64el': + boot = "ieee1275" + + eficode = None + match boot: + case "bios"|"none": + pass + case "efi": + match guest_arch: + case 'amd64': + eficode = "/usr/share/OVMF/OVMF_CODE.fd" + case 'i386': + eficode = "/usr/share/OVMF/OVMF32_CODE_4M.secboot.fd" + case 'arm64': + eficode = "/usr/share/AAVMF/AAVMF_CODE.fd" + case 'armhf': + eficode = "/usr/share/AAVMF/AAVMF32_CODE.fd" + case 'ppc64el': + print("efi not supported on ppc64el") + if eficode: + argv.extend(["-drive", f"if=pflash,format=raw,unit=0,read-only=on,file={eficode}"]) + + return argv + + +def main(): + parser = argparse.ArgumentParser( + description='Boot a VM using a QEMU image.', + ) + parser.add_argument('--read-write', + action='store_true', + help="Write changes back to the image, instead of using the image " + "read-only.", + ) + parser.add_argument( + '--snapshot', + action='store_true', + help="Create a snapshot of the image before changing it. Useful for " + "reproducibility purposes. Ignored if the image is not booted in " + "read-write mode, which is the default.", + ) + parser.add_argument( + '--shared-dir', + help="Share this directory on the host with the guest. This will only " + "work when the image was created with sbuild-qemu-create(1).", + ) + parser.add_argument( + '--arch', + help="Architecture to use (instead of attempting to auto-guess based " + "on the image name).", + ) + parser.add_argument( + '--ram-size', + metavar='MiB', + action='store', + default=2048, + help=f"VM memory size in MB. Default: 2048", + ) + parser.add_argument( + '--cpus', + metavar='CPUs', + action='store', + default=2, + help="VM CPU count. Default: 2", + ) + parser.add_argument( + '--ssh-port', + metavar='PORT', + action='store', + help="Forward local port PORT to port 22 within the guest. Package " + "'openssh-server' must be installed within the guest for this " + "to be useful.", + ) + parser.add_argument( + '--noexec', + action='store_true', + help="Don't actually do anything. Just print the command string that " + "would be executed, and then exit.", + ) + parser.add_argument( + '--boot', + choices=['auto', 'bios', 'efi', 'ieee1275', 'none'], + default='auto', + help="How to boot the image. Default is BIOS on amd64 and i386, EFI " + "on arm64 and armhf, and IEEE1275 on ppc64el.", + ) + parser.add_argument( + 'image', + help="Image. Will first be interpreted as a path. If no suitable " + "image exists at that location, then $IMAGEDIR\<image> is tried.", + ) + parsed_args = parser.parse_args() + + if os.path.exists(parsed_args.image): + image = parsed_args.image + elif os.path.exists(os.path.join(IMAGEDIR, parsed_args.image)): + image = os.path.join(IMAGEDIR, parsed_args.image) + else: + print("Image does not exist", file=sys.stderr) + sys.exit(1) + + nic = 'user,model=virtio' + if parsed_args.ssh_port: + nic += f',hostfwd=tcp:127.0.0.1:{parsed_args.ssh_port}-:22' + + args = get_qemu_base_args(parsed_args.image, parsed_args.arch, parsed_args.boot) + if not args: + sys.exit(1) + + args.extend([ + '-object', 'rng-random,filename=/dev/urandom,id=rng0', + '-device', 'virtio-rng-pci,rng=rng0,id=rng-device0', + '-device', 'virtio-serial', + '-nic', nic, + '-m', str(parsed_args.ram_size), + '-smp', str(parsed_args.cpus), + '-nographic', + ]) + + if parsed_args.shared_dir: + args.extend([ + '-virtfs', f'local,path={parsed_args.shared_dir},id=sbuild-qemu,' + 'mount_tag=sbuild-qemu,security_model=none', + ]) + + # Pass on host terminal rows/columns to guest + # FIXME: qemu-system-pp64le doesn't support fw_cfg? + if args[0] not in ['qemu-system-ppc64le']: + termsize = os.get_terminal_size() + args.extend([ + '-fw_cfg', f'name=opt/sbuild-qemu/tty-rows,string={termsize.lines}', + '-fw_cfg', f'name=opt/sbuild-qemu/tty-cols,string={termsize.columns}', + ]) + + args.append(image) + + print(' '.join(str(a) for a in args)) + if parsed_args.noexec: + return + + if parsed_args.read_write: + if parsed_args.snapshot and not make_snapshot(image): + return + else: + args.append('-snapshot') + + os.execvp(args[0], args) + + +if __name__ == '__main__': + main() diff --git a/bin/sbuild-qemu-create b/bin/sbuild-qemu-create new file mode 100755 index 0000000..ca59404 --- /dev/null +++ b/bin/sbuild-qemu-create @@ -0,0 +1,234 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright © 2020-2024 Christian Kastner <ckk@debian.org> +# 2024 Johannes Schauer Marin Rodrigues <josch@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + + +import argparse +import os +import subprocess +import sys +import textwrap + + +SUPPORTED_ARCHS = [ + 'amd64', + 'arm64', + 'armhf', + 'i386', + 'ppc64el', +] + +DEFAULT_ARCH = subprocess.check_output( + ['dpkg', '--print-architecture'], + text=True, +).strip() + + +def gen_sourceslist(mirror, dist, components, with_bpo=False): + """Generate a sources.list file for the VM. + + If distribution ends with '-backports', then its base distribution will + automatically be added. + + If distribution is 'experimental', then the 'unstable' distribution will + automatically be added. + """ + sl = textwrap.dedent( + f"""\ + deb {mirror} {dist} {' '.join(components)} + deb-src {mirror} {dist} {' '.join(components)} + """) + if dist == 'experimental': + sl += textwrap.dedent( + f""" + deb {mirror} unstable {' '.join(components)} + deb-src {mirror} unstable {' '.join(components)} + """) + elif dist.endswith('-backports'): + sl += textwrap.dedent( + f""" + deb {mirror} {dist[:-10]} {' '.join(components)} + deb-src {mirror} {dist[:-10]} {' '.join(components)} + """) + return sl + + +def main(): + parser = argparse.ArgumentParser( + description="Builds images for use with qemu-sbuild and autopkgtest.", + epilog="Note that qemu-sbuild-create is just a simple wrapper around " + "autopkgtest-build-qemu(1) that automates a few additional " + "steps commonly performed with package-building images.", + ) + parser.add_argument( + '--arch', + action='store', + default=DEFAULT_ARCH, + help="Architecture to use. Default is the host architecture. " + "Currently supported architectures are: " + f"{', '.join(SUPPORTED_ARCHS)}.", + ) + parser.add_argument( + '--install-packages', + action='store', + help="Comma-separated list of additional packages to install in the " + "image using 'apt-get install' from within the image.", + ) + parser.add_argument( + '--extra-deb', + action='append', + help="Package file (.deb) from the local filesystem to install. Can " + "be specified more than once.", + ) + parser.add_argument( + '--components', + action='store', + default='main', + help="Comma-separated list of components to use with sources.list " + "entries. Default: main.", + ) + # Not yet merged into autopkgtest, see #973457 + # parser.add_argument('--variant', action='store') + parser.add_argument( + '--skel', + type=str, + action='store', + help="Skeleton directory to use for /root.", + ) + parser.add_argument( + '--authorized-keys', + metavar='FILE', + action='store', + help="Install this file as /root/.ssh/authorized_keys within the " + "guest. This will automatically install the 'openssh-server' " + "package. This supersedes any copying of this file by the " + "--skel option.", + ) + parser.add_argument( + '--size', + type=str, + action='store', + default='10G', + help="Image size to use. Note that the images are in qcow2 format, so " + "they won't consume that space right away. Default: 10G.", + ) + parser.add_argument( + '-o', '--out-file', + action='store', + help="Output filename. If not supplied, then " + "DIST-autopkgtest-ARCH.img will be used.", + ) + parser.add_argument( + '--noexec', + action='store_true', + help="Don't actually do anything. Just print the autopkgtest-build-" + "qemu(1) command string that would be executed, and then exit.", + ) + parser.add_argument( + '--boot', + choices=['auto', 'bios', 'efi', 'ieee1275', 'none'], + default='auto', + help="How the image should boot. Default is BIOS on amd64 and i386, " + "EFI on arm64 and armhf, and IEEE1275 on ppc64el.", + ) + parser.add_argument( + 'distribution', + action='store', + help="The distribution to debootstrap.", + ) + parser.add_argument( + 'mirror', + action='store', + help="The mirror to use for the installation. Note that the mirror " + "will also be used for the sources.list file in the VM.", + ) + parsed = parser.parse_args() + + # Internal args + if parsed.arch not in SUPPORTED_ARCHS: + print( + f"Unsupported architecture: {parsed.arch}", + file=sys.stderr, + ) + sys.exit(1) + if parsed.out_file: + out_file = parsed.out_file + else: + out_file = f"{parsed.distribution}-autopkgtest-{parsed.arch}.img" + components = parsed.components.split(',') + + # We can only pass arguments to the other tools via the environment + # + # Args consumed by the modscript + if parsed.skel: + os.environ['SQC_SKEL'] = parsed.skel + print('export SQC_SKEL=' + os.environ['SQC_SKEL']) + if parsed.authorized_keys: + os.environ['SQC_AUTH_KEYS'] = parsed.authorized_keys + print('export SQC_AUTH_KEYS=' + os.environ['SQC_AUTH_KEYS']) + if parsed.extra_deb: + extra_debs = ' '.join(parsed.extra_deb) + os.environ['SQC_EXTRA_DEBS'] = extra_debs + print('export SQC_EXTRA_DEBS=' + extra_debs) + if parsed.install_packages: + install_packages = parsed.install_packages.replace(',', ' ') + os.environ['SQC_INSTALL_PACKAGES'] = install_packages + print('export SQC_INSTALL_PACKAGES=' + install_packages) + # Args consumed by autopkgtest-build-qemu + os.environ['AUTOPKGTEST_APT_SOURCES'] = gen_sourceslist( + parsed.mirror, + parsed.distribution, + components, + ) + print('sources.list (via export AUTOPKGTEST_APT_SOURCES)\n------------') + print(os.environ['AUTOPKGTEST_APT_SOURCES']) + + #if parsed.variant: + # args += ['--variant', parsed.variant] + dist = parsed.distribution + if dist.endswith('-backports'): + dist = dist[:-len('-backports')] + elif dist == 'experimental': + dist = 'unstable' + + args = [ + 'autopkgtest-build-qemu', + '--architecture', parsed.arch, + '--mirror', parsed.mirror, + '--size', parsed.size, + '--script', '/usr/share/sbuild/sbuild-qemu-create-modscript', + '--boot', parsed.boot, + dist, + out_file, + ] + + if os.getuid() != 0: + print('Must be root to use this.', file=sys.stderr) + sys.exit(1) + os.umask(22) + + print(' '.join(str(a) for a in args)) + if not parsed.noexec: + os.execvp(args[0], args) + + +if __name__ == '__main__': + main() diff --git a/bin/sbuild-qemu-create-modscript b/bin/sbuild-qemu-create-modscript new file mode 100755 index 0000000..0f139b5 --- /dev/null +++ b/bin/sbuild-qemu-create-modscript @@ -0,0 +1,137 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright © 2020 Christian Kastner <ckk@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + + +set -e +umask 0022 + + +VMROOT="$1" +if [ -z "$VMROOT" ] +then + echo "$0 expects the mounted root of the VM as first argument." >&2 + exit 1 +elif ! mountpoint -q "$VMROOT" +then + echo "$VMROOT is not a mountpoint." >&2 + exit 1 +fi + + +echo "### Customizing base image ###" + +if [ -n "$SQC_SKEL" ] +then + echo "Copying contents of $SQC_SKEL" + if [ ! -d "$SQC_SKEL" ] + then + echo "$SQC_SKEL is not a directory." >&2 + exit 1 + fi + cp -pr "$SQC_SKEL/." "$VMROOT/root" +fi + +if [ -n "$SQC_AUTH_KEYS" ] +then + echo "Copying $SQC_AUTH_KEYS to /root/.ssh/" + if [ ! -f "$SQC_AUTH_KEYS" ] + then + echo "$SQC_AUTH_KEYS is not a regular file." >&2 + exit 1 + fi + + TARGET_KEYS="$VMROOT/root/.ssh/authorized_keys" + if [ ! -d "$VMROOT/root/.ssh" ] + then + mkdir --mode=0700 "$VMROOT/root/.ssh" + fi + cp "$SQC_AUTH_KEYS" "$VMROOT/root/.ssh/authorized_keys" + chroot "$VMROOT" chmod 0600 /root/.ssh/authorized_keys + chroot "$VMROOT" chown root:root /root/.ssh/authorized_keys + chroot "$VMROOT" apt-get install --quiet --assume-yes openssh-server +fi + +if [ -n "$SQC_INSTALL_PACKAGES" ] +then + echo "Installing additional packages" + chroot "$VMROOT" apt-get install --quiet --assume-yes $SQC_INSTALL_PACKAGES +fi + +if [ -n "$SQC_EXTRA_DEBS" ] +then + echo "Installing extra .debs" + VMTMP=`mktemp -d -p "$VMROOT"` + cp -t "$VMTMP" $SQC_EXTRA_DEBS + chroot "$VMROOT" dpkg --recursive -i `basename "$VMTMP"` + chroot "$VMROOT" apt-get update + rm -rf "$VMTMP" +fi + +# Mount point for a shared folder, if the VM is launched with one +echo "Adding 9p to initramfs" +echo -e "9p\n9pnet\n9pnet_virtio" >> "$VMROOT/etc/initramfs-tools/modules" +chroot "$VMROOT" update-initramfs -u +echo "Adding shared folder to fstab" +mkdir -m 755 "$VMROOT/shared" +echo "sbuild-qemu /shared 9p trans=virtio,version=9p2000.L,auto,nofail 0 0" >> "$VMROOT/etc/fstab" + +echo "Updating GRUB menu" +echo "GRUB_TIMEOUT=1" >> "$VMROOT/etc/default/grub" +chroot "$VMROOT" update-grub + +# Enable automatically setting terminal rows/columns if the host passes us the +# params using -fw_cfg +echo "Creating script in /etc/profile.d/ to set terminal geometry to host" +cat > "$VMROOT/etc/profile.d/sbuild-qemu-terminal-settings.sh" <<"EOF" +#!/bin/sh +# Set VM tty rows/columns to host rows/columns +# +# This only works if the guest kernel was compiled with CONFIG_FW_CFG_FSYS, and +# the host rows/columns were passed on through QEMU using -fw_cfg. Regular +# users will also need permission to read this file (see the udev rule). + +ROWSFILE="/sys/firmware/qemu_fw_cfg/by_name/opt/sbuild-qemu/tty-rows/raw" +COLSFILE="/sys/firmware/qemu_fw_cfg/by_name/opt/sbuild-qemu/tty-cols/raw" + +DEB_HOST_ARCH="`dpkg-architecture -qDEB_HOST_ARCH`" +if [ "$DEB_HOST_ARCH" = "armhf" ] || [ "$DEB_HOST_ARCH" = "arm64" ] +then + TTY=/dev/ttyAMA0 +else + TTY=/dev/ttyS0 +fi + +if [ -f "$ROWSFILE" ] +then + stty -F "$TTY" rows `cat "$ROWSFILE"` +fi + +if [ -f "$COLSFILE" ] +then + stty -F "$TTY" cols `cat "$COLSFILE"` +fi +EOF + +# Makes the image significantly smaller +chroot "$VMROOT" apt-get --option Dir::Etc::SourceList=/dev/null --option Dir::Etc::SourceParts=/dev/null update +chroot "$VMROOT" apt-get clean + +echo "### Customization of base image complete. ###" diff --git a/bin/sbuild-qemu-update b/bin/sbuild-qemu-update new file mode 100755 index 0000000..af5c31c --- /dev/null +++ b/bin/sbuild-qemu-update @@ -0,0 +1,282 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright © 2020-2024 Christian Kastner <ckk@debian.org> +# 2021 Simon McVittie <smcv@debian.org> +# 2024 Johannes Schauer Marin Rodrigues <josch@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + + +# Note that there is significant overlap between this program and +# sbuild-qemu-boot. Both are in their developmental stages and I'd prefer to +# wait and see where this goes before refactoring them. --ckk + + +import argparse +import datetime +import os +import subprocess +import sys + +import pexpect + + +SUPPORTED_ARCHS = [ + 'amd64', + 'arm64', + 'armhf', + 'i386', + 'ppc64el', +] + +IMAGEDIR = os.environ.get( + 'IMAGEDIR', + os.path.join(os.path.expanduser('~'), '.cache', 'sbuild'), +) + + +def make_snapshot(image): + iso_stamp = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S') + run = subprocess.run( + ['qemu-img', 'snapshot', '-l', image], + capture_output=True + ) + tags = [t.split()[1].decode('utf-8') for t in run.stdout.splitlines()[2:]] + + if iso_stamp in tags: + print( + f"Error: snapshot for {iso_stamp} already exists.", + file=sys.stderr + ) + return False + + run = subprocess.run(['qemu-img', 'snapshot', '-c', iso_stamp, image]) + return True if run.returncode == 0 else False + + +def get_qemu_base_args(image, guest_arch=None, boot="auto"): + host_arch = subprocess.check_output( + ['dpkg', '--print-architecture'], + text=True, + ).strip() + + if not guest_arch: + # This assumes that images are named foo-bar-ARCH.img + root, _ = os.path.splitext(os.path.basename(image)) + components = root.split('-') + for c in reversed(components): + if c in SUPPORTED_ARCHS: + guest_arch = c + break + if not guest_arch: + print( + f"Could not guess guest architecture, please use --arch", + file=sys.stderr, + ) + return + else: + if not guest_arch in SUPPORTED_ARCHS: + print(f"Unsupported architecture: {guest_arch}", file=sys.stderr) + print("Supported architectures are: ", file=sys.stderr, end="") + print(f"{', '.join(SUPPORTED_ARCHS)}", file=sys.stderr) + return + + if guest_arch == 'amd64' : + argv = ['qemu-system-x86_64'] + if host_arch == 'amd64': + argv.append('-enable-kvm') + elif guest_arch == 'i386': + argv = ['qemu-system-i386', '-machine', 'q35'] + if host_arch in ['amd64', 'i386']: + argv.append('-enable-kvm') + elif guest_arch == 'ppc64el': + argv = ['qemu-system-ppc64le'] + if host_arch == 'ppc64el': + argv.append('-enable-kvm') + elif guest_arch == 'arm64': + argv = [ + 'qemu-system-aarch64', + '-machine', 'virt', + ] + if host_arch == 'arm64': + argv.extend(['-cpu', 'host', '-enable-kvm']) + else: + argv.extend(['-cpu', 'cortex-a53']) + elif guest_arch == 'armhf': + if host_arch == 'arm64': + argv = [ + 'qemu-system-aarch64', + '-cpu', 'host,aarch64=off', + '-enable-kvm' + ] + else: + argv = ['qemu-system-arm'] + argv.extend([ + '-machine', 'virt', + ]) + + if boot == "auto": + match guest_arch: + case 'amd64'|'i386': + boot = "bios" + case 'arm64'|'armhf': + boot = "efi" + case 'ppc64el': + boot = "ieee1275" + + eficode = None + match boot: + case "bios"|"none": + pass + case "efi": + match guest_arch: + case 'amd64': + eficode = "/usr/share/OVMF/OVMF_CODE.fd" + case 'i386': + eficode = "/usr/share/OVMF/OVMF32_CODE_4M.secboot.fd" + case 'arm64': + eficode = "/usr/share/AAVMF/AAVMF_CODE.fd" + case 'armhf': + eficode = "/usr/share/AAVMF/AAVMF32_CODE.fd" + case 'ppc64el': + print("efi not supported on ppc64el") + if eficode: + argv.extend(["-drive", f"if=pflash,format=raw,unit=0,read-only=on,file={eficode}"]) + + + return argv + + +def update_interaction(child, quiet): + child.expect('host login: ') + child.sendline('root') + if not quiet: + child.logfile = sys.stdout + child.expect('root@host:~# ') + child.sendline('DEBIAN_FRONTEND=noninteractive apt-get --quiet update') + child.expect('root@host:~# ') + child.sendline('DEBIAN_FRONTEND=noninteractive apt-get --quiet --assume-yes dist-upgrade') + child.expect('root@host:~# ') + child.sendline('DEBIAN_FRONTEND=noninteractive apt-get --quiet --assume-yes clean') + child.expect('root@host:~# ') + child.sendline('DEBIAN_FRONTEND=noninteractive apt-get --quiet --assume-yes autoremove') + child.expect('root@host:~# ') + child.sendline('sync') + child.expect('root@host:~# ') + # Don't recall what issue this solves, but it solves it + child.sendline('sleep 1') + child.expect('root@host:~# ') + child.sendline('shutdown -h now') + + +def main(): + parser = argparse.ArgumentParser( + description='sbuild-update analog for QEMU images.', + ) + parser.add_argument( + '--snapshot', + action='store_true', + help="Create a snapshot of the image before changing it. Useful for " + "reproducibility purposes.", + ) + parser.add_argument( + '--arch', + help="Architecture to use (instead of attempting to auto-guess based " + "on the image name).", + ) + parser.add_argument( + '--timeout', + type=int, + default=600, + metavar='SECONDS', + action='store', + help="Maximum time to wait for command to finish with expected " + "result. Mostly relevant for foreign architectures, where " + "`apt-get update` can take quite a while. Default: 600s." + ) + parser.add_argument( + '--noexec', + action='store_true', + help="Don't actually do anything. Just print the command string that " + "would be executed, and then exit.", + ) + parser.add_argument( + '--boot', + choices=['auto', 'bios', 'efi', 'ieee1275', 'none'], + default='auto', + help="How to boot the image. Default is BIOS on amd64 and i386, EFI " + "on arm64 and armhf, and IEEE1275 on ppc64el.", + ) + parser.add_argument( + 'image', + help="Image. Will first be interpreted as a path. If no suitable " + "image exists at that location, then $IMAGEDIR\<image> is tried.", + ) + dbglvlgroup = parser.add_mutually_exclusive_group() + dbglvlgroup.add_argument("-v", "--verbose", action="store_true") + dbglvlgroup.add_argument("-q", "--quiet", action="store_true") + + parser.add_argument("extra_args", nargs='*') + + parsed_args = parser.parse_args() + + if os.path.exists(parsed_args.image): + image = parsed_args.image + elif os.path.exists(os.path.join(IMAGEDIR, parsed_args.image)): + image = os.path.join(IMAGEDIR, parsed_args.image) + else: + print("Image does not exist", file=sys.stderr) + sys.exit(1) + + args = get_qemu_base_args(parsed_args.image, parsed_args.arch, parsed_args.boot) + if not args: + sys.exit(1) + + args.extend([ + '-object', 'rng-random,filename=/dev/urandom,id=rng0', + '-device', 'virtio-rng-pci,rng=rng0,id=rng-device0', + '-device', 'virtio-serial', + '-nic', 'user,model=virtio', + '-m', '1024', + '-smp', '1', + '-nographic', + image, + ]) + if parsed_args.extra_args: + args.extend(parsed_args.extra_args) + + print(' '.join(str(a) for a in args)) + if parsed_args.noexec: + return + elif parsed_args.snapshot and not make_snapshot(image): + return + + logfile = None + if parsed_args.verbose: + logfile = sys.stdout + child = pexpect.spawn(args[0], args[1:], logfile=logfile, encoding="utf-8") + child.timeout = parsed_args.timeout + try: + update_interaction(child, parsed_args.quiet) + except pexpect.TIMEOUT: + print("Update timed out. Consider using --timeout.", file=sys.stderr) + child.terminate() + + +if __name__ == '__main__': + main() diff --git a/bin/sbuild-shell b/bin/sbuild-shell new file mode 100755 index 0000000..90083f2 --- /dev/null +++ b/bin/sbuild-shell @@ -0,0 +1,49 @@ +#!/usr/bin/perl +# +# Run a shell in a chroot. +# Copyright © 2006 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +use Getopt::Long; +use Sbuild qw(help_text version_text usage_error check_group_membership); +use Sbuild::Utility qw(setup cleanup); +use Sbuild::ChrootSetup qw(shell); +use Sbuild::Sysconfig; +use Sbuild::Conf qw(); +use Sbuild::OptionsBase; + +my $conf = Sbuild::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::OptionsBase->new($conf, "sbuild-shell", "1"); +exit 1 if !defined($options); +check_group_membership(); + +usage_error("sbuild-shell", "Incorrect number of options") if (@ARGV != 1); + + +my $session = setup('source', $ARGV[0], $conf) or die "Chroot setup failed"; + +my $status = shell($session, $conf); +$status >>= 8; + +cleanup($conf); + +exit $status; diff --git a/bin/sbuild-unhold b/bin/sbuild-unhold new file mode 100755 index 0000000..12b1266 --- /dev/null +++ b/bin/sbuild-unhold @@ -0,0 +1,58 @@ +#!/usr/bin/perl -w +# changes the dpkg status of a package in a chroot to "installed" +# +# Copyright © 2006,2008 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +use Getopt::Long; +use Sbuild qw(help_text version_text usage_error check_group_membership); +use Sbuild::Utility qw(setup cleanup shutdown); +use Sbuild::ChrootSetup qw(unhold_packages list_packages); +use Sbuild::Conf qw(); +use Sbuild::OptionsBase; + +my $conf = Sbuild::Conf::new(); +exit 1 if !defined($conf); +my $options = Sbuild::OptionsBase->new($conf, "sbuild-unhold", "1"); +exit 1 if !defined($options); +check_group_membership(); + +usage_error("sbuild-unhold", "Incorrect number of options") if (@ARGV < 2); + +my $chroot = Sbuild::Utility::get_dist($ARGV[0]); + +my $session = setup('source', $ARGV[0], $conf) or die "Chroot setup failed for $chroot chroot"; + +print STDOUT "Unholding packages in $chroot chroot:"; +shift @ARGV; +foreach (@ARGV) { + print STDOUT " $_"; +} +print STDOUT ".\n\n"; + +my $status = unhold_packages($session, $conf, @ARGV); +$status >>= 8; + +list_packages($session, $conf, @ARGV); + +cleanup($conf); + +exit $status; diff --git a/bin/sbuild-update b/bin/sbuild-update new file mode 100755 index 0000000..396b526 --- /dev/null +++ b/bin/sbuild-update @@ -0,0 +1,228 @@ +#!/usr/bin/perl -w +# +# Copyright © 2006-2008 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +use Sbuild::ChrootRoot; +use Sbuild::Resolver qw(get_resolver); + +package Conf; + +sub setup { + my $conf = shift; + + my %update_keys = ( + 'COMPAT' => { + DEFAULT => 1 + }, + 'UPDATE' => { + DEFAULT => 0 + }, + 'UPGRADE' => { + DEFAULT => 0 + }, + 'DISTUPGRADE' => { + DEFAULT => 0 + }, + 'CLEAN' => { + DEFAULT => 0 + }, + 'AUTOCLEAN' => { + DEFAULT => 0 + }, + 'AUTOREMOVE' => { + DEFAULT => 0 + }, + 'CHROOT_MODE' => { + DEFAULT => 'schroot' + }, + ); + + $conf->set_allowed_keys(\%update_keys); +} + +package Options; + +use Sbuild::OptionsBase; +use Sbuild::Conf qw(); + +BEGIN { + use Exporter (); + our (@ISA, @EXPORT); + + @ISA = qw(Exporter Sbuild::OptionsBase); + + @EXPORT = qw(); +} + +sub set_options { + my $self = shift; + + $self->add_options( + "chroot-mode=s" => sub { + $self->set_conf('CHROOT_MODE', $_[1]); + }, + "arch=s" => sub { + $self->set_conf('ARCH', $_[1]); + $self->set_conf('HOST_ARCH', $_[1]); + $self->set_conf('BUILD_ARCH', $_[1]); + }, + "update|u" => sub { + $self->set_conf('UPDATE', 1); + $self->set_conf('COMPAT', 0); + }, + "upgrade|g" => sub { + $self->set_conf('UPGRADE', 1); + $self->set_conf('COMPAT', 0); + }, + "dist-upgrade|d" => sub { + $self->set_conf('DISTUPGRADE', 1); + $self->set_conf('COMPAT', 0); + }, + "clean|c" => sub { + $self->set_conf('CLEAN', 1); + $self->set_conf('COMPAT', 0); + }, + "autoclean|a" => sub { + $self->set_conf('AUTOCLEAN', 1); + $self->set_conf('COMPAT', 0); + }, + "autoremove|r" => sub { + $self->set_conf('AUTOREMOVE', 1); + $self->set_conf('COMPAT', 0); + }); +} + +package main; + +use Getopt::Long; +use Sbuild qw(help_text version_text usage_error check_group_membership); +use Sbuild::Utility qw(setup cleanup); + +my $conf = Sbuild::Conf::new(); +Conf::setup($conf); +exit 1 if !defined($conf); +my $options = Options->new($conf, "sbuild-update", "1"); +exit 1 if !defined($options); +check_group_membership() if $conf->get('CHROOT_MODE') eq 'schroot'; + +if ($conf->get('COMPAT')) { + my $msg = "$0 will perform apt-get command 'update' now, however this "; + $msg .= "may change at a later revision.\n"; + print "$msg"; + $conf->set('UPDATE', 1); +} + +if (@ARGV < 1) { + usage_error("sbuild-update", "No chroot was specified"); +} + +my $status = 0; + +my $host = Sbuild::ChrootRoot->new($conf); + +foreach (@ARGV) { + my $distribution = Sbuild::Utility::get_dist($_); + + my $session = setup('source', $distribution, $conf) or die "Chroot setup failed"; + if (!$host->begin_session()) { + die "Chroot setup failed"; + } + my $resolver = get_resolver($conf, $session, $host); + + if (!$session->lock_chroot('SBUILD_UPDATE', $$, $conf->get('USERNAME'))) { + goto cleanup_unlocked; + } + + $resolver->setup(); + + if ($conf->get('UPDATE')) { + print "$distribution: Performing update.\n"; + $status = $resolver->update($session, $conf); + $status >>= 8; + if ($status) { + print STDERR "Exiting from update with status $status.\n"; + goto cleanup; + } + } + + if ($conf->get('UPGRADE')) { + print "$distribution: Performing upgrade.\n"; + my $status = $resolver->upgrade($session, $conf); + $status >>= 8; + if ($status) { + print STDERR "Exiting from upgrade with status $status.\n"; + goto cleanup; + } + } + + if ($conf->get('DISTUPGRADE')) { + print "$distribution: Performing dist-upgrade.\n"; + my $status = $resolver->distupgrade($session, $conf); + $status >>= 8; + if ($status) { + print STDERR "Exiting from distupgrade with status $status.\n"; + goto cleanup; + } + } + + if ($conf->get('CLEAN')) { + print "$distribution: Performing clean.\n"; + my $status = $resolver->clean($session, $conf); + $status >>= 8; + if ($status) { + print STDERR "Exiting from update with status $status.\n"; + goto cleanup; + } + } + + if ($conf->get('AUTOCLEAN')) { + print "$distribution: Performing autoclean.\n"; + my $status = $resolver->autoclean($session, $conf); + $status >>= 8; + if ($status) { + print STDERR "Exiting from autoclean with status $status.\n"; + goto cleanup; + } + } + + if ($conf->get('AUTOREMOVE')) { + print "$distribution: Performing autoremove.\n"; + my $status = $resolver->autoremove($session, $conf); + $status >>= 8; + if ($status) { + print STDERR "Exiting from autoremove with status $status.\n"; + goto cleanup; + } + } + +cleanup: + $resolver->cleanup(); + # Unlock chroot now it's cleaned up and ready for other users. + $session->unlock_chroot(); + +cleanup_unlocked: + cleanup($conf); + + last if $status; +} + +exit($status ? 1 : 0); diff --git a/bin/sbuild-upgrade b/bin/sbuild-upgrade new file mode 100755 index 0000000..e338632 --- /dev/null +++ b/bin/sbuild-upgrade @@ -0,0 +1,26 @@ +#!/usr/bin/perl -w +# +# Copyright © 2006-2008 Roger Leigh <rleigh@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +print "$0 is deprecated. Use sbuild-update --upgrade directly instead.\n"; +exec("sbuild-update", "--upgrade", @ARGV) or + die "Can't run sbuild-update: $!"; diff --git a/bin/setup_system b/bin/setup_system new file mode 100755 index 0000000..d99939e --- /dev/null +++ b/bin/setup_system @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Copyright © 2005-2006 Ryan Murray <rmurray@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + + +# Needed sudoers entries: +#buildd ALL=NOPASSWD: ALL +#Defaults:buildd env_keep+="APT_CONFIG DEBIAN_FRONTEND" +# +# parts to be run as root. +# ons of these, depending on whether you have a buildd group or not +#sudo adduser --system --shell /bin/sh --uid 60000 --gecos 'Build Daemon' --ingroup buildd --disabled-password buildd +sudo adduser --system --shell /bin/sh --uid 60000 --gecos 'Build Daemon' --group --disabled-password buildd +sudo chown -R buildd:buildd /var/lib/wanna-build +sudo chmod -R 2775 /var/lib/wanna-build +# parts to be done as buildd. +cd ~buildd +zcat /usr/share/doc/buildd/examples/buildd.conf.gz > buildd.conf +zcat /usr/share/doc/sbuild/examples/sbuildrc.gz > .sbuildrc +mkdir -p .ssh build logs mqueue old-logs stats/graphs upload upload-security +chmod o= .ssh upload-security old-logs mqueue logs build +echo "|/usr/bin/buildd-mail-wrapper" > .forward +ssh-keygen -b 2048 -t rsa -f .ssh/id_rsa -N '' +echo I: setup .forward-porters with where you want buildd mail to go. +echo I: chroot creation commands: +echo buildd-make-chroot buildd sid build/chroot-unstable http://ftp.debian.org/debian +echo buildd-make-chroot buildd sarge build/chroot-sarge http://ftp.debian.org/debian +echo buildd-make-chroot buildd woody build/chroot-woody http://ftp.debian.org/debian +echo buildd-make-chroot buildd etch build/chroot-etch http://ftp.debian.org/debian +echo I: Link commands for the chroots: +echo ln -s chroot-woody chroot-oldstable-security +echo ln -s chroot-sarge chroot-stable +echo ln -s chroot-sarge chroot-stable-security +echo ln -s chroot-etch chroot-testing +echo ln -s chroot-etch chroot-testing-security +echo I: Done. +exit 0 diff --git a/bin/wb-ssh-wrapper b/bin/wb-ssh-wrapper new file mode 100755 index 0000000..85a6cb7 --- /dev/null +++ b/bin/wb-ssh-wrapper @@ -0,0 +1,38 @@ +#!/bin/bash +# +# A wrapper script to point ssh at in an authorized_keys file to only allow +# access to wanna-build +# Copyright © 2006 Ryan Murray <rmurray@debian.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +bin=/usr/bin/wanna-build + +[ -n "$SSH_ORIGINAL_COMMAND" ] || exit 1 + +set -- $SSH_ORIGINAL_COMMAND + +bn=$(basename "$1") +if [ "$bn" != "wanna-build" ]; then + exit 1 +fi + +shift + +[ -f "$bin" -a -x "$bin" ] || exit 1 + +exec $bin $@ |