diff --git a/checkrestart b/checkrestart index eeacbb8..7a0ddd7 100755 --- a/checkrestart +++ b/checkrestart @@ -1,27 +1,73 @@ #!/usr/bin/python +# Copyright (C) 2001 Matt Zimmerman +# +# Further updated by Javier Fernandez-Sanguino +# - included patch from Justin Pryzby +# to work with the latest Lsof - modify to reduce false positives by not +# complaining about deleted inodes/files under /tmp/, /var/log/ or named +# /SYSV. + +# PENDING: +# - included code from 'psdel' contributed by Sam Morris to +# make the program work even if lsof is not installed +# (available at http://robots.org.uk/src/psdel) +# +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA +# +# On Debian systems, a copy of the GNU General Public License may be +# found in /usr/share/common-licenses/GPL. + import sys -import os +import os, errno import re +import stat +import pwd +import sys +import string if os.getuid() != 0: sys.stderr.write('This program must be run as root\n') sys.stderr.write('in order to collect information about all open file descriptors\n') sys.exit(1) + +def find_cmd(cmd): + dirs = [ '/', '/usr/', '/usr/local/', sys.prefix ] + for d in dirs: + for sd in ('bin', 'sbin'): + location = os.path.join(d, sd, cmd) + if os.path.exists(location): + return location + return 0 + + def main(): process = None - processes = {} - for line in os.popen('lsof +L1 -F n').readlines(): - field, data = line[0], line[1:-1] - - if field == 'p': - process = processes.setdefault(data,Process(int(data))) - elif field == 'n': - process.files.append(data) - - toRestart = filter(lambda process: process.needsRestart(), - processes.values()) + toRestart = {} + + toRestart = lsofcheck() + # Check if we have lsof, if not, use psdel +# if find_cmd('lsof'): +# toRestart = lsofcheck() +# else: +# TODO - This does not work yet: +# toRestart = psdelcheck() + print "Found %d processes using old versions of upgraded files" % len(toRestart) if len(toRestart) == 0: @@ -97,10 +143,116 @@ for process in package.processes: print "\t%s\t%s" % (process.pid,process.program) +def lsofcheck(): + processes = {} + + for line in os.popen('lsof +XL -F nf').readlines(): + field, data = line[0], line[1:-1] + + if field == 'p': + process = processes.setdefault(data,Process(int(data))) + elif field == 'k': + process.links.append(data) + elif field == 'n': + # Remove the previous entry to check if this is something we should do + if data.startswith('/SYSV'): + last = process.descriptors.pop() + if not re.compile("DEL").search(last): + process.files.append(data) + else: + process.files.append(data) + elif field == 'f': + process.descriptors.append(data) + + toRestart = filter(lambda process: process.needsRestart(), + processes.values()) + return toRestart + +def psdelcheck(): +# TODO - Needs to be fixed to work here +# Useful for seeing which processes need to be restarted after you upgrade +# programs or shared libraries. Written to replace checkrestart(1) from the +# debian-goodies, which often misses out processes due to bugs in lsof; see +# for more information. + + numeric = re.compile(r'\d+') + toRestart = map (delmaps, map (string.atoi, filter (numeric.match, os.listdir('/proc')))) + return toRestart + +def delmaps (pid): + processes = {} + process = processes.setdefault(pid,Process(int(pid))) + deleted = re.compile(r'(.*) \(deleted\)$') + boring = re.compile(r'/(dev/zero|SYSV([\da-f]{8}))') + + mapline = re.compile(r'^[\da-f]{8}-[\da-f]{8} [r-][w-][x-][sp-] ' + r'[\da-f]{8} [\da-f]{2}:[\da-f]{2} (\d+) *(.+)( \(deleted\))?\n$') + maps = open('/proc/%d/maps' % (pid)) + for line in maps.readlines (): + m = mapline.match (line) + if (m): + inode = string.atoi (m.group (1)) + file = m.group (2) + if inode == 0: + continue + # remove ' (deleted)' suffix + if deleted.match (file): + file = file [0:-10] + if boring.match (file): + continue + # list file names whose inode numbers do not match their on-disk + # values; or files that do not exist at all + try: + if os.stat (file)[stat.ST_INO] != inode: + process = processes.setdefault(pid,Process(int(pid))) + except OSError, (e, strerror): + if e == errno.ENOENT: + process = processes.setdefault(pid,Process(int(pid))) + else: + sys.stderr.write ('checkrestart (psdel): %s %s: %s\n' % (SysProcess.get(pid).info (), file, os.strerror (e))) + else: + print 'checkrestart (psdel): Error parsing "%s"' % (line [0:-1]) + maps.close () + + return process + + +class SysProcess: + re_name = re.compile('Name:\t(.*)$') + re_uids = re.compile('Uid:\t(\d+)\t(\d+)\t(\d+)\t(\d+)$') + processes = {} + def get (pid): + try: + return Process.processes [pid] + except KeyError: + Process.processes [pid] = Process (pid) + return Process.get (pid) + + # private + def __init__ (self, pid): + self.pid = pid + + status = open ('/proc/%d/status' % (self.pid)) + for line in status.readlines (): + m = self.re_name.match (line) + if m: + self.name = m.group (1) + continue + m = self.re_uids.match (line) + if m: + self.user = pwd.getpwuid (string.atoi (m.group (1)))[0] + continue + status.close () + + def info (self): + return '%d %s %s' % (self.pid, self.name, self.user) + class Process: def __init__(self, pid): self.pid = pid self.files = [] + self.descriptors = [] + self.links = [] try: self.program = self.cleanFile(os.readlink('/proc/%d/exe' % self.pid)) @@ -115,10 +267,31 @@ f = f[:null] return re.sub('( \(deleted\)|.dpkg-new).*$','',f) + # Check if a process needs to be restarted, previously we would + # just check if it used libraries named '.dpkg-new' since that's + # what dpkg would do. Now we need to be more contrieved. def needsRestart(self): for f in self.files: - if f.endswith('.dpkg-new'): - return 1 + #print "Checking %s" % f + # We don't care about log files + if f.startswith('/var/log/'): + continue + # Or about files under /tmp + if f.startswith('/tmp/'): + continue + # TODO: it should only care about library files (i.e. /lib, /usr/lib and the like) + # build that check with a regexp to exclude others + if f.endswith(' (deleted)'): + return 1 + if re.compile("\(path inode=[0-9]+\)$").search(f): + return 1 + for f in self.descriptors: + # If it begins with SYSV it is not a deleted file + if re.compile("DEL").search(f): + return 1 + for f in self.links: + if f == 0: + return 1 return 0 class Package: @@ -129,3 +302,4 @@ if __name__ == '__main__': main() + diff --git a/checkrestart.1 b/checkrestart.1 new file mode 100644 index 0000000..c9c1829 --- /dev/null +++ b/checkrestart.1 @@ -0,0 +1,71 @@ +.\" checkrestart.1 - provide a list of processess that need to be restarted +.\" Copyright (C) 2006 Javier Fernandez-Sanguino +.\" Everybody is allowed to distribute this manual page, +.\" to modify it, and to distribute modifed versions of it. +.TH checkrestart 1 "December 19 2006" "debian\-goodies" "debian\-goodies" +.SH NAME +checkrestart \- check which processes need to be restarted after an upgrade +.SH SYNOPSIS +.B checkrestart +.SH DESCRIPTION +The +.B checkrestart +program tries to determine if there are processes in the system +that need to be restarted after a system upgrade. This is necessary since an upgrade +will usually bring new system libraries and running processes will be still +using the old versions of the libraries. In \fIstable\fP Debian GNU/Linux +systems this is typically needed to eliminate a system exposure to a +vulnerability which might have been fixed by upgrading a library which that +process makes use of. + +.P +Consequently, +.B checkrestart +is sometimes used as an audit tool to find outdated versions of libraries in use, +particularly after security upgrades. Administrators should not, however, rely +on its output completely (see \fBBUGS\fP below). + +.P +This script needs to run as root in order to obtain the information it needs +for analysis. + +.SH EXIT STATUS + +The program will exit with error (1) if a non-root user tries to run it. Otherwise, +it will always exit with error status 0. + +.SH BUGS +This program might fail if the output of the \fIlsof\fP utility changes since it +depends on it to detect which deleted files are used by processes. It might +also output some false positives depending on the processes' behaviour since +it does not check yet if the (deleted) files in use are really libraries. + +.P +.B Checkrestart +is also sensitive to the kernel version in use. And might fail to work with newer +(or older) versions. + +.P +A rewrite to make it less dependant on \fIlsof\fP could improve this, however. + +.SH SEE ALSO +.B lsof(8) + +.SH AUTHOR + +.B checkrestart +was written by Matt Zimmerman for the Debian +GNU/Linux distribution. + +.SH COPYRIGHT AND LICENCE + +Copyright (C) 2001 Matt Zimmerman + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +On Debian systems, a copy of the GNU General Public License may be +found in /usr/share/common-licenses/GPL. + diff --git a/debian/changelog b/debian/changelog index d535861..4263a30 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,35 @@ +debian-goodies (0.27) unstable; urgency=medium + + [ Fixes I would like to see in etch, even though we're on freeze now ] + * Fix checkrestart with patch provided by Justin Pryzby, modify to reduce + false positives by not complaining about deleted inodes/files under + /tmp/, /var/log/ or named /SYSV. This fixes the long-standing issue that + made this script rather useless (related to lsof, see bug #297029, and to + changes in dpkg) (Closes: #264985) + (Note I have included the code from Sam Morris, available at + http://robots.org.uk/src/psdel and contributed in the BTS. But it still + needs to be massaged to work as a replacement for lsof) + * Write a manpage for checkrestart so administrators are aware that this + tool should not be completely relied on. + * Update the version of network-test which fixes some of the bugs already + fixed in the experimental 'ifupdown-extra' package: + [ Note: Post-etch network-test will be removed from this package but + I rather not try to introduce an experimental package such as + ifupdown-extra into etch right now ] + * Make network-test use bash (Closed: #401363) + * Do not analyse resolv.conf lines that have been commented out. + * Change network-test so it does not complain loudly if ethtool is not + installed (just recommends its installation) + * Fix duplicate spaces in the script as well as some typos with patch + provided by Norbert Kiesel (Closed: #400463) + * Change behaviour of network-test when testing ethernet link + (based on ifupdown-extra's 'check-network-cable' tests) this is better + (and more reliable) than just using 'ifconfig' but requires users + install additional software. + * Lintian error fix: FSF address + + -- Javier Fernandez-Sanguino Pen~a Tue, 19 Dec 2006 01:59:13 +0100 + debian-goodies (0.26) unstable; urgency=low * Remove extra space from which-pkg-broke which introduced a syntax diff --git a/debian/copyright b/debian/copyright index f115ccb..f360fec 100644 --- a/debian/copyright +++ b/debian/copyright @@ -21,9 +21,8 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -# 02111-1307, USA. -# +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA On Debian systems, a copy of the GNU General Public License may be found in /usr/share/common-licenses/GPL. diff --git a/debian/rules b/debian/rules index 89d38ec..e558ff5 100755 --- a/debian/rules +++ b/debian/rules @@ -63,7 +63,7 @@ # dh_installinit # dh_installcron dh_installman debget.1 debman.1 dglob.1 dgrep.1 dpigs.1 popbugs.1 \ - which-pkg-broke.1 network-test.1 + which-pkg-broke.1 network-test.1 checkrestart.1 # dh_installinfo # dh_undocumented dh_installchangelogs diff --git a/network-test b/network-test index b2c47a6..6165d12 100755 --- a/network-test +++ b/network-test @@ -1,6 +1,6 @@ #!/bin/bash -# Network testing script v 1.3 -# (c) 2005 Javier Fernandez-Sanguino +# Network testing script v 1.8 +# (c) 2005,2006 Javier Fernandez-Sanguino # # This script will test your system's network configuration using basic # tests and providing both information (INFO messages), warnings (WARN) @@ -89,14 +89,35 @@ # Check if all commands we need are available # NOTE: if using nslookup add "nslookup dnsutils" -( echo -e "netstat net-tools\nifconfig net-tools\nping netkit-ping\n\ -arp net-tools\nip iproute\nhost bind9-host\nmktemp debianutils\nnc netcat" | +( echo -e "netstat net-tools\nifconfig net-tools\n\ +ping netkit-ping|inetutils-ping|iputils-ping\n\ +arp net-tools\nip iproute\nhost host|bind9-host\nmktemp debianutils\n\ +nc netcat" | while read cmd package; do if ! `which $cmd 2>/dev/null >&2`; then echo "$cmd is not available! (please install $package)" >&2 exit 1 fi done ) || exit 1 +# Recommended programs +( echo -e "ethtool ethtool" | +while read cmd package; do +if ! `which $cmd 2>/dev/null >&2`; then + echo "$cmd is not available (consider installing $package)" >&2 + exit 1 +fi +done ) + +# Default route for programs +ETHTOOL=/usr/sbin/ethtool +MIITOOL=/sbin/mii-tool + +# Other needs +# We need /proc/net +if [ ! -d /proc/net ] ; then + echo "/proc is not available! Please mount it ('mount -t /proc')" >&2 + exit 1 +fi # Extract the interface of our default route @@ -139,11 +160,86 @@ return $status } +check_if_link_miitool () { + ifname=$1 + [ ! -x "$MIITOOL" ] && return 0 + status=0 + if $MIITOOL $ifname 2>&1| grep -q "no link"; then + status=1 + fi + return $status +} + +check_if_link_ethtool () { +# Check if the interface has link +# Note: Unlike other sections of the script we need to be root +# to test this + ifname=$1 + [ ! -x "$ETHTOOL" ] && return 0 + status=0 + LINK=`$ETHTOOL $ifname 2>&1| grep "Link detected"` + # If ethtool fails to print out the link line we break off + # notice that ethtool cannot get the link status out of all + # possible network interfaces + [ -z "$LINK" ] && return + if ! echo $LINK | grep -q "Link detected: yes" ; then + status=1 + fi + return $status +} + +check_if_link_iplink () { + ifname=$1 + status=0 + [ ! -x /sbin/ip ] && return 0 + if /sbin/ip link show $ifname 2>&1 | grep -q "NO-CARRIER"; then + status=1 + fi + return $status +} + + + +check_if_link() { + status=-1 + iface=$1 + # Use ethtool if installed (preferable to mii-tool) + # If none are installed we will test using 'ip link show' + if [ "`id -u`" -eq 0 ] ; then + if [ -x "$ETHTOOL" ] ; then + check_if_link_ethtool $iface + status=$? + elif [ -x "$MIITOOL" ]; then + check_if_link_miitool $iface + status=$? + fi + fi + # If no test has done use ip link + if [ $status -eq -1 ]; then + check_if_link_iplink $iface + status=$? + fi + return $status +} + # Check network interfaces check_if () { ifname=$1 status=0 [ -z "$ifname" ] && return 1 +# Check if the interface has a link + case "$ifname" in + eth*) check_if_link $ifname ; status=$?;; + *) ;; + esac +# Print results + if [ $status -ne 0 ] ; then + if [ "$ifname" = "$defaultif" ] ; then + echo "ERR: The $ifname interface that is associated with your default route has no link!" + else + echo "WARN: Interface $ifname does not have link" + fi + fi # Find IP addresses for $ifname inetaddr=`ip addr show $ifname | grep inet | awk '{print $2}'` if [ -z "$inetaddr" ] ; then @@ -152,7 +248,7 @@ else # TODO: WARN if more than 2 IP addresses? echo $inetaddr | while read ipaddr; do - echo "INFO: The $ifname interface has IP address $ipaddr assigned" + echo "INFO: The $ifname interface has IP address $ipaddr assigned" done fi @@ -173,7 +269,7 @@ elif [ "$rxpkts" -eq 0 ] ; then echo "WARN: The $ifname interface has not received any packets." else - echo "INFO: The $ifname interface has tx and rx packets." + echo "INFO: The $ifname interface has tx and rx packets." fi # TODO: It should be best if there was a comparison with tx/rx packets. # a few errors are not uncommon if the card has been running for a long @@ -193,13 +289,15 @@ ip link show | egrep '^[[:digit:]]' | while read ifnumber ifname status extra; do ifname=`echo $ifname |sed -e 's/:$//'` - if [ -z "`echo $status | grep UP\>`" ] && \ - [ -z "`echo $status | grep UP,`" ] ; then + # TODO: this is redundant with the check if_link test + # (although faster since using it would make us call 'ip' + # twice. + if [ -n "`echo $status | grep NO-CARRIER`" ] ; then if [ "$ifname" = "$defaultif" ] ; then echo "ERR: The $ifname interface that is associated with your default route is down!" status=1 elif [ "$ifname" = "lo" ] ; then - echo "ERR: Your lo inteface is down, this might cause issues with local applications (but not necessarily with network connectivity)" + echo "ERR: Your lo interface is down, this might cause issues with local applications (but not necessarily with network connectivity)" else echo "WARN: The $ifname interface is down" fi @@ -280,9 +378,7 @@ nsok=0 tempfile=`mktemp tmptestnet.XXXXXX` || { echo "ERR: Cannot create temporary file! Aborting! " >&2 ; exit 1; } trap " [ -f \"$tempfile\" ] && /bin/rm -f -- \"$tempfile\"" 0 1 2 3 13 15 -# TODO should warn about resolv.conf entries with more than one -# ip address in the nameserver line - cat /etc/resolv.conf |grep -v ^# | grep nameserver | + cat /etc/resolv.conf | grep -v ^# | grep nameserver | awk '/nameserver/ { for (i=2;i<=NF;i++) { print $i ; } }' >$tempfile for nameserver in `cat $tempfile`; do nsfound=$(( $nsfound + 1 )) @@ -387,3 +483,8 @@ exit 0 + +# Set our locale environment, just in case ethtool gets translated +LC_ALL=C +export LC_ALL +