#! /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)"	

	case ",$REPLACEMENT_OPTS," in
	*",relatime,"*)
		echo "$PARSEDOPTS,relatime"
		;;
	*",noatime,"*)
		echo "$PARSEDOPTS,noatime"
		;;
	*)
		# Kind of strange: to go from relatime to atime, we have to
		# explicitly specify norelatime.
	 	echo "$PARSEDOPTS,atime,norelatime"
	 	;;
	esac
}

# 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.
	sed 's/[[:space:]]*#.*$//' /etc/fstab |
	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() {
	$LM_VERBOSE && echo "Executing: echo $2 > $1" >> $OUTPUT
	if ! echo "$2" > "$1" ; then
		echo "SETTING OF KERNEL PARAMETER FAILED: echo $2 \> $1"
	fi
}


if [ "$CONTROL_READAHEAD" -ne 0 ] ; then
	if /sbin/blockdev --help 2>&1 | grep -Fq -- '--setfra' ; then
		READAHEAD_OPTION=--setfra					
	else
		READAHEAD_OPTION=--setra
		if [ "$KLEVEL" = "2.4" ] ; then
			echo "Warning: Running a 2.4 kernel with blockdev that does not support --setfra."
			echo "File system readahead will not function properly."
		fi
	fi
fi



if [ $CONTROL_NOATIME -eq 1 ] ; then
	if [ "$KLEVEL" = "2.4" ] ; then
		$LM_VERBOSE && 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
		$LM_VERBOSE && 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.
		$LM_VERBOSE && 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.
		$LM_VERBOSE && 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.
		$LM_VERBOSE && 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.
		$LM_VERBOSE && 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")
			$LM_VERBOSE && 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")
			$LM_VERBOSE && echo "Adjusting 2.6 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
		$LM_VERBOSE && echo "Remounting filesystems." >> $OUTPUT
		cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
			case "$FST" in 
				rootfs|unionfs|tmpfs|squashfs|sysfs|usbfs|proc|devpts)
				  	continue
					;;
			esac
					
			DO=0
			
			case " $PARTITIONS " in
				*" $DEV "*)
					DO=1
					$LM_VERBOSE && echo "$DEV found in PARTITIONS." >> $OUTPUT
					;;
				*)
					$LM_VERBOSE && echo "$DEV not found in PARTITIONS." >> $OUTPUT
					;;
			esac
			case " $PARTITIONS " in
				*" $MP "*)
					DO=1
					$LM_VERBOSE && echo "$MP found in PARTITIONS." >> $OUTPUT
					;;
				*)
					$LM_VERBOSE && echo "$MP not found in PARTITIONS." >> $OUTPUT
			esac
			case " $PARTITIONS " in
				*" auto "*)
					$LM_VERBOSE && echo "Checking $DEV against HD because PARTITIONS contains \"auto\"." >> $OUTPUT
					for THISHD in $HD ; do
						$LM_VERBOSE && echo "   Considering $THISHD." >> $OUTPUT
						case " $DEV" in *"$THISHD"*)
							DO=1
							$LM_VERBOSE && echo "   $DEV contains $THISHD, which is in HD, so we will remount it." >> $OUTPUT
							break
							;;
						esac
					done
					;;
			esac
			if [ "$DO" -ne 0 ] ; then
				$LM_VERBOSE && echo "Original options: $OPTS" >> $OUTPUT
				if [ "$WAS_ACTIVE" -eq 0 ] ; then
					# Coming from inactive state: save last known mount options for the device.
					$LM_VERBOSE && 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
					$LM_VERBOSE && echo "Not updating /var/run/laptop-mode-tools/nolm-mountopts because laptop mode was already active." >> $OUTPUT
				fi
				if [ "$FST" = 'unknown' ]; then
					$LM_VERBOSE && echo "Deducing fstype for $MP." >> $OUTPUT
					FST=$(deduce_fstype $MP)
					$LM_VERBOSE && echo "Deduced fstype for $MP as $FST." >> $OUTPUT
				fi
				# Strip stuff like ext3,ext2 into just ext3.
				$LM_VERBOSE && echo "Reducing file system type."  >> $OUTPUT
				FST=${FST%%,*}
				case "$FST" in
					"ext3"|"reiserfs")
						$LM_VERBOSE && echo "Removing commit mount option from original options."  >> $OUTPUT
						PARSEDOPTS="$(remove_numeric_mount_option commit "$OPTS")"
						$LM_VERBOSE && echo "Executing: mount $DEV $MP -t $FST -o remount,$PARSEDOPTS,commit=$MAX_LOST_WORK_SECONDS$NOATIME_OPT" >> $OUTPUT
						if (! mount $DEV $MP -t $FST -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 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
						;;
					*)
						$LM_VERBOSE && echo "Executing: mount $DEV $MP -t $FST -o remount,$OPTS$NOATIME_OPT" >> $OUTPUT
						mount $DEV $MP -t $FST -o remount,$OPTS$NOATIME_OPT
						;;
				esac
				if [ -b $DEV -a "$CONTROL_READAHEAD" -ne 0 ] ; then
					$LM_VERBOSE && echo "Executing: /sbin/blockdev $READAHEAD_OPTION $(($LM_READAHEAD * 2)) $DEV" >> $OUTPUT
					/sbin/blockdev $READAHEAD_OPTION $(($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_*.
		$LM_VERBOSE && 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.
		$LM_VERBOSE && 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")
			$LM_VERBOSE && 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")
			$LM_VERBOSE && echo "Adjusting 2.6 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
		$LM_VERBOSE && echo "Remounting filesystems." >> $OUTPUT
		cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
			DO=0
			case " $PARTITIONS " in
				*" $DEV "*)
					DO=1
					$LM_VERBOSE && echo "$DEV found in PARTITIONS." >> $OUTPUT
					;;
				*)
					$LM_VERBOSE && echo "$DEV not found in PARTITIONS." >> $OUTPUT
					;;
			esac
			case " $PARTITIONS " in
				*" $MP "*)
					DO=1
					$LM_VERBOSE && echo "$MP found in PARTITIONS." >> $OUTPUT
					;;
				*)
					$LM_VERBOSE && echo "$MP not found in PARTITIONS." >> $OUTPUT
					;;
			esac
			case " $PARTITIONS " in
				*" auto "*)
					$LM_VERBOSE && echo "Checking $DEV against HD because PARTITIONS contains \"auto\"." >> $OUTPUT
					for THISHD in $HD ; do
						$LM_VERBOSE && echo "   Considering $THISHD." >> $OUTPUT
						case " $DEV" in *"$THISHD"*)
							DO=1
							$LM_VERBOSE && echo "   $DEV contains $THISHD, which is in HD, so we will remount it." >> $OUTPUT
							break
							;;
						esac
					done
					;;
			esac
			if [ "$DO" -ne 0 ] ; then
				# Reset commit and atime options to defaults.
				$LM_VERBOSE && echo "Original options: $OPTS" >> $OUTPUT
				if [ "$FST" = 'unknown' ]; then
					$LM_VERBOSE && echo "Deducing fstype for $MP." >> $OUTPUT
					FST=$(deduce_fstype $MP)
					$LM_VERBOSE && echo "Deduced fstype for $MP as $FST." >> $OUTPUT
				fi
				
				# Strip stuff like ext3,ext2 into just ext3.
				$LM_VERBOSE && echo "Reducing file system type." >> $OUTPUT
				FST=${FST%%,*}
				
				# 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)"
							$LM_VERBOSE && echo "Executing: mount $DEV $MP -t $FST -o remount,$PARSEDOPTS" >> $OUTPUT
							mount $DEV $MP -t $FST -o remount,$PARSEDOPTS
							;;
						*)
							PARSEDOPTS="$(replace_atime_mount_option $SAVED_OPTS $OPTS)"
							$LM_VERBOSE && echo "Executing: mount $DEV $MP -t $FST -o remount,$PARSEDOPTS" >> $OUTPUT
							mount $DEV $MP -t $FST -o remount,$PARSEDOPTS
							;;
					esac
				else
					$LM_VERBOSE && echo "No saved mount options, so apparently we never remounted this filesystem during this session." >> $OUTPUT
					$LM_VERBOSE && echo "Not remounting." >> $OUTPUT
				fi
				if [ -b $DEV -a "$CONTROL_READAHEAD" -ne 0 ] ; then
					$LM_VERBOSE && echo "Executing: /sbin/blockdev $READAHEAD_OPTION $(($NOLM_READAHEAD * 2)) $DEV" >> $OUTPUT
					/sbin/blockdev $READAHEAD_OPTION $(($NOLM_READAHEAD * 2)) $DEV >> $OUTPUT 2>&1
				fi
			fi
		done
	fi
fi

