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