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