From 3bbcbfe8c8a210aab420a97e1b0d2c9d75b0fd39 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 1 Jan 1970 00:00:00 +0000 Subject: [PATCH] Daily update. --- .hgignore | 6 ++ MANIFEST | 1 + Makefile | 48 ++++++++---- README.developers | 12 +-- inittab | 4 +- livecd-post.sh.in | 17 ++-- livecd.ks.in | 10 ++- update-iso.ml | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ virt-p2v-unpack | 2 + virt-p2v.ml | 158 +++++++++++++++++++++++++++++++------ 10 files changed, 434 insertions(+), 55 deletions(-) create mode 100755 update-iso.ml diff --git a/.hgignore b/.hgignore index a8818ec..5d29e49 100644 --- a/.hgignore +++ b/.hgignore @@ -1,2 +1,8 @@ ~$ \.orig$ +\.iso$ +\.img$ +^virt-p2v-.*\.tar\.gz$ +^livecd\.ks$ +^livecd-test\.ks$ +^livecd-post\.sh$ diff --git a/MANIFEST b/MANIFEST index 7ad265e..208ad60 100644 --- a/MANIFEST +++ b/MANIFEST @@ -8,5 +8,6 @@ Makefile MANIFEST README README.developers +virt-p2v.ml virt-p2v.sh virt-p2v-unpack diff --git a/Makefile b/Makefile index c8eafaf..d250b86 100644 --- a/Makefile +++ b/Makefile @@ -23,52 +23,60 @@ # General configuration PACKAGE := virt-p2v -VERSION := 0.7 +VERSION := 0.8 # i386 images also work on x86-64, so best to stick with i386. ARCH := i386 +# Base repository. +BASE := 8 +BASEURL := http://download.fedora.redhat.com/pub/fedora/linux/releases/$(BASE)/Everything/$(ARCH)/os/ + LANG := en_US.UTF-8 KEYBOARD := us TIMEZONE := US/Eastern -BASEREPO := http://download.fedora.redhat.com/pub/fedora/linux/releases/8/Fedora/$(ARCH)/os/ - # Select a suitable HTTP proxy. # The default assumes a local squid proxy. -#export http_proxy := http://127.0.0.1:3128/ -#export ftp_proxy := http://127.0.0.1:3128/ +export http_proxy := http://127.0.0.1:3128/ +export ftp_proxy := http://127.0.0.1:3128/ LABEL := $(PACKAGE)-$(VERSION) +ISO := $(LABEL).iso + #---------------------------------------------------------------------- all: @echo "make build Build the live CD ISO" @echo "make boot [HDA=hda.img] [HDB=hdb.img] [ISO=livecd.iso]" - @echo " Boot built/named ISO (uses qemu)" + @echo " Boot built/named ISO (uses qemu)" + @echo "make update Update an existing live CD ISO with new" + @echo " virt-p2v script, without doing full rebuild" # Build live CD. -build: checkroot livecd.ks - rm -f $(LABEL).iso +build: checkroot checkscript livecd.ks + rm -f $(ISO) livecd-creator --config=livecd.ks --fslabel=$(LABEL) ls -lhtr *.iso livecd.ks: livecd.ks.in livecd-post.sh Makefile rm -f $@ sed \ - -e 's|@BASEREPO@|$(BASEREPO)|g' \ + -e 's|@ARCH@|$(ARCH)|g' \ + -e 's|@BASE@|$(BASE)|g' \ + -e 's|@BASEURL@|$(BASEURL)|g' \ -e 's|@LANG@|$(LANG)|g' \ -e 's|@KEYBOARD@|$(KEYBOARD)|g' \ -e 's|@TIMEZONE@|$(TIMEZONE)|g' \ < $< | cat - livecd-post.sh > $@ -livecd-post.sh: livecd-post.sh.in virt-p2v.sh inittab lvm.conf Makefile +livecd-post.sh: livecd-post.sh.in virt-p2v.ml inittab lvm.conf Makefile rm -f $@ sed \ - -e '/@VIRT-P2V.SH@/ r virt-p2v.sh' \ - -e '/@VIRT-P2V.SH@/ d' \ + -e '/@VIRT-P2V.ML@/ r virt-p2v.ml' \ + -e '/@VIRT-P2V.ML@/ d' \ -e '/@INITTAB@/ r inittab' \ -e '/@INITTAB@/ d' \ -e '/@LVM.CONF@/ r lvm.conf' \ @@ -78,7 +86,6 @@ livecd-post.sh: livecd-post.sh.in virt-p2v.sh inittab lvm.conf Makefile # Run live CD under qemu. QEMU := qemu -ISO := $(LABEL).iso HDA := HDB := @@ -93,7 +100,13 @@ endif boot: $(QEMU) $(QEMU_ARGS) -# Standard rules. +# Update an existing ISO. + +update: checkroot + -./update-iso.ml delete $(ISO) + ./update-iso.ml add $(ISO) virt-p2v.ml + +# Check that we are root. checkroot: @if [ `id -u` -ne 0 ]; then \ @@ -101,6 +114,13 @@ checkroot: exit 1; \ fi +# Check that the script compiles. + +checkscript: + ./virt-p2v.ml --test + +# Clean. + clean: rm -f *~ core livecd.ks livecd-post.sh diff --git a/README.developers b/README.developers index 784504a..b7082f3 100644 --- a/README.developers +++ b/README.developers @@ -28,10 +28,10 @@ p2v.init This is installed on the live CD as /etc/init.d/p2v, and it causes the live CD to boot into the P2V configuration tool (see next). -virt-p2v.sh +virt-p2v.ml - This is the virt-p2v.sh P2V configuration tool itself. It is - installed on the live CD as /usr/bin/virt-p2v.sh and runs after the + This is the virt-p2v.ml P2V configuration tool itself. It is + installed on the live CD as /usr/bin/virt-p2v.ml and runs after the live CD has booted. All the P2V stuff happens from this script. It uses the 'dialog' program to ask questions. @@ -63,12 +63,12 @@ for devices in /sys/block. For (b) we can simply use 'dd' and 'ssh'. The general plan is to do this: - dd if=/dev/disk | gzip | ssh xenhost 'zcat > /var/lib/xen/images/disk.img' + dd if=/dev/disk | ssh -C xenhost 'cat > /var/lib/xen/images/disk.img' If the user doesn't have sshd installed on the Xen host, then they can also opt for a pure TCP transport: - dd if=/dev/disk | gzip | nc xenhost port + dd if=/dev/disk | nc xenhost port and on the remote host they do: nc -kl port > /var/lib/xen/images/disks @@ -76,7 +76,7 @@ also opt for a pure TCP transport: For (c) we can use device-mapper snapshots to mount a ramdisk above the disks themselves. This allows us to make non-destructive changes to files, and still see the "modified" block device (d). A hairy -shell script looks for candidate files to modify. +script looks for candidate files to modify. Non-generic virt-p2v ---------------------------------------------------------------------- diff --git a/inittab b/inittab index 0757286..0b2f417 100644 --- a/inittab +++ b/inittab @@ -25,8 +25,8 @@ pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down" # If power was restored before the shutdown kicked in, cancel it. pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled" -# Run virt-p2v.sh on tty1 -1:3:once:/usr/bin/virt-p2v.sh tty1 +# Run virt-p2v.ml on tty1 +1:3:once:/usr/bin/virt-p2v.ml --update tty1 # Run gettys but not on tty1 which is where virt-p2v runs. 2:2345:respawn:/sbin/mingetty tty2 diff --git a/livecd-post.sh.in b/livecd-post.sh.in index 4e3eee9..f85dbd0 100644 --- a/livecd-post.sh.in +++ b/livecd-post.sh.in @@ -22,23 +22,24 @@ # # $Id$ -# This is the virt-p2v shell script. +# This is the virt-p2v script. -cat > /usr/bin/virt-p2v.sh << '__EOF1234__' -@VIRT-P2V.SH@ +cat > /usr/bin/virt-p2v.ml << '__EOF1234__' +@VIRT-P2V.ML@ __EOF1234__ -chmod 0755 /usr/bin/virt-p2v.sh -/sbin/restorecon /usr/bin/virt-p2v.sh +chmod 0755 /usr/bin/virt-p2v.ml +/sbin/restorecon /usr/bin/virt-p2v.ml # Install custom inittab. cat > /etc/inittab << '__EOF4123__' @INITTAB@ __EOF4123__ -# Install custom lvm.conf.new (the shell script will rename to -# lvm.conf when the time comes). -cat > /etc/lvm/lvm.conf.new << '__EOF4312__' +# Install custom lvm.conf. The script selects this by adjusting +# $LVM_SYSTEM_DIR environment variable at the right time. +cp -a /etc/lvm /etc/lvm.new +cat > /etc/lvm.new/lvm.conf << '__EOF4312__' @LVM.CONF@ __EOF4312__ diff --git a/livecd.ks.in b/livecd.ks.in index fa9d4ef..8677bee 100644 --- a/livecd.ks.in +++ b/livecd.ks.in @@ -26,7 +26,8 @@ auth --useshadow --enablemd5 selinux --enforcing firewall --disabled -repo --name=a-dev --baseurl=@BASEREPO@ +repo --name=released --baseurl=@BASEURL@ +#repo --name=released --mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-@BASE@&arch=@ARCH@ %packages bash @@ -59,6 +60,13 @@ sed gawk findutils +# For OCaml +ocaml +ocaml-runtime +ocaml-pcre +ocaml-extlib +ocaml-libvirt + # For network configuration dhclient diff --git a/update-iso.ml b/update-iso.ml new file mode 100755 index 0000000..c50c755 --- /dev/null +++ b/update-iso.ml @@ -0,0 +1,231 @@ +#!/usr/bin/ocamlrun /usr/bin/ocaml +#load "unix.cma";; + +(* update-iso.ml attaches an updated 'virt-p2v.ml' file to the end of + * an ISO image. This is just for quick developer builds because it + * takes ages to rebuild a full ISO. + * + * Copyright (C) 2007-2008 Red Hat Inc. + * Written by Richard W.M. Jones + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + *) + +open Printf +open Unix + +(* CD-ROM format has 2048 byte "sectors" and the final ISO image + * must be a multiple of 2048 bytes in size. + * + * The basic plan is that we will use store the file followed by a + * trailer, all rounded up to 2048 bytes: + * + * +------ - - - - - - - -----+------------+----------------+ + * | file | padding | trailer | + * | | | magic | .... | + * +------ - - - - - - - -----+------------+----------------+ + * |<---- total size is a multiple of 2048 bytes ---->| + * + * The magic string is used to identify that there is an + * attachment, and is followed by a few extra fields which + * identify the file start and true file size. (Note the + * original filename is not stored because it is not needed). + * + * Attachments can be stacked. This script only deals with the + * top-most attachment (ie. the one at the very end of the file). + * If you want to attach lots of files, a better way is to + * stuff them into a tarball or ZIP file and attach that. + *) + +let magic = "ISOATTACHMENT002" +let magiclen = String.length magic (* = 16 bytes *) +let trailerlen = magiclen + 8 + 8 (* magic + file start + true size *) + +(* Ugh, would be really nice to have 64 bit pack/unpack functions + * in the stdlib, instead of this ugliness ... + *) +let string_of_int64 i = + let str = String.create 8 in + let shift_mask i shift = + Char.chr (Int64.to_int (Int64.shift_right_logical i shift) land 0xff) + in + str.[0] <- shift_mask i 56; str.[1] <- shift_mask i 48; + str.[2] <- shift_mask i 40; str.[3] <- shift_mask i 32; + str.[4] <- shift_mask i 24; str.[5] <- shift_mask i 16; + str.[6] <- shift_mask i 8; str.[7] <- shift_mask i 0; + str +let int64_of_string str = + let i = ref 0L in + let add offs shift = + i := + Int64.logor + (Int64.shift_left (Int64.of_int (Char.code str.[offs])) shift) !i + in + add 0 56; add 1 48; add 2 40; add 3 32; + add 4 24; add 5 16; add 6 8; add 7 0; + !i + +let rec main () = + let args = Array.to_list Sys.argv in + match args with + | [ _; "add"; isoname; attachment ] -> (* add an attachment *) + do_add isoname attachment + | [ _; "delete"; isoname ] -> (* delete any attachment *) + do_delete isoname + | [ _; "get"; isoname; output ] -> (* get attachment *) + do_get isoname output + | [ _; "has"; isoname ] -> (* is there an attachment? *) + do_has isoname + | _ -> + eprintf "\ +update-iso.ml add foo.iso file + Attach 'file' to 'foo.iso'. + +update-iso.ml delete foo.iso + Remove attachment (if any) from 'foo.iso'. + +update-iso.ml get foo.iso file + Get attachment from 'foo.iso' and save it as 'file'. + +update-iso.ml has foo.iso + Exit with 0 (true) if there is an attachment. + Exit with 1 (false) if there is no attachment. + Exit with 2 if there was some other error, eg. file not found. + +Note that attachments are stacked, so you can add more than one +attachment. In this case 'get' operation returns the most recently +added and 'delete' operation deletes only the most recently added. +"; + exit 1 + +and do_has isoname = + let fd = + try openfile isoname [O_RDONLY] 0 + with Unix_error (err, syscall, param) -> + eprintf "%s:%s: %s\n" syscall param (error_message err); + exit 2 in + try + ignore (LargeFile.lseek fd (Int64.of_int ~-trailerlen) SEEK_END); + let buf = String.create magiclen in + if read fd buf 0 magiclen <> magiclen then exit 1; + if buf <> magic then exit 1; + exit 0 + with + Unix_error (err, syscall, param) -> + eprintf "%s:%s: %s\n" syscall param (error_message err); + exit 1 + +and do_add isoname attachment = + let fd = openfile isoname [O_APPEND; O_WRONLY] 0 in + + let iso_size = (LargeFile.fstat fd).LargeFile.st_size in + if Int64.logand iso_size 2047L <> 0L then + failwith "ISO image is not a multiple of 2048 bytes in size"; + + (* Copy the attachment itself to the end of the file. *) + let fd2 = openfile attachment [O_RDONLY] 0 in + let bufsize = 4 * 1024 in + let buffer = String.create bufsize in + let rec copy size = + let n = read fd2 buffer 0 bufsize in + if n > 0 then ( + ignore (write fd buffer 0 n); + copy (size + n) + ) + else size + in + let file_size = copy 0 in + close fd2; + + (* How much padding to use so that file_size + trailer + padding + * = 2048 bytes multiple? + *) + let padding_size = + let size = file_size + trailerlen in + let over = size land 2047 in + if over > 0 then 2048-over else 0 in + assert ((padding_size + file_size + trailerlen) land 2047 = 0); + + (* Write the padding. *) + ignore (write fd (String.make padding_size 'x') 0 padding_size); + + (* Write the magic. *) + ignore (write fd magic 0 magiclen); + + (* Write the file start and true size. *) + let buffer = string_of_int64 iso_size in + ignore (write fd buffer 0 8); + let buffer = string_of_int64 (Int64.of_int file_size) in + ignore (write fd buffer 0 8); + + close fd + +and do_delete isoname = + let fd = openfile isoname [O_RDWR] 0 in + ignore (LargeFile.lseek fd (Int64.of_int ~-trailerlen) SEEK_END); + let buf = String.create magiclen in + if read fd buf 0 magiclen <> magiclen || buf <> magic then + failwith "no attachment found"; + + (* Read the start offset of the file. *) + let buf = String.create 8 in + if read fd buf 0 8 <> 8 then + failwith "cannot read attachment size"; + let offset = int64_of_string buf in + + (* Truncate to start of the file. *) + LargeFile.ftruncate fd offset; + + close fd + +and do_get isoname output = + let fd = openfile isoname [O_RDONLY] 0 in + ignore (LargeFile.lseek fd (Int64.of_int ~-trailerlen) SEEK_END); + let buf = String.create magiclen in + if read fd buf 0 magiclen <> magiclen || buf <> magic then + failwith "no attachment found"; + + (* Read the start and size. *) + let buf = String.create 8 in + if read fd buf 0 8 <> 8 then + failwith "cannot read attachment offset"; + let offset = int64_of_string buf in + let buf = String.create 8 in + if read fd buf 0 8 <> 8 then + failwith "cannot read attachment size"; + let size = Int64.to_int (int64_of_string buf) in + + (* Seek to beginning of the attachment. *) + ignore (LargeFile.lseek fd offset SEEK_SET); + + (* Copy out the attachment. *) + let fd2 = openfile output [O_WRONLY; O_CREAT; O_TRUNC] 0o644 in + let bufsize = 4 * 1024 in + let buffer = String.create bufsize in + let rec copy remaining = + if remaining > 0 then ( + let n = min remaining bufsize in + let n = read fd buffer 0 n in + if n = 0 then failwith "corrupted or partial attachment"; + ignore (write fd2 buffer 0 n); + copy (remaining - n) + ) + in + copy size; + close fd2; + + close fd + +let () = main () diff --git a/virt-p2v-unpack b/virt-p2v-unpack index 71b1b8b..ad3b702 100755 --- a/virt-p2v-unpack +++ b/virt-p2v-unpack @@ -26,6 +26,8 @@ use strict; use Getopt::Long; use Pod::Usage; +XXX This needs a rewrite + my $outputdir = "."; my $force = 0; my $noninteractive = 0; diff --git a/virt-p2v.ml b/virt-p2v.ml index c37271b..1640eed 100755 --- a/virt-p2v.ml +++ b/virt-p2v.ml @@ -262,7 +262,7 @@ let shfailok cmd = ignore (Sys.command cmd) let shwithstatus cmd = - eprintf "shfailok: %s\n%!" cmd; + eprintf "shwithstatus: %s\n%!" cmd; Sys.command cmd (* Same as `cmd` in shell. Any error message will be in the logfile. *) @@ -328,14 +328,16 @@ let get_lvs = function | [vg; lv; pvs; lvsize] | [_; vg; lv; pvs; lvsize] -> - let pvs = String.nsplit "," pvs in - let pvs = List.map ( + let pvs = String.nsplit pvs "," in + let pvs = List.filter_map ( fun pv -> try let subs = Pcre.exec ~rex:devname pv in - Pcre.get_substring subs 1 + Some (Pcre.get_substring subs 1) with - Not_found -> failwith ("lvs: unexpected device name: " ^ pv) + Not_found -> + eprintf "lvs: unexpected device name: %s\n%!" pv; + None ) pvs in LV (vg, lv), pvs, lvsize | line -> @@ -421,7 +423,7 @@ let auto_network state = chdir "/tmp"; (* Try to ping the remote host to see if this worked. *) - sh ("ping -c 3 " ^ Option.map_default quote "" state.remote_host); + shfailok ("ping -c 3 " ^ Option.map_default quote "" state.remote_host); if state.greeting then ( printf "\n\nDid automatic network configuration work?\n"; @@ -439,7 +441,7 @@ let auto_network state = * future, lots of complex possibilities. *) let remote_of_origin_dev = - let devsd = Pcre.regexp "^sd([[:alpha:]]+[[:digit:]]+)$" in + let devsd = Pcre.regexp "^sd([[:alpha:]]+[[:digit:]]*)$" in let devsd_subst = Pcre.subst "hd$1" in fun dev -> Pcre.replace ~rex:devsd ~itempl:devsd_subst dev @@ -510,6 +512,7 @@ let rec main ttyname = dup2 fd stdin; dup2 fd stdout; close fd); + printf "virt-p2v.ml starting up ...\n%!"; (* Search for all non-removable block devices. Do this early and bail * if we can't find anything. This is a list of strings, like "hda". @@ -579,7 +582,7 @@ let rec main ttyname = (* Dialogs. *) let ask_greeting state = - ignore (msgbox "virt-p2v" "\nWelcome to virt-p2v, a live CD for migrating a physical machine to a virtualized host.\n\nTo continue press the Return key.\n\nTo get a shell you can use [ALT] [F2] and log in as root with no password.\n\nExtra information is logged in /tmp/virt-p2v.log but this file disappears when the machine reboots." 18 50); + ignore (msgbox "virt-p2v" "\nUPDATED! Welcome to virt-p2v, a live CD for migrating a physical machine to a virtualized host.\n\nTo continue press the Return key.\n\nTo get a shell you can use [ALT] [F2] and log in as root with no password.\n\nExtra information is logged in /tmp/virt-p2v.log but this file disappears when the machine reboots." 18 50); Next state in @@ -587,7 +590,7 @@ let rec main ttyname = match radiolist "Connection type" ~backbutton:false "Connection type. If possible, select 'server' and run P2V server on the remote host" - 10 50 2 [ + 11 50 3 [ "server", "P2V server on remote host", state.transport = Some Server; "ssh", "SSH (secure shell)", @@ -770,15 +773,14 @@ Root (/) dev: %s" eprintf "finished dialog loop\nfinal state = %s\n%!" (string_of_state state); (* Check that the environment is a sane-looking live CD. If not, bail. *) - if is_dir "/mnt/root" <> Some true || - is_file "/etc/lvm/lvm.conf.new" <> Some true then + if is_dir "/mnt/root" <> Some true then fail_dialog "You should only run this script from the live CD or a USB key."; (* Switch LVM config. *) sh "vgchange -a n"; - sh "mv /etc/lvm/lvm.conf /etc/lvm/lvm.conf.old"; - sh "mv /etc/lvm/lvm.conf.new /etc/lvm/lvm.conf"; + putenv "LVM_SYSTEM_DIR" "/etc/lvm.new"; (* see lvm(8) *) sh "rm -f /etc/lvm/cache/.cache"; + sh "rm -f /etc/lvm.new/cache/.cache"; (* Snapshot the block devices to send. *) let devices_to_send = Option.get state.devices_to_send in @@ -809,7 +811,9 @@ Root (/) dev: %s" sh ("mount " ^ quote ("/dev/mapper/" ^ snapshot_dev) ^ " /mnt/root") | LV (vg, lv) -> - (* The LV will be backed by a snapshot device, so just mount directly. *) + (* The LV will be backed by a snapshot device, so just mount + * directly. + *) sh ("mount " ^ quote ("/dev/" ^ vg ^ "/" ^ lv) ^ " /mnt/root") ); @@ -819,11 +823,12 @@ Root (/) dev: %s" | Shell -> printf "Network configuration.\n\n"; printf "Please configure the network from this shell.\n\n"; - printf "When you have finished, exit the shell with ^D or exit.\n\n"; + printf "When you have finished, exit the shell with ^D or exit.\n\n%!"; shell () | Auto -> - printf "Trying network auto-configuration from root filesystem ...\n\n"; + printf + "Trying network auto-configuration from root filesystem ...\n\n%!"; if not (auto_network state) then ( printf "\nAuto-configuration failed. Starting a shell.\n\n"; printf "Please configure the network from this shell.\n\n"; @@ -839,6 +844,7 @@ Root (/) dev: %s" (origin_dev, snapshot_dev, remote_dev) ) devices_to_send in + (* Modify files on the root filesystem. *) rewrite_fstab state devices_to_send; (* XXX Other files to rewrite? *) @@ -846,6 +852,9 @@ Root (/) dev: %s" sh "umount /mnt/root"; sh "sync"; (* Ugh, should be in stdlib. *) + (* Disable screen blanking on console. *) + sh "setterm -blank 0"; + (* For Server and TCP type connections, we connect just once. *) let remote_host = Option.get state.remote_host in let remote_port = Option.get state.remote_port in @@ -922,17 +931,43 @@ Root (/) dev: %s" fd, chan in (* Copy the data. *) - let bufsize = 128 * 1024 in + let bufsize = 1024 * 1024 in let buffer = String.create bufsize in + let start = gettimeofday () in - let rec copy () = + let rec copy bytes_sent last_printed_at = let n = read fd buffer 0 bufsize in if n > 0 then ( ignore (write sock buffer 0 n); - copy () + + let bytes_sent = Int64.add bytes_sent (Int64.of_int n) in + let last_printed_at = + let now = gettimeofday () in + (* Print progress once per second. *) + if now -. last_printed_at > 1. then ( + let elapsed = Int64.to_float bytes_sent /. Int64.to_float size in + let secs_elapsed = now -. start in + printf "%.0f%%" (100. *. elapsed); + (* After 60 seconds has elapsed, start printing estimates. *) + if secs_elapsed >= 60. then ( + let remaining = 1. -. elapsed in + let secs_remaining = (remaining /. elapsed) *. secs_elapsed in + if secs_remaining > 120. then + printf " (about %.0f minutes remaining) " + (secs_remaining /. 60.) + else + printf " (about %.0f seconds remaining) " + secs_remaining + ); + printf "\r%!"; + now + ) + else last_printed_at in + + copy bytes_sent last_printed_at ) in - copy (); + copy 0L start; (* For SSH disconnect, for Server/TCP send a newline. *) match transport with @@ -979,6 +1014,77 @@ let handle_exn f arg = try f arg with exn -> print_endline (Printexc.to_string exn); raise exn +(* If the ISO image has an attachment then it could be a new version + * of virt-p2v.ml (this script). Get the attachment and run it + * instead. Useful mainly for testing, in conjunction with the + * 'make update' target in the virt-p2v Makefile. + *) +let magic = "ISOATTACHMENT002" +let magiclen = String.length magic (* = 16 bytes *) +let trailerlen = magiclen + 8 + 8 (* magic + file start + true size *) + +let int64_of_string str = + let i = ref 0L in + let add offs shift = + i := + Int64.logor + (Int64.shift_left (Int64.of_int (Char.code str.[offs])) shift) !i + in + add 0 56; add 1 48; add 2 40; add 3 32; + add 4 24; add 5 16; add 6 8; add 7 0; + !i + +let update ttyname = + let cdrom = "/dev/cdrom" in + let output = "/tmp/virt-p2v.ml" in + + try + let fd = openfile cdrom [O_RDONLY] 0 in + ignore (LargeFile.lseek fd (Int64.of_int ~-trailerlen) SEEK_END); + let buf = String.create magiclen in + if read fd buf 0 magiclen <> magiclen || buf <> magic then ( + close fd; + raise Exit + ); + + (* Read the size. *) + let buf = String.create 8 in + if read fd buf 0 8 <> 8 then + failwith "cannot read attachment offset"; + let offset = int64_of_string buf in + let buf = String.create 8 in + if read fd buf 0 8 <> 8 then + failwith "cannot read attachment size"; + let size = Int64.to_int (int64_of_string buf) in + + (* Seek to beginning of the attachment. *) + ignore (LargeFile.lseek fd offset SEEK_SET); + + (* Copy out the attachment. *) + let fd2 = openfile output [O_WRONLY; O_CREAT; O_TRUNC] 0o755 in + let bufsize = 4 * 1024 in + let buffer = String.create bufsize in + let rec copy remaining = + if remaining > 0 then ( + let n = min remaining bufsize in + let n = read fd buffer 0 n in + if n = 0 then failwith "corrupted or partial attachment"; + ignore (write fd2 buffer 0 n); + copy (remaining - n) + ) + in + copy size; + close fd2; + + close fd; + + (* Run updated virt-p2v script. *) + execv output [| output; ttyname |] + with + Unix_error _ | Exit -> + (* Some error, or no attachment, so keep running this script. *) + handle_exn main (Some ttyname) + (* Test harness for the Makefile. The Makefile invokes this script as * 'virt-p2v.ml --test' just to check it compiles. When it is running * from the actual live CD, there is a single parameter which is the @@ -987,9 +1093,13 @@ let handle_exn f arg = let () = match Array.to_list Sys.argv with | [ _; "--test" ] -> () (* Makefile test - do nothing. *) + | [ _; "--update"; ttyname ] -> (* Test for update and run. *) + update ttyname | [ _; ("--help"|"-help"|"-?"|"-h") ] -> usage (); - | [ _; ttyname ] -> - handle_exn main (Some ttyname) (* Run main with ttyname. *) - | [ _ ] -> - handle_exn main None (* Interactive - no ttyname. *) + | [ _; ttyname ] -> (* Run main with ttyname. *) + handle_exn main (Some ttyname) + | [ _ ] -> (* Interactive - no ttyname. *) + handle_exn main None | _ -> usage () + +(* This file must end with a newline *) -- 1.8.3.1