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