#!/bin/bash
#
# xchroot v2.7.5
# (c) copyright by Elmar Stellnberger, the original author: 2009, Jan 2010, Jan 2013, Aug 2013, Oct 2013, Dec 2019, 2020, Nov 2021, 2022, Jan 2023
#
#  further information: www.elstel.org/com; look here for an actual contact address
#  current email: estellnb@elstel.org; additional email estellnb@gmail.com
#
# v2.7.5
#  * xauth token creation if X-connection relies on xhost +local: or xhost +SI:localuser:xxx
#    necessary if xchroot changes the user and no matching xauth token is provided (like with Mageia 8)
#    now completely rewritten: xauth token passing code
#  - xchroot inst-root-sudo:  --quiet parameter introduced as may be used by a postinstall script: added for packaging
#
# v2.7.4
#  * mounting singleton files into root rather than just whole directories (/etc/resolv.conf,/run/initctl)
#  * mounting /dev/shm, mounting /boot/efi<->/boot/EFI
#  * plain busybox xchroots
#  * xchroot inst-root-sudo: sudo may not work otherwise due to regression in sudo
#  * no spurious error message: detects if dbus-launch not present in root
#  - default of '--on tmp': restores known to work default behaviour of v2.6 (--on mirrtmp seems buggy, apparently)
#  * work in progress: --setenv, --preserve-env options: especially useful with xchroot desktop sessions
#
# v2.7
#  * xchroot / for merely switching the user
#  * new: dbus-session started by default (otherwise use --off sess)
#  * --on/-off parameters to control dbus-session creation and mounts into /run/xx as well as /tmp & /var/tmp
#  * more secure tmp mirroring policy (now also selectable): tmpfs on /tmp and /var/tmp only --bind-mounting /tmp/.X11-unix
#  * combine XAUTHORITY if there are multiple xchroots to the same root and user
#  * create XDG_RUNTIME_DIR & set environment variable, correct --noX implementation (no XAUTH passing!)
#  * entire desktop sessions with xchroot, improved createstartup/.desktop file creation also with chroot-escape (/chroot/usr/share/xsession/*.dekstop)
#    createstartup: source and dest .desktop files with spaces allowed; correct backslash handling in filenames
#  * chroot-escape improvement: succeeds also if /dev/fd/89 closed by intermediate program, $XCHROOT_ESCAPE_XAUTHORITY propagated correctly
#  * correct process cleanup for nested chroots: uses $absroot instead of $root
#
# v2.6: 
#  * bugfix: no recursive mounts on entering chroot with -n (example: xchrot -m /mnt /mnt/debian)
#  * bugfix: mountd-daemon killed: restart: remember new pid correctly without doubling content of "$MMCNFFILE" 
#  * correct --noask passing for xchroot as user
#  * xchroot escape
#  * createstartup directly for executables rather than desktop files
#  * -d option: there was a double assignment of -b for $MINMIRROR_IN and bidi-mirroring
#  * corrected delsudoers for "all" pseude user
#  * updated & cleaned up --help screen
#
# v2.5.3: important bugfixes towards 2.5 (31.12.2020)
#  * user management was broken: had left $OPTARG; now corrected to $optarg
#  * no openroot macro required any more: auto-sudo by xchroot
#   - createstartup does now no more need bash completion and thus also runs without KDE; f.i. under Xfce 
#  * corrected/improved unmounts on exit
#  * xchroot /dst/deb32/bin/hostname now also works if /dst is a sudo praefix rather than /dst/deb32
#  * -q switch is now no more double assigned two different functions: use -b when appropriate
#
# v2.5: mount-mirroring (December 2020)
#  * faster child process search on exit, short sleep when user selects to poll for child termination
#  * --fast unmount/mount option skipping /sys/fs/*
#  * cleaner output, -vv switch, awk instead of cut & grep, faster integrated [[ instead of [/test
#
# v2.4.1: resolved unmounting problem (nounmount on exit, hang when invoked via cleanup)  (22.02.2020)
#  * spurious but ignorable error message on chroot ./subdir removed
#  * also keeping directory when current dir is already inside the chroot
#
# v2.4: pulseaudio support (15.12.2019)
#  * openroot /dst/buster/usr/bin/xine myfile    i.e. no need to separate root directory from executable to execute inside root
#  * parameters to xchroot may contain escaped spaces
#  * current working directory is preserved
#  * makro for desktop file creation
#  * showmount may be executed as non root
#
# v2.3.4: improved the --quiet option (April 2018)
#  * use of --userspec from chroot: no su or sudo required inside chroot any more
#  * use --genuine-retval allows to retrieve the return value directly from the chroot rather than the xchroot wrapper
#  * fixed recognition of short options -s and -v
#
# v2.3.3: enhanced user management, license (09.11.2013)
#  * chrooting from one user to an other is now possible
#   * resolved security issue when users of chroot environment and outside of it have different privileges 
#   - idea for using getent rather than grep-ing /etc/passwd was contributed by Nick Bannon on 2013-11-08 <nick@rcpt.to> 
#  * cd to home directory on startup
#
# v2.3.2: security + docs, license (27.10.2013)
#  * addressed several security issues for using xchroot with /etc/sudoers, doc-update, more clear license
#  * job control fix for newer systems (does no more get disabled when chrooting from new to old system)
#
# v2.3.1: aufs and unionfs support (23.10.2013)
# * updated, more distributor friendly license (now allows you to distributed modified versions of xchroot)
# * warn correctly if no X connection can be established; including the XCONNECT=noX environment variable to disable these warning
# * export XAUTHORITY also for remote hosts
#
# v2.3: aufs and unionfs support (28.8.2013)
# * aufs and unionfs support
#  * saving, restoring and augmenting .squashfs images for use with aufs and unionfs
# * noask-option for automatic xchroot invocation; does not ask on kill (if signal causes xchroot to exit)
# * recursive xchroot; XAUTHORITY envvar inheritance error resolved ( now just using last directory name as identifier; also possible: use ':' instead of '\\')
# * various minor issues: improved cleanup, socat check; check if it will chroot to a valid root-fs, precise return values (0-255)
# * better, prospectively oss-compliant license
#
# v2.25 bugfixes over v.2.2 (18.8.2013)
# * su -c did loose XAUTHORITY environment variable on elder Linux distros (necessary X access)
# * open same root several times: only ask to terminate running programs on last exit
# * use SHELL from /etc/passwd or as given by --shell: different shells than bash
# * trivial error fix: $root was hardcoded in v2.2 when asking to terminate programs still running in root
# * some minor corrections when using "cleanup" after having left everything mounted
#

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/xchroot/
(c) copyright by Elmar Stellnberger 2020

EOQ
  exit 0;
}

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

