#!/bin/bash
#
# confinedrv v1.7.11
# (c) copyright by Elmar Stellnberger, the original author: 2009, Jan 2010, Nov&Dez 2014, Aug 2016, Sep 2019, Jan 2020, Mar 2020
#
#  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.11: allow device names which contain numbers like /dev/nvm0e1
#
# v 1.7.10: correctly handle --mbr ro/rw and mbr=
#  * bugfix: partition images were not blended in correctly when they had lower access rights than the following partition
#
# v 1.7.9: do also backup the trailing space after the last partition by default: GPT partition tables
#
# v 1.7.8: support of whole partition table images (Sep 2019)
#  * blanking of mbr (and only of first 440B to boot), testing mbr for being blanked and reinstalling an mbr from file
#
# 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: neither package lockfile-progs containing the lockfile-create/-remove utility nor procmail containing the lockfile program are installed.";
  let lockprog=0;
fi

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

list_partitions() {
  if [[ virtual -eq 0 ]]; then
    LANG=en_US.UTF-8 parted "$1" <<<$'unit s\nprint\nquit\n' | sort -g -k 2 -s; 
  else
    cat "${1%.blocks}.parttbl";
  fi
}

list_partitions_plus_end() {
  if [[ virtual -eq 0 ]]; then
    LANG=en_US.UTF-8 parted "$1" <<<$'unit s\nprint\nquit\n' | sort -g -k 2 -s; 
    if [[ -b "$1" ]]; then 
      let size=$(blockdev --getsz "$1");
    else
      let size=$(stat -c%s "$1");
      let size=(size+511)/512
    fi
    echo "9999 ${size}s $((size-1))s 0 none none";
  else
    cat "${1%.blocks}.parttbl";
  fi
}

