#!/bin/bash
#
# confinedrv v1.7.7
# (c) copyright by Elmar Stellnberger, the original author: 2009, Jan 2010, Nov&Dez 2014, Aug 2016
#
#  further information: www.elstel.org/com; look here for an actual contact address
#  current email: estellnb@elstel.org; additional email estellnb@gmail.com
#
# v 1.7.7: allow to fade in a file for the mbr: sdy=sdb5,6 mbr=./sdb.mbr
#  * use lockfile-progs in order to lock the $shm/confinedrv-loops file (lockfile-create) or use the old lockfile-executable as fallback which is still distributed with some distros
#  * use /tmp if /dev/shm should not be accessible/mounted (and allow the user to mount /dev/shm later)
#  * some smaller fixes like f.i. touching the $shm/confinedrv-loops for the first time in order to avoid an unnecessary error message
#
# v 1.7.6: use of external partition images (06.12.2014)
#  * allows sth. like confinedrv sdy=sdb5,6 sdy5=/media/partitionimages/sdb5.img
#  * /dev/shm/confinedrv-loops now also contains a disk image offset (incompatible with versions before).
#  * correction of some minor issues: correct /dev/shm-cfdrv-lps. if loopdev deallocation should fail, do not accidentially drop loops on reallocation of the same device
#
# v 1.7.3: reuse of loop devices (01.12.2014)
#  * moved parsing and device setup out of main loop into own procedures
#  * new file format for /dev/shm/confinedrv-loops: "sdy /dev/loop0 /dev/sdb ro" (2 entries added); old program versions will not work on new format!
#  * multiple devices can again be set up in a single command line
#
# v 1.7.2: gpt support (30.11.2014)
#  * use of parted instead of fdisk
#  * small changes: prettier printing, loopusage handled before first call to confinedrv, error swallowed by bash 4.2.37 discovered: org=${groups[0]%%[0-9,]*}: colon was forgotten
#
# v 1.7.1: small fixups (16.11.2014)
#  * a problem in theory: eliminated race condition dropping loop devices, cleanup by --freeloop after kill -9
#  * writable loop device only allocated if needed (previously: if any even non writable partition was specified)
#  * acc2str -> access table printing: make a difference between zero and error
#  * skip function is now more thoroughly documented
#
# v 1.7: fixing some boot issues and providing enhanced functionality
#  * Certain boot problems which have arised under respective circumstances with v1.2.1 due to err-ing out gap spaces needed by grub or 
#    other loaders rather than zeroing them out or making them available as read-only should now be resolved.
#  * choose for any of three modes for every partition: read, write, error or zero (sdx=sda:r1:e2,3:w5,6,7:z8); default is now to zero
#    out any parition which has not been mentioned rather than letting any access err there.
#  * The --gap and --mbr options provide means for specifying the access rights for gaps between primary partitions or all the space
#    before the first partition including the partition table and the MBR respectively.
#  * The [--noannot] --info sdx now provides a better readable map of the partition table annotated with symbolic references for the
#    start and end of partitions by default. --loopusage shows all loop devices in use by confinedrv.
#
# v 1.2.1: small fix (17.11.2013)
#  * correctly allocate/deallocate loop drives if more than one virtual drive is specified on the command line
#
# v 1.2: new algorithm (17.11.2013)
#  * completely reworked and better algorithm
#
# v 1.1: added license (30.10.2013)
#  * better option parsing; some minor fixes
#  * reads page size by getconf
#
# v.1.0: originally published version (Jan 2010)
#  * original authors: Elmar Stellnberger
#

license() {
  cat <<EOQ
This program may be used under the terms of GPLv3; see: https://www.gnu.org/licenses/gpl-3.0.en.html.
If you apply changes please sign our contributor license agreement at https://www.elstel.org/license/CLA-elstel.pdf
so that your changes can be included into the main trunk at www.elstel.org/qemu/
(c) copyright by Elmar Stellnberger 2016
 
EOQ
  exit 0;
}

rot=$'\e[1;31m'; drot=$'\e[0;31m'; blau=$'\e[1;34m'; nv=$'\e[0m'; ul=$'\e[4m';
err() { echo -e "${rot}$@${nv}" >&2; }
warn() { echo -e "${drot}$@${nv}" >&2; }
msg() { echo -e "${blau}$@${nv}" >&2; }

