Patch-ID: bash41-003
[bash.git] / examples / loadables / pathchk.c
blobc5fd24a899629ae7d58c5cb8261833f0cdf1f8bd
1 /* pathchk - check pathnames for validity and portability */
3 /* Usage: pathchk [-p] path ...
5 For each PATH, print a message if any of these conditions are false:
6 * all existing leading directories in PATH have search (execute) permission
7 * strlen (PATH) <= PATH_MAX
8 * strlen (each_directory_in_PATH) <= NAME_MAX
10 Exit status:
11 0 All PATH names passed all of the tests.
12 1 An error occurred.
14 Options:
15 -p Instead of performing length checks on the
16 underlying filesystem, test the length of the
17 pathname and its components against the POSIX.1
18 minimum limits for portability, _POSIX_NAME_MAX
19 and _POSIX_PATH_MAX in 2.9.2. Also check that
20 the pathname contains no character not in the
21 portable filename character set. */
23 /* See Makefile for compilation details. */
26 Copyright (C) 1999-2009 Free Software Foundation, Inc.
28 This file is part of GNU Bash.
29 Bash is free software: you can redistribute it and/or modify
30 it under the terms of the GNU General Public License as published by
31 the Free Software Foundation, either version 3 of the License, or
32 (at your option) any later version.
34 Bash is distributed in the hope that it will be useful,
35 but WITHOUT ANY WARRANTY; without even the implied warranty of
36 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 GNU General Public License for more details.
39 You should have received a copy of the GNU General Public License
40 along with Bash. If not, see <http://www.gnu.org/licenses/>.
43 #include <config.h>
45 #include <sys/types.h>
46 #include "posixstat.h"
48 #if defined (HAVE_UNISTD_H)
49 # include <unistd.h>
50 #endif
52 #if defined (HAVE_LIMITS_H)
53 # include <limits.h>
54 #endif
56 #include "bashansi.h"
58 #include <stdio.h>
59 #include <errno.h>
61 #include "builtins.h"
62 #include "shell.h"
63 #include "stdc.h"
64 #include "bashgetopt.h"
65 #include "maxpath.h"
66 #include "common.h"
68 #if !defined (errno)
69 extern int errno;
70 #endif
72 #if !defined (_POSIX_PATH_MAX)
73 # define _POSIX_PATH_MAX 255
74 #endif
75 #if !defined (_POSIX_NAME_MAX)
76 # define _POSIX_NAME_MAX 14
77 #endif
79 /* How do we get PATH_MAX? */
80 #if defined (_POSIX_VERSION) && !defined (PATH_MAX)
81 # define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
82 #endif
84 /* How do we get NAME_MAX? */
85 #if defined (_POSIX_VERSION) && !defined (NAME_MAX)
86 # define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX)
87 #endif
89 #if !defined (PATH_MAX_FOR)
90 # define PATH_MAX_FOR(p) PATH_MAX
91 #endif
93 #if !defined (NAME_MAX_FOR)
94 # define NAME_MAX_FOR(p) NAME_MAX
95 #endif
97 extern char *strerror ();
99 static int validate_path ();
101 pathchk_builtin (list)
102 WORD_LIST *list;
104 int retval, pflag, opt;
106 reset_internal_getopt ();
107 while ((opt = internal_getopt (list, "p")) != -1)
109 switch (opt)
111 case 'p':
112 pflag = 1;
113 break;
114 default:
115 builtin_usage ();
116 return (EX_USAGE);
119 list = loptend;
121 if (list == 0)
123 builtin_usage ();
124 return (EX_USAGE);
127 for (retval = 0; list; list = list->next)
128 retval |= validate_path (list->word->word, pflag);
130 return (retval ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
133 char *pathchk_doc[] = {
134 "Check pathnames for validity.",
136 "Check each pathname argument for validity (i.e., it may be used to",
137 "create or access a file without casuing syntax errors) and portability",
138 "(i.e., no filename truncation will result). If the `-p' option is",
139 "supplied, more extensive portability checks are performed.",
140 (char *)NULL
143 /* The standard structure describing a builtin command. bash keeps an array
144 of these structures. */
145 struct builtin pathchk_struct = {
146 "pathchk", /* builtin name */
147 pathchk_builtin, /* function implementing the builtin */
148 BUILTIN_ENABLED, /* initial flags for builtin */
149 pathchk_doc, /* array of long documentation strings. */
150 "pathchk [-p] pathname ...", /* usage synopsis */
151 0 /* reserved for internal use */
154 /* The remainder of this file is stolen shamelessly from `pathchk.c' in
155 the sh-utils-1.12 distribution, by
157 David MacKenzie <djm@gnu.ai.mit.edu>
158 and Jim Meyering <meyering@cs.utexas.edu> */
160 /* Each element is nonzero if the corresponding ASCII character is
161 in the POSIX portable character set, and zero if it is not.
162 In addition, the entry for `/' is nonzero to simplify checking. */
163 static char const portable_chars[256] =
165 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
166 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
167 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
168 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
169 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
170 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
171 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
172 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
173 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
174 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
175 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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
183 /* If PATH contains only portable characters, return 1, else 0. */
185 static int
186 portable_chars_only (path)
187 const char *path;
189 const char *p;
191 for (p = path; *p; ++p)
192 if (portable_chars[(const unsigned char) *p] == 0)
194 builtin_error ("path `%s' contains nonportable character `%c'", path, *p);
195 return 0;
197 return 1;
200 /* On some systems, stat can return EINTR. */
202 #ifndef EINTR
203 # define SAFE_STAT(name, buf) stat (name, buf)
204 #else
205 # define SAFE_STAT(name, buf) safe_stat (name, buf)
206 static inline int
207 safe_stat (name, buf)
208 const char *name;
209 struct stat *buf;
211 int ret;
214 ret = stat (name, buf);
215 while (ret < 0 && errno == EINTR);
217 return ret;
219 #endif
221 /* Return 1 if PATH is a usable leading directory, 0 if not,
222 2 if it doesn't exist. */
224 static int
225 dir_ok (path)
226 const char *path;
228 struct stat stats;
230 if (SAFE_STAT (path, &stats))
231 return 2;
233 if (!S_ISDIR (stats.st_mode))
235 builtin_error ("`%s' is not a directory", path);
236 return 0;
239 /* Use access to test for search permission because
240 testing permission bits of st_mode can lose with new
241 access control mechanisms. Of course, access loses if you're
242 running setuid. */
243 if (access (path, X_OK) != 0)
245 if (errno == EACCES)
246 builtin_error ("directory `%s' is not searchable", path);
247 else
248 builtin_error ("%s: %s", path, strerror (errno));
249 return 0;
252 return 1;
255 static char *
256 xstrdup (s)
257 char *s;
259 return (savestring (s));
262 /* Make sure that
263 strlen (PATH) <= PATH_MAX
264 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
266 If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
267 _POSIX_NAME_MAX instead, and make sure that PATH contains no
268 characters not in the POSIX portable filename character set, which
269 consists of A-Z, a-z, 0-9, ., _, -.
271 Make sure that all leading directories along PATH that exist have
272 `x' permission.
274 Return 0 if all of these tests are successful, 1 if any fail. */
276 static int
277 validate_path (path, portability)
278 char *path;
279 int portability;
281 int path_max;
282 int last_elem; /* Nonzero if checking last element of path. */
283 int exists; /* 2 if the path element exists. */
284 char *slash;
285 char *parent; /* Last existing leading directory so far. */
287 if (portability && !portable_chars_only (path))
288 return 1;
290 if (*path == '\0')
291 return 0;
293 #ifdef lint
294 /* Suppress `used before initialized' warning. */
295 exists = 0;
296 #endif
298 /* Figure out the parent of the first element in PATH. */
299 parent = xstrdup (*path == '/' ? "/" : ".");
301 slash = path;
302 last_elem = 0;
303 while (1)
305 int name_max;
306 int length; /* Length of partial path being checked. */
307 char *start; /* Start of path element being checked. */
309 /* Find the end of this element of the path.
310 Then chop off the rest of the path after this element. */
311 while (*slash == '/')
312 slash++;
313 start = slash;
314 slash = strchr (slash, '/');
315 if (slash != NULL)
316 *slash = '\0';
317 else
319 last_elem = 1;
320 slash = strchr (start, '\0');
323 if (!last_elem)
325 exists = dir_ok (path);
326 if (dir_ok == 0)
328 free (parent);
329 return 1;
333 length = slash - start;
334 /* Since we know that `parent' is a directory, it's ok to call
335 pathconf with it as the argument. (If `parent' isn't a directory
336 or doesn't exist, the behavior of pathconf is undefined.)
337 But if `parent' is a directory and is on a remote file system,
338 it's likely that pathconf can't give us a reasonable value
339 and will return -1. (NFS and tempfs are not POSIX . . .)
340 In that case, we have no choice but to assume the pessimal
341 POSIX minimums. */
342 name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
343 if (name_max < 0)
344 name_max = _POSIX_NAME_MAX;
345 if (length > name_max)
347 builtin_error ("name `%s' has length %d; exceeds limit of %d",
348 start, length, name_max);
349 free (parent);
350 return 1;
353 if (last_elem)
354 break;
356 if (exists == 1)
358 free (parent);
359 parent = xstrdup (path);
362 *slash++ = '/';
365 /* `parent' is now the last existing leading directory in the whole path,
366 so it's ok to call pathconf with it as the argument. */
367 path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
368 if (path_max < 0)
369 path_max = _POSIX_PATH_MAX;
370 free (parent);
371 if (strlen (path) > path_max)
373 builtin_error ("path `%s' has length %d; exceeds limit of %d",
374 path, strlen (path), path_max);
375 return 1;
378 return 0;