#! /bin/sh

# Author:
#
#     Alberto Bursi <alberto.bursi@outlook.it>
#
# Copyright:
#
#     Alberto Bursi 2015-2021
#
# License:
#
#     This program is free software: you can redistribute it and/or modify
#     it under the terms of the GNU General Public License as published by
#     the Free Software Foundation, either version 3 of the License, or
#     (at your option) any later version.
#
#     This package is distributed in the hope that it will be useful,
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#     GNU General Public License for more details.
#
#     You should have received a copy of the GNU General Public License
#     along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# On Debian systems, the complete text of the GNU General
# Public License version 3 can be found in `/usr/share/common-licenses/GPL-3'.

# The general concept is making something like fs2ram, that you can find in Debian Unstable (as of 2016)
# but smarter, easier and safer.
#
# The core idea comes from an init script called "transientlog" by Matteo Cortese <matteo_cortese@fastwebnet.it>
# Available from here www.debian-administration.org/article/661/A_transient_/var/log
# That page has been also saved and is available in the /doc/folder2ram folder for posterity.
#
# This modified version can do much more than just /var/log, runs with systemd, and can be shut down safely.
#
# "transientlog" borrowed quite a few of its logic from a script called "ramlog" by Jan Andrejkovic.
# www.tremende.com/ramlog/index.htm

#This script is HEAVILY commented, for the sake of easy understanding and mainteneance

#If you edit it, please comment what you are doing

VERSION="0.4.1"

############### USER INTERFACE FUNCTIONS ########################

print_usage() {
  echo "Welcome to folder2ram version $VERSION !"
  echo "folder2ram is a script-based utility that relocates the contents of a folder to RAM"
  echo "and on shutdown unmounts it safely synching the data back to the permanent storage."
  echo ""
  echo "There are four main components of folder2ram system:"
  echo "--the init script in /etc/init.d or the systemd service in /etc/folder2ram that calls this main script on boot and shutdown"
  echo "--the main script in /sbin/folder2ram"
  echo "--the configuration file in /etc/folder2ram/folder2ram.conf"
  echo "--the folders in /var/folder2ram, the bind-mounted folders"
  echo "  they allow easy access to the original folder in permanent storage"
  echo "  since if you mount folder A on folder B you lose access to folder B"
  echo "  this trick allows access to B, allowing synching with the tmpfs at will"
  echo ""
  echo "for first startup use -configure action, edit the mount points as you wish, then -mountall"
  echo ""
  echo "list of actions (only one at a time):"
  echo ""
  echo "-enableinit"
  echo "::::::::::sets up an appropriate autostart/stop init script, does not start it"
  echo ""
  echo "-enablesystemd"
  echo "::::::::::sets up an appropriate autostart/stop systemd service, does not start it"
  echo ""
  echo "-disableinit"
  echo "::::::::::removes the autostart/stop init script and unmounts all mount points"
  echo ""
  echo "-disablesystemd"
  echo "::::::::::removes the autostart/stop systemd service and unmounts all mount points"
  echo ""
  echo "-safe-disableinit"
  echo "::::::::::removes the autostart/stop init script but unmounts only at shutdown (hence safely)"
  echo "::::::::::it also works if folder2ram is unistalled shortly afterwards"
  echo ""
  echo "-safe-disablesystemd"
  echo "::::::::::removes the autostart/stop systemd service but unmounts only at shutdown (hence safely)"
  echo "::::::::::it also works if folder2ram is unistalled shortly afterwards"
  echo ""
  echo "-status"
  echo "::::::::::print all mountpoints and their status (mounted or unmounted)"
  echo ""
  echo "-sync X"
  echo "::::::::::sync to disk the content of folder2ram's tmpfs folder number X (start counting from top entry in the config file)"
  echo ""
  echo "-syncall"
  echo "::::::::::sync to disk the content of folder2ram's tmpfs folders"
  echo ""
  echo "-mount /path/to/folder"
  echo "::::::::::folder2ram will mount this folder, if it's in the config file"
  echo ""
  echo "-umount /path/to/folder"
  echo "::::::::::folder2ram will unmount this folder, if it's in the config file"
  echo ""
  echo "-mountall"
  echo "::::::::::folder2ram will mount all folders in the config file"
  echo ""
  echo "-umountall"
  echo "::::::::::folder2ram will unmount all folders in the config file"
  echo ""
  echo "-configure"
  echo "::::::::::folder2ram will open the configuration file in a text editor"
  echo ""
  echo "-reset"
  echo "::::::::::restore default config file"
  echo ""
  echo "-clean"
  echo "::::::::::unmounts all folders then removes any autostart"
  echo "::::::::::WARNING: this might break programs that are using files in the tmpfs"
  echo "::::::::::if you have programs using the tmpfs please use -safe-disableinit or"
  echo "::::::::::-safe-disablesystemd, and then reboot the system"
  echo ""
} #### END print_usage

echo_with_timestamp() {
  echo "$(date --iso-8601=seconds) $*"
}

