*** empty log message ***
[coreutils.git] / src / pathchk.c
blob2fc24dd699f2287b6fc17b59abcb07c5d169eeab
1 /* pathchk -- check whether pathnames are valid or portable
2 Copyright (C) 1991-1999 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.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 # ifndef PATH_MAX
51 # define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
52 # endif /* not PATH_MAX */
53 # ifndef NAME_MAX
54 # define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX);
55 # endif /* not NAME_MAX */
57 #else /* not _POSIX_VERSION */
59 # include <sys/param.h>
60 # ifndef PATH_MAX
61 # ifdef MAXPATHLEN
62 # define PATH_MAX MAXPATHLEN
63 # else /* not MAXPATHLEN */
64 # define PATH_MAX _POSIX_PATH_MAX
65 # endif /* not MAXPATHLEN */
66 # endif /* not PATH_MAX */
68 # ifndef NAME_MAX
69 # ifdef MAXNAMLEN
70 # define NAME_MAX MAXNAMLEN
71 # else /* not MAXNAMLEN */
72 # define NAME_MAX _POSIX_NAME_MAX
73 # endif /* not MAXNAMLEN */
74 # endif /* not NAME_MAX */
76 #endif /* not _POSIX_VERSION */
78 #ifndef _POSIX_PATH_MAX
79 # define _POSIX_PATH_MAX 255
80 #endif
81 #ifndef _POSIX_NAME_MAX
82 # define _POSIX_NAME_MAX 14
83 #endif
85 #ifndef PATH_MAX_FOR
86 # define PATH_MAX_FOR(p) PATH_MAX
87 #endif
88 #ifndef NAME_MAX_FOR
89 # define NAME_MAX_FOR(p) NAME_MAX
90 #endif
92 char *xstrdup ();
94 static int validate_path PARAMS ((char *path, int portability));
96 /* The name this program was run with. */
97 char *program_name;
99 /* If nonzero, display usage information and exit. */
100 static int show_help;
102 /* If nonzero, print the version on standard output and exit. */
103 static int show_version;
105 static struct option const longopts[] =
107 {"help", no_argument, &show_help, 1},
108 {"portability", no_argument, NULL, 'p'},
109 {"version", no_argument, &show_version, 1},
110 {NULL, 0, NULL, 0}
113 void
114 usage (int status)
116 if (status != 0)
117 fprintf (stderr, _("Try `%s --help' for more information.\n"),
118 program_name);
119 else
121 printf (_("Usage: %s [OPTION]... NAME...\n"), program_name);
122 printf (_("\
123 Diagnose unportable constructs in NAME.\n\
125 -p, --portability check for all POSIX systems, not only this one\n\
126 --help display this help and exit\n\
127 --version output version information and exit\n\
128 "));
129 puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
131 exit (status);
135 main (int argc, char **argv)
137 int exit_status = 0;
138 int check_portability = 0;
139 int optc;
141 program_name = argv[0];
142 setlocale (LC_ALL, "");
143 bindtextdomain (PACKAGE, LOCALEDIR);
144 textdomain (PACKAGE);
146 while ((optc = getopt_long (argc, argv, "p", longopts, NULL)) != -1)
148 switch (optc)
150 case 0:
151 break;
153 case 'p':
154 check_portability = 1;
155 break;
157 default:
158 usage (1);
162 if (show_version)
164 printf ("pathchk (%s) %s\n", GNU_PACKAGE, VERSION);
165 exit (0);
168 if (show_help)
169 usage (0);
171 if (optind == argc)
173 error (0, 0, _("too few arguments"));
174 usage (1);
177 for (; optind < argc; ++optind)
178 exit_status |= validate_path (argv[optind], check_portability);
180 exit (exit_status);
183 /* Each element is nonzero if the corresponding ASCII character is
184 in the POSIX portable character set, and zero if it is not.
185 In addition, the entry for `/' is nonzero to simplify checking. */
186 static char const portable_chars[256] =
188 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
189 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
190 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
191 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
192 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
193 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
194 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
195 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
196 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
197 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
198 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
199 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
200 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
201 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
202 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
203 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
206 /* If PATH contains only portable characters, return 1, else 0. */
208 static int
209 portable_chars_only (const char *path)
211 const char *p;
213 for (p = path; *p; ++p)
214 if (portable_chars[(const unsigned char) *p] == 0)
216 error (0, 0, _("path `%s' contains nonportable character `%c'"),
217 path, *p);
218 return 0;
220 return 1;
223 /* Return 1 if PATH is a usable leading directory, 0 if not,
224 2 if it doesn't exist. */
226 static int
227 dir_ok (const char *path)
229 struct stat stats;
231 if (stat (path, &stats))
232 return 2;
234 if (!S_ISDIR (stats.st_mode))
236 error (0, 0, _("`%s' is not a directory"), path);
237 return 0;
240 /* Use access to test for search permission because
241 testing permission bits of st_mode can lose with new
242 access control mechanisms. Of course, access loses if you're
243 running setuid. */
244 if (access (path, X_OK) != 0)
246 if (errno == EACCES)
247 error (0, 0, _("directory `%s' is not searchable"), path);
248 else
249 error (0, errno, "%s", path);
250 return 0;
253 return 1;
256 /* Make sure that
257 strlen (PATH) <= PATH_MAX
258 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
260 If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
261 _POSIX_NAME_MAX instead, and make sure that PATH contains no
262 characters not in the POSIX portable filename character set, which
263 consists of A-Z, a-z, 0-9, ., _, -.
265 Make sure that all leading directories along PATH that exist have
266 `x' permission.
268 Return 0 if all of these tests are successful, 1 if any fail. */
270 static int
271 validate_path (char *path, int portability)
273 int path_max;
274 int last_elem; /* Nonzero if checking last element of path. */
275 int exists; /* 2 if the path element exists. */
276 char *slash;
277 char *parent; /* Last existing leading directory so far. */
279 if (portability && !portable_chars_only (path))
280 return 1;
282 if (*path == '\0')
283 return 0;
285 #ifdef lint
286 /* Suppress `used before initialized' warning. */
287 exists = 0;
288 #endif
290 /* Figure out the parent of the first element in PATH. */
291 parent = xstrdup (*path == '/' ? "/" : ".");
293 slash = path;
294 last_elem = 0;
295 while (1)
297 int name_max;
298 int length; /* Length of partial path being checked. */
299 char *start; /* Start of path element being checked. */
301 /* Find the end of this element of the path.
302 Then chop off the rest of the path after this element. */
303 while (*slash == '/')
304 slash++;
305 start = slash;
306 slash = strchr (slash, '/');
307 if (slash != NULL)
308 *slash = '\0';
309 else
311 last_elem = 1;
312 slash = strchr (start, '\0');
315 if (!last_elem)
317 exists = dir_ok (path);
318 if (dir_ok == 0)
320 free (parent);
321 return 1;
325 length = slash - start;
326 /* Since we know that `parent' is a directory, it's ok to call
327 pathconf with it as the argument. (If `parent' isn't a directory
328 or doesn't exist, the behavior of pathconf is undefined.)
329 But if `parent' is a directory and is on a remote file system,
330 it's likely that pathconf can't give us a reasonable value
331 and will return -1. (NFS and tempfs are not POSIX . . .)
332 In that case, we have no choice but to assume the pessimal
333 POSIX minimums. */
334 name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
335 if (name_max < 0)
336 name_max = _POSIX_NAME_MAX;
337 if (length > name_max)
339 error (0, 0, _("name `%s' has length %d; exceeds limit of %d"),
340 start, length, name_max);
341 free (parent);
342 return 1;
345 if (last_elem)
346 break;
348 if (exists == 1)
350 free (parent);
351 parent = xstrdup (path);
354 *slash++ = '/';
357 /* `parent' is now the last existing leading directory in the whole path,
358 so it's ok to call pathconf with it as the argument. */
359 path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
360 if (path_max < 0)
361 path_max = _POSIX_PATH_MAX;
362 free (parent);
363 if (strlen (path) > (size_t) path_max)
365 error (0, 0, _("path `%s' has length %d; exceeds limit of %d"),
366 path, strlen (path), path_max);
367 return 1;
370 return 0;