#!/bin/sh
#
# chkconfig: 2345 02 99
# description: ramlog daemon moves /var/log to ramdisk on startup and copies it back to harddrive on shutdown or restart

# RAMLOG script - moves /var/log into ramdisk (c) 2001,2007 Jan Andrejkovic, Licence GNU 3 or any newer GNU licence

### BEGIN INIT INFO
# Provides: ramlog
# Required-Start: sysklogd klogd
# Should-Start:
# Required-Stop: sysklogd klogd
# Default-Start: 2 3 4 5
# Default-Stop: S 0 1 6
# Short-Description: moves /var/log into ramdisk
# Description: ramlog daemon moves /var/log to ramdisk on startup and copies it back to harddrive on shutdown or restart
### END INIT INFO

############################################################
# Ramlog variables:

VERSION=1.1.0
RAMDISKSIZE=MAX         #put here ramdisk size in kilobytes or MAX to use entire ramdisk. Also check kernel parameter "ramdisk_size"
DEVRAM=/dev/ram9
LOG=/var/log
LOGHDD=/var/log.hdd
prog="ramlog"
#MESSAGE="$prog shutdown"
DAEMONDIR=/etc/init.d
UNINSTALL_FLAG_FILE=/var/tmp/ramlog_uninstall_flag_file
LOGGING=1		#0=off, 1=on
LOGFILE=/var/log/ramlog

############################################################
# Preparation - system check, files check:

# Avoid using root's TMPDIR
unset TMPDIR

if [ -d /var/lock/subsys ]; then RAMLOGLOCK=/var/lock/subsys/$prog
elif [ -d /var/run ]; then RAMLOGLOCK=/var/run/$prog.lock
else { echo "$prog err: your platform is not supported - lockpath not found" > /dev/stderr; exit 254; }
fi

tfdv() { #test file def var
        [ "$1" = "--optional" ] && { tfdvOF=1; shift; } || tfdvOF=0
        ( [ -z "$2" ] || [ -n "$5" ] ) && { echo $prog err: tfdv requires at least two and maximum five arguments\!; exit 253; }
        if   [ -f "$2" ] && [ -x "$2" ]; then eval $1=$2
        elif [ -f "$3" ] && [ -x "$3" ]; then eval $1=$3
        elif [ -f "$4" ] && [ -x "$4" ]; then eval $1=$4
        elif [ $tfdvOF -eq 0 ]; then { echo $prog err: Wrong path or file\(s\) $2 $3 $4 do\(es\) not exist\!; exit 252; }
        fi
}

tfdv DD /bin/dd
tfdv MKE2FS /sbin/mke2fs
tfdv MKDIR /bin/mkdir
tfdv CP /bin/cp
tfdv MV /bin/mv
tfdv RM /bin/rm
tfdv RMDIR /bin/rmdir
tfdv MOUNT /bin/mount
tfdv UMOUNT /bin/umount
tfdv LSOF /usr/sbin/lsof /usr/bin/lsof
tfdv GREP /bin/grep
tfdv WC /usr/bin/wc
tfdv DU /usr/bin/du
tfdv TAIL /usr/bin/tail
tfdv AWK /bin/awk /usr/bin/awk
tfdv TOUCH /bin/touch
tfdv WHOAMI /usr/bin/whoami
#  Update script for /etc/init.d for Ubuntu:
tfdv --optional UPDATE_RCD /usr/sbin/update-rc.d
#  Update script for /etc/init.d for Fedora/RH/CentOS
tfdv --optional CHKCONFIG /sbin/chkconfig
# Selinux required files (Selinux as whole is optional): 
tfdv --optional SESTATUS /usr/sbin/sestatus
tfdv --optional CHCON /usr/bin/chcon
tfdv --optional LS /bin/ls
[ "$LOGGING" = "1" ] && tfdv DATE /bin/date

if [ "$UPDATE_RCD" != "" ]; then INITDUPDATE_TYPE=2
elif [ "$CHKCONFIG" != "" ]; then INITDUPDATE_TYPE=1
else INITDUPDATE_TYPE=0
fi

# Logging starts from here:

# Echo tweak for Fedora and Ubuntu:
[ "`echo -e`" = "-e" ] || alias echo='echo -e'

# ECHO and ECHON definition:
if [ "$LOGGING" != "1" ]; then
        alias ECHO=echo
        ECHON () { echo "$@\\c"; } # Echo -n equivalent (works under dash, bash and sh:
else
        ECHO () { echo "$@"; echo "`date` $@" >> $LOGFILE; }
	ECHON_BEG () { echo "$@\\c"; echo "`date` $@\\c" >> $LOGFILE; } #ECHON with timestamp in logs
	ECHON () { echo "$@\\c"; echo "$@\\c" >> $LOGFILE; }   #ECHON without timestamp in logs

	#LOG only alternatives:
	ECHON_ () { echo "$@\\c"; }
	ECHO_LOG () { echo "`date` $@" >> $LOGFILE; }
	ECHO_END_LOG () { echo "$@" >> $LOGFILE; }
	ECHON_BEG_LOG () { echo "`date` $@\\c" >> $LOGFILE; }
	ECHON_LOG () { echo "$@\\c" >> $LOGFILE; }
fi

if [ "$LOGGING" != "1" ]; then
	# Test system startup script type:
	if [ -f /etc/init.d/functions ]; then
		# Fedora/CentOS style:  (Fedora has /lib/lsb/init-functions as well...)
		. /etc/init.d/functions
		alias START_DAEMON=daemon
		alias LOG_BEG_MSG=ECHON
		alias LOG_MID_MSG=ECHON
	  	LOG_END_MSG () { daemon exit $1; echo; }
		alias LOG_SUCCESS='success; echo'
		alias LOG_FAILURE='failure; echo'
		LOG_WARNING_MSG () { ECHON "$@"; warning; echo; }
		#alias LOG_PASSED=passed
		STARTUP_TYPE=1
	elif [ -f /lib/lsb/init-functions ]; then
		# Debian/Ubuntu style:
		. /lib/lsb/init-functions
		alias START_DAEMON=start_daemon
		LOG_BEG_MSG () { PARS="$@"; TABN=$((${#PARS}/8 + 1)); log_begin_msg "$@"; };
		LOG_MID_MSG () { TABS=; for i in $(seq 1 $TABN); do TABS="$TABS\t"; done;\
				 [ "$TABN" -ne "0"  ] && TABS="\r$TABS" ;ECHON "$TABS$@"; TABN=0; }
		alias LOG_END_MSG=log_end_msg
		alias LOG_SUCCESS='log_end_msg 0'
		alias LOG_FAILURE='log_end_msg 1'
		alias LOG_WARNING_MSG=log_warning_msg
		STARTUP_TYPE=2
	else
		# Uknown style:
		#alias START_DAEMON=daemon    #- I do not need it for ramlog, otherwise it should be defined
		alias LOG_BEG_MSG=ECHON
		alias LOG_MID_MSG=ECHON
		LOG_END_MSG () { [ "$1" == "0" ] && LOG_SUCCESS || LOG_FAILURE ; }
		alias LOG_SUCCESS='echo  [ OK ]'
		alias LOG_FAILURE='echo  [failed]'
		LOG_WARNING_MSG () { echo "$@  [warning]"; }
		#echo "$prog err: your platform is not supported" > /dev/stderr; exit 255
		STARTUP_TYPE=0
	fi
else 
	#Now the same but with logging:
	# Test system startup script type:
        if [ -f /etc/init.d/functions ]; then
                # Fedora/CentOS style:  (Fedora has /lib/lsb/init-functions as well...)
                . /etc/init.d/functions
                alias START_DAEMON=daemon
                alias LOG_BEG_MSG=ECHON_BEG
                alias LOG_MID_MSG=ECHON
		LOG_END_MSG () { if [ "$1" = "0" ]; then LOG_SUCCESS; else LOG_FAILURE; fi }
                LOG_SUCCESS () { success; echo; ECHO_END_LOG "  [  OK  ]"; }
                LOG_FAILURE () { failure; echo; ECHO_END_LOG "  [FAILED]"; }
                LOG_WARNING_MSG () { ECHON_BEG "$@"; warning; echo; ECHO_END_LOG "  [WARNING]"; }
                #alias LOG_PASSED=passed
                STARTUP_TYPE=1
        elif [ -f /lib/lsb/init-functions ]; then
                # Debian/Ubuntu style:
                . /lib/lsb/init-functions
                alias START_DAEMON=start_daemon
                LOG_BEG_MSG () { PARS="$@"; TABN=$((${#PARS}/8 + 1)); log_begin_msg "$@"; ECHON_BEG_LOG "$@"; };
                LOG_MID_MSG () { TABS=; for i in $(seq 1 $TABN); do TABS="$TABS\t"; done;\
                                 [ "$TABN" -ne "0"  ] && TABS="\r$TABS" ;ECHON_ "$TABS$@"; TABN=0; ECHON_LOG "$@"; }
		LOG_END_MSG () { if [ "$1" = "0" ]; then LOG_SUCCESS; else LOG_FAILURE; fi }
                LOG_SUCCESS () { log_end_msg 0; ECHO_END_LOG "  [ OK ]"; }
                LOG_FAILURE () { log_end_msg 1; ECHO_END_LOG "  [fail]"; }
		LOG_WARNING_MSG () { log_warning_msg "$@"; ECHO_LOG "$@  [warning]"; }
                STARTUP_TYPE=2
        else
                # Uknown style:
                #alias START_DAEMON=daemon    #- I do not need it for ramlog, otherwise it should be defined
                alias LOG_BEG_MSG=ECHON_BEG
                alias LOG_MID_MSG=ECHON
                LOG_END_MSG () { [ "$1" == "0" ] && LOG_SUCCESS || LOG_FAILURE ; }
                LOG_SUCCESS () { ECHO "$@  [ OK ]"; }
                LOG_FAILURE () { ECHO "$@  [failed]"; }
                LOG_WARNING_MSG () { ECHO "$@  [warning]"; }
                #echo "$prog err: your platform is not supported" > /dev/stderr; exit 255
                STARTUP_TYPE=0
        fi

fi

#SELINUX check:
selinuxF=0;
if [ -n "$SESTATUS" ]; then
	[ "`$SESTATUS | $GREP 'SELinux status:' | $AWK '{print $3}'`" = "enabled" ] && selinuxF=1;
fi

[ $selinuxF -eq 1 ] && CP="$CP -c "

############################################################
#Ramlog sub-routinnes:

alias UPD_RETVAL='RETVAL=$(($RETVAL+$?))'
RETVAL=1

number_of_open_files () {
	return `$LSOF 2>/dev/null +D /var/log | $WC -l`
}

uninstall_core () {
	
	case "$INITDUPDATE_TYPE" in
	2)
		$UPDATE_RCD -f $prog remove >/dev/null
		UPD_RETVAL
		;;
	1)
	        $CHKCONFIG $prog off
	        UPD_RETVAL
	        $CHKCONFIG --del $prog
	        UPD_RETVAL
		;;
	*)
		LOG_BEG_MSG "$prog: Internal error in uninstall_core."; LOG_FAILURE;
		return 150
		;;
	esac

        [ -f $RAMLOGLOCK ] && { $RM -f $RAMLOGLOCK; UPD_RETVAL; }
        [ -f $UNINSTALL_FLAG_FILE ] && { $RM -f $UNINSTALL_FLAG_FILE; UPD_RETVAL; }
        #Delete itself:
        [ -f $DAEMONDIR/$prog ] && { $RM -f $DAEMONDIR/$prog; UPD_RETVAL; }

        #Display status:
        [ $RETVAL -eq 0 ] && { LOG_BEG_MSG "$prog: program has been uninstalled successfully\t   "; LOG_SUCCESS; }
        [ $RETVAL -ne 0 ] && { LOG_BEG_MSG "$prog: program has not been uninstalled successfully\t   "; LOG_FAILURE; }
}

stop_core () {
        $CP -rfup $LOG/. $LOGHDD
        UPD_RETVAL
        $UMOUNT -l $LOG 2>/dev/null                #lazy umount - just in case...
        UPD_RETVAL
        $RMDIR $LOG
        RETVALRMDIR=$?
        #If $LOG was not deleted then it is most likely not empty. So let's save if it contains anything valuable and let's delete it...
        [ $RETVALRMDIR -eq 0 ] || { $CP -rfup $LOG/. $LOGHDD; UPD_RETVAL; $RM -rf $LOG; UPD_RETVAL; }
        #[ -d $LOG ] || ($MV $LOGHDD $LOG; RETVAL=$(($RETVAL+$?)))
        $MV -T $LOGHDD $LOG
        UPD_RETVAL
}

start () {
        # Check if it is already running
        if [ -f $RAMLOGLOCK ]; then
                LOG_WARNING_MSG "Starting $prog: Warning: $prog is already started"
                return 100
        fi

	# Uninstallation after host crashed and it is booting again:
	if [ -f $UNINSTALL_FLAG_FILE ]; then
		RETVAL=0
		uninstall_core
		return $RETVAL
	fi

        LOG_BEG_MSG "Starting $prog: "

        if ! number_of_open_files; then
                LOG_MID_MSG "Error: /var/log is already in use..."
                LOG_FAILURE
                return 101
        fi

        RETVAL=0
        #First, recovery after possible crash:
        if [ -d $LOGHDD ]; then
                if [ -d $LOG ]; then
                        $CP -rfup $LOGHDD/. $LOG
                        UPD_RETVAL
                        $RM -rf $LOGHDD
                        UPD_RETVAL
                else
                        $MV -T $LOGHDD $LOG
                        UPD_RETVAL
                fi
        fi

        #Second, normal procedure:
        [ $RETVAL -eq 0 ] && { $MV -T $LOG $LOGHDD 2>/dev/null; UPD_RETVAL; }
	[ $RETVAL -eq 0 ] && { $MKDIR -p $LOG; UPD_RETVAL; }

	if ([ $RAMDISKSIZE = "MAX" ] || [ $RAMDISKSIZE = "max" ]); then
		[ $RETVAL -eq 0 ] && $DD if=/dev/zero of=$DEVRAM bs=1k 2>/dev/null; 
		#^^^Without "count=" we always end with "No space left on device", therefore we do not update UPD_RETVAL
		#[ $RETVAL -eq 0 ] && ($MKE2FS -m 0 $DEVRAM >/dev/null 2>/dev/null; RETVAL=$(($RETVAL+$?)))
		[ $RETVAL -eq 0 ] && { $MKE2FS $DEVRAM >/dev/null 2>/dev/null; UPD_RETVAL; }
	else
		[ $RETVAL -eq 0 ] && { $DD if=/dev/zero of=$DEVRAM bs=1k count=$RAMDISKSIZE 2>/dev/null; UPD_RETVAL; }
		#[ $RETVAL -eq 0 ] && $MKE2FS -m 0 $DEVRAM $RAMDISKSIZE >/dev/null 2>/dev/null
		[ $RETVAL -eq 0 ] && { $MKE2FS $DEVRAM $RAMDISKSIZE >/dev/null 2>/dev/null; UPD_RETVAL; }
	fi

	[ $RETVAL -eq 0 ] && { $MOUNT $DEVRAM $LOG; UPD_RETVAL; }

	#If SELINUX is enabled we will copy security context of /var/log.hdd to to new /var/log:
	if [ $selinuxF -eq 1 ]; then
		VARLOG_SEC_CONTEXT=`$LS -dZ $LOGHDD | $AWK '{print $4}' | $AWK -F':' '{print "-u "$1" -r "$2" -t "$3}'`
		$CHCON $VARLOG_SEC_CONTEXT $LOG 
	fi

        RETVALCP=0
        [ $RETVAL -eq 0 ] && { $CP -rfp $LOGHDD/. $LOG; RETVALCP=$?; }
        #If problem during copying occured - possibly disk is full -
        # - we remove all copied files so truncated files are not copied back:
        [ $RETVALCP -ne 0 ] && { $RM -rf $LOG >/dev/null 2>/dev/null; RETVAL=$(($RETVAL+$RETVALCP)); }

        [ $RETVAL -ne 0 ] && stop_core

        LOG_END_MSG $RETVAL     #$optIONS
        [ $RETVAL -eq 0 ] && $TOUCH $RAMLOGLOCK

        return $RETVAL
}

stop () {
        if [ -f $RAMLOGLOCK ]; then

                LOG_BEG_MSG "Stopping $prog: "

		if ! number_of_open_files; then
			$CP -rfup $LOG/. $LOGHDD	#Even if stop is not successful we will sync:

                        LOG_MID_MSG "Error: /var/log is still in use..."
                        LOG_FAILURE
			##echo "`date` ramlog: /var/log in use ">>/tmp/ramlog.log
                        return 102
		fi

                #killproc $prog
                RETVAL=0

                stop_core

                [ $RETVAL -eq 0 ] && $RM -f $RAMLOGLOCK
                LOG_END_MSG $RETVAL

                #Uninstallation:
                [ -f $UNINSTALL_FLAG_FILE ] && uninstall_core

        else

	        # Uninstallation if flag was set but ramlog is not running (should not really happen):
        	if [ -f $UNINSTALL_FLAG_FILE ]; then
                	RETVAL=0
	                uninstall_core
	                return $RETVAL
	        fi

                LOG_BEG_MSG "Stopping $prog: Error: $prog is not running"
                LOG_FAILURE
        fi

	return $RETVAL
}

restart () {
        if [ ! -f $RAMLOGLOCK ]; then
                #stop
                #start
                LOG_BEG_MSG "Error: $prog is not running..."
                LOG_FAILURE
		RETVAL=110
        else
                LOG_BEG_MSG "Restarting $prog = saving logs to hdd: "

                $CP -rfup $LOG/. $LOGHDD
                RETVAL=$?

                $TOUCH $RAMLOGLOCK
                LOG_END_MSG $RETVAL
        fi
	
	return $RETVAL
}

uninstall () {
	if [ $INITDUPDATE_TYPE -eq 0 ]; then
		LOG_BEG_MSG "Error: uninstall option is not supported on your platform."; 
		LOG_FAILURE
		return 120
	fi


	RETVAL=0
        if [ ! -f $RAMLOGLOCK ]; then
                #ramlog is not started:
                uninstall_core
		
        else
                #ramlog is started:
                if ! number_of_open_files; then
                        #ramlog will be uninstalled during the next shutdown - during the stop process:

                        #$TOUCH $RAMLOGLOCK         #it is already there
                        #UPD_RETVAL
                        $TOUCH $UNINSTALL_FLAG_FILE
                        UPD_RETVAL

                        [ $RETVAL -eq 0 ] && (LOG_BEG_MSG "$prog will be uninstalled during the next shutdown  \t"; LOG_SUCCESS )
                        [ $RETVAL -ne 0 ] && (LOG_BEG_MSG "$prog: Uninstall failed problem to touch files...   \t"; LOG_FAILURE )

		else
			#ramlog will be uninstalled now:
                        stop_core
                        uninstall_core
                fi
        fi

	return $RETVAL
}

teststartstop () {
        OUTPUT=`$LSOF 2>/dev/null +D /var/log`
        RETVAL=$?

        if [ -z "$OUTPUT" ]; then
		echo No open files in /var/log
	else
		echo "The list of open files: (You need to close below daemons if you want to start/stop ramlog manually)\n\n$OUTPUT"
	fi

        echo
        if [ -z "$OUTPUT" ] && [ -z "$slogF" ]; then
                ECHO Test result: $prog can be started or stopped.
        else
                ECHO Test result: $prog cannot be started or stopped at the moment.
        fi

	return $RETVAL
}

############################################################
# Main body:

# CHeck if you are root:
if [ "$1" != "status" ] && [ "$1" != "version" ] && [ "$1" != "" ]; then
        if [ `$WHOAMI` != root ]; then
                echo Ramlog error: Ramlog requires superuser privilege. Run it as root or use sudo.
                RETVAL=254
                exit $RETVAL
        fi
fi

# Check if uninstall flag was set:
if [ "$1" != "start" ] && [ "$1" != "stop" ] && [ "$1" != "uninstall" ]; then
        if [ -f $UNINSTALL_FLAG_FILE ]; then
                LOG_BEG_MSG "$prog: Program was set to be uninstalled"
                LOG_FAILURE
                RETVAL=128
                exit $RETVAL
        fi
fi

##echo "`date` ramlog: $1">>/tmp/ramlog.log

# See how we were called:
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  status)
        [ -f $RAMLOGLOCK ] && ( ECHO "$prog is running..." 2>/dev/null; exit 0; ) || ECHO "$prog is stopped" 2>/dev/null
	( exit 0 )
        ;;
  restart|reload)
        restart
        ;;
  getlogsize)
        ECHO "Ramlog: Size of /var/log is: `$DU -c /var/log 2>/dev/null | $TAIL -1 | $AWK {'print $1" kbytes"'}`"
	( exit 0 )
        # value of $? will be returned by ramlog
        ;;
  teststartstop)
        teststartstop
        ;;
  version)
	echo "$prog version: $VERSION"
	( exit 0 )
	;;
  uninstall)
        uninstall
        ;;
  *)
        echo "Usage: $0 {start|stop|status|restart|reload|getlogsize|teststartstop|version}"
	[ "$LOGGING" = "1" ] && ECHO_LOG "$prog is called with wrong argument(s): '$@'" 2>/dev/null
        ( exit 111 )
esac

exit $?


