#! /bin/sh
#
# Laptop mode tools module: core laptop mode functionality
#




# Remove an option (the first parameter) of the form option=<number> from
# a mount options string (the rest of the parameters).
remove_numeric_mount_option () {
	OPT="$1"
	shift
	echo ",$*," | sed		\
	 -e 's|,'"$OPT"'=[0-9]*,|,|g'	\
	 -e 's/,,*/,/g'			\
	 -e 's/^,//'			\
	 -e 's/,$//'
}

# Remove an option (the first parameter) without any arguments from
# a mount option string (the rest of the parameters).
remove_fixed_mount_option () {
	OPT="$1"
	shift
	echo ",$*," | sed		\
	 -e 's|,'"$OPT"',|,|g'		\
	 -e 's/,,*/,/g'			\
	 -e 's/^,//'			\
	 -e 's/,$//'
}

# Find out the state of an atime option ("atime"/"noatime"/"relatime"/"norelatime")
# in a set of mount options, and use this state to replace the value of the
# option in another mount options string.
#
# Example:
# replace_atime_mount_option defaults,user=1000,atime defaults,noatime
#
# This yields "defaults,atime".
replace_atime_mount_option () {
	REPLACEMENT_OPTS="$1"
	OPTS="$2"
	PARSEDOPTS="$(remove_fixed_mount_option atime $OPTS)"
	PARSEDOPTS="$(remove_fixed_mount_option noatime $PARSEDOPTS)"
	PARSEDOPTS="$(remove_fixed_mount_option relatime $PARSEDOPTS)"	
	PARSEDOPTS="$(remove_fixed_mount_option norelatime $PARSEDOPTS)"	

	if echo ",$REPLACEMENT_OPTS," | grep ",relatime," > /dev/null ; then
		echo "$PARSEDOPTS,relatime"
	elif echo ",$REPLACEMENT_OPTS," | grep ",noatime," > /dev/null ; then
		echo "$PARSEDOPTS,noatime"
	else
		# Kind of strange: to go from relatime to atime, we have to
		# explicitly specify norelatime.
	 	echo "$PARSEDOPTS,atime,norelatime"
	fi
}

# Find out the state of a numbered option (e.g. "commit=NNN") in
# a set of options, and use this state to replace the
# value of the option in another mount options string. 
#
# Example:
# replace_numeric_mount_option commit defaults,user=1000,commit=3 defaults,commit=7
#
# This example yields "defaults,commit=3".
replace_numeric_mount_option () {
	OPT="$1"
	DEF_OPT="$2"
	REPLACEMENT_OPTS="$3"
	OPTS="$4"	
	PARSEDOPTS="$(remove_numeric_mount_option $OPT $OPTS)"
	
	if echo ",$REPLACEMENT_OPTS," | grep ",$OPT=[0123456789]+," > /dev/null ; then
		echo -n "$PARSEDOPTS,$OPT="
		echo ",$REPLACEMENT_OPTS," | sed \
		 -e 's/.*,'"$OPT"'=//'	\
		 -e 's/,.*//'
	else
		# Option not present in REPLACEMENT_OPTS: use the default.
		echo "$PARSEDOPTS,$DEF_OPT"
	fi
}

deduce_fstype () {
	MP="$1"
	# My root filesystem unfortunately has type "unknown" in
	# /etc/mtab. If we encounter "unknown", we try to get the
	# type from fstab. This still might be wrong, in which
	# case the code further down will issue a big warning.
	cat /etc/fstab |
	sed 's/[[:space:]]*#.*$//' |
	while read FSTAB_DEV FSTAB_MP FSTAB_FST FSTAB_OPTS FSTAB_DUMP FSTAB_DUMP ; do
		if [ "$FSTAB_MP" = "$MP" ]; then
			echo $FSTAB_FST
			exit 0
		fi
	done
}


#
# Set kernel setting, showing an error if this fails.
#
# Parameter 1: sysctl/proc path
# Parameter 2: the value
#
set_sysctl() {
	echo "Executing: echo $2 > $1" >> $OUTPUT
	if ! echo "$2" > "$1" ; then
		echo "SETTING OF KERNEL PARAMETER FAILED: echo $2 \> $1"
	fi
}






if [ $CONTROL_NOATIME -eq 1 ] ; then
	if [ "$KLEVEL" = "2.4" ] ; then
		echo "Relatime is not supported on 2.4 kernels. Using noatime instead" >> $OUTPUT 
		USE_RELATIME=0
	elif [ "$KLEVEL" = "2.6" -a "$KMINOR" -lt 23 ] ; then
		echo "Relatime is not supported on kernels before 2.6.23. Using noatime instead." >> $OUTPUT
		USE_RELATIME=0
	fi
	if [ "$USE_RELATIME" = 1 ] ; then
		NOATIME_OPT=",relatime"
	else
		NOATIME_OPT=",noatime"
	fi
