]> rtime.felk.cvut.cz Git - novaboot.git/blobdiff - novaboot
nix: Update flake inputs
[novaboot.git] / novaboot
index 44d4a310daf7cb882f1eb06830ca511651ebf248..cd59748d35e6cb9633f345f6ce2d6d504ec4c149 100755 (executable)
--- a/novaboot
+++ b/novaboot
 
 use strict;
 use warnings;
 
 use strict;
 use warnings;
-use warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
+use warnings (exists $ENV{NOVABOOT_TEST} ?
+              (FATAL => 'all') :
+              (FATAL => qw(inplace))); # Open warnings in <<>> are fatal
 use Getopt::Long qw(GetOptionsFromString GetOptionsFromArray);
 use Pod::Usage;
 use File::Basename;
 use File::Spec;
 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;
 use IO::Handle;
 use Time::HiRes("usleep");
 use Socket;
@@ -55,9 +58,9 @@ $CFG::default_target = '';
 $CFG::netif = 'eth0';
 %CFG::targets = (
     'qemu' => '--qemu',
 $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',
     "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"',
     "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"',
@@ -233,6 +236,7 @@ my %opt_spec = (
     "sendcont=s"     => \&handle_send,
     "serial|s:s"     => \$serial,
     "server:s"              => \$server,
     "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,
     "ssh:s"         => \&handle_novaboot_server,
     "strip-rom"             => sub { $rom_prefix = ''; },
     "stty=s"        => \$stty,
@@ -346,7 +350,7 @@ if (defined $serial) {
     $ENV{NB_SERIAL} = $serial;
 }
 if (defined $grub_config) { $grub_config ||= "menu.lst"; }
     $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;
 
 ## Parse the novaboot script(s)
 my @scripts;
@@ -355,7 +359,7 @@ my $EOF;
 my $last_fn = '';
 my ($modules, $variables, $generated, $copy, $chainload, $continuation) = ([], {}, [], []);
 my $skip_reading = defined($on_opt) || defined($off_opt);
 my $last_fn = '';
 my ($modules, $variables, $generated, $copy, $chainload, $continuation) = ([], {}, [], []);
 my $skip_reading = defined($on_opt) || defined($off_opt);
-while (!$skip_reading && ($_ = <>)) {
+while (!$skip_reading && ($_ = <<>>)) {
     if ($ARGV ne $last_fn) { # New script
        die "Missing EOF in $last_fn" if $file;
        die "Unfinished line in $last_fn" if $continuation;
     if ($ARGV ne $last_fn) { # New script
        die "Missing EOF in $last_fn" if $file;
        die "Unfinished line in $last_fn" if $continuation;
@@ -412,6 +416,7 @@ while (!$skip_reading && ($_ = <>)) {
            push @$generated, {filename => $1, command => $3};
            return "$1$2";
        }
            push @$generated, {filename => $1, command => $3};
            return "$1$2";
        }
+       s/\s*$//;               # Strip trailing whitespace
        return $_;
     }
     if (s/^load *//) {         # Load line
        return $_;
     }
     if (s/^load *//) {         # Load line
@@ -542,7 +547,7 @@ sub generate_syslinux_config($$$$)
 
     if (system("file $kbin|grep 'Linux kernel'") == 0) {
        my $initrd = @$modules_ref[1];
 
     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";
        print $fg "LINUX $base$kbin\n";
        print $fg "APPEND $kcmd\n";
        print $fg "INITRD $base$initrd\n";
@@ -567,24 +572,36 @@ sub generate_grub2_config($$$$;$$)
 {
     my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
     if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; };
 {
     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;
     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;
     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;
        } 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";
        }
     }
     print $fg "}\n";
@@ -1000,7 +1017,7 @@ foreach my $script (@scripts) {
        my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
        my $progress = $istty ? "--progress" : "";
        if ($files) {
        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);
            if ($server =~ m|/\$NAME$| && $concat) {
                my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
                system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
@@ -1126,10 +1143,19 @@ if (defined $dhcp_tftp)
     open(my $fh, '>', 'dhcpd.conf');
     my $mac = `cat /sys/class/net/$netif/address`;
     chomp $mac;
     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;
 }
 host server {
        hardware ethernet $mac;
@@ -1149,16 +1175,28 @@ host server {
 
 if (defined $dhcp_tftp || defined $tftp) {
     $tftp_port ||= 69;
 
 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().
     # 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);
 
     # 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
 
     # 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
 }
 
 ### AMT IDE-R
@@ -1257,8 +1295,9 @@ if (defined $uboot) {
            die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
            $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
            $exp->expect(15,
            die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
            $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
            $exp->expect(15,
-                        [qr/##/, sub { exp_continue; }],
-                        $uboot_prompt) || die "Kernel load: " . ($! || "timeout");
+                         $uboot_prompt,
+                        [qr/#/, sub { exp_continue; }]
+                ) || die "Kernel load: " . ($! || "timeout");
        }
        if (defined $dtb) {
            die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
        }
        if (defined $dtb) {
            die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
@@ -1391,7 +1430,7 @@ options. Understanding all these options is not always needed,
 depending on the used setup. The L<figure from the doc directory
 |https://github.com/wentasah/novaboot/blob/master/doc/typical-setups.svg>
 shows different setups that vary in how much effort is needed
 depending on the used setup. The L<figure from the doc directory
 |https://github.com/wentasah/novaboot/blob/master/doc/typical-setups.svg>
 shows different setups that vary in how much effort is needed
-configure novaboot for them. The setups are:
+to configure novaboot for them. The setups are:
 
 =over 3
 
 
 =over 3
 
@@ -1400,13 +1439,13 @@ configure novaboot for them. The setups are:
 This requires to configure everything on the laptop side, including a
 serial line connection (L</--serial>, L</--remote-cmd>, ...), power
 on/off/reset commands (L</--reset-cmd>, ...), TFTP server
 This requires to configure everything on the laptop side, including a
 serial line connection (L</--serial>, L</--remote-cmd>, ...), power
 on/off/reset commands (L</--reset-cmd>, ...), TFTP server
-(L</--server>, L</--prefix>...), device IP addresses, etc.
+(L</--copy>, L</--prefix>...), 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
 
 =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</--server>) and which IP
+copy their files to the TFTP server (L</--copy>) and which IP
 addresses their target will get, but do not need to configure the
 servers themselves.
 
 addresses their target will get, but do not need to configure the
 servers themselves.
 
@@ -1450,7 +1489,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.
 
 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:
 
 Alternatively, you can put these switches to the configuration file
 and run:
@@ -1602,7 +1641,7 @@ Configures novaboot to control the target via C<novaboot-shell>
 running remotely via SSH.
 
 Using this option is the same as specifying B<--remote-cmd>,
 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<novaboot-shell>.
 The server can be configured to provide other, safe bootloader-related
 options, to the client. When this happens, novaboot prints them to
 B<--reset-cmd> manually in a way compatible with C<novaboot-shell>.
 The server can be configured to provide other, safe bootloader-related
 options, to the client. When this happens, novaboot prints them to
@@ -1717,10 +1756,14 @@ Alias for B<--prefix>.
 =item --grub2[=I<filename>]
 
 Generate GRUB2 menu entry in I<filename>. If I<filename> is not
 =item --grub2[=I<filename>]
 
 Generate GRUB2 menu entry in I<filename>. If I<filename> is not
-specified F<grub.cfg> 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.
 
 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</BOOT_METHOD> could be used to specify the command used by GRUB2 to
+load the kernel. See L<GNU GRUB Manual|https://www.gnu.org/software/grub/manual/grub/grub.html#Booting>.
+
 To use the generated menu entry on your development
 machine that uses GRUB2, append the following snippet to
 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
 To use the generated menu entry on your development
 machine that uses GRUB2, append the following snippet to
 F</etc/grub.d/40_custom> file and regenerate your grub configuration,
@@ -1910,6 +1953,10 @@ of the novaboot script (see also B<--name>).
 
 =item --server[=[[user@]server:]path]
 
 
 =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<rsync> tool) to the directory I<path>. If the I<path>
 contains string $NAME, it will be replaced with the name of the
 Copy all files needed for booting to another location. The files will
 be copied (by B<rsync> tool) to the directory I<path>. If the I<path>
 contains string $NAME, it will be replaced with the name of the
@@ -1918,13 +1965,13 @@ novaboot script (see also B<--name>).
 =item --rsync-flags=I<flags>
 
 Specifies I<flags> to append to F<rsync> command line when
 =item --rsync-flags=I<flags>
 
 Specifies I<flags> to append to F<rsync> command line when
-copying files as a result of I<--server> option.
+copying files as a result of I<--copy> option.
 
 =item --concat
 
 
 =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)
 copying the files, a new bootloader configuration file (e.g. menu.lst)
-is created at I<path-wo-name>, i.e. the path specified by B<--server>
+is created at I<path-wo-name>, 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<path-wo-name> found on the "server".
 with $NAME part removed. The content of the file is created by
 concatenating all files of the same name from all subdirectories of
 I<path-wo-name> found on the "server".
@@ -2273,6 +2320,11 @@ The following variables are interpreted in the novaboot script:
 
 =over 8
 
 
 =over 8
 
+=item BOOT_METHOD
+
+Specifies the way GRUB2 boots the kernel. For kernels with multiboot
+support use C<multiboot> method (the default). For Linux kernel use C<linux> method.
+
 =item BUILDDIR
 
 Novaboot chdir()s to this directory before file generation phase. The
 =item BUILDDIR
 
 Novaboot chdir()s to this directory before file generation phase. The
@@ -2378,11 +2430,11 @@ 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:
 
 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:
 
 
 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
  ./myos -t mybox
 
 =back