#!/bin/sh # # Copyright (C) 2014-2015 Codethink Limited # # 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; version 2 of the License. # # 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, see . # # =*= License: GPL-2 =*= # Receive a data stream describing a sparse file, and reproduce it, # either to a named file or stdout. # # The data stream is simple: it's a sequence of DATA or HOLE records: # # DATA # 123 # <123 bytes of binary data, NOT including newline at the end> # # HOLE # 123 # # This shell script can be executed over ssh (given to ssh as an arguemnt, # with suitable escaping) on a different computer. This allows a large # sparse file (e.g., disk image) be transferred quickly. # # This script should be called in one of the following ways: # # recv-hole file FILENAME # recv-hole vbox FILENAME DISKSIZE # # In both cases, FILENAME is the pathname of the disk image on the # receiving end. DISKSIZE is the size of the disk image in bytes. The # first form is used when transferring a disk image to become an # identical file on the receiving end. # # The second form is used when the disk image should be converted for # use by VirtualBox. In this case, we want to avoid writing a # temporary file on disk, and then calling the VirtualBox VBoxManage # tool to do the conversion, since that would involve large amounts of # unnecessary I/O and disk usage. Instead we pipe the file directly to # VBoxManage, avoiding those issues. The piping is done here in this # script, instead of in the caller, to make it easier to run things # over ssh. # # However, since it's not possible seek in a Unix pipe, we have to # explicitly write the zeroes into the pipe. This is not # super-efficient, but the way to avoid that would be to avoid sending # a sparse file, and do the conversion to a VDI on the sending end. # That is out of scope for xfer-hole and recv-hole. set -eu die() { echo "$@" 1>&2 exit 1 } recv_hole_to_file() { local n read n truncate --size "+$n" "$1" } recv_data_to_file() { local n read n local blocksize=1048576 local blocks=$(($n / $blocksize)) local extra=$(($n % $blocksize)) xfer_data_to_stdout "$blocksize" "$blocks" >> "$1" xfer_data_to_stdout 1 "$extra" >> "$1" } recv_hole_to_stdout() { local n read n (echo "$n"; cat /dev/zero) | recv_data_to_stdout } recv_data_to_stdout() { local n read n local blocksize=1048576 local blocks=$(($n / $blocksize)) local extra=$(($n % $blocksize)) xfer_data_to_stdout "$blocksize" "$blocks" xfer_data_to_stdout 1 "$extra" } xfer_data_to_stdout() { local log="$(mktemp)" if ! dd "bs=$1" count="$2" iflag=fullblock status=noxfer 2> "$log" then cat "$log" 1>&2 rm -f "$log" exit 1 else rm -f "$log" fi } type="$1" case "$type" in file) output="$2" truncate --size=0 "$output" while read what do case "$what" in DATA) recv_data_to_file "$output" ;; HOLE) recv_hole_to_file "$output" ;; *) die "Unknown instruction: $what" ;; esac done ;; vbox) output="$2" disk_size="$3" while read what do case "$what" in DATA) recv_data_to_stdout ;; HOLE) recv_hole_to_stdout ;; *) die "Unknown instruction: $what" ;; esac done | VBoxManage convertfromraw stdin "$output" "$disk_size" ;; esac