| #!/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 . "$@" |
| } |