summaryrefslogtreecommitdiff
path: root/kernellab
diff options
context:
space:
mode:
Diffstat (limited to 'kernellab')
-rwxr-xr-xkernellab510
1 files changed, 510 insertions, 0 deletions
diff --git a/kernellab b/kernellab
new file mode 100755
index 0000000..55e4b37
--- /dev/null
+++ b/kernellab
@@ -0,0 +1,510 @@
+#!/usr/bin/perl -w
+#
+# kernellab - manage kernel configs for many machines easily
+# Copyright (C) 1999 Tommi Virtanen <tv@havoc.fi>
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+use strict;
+use vars qw(@BASEPATH @SOURCES @ALANCOX @MODULES @IMAGES @CONFIG
+ $TEMPDIR $VERBOSE $DO_CONFIG);
+
+use vars qw($BUILDDIR $_host_regexp %FLAGS $MAKE_HEADERS $SOURCE_TYPE $PATCH_TYPE $KERNEL_REVISION
+ $WRITE_CONFIG $WRITE_IMAGE);
+use File::Find;
+use POSIX qw(strftime);
+
+my $_programname=$0;
+$_programname=~s{^.*/}{};
+sub fail(@) { die "$_programname: @_\n" }
+sub info(@) {print "$_programname: info: @_\n" if $VERBOSE}
+sub debug(@) {print "$_programname: debug: @_\n" if $VERBOSE>1}
+
+do "$ENV{HOME}/.kernellab.conf"
+ or do '/etc/kernellab.conf'
+ or fail "config error; $@\n";
+
+sub find_first_matching(&@) {
+ my $match = shift;
+ foreach (@_) {
+ return $_ if $match->($_);
+ }
+ return undef;
+}
+
+$BUILDDIR = $TEMPDIR . '/kernellab.' . time() . '.' . $$;
+@BASEPATH = map { append_slash($_) } grep {-d $_} @BASEPATH;
+@SOURCES =
+ map { append_slash($_) }
+ grep {-d $_}
+ map { prefix_relative($_, @BASEPATH) }
+ @SOURCES;
+@ALANCOX =
+ map { append_slash($_) }
+ grep {-d $_}
+ map { prefix_relative($_, @BASEPATH) }
+ @ALANCOX;
+@MODULES =
+ map { append_slash($_) }
+ grep {-d $_}
+ map { prefix_relative($_, @BASEPATH) }
+ @MODULES;
+@IMAGES =
+ map { append_slash($_) }
+ grep {-d $_}
+ map { prefix_relative($_, @BASEPATH) }
+ @IMAGES;
+@CONFIG =
+ map { append_slash($_) }
+ grep {-d $_}
+ map { prefix_relative($_, @BASEPATH) }
+ @CONFIG;
+$WRITE_IMAGE = find_first_matching {-w $_} @IMAGES
+ or fail "cannot find a writable place to store kernel images\n",
+ "perhaps you should run 'mkdir -p ~/kernellab/images'";
+$WRITE_CONFIG = find_first_matching {-w $_} @CONFIG
+ or fail "cannot find a writable place to store configs\n",
+ "perhaps you should run 'mkdir -p ~/kernellab/configs'";
+@BASEPATH and @SOURCES and @ALANCOX and @MODULES and @IMAGES and @CONFIG
+ or fail "some of the base directories did not exist. Check config";
+
+$_host_regexp = '[a-z0-9.-]+';
+
+sub usage() {
+ print <<EOF;
+usage: $_programname [options] <host> [<version>[-ac<patchlevel>] [<module>..]]
+ where options are
+ -O, --official use official linux-x.x.x sources (default)
+ -D, --debian use Debian kernel-source-x.x.x sources
+ -a, --ac use Alan Cox patches (default)
+ -p, --pre use Linus Thorvalds pre-patches
+ --ben use Benjamin Herrenschmidt ben patches
+ --benh use Benjamin Herrenschmidt benh patches
+ --paulus use Paul Mackerras patches
+ -c, --configure always run menuconfig
+ -H, --headers also create a kernel-headers -package
+ -v, --verbose be more verbose
+ -h, --help show this help
+EOF
+}
+
+sub prefix($@;) {
+ my ($file)=shift;
+ return map {$_.$file} @_;
+}
+
+sub prefix_relative($@;) {
+ my ($file) = shift;
+ return $file if $file=~m{^[/.]};
+ return prefix($file, @_);
+}
+
+sub append_slash($;) {
+ for (@_) {
+ return m{ /$ }x ? $_ : $_.'/'
+ }
+}
+
+sub getdir($;) {
+ -d $_[0] or return ();
+ opendir(DIR, $_[0]) or fail "cannot open directory $_[0]; $!";
+ my @r = readdir(DIR);
+ closedir DIR;
+ return @r;
+}
+
+sub _find_filename(@) { # for filename_* functions
+ foreach (@_) { return $_ if -e $_ }
+ return undef;
+}
+
+sub filename_kernel($$;) {
+ my ($basename, $file) = @_;
+ _find_filename(prefix($basename . '-' . $file . '.tar.bz2', @SOURCES),
+ prefix($basename . '-' . $file . '.tar.gz', @SOURCES));
+}
+
+sub filename_patch($$$;) {
+ my ($k, $patch, $patchver) = @_;
+ _find_filename(prefix('patch-' . $k . '-' . $patch . $patchver . '.bz2', @ALANCOX),
+ prefix('patch-' . $k . '-' . $patch . $patchver . '.gz', @ALANCOX));
+}
+
+sub filename_module($;) {
+ _find_filename(prefix($_[0] . '.tar.gz', @MODULES),
+ prefix($_[0] . '.tar.bz2', @MODULES));
+}
+
+sub max(@) {
+ my $max;
+ foreach (@_) {
+ $max=$_ if not defined $max or $max<$_;
+ }
+ return $max;
+}
+
+sub source_basename($;) {
+ my ($source) = @_;
+ my ($basename);
+ for ($source) {
+ /^debian$/ and $basename='kernel-source', next;
+ /^official$/ and $basename='linux', next;
+ fail "unknown kernel source type \"$source\"";
+ }
+ return $basename;
+}
+
+sub latest_kernel($$;) {
+ my ($basename, $patch) = @_;
+ my @kernels =
+ map {/^$basename-(\d+)\.(\d+)\.(\d+)\./; [$_,$1,$2,$3]}
+ grep {/^$basename-\d+\.\d+\.\d+\.tar\.(?:gz|bz2)$/}
+ map {getdir $_} @SOURCES;
+ my $a = max map {$_->[1]} @kernels;
+ defined $a or fail "cannot determine latest kernel version (1)";
+ @kernels = grep {$_->[1] == $a} @kernels;
+ my $b = max map {$_->[2]} @kernels;
+ defined $b or fail "cannot determine latest kernel version (2)";
+ @kernels = grep {$_->[2] == $b} @kernels;
+ my $c = max map {$_->[3]} @kernels;
+ defined $c or fail "cannot determine latest kernel version (3)";
+ my $kernel = "$a.$b.$c";
+
+ my @patches =
+ map {/^patch-\d+\.\d+\.\d+.-$patch(\d+\w*)\./; [$_,$1]}
+ grep {/^patch-$a\.$b\.$c-$patch\d+\w*\.(?:gz|bz2)$/}
+ map {getdir $_} @ALANCOX;
+ my $patchver = max map {$_->[1]} @patches;
+ $kernel .= '-' . $patch . $patchver if defined $patchver;
+
+ return $kernel;
+}
+
+sub closest_config($$$;$;) {
+ my ($host, $kver, $patch, $patchver) = @_;
+ debug "finding closest config for $host $kver"
+ . (defined $patchver ? "-$patch$patchver" : '');
+ my @configs;
+ foreach my $confdir (@CONFIG) {
+ foreach my $conffile (getdir $confdir) {
+ $conffile =~
+ /^config-($_host_regexp)-(\d+)\.(\d+)\.(\d+)(?:-$patch(\d+)\w*)?$/
+ or next;
+ $1 eq $host or next;
+ push @configs, [$confdir.$conffile, $1,$2,$3,$4,$5];
+ }
+ }
+
+ @configs = sort { #descending
+ $b->[2] <=> $a->[2]
+ ||
+ $b->[3] <=> $a->[3]
+ ||
+ $b->[4] <=> $a->[4]
+ ||
+ $b->[5] <=> $a->[5]
+ # rely on (undef<=>0)==(0<=>undef)==0
+ } @configs;
+
+ return $configs[0]->[0];
+}
+
+sub next_revision($$$$;) {
+ my ($host, $kver, $date, $patch) = @_;
+ my @revs =
+ sort {$b<=>$a}
+ map { $_->[3] }
+ grep {
+ $_->[1] eq $host
+ and $_->[2] eq $date
+ }
+ map {
+ /^kernel-image-
+ (\d+\.\d+\.\d+(?:-$patch\d+\w*)?)_ # kernel version number
+ ($_host_regexp)\. # hostname
+ (\d\d\d\d\d\d\d\d)\. # yyyymmdd
+ (\d+)_ # revision
+ .*\.deb$
+ /x;
+ [$1,$2,$3,$4]
+ }
+ grep {/^kernel-image-/}
+ map {getdir "$_/$host"} @IMAGES;
+ return "$host.$date.".($revs[0]+1) if @revs;
+ return "$host.$date.1";
+}
+
+sub extract($;) {
+ my $cmd;
+ for ($_[0]) {
+ /\.bz2$/ and $cmd='/usr/bin/bzip2', next;
+ /\.gz$/ and $cmd='/bin/gzip', next;
+ fail "unknown package format, file $_";
+ }
+ system('/bin/tar', '-xf', $_[0], '--use-compress-program', $cmd) == 0
+ or fail "unpacking $_[0] failed: $?";
+}
+
+sub apply_patch($;) {
+debug "YOW!";
+ my ($file) = @_;
+debug "applying patch from file $file";
+ my $zcat;
+ for ($file) {
+ /\.bz2$/ and $zcat='/usr/bin/bzcat', next;
+ /\.gz$/ and $zcat='/bin/zcat', next;
+ fail "unknown patch compression, file $_";
+ }
+ system("$zcat \"$file\" | patch -p1") == 0
+ or fail "applying patch $file failed: $?";
+ find(sub {/\.rej$/
+ and fail "patch $file failed for file $File::Find::name"},
+ '.');
+}
+
+%FLAGS = (
+ v => 'verbose',
+ verbose => sub {$VERBOSE++},
+ h => 'help',
+ help => sub {usage(); exit(0)},
+ c => 'configure',
+ config => 'configure',
+ configure => sub {$DO_CONFIG++},
+ H => 'headers',
+ headers => sub {$MAKE_HEADERS++},
+ O => 'official',
+ official => sub {$SOURCE_TYPE = 'official'},
+ D => 'debian',
+ debian => sub {$SOURCE_TYPE = 'debian'},
+ a => 'ac',
+ ac => sub {$PATCH_TYPE = 'ac'},
+ p => 'pre',
+ pre => sub {$PATCH_TYPE = 'pre'},
+ ben => sub {$PATCH_TYPE = 'ben'},
+ benh => sub {$PATCH_TYPE = 'benh'},
+ paulus => sub {$PATCH_TYPE = 'paulus'},
+ revision => sub {$KERNEL_REVISION = undef},
+ );
+
+$SOURCE_TYPE = 'official';
+$PATCH_TYPE = 'ac';
+while (@ARGV and $ARGV[0] =~ /^-/) {
+ local $_ = shift;
+ s/^-//g;
+ if (/^-/) { # long opt
+ s/^-//g;
+ exists $FLAGS{$_} or usage(), exit(1);
+ $_=$FLAGS{$_} if not ref $FLAGS{$_};
+ &{$FLAGS{$_}};
+ } else {
+ foreach (split //, $_) {
+ exists $FLAGS{$_} or usage(), exit(1);
+ $_=$FLAGS{$_} if not ref $FLAGS{$_};
+ &{$FLAGS{$_}};
+ }
+ }
+}
+
+my ($host, $version, @modules) = @ARGV;
+defined $host and $host =~ /^$_host_regexp$/ or usage(), exit(1);
+
+my ($srcbasename) = source_basename($SOURCE_TYPE);
+$version = latest_kernel($srcbasename, $PATCH_TYPE) if not defined $version;
+my ($kernver, $kernver_first, $kernver_last, $patchver) = ($version =~ /^((\d+\.\d+\.)(\d+))(?:-$PATCH_TYPE(\d+\w*))?$/);
+defined $kernver or fail "invalid kernel version $version";
+my ($kernfile);
+if ($PATCH_TYPE eq 'pre') {
+ if ($kernver_last gt 0) {
+ $kernfile=filename_kernel($srcbasename, $kernver_first . ($kernver_last - 1));
+ } else {
+ fail "pre-patched x.x.0 sources not supported in kernellab (can't guess earlier version)";
+ }
+} else {
+ $kernfile=filename_kernel($srcbasename, $kernver);
+}
+defined $kernfile or fail "kernel $kernver not found.";
+info "kernel version=$kernver, filename=$kernfile";
+my $patchfile;
+if (defined $patchver) {
+ $patchfile=filename_patch($kernver, $PATCH_TYPE, $patchver);
+ defined $patchfile or fail "$PATCH_TYPE patch $kernver-$PATCH_TYPE$patchver not found.";
+ info "$PATCH_TYPE patch $patchver, filename=$patchfile";
+}
+
+if (@modules) {
+ foreach (@modules) {
+ defined filename_module($_)
+ or fail "module $_ not found.";
+ }
+ info "modules=", join(', ', @modules);
+}
+
+info "making build dir";
+mkdir $BUILDDIR, 0755 or fail "cannot mkdir build directory $BUILDDIR; $!";
+chdir $BUILDDIR or fail "cannot chdir to build directory $BUILDDIR; $!";
+info "extracting kernel sources";
+extract($kernfile);
+if (-d "kernel-source-$kernver") {
+ info "creating a symlink from '$kernfile' to 'linux'";
+ symlink ("kernel-source-$kernver",'linux') or fail "couldn't symlink 'kernel-source-$kernver' to 'linux'";
+}
+-d 'linux' or fail "kernel source didn't unpack in 'linux'";
+chdir 'linux' or fail "cannot chdir to kernel subdir; $!";
+
+if (defined $patchfile) {
+ info "applying $PATCH_TYPE patches";
+ apply_patch($patchfile);
+}
+
+# find closest config file, copy to .config
+my $config=closest_config($host, $kernver, $PATCH_TYPE, $patchver);
+if (defined $config) { #found one
+ info "using old config $config";
+ system('cp', $config, '.config') == 0
+ or fail "copying $config to .config failed; $?";
+ system('make', 'oldconfig') == 0
+ or fail "make oldconfig failed; $?";
+}
+else {$DO_CONFIG++} # default settings -> configure
+
+if ($DO_CONFIG) {
+ system('make', 'menuconfig') == 0
+ or fail "make menuconfig failed; $?";
+ -e '.config'
+ or fail "menuconfig didn't write a config file, exiting..";
+}
+
+info "storing config";
+system('cp', '.config',
+ $WRITE_CONFIG . 'config-' . $host . '-' . $version) == 0
+ or fail "copying .config to $WRITE_CONFIG failed; $?";
+
+info "building kernel...";
+my $revision;
+if (defined $KERNEL_REVISION) {
+ $revision = $KERNEL_REVISION ? "--revision $KERNEL_REVISION" : "";
+} else {
+ $revision = '--revision 1:'.next_revision($host, $kernver,
+ strftime('%Y%m%d',localtime()), $PATCH_TYPE);
+}
+system('fakeroot', '/usr/bin/make-kpkg', "$revision",
+ 'kernel_image') == 0
+ or fail "make-kpkg kernel_image failed; $?";
+
+if ($MAKE_HEADERS) {
+ info "building headers...";
+ system('fakeroot', '/usr/bin/make-kpkg',
+ 'kernel_headers') == 0
+ or fail "make-kpkg kernel_headers failed; $?";
+}
+
+chdir $BUILDDIR or fail "cannot chdir to build directory $BUILDDIR; $!";
+
+info "extracting modules";
+#modules have to unpack into modules/<name>
+foreach(@modules) {
+ extract filename_module $_;
+}
+if (@modules) {
+ # this check is too many false positives
+ -d 'modules' or fail "modules didn't unpack in 'modules'";
+
+ chdir 'linux' or fail "cannot chdir to kernel subdir; $!";
+
+ info "building modules...";
+ $ENV{MODULE_LOC}=$BUILDDIR . '/modules';
+# $ENV{SRCTOP}=$BUILDDIR . '/linux';
+# $ENV{PWD}=$BUILDDIR . '/linux';
+ system('fakeroot', '/usr/bin/make-kpkg', 'modules_image') == 0
+ or fail "make-kpkg modules_image failed; $?";
+}
+
+chdir $BUILDDIR or fail "cannot chdir to build directory $BUILDDIR; $!";
+info "cleaning";
+system('rm', '-rf', 'linux', 'modules', 'kernel-source-*');
+
+info "moving images to", $WRITE_IMAGE.$host;
+-d "$WRITE_IMAGE/$host"
+ or mkdir "$WRITE_IMAGE/$host", 0755
+ or fail "cannot make directory $WRITE_IMAGE/$host; $!";
+
+my $image_exists_error = defined($KERNEL_REVISION)
+ ? "don't build with same revision twice!"
+ : "this can't happen!";
+foreach (grep {!/^\./} getdir '.') {
+ fail "$_ exists in $WRITE_IMAGE, $image_exists_error"
+ if -e "$WRITE_IMAGE/$host/$_";
+ system('mv', '-i', $_, "$WRITE_IMAGE/$host/$_") == 0
+ or fail "moving images failed on file $_: $!";
+}
+
+info "final cleaning";
+chdir '/' or fail "cannot chdir out from build dir; $!";
+rmdir $BUILDDIR or fail "cannot remove build dir $BUILDDIR; $!";
+
+print "$_programname: Done.\n";
+exit(0);
+
+__END__
+
+=head1 NAME
+
+kernellab - manage kernel configs for many machines easily
+
+=head1 SYNOPSIS
+
+kernellab [options] <host> [<version>[-ac<patchlevel>] [<module>..]]
+
+=head1 DESCRIPTION
+
+Kernellab helps you manage kernel configs for many heterogenous
+machines. The configs are just stored in their normal format in
+/var/state/kernellab/configs/config-<hostname>-<kernversion>[-ac<acver>].
+This and placing the kernel sources in a format accessible to
+kernellab allows you to easily build a new kernel for your computers.
+
+Let's take an example: say you have 20 miscellanous machines working
+as routers all over your network, with different ethernet cards and
+other kernel options. Say someone discovers a denial of service
+-attack in the linux TCP/IP stack. So you wait two hours till Alan Cox
+puts out a new -ac42 patch, download this patch and put in to
+/var/state/kernellab/alancox/patch-n.n.n-ac42.bz2. Now, all you need
+to do to recompile the new, fixed, kernel for all your routers, is
+
+ for a in router1 router2 router3 ...; do kernellab "$a"; done
+
+=head1 OPTIONS
+
+ -O use official linux-x.x.x sources (default)
+ -D use Debian kernel-source-x.x.x sources
+ -a use Alan Cox patches (default)
+ -p use Linus Thorvalds pre-patches
+ --ben use Benjamin Herrenschmidt ben patches
+ --benh use Benjamin Herrenschmidt benh patches
+ --paulus use Paul Mackerras patches
+ -c always run make menuconfig
+ -H also create a kernel-headers -package
+ -v increase verbosity (can specify many times)
+ -h show usage
+
+=head1 BUGS
+
+This manpage.
+
+=head1 AUTHOR
+
+Tommi Virtanen <tv@havoc.fi>
+
+=cut