postrun revision 7082
#!/bin/ksh
#
# Script for starting a postponed post-installation command in
# a Live-Upgrade-safe environment
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License, Version 1.0 only
# (the "License"). You may not use this file except in compliance
# with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2004-2005 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
if [ `/usr/xpg4/bin/id -u` != 0 ]; then
echo "postrun: error: run this script as root"
exit 1
fi
export PATH=/usr/bin
MYDIR=$(cd $(dirname $0); pwd)
SPOOLDIR="$MYDIR/../../var/spool/postrun"
LOCKFILE="$SPOOLDIR/.lock"
LOGFILE="$MYDIR/../../var/log/postrun.log"
SEQFILE="$SPOOLDIR/.seq"
usage() {
echo 'Usage: postrun [options]'
echo
echo 'Options:'
echo ' -u, --uniq'
echo ' If the same command is requested multiple times, the command'
echo ' is only run once. If it is safe to execute the command'
echo ' immediately, it will be delayed by 5 minutes, or as set'
echo ' using the --timeout option'
echo
echo ' -t <n>, --timeout <n>'
echo ' Delay the execution of uniq commands by <n> minutes.'
echo
echo ' -b, --bg'
echo ' Run the command in the background and return control'
echo ' immediately'
echo
echo ' -f <file>'
echo ' Read the commands from <file> instead of the standard'
echo ' input.'
echo
echo ' -h, -?, --help'
echo ' Display this help'
exit 1
}
#LOCK_UNLOCK_FUNCTIONS_START
# lock the postrun spool or log file
# if $1 is 'log' then lock the log file, otherwise log the spool
postrun_lock() {
this_lock=$LOCKFILE
if [ "x$1" = xlog ]; then
this_lock=${LOCKFILE}.log
fi
# lock file exists (contains the pid of the process that locked it
while test -f $this_lock; do
# get the pid that holds the lock
pid=`cat $this_lock 2>/dev/null` || continue
# already locked by this process
test "$pid" == "$$" && return
# check if the process is still running or else delete the lock
ps -g $pid -o 'pid' | egrep -s "^ *$pid *" 2>&1 \
&& sleep 1 || rm -f $this_lock
done
# run false so that we enter the while loop
false
while [ $? != 0 ]; do
# write the pid to the lock file
if ! echo "$$" > $this_lock; then
echo "postrun: error: cannot create lock file $this_lock"
exit 1
fi
# read it back in case another process also wrote it's pid there
# in the meantime
pid=`cat $this_lock 2>/dev/null`
# the loop will restart is the file cannot be read
done
# check if this process holds the lock or else try the whole thing again
test "$pid" == "$$" || postrun_lock $1
}
# release the lock
# unlock the log file if $1 == 'log', unlock the spool otherwise
postrun_unlock() {
this_lock=$LOCKFILE
if [ "x$1" = xlog ]; then
this_lock=${LOCKFILE}.log
fi
if ! rm -f $this_lock; then
echo "postrun: error: cannot remove lock file $this_lock"
exit 1
fi
}
#LOCK_UNLOCK_FUNCTIONS_END
# get the next job id
postrun_get_seq() {
postrun_lock
seq=`cat $SEQFILE 2>/dev/null`
next_seq=$(($seq + 1))
echo $next_seq > $SEQFILE
postrun_unlock
echo $next_seq
}
is_number() {
echo "$1" | egrep -vs '^[0-9]+$' && return 1
echo "$1" | egrep -s '^[0-9]+$' || return 1
return 0
}
postrun_spool_command() {
cd $SPOOLDIR
# check if there's already a spooled job for the same command
uniq_job_nr=
IFS=' '
postrun_lock
for f in *.cmd; do
cmp -s $postrun_command_file $f && {
if [ $postrun_is_uniq = yes ]; then
uniq_job_nr=`basename $f .cmd`
break
fi
egrep -s '^uniq_command: yes' `basename $f .cmd`.ctrl && {
uniq_job_nr=`basename $f .cmd`
break
}
}
done
if [ "x$uniq_job_nr" != x ]; then
# we found a matching spooled uniq job
# all we need to do is update the uniq time and make sure it's
# flagged as a uniq job
sed -e 's/^uniq_command: .*/uniq_command: yes/' \
-e 's/^(pkginst: .*)/\1, '$PKGINST'/' \
-e 's/^uniq_time: .*/uniq_time: '`date +%Y.%m.%d.%H.%M.%S`'/' \
$uniq_job_nr.ctrl > $uniq_job_nr.ctrl.new
mv $uniq_job_nr.ctrl.new $uniq_job_nr.ctrl
else
postrun_unlock
job_seq=`postrun_get_seq`
postrun_lock
ctrl_file="$SPOOLDIR/$job_seq.ctrl"
cmd_file="$SPOOLDIR/$job_seq.cmd"
cat $postrun_command_file > $cmd_file
cat /dev/null > $ctrl_file
echo "pkginst: $PKGINST" >> $ctrl_file
echo "submit_time: `date +%Y.%m.%d.%H.%M.%S`" >> $ctrl_file
echo "uniq_command: $postrun_is_uniq" >> $ctrl_file
echo "uniq_time: `date +%Y.%m.%d.%H.%M.%S`" >> $ctrl_file
echo "uniq_timeout: $postrun_uniq_timeout" >> $ctrl_file
echo "background: $postrun_bg_job" >> $ctrl_file
fi
postrun_unlock
}
postrun_run_command() {
echo DEBUG: postrun_run_command
cmdout=`mktemp /tmp/postrun.out.XXXX`
# create a background jobs script that executes the commands
# then locks the spool/log file and appends the output to the
# log file and finally unlocks
cmdfile=`mktemp /tmp/postrun.job.XXXX`
cat /dev/null > $cmdfile
cat /dev/null > $cmdout
echo '#!/bin/ksh' >> $cmdfile
# copy the postrun_lock and postrun_unlock commands from
# this script to the background job script
echo "LOCKFILE=$LOCKFILE" >> $cmdfile
sed -e '1,/#LOCK_UNLOCK_FUNCTIONS_START/d' \
-e '/#LOCK_UNLOCK_FUNCTIONS_END/,$d' $0 >> $cmdfile
# save the stdout file description
echo 'exec 3<&1' >> $cmdfile
echo "exec >> $cmdout 2>&1" >> $cmdfile
echo 'PATH=/usr/bin; export PATH' >> $cmdfile
echo 'echo Starting postrun job at `date`' >> $cmdfile
if [ "x$postrun_submit_time" != x ]; then
if [ $postrun_bg_job = yes ]; then
echo 'echo This is a spooled background job' >> $cmdfile
else
echo 'echo This is a spooled foreground job' >> $cmdfile
fi
echo "echo Job submitted by $postrun_pkginst at $postrun_submit_time" \
>> $cmdfile
else
if [ $postrun_bg_job = yes ]; then
echo 'echo This is an immediate background job' >> $cmdfile
else
echo 'echo This is an immediate foreground job' >> $cmdfile
fi
echo "echo Job submitted by $postrun_pkginst"\
>> $cmdfile
fi
echo 'echo Running commands:' >> $cmdfile
echo "echo '>>>' commands follow:" >> $cmdfile
echo 'cat <<EOF' >> $cmdfile
cat $postrun_command_file >> $cmdfile
echo 'EOF' >> $cmdfile
echo "echo '<<<' commands end" >> $cmdfile
echo "echo '>>>' Command output follows:" >> $cmdfile
cat $postrun_command_file >> $cmdfile
echo "echo '<<<' Command completed with exit status \$?" \
>> $cmdfile
echo 'echo Job finished at `date`' >> $cmdfile
echo 'echo --' >> $cmdfile
# restore PATH in case the command changed it
echo 'PATH=/usr/bin; export PATH' >> $cmdfile
# restore stdout
echo 'exec 1<&3' >> $cmdfile
# close file descriptor 3
echo 'exec 3<&-' >> $cmdfile
echo 'exec 2>&1' >> $cmdfile
# append the messages to the real log file
# need to lock the log file to avoid 2 postrun commands
# writing at the same time and messing up the log
echo 'postrun_lock log' >> $cmdfile
echo "cat $cmdout >> $LOGFILE" >> $cmdfile
echo 'postrun_unlock log' >> $cmdfile
echo "rm -f $cmdout" >> $cmdfile
echo "rm -f $cmdfile" >> $cmdfile
chmod +x $cmdfile
if [ $postrun_bg_job = yes ]; then
$cmdfile &
else
$cmdfile
fi
exitval=$?
}
postrun_defaults() {
# default settings
postrun_pkginst="$PKGINST"
postrun_submit_time=""
postrun_uniq_time=""
postrun_is_uniq=no
postrun_uniq_timeout=5
postrun_bg_job=no
postrun_command_file=""
}
# usage: is_leap_year yyyy
is_leap_year() {
cal 02 $1 | egrep -s 29 && return 0
return 1
}
# get_abstime yy mm dd hh mm ss
#
# prints the elapsed time in seconds since 1970.01.01.00.00.00
#Length of the months:
# JA FE MA AP MY JN JL AU SE OC NO DE
set -A MONTH 0 31 28 31 30 31 30 31 31 30 31 30 31
get_abstime() {
# the absolute time since 1970...
t=0
# number of years
t=$(($t + ($1 - 1970) * 31536000))
# add 1 day for each leap year
y=1972
end_y=$1
if [ $2 -lt 2 ]; then
end_y=$(($1 - 1))
fi
while [ $y -le $end_y ]; do
is_leap_year $y && t=$(($t + 86400))
y=$(($y + 4))
done
# number of months
m=1
while [ $m -lt $2 ]; do
t=$(($t + ${MONTH[$m]} * 86400))
m=$(($m + 1))
done
# number of days, hours, minutes and seconds:
echo $(($t + ($3 - 1) * 86400 + $4 * 3600 + $5 * 60 + $6))
}
# get_timediff: prints the difference in seconds between 2 time strings
# the time strings should be of the following format:
# YYYY.MM.DD.HH.MM.SS as printed by date +%Y.%m.%d.%H.%M.%S
#
# Works for dates after 1970.01.01.00.00.00
#
get_timediff() {
year1=$(expr "$1" : "^\([^.]*\)\..*")
month1=$(expr "$1" : "^[^.]*\.\([^.]*\)\..*")
day1=$(expr "$1" : "^[^.]*\.[^.]*\.\([^.]*\)\..*")
hour1=$(expr "$1" : "^[^.]*\.[^.]*\.[^.]*\.\([^.]*\)\..*")
min1=$(expr "$1" : "^[^.]*\.[^.]*\.[^.]*\.[^.]*\.\([^.]*\)\..*")
sec1=$(expr "$1" : "^[^.]*\.[^.]*\.[^.]*\.[^.]*\.[^.]*\.\([^.]*\)")
year2=$(expr "$2" : "^\([^.]*\)\..*")
month2=$(expr "$2" : "^[^.]*\.\([^.]*\)\..*")
day2=$(expr "$2" : "^[^.]*\.[^.]*\.\([^.]*\)\..*")
hour2=$(expr "$2" : "^[^.]*\.[^.]*\.[^.]*\.\([^.]*\)\..*")
min2=$(expr "$2" : "^[^.]*\.[^.]*\.[^.]*\.[^.]*\.\([^.]*\)\..*")
sec2=$(expr "$2" : "^[^.]*\.[^.]*\.[^.]*\.[^.]*\.[^.]*\.\([^.]*\)")
# calculate seconds since 1970.01.01.00.00.00
t1=`get_abstime $year1 $month1 $day1 $hour1 $min1 $sec1`
t2=`get_abstime $year2 $month2 $day2 $hour2 $min2 $sec2`
# print difference
expr $t1 - $t2
}
postrun_runq() {
cd $SPOOLDIR
IFS=' '
postrun_lock
for job in *.ctrl; do
postrun_defaults
echo DEBUG: Reading job $job
while read var val; do
case "$var" in
pkginst: )
postrun_pkginst="$val"
;;
submit_time: )
postrun_submit_time="$val"
;;
uniq_command: )
postrun_is_uniq="$val"
;;
uniq_time: )
postrun_uniq_time="$val"
;;
uniq_timeout: )
postrun_uniq_time="$val"
;;
background: )
postrun_bg_job="$val"
;;
* )
echo "postrun: WARNING: invalid setting in $job: $var"
;;
esac
done < $job
echo DEBUG: done
postrun_command_file=$SPOOLDIR/`basename $job .ctrl`.cmd
if [ $postrun_ignore_timeout = no ]; then
# if it's a uniq job, check if it timed out
if [ "x$postrun_is_uniq" = xyes ]; then
# calculate time difference (seconds)
tdiff=$(get_timediff $(date +%Y.%m.%d.%H.%M.%S) \
$postrun_uniq_time)
timeout_sec=$((postrun_uniq_timeout * 60))
if [ $tdiff -gt $timeout_sec ]; then
postrun_run_command
rm -f $postrun_command_file $job
fi
else
postrun_run_command
rm -f $postrun_command_file $job
fi
else
# ignore timeout, just run the job
postrun_run_command
rm -f $postrun_command_file $job
fi
done
postrun_unlock
exit 0
}
postrun_defaults
exitval=0
postrun_ignore_timeout=no
if [ $# = 1 -a "x$1" = 'x-qf' ]; then
# postrun-runq mode (ignore timeout for uniq jobs, since this is
# expected to be run at system boot)
postrun_ignore_timeout=yes
postrun_runq
exit 1
fi
if [ $# = 1 -a "x$1" = 'x-q' ]; then
# postrun-runq mode, to be run from at(1)
postrun_runq
exit 1
fi
# process the command line
while [ $# -gt 0 ]; do
case "$1" in
-h|-\?|--help)
usage
;;
-u|--uniq)
postrun_is_uniq=yes
;;
-b|--bg)
postrun_bg_job=yes
;;
-t|--timeout)
opt="$1"
if [ $# == 0 ]; then
echo "postrun: error: argument expected after $opt"
exit 1
fi
shift
timeout=$1
if ! is_number "$timeout"; then
echo "postrun: error: interger number expected after $opt (found \"$timeout\")"
exit 1
fi
postrun_uniq_timeout=$timeout
;;
-f)
opt="$1"
if [ $# == 0 ]; then
echo "postrun: error: argument expected after $opt"
exit 1
fi
shift
postrun_command_file="$1"
;;
--)
break
;;
*)
echo "postrun: error: invalid argument: $1"
exit 1
;;
esac
shift
done
if [ "x$postrun_command_file" = x ]; then
# save the standard input in a temporary file
tmp_cmd_file=`mktemp /tmp/postrun.cmd.XXXX`
cat > $tmp_cmd_file
postrun_command_file=$tmp_cmd_file
fi
if [ "$LUBIN" != "" ]; then
#
# Live Upgrade. Unsafe to run the command now.
# Put into spool and defer to next boot.
#
postrun_spool_command "${@}"
elif [ "$PKG_INSTALL_ROOT" != "" -a "$PKG_INSTALL_ROOT" != "/" ]; then
#
# Installation to an alternate root directory
# Put command into spool and defer to next boot.
#
postrun_spool_command "${@}"
else
#
# Local package install. Everything's shiny happy,
# safe to run the command right now
#
if [ $postrun_is_uniq = yes ]; then
# don't run the command yet in case the same command is requested
# within the next postrun_uniq_timeout minutes
postrun_spool_command "${@}"
# run the spooled jobs in postrun_uniq_timeout minutes
echo "$MYDIR/postrun -q" | \
at now "+${postrun_uniq_timeout}minutes"
else
postrun_run_command "${@}"
fi
fi
if [ "x$tmp_cmd_file" != x ]; then
rm -f $tmp_cmd_file
fi
exit $exitval