help() {
  cat <<EOQ
xchroot [options] rootdir [commands]      v.2.7.5
xchroot [options] /command-inside-rootdir ... searches for a root in addsudoer entries and executes the command inside this root
xchroot [options] cleanup rootdir ... clean all aufs-data-dirs in /tmp and all aufs-mountpoints of the form root-[0-9]*
xchroot --dir[pfx] »dir« add/delsudoers »user« ... add an entry for »user« into /etc/sudoers being able to xchroot in any dir. below »dir«
xchroot --escape add/delsudoers »user« ... add/del xchroot-escape entries for »user« into /etc/sudoers
xchroot listsudoers »user« ... list all /etc/sudoer entries for xchroot
xchroot -t rootdir -- additional options ... pass additional options when invoked as user
xchroot createstartup --desktop [--category AddCatgy] [--no-which] /dst/buster/usr/share/applications/inkscape.desktop ... creates .desktop file in /usr/share/applications
xchroot list-desktop-categories / listrunmounts / ppids / xchppids
xchroot createstartup --command --category Education /dst/buster/usr/bin/inkscape ... creates .desktop file in /usr/share/applications
xchroot showmount rootdir
 --license / -l ... show license information (private usage is free).
 --alldisplays ... do not solely forward \$DISPLAY to the chroot environment
 --user/-u »user« ... chown to user after chrooting.
 --norc ... do not execute /etc/bash.bashrc and run bash with --norc
 --shell exec ... use instead of default shell usually found in /etc/passwd
 Xorg connection (for graphical apps):
   --socat/-s ... most secure (note that chroot is not a security feature under Linux in contrast to the FreeBSD jails
   --mntmp ... std: mounting socket dir along with /tmp,/var/tmp,/var/spool: less secure
   --noX ... do not take any provisions for running Xorg/X11 based apps
 umounting on exit:
   -o dirs, -p, -d: mount on startup: specified dirs / mnt & media & home / mnt & media
   -m dirs, -n, -w: mount mirror into: specified dirs  / mnt & media & home / mnt & media
   --mirror-out dirs, -b dirs, -b -... / -b --: mirror out of root dir, mirror bidirectional, mirror default dirs bidirectionally
   -f/--fast .... do not mount below /sys/fs/
 ( mount points containing spaces are not supported yet.)
 aufs and unionfs:
   --aufs / --unionfs ... do not change root; make all changes temporary via unionfs/aufs
   --save xy-aufs/unionfs.squashfs / --restore xy-aufs/unionfs.squashfs  ... save/restore changes to unionfs-environment
  
 f.i. xchroot --mntmp cleanup /dst/debian/
   or xchroot --umountall cleanup /dst/debian

 possible result codes:
   2xx ~ xchroot failed:  255 ~ EINVAL (wrong parameters), 200 ~ EPERM (not run as root/ other permission error),  ...
   132 ~ other xchroot instances still running	
   100 ~ processes left running in the previous chroot (no other xchroot instances running)
    +1 ~ umounting error by user, +2 ~ umounting error by xchroot, +4 file deletion/rmdir error
   +16 ~ rescuable changes to unionfs environment when exiting without full cleanup, +8 possible error at freezing changes (auplink: concerning hard links)

EOQ
exit 0;
}

exec 9>/dev/null;
original_params=("$@")
MMCNFFILE=/tmp/xchroot/mirror-mounts.lis

mylockfile-create() {
  [[ -n "$2" ]] && { err "internal error: wrong invocation of mylockfile-create."; exit 221; }
  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 4 5 6 7 8 9 0; do
      [[ -e "$1" ]] || { touch "$1"; return 0; }
      sleep 0.1;
    done;
    [[ -e "$1" ]] && return 4;
    touch "$1"; return 0;
  fi
  for retries in 1 2 3 4 5 6 7 8 9; do   # good; no race condition
    lockfile-create --retry 0 --lock-name "$1" 2>&9 && return 0;
    sleep 0.1;
  done;
  lockfile-create --retry 0 --lock-name "$1" 2>&9 && return 0;
  echo "error creating lockfile; proceeding unlocked" >&2
  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
  rm -f "$1";
}

cndmount() { let moi=$#-1; eval local mp="\${$#}" mo="\${$moi}";
  let isdir=1; [[ "${mp%$mo}" != "$mp" && ! -d "$mo" && -e "$mo" ]] && let isdir=0
  if [[ isdir -ne 0 ]]; then 
    [[ -e "$mp" && ! -d "$mp" && ! -s "$mp" ]] && rm -f "$mp";  # file of zero size
    mkdir -p "$mp"; 
  else 
    [[ -d "$mp" ]] && rmdir "$mp"; 
    touch "$mp"; 
  fi
  grep -q "^[^ ]\+ $mp " $mtab || { vmsg 3 "mounting $mp"; mount "$@"; }
}

efimount() {
  local mntfrom="" mntto mp
  for mp in /boot/efi /boot/EFI; do
    if [[ -d "$mp" ]] && grep -q "^[^ ]\+ $mp " $mtab; then
      mntfrom="$mp"; break;
    fi
  done
  [[ -n "$mntfrom" ]] || return 0
  for mp in "$root/boot/efi" "$root/boot/EFI"; do
    if [[ -d "$mp" ]]; then
      grep -q "^[^ ]\+ $mp " $mtab && return 0;
      mntto="$mp"; break;
    fi
  done
  mount --bind "$mntfrom" "$mntto"
  return $?;
}

isin() { local tok="$1"; while [[ $# -gt 1 ]]; do [[ "$tok" = "$2" ]] && return 0; shift; done; return 1; }
issuchadir() { local tok="$1"; while [[ $# -gt 1 ]]; do [[ "${tok%$2}" != "$tok" || "${tok%$2/*}" != "$tok" ]] && return 0; shift; done; return 1; }
stripout() { local tok="$1"; while [[ $# -gt 1 ]]; do [[ "$tok" != "$2" ]] && echo -n "$2 "; shift; done; }

slurp_mountd_conf() { local pid direction root new_subdirs i idx subdir subdir_cleanup tokroot;
  let newidx=1 todo=0 line=0 seen_line=0; unset intok outtok in out exiting running; # become supplemented: cleantok cleanup
  [[ verbose -ge 2 ]] && echo "slurping mounttab" 
  mv $MMCNFFILE $MMCNFFILE.tmp
  while read pid direction root new_subdirs; do
    if [[ "$pid" = "&mirror-mountd" ]]; then echo "$pid $direction $root $new_subdirs"; let line++; continue; fi
    if [[ "$direction" = "in" ]]; then reg=intok; ary=in; 
    elif [[ "$direction" = "out" ]]; then reg=outtok; ary=out;
    elif [[ "$direction" = "cleanup" ]]; then reg=cleantok; ary=cleanup;
    elif [[ "$direction" = "exiting" ]]; then
      for i in ${!outtok[@]}; do
	if [[ "${outtok[i]}" = "$root" ]]; then
	  exiting[i]="yes"; 
	  break;
	fi
      done
      for i in ${!cleantok[@]}; do
	if [[ "${cleantok[i]}" = "$root" ]]; then
	  running[i]=$(stripout $pid ${running[i]}); 
	  break;
	fi
      done
      ps -p $pid -o pid h >&9 && { echo "$pid $direction $root $new_subdirs"; let line++; }
      continue;
    fi
    if [[ "$direction" != "cleanup" ]]; then
      ps -p $pid -o pid h >&9 || if [[ "$direction" = "in" ]]; then direction="cleanup"; else continue; fi
      [[ "$direction" != "cleanup" ]] && let todo=1
    fi
    echo "$pid $direction $root $new_subdirs"; let line++;
    [[ "$direction" = "cleanup" ]] && let seen_line=line
    # process the cnffile-line now
    let idx=-1; eval idxx="\"\${!$reg[@]}\""
    for i in $idxx; do
      eval tokroot=\"\${$reg[$i]}\"
      if [[ "$tokroot" = "$root" ]]; then
	let idx=i;
	break;
      fi
    done
    if [[ idx -eq -1 ]]; then
      let idx=newidx newidx++;
      eval $reg[$idx]="${root@Q}";
      cleantok[idx]="$root"
    fi
    eval subdirs="\"\${$ary[$idx]}\""
    [[ "$direction" = "in" ]] && subdirs_cleanup="${cleanup[idx]}";
    for sd in $new_subdirs; do
      isin "${sd%/}" $subdirs || subdirs="${subdirs}${subdirs:+ }${sd%/}"
      [[ "$direction" = "in" ]] && isin "${sd%/}" $subdirs_cleanup || subdirs_cleanup="${subdirs_cleanup}${subdirs_cleanup:+ }${sd%/}"
    done
    eval $ary[$idx]="${subdirs@Q}"
    [[ "$direction" != "out" ]] && cleanup[idx]="${subdirs_cleanup}";
    [[ "$direction" != "cleanup" ]] && { isin $pid ${running[idx]} || running[idx]="${running[idx]}${running[idx]:+ }$pid"; }
  done <$MMCNFFILE.tmp >$MMCNFFILE
  rm $MMCNFFILE.tmp
  [[ todo -eq 0 ]] && rm -f $MMCNFFILE
  if [[ verbose -ge 2 ]]; then
    for i in "${!intok[@]}"; do
      echo "in: ${intok[$i]} ${in[$i]}"; done
    for i in "${!outtok[@]}"; do
      echo "out: ${outtok[$i]} ${out[$i]}"; done
    for i in "${!cleantok[@]}"; do
      echo "cleanup: ${cleantok[$i]} ${cleanup[$i]}"; done
    if [[ todo -eq 0 ]]; then
      echo -n "nothing more to mount-mirror;"
    fi
  fi
  [[ todo -ne 0 ]]
}

cleanup-umounts() {
  local had_a_cleanup="" new_line pid direction root new_subdirs
  for i in "${!cleantok[@]}"; do
    groot="${cleantok[i]}"
    if [[ -z "${running[i]% }" ]]; then 
      for mdir in ${cleanup[i]}; do
	awk '/^[^ ]+ '"${groot////\\/}${mdir////\\/}"'\/[^ ]/{ print $2; }' $mtab | sort -r | while read mp; do
	  umount $mp
	done;
	grep -q "^[^ ]* $groot$mdir " $mtab && umount "$groot$mdir";
      done
      unset cleanup[i] cleantok[i]
      had_a_cleanup="$had_a_cleanup${had_a_cleanup:+ }$groot"
    fi
  done;
  if [[ -n "$had_a_cleanup" && -e $MMCNFFILE ]]; then  # remove cleanup entries for done cleanups
    let line=0 new_line=0;
    mylockfile-create $MMCNFFILE.lock
    mv $MMCNFFILE $MMCNFFILE.tmp
    while read pid direction root new_subdirs; do
      if [[ line -gt seen_line ]]; then
	echo "$pid $direction $root $new_subdirs";
      elif [[ "$pid" = "&mirror-mountd" ]]; then 
	echo "$pid $direction $root $new_subdirs"; 
	let line++; let new_line++;
	continue;
      else
	if [[ "$direction" != "cleanup" ]] || ! isin $root $had_a_cleanup; then   
	  echo "$pid $direction $root $new_subdirs"; let new_line++; 
	fi
	let line++;
      fi
    done <$MMCNFFILE.tmp >$MMCNFFILE
    rm $MMCNFFILE.tmp; let line=new_line
    mylockfile-remove $MMCNFFILE.lock
  fi
}

mirror-mountd() {
  cd /; trap '' SIGINT; 
  mtab=/proc/mounts; [[ -e "/proc/mounts" ]] || mtab=/etc/mtab
  if which lockfile-create 2>&9 >&9; then let lockprog=1;
  elif which lockfile 2>&9 >&9; then let lockprog=2;
  else let lockprog=0;
  fi
  while ! [[ -e $MMCNFFILE ]]; do sleep 0.02; done
  last_slurp="$(stat --printf "%Y" $MMCNFFILE)";
  slurp_mountd_conf || exit 0;  # nothing more to mount-mirror
  mypid=$$
  findmnt --list --kernel --poll -n -o ACTION,TARGET | while read action target; do
    issuchadir "$target" /sys /dev && continue;
    if [[ "$action" = "umount" && "${target%/proc}" != "$target" ]]; then
      sleep 0.5; let oneexited=1;
    else let oneexited=0;
    fi
    mylockfile-create $MMCNFFILE.lock
    new_slurp="$(stat --printf "%Y" $MMCNFFILE)"
    if [[ oneexited -eq 1 || "$new_slurp" != "$last_slurp" ]]; then
      if ! slurp_mountd_conf; then
	# nothing more to mount-mirror
	[[ verbose -ge 2 ]] && echo "exiting";
	mylockfile-remove $MMCNFFILE.lock; 
	cleanup-umounts
	kill $(ps --ppid $mypid -o pid,comm h | awk '/findmnt/{ print $1; }');  # get rid of findmnt
	exit 0;
      fi
      last_slurp="$(stat --printf "%Y" $MMCNFFILE)"
    fi
    mylockfile-remove $MMCNFFILE.lock
    issuchadir "$target" /proc && continue;
    [[ verbose -ge 2 ]] && echo "&$action $target";
    for i in "${!intok[@]}"; do
      groot="${intok[i]}"
      for mdir in ${in[i]}; do
	#echo "$mdir $target ${target#$mdir/}"
	if [[ "${target#$mdir/}" != "$target" || "$target" = "$mdir" ]] && [[ "${target#$groot}" = "$target" ]]; then
	  if [[ "$action" = "mount" ]]; then cndmount --bind "$target" "$groot$target"
	                                else grep -q "^[^ ]* $groot$target " $mtab && umount "$groot$target";
	fi; fi
    done; done;
    for i in "${!outtok[@]}"; do
      groot="${outtok[i]}"
      for mdir in ${out[i]}; do
	if [[ "${target#$groot$mdir/}" != "$target" || "$target" = "$groot$mdir" ]] && [[ "${target#$groot$groot}" = "$target" ]]; then
	  outertarget="${target#$groot}"
	  if [[ "$action" = "mount" ]]; then cndmount --bind "$target" "$outertarget"
	                                else grep -q "^[^ ]* $outertarget " $mtab && [[ -z "${exiting[i]}" ]] && umount "$outertarget";
	fi; fi
    done; done
    cleanup-umounts
  done
  exit 0;
}

check_umount_others() {
  [[ -e $MMCNFFILE ]] || return 0
  read tag mmpid <$MMCNFFILE 
  [[ "$tag" = "&mirror-mountd" ]] && ps -p $mmpid -o pid h >&9 && return 0
  # no mirror-mountd is running; still see for cleanup-umounts on exit
  slurp_mountd_conf
  cleanup-umounts
}

[[ "$1" = "mirror-mountd" ]] && mirror-mountd;

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

if [[ "${XCONNECT}" = "noX" ]] && [[ -n "$DISPLAY" || -n "$XAUTHORITY" ]]; then
  echo "\$XCONNECT=noX but \$DISPLAY or \$XAUTHORITY set; ignoring \$XCONNECT (defaults now to $Xconnect)." >&2; 
  unset XCONNECT
fi
Xconnect=${XCONNECT:-mntmp}; tgUserGroup=''; tgUser='root'; tgGroup=''; let doumount=1; let fastmnt=0;
unionfs=""; let verbose=1 alldisplays=0 bashrc=1; shell=""; let hadenv=0 erropt=0 noescape=0 isescape=0; udba=none; 
options=""; noask=false; save=""; restore=""; tailopts=false; NoAudio=false; GenuineRetVal=false;
STDMIRROR_IN="/media:/home:/mnt"; MINMIRROR_IN="/media:/mnt"; STDMIRROR_OUT="/media:/mnt";
mirror_in=""; mirror_out=""; startup_mount=""; ADDSTDMOUNT="/var/spool"; PRESERVE_ENV=0; ENV_FILE=""; ADDCMD=();
MNTON="sess:dbus:alsa:blkid:udev:cups:network:nwm:sddm:systemd:mirrtmp:initctl:resolv.conf:efi";  MNTOFF="";

if [[ "$SUDO_COMMAND" = "$0${*:+ }$*" ]]; then
  user="$SUDO_USER"; gid="$SUDO_GID"; group="$( getent group $SUDO_GID | cut -f 1 -d : )"
else
  user="$(id -un)"; gid=$(id -g); group="$(id -gn)"
fi

assert_frontopt() {
  if ! $frontopt; then err "$opt is for resaons of security only allowed as a front option.\n"; exit 200; fi
}

assert_empty() {
  [[ -n "${!1}" ]] && echo "warning: $1 overwritten; stated twice."
}

ALLOWEDENVVARS="DISPLAY XAUTHORITY XCHROOT_MYROOT XCHROOT_ESCAPE_XAUTHORITY XCHROOT_ESCAPED XCONNECT";

setenv() {
  varname="${1%%=*}"; value="${1#*=}";
  if isin "$varname" $ALLOWEDENVVARS; then
    export "$varname=$value"
    let hadenv=1;
  else err "tried to trespass unapproved environment variable »$varname« into target chroot environment."; 
       echo -e "execute $(basename $0) --approved in order to list all currently approved environment variables.\n" >&2;
       exit 200;
  fi
}

quote() {   # better: use ${i@Q} 
  while [[ $# -gt 0 ]]; do 
    if [[ "${1#*\'}" != "$1" ]]; then
      echo -n "\"${1//\"/\\\"}\""; 
    else
      echo -n "'${1}' "; 
    fi
    shift; 
  done; 
}

if grep -q -- --group <( su --help; ); then suknowsgroups=true; else suknowsgroups=false; fi

userPerm() { #local cmd="$1"; shift;
  if which sudo 2>&9 1>&9;
    then sudo -u $user -g '#'$gid "$@"; return $?;
  elif $suknowsgroups;
    then su $user -g $group -c "${@@Q}"; return $?
  else
    echo su without groups
    su $user -c "${@@Q}"; return $?
  fi
}

mygetopts() {
  [[ ii -ge ${#opt} ]] && return 1;
  o="${opt:ii:1}"; let ii++;
  if [[ "${canhavprm#*$o}" != "$canhavprm" ]]; then
    if [[ "${!nexti:0:1}" != "-" ]]; then 
      optarg="${!nexti}"; let nexti++; 
    else optarg="";
    fi
  else optarg="";
  fi
  return 0;
}

parseopts() { local i ii nexti opt canhavprm o optarg
  let i=$1; shift; opt="${!i}"; canhavprm="umbo";
  while [[ "${opt:0:1}" = "-" ]]; do

    while [[ "${opt:0:2}" != "--"  && "${opt:0:1}" = "-" ]]; do
      let ii=1 nexti=i+1;
      while mygetopts "$@"; do case $o in
	a) unionfs='aufs';; s) Xconnect=socat;;
	u) assert_frontopt; tgUserGroup=$optarg;; 
	h) help;; v) let verbose+=1;; q) let verbose=0;;
	t) tailopts=true;; f) let fastmnt=1;; E) let noescape=1;; z) let doumount=0;;
	m|n) mirror_in="$mirror_in${mirror_in:+:}${optarg:-$STDMIRROR_IN}";;
	w) mirror_in="$mirror_in${mirror_in:+:}$MINMIRROR_IN";;
	b) mirror_in="$mirror_in${mirror_in:+:}${optarg:-$STDMIRROR_IN}"; 
	   mirror_out="$mirror_out${mirror_out:+:}${optarg:-$STDMIRROR_OUT}";;
	o|p) startup_mount="$startup_mount${startup_mount:+:}${optarg:-$STDMIRROR_IN}";;
	d) startup_mount="$startup_mount${startup_mount:+:}$MINMIRROR_IN";;
	i) let PRESERVE_ENV=1;; I) let PRESERVE_ENV=0;;
	*) let erropt=1;;  # getopts has already printed an error message
      esac; done
      let i=nexti; opt="${!i}"; 
    done
    #shift $(( OPTIND - 1 )); let OPTIND=0;
    let i=i+1;

    while [[ "${opt:0:2}" = "--" ]]; do 
      case $opt in
	--) shift; break 2;;  # -- indicates end of options
	--useaufs|--aufs) unionfs='aufs';; --unionfs) unionfs='unionfs';; --udba) udba="${!i}"; let i++;;
	--unionopts) unionopts="${!i}"; let i++;; --maxfiles) maxfiles="${!i}"; let i++;;
	--user) assert_frontopt; tgUserGroup="${!i}"; let i++;; --norc) let bashrc=0;; --shell) shell="${!i}"; let i++;;
	--noumount) let doumount=0;;  --stdumount) let doumount=1;; --umountall) let doumount=2;;
	--socat) Xconnect=socat;; --mntmp) Xconnect=mntmp;; --noX) Xconnect=noX;;
	--noask) noask=true;; --save) save="${!i}"; let i++;; --restore) restore="${!i}"; let i++;;
	--squashopts) squashopts="${!i}"; let i++;;
	--dirpfx) assert_empty dirpfx; dirpfx="${!i}"; let i++;; --dir) assert_empty dirpfx; dirpfx="${!i%/}/ "; let i++;;
	--escape) assert_empty dirpfx; dirpfx="escape ";;
        --env) setenv "${!i}"; let i++;; --approved) echo -e "$ALLOWEDENVVARS\n"; exit 0;;
	--setenv) : ${ENV_FILE:="${!i}"}; let i++;;
	--no-audio) NoAudio=true;; --fast) let fastmnt=1;; --genuine-retval) GenuineRetVal=true;;
	--no-escape) let noescape=1;;
	--on) MNTONT="$MNTONT:${!i}"; let i++;;  --off) MNTOFF="$MNTOFF${MNTOFF:+:}:${!i}"; let i++;;
	--mirror|--mirror-in) 
	  param="${!i}"; if [[ "${param:0:1}" != "-" ]]; then let i++; else param="$STDMIRROR_IN"; fi
	  mirror_in="$mirror_in${mirror_in:+:}${param}";;
	--mirror-out) 
	  param="${!i}"; if [[ "${param:0:1}" != "-" ]]; then let i++; else param="$STDMIRROR_OUT"; fi
	  mirror_out="$mirror_out${mirror_out:+:}${param}";;
	--mirror-bidi) 
	  if [[ "${!i:0:1}" != "-" ]]; then 
	    mirror_in="$mirror_in${mirror_in:+:}${!i}"; mirror_out="$mirror_out${mirror_out:+:}${!i}"; let i++; 
	  else 
	    mirror_in="$mirror_in${mirror_in:+:}$STDMIRROR_IN"; mirror_out="$mirror_out${mirror_out:+:}$STDMIRROR_OUT";
	  fi;;
	--preserve-env|--keep-env) let PRESERVE_ENV=1;; 
	--no-preserve-env|--no-keep-env) let PRESERVE_ENV=0;; 
      --help) help;; --license) license;; --verbose) let verbose+=1;; --quiet) let verbose=0;; --alldisplays) let alldisplays=1;;
	*) erropt=1; err unknown long option: $opt;;
      esac; 
      opt="${!i}"; let i++;
    done;
    #echo "***$i****"
    let i--;
    if [[ "$opt" = "-" ]]; then erropt=1; err "stale - in options"; let i++; opt="${!i}"; fi

  done
  [[ i -gt 255 ]] && { err "too many options (max 255)!"; exit 255; }
  return $i
}

