#!/bin/sh
#
# license: Standard BSD2CLAUSE (BSD 2-clause Simplified License),
# Please read from the web.
#
 
################################
# Start of variable initialization.
#
qchroot_local="/usr/local/etc/qchroot.local"
system_dir="/usr/qchroot"
template="${system_dir}/template"
sharedfs="${system_dir}/sharedfs"
download="${system_dir}/download"
ftp_host="ftp2.freebsd.org"
version="qchroot-0.1 03/31/2015"
         
####### End of variable initialization. #######################
                
###############################################################
#        
# Start of function definitions. IE: subroutines
#          
       
# Define the terminate shortcut
kill () {
  echo -e "$*"
  exit 3
}


create () {
####jjbc#################### CREATE ########################

  # Did you pass the required parameter?
  # 
      
  if [ ! $2 ]; then
    kill "The required chroot name parameter is missing."
  fi     
        
  rootdir="${system_dir}/$2"

  # Inspect the entered chroot name.
  safename=`echo -n "$2" | tr -c '[:alnum:]-_' _`
  if [ "${safename}" != "$2" ]; then
    echo "Error; Invalid chroot name."
    kill "Only underscore, dash and alphanumeric characters are valid."
  fi

  # Check that the chroot name is not all numeric.
  if expr "$2" : "[0-9]*$" > /dev/null
    then
     kill "Error; All numeric chroot names are invalid. $2"
  fi


  # Check to see if chroot name is used already.
  if [ -d "${rootdir}" ]; then
    kill "Error; $2 chroot name is already used."
  fi


  # Create the chroot name filesystem.
  mkdir -p "${rootdir}"
  cd "${template}"
  find . | cpio -p "${rootdir}" 1> /dev/null 2>&1

  if [ $? -ne 0 ]; then
    kill "Error; Couldn't copy template."
  fi

  status="stopped"
  ( echo "status=\"${status}\""
    echo "servicename=\"${servicename}\"" ) > "${qchroot_local}/$2"


  # Populate chroot with files from the host
  # necessary for a network accessiblity.
  #
  cp /etc/resolv.conf "${rootdir}/etc/"
  cp /etc/localtime "${rootdir}/etc/"
  
  # Populate chroot with pkg default.
  #
  mkdir -p "${rootdir}/usr/local/etc"

  echo "ASSUME_ALWAYS_YES  :  YES" > "${rootdir}/usr/local/etc/pkg.conf"

  echo "set prompt = \"chroot.$2: %/ >\"" >> "${rootdir}/.cshrc"

  echo "$2 filesystem created."

  exit 0
}              
               
delete () {      
###jjbd##################### DELETE ########################
         
  # Did you pass the required parameters
  if [ ! $2 ]; then
    kill "Error; The required chroot name parameter is missing."
  fi  
   
  # Build a list of all the created chroot names.
  if [ $2 = "-A" ]; then
    cd "${qchroot_local}"
    qchroot_name_list=`ls`
  else
    shift;
    qchroot_name_list="$*"
  fi
    
  # Process the list of known chroot names.
  for name in ${qchroot_name_list}; do
     . "${qchroot_local}/$name"
     eval status=\"\${status}\"
     if [ "${status}" = "running" ]; then
       echo "Error; Chroot $name is still running"
       echo "It MUST be stopped before you can delete its filesystem"
       continue
     fi
     if [ "${status}" = "stopped" ]; then
       rootdir="${system_dir}/$name"
       if [ -d "${rootdir}" ]; then
         rm -r ${rootdir}
         rm -r ${qchroot_local}/$name
         echo "Delete chroot name $name completed."
         continue
       else
         kill "Error; chroot name $name not found."
       fi
     fi
  done
exit 0
}                
 
list () {          
     
#######jjbl################# LIST ########################

  # Build a list of all the created chroot names.
  cd "${qchroot_local}"
  qchroot_name_list=`ls`

  echo " "
  printf "%-7s %-15s %-30s %s\\n" Status  "Chroot Name"  "Services to Start"
  dash7="-------"
  dash15="---------------"
  dash45="---------------------------------------------"
  printf "%-7s %-15s %-45s %s\\n" "${dash7}"  "${dash15}" "${dash45}"

  # Process the list of known chroot names.
  for name in ${qchroot_name_list}; do
     . "${qchroot_local}/$name"
     eval status=\"\${status}\"
     eval servicename=\"\${servicename}\"
     printf "%-7s %-15s %-45s %s\\n" "${status}" "${name}"  "${servicename}" 
  done
  echo " "
exit 0
}      
         
