#!/bin/bash # Auto-restart binaries with changed libraries # Copyright 2010-2012 Gentoo Foundation; Distributed under the GPL v2 # Author: Christian Ruppert VERBOSE=0 REPORT_ONLY=0 # lsof -n Do not resolve hostnames (no DNS). # -P Do not resolve port names (list port number instead of its name). # pstree -a -pu get_ppids() { local pid=${1:-1} [ $pid -eq 1 ] && return local ppid="$(awk '$1 == "PPid:" { print $2 }' /proc/$pid/status 2>/dev/null || echo 0)" [ $ppid -eq 1 ] && return # "Error" case [ $ppid -eq 0 ] && return echo $ppid get_ppids $ppid } init_restart() { local service=$1 # Only restart if it is really running through the init script if /etc/init.d/$service --quiet status 2> /dev/null; then echo "Restarting ${service}..." /etc/init.d/$service restart else # service crashed if [ ${?:-0} -eq 32 ]; then echo "Restarting crashed service: ${service} ..." /etc/init.d/$service restart fi fi } deleted_files() { local report=${1:-0} # deleted files test, lsof alternative for proc_pid in /proc/[0-9]*; do local pid=$(basename -- "${proc_pid}") # Skip init [ $pid -eq 1 ] && continue # Skip already fixed or gone procs [ ! -d "${proc_pid}" ] && continue local command="$(awk 'BEGIN { FS = "\0"; } { print $1 }' $proc_pid/cmdline 2>/dev/null)" [ -z "${command}" ] && continue local cmdline="$(awk 'BEGIN { ORS = " "; FS = "\0"; } { for (i = 2; i < NF; i++) { print $i } }' $proc_pid/cmdline 2>/dev/null | sed 's/[[:space:]]$//g')" # NOTE: "uniq" wouldn't work here local deleted="$(awk '$7 == "(deleted)" { print $6 }' $proc_pid/maps 2>/dev/null |egrep -v '^/SYSV[[:xdigit:]]+' | sort -u)" [ -z "${deleted}" ] && continue # Skip Threads local ppid="$(awk '$1 == "PPid:" { print $2 }' $proc_pid/status 2>/dev/null)" if [ $ppid -gt 1 ]; then local ppid_command="$(awk 'BEGIN { FS = "\0"; } { print $1 }' /proc/$ppid/cmdline 2>/dev/null)" [ "${command}" = "${ppid_command}" ] && continue fi local ppids="$(get_ppids $pid | sort -r | xargs)" local ppid_first="$(echo $ppids | awk 'BEGIN { FS = " "; } { print $1 }')" [ -z $ppid_first ] && ppid_first=1 local command_base="$(basename -- "${command}")" local command_dir="$(dirname -- "${command}")" if [ $report -eq 1 ]; then case $command in # Ignore those SCREEN|sshd:*|su|sudo|/bin/login|ssh-agent|irssi ) if [ $VERBOSE -eq 1 ]; then echo "Found a process which is still using deleted/updated files/libs:" echo "PID: ${pid}" [ -n "${ppids}" ] && echo "PPIDs: ${ppids}" echo "Command: ${command}" echo "Cmdline: ${cmdline}" echo "${deleted}" echo [ $ppid_first -gt 1 ] && pstree -naup $ppid_first echo fi continue ;; * ) # Special hack for nscd, ignore "false-positives" if [ "${command}" = "/usr/sbin/nscd" ]; then [ -z "$(echo $deleted | awk '{ for (i = 1; i <= NF; i++) { if (!match($i, /^\/var\/run\/nscd\//)) print $i; } }')" ] && continue elif [ "${command}" = "/usr/sbin/apache2" -o "${command}" = "/var/www/bugs.gen" ]; then [ -z "$(echo $deleted | awk '{ for (i = 1; i <= NF; i++) { if (!match($i, /^\/(dev\/zero)/)) print $i; } }')" ] && continue fi echo "Found a process which is still using deleted/updated files/libs:" echo "PID: ${pid}" [ -n "${ppids}" ] && echo "PPIDs: ${ppids}" echo "Command: ${command}" echo "Cmdline: ${cmdline}" echo "${deleted}" echo [ $ppid_first -gt 1 ] && pstree -naup $ppid_first echo ;; esac else case $command_dir in /usr/sbin|/sbin|/usr/bin|/usr/local/bin|/usr/local/sbin ) case $command_base in atd|snmpd|sshd|multipathd|thrulayd|ntpd|fprobe|ulogd|syslog-ng|stunnel|xinetd|incrond|memcached|haproxy|cfservd|nrpe|named|auditd|dmeventd|smartd|nagios|varnishd|keepalived|dovecot ) init_restart $command_base continue ;; agetty ) pkill agetty continue ;; cron ) init_restart vixie-cron continue ;; rsync ) # Evil rsync hack rsyncd_conf="$(awk 'BEGIN { FS = "\0"; } { for (i = 1; i < NF; i++) { if (match($i, /^--config=(.*\/)?([^\/]+)\.conf$/, m)) print m[2]; } }' ${proc_pid}/cmdline)" if [ -n "${rsyncd_conf}" ] && [ -f "/etc/init.d/${rsyncd_conf}" ]; then init_restart $rsyncd_conf else init_restart rsyncd fi continue ;; nscd ) # Special hack for nscd, ignore "false-positives" [ -z "$(echo $deleted | awk '{ for (i = 1; i <= NF; i++) { if (!match($i, /^\/var\/run\/nscd\//)) print $i; } }')" ] && continue init_restart nscd continue ;; apache2 ) [ -z "$(echo $deleted | awk '{ for (i = 1; i <= NF; i++) { if (!match($i, /^\/(dev\/zero)/)) print $i; } }')" ] && continue init_restart apache2 continue ;; mysqld ) mysqld_conf="$(awk 'BEGIN { FS = "\0"; } { for (i = 1; i < NF; i++) { if (match($i, /^--defaults-file=(.*\/)?(my-?)?([^\/]+)\.cnf$/, m)) print m[3]; } }' ${proc_pid}/cmdline)" if [ -n "${mysqld_conf}" ] && [ -f "/etc/init.d/mysql-${mysqld_conf}" ]; then init_restart mysql-${mysqld_conf} else init_restart mysql fi continue ;; flow-capture) init_restart flowcapture continue ;; bmc-watchdog) init_restart bmc-watchdog continue ;; esac ;; * ) case $command in tlsmgr|qmgr|/usr/lib/postfix/master ) init_restart postfix continue ;; mdadm ) init_restart mdadm continue ;; nrpe ) init_restart nrpe continue ;; /usr/lib*/openldap/slapd ) init_restart slapd continue ;; /usr/lib*/postgresql-9.0/bin/postgres) init_restart postgresql-9.0 continue ;; /usr/lib*/postgresql-8.4/bin/postgres) init_restart postgresql-8.4 continue ;; # Workaround for "broken" proc titles with mod_perl/bugzilla /var/www/bugs.gen ) [ -z "$(echo $deleted | awk '{ for (i = 1; i <= NF; i++) { if (!match($i, /^\/(dev\/zero)/)) print $i; } }')" ] && continue init_restart apache2 continue ;; esac ;; esac # Try based on the whole cmdline (cmdline+1) as well # This special case is useful when a interpreter is used, like perl # or python. # NOTE: We may need to tweak that more later to use /proc//cmdline[1] only if [ -n "${cmdline}" ]; then case $cmdline in /usr/local/sbin/apache-segfault-watch ) kill $pid /usr/local/sbin/apache-segfault-watch continue ;; esac fi fi done } while getopts hvr name; do case $name in v) VERBOSE=1 ;; r ) REPORT_ONLY=1 ;; ?|h) printf "Usage: %s: [-v] [-r]\n" $0 printf "\t-v - verbose during report, will also show otherwise ignored procs\n" printf "\t-r - report only\n" exit 2 ;; esac done shift $(($OPTIND - 1)) [ $REPORT_ONLY -eq 0 ] && deleted_files 0 deleted_files 1