#################### FUNCTIONS THAT WRITE FILES AND CONFIGS #################################

write_initscript() {

  #writing a ridicolous amount of boilerplate initscript to ask for a simple line to be called on startup and shutdown
  cat <<'EOF' >"/etc/init.d/folder2ram"
#! /bin/sh
### BEGIN INIT INFO
# Provides: folder2ram
# X-Start-Before:	$syslog
# X-Stop-After:		$syslog
# X-Interactive:	yes
# Required-Start:
# Required-Stop:
# Default-Start:	2 3 4 5
# Default-Stop:		0 1 6
# Short-Description:	Keeps folders in RAM
# Description: Moves the contents of user-defined folders to RAM during boot
#              and keeps it there until shutdown/reboot, when it
#              copies the contents back to permanent storage.
### END INIT INFO

##### WARNING::::AUTOGENERATED::::INIT.SCRIPT::::BY::::FOLDER2RAM

PATH="/sbin:/bin:/usr/sbin:/usr/bin"

Timeout=$(grep "^\s*#\s*TIMEOUT\s*=\s*[0-9]\+\s*m" /etc/folder2ram/folder2ram.conf | tail -n1 | sed -e 's/.*=//' -e 's/min/m/' -e 's/#.*//' -e 's/\s*//g')

if [ "$Timeout" = '' ] ; then
       Timeout=2m
fi

timeout_monitor() {
   sleep "$Timeout"
   kill "$1"
}

# start the timeout monitor in
# background and pass the PID:
timeout_monitor "$$" &
Timeout_monitor_pid=$!

case "$1" in
  start)
  echo "Starting folder2ram"
  /sbin/folder2ram -mountall
  ;;
  stop)
  echo "Stopping folder2ram"
  /sbin/folder2ram -umountall
  ;;
  *)
  echo "Usage: {start|stop}"
  ;;
esac

# kill timeout monitor when terminating:
kill "$Timeout_monitor_pid"

exit 0
EOF

  #chmodding this script to make it only root/group-writable and root/group-executable
  chmod 774 "/etc/init.d/folder2ram"

} #### END write_initscript

write_cleanup_initscript() {

  cat <<'EOF' >"/etc/init.d/folder2ram_temporary"
#! /bin/sh
### BEGIN INIT INFO
# Provides: folder2ram
# X-Start-Before:	$syslog
# X-Stop-After:		$syslog
# X-Interactive:	yes
# Required-Start:
# Required-Stop:
# Default-Start:	2 3 4 5
# Default-Stop:		0 1 6
# Short-Description:	Keeps folders in RAM
# Description: Moves the contents of user-defined folders to RAM during boot
#              and keeps it there until shutdown/reboot, when it
#              copies the contents back to permanent storage.
#              this component is needed for a safe shutdown.
### END INIT INFO

##### WARNING::::AUTOGENERATED::::INIT.SCRIPT::::BY::::FOLDER2RAM

PATH="/sbin:/bin:/usr/sbin:/usr/bin"

case "$1" in
  start)
  ## nothing
  ;;
  stop)
  echo "Stopping folder2ram for the last time"
  /sbin/folder2ram_cleaner
  ;;
  *)
  echo "Usage: {start|stop}"
  ;;
esac

exit
EOF

  #chmodding this script to make it only root/group-writable and root/group-executable
  chmod 774 "/etc/init.d/folder2ram_temporary"

} #### END write_cleanup_initscript

write_systemd_startup_service() {

  if [ "$timeout_setting" != '' ]; then
    timeout_line="TimeoutSec=$timeout_setting"
  fi

  #writing a tiny amount of stuff to have systemd do the same thing as above.
  #clear symptom that systemd is master race.
  cat <<EOF >"$systemd_startup_service_file"
[Unit]
Description=folder2ram systemd service
After=local-fs-pre.target
After=blk-availability.service
DefaultDependencies=no
RequiresMountsFor=/var
Wants=tmp.mount
Before=monit.service rrdcached.service nginx.service nmbd.service postfix@-.service chrony.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/folder2ram -mountall
$timeout_line

[Install]
WantedBy=basic.target
EOF

  #chmodding this file to make it only root/group-writable
  chmod 664 "$systemd_startup_service_file"

} #### END write_systemd_service

write_systemd_shutdown_service() {

  if [ "$timeout_setting" != '' ]; then
    timeout_line="TimeoutSec=$timeout_setting"
  fi

  #writing a tiny amount of stuff to have systemd do the same thing as above.
  #clear symptom that systemd is master race.

  cat <<EOF >"$systemd_shutdown_service_file"
[Unit]
Description=folder2ram systemd service
After=blk-availability.service
BindsTo=blk-availability.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStop=/sbin/folder2ram -umountall
$timeout_line

[Install]
WantedBy=multi-user.target
EOF

  #chmodding this file to make it only root/group-writable
  chmod 664 "$systemd_shutdown_service_file"

} #### END write_systemd_service

