]> rtime.felk.cvut.cz Git - novaboot.git/blobdiff - novaboot
Add --tftp-port option
[novaboot.git] / novaboot
index 58ff4d322c8fcdf3e429925508ca10ca8de4e87c..6f212fb6cd2fef3d2d044260c37c499b4673992a 100755 (executable)
--- a/novaboot
+++ b/novaboot
@@ -40,7 +40,7 @@ my $invocation_dir = $ENV{PWD} || getcwd();
 $CFG::hypervisor = "";
 $CFG::hypervisor_params = "serial";
 $CFG::genisoimage = "genisoimage";
-$CFG::qemu = 'qemu -cpu coreduo -smp 2';
+$CFG::qemu = 'qemu-system-i386 -cpu coreduo -smp 2';
 $CFG::default_target = 'qemu';
 %CFG::targets = (
     'qemu' => '--qemu',
@@ -99,7 +99,7 @@ read_config($_) foreach $cfg or reverse @cfgs;
 my $explicit_target;
 GetOptions ("target|t=s" => \$explicit_target);
 
-my ($amt, @append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, @expect_raw, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $iprelay, $iso_image, $interactive, $kernel_opt, $make, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $remote_cmd, $remote_expect, $reset_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $uboot, $uboot_init);
+my ($amt, @append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, @expect_raw, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $iprelay, $iso_image, $interactive, $kernel_opt, $make, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $remote_cmd, $remote_expect, $reset_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $tftp, $tftp_port, $uboot, @uboot_init);
 
 $rsync_flags = '';
 $rom_prefix = 'rom://';
@@ -171,8 +171,10 @@ my %opt_spec;
     "server:s"              => \$server,
     "strip-rom"             => sub { $rom_prefix = ''; },
     "stty=s"        => \$stty,
-    "uboot"         => \$uboot,
-    "uboot-init=s"   => \$uboot_init,
+    "tftp"          => \$tftp,
+    "tftp-port=i"    => \$tftp_port,
+    "uboot:s"       => \$uboot,
+    "uboot-init=s"   => \@uboot_init,
     "h"             => \$help,
     "help"          => \$man,
     );
@@ -288,6 +290,10 @@ while (!$skip_reading && ($_ = <>)) {
        push @$modules, $_;
        next;
     }
+    if (/^run (.*)/) {         # run line
+       push @$generated, {command => $1};
+       next;
+    }
 
     die("novaboot: Cannot parse script '$last_fn' line $.. Didn't you forget 'load' keyword?\n");
 }
@@ -339,7 +345,11 @@ sub generate_configs($$$) {
        print "novaboot: Created $fn\n";
       } elsif (exists $$g{command} && ! $no_file_gen) {
        $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
-       system_verbose("( $$g{command} ) > $$g{filename}");
+       if (exists $$g{filename}) {
+           system_verbose("( $$g{command} ) > $$g{filename}");
+       } else {
+           system_verbose($$g{command});
+       }
       }
     }
 }
@@ -369,6 +379,39 @@ sub generate_grub_config($$$$;$)
     return $filename;
 }
 
