blob: bba39ac04290f62764319dacafb81b9226da46c8 [file] [log] [blame]
#!/bin/sh
# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Timeouts in msec
readonly SIGTERM_TIMEOUT=2500
readonly SIGKILL_TIMEOUT=2000
# Where to write debug logs to.
LOGPATH="/var/log/shutdown_force_kill_processes"
# Send SIGTERM once to all processes that match regex in $1, then wait for
# them all to terminate. If processes matching $1 are still running after
# SIGTERM_TIMEOUT msec they are all sent SIGKILL once. If processes matching
# $1 are still present after SIGKILL_TIMEOUT msec a message is logged.
term_process() {
local process="$1"
pkill "${process}"
local pid i=0
while [ "${i}" -lt "${SIGTERM_TIMEOUT}" ]; do
pid="$(pgrep "${process}")"
if [ $? -ne 0 ] ; then
return
fi
sleep .1
: "$(( i += 100 ))"
done
logger -t "term_process(${process})" \
"PIDs [${pid}] did not terminate, sending SIGKILL"
echo "term_process(${process})" \
"PIDs [${pid}] did not terminate, sending SIGKILL"
pkill -KILL "${process}"
i=0
while [ "${i}" -lt "${SIGKILL_TIMEOUT}" ]; do
pid="$(pgrep "${process}")"
if [ $? -ne 0 ] ; then
return
fi
sleep .1
: "$(( i += 100 ))"
done
logger -t "term_process(${process})" "PIDs [${pid}] not dead after SIGKILL"
echo "term_process(${process})" "PIDs [${pid}] not dead after SIGKILL"
}
# Parses the output of `lsof -Fn` in the format (p<pid>\n(n<filename>\n)*)*,
# and returns the list of <pid>s that were followed by some filename matching
# the regex argument $1. This is an utility function for use in
# kill_with_open_files_on_path_and_mountpoints().
_lsofFn_filter() {
sed -n -E -e "
# Hold all the most recent 'p' line in the buffer.
/^p/h
# Look for a matching 'n' line. Pick % as it's unlikely to be in a path.
# Enclose $1 by parentheses so that the caller can pass in regular
# expressions like /home|/foo.
\\%^n($1)%{
# Get the last 'p' line out of the buffer.
g
# Delete the leading 'p'.
s:^p::
# Print it.
p
}
" | sort -u
}
# For a given path pattern regex (the first argument) and given mountpoints
# (the rest), this will kill all processes with open files on that mountpoint
# with its path matches the regex, so that it can be unmounted. It starts off
# by sending a TERM and if the process hasn't exited quickly enough it will
# send KILL.
#
# Since a typical shutdown should have no processes with open files on a
# partition that we care about at this point, we log the set of processes
# to /var/log/shutdown_force_kill_processes
kill_with_open_files_on_path_and_mountpoints() {
local pids pid i
local path_regex="$1"
shift
local log_path_label="$1"
shift
# Remove old log files.
rm -f "${LOGPATH}" "${LOGPATH}.${log_path_label}"
pids="$(lsof -n -Fn "$@" | _lsofFn_filter "${path_regex}")"
if [ -z "${pids}" ] ; then
return # The typical case; no open files at this point.
fi
# pids should have been empty. Since it is not, we log for future inspection.
lsof -n "$@" >"${LOGPATH}.${log_path_label}"
# First try a gentle kill -TERM
for i in 1 2 3 4 5 6 7 8 9 10; do
for pid in ${pids} ; do
! kill -TERM "${pid}"
done
pids="$(lsof -n -Fn "$@" | _lsofFn_filter "${path_regex}")"
if [ -z "${pids}" ] ; then
return
fi
sleep .1
done
# Now kill -KILL as necessary
for i in 1 2 3 4 5 6 7 8 9 10; do
for pid in ${pids} ; do
! kill -KILL "${pid}"
done
pids=$(lsof -n -Fn "$@" | _lsofFn_filter "${path_regex}")
if [ -z "${pids}" ] ; then
return
fi
sleep .1
done
}
# For a given mountpoint, this will kill all processes with open files
# on that mountpoint so that it can be unmounted. It starts off by sending
# a TERM and if the process hasn't exited quickly enough it will send KILL.
kill_with_open_files_on() {
kill_with_open_files_on_path_and_mountpoints . "$@"
}