write_systemd_service_cleanup() {

  #asking to delete all the stuff we are leaving behind after we are done with the safe shutdown
  cat <<'EOF' >"$systemd_service_cleanup_file"
[Unit]
Description=temporary folder2ram systemd service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/true
ExecStop=/sbin/folder2ram_cleaner
TimeoutSec=30m

[Install]
WantedBy=multi-user.target
EOF

  #chmodding this file to make it only root/group-writable
  chmod 664 "$systemd_service_cleanup_file"

} #### END write_systemd_service_cleanup

cleaner_script_generic() {

  # this function generates a generic cleaner script and customizes it for either systemd or initscripts

  systemd_or_init=$1
  #possible values: init systemd

  cat <<'EOF' >"/sbin/folder2ram_cleaner"

# CLEANER SCRIPT DESIGNED TO UNMOUNT ALL ON SHUTDOWN THEN DELETE ITSELF

read_mount_point () {
# this reads config file at a predetemined line and extracts mount point
# $line_number must come from outside
# blank lines and commented lines are ignored, so line_number refers only to actual mount points
# a similar function can be used to extract additional options in the future

line_number=$1

# remove blank and commented lines with sed, get variable form outside in awk, and use it to print line at $line_number
# then remove trailing ///// with sed
mount_point=$( sed '/^[[:space:]]*$/d' /etc/folder2ram_cleaner.conf | sed '/^#/d' | awk -v line="$line_number" 'NR == line {print $2}' | sed 's:/*$::' | grep -vE 'TYPE|OPTIONS|TIMEOUT' )

# checking if mount point variable is empty, and if it is returning a keyword
if [ "x$mount_point" != "x" ]; then
echo "$mount_point";
else
echo "no_more_mount_points";
fi

} #### END read_mount_point


############# MAIN PART ##################

# initializing variables
line_number=1
start_or_stop="stop"

echo "will now $start_or_stop all mountpoints for the last time"

# reading first mountpoint
# calling another function (above) to fill the mount point at this line number
mount_point=$(read_mount_point "$line_number")

####:::::#### BEGIN MAIN mount_umount_all UNTIL LOOP ####:::::####

until [ "$mount_point" = "no_more_mount_points" ] ;  do

####:::::#### BEGIN PAYLOAD ####:::::####

#Setting up common variables
DIR="$mount_point"
LOCKFILE="/run/lock/$NAME.lock"
TYPE="tmpfs"

# DIRPERM is the bind mount to the directory in permanent storage
# DIR is the directory we are working with

#setting the place where all this stuff will be bind-mounted
DIRPERM="/var/folder2ram$DIR"

# unmounting stuff
  output_flag=0
# output_flag values:
  #   0 if all went well
  #   1 if it was unmounted already

  [ -f "$LOCKFILE" ] || output_flag=1

  case $output_flag in

    0) # Merge back to permanent storage with
          #rsync preserve-ACLs preserve-owner preserve-group preserve-extended-attributes quiet recursive links time archive --delete SOURCE ---> DESTINATION
      if rsync -o -g -A -X -q -r -l -t -a --delete "$DIR"'/' "$DIRPERM"; then
        # Success!
        rm "$LOCKFILE"
        umount -l "$DIR"
        umount -l "$DIRPERM"
      else
        echo "could not merge back to permanent storage"
      fi
    ;;

    1) # already unmounted
      echo "$DIR already unmounted"
    ;;
  esac

####:::::#### END PAYLOAD ####:::::####

# increasing line number
line_number=$((line_number+1))

# reading next mountpoint before next iteration in loop
# calling another function (above) to fill the mount point at this line number
mount_point=$(read_mount_point "$line_number")

done ####:::::#### END OF mount_umount_all UNTIL LOOP ####:::::####

EOF

  #now adding customization to the script
  case $systemd_or_init in

  init)
    cat <<'EOF' >>"/sbin/folder2ram_cleaner"
rm -f "/etc/init.d/folder2ram_cleaner"
rm -f "/etc/folder2ram_cleaner.conf"
rm -f "/sbin/folder2ram_cleaner"
exit
EOF
    ;;
  systemd)
    cat <<EOF >>"/sbin/folder2ram_cleaner"
systemctl disable folder2ram_cleaner.service
rm -f "$systemd_service_cleanup_file"
rm -f "/etc/folder2ram_cleaner.conf"
rm -f "/sbin/folder2ram_cleaner"
exit
EOF
    ;;
  esac

  #chmodding this script to make it only root/group-writable and root/group-executable
  chmod 774 "/sbin/folder2ram_cleaner"

} #### END cleaner_script_generic

################ CONFIGURATION FILE MANIPULATION FUNCTIONS ######################