+sub generate_syslinux_config($$$$)
+{
+    my ($filename, $title, $base, $modules_ref) = @_;
+    if ($base && $base !~ /\/$/) { $base = "$base/"; };
+    open(my $fg, '>', $filename) or die "$filename: $!";
+    print $fg "LABEL $title\n";
+    #TODO print $fg "MENU LABEL $human_readable_title\n";
+
+    my ($kbin, $kcmd) = split(' ', @$modules_ref[0], 2);
+
+    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);
+       print $fg "LINUX $base$kbin\n";
+       print $fg "APPEND $kcmd\n";
+       print $fg "INITRD $base$initrd\n";
+    } else {
+       print $fg "KERNEL mboot.c32\n";
+       my @append;
+       foreach (@$modules_ref) {
+           s|\brom://([^ ]*)|$rom_prefix$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0
+           push @append, "$base$_";
+           print $fg "APPEND ".join(' --- ', @append)."\n";
+       }
+    }
+    #TODO print $fg "TEXT HELP\n";
+    #TODO print $fg "some help here\n";
+    #TODO print $fg "ENDTEXT\n";
+    close($fg);
+    print("novaboot: Created $builddir/$filename\n");
+    return $filename;
+}
+
 sub generate_grub2_config($$$$;$$)
 {
     my ($filename, $title, $base, $modules_ref, $preamble, $prolog) = @_;
@@ -433,6 +476,7 @@ sub exec_verbose(@)
 {
     print "novaboot: Running: ".shell_cmd_string(@_)."\n";
     exec(@_);
+    exit(1); # should not be reached
 }
 
 sub system_verbose($)
@@ -548,8 +592,8 @@ elsif ($remote_cmd) {
     $exp = Expect->spawn($remote_cmd);
 }
 elsif (defined $amt) {
-    use LWP::UserAgent;
-    use LWP::Authen::Digest;
+    require LWP::UserAgent;
+    require LWP::Authen::Digest;
 
     sub genXML {
         my ($host, $username, $password, $schema, $className, $pstate) = @_;
@@ -707,7 +751,7 @@ foreach my $script (@scripts) {
     {
        my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
        # Filter-out generated files
-       my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files);
+       my @to_build = grep({ my $file = $_; !scalar(grep($file eq ($$_{filename} || ''), @$generated)) } @files);
 
        system_verbose($scons || $CFG::scons." ".join(" ", @to_build)) if (defined $scons);
        system_verbose($make  || $CFG::make ." ".join(" ", @to_build)) if (defined $make);
@@ -737,7 +781,7 @@ foreach my $script (@scripts) {
     if (defined $iso_image) {
        generate_configs("(cd)", $generated, $filename);
        my $menu;
-       generate_grub_config(\$menu, $config_name, "(cd)", $modules);
+       generate_syslinux_config(\$menu, $config_name, "/", $modules);
        $menu_iso .= "$menu\n";
        map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules;
     }
@@ -745,12 +789,26 @@ foreach my $script (@scripts) {
 
 ## Generate ISO image
 if (defined $iso_image) {
-    open(my $fh, ">menu-iso.lst");
-    print $fh "timeout 5\n\n$menu_iso";
+    system_verbose("mkdir -p isolinux");
+    system_verbose('cp /usr/lib/syslinux/isolinux.bin /usr/lib/syslinux/mboot.c32 /usr/lib/syslinux/menu.c32 isolinux');
+    open(my $fh, ">isolinux/isolinux.cfg");
+    if ($#scripts) {
+       print $fh "TIMEOUT 50\n";
+       print $fh "DEFAULT menu\n";
+    } else {
+       print $fh "DEFAULT $config_name\n";
+    }
+    print $fh "$menu_iso";
     close($fh);
-    my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso)));
+
+    my $files = join(" ", map("$_=$_", (keys(%files_iso), 'isolinux/isolinux.bin', 'isolinux/isolinux.cfg', 'isolinux/mboot.c32', 'isolinux/menu.c32')));
     $iso_image ||= "$config_name.iso";
-    system_verbose("$CFG::genisoimage -R -b stage2_eltorito -no-emul-boot -boot-load-size 4 -boot-info-table -hide-rr-moved -J -joliet-long -o $iso_image -graft-points bin/boot/grub/ $files");
+
+    # Note: We use -U flag below to "Allow 'untranslated' filenames,
+    # completely violating the ISO9660 standards". Without this
+    # option, isolinux is not able to read files names for example
+    # bzImage-3.0.
+    system_verbose("$CFG::genisoimage -R -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -hide-rr-moved -U -o $iso_image -graft-points $files");
     print("ISO image created: $builddir/$iso_image\n");
 }
 
@@ -786,7 +844,7 @@ if (defined $qemu) {
 
     if (defined $iso_image) {
        # Boot NOVA with grub (and test the iso image)
-       push(@qemu_flags, ('-cdrom', "$config_name.iso"));
+       push(@qemu_flags, ('-cdrom', $iso_image));
     } else {
        # Boot NOVA without GRUB
 
@@ -816,6 +874,8 @@ if (defined $qemu) {
 
 my ($dhcpd_pid, $tftpd_pid);
 
+$tftp=1 if $tftp_port;
+
 if (defined $dhcp_tftp)
 {
     generate_configs("(nd)", $generated, $filename);
@@ -843,12 +903,20 @@ host server {
     # process group (e.g. Ctrl-C on terminal).
     $dhcpd_pid = fork();
     exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid") if ($dhcpd_pid == 0);
-    $tftpd_pid = fork();
-    exec_verbose("sudo in.tftpd --foreground --secure -v -v -v --pidfile tftpd.pid $builddir") if ($tftpd_pid == 0);
+}
+
+if (defined $dhcp_tftp || defined $tftp) {
+    $tftp_port ||= 69;
+    # 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");
 
     # Kill server when we die
-    $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid');
+    $SIG{__DIE__} = sub { system_verbose('sudo pkill --pidfile=dhcpd.pid') if (defined $dhcp_tftp);
                          system_verbose('sudo pkill --pidfile=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); };
 }
 
 ### Reset target (IP relay, AMT, ...)
@@ -861,46 +929,53 @@ if (defined $target_reset) {
 
 ### U-boot conversation
 if (defined $uboot) {
+    my $uboot_prompt = $uboot || '=> ';
     print "novaboot: Waiting for uBoot prompt...\n";
     $exp->log_stdout(1);
     #$exp->exp_internal(1);
     $exp->expect(20,
                 [qr/Hit any key to stop autoboot:/, sub { $exp->send("\n"); exp_continue; }],
-                '=> ') || die "No uBoot prompt deteceted";
-    $exp->send("$uboot_init\n") if $uboot_init;
-    $exp->expect(10, '=> ') || die "uBoot prompt timeout";
-
-    my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
-    my $dtb;
-    @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
-    my $initrd = shift @$modules;
-
-    my $kern_addr = '800000';
-    my $initrd_addr = '-';
-    my $dtb_addr = '';
-
-    $exp->send("tftp $kern_addr $kbin\n");
-    $exp->expect(10,
-                [qr/#/, sub { exp_continue; }],
-                '=> ') || die "Kernel load failed";
-    if (defined $dtb) {
-       $dtb_addr = '7f0000';
-       $exp->send("tftp $dtb_addr $dtb\n");
-       $exp->expect(10,
-                    [qr/#/, sub { exp_continue; }],
-                    '=> ') || die "Device tree load failed";
+                $uboot_prompt) || die "No uBoot prompt deteceted";
+    while (@uboot_init) {
+       my $cmd = shift @uboot_init;
+       $exp->send("$cmd\n");
+       $exp->expect(10, $uboot_prompt) || die "uBoot prompt timeout";
     }
-    if (defined $initrd) {
-       $initrd_addr = 'b00000';
-       $exp->send("tftp $initrd_addr $initrd\n");
+
+    # Boot the system if there are some load lines in the script
+    if (scalar(@$modules) > 0) {
+       my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
+       my $dtb;
+       @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
+       my $initrd = shift @$modules;
+
+       my $kern_addr = '800000';
+       my $initrd_addr = '-';
+       my $dtb_addr = '';
+
+       $exp->send("tftp $kern_addr $kbin\n");
        $exp->expect(10,
                     [qr/#/, sub { exp_continue; }],
-                    '=> ') || die "Initrd load failed";
+                    $uboot_prompt) || die "Kernel load failed";
+       if (defined $dtb) {
+           $dtb_addr = '7f0000';
+           $exp->send("tftp $dtb_addr $dtb\n");
+           $exp->expect(10,
+                        [qr/#/, sub { exp_continue; }],
+                        $uboot_prompt) || die "Device tree load failed";
+       }
+       if (defined $initrd) {
+           $initrd_addr = 'b00000';
+           $exp->send("tftp $initrd_addr $initrd\n");
+           $exp->expect(10,
+                        [qr/#/, sub { exp_continue; }],
+                        $uboot_prompt) || die "Initrd load failed";
+       }
+       $exp->send("set bootargs '$kcmd'\n");
+       $exp->expect(5, $uboot_prompt)  || die "uBoot prompt timeout";
+       $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
+       $exp->expect(5, "\n")  || die "uBoot command timeout";
     }
-    $exp->send("set bootargs '$kcmd'\n");
-    $exp->expect(5, '=> ')  || die "uBoot prompt timeout";
-    $exp->send("bootm $kern_addr $initrd_addr $dtb_addr\n");
-    $exp->expect(5, "\n")  || die "uBoot command timeout";
 }
 
 ### Serial line interaction
@@ -910,7 +985,8 @@ if (defined $exp) {
     if ($interactive && !@exiton) {
        $interrupt = '"~~."';
     }
-    print "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
+    my $note = (-t STDIN) ? '' : '- only target->host ';
+    print "novaboot: Serial line interaction $note(press $interrupt to interrupt)...\n";
     $exp->log_stdout(1);
     if (@exiton) {
        $exp->expect(undef, @expect_raw, @exiton);
@@ -938,7 +1014,7 @@ if (defined $exp) {
 }
 
 ## Kill dhcpc or tftpd
-if (defined $dhcp_tftp) {
+if (defined $dhcp_tftp || defined $tftp) {
     die("novaboot: This should kill servers on background\n");
 }
 
@@ -1202,8 +1278,9 @@ server directory where the boot files are copied to.
 
 =item --no-file-gen
 
-Do not generate files on the fly (i.e. "<" syntax) except for the
-files generated via "<<WORD" syntax.
+Do not run external commands to generate files (i.e. "<" syntax and
+C<run> keyword). This switch does not influence generation of files
+specified with "<<WORD" syntax.
 
 =item -p, --pulsar[=mac]
 
@@ -1293,14 +1370,30 @@ Turns your workstation into a DHCP and TFTP server so that the OS can
 be booted via PXE BIOS (or similar mechanism) on the test machine
 directly connected by a plain Ethernet cable to your workstation.
 
-The DHCP and TFTP servers require root privileges and C<novaboot>
+The DHCP and TFTP servers requires root privileges and C<novaboot>
 uses C<sudo> command to obtain those. You can put the following to
-I</etc/sudoers> to allow running the necessary commands without
-asking for password.
+I</etc/sudoers> to allow running the necessary commands without asking
+for password.
+
+ Cmnd_Alias NOVABOOT = /bin/ip a add 10.23.23.1/24 dev eth0, /bin/ip l set dev eth0 up, /usr/sbin/dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid, /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/touch dhcpd.leases, /usr/bin/pkill --pidfile=dhcpd.pid, /usr/bin/pkill --pidfile=tftpd.pid
+ your_login ALL=NOPASSWD: NOVABOOT
+
+=item --tftp
 
- Cmnd_Alias NOVABOOT = /bin/ip a add 10.23.23.1/24 dev eth0, /bin/ip l set dev eth0 up, /usr/sbin/dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid, /usr/sbin/in.tftpd --foreground --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/touch dhcpd.leases, /usr/bin/pkill --pidfile=dhcpd.pid, /usr/bin/pkill --pidfile=tftpd.pid
+Starts a TFTP server on your workstation. This is similar to
+B<--dhcp-tftp> except that DHCP server is not started.
+
+The TFTP server require root privileges and C<novaboot> uses C<sudo>
+command to obtain those. You can put the following to I</etc/sudoers>
+to allow running the necessary commands without asking for password.
+
+ Cmnd_Alias NOVABOOT =  /usr/sbin/in.tftpd --listen --secure -v -v -v --pidfile tftpd.pid *, /usr/bin/pkill --pidfile=tftpd.pid
  your_login ALL=NOPASSWD: NOVABOOT
 
+=item --tftp-port=I<port>
+
+Port to run the TFTP server on. Implies B<--tftp>.
+
 =item --iso[=filename]
 
 Generates the ISO image that boots NOVA system via GRUB. If no filename
@@ -1366,17 +1459,19 @@ Command that resets the target.
 
 =over 8
 
-=item --uboot
+=item --uboot[=I<prompt>]
 
 Interact with uBoot bootloader to boot the thing described in the
-novaboot script. Implementation of this option is currently tied to a
-particular board that we use. It may be subject to changes in the
-future!
+novaboot script. I<prompt> specifies the U-Boot's prompt (default is
+"=> ", other common prompts are "U-Boot> " or "U-Boot# ").
+Implementation of this option is currently tied to a particular board
+that we use. It may be subject to changes in the future!
 
 =item --uboot-init
 
 Command(s) to send the U-Boot bootloader before loading the images and
-booting them.
+booting them. This option can be given multiple times. After sending
+commands from each option novaboot waits for U-Boot I<prompt>.
 
 =back
 
@@ -1470,10 +1565,17 @@ that line. The SRCDIR variable in CMD's environment is set to the
 absolute path of the directory containing the interpreted novaboot
 script.
 
+Lines starting with C<run> keyword contain shell commands that are run
+during file generation phase. This is the same as the "< CMD" syntax
+for C<load> keyboard except that the command's output is not
+redirected to a file. The ordering of commands is the same as they
+appear in the novaboot script.
+
 Example (Linux):
 
   #!/usr/bin/env novaboot
   load bzImage console=ttyS0,115200
+  run  make -C buildroot
   load rootfs.cpio < gen_cpio buildroot/images/rootfs.cpio "myapp->/etc/init.d/S99myapp"
 
 Example (NOVA User Land - NUL):