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