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