install () {        
#####jjbin################### INSTALL ########################
      
  # Sanity check to see if chroot system exists.
  if [ -d "${sharedfs}" -o -d "${template}" ]; then
    rm -rf "${sharedfs}"
    rm -rf "${template}"
  fi
            
  # Check that host OS is not running a -BETA or -RC version.
  # The output of the uname -r command is different format.
  # IE: [9.1-BETA1 or 9.1-RC1] where as for normal releases
  # [9.0-RELEASE or 9.0-RELEASE-p1].
                      
  # Get release of running host. (IE: 9.0-RELEASE)
  release=`uname -r`
                    
    # Strip off the release number from in front of the release name
    # including the - of the -RC. IE: remove  9.0- leaving RC1 
    # Or in the case of security updates 9.0-RELEASE-p3 leaves p3
    # Or in the case of normal systems 9.0-RELEASE leaves RELEASE.
    os_release=${release##*-}
           
    case ${os_release} in BETA1|BETA2|BETA3|BETA4|BETA5|BETA6|BETA7) \
    echo "Error: The Host is running a -BETA version."
    kill "There is no binary source available for download.";; esac
          
    case ${os_release} in RC1|RC2|RC3|RC4|RC5|RC6|RC7|RC8|RC9|RC10|RC11) \
    echo "Error: The Host is running a -RC version."
    kill "There is no binary source available for download.";; esac
           
    case ${os_release} in PRERELEASE|STABLE|CURRENT) \
    echo "Error: The Host is running a ${os_release} version."
    kill "There is no binary source available for download.";; esac
               
    # Remove the security binary patch update version suffix -p3 [if present]. 
    #  (IE: 9.0-RELEASE-p3)
    case ${os_release} in p1|p2|p3|p4|p5|p6|p7|p8|p9|p10|p11|p12|p13) \
    # Strip off the word -RELEASE-p3 leaving 9.0
    release_number=${release%%-*}
    release="${release_number}-RELEASE"
    ;; esac
      
          
  # Starting with the 9.0 release the distribution source has a new format and 
  # a new directory path to the install source on the FTP server and on the
  # install .iso file cdrom/dvd. It is now a single archive named base.txz.
  # The path now has the platform name twice in the path  /i386/i386/
  # This code only handles the distribution source format for 9.0 and newer.
  #          
           
  # Strip off -RELEASE word leaving just major release number IE: 9.0
  release_number=${release%%-*}
             
  # Replace the . separating 9.0 into 9 0.
  release_number=`echo -n "${release_number}" | tr '.' ' '`
             
  # Concatenate into single number.
  number=`echo "${release_number}" | awk '{print $1}'`
  number=${number}`echo "${release_number}" | awk '{print $2}'`
              
  if [ ${number} -ge 93 ]; then
    installarch=`uname -p`
    installarch="${installarch}/${installarch}"
  else        
    echo "Error: The Host is running ${release}."
    kill "This qchroot version only runs on 9.0-RELEASE or newer."
  fi     
        
  #    
  # If the download directory exists, then delete it. 
  # The download directory gets populated with the RELEASE distribution file 
  # from the FTP download. Then the compressed archive file is un-compressed
  # populating template with the system directory tree content. Then all the 
  # executable libraries are copied to populate sharedfs and linked to template
  # while being deleted from template. The template filesystem is the template 
  # from which all chroot fielsystems are created from.  
  # The /boot/kernel directory is not used in chroot containers so the 
  # /boot directory is totally excluded. The usr/ports directory and usr/src 
  # directory is not populated during the install process but their 
  # directories are allocated.        
         
  #  Do housekeeping to cleanup and setup for the install.
  #  Delete the download directory that maybe left over from previous runs.
            
  [ -e "${download}" ] && rm -rf "${download}" 
        
  #        
  # Start of logic to process the remote FTP download of the  
  # RELEASE distribution install file named base.txz. For the 9.x method. 	  
  #           
       
    mkdir -p "${download}" || kill \
        "Error: Failed to create download directory."
             
    cd "${download}" || kill \
        "Error: Could not cd to ${download}."
            
    path="pub/FreeBSD/releases"
    ftp_path="${path}/${installarch}/${release}"
    ftp "${ftp_host}:${ftp_path}/base.txz"
    if [ $? -ne 0 ]; then
      echo "ftp ${ftp_host}:${ftp_path}/base.txz"
      kill "Error: Failed to ftp base.txz file."
    fi         

    #   
    # By this point the download directory has successfully been populated
    # with the FTP downloaded source file. The following code executes the
    # 9.x RELEASE install method, which populates the template directory
    # with a full system directory tree.
    #
    mkdir -p "${template}" || kill \
       "Error: Couldn't create template directory."
          
    DESTDIR=${template}
             
    cd "${download}" || kill \
      "Error: Could not cd to ${download}."
             
    echo " "
    echo "The RELEASE distribution files are populating template."
    echo "Est LT 1 minute elapse time for this to complete."
          
    xzdec base.txz | tar --unlink -xpJf - -C ${DESTDIR}
    [ $? -eq 0 ] || kill "Error: RELEASE distribution install failed."
    rm -rf "${download}"
             
  # Selectively populate the sharedfs from the just created template.
  # This is fall through logic for all cases. 
  #         
          
  # Verify that template exists.
  cd "${template}" || kill \
     "Error: Couldn't cd into template directory for populating sharedfs."
           
  # All the schg flaged files end up belonging to sharedfs so they really 
  # have no effect in the qchroot system. 
  # Remove them now so they don't cause problems later.
  chflags -R noschg "${template}"
  chflags -R nosunlink "${template}"

  # sharedfs directory does not exist yet, so allocate the
  # sharedfs directory.
  #         
  mkdir -p "${sharedfs}"
          
  # src, ports, are not included in the distribution, so create their 
  # directories now on sharedfs and add link to them on template so all 
  # chroot containers share single copy of them.
  #       
  rm -r usr/src
  mkdir -p "${sharedfs}/usr/src"
  mkdir -p "${sharedfs}/usr/ports"
  ln -s /sharedfs/usr/src usr/src
  ln -s /sharedfs/usr/ports usr/ports
          
  echo " "
  echo "sharedfs is being populated."
  echo "Est LT 1 minute elapse time for this to complete."
           
  # Using the dirlist the desired directories are copied to the
  # sharedfs directory tree and deleted from the template directory tree.
  #      
           
  dirlist="bin lib libexec sbin sys usr/bin usr/include usr/lib "
  dirlist="${dirlist}usr/libdata usr/libexec usr/sbin usr/share "
          
  # amd64 needs some extra libs
  case `uname -p` in amd64) dirlist="${dirlist} usr/lib32";; esac
           
  for dir in ${dirlist}; do
    find ${dir} | cpio -dmp "${sharedfs}" 1> /dev/null 2>&1 || \
    kill "Error: Installation of ${dir} failed." 
    rm -r ${dir}; 
    ln -s /sharedfs/${dir} ${dir}
  done
           
  # Delete some un-needed stuff to make template smaller.
  rm -rf "${template}"/boot
  rm -rf "${template}"/rescue
  rm -rf "${template}"/usr/games
          
  mkdir "${template}"/sharedfs
  ln -s usr/home "${template}"/home 
             
  # qchroot has a control directory located at /usr/local/etc
  #            
  # "qchroot.local" Inside of this directory are files named with the 
  #  chrootname. The mear present of a file in this directory means
  #  there is a chroot filesystem by that name.
  #             
             
  # If the control directory is not allocated yet, do it now.
  [ -d "${qchroot_local}" ] || mkdir -p "${qchroot_local}"

  # Create the perl link if not all ready done.
  [ ! -L "${sharedfs}/usr/bin/perl" ] && \
   ln -s /usr/local/bin/perl "${sharedfs}/usr/bin/perl"
          
  echo " "
  echo "Successfully installed qchroot system."
  echo " "          
  exit 0
}         
        
