#!/bin/sh #============================================================================= # # Copyright (C) 2011-2012 O. LODYGENSKY, E. URBAH # at LAL, Univ Paris-Sud, IN2P3/CNRS, Orsay, France # License GPL v3 # # Shell script using VirtualBox to create a VDI disk image # (for example for contextualization of virtual machines). # # Under Linux, it currently requires to be sudoer. # # Parameters : SOURCE : File or folder to be (recursively) copied to the # disk image # # Output : $SOURCE.vdi # #============================================================================= #============================================================================= # Function usage() #============================================================================= usage() { if [ "$*" ]; then echo > /dev/stderr echo "$*" > /dev/stderr fi cat << "END_OF_HELP" > /dev/stderr Usage : $0 --help | SOURCE --help : This help SOURCE : After lexical compaction, SOURCE can NOT be / or contain .. SOURCE is copied to the disk image. If SOURCE is a folder, this script does NOT copy the folder itself, but its content. END_OF_HELP exit 1 } #============================================================================= # Function fatal() #============================================================================= fatal() { RC=$? if [ $RC -eq 0 ]; then RC=1; fi echo "$(date "$DATE_FORMAT") $0 FATAL: ${*:-Ctrl+C}" > /dev/stderr exit $RC } #============================================================================= # Function clean() #============================================================================= clean() { RC=$? if [ $RC -eq 0 ]; then RC=1; fi echo "$(date "$DATE_FORMAT") $0 FATAL: $1" > /dev/stderr echo > /dev/stderr echo "$(date "$DATE_FORMAT") $0 Trying to clean" > /dev/stderr case $OSTYPE in darwin* ) ( set -x; hdiutil detach "$MOUNT_POINT" ) ;; linux* ) echo + umount "$LINUX_DEVICE" > /dev/stderr sudo umount "$LINUX_DEVICE" echo + /sbin/losetup -d "$LINUX_DEVICE" > /dev/stderr sudo /sbin/losetup -d "$LINUX_DEVICE" ;; esac exit $RC } #============================================================================= # MAIN #============================================================================= trap fatal SIGINT SIGTERM #----------------------------------------------------------------------------- # There must be 1 parameter : A file or folder name. In this parameter : # - Replace all '//' by '/', all '/./' by '/', remove leading './'. # - If empty or leading '../' or embedded '/../' : Prefix by $PWD, # replace all '/xyz/../' by '/', then forbid any remaining '/../'. # - Remove any trailing '/'. #----------------------------------------------------------------------------- if [ '(' "$1" = "" ')' -o '(' "$1" = "--help" ')' ]; then usage; fi SOURCE=$(echo "$1/" | perl -wpe 's=//+=/=g; s=(^|/)(\./)+=$1=g') if [ '(' -z "${SOURCE##../*}" ')' -o \ '(' -z "${SOURCE##[^/]*/../*}" ')' ]; then SOURCE="$PWD/$SOURCE" fi while expr "$SOURCE" : ".*/\.*[^./][^/]*/\.\./" > /dev/null do SOURCE=$(echo "$SOURCE" | perl -wpe 's=/\.*[^./][^/]*/\.\./=/=g') done if [ -z "${SOURCE##*/../*}" ]; then usage "SOURCE='$SOURCE' NOT allowed" fi SOURCE=$(echo "$SOURCE" | perl -wpe 's=(.)/+$=$1=') #----------------------------------------------------------------------------- # Depending on the OS, set : # - The date format # - VB_MGT as the VirtualBox management command #----------------------------------------------------------------------------- case $OSTYPE in darwin* ) DATE_FORMAT='+%Y-%m-%d %H:%M:%S%z' VB_MGT=/Applications/VirtualBox.app/Contents/MacOS/VBoxManage ;; linux* ) DATE_FORMAT='--rfc-3339=seconds' VB_MGT=/usr/bin/vboxmanage ;; * ) fatal "$OSTYPE not supported" ;; esac VBVERSION=$($VB_MGT -v | cut -d '.' -f 1-2) #----------------------------------------------------------------------------- # Elementary checks #----------------------------------------------------------------------------- echo > /dev/stderr [ ! -x "$VB_MGT" ] && fatal "Can NOT find VirtualBox application : $VB_MGT" [ ! -r "$SOURCE" ] && fatal "Source NOT found : $SOURCE" [ '(' "$SOURCE" = "context" ')' -a '(' ! -f context/context.sh ')' ] && \ fatal "Source = 'context', but 'context/context.sh' NOT found" #----------------------------------------------------------------------------- # Choose folder for temp files in R/W filesystem having greatest free space. # Verify that it is NOT $SOURCE or a subfolder of it. #----------------------------------------------------------------------------- echo "$(date "$DATE_FORMAT") $0 INFO: Begin for '$SOURCE'" > /dev/stderr echo > /dev/stderr TMP_FOLDER=/var/tmp if [ '(' -d /scratch ')' -a '(' -w /scratch ')' ]; then if [ $(expr $(stat -f -c '%S' /scratch) '*' $(stat -f -c '%a' /scratch) ) \ -gt \ $(expr $(stat -f -c '%S' /var/tmp) '*' $(stat -f -c '%a' /var/tmp) ) ] then TMP_FOLDER=/scratch fi fi { [ "$SOURCE" = "/" ] || expr "$TMP_FOLDER/" : "$SOURCE/" > /dev/null; } && \ fatal "Can NOT recursively copy '$SOURCE' to '$TMP_FOLDER'" #----------------------------------------------------------------------------- # Calculate disk and file names #----------------------------------------------------------------------------- DISK_NAME=$(basename "$SOURCE" | tr ' .' '-_') VDI_NAME="$DISK_NAME.vdi" TMP_DISK="$TMP_FOLDER/$DISK_NAME.dmg" # Linux: Format is NOT really DMG VOLNAME=$(basename "$SOURCE" | tr 'a-z .' 'A-Z-_') #----------------------------------------------------------------------------- # Purge temporary files #----------------------------------------------------------------------------- [ -f "$VDI_NAME" ] && fatal "'$VDI_NAME' already exists." \ "Cowardly refuse to overwrite it." rm -f "$TMP_DISK" || \ fatal "Can NOT delete '$TMP_DISK' (maybe the disk is already mounted)" #----------------------------------------------------------------------------- # Create the disk containing the source. # Its size will be 1.1 * (size of the source) + 10 Mb. #----------------------------------------------------------------------------- DISK_SIZE_MB=$(du -s -m "$SOURCE" | cut -f 1) DISK_SIZE_MB=$(expr $DISK_SIZE_MB + 0${DISK_SIZE_MB%?} + 10) echo "$(date "$DATE_FORMAT") $0 INFO: For '$SOURCE', disk '$VDI_NAME'" \ "will be created with $DISK_SIZE_MB Mb" > /dev/stderr case $OSTYPE in #--------------------------------------------------------------------------- # Mac OS X : Use the 'hdiutil' command #--------------------------------------------------------------------------- darwin* ) #------------------------------------------------------------------------- # Create an empty disk in FAT32 #------------------------------------------------------------------------- echo > /dev/stderr ( set -x; hdiutil create -size "${DISK_SIZE_MB}m" -fs MS-DOS \ -volname "$VOLNAME" "$TMP_DISK" ) || \ fatal "Can NOT create new disk '$TMP_DISK' in FAT32" #------------------------------------------------------------------------- # Attach the disk R/W #------------------------------------------------------------------------- echo > /dev/stderr DISK_LOG="$TMP_DISK.log" rm -f "$DISK_LOG" ( set -x; hdiutil attach -readwrite "$TMP_DISK" 2>&1 | \ tee "$DISK_LOG" ) || \ fatal "Can NOT attach '$TMP_DISK'" #------------------------------------------------------------------------- # OBSOLETE: Format the disk in FAT32 #------------------------------------------------------------------------- # echo > /dev/stderr # MAC_DEVICE=$(grep "$VOLNAME" "$DISK_LOG" | cut -f 1) # echo "MAC_DEVICE=$MAC_DEVICE" > /dev/stderr # ( set -x; newfs_msdos -v "$VOLNAME" -F 32 "$MAC_DEVICE" ) || \ # clean "Can NOT format '$TMP_DISK' in FAT32" #------------------------------------------------------------------------- # Copy the source to the disk #------------------------------------------------------------------------- echo > /dev/stderr MOUNT_POINT="/Volumes/$VOLNAME" if [ -f "$SOURCE" ]; then ( set -x; cp -v -f -p "$SOURCE" "$MOUNT_POINT/" ) else ( set -x; cp -v -f -p -r "$SOURCE"/* "$MOUNT_POINT/" ) fi #------------------------------------------------------------------------- # Detach the disk #------------------------------------------------------------------------- echo > /dev/stderr ( set -x; hdiutil detach "$MOUNT_POINT" ) || \ clean "Can NOT detach '$MOUNT_POINT'" ;; #--------------------------------------------------------------------------- # Linux : Use the 'dd', 'losetup', 'mkfs' and 'mount' commands. # This requires to be sudoer. #--------------------------------------------------------------------------- linux* ) #------------------------------------------------------------------------- # Verify access to sudo #------------------------------------------------------------------------- echo > /dev/stderr ( set -x; sudo -v ) || fatal "NOT sudoer" #------------------------------------------------------------------------- # Create an unformatted disk #------------------------------------------------------------------------- echo > /dev/stderr MEGA_BYTE=$(expr 1024 '*' 1024) ( set -x; dd if=/dev/zero of="$TMP_DISK" bs="$MEGA_BYTE" \ count="$DISK_SIZE_MB" ) || \ fatal "Can NOT create a disk of '$DISK_SIZE_MB' blocs of" \ "'$MEGA_BYTE' bytes at '$TMP_DISK'" #------------------------------------------------------------------------- # Find a free loop device #------------------------------------------------------------------------- LINUX_DEVICE="$(sudo /sbin/losetup -f)" echo > /dev/stderr echo + /sbin/losetup "$LINUX_DEVICE" "$TMP_DISK" > /dev/stderr sudo /sbin/losetup "$LINUX_DEVICE" "$TMP_DISK" #------------------------------------------------------------------------- # OBSOLETE: Use 'fdisk' #------------------------------------------------------------------------- # echo > /dev/stderr # echo + /sbin/fdisk "$LINUX_DEVICE" > /dev/stderr # sudo /sbin/fdisk "$LINUX_DEVICE" << 'END_OF_FDISK' #n #p #1 # # #w #END_OF_FDISK #------------------------------------------------------------------------- # Format the disk in 'ext3' #------------------------------------------------------------------------- echo > /dev/stderr echo + /sbin/mkfs -t ext3 -L "$VOLNAME" "$LINUX_DEVICE" > /dev/stderr sudo /sbin/mkfs -t ext3 -L "$VOLNAME" "$LINUX_DEVICE" || \ clean "Can NOT format '$LINUX_DEVICE' in 'ext3' successfully" #------------------------------------------------------------------------- # Mount the disk #------------------------------------------------------------------------- echo > /dev/stderr MOUNT_POINT="/mnt/$DISK_NAME" sudo mkdir -p "$MOUNT_POINT" || \ clean "Can NOT execute 'mkdir' on '$MOUNT_POINT' successfully" echo + mount -t auto "$LINUX_DEVICE" "$MOUNT_POINT" > /dev/stderr sudo mount -t auto "$LINUX_DEVICE" "$MOUNT_POINT" || \ clean "Can NOT mount '$LINUX_DEVICE' on '$MOUNT_POINT' successfully" #------------------------------------------------------------------------- # Copy the source to the disk #------------------------------------------------------------------------- echo > /dev/stderr if [ -f "$SOURCE" ]; then echo + cp -v -f -p "$SOURCE" "$MOUNT_POINT/" > /dev/stderr sudo cp -v -f -p "$SOURCE" "$MOUNT_POINT/" else echo + cp -v -f -p -r "$SOURCE"/* "$MOUNT_POINT/" > /dev/stderr sudo cp -v -f -p -r "$SOURCE"/* "$MOUNT_POINT/" fi || \ clean "Can NOT copy '$SOURCE' on '$MOUNT_POINT' successfully" #------------------------------------------------------------------------- # Unmount the disk #------------------------------------------------------------------------- echo > /dev/stderr echo + umount "$MOUNT_POINT" > /dev/stderr sudo umount "$MOUNT_POINT" || \ clean "Can NOT umount '$MOUNT_POINT' successfully" #------------------------------------------------------------------------- # Release the loop device #------------------------------------------------------------------------- echo > /dev/stderr echo + /sbin/losetup -d "$LINUX_DEVICE" > /dev/stderr sudo /sbin/losetup -d "$LINUX_DEVICE" || \ clean "Can NOT release loop device '$LINUX_DEVICE' successfully" ;; esac #----------------------------------------------------------------------------- # Use VirtualBox to convert the disk from raw to VDI #----------------------------------------------------------------------------- echo > /dev/stderr ( set -x; "$VB_MGT" convertfromraw "$TMP_DISK" "$VDI_NAME" ) || \ fatal "Can't create VDI : $TMP_DISK" rm -f "$TMP_DISK" echo > /dev/stderr echo "$(date "$DATE_FORMAT") $0 INFO: Successfully stored '$SOURCE' in" \ "'$VDI_NAME'" > /dev/stderr