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