*** empty log message ***
[coreutils.git] / src / pathchk.c
blob024240c36bddbd4530cf2c06a126ea7524bc458f
1 /* pathchk -- check whether pathnames are valid or portable
2 Copyright (C) 1991-2004 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 "system.h"
47 #include "error.h"
48 #include "long-options.h"
50 /* The official name of this program (e.g., no `g' prefix). */
51 #define PROGRAM_NAME "pathchk"
53 #define AUTHORS "David MacKenzie", "Jim Meyering"
55 #define NEED_PATHCONF_WRAPPER 0
56 #if HAVE_PATHCONF
57 # ifndef PATH_MAX
58 # define PATH_MAX_FOR(p) pathconf_wrapper ((p), _PC_PATH_MAX)
59 # define NEED_PATHCONF_WRAPPER 1
60 # endif /* not PATH_MAX */
61 # ifndef NAME_MAX
62 # define NAME_MAX_FOR(p) pathconf_wrapper ((p), _PC_NAME_MAX);
63 # undef NEED_PATHCONF_WRAPPER
64 # define NEED_PATHCONF_WRAPPER 1
65 # endif /* not NAME_MAX */
67 #else
69 # include <sys/param.h>
70 # ifndef PATH_MAX
71 # ifdef MAXPATHLEN
72 # define PATH_MAX MAXPATHLEN
73 # else /* not MAXPATHLEN */
74 # define PATH_MAX _POSIX_PATH_MAX
75 # endif /* not MAXPATHLEN */
76 # endif /* not PATH_MAX */
78 # ifndef NAME_MAX
79 # ifdef MAXNAMLEN
80 # define NAME_MAX MAXNAMLEN
81 # else /* not MAXNAMLEN */
82 # define NAME_MAX _POSIX_NAME_MAX
83 # endif /* not MAXNAMLEN */
84 # endif /* not NAME_MAX */
86 #endif
88 #ifndef _POSIX_PATH_MAX
89 # define _POSIX_PATH_MAX 255
90 #endif
91 #ifndef _POSIX_NAME_MAX
92 # define _POSIX_NAME_MAX 14
93 #endif
95 #ifndef PATH_MAX_FOR
96 # define PATH_MAX_FOR(p) PATH_MAX
97 #endif
98 #ifndef NAME_MAX_FOR
99 # define NAME_MAX_FOR(p) NAME_MAX
100 #endif
102 static int validate_path (char *path, int portability);
104 /* The name this program was run with. */
105 char *program_name;
107 static struct option const longopts[] =
109 {"portability", no_argument, NULL, 'p'},
110 {NULL, 0, NULL, 0}
113 #if NEED_PATHCONF_WRAPPER
114 /* Distinguish between the cases when pathconf fails and when it reports there
115 is no limit (the latter is the case for PATH_MAX on the Hurd). When there
116 is no limit, return LONG_MAX. Otherwise, return pathconf's return value. */
118 static long int
119 pathconf_wrapper (const char *filename, int param)
121 long int ret;
123 errno = 0;
124 ret = pathconf (filename, param);
125 if (ret < 0 && errno == 0)
126 return LONG_MAX;
128 return ret;
130 #endif
132 void
133 usage (int status)
135 if (status != EXIT_SUCCESS)
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 initialize_main (&argc, &argv);
161 program_name = argv[0];
162 setlocale (LC_ALL, "");
163 bindtextdomain (PACKAGE, LOCALEDIR);
164 textdomain (PACKAGE);
166 atexit (close_stdout);
168 parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION,
169 usage, AUTHORS, (char const *) NULL);
171 while ((optc = getopt_long (argc, argv, "p", longopts, NULL)) != -1)
173 switch (optc)
175 case 0:
176 break;
178 case 'p':
179 check_portability = 1;
180 break;
182 default:
183 usage (EXIT_FAILURE);
187 if (optind == argc)
189 error (0, 0, _("too few arguments"));
190 usage (EXIT_FAILURE);
193 for (; optind < argc; ++optind)
194 exit_status |= validate_path (argv[optind], check_portability);
196 exit (exit_status);
199 /* Each element is nonzero if the corresponding ASCII character is
200 in the POSIX portable character set, and zero if it is not.
201 In addition, the entry for `/' is nonzero to simplify checking. */
202 static char const portable_chars[256] =
204 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
205 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
206 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
207 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
208 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
209 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
210 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
211 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
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 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
222 /* If PATH contains only portable characters, return 1, else 0. */
224 static int
225 portable_chars_only (const char *path)
227 const char *p;
229 for (p = path; *p; ++p)
230 if (portable_chars[(unsigned char) *p] == 0)
232 error (0, 0, _("path `%s' contains nonportable character `%c'"),
233 path, *p);
234 return 0;
236 return 1;
239 /* Return 1 if PATH is a usable leading directory, 0 if not,
240 2 if it doesn't exist. */
242 static int
243 dir_ok (const char *path)
245 struct stat stats;
247 if (stat (path, &stats))
248 return 2;
250 if (!S_ISDIR (stats.st_mode))
252 error (0, 0, _("`%s' is not a directory"), path);
253 return 0;
256 /* Use access to test for search permission because
257 testing permission bits of st_mode can lose with new
258 access control mechanisms. Of course, access loses if you're
259 running setuid. */
260 if (access (path, X_OK) != 0)
262 if (errno == EACCES)
263 error (0, 0, _("directory `%s' is not searchable"), path);
264 else
265 error (0, errno, "%s", path);
266 return 0;
269 return 1;
272 /* Make sure that
273 strlen (PATH) <= PATH_MAX
274 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
276 If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
277 _POSIX_NAME_MAX instead, and make sure that PATH contains no
278 characters not in the POSIX portable filename character set, which
279 consists of A-Z, a-z, 0-9, ., _, -.
281 Make sure that all leading directories along PATH that exist have
282 `x' permission.
284 Return 0 if all of these tests are successful, 1 if any fail. */
286 static int
287 validate_path (char *path, int portability)
289 long int path_max;
290 int last_elem; /* Nonzero if checking last element of path. */
291 int exists IF_LINT (= 0); /* 2 if the path element exists. */
292 char *slash;
293 char *parent; /* Last existing leading directory so far. */
295 if (portability && !portable_chars_only (path))
296 return 1;
298 if (*path == '\0')
299 return 0;
301 /* Figure out the parent of the first element in PATH. */
302 parent = xstrdup (*path == '/' ? "/" : ".");
304 slash = path;
305 last_elem = 0;
306 while (1)
308 long int name_max;
309 long int length; /* Length of partial path being checked. */
310 char *start; /* Start of path element being checked. */
312 /* Find the end of this element of the path.
313 Then chop off the rest of the path after this element. */
314 while (*slash == '/')
315 slash++;
316 start = slash;
317 slash = strchr (slash, '/');
318 if (slash != NULL)
319 *slash = '\0';
320 else
322 last_elem = 1;
323 slash = strchr (start, '\0');
326 if (!last_elem)
328 exists = dir_ok (path);
329 if (exists == 0)
331 free (parent);
332 return 1;
336 length = slash - start;
337 /* Since we know that `parent' is a directory, it's ok to call
338 pathconf with it as the argument. (If `parent' isn't a directory
339 or doesn't exist, the behavior of pathconf is undefined.)
340 But if `parent' is a directory and is on a remote file system,
341 it's likely that pathconf can't give us a reasonable value
342 and will return -1. (NFS and tempfs are not POSIX . . .)
343 In that case, we have no choice but to assume the pessimal
344 POSIX minimums. */
345 name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
346 if (name_max < 0)
347 name_max = _POSIX_NAME_MAX;
348 if (length > name_max)
350 error (0, 0, _("name `%s' has length %ld; exceeds limit of %ld"),
351 start, length, name_max);
352 free (parent);
353 return 1;
356 if (last_elem)
357 break;
359 if (exists == 1)
361 free (parent);
362 parent = xstrdup (path);
365 *slash++ = '/';
368 /* `parent' is now the last existing leading directory in the whole path,
369 so it's ok to call pathconf with it as the argument. */
370 path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
371 if (path_max < 0)
372 path_max = _POSIX_PATH_MAX;
373 free (parent);
374 if (strlen (path) > (size_t) path_max)
376 error (0, 0, _("path `%s' has length %lu; exceeds limit of %ld"),
377 path, (unsigned long) strlen (path), path_max);
378 return 1;
381 return 0;