Add tests with filenames containing newline and backslash characters.
[coreutils.git] / src / pathchk.c
blob90f0ce1055412c8c05b723c8920ecd21aeb24279
1 /* pathchk -- check whether pathnames are valid or portable
2 Copyright (C) 91, 92, 93, 94, 95, 96, 1997 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));
95 static void usage PARAMS ((int status));
97 /* The name this program was run with. */
98 char *program_name;
100 /* If nonzero, display usage information and exit. */
101 static int show_help;
103 /* If nonzero, print the version on standard output and exit. */
104 static int show_version;
106 static struct option const longopts[] =
108 {"help", no_argument, &show_help, 1},
109 {"portability", no_argument, NULL, 'p'},
110 {"version", no_argument, &show_version, 1},
111 {NULL, 0, NULL, 0}
115 main (int argc, char **argv)
117 int exit_status = 0;
118 int check_portability = 0;
119 int optc;
121 program_name = argv[0];
122 setlocale (LC_ALL, "");
123 bindtextdomain (PACKAGE, LOCALEDIR);
124 textdomain (PACKAGE);
126 while ((optc = getopt_long (argc, argv, "p", longopts, NULL)) != -1)
128 switch (optc)
130 case 0:
131 break;
133 case 'p':
134 check_portability = 1;
135 break;
137 default:
138 usage (1);
142 if (show_version)
144 printf ("pathchk (%s) %s\n", GNU_PACKAGE, VERSION);
145 exit (0);
148 if (show_help)
149 usage (0);
151 if (optind == argc)
153 error (0, 0, _("too few arguments"));
154 usage (1);
157 for (; optind < argc; ++optind)
158 exit_status |= validate_path (argv[optind], check_portability);
160 exit (exit_status);
163 /* Each element is nonzero if the corresponding ASCII character is
164 in the POSIX portable character set, and zero if it is not.
165 In addition, the entry for `/' is nonzero to simplify checking. */
166 static char const portable_chars[256] =
168 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
169 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
170 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
171 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
172 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
173 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
174 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
175 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
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,
183 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
186 /* If PATH contains only portable characters, return 1, else 0. */
188 static int
189 portable_chars_only (const char *path)
191 const char *p;
193 for (p = path; *p; ++p)
194 if (portable_chars[(const unsigned char) *p] == 0)
196 error (0, 0, _("path `%s' contains nonportable character `%c'"),
197 path, *p);
198 return 0;
200 return 1;
203 /* Return 1 if PATH is a usable leading directory, 0 if not,
204 2 if it doesn't exist. */
206 static int
207 dir_ok (const char *path)
209 struct stat stats;
211 if (stat (path, &stats))
212 return 2;
214 if (!S_ISDIR (stats.st_mode))
216 error (0, 0, _("`%s' is not a directory"), path);
217 return 0;
220 /* Use access to test for search permission because
221 testing permission bits of st_mode can lose with new
222 access control mechanisms. Of course, access loses if you're
223 running setuid. */
224 if (access (path, X_OK) != 0)
226 if (errno == EACCES)
227 error (0, 0, _("directory `%s' is not searchable"), path);
228 else
229 error (0, errno, "%s", path);
230 return 0;
233 return 1;
236 /* Make sure that
237 strlen (PATH) <= PATH_MAX
238 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
240 If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
241 _POSIX_NAME_MAX instead, and make sure that PATH contains no
242 characters not in the POSIX portable filename character set, which
243 consists of A-Z, a-z, 0-9, ., _, -.
245 Make sure that all leading directories along PATH that exist have
246 `x' permission.
248 Return 0 if all of these tests are successful, 1 if any fail. */
250 static int
251 validate_path (char *path, int portability)
253 int path_max;
254 int last_elem; /* Nonzero if checking last element of path. */
255 int exists; /* 2 if the path element exists. */
256 char *slash;
257 char *parent; /* Last existing leading directory so far. */
259 if (portability && !portable_chars_only (path))
260 return 1;
262 if (*path == '\0')
263 return 0;
265 #ifdef lint
266 /* Suppress `used before initialized' warning. */
267 exists = 0;
268 #endif
270 /* Figure out the parent of the first element in PATH. */
271 parent = xstrdup (*path == '/' ? "/" : ".");
273 slash = path;
274 last_elem = 0;
275 while (1)
277 int name_max;
278 int length; /* Length of partial path being checked. */
279 char *start; /* Start of path element being checked. */
281 /* Find the end of this element of the path.
282 Then chop off the rest of the path after this element. */
283 while (*slash == '/')
284 slash++;
285 start = slash;
286 slash = strchr (slash, '/');
287 if (slash != NULL)
288 *slash = '\0';
289 else
291 last_elem = 1;
292 slash = strchr (start, '\0');
295 if (!last_elem)
297 exists = dir_ok (path);
298 if (dir_ok == 0)
300 free (parent);
301 return 1;
305 length = slash - start;
306 /* Since we know that `parent' is a directory, it's ok to call
307 pathconf with it as the argument. (If `parent' isn't a directory
308 or doesn't exist, the behavior of pathconf is undefined.)
309 But if `parent' is a directory and is on a remote file system,
310 it's likely that pathconf can't give us a reasonable value
311 and will return -1. (NFS and tempfs are not POSIX . . .)
312 In that case, we have no choice but to assume the pessimal
313 POSIX minimums. */
314 name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
315 if (name_max < 0)
316 name_max = _POSIX_NAME_MAX;
317 if (length > name_max)
319 error (0, 0, _("name `%s' has length %d; exceeds limit of %d"),
320 start, length, name_max);
321 free (parent);
322 return 1;
325 if (last_elem)
326 break;
328 if (exists == 1)
330 free (parent);
331 parent = xstrdup (path);
334 *slash++ = '/';
337 /* `parent' is now the last existing leading directory in the whole path,
338 so it's ok to call pathconf with it as the argument. */
339 path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
340 if (path_max < 0)
341 path_max = _POSIX_PATH_MAX;
342 free (parent);
343 if (strlen (path) > (size_t) path_max)
345 error (0, 0, _("path `%s' has length %d; exceeds limit of %d"),
346 path, strlen (path), path_max);
347 return 1;
350 return 0;
353 static void
354 usage (int status)
356 if (status != 0)
357 fprintf (stderr, _("Try `%s --help' for more information.\n"),
358 program_name);
359 else
361 printf (_("Usage: %s [OPTION]... NAME...\n"), program_name);
362 printf (_("\
363 Diagnose unportable constructs in NAME.\n\
365 -p, --portability check for all POSIX systems, not only this one\n\
366 --help display this help and exit\n\
367 --version output version information and exit\n\
368 "));
369 puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
371 exit (status);