*** empty log message ***
[coreutils.git] / src / pathchk.c
blobfa81b1b6875d258f2dfaf5ec14fbaf0ddc37603b
1 /* pathchk -- check whether pathnames are valid or portable
2 Copyright (C) 1991-2002 Free Software Foundation, Inc.
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.
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.
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. */
18 /* Usage: pathchk [-p] [--portability] path...
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
25 Exit status:
26 0 All PATH names passed all of the tests.
27 1 An error occurred.
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.
38 David MacKenzie <djm@gnu.ai.mit.edu>
39 and Jim Meyering <meyering@cs.utexas.edu> */
41 #include <config.h>
42 #include <stdio.h>
43 #include <getopt.h>
44 #include <sys/types.h>
46 #include <errno.h>
47 #ifndef errno
48 extern int errno;
49 #endif
51 #include "system.h"
52 #include "error.h"
53 #include "long-options.h"
54 #include "closeout.h"
56 /* The official name of this program (e.g., no `g' prefix). */
57 #define PROGRAM_NAME "pathchk"
59 #define AUTHORS N_ ("David MacKenzie and Jim Meyering")
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 */
69 #else
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 */
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 */
88 #endif
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
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
104 static int validate_path PARAMS ((char *path, int portability));
106 /* The name this program was run with. */
107 char *program_name;
109 static struct option const longopts[] =
111 {"portability", no_argument, NULL, 'p'},
112 {NULL, 0, NULL, 0}
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. */
119 static long int
120 pathconf_wrapper (const char *filename, int param)
122 long int ret;
124 errno = 0;
125 ret = pathconf (filename, param);
126 if (ret < 0 && errno == 0)
127 return LONG_MAX;
129 return ret;
132 void
133 usage (int status)
135 if (status != 0)
136 fprintf (stderr, _("Try `%s --help' for more information.\n"),
137 program_name);
138 else
140 printf (_("Usage: %s [OPTION]... NAME...\n"), program_name);
141 fputs (_("\
142 Diagnose unportable constructs in NAME.\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 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
150 exit (status);
154 main (int argc, char **argv)
156 int exit_status = 0;
157 int check_portability = 0;
158 int optc;
160 program_name = argv[0];
161 setlocale (LC_ALL, "");
162 bindtextdomain (PACKAGE, LOCALEDIR);
163 textdomain (PACKAGE);
165 atexit (close_stdout);
167 parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION,
168 AUTHORS, usage);
170 while ((optc = getopt_long (argc, argv, "p", longopts, NULL)) != -1)
172 switch (optc)
174 case 0:
175 break;
177 case 'p':
178 check_portability = 1;
179 break;
181 default:
182 usage (1);
186 if (optind == argc)
188 error (0, 0, _("too few arguments"));
189 usage (1);
192 for (; optind < argc; ++optind)
193 exit_status |= validate_path (argv[optind], check_portability);
195 exit (exit_status);
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] =
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
221 /* If PATH contains only portable characters, return 1, else 0. */
223 static int
224 portable_chars_only (const char *path)
226 const char *p;
228 for (p = path; *p; ++p)
229 if (portable_chars[(const unsigned char) *p] == 0)
231 error (0, 0, _("path `%s' contains nonportable character `%c'"),
232 path, *p);
233 return 0;
235 return 1;
238 /* Return 1 if PATH is a usable leading directory, 0 if not,
239 2 if it doesn't exist. */
241 static int
242 dir_ok (const char *path)
244 struct stat stats;
246 if (stat (path, &stats))
247 return 2;
249 if (!S_ISDIR (stats.st_mode))
251 error (0, 0, _("`%s' is not a directory"), path);
252 return 0;
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)
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;
268 return 1;
271 /* Make sure that
272 strlen (PATH) <= PATH_MAX
273 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
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, ., _, -.
280 Make sure that all leading directories along PATH that exist have
281 `x' permission.
283 Return 0 if all of these tests are successful, 1 if any fail. */
285 static int
286 validate_path (char *path, int portability)
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. */
294 if (portability && !portable_chars_only (path))
295 return 1;
297 if (*path == '\0')
298 return 0;
300 /* Figure out the parent of the first element in PATH. */
301 parent = xstrdup (*path == '/' ? "/" : ".");
303 slash = path;
304 last_elem = 0;
305 while (1)
307 long int name_max;
308 long int length; /* Length of partial path being checked. */
309 char *start; /* Start of path element being checked. */
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
321 last_elem = 1;
322 slash = strchr (start, '\0');
325 if (!last_elem)
327 exists = dir_ok (path);
328 if (exists == 0)
330 free (parent);
331 return 1;
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)
349 error (0, 0, _("name `%s' has length %ld; exceeds limit of %ld"),
350 start, length, name_max);
351 free (parent);
352 return 1;
355 if (last_elem)
356 break;
358 if (exists == 1)
360 free (parent);
361 parent = xstrdup (path);
364 *slash++ = '/';
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)
375 error (0, 0, _("path `%s' has length %d; exceeds limit of %ld"),
376 path, strlen (path), path_max);
377 return 1;
380 return 0;