fi


# Adjust kernel settings and mount options (but only if data loss 
# sensitive features are active)
if [ "$ACTIVATE_WITH_POSSIBLE_DATA_LOSS" -eq 1 ] ; then
	# Take MAX_LOST_WORK_SECONDS from LM_BATT_MAX_LOST_WORK_SECONDS or LM_AC_MAX_LOST_WORK_SECONDS_WITH_LM, depending on power state.
	MAX_LOST_WORK_SECONDS=$LM_BATT_MAX_LOST_WORK_SECONDS
	if [ $ON_AC -eq 1 ] ; then
		MAX_LOST_WORK_SECONDS=$LM_AC_MAX_LOST_WORK_SECONDS
	fi

	AGE=$((100*$MAX_LOST_WORK_SECONDS))
	XFS_AGE=$(($XFS_HZ*$MAX_LOST_WORK_SECONDS))

	if [ -d /proc/sys/vm/pagebuf ] ; then
		# (For 2.4 and early 2.6.)
		# This only needs to be set, not reset -- it is only used when
		# laptop mode is enabled.
		echo "Adjusting XFS kernel parameters for 2.4 and early 2.6 kernels." >> $OUTPUT
		set_sysctl /proc/sys/vm/pagebuf/lm_flush_age  $XFS_AGE
		set_sysctl /proc/sys/fs/xfs/lm_sync_interval  $XFS_AGE
	elif [ -f /proc/sys/fs/xfs/lm_age_buffer ] ; then
		# (A couple of early 2.6 laptop mode patches had these.)
		# This only needs to be set, not reset -- it is only used when
		# laptop mode is enabled.
		echo "Adjusting XFS kernel parameters for early patched 2.6 kernels." >> $OUTPUT
		set_sysctl /proc/sys/fs/xfs/lm_age_buffer    $XFS_AGE
		set_sysctl /proc/sys/fs/xfs/lm_sync_interval $XFS_AGE
	elif [ -f /proc/sys/fs/xfs/age_buffer ] ; then
		# (2.6.6)
		# But not for these -- they are also used in normal
		# operation.
		echo "Adjusting XFS kernel parameters for 2.6.6 kernel." >> $OUTPUT
		set_sysctl /proc/sys/fs/xfs/age_buffer       $XFS_AGE 
		set_sysctl /proc/sys/fs/xfs/sync_interval    $XFS_AGE
	elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then
		# (2.6.7 upwards)
		# And not for these either. These are in centisecs,
		# not USER_HZ, so we have to use $AGE, not $XFS_AGE.
		echo "Adjusting XFS kernel parameters for >2.6.7 kernel." >> $OUTPUT
		set_sysctl /proc/sys/fs/xfs/age_buffer_centisecs  $AGE
		set_sysctl /proc/sys/fs/xfs/xfssyncd_centisecs    $AGE
		set_sysctl /proc/sys/fs/xfs/xfsbufd_centisecs     3000
	fi

	case "$KLEVEL" in
		"2.4")
			echo "Adjusting 2.4 kernel parameters to enable laptop mode." >> $OUTPUT
			set_sysctl /proc/sys/vm/laptop_mode   1
			set_sysctl /proc/sys/vm/bdflush       "30 500 0 0 $AGE $AGE 60 20 0"
			;;
		"2.6"|3.*)
			echo "Adjusting 2.6/3.x kernel parameters to enable laptop mode." >> $OUTPUT
			set_sysctl /proc/sys/vm/laptop_mode		  "$LM_SECONDS_BEFORE_SYNC"
			set_sysctl /proc/sys/vm/dirty_writeback_centisecs "$AGE"
			set_sysctl /proc/sys/vm/dirty_expire_centisecs    "$AGE"
			set_sysctl /proc/sys/vm/dirty_ratio		  "$LM_DIRTY_RATIO"
			set_sysctl /proc/sys/vm/dirty_background_ratio    "$LM_DIRTY_BACKGROUND_RATIO"
			;;
	esac
	if [ $CONTROL_MOUNT_OPTIONS -eq 1 ]; then
		echo "Remounting filesystems." >> $OUTPUT
		cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
			DO=0
			if ( echo " $PARTITIONS " | grep " $DEV " > /dev/null ) ; then
				DO=1
				echo "$DEV found in PARTITIONS." >> $OUTPUT
			else
				echo "$DEV not found in PARTITIONS." >> $OUTPUT
			fi
			if ( echo " $PARTITIONS " | grep " $MP " > /dev/null ) ; then
				DO=1
				echo "$MP found in PARTITIONS." >> $OUTPUT
			else
				echo "$MP not found in PARTITIONS." >> $OUTPUT
			fi
			if ( echo " $PARTITIONS " | grep " auto " > /dev/null ) ; then
				echo "Checking $DEV against HD because PARTITIONS contains \"auto\"." >> $OUTPUT
				for THISHD in $HD ; do
					echo "   Considering $THISHD." >> $OUTPUT
					if ( echo " $DEV" | grep "$THISHD" > /dev/null ) ; then
						DO=1
						echo "   $DEV contains $THISHD, which is in HD, so we will remount it." >> $OUTPUT
					fi
				done
			fi
			if [ "$DO" -ne 0 ] ; then
				echo "Original options: $OPTS" >> $OUTPUT
				if [ "$WAS_ACTIVE" -eq 0 ] ; then
					# Coming from inactive state: save last known mount options for the device.
					echo Updating /var/run/laptop-mode-tools/nolm-mountopts.  >> $OUTPUT
					if [ -f /var/run/laptop-mode-tools/nolm-mountopts ] ; then 
						sed -i "s|^$DEV .*$||" /var/run/laptop-mode-tools/nolm-mountopts
					fi
					echo $DEV $OPTS >> /var/run/laptop-mode-tools/nolm-mountopts
				else
					echo Not updating /var/run/laptop-mode-tools/nolm-mountopts because laptop mode was already active. >> $OUTPUT
				fi
				if [ "$FST" = 'unknown' ]; then
					echo "Deducing fstype for $MP." >> $OUTPUT
					FST=$(deduce_fstype $MP)
					echo "Deduced fstype for $MP as $FST." >> $OUTPUT
				fi
				# Strip stuff like ext3,ext2 into just ext3.
				echo Reducing file system type.  >> $OUTPUT
				FST=`echo $FST | sed s/,.*//`
				case "$FST" in
					"ext3"|"reiserfs")
						echo Removing commit mount option from original options.  >> $OUTPUT
						PARSEDOPTS="$(remove_numeric_mount_option commit "$OPTS")"
						echo "Executing: mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_LOST_WORK_SECONDS$NOATIME_OPT" >> $OUTPUT
						if (! mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_LOST_WORK_SECONDS$NOATIME_OPT) ; then
							if [ "$FST" == "ext3" -a "$MP" == "/" ] ; then
								echo "BIG FAT WARNING: Your root filesystem mounted as ext3 seems to lack support for"
								echo "the commit mount option. This usually means that your root filesystem is"
								echo "mounted as ext2 because there is no ext3 support in the kernel at boot time,"
								echo "usually because you compiled ext3 as a module and don't load it in an initrd."
								echo "Note that on recent 2.6/3.x kernels, /proc/mounts shows the correct fs type for"
								echo "the device /dev/root. You can check your actual root filesystem mount type"
								echo "there. To fix the problem, either make ext3 available at boot time by compiling"
								echo "it statically into the kernel, or configure the correct filesystem type in"
								echo "/etc/fstab."
							fi
						fi
						;;
					*)
						echo "Executing: mount $DEV -t $FST $MP -o remount,$OPTS$NOATIME_OPT" >> $OUTPUT
						mount $DEV -t $FST $MP -o remount,$OPTS$NOATIME_OPT
						;;
				esac
				if [ -b $DEV -a "$CONTROL_READAHEAD" -ne 0 ] ; then
					echo "Executing: /sbin/blockdev --setfra $(($LM_READAHEAD * 2)) $DEV" >> $OUTPUT
					/sbin/blockdev --setfra $(($LM_READAHEAD * 2)) $DEV >> $OUTPUT 2>&1
				fi
			fi
		done
	fi
