*** empty log message ***
[coreutils.git] / src / pathchk.c
blobcb070a4f7fb77bfc92161a18637e340fbeee797f
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"
48 #include "long-options.h"
50 /* The official name of this program (e.g., no `g' prefix). */
51 #define PROGRAM_NAME "pathchk"
53 #define AUTHORS "David MacKenzie and Jim Meyering"
55 #ifdef _POSIX_VERSION
56 # ifndef PATH_MAX
57 # define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
58 # endif /* not PATH_MAX */
59 # ifndef NAME_MAX
60 # define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX);
61 # endif /* not NAME_MAX */
63 #else /* not _POSIX_VERSION */
65 # include <sys/param.h>
66 # ifndef PATH_MAX
67 # ifdef MAXPATHLEN
68 # define PATH_MAX MAXPATHLEN
69 # else /* not MAXPATHLEN */
70 # define PATH_MAX _POSIX_PATH_MAX
71 # endif /* not MAXPATHLEN */
72 # endif /* not PATH_MAX */
74 # ifndef NAME_MAX
75 # ifdef MAXNAMLEN
76 # define NAME_MAX MAXNAMLEN
77 # else /* not MAXNAMLEN */
78 # define NAME_MAX _POSIX_NAME_MAX
79 # endif /* not MAXNAMLEN */
80 # endif /* not NAME_MAX */
82 #endif /* not _POSIX_VERSION */
84 #ifndef _POSIX_PATH_MAX
85 # define _POSIX_PATH_MAX 255
86 #endif
87 #ifndef _POSIX_NAME_MAX
88 # define _POSIX_NAME_MAX 14
89 #endif
91 #ifndef PATH_MAX_FOR
92 # define PATH_MAX_FOR(p) PATH_MAX
93 #endif
94 #ifndef NAME_MAX_FOR
95 # define NAME_MAX_FOR(p) NAME_MAX
96 #endif
98 char *xstrdup ();
100 static int validate_path PARAMS ((char *path, int portability));
102 /* The name this program was run with. */
103 char *program_name;
105 static struct option const longopts[] =
107 {NULL, 0, NULL, 0}
110 void
111 usage (int status)
113 if (status != 0)
114 fprintf (stderr, _("Try `%s --help' for more information.\n"),
115 program_name);
116 else
118 printf (_("Usage: %s [OPTION]... NAME...\n"), program_name);
119 printf (_("\
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\
125 "));
126 puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
128 exit (status);
132 main (int argc, char **argv)
134 int exit_status = 0;
135 int check_portability = 0;
136 int optc;
138 program_name = argv[0];
139 setlocale (LC_ALL, "");
140 bindtextdomain (PACKAGE, LOCALEDIR);
141 textdomain (PACKAGE);
143 parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION,
144 AUTHORS, usage);
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 (optind == argc)
164 error (0, 0, _("too few arguments"));
165 usage (1);
168 for (; optind < argc; ++optind)
169 exit_status |= validate_path (argv[optind], check_portability);
171 exit (exit_status);
174 /* Each element is nonzero if the corresponding ASCII character is
175 in the POSIX portable character set, and zero if it is not.
176 In addition, the entry for `/' is nonzero to simplify checking. */
177 static char const portable_chars[256] =
179 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
180 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
181 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
182 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
183 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
184 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
185 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
186 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
187 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
188 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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
197 /* If PATH contains only portable characters, return 1, else 0. */
199 static int
200 portable_chars_only (const char *path)
202 const char *p;
204 for (p = path; *p; ++p)
205 if (portable_chars[(const unsigned char) *p] == 0)
207 error (0, 0, _("path `%s' contains nonportable character `%c'"),
208 path, *p);
209 return 0;
211 return 1;
214 /* Return 1 if PATH is a usable leading directory, 0 if not,
215 2 if it doesn't exist. */
217 static int
218 dir_ok (const char *path)
220 struct stat stats;
222 if (stat (path, &stats))
223 return 2;
225 if (!S_ISDIR (stats.st_mode))
227 error (0, 0, _("`%s' is not a directory"), path);
228 return 0;
231 /* Use access to test for search permission because
232 testing permission bits of st_mode can lose with new
233 access control mechanisms. Of course, access loses if you're
234 running setuid. */
235 if (access (path, X_OK) != 0)
237 if (errno == EACCES)
238 error (0, 0, _("directory `%s' is not searchable"), path);
239 else
240 error (0, errno, "%s", path);
241 return 0;
244 return 1;
247 /* Make sure that
248 strlen (PATH) <= PATH_MAX
249 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
251 If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
252 _POSIX_NAME_MAX instead, and make sure that PATH contains no
253 characters not in the POSIX portable filename character set, which
254 consists of A-Z, a-z, 0-9, ., _, -.
256 Make sure that all leading directories along PATH that exist have
257 `x' permission.
259 Return 0 if all of these tests are successful, 1 if any fail. */
261 static int
262 validate_path (char *path, int portability)
264 int path_max;
265 int last_elem; /* Nonzero if checking last element of path. */
266 int exists; /* 2 if the path element exists. */
267 char *slash;
268 char *parent; /* Last existing leading directory so far. */
270 if (portability && !portable_chars_only (path))
271 return 1;
273 if (*path == '\0')
274 return 0;
276 #ifdef lint
277 /* Suppress `used before initialized' warning. */
278 exists = 0;
279 #endif
281 /* Figure out the parent of the first element in PATH. */
282 parent = xstrdup (*path == '/' ? "/" : ".");
284 slash = path;
285 last_elem = 0;
286 while (1)
288 int name_max;
289 int length; /* Length of partial path being checked. */
290 char *start; /* Start of path element being checked. */
292 /* Find the end of this element of the path.
293 Then chop off the rest of the path after this element. */
294 while (*slash == '/')
295 slash++;
296 start = slash;
297 slash = strchr (slash, '/');
298 if (slash != NULL)
299 *slash = '\0';
300 else
302 last_elem = 1;
303 slash = strchr (start, '\0');
306 if (!last_elem)
308 exists = dir_ok (path);
309 if (dir_ok == 0)
311 free (parent);
312 return 1;
316 length = slash - start;
317 /* Since we know that `parent' is a directory, it's ok to call
318 pathconf with it as the argument. (If `parent' isn't a directory
319 or doesn't exist, the behavior of pathconf is undefined.)
320 But if `parent' is a directory and is on a remote file system,
321 it's likely that pathconf can't give us a reasonable value
322 and will return -1. (NFS and tempfs are not POSIX . . .)
323 In that case, we have no choice but to assume the pessimal
324 POSIX minimums. */
325 name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
326 if (name_max < 0)
327 name_max = _POSIX_NAME_MAX;
328 if (length > name_max)
330 error (0, 0, _("name `%s' has length %d; exceeds limit of %d"),
331 start, length, name_max);
332 free (parent);
333 return 1;
336 if (last_elem)
337 break;
339 if (exists == 1)
341 free (parent);
342 parent = xstrdup (path);
345 *slash++ = '/';
348 /* `parent' is now the last existing leading directory in the whole path,
349 so it's ok to call pathconf with it as the argument. */
350 path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
351 if (path_max < 0)
352 path_max = _POSIX_PATH_MAX;
353 free (parent);
354 if (strlen (path) > (size_t) path_max)
356 error (0, 0, _("path `%s' has length %d; exceeds limit of %d"),
357 path, strlen (path), path_max);
358 return 1;
361 return 0;