merge with 1.10t
[platform/upstream/coreutils.git] / src / pathchk.c
1 /* pathchk -- check whether pathnames are valid or portable
2    Copyright (C) 91, 92, 93, 1994 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
17
18 /* Usage: pathchk [-p] [--portability] path...
19
20    For each PATH, print a message if any of these conditions are false:
21    * all existing leading directories in PATH have search (execute) permission
22    * strlen (PATH) <= PATH_MAX
23    * strlen (each_directory_in_PATH) <= NAME_MAX
24
25    Exit status:
26    0                    All PATH names passed all of the tests.
27    1                    An error occurred.
28
29    Options:
30    -p, --portability    Instead of performing length checks on the
31                         underlying filesystem, test the length of the
32                         pathname and its components against the POSIX.1
33                         minimum limits for portability, _POSIX_NAME_MAX
34                         and _POSIX_PATH_MAX in 2.9.2.  Also check that
35                         the pathname contains no character not in the
36                         portable filename character set.
37
38    David MacKenzie <djm@gnu.ai.mit.edu>
39    and Jim Meyering <meyering@cs.utexas.edu> */
40
41 #include <config.h>
42 #include <stdio.h>
43 #include <getopt.h>
44 #include <sys/types.h>
45
46 #include "version.h"
47 #include "system.h"
48 #include "safe-stat.h"
49
50 #ifdef _POSIX_VERSION
51 #include <limits.h>
52 #ifndef PATH_MAX
53 #define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
54 #endif /* not PATH_MAX */
55 #ifndef NAME_MAX
56 #define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX);
57 #endif /* not NAME_MAX */
58
59 #else /* not _POSIX_VERSION */
60
61 #include <sys/param.h>
62 #ifndef PATH_MAX
63 #ifdef MAXPATHLEN
64 #define PATH_MAX MAXPATHLEN
65 #else /* not MAXPATHLEN */
66 #define PATH_MAX _POSIX_PATH_MAX
67 #endif /* not MAXPATHLEN */
68 #endif /* not PATH_MAX */
69
70 #ifndef NAME_MAX
71 #ifdef MAXNAMLEN
72 #define NAME_MAX MAXNAMLEN
73 #else /* not MAXNAMLEN */
74 #define NAME_MAX _POSIX_NAME_MAX
75 #endif /* not MAXNAMLEN */
76 #endif /* not NAME_MAX */
77
78 #endif /* not _POSIX_VERSION */
79
80 #ifndef _POSIX_PATH_MAX
81 #define _POSIX_PATH_MAX 255
82 #endif
83 #ifndef _POSIX_NAME_MAX
84 #define _POSIX_NAME_MAX 14
85 #endif
86
87 #ifndef PATH_MAX_FOR
88 #define PATH_MAX_FOR(p) PATH_MAX
89 #endif
90 #ifndef NAME_MAX_FOR
91 #define NAME_MAX_FOR(p) NAME_MAX
92 #endif
93
94 char *xstrdup ();
95 void error ();
96
97 static int validate_path ();
98 static void usage ();
99
100 /* The name this program was run with. */
101 char *program_name;
102
103 /* If non-zero, display usage information and exit.  */
104 static int show_help;
105
106 /* If non-zero, print the version on standard output and exit.  */
107 static int show_version;
108
109 static struct option const longopts[] =
110 {
111   {"help", no_argument, &show_help, 1},
112   {"portability", no_argument, NULL, 'p'},
113   {"version", no_argument, &show_version, 1},
114   {NULL, 0, NULL, 0}
115 };
116
117 void
118 main (argc, argv)
119      int argc;
120      char **argv;
121 {
122   int exit_status = 0;
123   int check_portability = 0;
124   int optc;
125
126   program_name = argv[0];
127
128   while ((optc = getopt_long (argc, argv, "p", longopts, (int *) 0)) != EOF)
129     {
130       switch (optc)
131         {
132         case 0:
133           break;
134
135         case 'p':
136           check_portability = 1;
137           break;
138
139         default:
140           usage (1);
141         }
142     }
143
144   if (show_version)
145     {
146       printf ("pathchk - %s\n", version_string);
147       exit (0);
148     }
149
150   if (show_help)
151     usage (0);
152
153   if (optind == argc)
154     {
155       error (0, 0, "too few arguments");
156       usage (1);
157     }
158
159   for (; optind < argc; ++optind)
160     exit_status |= validate_path (argv[optind], check_portability);
161
162   exit (exit_status);
163 }
164
165 /* Each element is nonzero if the corresponding ASCII character is
166    in the POSIX portable character set, and zero if it is not.
167    In addition, the entry for `/' is nonzero to simplify checking. */
168 static char const portable_chars[256] =
169 {
170   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
171   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
172   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
173   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
174   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
175   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
176   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
177   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
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   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
182   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
183   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
184   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
185   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
186 };
187
188 /* If PATH contains only portable characters, return 1, else 0.  */
189
190 static int
191 portable_chars_only (path)
192      const char *path;
193 {
194   const char *p;
195
196   for (p = path; *p; ++p)
197     if (portable_chars[(const unsigned char) *p] == 0)
198       {
199         error (0, 0, "path `%s' contains nonportable character `%c'",
200                path, *p);
201         return 0;
202       }
203   return 1;
204 }
205
206 /* Return 1 if PATH is a usable leading directory, 0 if not,
207    2 if it doesn't exist.  */
208
209 static int
210 dir_ok (path)
211      const char *path;
212 {
213   struct stat stats;
214
215   if (SAFE_STAT (path, &stats))
216     return 2;
217
218   if (!S_ISDIR (stats.st_mode))
219     {
220       error (0, 0, "`%s' is not a directory", path);
221       return 0;
222     }
223
224   /* Use access to test for search permission because
225      testing permission bits of st_mode can lose with new
226      access control mechanisms.  Of course, access loses if you're
227      running setuid. */
228   if (access (path, X_OK) != 0)
229     {
230       if (errno == EACCES)
231         error (0, 0, "directory `%s' is not searchable", path);
232       else
233         error (0, errno, "%s", path);
234       return 0;
235     }
236
237   return 1;
238 }
239
240 /* Make sure that
241    strlen (PATH) <= PATH_MAX
242    && strlen (each-existing-directory-in-PATH) <= NAME_MAX
243
244    If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
245    _POSIX_NAME_MAX instead, and make sure that PATH contains no
246    characters not in the POSIX portable filename character set, which
247    consists of A-Z, a-z, 0-9, ., _, -.
248
249    Make sure that all leading directories along PATH that exist have
250    `x' permission.
251
252    Return 0 if all of these tests are successful, 1 if any fail. */
253
254 static int
255 validate_path (path, portability)
256      char *path;
257      int portability;
258 {
259   int path_max;
260   int last_elem;                /* Nonzero if checking last element of path. */
261   int exists;                   /* 2 if the path element exists.  */
262   char *slash;
263   char *parent;                 /* Last existing leading directory so far.  */
264
265   if (portability && !portable_chars_only (path))
266     return 1;
267
268   if (*path == '\0')
269     return 0;
270
271 #ifdef lint
272   /* Suppress `used before initialized' warning.  */
273   exists = 0;
274 #endif
275
276   /* Figure out the parent of the first element in PATH.  */
277   parent = xstrdup (*path == '/' ? "/" : ".");
278
279   slash = path;
280   last_elem = 0;
281   while (1)
282     {
283       int name_max;
284       int length;               /* Length of partial path being checked. */
285       char *start;              /* Start of path element being checked. */
286
287       /* Find the end of this element of the path.
288          Then chop off the rest of the path after this element. */
289       while (*slash == '/')
290         slash++;
291       start = slash;
292       slash = index (slash, '/');
293       if (slash != NULL)
294         *slash = '\0';
295       else
296         {
297           last_elem = 1;
298           slash = index (start, '\0');
299         }
300
301       if (!last_elem)
302         {
303           exists = dir_ok (path);
304           if (dir_ok == 0)
305             {
306               free (parent);
307               return 1;
308             }
309         }
310
311       length = slash - start;
312       /* Since we know that `parent' is a directory, it's ok to call
313          pathconf with it as the argument.  (If `parent' isn't a directory
314          or doesn't exist, the behavior of pathconf is undefined.)
315          But if `parent' is a directory and is on a remote file system,
316          it's likely that pathconf can't give us a reasonable value
317          and will return -1.  (NFS and tempfs are not POSIX . . .)
318          In that case, we have no choice but to assume the pessimal
319          POSIX minimums.  */
320       name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
321       if (name_max < 0)
322         name_max = _POSIX_NAME_MAX;
323       if (length > name_max)
324         {
325           error (0, 0, "name `%s' has length %d; exceeds limit of %d",
326                  start, length, name_max);
327           free (parent);
328           return 1;
329         }
330
331       if (last_elem)
332         break;
333
334       if (exists == 1)
335         {
336           free (parent);
337           parent = xstrdup (path);
338         }
339
340       *slash++ = '/';
341     }
342
343   /* `parent' is now the last existing leading directory in the whole path,
344      so it's ok to call pathconf with it as the argument.  */
345   path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
346   if (path_max < 0)
347     path_max = _POSIX_PATH_MAX;
348   free (parent);
349   if (strlen (path) > path_max)
350     {
351       error (0, 0, "path `%s' has length %d; exceeds limit of %d",
352              path, strlen (path), path_max);
353       return 1;
354     }
355
356   return 0;
357 }
358
359 static void
360 usage (status)
361      int status;
362 {
363   if (status != 0)
364     fprintf (stderr, "Try `%s --help' for more information.\n",
365              program_name);
366   else
367     {
368       printf ("Usage: %s [OPTION]... PATH...\n", program_name);
369       printf ("\
370 \n\
371   -p, --portability   check for all POSIX systems, not only this one\n\
372       --help          display this help and exit\n\
373       --version       output version information and exit\n\
374 ");
375     }
376   exit (status);
377 }