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 |