merge with 1.8.1i
[platform/upstream/coreutils.git] / src / pathchk.c
1 /* pathchk -- check whether pathnames are valid or portable
2    Copyright (C) 1991, 1992 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 #ifdef HAVE_CONFIG_H
42 #if defined (CONFIG_BROKETS)
43 /* We use <config.h> instead of "config.h" so that a compilation
44    using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
45    (which it would do because it found this file in $srcdir).  */
46 #include <config.h>
47 #else
48 #include "config.h"
49 #endif
50 #endif
51
52 #include <stdio.h>
53 #include <getopt.h>
54 #include <sys/types.h>
55
56 #include "version.h"
57 #include "system.h"
58
59 #ifdef _POSIX_VERSION
60 #include <limits.h>
61 #ifndef PATH_MAX
62 #define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
63 #endif /* not PATH_MAX */
64 #ifndef NAME_MAX
65 #define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX);
66 #endif /* not NAME_MAX */
67
68 #else /* not _POSIX_VERSION */
69
70 #include <sys/param.h>
71 #ifndef PATH_MAX
72 #ifdef MAXPATHLEN
73 #define PATH_MAX MAXPATHLEN
74 #else /* not MAXPATHLEN */
75 #define PATH_MAX _POSIX_PATH_MAX
76 #endif /* not MAXPATHLEN */
77 #endif /* not PATH_MAX */
78
79 #ifndef NAME_MAX
80 #ifdef MAXNAMLEN
81 #define NAME_MAX MAXNAMLEN
82 #else /* not MAXNAMLEN */
83 #define NAME_MAX _POSIX_NAME_MAX
84 #endif /* not MAXNAMLEN */
85 #endif /* not NAME_MAX */
86
87 #endif /* not _POSIX_VERSION */
88
89 #ifndef _POSIX_PATH_MAX
90 #define _POSIX_PATH_MAX 255
91 #endif
92 #ifndef _POSIX_NAME_MAX
93 #define _POSIX_NAME_MAX 14
94 #endif
95
96 #ifndef PATH_MAX_FOR
97 #define PATH_MAX_FOR(p) PATH_MAX
98 #endif
99 #ifndef NAME_MAX_FOR
100 #define NAME_MAX_FOR(p) NAME_MAX
101 #endif
102
103 char *xstrdup ();
104 void error ();
105
106 static int validate_path ();
107 static void usage ();
108
109 /* The name this program was run with. */
110 char *program_name;
111
112 /* If non-zero, display usage information and exit.  */
113 static int show_help;
114
115 /* If non-zero, print the version on standard output and exit.  */
116 static int show_version;
117
118 static struct option const longopts[] =
119 {
120   {"help", no_argument, &show_help, 1},
121   {"portability", no_argument, NULL, 'p'},
122   {"version", no_argument, &show_version, 1},
123   {NULL, 0, NULL, 0}
124 };
125
126 void
127 main (argc, argv)
128      int argc;
129      char **argv;
130 {
131   int exit_status = 0;
132   int check_portability = 0;
133   int optc;
134
135   program_name = argv[0];
136
137   while ((optc = getopt_long (argc, argv, "p", longopts, (int *) 0)) != EOF)
138     {
139       switch (optc)
140         {
141         case 0:
142           break;
143
144         case 'p':
145           check_portability = 1;
146           break;
147
148         default:
149           usage (1);
150         }
151     }
152
153   if (show_version)
154     {
155       printf ("%s\n", version_string);
156       exit (0);
157     }
158
159   if (show_help)
160     usage (0);
161
162   if (optind == argc)
163     usage (1);
164
165   for (; optind < argc; ++optind)
166     exit_status |= validate_path (argv[optind], check_portability);
167
168   exit (exit_status);
169 }
170
171 /* Each element is nonzero if the corresponding ASCII character is
172    in the POSIX portable character set, and zero if it is not.
173    In addition, the entry for `/' is nonzero to simplify checking. */
174 static char const portable_chars[256] =
175 {
176   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
177   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
178   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
179   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
180   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
181   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
182   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
183   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
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   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
187   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
188   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
189   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
190   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
191   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
192 };
193
194 /* If PATH contains only portable characters, return 1, else 0.  */
195
196 static int
197 portable_chars_only (path)
198      const char *path;
199 {
200   const char *p;
201
202   for (p = path; *p; ++p)
203     if (portable_chars[(const unsigned char) *p] == 0)
204       {
205         error (0, 0, "path `%s' contains nonportable character `%c'",
206                path, *p);
207         return 0;
208       }
209   return 1;
210 }
211
212 /* Return 1 if PATH is a usable leading directory, 0 if not,
213    2 if it doesn't exist.  */
214
215 static int
216 dir_ok (path)
217      const char *path;
218 {
219   struct stat stats;
220
221   if (stat (path, &stats))
222     return 2;
223
224   if (!S_ISDIR (stats.st_mode))
225     {
226       error (0, 0, "`%s' is not a directory", path);
227       return 0;
228     }
229
230   /* Use access to test for search permission because
231      testing permission bits of st_mode can lose with new
232      access control mechanisms.  Of course, access loses if you're
233      running setuid. */
234   if (access (path, X_OK) != 0)
235     {
236       if (errno == EACCES)
237         error (0, 0, "directory `%s' is not searchable", path);
238       else
239         error (0, errno, "%s", path);
240       return 0;
241     }
242
243   return 1;
244 }
245
246 /* Make sure that
247    strlen (PATH) <= PATH_MAX
248    && strlen (each-existing-directory-in-PATH) <= NAME_MAX
249
250    If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
251    _POSIX_NAME_MAX instead, and make sure that PATH contains no
252    characters not in the POSIX portable filename character set, which
253    consists of A-Z, a-z, 0-9, ., _, -.
254
255    Make sure that all leading directories along PATH that exist have
256    `x' permission.
257
258    Return 0 if all of these tests are successful, 1 if any fail. */
259
260 static int
261 validate_path (path, portability)
262      char *path;
263      int portability;
264 {
265   int path_max;
266   int last_elem;                /* Nonzero if checking last element of path. */
267   int exists;                   /* 2 if the path element exists.  */
268   char *slash;
269   char *parent;                 /* Last existing leading directory so far.  */
270
271   if (portability && !portable_chars_only (path))
272     return 1;
273
274   if (*path == '\0')
275     return 0;
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 = index (slash, '/');
294       if (slash != NULL)
295         *slash = '\0';
296       else
297         {
298           last_elem = 1;
299           slash = index (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           error (0, 0, "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       error (0, 0, "path `%s' has length %d; exceeds limit of %d",
353              path, strlen (path), path_max);
354       return 1;
355     }
356
357   return 0;
358 }
359
360 static void
361 usage (status)
362      int status;
363 {
364   if (status != 0)
365     fprintf (stderr, "Try `%s --help' for more information.\n",
366              program_name);
367   else
368     {
369       printf ("Usage: %s [OPTION]... PATH...\n", program_name);
370       printf ("\
371 \n\
372   -p, --portability   check for all POSIX systems, not only this one\n\
373       --help          display this help and exit\n\
374       --version       output version information and exit\n\
375 ");
376     }
377   exit (status);
378 }