Make file-scope variable static.
[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 int validate_path ();
92 void error ();
93 void usage ();
94
95 /* The name this program was run with. */
96 char *program_name;
97
98 struct option longopts[] =
99 {
100   {"portability", 0, NULL, 'p'},
101   {NULL, 0, NULL, 0}
102 };
103
104 void
105 main (argc, argv)
106      int argc;
107      char **argv;
108 {
109   int exit_status = 0;
110   int check_portability = 0;
111   int optc;
112
113   program_name = argv[0];
114
115   while ((optc = getopt_long (argc, argv, "p", longopts, (int *) 0)) != EOF)
116     {
117       switch (optc)
118         {
119         case 'p':
120           check_portability = 1;
121           break;
122         default:
123           usage ();
124         }
125     }
126
127   if (optind == argc)
128     usage ();
129
130   for (; optind < argc; ++optind)
131     exit_status |= validate_path (argv[optind], check_portability);
132
133   exit (exit_status);
134 }
135
136 /* Each element is nonzero if the corresponding ASCII character is
137    in the POSIX portable character set, and zero if it is not.
138    In addition, the entry for `/' is nonzero to simplify checking. */
139 static char portable_chars[] =
140 {
141   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
142   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
143   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
144   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
145   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
146   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
147   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
148   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
149   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
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 };
158
159 /* If PATH contains only portable characters, return 1, else 0.  */
160
161 int
162 portable_chars_only (path)
163      char *path;
164 {
165   char *p;
166
167   for (p = path; *p; ++p)
168     if (portable_chars[*p] == 0)
169       {
170         error (0, 0, "path `%s' contains nonportable character `%c'",
171                path, *p);
172         return 0;
173       }
174   return 1;
175 }
176
177 /* Return 1 if PATH is a usable leading directory, 0 if not,
178    2 if it doesn't exist.  */
179
180 int
181 dir_ok (path)
182      char *path;
183 {
184   struct stat stats;
185
186   if (stat (path, &stats))
187     return 2;
188
189   if (!S_ISDIR (stats.st_mode))
190     {
191       error (0, 0, "`%s' is not a directory", path);
192       return 0;
193     }
194
195   /* Use access to test for search permission because
196      testing permission bits of st_mode can lose with new
197      access control mechanisms.  Of course, access loses if you're
198      running setuid. */
199   if (access (path, X_OK) != 0)
200     {
201       if (errno == EACCES)
202         error (0, 0, "directory `%s' is not searchable", path);
203       else
204         error (0, errno, "%s", path);
205       return 0;
206     }
207
208   return 1;
209 }
210
211 /* Make sure that
212    strlen (PATH) <= PATH_MAX
213    && strlen (each-existing-directory-in-PATH) <= NAME_MAX
214
215    If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
216    _POSIX_NAME_MAX instead, and make sure that PATH contains no
217    characters not in the POSIX portable filename character set, which
218    consists of A-Z, a-z, 0-9, ., _, -.
219
220    Make sure that all leading directories along PATH that exist have
221    `x' permission.
222
223    Return 0 if all of these tests are successful, 1 if any fail. */
224
225 int
226 validate_path (path, portability)
227      char *path;
228      int portability;
229 {
230   int path_max;
231   int last_elem;                /* Nonzero if checking last element of path. */
232   int exists;                   /* 2 if the path element exists.  */
233   char *slash;
234   char *parent;                 /* Last existing leading directory so far.  */
235
236   if (portability && !portable_chars_only (path))
237     return 1;
238
239   if (*path == '\0')
240     return 0;
241
242   /* Figure out the parent of the first element in PATH.  */
243   parent = xstrdup (*path == '/' ? "/" : ".");
244
245   slash = path;
246   last_elem = 0;
247   while (1)
248     {
249       int name_max;
250       int length;               /* Length of partial path being checked. */
251       char *start;              /* Start of path element being checked. */
252
253       /* Find the end of this element of the path.
254          Then chop off the rest of the path after this element. */
255       while (*slash == '/')
256         slash++;
257       start = slash;
258       slash = index (slash, '/');
259       if (slash != NULL)
260         *slash = '\0';
261       else
262         {
263           last_elem = 1;
264           slash = index (start, '\0');
265         }
266
267       if (!last_elem)
268         {
269           exists = dir_ok (path);
270           if (dir_ok == 0)
271             {
272               free (parent);
273               return 1;
274             }
275         }
276
277       length = slash - start;
278       /* Since we know that `parent' is a directory, it's ok to call
279          pathconf with it as the argument.  (If `parent' isn't a directory
280          or doesn't exist, the behavior of pathconf is undefined.)
281          But if `parent' is a directory and is on a remote file system,
282          it's likely that pathconf can't give us a reasonable value
283          and will return -1.  (NFS and tempfs are not POSIX . . .)
284          In that case, we have no choice but to assume the pessimal
285          POSIX minimums.  */
286       name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
287       if (name_max < 0)
288         name_max = _POSIX_NAME_MAX;
289       if (length > name_max)
290         {
291           error (0, 0, "name `%s' has length %d; exceeds limit of %d",
292                  start, length, name_max);
293           free (parent);
294           return 1;
295         }
296
297       if (last_elem)
298         break;
299
300       if (exists == 1)
301         {
302           free (parent);
303           parent = xstrdup (path);
304         }
305
306       *slash++ = '/';
307     }
308
309   /* `parent' is now the last existing leading directory in the whole path,
310      so it's ok to call pathconf with it as the argument.  */
311   path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
312   if (path_max < 0)
313     path_max = _POSIX_PATH_MAX;
314   free (parent);
315   if (strlen (path) > path_max)
316     {
317       error (0, 0, "path `%s' has length %d; exceeds limit of %d",
318              path, strlen (path), path_max);
319       return 1;
320     }
321
322   return 0;
323 }
324
325 void
326 usage ()
327 {
328   fprintf (stderr, "\
329 Usage: %s [-p] [--portability] path...\n",
330            program_name);
331   exit (1);
332 }