(usage): Include one- or two-line synopsis in --help output.
[platform/upstream/coreutils.git] / src / pathchk.c
1 /* pathchk -- check whether pathnames are valid or portable
2    Copyright (C) 91, 92, 93, 94, 1995 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 "error.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
96 static int validate_path ();
97 static void usage ();
98
99 /* The name this program was run with. */
100 char *program_name;
101
102 /* If non-zero, display usage information and exit.  */
103 static int show_help;
104
105 /* If non-zero, print the version on standard output and exit.  */
106 static int show_version;
107
108 static struct option const longopts[] =
109 {
110   {"help", no_argument, &show_help, 1},
111   {"portability", no_argument, NULL, 'p'},
112   {"version", no_argument, &show_version, 1},
113   {NULL, 0, NULL, 0}
114 };
115
116 void
117 main (argc, argv)
118      int argc;
119      char **argv;
120 {
121   int exit_status = 0;
122   int check_portability = 0;
123   int optc;
124
125   program_name = argv[0];
126
127   while ((optc = getopt_long (argc, argv, "p", longopts, (int *) 0)) != EOF)
128     {
129       switch (optc)
130         {
131         case 0:
132           break;
133
134         case 'p':
135           check_portability = 1;
136           break;
137
138         default:
139           usage (1);
140         }
141     }
142
143   if (show_version)
144     {
145       printf ("pathchk - %s\n", version_string);
146       exit (0);
147     }
148
149   if (show_help)
150     usage (0);
151
152   if (optind == argc)
153     {
154       error (0, 0, "too few arguments");
155       usage (1);
156     }
157
158   for (; optind < argc; ++optind)
159     exit_status |= validate_path (argv[optind], check_portability);
160
161   exit (exit_status);
162 }
163
164 /* Each element is nonzero if the corresponding ASCII character is
165    in the POSIX portable character set, and zero if it is not.
166    In addition, the entry for `/' is nonzero to simplify checking. */
167 static char const portable_chars[256] =
168 {
169   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
170   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
171   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
172   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
173   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
174   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
175   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
176   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
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   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 };
186
187 /* If PATH contains only portable characters, return 1, else 0.  */
188
189 static int
190 portable_chars_only (path)
191      const char *path;
192 {
193   const char *p;
194
195   for (p = path; *p; ++p)
196     if (portable_chars[(const unsigned char) *p] == 0)
197       {
198         error (0, 0, "path `%s' contains nonportable character `%c'",
199                path, *p);
200         return 0;
201       }
202   return 1;
203 }
204
205 /* Return 1 if PATH is a usable leading directory, 0 if not,
206    2 if it doesn't exist.  */
207
208 static int
209 dir_ok (path)
210      const char *path;
211 {
212   struct stat stats;
213
214   if (stat (path, &stats))
215     return 2;
216
217   if (!S_ISDIR (stats.st_mode))
218     {
219       error (0, 0, "`%s' is not a directory", path);
220       return 0;
221     }
222
223   /* Use access to test for search permission because
224      testing permission bits of st_mode can lose with new
225      access control mechanisms.  Of course, access loses if you're
226      running setuid. */
227   if (access (path, X_OK) != 0)
228     {
229       if (errno == EACCES)
230         error (0, 0, "directory `%s' is not searchable", path);
231       else
232         error (0, errno, "%s", path);
233       return 0;
234     }
235
236   return 1;
237 }
238
239 /* Make sure that
240    strlen (PATH) <= PATH_MAX
241    && strlen (each-existing-directory-in-PATH) <= NAME_MAX
242
243    If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
244    _POSIX_NAME_MAX instead, and make sure that PATH contains no
245    characters not in the POSIX portable filename character set, which
246    consists of A-Z, a-z, 0-9, ., _, -.
247
248    Make sure that all leading directories along PATH that exist have
249    `x' permission.
250
251    Return 0 if all of these tests are successful, 1 if any fail. */
252
253 static int
254 validate_path (path, portability)
255      char *path;
256      int portability;
257 {
258   int path_max;
259   int last_elem;                /* Nonzero if checking last element of path. */
260   int exists;                   /* 2 if the path element exists.  */
261   char *slash;
262   char *parent;                 /* Last existing leading directory so far.  */
263
264   if (portability && !portable_chars_only (path))
265     return 1;
266
267   if (*path == '\0')
268     return 0;
269
270 #ifdef lint
271   /* Suppress `used before initialized' warning.  */
272   exists = 0;
273 #endif
274
275   /* Figure out the parent of the first element in PATH.  */
276   parent = xstrdup (*path == '/' ? "/" : ".");
277
278   slash = path;
279   last_elem = 0;
280   while (1)
281     {
282       int name_max;
283       int length;               /* Length of partial path being checked. */
284       char *start;              /* Start of path element being checked. */
285
286       /* Find the end of this element of the path.
287          Then chop off the rest of the path after this element. */
288       while (*slash == '/')
289         slash++;
290       start = slash;
291       slash = strchr (slash, '/');
292       if (slash != NULL)
293         *slash = '\0';
294       else
295         {
296           last_elem = 1;
297           slash = strchr (start, '\0');
298         }
299
300       if (!last_elem)
301         {
302           exists = dir_ok (path);
303           if (dir_ok == 0)
304             {
305               free (parent);
306               return 1;
307             }
308         }
309
310       length = slash - start;
311       /* Since we know that `parent' is a directory, it's ok to call
312          pathconf with it as the argument.  (If `parent' isn't a directory
313          or doesn't exist, the behavior of pathconf is undefined.)
314          But if `parent' is a directory and is on a remote file system,
315          it's likely that pathconf can't give us a reasonable value
316          and will return -1.  (NFS and tempfs are not POSIX . . .)
317          In that case, we have no choice but to assume the pessimal
318          POSIX minimums.  */
319       name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
320       if (name_max < 0)
321         name_max = _POSIX_NAME_MAX;
322       if (length > name_max)
323         {
324           error (0, 0, "name `%s' has length %d; exceeds limit of %d",
325                  start, length, name_max);
326           free (parent);
327           return 1;
328         }
329
330       if (last_elem)
331         break;
332
333       if (exists == 1)
334         {
335           free (parent);
336           parent = xstrdup (path);
337         }
338
339       *slash++ = '/';
340     }
341
342   /* `parent' is now the last existing leading directory in the whole path,
343      so it's ok to call pathconf with it as the argument.  */
344   path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
345   if (path_max < 0)
346     path_max = _POSIX_PATH_MAX;
347   free (parent);
348   if (strlen (path) > path_max)
349     {
350       error (0, 0, "path `%s' has length %d; exceeds limit of %d",
351              path, strlen (path), path_max);
352       return 1;
353     }
354
355   return 0;
356 }
357
358 static void
359 usage (status)
360      int status;
361 {
362   if (status != 0)
363     fprintf (stderr, "Try `%s --help' for more information.\n",
364              program_name);
365   else
366     {
367       printf ("Usage: %s [OPTION]... PATH...\n", program_name);
368       printf ("\
369 \n\
370   -p, --portability   check for all POSIX systems, not only this one\n\
371       --help          display this help and exit\n\
372       --version       output version information and exit\n\
373 ");
374     }
375   exit (status);
376 }