Disabling the ksm and ksmtuned services during boot seems to cause problems, so do it only once when the profile is first applied and reenable the services only when full rollback is required. Resolves: rhbz#1622239 Signed-off-by: Ondřej Lysoněk <olysonek@redhat.com>
620 lines
14 KiB
Text
620 lines
14 KiB
Text
#
|
|
# This is library of helper functions that can be used in scripts in tuned profiles.
|
|
#
|
|
# API provided by this library is under heavy development and could be changed anytime
|
|
#
|
|
|
|
#
|
|
# Config
|
|
#
|
|
STORAGE=/run/tuned
|
|
STORAGE_PERSISTENT=/var/lib/tuned
|
|
STORAGE_SUFFIX=".save"
|
|
|
|
#
|
|
# Helpers
|
|
#
|
|
|
|
# Save value
|
|
# $0 STORAGE_NAME VALUE
|
|
save_value() {
|
|
[ "$#" -ne 2 ] && return
|
|
[ "$2" -a -e "${STORAGE}" ] && echo "$2" > "${STORAGE}/${1}${STORAGE_SUFFIX}"
|
|
}
|
|
|
|
# Parse sysfs value, i.e. for "val1 [val2] val3" return "val2"
|
|
# $0 SYSFS_NAME
|
|
parse_sys() {
|
|
local V1 V2
|
|
[ -r "$1" ] || return
|
|
V1=`cat "$1"`
|
|
V2="${V1##*[}"
|
|
V2="${V2%%]*}"
|
|
echo "${V2:-$V1}"
|
|
}
|
|
|
|
# Save sysfs value
|
|
# $0 STORAGE_NAME SYSFS_NAME
|
|
save_sys() {
|
|
[ "$#" -ne 2 ] && return
|
|
[ -r "$2" -a ! -e "${STORAGE}/${1}${STORAGE_SUFFIX}" ] && parse_sys "$2" > "${STORAGE}/${1}${STORAGE_SUFFIX}"
|
|
}
|
|
|
|
# Set sysfs value
|
|
# $0 SYSFS_NAME VALUE
|
|
set_sys() {
|
|
[ "$#" -ne 2 ] && return
|
|
[ -w "$1" ] && echo "$2" > "$1"
|
|
}
|
|
|
|
# Save and set sysfs value
|
|
# $0 STORAGE_NAME SYSFS_NAME VALUE
|
|
save_set_sys() {
|
|
[ "$#" -ne 3 ] && return
|
|
save_sys "$1" "$2"
|
|
set_sys "$2" "$3"
|
|
}
|
|
|
|
# Get stored sysfs value from storage
|
|
# $0 STORAGE_NAME
|
|
get_stored_sys() {
|
|
[ "$#" -ne 1 ] && return
|
|
[ -r "${STORAGE}/${1}${STORAGE_SUFFIX}" ] && cat "${STORAGE}/${1}${STORAGE_SUFFIX}"
|
|
}
|
|
|
|
# Restore value from storage
|
|
# $0 STORAGE_NAME
|
|
restore_value() {
|
|
[ "$#" -ne 1 ] && return
|
|
_rs_value="`get_stored_sys \"$1\"`"
|
|
unlink "${STORAGE}/${1}${STORAGE_SUFFIX}" >/dev/null 2>&1
|
|
[ "$_rs_value" ] && echo "$_rs_value"
|
|
}
|
|
|
|
# Restore sysfs value from storage, if nothing is stored, use VALUE
|
|
# $0 STORAGE_NAME SYSFS_NAME [VALUE]
|
|
restore_sys() {
|
|
[ "$#" -lt 2 -o "$#" -gt 3 ] && return
|
|
_rs_value="`get_stored_sys \"$1\"`"
|
|
unlink "${STORAGE}/${1}${STORAGE_SUFFIX}" >/dev/null 2>&1
|
|
[ "$_rs_value" ] || _rs_value="$3"
|
|
[ "$_rs_value" ] && set_sys "$2" "$_rs_value"
|
|
}
|
|
|
|
|
|
#
|
|
# DISK tuning
|
|
#
|
|
|
|
DISKS_DEV="$(command ls -d1 /dev/[shv]d*[a-z] 2>/dev/null)"
|
|
DISKS_SYS="$(command ls -d1 /sys/block/{sd,cciss,dm-,vd,dasd,xvd}* 2>/dev/null)"
|
|
|
|
_check_elevator_override()
|
|
{
|
|
/bin/fgrep -q 'elevator=' /proc/cmdline
|
|
}
|
|
|
|
# $0 OPERATOR DEVICES ELEVATOR
|
|
_set_elevator_helper() {
|
|
_check_elevator_override && return
|
|
SYS_BLOCK_SDX=""
|
|
[ "$2" ] && SYS_BLOCK_SDX=$(eval LANG=C /bin/ls -1 "${2}" 2>/dev/null)
|
|
|
|
# if there is no kernel command line elevator settings, apply the elevator
|
|
if [ "$1" -a "$SYS_BLOCK_SDX" ]; then
|
|
for i in $SYS_BLOCK_SDX; do
|
|
se_dev="`echo \"$i\" | sed 's|/sys/block/\([^/]\+\)/queue/scheduler|\1|'`"
|
|
$1 "elevator_${se_dev}" "$i" "$3"
|
|
done
|
|
fi
|
|
}
|
|
|
|
# $0 DEVICES ELEVATOR
|
|
set_elevator() {
|
|
_set_elevator_helper save_set_sys "$1" "$2"
|
|
}
|
|
|
|
# $0 DEVICES [ELEVATOR]
|
|
restore_elevator() {
|
|
re_elevator="$2"
|
|
[ "$re_elevator" ] || re_elevator=cfq
|
|
_set_elevator_helper restore_sys "$1" "$re_elevator"
|
|
}
|
|
|
|
# SATA Aggressive Link Power Management
|
|
# usage: set_disk_alpm policy
|
|
set_disk_alpm() {
|
|
policy=$1
|
|
|
|
for host in /sys/class/scsi_host/*; do
|
|
if [ -f $host/ahci_port_cmd ]; then
|
|
port_cmd=`cat $host/ahci_port_cmd`;
|
|
if [ $((0x$port_cmd & 0x240000)) = 0 -a -f $host/link_power_management_policy ]; then
|
|
echo $policy >$host/link_power_management_policy;
|
|
else
|
|
echo "max_performance" >$host/link_power_management_policy;
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
# usage: set_disk_apm level
|
|
set_disk_apm() {
|
|
level=$1
|
|
for disk in $DISKS_DEV; do
|
|
hdparm -B $level $disk &>/dev/null
|
|
done
|
|
}
|
|
|
|
# usage: set_disk_spindown level
|
|
set_disk_spindown() {
|
|
level=$1
|
|
for disk in $DISKS_DEV; do
|
|
hdparm -S $level $disk &>/dev/null
|
|
done
|
|
}
|
|
|
|
# usage: multiply_disk_readahead by
|
|
multiply_disk_readahead() {
|
|
by=$1
|
|
|
|
# float multiplication not supported in bash
|
|
# bc might not be installed, python is available for sure
|
|
|
|
for disk in $DISKS_SYS; do
|
|
control="${disk}/queue/read_ahead_kb"
|
|
old=$(cat $control)
|
|
new=$(echo "print int($old*$by)" | python)
|
|
|
|
(echo $new > $control) &>/dev/null
|
|
done
|
|
}
|
|
|
|
# usage: remount_disk options partition1 partition2 ...
|
|
remount_partitions() {
|
|
options=$1
|
|
shift
|
|
|
|
for partition in $@; do
|
|
mount -o remount,$options $partition >/dev/null 2>&1
|
|
done
|
|
}
|
|
|
|
remount_all_no_rootboot_partitions() {
|
|
[ "$1" ] || return
|
|
# Find non-root and non-boot partitions, disable barriers on them
|
|
rootvol=$(df -h / | grep "^/dev" | awk '{print $1}')
|
|
bootvol=$(df -h /boot | grep "^/dev" | awk '{print $1}')
|
|
volumes=$(df -hl --exclude=tmpfs | grep "^/dev" | awk '{print $1}')
|
|
nobarriervols=$(echo "$volumes" | grep -v $rootvol | grep -v $bootvol)
|
|
remount_partitions "$1" $nobarriervols
|
|
}
|
|
|
|
|
|
DISK_QUANTUM_SAVE="${STORAGE}/disk_quantum${STORAGE_SUFFIX}"
|
|
|
|
set_disk_scheduler_quantum() {
|
|
value=$1
|
|
rm -f "$DISK_QUANTUM_SAVE"
|
|
for disk in $DISKS_SYS; do
|
|
control="${disk}/queue/iosched/quantum"
|
|
echo "echo $(cat $control) > $control" >> "$DISK_QUANTUM_SAVE" 2>/dev/null
|
|
(echo $value > $control) &2>/dev/null
|
|
done
|
|
}
|
|
|
|
restore_disk_scheduler_quantum() {
|
|
if [ -r "$DISK_QUANTUM_SAVE" ]; then
|
|
/bin/sh "$DISK_QUANTUM_SAVE" &>/dev/null
|
|
rm -f "$DISK_QUANTUM_SAVE"
|
|
fi
|
|
}
|
|
|
|
#
|
|
# CPU tuning
|
|
#
|
|
|
|
CPUSPEED_SAVE_FILE="${STORAGE}/cpuspeed${STORAGE_SUFFIX}"
|
|
CPUSPEED_ORIG_GOV="${STORAGE}/cpuspeed-governor-%s${STORAGE_SUFFIX}"
|
|
CPUSPEED_STARTED="${STORAGE}/cpuspeed-started"
|
|
CPUSPEED_CFG="/etc/sysconfig/cpuspeed"
|
|
CPUSPEED_INIT="/etc/init.d/cpuspeed"
|
|
# do not use cpuspeed
|
|
CPUSPEED_USE="0"
|
|
CPUS="$(ls -d1 /sys/devices/system/cpu/cpu* | sed 's;^.*/;;' | grep "cpu[0-9]\+")"
|
|
|
|
# set CPU governor setting and store the old settings
|
|
# usage: set_cpu_governor governor
|
|
set_cpu_governor() {
|
|
governor=$1
|
|
|
|
# always patch cpuspeed configuration if exists, if it doesn't exist and is enabled,
|
|
# explictly disable it with hint
|
|
if [ -e $CPUSPEED_INIT ]; then
|
|
if [ ! -e $CPUSPEED_SAVE_FILE -a -e $CPUSPEED_CFG ]; then
|
|
cp -p $CPUSPEED_CFG $CPUSPEED_SAVE_FILE
|
|
sed -e 's/^GOVERNOR=.*/GOVERNOR='$governor'/g' $CPUSPEED_SAVE_FILE > $CPUSPEED_CFG
|
|
fi
|
|
else
|
|
if [ "$CPUSPEED_USE" = "1" ]; then
|
|
echo >&2
|
|
echo "Suggestion: install 'cpuspeed' package to get best tuning results." >&2
|
|
echo "Falling back to sysfs control." >&2
|
|
echo >&2
|
|
fi
|
|
|
|
CPUSPEED_USE="0"
|
|
fi
|
|
|
|
if [ "$CPUSPEED_USE" = "1" ]; then
|
|
service cpuspeed status &> /dev/null
|
|
[ $? -eq 3 ] && touch $CPUSPEED_STARTED || rm -f $CPUSPEED_STARTED
|
|
|
|
service cpuspeed restart &> /dev/null
|
|
|
|
# direct change using sysfs
|
|
elif [ -e /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]; then
|
|
|
|
for cpu in $CPUS; do
|
|
gov_file=/sys/devices/system/cpu/$cpu/cpufreq/scaling_governor
|
|
save_file=$(printf $CPUSPEED_ORIG_GOV $cpu)
|
|
rm -f $save_file
|
|
if [ -e $gov_file ]; then
|
|
cat $gov_file > $save_file
|
|
echo $governor > $gov_file
|
|
fi
|
|
done
|
|
fi
|
|
}
|
|
|
|
# re-enable previous CPU governor settings
|
|
# usage: restore_cpu_governor
|
|
restore_cpu_governor() {
|
|
if [ -e $CPUSPEED_INIT ]; then
|
|
if [ -e $CPUSPEED_SAVE_FILE ]; then
|
|
cp -fp $CPUSPEED_SAVE_FILE $CPUSPEED_CFG
|
|
rm -f $CPUSPEED_SAVE_FILE
|
|
fi
|
|
|
|
if [ "$CPUSPEED_USE" = "1" ]; then
|
|
if [ -e $CPUSPEED_STARTED ]; then
|
|
service cpuspeed stop &> /dev/null
|
|
else
|
|
service cpuspeed restart &> /dev/null
|
|
fi
|
|
fi
|
|
if [ -e $CPUSPEED_STARTED ]; then
|
|
rm -f $CPUSPEED_STARTED
|
|
fi
|
|
else
|
|
CPUSPEED_USE="0"
|
|
fi
|
|
|
|
if [ "$CPUSPEED_USE" != "1" -a -e /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]; then
|
|
for cpu in $CPUS; do
|
|
cpufreq_dir=/sys/devices/system/cpu/$cpu/cpufreq
|
|
save_file=$(printf $CPUSPEED_ORIG_GOV $cpu)
|
|
|
|
if [ -e $cpufreq_dir/scaling_governor ]; then
|
|
if [ -e $save_file ]; then
|
|
cat $save_file > $cpufreq_dir/scaling_governor
|
|
rm -f $save_file
|
|
else
|
|
echo userspace > $cpufreq_dir/scaling_governor
|
|
cat $cpufreq_dir/cpuinfo_max_freq > $cpufreq_dir/scaling_setspeed
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
}
|
|
|
|
_cpu_multicore_powersave() {
|
|
value=$1
|
|
[ -e /sys/devices/system/cpu/sched_mc_power_savings ] && echo $value > /sys/devices/system/cpu/sched_mc_power_savings
|
|
}
|
|
|
|
# enable multi core power savings for low wakeup systems
|
|
enable_cpu_multicore_powersave() {
|
|
_cpu_multicore_powersave 1
|
|
}
|
|
|
|
disable_cpu_multicore_powersave() {
|
|
_cpu_multicore_powersave 0
|
|
}
|
|
|
|
#
|
|
# MEMORY tuning
|
|
#
|
|
|
|
THP_ENABLE="/sys/kernel/mm/transparent_hugepage/enabled"
|
|
THP_SAVE="${STORAGE}/thp${STORAGE_SUFFIX}"
|
|
|
|
[ -e "$THP_ENABLE" ] || THP_ENABLE="/sys/kernel/mm/redhat_transparent_hugepage/enabled"
|
|
|
|
enable_transparent_hugepages() {
|
|
if [ -e $THP_ENABLE ]; then
|
|
cut -f2 -d'[' $THP_ENABLE | cut -f1 -d']' > $THP_SAVE
|
|
(echo always > $THP_ENABLE) &> /dev/null
|
|
fi
|
|
}
|
|
|
|
restore_transparent_hugepages() {
|
|
if [ -e $THP_SAVE ]; then
|
|
(echo $(cat $THP_SAVE) > $THP_ENABLE) &> /dev/null
|
|
rm -f $THP_SAVE
|
|
fi
|
|
}
|
|
|
|
#
|
|
# WIFI tuning
|
|
#
|
|
|
|
# usage: _wifi_set_power_level level
|
|
_wifi_set_power_level() {
|
|
# 0 auto, PM enabled
|
|
# 1-5 least savings and lowest latency - most savings and highest latency
|
|
# 6 disable power savings
|
|
level=$1
|
|
|
|
# apply the settings using iwpriv
|
|
ifaces=$(cat /proc/net/wireless | grep -v '|' | sed 's@^ *\([^:]*\):.*@\1@')
|
|
for iface in $ifaces; do
|
|
iwpriv $iface set_power $level
|
|
done
|
|
|
|
# some adapters may relay on sysfs
|
|
for i in /sys/bus/pci/devices/*/power_level; do
|
|
(echo $level > $i) &> /dev/null
|
|
done
|
|
}
|
|
|
|
enable_wifi_powersave() {
|
|
_wifi_set_power_level 5
|
|
}
|
|
|
|
disable_wifi_powersave() {
|
|
_wifi_set_power_level 0
|
|
}
|
|
|
|
#
|
|
# BLUETOOTH tuning
|
|
#
|
|
|
|
disable_bluetooth() {
|
|
hciconfig hci0 down >/dev/null 2>&1
|
|
lsmod | grep -q btusb && rmmod btusb
|
|
}
|
|
|
|
enable_bluetooth() {
|
|
modprobe btusb
|
|
hciconfig hci0 up >/dev/null 2>&1
|
|
}
|
|
|
|
#
|
|
# USB tuning
|
|
#
|
|
|
|
_usb_autosuspend() {
|
|
value=$1
|
|
for i in /sys/bus/usb/devices/*/power/autosuspend; do echo $value > $i; done &> /dev/null
|
|
}
|
|
|
|
enable_usb_autosuspend() {
|
|
_usb_autosuspend 1
|
|
}
|
|
|
|
disable_usb_autosuspend() {
|
|
_usb_autosuspend 0
|
|
}
|
|
|
|
#
|
|
# SOUND CARDS tuning
|
|
#
|
|
|
|
enable_snd_ac97_powersave() {
|
|
save_set_sys ac97 /sys/module/snd_ac97_codec/parameters/power_save Y
|
|
}
|
|
|
|
disable_snd_ac97_powersave() {
|
|
save_set_sys ac97 /sys/module/snd_ac97_codec/parameters/power_save N
|
|
}
|
|
|
|
restore_snd_ac97_powersave() {
|
|
restore_sys ac97 /sys/module/snd_ac97_codec/parameters/power_save $1
|
|
}
|
|
|
|
set_hda_intel_powersave() {
|
|
save_set_sys hda_intel /sys/module/snd_hda_intel/parameters/power_save $1
|
|
}
|
|
|
|
restore_hda_intel_powersave() {
|
|
restore_sys hda_intel /sys/module/snd_hda_intel/parameters/power_save $1
|
|
}
|
|
|
|
#
|
|
# VIDEO CARDS tuning
|
|
#
|
|
|
|
# Power savings settings for Radeon
|
|
# usage: set_radeon_powersave dynpm | default | low | mid | high
|
|
set_radeon_powersave () {
|
|
[ "$1" ] || return
|
|
[ -e /sys/class/drm/card0/device/power_method ] || return
|
|
if [ "$1" = default -o "$1" = auto -o "$1" = low -o "$1" = med -o "$1" = high ]; then
|
|
[ -w /sys/class/drm/card0/device/power_profile ] || return
|
|
save_sys radeon_profile /sys/class/drm/card0/device/power_profile
|
|
save_set_sys radeon_method /sys/class/drm/card0/device/power_method profile
|
|
set_sys /sys/class/drm/card0/device/power_profile "$1"
|
|
elif [ "$1" = dynpm ]; then
|
|
save_sys radeon_profile /sys/class/drm/card0/device/power_profile
|
|
save_set_sys radeon_method /sys/class/drm/card0/device/power_method dynpm
|
|
fi
|
|
}
|
|
|
|
restore_radeon_powersave () {
|
|
restore_sys radeon_method /sys/class/drm/card0/device/power_method profile
|
|
_rrp_method="`get_stored_sys radeon_method`"
|
|
[ -z "$_rrp_method" -o _rrp_method="profile" ] && restore_sys radeon_profile /sys/class/drm/card0/device/power_profile default
|
|
}
|
|
|
|
#
|
|
# SOFTWARE tuning
|
|
#
|
|
|
|
RSYSLOG_CFG="/etc/rsyslog.conf"
|
|
RSYSLOG_SAVE="${STORAGE}/cpuspeed${STORAGE_SUFFIX}"
|
|
|
|
disable_logs_syncing() {
|
|
cp -p $RSYSLOG_CFG $RSYSLOG_SAVE
|
|
sed -i 's/ \/var\/log/-\/var\/log/' $RSYSLOG_CFG
|
|
}
|
|
|
|
restore_logs_syncing() {
|
|
mv -Z $RSYSLOG_SAVE $RSYSLOG_CFG || mv $RSYSLOG_SAVE $RSYSLOG_CFG
|
|
}
|
|
|
|
#
|
|
# HARDWARE SPECIFIC tuning
|
|
#
|
|
|
|
# Asus EEE with Intel Atom
|
|
_eee_fsb_control() {
|
|
value=$1
|
|
if [ -e /sys/devices/platform/eeepc/she ]; then
|
|
echo $value > /sys/devices/platform/eeepc/she
|
|
elif [ -e /sys/devices/platform/eeepc/cpufv ]; then
|
|
echo $value > /sys/devices/platform/eeepc/cpufv
|
|
elif [ -e /sys/devices/platform/eeepc-wmi/cpufv ]; then
|
|
echo $value > /sys/devices/platform/eeepc-wmi/cpufv
|
|
fi
|
|
}
|
|
|
|
eee_set_reduced_fsb() {
|
|
_eee_fsb_control 2
|
|
}
|
|
|
|
eee_set_normal_fsb() {
|
|
_eee_fsb_control 1
|
|
}
|
|
|
|
#
|
|
# modprobe configuration handling
|
|
#
|
|
|
|
kvm_modprobe_file=/etc/modprobe.d/kvm.rt.tuned.conf
|
|
|
|
setup_kvm_mod_low_latency()
|
|
{
|
|
if [ -f $kvm_modprobe_file ]; then
|
|
return
|
|
fi
|
|
|
|
modinfo -p kvm | grep -q kvmclock_periodic_sync
|
|
if [ "$?" -eq 0 ]; then
|
|
echo "options kvm kvmclock_periodic_sync=0" > $kvm_modprobe_file
|
|
fi
|
|
|
|
modinfo -p kvm_intel | grep -q ple_gap
|
|
if [ "$?" -eq 0 ]; then
|
|
echo "options kvm_intel ple_gap=0" >> $kvm_modprobe_file
|
|
fi
|
|
}
|
|
|
|
teardown_kvm_mod_low_latency()
|
|
{
|
|
rm -f $kvm_modprobe_file
|
|
}
|
|
|
|
#
|
|
# KSM
|
|
#
|
|
|
|
KSM_SERVICES="ksm ksmtuned"
|
|
KSM_RUN_PATH=/sys/kernel/mm/ksm/run
|
|
KSM_MASK_FILE="${STORAGE_PERSISTENT}/ksm-masked"
|
|
|
|
disable_ksm()
|
|
{
|
|
if [ ! -f $KSM_MASK_FILE ]; then
|
|
# Always create $KSM_MASK_FILE, since we don't want to
|
|
# run any systemctl commands during boot
|
|
if ! touch $KSM_MASK_FILE; then
|
|
die "failed to create $KSM_MASK_FILE"
|
|
fi
|
|
systemctl --now --quiet mask $KSM_SERVICES
|
|
# Unmerge all shared pages
|
|
test -f $KSM_RUN_PATH && echo 2 > $KSM_RUN_PATH
|
|
fi
|
|
}
|
|
|
|
# Should only be called when full_rollback == true
|
|
enable_ksm()
|
|
{
|
|
if [ -f $KSM_MASK_FILE ]; then
|
|
if systemctl --quiet unmask $KSM_SERVICES; then
|
|
rm -f $KSM_MASK_FILE
|
|
fi
|
|
fi
|
|
}
|
|
|
|
die() {
|
|
echo "$@" >&2
|
|
exit 1
|
|
}
|
|
|
|
#
|
|
# ACTION PROCESSING
|
|
#
|
|
|
|
error_not_implemented() {
|
|
echo "tuned: script function '$1' is not implemented." >&2
|
|
}
|
|
|
|
# implicit actions, will be used if not provided by profile script:
|
|
#
|
|
# * start must be implemented
|
|
# * stop must be implemented
|
|
|
|
start() {
|
|
error_not_implemented start
|
|
return 16
|
|
}
|
|
|
|
stop() {
|
|
error_not_implemented stop
|
|
return 16
|
|
}
|
|
|
|
#
|
|
# main processing
|
|
#
|
|
|
|
process() {
|
|
ARG="$1"
|
|
shift
|
|
case "$ARG" in
|
|
start)
|
|
start "$@"
|
|
RETVAL=$?
|
|
;;
|
|
stop)
|
|
stop "$@"
|
|
RETVAL=$?
|
|
;;
|
|
verify)
|
|
if declare -f verify &> /dev/null;
|
|
then
|
|
verify "$@"
|
|
else
|
|
:
|
|
fi
|
|
RETVAL=$?
|
|
;;
|
|
*)
|
|
echo $"Usage: $0 {start|stop|verify}"
|
|
RETVAL=2
|
|
;;
|
|
esac
|
|
|
|
exit $RETVAL
|
|
}
|