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 |