| #!/bin/sh | |
| # Copyright 2010 The ChromiumOS Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| # Where to write debug logs to. | |
| LOGPATH="/var/log/shutdown_force_kill_processes" | |
| # 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 . "$@" | |
| } |