Don't include errno.h; system.h already does it.
[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 "system.h"
47 #include "error.h"
48 #include "long-options.h"
49 #include "closeout.h"
50
51 /* The official name of this program (e.g., no `g' prefix).  */
52 #define PROGRAM_NAME "pathchk"
53
54 #define AUTHORS N_ ("David MacKenzie and 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 int validate_path (char *path, int 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 != 0)
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   int exit_status = 0;
158   int check_portability = 0;
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                       AUTHORS, usage);
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 = 1;
181           break;
182
183         default:
184           usage (EXIT_FAILURE);
185         }
186     }
187
188   if (optind == argc)
189     {
190       error (0, 0, _("too few arguments"));
191       usage (EXIT_FAILURE);
192     }
193
194   for (; optind < argc; ++optind)
195     exit_status |= validate_path (argv[optind], check_portability);
196
197   exit (exit_status);
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 1, else 0.  */
224
225 static int
226 portable_chars_only (const char *path)
227 {
228   const char *p;
229
230   for (p = path; *p; ++p)
231     if (portable_chars[(unsigned char) *p] == 0)
232       {
233         error (0, 0, _("path `%s' contains nonportable character `%c'"),
234                path, *p);
235         return 0;
236       }
237   return 1;
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.  Of course, access loses if you're
260      running setuid. */
261   if (access (path, X_OK) != 0)
262     {
263       if (errno == EACCES)
264         error (0, 0, _("directory `%s' is not searchable"), path);
265       else
266         error (0, errno, "%s", path);
267       return 0;
268     }
269
270   return 1;
271 }
272
273 /* Make sure that
274    strlen (PATH) <= PATH_MAX
275    && strlen (each-existing-directory-in-PATH) <= NAME_MAX
276
277    If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
278    _POSIX_NAME_MAX instead, and make sure that PATH contains no
279    characters not in the POSIX portable filename character set, which
280    consists of A-Z, a-z, 0-9, ., _, -.
281
282    Make sure that all leading directories along PATH that exist have
283    `x' permission.
284
285    Return 0 if all of these tests are successful, 1 if any fail. */
286
287 static int
288 validate_path (char *path, int portability)
289 {
290   long int path_max;
291   int last_elem;                /* Nonzero if checking last element of path. */
292   int exists IF_LINT (= 0);     /* 2 if the path element exists.  */
293   char *slash;
294   char *parent;                 /* Last existing leading directory so far.  */
295
296   if (portability && !portable_chars_only (path))
297     return 1;
298
299   if (*path == '\0')
300     return 0;
301
302   /* Figure out the parent of the first element in PATH.  */
303   parent = xstrdup (*path == '/' ? "/" : ".");
304
305   slash = path;
306   last_elem = 0;
307   while (1)
308     {
309       long int name_max;
310       long int length;          /* Length of partial path being checked. */
311       char *start;              /* Start of path element being checked. */
312
313       /* Find the end of this element of the path.
314          Then chop off the rest of the path after this element. */
315       while (*slash == '/')
316         slash++;
317       start = slash;
318       slash = strchr (slash, '/');
319       if (slash != NULL)
320         *slash = '\0';
321       else
322         {
323           last_elem = 1;
324           slash = strchr (start, '\0');
325         }
326
327       if (!last_elem)
328         {
329           exists = dir_ok (path);
330           if (exists == 0)
331             {
332               free (parent);
333               return 1;
334             }
335         }
336
337       length = slash - start;
338       /* Since we know that `parent' is a directory, it's ok to call
339          pathconf with it as the argument.  (If `parent' isn't a directory
340          or doesn't exist, the behavior of pathconf is undefined.)
341          But if `parent' is a directory and is on a remote file system,
342          it's likely that pathconf can't give us a reasonable value
343          and will return -1.  (NFS and tempfs are not POSIX . . .)
344          In that case, we have no choice but to assume the pessimal
345          POSIX minimums.  */
346       name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
347       if (name_max < 0)
348         name_max = _POSIX_NAME_MAX;
349       if (length > name_max)
350         {
351           error (0, 0, _("name `%s' has length %ld; exceeds limit of %ld"),
352                  start, length, name_max);
353           free (parent);
354           return 1;
355         }
356
357       if (last_elem)
358         break;
359
360       if (exists == 1)
361         {
362           free (parent);
363           parent = xstrdup (path);
364         }
365
366       *slash++ = '/';
367     }
368
369   /* `parent' is now the last existing leading directory in the whole path,
370      so it's ok to call pathconf with it as the argument.  */
371   path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
372   if (path_max < 0)
373     path_max = _POSIX_PATH_MAX;
374   free (parent);
375   if (strlen (path) > (size_t) path_max)
376     {
377       error (0, 0, _("path `%s' has length %d; exceeds limit of %ld"),
378              path, strlen (path), path_max);
379       return 1;
380     }
381
382   return 0;
383 }