summaryrefslogtreecommitdiff
path: root/kernellab
blob: 55e4b37aac5a456d3cf106ad3fb263f646714e9d (plain)
  1. #!/usr/bin/perl -w
  2. #
  3. # kernellab - manage kernel configs for many machines easily
  4. # Copyright (C) 1999 Tommi Virtanen <tv@havoc.fi>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software
  18. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  19. #
  20. use strict;
  21. use vars qw(@BASEPATH @SOURCES @ALANCOX @MODULES @IMAGES @CONFIG
  22. $TEMPDIR $VERBOSE $DO_CONFIG);
  23. use vars qw($BUILDDIR $_host_regexp %FLAGS $MAKE_HEADERS $SOURCE_TYPE $PATCH_TYPE $KERNEL_REVISION
  24. $WRITE_CONFIG $WRITE_IMAGE);
  25. use File::Find;
  26. use POSIX qw(strftime);
  27. my $_programname=$0;
  28. $_programname=~s{^.*/}{};
  29. sub fail(@) { die "$_programname: @_\n" }
  30. sub info(@) {print "$_programname: info: @_\n" if $VERBOSE}
  31. sub debug(@) {print "$_programname: debug: @_\n" if $VERBOSE>1}
  32. do "$ENV{HOME}/.kernellab.conf"
  33. or do '/etc/kernellab.conf'
  34. or fail "config error; $@\n";
  35. sub find_first_matching(&@) {
  36. my $match = shift;
  37. foreach (@_) {
  38. return $_ if $match->($_);
  39. }
  40. return undef;
  41. }
  42. $BUILDDIR = $TEMPDIR . '/kernellab.' . time() . '.' . $$;
  43. @BASEPATH = map { append_slash($_) } grep {-d $_} @BASEPATH;
  44. @SOURCES =
  45. map { append_slash($_) }
  46. grep {-d $_}
  47. map { prefix_relative($_, @BASEPATH) }
  48. @SOURCES;
  49. @ALANCOX =
  50. map { append_slash($_) }
  51. grep {-d $_}
  52. map { prefix_relative($_, @BASEPATH) }
  53. @ALANCOX;
  54. @MODULES =
  55. map { append_slash($_) }
  56. grep {-d $_}
  57. map { prefix_relative($_, @BASEPATH) }
  58. @MODULES;
  59. @IMAGES =
  60. map { append_slash($_) }
  61. grep {-d $_}
  62. map { prefix_relative($_, @BASEPATH) }
  63. @IMAGES;
  64. @CONFIG =
  65. map { append_slash($_) }
  66. grep {-d $_}
  67. map { prefix_relative($_, @BASEPATH) }
  68. @CONFIG;
  69. $WRITE_IMAGE = find_first_matching {-w $_} @IMAGES
  70. or fail "cannot find a writable place to store kernel images\n",
  71. "perhaps you should run 'mkdir -p ~/kernellab/images'";
  72. $WRITE_CONFIG = find_first_matching {-w $_} @CONFIG
  73. or fail "cannot find a writable place to store configs\n",
  74. "perhaps you should run 'mkdir -p ~/kernellab/configs'";
  75. @BASEPATH and @SOURCES and @ALANCOX and @MODULES and @IMAGES and @CONFIG
  76. or fail "some of the base directories did not exist. Check config";
  77. $_host_regexp = '[a-z0-9.-]+';
  78. sub usage() {
  79. print <<EOF;
  80. usage: $_programname [options] <host> [<version>[-ac<patchlevel>] [<module>..]]
  81. where options are
  82. -O, --official use official linux-x.x.x sources (default)
  83. -D, --debian use Debian kernel-source-x.x.x sources
  84. -a, --ac use Alan Cox patches (default)
  85. -p, --pre use Linus Thorvalds pre-patches
  86. --ben use Benjamin Herrenschmidt ben patches
  87. --benh use Benjamin Herrenschmidt benh patches
  88. --paulus use Paul Mackerras patches
  89. -c, --configure always run menuconfig
  90. -H, --headers also create a kernel-headers -package
  91. -v, --verbose be more verbose
  92. -h, --help show this help
  93. EOF
  94. }
  95. sub prefix($@;) {
  96. my ($file)=shift;
  97. return map {$_.$file} @_;
  98. }
  99. sub prefix_relative($@;) {
  100. my ($file) = shift;
  101. return $file if $file=~m{^[/.]};
  102. return prefix($file, @_);
  103. }
  104. sub append_slash($;) {
  105. for (@_) {
  106. return m{ /$ }x ? $_ : $_.'/'
  107. }
  108. }
  109. sub getdir($;) {
  110. -d $_[0] or return ();
  111. opendir(DIR, $_[0]) or fail "cannot open directory $_[0]; $!";
  112. my @r = readdir(DIR);
  113. closedir DIR;
  114. return @r;
  115. }
  116. sub _find_filename(@) { # for filename_* functions
  117. foreach (@_) { return $_ if -e $_ }
  118. return undef;
  119. }
  120. sub filename_kernel($$;) {
  121. my ($basename, $file) = @_;
  122. _find_filename(prefix($basename . '-' . $file . '.tar.bz2', @SOURCES),
  123. prefix($basename . '-' . $file . '.tar.gz', @SOURCES));
  124. }
  125. sub filename_patch($$$;) {
  126. my ($k, $patch, $patchver) = @_;
  127. _find_filename(prefix('patch-' . $k . '-' . $patch . $patchver . '.bz2', @ALANCOX),
  128. prefix('patch-' . $k . '-' . $patch . $patchver . '.gz', @ALANCOX));
  129. }
  130. sub filename_module($;) {
  131. _find_filename(prefix($_[0] . '.tar.gz', @MODULES),
  132. prefix($_[0] . '.tar.bz2', @MODULES));
  133. }
  134. sub max(@) {
  135. my $max;
  136. foreach (@_) {
  137. $max=$_ if not defined $max or $max<$_;
  138. }
  139. return $max;
  140. }
  141. sub source_basename($;) {
  142. my ($source) = @_;
  143. my ($basename);
  144. for ($source) {
  145. /^debian$/ and $basename='kernel-source', next;
  146. /^official$/ and $basename='linux', next;
  147. fail "unknown kernel source type \"$source\"";
  148. }
  149. return $basename;
  150. }
  151. sub latest_kernel($$;) {
  152. my ($basename, $patch) = @_;
  153. my @kernels =
  154. map {/^$basename-(\d+)\.(\d+)\.(\d+)\./; [$_,$1,$2,$3]}
  155. grep {/^$basename-\d+\.\d+\.\d+\.tar\.(?:gz|bz2)$/}
  156. map {getdir $_} @SOURCES;
  157. my $a = max map {$_->[1]} @kernels;
  158. defined $a or fail "cannot determine latest kernel version (1)";
  159. @kernels = grep {$_->[1] == $a} @kernels;
  160. my $b = max map {$_->[2]} @kernels;
  161. defined $b or fail "cannot determine latest kernel version (2)";
  162. @kernels = grep {$_->[2] == $b} @kernels;
  163. my $c = max map {$_->[3]} @kernels;
  164. defined $c or fail "cannot determine latest kernel version (3)";
  165. my $kernel = "$a.$b.$c";
  166. my @patches =
  167. map {/^patch-\d+\.\d+\.\d+.-$patch(\d+\w*)\./; [$_,$1]}
  168. grep {/^patch-$a\.$b\.$c-$patch\d+\w*\.(?:gz|bz2)$/}
  169. map {getdir $_} @ALANCOX;
  170. my $patchver = max map {$_->[1]} @patches;
  171. $kernel .= '-' . $patch . $patchver if defined $patchver;
  172. return $kernel;
  173. }
  174. sub closest_config($$$;$;) {
  175. my ($host, $kver, $patch, $patchver) = @_;
  176. debug "finding closest config for $host $kver"
  177. . (defined $patchver ? "-$patch$patchver" : '');
  178. my @configs;
  179. foreach my $confdir (@CONFIG) {
  180. foreach my $conffile (getdir $confdir) {
  181. $conffile =~
  182. /^config-($_host_regexp)-(\d+)\.(\d+)\.(\d+)(?:-$patch(\d+)\w*)?$/
  183. or next;
  184. $1 eq $host or next;
  185. push @configs, [$confdir.$conffile, $1,$2,$3,$4,$5];
  186. }
  187. }
  188. @configs = sort { #descending
  189. $b->[2] <=> $a->[2]
  190. ||
  191. $b->[3] <=> $a->[3]
  192. ||
  193. $b->[4] <=> $a->[4]
  194. ||
  195. $b->[5] <=> $a->[5]
  196. # rely on (undef<=>0)==(0<=>undef)==0
  197. } @configs;
  198. return $configs[0]->[0];
  199. }
  200. sub next_revision($$$$;) {
  201. my ($host, $kver, $date, $patch) = @_;
  202. my @revs =
  203. sort {$b<=>$a}
  204. map { $_->[3] }
  205. grep {
  206. $_->[1] eq $host
  207. and $_->[2] eq $date
  208. }
  209. map {
  210. /^kernel-image-
  211. (\d+\.\d+\.\d+(?:-$patch\d+\w*)?)_ # kernel version number
  212. ($_host_regexp)\. # hostname
  213. (\d\d\d\d\d\d\d\d)\. # yyyymmdd
  214. (\d+)_ # revision
  215. .*\.deb$
  216. /x;
  217. [$1,$2,$3,$4]
  218. }
  219. grep {/^kernel-image-/}
  220. map {getdir "$_/$host"} @IMAGES;
  221. return "$host.$date.".($revs[0]+1) if @revs;
  222. return "$host.$date.1";
  223. }
  224. sub extract($;) {
  225. my $cmd;
  226. for ($_[0]) {
  227. /\.bz2$/ and $cmd='/usr/bin/bzip2', next;
  228. /\.gz$/ and $cmd='/bin/gzip', next;
  229. fail "unknown package format, file $_";
  230. }
  231. system('/bin/tar', '-xf', $_[0], '--use-compress-program', $cmd) == 0
  232. or fail "unpacking $_[0] failed: $?";
  233. }
  234. sub apply_patch($;) {
  235. debug "YOW!";
  236. my ($file) = @_;
  237. debug "applying patch from file $file";
  238. my $zcat;
  239. for ($file) {
  240. /\.bz2$/ and $zcat='/usr/bin/bzcat', next;
  241. /\.gz$/ and $zcat='/bin/zcat', next;
  242. fail "unknown patch compression, file $_";
  243. }
  244. system("$zcat \"$file\" | patch -p1") == 0
  245. or fail "applying patch $file failed: $?";
  246. find(sub {/\.rej$/
  247. and fail "patch $file failed for file $File::Find::name"},
  248. '.');
  249. }
  250. %FLAGS = (
  251. v => 'verbose',
  252. verbose => sub {$VERBOSE++},
  253. h => 'help',
  254. help => sub {usage(); exit(0)},
  255. c => 'configure',
  256. config => 'configure',
  257. configure => sub {$DO_CONFIG++},
  258. H => 'headers',
  259. headers => sub {$MAKE_HEADERS++},
  260. O => 'official',
  261. official => sub {$SOURCE_TYPE = 'official'},
  262. D => 'debian',
  263. debian => sub {$SOURCE_TYPE = 'debian'},
  264. a => 'ac',
  265. ac => sub {$PATCH_TYPE = 'ac'},
  266. p => 'pre',
  267. pre => sub {$PATCH_TYPE = 'pre'},
  268. ben => sub {$PATCH_TYPE = 'ben'},
  269. benh => sub {$PATCH_TYPE = 'benh'},
  270. paulus => sub {$PATCH_TYPE = 'paulus'},
  271. revision => sub {$KERNEL_REVISION = undef},
  272. );
  273. $SOURCE_TYPE = 'official';
  274. $PATCH_TYPE = 'ac';
  275. while (@ARGV and $ARGV[0] =~ /^-/) {
  276. local $_ = shift;
  277. s/^-//g;
  278. if (/^-/) { # long opt
  279. s/^-//g;
  280. exists $FLAGS{$_} or usage(), exit(1);
  281. $_=$FLAGS{$_} if not ref $FLAGS{$_};
  282. &{$FLAGS{$_}};
  283. } else {
  284. foreach (split //, $_) {
  285. exists $FLAGS{$_} or usage(), exit(1);
  286. $_=$FLAGS{$_} if not ref $FLAGS{$_};
  287. &{$FLAGS{$_}};
  288. }
  289. }
  290. }
  291. my ($host, $version, @modules) = @ARGV;
  292. defined $host and $host =~ /^$_host_regexp$/ or usage(), exit(1);
  293. my ($srcbasename) = source_basename($SOURCE_TYPE);
  294. $version = latest_kernel($srcbasename, $PATCH_TYPE) if not defined $version;
  295. my ($kernver, $kernver_first, $kernver_last, $patchver) = ($version =~ /^((\d+\.\d+\.)(\d+))(?:-$PATCH_TYPE(\d+\w*))?$/);
  296. defined $kernver or fail "invalid kernel version $version";
  297. my ($kernfile);
  298. if ($PATCH_TYPE eq 'pre') {
  299. if ($kernver_last gt 0) {
  300. $kernfile=filename_kernel($srcbasename, $kernver_first . ($kernver_last - 1));
  301. } else {
  302. fail "pre-patched x.x.0 sources not supported in kernellab (can't guess earlier version)";
  303. }
  304. } else {
  305. $kernfile=filename_kernel($srcbasename, $kernver);
  306. }
  307. defined $kernfile or fail "kernel $kernver not found.";
  308. info "kernel version=$kernver, filename=$kernfile";
  309. my $patchfile;
  310. if (defined $patchver) {
  311. $patchfile=filename_patch($kernver, $PATCH_TYPE, $patchver);
  312. defined $patchfile or fail "$PATCH_TYPE patch $kernver-$PATCH_TYPE$patchver not found.";
  313. info "$PATCH_TYPE patch $patchver, filename=$patchfile";
  314. }
  315. if (@modules) {
  316. foreach (@modules) {
  317. defined filename_module($_)
  318. or fail "module $_ not found.";
  319. }
  320. info "modules=", join(', ', @modules);
  321. }
  322. info "making build dir";
  323. mkdir $BUILDDIR, 0755 or fail "cannot mkdir build directory $BUILDDIR; $!";
  324. chdir $BUILDDIR or fail "cannot chdir to build directory $BUILDDIR; $!";
  325. info "extracting kernel sources";
  326. extract($kernfile);
  327. if (-d "kernel-source-$kernver") {
  328. info "creating a symlink from '$kernfile' to 'linux'";
  329. symlink ("kernel-source-$kernver",'linux') or fail "couldn't symlink 'kernel-source-$kernver' to 'linux'";
  330. }
  331. -d 'linux' or fail "kernel source didn't unpack in 'linux'";
  332. chdir 'linux' or fail "cannot chdir to kernel subdir; $!";
  333. if (defined $patchfile) {
  334. info "applying $PATCH_TYPE patches";
  335. apply_patch($patchfile);
  336. }
  337. # find closest config file, copy to .config
  338. my $config=closest_config($host, $kernver, $PATCH_TYPE, $patchver);
  339. if (defined $config) { #found one
  340. info "using old config $config";
  341. system('cp', $config, '.config') == 0
  342. or fail "copying $config to .config failed; $?";
  343. system('make', 'oldconfig') == 0
  344. or fail "make oldconfig failed; $?";
  345. }
  346. else {$DO_CONFIG++} # default settings -> configure
  347. if ($DO_CONFIG) {
  348. system('make', 'menuconfig') == 0
  349. or fail "make menuconfig failed; $?";
  350. -e '.config'
  351. or fail "menuconfig didn't write a config file, exiting..";
  352. }
  353. info "storing config";
  354. system('cp', '.config',
  355. $WRITE_CONFIG . 'config-' . $host . '-' . $version) == 0
  356. or fail "copying .config to $WRITE_CONFIG failed; $?";
  357. info "building kernel...";
  358. my $revision;
  359. if (defined $KERNEL_REVISION) {
  360. $revision = $KERNEL_REVISION ? "--revision $KERNEL_REVISION" : "";
  361. } else {
  362. $revision = '--revision 1:'.next_revision($host, $kernver,
  363. strftime('%Y%m%d',localtime()), $PATCH_TYPE);
  364. }
  365. system('fakeroot', '/usr/bin/make-kpkg', "$revision",
  366. 'kernel_image') == 0
  367. or fail "make-kpkg kernel_image failed; $?";
  368. if ($MAKE_HEADERS) {
  369. info "building headers...";
  370. system('fakeroot', '/usr/bin/make-kpkg',
  371. 'kernel_headers') == 0
  372. or fail "make-kpkg kernel_headers failed; $?";
  373. }
  374. chdir $BUILDDIR or fail "cannot chdir to build directory $BUILDDIR; $!";
  375. info "extracting modules";
  376. #modules have to unpack into modules/<name>
  377. foreach(@modules) {
  378. extract filename_module $_;
  379. }
  380. if (@modules) {
  381. # this check is too many false positives
  382. -d 'modules' or fail "modules didn't unpack in 'modules'";
  383. chdir 'linux' or fail "cannot chdir to kernel subdir; $!";
  384. info "building modules...";
  385. $ENV{MODULE_LOC}=$BUILDDIR . '/modules';
  386. # $ENV{SRCTOP}=$BUILDDIR . '/linux';
  387. # $ENV{PWD}=$BUILDDIR . '/linux';
  388. system('fakeroot', '/usr/bin/make-kpkg', 'modules_image') == 0
  389. or fail "make-kpkg modules_image failed; $?";
  390. }
  391. chdir $BUILDDIR or fail "cannot chdir to build directory $BUILDDIR; $!";
  392. info "cleaning";
  393. system('rm', '-rf', 'linux', 'modules', 'kernel-source-*');
  394. info "moving images to", $WRITE_IMAGE.$host;
  395. -d "$WRITE_IMAGE/$host"
  396. or mkdir "$WRITE_IMAGE/$host", 0755
  397. or fail "cannot make directory $WRITE_IMAGE/$host; $!";
  398. my $image_exists_error = defined($KERNEL_REVISION)
  399. ? "don't build with same revision twice!"
  400. : "this can't happen!";
  401. foreach (grep {!/^\./} getdir '.') {
  402. fail "$_ exists in $WRITE_IMAGE, $image_exists_error"
  403. if -e "$WRITE_IMAGE/$host/$_";
  404. system('mv', '-i', $_, "$WRITE_IMAGE/$host/$_") == 0
  405. or fail "moving images failed on file $_: $!";
  406. }
  407. info "final cleaning";
  408. chdir '/' or fail "cannot chdir out from build dir; $!";
  409. rmdir $BUILDDIR or fail "cannot remove build dir $BUILDDIR; $!";
  410. print "$_programname: Done.\n";
  411. exit(0);
  412. __END__
  413. =head1 NAME
  414. kernellab - manage kernel configs for many machines easily
  415. =head1 SYNOPSIS
  416. kernellab [options] <host> [<version>[-ac<patchlevel>] [<module>..]]
  417. =head1 DESCRIPTION
  418. Kernellab helps you manage kernel configs for many heterogenous
  419. machines. The configs are just stored in their normal format in
  420. /var/state/kernellab/configs/config-<hostname>-<kernversion>[-ac<acver>].
  421. This and placing the kernel sources in a format accessible to
  422. kernellab allows you to easily build a new kernel for your computers.
  423. Let's take an example: say you have 20 miscellanous machines working
  424. as routers all over your network, with different ethernet cards and
  425. other kernel options. Say someone discovers a denial of service
  426. -attack in the linux TCP/IP stack. So you wait two hours till Alan Cox
  427. puts out a new -ac42 patch, download this patch and put in to
  428. /var/state/kernellab/alancox/patch-n.n.n-ac42.bz2. Now, all you need
  429. to do to recompile the new, fixed, kernel for all your routers, is
  430. for a in router1 router2 router3 ...; do kernellab "$a"; done
  431. =head1 OPTIONS
  432. -O use official linux-x.x.x sources (default)
  433. -D use Debian kernel-source-x.x.x sources
  434. -a use Alan Cox patches (default)
  435. -p use Linus Thorvalds pre-patches
  436. --ben use Benjamin Herrenschmidt ben patches
  437. --benh use Benjamin Herrenschmidt benh patches
  438. --paulus use Paul Mackerras patches
  439. -c always run make menuconfig
  440. -H also create a kernel-headers -package
  441. -v increase verbosity (can specify many times)
  442. -h show usage
  443. =head1 BUGS
  444. This manpage.
  445. =head1 AUTHOR
  446. Tommi Virtanen <tv@havoc.fi>
  447. =cut