slurp_virtfile() {
  local cmdident start end virtstart;
  let virtual=0 virtmax=0;
  while read cmdident start end virtstart; do
    if [[ "$cmdident" = "blocks" ]]; then
      let blocks_start[virtual]=start;
      let blocks_end[virtual]=end
      let blocks_virtstart[virtual]=virtstart
      let virtual+=1
    elif [[ "$cmdident" = "disksize" ]]; then
      let virtmax=start
    fi
  done <"$virtfile.parttbl";
  if [[ virtual -eq 0 ]]; then err "no mapping for $virtfile.blocks found in $virtfile.parttbl"; exit 244; 
  elif [[ virtmax -eq 0 ]]; then err "no disksize directive found in $virtfile.parttbl"; exit 244;
  fi
}

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 244; }
  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 < <( list_partitions "$basedev"; ) 
  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 "${loopusage[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; virtual=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" = "$1" && offset -eq 0 ]]; then 
      if [[ "$access" = "rw" ]]; then 
	[[ -n "$loop" && "$loop" != "$loopdev" ]] && warn "double allocation in [/dev/shm|/tmp]/confinedrv-loops: $1:ro ~ $loop $loopdev"
	export loop="$loopdev"; 
      elif [[ "$access" = "ro" ]]; then 
	[[ -n "$rolp" && "$rolp" != "$loopdev" ]] && warn "double allocation in [/dev/shm|/tmp]/confinedrv-loops: $1: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 virtstart=start
  [[ access -lt -1 ]] && return
  if [[ virtual -gt 0 && partno -lt 0 && access -ge 1 ]]; then
    let virtstart=-1;
    for((i=0;i<virtual;i+=1)); do
      if [[ blocks_start[i] -le start && end -le blocks_end[i] ]]; then
	let virtstart=start-blocks_start[i]+blocks_virtstart[i]
	break;
      fi
    done
    if [[ virtstart -lt 0 ]]; then
      err "required intermediate space at $start not found in partition image";
      exit 244;
    fi
  fi
  if [[ partno -ge 0 ]]; then echo "$start $length linear ${partloop[partno]} 0" >&8
  elif [[ access -ge 2 ]]; then echo "$start $length linear $loop $virtstart" >&8
  elif [[ access -eq 1 ]]; then echo "$start $length linear $rolp $virtstart" >&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 warn_downgraded=0
  [[ 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 intermed_start=$(rounddown prevend)
      if [[ prevacc -ge 2 ]]; then
	let intermed_acc=1; warn "warning: access rights downgraded rw->ro for intermediate space at [$intermed_start..$start["; let warn_downgraded=1
      else
	let intermed_acc=prevacc
      fi
      IssuePartRegion $prevpartno $prevacc $prevstart $intermed_start
      IssuePartRegion -1 $intermed_acc $intermed_start $start
      [[ partno -ge 0 && -n "${partsrc[partno]}" ]] && warn "warning: intermediate space defaults to original disk instead of partition #$partno image ${partsrc[partno]} at [$intermed_start..$start["
    else
      IssuePartRegion $prevpartno $prevacc $prevstart $start
    fi
    [[ prevend -lt start && verbose -gt 1 && warn_downgraded -eq 0 ]] && warn "warning: access right extension $(acc2str $access)->$(acc2str $prevacc) [$prevend..$start["
  else # prevacc -lt access
    let start=$(rounddown prevend)
    [[ partno -le -1 ]] || { err "internal error at $LINENO."; exit 244; }
    IssuePartRegion $prevpartno $prevacc $prevstart $start
    [[ start -lt prevend && verbose -gt 1 ]] && warn "warning: access right extension $(acc2str $prevacc)->$(acc2str $access) [$start..$prevend["
  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[@]}
  [[ -n ${partsrc[0]} ]] && let partnum++;

  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 < <( list_partitions "$org"; ) 
  # done < <( fdisk -lu -b 512 $org | tr '*' ' ' | sort -g -k 2 -s; ) 
  if [[ virtual -eq 0 ]]; then
    let max=$(blockdev --getsz $org)
  else
    let max=virtmax
  fi
  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 && extend -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 (including mbr region) 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 [[ srcno -eq 0 ]]; then
      case $mbrmode in
	1) partaccess[srcno]="ro"; let partsrcc++;;
	0|-1) unset partsrc[0];;
	*) partaccess[srcno]="rw"; let partsrcc++;;   # 2)
      esac;
    else
      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
    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?
	  # Problem: das würde nicht gehen, da er sonst vor dem Image mit dem Einblenden beginnen müßte oder den Beginn verliert
	  if [[ -b "${partsrc[dno]}" ]]; then let filesize=$(blockdev --getsz "${partsrc[dno]}")*512; 
	  else let filesize=$(stat -c%s "${partsrc[dno]}"); 
	  fi
	  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
	  [[ filesize -lt partsize ]] && { err "file size $filesize for MBR-file is shorter than the unpartitioned space before the first partition ($partsize); please extend with zeroes dd if=/dev/zero bs=512 count=$(( (partsize-filesize+511) / 512 )) of=/dev/stdout >>my.mbr"; let err=223; }
	  let partoffset[0]=0
	fi
      fi
    done < <( list_partitions "$org"; ) 

    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

  if [[ -n "$virtfile" ]]; then
    slurp_virtfile
    [[ -n "$org" && "$org" != "$virtfile" ]] && { err "parttbl-file needs to be called like virtual base disk: sdx=sda1,2,3 parttbl=sda.parttbk or use sdx=1,2,3"; exit 244; }
    org="$virtfile.blocks"
  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[@]} )) && -z "$virtfile" ]]; 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" || "$dest_first" = "parttbl" ) ]]; 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;
      if [[ "${destt[0]}" != "parttbl" ]]; then
        [[ -e "$org0" ]] || { err "partition from external source: no such file, device or directory: $org0"; err=1; }
      fi
      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 [[ "$destpart" = "parttbl" ]]; then
	  virtfile="${org0%.xx}";
	  [[ -f "$virtfile.blocks" && -f "$virtfile.parttbl" ]] || { err "either $virtfile.blocks or $virtfile.parttbl do not exist."; err=1; }
	elif [[ -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; }
      [[ "${dest#parttbl}" != "$dest" ]] && { err "no virtual drive is allowed to start with 'parttbl'; use drive names like sdx/sdy/sdz.\n"; exit 201; }

      read -a groups <<<"${org//:/ }"
      if [[ ${#groups[@]} -le 1 ]]; then
        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)
      else
	org="${groups[0]}";
        let ng=${#groups[@]}; groups[0]="${groups[ng-1]}"; unset groups[ng-1];
      fi
      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 usedd=0 skipend=0;

while [[ $# -gt 0 ]]; do
case "$1" in
  --version) echo "confinedrv 1.7.11"; 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 more than the MBR including all the unpartitioned space in front and the primary part of the partition table)"
	     echo -e "confinedrv reinstall-mbr --from myfile.mbr --into /dev/sda ... copy first 440 Byte from file to disk device"
	     echo -e "confinedrv compare-mbr --from myfile.mbr --with [/dev/]sda ... compare myfile.mbr with the beginning of /dev/sda"
	     echo -e "confinedrv blank-mbr --into /dev/sda ... overwrite first 440 bytes with zeroes"
	     echo -e "confinedrv test-mbr-blanked --from [/dev/]sda ... check if first 440 bytes are zero"
	     echo -e "confinedrv readout-parttbl [--usedd] [--skipend] --from [/dev/]sda --into disk-images/sda.xx ... reads partition table into files sda.blocks (binary) and sda.parttbl (text)"
	     echo -e "confinedrv sdx=1,2,3 parttbl=sda.xx sdx1=sda1.part sdx2=sda2.part sdx3=sda3.part ... uses the partition table image files sda.blocks (binary) and sda.parttbl (text)\n"
	     subcommand="";
	     set --
	     ;;
  -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) into="$2";
          shift;;
  --from) if [[ -e "$2" ]]; then from="$2"; 
          elif [[ -e "/dev/$2" ]]; then  from="/dev/$2"; 
	  else err "error: device not found: $2"; echo >&2; exit 211; 
	  fi
          shift;;
  --with) if [[ -e "$2" ]]; then with="$2"; 
          elif [[ -e "/dev/$2" ]]; then  with="/dev/$2"; 
	  else err "error: device not found: $2"; echo >&2; exit 211; 
	  fi
          shift;;
  --usedd) usedd=1;;
  --skipend) skipend=1;;
  -*) err "unknown option $1"; echo >&2; exit 201;; 

  readout-mbr|compare-mbr|reinstall-mbr|blank-mbr|test-mbr-blanked|readout-parttbl) subcommand="$1";;

  *)  [[ readout_mbr -eq 1 ]] && { err "superfluous parameters for readout-mbr: $*"; echo >&2; exit 201; }
      [[ -n "$from" || -n "$into" || -n "$with" ]]  && { err "use --from, --with and --into only together with readout-mbr, compare-mbr and related commands."; 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 [[ "$subcommand" = "readout-mbr" ]]; then
  [[ -z "$into" ]]  && { err "use --into to specify the backup mbr file."; echo >&2; exit 201; }
  [[ -z "$from" ]]  && { err "use --from to specify device to read MBR from."; echo >&2; exit 201; }
  [[ -n "$with" ]]  && { err " --with has no meaning for $subcommand."; echo >&2; exit 201; }
  backup_mbr_file="$into"; read_mbr_from="$from";
  [[ -e "$into" ]] && { err "error: file does already exist: '$into'"; echo >&2; exit 210;  }
  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 < <( list_partitions "$read_mbr_from"; ) 
  [[ 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" >&2
  dd if="$read_mbr_from" bs=$PAGE_SIZE count=$count of="$backup_mbr_file";
  [[ $? -eq 0 ]] && echo "done." >&2;
  echo >&2;

elif [[ "$subcommand" = "compare-mbr" ]]; then
  [[ -z "$from" ]]  && { err "use --from to specify the file with MBR/ blocks from start of the disk."; echo >&2; exit 201; }
  [[ -z "$with" ]]  && { err "use --with to specify the device to compare with."; echo >&2; exit 201; }
  [[ -n "$into" ]]  && { err " --into has no meaning for $subcommand."; echo >&2; exit 201; }
  [[ -e "$with" ]] || { err "error: device does not exist: '$with'"; echo >&2; exit 210;  }

  from_size=$(stat --printf %s "$from")
  if which bindiff 2>&9 >&9; then
    echo "comparing (master boot record is within first 440 Byte, then comes the disk signature (4 bytes) and from 444 to 512 the partition table with boot signature, bindiff counts from zero ..." >&2
    dd if="$with" bs=$from_size count=1 of=/dev/stdout 2>&9 | bindiff --print bs=1 /dev/stdin "$from"
  else
    echo "comparing (master boot record is within first 440 Byte, then comes the disk signature (4 bytes) and from 444 to 512 the partition table with boot signature (cmp: Byte 445 since it counts from one)..." >&2
    dd if="$with" bs=$from_size count=1 of=/dev/stdout 2>&9 | cmp /dev/stdin "$from"
    [[ $? -eq 0 ]] && echo "compares ok; no differences found." >&2;
  fi

elif [[ "$subcommand" = "reinstall-mbr"  || "$subcommand" = "blank-mbr" ]]; then
  if [[ "$subcommand" = "blank-mbr" ]]; then
    [[ -n "$from" ]]  && { err " --from has no meaning for $subcommand."; echo >&2; exit 201; }
    from="/dev/zero";
  else
    [[ -z "$from" ]]  && { err "use --from to specify the file to read MBR from (first 440 Byte of that file)."; echo >&2; exit 201; }
  fi
  [[ -z "$into" ]]  && { err "use --into for the device to insert the MBR into."; echo >&2; exit 201; }
  [[ -n "$with" ]]  && { err " --with has no meaning for $subcommand."; echo >&2; exit 201; }
  [[ -e "$into" ]] || { err "error: device does not exist: '$into'"; echo >&2; exit 210;  }

  echo "inserting (overwriting) MBR of $into with $from ..." >&2
  echo "binreplace if=$from seek=0 size=440 of=$into" >&2
  binreplace if="$from" seek=0 size=440 of="$into"

elif [[ "$subcommand" = "test-mbr-blanked" ]]; then
  [[ -z "$from" ]]  && { err "use --from to specify the device to test for a blanked mbr."; echo >&2; exit 201; }
  [[ -n "$with" ]]  && { err " --with has no meaning for $subcommand."; echo >&2; exit 201; }
  [[ -n "$into" ]]  && { err " --into has no meaning for $subcommand."; echo >&2; exit 201; }

  echo "comparing first 440 bytes with /dev/zero" >&2
  if which bindiff 2>&9 >&9; then
    ( dd if="$from" bs=440 count=1 of=/dev/stdout 2>&9 | bindiff --print bs=1 if1=/dev/stdin if2=/dev/fd/3 3< <( dd if=/dev/zero bs=440 count=1 of=/dev/stdout 2>&9; ); ) 
  else
    ( dd if="$from" bs=440 count=1 of=/dev/stdout 2>&9 | cmp /dev/stdin /dev/fd/3 3< <( dd if=/dev/zero bs=440 count=1 of=/dev/stdout 2>&9; ); ) 
    [[ $? -eq 0 ]] && echo "compares ok; no differences found." >&2;
  fi

elif [[ "$subcommand" = "readout-parttbl" ]]; then
  [[ -z "$into" ]]  && { err "use --into to specify the partition table blocks and index files (--into sda.xx)."; echo >&2; exit 201; }
  [[ -z "$from" ]]  && { err "use --from to specify device to read the partition table from."; echo >&2; exit 201; }
  [[ -n "$with" ]]  && { err " --with has no meaning for $subcommand."; echo >&2; exit 201; }
  [[ "$into" = "${into%.xx}" ]] && { err "--into file must end with .xx where .xx will be replaced with .blocks and .parttbl" >&2; echo >&2; exit 201; }
  blocks_file="${into%.xx}.blocks";
  parttbl_file="${into%.xx}.parttbl";
  for f in $blocks_file $parttbl_file; do
    [[ -e "$i" ]] && { err "error: file does already exist: '$i'"; echo >&2; exit 210;  }
  done
  check4parted;
  if [[ usedd -eq 0 ]]; then
    if which binreplace 2>&9 >&9; then replok=1; else replok=0; fi
  else replok=0; 
  fi
  echo "reading out partition table and spare spaces" >&2
  let prevend=0 virtual=0 virtoffset=0;
  while read dno start end blocks partttype fsandflags; do
    if [[ -z "${dno##[0-9]*}" && -n "$dno$start" ]]; 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}; let end+=1;

      if [[ "${parttype#*erweitert}" != "$parttype" || "${parttype#*extended}" != "$parttype"  ]];     # only very old versions of parted may also output in German
	then let isext=1;
	else let isext=0; 
      fi

      #echo "#$start $end"
      if [[ prevend -lt start && isext -eq 0 ]]; then
	let blockstart=$(rounddown prevend);
	let blockend=$(roundup start);
	let blocknum=blockend-blockstart;
	#echo "$prevend $start"
	#echo "$blockstart $blocknum $virtoffset # $((blockstart*512)) size=$((blocknum*512)) seek=$((virtoffset*512))" 
	if [[ replok -eq 1 ]]; then
	  binreplace --quiet if="$from" skip=$((blockstart*512)) size=$((blocknum*512)) seek=$((virtoffset*512)) of="$blocks_file"
	else
	  dd if="$from" bs=512 skip=$blockstart count=$blocknum seek=$virtoffset of="$blocks_file"
	fi
	let blocks_start[virtual]=blockstart
	let blocks_end[virtual]=blockend
	let blocks_virtstart[virtual]=virtoffset
	let virtual+=1 virtoffset+=blocknum
      fi
      let prevend=end
    fi
  done < <( if [[ skipend -eq 0 ]]; then list_partitions_plus_end "$from"; else list_partitions "$from"; fi | tee "$parttbl_file"; )
  for((i=0;i<virtual;i++)); do
    echo "blocks ${blocks_start[i]} ${blocks_end[i]} ${blocks_virtstart[i]}";
  done >>"$parttbl_file"
  if [[ -b "$from" ]]; then
    echo "disksize $(blockdev --getsz "$from")" >>$parttbl_file;
  else
    echo "disksize $((($(stat -c%s "$from")+511)/512))" >>$parttbl_file;
  fi
  echo "done" >&2;
  echo >&2;

fi 9>/dev/null

