source: ProjectBuilder/devel/pbmkbm/bin/pbmkbm@ 1414

Last change on this file since 1414 was 1414, checked in by Bruno Cornec, 12 years ago

r4400@localhost: bruno | 2012-02-21 13:15:45 +0100

  • Start adding keyboard capture support
File size: 17.9 KB
Line 
1#!/usr/bin/perl -w
2#
3# pbmkbm, a project-builder.org utility to make boot media
4#
5# $Id$
6#
7# Copyright B. Cornec 2011
8# Provided under the GPL v2
9
10# Syntax: see at end
11
12use strict 'vars';
13use Getopt::Long qw(:config auto_abbrev no_ignore_case);
14use Data::Dumper;
15use English;
16use File::Basename;
17use File::Copy;
18use File::Find;
19use POSIX qw(strftime);
20
21use ProjectBuilder::Version;
22use ProjectBuilder::Base;
23use ProjectBuilder::Env;
24use ProjectBuilder::Conf;
25use ProjectBuilder::Distribution;
26use ProjectBuilder::VE;
27
28# Global variables
29my %opts; # CLI Options
30
31=pod
32
33=head1 NAME
34
35pbmkbm - a project-builder.org utility to make boot media
36
37=head1 DESCRIPTION
38
39pbmkbm creates a bootable media (CD/DVD, USB device, Network, tape, ...)
40with a minimal distribution in it, suited for building packages for example.
41It aims at supporting all distributions supported by project-builder.org
42(RHEL, RH, Fedora, OpeSUSE, SLES, Mandriva, ...)
43
44It is inspired by work done by Jean-Marc André around the HP SSSTK and
45aim at replacing the mindi project (http://www.mondorescue.org), but
46fully integrated with project-builder.org
47
48pbmkbm works in different phases. The first one is to check all
49
50pbmkbm needs to gather a certain number of components that could come
51from various sources and could be put on a different target media.
52We need a kernel, an initrd/initramfs for additional modules and init script,
53a root filesystem and a boot configuration file.
54Kernel, modules could come either from the local installed system
55(typically for disaster recovery context) or from a kernel package of a
56given configuration or a referenced content.
57Utilities could come from busybox, local utilities or set of packages.
58The root filesystem is made with them.
59The initrd/initramfs could be made internaly or by calling dracut.
60The boot config file is generated from analysis content or provided externally.
61
62=head1 SYNOPSIS
63
64pbmkbm [-vhq][-t boot-type [-d device]][-b boot-method][-m os-ver-arch]
65[-s script][-a pkg1[,pkg2,...]] [target-dir]
66
67pbmkbm [--verbose][--help][--man][--quiet][--type boot-type [--device device]]
68[--machine os-ver-arch][--boot boot-method]
69[--script script][--add pkg1,[pkg2,...]] [target-dir]
70
71=head1 OPTIONS
72
73=over 4
74
75=item B<-v|--verbose>
76
77Print a brief help message and exits.
78
79=item B<-h|--help>
80
81Print a brief help message and exits.
82
83=item B<--man>
84
85Prints the manual page and exits.
86
87=item B<-q|--quiet>
88
89Do not print any output.
90
91=item B<-t|--type boot-type>
92
93Type of the boot device to generate. A boot-type can be:
94
95=over 4
96
97=item B<iso>
98
99Generate an ISO9660 image format (suitable to be burned later on or loopback mounted. Uses isolinux.
100
101=item B<usb>
102
103Generate a USB image format (typically a key of external hard drive). Uses syslinux.
104
105=item B<pxe>
106
107Generate a PXE environement (suitable to be integrated in a PXElinux configuration). Uses pxelinux.
108
109=back
110
111=item B<-d|--device device-file>
112
113Name of the device or file on which you want to create the boot media.
114
115=item B<-b|--boot boot-method>
116
117This is the boot method to use to create the boot media. A boot-method can be:
118
119=over 4
120
121=item B<native>
122
123Use the tools of the native distribution to create the boot media. No other dependency.
124
125=item B<ve>
126
127Use the project-builder.org virtual environment notion to create the boot media. No other dependency outside of the project.
128
129=item B<busybox>
130
131Use the busybox tool to create the boot media. Cf: L<http://www.busybox.net>
132
133=item B<dracut>
134
135Use the dracut tool to create the boot media. Cf: L<http://www.dracut.net>
136
137=back
138
139=item B<-s|--script script>
140
141Name of the script you want to execute on the related boot media at the end of the build.
142
143=item B<-a|--add pkg1[,pkg2,...]>
144
145Additional packages to add from the distribution you want to install on the related boot media
146at the end of the build.
147
148=item B<-m|--machine os-ver-arch>
149
150This is the target tuple operating system-version-architecture for which you want to create the boot media.
151
152=back
153
154=head1 ARGUMENTS
155
156target-dir is the directory under which the boot media will be build.
157
158=over 4
159
160=back
161
162=head1 EXAMPLE
163
164To setup a USB busybox based boot media on the /dev/sdb device for a Fedora 12 distribution with an i386 architecture issue:
165
166pbmkbm -t usb -d /dev/sdb -m fedora-12-i386 -b busybox
167
168To setup an ISO image under /tmp for a RHEL 6 x86_64 distribution issue using the native environment:
169
170pbmkbm -t iso -d /tmp -m rhel-6-x86_64 -b ve
171
172=head1 WEB SITES
173
174The main Web site of the project is available at L<http://www.project-builder.org/>.
175Bug reports should be filled using the trac instance of the project at L<http://trac.project-builder.org/>.
176
177=head1 USER MAILING LIST
178
179Cf: L<http://www.mondorescue.org/sympa/info/pb-announce> for announces and
180L<http://www.mondorescue.org/sympa/info/pb-devel> for the development of the pb project.
181
182=head1 CONFIGURATION FILE
183
184Uses Project-Builder.org configuration file (/etc/pb/pb.conf or /usr/local/etc/pb/pb.conf)
185
186=head1 AUTHORS
187
188The Project-Builder.org team L<http://trac.project-builder.org/> lead by Bruno Cornec L<mailto:bruno@project-builder.org>.
189
190=head1 COPYRIGHT
191
192Project-Builder.org is distributed under the GPL v2.0 license
193described in the file C<COPYING> included with the distribution.
194
195=cut
196
197# ---------------------------------------------------------------------------
198
199my ($projectbuilderver,$projectbuilderrev) = pb_version_init();
200my $appname = "pbmkbm";
201$ENV{'PBPROJ'} = $appname;
202
203# Initialize the syntax string
204
205pb_syntax_init("$appname Version $projectbuilderver-$projectbuilderrev\n");
206pb_temp_init();
207
208GetOptions("help|?|h" => \$opts{'h'},
209 "man" => \$opts{'man'},
210 "verbose|v+" => \$opts{'v'},
211 "quiet|q" => \$opts{'q'},
212 "log-files|l=s" => \$opts{'l'},
213 "script|s=s" => \$opts{'s'},
214 "machine|m=s" => \$opts{'m'},
215 "add|a=s" => \$opts{'a'},
216 "device|d=s" => \$opts{'d'},
217 "type|t=s" => \$opts{'t'},
218 "boot|b=s" => \$opts{'b'},
219 "version|V=s" => \$opts{'V'},
220) || pb_syntax(-1,0);
221
222if (defined $opts{'h'}) {
223 pb_syntax(0,1);
224}
225if (defined $opts{'man'}) {
226 pb_syntax(0,2);
227}
228if (defined $opts{'v'}) {
229 $pbdebug = $opts{'v'};
230}
231if (defined $opts{'q'}) {
232 $pbdebug=-1;
233}
234if (defined $opts{'l'}) {
235 open(pbLOG,"> $opts{'l'}") || die "Unable to log to $opts{'l'}: $!";
236 $pbLOG = \*pbLOG;
237 $pbdebug = 0 if ($pbdebug == -1);
238}
239pb_log_init($pbdebug, $pbLOG);
240pb_log(1,"$appname Version $projectbuilderver-$projectbuilderrev\n");
241my @date = pb_get_date();
242my $pbdate = strftime("%Y-%m-%d %H:%M:%S", @date);
243
244pb_log(1,"Start: $pbdate\n");
245
246# Get VE name
247$ENV{'PBV'} = $opts{'m'};
248
249#
250# Initialize distribution info from pb conf file
251#
252my $pbos = pb_distro_get_context($ENV{'PBV'});
253pb_log(0,"Starting boot media build for $pbos->{'name'}-$pbos->{'version'}-$pbos->{'arch'}\n");
254
255pb_env_init_pbrc(); # to get content of HOME/.pbrc
256
257# Global hash containing all the configuration information
258my %mkbm;
259
260#
261# Check target dir
262# Create if not existent and use default if none given
263#
264$mkbm{'targetdir'} = shift @ARGV;
265
266#
267# Check for command requirements
268#
269my ($req,$opt) = pb_conf_get_if("oscmd","oscmdopt");
270pb_check_requirements($req,$opt,$appname);
271
272# After that we will need root access
273die "$appname needs to be run as root" if ($EFFECTIVE_USER_ID != 0);
274
275#
276# Where is our build target directory
277#
278if (not defined $mkbm{'targetdir'}) {
279 $mkbm{'targetdir'} = "/var/cache/pbmkbm";
280 my ($vestdpath) = pb_conf_get("mkbmpath");
281 $mkbm{'targetdir'} = "$vestdpath->{'default'}/$pbos->{'name'}/$pbos->{'version'}/$pbos->{'arch'}" if (defined $vestdpath->{'default'});
282 pb_log(1,"No target-dir specified, using $mkbm{'targetdir'}\n");
283}
284
285# Point to the right subdir and create it if needed
286pb_mkdir_p($mkbm{'targetdir'}) if (! -d $mkbm{'targetdir'});
287
288# Log information on our system
289# TODO: this should be put in a subfunction
290my ($logcmd) = pb_conf_get("logcmd");
291my ($logopt) = pb_conf_get_if("logopt");
292# check that the command is there first and then use it.
293if ($logcmd->{$appname} ne "internal") {
294 $logcmd = pb_check_req($logcmd->{$appname},1);
295 if (not defined $logcmd) {
296 pb_log(1,"INFO: command $logcmd->{$appname} doesn't exist. No log report is available\n");
297 } else {
298 my $c = $logcmd->{$appname};
299 $c .= " $logopt->{$appname}" if (defined $logopt->{$appname});
300 pb_system("$c","Creating a log report of your system");
301 }
302} else {
303 # We provide our own internal log reporter as a std one isn't found
304 my ($lcmds,$lfiles) = pb_conf_get_if("logcommands","logfiles");
305 pb_log(1,"------------------\n");
306 if (defined $lcmds->{$appname}) {
307 foreach my $c (split(/,/,$lcmds->{$appname})) {
308 my $lcmd = $c;
309 $lcmd =~ s/ .*$//;
310 $lcmd = pb_check_req($lcmd,1);
311 if (not defined $lcmd) {
312 pb_log(1,"INFO: command $c doesn't exist\n");
313 pb_log(1,"------------------\n");
314 next;
315 }
316 pb_log(1,"Execution of $c\n");
317 pb_log(1,"------------------\n");
318 pb_system($c,"",1);
319 pb_log(1,"------------------\n");
320 }
321 }
322 if (defined $lfiles->{$appname}) {
323 foreach my $f (split(/,/,$lfiles->{$appname})) {
324 if (! -e $f) {
325 pb_log(1,"INFO: $f doesn't exist\n");
326 pb_log(1,"------------------\n");
327 next;
328 }
329 if (! -r $f) {
330 pb_log(1,"INFO: $f isn't readable\n");
331 pb_log(1,"------------------\n");
332 next;
333 }
334 if ((-f $f) || (-l $f)) {
335 if (! open(FILE,$f)) {
336 pb_log(1,"INFO: Unable to open $f\n");
337 pb_log(1,"------------------\n");
338 next;
339 }
340 pb_log(1,"Content of $f\n");
341 pb_log(1,"------------------\n");
342 while (<FILE>) {
343 pb_log(1,$_);
344 }
345 close(FILE);
346 pb_log(1,"------------------\n");
347 } elsif (-d $f) {
348 my $dh;
349 if (! opendir($dh,$f)) {
350 pb_log(1,"INFO: Unable to opendir $f\n");
351 pb_log(1,"------------------\n");
352 next;
353 }
354 pb_log(1,"Content of directory $f\n");
355 pb_log(1,"----------------------------\n");
356 while (readdir $dh) {
357 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat("$f/$_");
358 my $str = sprintf("%10s %2s %5s %5s %10s %14s %s",$mode,$ino,$uid,$gid,$size,$mtime,$_);
359 pb_log(1,"$str\n");
360 }
361 closedir($dh);
362 pb_log(1,"------------------\n");
363 } else {
364 pb_log(1,"INFO: $f is special\n");
365 pb_log(1,"------------------\n");
366 }
367 }
368 }
369}
370
371# Now the preparation is over, we need to do something useful :-)
372# But it all depends on how we're asked to do it.
373#
374# First we need to copy into the target dir all the relevant content
375pb_mkbm_create_content();
376
377# Then we need to package this content in the destination format
378pb_mkbm_create_media();
379
380@date = pb_get_date();
381$pbdate = strftime("%Y-%m-%d %H:%M:%S", @date);
382
383pb_log(1,"End: $pbdate\n");
384
385sub pb_mkbm_create_content {
386
387pb_log(1,"Creating boot media content\n");
388
389# If not defined use VE mode by default
390$opts{'b'} = "ve" if (not defined $opts{'b'});
391
392# Hash of target content
393# atribute could be, copy, remove, link, dir
394my %targettree;
395
396if ($opts{'b'} eq "ve") {
397 # Use project-builder VE mecanism to create a good VE !
398 pb_ve_launch($ENV{'PBV'},1);
399} elsif ($opts{'b'} eq "native") {
400 # Use native tools to create a good VE !
401} elsif ($opts{'b'} eq "drakut") {
402 # Use drakut to create a good VE !
403} elsif ($opts{'b'} eq "busybox") {
404 # Use busybox to create a good VE !
405 pb_mkbm_create_busybox_ve(\%targettree);
406} else {
407 die "Unknown method $opts{'b'} used to create the media content";
408}
409
410# Create the directory structure needed on the target dir
411my ($tdirs,$bdirs,$bfiles,$bcmds) = pb_distro_get_param($pbos,pb_conf_get("mkbmtargetdirs","mkbmbootdirs","mkbmbootfiles","mkbmbootcmds"));
412# Create empty dirs for these
413foreach my $d (split(/,/,$tdirs)) {
414 $targettree{$d} = "emptydir";
415}
416# And copy dirs for those
417foreach my $d (split(/,/,$bdirs)) {
418 if (-d $d) {
419 $targettree{$d} = "dir";
420 } elsif (-l $d) {
421 $targettree{$d} = "link";
422 } else {
423 pb_log(1,"INFO: Directory $d doesn't exist\n");
424 }
425}
426foreach my $f (split(/,/,$bfiles)) {
427 $targettree{$f} = "file";
428}
429pb_log(2,"INFO: Target Tree is now: ".Dumper(%targettree)."\n");
430# Once the environment is made, add what is needed for this boot media to it.
431# Keyboard
432pb_mkbm_find_keyboard(\%targettree);
433# Terminfo
434# List of commands
435# List of dependencies
436# Kernel - We use 2 objects, the running kernel and the target kernel which could be different
437my %rkernel;
438my %tkernel;
439pb_mkbm_find_kernel(\%rkernel);
440# Initrd
441# init
442# BootLoader and its configuration
443# Additional data files coming from a potential caller (MondoRescue/Mindi e.g. with fstab, LVM, mountlist, ...)
444pb_log(1,"End of boot media creation\n");
445}
446
447sub pb_mkbm_create_busybox_ve {
448
449my $tgtree = shift;
450
451pb_log(1,"Analyzing your busybox's configuration\n");
452# First, check which are the supported command in that version of busybox
453# and create the links for it in the target VE
454
455my $busycmd = pb_distro_get_param($pbos,pb_conf_get("ospathcmd-busybox"));
456open(BUSY,"$busycmd |") || die "Unable to execute $busycmd";
457my $cmdlist = 0;
458while (<BUSY>) {
459 pb_log(3,"busybox line : $_");
460 chomp($_);
461 # After these words, we have the list of functions, so trigger their analysis
462 if ($_ =~ /defined functions:/) {
463 $cmdlist = 1;
464 next;
465 }
466 # Analyse the list of commands provided by that busybox (, separated)
467 if ($cmdlist == 1) {
468 pb_log(3,"Analyzing that busybox line\n");
469 foreach my $c (split(/,/,$_)) {
470 $c =~ s/\s*//g;
471 # skip empty strings
472 next if ($c =~ /^$/);
473 my $c1 = pb_check_req($c,0);
474 if (defined $c1) {
475 $tgtree->{$c1} = "link:$busycmd";
476 } else {
477 # When not found on the system, create the link under /usr/bin by default
478 $tgtree->{"/usr/bin/$c"} = "link:$busycmd";
479 }
480 }
481 }
482}
483pb_log(1,"Target Tree is now: ".Dumper($tgtree)."\n");
484close(BUSY);
485pb_log(1,"End of busybox analysis\n");
486}
487
488sub pb_mkbm_find_keyboard {
489
490my $tgtree = shift;
491
492pb_log(1,"Analyzing your keyboard's configuration\n");
493my $keyfile = pb_distro_get_param($pbos,pb_conf_get("ospathcmd-keyfile"));
494die "Unable to read the keyfile $keyfile" if ((not defined $keyfile) || (! -r $keyfile));
495my $keymapdir = pb_distro_get_param($pbos,pb_conf_get("ospathcmd-keymapdir"));
496die "Unable to read the keymapdir $keymapdir" if (not defined $keymapdir) || (! -d $keymapdir));
497my $keymapre = pb_distro_get_param($pbos,pb_conf_get("ospathcmd-keymapre"));
498die "Unable to read the keymapre $keymapre" if (not defined $keymapre);
499
500# if a direct keymap file is given as keyfile, use only the first existing one it and return
501my $foundkmap = 0;
502foreach my $f (split(/,/,$keyfile)) {
503 next if ($f !~ /\.gz$/);
504 $foundkmap = 1;
505 if (-l $f) {
506 $tgtree->{$f} = "link:$f";
507 pb_log(1,"Using Keymap file $f\n");
508 last;
509 } elsif (-r $f) {
510 $tgtree->{$f} = "file";
511 pb_log(1,"Using Keymap file $f\n");
512 last;
513 } else {
514 next;
515 }
516}
517return() if ($foundkmap eq 1);
518
519pb_log(1,"Using Keyfile $keyfile and Keymap directory $keymapdir\n");
520my $locale="";
521open(KEYMAP,"$keyfile") || die "Unable to read $keyfile";
522# Depending on the format of the keymap we look for various strings
523while (<KEYMAP>) {
524 $locale =~ $keymapre;
525}
526close(KEYMAP);
527pb_log(1,"Found locale $locale\n");
528
529pb_log(1,"End of keyboard analysis\n");
530}
531
532sub pb_mkbm_find_kernel {
533
534my $kernel = shift;
535
536pb_log(1,"Analyzing your kernel's configuration\n");
537$kernel->{"is_xen"} = undef;
538# See if we're booted from a Xen kernel
539# From http://wiki.xensource.com/xenwiki/XenCommonProblems#head-26434581604cc8357d9762aaaf040e8d87b37752
540if ( -f "/proc/xen/capabilities") {
541 # It's a Xen kernel
542 pb_log(2,"INFO: We found a Xen Kernel running\n");
543 $kernel->{"is_xen"} = 1;
544}
545$kernel->{"release"} = pb_get_osrelease();
546
547my $kfile = pb_distro_get_param($pbos,pb_conf_get_if("mkbmkernelfile"));
548if ((defined $kfile) && ($kfile ne "")) {
549 pb_log(1,"INFO: You specified your kernel as $kfile, so using it\n");
550 $kernel->{"file"} = $kfile;
551} else {
552 $kernel->{"dir"} = pb_distro_get_param($pbos,pb_conf_get("mkbmkerneldir"));
553 die "ERROR: The mkbmkerneldir content ($kernel->{'dir'}) doesn't refer to a directory\n" if (! -d $kernel->{"dir"});
554 pb_log(1,"INFO: Analyzing directory $kernel->{'dir'} to find your kernel\n");
555 $kernel->{"namere"} = pb_distro_get_param($pbos,pb_conf_get("mkbmkernelnamere"));
556
557 # TODO: Look at a better way to find the name of the kernel we run
558 # look at /proc/sys/kernel/bootloader_type /proc/sys/kernel/bootloader_version
559 # to have a better guess
560 my $dh;
561 die "ERROR: Unable to open the mkbmkerneldir content ($kernel->{'dir'})\n" if (! opendir($dh,$kernel->{"dir"}));
562 while (readdir $dh) {
563 pb_log(3,"Potential kernel file: $_\n");
564 # Skip non-files
565 next if (! -f "$kernel->{'dir'}/$_");
566 # Skip files not correpsonding to the RE planned
567 next if ($_ !~ /$kernel->{"namere"}/);
568 # We now have a candidate. Analyze further
569 pb_log(3,"Potential kernel file 2: $_\n");
570 eval
571 {
572 require File::MimeInfo;
573 File::MimeInfo->import();
574 };
575 if ($@) {
576 # File::MimeInfo not found
577 die("ERROR: Install File::MimeInfo to handle kernel file detection\n");
578 }
579 my $mm = mimetype("$kernel->{'dir'}/$_");
580 # Skip symlinks
581 next if ($mm =~ /inode\/symlink/);
582 pb_log(2,"file $_ mimetype: $mm\n");
583 if ($mm =~ /\/x-gzip/) {
584 # on ia64 kernel are gzip compressed
585 }
586 next if (pb_get_content("$kernel->{'dir'}/$_") !~ /$kernel->{"release"}/);
587 pb_log(3,"Potential kernel file 3: $_\n");
588 $kernel->{"file"} = "$kernel->{'dir'}/$_";
589 #my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat("$f/$_");
590 }
591 closedir($dh);
592}
593pb_log(1,"INFO: kernel is ".Dumper($kernel)."\n");
594pb_log(1,"End of kernel analysis\n");
595}
596
597sub pb_mkbm_create_media {
598
599}
600
601# Get the package list to download, store them in a cache directory
602#
603#my ($mkbmcachedir) = pb_conf_get_if("mkbmcachedir");
604#my ($pkgs) = pb_distro_get_param($pbos,pb_conf_get("mkbmmindep"));
605
606#
607# /proc needed
608#
609#pb_system("mount -o bind /proc $targetdir/proc","Mounting /proc");
610
611# Installed additional packages we were asked to
612#if (defined $opts{'a'}) {
613#$opts{'a'} =~ s/,/ /g;
614#pb_system("chroot $targetdir /bin/bash -c \"$pbos->{'install'} $opts{'a'} \"","Adding packages to OS by running $pbos->{'install'} $opts{'a'}");
615#}
616
617#
618# Clean up
619#
620#pb_log(1,"Cleaning up\n");
621#pb_system("umount $targetdir/proc","Unmounting /proc");
622
623# Executes post-install step if asked for
624#if ($opts{'s'}) {
625#pb_system("$opts{'s'} $targetdir","Executing the post-install script: $opts{'s'} $targetdir");
626#}
Note: See TracBrowser for help on using the repository browser.