Example script: chk_path_conflicts.ksh


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



  last modified 09/02/2006 Introduction Table of Contents
(frame/no frame)
Printable
(single file)
© Dartmouth College