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