write_config_file() {
  cat <<EOF >"/etc/folder2ram/folder2ram.conf"
#############################
#folder2ram main config file#
#############################
#
#Protip: to make /var/lock or /tmp available as ram filesystems,
#        it is preferable to set the variables RAMTMP, RAMLOCK
#        in /etc/default/tmpfs.
##############################
#
#Important: leave the # in front of these 3 settings:
#TYPE: options available are "tmpfs" (for a ram folder)
#
#OPTIONS: mount option (will be passed as options to mount), if left blank "defaults" will be used
#
#TIMEOUT=2m #write here the timeout limit for folder2ram service activity
#           #(the time specified here must cover the mount/unmount time of all folders)
#           #if you are using systemd init, when you change this value you must run
#           #folder2ram -enablesystemd again to update the systemd service units with the new value
#
#############################
#Important: use 2 Tabs to separate "type" from "mount point" from "options", the script needs them to read correctly the configuration.
#
#<type>		<mount point>			<options>
#tmpfs		/var/log
EOF

  #chmodding the config to be root/group-writable
  chmod 664 "/etc/folder2ram/folder2ram.conf"

} ## END write_config_file

configure() {

  #if there is no folder, we create it
  [ -d "/etc/folder2ram" ] || mkdir "/etc/folder2ram"

  #if there is no config file it is generated from the template
  if [ ! -e "/etc/folder2ram/folder2ram.conf" ]; then
    # calling the function to write the config file
    write_config_file
  fi

  #will now ask for what text editor to use and then open it
  echo "will now open the configuration file with your favourite text editor"
  echo "write its name and press enter (nano, vim, gedit are the most common)"
  read -r editor

  "$editor" "/etc/folder2ram/folder2ram.conf"

} #### END configure

#################### UTILITY FUNCTIONS CALLED BY PRIMARY FUNCTIONS #############################

read_type() {
  # this reads config file at a predetemined line and extracts the filesystem option
  # $line_number must come from outside
  # blank lines and commented lines are ignored, so line_number refers only to actual mount points

  line_number=$1

  # remove blank and commented lines with sed, get variable from outside in awk, and use it to print line at $line_number
  # then removing trailing //// with sed
  type=$(sed '/^[[:space:]]*$/d' /etc/folder2ram/folder2ram.conf | sed '/^#/d' | awk -v line="$line_number" 'NR == line {print $1}' | sed 's:/*$::' | grep -vE 'TYPE|OPTIONS|TIMEOUT')

  # checking if filesystem variable is empty, and if it is returning a keyword
  if [ "x$type" != "x" ]; then
    echo "$type"
  else
    echo "no_type"
  fi

} #### END read_filesystem

read_mount_point() {
  # this reads config file at a predetemined line and extracts mount point
  # $line_number must come from outside
  # blank lines and commented lines are ignored, so line_number refers only to actual mount points

  line_number=$1

  # remove blank and commented lines with sed, get variable from outside in awk, and use it to print line at $line_number
  # then removing trailing //// with sed
  local mount_point="$(sed '/^[[:space:]]*$/d' /etc/folder2ram/folder2ram.conf | sed '/^#/d' | awk -v line="$line_number" 'NR == line {print $0}' | awk -F'\t\t' '{print $2}' | sed 's:/*$::' | tr -d '\t' | grep -vE 'TYPE|OPTIONS|TIMEOUT')"

  # checking if mount point variable is empty, and if it is returning a keyword
  if [ "x$mount_point" != "x" ]; then
    echo "$mount_point"
  else
    echo "no_more_mount_points"
  fi

} #### END read_mount_point

read_options() {
  # this reads config file at a predetemined line and extracts options
  # $line_number must come from outside
  # blank lines and commented lines are ignored, so line_number refers only to actual mount points

  line_number=$1

  # remove blank and commented lines with sed, get variable from outside in awk, and use it to print line at $line_number
  # then removing trailing //// with sed
  options=$(sed '/^[[:space:]]*$/d' /etc/folder2ram/folder2ram.conf | sed '/^#/d' | awk -v line="$line_number" 'NR == line {print $0}' | awk -F'\t\t' '{print $3}' | sed 's:/*$::' | tr -d '\t')

  # checking if options variable is empty, and if it is returning a keyword
  if [ "x$options" != "x" ]; then
    echo "$options"
  else
    echo "defaults"
  fi

} #### END read_options

generate_mount_name() {

  # cleaning the mount point string to generate a name that won't break anything
  # basically turning /this/is/a/path + mount type into -this-is-a-path-mount_type

  #initializing variables
  mount_point=$1

  # mount_type is hardcoded for now, will be set by something else if/when I implement more folder2ram options
  mount_type="tmpfs"

  # removing trailing slashes with sed then let awk handle this
  # field separator is set as "/" then the record separator is set as "-" , the loop prints one by one the fields
  # (because I could not find a better way to get the damn "-" to be inserted properly)
  # then disables the record separators and prints the variable "mount_type" coming from outside awk (see the -v option)
  #then replaces spaces in the names with "-"
  clean_mount_point=$(echo "$mount_point" | sed 's:/*$::' | awk -v mount_type="$mount_type" -F '/' '{ORS="-"; out=$1; for(i=1;i<=NF;i++){out=$i; print out}; ORS=""; print mount_type}' | tr ' ' '-')

  # generating the name, will look like this "folder2ram-this-is-a-path-mount_type"
  clean_name="f2r$clean_mount_point"

  #outputting the result
  echo "$clean_name"

} #### END generate_mount_name

