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