]> git.ipfire.org Git - thirdparty/bash.git/blame - examples/loadables/pathchk.c
Imported from ../bash-2.03.tar.gz.
[thirdparty/bash.git] / examples / loadables / pathchk.c
CommitLineData
ccc6cda3
JA
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#include <config.h>
26
27#include <sys/types.h>
28#include "posixstat.h"
29
30#if defined (HAVE_UNISTD_H)
31# include <unistd.h>
32#endif
33
34#if defined (HAVE_LIMITS_H)
35# include <limits.h>
36#endif
37
38#include "bashansi.h"
39
40#include <stdio.h>
41#include <errno.h>
42
43#include "builtins.h"
44#include "shell.h"
45#include "stdc.h"
46#include "bashgetopt.h"
47#include "maxpath.h"
48
49#if !defined (errno)
50extern int errno;
51#endif
52
53#if !defined (_POSIX_PATH_MAX)
54# define _POSIX_PATH_MAX 255
55#endif
56#if !defined (_POSIX_NAME_MAX)
57# define _POSIX_NAME_MAX 14
58#endif
59
60/* How do we get PATH_MAX? */
61#if defined (_POSIX_VERSION) && !defined (PATH_MAX)
62# define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
63#endif
64
65/* How do we get NAME_MAX? */
66#if defined (_POSIX_VERSION) && !defined (NAME_MAX)
67# define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX)
68#endif
69
70#if !defined (PATH_MAX_FOR)
71# define PATH_MAX_FOR(p) PATH_MAX
72#endif
73
74#if !defined (NAME_MAX_FOR)
75# define NAME_MAX_FOR(p) NAME_MAX
76#endif
77
78extern char *strerror ();
79
80static int validate_path ();
81
82pathchk_builtin (list)
83 WORD_LIST *list;
84{
85 int retval, pflag, opt;
86
87 reset_internal_getopt ();
88 while ((opt = internal_getopt (list, "p")) != -1)
89 {
90 switch (opt)
91 {
92 case 'p':
93 pflag = 1;
94 break;
95 default:
96 builtin_usage ();
97 return (EX_USAGE);
98 }
99 }
100 list = loptend;
101
102 if (list == 0)
103 {
104 builtin_usage ();
105 return (EX_USAGE);
106 }
107
108 for (retval = 0; list; list = list->next)
109 retval |= validate_path (list->word->word, pflag);
110
111 return (retval ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
112}
113
114char *pathchk_doc[] = {
115 "Check each pathname argument for validity (i.e., it may be used to",
116 "create or access a file without casuing syntax errors) and portability",
117 "(i.e., no filename truncation will result). If the `-p' option is",
118 "supplied, more extensive portability checks are performed.",
119 (char *)NULL
120};
121
122/* The standard structure describing a builtin command. bash keeps an array
123 of these structures. */
124struct builtin pathchk_struct = {
125 "pathchk", /* builtin name */
126 pathchk_builtin, /* function implementing the builtin */
127 BUILTIN_ENABLED, /* initial flags for builtin */
128 pathchk_doc, /* array of long documentation strings. */
129 "pathchk [-p] pathname ...", /* usage synopsis */
130 0 /* reserved for internal use */
131};
132
133/* The remainder of this file is stolen shamelessly from `pathchk.c' in
134 the sh-utils-1.12 distribution, by
135
136 David MacKenzie <djm@gnu.ai.mit.edu>
137 and Jim Meyering <meyering@cs.utexas.edu> */
138
139/* Each element is nonzero if the corresponding ASCII character is
140 in the POSIX portable character set, and zero if it is not.
141 In addition, the entry for `/' is nonzero to simplify checking. */
142static char const portable_chars[256] =
143{
144 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
145 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
146 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
147 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
148 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
149 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
150 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
151 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
152 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
153 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
154 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
155 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
156 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
157 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
158 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
159 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
160};
161
162/* If PATH contains only portable characters, return 1, else 0. */
163
164static int
165portable_chars_only (path)
166 const char *path;
167{
168 const char *p;
169
170 for (p = path; *p; ++p)
171 if (portable_chars[(const unsigned char) *p] == 0)
172 {
b72432fd 173 builtin_error ("path `%s' contains nonportable character `%c'", path, *p);
ccc6cda3
JA
174 return 0;
175 }
176 return 1;
177}
178
179/* On some systems, stat can return EINTR. */
180
181#ifndef EINTR
182# define SAFE_STAT(name, buf) stat (name, buf)
183#else
184# define SAFE_STAT(name, buf) safe_stat (name, buf)
185static inline int
186safe_stat (name, buf)
187 const char *name;
188 struct stat *buf;
189{
190 int ret;
191
192 do
193 ret = stat (name, buf);
194 while (ret < 0 && errno == EINTR);
195
196 return ret;
197}
198#endif
199
200/* Return 1 if PATH is a usable leading directory, 0 if not,
201 2 if it doesn't exist. */
202
203static int
204dir_ok (path)
205 const char *path;
206{
207 struct stat stats;
208
209 if (SAFE_STAT (path, &stats))
210 return 2;
211
212 if (!S_ISDIR (stats.st_mode))
213 {
b72432fd 214 builtin_error ("`%s' is not a directory", path);
ccc6cda3
JA
215 return 0;
216 }
217
218 /* Use access to test for search permission because
219 testing permission bits of st_mode can lose with new
220 access control mechanisms. Of course, access loses if you're
221 running setuid. */
222 if (access (path, X_OK) != 0)
223 {
224 if (errno == EACCES)
225 builtin_error ("directory `%s' is not searchable", path);
226 else
227 builtin_error ("%s: %s", path, strerror (errno));
228 return 0;
229 }
230
231 return 1;
232}
233
234static char *
235xstrdup (s)
236 char *s;
237{
238 return (savestring (s));
239}
240
241/* Make sure that
242 strlen (PATH) <= PATH_MAX
243 && strlen (each-existing-directory-in-PATH) <= NAME_MAX
244
245 If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
246 _POSIX_NAME_MAX instead, and make sure that PATH contains no
247 characters not in the POSIX portable filename character set, which
248 consists of A-Z, a-z, 0-9, ., _, -.
249
250 Make sure that all leading directories along PATH that exist have
251 `x' permission.
252
253 Return 0 if all of these tests are successful, 1 if any fail. */
254
255static int
256validate_path (path, portability)
257 char *path;
258 int portability;
259{
260 int path_max;
261 int last_elem; /* Nonzero if checking last element of path. */
262 int exists; /* 2 if the path element exists. */
263 char *slash;
264 char *parent; /* Last existing leading directory so far. */
265
266 if (portability && !portable_chars_only (path))
267 return 1;
268
269 if (*path == '\0')
270 return 0;
271
272#ifdef lint
273 /* Suppress `used before initialized' warning. */
274 exists = 0;
275#endif
276
277 /* Figure out the parent of the first element in PATH. */
278 parent = xstrdup (*path == '/' ? "/" : ".");
279
280 slash = path;
281 last_elem = 0;
282 while (1)
283 {
284 int name_max;
285 int length; /* Length of partial path being checked. */
286 char *start; /* Start of path element being checked. */
287
288 /* Find the end of this element of the path.
289 Then chop off the rest of the path after this element. */
290 while (*slash == '/')
291 slash++;
292 start = slash;
293 slash = strchr (slash, '/');
294 if (slash != NULL)
295 *slash = '\0';
296 else
297 {
298 last_elem = 1;
299 slash = strchr (start, '\0');
300 }
301
302 if (!last_elem)
303 {
304 exists = dir_ok (path);
305 if (dir_ok == 0)
306 {
307 free (parent);
308 return 1;
309 }
310 }
311
312 length = slash - start;
313 /* Since we know that `parent' is a directory, it's ok to call
314 pathconf with it as the argument. (If `parent' isn't a directory
315 or doesn't exist, the behavior of pathconf is undefined.)
316 But if `parent' is a directory and is on a remote file system,
317 it's likely that pathconf can't give us a reasonable value
318 and will return -1. (NFS and tempfs are not POSIX . . .)
319 In that case, we have no choice but to assume the pessimal
320 POSIX minimums. */
321 name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
322 if (name_max < 0)
323 name_max = _POSIX_NAME_MAX;
324 if (length > name_max)
325 {
b72432fd 326 builtin_error ("name `%s' has length %d; exceeds limit of %d",
ccc6cda3
JA
327 start, length, name_max);
328 free (parent);
329 return 1;
330 }
331
332 if (last_elem)
333 break;
334
335 if (exists == 1)
336 {
337 free (parent);
338 parent = xstrdup (path);
339 }
340
341 *slash++ = '/';
342 }
343
344 /* `parent' is now the last existing leading directory in the whole path,
345 so it's ok to call pathconf with it as the argument. */
346 path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
347 if (path_max < 0)
348 path_max = _POSIX_PATH_MAX;
349 free (parent);
350 if (strlen (path) > path_max)
351 {
b72432fd 352 builtin_error ("path `%s' has length %d; exceeds limit of %d",
ccc6cda3
JA
353 path, strlen (path), path_max);
354 return 1;
355 }
356
357 return 0;
358}