1: #!/bin/ksh 2: # 3: # Check elements of PATH for accessibilty, and for possibly conflicting program names. 4: # 5: # $Header: /afs/northstar/ufac/richard/bin/RCS/checkpath,v 1.5 2009/02/07 08:41:15 richard Exp $ 6: # 7: # This script checks all the directories in $PATH for accessibility, then it checks specific 8: # named arguments for possible conflicts between different directories in the $PATH. 9: # If no arguments are named, all programs in the $PATH are examined for conflicts. 10: # 11: # Directories which are equivalent (symlinks) are removed from the list before the 12: # file analysis is made, to cut down on spurious conflicts. Apparent conflicts which are symlinks 13: # to the same file are also not reported. Most systems seem to have many of these. 14: # 15: # This cannot get all possible conflicts for all shells and all situations. 16: # Specifically, it does not address shell aliases, built-ins or global shell functions, 17: # all of which are shell-dependant. 18: # Nor can it address temporary conflicts caused by a startup script which augments $PATH 19: # and then spawns child processes which perform a $PATH search. 20: # 21: # If "." is in the path, and the current directory also happens to be in the path, spurious 22: # conflicts are not reported because of the path trimming performed on equivalent directories. 23: # 24: # Warning: A path element containing "~" is not valid in sh/ksh, but is valid in bash and some csh/tcsh. 25: # Normally the "~" is expanded when the path is set. We test (with "-d") for the presence of a directory 26: # and this fails because "~" is not expanded inside the test operation. In bash, the test fails, 27: # the "~" still works as a path element. It is most reliable to use $HOME explicitly, not "~", in $PATH 28: # 29: # Options: 30: # -v verbosity 31: # 0 - terse output if conflicts are found. 32: # 1 - medium output if conflicts are found (default) 33: # 2 - long output. List all potential conflicts, even if multiple pathnames resolve to the same file. 34: # Follow symlinks to their final destinations. Run 'file' on each conflicting pathname. 35: # 3 - Additional debugging information 36: # -d directory check only - don't analyse any filenames 37: # -p path check only, no directory report. 38: # 39: # Exit status: 40: # 0 = all directories in PATH are accessible. 41: # >0 = a count of the inaccessible directories. 42: # (the exit status does not reflect whether pathname conflicts were discovered) 43: # 44: # 2004/11/03 Richard Brittain, Dartmouth College Computing Services. 45: 46: # Collect options and set defaults 47: 48: verbosity=1 49: dircheckonly=0 50: pathcheckonly=0 51: while getopts pdv: o ; do 52: case $o in 53: p) pathcheckonly=1;; 54: d) dircheckonly=1;; 55: v) verbosity=$OPTARG;; 56: esac 57: done 58: shift $OPTIND-1 59: 60: # Functions for use later - control starts near the end of the script 61: 62: wordsplit() 63: { 64: # Take a string in $1, a set of delimiters in $2, and print the token 65: # indexed by $3 66: # Set noglob in the calling routine to avoid expanding wildcards in the result 67: typeset arg=$3 68: IFS=$2 ; set -- $1 69: eval print -R \${$arg} 70: } 71: 72: 73: verboseprint() 74: { 75: # Print erguments if $verbosity (global) is set high enough 76: threshold=$1; shift 77: if [[ $verbosity -ge $threshold ]]; then 78: for arg in "$@"; do 79: print "$arg" 80: done 81: fi 82: } 83: 84: 85: follow_links() 86: { 87: ( 88: # Run in a subshell since we need to change directories 89: # Follow the symlinks in $1 and return the final location. 90: val=$1 91: line=$(ls -ld $val) 92: link=$(wordsplit "$line" " " 11) 93: # $link now contains something if there is a link 94: while [ -n "$link" ]; do 95: # $val is the full pathname of the file we searched on 96: # $link is the linked-to name (path may be relative or absolute) 97: # $line is the output of ls -ld $val 98: # change directories to the location of $val, and try again 99: # Note that dirname is not the same as ${val%/*} if there is only one / 100: cd $(dirname $val) 101: # if $link is a relative pathname, stick it onto current directory 102: # otherwise, use it as an absolute name 103: case $link in 104: /*) val=$link ;; 105: *) case $PWD in 106: /) val=/$link ;; 107: *) val=$PWD/$link ;; 108: esac 109: ;; 110: esac 111: line=$(ls -ld $val) 112: # now see if we have another link 113: link=$(wordsplit "$line" " " 11) 114: done 115: # no [more] links - just return the final pathname 116: print $val 117: ) 118: } 119: 120: check_equivs() 121: { 122: # Check the path $1 for equivalence with each of the remaining arguments. 123: # Echo all that match 124: p=$1; shift 125: for d in "$@" ; do 126: [ $p -ef $d ] && print -R "$d" 127: done 128: } 129: 130: 131: elim_equivs() 132: { 133: # Check the path $1 for equivalence with each of the remaining arguments 134: # Echo the ones that do NOT match (i.e., are unique) 135: # If less than two arguments, echo the argument and return 136: if [ $# -lt 2 ]; then 137: print -R "$1" 138: else 139: p=$1; shift 140: for d in "$@"; do 141: [ ! $p -ef $d ] && print -R "$d" 142: done 143: fi 144: } 145: 146: listdirs() 147: { 148: # list all the files in each directory argument. No directory parts or headers 149: for dir in "$@"; do 150: # arguments should be clean, but double check 151: [ -d "$dir" ] && ls -1 $dir 152: done 153: } 154: 155: check_path() 156: { 157: # For each directory in a list passed as $1 ($PATH format), make sure the 158: # directory exists, and is read/execute 159: # If a directory is a symlink, check whether the linked-to directory is also in the path. 160: 161: # $pathels is created as a space-separated list of path elements, with duplicates 162: # (e.g. symlinks) and inaccessible elements removed. This is returned to the caller 163: # as a global variable. 164: pathels= 165: 166: # Uses global $verbosity to control messages to stdout. 167: # For each path element in turn, output is 168: # "seq#: directory [errors or warnings]" 169: 170: verboseprint 1 "Path directories, in search order\n" 171: status=0 172: seq=0 173: 174: path=$1 175: OIFS=$IFS; IFS=:; set $path; IFS=$OIFS 176: for dir in "$@"; do 177: seq=$((seq + 1)) 178: verboseprint 1 "$seq: $dir\c" 179: if [[ -L $dir ]]; then 180: linked_dir=$(follow_links $dir) 181: verboseprint 1 " \tWARNING: $dir symlinks to $linked_dir\c" 182: fi 183: if [[ ! -d $dir ]]; then 184: verboseprint 1 " \tERROR: Missing directory\c" 185: status=$((status + 1)) 186: elif [[ ! ( -x $dir && -r $dir ) ]]; then 187: # Note - directories owned by the current user always seem to pass this test 188: # regardless of permissions 189: verboseprint 1 " \tERROR: Inaccessible directory: check permissions\c" 190: status=$((status + 1)) 191: else 192: # No access problems, but check for duplicates (symlinks or real duplicates) 193: # and do not add those to $pathels or we'll get bogus conflicts. 194: equivdir=$(check_equivs "$dir" $pathels) 195: if [ "$equivdir" ] ; then 196: verboseprint 1 " (equivalent to $equivdir, already in PATH)\c" 197: else 198: pathels="$pathels $dir" 199: fi 200: fi 201: # Generate a newline - any mesages above are on the same line. 202: verboseprint 1 "" 203: # Debugging - show the directory details, but suppress errors from missing directories etc. 204: [ $verbosity -gt 1 ] && ls -ld $dir 2>/dev/null 205: done 206: # output spacing only. 207: verboseprint 1 "" 208: # Return an exit status indicating bad path elements. 209: return $status 210: } 211: 212: 213: searchpath() 214: { 215: # Look for the program given as $1 in each of the directories given in the remaining arguments. 216: # Print warning messages to stdout for each real conflict. Ignore apparent conflicts which 217: # actually resolve to the same file. 218: # Return an exit status which is the number of real conflicts located. 219: [ $verbosity -ge 4 ] && set -x 220: prog=$1; shift 221: confpaths= 222: nconf=0 223: for dir in "$@"; do 224: if [[ -f $dir/$prog && -x $dir/$prog ]]; then 225: confpaths="$confpaths $dir/$prog" 226: nconf=$((nconf + 1)) 227: fi 228: done 229: # We have a list of $nconf items, but some may be equivalent to others. We need to 230: # eliminate the duplicates and return the number of real conflicts. The list can be 231: # empty or have just one item, in which case we have no conflicts, but may want to 232: # present the file details anyway 233: 234: if [ $nconf -eq 0 ]; then 235: # Could get here if the user specified a program name which doesn't exist 236: # OR, files appear in the path but are not executable. 237: verboseprint 1 "$prog not found in \$PATH" 238: elif [ $nconf -eq 1 ]; then 239: # Found the program, but only once - don't report anything. 240: return 0 241: else 242: # We have two or more potential pathnames in conflict 243: # Detect linked files. Do not count paths which resolve to the same file 244: # Reset the arguments to the function, for easier parsing 245: rconf=0 246: rconfpaths= 247: set -- $confpaths 248: p1=$1; shift 249: remainder=$(elim_equivs "$p1" "$@") 250: while [ -n "$remainder" ]; do 251: rconfpaths="$rconfpaths $p1" 252: rconf=$((rconf + 1)) 253: p1=$1 254: [ $# -gt 0 ] && shift 255: remainder=$(elim_equivs "$p1" "$@") 256: done 257: # $rconf now contains a count of the non-equivalent pathnames, which may be 0 (no real conflicts) 258: if [ $rconf -eq 0 ] ; then 259: # No real conflicts, but print the info anyway if we are being verbose 260: verboseprint 2 "$prog has 0 conflicts" 261: if [ $verbosity -ge 2 ]; then 262: set -- $confpaths 263: for path in "$@"; do 264: print "0: \c" ; ls -l $path 265: if [[ -L $path ]] ; then 266: print " -> $(follow_links $path)" 267: fi 268: done 269: print 270: for path in "$@"; do 271: if [[ -L $path ]]; then 272: print -R "-> $(file $(follow_links $path))" 273: else 274: print -R " $(file $path)" 275: fi 276: done 277: print 278: fi 279: return 0 280: else 281: # We have 2 or more real conflicts - list them, with 'ls -l' and 'file' output 282: verboseprint 0 "$prog has $rconf conflicts" 283: if [ $verbosity -ge 1 ]; then 284: # At this point, $rconfpaths has the conflicting pathnames in order, so we should 285: # be able to do "ls -l $rconfpaths". However, 'ls' sometimes generates output not in the 286: # same order as the arguments, so step through explicitly and add a counter. 287: set -- $rconfpaths 288: i=0 289: for path in "$@"; do 290: print "$i: \c" ; ls -l $path 291: if [[ $verbosity -ge 2 && -L $path ]] ; then 292: print " -> $(follow_links $path)" 293: fi 294: i=$((i+1)) 295: done 296: print 297: # repeat for the 'file' information 298: for path in "$@"; do 299: if [[ $verbosity -ge 2 && -L $path ]] ; then 300: print -R "-> $(file $(follow_links $path))" 301: else 302: print -R " $(file $path)" 303: fi 304: done 305: print 306: fi 307: fi 308: return $rconf 309: fi 310: } 311: 312: 313: # Control starts here. 314: 315: # First check once that all the directories in the $PATH exist and are read/execute. 316: # This is always performed, to validate the path elements for later, 317: # but the report to stdout may be suppressed 318: if [ $pathcheckonly -eq 1 ]; then 319: check_path $PATH >/dev/null 320: estat=$? 321: else 322: check_path $PATH; estat=$? 323: estat=$? 324: fi 325: 326: verboseprint 3 "Modified path elements:" "$pathels" 327: 328: # Next check all the named arguments, or default to all the executables in the path. 329: 330: dupcount=0 331: if [ $dircheckonly -ne 1 ]; then 332: if [ $# -gt 0 ]; then 333: # Examine specific programs named as arguments 334: 335: for pathname in "$@"; do 336: prog=${pathname##*/} 337: searchpath $prog $pathels 338: [ $? -gt 0 ] && dupcount=$((dupcount + 1)) 339: done 340: else 341: # No arguments given - analyse all the directories on the path. 342: # The pattern to grep has a space and a tab. 343: totcount=$(listdirs $pathels | wc -l) 344: listdirs $pathels | sort | uniq -c | grep -v '^ *1[ ]' | while read ndups conflict ; do 345: # searchpath function will check $conflict against $pathels and print messages to stdout. 346: # The exit status is the number of real conflicts found (not counting symlinks to the same file) 347: # By using $pathels we already eliminate most of the spurious conflicts caused by the same 348: # directory appearing the in the path multiple times. 349: searchpath $conflict $pathels 350: [ $? -gt 0 ] && dupcount=$((dupcount + 1)) 351: done 352: verboseprint 1 "Total files examined: $totcount" 353: fi 354: verboseprint 1 "Total conflicting names: $dupcount" 355: fi 356: exit $estat
last modified 22/03/2012 | Introduction | Table of Contents (frame/no frame) |
Printable (single file) |
© Dartmouth College |