generate_folder_with_same_permission() {

  newdir=$1
  last_existing_parent_dir=$newdir

  #running loop to find out what's the last folder that exists in the path

  #while the folder entered is NOT found run
  while (! [ -d "$last_existing_parent_dir" ]); do

    #removing last folder from the path with sed because dirname complains if the parent folder does not exist
    last_existing_parent_dir=$(echo "$last_existing_parent_dir" | sed 's,/*[^/]\+/*$,,')

  done

  #making the folder and its path, not setting permissions now as mkdir -m does not set all permissions, chmod does.
  mkdir -p "$newdir"

  #chmodding recursively all folder path with same permissions as existing parent
  chmod -R --reference="$last_existing_parent_dir $last_existing_parent_dir"

  #chowning recursively all folder path with same permissions as existing parent
  chown -R --reference="$last_existing_parent_dir $last_existing_parent_dir"

} #### END generate_folder_with_same_permission

rsyslog_logrotate(){
#The following hack (forcing a logrotate) is done only for systemd's journald and rsyslog
#because they are system services and it's too complex to move the service priority around journald

#moving system logs from syslog while the files are open will mean logging will go on to the same log
#files until they are rotated.
#so we have to force the logrotate of this after the move is done
logrotate_test=$( logrotate --version > /dev/null 2>&1  ; echo $? )
if [ "$journalctl_test" -eq 0 ]; then
  logrotate --force /etc/logrotate.conf
fi
}

journald_logrotate(){
  #The following hack (forcing a logrotate) is done only for systemd's journald and rsyslog
  #because they are system services and it's too complex to move the service priority around journald

  #moving journald's log folders can break the binary log files in /var/log/journal
  #so before we run the mount or unmount logic we force journald to logrotate
  #the old logs to a static file that can be moved safely

  journalctl_test=$( journalctl --version > /dev/null 2>&1  ; echo $? )

  if [ "$journalctl_test" -eq 0 ]; then
  journalctl --rotate
  fi
}


########################## FILE AND MOUNT FUNCTIONS ################################

