2e36f8f6b69b3e3fd661a93cd5a9515deb2cfe32
[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         builtin_error ("path `%s' contains nonportable character `%c'", path, *p);
174         return 0;
175       }
176   return 1;
177 }
178
179 /* On some systems, stat can return EINTR.  */
180
181 #ifndef EINTR
182 # define SAFE_STAT(name, buf) stat (name, buf)
183 #else
184 # define SAFE_STAT(name, buf) safe_stat (name, buf)
185 static inline int
186 safe_stat (name, buf)
187      const char *name;
188      struct stat *buf;
189 {
190   int ret;
191
192   do
193     ret = stat (name, buf);
194   while (ret < 0 && errno == EINTR);
195
196   return ret;
197 }
198 #endif
199
200 /* Return 1 if PATH is a usable leading directory, 0 if not,
201    2 if it doesn't exist.  */
202
203 static int
204 dir_ok (path)
205      const char *path;
206 {
207   struct stat stats;
208
209   if (SAFE_STAT (path, &stats))
210     return 2;
211
212   if (!S_ISDIR (stats.st_mode))
213     {
214       builtin_error ("`%s' is not a directory", path);
215       return 0;
216     }
217
218   /* Use access to test for search permission because
219      testing permission bits of st_mode can lose with new
220      access control mechanisms.  Of course, access loses if you're
221      running setuid. */
222   if (access (path, X_OK) != 0)
223     {
224       if (errno == EACCES)
225         builtin_error ("directory `%s' is not searchable", path);
226       else
227         builtin_error ("%s: %s", path, strerror (errno));
228       return 0;
229     }
230
231   return 1;
232 }
233
234 static char *
235 xstrdup (s)
236      char *s;
237 {
238   return (savestring (s));
239 }
240
241 /* Make sure that
242    strlen (PATH) <= PATH_MAX
243    && strlen (each-existing-directory-in-PATH) <= NAME_MAX
244
245    If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
246    _POSIX_NAME_MAX instead, and make sure that PATH contains no
247    characters not in the POSIX portable filename character set, which
248    consists of A-Z, a-z, 0-9, ., _, -.
249
250    Make sure that all leading directories along PATH that exist have
251    `x' permission.
252
253    Return 0 if all of these tests are successful, 1 if any fail. */
254
255 static int
256 validate_path (path, portability)
257      char *path;
258      int portability;
259 {
260   int path_max;
261   int last_elem;                /* Nonzero if checking last element of path. */
262   int exists;                   /* 2 if the path element exists.  */
263   char *slash;
264   char *parent;                 /* Last existing leading directory so far.  */
265
266   if (portability && !portable_chars_only (path))
267     return 1;
268
269   if (*path == '\0')
270     return 0;
271
272 #ifdef lint
273   /* Suppress `used before initialized' warning.  */
274   exists = 0;
275 #endif
276
277   /* Figure out the parent of the first element in PATH.  */
278   parent = xstrdup (*path == '/' ? "/" : ".");
279
280   slash = path;
281   last_elem = 0;
282   while (1)
283     {
284       int name_max;
285       int length;               /* Length of partial path being checked. */
286       char *start;              /* Start of path element being checked. */
287
288       /* Find the end of this element of the path.
289          Then chop off the rest of the path after this element. */
290       while (*slash == '/')
291         slash++;
292       start = slash;
293       slash = strchr (slash, '/');
294       if (slash != NULL)
295         *slash = '\0';
296       else
297         {
298           last_elem = 1;
299           slash = strchr (start, '\0');
300         }
301
302       if (!last_elem)
303         {
304           exists = dir_ok (path);
305           if (dir_ok == 0)
306             {
307               free (parent);
308               return 1;
309             }
310         }
311
312       length = slash - start;
313       /* Since we know that `parent' is a directory, it's ok to call
314          pathconf with it as the argument.  (If `parent' isn't a directory
315          or doesn't exist, the behavior of pathconf is undefined.)
316          But if `parent' is a directory and is on a remote file system,
317          it's likely that pathconf can't give us a reasonable value
318          and will return -1.  (NFS and tempfs are not POSIX . . .)
319          In that case, we have no choice but to assume the pessimal
320          POSIX minimums.  */
321       name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
322       if (name_max < 0)
323         name_max = _POSIX_NAME_MAX;
324       if (length > name_max)
325         {
326           builtin_error ("name `%s' has length %d; exceeds limit of %d",
327                  start, length, name_max);
328           free (parent);
329           return 1;
330         }
331
332       if (last_elem)
333         break;
334
335       if (exists == 1)
336         {
337           free (parent);
338           parent = xstrdup (path);
339         }
340
341       *slash++ = '/';
342     }
343
344   /* `parent' is now the last existing leading directory in the whole path,
345      so it's ok to call pathconf with it as the argument.  */
346   path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
347   if (path_max < 0)
348     path_max = _POSIX_PATH_MAX;
349   free (parent);
350   if (strlen (path) > path_max)
351     {
352       builtin_error ("path `%s' has length %d; exceeds limit of %d",
353              path, strlen (path), path_max);
354       return 1;
355     }
356
357   return 0;
358 }