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