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