.
[coreutils.git] / src / pathchk.c
blobfecd610db74f113581f251c2292915520658ea40
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 ();
97 static void usage ();
99 /* The name this program was run with. */
100 char *program_name;
102 /* If non-zero, display usage information and exit. */
103 static int show_help;
105 /* If non-zero, 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 (argc, argv)
118 int argc;
119 char **argv;
121 int exit_status = 0;
122 int check_portability = 0;
123 int optc;
125 program_name = argv[0];
127 while ((optc = getopt_long (argc, argv, "p", longopts, (int *) 0)) != EOF)
129 switch (optc)
131 case 0:
132 break;
134 case 'p':
135 check_portability = 1;
136 break;
138 default:
139 usage (1);
143 if (show_version)
145 printf ("pathchk - %s\n", version_string);
146 exit (0);
149 if (show_help)
150 usage (0);
152 if (optind == argc)
154 error (0, 0, "too few arguments");
155 usage (1);
158 for (; optind < argc; ++optind)
159 exit_status |= validate_path (argv[optind], check_portability);
161 exit (exit_status);
164 /* Each element is nonzero if the corresponding ASCII character is
165 in the POSIX portable character set, and zero if it is not.
166 In addition, the entry for `/' is nonzero to simplify checking. */
167 static char const portable_chars[256] =
169 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
170 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
171 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
172 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
173 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
174 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
175 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
176 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
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,
183 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
184 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
187 /* If PATH contains only portable characters, return 1, else 0. */
189 static int
190 portable_chars_only (path)
191 const char *path;
193 const char *p;
195 for (p = path; *p; ++p)
196 if (portable_chars[(const unsigned char) *p] == 0)
198 error (0, 0, "path `%s' contains nonportable character `%c'",
199 path, *p);
200 return 0;
202 return 1;
205 /* Return 1 if PATH is a usable leading directory, 0 if not,
206 2 if it doesn't exist. */
208 static int
209 dir_ok (path)
210 const char *path;
212 struct stat stats;
214 if (stat (path, &stats))
215 return 2;
217 if (!S_ISDIR (stats.st_mode))
219 error (0, 0, "`%s' is not a directory", path);
220 return 0;
223 /* Use access to test for search permission because
224 testing permission bits of st_mode can lose with new
225 access control mechanisms. Of course, access loses if you're
226 running setuid. */
227 if (access (path, X_OK) != 0)
229 if (errno == EACCES)
230 error (0, 0, "directory `%s' is not searchable", path);
231 else
232 error (0, errno, "%s", path);
233 return 0;
236 return 1;
239 /* Make sure that
240 strlen (PATH) <= PATH_MAX
241 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
243 If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
244 _POSIX_NAME_MAX instead, and make sure that PATH contains no
245 characters not in the POSIX portable filename character set, which
246 consists of A-Z, a-z, 0-9, ., _, -.
248 Make sure that all leading directories along PATH that exist have
249 `x' permission.
251 Return 0 if all of these tests are successful, 1 if any fail. */
253 static int
254 validate_path (path, portability)
255 char *path;
256 int portability;
258 int path_max;
259 int last_elem; /* Nonzero if checking last element of path. */
260 int exists; /* 2 if the path element exists. */
261 char *slash;
262 char *parent; /* Last existing leading directory so far. */
264 if (portability && !portable_chars_only (path))
265 return 1;
267 if (*path == '\0')
268 return 0;
270 #ifdef lint
271 /* Suppress `used before initialized' warning. */
272 exists = 0;
273 #endif
275 /* Figure out the parent of the first element in PATH. */
276 parent = xstrdup (*path == '/' ? "/" : ".");
278 slash = path;
279 last_elem = 0;
280 while (1)
282 int name_max;
283 int length; /* Length of partial path being checked. */
284 char *start; /* Start of path element being checked. */
286 /* Find the end of this element of the path.
287 Then chop off the rest of the path after this element. */
288 while (*slash == '/')
289 slash++;
290 start = slash;
291 slash = strchr (slash, '/');
292 if (slash != NULL)
293 *slash = '\0';
294 else
296 last_elem = 1;
297 slash = strchr (start, '\0');
300 if (!last_elem)
302 exists = dir_ok (path);
303 if (dir_ok == 0)
305 free (parent);
306 return 1;
310 length = slash - start;
311 /* Since we know that `parent' is a directory, it's ok to call
312 pathconf with it as the argument. (If `parent' isn't a directory
313 or doesn't exist, the behavior of pathconf is undefined.)
314 But if `parent' is a directory and is on a remote file system,
315 it's likely that pathconf can't give us a reasonable value
316 and will return -1. (NFS and tempfs are not POSIX . . .)
317 In that case, we have no choice but to assume the pessimal
318 POSIX minimums. */
319 name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
320 if (name_max < 0)
321 name_max = _POSIX_NAME_MAX;
322 if (length > name_max)
324 error (0, 0, "name `%s' has length %d; exceeds limit of %d",
325 start, length, name_max);
326 free (parent);
327 return 1;
330 if (last_elem)
331 break;
333 if (exists == 1)
335 free (parent);
336 parent = xstrdup (path);
339 *slash++ = '/';
342 /* `parent' is now the last existing leading directory in the whole path,
343 so it's ok to call pathconf with it as the argument. */
344 path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
345 if (path_max < 0)
346 path_max = _POSIX_PATH_MAX;
347 free (parent);
348 if (strlen (path) > path_max)
350 error (0, 0, "path `%s' has length %d; exceeds limit of %d",
351 path, strlen (path), path_max);
352 return 1;
355 return 0;
358 static void
359 usage (status)
360 int status;
362 if (status != 0)
363 fprintf (stderr, "Try `%s --help' for more information.\n",
364 program_name);
365 else
367 printf ("Usage: %s [OPTION]... NAME...\n", program_name);
368 printf ("\
369 Diagnose unportable constructs in NAME.\n\
371 -p, --portability check for all POSIX systems, not only this one\n\
372 --help display this help and exit\n\
373 --version output version information and exit\n\
376 exit (status);