(longopts): Add --help, --version.
[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 file system, 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 "euidaccess.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 bool validate_path (char *path, bool 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   {GETOPT_HELP_OPTION_DECL},
111   {GETOPT_VERSION_OPTION_DECL},
112   {NULL, 0, NULL, 0}
113 };
114
115 #if NEED_PATHCONF_WRAPPER
116 /* Distinguish between the cases when pathconf fails and when it reports there
117    is no limit (the latter is the case for PATH_MAX on the Hurd).  When there
118    is no limit, return LONG_MAX.  Otherwise, return pathconf's return value.  */
119
120 static long int
121 pathconf_wrapper (const char *filename, int param)
122 {
123   long int ret;
124
125   errno = 0;
126   ret = pathconf (filename, param);
127   if (ret < 0 && errno == 0)
128     return LONG_MAX;
129
130   return ret;
131 }
132 #endif
133
134 void
135 usage (int status)
136 {
137   if (status != EXIT_SUCCESS)
138     fprintf (stderr, _("Try `%s --help' for more information.\n"),
139              program_name);
140   else
141     {
142       printf (_("Usage: %s [OPTION]... NAME...\n"), program_name);
143       fputs (_("\
144 Diagnose unportable constructs in NAME.\n\
145 \n\
146   -p, --portability   check for all POSIX systems, not only this one\n\
147 "), stdout);
148       fputs (HELP_OPTION_DESCRIPTION, stdout);
149       fputs (VERSION_OPTION_DESCRIPTION, stdout);
150       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
151     }
152   exit (status);
153 }
154
155 int
156 main (int argc, char **argv)
157 {
158   bool ok = true;
159   bool check_portability = false;
160   int optc;
161
162   initialize_main (&argc, &argv);
163   program_name = argv[0];
164   setlocale (LC_ALL, "");
165   bindtextdomain (PACKAGE, LOCALEDIR);
166   textdomain (PACKAGE);
167
168   atexit (close_stdout);
169
170   while ((optc = getopt_long (argc, argv, "+p", longopts, NULL)) != -1)
171     {
172       switch (optc)
173         {
174         case 'p':
175           check_portability = true;
176           break;
177
178         case_GETOPT_HELP_CHAR;
179
180         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
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     ok &= validate_path (argv[optind], check_portability);
195
196   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
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 true, else false.  */
223
224 static bool
225 portable_chars_only (const char *path)
226 {
227   const char *p;
228
229   for (p = path; *p; ++p)
230     if (portable_chars[to_uchar (*p)] == 0)
231       {
232         error (0, 0, _("path `%s' contains nonportable character `%c'"),
233                path, *p);
234         return false;
235       }
236   return true;
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.  */
259   if (euidaccess (path, X_OK) != 0)
260     {
261       if (errno == EACCES)
262         error (0, 0, _("directory `%s' is not searchable"), path);
263       else
264         error (0, errno, "%s", path);
265       return 0;
266     }
267
268   return 1;
269 }
270
271 /* Make sure that
272    strlen (PATH) <= PATH_MAX
273    && strlen (each-existing-directory-in-PATH) <= NAME_MAX
274
275    If PORTABILITY is true, compare against _POSIX_PATH_MAX and
276    _POSIX_NAME_MAX instead, and make sure that PATH contains no
277    characters not in the POSIX portable filename character set, which
278    consists of A-Z, a-z, 0-9, ., _, -.
279
280    Make sure that all leading directories along PATH that exist have
281    `x' permission.
282
283    Return true if all of these tests are successful, false if any fail.  */
284
285 static bool
286 validate_path (char *path, bool portability)
287 {
288   long int path_max;
289   int last_elem;                /* Nonzero if checking last element of path. */
290   int exists IF_LINT (= 0);     /* 2 if the path element exists.  */
291   char *slash;
292   char *parent;                 /* Last existing leading directory so far.  */
293
294   if (portability && !portable_chars_only (path))
295     return false;
296
297   if (*path == '\0')
298     return true;
299
300   /* Figure out the parent of the first element in PATH.  */
301   parent = xstrdup (*path == '/' ? "/" : ".");
302
303   slash = path;
304   last_elem = 0;
305   while (1)
306     {
307       long int name_max;
308       long int length;          /* Length of partial path being checked. */
309       char *start;              /* Start of path element being checked. */
310
311       /* Find the end of this element of the path.
312          Then chop off the rest of the path after this element. */
313       while (*slash == '/')
314         slash++;
315       start = slash;
316       slash = strchr (slash, '/');
317       if (slash != NULL)
318         *slash = '\0';
319       else
320         {
321           last_elem = 1;
322           slash = strchr (start, '\0');
323         }
324
325       if (!last_elem)
326         {
327           exists = dir_ok (path);
328           if (exists == 0)
329             {
330               free (parent);
331               return false;
332             }
333         }
334
335       length = slash - start;
336       /* Since we know that `parent' is a directory, it's ok to call
337          pathconf with it as the argument.  (If `parent' isn't a directory
338          or doesn't exist, the behavior of pathconf is undefined.)
339          But if `parent' is a directory and is on a remote file system,
340          it's likely that pathconf can't give us a reasonable value
341          and will return -1.  (NFS and tempfs are not POSIX . . .)
342          In that case, we have no choice but to assume the pessimal
343          POSIX minimums.  */
344       name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
345       if (name_max < 0)
346         name_max = _POSIX_NAME_MAX;
347       if (length > name_max)
348         {
349           error (0, 0, _("name `%s' has length %ld; exceeds limit of %ld"),
350                  start, length, name_max);
351           free (parent);
352           return false;
353         }
354
355       if (last_elem)
356         break;
357
358       if (exists == 1)
359         {
360           free (parent);
361           parent = xstrdup (path);
362         }
363
364       *slash++ = '/';
365     }
366
367   /* `parent' is now the last existing leading directory in the whole path,
368      so it's ok to call pathconf with it as the argument.  */
369   path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
370   if (path_max < 0)
371     path_max = _POSIX_PATH_MAX;
372   free (parent);
373   if (strlen (path) > (size_t) path_max)
374     {
375       error (0, 0, _("path `%s' has length %lu; exceeds limit of %ld"),
376              path, (unsigned long int) strlen (path), path_max);
377       return false;
378     }
379
380   return true;
381 }