all files: make most variables static and const when possible.
[platform/upstream/coreutils.git] / src / pathchk.c
1 /* pathchk -- check whether pathnames are valid or portable
2    Copyright (C) 1991, 1992 Free Software Foundation, Inc.
3
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.
8
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.
13
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
16    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
17
18 /* Usage: pathchk [-p] [--portability] path...
19
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
24
25    Exit status:
26    0                    All PATH names passed all of the tests.
27    1                    An error occurred.
28
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 characters not in the
36                         portable filename character set.
37
38    David MacKenzie <djm@gnu.ai.mit.edu>
39    and Jim Meyering <meyering@cs.utexas.edu> */
40
41 #include <stdio.h>
42 #include <getopt.h>
43 #include <sys/types.h>
44 #include "system.h"
45
46 #ifdef _POSIX_VERSION
47 #include <limits.h>
48 #ifndef PATH_MAX
49 #define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
50 #endif /* not PATH_MAX */
51 #ifndef NAME_MAX
52 #define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX);
53 #endif /* not NAME_MAX */
54
55 #else /* not _POSIX_VERSION */
56
57 #include <sys/param.h>
58 #ifndef PATH_MAX
59 #ifdef MAXPATHLEN
60 #define PATH_MAX MAXPATHLEN
61 #else /* not MAXPATHLEN */
62 #define PATH_MAX _POSIX_PATH_MAX
63 #endif /* not MAXPATHLEN */
64 #endif /* not PATH_MAX */
65
66 #ifndef NAME_MAX
67 #ifdef MAXNAMLEN
68 #define NAME_MAX MAXNAMLEN
69 #else /* not MAXNAMLEN */
70 #define NAME_MAX _POSIX_NAME_MAX
71 #endif /* not MAXNAMLEN */
72 #endif /* not NAME_MAX */
73
74 #endif /* not _POSIX_VERSION */
75
76 #ifndef _POSIX_PATH_MAX
77 #define _POSIX_PATH_MAX 255
78 #endif
79 #ifndef _POSIX_NAME_MAX
80 #define _POSIX_NAME_MAX 14
81 #endif
82
83 #ifndef PATH_MAX_FOR
84 #define PATH_MAX_FOR(p) PATH_MAX
85 #endif
86 #ifndef NAME_MAX_FOR
87 #define NAME_MAX_FOR(p) NAME_MAX
88 #endif
89
90 char *xstrdup();
91 void error ();
92
93 static int validate_path ();
94 static void usage ();
95
96 /* The name this program was run with. */
97 char *program_name;
98
99 static struct option const longopts[] =
100 {
101   {"portability", 0, NULL, 'p'},
102   {NULL, 0, NULL, 0}
103 };
104
105 void
106 main (argc, argv)
107      int argc;
108      char **argv;
109 {
110   int exit_status = 0;
111   int check_portability = 0;
112   int optc;
113
114   program_name = argv[0];
115
116   while ((optc = getopt_long (argc, argv, "p", longopts, (int *) 0)) != EOF)
117     {
118       switch (optc)
119         {
120         case 'p':
121           check_portability = 1;
122           break;
123         default:
124           usage ();
125         }
126     }
127
128   if (optind == argc)
129     usage ();
130
131   for (; optind < argc; ++optind)
132     exit_status |= validate_path (argv[optind], check_portability);
133
134   exit (exit_status);
135 }
136
137 /* Each element is nonzero if the corresponding ASCII character is
138    in the POSIX portable character set, and zero if it is not.
139    In addition, the entry for `/' is nonzero to simplify checking. */
140 static char const portable_chars[] =
141 {
142   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
143   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
144   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
145   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
146   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
147   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
148   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
149   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
150   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
151   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
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 };
159
160 /* If PATH contains only portable characters, return 1, else 0.  */
161
162 static int
163 portable_chars_only (path)
164      char *path;
165 {
166   char *p;
167
168   for (p = path; *p; ++p)
169     if (portable_chars[(unsigned char)*p] == 0)
170       {
171         error (0, 0, "path `%s' contains nonportable character `%c'",
172                path, *p);
173         return 0;
174       }
175   return 1;
176 }
177
178 /* Return 1 if PATH is a usable leading directory, 0 if not,
179    2 if it doesn't exist.  */
180
181 static int
182 dir_ok (path)
183      char *path;
184 {
185   struct stat stats;
186
187   if (stat (path, &stats))
188     return 2;
189
190   if (!S_ISDIR (stats.st_mode))
191     {
192       error (0, 0, "`%s' is not a directory", path);
193       return 0;
194     }
195
196   /* Use access to test for search permission because
197      testing permission bits of st_mode can lose with new
198      access control mechanisms.  Of course, access loses if you're
199      running setuid. */
200   if (access (path, X_OK) != 0)
201     {
202       if (errno == EACCES)
203         error (0, 0, "directory `%s' is not searchable", path);
204       else
205         error (0, errno, "%s", path);
206       return 0;
207     }
208
209   return 1;
210 }
211
212 /* Make sure that
213    strlen (PATH) <= PATH_MAX
214    && strlen (each-existing-directory-in-PATH) <= NAME_MAX
215
216    If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
217    _POSIX_NAME_MAX instead, and make sure that PATH contains no
218    characters not in the POSIX portable filename character set, which
219    consists of A-Z, a-z, 0-9, ., _, -.
220
221    Make sure that all leading directories along PATH that exist have
222    `x' permission.
223
224    Return 0 if all of these tests are successful, 1 if any fail. */
225
226 static int
227 validate_path (path, portability)
228      char *path;
229      int portability;
230 {
231   int path_max;
232   int last_elem;                /* Nonzero if checking last element of path. */
233   int exists;                   /* 2 if the path element exists.  */
234   char *slash;
235   char *parent;                 /* Last existing leading directory so far.  */
236
237   if (portability && !portable_chars_only (path))
238     return 1;
239
240   if (*path == '\0')
241     return 0;
242
243   /* Figure out the parent of the first element in PATH.  */
244   parent = xstrdup (*path == '/' ? "/" : ".");
245
246   slash = path;
247   last_elem = 0;
248   while (1)
249     {
250       int name_max;
251       int length;               /* Length of partial path being checked. */
252       char *start;              /* Start of path element being checked. */
253
254       /* Find the end of this element of the path.
255          Then chop off the rest of the path after this element. */
256       while (*slash == '/')
257         slash++;
258       start = slash;
259       slash = index (slash, '/');
260       if (slash != NULL)
261         *slash = '\0';
262       else
263         {
264           last_elem = 1;
265           slash = index (start, '\0');
266         }
267
268       if (!last_elem)
269         {
270           exists = dir_ok (path);
271           if (dir_ok == 0)
272             {
273               free (parent);
274               return 1;
275             }
276         }
277
278       length = slash - start;
279       /* Since we know that `parent' is a directory, it's ok to call
280          pathconf with it as the argument.  (If `parent' isn't a directory
281          or doesn't exist, the behavior of pathconf is undefined.)
282          But if `parent' is a directory and is on a remote file system,
283          it's likely that pathconf can't give us a reasonable value
284          and will return -1.  (NFS and tempfs are not POSIX . . .)
285          In that case, we have no choice but to assume the pessimal
286          POSIX minimums.  */
287       name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
288       if (name_max < 0)
289         name_max = _POSIX_NAME_MAX;
290       if (length > name_max)
291         {
292           error (0, 0, "name `%s' has length %d; exceeds limit of %d",
293                  start, length, name_max);
294           free (parent);
295           return 1;
296         }
297
298       if (last_elem)
299         break;
300
301       if (exists == 1)
302         {
303           free (parent);
304           parent = xstrdup (path);
305         }
306
307       *slash++ = '/';
308     }
309
310   /* `parent' is now the last existing leading directory in the whole path,
311      so it's ok to call pathconf with it as the argument.  */
312   path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
313   if (path_max < 0)
314     path_max = _POSIX_PATH_MAX;
315   free (parent);
316   if (strlen (path) > path_max)
317     {
318       error (0, 0, "path `%s' has length %d; exceeds limit of %d",
319              path, strlen (path), path_max);
320       return 1;
321     }
322
323   return 0;
324 }
325
326 static void
327 usage ()
328 {
329   fprintf (stderr, "\
330 Usage: %s [-p] [--portability] path...\n",
331            program_name);
332   exit (1);
333 }