(validate-path, main, portable_chars_only): Use bool for booleans.
[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 #include "long-options.h"
50
51 /* The official name of this program (e.g., no `g' prefix).  */
52 #define PROGRAM_NAME "pathchk"
53
54 #define AUTHORS "David MacKenzie", "Jim Meyering"
55
56 #define NEED_PATHCONF_WRAPPER 0
57 #if HAVE_PATHCONF
58 # ifndef PATH_MAX
59 #  define PATH_MAX_FOR(p) pathconf_wrapper ((p), _PC_PATH_MAX)
60 #  define NEED_PATHCONF_WRAPPER 1
61 # endif /* not PATH_MAX */
62 # ifndef NAME_MAX
63 #  define NAME_MAX_FOR(p) pathconf_wrapper ((p), _PC_NAME_MAX);
64 #  undef NEED_PATHCONF_WRAPPER
65 #  define NEED_PATHCONF_WRAPPER 1
66 # endif /* not NAME_MAX */
67
68 #else
69
70 # include <sys/param.h>
71 # ifndef PATH_MAX
72 #  ifdef MAXPATHLEN
73 #   define PATH_MAX MAXPATHLEN
74 #  else /* not MAXPATHLEN */
75 #   define PATH_MAX _POSIX_PATH_MAX
76 #  endif /* not MAXPATHLEN */
77 # endif /* not PATH_MAX */
78
79 # ifndef NAME_MAX
80 #  ifdef MAXNAMLEN
81 #   define NAME_MAX MAXNAMLEN
82 #  else /* not MAXNAMLEN */
83 #   define NAME_MAX _POSIX_NAME_MAX
84 #  endif /* not MAXNAMLEN */
85 # endif /* not NAME_MAX */
86
87 #endif
88
89 #ifndef _POSIX_PATH_MAX
90 # define _POSIX_PATH_MAX 255
91 #endif
92 #ifndef _POSIX_NAME_MAX
93 # define _POSIX_NAME_MAX 14
94 #endif
95
96 #ifndef PATH_MAX_FOR
97 # define PATH_MAX_FOR(p) PATH_MAX
98 #endif
99 #ifndef NAME_MAX_FOR
100 # define NAME_MAX_FOR(p) NAME_MAX
101 #endif
102
103 static bool validate_path (char *path, bool portability);
104
105 /* The name this program was run with. */
106 char *program_name;
107
108 static struct option const longopts[] =
109 {
110   {"portability", no_argument, NULL, 'p'},
111   {NULL, 0, NULL, 0}
112 };
113
114 #if NEED_PATHCONF_WRAPPER
115 /* Distinguish between the cases when pathconf fails and when it reports there
116    is no limit (the latter is the case for PATH_MAX on the Hurd).  When there
117    is no limit, return LONG_MAX.  Otherwise, return pathconf's return value.  */
118
119 static long int
120 pathconf_wrapper (const char *filename, int param)
121 {
122   long int ret;
123
124   errno = 0;
125   ret = pathconf (filename, param);
126   if (ret < 0 && errno == 0)
127     return LONG_MAX;
128
129   return ret;
130 }
131 #endif
132
133 void
134 usage (int status)
135 {
136   if (status != EXIT_SUCCESS)
137     fprintf (stderr, _("Try `%s --help' for more information.\n"),
138              program_name);
139   else
140     {
141       printf (_("Usage: %s [OPTION]... NAME...\n"), program_name);
142       fputs (_("\
143 Diagnose unportable constructs in NAME.\n\
144 \n\
145   -p, --portability   check for all POSIX systems, not only this one\n\
146 "), stdout);
147       fputs (HELP_OPTION_DESCRIPTION, stdout);
148       fputs (VERSION_OPTION_DESCRIPTION, stdout);
149       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
150     }
151   exit (status);
152 }
153
154 int
155 main (int argc, char **argv)
156 {
157   bool ok = true;
158   bool check_portability = false;
159   int optc;
160
161   initialize_main (&argc, &argv);
162   program_name = argv[0];
163   setlocale (LC_ALL, "");
164   bindtextdomain (PACKAGE, LOCALEDIR);
165   textdomain (PACKAGE);
166
167   atexit (close_stdout);
168
169   parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION,
170                       usage, AUTHORS, (char const *) NULL);
171
172   while ((optc = getopt_long (argc, argv, "p", longopts, NULL)) != -1)
173     {
174       switch (optc)
175         {
176         case 0:
177           break;
178
179         case 'p':
180           check_portability = true;
181           break;
182
183         default:
184           usage (EXIT_FAILURE);
185         }
186     }
187
188   if (optind == argc)
189     {
190       error (0, 0, _("missing operand"));
191       usage (EXIT_FAILURE);
192     }
193
194   for (; optind < argc; ++optind)
195     ok &= validate_path (argv[optind], check_portability);
196
197   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
198 }
199
200 /* Each element is nonzero if the corresponding ASCII character is
201    in the POSIX portable character set, and zero if it is not.
202    In addition, the entry for `/' is nonzero to simplify checking. */
203 static char const portable_chars[256] =
204 {
205   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
206   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
207   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
208   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
209   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
210   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
211   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
212   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
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   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
221 };
222
223 /* If PATH contains only portable characters, return true, else false.  */
224
225 static bool
226 portable_chars_only (const char *path)
227 {
228   const char *p;
229
230   for (p = path; *p; ++p)
231     if (portable_chars[to_uchar (*p)] == 0)
232       {
233         error (0, 0, _("path `%s' contains nonportable character `%c'"),
234                path, *p);
235         return false;
236       }
237   return true;
238 }
239
240 /* Return 1 if PATH is a usable leading directory, 0 if not,
241    2 if it doesn't exist.  */
242
243 static int
244 dir_ok (const char *path)
245 {
246   struct stat stats;
247
248   if (stat (path, &stats))
249     return 2;
250
251   if (!S_ISDIR (stats.st_mode))
252     {
253       error (0, 0, _("`%s' is not a directory"), path);
254       return 0;
255     }
256
257   /* Use access to test for search permission because
258      testing permission bits of st_mode can lose with new
259      access control mechanisms.  */
260   if (euidaccess (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 true, 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 true if all of these tests are successful, false if any fail.  */
285
286 static bool
287 validate_path (char *path, bool 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 false;
297
298   if (*path == '\0')
299     return true;
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 false;
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 false;
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 int) strlen (path), path_max);
378       return false;
379     }
380
381   return true;
382 }