start () {        
######jjbs######### START / STOP ########################

  [ ! $2 ] && kill "The required chroot name parameter is missing."
  
  
  # Action is first variable in command list, Can only be start, or stop.
  action="$1"
       
  # Build a list of all the created chroot names.
  if [ $2 = "-A" ]; then
    cd "${qchroot_local}"
    qchroot_name_list=`ls`
  else
    shift;
    qchroot_name_list="$*"  
  fi

  # Process the list of known chroot names. 
  for name in ${qchroot_name_list}; do
     . "${qchroot_local}/$name"
     eval status=\"\${status}\"
     eval servicename=\"\${servicename}\"
     if [ ! "${servicename}" ]; then
       echo "Bypassed; No service configured yet for $name"
       continue
     fi
     if [ "${status}" = "running" -a "${action}" = "start" ]; then
       echo "Error; Chroot $name is already started."
       continue
     fi
     if [ "${status}" = "running" -a "${action}" = "stop" ]; then
       # Process the list of service names.
       for service_name in ${servicename}; do
         # The chroot "command option" defaults to prefix of /bin/csh
         # and looks in chroot path for "command option" path.
         #command="/bin/sh /usr/local/etc/rc.d/$service_name onestop"
         command="/usr/local/etc/rc.d/$service_name onestop"
         chroot ${system_dir}/$name $command
       done 
       sleep 1
       umount $system_dir/$name/sharedfs
       umount $system_dir/$name/dev
       status="stopped"
       ( echo "status=\"${status}\""
         echo "servicename=\"${servicename}\"" ) > "${qchroot_local}/$name"
       echo "Chroot $name is stopped."
       continue
     fi
     if [ "${status}" = "stopped" -a "${action}" = "stop" ]; then
       echo "Error; Chroot $name is already stopped."
       continue
     fi

     if [ "${status}" = "stopped" -a "${action}" = "start" ]; then
       # Issue command to start the chroot
       mount -t devfs devfs $system_dir/$name/dev
       mount_nullfs -o ro $system_dir/sharedfs $system_dir/$name/sharedfs
       # Process the list of service names.
       for service_name in ${servicename}; do
         # The chroot "command option" defaults to prefix of /bin/csh
         # and looks in chroot path for "command option" path.
         #command="/bin/sh /usr/local/etc/rc.d/$service_name onestart"
         command="/usr/local/etc/rc.d/$service_name onestart"
         chroot ${system_dir}/$name $command
       done
       status="running"
       ( echo "status=\"${status}\""
         echo "servicename=\"${servicename}\"" ) > "${qchroot_local}/$name"
       echo "Start chroot $name completed."
       continue
     fi
  done
exit 0
}           
        
