Don't include version.h.
[coreutils.git] / src / pathchk.c
blob224d517c3b13c6b28aa37787f64340ff2d6d55d7
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 "system.h"
47 #include "error.h"
49 #ifdef _POSIX_VERSION
50 #include <limits.h>
51 #ifndef PATH_MAX
52 #define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
53 #endif /* not PATH_MAX */
54 #ifndef NAME_MAX
55 #define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX);
56 #endif /* not NAME_MAX */
58 #else /* not _POSIX_VERSION */
60 #include <sys/param.h>
61 #ifndef PATH_MAX
62 #ifdef MAXPATHLEN
63 #define PATH_MAX MAXPATHLEN
64 #else /* not MAXPATHLEN */
65 #define PATH_MAX _POSIX_PATH_MAX
66 #endif /* not MAXPATHLEN */
67 #endif /* not PATH_MAX */
69 #ifndef NAME_MAX
70 #ifdef MAXNAMLEN
71 #define NAME_MAX MAXNAMLEN
72 #else /* not MAXNAMLEN */
73 #define NAME_MAX _POSIX_NAME_MAX
74 #endif /* not MAXNAMLEN */
75 #endif /* not NAME_MAX */
77 #endif /* not _POSIX_VERSION */
79 #ifndef _POSIX_PATH_MAX
80 #define _POSIX_PATH_MAX 255
81 #endif
82 #ifndef _POSIX_NAME_MAX
83 #define _POSIX_NAME_MAX 14
84 #endif
86 #ifndef PATH_MAX_FOR
87 #define PATH_MAX_FOR(p) PATH_MAX
88 #endif
89 #ifndef NAME_MAX_FOR
90 #define NAME_MAX_FOR(p) NAME_MAX
91 #endif
93 char *xstrdup ();
95 static int validate_path __P ((char *path, int portability));
96 static void usage __P ((int status));
98 /* The name this program was run with. */
99 char *program_name;
101 /* If nonzero, display usage information and exit. */
102 static int show_help;
104 /* If nonzero, print the version on standard output and exit. */
105 static int show_version;
107 static struct option const longopts[] =
109 {"help", no_argument, &show_help, 1},
110 {"portability", no_argument, NULL, 'p'},
111 {"version", no_argument, &show_version, 1},
112 {NULL, 0, NULL, 0}
115 void
116 main (int argc, char **argv)
118 int exit_status = 0;
119 int check_portability = 0;
120 int optc;
122 program_name = argv[0];
123 setlocale (LC_ALL, "");
124 bindtextdomain (PACKAGE, LOCALEDIR);
125 textdomain (PACKAGE);
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", PACKAGE_VERSION);
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 (const char *path)
192 const char *p;
194 for (p = path; *p; ++p)
195 if (portable_chars[(const unsigned char) *p] == 0)
197 error (0, 0, _("path `%s' contains nonportable character `%c'"),
198 path, *p);
199 return 0;
201 return 1;
204 /* Return 1 if PATH is a usable leading directory, 0 if not,
205 2 if it doesn't exist. */
207 static int
208 dir_ok (const char *path)
210 struct stat stats;
212 if (stat (path, &stats))
213 return 2;
215 if (!S_ISDIR (stats.st_mode))
217 error (0, 0, _("`%s' is not a directory"), path);
218 return 0;
221 /* Use access to test for search permission because
222 testing permission bits of st_mode can lose with new
223 access control mechanisms. Of course, access loses if you're
224 running setuid. */
225 if (access (path, X_OK) != 0)
227 if (errno == EACCES)
228 error (0, 0, _("directory `%s' is not searchable"), path);
229 else
230 error (0, errno, "%s", path);
231 return 0;
234 return 1;
237 /* Make sure that
238 strlen (PATH) <= PATH_MAX
239 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
241 If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
242 _POSIX_NAME_MAX instead, and make sure that PATH contains no
243 characters not in the POSIX portable filename character set, which
244 consists of A-Z, a-z, 0-9, ., _, -.
246 Make sure that all leading directories along PATH that exist have
247 `x' permission.
249 Return 0 if all of these tests are successful, 1 if any fail. */
251 static int
252 validate_path (char *path, int portability)
254 int path_max;
255 int last_elem; /* Nonzero if checking last element of path. */
256 int exists; /* 2 if the path element exists. */
257 char *slash;
258 char *parent; /* Last existing leading directory so far. */
260 if (portability && !portable_chars_only (path))
261 return 1;
263 if (*path == '\0')
264 return 0;
266 #ifdef lint
267 /* Suppress `used before initialized' warning. */
268 exists = 0;
269 #endif
271 /* Figure out the parent of the first element in PATH. */
272 parent = xstrdup (*path == '/' ? "/" : ".");
274 slash = path;
275 last_elem = 0;
276 while (1)
278 int name_max;
279 int length; /* Length of partial path being checked. */
280 char *start; /* Start of path element being checked. */
282 /* Find the end of this element of the path.
283 Then chop off the rest of the path after this element. */
284 while (*slash == '/')
285 slash++;
286 start = slash;
287 slash = strchr (slash, '/');
288 if (slash != NULL)
289 *slash = '\0';
290 else
292 last_elem = 1;
293 slash = strchr (start, '\0');
296 if (!last_elem)
298 exists = dir_ok (path);
299 if (dir_ok == 0)
301 free (parent);
302 return 1;
306 length = slash - start;
307 /* Since we know that `parent' is a directory, it's ok to call
308 pathconf with it as the argument. (If `parent' isn't a directory
309 or doesn't exist, the behavior of pathconf is undefined.)
310 But if `parent' is a directory and is on a remote file system,
311 it's likely that pathconf can't give us a reasonable value
312 and will return -1. (NFS and tempfs are not POSIX . . .)
313 In that case, we have no choice but to assume the pessimal
314 POSIX minimums. */
315 name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
316 if (name_max < 0)
317 name_max = _POSIX_NAME_MAX;
318 if (length > name_max)
320 error (0, 0, _("name `%s' has length %d; exceeds limit of %d"),
321 start, length, name_max);
322 free (parent);
323 return 1;
326 if (last_elem)
327 break;
329 if (exists == 1)
331 free (parent);
332 parent = xstrdup (path);
335 *slash++ = '/';
338 /* `parent' is now the last existing leading directory in the whole path,
339 so it's ok to call pathconf with it as the argument. */
340 path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
341 if (path_max < 0)
342 path_max = _POSIX_PATH_MAX;
343 free (parent);
344 if (strlen (path) > (size_t) path_max)
346 error (0, 0, _("path `%s' has length %d; exceeds limit of %d"),
347 path, strlen (path), path_max);
348 return 1;
351 return 0;
354 static void
355 usage (int status)
357 if (status != 0)
358 fprintf (stderr, _("Try `%s --help' for more information.\n"),
359 program_name);
360 else
362 printf (_("Usage: %s [OPTION]... NAME...\n"), program_name);
363 printf (_("\
364 Diagnose unportable constructs in NAME.\n\
366 -p, --portability check for all POSIX systems, not only this one\n\
367 --help display this help and exit\n\
368 --version output version information and exit\n\
369 "));
371 exit (status);