mount_umount_all() {

  # initializing variables
  start_or_stop=$1
  single_mount_point=$2

  if [ -z "$single_mount_point" ]; then
    echo "will now $start_or_stop all mountpoints"
  else
    echo "will now $start_or_stop $single_mount_point"
  fi

  line_number=1

  # reading first mountpoint

  # calling another function (above) to fill the type at this line number
  TYPE=$(read_type "$line_number")

  # calling another function (above) to fill the mount point at this line number
  mount_point="$(read_mount_point "$line_number")"

  # calling another function (above) to fill the options at this line number
  options=$(read_options "$line_number")

  ####:::::#### BEGIN MAIN mount_umount_all UNTIL LOOP ####:::::####
  until [ "$mount_point" = "no_more_mount_points" ]; do

    if [ -z "$single_mount_point" ] || [ "$mount_point" = "$single_mount_point" ]; then

      echo "$start_or_stop" "$mount_point"

      ####:::::#### BEGIN PAYLOAD ####:::::####

      #Setting up common variables
      NAME=$(generate_mount_name "$mount_point")
      DIR="$mount_point"
      LOCKFILE="/run/lock/$NAME.lock"

      #loading the mode of the parent directory
      MODE=$(env stat -c "%a " "$DIR")
      #echo $MODE

      # DIRPERM is the bind mount to the directory in permanent storage
      # DIR is the directory we are working with

      #setting the place where all this stuff will be bind-mounted
      DIRPERM="/var/folder2ram$DIR"
      #echo $DIRPERM

      #Deciding if mounting or unmounting stuff
      case "$start_or_stop" in

      #################
      start)
        output_flag=0
        # output_flag values:
        #   0 if all went well
        #   1 if the folder is already mounted
        #   2 if the folder does not exist

        [ -f "$LOCKFILE" ] && output_flag=1

        # If DIR does not exist?
        [ -d "$DIR" ] || output_flag=2

        # DIRPERM either does not exist (first invocation)
        # or is empty (left from previous invocation).
        #
        [ -d "$DIRPERM" ] || mkdir -p "$DIRPERM" || output_flag=2
        #[ -d "$DIRPERM" ] || generate_folder_with_same_permission "$DIRPERM" || output_flag=2

        #echo "done dirperm folder"

        case $output_flag in

        0)

          #logrotate journald logs to preserve the current logged events
          journald_logrotate

          #switching mount operation depending on type of mount point
          case $TYPE in

          tmpfs)
            # Mount a tmpfs over DIR.
            # The mount will shadow the current contents of DIR.
            # So, before, make a bind mount so that looking into DIRPERM
            # we'll see the current contents of DIR, which
            # will not be available anymore as soon as we mount
            # a tmpfs over it.
            #
            # the --make-private option overrides the standard behavriour of systemd
            # which would be to propagate mounts through bindmounts.
            # Without it the mount-to-tmpfs command mounts both DIR and DIRPERM as tmpfs
            # which breaks everything.

            #	mount -t tmpfs -o nosuid,noexec,nodev,mode=$MODE,size=$SIZE $NAME $DIR
            # original line, allows to accept options, a feature that I hope to add in the future.
            mount --bind --make-private "$DIR" "$DIRPERM"

            #here we generate/mount a tmpfs over it
            mount -t "tmpfs" -o "$options" "folder2ram" "$DIR"
            #echo "mount -t "tmpfs" -o "$options" "folder2ram" "$DIR""

            #changing permissions, owner and groups to be the same as original

            #chmodding recursively all folder path with same permissions as existing parent
            chmod -R --reference="$DIRPERM" "$DIR"

            #chowning recursively all folder path with same permissions as existing parent
            chown -R --reference="$DIRPERM" "$DIR"

            # Populate the tmpfs with a simple cp
            if cp -rfp "$DIRPERM" -T "$DIR"; then
              # Success!
              touch "$LOCKFILE"
            else
              echo "copy files to $DIR failure, rolling back the mount"
              umount -l "$DIR"
              # Rollback the directory mangling
              umount -l "$DIRPERM"
            fi

            ;;

          esac

          #logrotate syslog logs to force the use of the new folder
          rsyslog_logrotate

          ;;

        1) # already mounted
          echo "$DIR already mounted"
          ;;

        2) # Something went wrong...
          # Rollback the mount
          umount -l "$DIR"
          # Rollback the directory mangling
          umount -l "$DIRPERM"
          ;;
        esac
        ;;
        #################

      stop)

        output_flag=0
        # output_flag values:
        #   0 if all went well
        #   1 if it was unmounted already

        [ -f "$LOCKFILE" ] || output_flag=1

        case $output_flag in

        0)

          #logrotate journald logs to preserve the current logged events
          journald_logrotate

          case $TYPE in

          tmpfs)
            # Merge back to permanent storage with
            #rsync preserve-ACLs preserve-owner preserve-group preserve-extended-attributes quiet recursive links time archive --delete
            #SOURCE ---> DESTINATION
            if rsync -o -g -A -X -r -l -t -a --delete "$DIR"'/' "$DIRPERM"; then
              # Success!
              rm "$LOCKFILE"
              umount -l "$DIR"
              umount -l "$DIRPERM"
            else
              echo "could not merge back to permanent storage"
            fi
            ;;

          esac

          #logrotate syslog logs to force the use of the new folder
          rsyslog_logrotate

          ;;

        1) # already unmounted
          echo "$DIR already unmounted"
        ;;
        esac
        ;;
      #################
      esac

    ####:::::#### END PAYLOAD ####:::::####
    fi

    # increasing line number
    line_number=$((line_number + 1))

    # reading next mountpoint before next iteration in loop

    # calling another function (above) to fill the type at this line number
    TYPE=$(read_type "$line_number")

    # calling another function (above) to fill the mount point at this line number
    mount_point=$(read_mount_point "$line_number")

    # calling another function (above) to fill the options at this line number
    options=$(read_options "$line_number")

  done ####:::::#### END OF mount_umount_all UNTIL LOOP ####:::::####

} ##### END mount_umount_all

print_status() {

  #prints the status of all mounted folders

  # initializing variables
  line_number=1 #as we always start with the first line

  # calling another function (above) to fill the mount point at this line number
  mount_point=$(read_mount_point "$line_number")

  ####:::::#### BEGIN MAIN print_status UNTIL LOOP ####:::::####

  until [ "$mount_point" = "no_more_mount_points" ]; do

    ####:::::#### BEGIN PAYLOAD ####:::::####

    #generating lockfile mountname for this mount point
    NAME=$(generate_mount_name "$mount_point")
    LOCKFILE="/run/lock/$NAME.lock"

    #checking if this lockfile exists
    if [ -f "$LOCKFILE" ]; then
      echo "$mount_point is mounted"
    else
      echo "$mount_point is NOT mounted"
    fi

    ####:::::#### END PAYLOAD ####:::::####

    # increasing line number
    line_number=$((line_number + 1))

    # reading next mountpoint before next iteration in loop
    # calling another function (above) to fill the mount point at this line number
    mount_point=$(read_mount_point "$line_number")

  done ####:::::#### END MAIN print_status UNTIL LOOP ####:::::####

} #### END print_status

