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