frontopt=true;
parseopts 1 "$@";
let i=$?-1
options="${@:1:i}"
shift $i;

frontopt=false;
if $tailopts; then
  let i=$#;
  while [[ i -gt 0 ]] && [[ "${!i}" != "--" ]]; do let i--; done
  if [[ i -le 0 ]]; then err " -t for tailoptions specified but no -- delimiter for tail options found."; exit 255; fi
  parseopts $((i+1)) "$@";
  let n=$?-1; let nn=n-i;
  if [[ $# -ne $n ]]; then err "spurious term in tail options (something that is not an option after --)"; exit 255; fi
  options="$options ${@:i+1:nn}"
  set -- "${@:1:i-1}"
elif [[ $# -gt 0 ]]; then
  let i=$#
  if [[ "${!i}" = "--noask" ]]; then
    noask=true; let i--;
  fi
  let i--;
  while [[ "${!i}" = "--env" || "${!i}" = "--setenv" ]]; do
    if [[ "${!i}" = "--setenv" ]]; then
      let i=i+1; : ${ENV_FILE:="${!i}"};
    else
      let i=i+1; setenv "${!i}";
    fi;
    let i=i-3
  done
  set -- "${@:1:i+1}"
fi

[[ "$1" = "help" ]] && help
[[ erropt -gt 0 ]] && exit 255;
[[ $# -eq 0 ]] && { echo "xchroot --license/--help";echo; exit 255; } >&2

if which lockfile-create 2>&9 >&9; then
  let lockprog=1;
elif which lockfile 2>&9 >&9; then
  let lockprog=2;
else
  [[ -n "$mirror_in$mirror_out" ]] && err "error: neither package lockfile-progs containing the lockfile-create/-remove utility nor procmail containing the lockfile program are installed.";
  let lockprog=0;
fi

if [[ -n "$tgUserGroup" ]]; then
  tgUser=${tgUserGroup%:*}; 
  if [[ "$tgUser" != "$tgUserGroup" ]]; then
    tgGroup=${tgUserGroup#*:}; 
  else
    tgGroup="";
  fi
fi

newMNTON=""; MNTOFF="${MNTOFF//:/ }"; let DBUS_SESS=0 MIRRTMP=0 MNTMP=0;
if ! isin all $MNTOFF; then
  for runMnt in ${MNTON//:/ }; do
    isin $runMnt $MNTOFF || {
      if [[ "$runMnt" = "nwm" ]]; then newMNTON="$newMNTON${newMNTON:+ }NetworkManager";
      elif [[ "$runMnt" = "mirrtmp" ]]; then let MIRRTMP=1; 
      elif [[ "$runMnt" = "tmp" ]]; then let MNTMP=1;
      elif [[ "$runMnt" = "sess" ]]; then let DBUS_SESS=1
      else newMNTON="$newMNTON${newMNTON:+ }$runMnt";
      fi
    }
  done
fi
MNTON="$newMNTON"; unset newMNTON;
[[ "$Xconnect" = "noX" ]] && let DBUS_SESS=0
[[ MIRRTMP -ne 0 ]] && let MNTMP=0;

#echo $user:$gid~$group // $tgUser:$tgGroup
#exit 0

if [[ -n "$restore" ]]; then
  if [[ "$user" = "root" ]]; 
    then [[ -e "$restore" && ! -d "$restore" ]]
    else userPerm test -e "$restore" -a ! -d "$restore"
  fi || { err "squashfs image to be restored not found: $restore\n"; exit 254; }
  [[ "$user" != "root" ]] && ! userPerm test -r "$restore" && { err "$user has no permission to read squashfs image $restore\n"; exit 200; }
fi

saveok() { local save=$1;
  dirname="$(dirname "$save")";
  if [[ "$user" = "root" ]]; then [[ -d "$dirname" ]]; else userPerm test -d "$dirname"; fi || { err "directory in which '$save' should reside not found\n"; return 253; }; # do not let user discover which root-only listable directories exist
  if [[ "$user" = "root" ]]; then [[ -e "$save" ]]; else userPerm test -e "$save"; fi 
  if [[ $? -eq 0 ]]; then
    err "error: file to save unionfs-environment to does already exist."; echo >&2; return 222; 
  else
    [[ "$user" != "root" ]] && ! userPerm test -w "$dirname" && { err "$user has no permission to create files in $dirname.\n"; return 200; }
  fi
  return 0;
}

[[ -n "$save" ]] && { saveok "$save" || exit $?; }

#echo "options: $options"
#echo "rest: $@"
#echo "$user:$group";

shopt -s extglob nullglob

addsudoers() {
  [[ $# -le 0 ]] && echo "no users specified for adding\n" >&2
  self=$(which $0)
  while [[ $# -gt 0 ]]; do user="$1";
    if [[ "${user////}" != "$user" ]]; then err "wrong user spec '$user'"; shift; continue; fi
    if [[ "$tgUser" != "root" ]]; then fromUser="$tgUser"; else fromUser="$user"; fi

    if [[ -z "$dirpfx" || "$dirpfx" = "/" ]]; then isanytarget=" !! for any target dir !!"; else isanytarget=""; fi
    if [[ "${dirpfx% }" != "${dirpfx}" ]]; then
      msg "adding $fromUser->$user ${dirpfx:+with directory }$dirpfx${isanytarget}to /etc/sudoers ...";
    else
      msg "adding $fromUser->$user ${dirpfx:+with directory praefix }$dirpfx${isanytarget} to /etc/sudoers ...";
    fi
    { echo "$fromUser	ALL=(root) NOPASSWD: $self [-]u $user $dirpfx*" 
      echo "$fromUser	ALL=(root) NOPASSWD: $self [-]u $user [-]t $dirpfx*" 
      echo "$fromUser	ALL=(root) NOPASSWD: $self [-]u $user cleanup $dirpfx*" 
      echo "$fromUser	ALL=(root) NOPASSWD: $self [-]u $user [-]t cleanup $dirpfx*" 
    } >>/etc/sudoers
    shift;
  done
  if [[ "$dirpfx" = "escape " ]] && ! grep -q $'^[ \t]*Defaults[ \t]\+closefrom_override[ \t]*$' /etc/sudoers; then
     msg "adding closefrom_override default"
     echo $'Defaults\tclosefrom_override' >>/etc/sudoers
  fi
  echo >&2;
}

delsudoers() {
  [[ $# -le 0 ]] && echo "no users specified for adding (use 'all' to delete all entries.).\n" >&2
  selfbase="$(basename $0)"
  while [[ $# -gt 0 ]]; do user="$1";
    if [[ "$user" = "all"  && "$tgUser" = "all" ]]; then
      msg "deleting all xchroot entries in /etc/sudoers";
      let n1=$(wc -l /etc/sudoers | cut -f 1 -d ' ')
      sed -i "/xchroot/d" /etc/sudoers
      let n2=$(wc -l /etc/sudoers | cut -f 1 -d ' ')
      echo "$((n1-n2)) lines removed from /etc/sudoers" >&2
      break
    fi
    if [[ "${user////}" != "$user" ]]; then err "wrong user spec '$user'"; shift; continue; fi
    if [[ "$tgUser" != "root" ]]; then fromUser="$tgUser"; else fromUser="$user"; fi

    msg "deleting xchroot entries for $fromUser->$user ${dirpfx:+and }$dirpfx ..."
    let n1=$(wc -l /etc/sudoers | cut -f 1 -d ' ')
    if [[ "$user" = "all" && "$fromUser" = "all" ]]; then
      sed -i "/[A-Za-z0-9]\+	ALL=(root) NOPASSWD: [^ \t]*xchroot \[-\]u [A-Za-z0-9]\+ \(\[-\]t \)\?\(cleanup \)\?${dirpfx////[/]}[*]/d" /etc/sudoers
    elif [[ "$user" = "all" ]]; then
      sed -i "/$fromUser	ALL=(root) NOPASSWD: [^ \t]*xchroot \[-\]u [A-Za-z0-9]\+ \(\[-\]t \)\?\(cleanup \)\?${dirpfx////[/]}[*]/d" /etc/sudoers
    elif [[ "$fromUser" = "all" ]]; then
      sed -i "/[A-Za-z0-9]\+	ALL=(root) NOPASSWD: [^ \t]*xchroot \[-\]u $user \(\[-\]t \)\?\(cleanup \)\?${dirpfx////[/]}[*]/d" /etc/sudoers
    else
      sed -i "/$fromUser	ALL=(root) NOPASSWD: [^ \t]*xchroot \[-\]u $user \(\[-\]t \)\?\(cleanup \)\?${dirpfx////[/]}[*]/d" /etc/sudoers
    fi
    let n2=$(wc -l /etc/sudoers | cut -f 1 -d ' ')
    echo "$((n1-n2)) lines removed from /etc/sudoers" >&2
    shift;
  done
  echo >&2;
}

RootSudoLine=$'^[ \t]*root[ \t]+ALL[ \t]*=[ \t]*[(]ALL[)][ \t]*NOPASSWD:[ /t]*ALL[ \t]*$';

listsudoers() {
  msg "xchroot entries in /etc/sudoers:";
  grep xchroot /etc/sudoers | while read; do
    read user who nopasswd self uopt asuser rest <<<"$REPLY"
    if [[ "$who $nopasswd $uopt" = "ALL=(root) NOPASSWD: [-]u" ]]; then 
      echo "$self: $user->$asuser $rest"
    else
      echo "other entry ~ $REPLY"
    fi
  done
  if ! egrep -q "$RootSudoLine" /etc/sudoers >&9 2>&9; then
    echo "${rot}execute 'xchroot inst-root-sudo'; sudo as root may not work otherwise.${nv}" >&2
  fi
  echo
}

instrootsudo() {
  if egrep -q "$RootSudoLine" /etc/sudoers >&9 2>&9; then
    [[ "$2" != "-q" && "$2" != "--quiet" ]] && echo "sudo4root was already installed before; not doing anything." >&2;
    true
  else
    [[ "$2" != "-q" && "$2" != "--quiet" ]] && echo "adding sudo4root entry in /etc/sudores" >&2;
    echo "root    ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
  fi
}

bashrclines() {
  echo "${rot}bashrclines is deprecated; use xchroot directly.${nv}" >&2
  self=$(which $0)
  cat <<EOQ
openroot() { 
  if [[ "\$1" = "-u" ]]; then
    sudo $self "\$@" --env XCHROOT_MYROOT="\$XCHROOT_MYROOT" --env XCHROOT_ESCAPE_XAUTHORITY="\$XCHROOT_ESCAPE_XAUTHORITY" --env XAUTHORITY="\$XAUTHORITY" --env DISPLAY="\$DISPLAY";  
  else
    sudo $self -u \$(whoami) "\$@" --env XCHROOT_MYROOT="\$XCHROOT_MYROOT" --env XCHROOT_ESCAPE_XAUTHORITY="\$XCHROOT_ESCAPE_XAUTHORITY" --env XAUTHORITY="\$XAUTHORITY" --env DISPLAY="\$DISPLAY";  
  fi
}

# for further information on how to address issues with su and Xorg
# see at: www.elstel.org/xchroot, or /usr/share/doc[/packages]/xchroot
#

EOQ
}

STD_X_PATH="/bin:/usr/local/bin:/usr/bin:/sbin:/usr/local/sbin:/usr/sbin"

findxpath() { 
  local root="${2%/}" dir; 
  for dir in ${3//:/ }; do 
    dir="${dir#/}"; dir="${dir%/}"; 
    if [[ -x "$root/$dir/$1" ]]; then echo "/$dir/$1"; return 0; fi; 
  done; 
  return 1; 
}

mkfchdir() {
  findxpath fchdir "$root" "$STD_X_PATH" && return 0;
  fchdirFile=$(which fchdir) 2>&9
  [[ -x "$fchdirFile" ]] || { gcc -static -x c /dev/stdin -o /usr/local/bin/fchdir <<<$'#include <unistd.h>\n#include <stdlib.h>\n#include <stdio.h>\n\nint main(int argc,char *argv[]) {\n  int ret;\n  if(argc<3) { fprintf(stderr,"fchdir filedescriptornumber program args\\n\\n"); return 252; }\n  ret=fchdir(atoi(argv[1]));\n  if(ret) { perror("fchdir"); return 253; }\n  ret=execvp(argv[2],argv+2);\n  perror("execvp"); return 254; \n}\n'; fchdirFile=/usr/local/bin/fchdir; }
  [[ -x "$fchdirFile" ]] || { echo "gcc not installed; could not create fchdir for escaping from chroot" >&2 ; return 1; }
  cp "$fchdirFile" $root/usr/local/bin/
  echo "/usr/local/bin/fchdir"
}

xchppids() {
  local prn; let prn=0;
  if [[ "$1" = "--list" ]];then let prn=1;
  elif [[ "$1" = "--all" ]];then let prn=2;
  fi
  let lastpid=0 pid=$$ ppid=1 n=1;
  [[ prn -ne 0 ]] && pid=$(ps -p $pid -o ppid h);
  while [[ ppid -ne 0 && n -le 100 ]]; do
    read ppid prog args < <( ps -p $pid -o ppid,comm,args h; );
    if [[ "$prog" = "xchroot" ]]; then
      if [[ -d "/proc/$lastpid/fd/89" ]]; then
        [[ prn -eq 0 ]] && { result="/proc/$lastpid/fd/89"; return 0; }
	echo "/proc/$lastpid/fd/89 $pid $comm $args";
      else
	[[ prn -ne 0 ]] && echo "noescape $pid $comm $args";
      fi
    elif [[ prn -eq 2 ]]; then 
      echo "- $pid $comm $args";
    fi
    #echo "#$ppid#$pid#$prog#$args#" >&2;
    let n+=1 lastpid=pid pid=ppid;
  done
  result=""; return -1;
}

list-desktop-categories() {
  sed -n 's#^Categories=##p' /usr/share/applications/*.desktop | tr ';' '\n' | sort -u | grep -v "^$"
}

createstartup-exec() {
  shift 3;
  (
    echo "[Desktop Entry]";
    echo "Name=$name [$prnroot]"
    if [[ escape -eq 0 ]]; then
      echo "Comment=direct xchroot startup for $name in $prnroot"
      echo "Exec=$xchroot $srcfile ${@@Q} --noask"
      echo "TryExec=$srcfile"
    else
      echo "Comment=direct xchroot startup for $name in $prnroot-chroot"
      echo "Exec=$xchroot $prnroot $src ${@@Q} --noask"
      echo "TryExec=$xchroot"
    fi
    echo "Terminal=false"
    echo "Type=application"
    echo "Categories=${category%;};"
    echo "StartupNotify=false"
  )  >"$dest"
}


createstartup() {
  let escape=0
  if [[ "$1" = "--command" ]]; then 
    [[ $# -lt 4 || "$2" != "--category" ]] && { echo "xchroot createstartup --category xy root" >&2; exit 1; }
    category="$3";
    if [[ "$4" = "escape" ]]; then let escape=1; shift; fi
    source="$4"; srcfile="$4";
    [[ -z "$4" ]] && { echo "executable was not given" >&2; exit 1; }
    [[ "${source%.desktop}" != "$source" ]] && echo "${rot}warning: .desktop file used with --command rather than --desktop${nv}" >&2;
    mode="command"
  elif [[ "$1" = "--desktop" ]]; then
    nowhich=false; keephome=0; category="";
    while [[ "${2#--}" != "$2" ]]; do
      [[ "$2" = "--" ]] && break;
      if [[ "$2" = "--no-which" ]]; then nowhich=true; shift; fi
      if [[ "$2" = "--keep-home" ]]; then keephome=1; shift; fi
      if [[ "$2" = "--category" && -n "$3" ]]; then category="$category${category:+;}$3"; shift 2; fi
    done;
    if [[ "$2" = "escape" ]]; then let escape=1; shift; fi
    source="$2"; srcfile="$2";
    [[ -z "$2" ]] && { echo "desktop file was not given" >&2; exit 1; }
    [[ $# -gt 2 ]] && { echo "too many parameters" >&2; exit 1; }
    mode="desktop-file"
  else
    echo "unknown createstartup mode '$1'" >&2
    exit 1;
  fi
  if [[ escape -eq 0 ]]; then
    if [[ "${source#/}" = "$source" ]]; then
      dirname="$(dirname "$source")";
      [[ -d "$dirname" ]] && { echo "directory $dirname does not exist" >&2; exit 1; }
      pushd "$dirname" 2>&9 >&9  && {
	source="$(pwd)/$(basename "$source")";
	popd >&9
      }
      unset dirname
    fi 9>/dev/null
    root="${source#/}"; root="${root%%/*}" src="${source#/$root}"; [[ "$src" = "$source" ]] && src="${source#$root}"
    while read user who nopasswd self uopt asuser thisroot rest; do
      if [[ "$who $nopasswd $uopt" = "ALL=(root) NOPASSWD: [-]u" ]]; then 
	let i=${#thisroot}-1;
	[[ i -gt 0 && "${thisroot:i:1}" = "*" ]] && thisroot="${thisroot:0:i}";
	if [[ "${source#$thisroot}" != "$source" ]]; then
	  src="${source#$thisroot}";      # root ... chroot found in sudoers, src ... rest 
	  root="$thisroot";
	  if [[ "${thisroot%/}" = "$thisroot" ]]; then   # if root is not an individual directory but a dir-wildcard
	    root="$root${src%%/*}";   # f.i. #$root#$src# = #/dst/de#b32/usr/bin/wine#
	    src="${source#$root}";  #echo "#$root#$src#"
	  else
	    src="/$src";
	  fi
	  root="${root%/}";
	  break;
	fi
      fi
    done < <( grep xchroot /etc/sudoers; );
    if [[ ! -d "$root/bin" ]]; then
      src="${src#/}"
      while [[ "${src%/*}" != "$src" && ! -d "$root/bin" ]]; do
	nextdir="${src%%/*}"; src="${src#*/}"
	root="$root/$nextdir";
      done
      src="/$src";
    fi
    [[ -d "$root" ]] || { echo "${rot}root directory not found in sudoers or down from /; you should also state a file with absolute path.${nv}" >&2; exit 3; }
    rootname="${root##*/}"; prnroot="$root";
  else
    source="${source#/dev/fd/89}"; src="$source";
    srcfile="/dev/fd/89/${source#/}";
    rootname="escape"; prnroot="escape"; root="/dev/fd/89";
  fi;
  sudo="$(which sudo 2>&9)"; [[ -z "$sudo" ]] && echo "${rot}warning: sudo not installed; starting .desktop will fail.${nv}" >&2 
  sudo="${sudo:-/bin/sudo}"
  if [[ "$mode" = "command" ]]; then 
    name="${src##*/}"; 
    isin $name wine wine-stable && name="${5##*/}"
    name="${name%.exe}"; name="${name%.com}"; name="${name##*\\}";
    isin $name mviewer2 && [[ -n "$6" ]] && { name="${6##*/}"; name="${name%.*}"; }
    dest="/usr/share/applications/$name-$rootname.desktop";
  else
    if [[ keephome -eq 1 || "${src#/home}" = "$src" ]];
      then dest="${src%.desktop}-$rootname.desktop";
      else basename="$(basename "${src%.desktop}")";
	   dest="/usr/share/applications/$basename-$rootname.desktop";
    fi
  fi
  xchroot="$(which xchroot)";
  while [[ -L "$srcfile" ]]; do
    target="$(readlink "$srcfile")"
    if [[ "${target#/}" != "$target" ]];
      then srcfile="$root$target";
      else srcfile="${srcfile%/*}/$target"
    fi
  done
  root="${root%/}/";
  [[ -f "$srcfile" ]] || { echo "file not found $source (may be a dead link) - $srcfile"; exit 2; }
  echo "source file: $srcfile" >&2;
  echo "destination file: $dest" >&2;
  echo "root: $root" >&2;
  echo "xchroot: $xchroot" >&2;
  [[ -e "$dest" ]] && { echo "${rot}error: destination file already exists.${nv}" >&2; exit 4; }
  if [[ "$mode" = "command" ]]; then 
    shift; createstartup-exec "$@"
    return $?
  fi
  if [[ "${srcfile%.desktop}" = "$srcfile" ]]; then
    err "error: source file must be a .desktop file";
    return 3;
  fi
  icon=""; hadnotify=0; hadcategory=0; hadsessionfile=0;
  execnamebase="${src%.desktop}";
  {
    while read; do
      varname="${REPLY%%=*}";
      value="${REPLY#*=}";
      if [[ "${varname}=${value}" != "$REPLY" ]]; then echo "$REPLY"; continue; fi
      if [[ "${varname#DesktopNames}" != "$varname" ]]; then  
	echo "$varname=$value [$prnroot]";
	let hadsessionfile=1;
      elif [[ "${varname#Name}" != "$varname" ]]; then  # actually the description || "${varname#GenericName}" != "$varname"  || "${varname#X-GNOME-FullName}" != "$varname"
	echo "$varname=$value [$prnroot]";
      elif [[ "$varname" = "Categories" ]]; then 
	echo "Categories=${value%;}${category:+;}${category%;};"
	hadcategory=1;
      elif [[ "$varname" = "Icon" ]]; then 
	icon="$value";
	iconbase="$icon"; maxres=-1;
	while read name; do
	  res="${name#${root}usr/share/icons/}"; 
	  let i=0; while [[ i -lt ${#res} ]] && ! isin "${res:i:1}" 0 1 2 3 4 5 6 7 8 9; do let i+=1; done
	  let j=i+1; while [[ j -lt ${#res} ]] && isin "${res:j:1}" 0 1 2 3 4 5 6 7 8 9; do let j+=1; done
	  let res=${res:i:j-i}
	  if [[ res -gt maxres ]]; then
	    icon="$name";
	    let maxres=res;
	  fi
	done < <( find ${root}usr/share/icons/ -name "$iconbase.png"; );
	echo "icon: $icon" >&2;
	echo "$varname=$icon";
      elif [[ "$varname" = "Exec" ]]; then
	exe="${value%% *}"; if [[ "$exe" != "$value" ]]; then value="${value#* }"; else value=""; fi
	value="${value//\\/\\\\}"
	if [[ "${exe#/}" = "$exe" ]]; then
	  newexe="";
	  if ! $nowhich; then
	    for path in /bin /usr/local/bin /usr/bin /opt/kde3/bin /opt/kde3/sbin /usr/lib/qt3/bin/ /usr/X11R6/bin /opt/gnome/bin /sbin /etc/init.d /usr/local/sbin /usr/sbin; do
	      [[ -x "$root$path/$exe" ]] && newexe="$path/$exe";
	    done
	  fi
	  if [[ -n "$newexe" ]]; then
	    echo "Exec=$xchroot $prnroot $newexe $value --noask";
	    if [[ escape -eq 0 ]]; then echo "TryExec=$root${newexe#/}";  # test whether the executable still exists before trying to start it
	    else echo "TryExec=$xchroot";
	    fi
	    execnamebase="$newexe"
	  else
	    echo "Exec=$xchroot -t $prnroot $exe $value -- --shell /bin/bash --noask";
	    execnamebase="$exe"
	  fi
	else
	  echo "Exec=$xchroot $prnroot $exe $value --noask";
	  if [[ escape -eq 0 ]]; then echo "TryExec=$root${exe#/}"
	  else echo "TryExec=$xchroot";
	  fi
	  execnamebase="$exe"
	fi
      elif [[ "$varname" = "TryExec" ]]; then
	: pass
      elif [[ "$varname" = "StartupNotify" ]]; then
	echo "StartupNotify=false" 
	hadnotify=1;
      else
	echo "$REPLY";
      fi
    done 
    [[ hadcategory -eq 0 && -n "$category" ]] && echo "Categories=${category%;};";
    [[ hadnotify -eq 0 && hadsessionfile -eq 0 ]] && echo "StartupNotify=false";
  } <"$srcfile" >"$dest"
  if [[ hadsessionfile -ne 0 ]]; then
    execnamebase="$(basename "$execnamebase")";
    execname="/usr/local/bin/${execnamebase%-session}-session-$rootname"
    while [[ -e "$execname" ]]; do execname="${execname}I"; done
    { echo "#!/bin/dash";
      sed -n 's#^Exec=#exec #p' "$dest"
    } >"$execname"
    chmod +x "$execname";
    sed -i "s#^Exec=.*#Exec=$execname#" "$dest";
  fi
  echo >&2;
}

CleanAufsRoots() {  shopt -s extglob nullglob
  echo cleaning up all union/aufs-sideroots ...
  rm -fr /tmp/xchroot/{unionfs,aufs}-* 2>&9
  ls -d /tmp/xchroot/{unionfs,aufs}-*
  rmdir /tmp/xchroot/mount-*

  # nur subdirectories löschen; nicht die session cookies
  
  #  while [[ -n "$2" ]]; do
  #    echo "delete all [y/n]? :" $2-+([0-9])
  #    read -n 1 key; test "$key" = "y" && rmdir $2-+([0-9]); echo
  #    shift
  #  done
}

ShowMount() {
  mount | grep " on $root" | cut -f 3 -d ' ' | sort; echo
}

TermChRootProcesses() {
  # get child processes:  pstree -npT $$ | sed 's#[^(]*(##;s#[)][^(]*[(]#\n#g;s#)[^(]*$##'
  # get processes reparented to init: ps --ppid 1 -o pid h
  let haddblk=0;
  while true; do
    pss=""; let haddbl=0; 
    for p in $(ps --ppid 1 -o pid h) $(pstree -npT $$ | sed 's#[^(]*(##;s#[)][^(]*[(]#\n#g;s#)[^(]*$##'); do
      [[ $$ -eq $p ]] && continue;
      link="$(readlink /proc/$p/root)"; 
      [[ -z "$link" ]] && continue;
      unset np
      if [[ "$link" = "$absroot" ]]; then np=$p
      elif [[ -n "$action" ]]; then
       	chrpid="${link#/tmp/xchroot/mount-$xrootname-}";
        [[ "$chrpid" != "$link" && ! -d "/proc/$chrpid" ]] && np=$p
      fi
      if [[ -n "$np" && haddblk -le 3 && "$(ps -p $np h -o comm)" = "dbus-launch" ]]; then kill $np; let haddbl=1 haddblk+=1;
      elif [[ -n "$np" ]]; then pss="$pss $np";
      fi
    done
    [[ haddbl -ne 0 ]] && { sleep 0.01; continue; }
    [[ -z "$pss" ]] && return 0;
    if [[ verbose -gt 0 ]] || ! $noask; then
      msg "some processes are still running in the chroot."
      ps -p $pss; echo
    fi
    $noask && return 1;
    echo -n "${ul}K${nv}ill them, Kill -${ul}9${nv} them, fire up a ${ul}B${nv}ash, ${ul}R${nv}etry, ${ul}L${nv}eave without killing [K/9/B/R/L]?";
    while true; do read -n 1 key; echo;
      case $key in
	b|B) msg "type exit when you are done."; bash;; k|K) kill $pss; sleep 1.3;; 9) kill -9 $pss; sleep 1;; r|R) sleep 0.3;; l|L) return 1;; *) continue;;
      esac
      break;
    done
  done
}

cleanup() {
  # everything inside root will be umounted now; avoid mirroring the unmounts out
  if [[ "$root" != "/" ]]; then
    mylockfile-create $MMCNFFILE.lock
    if [[ -n "$mirror_out" ]]; then
      echo "$$ exiting $root" >>$MMCNFFILE
    fi
    # remember that bash has to fork for $(..); as a consequence we have to subtract one
    #instancesrunning="$(ps axh -o pid,comm,args | grep "^[[:space:]]*[0-9]*[[:space:]]xchroot " | egrep -c "$root/*( |\t|$)" | awk '{ print $1; }')"
    instancesrunning=($(ps axh -o pid,comm,args | awk '/^[ \t]*[0-9]*[ \t]xchroot[ \t]*.*[ \t]'"${root////\\/}"'\/*([ \t/]|$)/{ if($1!='"$$"') print $1; }'))
    #echo "#${#instancesrunning[@]}"
    if [[ ${#instancesrunning[@]} -gt 1 ]]; then 
      if [[ verbose -gt 0 ]]; then
	msg "several xchroot instances (${#instancesrunning[@]}) are running on the same root; not doing any umounts";
	if [[ verbose -ge 2 ]]; then
	  echo "(last terminating xchroot instance should unmount and terminate xchroot child processes)." >&2;
	  ps -o pid,comm,args -p ${instancesrunning[@]}
	  echo >&2;
	fi
      fi
      doumount=0; let result=132;
    fi
    if [[ doumount -gt 0 ]]; then 
      TermChRootProcesses || {
	if [[ verbose -gt 0 ]] || ! $noask; then
	  msg "you have chosen to leave some programs running;"; msg "not umounting anything; - do this at a later time";
	  if $tailopt; then echo "xchroot ${options# } cleanup $origroot --" >&2; echo >&2;
	  else echo "xchroot ${options# } cleanup $origroot" >&2; echo >&2;
	  fi
	fi
	let result=100; if [[ -n "$unionfs" && "$action" != "cleanup" ]] && AnyChanged /tmp/xchroot/$unionfs-$xrootname-$$; then let result+=16; fi
	mylockfile-remove $MMCNFFILE.lock
	return $result;
      }
    fi
  fi
  let errbits=0
  # [[ -e "$tmprd/startup-$$" ]] || echo "can not delete $tmprd/startup-??; delete the appropriate file by hand."
  # delete command line file
  if [[ "$action" != "cleanup" ]]; then
    rm "$tmprd/startup-$$" || let errbits\|=4
    [[ -n "$DBUS_SESSION_CONF" ]] && rm -f "$tmprd/$DBUS_SESSION_CONF"
    [[ -n "$rd_ENV_FILE" ]] && rm -f "$rd_ENV_FILE"
    [[ -n "$socker" ]] && kill $socker

  else
    rm -f "$tmprd/session-${absroot////_}-$tgUser.conf"
    if [[ doumount -gt 0 ]]; then
      while read socker rest; do
	kill $socker;
      done < <( ps axh -o pid,comm,args | grep "^[0-9[:space:]]*[[:space:]]socat " | grep "$root/tmp/.X11-unix/"; )
    fi

  fi
  # stale startup file deletion, also delete psenv-$$ files referenced from there
  for startupfile in "$tmprd"/startup-*; do
    pid="${startupfile#$tmprd/startup-}"
    if [[ ! -d /proc/$pid ]]; then
      rm $startupfile 
    fi
  done

  # stale xauthf & otherfile deletion
  does_exist=0; for i in "$tmprd"/startup-*; do does_exist=1; break; done
  used_psenvf=();
  if [[ does_exist -ne 0 ]];
    then 
      IFS=$'\n' read -a used_xauthf -d $'\004' < <(sed -n "s#^export XAUTHORITY='##;Te;s#'\$##;p;:e" "$tmprd"/startup-*)
      local -i restoreNullGlob=1; shopt nullglob | grep -q on && let restoreNullGlob=0
      shopt -s nullglob
      for sf in "$tmprd"/startup-*; do
	{ read; read source_cmd envfile; } <$startupfile
        [[ "$source_cmd" = "source" ]] && used_psenvf[${#used_psenvf[@]}]="$envfile";
      done
      [[ restoreNullGlob -eq 1 ]] && shopt -u nullglob
    else 
      used_xauthf=();
  fi

  for path_xauthf in "$tmprd"/xauth-*; do
    xauthf="/tmp/xchroot${path_xauthf#$tmprd}";
    isin "$xauthf" "${used_xauthf[@]}"|| rm "$path_xauthf"
  done

  for path_psenvf in "$tmprd"/psenv-*; do
    psenvf="/tmp/xchroot${path_psenvf#$tmprd}";
    isin "$psenvf" "${used_psenvf[@]}"|| rm "$path_psenvf"
  done

  [[ "$root" = "/" ]] && return $errbits

  #echo "*****"
  #[[ doumount -gt 0 ]] && rm "$tmprd/$xauthf-$tgUser"
  mtab=/proc/mounts; [[ -e "/proc/mounts" ]] || mtab=/etc/mtab
  case $doumount in
    1|2) for submp in /dev /sys /run /selinux $ADDSTDMOUNT ${mirror_in//:/ } ${startup_mount//:/ } /proc /tmp/.X11-unix /tmp /var/tmp /boot/efi /boot/EFI; do
         awk "/^[^ ]+[ ]+${root////\\/}${submp////\\/}[/ ]/{ print \$2; }" <$mtab | sort -r | while read mp; do vmsg 3 "umounting $mp"; umount "$mp"||let errbits\|=2; done 
       done;
       check_umount_others;;
    0) [[ verbose -gt 0 ]] && [[ ${#instancesrunning[@]} -le 1  ]] && msg "leaving everything mounted; umount later on.";; 
    *) err "umounting error.";;
  esac

  if [[ -n "$unionfs" || -d "/tmp/xchroot/mount-$xrootname-$$" ]]; then
    if [[ "$action" != "cleanup" ]]; then

      if [[ "$unionfs" = "aufs" ]]; then
	# make changes to hard linked files permanent
	auplink /tmp/xchroot/mount-$xrootname-$$ flush || let errbits\|=8;
      fi
      stillovermounted=$( mount | grep "on /tmp/xchroot/mount-$xrootname-$$/." | wc -l )
      if [[ stillovermounted -gt 0 ]]; then
	err "cannot umount aufs root; some over--bind-mounts still present; please umount first."
	mount | grep "on /tmp/xchroot/mount-$xrootname-$$/."
	err "xchroot ${options# } cleanup $origroot"; echo >&2;
	let errbits\|=1;

      elif umount /tmp/xchroot/mount-$xrootname-$$; then
	rmdir /tmp/xchroot/mount-$xrootname-$$ || let errbits\|=4
	checkSaveRemove $$ /tmp/xchroot/$unionfs-$xrootname-$$ 
	cleanupLayerDir $addlayerdir
      else 
	echo "xchroot ${options# } cleanup $origroot" >&2; echo >&2;
	let errbits\|=2;
	bash -i
      fi

    else	# action = cleanup
      for mntdir in /tmp/xchroot/mount-$xrootname-*; do
	msg "approaching to umount $mntdir ..."; mntdir="${mntdir%/}"
	grep "[[:space:]]*[^[:space:]]\+[[:space:]]\+$mntdir/..*" $mtab | sort -u | while read what mountpoint rest; do
	  umount -f $mountpoint || { let errbits\|=1; err "error umounting $mountpoint ..."; }
	done
	pid=${mntdir#/tmp/xchroot/mount-$xrootname-}
	if [[ "$unionfs" = "aufs" ]]; then
	  # make changes to hard linked files permanent
	  auplink /tmp/xchroot/mount-$xrootname-$pid flush || let errbits\|=8;
	fi
	if umount $mntdir; then
	  rmdir $mntdir || let errbits\|=4
	  unionfsdir=$(for d in /tmp/xchroot/{unionfs,aufs}-$xrootname-$pid; do [[ -d "$d" ]] && echo $d; done )
	  # set $unionfs, $addlayerdir and $addbranch for checkSaveRemove
	  unionfs=${unionfsdir#/tmp/xchroot/}; unionfs=${unionfs%%-*}
	  addlayerdir=/tmp/xchroot/squashfs-$pid
	  if [[ "$unionfs" = "unionfs" ]]; then
	    addbranch=":$addlayerdir=RO";
	  else
	    addbranch=":$addlayerdir=rr+wh";	  # real-readonly + acknowledging whiteout files
	  fi
	  unionrwbranch="/tmp/xchroot/$unionfs-$xrootname-$pid";
	  checkSaveRemove $pid $unionfsdir; 				# /tmp/xchroot/{unionfs,aufs}-$xrootname-$pid
	  cleanupLayerDir $addlayerdir
	  #rm -fr /tmp/xchroot/{unionfs,aufs}-$xrootname-$pid || let errbits\|=4
	else 
	  err "error umounting $mntdir ..." >&2;
	  echo "xchroot ${options# } cleanup $origroot" >&2; echo >&2;
	  let errbits\|=2;
	fi
      done

    fi
  fi

  case $doumount in
    2) awk "/^[^ ]+[ ]+${origroot////\\/}/{ print \$2; }" <$mtab | sort -r | while read mp; do vmsg 2 "umounting $mp"; umount "$mp"||let errbits\|=2; done;;
  esac
  mylockfile-remove $MMCNFFILE.lock

  [[ verbose -gt 0 ]] && echo
  let result+=errbits
  return $result
}

cleanupsilent() {
  noask=true; traphandler=true;
  cleanup
}

cleanupLayerDir() {
  if [[ -d "$1" ]]; then
    if umount "$1"; then
      rmdir "$1" || let errbits\|=4
    else let errbits\|=2; fi
  fi
}

AnyChanged() {
  let hasfiles=1;
  { if read line; then
      let hasfiles=0; # success
    fi
  } < <( find "$1" -type f | egrep -v "/\.bash_history$|/\.wh\.\.wh\."; )
  return $hasfiles;
}

checkSaveRemove() { 
  # ~unionrwbranch
  pid=$1
  unionrwdir=/tmp/xchroot/${2#/tmp/xchroot/}
  #msg "checkSaveRemove $unionrwdir"
  if [[ -z "$save" ]] && ! $noask && AnyChanged $unionrwdir; then
    msg "save changes to chroot environment? ${nv} - $unionrwdir"; echo "Press <Enter> or enter an empty line to continue without saving." >&2;
    echo "note: filename will be appended with -$unionfs.squashfs" >&2
    while true; do
      read -p "filename to save to [list content before: ??<return>]: " save
      if [[ "$save" = "??" ]]; then
	find $unionrwdir
	continue;
      fi
      save="${save%.squashfs}"; save="${save%-$unionfs}"
      [[ -z "$save" ]] && break;
      save="$save${unionfs:+-}$unionfs.squashfs";
      saveok "$save" && break;
      #err "file does already exist or any other error trying to save at $save."
    done  
  fi
  let imgcreaterr=0
  if [[ -n "$save" ]]; then
    msg "mksquashfs --normalized $unionrwdir $addlayerdir $save $squashopts"
    [[ "${save:0:1}" != "/" ]] && save="$(pwd)/$save"
    if [[ -z "$addlayerdir" ]]; then
      pushd $unionrwdir >&9 && \
	echo mksquashfs ./ $save $squashopts
	mksquashfs ./ $save $squashopts || let imgcreaterr=1
      popd >&9

    else
      mountdir=/tmp/xchroot/mount-$xrootname-$pid; mkdir $mountdir
      if [[ "$unionfs" = "unionfs" ]]; then
        echo unionfs suid,dev "$unionrwbranch"=RO$addbranch "$mountdir"
        unionfs -o cow,suid,dev "$unionrwbranch"=RO$addbranch "$mountdir" || let imgcreaterr=1
      else
	echo mount -t aufs -o udba=none,br:"$unionrwbranch"=ro+wh$addbranch none "$mountdir"
	mount -t aufs -o udba=none,br:"$unionrwbranch"=ro+wh$addbranch none "$mountdir" || let imgcreaterr=1
      fi
      pushd $mountdir >&9
      mksquashfs ./ $save $squashopts || let imgcreaterr=1
      popd >&9
      if umount $mountdir; then rmdir $mountdir || let errbits\|=4; else let errbits\|=2; fi

    fi
    [[ -e "$save" ]] && chown $user:$group $save
  fi
  if [[ imgcreaterr -le 0 ]]; then
    rm -fr $unionrwdir || let errbits\|=4
  else
    let errbits\|=8;  # an error occurred when trying to freeze changes
    let errbits\|=16;  # nothing deleted; indicate that it can still be rescued
  fi
}

case $1 in
  bashrclines) bashrclines; exit $?;;
  *) ;;
esac

amiroot() {
  [[ $(id -u) -ne 0 ]] && { err "xchroot must be run as root.";echo; exit 200; }
}

makemeroot() {
  if [[ $(id -u) -ne 0 ]]; then
    psenvp=();
    if [[ PRESERVE_ENV -ne 0 && -z "$1" ]]; then
      psenvf=/tmp/xchroot/psenv-$$; env | while read; do var="${REPLY%%=*}"; value="${REPLY#*=}"; echo "$var=${value@Q}"; echo "export $var"; done >$psenvf;
      psenvp=("--setenv" "$psenvf");
    fi
    if [[ hadenv -eq 0 ]]; then
      let lsp=${#original_params[@]}-1;
      if [[ "${original_params[lsp]}" = "--noask" ]]; then 
	original_params[lsp]="--env"; # does not work: unset original_params[lsp]; 
        original_params=("${original_params[@]}" "XCHROOT_MYROOT=${XCHROOT_MYROOT@Q}" "--env" "XCHROOT_ESCAPE_XAUTHORITY=$XCHROOT_ESCAPE_XAUTHORITY" "${psenvp[@]}" "--env" "XAUTHORITY=$XAUTHORITY" "--env" "DISPLAY=$DISPLAY" --noask);
      else
        original_params=("${original_params[@]}" "--env" "XCHROOT_MYROOT=${XCHROOT_MYROOT@Q}" "--env" "XCHROOT_ESCAPE_XAUTHORITY=$XCHROOT_ESCAPE_XAUTHORITY" "${psenvp[@]}" "--env" "XAUTHORITY=$XAUTHORITY" "--env" "DISPLAY=$DISPLAY");
      fi
    fi 
    #echo "${original_params[@]}"
    if [[ -n "$tgUserGroup" ]]; then
      #echo exec sudo "$0" "${original_params[@]@Q}"
      if [[ noescape -eq 0 && -e /dev/fd/89 ]]; 
	then exec sudo -C 90 "$0" "${original_params[@]}"
	else exec sudo "$0" "${original_params[@]}"
      fi
    else
      #echo exec sudo "$0" -u $(whoami) "${original_params[@]@Q}"
      if [[ noescape -eq 0 && -e /dev/fd/89 ]]; 
        then exec sudo -C 90 "$0" -u $(whoami) "${original_params[@]}"
	else exec sudo "$0" -u $(whoami) "${original_params[@]}"
      fi
    fi
  fi
}

case $1 in
  cleanup) makemeroot "$1"; shift; action=cleanup
    if [[ "$Xconnect" = "mntmp" ]]; then tmprd="/tmp/xchroot"; else tmprd="$root/tmp/xchroot"; fi
    ;;
  cleanaufsroots) makemeroot "$1"; shift; action=CleanAufsRoots;;
  ppids) xchppids --all; exit $?;;
  xchppids) xchppids --list; exit $?;;
  showmount) shift; action=ShowMount;;
  mirror-mountd) mirror-mountd;;
  addsudoers) amiroot; shift; addsudoers "$@"; exit $?;;
  delsudoers) amiroot; shift; delsudoers "$@"; exit $?;;
  listsudoers) makemeroot "$1"; shift; listsudoers "$@"; exit $?;;
  listrunmounts) echo "--on ${MNTON// /:} (nwm=NetworkManager), DBUS_SESS=$DBUS_SESS (control with sess)"; exit 0;;
  createstartup) amiroot; shift; createstartup "$@"; exit $?;;
  list-desktop-categories) list-desktop-categories; exit 0;;
  inst-root-sudo) instrootsudo "$@"; exit $?;;
  *) makemeroot; action=""; [[ verbose -gt 0 ]] && echo $'\e[;33mxchroot - visit us on www.elstel.org/xchroot\e[0m' >&2; ;;
esac;

if [[ -n "$dirpfx" ]]; then
  err "--dirpfx option may only be given for xchroot addsudoers";
  exit 255;
fi

root="${1%/}"; [[ -z "$root" ]] && root="/"; shift

if [[ "$root" = "/" ]]; then tgUID="$(getent passwd $tgUser | cut -f 3 -d :)"
			else tgUID="$(grep "^${tgUser}:" $roroot/etc/passwd | cut -f 3 -d :)"
fi

if [[ "$root" = "escape" ]]; then
  if [[ -d /dev/fd/89 ]]; then 
    root=/dev/fd/89;
  else
    if xchppids; then root="$result";
    else echo "error: inside --no-escape chroots, only or not in a chroot" >&2; exit 228;
    fi
  fi
  [[ -n "$unionfs" ]] && {  echo "error: no aufs/unionfs escapes possible: not implemented" >&2; exit 228; }
  if [[ -n "$mirror_in$mirror_out$startup_mount" ]]; then echo echo "error: no mount-mirroring on xchroot escapes" >&2; exit 255; fi
  let isescape=1 noescape=1;
fi
command="";

if [[ -f "$root" ]]; then
  while read user who nopasswd self uopt asuser thisroot rest; do
    if [[ "$who $nopasswd $uopt" = "ALL=(root) NOPASSWD: [-]u" && "${root:0:1}" = "/" ]]; then 
      let i=${#thisroot}-1;
      [[ i -gt 0 && "${thisroot:i:1}" = "*" ]] && thisroot="${thisroot:0:i}";
      if [[ "${root#$thisroot}" != "$root" ]]; then
        command="${root#$thisroot}"; 
	root="$thisroot";
	if [[ "${thisroot%/}" = "$thisroot" ]]; then
	  root="$root${command%%/*}";
	  command="${command#*/}";
	fi
	command="/$command ";    # space at the end
	root="${root%/}";
	break;
      fi
    fi
  done < <( grep xchroot /etc/sudoers; );
  if [[ ! -d "$root/bin" ]]; then
    command="${command#/}"
    while [[ "${command%/*}" != "$command" && ! -d "$root/bin" ]]; do
      nextdir="${command%%/*}"; command="${command#*/}"
      root="$root/$nextdir";
    done
    command="/$command";
  fi
fi

#echo "##$root" >&2
#echo "##$command" >&2

if [[ ! -d "$root" ]]; then
  echo "root directory '$root' not found." >&2
  echo
  exit 1
fi

if [[ "${root:0:1}" != "/" ]]; then
  pushd "$root" >&9 || exit 1
  root="$(pwd)"
  popd >&9
fi

xrootname="${root##*/}"
origroot="$root"
curdir="$(pwd)"

if [[ -n "$unionfs" ]]; then
  roroot="$root";
  root="/tmp/xchroot/mount-$xrootname-$$";
  unionrwbranch="/tmp/xchroot/$unionfs-$xrootname-$$";
fi

absroot="$XCHROOT_MYROOT$root"

if [[ -n "$action" ]]; then
  $action
  exit $?
fi


[[ verbose -gt 0 ]] && echo -----------------------------------------------------------
exit2() { 
  result=$1;
  while [[ $# -gt 1 ]]; do
    # aufs may be half-mounted on ioctl errors
    grep -q "^none $2" /proc/mounts && umount $2
    rmdir $2
    shift
  done
  [[ verbose -gt 0 ]] && echo -----------------------------------------------------------; 
  exit $result; 
}

#
#  *** mount ***
#

mtab=/proc/mounts
[[ -e "/proc/mounts" ]] || mtab=/etc/mtab

#
#  mount --bind (1)
#
# mount points with spaces: use My\ Directory, but not: "My Directory"
# mp is second field: predeceded by [[:space::]], strip out comments
if [[ "$root" != "/" ]]; then
  mpp=($({ grep "[[:space:]]$origroot" /etc/fstab | grep -v "[[:space:]]*#" | while read mdev mp rest; do echo "$mp"; done; } | sort -u))
  for mp in "${mpp[@]}"; do cndmount "$mp"; done
fi

if [[ -n "$unionfs" ]]; then
  [[ -x "$(which mksquashfs 2>/dev/null)" ]] || warn "warning: mksquashfs not found: will not be able to safe image."
  mkdir -p "$root"
  mkdir -p "$unionrwbranch"
  if [[ -n "$restore" ]]; then
   if [[ -e "$restore" ]]; then
     addlayerdir=/tmp/xchroot/squashfs-$$
     mkdir "$addlayerdir" || exit2 223;
     mount -t squashfs -o loop "$restore" $addlayerdir
     if [[ "$unionfs" = "unionfs" ]]; then
       addbranch=":$addlayerdir=RO";
     else
       addbranch=":$addlayerdir=rr+wh";	  # real-readonly + acknowledging whiteout files
     fi
   else
     err "file '$restore' not found.";
   fi
  fi
  if [[ "$unionfs" = "unionfs" ]]; then
    echo unionfs -o cow,max_files=${maxfiles:-32768},allow_other,suid,dev${unionopts:+,}$unionopts "$unionrwbranch"=RW:"${roroot}"=RO "$root"
    unionfs -o cow,max_files=${maxfiles:-32768},allow_other,suid,dev${unionopts:+,}$unionopts "$unionrwbranch"=RW$addbranch:"${roroot}"=RO "$root" ||  exit2 221 "$root" $unionrwbranch $addlayerdir; 
  elif [[ "$unionfs" = "aufs" ]]; then
    echo mount -t aufs -o udba=${udba:-none}${unionopts:+,}$unionopts,br:"$unionrwbranch"=rw$addbranch:"${roroot}"=ro none "$root" 
    mount -t aufs -o udba=${udba:-none}${unionopts:+,}$unionopts,br:"$unionrwbranch"=rw$addbranch:"${roroot}"=ro none "$root" || exit2 221 "$root" $unionrwbranch $addlayerdir; 
  else err "unknown unification filesystem: $unionfs"; exit2 255;
  fi

fi

[[ -d "$root/bin" ]] || { 
  if [[ -n "$unionfs" ]]; then umount "$root"; rmdir "$root" $unionrwbranch $addlayerdir; fi
  err "$root does not look like chroot-environment (no /bin-dir); exiting."; 
  exit2 228; 
}

#
#  mount --bind (2)
#
if [[ isescape -eq 0 ]]; then

  mkdir -p $root/run/user
  chmod 755 $root/run $root/run/user

  mkdir -p $root/run/user/$tgUID
  if [[ -n "$tgGroup" ]]; then
    chown $tgUser:$tgGroup $root/run/user/$tgUID
  else # do it a bit sloppy since $tgGroup will never be used due to chmod 700
    chown $tgUser:$tgUser $root/run/user/$tgUID 2>/dev/null || chown $tgUser $root/run/user/$tgUID
  fi
  chmod 700 $root/run/user/$tgUID

fi

if [[ isescape -eq 0 && "$root" != "/" ]]; then

  cndmount --bind /dev "$root/dev"
  cndmount --bind /dev/pts "$root/dev/pts"
  cndmount --bind /dev/shm "$root/dev/shm"
  cndmount --bind /sys "$root/sys"
  cndmount --bind /proc "$root/proc"
  [[ -d "/selinux" ]] && cndmount --bind /selinux "$root/selinux"
  if [[ MIRRTMP -ne 0 ]]; then
    ADDSTDMOUNT="$ADDSTDMOUNT /tmp /var/tmp";
  else
    if [[ MNTMP -ne 0 ]]; then
      cndmount -t tmpfs none "$root/tmp"
      cndmount -t tmpfs none "$root/var/tmp"
    fi
    if [[ "$Xconnect" = "mntmp" ]]; then 
      cndmount --bind /tmp/.X11-unix "$root/tmp/.X11-unix/"
    fi;
  fi

  awk '/^[^ ]* \/sys\/[^ ]/{ print $2; }' $mtab | if [[ fastmnt -eq 0 ]]; then cat; else grep -v "^/sys/fs/"; fi | sort | while read mp; do
    cndmount --bind $mp "$root$mp" >&2
  done

  #cndmount --bind /run/udev "$root/run/udev"
  for runDir in $MNTON; do
    if [[ "$runDir" = "resolv.conf" ]]; then cndmount --bind /etc/$runDir $root/etc/$runDir 
    elif [[ "$runDir" = "efi" ]]; then efimount
    elif [[ "${runDir:0:1}" = "/" ]]; then cndmount --bind $runDir $root$runDir 
    elif [[ -e "/run/$runDir" ]]; then cndmount --bind /run/$runDir $root/run/$runDir 
    fi
  done

  #mkdir -p "$root/media"
  for sm in ${startup_mount//:/ } ${mirror_in//:/ }; do
    sm="/${sm#/}"; sm="${sm%/}";
    #if [[ -d "$root$sm" ]]; then 
    grep -q "^[^ ]+ $sm " $mtab && cndmount --bind $sm "$root$sm"
    awk '/^[^ ]+ '"${sm////\\/}"'\/[^ ]/{ print $2; }' $mtab | sort -u | while read mp; do
      if [[ "${mp#$root}" = "$mp" ]]; then    # prevent recursive mounts
	if [[ verbose -eq 2 ]]; then echo "mounting $mp" >&2; fi;
	cndmount --bind $mp $root$mp
      fi
    done
    #else err "could not create mount point $root/media: not mounted."
    #fi
  done

  for mp in $ADDSTDMOUNT; do
    cndmount --bind $mp "$root$mp"
  done

  if ! $NoAudio && ! grep "^[^ ]* $root/run " $mtab; then

    for i in /run/user/*/pulse; do
      uid=${i#/run/user/}; uid=${uid%/pulse};
      if ! grep -q "^[^ ]* $root/run/user/$uid " $mtab; then
	mkdir -p $root/run/user/$uid/pulse
	chown $uid $root/run/user/$uid
	cndmount --bind $i $root/run/user/$uid/pulse
	chown $uid $root/run/user/$uid/pulse;    # this will also fix the user issue for the baseroot/hostroot
      fi
    done

  fi

fi

#
# set some variables
#

tmprd="$root/tmp/xchroot"
mkdir -p "$tmprd"; chmod 1777 "$tmprd";
tmpd="/tmp/xchroot"
# $tmprd shall already be mounted now
if [[ -n "$ENV_FILE" && "${ENV_FILE#/tmp/}" != "$ENV_FILE" && MIRRTMP -eq 0 ]]; then
  [[ "${ENV_FILE:0:1}" != "/" ]] && ENV_FILE="$(pwd)/$ENV_FILE";
  rd_ENV_FILE="$root/$ENV_FILE";
  mv "$ENV_FILE" "$rd_ENV_FILE";
elif [[ -n "$ENV_FILE" ]]; then
  rd_ENV_FILE="$ENV_FILE";
  ENV_FILE="${ENV_FILE#$root}"
fi


#
# *** prepare dbus session.conf ***
#

if [[ DBUS_SESS -eq 1 ]]; then
  DBUS_SOCK="/run/user/$tgUID/bus"
  tg_DBUS_SESSION_BUS_ADDRESS="unix:path=$DBUS_SOCK"
  unset DBUS_SESSION_CONF;
  if [[ ! -e "$root$DBUS_SOCK" ]] && findxpath dbus-launch "$root" "$STD_X_PATH" >&9; then
    DBUS_SESSION_CONF="session-${absroot////_}-$tgUser.conf";
    if [[ ! -e "$tmprd/$DBUS_SESSION_CONF" ]]; then
      basefile="$root/usr/share/dbus-1/session.conf"; 
      if [[ -e "$basefile" ]]; then
	sed "s#<listen>.*</listen>#<listen>$tg_DBUS_SESSION_BUS_ADDRESS</listen>#" "$basefile" >"$tmprd/$DBUS_SESSION_CONF"
	grep -q "<listen>.*</listen>" "$tmprd/$DBUS_SESSION_CONF" || { rm -f "$tmprd/$DBUS_SESSION_CONF"; unset basefile; }
      fi
      if [[ -z "$basefile" ]]; then
	cat >"$tmprd/$DBUS_SESSION_CONF" <<EOQ
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <type>session</type>
  <keep_umask/>
  <listen>$tg_DBUS_SESSION_BUS_ADDRESS</listen>
</busconfig>
EOQ
      fi
  fi; fi
fi

#
# *** prepare startup file ***
#

if [[ isescape -eq 0 ]]; then
  #xauthf="xauth-${absroot////\\}-$$"
  # otherwise later on: XAUTHORITY=${XAUTHORITY////\\} xauth extract - $DISPLAY
  #xauthf="xauth-${absroot////:}-$$"
  #xauthf="xauth-${xrootname}-$$"
  xauthf="xauth-${absroot////_}"
  #xauthf="xauth-${absroot////\\}"
fi

#if ! [[ -x "$root$SHELL" ]];then
# if [[ -e "$root/bin/bash" ]]; then err "shell $SHELL not found in $root; bash-fallback applied."; SHELL="/bin/bash"; 
# elif [[ -e "$root/bin/csh" ]]; then err "shell $SHELL not found in $root; csh-fallback applied."; SHELL="/bin/csh"; 
# fi
#fi

if findxpath getent "$root" "$STD_X_PATH" >&9; then
  do_getent="getent passwd '$tgUser'"
else 
  if [[ -e "$root/etc/passwd" ]];
    then do_getent="grep '^$tgUser:' /etc/passwd";
    else do_getent="";
fi; fi


{

usingdash=0;
if [[ -x "$root/bin/bash" ]];then echo "#!/bin/bash"; usingdash=2; 
elif [[ -x "$root/bin/dash" ]];then echo "#!/bin/dash"; usingdash=1; 
elif [[ -x "$root/bin/sh" ]];then echo "#!/bin/sh";
fi >&8
[[ -n "$ENV_FILE" ]] && echo "source $ENV_FILE" >&8
if [[ isescape -eq 0 ]]; then
  echo "export XCHROOT_MYROOT='$XCHROOT_MYROOT$root'" >&8
  echo "export XCHROOT_NAME='$xrootname'" >&8
  echo "export XCHROOT_ESCAPE_XAUTHORITY=${XAUTHORITY@Q}" >&8
else 
  echo "unset XCHROOT_MYROOT XCHROOT_NAME XCHROOT_ESCAPE_XAUTHORITY" >&8
  echo "export XAUTHORITY=${XCHROOT_ESCAPE_XAUTHORITY@Q}" >&8
  echo "export XCHROOT_ESCAPED=1" >&8
fi
echo "export XCHROOT_USER='$tgUser'" >&8
echo "export XCHROOT_GROUP='$tgGroup'" >&8
if [[ -n "$do_getent" ]]; then
  echo "export HOME=\"\$($do_getent | cut -f 6 -d :)\"" >&8
fi
echo "export LOGNAME='$tgUser'" >&8
echo "export USER='$tgUser'" >&8;   # added lately
if [[ -z "$shell" ]]; then
  if [[ -n "$do_getent" ]]; 
    then echo "export SHELL=\"\$($do_getent | cut -f 7 -d :)\"" >&8
    else echo "export SHELL=/bin/sh" >&8
  fi
else
  echo "export SHELL='$shell'" >&8
fi
echo "export DISPLAY='$DISPLAY'" >&8
if [[ isescape -eq 0 ]]; then
 if [[ "$Xconnect" != "noX" ]]; then echo "export XAUTHORITY='$tmpd/$xauthf-$tgUser'" >&8
                                else echo "unset XAUTHORITY" >&8
fi;fi;
echo "export XDG_RUNTIME_DIR=/run/user/$tgUID" >&8;
[[ -n "$tg_DBUS_SESSION_BUS_ADDRESS" ]] && echo "export DBUS_SESSION_BUS_ADDRESS=$tg_DBUS_SESSION_BUS_ADDRESS" >&8;
[[ bashrc -gt 0 && usingdash -eq 2 ]] && echo "[ -e /etc/bash.bashrc ] && source /etc/bash.bashrc" >&8

if [[ $# -gt 0 ]]; then
  command="$command${@@Q}"
else 
  #if [[ bashrc -gt 0 ]]; then command="$SHELL"; else command="$SHELL --norc"; fi
  command="$command";
fi

if [[ "$tgUser" != "root" ]]; then
  #echo "su -c \"$command\" $tgUser" >&8
  echo 'unset MAIL' >&8
  echo 'if [ -z "$XCHROOT_GROUP" ]; then ' >&8
  echo '  gid="$(getent passwd "$XCHROOT_USER" | cut -f 4 -d :)"' >&8
  echo '  if [ -z "$gid" ]; then export XCHROOT_GROUP=nobody' >&8
  echo '  else export XCHROOT_GROUP="$(getent group $gid | cut -f 1 -d :)"' >&8
  echo '  fi; defaultgroup=true' >&8
  echo 'else defaultgroup=false' >&8
  echo 'fi ' >&8
  echo 'if [ $(id -u) -eq 0 ]; then' >&8
  echo '  if su --help 2>&1 | grep -q -- --group; then SUGRP="-g $XCHROOT_GROUP"; else SUGRP=""; fi' >&8
  echo '  if which sudo 1>/dev/null 2>/dev/null; then ' >&8
  echo '    if $defaultgroup; then exec sudo -u '"$tgUser $tmpd/startup-$$;" >&8
  echo '                      else exec sudo -u '"$tgUser -g \$XCHROOT_GROUP $tmpd/startup-$$; fi" >&8
  echo '  elif grep -q session-command <( su --help; ); then' >&8
  echo '    exec su $SUGRP --session-command '"$tmpd/startup-$$ $tgUser" >&8
  echo '  else ' >&8
  [[ verbose -ge 1 ]] && echo "    echo $'\e[0;31mold version of su; you may want to install sudo for bash to allow job control.\e[0m' >&2 " >&8
  echo "    exec su \$SUGRP -c $tmpd/startup-$$ $tgUser" >&8
  echo 'fi; fi' >&8
fi

if [[ -n "$DBUS_SESSION_CONF" ]]; then
# note that DBUS_SESSION_BUS_PID is only the pid of the dbus daemon not of its babysitter process; on exit the babysitter must be killed
  #echo -n 'test "$SHELL" != "/bin/csh" && eval `dbus-launch --sh-syntax --config-file="' >&8;
  #echo "$tmpd/$DBUS_SESSION_CONF"'" --exit-with-session;echo "export DBUS_SESSION_BUS_PID;"`' >&8
  #echo -n 'test "$SHELL" = "/bin/csh" && eval `dbus-launch --csh-syntax --config-file="' >&8;
  #echo "$tmpd/$DBUS_SESSION_CONF"'" --exit-with-session;echo "setenv DBUS_SESSION_BUS_PID=$DBUS_SESSION_BUS_PID;"`' >&8
  echo 'eval `dbus-launch --sh-syntax --config-file="'"$tmpd/$DBUS_SESSION_CONF"'" --exit-with-x11; echo "export DBUS_SESSION_BUS_PID;"`' >&8
fi

if [[ "${curdir#$root}" != "$curdir" ]]; then
  echo "if [ -d \"${curdir#$root}\" ]; then cd \"${curdir#$root}\"; else cd ~; fi" >&8
else
  if [[ -n "$do_getent" ]]; 
    then echo "if [ -d \"$curdir\" ]; then cd \"$curdir\"; else cd ~; fi" >&8
    else echo "if [ -d \"$curdir\" ]; then cd \"$curdir\"; fi" >&8
  fi
fi

if [[ verbose -gt 0 ]]; then
  if [[ "$root" != "/" ]]; then echo "if [ -e /etc/issue ]; then cat /etc/issue; else echo 'unknown Linux distro.';echo; fi" >&8
                           else echo "echo user $tgUser${tgGroup:+:}$tgGroup" >&8;
fi; fi

if [[ -n "$command" ]]; then
  if [[ -z "$shell" ]]; then
    echo "exec $command" >&8
  else
    #echo "exec $shell -c '$command'" >&8
    use_norc="";
    if [[ bashrc -le 0 ]] && [[ "$SHELL" = "/bin/bash" || "$SHELL" = "/bin/dash" ]]; then use_norc="--norc"; fi
    echo "exec $shell $use_norc -c \"${command//\"/\\\"}\"" >&8
  fi
else
  if [[ bashrc -le 0 ]]; then 
    echo 'if [ "$SHELL" = "/bin/bash" ]; then exec /bin/bash --norc; elif [ "$SHELL" = "/bin/dash" ]; then exec /bin/dash --norc; else exec $SHELL; fi ' >&8
  else
    #echo 'echo exec $SHELL' >&8
    echo 'exec $SHELL' >&8
  fi
fi


} 8>"$tmprd"/startup-$$

#cat $tmprd/startup-$$
#cp $tmprd/startup-$$ /home/elm/aux/startup-chroot


#set -- bash --norc
chmod +x "$tmprd"/startup-$$;
set -- $tmpd/startup-$$; 
#cat $tmprd/startup-$$


#
# *** establish Xorg interconnection ***
#

if [[ "$Xconnect" != "noX" && isescape -eq 0 ]]; then

  if [[ -z "$DISPLAY" ]]; then
    warn "\$DISPLAY not set; may not be able to run X programs \e[0m(use --noX to get rid of this message)";
  fi

  #xhost +localhost
  socker=''
  if [[ "${DISPLAY:0:1}" = ":" ]]; then

    sockno="${DISPLAY#:}"; sockno="${sockno%.*}";
    case $Xconnect in
      socat)
	if ! [[ -e $root/tmp/.X11-unix/X$sockno ]]; then
	  if ! which socat 2>&9 >&9 ; then err "socat not on path; please install it"; trap '' EXIT; cleanup; exit2 221; fi
	  mkdir -p $root/tmp/.X11-unix
	  if [[ -e $root/tmp/.X11-unix/X$sockno ]]; then
	    warn "$root/tmp/.X11-unix/X$sockno already exists; you may want to retry without socat.";
	  else
	    socat UNIX-LISTEN:$root/tmp/.X11-unix/X$sockno,fork UNIX-CONNECT:/tmp/.X11-unix/X$sockno & socker=$!
	  fi
	fi
	;;
      mntmp)
	# we have already mounted /tmp etc. so that our socks file is on path
	;;
      noX) ;; *) err "unknown X connection mode.";;
    esac

  fi

  userdir="$(getent passwd "$user" | cut -f 6 -d :)"; userdir="${userdir%/}/"


  if [[ "$Xconnect" != "noX" ]]; then
    tgXAUTH="$tmprd/$xauthf-$tgUser"
    touch "$tgXAUTH"
    chmod 600 "$tgXAUTH"
    chown $tgUser "$tgXAUTH"
    #: ${XAUTHORITY:=$userdir.Xauthority}
    #echo "${XAUTHORITY} $alldisplays" >&2;
    
    if [[ -n "$DISPLAY" ]]; then 
      # if DISPLAY is set attempt to extract or obtain a valid cookie for $DISPLAY
      # alldisplays -gt 0 && -z "$DISPLAY" may be a vaild invocation though
      #echo "XXXX"

      realDISP=${DISPLAY%.[0-9]}; realDISP=${realDISP%.[0-9][0-9]}; rawDISP=""; bareRawDISP=""; plainShortDISP="";
      dispHost=${realDISP%:*}; dispScreen=${realDISP#*:}; [[ "$dispHost" = "$dispScreen" ]] && dispHost="";
      if [[ -z "$dispHost" ]]; then
	rawDISP="unix:$dispScreen";
	bareRawDISP=":$dispScreen";
	dispHost="$(hostname)";
      else
	plainShortDISP="$dispHost:$dispScreen";
      fi
      # expected xauth-token is $plainDISP, other variants do normally not occur - still test for them as a backup
      plainDISP="$dispHost/unix:$dispScreen";

      xtrAuth() {
	xauth ${XAUTHORITY:+-f} $XAUTHORITY list | ( result=""; \
	  while read cookie rest; do
	    if [[ "$cookie" = "$plainDISP" ]]; then result="$plainDISP${result:+ }$result"; #break;
	    elif isin "$cookie" "$rawDISP" "$bareRawDISP" "$plainShortDISP"; then result="$result${result:+ }$cookie";
	    fi;
	  done; echo "$result"; )
      }
      trap 'xauth -b list' SIGINT
      useAuth="$(xtrAuth)"; let msgd=0
      #echo $plainDISP $plainShortDISP $rawDISP $bareRawDISP; 
      #echo useAuth="$useAuth";
      
      if [[ -z "$useAuth" ]]; then
	vmsg 2 "$DISPLAY $XAUTHORITY" sudo -u $user --preserve-env="DISPLAY,XAUTHORITY" xauth generate .
	sudo -u $user --preserve-env="DISPLAY,XAUTHORITY" xauth generate .
	ret=$?
	useAuth="$(xtrAuth)";
	if [[ -z "$useAuth" && ret -eq 0 ]]; then
	  vmsg 1 "internal error in xchroot: "xauth generate ." returned zero errors but no fitting xauth-token found" >&2
	  vmsg 1 "please report this bug; will use all xauth-tokens though this could be unsafe" >&2
	  [[ verbose -ge 1 ]] && let msgd=1
	fi
      fi
      trap '' SIGINT

      if [[ -z "$useAuth" && msgd -eq 0 ]]; then
	warn "warning: no xauth token; there may be no way for an X-connection" >&2
      fi
      #echo "done"
    fi

    # even if file mode is 0600 and the owner is non-root, root can still read the file under Linux
    if [[ alldisplays -gt 0 || -z "$useAuth" ]]; 
     then cat "${XAUTHORITY}"; 
     else xauth ${XAUTHORITY:+-f} $XAUTHORITY extract - $useAuth
    fi | sudo -u $tgUser xauth -f "$tgXAUTH" merge -

  fi

fi

# setup mount mirroring

if [[ -n "$mirror_in$mirror_out" && "$root" != "/" ]]; then 

  let running=0 mmpid=0
  if [[ -e $MMCNFFILE ]]; then
    read tag mmpid <$MMCNFFILE
    if [[ "$tag" = "&mirror-mountd" ]]; then
      if ps -p $mmpid -o pid h >&9; then let running=1; else let running=-1; fi
    fi
  fi

  mylockfile-create $MMCNFFILE.lock

  if [[ running -ne 1 ]]; then
    $0 mirror-mountd &
    let mmpid=$!; disown $mmpid;
  fi

  if [[ runnning -eq -1 ]]; then
    ( read tag rest; [[ "$tag" != "&mirror-mountd" ]] && echo "$tag $rest"; cat; ) <$MMCNFFILE >$MMCNFFILE.tmp
    rm $MMCNFFILE
  fi
  let mypid=$$; (
    [[ mmpid -ne 0 ]] && echo "&mirror-mountd $mmpid"
    [[ -n "$mirror_in" ]] && echo "$mypid in $root ${mirror_in//:/ }"
    [[ -n "$mirror_out" ]] && echo "$mypid out $root ${mirror_out//:/ }" 
  ) >>$MMCNFFILE
  if [[ runnning -eq -1 ]]; then cat $MMCNFFILE.tmp >>$MMCNFFILE; rm $MMCNFFILE.tmp; fi

  mylockfile-remove $MMCNFFILE.lock
  #echo lock released

fi

if [[ -n "$startup_mount" && "$root" != "/" ]]; then
  mylockfile-create $MMCNFFILE.lock
  echo "$$ cleanup $root ${startup_mount//:/ }" >>$MMCNFFILE
  mylockfile-remove $MMCNFFILE.lock
fi

#
# *** perform chroot ***
#

[[ verbose -ge 2 ]] && echo chroot "$root" "$@" >&2
result=0; traphandler=false;
if [[ isescape -eq 0 && "$root" != "/" ]]; then
  trap cleanupsilent EXIT
fi
#XAUTHORITY=/root/.Xauthority chroot "$root" "$@"

if [[ "$root" != "/" ]]; then

  if [[ "$tgUser" = "root" ]] || ! grep -q -- --userspec <( chroot --help; ); then
    if [[ noescape -eq 0 ]];
      then chroot "$root" "$@" 89</ 
      else chroot "$root" "$@"
    fi
  else
    if [[ noescape -eq 0 ]]; 
      then chroot --userspec=$tgUser${tgGroup:+:}${tgGroup} "$root" "$@" 89</
      else chroot --userspec=$tgUser${tgGroup:+:}${tgGroup} "$root" "$@"
    fi
  fi
  retval=$?;

else
  "$tmprd"/startup-$$
  retval=$?;
  rm -f "$tmprd/startup-$$" "$tmprd/$xauthf-$tgUser" 
  [[ -n "$DBUS_SESSION_CONF" ]] && rm -f "$tmprd/$DBUS_SESSION_CONF"
  [[ -n "$ENV_FILE" ]] && rm -f "$rd_ENV_FILE"

fi

#DISPLAY="localhost:0" chroot "$root" "$@" 



if [[ retval -ne 0 ]]; then
[[ verbose -gt 0 ]] && echo "--------------------- chroot - error ----------------------"
err "chroot returned $retval."
#( set -x;
#ls $root
#mount | grep aufs; )

else
[[ verbose -gt 0 ]] && echo -----------------------------------------------------------

fi

#cat $tmprd/startup-$$;

#
# *** cleanup & exit ***
#


trap '' EXIT
[[ isescape -eq 0 && "$root" != "/" ]] && cleanup
$GenuineRetVal && let result=retval
setretval() { return $1; }
setretval $result;
#$traphandler || exit $result
  


#[[ verbose -ge 2 ]] && echo ------- done -------







# in yast-system-/etc/syscfg/-displaymanager-xserver_tcp_port_6000_open

# grep "$roroot" /etc/fstab | while read mdev mp rest; do echo "$mp"; done | sort | while read mp; do 
#   if [[ -z "$useaufs" ]]; then 
#    cndmount "$mp"
#   else
#     echo mount -t aufs -o append:"$mp" none "$root"
#     mount -t aufs -o append:"$mp" none "$root"
#     Speicherzugriffsfehler
#   fi
# done