console () {
###########jjbcc############# CONSOLE ########################

  # Need chroot name of chroot running chroot open console for it.
  [ $2 ] || kill "Error: No chroot name supplied."

  # Check to see if chroot name exists.
  [ -e "${qchroot_local}/$2" ] || \
   kill "Error: This chroot name don't exist. $2"

  . "${qchroot_local}/$2"
  eval status=\"\${status}\"
  
  if [ "${status}" = "running" ]; then
    kill "Error; $2 has to be in stopped status."
  fi

  if [ "${status}" = "stopped" ]; then
    mount -t devfs devfs /usr/qchroot/$2/dev
    mount_nullfs -o ro /usr/qchroot/sharedfs /usr/qchroot/$2/sharedfs
    chroot ${system_dir}/$2
    umount /usr/qchroot/$2/sharedfs
    umount /usr/qchroot/$2/dev
  fi

exit 0
}

config () {       
###########jjbf############# CONFIG ########################

  # Need chroot name of chroot.
  [ $2 ] || kill "Error: No chroot name supplied."
  cname="$2"  

  # Need service name of chroot. 
  [ $3 ] || kill "Error: No service name supplied."
        
  # Check to see if chroot name exists.
  [ -e "${qchroot_local}/$cname" ] || \
   kill "Error: This chroot name don't exist. $cname"
                  
  . "${qchroot_local}/$cname"
  eval status=\"\${status}\"
  eval servicename=\"\${servicename}\"
  shift; shift;
  servicename="$*"
  ( echo "status=\"${status}\""
    echo "servicename=\"${servicename}\"" ) > "${qchroot_local}/$cname"
  exit 0
}   

#############################
# End of function definitions.
#

# This is the beginning of the script processing.

if [ "$( id -u )" != "0" ]; then
   echo "qchroot must be run by root."
   exit 2
fi

if [ "`sysctl -n kern.securelevel`" -gt 0 ]; then
   echo "Error: The host is running in a secure level higher than 0."
   kill "Reboot the host into a lower secure level."
fi


# Check that the first word after "qchroot" is a sub-command.
subcommand="$1"

[ "${subcommand}" = "start" ]   && start   $*  && exit 0
[ "${subcommand}" = "stop" ]    && start   $*  && exit 0
[ "${subcommand}" = "list" ]    && list    $*  && exit 0
[ "${subcommand}" = "create" ]  && create  $*  && exit 0
[ "${subcommand}" = "delete" ]  && delete  $*  && exit 0
[ "${subcommand}" = "console" ] && console $*  && exit 0
[ "${subcommand}" = "config" ]  && config  $*  && exit 0
[ "${subcommand}" = "install" ] && install $*  && exit 0
[ "${subcommand}" = "version" ] && echo "$version"  && exit 0
[ "${subcommand}" ] &&  kill "No subcommand entered"

################ End of Sub-command logic #########


