1 /* pathchk -- check whether pathnames are valid or portable
2 Copyright (C) 1991-2000 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)
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
26 0 All PATH names passed all of the tests.
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.
38 David MacKenzie <djm@gnu.ai.mit.edu>
39 and Jim Meyering <meyering@cs.utexas.edu> */
44 #include <sys/types.h>
48 #include "long-options.h"
51 /* The official name of this program (e.g., no `g' prefix). */
52 #define PROGRAM_NAME "pathchk"
54 #define AUTHORS "David MacKenzie and Jim Meyering"
58 # define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
59 # endif /* not PATH_MAX */
61 # define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX);
62 # endif /* not NAME_MAX */
64 #else /* not _POSIX_VERSION */
66 # include <sys/param.h>
69 # define PATH_MAX MAXPATHLEN
70 # else /* not MAXPATHLEN */
71 # define PATH_MAX _POSIX_PATH_MAX
72 # endif /* not MAXPATHLEN */
73 # endif /* not PATH_MAX */
77 # define NAME_MAX MAXNAMLEN
78 # else /* not MAXNAMLEN */
79 # define NAME_MAX _POSIX_NAME_MAX
80 # endif /* not MAXNAMLEN */
81 # endif /* not NAME_MAX */
83 #endif /* not _POSIX_VERSION */
85 #ifndef _POSIX_PATH_MAX
86 # define _POSIX_PATH_MAX 255
88 #ifndef _POSIX_NAME_MAX
89 # define _POSIX_NAME_MAX 14
93 # define PATH_MAX_FOR(p) PATH_MAX
96 # define NAME_MAX_FOR(p) NAME_MAX
99 static int validate_path PARAMS ((char *path, int portability));
101 /* The name this program was run with. */
104 static struct option const longopts[] =
106 {"portability", no_argument, NULL, 'p'},
114 fprintf (stderr, _("Try `%s --help' for more information.\n"),
118 printf (_("Usage: %s [OPTION]... NAME...\n"), program_name);
120 Diagnose unportable constructs in NAME.\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\
126 puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
132 main (int argc, char **argv)
135 int check_portability = 0;
138 program_name = argv[0];
139 setlocale (LC_ALL, "");
140 bindtextdomain (PACKAGE, LOCALEDIR);
141 textdomain (PACKAGE);
143 atexit (close_stdout);
145 parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION,
148 while ((optc = getopt_long (argc, argv, "p", longopts, NULL)) != -1)
156 check_portability = 1;
166 error (0, 0, _("too few arguments"));
170 for (; optind < argc; ++optind)
171 exit_status |= validate_path (argv[optind], check_portability);
176 /* Each element is nonzero if the corresponding ASCII character is
177 in the POSIX portable character set, and zero if it is not.
178 In addition, the entry for `/' is nonzero to simplify checking. */
179 static char const portable_chars[256] =
181 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
182 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
183 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
184 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
185 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
186 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
187 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
188 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
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 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
196 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
199 /* If PATH contains only portable characters, return 1, else 0. */
202 portable_chars_only (const char *path)
206 for (p = path; *p; ++p)
207 if (portable_chars[(const unsigned char) *p] == 0)
209 error (0, 0, _("path `%s' contains nonportable character `%c'"),
216 /* Return 1 if PATH is a usable leading directory, 0 if not,
217 2 if it doesn't exist. */
220 dir_ok (const char *path)
224 if (stat (path, &stats))
227 if (!S_ISDIR (stats.st_mode))
229 error (0, 0, _("`%s' is not a directory"), path);
233 /* Use access to test for search permission because
234 testing permission bits of st_mode can lose with new
235 access control mechanisms. Of course, access loses if you're
237 if (access (path, X_OK) != 0)
240 error (0, 0, _("directory `%s' is not searchable"), path);
242 error (0, errno, "%s", path);
250 strlen (PATH) <= PATH_MAX
251 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
253 If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
254 _POSIX_NAME_MAX instead, and make sure that PATH contains no
255 characters not in the POSIX portable filename character set, which
256 consists of A-Z, a-z, 0-9, ., _, -.
258 Make sure that all leading directories along PATH that exist have
261 Return 0 if all of these tests are successful, 1 if any fail. */
264 validate_path (char *path, int portability)
267 int last_elem; /* Nonzero if checking last element of path. */
268 int exists IF_LINT (= 0); /* 2 if the path element exists. */
270 char *parent; /* Last existing leading directory so far. */
272 if (portability && !portable_chars_only (path))
278 /* Figure out the parent of the first element in PATH. */
279 parent = xstrdup (*path == '/' ? "/" : ".");
286 int length; /* Length of partial path being checked. */
287 char *start; /* Start of path element being checked. */
289 /* Find the end of this element of the path.
290 Then chop off the rest of the path after this element. */
291 while (*slash == '/')
294 slash = strchr (slash, '/');
300 slash = strchr (start, '\0');
305 exists = dir_ok (path);
313 length = slash - start;
314 /* Since we know that `parent' is a directory, it's ok to call
315 pathconf with it as the argument. (If `parent' isn't a directory
316 or doesn't exist, the behavior of pathconf is undefined.)
317 But if `parent' is a directory and is on a remote file system,
318 it's likely that pathconf can't give us a reasonable value
319 and will return -1. (NFS and tempfs are not POSIX . . .)
320 In that case, we have no choice but to assume the pessimal
322 name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
324 name_max = _POSIX_NAME_MAX;
325 if (length > name_max)
327 error (0, 0, _("name `%s' has length %d; exceeds limit of %d"),
328 start, length, name_max);
339 parent = xstrdup (path);
345 /* `parent' is now the last existing leading directory in the whole path,
346 so it's ok to call pathconf with it as the argument. */
347 path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
349 path_max = _POSIX_PATH_MAX;
351 if (strlen (path) > (size_t) path_max)
353 error (0, 0, _("path `%s' has length %d; exceeds limit of %d"),
354 path, strlen (path), path_max);