X-Git-Url: http://rtime.felk.cvut.cz/gitweb/novaboot.git/blobdiff_plain/2aa4772d61ed61dedaa07430cde886991395114f..0681eae62148fbaa18aff8e0bcf32e8099334e61:/novaboot diff --git a/novaboot b/novaboot index 11930c9..7fa3816 100755 --- a/novaboot +++ b/novaboot @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/env perl # 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 @@ -22,6 +22,7 @@ use Getopt::Long qw(GetOptionsFromString GetOptionsFromArray); use Pod::Usage; use File::Basename; use File::Spec; +use File::Path qw(make_path); use IO::Handle; use Time::HiRes("usleep"); use Socket; @@ -52,11 +53,12 @@ $CFG::hypervisor_params = "serial"; $CFG::genisoimage = "genisoimage"; $CFG::qemu = 'qemu-system-i386 -cpu coreduo -smp 2'; $CFG::default_target = ''; +$CFG::netif = 'eth0'; %CFG::targets = ( 'qemu' => '--qemu', - "tud" => '--server=erwin.inf.tu-dresden.de:~sojka/boot/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --grub --grub-prefix=(nd)/tftpboot/sojka/novaboot --grub-preamble="timeout 0" --concat --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g', + "tud" => '--copy=erwin.inf.tu-dresden.de:~sojka/boot/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --grub --grub-prefix=(nd)/tftpboot/sojka/novaboot --grub-preamble="timeout 0" --concat --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g', "novabox" => '--ssh=novabox@rtime.felk.cvut.cz', - "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"', + "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --copy=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog=" set root=\'(hd0,msdos1)\'"', "ryu" => '--uboot --uboot-init="mw f0000b00 \${psc_cfg}; sleep 1" --uboot-addr kernel=800000 --uboot-addr ramdisk=b00000 --uboot-addr fdt=7f0000', "ryuglab" => '--target ryu --ssh=ryu@pc-sojkam.felk.cvut.cz', "ryulocal" => '--target ryu --dhcp-tftp --serial --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"', @@ -141,11 +143,11 @@ my ($target_reset, $target_power_on, $target_power_off); ); $rsync_flags = ''; $rom_prefix = 'rom://'; -$stty = 'raw -crtscts -onlcr 115200'; +$stty = 'raw -crtscts -onlcr -echo 115200'; $reset = 1; # Reset target by default $interaction = 1; # Perform target interaction by default $final_eol = 1; -$netif = 'eth0'; +$netif = $CFG::netif; $remote_expect_timeout = -1; my @expect_seen = (); @@ -176,6 +178,9 @@ my %opt_spec_safe = ( "prefix|grub-prefix=s" => \$grub_prefix, "pulsar-root=s" => \$pulsar_root, "pulsar|p:s" => \$pulsar, + "remote-expect=s"=> \$remote_expect, + "remote-expect-silent=s"=> sub { $remote_expect=$_[1]; $remote_expect_silent=1; }, + "remote-expect-timeout=i"=> \$remote_expect_timeout, "uboot-addr=s" => \%uboot_addr, "uboot-cmd=s" => \$uboot_cmd, "uboot-stop-key=s" => \$uboot_stop_key, @@ -219,9 +224,6 @@ my %opt_spec = ( "qemu-append=s" => \$qemu_append, "qemu-flags|q=s" => \$qemu_flags_cmd, "remote-cmd=s" => sub { @remote_cmd = ($_[1]); }, - "remote-expect=s"=> \$remote_expect, - "remote-expect-silent=s"=> sub { $remote_expect=$_[1]; $remote_expect_silent=1; }, - "remote-expect-timeout=i"=> \$remote_expect_timeout, "reset!" => \$reset, "reset-cmd=s" => sub { @reset_cmd = ($_[1]); }, "reset-send=s" => \$reset_send, @@ -232,6 +234,7 @@ my %opt_spec = ( "sendcont=s" => \&handle_send, "serial|s:s" => \$serial, "server:s" => \$server, + "copy:s" => \$server, "ssh:s" => \&handle_novaboot_server, "strip-rom" => sub { $rom_prefix = ''; }, "stty=s" => \$stty, @@ -345,7 +348,7 @@ if (defined $serial) { $ENV{NB_SERIAL} = $serial; } if (defined $grub_config) { $grub_config ||= "menu.lst"; } -if (defined $grub2_config) { $grub2_config ||= "grub.cfg"; } +if (defined $grub2_config) { $grub2_config ||= "./boot/grub/grub.cfg"; } ## Parse the novaboot script(s) my @scripts; @@ -411,6 +414,7 @@ while (!$skip_reading && ($_ = <>)) { push @$generated, {filename => $1, command => $3}; return "$1$2"; } + s/\s*$//; # Strip trailing whitespace return $_; } if (s/^load *//) { # Load line @@ -541,7 +545,7 @@ sub generate_syslinux_config($$$$) if (system("file $kbin|grep 'Linux kernel'") == 0) { my $initrd = @$modules_ref[1]; - die('To many "load" lines for Linux kernel') if (scalar @$modules_ref > 2); + die('Too many "load" lines for Linux kernel') if (scalar @$modules_ref > 2); print $fg "LINUX $base$kbin\n"; print $fg "APPEND $kcmd\n"; print $fg "INITRD $base$initrd\n"; @@ -566,24 +570,36 @@ sub generate_grub2_config($$$$;$$) { my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_; if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; }; + my $dir = dirname($filename); + make_path($dir, { + chmod => 0755, + }); open(my $fg, '>', $filename) or die "$filename: $!"; print $fg "$preamble\n" if $preamble; $title ||= 'novaboot'; print $fg "menuentry $title {\n"; print $fg "$prolog\n" if $prolog; my $first = 1; + my $boot_method = $variables->{BOOT_METHOD} // "multiboot"; + my $module_load_method = "module"; + if ($boot_method eq "linux") { + $module_load_method = "initrd"; + die('Too many "load" lines for Linux kernel') if (scalar(@$modules_ref) > 2); + } foreach (@$modules_ref) { if ($first) { $first = 0; my ($kbin, $kcmd) = split(' ', $_, 2); $kcmd = '' if !defined $kcmd; - print $fg " multiboot ${base}$kbin $kcmd\n"; + print $fg " $boot_method ${base}$kbin $kcmd\n"; } else { my @args = split; - # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here - $_ = join(' ', ($args[0], @args)); - s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2 - print $fg " module $base$_\n"; + if ($boot_method eq "multiboot") { + # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here + $_ = join(' ', ($args[0], @args)); + s|\brom://|$rom_prefix|g; # We do not need to translate path for GRUB2 + } + print $fg " $module_load_method $base$_\n"; } } print $fg "}\n"; @@ -995,11 +1011,11 @@ foreach my $script (@scripts) { $hostname = ""; } my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs, @$copy))); - map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules); + map({ my $file = (split)[0]; die "Not a file: $file: $!" if ! -e $file || -d $file; } @$modules); my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb'; my $progress = $istty ? "--progress" : ""; if ($files) { - system_verbose("rsync $progress -RLp $rsync_flags $files $real_server"); + system_verbose("rsync $progress -RL --chmod=ugo=rwX $rsync_flags $files $real_server"); if ($server =~ m|/\$NAME$| && $concat) { my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs); system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd); @@ -1025,11 +1041,17 @@ if (defined $iso_image) { if (-f '/usr/lib/ISOLINUX/isolinux.bin') { # Newer ISOLINUX version @files = qw(/usr/lib/ISOLINUX/isolinux.bin /usr/lib/syslinux/modules/bios/mboot.c32 /usr/lib/syslinux/modules/bios/libcom32.c32 /usr/lib/syslinux/modules/bios/menu.c32 /usr/lib/syslinux/modules/bios/ldlinux.c32); - } else { + } elsif (-f '/usr/lib/syslinux/isolinux.bin') { # Older ISOLINUX version @files = qw(/usr/lib/syslinux/isolinux.bin /usr/lib/syslinux/mboot.c32 /usr/lib/syslinux/menu.c32); + } else { + # NixOS and maybe others + my $syslinux = `which syslinux` || die "Cannot find syslinux"; + chomp $syslinux; + $syslinux =~ s,/bin/syslinux$,,; + @files = ("$syslinux/share/syslinux/isolinux.bin", "$syslinux/share/syslinux/mboot.c32", "$syslinux/share/syslinux/libcom32.c32", "$syslinux/share/syslinux/menu.c32", "$syslinux/share/syslinux/ldlinux.c32"); } - system_verbose("cp @files isolinux"); + system_verbose("cp @files isolinux && chmod +w isolinux/*"); open(my $fh, ">isolinux/isolinux.cfg"); if ($#scripts) { print $fh "TIMEOUT 50\n"; @@ -1119,10 +1141,19 @@ if (defined $dhcp_tftp) open(my $fh, '>', 'dhcpd.conf'); my $mac = `cat /sys/class/net/$netif/address`; chomp $mac; - print $fh "subnet 10.23.23.0 netmask 255.255.255.0 { - range 10.23.23.10 10.23.23.100; - filename \"bin/boot/grub/pxegrub.pxe\"; - next-server 10.23.23.1; + print $fh " +subnet 10.23.23.0 netmask 255.255.255.0 { + range 10.23.23.10 10.23.23.100; + next-server 10.23.23.1; +} +class \"pxe-clients\" { + match option vendor-class-identifier; +} +subclass \"pxe-clients\" \"PXEClient:Arch:00000:UNDI:002001\" { + option bootfile-name \"boot/grub/i386-pc/core.0\"; +} +subclass \"pxe-clients\" \"PXEClient:Arch:00007:UNDI:003016\" { + option bootfile-name \"boot/grub/x86_64-efi/core.efi\"; } host server { hardware ethernet $mac; @@ -1142,16 +1173,28 @@ host server { if (defined $dhcp_tftp || defined $tftp) { $tftp_port ||= 69; + my $tftp_root = "$builddir"; + $tftp_root = "$server" if(defined $server); + + # Prepare a GRUB netboot directory + system_verbose("grub-mknetdir --net-directory=$tftp_root") if (defined $grub2_config); + + # Generate TFTP mapfile + open(my $fh, '>', "$tftp_root/mapfile"); + print $fh "# Some PXE clients (mainly UEFI) have bug. They add zero byte to the end of the +# path name. This rule removes it +r \\.efi.* \\.efi"; + close($fh); # Unfortunately, tftpd requires root privileges even with # non-privileged (>1023) port due to initgroups(). - system_verbose("sudo in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid --address :$tftp_port $builddir"); + system_verbose("sudo in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid -m mapfile --address :$tftp_port $tftp_root"); # Kill server when we die $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp); - system_verbose('sudo pkill --pidfile=tftpd.pid'); }; + system_verbose("sudo pkill --pidfile=$tftp_root/tftpd.pid"); }; # We have to kill tftpd explicitely, because it is not in our process group - $SIG{INT} = sub { system_verbose('sudo pkill --pidfile=tftpd.pid'); exit(0); }; + $SIG{INT} = sub { system_verbose("sudo pkill --pidfile=$tftp_root/tftpd.pid"); exit(0); }; } ### AMT IDE-R @@ -1171,7 +1214,7 @@ if (defined $ider) { ### Reset target (IP relay, AMT, ...) if (defined $target_reset && $reset) { - print STDERR "novaboot: Reseting the test box... "; + print STDERR "novaboot: Resetting the test box... "; &$target_reset(); print STDERR "done\n"; if (defined $exp) { @@ -1246,7 +1289,7 @@ if (defined $uboot) { @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules; my $initrd = shift @$modules; - if (defined $kbin) { + if (defined $kbin && $kbin ne '/dev/null') { die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel}; $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n"); $exp->expect(15, @@ -1298,7 +1341,7 @@ if ($interaction && defined $exp) { print STDERR "novaboot: Serial line interaction (press $interrupt to interrupt)...\n"; $exp->log_stdout(1); if (@exiton) { - $exp->expect($exiton_timeout, @expect_raw, @exiton) || die("exiton: " . ($! || "timeout")); + $exp->expect($exiton_timeout, @exiton, @expect_raw) || die("exiton: " . ($! || "timeout")); } else { my @inputs = ($exp); my $infile = new IO::File; @@ -1379,6 +1422,40 @@ having per-system, per-user or per-project configurations. Configuration file syntax is described in section L. +Novaboot newcomers may be confused by a large number of configuration +options. Understanding all these options is not always needed, +depending on the used setup. The L
+shows different setups that vary in how much effort is needed +to configure novaboot for them. The setups are: + +=over 3 + +=item A: Laptop and target device only + +This requires to configure everything on the laptop side, including a +serial line connection (L, L, ...), power +on/off/reset commands (L, ...), TFTP server +(L, L...), device IP addresses, etc. + +=item B: Laptop, target device and external TFTP server + +Like the previous setup, but the TFTP (and maybe DHCP) configuration +is handled by a server. Novaboot users need to understand where to +copy their files to the TFTP server (L) and which IP +addresses their target will get, but do not need to configure the +servers themselves. + +=item C: Novaboot server running novaboot-shell + +With this setup, the configuration is done on the server. Users only +need to know the SSH account (L) used to communicate between +novaboot and novaboot server. The server is implemented as a +restricted shell (L) on the server. No need to give +full shell access to novaboot users on the server. + +=back + =head2 Simple examples of using C: To boot Linux (files F and F in current @@ -1409,7 +1486,7 @@ with all other files needed for booting to a remote TFTP server. Then use a TCP/IP-controlled relay/serial-to-TCP converter to reset the target and receive its serial output. - ./mylinux --grub2 --server=192.168.1.1:/tftp --iprelay=192.168.1.2 + ./mylinux --grub2 --copy=192.168.1.1:/tftp --iprelay=192.168.1.2 Alternatively, you can put these switches to the configuration file and run: @@ -1452,7 +1529,7 @@ I configurations. =back -=head1 PHASES AND OPTIONS +=head1 OPTIONS AND PHASES Novaboot performs its work in several phases. Command line options described bellow influence the execution of each phase or allow their @@ -1561,7 +1638,7 @@ Configures novaboot to control the target via C running remotely via SSH. Using this option is the same as specifying B<--remote-cmd>, -B<--remote-expect>, B<--server> B<--rsync-flags>, B<--prefix> and +B<--remote-expect>, B<--copy> B<--rsync-flags>, B<--prefix> and B<--reset-cmd> manually in a way compatible with C. The server can be configured to provide other, safe bootloader-related options, to the client. When this happens, novaboot prints them to @@ -1676,10 +1753,14 @@ Alias for B<--prefix>. =item --grub2[=I] Generate GRUB2 menu entry in I. If I is not -specified F is used. The content of the menu entry can be +specified F<./boot/grub/grub.cfg> is used. The content of the menu entry can be customized with B<--grub-preamble>, B<--grub2-prolog> or B<--grub_prefix> options. +GRUB2 can boot multiboot-compliant kernels and a few kernels with specific +support. L could be used to specify the command used by GRUB2 to +load the kernel. See L. + To use the generated menu entry on your development machine that uses GRUB2, append the following snippet to F file and regenerate your grub configuration, @@ -1782,10 +1863,23 @@ example C. =item --remote-expect=I -Wait for reception of I after establishing the remote -connection. This option is needed when novaboot should wait for -confirmation before deploying files to the target, e.g. to not -overwrite other user's files when they are using the target. +Wait for reception of I after establishing the remote serial +line connection. Novaboot assumes that after establishing the serial +line connection, the user running novaboot has exclusive access to the +target. If establishing of the serial line connection happens +asynchronously (e.g. running a command remotely via SSH), we need this +option to wait until the exclusive access is confirmed by the remote +side. + +Depending on target configuration, this option can solve two practical +problems: 1) Overwriting of files deployed by another user currently +using the target. 2) Resetting the target board before serial line +connection is established and thus missing bootloader interaction. + +Example of usage with the L: + + --remote-cmd='ssh -tt example.com sterm -v /dev/ttyUSB0' --remote-expect='sterm: Connected' =item --remote-expect-silent=I @@ -1842,10 +1936,11 @@ Port to run the TFTP server on. Implies B<--tftp>. =item --netif=I -Network interface used to deploy files to the target. The default value is -I. This option influences the configuration of the DHCP server started -by B<--dhcp-tftp> and the value that B<$NB_MYIP> get replaced with during -U-Boot conversation. +Network interface used to deploy files to the target. This option +influences the configuration of the DHCP server started by +B<--dhcp-tftp> and the value that B<$NB_MYIP> get replaced with during +U-Boot conversation. The default value is C<$netif> variable from +configuration files, which defaults to I. =item --iso[=filename] @@ -1855,6 +1950,10 @@ of the novaboot script (see also B<--name>). =item --server[=[[user@]server:]path] +Alias of B<--copy> (kept for backward compatibility). + +=item --copy[=[[user@]server:]path] + Copy all files needed for booting to another location. The files will be copied (by B tool) to the directory I. If the I contains string $NAME, it will be replaced with the name of the @@ -1863,13 +1962,13 @@ novaboot script (see also B<--name>). =item --rsync-flags=I Specifies I to append to F command line when -copying files as a result of I<--server> option. +copying files as a result of I<--copy> option. =item --concat -If B<--server> is used and its value ends with $NAME, then after +If B<--copy> is used and its value ends with $NAME, then after copying the files, a new bootloader configuration file (e.g. menu.lst) -is created at I, i.e. the path specified by B<--server> +is created at I, i.e. the path specified by B<--copy> with $NAME part removed. The content of the file is created by concatenating all files of the same name from all subdirectories of I found on the "server". @@ -2095,9 +2194,16 @@ word after C is a file name (relative to the build directory (see B<--build-dir>) of the module to load and the remaining words are passed to it as the command line parameters. +When booting Linux, the first C line usually refers to the +kernel image and its command line parameters (unless you use some +special pre-loader). Other C lines may refer to an initramfs +image and/or a device tree blob. Their order is not important, as the +device tree is recognized as the file name ending with C<.dtb>. + When the C line ends with "< line ends with "< CMD" then command CMD is executed with F and its standard output is stored in the file named on @@ -2211,6 +2317,11 @@ The following variables are interpreted in the novaboot script: =over 8 +=item BOOT_METHOD + +Specifies the way GRUB2 boots the kernel. For kernels with multiboot +support use C method (the default). For Linux kernel use C method. + =item BUILDDIR Novaboot chdir()s to this directory before file generation phase. The @@ -2305,17 +2416,22 @@ Default target (see below) to use when no target is explicitly specified with the B<--target> command line option or B environment variable. +=item $netif + +Default value for the B<--netif> option. If not specified, it defaults +to I. + =item %targets Hash of target definitions to be used with the B<--target> option. The key is the identifier of the target, the value is the string with command line options. For instance, if the configuration file contains: - $targets{'mybox'} = '--server=boot:/tftproot --serial=/dev/ttyUSB0 --grub', + $targets{'mybox'} = '--copy=boot:/tftproot --serial=/dev/ttyUSB0 --grub', then the following two commands are equivalent: - ./myos --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub + ./myos --copy=boot:/tftproot --serial=/dev/ttyUSB0 --grub ./myos -t mybox =back