sync_to_disk() {

  echo "-----------------------------------------"
  #used to allow selective sync of some folder
  choice="$1"

  # initializing variables
  line_number=1

  if [ "$choice" = "0" ]; then
    echo_with_timestamp "will now sync all mountpoints"
  else
    echo_with_timestamp "will sync only mountpoint $choice"
  fi
  # reading first mountpoint

  # calling another function (above) to fill the type at this line number
  TYPE=$(read_type "$line_number")

  # calling another function (above) to fill the mount point at this line number
  mount_point=$(read_mount_point "$line_number")

  # calling another function (above) to fill the options at this line number
  options=$(read_options "$line_number")

  #logrotate journald logs to preserve the current logged events
  journald_logrotate

  ####:::::#### BEGIN MAIN sync_to_disk UNTIL LOOP ####:::::####

  until [ "$mount_point" = "no_more_mount_points" ]; do

    if [ "$choice" = "0" ] || [ "$choice" = "$line_number" ]; then

      ####:::::#### BEGIN PAYLOAD ####:::::####

      #Setting up common variables
      NAME=$(generate_mount_name "$mount_point")
      source="$mount_point"
      LOCKFILE="/run/lock/$NAME.lock"
      #SIZE=16M this can be interesting for later, for now it stays disabled
      #TYPE="tmpfs"
      #MODE=0755

      # DIRPERM is the bind mount to the directory in permanent storage
      # DIR is the directory we are working with

      #setting the place where all this stuff will be bind-mounted

      case $TYPE in

      tmpfs)
        destination="/var/folder2ram$source"
        ;;

      esac

      # synching to disk

      output_flag=0
      # output_flag values:
      #   0 if all went well
      #   1 if it was unmounted already

      [ -f "$LOCKFILE" ] || output_flag=1

      case $output_flag in

      0) # Merge back to permanent storage with
        #rsync preserve-ACLs preserve-owner preserve-group preserve-extended-attributes quiet recursive links time archive --delete SOURCE ---> DESTINATION
        if rsync -o -g -A -X -q -r -l -t -a --delete "$source"'/' "$destination"; then
          echo_with_timestamp "sync of $source successful!"
        else
          echo_with_timestamp "could not sync $source"
        fi
        ;;

      1) # already unmounted
        echo_with_timestamp "$source unmounted, cannot comply"
        ;;
      esac
      ####:::::#### END PAYLOAD ####:::::####

    fi

    # increasing line number
    line_number=$((line_number + 1))

    # reading next mountpoint before next iteration in loop

    # calling another function (above) to fill the type at this line number
    TYPE=$(read_type "$line_number")

    # calling another function (above) to fill the mount point at this line number
    mount_point=$(read_mount_point "$line_number")

    # calling another function (above) to fill the options at this line number
    options=$(read_options "$line_number")

  done ####:::::#### END MAIN sync_to_disk UNTIL LOOP ####:::::####

  if [ "$choice" -ge "$line_number" ] && [ "$choice" != "0" ]; then
    echo_with_timestamp "mountpoint $choice does not exist"
  fi

} #### END sync_to_disk

########################## AUTOSTART FUNCTIONS ################################

setup_autostart() {

  # initializing variables
  setup=$1

  #detecting where is the best place for systemd stuff
  #as debian likes to place it in /lib/systemd/system/
  #but opensuse likes to place it in /usr/lib/systemd/system/ and has no /lib/systemd/system/
  #so we detect where the "systemd" binary file is located, if it is in /lib/systemd we use that
  #if it is in /usr/lib/systemd we use that.
  if [ -f "/lib/systemd/systemd" ]; then

    systemd_startup_service_file="/lib/systemd/system/folder2ram_startup.service"
    systemd_shutdown_service_file="/lib/systemd/system/folder2ram_shutdown.service"
    systemd_service_cleanup_file="/lib/systemd/system/folder2ram_cleaner.service"

  fi

  if [ -f "/usr/lib/systemd/systemd" ]; then

    systemd_startup_service_file="/usr/lib/systemd/system/folder2ram_startup.service"
    systemd_shutdown_service_file="/usr/lib/systemd/system/folder2ram_shutdown.service"
    systemd_service_cleanup_file="/usr/lib/systemd/system/folder2ram_cleaner.service"

  fi

  #reading timeout setting
  timeout_setting=$(grep "^\s*#\s*TIMEOUT\s*=\s*[0-9]\+\s*m" /etc/folder2ram/folder2ram.conf | tail -n1 | sed -e 's/.*=//' -e 's/min/m/' -e 's/#.*//' -e 's/\s*//g')

  # Deciding if we want init script or systemd module
  # Also deciding if we want to enable or disable NOW, or if we want to enable/disable on reboot.
  case "$setup" in

  init_install)

    #calling the function that writes the initscript and sets permissions
    write_initscript

    #activating it in init
    insserv folder2ram
    ;;
  init_remove)

    #stopping everything first
    mount_umount_all stop

    # to remove from init script
    insserv -r folder2ram

    #to delete the script
    rm -f "/etc/init.d/folder2ram"
    ;;
  init_safe_remove)

    # to remove from init script
    insserv -r folder2ram

    #to delete the script
    rm -f "/etc/init.d/folder2ram"

    #making dedicated cleanup script by calling function first
    #asking for the init-specific self-destruct and sets permissions
    cleaner_script_generic init

    # now we clone folder2ram's config file
    cp "/etc/folder2ram/folder2ram.conf" "/etc/folder2ram_cleaner.conf"

    # now we need to place a one-shot initscript and activate it and set its permissions
    write_cleanup_initscript

    #activating it in init
    insserv folder2ram_temporary

    ;;
  systemd_install)

    #calling the function that writes the systemd service file and sets permissions
    write_systemd_startup_service

    # enabling the service, will be started on reboot
    systemctl enable "$systemd_startup_service_file"

    #calling the function that writes the systemd service file and sets permissions
    write_systemd_shutdown_service

    # enabling the service, will be started on reboot
    systemctl enable "$systemd_shutdown_service_file"

    ;;
  systemd_remove)

    #stopping everything first
    mount_umount_all stop

    #disabling the service
    systemctl disable "$systemd_startup_service_file"
    systemctl disable "$systemd_shutdown_service_file"
    ;;
  systemd_safe_remove)

    #disabling the service
    systemctl disable "$systemd_startup_service_file"
    systemctl disable "$systemd_shutdown_service_file"

    #now we pull the same trick as with the initscript above, but modified to work with systemd

    #making dedicated cleanup script by calling function first
    #adding the systemd-specific self-destruct and sets permissions
    cleaner_script_generic systemd

    # now we clone folder2ram's config file
    cp "/etc/folder2ram/folder2ram.conf" "/etc/folder2ram_cleaner.conf"

    #making a new service pointing to a the cleaner script calling this function and sets permissions
    write_systemd_service_cleanup

    # enabling the service
    systemctl enable "$systemd_service_cleanup_file"
    ;;
  esac

} ####END setup_autostart