ein() { local tok="$1";
  while [ $# -gt 1 ]; do [[ "$tok" = "$2" ]] && return 0; shift; done
  return 1;
}

exec 9>/dev/null;
if which lockfile-create 2>&9 >&9; then
  let lockprog=1;
elif which lockfile 2>&9 >&9; then
  let lockprog=2;
else
  err "error: package lockfile-progs containing the lockfile-create/-remove utility programs is not installed.";
  let lockprog=0;
fi

check4parted() {
  which parted 2>&9 >&9 || { err "parted does not seem to be installed; exiting."; echo >&2; exit 208; }
}

mylockfile-create() {
  [[ -n "$2" ]] && { err "internal error: wrong invocation of mylockfile-create."; exit 244; }
  if [[ lockprog -eq 2 ]]; then
    lockfile -1 -r 3 "$1";
    return $?;
  elif [[ lockprog -eq 0 ]]; then   #  unsafe because of race-condition! - user has already been warned.
    for retries in 1 2 3; do
      [[ -e "$1" ]] || { touch "$1"; return 0; }
      sleep 1;
    done;
    [[ -e "$1" ]] && return 4;
    touch "$1"; return 0;
  fi
  for retries in 1 2 3; do   # good; no race condition
    lockfile-create --retry 0 --lock-name "$1" 2>&9 && return 0;
    sleep 1;
  done;
  lockfile-create --retry 0 --lock-name "$1" 2>&9 && return 0;
  return 4;
}

mylockfile-remove() {
  if [[ lockprog -eq 1 ]]; then
    lockfile-remove --lock-name "$1";     # does not delete the lock file (an optimization) at least while the machine is booted 
    return;                   # ... but successfully releases the lock
  fi
  if [[ $(stat -c%s "$1") -ge 32 ]]; then     # also: find "$1" -printf "%s"
    err "warning: lockfile $1 has a size greater than 32 bytes; please remove it manually after having a look!"; 
    [[ lockprog -eq 1 ]] && lockfile-remove --lock-name "$1";   # never executed unless somebody would remove the code in front
  else
    rm -f "$1";
  fi
}

if [[ -d "/dev/shm" ]]; then
  # gonna have to check first whether there are residuals in /tmp from the time of before of shm has been mounted
  #  first wait until /tmp/confinedrv-loops gets unmounted; i.e. there is no confinedrv-loops.lock file in /tmp
  let lockexists=255;  # 255 ~ error / lock does not exist
  for i in 0 1 2 3; do
    if [[ lockprog -eq 1 ]]; then
      lockfile-check --lock-name /tmp/confinedrv-loops.lock; let lockexists=$?;
    else [[ -e /tmp/confinedrv-loops.lock ]]; let lockexists=$?;
    fi
    [[ $lockexists -eq 0 ]] || break;
    [[ i -ne 3 ]] && sleep 1;
  done
  [[ lockexists -eq 0 ]] && { err "/dev/shm mounted but old lockfile still present in /tmp/confinedrv-loops.lock; please delete it first."; exit 209; }
  # then try to migrate the confinedrv-loops file from /tmp to /dev/shm
  if [[ -e "/tmp/confinedrv-loops" ]]; then
    mylockfile-create /dev/shm/confinedrv-loops.lock
    [[ -e "/dev/shm/confinedrv-loops" ]] && { err "tmp->shm migration error: [/tmp&/dev/shm]/confinedrv-loops: both files exist at the same time."; exit 209; }
    mv --no-clobber /tmp/confinedrv-loops /dev/shm/confinedrv-loops
    mylockfile-remove /dev/shm/confinedrv-loops.lock
  fi
  # now we can be sure that it is safe to use /dev/shm
  shm="/dev/shm";
else
  shm="/tmp";
fi

mkvar() { local rndup=$(roundup $1); local rnddwn=$(rounddown $1);
  [[ -z "$annotdev" ]] && { err "localvars;"; exit; }
  if [[ $rndup = $rnddwn ]]; then
    export ${annotdev}${3/./_}_$rndup=$2$3; >&2
  else
    export ${annotdev}${3/./_}_$rndup=$2$3+$((rndup-$1)); >&2
    export ${annotdev}${3/./_}_$rnddwn=$2$3-$(($1-rnddwn)); >&2
  fi
}

slurpdev() { local annotdev basedev; annotdev="$1"; basedev="$2";
  msg "slurping $basedev"
  while read dno start end blocks id type; do
    if [[ "${dno,,}" = "disk" && "${start%:}" != "$start" ]]; then
      endofdisk=${end%s}; [[ "$endofdisk" != "$end" ]] && export ${annotdev}_endofdisk=$endofdisk;
    elif [[ -z "${dno##[0-9]*}" && -n "$dno$start$end" ]]; then 
      start="${start%s}"; end="${end%s}"; dev="${basedev}${dno}";
      mkvar $start ${dev#/dev/} .start
      mkvar $((end+1)) ${dev#/dev/} .end
    fi
  done < <( parted $basedev <<<$'unit s\nprint\nquit\n' ) 
  export $annotdev="slurped"
}

droploop() {
  local dest target loop origin access offset loopusage loopusage_origin loopusage_access loopusage_offset err=0 
  target="$1"; shift;
  mylockfile-create $shm/confinedrv-loops.lock || { 
    err "error: could not lock [/dev/shm|/tmp]/confinedrv-loops: will retry"
    mylockfile-create $shm/confinedrv-loops.lock || { 
      err "error: lock did not succeed; please execute only once but somewhat later:"
      echo " confinedrv --freeloop $target" >&2;
      return 1;
  };};
  trap '' SIGQUIT SIGTERM SIGINT; declare -a loopusage loopusage_origin loopusage_access loopusage_offset 
  touch $shm/confinedrv-loops; 
  mv $shm/confinedrv-loops $shm/confinedrv-loops.tmp
  while read dest loop origin access offset; do 
    [[ -z "$dest" || -z "$loop" ]] && continue;
    #echo losetup -d $loop;
    loopno="${loop#/dev/loop}"
    if [[ "$dest" = "$target" ]] ; then
      if [[ -z "${loopno##[0-9]*}" ]]; then
	if [[ -n "${loopuasge[loopno]}" && ( "${loopusage_origin[loopno]}" != "$origin" || "${loopusage_access[loopno]}" != "$access" || "${loopusage_offset[loopno]}" != "$offset" ) ]];then
	  warn "inconsistant entries for $dest /dev/loop$loopno in $shm/confinedrv-loops: ignoring any but the last one.";
	  echo ">> $dest /dev/loop$loopno ${loopusage_origin[loopno]} ${loopusage_access[loopno]} ${loopusage_offset[loopno]}" >&2;
	  echo ">> $dest $loop $origin $access $offset" >&2;
	fi
	if ein $loop $@; then let loopusage[loopno]+=1;
	else let loopusage[loopno]+=0; loopusage_origin[loopno]="$origin"; loopusage_access[loopno]="$access"; let loopusage_offset[loopno]=offset;
	fi
      elif ! ein $loop $@; then
	err "loop device $loop in use by $target does not seem to have a number; trying to deallocate though it may be in use by another instance of confinedrv; ";
        losetup -d "$loop" || { err=1; err "error trying to deallocate $loop."; echo "$dest $loop $origin $access $offset"; }
      fi
    else
      if [[ -z "${loopno##[0-9]*}" ]]; then
        let loopusage[loopno]+=1;
      fi
      echo "$dest $loop $origin $access $offset"
    fi
  done <$shm/confinedrv-loops.tmp >$shm/confinedrv-loops;
  #echo "loopusage: ${!loopusage[*]} ~ ${loopusage[@]}" >&2;
  for loopno in ${!loopusage[*]}; do
    if [[ loopusage[loopno] -le 0 ]]; then
      losetup -d /dev/loop$loopno || { err=1; err "error trying to deallocate /dev/loop$loopno.";
        echo "$target /dev/loop$loopno ${loopusage_origin[loopno]} ${loopusage_access[loopno]} ${loopusage_offset[loopno]}"; }
    fi
  done >>$shm/confinedrv-loops;
  [[ err -ne 0 ]] && { err "error freeing loop devices for $target; please execute somewhat later:";
	               echo " confinedrv --freeloop $target" >&2; }
  rm -f $shm/confinedrv-loops.tmp; mylockfile-remove $shm/confinedrv-loops.lock
  trap - SIGQUIT SIGTERM SIGINT;
  return $err;
}

verbose=1; readall=false; annotate=true; gapmode=0; mbrmode=1; ret=0;

if [[ $# -le 0 ]]; then
  echo -e "$(basename "$0") --help/--license\n"
fi

getdevno() {
  local attr x usr grp major minor rest
  read attr x usr grp major minor rest < <(ls -l "$1";)
  echo "${major%,}:$minor"
}

# /dev/zero can not be used because it is a character rather than a block device
# zerodev="$(getdevno /dev/zeroblock 2>/dev/null)";
# if [ -z "$zerodev" ]; then && { zerodev=1:5; mknod /dev/zero c 1 5; }

getloops() {
  local destination loopdev origin access;
  mylockfile-create $shm/confinedrv-loops.lock || err "possible inconsistency reading [/dev/shm|/tmp]/confinedrv-loops: could not lock (timeout)."
  trap "mylockfile-remove $shm/confinedrv-loops.lock" EXIT
  touch $shm/confinedrv-loops
  while read destination loopdev origin access offset; do
    if [[ "$origin" = "$org" && offset -eq 0 ]]; then 
      if [[ "$access" = "rw" ]]; then 
	[[ -n "$loop" && "$loop" != "$loopdev" ]] && warn "double allocation in [/dev/shm|/tmp]/confinedrv-loops: $org:ro ~ $loop $loopdev"
	export loop="$loopdev"; 
      elif [[ "$access" = "ro" ]]; then 
	[[ -n "$rolp" && "$rolp" != "$loopdev" ]] && warn "double allocation in [/dev/shm|/tmp]/confinedrv-loops: $org:rw ~ $rolp $loopdev"
	export rolp="$loopdev"; 
      fi
    fi
    for srcno in ${!partsrc[@]}; do
      if [[ "$origin" = "${partsrc[srcno]}" && "$access" = "${partaccess[srcno]}" && offset -eq ${partoffset[srcno]} ]]; then
	[[ -n "${partloop[srcno]}" && "${partloop[srcno]}" != "$loopdev" ]] && warn "double allocation in [/dev/shm|/tmp]/confinedrv-loops: ${partsrc[srcno]}:${pasrtaccess[srcno]} ~ ${partloop[srcno]} $loopdev"
	partloop[srcno]=$loopdev
      fi
    done
  done <$shm/confinedrv-loops;
  mylockfile-remove $shm/confinedrv-loops.lock
  trap - EXIT
  [[ verbose -gt 1 ]] && echo "loop devices found: $loop $rolp" >&2
}

initloops() { local partno=$1;
  local transient_loop transient_rolp thisloop thisaccess thiserr partmarked; declare -a partmarked;
  [[ -n "$rolp" || -n "$loop" ]] && { err "internal error: ${rolp:+ro-}loopdev already initialized."; exit 244; }

  getloops "$org";  # gets $rolp and $loop

  mkdir -p $shm
  if [[ partno -gt 0 ]]; then
    # loop=$(getoneloop "$org" rw)
    if [[ -z "$loop" ]]; then
      transient_loop=$(losetup -f) || { err "all loop devices in use (see --loopusage)."; exit 202; }
      losetup $transient_loop $org
    fi
  else unset loop;
  fi

  #rolp=$(getoneloop "$org" ro)
  if [[ -z "$rolp" ]]; then
    transient_rolp=$(losetup -f) || { [[ -n "$transient_loop" ]] && losetup -d $transient_loop; err "all loop devices in use (see --loopusage)."; exit 202; }
    losetup -r $transient_rolp $org
    blockdev --setro $transient_rolp
    chmod gua-w $transient_rolp >&9 2>&1 
  fi

  let thiserr=0;
  for srcno in ${!partsrc[@]}; do
    if [[ -z "${partloop[srcno]}" ]]; then
      # same file used twice?
      for src2no in ${!partsrc[@]}; do
	if [[ "${partsrc[srcno]}" = "${partsrc[src2no]}" && "${partoffset[srcno]}" = "${partoffset[src2no]}" && "${partaccess[srcno]}" = "${partaccess[src2no]}" && -n "${partloop[src2no]}" ]]; then
	  partloop[srcno]="${partloop[src2no]}"; partmarked[srcno]=yes;
	  break;
	fi
      done
      if [[ -z "${partloop[srcno]}" ]]; then
	thisloop=$(losetup -f) || { err "all loop devices in use (see --loopusage)."; let thiserr=202; break; }
	parttransloop[srcno]=$thisloop
	thisaccess="${partaccess[srcno]}"
	if [[ "$thisaccess" = "rw" ]]; then
	  losetup --offset ${partoffset[srcno]} $thisloop ${partsrc[srcno]}
	elif [[ "$thisaccess" = "ro" ]]; then
	  losetup -r --offset ${partoffset[srcno]} $thisloop ${partsrc[srcno]}
	  blockdev --setro $thisloop
	  chmod gua-w $thisloop >&9 2>&1
	else err "unknown access rights for external partition image: $dest$srcno=${partsrc[srcno]}:$thisaccess."; unset parttransloop[srcno]; thiserr=244;
	fi
      fi
    fi
  done
  if [[ thiserr -gt 0 ]]; then
    [[ -n "$transient_loop" ]] && losetup -d $transient_loop;  losetup -d "$transient_rolp";
    for thisloop in ${parttransloop[@]}; do losetup -d $thisloop; done
    echo >&2; exit $thiserr ; 
  fi

  if [ -b /dev/mapper/$dest ]; then
    dmsetup remove $dest
    droploop $dest $loop $rolp ${partloop[@]}; 
    reloading=yes
  fi

  mylockfile-create $shm/confinedrv-loops.lock || err "error locking [/dev/shm|/tmp]/confinedrv-loops."
  trap '' SIGQUIT SIGINT SIGTERM;    # ignore signal
  : ${rolp:="$transient_rolp"} ${loop:="$transient_loop"};
  { [ -n "$loop" ] && echo "$dest $loop $org rw 0";
                      echo "$dest $rolp $org ro 0"; 
    for srcno in ${!partsrc[@]}; do
      [[ -n "${partmarked[srcno]}" ]] && continue
      : ${partloop[srcno]:="${parttransloop[srcno]}"}
      echo "$dest ${partloop[srcno]} ${partsrc[srcno]} ${partaccess[srcno]} ${partoffset[srcno]}";
      unset parttransloop[srcno];
    done
  } >>$shm/confinedrv-loops
  mylockfile-remove $shm/confinedrv-loops.lock
  trap - SIGQUIT SIGINT SIGTERM;
  unset transient_rolp transient_loop

}

# device mapper can only control access in chunks of pagesize = 4096 = 2**3*512

PAGE_SIZE=$(getconf PAGE_SIZE)
[[ PAGE_SIZE -eq 0 ]] && { warn "assuming page size of 4096."; PAGE_SIZE=4096; }
[[ PAGE_SIZE/512*512 -ne PAGE_SIZE ]] && { err "page size not divisable by 512; exiting."; exit 222; }
let PGsize=PAGE_SIZE/512
let PGspare=PGsize-1
possible_page_sizes=(4096 8192 16384 32768 65536);

IssuePartRegion() { # access start end
  local partno=$1 access=$2 start=$3 end=$4 length; let length=end-start
  [[ access -lt -1 ]] && return
  if [[ partno -ge 0 ]]; then echo "$start $length linear ${partloop[partno]} 0" >&8
  elif [[ access -ge 2 ]]; then echo "$start $length linear $loop $start" >&8
  elif [[ access -eq 1 ]]; then echo "$start $length linear $rolp $start" >&8
  elif [[ access -eq 0 ]]; then echo "$start $length zero" >&8
  else echo "$start $length error" >&8
  fi
}

roundup() { echo  $(( ($1+PGspare) / PGsize * PGsize )); }
rounddown() { echo  $(( ($1) / PGsize * PGsize )); }
min() { if [[ $1 -le $2 ]]; then echo $1; else echo $2; fi }
max() { if [[ $1 -ge $2 ]]; then echo $1; else echo $2; fi }
acc2str() { if [[ $1 -ge 2 ]]; then echo "rw"; elif [[ $1 -eq 1 ]]; then echo "r"; elif [[ $1 -eq 0 ]]; then echo "0"; else echo "-"; fi }

NotePartRegion() {
  local partno=$1 access=$2 start=$3 end=$4
  [[ start -le end ]] || return
  [[ prevend -eq start ]] || { err "internal error in NotePartRegion ($prevstart-$prevend, $start-$end+1)"; exit 244; }
  #echo "{$prevacc,$prevstart,$prevend}"
  #echo "<$access,$start,$end>"
  [[ partno -le -1 || -z "${partsrc[partno]}" ]] && partno=-1;   # partno indicates non-mirrored space from an external partition file
  if [[ prevacc -eq access && prevpartno -eq partno ]]; then let prevend=end+1; return
  elif [[ prevacc -gt access || partno -ge 0 ]]; then
    let start=$(roundup prevend)
    [[ partno -ge 0 && ${partoffset[partno]} != $(((start-prevend)*512)) ]] && { err "internal error: did offset ${partsrc[partno]} with ${partoffset[partno]} though an offset of $((start-prevend)) would have been required."; exit 244; }
    if [[ prevpartno -ge 0 && start -ne prevend ]]; then
      let intermedstart=$(rounddown prevend)
      IssuePartRegion $prevpartno $prevacc $prevstart $intermedstart
      IssuePartRegion -1 $prevacc $intermedstart $start
    else
      IssuePartRegion $prevpartno $prevacc $prevstart $start
    fi
    [[ prevend -lt start && verbose -gt 1 ]] && warn "warning: access right extension $(acc2str $access)->$(acc2str $prevacc) $prevend..$((start-1))"
  else # prevacc -lt access
    let start=$(rounddown prevend)
    [[ partno -le -1 ]] || { err "internal error at $LINENO."; exit 244; }
    IssuePartRegion -1 $prevacc $prevstart $start
    [[ start -lt prevend && verbose -gt 1 ]] && warn "warning: access right extension $(acc2str $prevacc)->$(acc2str $access) $start..$(($prevend-1))"
  fi
  let prevstart=start prevend=end+1 prevacc=access prevpartno=partno;
}

# global pos extstart extend

NoteExtendedPartition() {
  let extstart=$(rounddown $1) extend=$(roundup $(($2+1)))
}

skip() { local start=$1 extpos dstart;
  [[ pos -le start ]] || { err "trying to skip backwards from $start back to $pos"; exit 244; }
  if [[ extstart -lt extend ]]; then
    let extpos=$(min $(max $pos $extstart) $start);	# extpos ... what we have to skip from $pos until we reach the extended partition header or the $start of the partition $1 before
    let dstart=$(max $(min $start $extend) $extpos);	# 'diminished start position' ... $start of partition inside extended partition or end of the extended partition, but not before $extpos
    # also: let dstart=$(min $start $extend)  + never skip in backward direction: max $extpos (the following command will not condone it; relevant iff $1=$start after $extend)
    NotePartRegion -1 $gapmode $pos $((extpos-1));	# ... skipping before extended partition
    NotePartRegion -1 1 $extpos $((dstart-1));	# ... extended partition header, intermediate space in ext. part or whole ext. part. if it has no sub-partitions
    NotePartRegion -1 $gapmode $dstart $((start-1));	# ... skip for what comes after the extended partition
    # if we wanted to verify that the previous commands should work under any condition:
    #  $start is before extended partition: pos-extpos = pos-start: $gap,  extpos = start = dstart   =>   extpos-dstart: zerolen,  dstart-start: zerolen
    #  $start is inside extended partition: extpos = max(pos,exstart), dstart = max(pos,start)   =>   pos-extpos: $gap,  extpos-dstart: readable,  dstart-start: zerolen
    #  $start is after extended partition: extpos = max(pos,extstart), dstart = max(pos,extend)   =>  pos-extpos: $gap,  extpos-dstart: readable,  dstart-start: $gap
    #  annot: $start is after extended partition and extended partition unmarked: before ext. part: $gap, rest of ext.part gets readable, then skips with $gap
    #  annot: $start is after extended partition and extended partition already marked: extpos = dstart = end of ext.part: simply skipping with $gap
  else
    NotePartRegion -1 $gapmode $pos $((start-1))
  fi
}

prn_drvtable() {
  while read winstart winend mode dev devstart; do
    if [ "$dev" = "$loop" ]; then dev="(${org#/dev/}:rw)"
    elif [ "$dev" = "$rolp" ]; then dev="(${org#/dev/}:ro)"
    fi
    if [[ winstart -eq devstart ]]; then echo "$winstart $winend $mode $dev";
    else echo "$winstart $winend $mode $dev $devstart"; fi
  done <"$drvtable"
}

cleanup() { local thisloop;
  sleep 0.3; rm -f "$drvtable"; [[ -n "$transient_rolp" ]] && losetup -d $transient_rolp; unset rolp; [[ -n "$transient_loop" ]] && losetup -d $transient_loop; unset loop;
  for thisloop in ${parttransloop[@]}; do loestup -d $thisloop; done; unset parttransloop
}

setupdevice() {
  let partnum=${#parts_w[@]}+${#parts_r[@]}+${#parts_z[@]}+${#parts_e[@]}

  unset loop rolp
  initloops ${#parts_w[@]}

  trap "cleanup" EXIT
  drvtable="$(mktemp confinedrv-XXXX-XXXX.drvtable)"; 
  pos=0; prevacc=-2; prevpartno=-1; prevstart=-1; prevend=0; extstart=-1; extend=-2; extwritable=false; parttbl=""; found=0; maxdno=0;  #max=0;
  {
  while read dno start end blocks parttype fsandflags; do
    if [[ -n "${dno##[0-9]*}" || -z "$dno$start$end$blocks$id$typeandflags" ]]; then 
      if [[ "${dno,,}" = "partition" && "${start,,}" = "table:" ]]; then
	parttbl="${end,,}";
	! ein "$parttbl" gpt msdos && warn "warning: confinedrv has never been tested with the $parttbl partition table type (assuming like gpt).";
      fi
      # upcase="${start^^} ${end^^} ${blocks^^} ${id^^} ${type^^}"; 
      # if [ "${upcase##*GPT}" != "${upcase}" ]; then isgpt=true; warn "warning: program was not sufficiently tested with gpt disks.";
      # elif [ "${upcase#WARN}" != "$upcase" ]; then warn "fdisk -lu: $start $end $blocks $id $type"
      # fi
    else
      [[ dno -gt maxdno ]] && let maxdno=dno
      if [[ -z "$parttbl" ]]; then warn "warning: parted did not seem to list the type of partition table: assuming msdos."; parttbl="msdos"; fi
      start="${start%s}"; end="${end%s}"; type="${parttype,,}"; # echo "$start $end $type";

      if [[ pos -eq 0 ]]; then
	NotePartRegion 0 $mbrmode 0 $start
	[[ -n ${partsrc[0]} ]] && let found++;
	let pos=start prevend=start
      fi
      #echo "$dev $pos $start" >&2

      if [[ "${type#*erweitert}" != "$type" || "${type#*extended}" != "$type"  ]];     # only very old versions of parted may also output in German
	then isext=true; [[ "$parttbl" != "msdos" ]] && { err "error: extended partitions only supported with dos partition tables."; exit 208; }
	else isext=false; 
      fi

      if $extwritable && [[ extstart -le start && end -lt extend ]];
	then continue; fi

      if ein $dno "${parts_w[@]}"; then
	skip $start
	NotePartRegion $dno 2 $start $end
	let pos=end+1; let found++;
	$isext && { extwritable=true; NoteExtendedPartition $start $end; }

      elif ein $dno "${parts_r[@]}"; then
	skip $start
	NotePartRegion $dno 1 $start $end
	let pos=end+1; let found++;
	$isext && { extwritable=true; NoteExtendedPartition $start $end; }

      elif ein $dno "${parts_z[@]}"; then
	skip $start
	NotePartRegion $dno 0 $start $end
	let pos=end+1; let found++;
	$isext && { extwritable=true; NoteExtendedPartition $start $end; }

      elif ein $dno "${parts_e[@]}"; then
	skip $start
	NotePartRegion $dno -1 $start $end
	let pos=end+1; let found++;
	$isext && { extwritable=true; NoteExtendedPartition $start $end; }

      elif $isext; then
	NoteExtendedPartition $start $end

      elif $readall; then
	skip $start
	NotePartRegion $dno 1 $start $end
	let pos=end+1;

      else
	skip $start
	NotePartRegion $dno 0 $start $end
	let pos=end+1;

      fi
      #[[ max -lt end ]] && let max=end
      #[[ min -gt start ]] && let min=start
    fi
  done < <( LANG=en_US.UTF-8 parted $org <<<$'unit s\nprint\nquit\n' | sort -g -k 2 -s; ) 
  # done < <( fdisk -lu -b 512 $org | tr '*' ' ' | sort -g -k 2 -s; ) 
  max=$(blockdev --getsz $org)
  if [[ "$parttbl" = "msdos" ]]; then
    skip $max
  else
    # alternative recovery partition table at the end (not included in mbr=image)
    NotePartRegion -1 $mbrmode $pos $(($max-1))
  fi
  virtualdiskend=$(roundup $prevend); if [[ prevpartno -lt 0 || -z "${partsrc[prevpartno]}" ]]; then prevpartno=-1; 
    else cat $drvtable; echo "$prevpartno ${partsrc[prevpartno]}"; err "internal error: expected having switched back to the original source disk at the end."; exit 244; 
  fi
  IssuePartRegion $prevpartno $prevacc $prevstart $virtualdiskend

  } 8>"$drvtable"

  if [[ maxdno -ge 5 && extpend -lt 0 ]] && [[ "$parttbl" = "msdos" ]]; then
    err "extended partitions given as parameter but no extended partition called '...erweitert' or '...extended' found/listed by parted 'print'";
    echo "you may wish to correct the matching for the name of the exteneded partition in the sources" >&2;
    err "as this is used as msdos disk the mapping will not function for extended partitions."
    echo >&2;
  fi

  if [[ verbose -gt 1 ]]; then
    msg "hdd mapping table:"
    prn_drvtable
    echo
  fi


  if [[ found -ne partnum ]]; then
    err "overlapping partitions specified (extended partition and partition inside extended partition) or";  
    err "no such partition on disk." # can detect overlapping partitions only in cases where partitions become fully overlapped
    msg "$found out of $partnum partitions processed.\n"
    [[ verbose -le 1 ]] && { prn_drvtable; echo >&2; }
    cleanup
    trap '' EXIT
    ret=207
    
  else
   if [ -b /dev/mapper/$dest ]; then 
     echo dmsetup reload $dest \<"$drvtable"
     #dmsetup reload $dest <"$drvtable";
     dmsetup remove $dest
     # droploop "$dest";	# should have allocated the same loops for new device: never drop
     dmsetup create $dest <"$drvtable"
   else 
     if [[ -n "$reloading" ]]; then echo dmsetup reload $dest \<"$drvtable"
     else echo dmsetup create $dest \<"$drvtable"
     fi
     dmsetup create $dest <"$drvtable"
   fi
   ret=$?;
   #if [ $? -eq 0 ]; then rm "$drvtable"; else echo "please remove $drvtable manually!"; fi
   if [ $ret -ne 0 ]; then
     cat $drvtable
     cleanup; trap '' EXIT;
     droploop "$dest";
   else
     rm "$drvtable"
   fi
   trap '' EXIT
   echo
   
  fi
  return $ret
  #[[ ret -gt 0 ]] && exit $ret
}

initpartsrc() { local partsrcc err offset filesize partsize dno start end blocks parttype fsandflags;
  let partsrcc=0 err=0;
  for srcno in ${!partsrc[@]}; do
    if ein $srcno ${parts_w[@]}; then partaccess[srcno]="rw"; let partsrcc++;
    elif ein $srcno ${parts_r[@]}; then partaccess[srcno]="ro"; let partsrcc++;
    elif ein $srcno ${parts_z[@]} ${parts_e[@]}; then unset partsrc[srcno];
    else partaccess[srcno]="rw"; parts_w+=($srcno); let partsrcc++;
    fi
  done
  if [[ partsrcc -gt 0 ]]; then
    while read dno start end blocks parttype fsandflags; do
      if [[ -z "${dno##[0-9]*}" && -n "$dno$start$end$blocks" ]]; then 
	[[ "${start%s}" = "$start" || "${end%s}" = "$end" ]] && { err "internal error: parted / unit s did not seem to output by sector."; exit 244; }
	let start=${start%s} end=${end%s};
	if [[ -n "${partsrc[dno]}" ]]; then
	  let offset=($(roundup $start)-start)*512;       # ?? stimmt roundup hier? - wird es nicht abgerundet, i.e. größere Zugriffsrechte gewinnen?
	  let filesize=$(stat -c%s "${partsrc[dno]}");   # Problem: das würde nicht gehen, da er sonst vor dem Image mit dem Einblenden beginnen müßte oder den Beginn verliert
	  let partsize=(end-start+1)*512
	  [[ filesize -ne partsize ]] && { err "file size $filesize of ${partsrc[dno]} does not match partition size $partsize."; let err=223; }
	  let partoffset[dno]=offset
	  [[ offset -ne 0 && verbose -gt 0 ]] && warn "$org$dno: ${partsrc[dno]} - Will have to accept offset of $offset for disk image; file system there may thus be unreadable!";
	fi
	# new and independent if clause: handle a redefined MBR
	if [[ dno -eq 1 && -n "${partsrc[0]}" ]]; then
	  let filesize=$(stat -c%s "${partsrc[0]}");
	  let partsize=(start)*512
	  let sizeok=1
	  for allowed_multiple in 1 $possible_page_sizes; do
	    [[ filesize -eq $(( ( partsize + allowed_multiple - 1 ) / allowed_multiple * allowed_multiple )) ]] && { let sizeok=0; break; }
	  done
	  [[ sizeok -ne 0 ]] && { err "file size $filesize for MBR-file does not match the size of the empty space before the first partition ($partsize) and was not rounded up to an allowed page size multiple ($possible_page_sizes) either."; let err=223; }
	  let partoffset[0]=offset
	fi
      fi
    done < <( parted $org <<<$'unit s\nprint\nquit\n' ) 

    for srcno in ${!partsrc[@]}; do
      if [[ -z "${partoffset[srcno]}" ]]; then
	err "partition $org$srcno for ${partsrc[srcno]} does not exist.";
	unset partaccess[srcno] partsrc[srcno] partoffset[srcno];
	let err=204;
      fi
    done
  fi
  [[ err -gt 0 ]] && { echo >&2; exit $err; }
}

initdest() {
  if [[ verbose -gt 1 ]]; then
    echo "dest - $dest: ${!partsrc[*]} / ${partsrc[*]}";  # indices list - content list: partitions laoded from file
    echo "org - $org e:${parts_e[@]}/z:${parts_z[@]}/r:${parts_r[@]}/w:${parts_w[@]}"
    echo "  (groups: ${groups[@]})"
    echo; #exit
  fi

  [[ -z "$org" ]] && { echo "usage: confinedrv newdevice=olddevice or newdevice=olddevice1,2,3 respectively (see --help)." >&2; echo >&2; exit 201; }
  [[ "${org#./}" = "${org#../}" ]] && { [ -e "/dev/$org" ] && org="/dev/$org"; }
  [ -e "$org" ] || { err "device $org not found."; echo >&2; exit 203; }
  if [ -e "/dev/$dest" ]; then
    if [[ "${dest%[0-9]}" = "${dest}" ]]; then
      warn "newly created device /dev/mapper/$dest has a similar name as a hard drive which already exists (/dev/$dest); please avoid this."; 
    else
      err "device /dev/mapper/$dest has a similar name as a hard drive which already exists (/dev/$dest); please avoid this."; 
      echo "confinedrv thought that this was a destination argument for a drive to be set up." >&2;
      echo "However destination arguments (like /dev/mapper/sdx) do not use to end with a number under Linux." >&2
      echo "did you mean that /dev/$dest was a source argument rather than a destination argument?" >&2
      echo "make sure you write something like ${prevdest:-sdz}=sda1,2,3 ... ${prevdest:-sdz}2=./mypartitionfile to fade in a partition image from an external source (left side arguments for external image sources must be a praefix of left side at device setup.)." >&2;
      echo >&2; exit 201;
    fi
  fi
  if [[ 0 -eq $(( ${#parts_e[@]} + ${#parts_z[@]} + ${#parts_r[@]} + ${#parts_w[@]} )) ]]; then
    err "drives without a single partition specifier are not allowed.";
    echo "did you mean $dest=$org:r1,2,3:w4,5,6;" >&2;
    echo "or did you mean ${prevdest:-sdz}=./${org#./} (partition image source argument) where '${prevdest:-sdz}' is a/the drive to be set up (being mentioned right before the partition image source argument)." >&2;
    echo >&2; exit 201;
  fi

  #echo initpartsrc
  initpartsrc;
  #echo initpartsrc done.

  # echo $dest#$org#${#parts[@]}
  # echo ${parts[@]}
  # echo ----------; exit
  prevdest="$dest"; # echo "prevdest: $prevdest";
}

processparams() {
  [[ verbose -gt 0 ]] && echo $'\e[;33mconfinedrv - visit us on www.elstel.org/qemu\e[0m' >&2;
  #
  # sdx=sda4,5:r2,3:w4,5:e6:z7,8,9	# note: with bash 4.2.37: ${aa%%[0-9]*} yields the same as ${aa%%[0-9,]*} !! 
  #
  local dest0 org0 destt groups dest_first isdir srcpartfile orgg 
  unset dest org parts_w parts_r parts_z parts_e partsrc partloop parttransloop partoffset partacccess 
  declare -a parts_w parts_r parts_z parts_e partsrc partloop parttransloop partoffset partacccess 

  while [[ $# -gt 0 ]]; do

    #
    # confinedrv sdy=sdb:r3:w5,6 sdy5=/media/esatahdd/partitionimages/sdb5.part
    # i.e. if destt[0] is an individual partition (praefixed by $dest) rather than a whole drive then it will be the assignment of a partition to a file name
    #     if so:       destt will be an array of partition numbers (with the $dest-praefix removed from the first of them)
    #     otherwise:   destt will be empty (unset destt)
    # new in v1.7.7: confinedrv sdy=sdb:r3:w5,6 mbr=/boot/my.mbr (no virtual drive must start with the letters 'mbr' though)
    #
    read dest0 org0 <<<"${1/=/ }"
    read -a destt <<<"${dest0//,/ }"
    dest_first="${destt[0]#$dest}"; if [[ ${#dest_first} -ne ${#destt[0]} || -n "$dest" && "$dest_first" = "mbr" ]]; then destt[0]="$dest_first"; else 
      if [[ ${#destt[@]} -gt 1 ]]; then
	err "'$dest0': praefix does not match the current drive '$dest'"; echo >&2; 
	exit 201;
      fi
      unset destt; 
    fi

    #
    # part1=~/file ... or ... mbr=~/file
    #
    if [[ ${#destt[@]} -ge 1 ]]; then
      let err=0;
      [ -e "$org0" ] || { err "partition from external source: no such file, device or directory: $org0"; err=1; }
      if  [ -d "$org0" ]; then 
	isdir=1; org0="${org0%/}"; 
	orgg="$( awk '/[Dd][Ii][Ss][Kk]/{ $0=$2; gsub(/^[/]dev[/]/,""); gsub(":$",""); print; }' "$org0/unitsprint.parted" )";
	[[ -z "$orgg" ]] && { err "partition index file $org0/unitsprint.parted seems to be corrupt (does not contain a Disk-line) or does not exist at all."; }
      else 
	isdir=0; 
      fi
      for destpart in "${destt[@]}"; do
	if [[ -z "${destpart##[0-9]*}" && -n "${destpart##0*}" || "$destpart" = "mbr" ]]; then
	  [[ "$destpart" = "mbr" ]] && let destpart=0
	  if [[ isdir -eq 0 ]]; then 
	    partsrc[$destpart]="$org0"; 
	  else
	    if [[ destpart -eq 0 ]]; then srcpartfile="$org0/$orgg.mbr";
	    else srcpartfile="$org0/$orgg$destpart.part";
	    fi
	    [ -e "$srcpartfile" ] && ! [ -d  "$srcpartfile" ]; [[ $? -eq 0 ]] || { err "either $srcpartfile does not exist or it is a directory: expected partition image file."; err=1; }
	    partsrc[$destpart]="$srcpartfile"
	  fi
	else err "partition from external source: invalid destination partition number: '$destpart'"; err=1;
	fi
      done
      [[ err -gt 0 ]] && { echo >&2; exit 201; }

    else
      #
      # if multiple drives are set up within the same command line i.e. sdx=... sdy=... perform the setup now before changing to a new virtual target drive
      #
      if [[ -n "$dest" ]]; then
	initdest
	setupdevice;
	unset dest org parts_w parts_r parts_z parts_e groups partsrc partloop parttransloop partoffset partacccess 
	declare -a parts_w parts_r parts_z parts_e partsrc partloop parttransloop partoffset partacccess 

      fi	

      dest="$dest0"; org="$org0";
      [[ "${dest#mbr}" != "$dest" ]] && { err "no virtual drive is allowed to start with 'mbr'; use drive names like sdx/sdy/sdz.\n"; exit 201; }

      read -a groups <<<"${org//:/ }"
      org=${groups[0]%%[0-9,]*}; groups[0]=${groups[0]#$org};  # now: can be sth. like "sda1,3" or "sda:r1,3:z2" (iff first group empty then move last group into first position)
      [[ -z "${groups[0]}" ]] && { let ng=${#groups[@]}; groups[0]="${groups[ng-1]}"; unset groups[ng-1]; }  
      for group in "${groups[@]}"; do
	if [[ "${group##[0-9]*}" != "$group" ]]; then
	  typ='w';
	else typ="${group:0:1}"; group="${group:1}";
	fi
	ein "$typ" e z r w || { err "access rights for partitions must be one of r/w/z/e."; echo >&2; exit 201; }
	targetvar=parts_$typ[0]; [[ -n "${!targetvar}" ]] && { err "error: already specified: $typ-access rights for $org ($group, ${!targetvar}, ... )."; echo >&2; exit 201; }
	read -a parts_$typ <<<"${group//,/ }"
	# parts=($(sort -g <<<"${parts[@]}";))
	err=false
	for p in ${group//,/ }; do
	  [[ -n "${p##[0-9]*}" || -z "${p##0*}" ]] && { err=true; err "invalid partition number: $p.";echo >&2; }
	done
	$err && exit 201;
      done

    fi

    shift

  done;

  if [[ -n "$dest" ]]; then
    initdest
    setupdevice;
    unset dest org parts_w parts_r parts_z parts_e groups partsrc partloop parttransloop partoffset partacccess 

  fi	

}

let readout_mbr=0;

while [[ $# -gt 0 ]]; do
case "$1" in
  --version) echo "confinedrv 1.7.7"; echo;;
  -h|--help) echo -e "confinedrv [--ra] sdx=sda1,2,3,4 sdy=sdb1,2,3,4"
             echo -e "confinedrv [--ra] sdx=sda1,2,3 mbr=./myfile.mbr sdx1=./image1.part"
             echo -e "confinedrv [--ra] sdx=sda:r1,2:w3:z4:e7 sdy=sdb:w3"
             echo -e "  --ra ... everything at least readable; default is zeroing out."
	     echo -e "  --mbr err/zero/ro/rw ... master partition table & boot record access rights (std:ro)"
	     echo -e "  --gap err/zero/ro/rw ... access rights for gaps (std:zero)"
	     echo -e "confinedrv -d/-r/--remove sdx sdy ...";
	     echo -e "confinedrv [--noannot] -i/--info sdx sdy ... show annotated drive mapping table";
	     echo -e "confinedrv --loopusage [sdx sdy] ... show all loop devices used for sdx.";
	     echo -e "           -v/--verbose/-q/--quiet: show mapping table / do not show alignement warnings"
	     echo -e "confinedrv readout-mbr --from [/dev/]sda --into myfile.mbr (actually reads a bit more than the MBR)\n"
	     ;;
  -d|-r|--remove) while [ -n "$2" ]; do
		    drv="${2#/dev/mapper/}"
		    dmsetup remove "$drv"
		    droploop "$drv"; [[ $? -ne 0 ]] && let ret=206
		    shift
		  done; 
		  exit $ret;;

  -i|--info) check4parted;
             while [ -n "$2" ]; do
	       echo "*** $2 ***"; unset slurped disk endofdisk ${!loop_*};
	       while read winstart winlen mode dev devstart; do
		 loopno="${dev#7:}";   # '7:' - is the major number of all loop devices
		 if [[ "$loopno" != "$dev" ]]; then
		   devar="loop_${loopno}_ident"; devident=${!devar};
		   devar="loop_${loopno}"; basedev=${!devar}; 
		   if [[ -z "$devident" ]]; then
		     read loopdev data basedev < <( losetup /dev/loop${loopno}; ); 
		     basedev="${basedev#(}"; basedev="${basedev%)}";
		     [[ -z "$basedev" ]] && basedev=/dev/loop$loopno
		     isro="$(blockdev --getro /dev/loop$loopno)";
		     if [[ $isro -eq 0 ]]; then devident="(${basedev}:rw)"; else devident="(${basedev}:ro)"; fi
		     export $devar="$basedev"; export ${devar}_ident="$devident";
		   fi
		 else unset devident;
		 fi
		 if ! $annotate; then
		   if [[ winstart -eq devstart ]]; then echo "$winstart $winlen $mode $devident";
		   else echo "$winstart $winlen $mode $devident $devstart"; fi
		 else
		   annotdev=$(awk '{ gsub(/[^A-Za-z0-9]/,"_"); print; }' <<<"$basedev");
		   # haspartitions: simply takes all /dev/sda which are not a subpartitions for annotation
		   let haspartitions=1; [[ "${basedev:0:5}" = "/dev/" && "${basedev%[0-9]}" = "$basedev" ]] && let haspartitions=0;
		   if [[ haspartitions -eq 0 ]] && ! ein $annotdev $slurped; then 
		     slurpdev $annotdev "$basedev"
		     slurped="$annotdev $slurped"
		   fi
		 fi
	       done < <( dmsetup table $2; ) 
	       if $annotate; then
		 while read winstart winlen mode dev devstart; do
		   loopno="${dev#7:}";   # '7:' - is the major number of all loop devices
		   if [[ "$loopno" != "$dev" ]]; then
		     devar="loop_${loopno}_ident"; devident=${!devar};
		     devar="loop_${loopno}"; basedev=${!devar}; 
		   else
		     devident="$dev"; basedev="$dev";
		   fi
		   let winend=winstart+winlen
		   for annotorgdev in $slurped; do
		     eodvar=${annotorgdev}_endofdisk; let endofdisk=${!eodvar}
		     annotstart=${annotorgdev}_start_${winstart}; if [[ -n "${!annotstart}" ]]; then annotstart="${!annotstart}"; else 
		       annotstart=${annotorgdev}_end_${winstart}; if [[ -n "${!annotstart}" ]]; then annotstart="${!annotstart}";
		     else annotstart="$winstart"; fi; fi
		     annotend=${annotorgdev}_end_${winend}; if [[ -n "${!annotend}" ]]; then annotend="${!annotend}\t"; else
		     annotend=${annotorgdev}_start_${winend}; if [[ -n "${!annotend}" ]]; then annotend="${!annotend}"; [[ winlen -le 2048 ]] && annotend="${annotend}=(+)$winlen"
		     elif [[ $winend -eq $endofdisk ]]; then annotend="endofdisk(${annotorgdev##*_})";
		     else annotend="(+)$winlen\t"; fi; fi
		   done;
		   if [[ winstart -eq devstart ]]; then echo -e "$annotstart\t$annotend   $mode\t$devident";
		   else echo -e "$annotstart\t$annotend   $mode\t$devident\t$devstart"; fi
		 done < <( dmsetup table $2; ) 
	       fi
	       echo; shift;
	     done;
	     exit 0;;

  --loopusage) if [ -e $shm/confinedrv-loops.lock -o -e $shm/confinedrv-loops ]; then 
		  if mylockfile-create $shm/confinedrv-loops.lock; then didlock=true; else didlock=false; err "error: could not lock [/dev/shm|/tmp]/confinedrv-loops"; fi
		  if [[ $# -le 1 ]];
		  then cat $shm/confinedrv-loops
		  else shift; ( IFS="|"; egrep "$*" $shm/confinedrv-loops; )
		  fi
		  $didlock && mylockfile-remove $shm/confinedrv-loops.lock 
               else echo "no loop devices in use by confinedrv." >&2; 
	       fi; echo >&2; 
	       exit 0;;

  --freeloop) let ret=0; while [ -n "$2" ]; do droploop "$2" || let ret=206; shift; done; exit $ret;;

  --ra) readall=true;;
  --gap) case "$2" in
           zero|z|0) gapmode=0;; error|err|e) gapmode=-1;; read|ro|r|1) gapmode=1;; write|read-write|readwrite|w|rw|2) gapmode=2;;
         esac; shift;;
  --mbr) case "$2" in
           zero|z|0) mbrmode=0;; error|err|e) mbrmode=-1;; read|ro|r|1) mbrmode=1;; write|read-write|readwrite|w|rw|2) mbrmode=2;;
         esac; shift;;
  -v|--verbose) let verbose++;;
  -q|--quiet) let verbose--;;
  --noannot|--no-annot) annotate=false;;
  --license) license;;

  --into) backup_mbr_file="$2";
          [[ -e "$backup_mbr_file" ]] && { err "error: file does already exist: '$backup_mbr_file'"; echo >&2; exit 210;  }
          shift;;
  --from) if [[ -e "$2" ]]; then read_mbr_from="$2"; 
          elif [[ -e "/dev/$2" ]]; then  read_mbr_from="/dev/$2"; 
	  else err "error: device not found: $2"; echo >&2; exit 211; 
	  fi
          shift;;
  -*) err "unknown option $1"; echo >&2; exit 201;; 

  readout-mbr) let readout_mbr=1;;

  *)  [[ readout_mbr -eq 1 ]] && { err "superfluous parameters for readout-mbr: $*"; echo >&2; exit 201; }
      [[ -n "$backup_mbr_file" || -n "$read_mbr_from" ]]  && { err "use --from and --into only together with readout-mbr."; echo >&2; exit 201; }
      [[ $(id -u) -ne 0 ]] && { err "confinedrv must be run as root.";echo >&2; exit 200; }
      check4parted;
      processparams "$@"
      shift $#

;;
esac;
shift;
done

if [[ readout_mbr -eq 1 ]]; then
  [[ -z "$backup_mbr_file" ]]  && { err "use --into to specify the backup mbr file."; echo >&2; exit 201; }
  [[ -z "$read_mbr_from" ]]  && { err "use --from to specify device to read MBR from."; echo >&2; exit 201; }
  echo "reading out spare space before first partition including the MBR from $read_mbr_from:" >&2
  check4parted;
  let length=0;
  while read dno start end blocks parttype fsandflags; do
    if [[ "$dno" = "1" ]]; then 
      [[ "${start%s}" = "$start" || "${end%s}" = "$end" ]] && { err "internal error: parted / unit s did not seem to output by sector."; exit 244; }
      let sectors_indeed=${start%s}; let read_sectors=$(roundup $sectors_indeed);
      let count=read_sectors/PGsize;
      break;
    fi
  done < <( parted $read_mbr_from <<<$'unit s\nprint\nquit\n' ) 
  [[ sectors_indeed -eq 0 ]] && { err "error by parted apparently caused by insufficient permissions." >&2; echo >&2; exit 200; }
  if [[ read_sectors -ne sectors_indeed ]]; then
    echo "reading $count * $PAGE_SIZE bytes which is a little bit more than the unused space before the first partition ($read_sectors * 512) ..." >&2;
  else
    echo "reading $count * $PAGE_SIZE bytes ..." >&2;
  fi
  echo "dd if=$read_mbr_from bs=$PAGE_SIZE count=$count of=$backup_mbr_file"
  dd if=$read_mbr_from bs=$PAGE_SIZE count=$count of=$backup_mbr_file;
  [[ $? -eq 0 ]] && echo "done." >&2;
  echo >&2;
fi

