Imported from ../bash-2.0.tar.gz.
[platform/upstream/bash.git] / examples / loadables / pathchk.c
1 /* pathchk - check pathnames for validity and portability */
2
3 /* Usage: pathchk [-p] path ...
4
5    For each PATH, print a message if any of these conditions are false:
6    * all existing leading directories in PATH have search (execute) permission
7    * strlen (PATH) <= PATH_MAX
8    * strlen (each_directory_in_PATH) <= NAME_MAX
9
10    Exit status:
11    0                    All PATH names passed all of the tests.
12    1                    An error occurred.
13
14    Options:
15    -p                   Instead of performing length checks on the
16                         underlying filesystem, test the length of the
17                         pathname and its components against the POSIX.1
18                         minimum limits for portability, _POSIX_NAME_MAX
19                         and _POSIX_PATH_MAX in 2.9.2.  Also check that
20                         the pathname contains no character not in the
21                         portable filename character set. */
22
23 /* See Makefile for compilation details. */
24
25 #include <config.h>
26
27 #include <sys/types.h>
28 #include "posixstat.h"
29
30 #if defined (HAVE_UNISTD_H)
31 #  include <unistd.h>
32 #endif
33
34 #if defined (HAVE_LIMITS_H)
35 #  include <limits.h>
36 #endif
37
38 #include "bashansi.h"
39
40 #include <stdio.h>
41 #include <errno.h>
42
43 #include "builtins.h"
44 #include "shell.h"
45 #include "stdc.h"
46 #include "bashgetopt.h"
47 #include "maxpath.h"
48
49 #if !defined (errno)
50 extern int errno;
51 #endif
52
53 #if !defined (_POSIX_PATH_MAX)
54 #  define _POSIX_PATH_MAX 255
55 #endif
56 #if !defined (_POSIX_NAME_MAX)
57 #  define _POSIX_NAME_MAX 14
58 #endif
59
60 /* How do we get PATH_MAX? */
61 #if defined (_POSIX_VERSION) && !defined (PATH_MAX)
62 #  define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
63 #endif
64
65 /* How do we get NAME_MAX? */
66 #if defined (_POSIX_VERSION) && !defined (NAME_MAX)
67 #  define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX)
68 #endif
69
70 #if !defined (PATH_MAX_FOR)
71 #  define PATH_MAX_FOR(p)       PATH_MAX
72 #endif
73
74 #if !defined (NAME_MAX_FOR)
75 #  define NAME_MAX_FOR(p)       NAME_MAX
76 #endif
77
78 extern char *strerror ();
79
80 static int validate_path ();
81
82 pathchk_builtin (list)
83      WORD_LIST *list;
84 {
85   int retval, pflag, opt;
86
87   reset_internal_getopt ();
88   while ((opt = internal_getopt (list, "p")) != -1)
89     {
90       switch (opt)
91         {
92         case 'p':
93           pflag = 1;
94           break;
95         default:
96           builtin_usage ();
97           return (EX_USAGE);
98         }
99     }
100   list = loptend;
101
102   if (list == 0)
103     {
104       builtin_usage ();
105       return (EX_USAGE);
106     }
107
108   for (retval = 0; list; list = list->next)
109     retval |= validate_path (list->word->word, pflag);
110
111   return (retval ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
112 }
113
114 char *pathchk_doc[] = {
115         "Check each pathname argument for validity (i.e., it may be used to",
116         "create or access a file without casuing syntax errors) and portability",
117         "(i.e., no filename truncation will result).  If the `-p' option is",
118         "supplied, more extensive portability checks are performed.",
119         (char *)NULL
120 };
121
122 /* The standard structure describing a builtin command.  bash keeps an array
123    of these structures. */
124 struct builtin pathchk_struct = {
125         "pathchk",              /* builtin name */
126         pathchk_builtin,        /* function implementing the builtin */
127         BUILTIN_ENABLED,        /* initial flags for builtin */
128         pathchk_doc,            /* array of long documentation strings. */
129         "pathchk [-p] pathname ...",    /* usage synopsis */
130         0                       /* reserved for internal use */
131 };
132
133 /* The remainder of this file is stolen shamelessly from `pathchk.c' in
134    the sh-utils-1.12 distribution, by 
135
136    David MacKenzie <djm@gnu.ai.mit.edu>
137    and Jim Meyering <meyering@cs.utexas.edu> */
138
139 /* Each element is nonzero if the corresponding ASCII character is
140    in the POSIX portable character set, and zero if it is not.
141    In addition, the entry for `/' is nonzero to simplify checking. */
142 static char const portable_chars[256] =
143 {
144   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
145   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
146   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
147   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
148   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
149   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
150   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
151   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
152   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
153   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
154   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
155   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
156   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
157   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
158   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
159   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
160 };
161
162 /* If PATH contains only portable characters, return 1, else 0.  */
163
164 static int
165 portable_chars_only (path)
166      const char *path;
167 {
168   const char *p;
169
170   for (p = path; *p; ++p)
171     if (portable_chars[(const unsigned char) *p] == 0)
172       {
173         error (0, 0, "path `%s' contains nonportable character `%c'",
174                path, *p);
175         return 0;
176       }
177   return 1;
178 }
179
180 /* On some systems, stat can return EINTR.  */
181
182 #ifndef EINTR
183 # define SAFE_STAT(name, buf) stat (name, buf)
184 #else
185 # define SAFE_STAT(name, buf) safe_stat (name, buf)
186 static inline int
187 safe_stat (name, buf)
188      const char *name;
189      struct stat *buf;
190 {
191   int ret;
192
193   do
194     ret = stat (name, buf);
195   while (ret < 0 && errno == EINTR);
196
197   return ret;
198 }
199 #endif
200
201 /* Return 1 if PATH is a usable leading directory, 0 if not,
202    2 if it doesn't exist.  */
203
204 static int
205 dir_ok (path)
206      const char *path;
207 {
208   struct stat stats;
209
210   if (SAFE_STAT (path, &stats))
211     return 2;
212
213   if (!S_ISDIR (stats.st_mode))
214     {
215       error (0, 0, "`%s' is not a directory", path);
216       return 0;
217     }
218
219   /* Use access to test for search permission because
220      testing permission bits of st_mode can lose with new
221      access control mechanisms.  Of course, access loses if you're
222      running setuid. */
223   if (access (path, X_OK) != 0)
224     {
225       if (errno == EACCES)
226         builtin_error ("directory `%s' is not searchable", path);
227       else
228         builtin_error ("%s: %s", path, strerror (errno));
229       return 0;
230     }
231
232   return 1;
233 }
234
235 static char *
236 xstrdup (s)
237      char *s;
238 {
239   return (savestring (s));
240 }
241
242 /* Make sure that
243    strlen (PATH) <= PATH_MAX
244    && strlen (each-existing-directory-in-PATH) <= NAME_MAX
245
246    If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
247    _POSIX_NAME_MAX instead, and make sure that PATH contains no
248    characters not in the POSIX portable filename character set, which
249    consists of A-Z, a-z, 0-9, ., _, -.
250
251    Make sure that all leading directories along PATH that exist have
252    `x' permission.
253
254    Return 0 if all of these tests are successful, 1 if any fail. */
255
256 static int
257 validate_path (path, portability)
258      char *path;
259      int portability;
260 {
261   int path_max;
262   int last_elem;                /* Nonzero if checking last element of path. */
263   int exists;                   /* 2 if the path element exists.  */
264   char *slash;
265   char *parent;                 /* Last existing leading directory so far.  */
266
267   if (portability && !portable_chars_only (path))
268     return 1;
269
270   if (*path == '\0')
271     return 0;
272
273 #ifdef lint
274   /* Suppress `used before initialized' warning.  */
275   exists = 0;
276 #endif
277
278   /* Figure out the parent of the first element in PATH.  */
279   parent = xstrdup (*path == '/' ? "/" : ".");
280
281   slash = path;
282   last_elem = 0;
283   while (1)
284     {
285       int name_max;
286       int length;               /* Length of partial path being checked. */
287       char *start;              /* Start of path element being checked. */
288
289       /* Find the end of this element of the path.
290          Then chop off the rest of the path after this element. */
291       while (*slash == '/')
292         slash++;
293       start = slash;
294       slash = strchr (slash, '/');
295       if (slash != NULL)
296         *slash = '\0';
297       else
298         {
299           last_elem = 1;
300           slash = strchr (start, '\0');
301         }
302
303       if (!last_elem)
304         {
305           exists = dir_ok (path);
306           if (dir_ok == 0)
307             {
308               free (parent);
309               return 1;
310             }
311         }
312
313       length = slash - start;
314       /* Since we know that `parent' is a directory, it's ok to call
315          pathconf with it as the argument.  (If `parent' isn't a directory
316          or doesn't exist, the behavior of pathconf is undefined.)
317          But if `parent' is a directory and is on a remote file system,
318          it's likely that pathconf can't give us a reasonable value
319          and will return -1.  (NFS and tempfs are not POSIX . . .)
320          In that case, we have no choice but to assume the pessimal
321          POSIX minimums.  */
322       name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
323       if (name_max < 0)
324         name_max = _POSIX_NAME_MAX;
325       if (length > name_max)
326         {
327           error (0, 0, "name `%s' has length %d; exceeds limit of %d",
328                  start, length, name_max);
329           free (parent);
330           return 1;
331         }
332
333       if (last_elem)
334         break;
335
336       if (exists == 1)
337         {
338           free (parent);
339           parent = xstrdup (path);
340         }
341
342       *slash++ = '/';
343     }
344
345   /* `parent' is now the last existing leading directory in the whole path,
346      so it's ok to call pathconf with it as the argument.  */
347   path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
348   if (path_max < 0)
349     path_max = _POSIX_PATH_MAX;
350   free (parent);
351   if (strlen (path) > path_max)
352     {
353       error (0, 0, "path `%s' has length %d; exceeds limit of %d",
354              path, strlen (path), path_max);
355       return 1;
356     }
357
358   return 0;
359 }