else
	# DEACTIVATE w.r.t. kernel options and mount point settings
	U_AGE=$((100*$DEF_UPDATE))
	B_AGE=$((100*$DEF_MAX_AGE))
	set_sysctl /proc/sys/vm/laptop_mode 0
	if [ -f /proc/sys/fs/xfs/age_buffer -a ! -f /proc/sys/fs/xfs/lm_age_buffer ] ; then
		# These need to be restored, if there are no lm_*.
		echo "Restoring default XFS settings (pre-centisecs version)." >> $OUTPUT
		set_sysctl /proc/sys/fs/xfs/age_buffer    $(($XFS_HZ*$DEF_XFS_AGE_BUFFER))
		set_sysctl /proc/sys/fs/xfs/sync_interval $(($XFS_HZ*$DEF_XFS_SYNC_INTERVAL))
	elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then
		# These need to be restored as well.
		echo "Restoring default XFS settings." >> $OUTPUT
		set_sysctl /proc/sys/fs/xfs/age_buffer_centisecs  $((100*$DEF_XFS_AGE_BUFFER))
		set_sysctl /proc/sys/fs/xfs/xfssyncd_centisecs    $((100*$DEF_XFS_SYNC_INTERVAL))
		set_sysctl /proc/sys/fs/xfs/xfsbufd_centisecs     $((100*$DEF_XFS_BUFD_INTERVAL))
	fi
	case "$KLEVEL" in
		"2.4")
			echo "Adjusting 2.4 kernel parameters to disable laptop mode." >> $OUTPUT
			set_sysctl /proc/sys/vm/bdflush "30 500 0 0 $U_AGE $B_AGE 60 20 0"
			;;
		"2.6"|3.*)
			echo "Adjusting 2.6/3.x kernel parameters to disable laptop mode." >> $OUTPUT
			set_sysctl /proc/sys/vm/dirty_writeback_centisecs   "$U_AGE"
			set_sysctl /proc/sys/vm/dirty_expire_centisecs      "$B_AGE"
			set_sysctl /proc/sys/vm/dirty_ratio		    "$NOLM_DIRTY_RATIO"
			set_sysctl /proc/sys/vm/dirty_background_ratio	    "$NOLM_DIRTY_BACKGROUND_RATIO"
			;;
	esac
	if [ $CONTROL_MOUNT_OPTIONS -eq 1 ] ; then
		echo "Remounting filesystems." >> $OUTPUT
		cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
			DO=0
			if ( echo " $PARTITIONS " | grep " $DEV " > /dev/null ) ; then
				DO=1
				echo "$DEV found in PARTITIONS." >> $OUTPUT
			else
				echo "$DEV not found in PARTITIONS." >> $OUTPUT
			fi
			if ( echo " $PARTITIONS " | grep " $MP " > /dev/null ) ; then
				DO=1
				echo "$MP found in PARTITIONS." >> $OUTPUT
			else
				echo "$MP not found in PARTITIONS." >> $OUTPUT
			fi
			if ( echo " $PARTITIONS " | grep " auto " > /dev/null ) ; then
				echo "Checking $DEV against HD because PARTITIONS contains \"auto\"." >> $OUTPUT
				for THISHD in $HD ; do
					echo "   Considering $THISHD." >> $OUTPUT
					if ( echo " $DEV" | grep "$THISHD" > /dev/null ) ; then
						DO=1
						echo "   $DEV contains $THISHD, which is in HD, so we will remount it." >> $OUTPUT
					fi
				done
			fi
			if [ "$DO" -ne 0 ] ; then
				# Reset commit and atime options to defaults.
				echo "Original options: $OPTS" >> $OUTPUT
				if [ "$FST" = 'unknown' ]; then
					echo "Deducing fstype for $MP." >> $OUTPUT
					FST=$(deduce_fstype $MP)
					echo "Deduced fstype for $MP as $FST." >> $OUTPUT
				fi
				
				# Strip stuff like ext3,ext2 into just ext3.
				echo Reducing file system type.  >> $OUTPUT
				FST=`echo $FST | sed s/,.*//`
				
				# Retrieve original non-laptop mode mount options and restore them.
				# If the file that stores them doesn't exist, then laptop mode
				# has never been started.
				if [ "$WAS_ACTIVE" -ne 0 -a -f /var/run/laptop-mode-tools/nolm-mountopts ] ; then						
					SAVED_OPTS=`grep "^$DEV " /var/run/laptop-mode-tools/nolm-mountopts`
					SAVED_OPTS=${SAVED_OPTS#* } # trim device name
				
					case "$FST" in
						"ext3"|"reiserfs")								
							PARSEDOPTS="$(replace_numeric_mount_option commit commit=0 $SAVED_OPTS $OPTS)"
							PARSEDOPTS="$(replace_atime_mount_option $SAVED_OPTS $PARSEDOPTS)"
							echo Executing: mount $DEV -t $FST $MP -o remount,$PARSEDOPTS >> $OUTPUT
							mount $DEV -t $FST $MP -o remount,$PARSEDOPTS
							;;
						*)
							PARSEDOPTS="$(replace_atime_mount_option $SAVED_OPTS $OPTS)"
							echo Executing: mount $DEV -t $FST $MP -o remount,$PARSEDOPTS >> $OUTPUT
							mount $DEV -t $FST $MP -o remount,$PARSEDOPTS
							;;
					esac
				else
					echo "No saved mount options, so apparently we never remounted this filesystem during this session." >> $OUTPUT
					echo "Not remounting." >> $OUTPUT
				fi
				if [ -b $DEV -a "$CONTROL_READAHEAD" -ne 0 ] ; then
					echo "Executing: /sbin/blockdev --setfra $(($NOLM_READAHEAD * 2)) $DEV" >> $OUTPUT
					/sbin/blockdev --setfra $(($NOLM_READAHEAD * 2)) $DEV >> $OUTPUT 2>&1
				fi
			fi
		done
	fi
fi