clean() {

  #this stops folder2ram and removes autostarts
  #it asks other functions to do the leg work

  #making a few checks to run cleaners or unmounter only if there is something to clean
  #as running it twice would unmount folders three times giving confusing error output

  if [ -f "/etc/init.d/folder2ram" ]; then
    setup_autostart init_remove

  else
    if [ -f "$systemd_startup_service_file" ] || [ -f "$systemd_shutdown_service_file" ]; then
      setup_autostart systemd_remove
    else
      mount_umount_all stop
    fi
  fi

} #### END clean

reset_config() {

  echo "will revert all changes of folder2ram.conf"
  echo "you sure you want to do that (y or n)"
  read -r choice

  case "$choice" in

  Y | y | yes | Yes | YES)

    #if there is no folder, we create it
    [ -d "/etc/folder2ram" ] || mkdir "/etc/folder2ram"

    #removing current config and making a new one
    rm -f "/etc/folder2ram/folder2ram.conf"
    # calling the function to write the config file
    write_config_file

    ;;

  N | n | no | No | NO)
    echo "ok, nevermind then"
    exit
    ;;

  *)
    echo "please write y for yes or n for no"
    ;;

  esac

} #### END reset_config

#########################END MAIN FUNCTIONS###########################

#####################--START MAIN PROGRAM--################################

action="$1"

#doing a root check, because folder2ram must be run as root for obvious reasons
if [ "$(id -u)" -eq 0 ]; then
  echo
else
  echo "you must run folder2ram as root"
  exit
fi

#checking that crucial tools are installed

for tool in cp rsync mount umount rm chown chmod mkdir sed tr; do

  tool_test=$( $tool --version > /dev/null 2>&1  ; echo $? )
  if [ "$tool_test" -ne 0 ]; then
    echo ERROR: $tool tool not found, please install it. Quitting
    exit 1
  fi

done

#awk is different so it gets its own checker
  tool_test=$( awk -W version > /dev/null 2>&1  ; echo $? )
  if [ "$tool_test" -ne 0 ]; then
    echo ERROR: awk tool not found, please install it. Quitting
    exit 1
  fi

case "$action" in

-status)
  print_status
  ;;
-mountall)
  mount_umount_all start
  ;;
-umountall)
  mount_umount_all stop
  ;;
-mount)
  mount_umount_all start $2
  ;;
-umount)
  mount_umount_all stop $2
  ;;
-enableinit)
  setup_autostart init_install
  ;;
-enablesystemd)
  setup_autostart systemd_install
  echo "systemd services enabled but not started, it is recommended to reboot for a cleaner transition to tmpfs folders"
  echo "otherwise you can start them now (both services are needed) with "
  echo "systemctl start folder2ram_startup.service"
  echo "systemctl start folder2ram_shutdown.service"
  ;;
-disableinit)
  setup_autostart init_remove
  ;;
-disablesystemd)
  setup_autostart systemd_remove
  ;;
-safe-disableinit)
  setup_autostart init_safe_remove
  ;;
-safe-disablesystemd)
  setup_autostart systemd_safe_remove
  ;;
-configure)
  configure
  ;;
-reset)
  reset_config
  ;;
-sync)
  sync_to_disk "$2"
  ;;
-syncall)
  sync_to_disk "0"
  ;;
-clean)
  clean
  ;;
*)
  print_usage
  ;;
esac

exit
