]> git.ipfire.org Git - thirdparty/bash.git/blob - examples/loadables/pathchk.c
Imported from ../bash-4.0-rc1.tar.gz.
[thirdparty/bash.git] / examples / loadables / pathchk.c
1 /* pathchk - check pathnames for validity and portability */
2
3 /* Usage: pathchk [-p] path ...
4
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
9
10 Exit status:
11 0 All PATH names passed all of the tests.
12 1 An error occurred.
13
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. */
22
23 /* See Makefile for compilation details. */
24
25 /*
26 Copyright (C) 1999-2009 Free Software Foundation, Inc.
27
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.
33
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.
38
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/>.
41 */
42
43 #include <config.h>
44
45 #include <sys/types.h>
46 #include "posixstat.h"
47
48 #if defined (HAVE_UNISTD_H)
49 # include <unistd.h>
50 #endif
51
52 #if defined (HAVE_LIMITS_H)
53 # include <limits.h>
54 #endif
55
56 #include "bashansi.h"
57
58 #include <stdio.h>
59 #include <errno.h>
60
61 #include "builtins.h"
62 #include "shell.h"
63 #include "stdc.h"
64 #include "bashgetopt.h"
65 #include "maxpath.h"
66 #include "common.h"
67
68 #if !defined (errno)
69 extern int errno;
70 #endif
71
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
78
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
83
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
88
89 #if !defined (PATH_MAX_FOR)
90 # define PATH_MAX_FOR(p) PATH_MAX
91 #endif
92
93 #if !defined (NAME_MAX_FOR)
94 # define NAME_MAX_FOR(p) NAME_MAX
95 #endif
96
97 extern char *strerror ();
98
99 static int validate_path ();
100
101 pathchk_builtin (list)
102 WORD_LIST *list;
103 {
104 int retval, pflag, opt;
105
106 reset_internal_getopt ();
107 while ((opt = internal_getopt (list, "p")) != -1)
108 {
109 switch (opt)
110 {
111 case 'p':
112 pflag = 1;
113 break;
114 default:
115 builtin_usage ();
116 return (EX_USAGE);
117 }
118 }
119 list = loptend;
120
121 if (list == 0)
122 {
123 builtin_usage ();
124 return (EX_USAGE);
125 }
126
127 for (retval = 0; list; list = list->next)
128 retval |= validate_path (list->word->word, pflag);
129
130 return (retval ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
131 }
132
133 char *pathchk_doc[] = {
134 "Check pathnames for validity.",
135 "",
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
141 };
142
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 */
152 };
153
154 /* The remainder of this file is stolen shamelessly from `pathchk.c' in
155 the sh-utils-1.12 distribution, by
156
157 David MacKenzie <djm@gnu.ai.mit.edu>
158 and Jim Meyering <meyering@cs.utexas.edu> */
159
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] =
164 {
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
181 };
182
183 /* If PATH contains only portable characters, return 1, else 0. */
184
185 static int
186 portable_chars_only (path)
187 const char *path;
188 {
189 const char *p;
190
191 for (p = path; *p; ++p)
192 if (portable_chars[(const unsigned char) *p] == 0)
193 {
194 builtin_error ("path `%s' contains nonportable character `%c'", path, *p);
195 return 0;
196 }
197 return 1;
198 }
199
200 /* On some systems, stat can return EINTR. */
201
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;
210 {
211 int ret;
212
213 do
214 ret = stat (name, buf);
215 while (ret < 0 && errno == EINTR);
216
217 return ret;
218 }
219 #endif
220
221 /* Return 1 if PATH is a usable leading directory, 0 if not,
222 2 if it doesn't exist. */
223
224 static int
225 dir_ok (path)
226 const char *path;
227 {
228 struct stat stats;
229
230 if (SAFE_STAT (path, &stats))
231 return 2;
232
233 if (!S_ISDIR (stats.st_mode))
234 {
235 builtin_error ("`%s' is not a directory", path);
236 return 0;
237 }
238
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)
244 {
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;
250 }
251
252 return 1;
253 }
254
255 static char *
256 xstrdup (s)
257 char *s;
258 {
259 return (savestring (s));
260 }
261
262 /* Make sure that
263 strlen (PATH) <= PATH_MAX
264 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
265
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, ., _, -.
270
271 Make sure that all leading directories along PATH that exist have
272 `x' permission.
273
274 Return 0 if all of these tests are successful, 1 if any fail. */
275
276 static int
277 validate_path (path, portability)
278 char *path;
279 int portability;
280 {
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. */
286
287 if (portability && !portable_chars_only (path))
288 return 1;
289
290 if (*path == '\0')
291 return 0;
292
293 #ifdef lint
294 /* Suppress `used before initialized' warning. */
295 exists = 0;
296 #endif
297
298 /* Figure out the parent of the first element in PATH. */
299 parent = xstrdup (*path == '/' ? "/" : ".");
300
301 slash = path;
302 last_elem = 0;
303 while (1)
304 {
305 int name_max;
306 int length; /* Length of partial path being checked. */
307 char *start; /* Start of path element being checked. */
308
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
318 {
319 last_elem = 1;
320 slash = strchr (start, '\0');
321 }
322
323 if (!last_elem)
324 {
325 exists = dir_ok (path);
326 if (dir_ok == 0)
327 {
328 free (parent);
329 return 1;
330 }
331 }
332
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)
346 {
347 builtin_error ("name `%s' has length %d; exceeds limit of %d",
348 start, length, name_max);
349 free (parent);
350 return 1;
351 }
352
353 if (last_elem)
354 break;
355
356 if (exists == 1)
357 {
358 free (parent);
359 parent = xstrdup (path);
360 }
361
362 *slash++ = '/';
363 }
364
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)
372 {
373 builtin_error ("path `%s' has length %d; exceeds limit of %d",
374 path, strlen (path), path_max);
375 return 1;
376 }
377
378 return 0;
379 }