(memcasecmp): Declare local I to be unsigned to avoid warning from gcc -Wall.
[coreutils.git] / src / pathchk.c
blob3aaa09968853e517f5706ebe929db527b8fb7075
1 /* pathchk -- check whether pathnames are valid or portable
2 Copyright (C) 91, 92, 93, 94, 1995 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
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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.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> */
41 #include <config.h>
42 #include <stdio.h>
43 #include <getopt.h>
44 #include <sys/types.h>
46 #include "version.h"
47 #include "system.h"
48 #include "error.h"
50 #ifdef _POSIX_VERSION
51 #include <limits.h>
52 #ifndef PATH_MAX
53 #define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
54 #endif /* not PATH_MAX */
55 #ifndef NAME_MAX
56 #define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX);
57 #endif /* not NAME_MAX */
59 #else /* not _POSIX_VERSION */
61 #include <sys/param.h>
62 #ifndef PATH_MAX
63 #ifdef MAXPATHLEN
64 #define PATH_MAX MAXPATHLEN
65 #else /* not MAXPATHLEN */
66 #define PATH_MAX _POSIX_PATH_MAX
67 #endif /* not MAXPATHLEN */
68 #endif /* not PATH_MAX */
70 #ifndef NAME_MAX
71 #ifdef MAXNAMLEN
72 #define NAME_MAX MAXNAMLEN
73 #else /* not MAXNAMLEN */
74 #define NAME_MAX _POSIX_NAME_MAX
75 #endif /* not MAXNAMLEN */
76 #endif /* not NAME_MAX */
78 #endif /* not _POSIX_VERSION */
80 #ifndef _POSIX_PATH_MAX
81 #define _POSIX_PATH_MAX 255
82 #endif
83 #ifndef _POSIX_NAME_MAX
84 #define _POSIX_NAME_MAX 14
85 #endif
87 #ifndef PATH_MAX_FOR
88 #define PATH_MAX_FOR(p) PATH_MAX
89 #endif
90 #ifndef NAME_MAX_FOR
91 #define NAME_MAX_FOR(p) NAME_MAX
92 #endif
94 char *xstrdup ();
96 static int validate_path __P ((char *path, int portability));
97 static void usage __P ((int status));
99 /* The name this program was run with. */
100 char *program_name;
102 /* If nonzero, display usage information and exit. */
103 static int show_help;
105 /* If nonzero, print the version on standard output and exit. */
106 static int show_version;
108 static struct option const longopts[] =
110 {"help", no_argument, &show_help, 1},
111 {"portability", no_argument, NULL, 'p'},
112 {"version", no_argument, &show_version, 1},
113 {NULL, 0, NULL, 0}
116 void
117 main (int argc, char **argv)
119 int exit_status = 0;
120 int check_portability = 0;
121 int optc;
123 program_name = argv[0];
125 while ((optc = getopt_long (argc, argv, "p", longopts, (int *) 0)) != EOF)
127 switch (optc)
129 case 0:
130 break;
132 case 'p':
133 check_portability = 1;
134 break;
136 default:
137 usage (1);
141 if (show_version)
143 printf ("pathchk - %s\n", version_string);
144 exit (0);
147 if (show_help)
148 usage (0);
150 if (optind == argc)
152 error (0, 0, _("too few arguments"));
153 usage (1);
156 for (; optind < argc; ++optind)
157 exit_status |= validate_path (argv[optind], check_portability);
159 exit (exit_status);
162 /* Each element is nonzero if the corresponding ASCII character is
163 in the POSIX portable character set, and zero if it is not.
164 In addition, the entry for `/' is nonzero to simplify checking. */
165 static char const portable_chars[256] =
167 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
168 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
169 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
170 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
171 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
172 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
173 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
174 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
175 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
176 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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
185 /* If PATH contains only portable characters, return 1, else 0. */
187 static int
188 portable_chars_only (const char *path)
190 const char *p;
192 for (p = path; *p; ++p)
193 if (portable_chars[(const unsigned char) *p] == 0)
195 error (0, 0, _("path `%s' contains nonportable character `%c'"),
196 path, *p);
197 return 0;
199 return 1;
202 /* Return 1 if PATH is a usable leading directory, 0 if not,
203 2 if it doesn't exist. */
205 static int
206 dir_ok (const char *path)
208 struct stat stats;
210 if (stat (path, &stats))
211 return 2;
213 if (!S_ISDIR (stats.st_mode))
215 error (0, 0, _("`%s' is not a directory"), path);
216 return 0;
219 /* Use access to test for search permission because
220 testing permission bits of st_mode can lose with new
221 access control mechanisms. Of course, access loses if you're
222 running setuid. */
223 if (access (path, X_OK) != 0)
225 if (errno == EACCES)
226 error (0, 0, _("directory `%s' is not searchable"), path);
227 else
228 error (0, errno, "%s", path);
229 return 0;
232 return 1;
235 /* Make sure that
236 strlen (PATH) <= PATH_MAX
237 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
239 If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
240 _POSIX_NAME_MAX instead, and make sure that PATH contains no
241 characters not in the POSIX portable filename character set, which
242 consists of A-Z, a-z, 0-9, ., _, -.
244 Make sure that all leading directories along PATH that exist have
245 `x' permission.
247 Return 0 if all of these tests are successful, 1 if any fail. */
249 static int
250 validate_path (char *path, int portability)
252 int path_max;
253 int last_elem; /* Nonzero if checking last element of path. */
254 int exists; /* 2 if the path element exists. */
255 char *slash;
256 char *parent; /* Last existing leading directory so far. */
258 if (portability && !portable_chars_only (path))
259 return 1;
261 if (*path == '\0')
262 return 0;
264 #ifdef lint
265 /* Suppress `used before initialized' warning. */
266 exists = 0;
267 #endif
269 /* Figure out the parent of the first element in PATH. */
270 parent = xstrdup (*path == '/' ? "/" : ".");
272 slash = path;
273 last_elem = 0;
274 while (1)
276 int name_max;
277 int length; /* Length of partial path being checked. */
278 char *start; /* Start of path element being checked. */
280 /* Find the end of this element of the path.
281 Then chop off the rest of the path after this element. */
282 while (*slash == '/')
283 slash++;
284 start = slash;
285 slash = strchr (slash, '/');
286 if (slash != NULL)
287 *slash = '\0';
288 else
290 last_elem = 1;
291 slash = strchr (start, '\0');
294 if (!last_elem)
296 exists = dir_ok (path);
297 if (dir_ok == 0)
299 free (parent);
300 return 1;
304 length = slash - start;
305 /* Since we know that `parent' is a directory, it's ok to call
306 pathconf with it as the argument. (If `parent' isn't a directory
307 or doesn't exist, the behavior of pathconf is undefined.)
308 But if `parent' is a directory and is on a remote file system,
309 it's likely that pathconf can't give us a reasonable value
310 and will return -1. (NFS and tempfs are not POSIX . . .)
311 In that case, we have no choice but to assume the pessimal
312 POSIX minimums. */
313 name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
314 if (name_max < 0)
315 name_max = _POSIX_NAME_MAX;
316 if (length > name_max)
318 error (0, 0, _("name `%s' has length %d; exceeds limit of %d"),
319 start, length, name_max);
320 free (parent);
321 return 1;
324 if (last_elem)
325 break;
327 if (exists == 1)
329 free (parent);
330 parent = xstrdup (path);
333 *slash++ = '/';
336 /* `parent' is now the last existing leading directory in the whole path,
337 so it's ok to call pathconf with it as the argument. */
338 path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
339 if (path_max < 0)
340 path_max = _POSIX_PATH_MAX;
341 free (parent);
342 if (strlen (path) > path_max)
344 error (0, 0, _("path `%s' has length %d; exceeds limit of %d"),
345 path, strlen (path), path_max);
346 return 1;
349 return 0;
352 static void
353 usage (int status)
355 if (status != 0)
356 fprintf (stderr, _("Try `%s --help' for more information.\n"),
357 program_name);
358 else
360 printf (_("Usage: %s [OPTION]... NAME...\n"), program_name);
361 printf (_("\
362 Diagnose unportable constructs in NAME.\n\
364 -p, --portability check for all POSIX systems, not only this one\n\
365 --help display this help and exit\n\
366 --version output version information and exit\n\
367 "));
369 exit (status);