(main): Initialize for internationalized message support:
[platform/upstream/coreutils.git] / src / pathchk.c
1 /* pathchk -- check whether pathnames are valid or portable
2    Copyright (C) 91, 92, 93, 94, 1995 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 character 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 <config.h>
42 #include <stdio.h>
43 #include <getopt.h>
44 #include <sys/types.h>
45
46 #include "version.h"
47 #include "system.h"
48 #include "error.h"
49
50 #ifdef _POSIX_VERSION
51 #include <limits.h>
52 #ifndef PATH_MAX
53 #define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
54 #endif /* not PATH_MAX */
55 #ifndef NAME_MAX
56 #define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX);
57 #endif /* not NAME_MAX */
58
59 #else /* not _POSIX_VERSION */
60
61 #include <sys/param.h>
62 #ifndef PATH_MAX
63 #ifdef MAXPATHLEN
64 #define PATH_MAX MAXPATHLEN
65 #else /* not MAXPATHLEN */
66 #define PATH_MAX _POSIX_PATH_MAX
67 #endif /* not MAXPATHLEN */
68 #endif /* not PATH_MAX */
69
70 #ifndef NAME_MAX
71 #ifdef MAXNAMLEN
72 #define NAME_MAX MAXNAMLEN
73 #else /* not MAXNAMLEN */
74 #define NAME_MAX _POSIX_NAME_MAX
75 #endif /* not MAXNAMLEN */
76 #endif /* not NAME_MAX */
77
78 #endif /* not _POSIX_VERSION */
79
80 #ifndef _POSIX_PATH_MAX
81 #define _POSIX_PATH_MAX 255
82 #endif
83 #ifndef _POSIX_NAME_MAX
84 #define _POSIX_NAME_MAX 14
85 #endif
86
87 #ifndef PATH_MAX_FOR
88 #define PATH_MAX_FOR(p) PATH_MAX
89 #endif
90 #ifndef NAME_MAX_FOR
91 #define NAME_MAX_FOR(p) NAME_MAX
92 #endif
93
94 char *xstrdup ();
95
96 static int validate_path __P ((char *path, int portability));
97 static void usage __P ((int status));
98
99 /* The name this program was run with. */
100 char *program_name;
101
102 /* If nonzero, display usage information and exit.  */
103 static int show_help;
104
105 /* If nonzero, print the version on standard output and exit.  */
106 static int show_version;
107
108 static struct option const longopts[] =
109 {
110   {"help", no_argument, &show_help, 1},
111   {"portability", no_argument, NULL, 'p'},
112   {"version", no_argument, &show_version, 1},
113   {NULL, 0, NULL, 0}
114 };
115
116 void
117 main (int argc, char **argv)
118 {
119   int exit_status = 0;
120   int check_portability = 0;
121   int optc;
122
123   program_name = argv[0];
124   setlocale (LC_ALL, "");
125   bindtextdomain (PACKAGE, LOCALEDIR);
126   textdomain (PACKAGE);
127
128   while ((optc = getopt_long (argc, argv, "p", longopts, (int *) 0)) != EOF)
129     {
130       switch (optc)
131         {
132         case 0:
133           break;
134
135         case 'p':
136           check_portability = 1;
137           break;
138
139         default:
140           usage (1);
141         }
142     }
143
144   if (show_version)
145     {
146       printf ("pathchk - %s\n", version_string);
147       exit (0);
148     }
149
150   if (show_help)
151     usage (0);
152
153   if (optind == argc)
154     {
155       error (0, 0, _("too few arguments"));
156       usage (1);
157     }
158
159   for (; optind < argc; ++optind)
160     exit_status |= validate_path (argv[optind], check_portability);
161
162   exit (exit_status);
163 }
164
165 /* Each element is nonzero if the corresponding ASCII character is
166    in the POSIX portable character set, and zero if it is not.
167    In addition, the entry for `/' is nonzero to simplify checking. */
168 static char const portable_chars[256] =
169 {
170   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
171   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
172   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
173   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
174   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
175   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
176   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
177   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
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   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
182   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
183   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
184   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
185   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
186 };
187
188 /* If PATH contains only portable characters, return 1, else 0.  */
189
190 static int
191 portable_chars_only (const char *path)
192 {
193   const char *p;
194
195   for (p = path; *p; ++p)
196     if (portable_chars[(const unsigned char) *p] == 0)
197       {
198         error (0, 0, _("path `%s' contains nonportable character `%c'"),
199                path, *p);
200         return 0;
201       }
202   return 1;
203 }
204
205 /* Return 1 if PATH is a usable leading directory, 0 if not,
206    2 if it doesn't exist.  */
207
208 static int
209 dir_ok (const char *path)
210 {
211   struct stat stats;
212
213   if (stat (path, &stats))
214     return 2;
215
216   if (!S_ISDIR (stats.st_mode))
217     {
218       error (0, 0, _("`%s' is not a directory"), path);
219       return 0;
220     }
221
222   /* Use access to test for search permission because
223      testing permission bits of st_mode can lose with new
224      access control mechanisms.  Of course, access loses if you're
225      running setuid. */
226   if (access (path, X_OK) != 0)
227     {
228       if (errno == EACCES)
229         error (0, 0, _("directory `%s' is not searchable"), path);
230       else
231         error (0, errno, "%s", path);
232       return 0;
233     }
234
235   return 1;
236 }
237
238 /* Make sure that
239    strlen (PATH) <= PATH_MAX
240    && strlen (each-existing-directory-in-PATH) <= NAME_MAX
241
242    If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
243    _POSIX_NAME_MAX instead, and make sure that PATH contains no
244    characters not in the POSIX portable filename character set, which
245    consists of A-Z, a-z, 0-9, ., _, -.
246
247    Make sure that all leading directories along PATH that exist have
248    `x' permission.
249
250    Return 0 if all of these tests are successful, 1 if any fail. */
251
252 static int
253 validate_path (char *path, int portability)
254 {
255   int path_max;
256   int last_elem;                /* Nonzero if checking last element of path. */
257   int exists;                   /* 2 if the path element exists.  */
258   char *slash;
259   char *parent;                 /* Last existing leading directory so far.  */
260
261   if (portability && !portable_chars_only (path))
262     return 1;
263
264   if (*path == '\0')
265     return 0;
266
267 #ifdef lint
268   /* Suppress `used before initialized' warning.  */
269   exists = 0;
270 #endif
271
272   /* Figure out the parent of the first element in PATH.  */
273   parent = xstrdup (*path == '/' ? "/" : ".");
274
275   slash = path;
276   last_elem = 0;
277   while (1)
278     {
279       int name_max;
280       int length;               /* Length of partial path being checked. */
281       char *start;              /* Start of path element being checked. */
282
283       /* Find the end of this element of the path.
284          Then chop off the rest of the path after this element. */
285       while (*slash == '/')
286         slash++;
287       start = slash;
288       slash = strchr (slash, '/');
289       if (slash != NULL)
290         *slash = '\0';
291       else
292         {
293           last_elem = 1;
294           slash = strchr (start, '\0');
295         }
296
297       if (!last_elem)
298         {
299           exists = dir_ok (path);
300           if (dir_ok == 0)
301             {
302               free (parent);
303               return 1;
304             }
305         }
306
307       length = slash - start;
308       /* Since we know that `parent' is a directory, it's ok to call
309          pathconf with it as the argument.  (If `parent' isn't a directory
310          or doesn't exist, the behavior of pathconf is undefined.)
311          But if `parent' is a directory and is on a remote file system,
312          it's likely that pathconf can't give us a reasonable value
313          and will return -1.  (NFS and tempfs are not POSIX . . .)
314          In that case, we have no choice but to assume the pessimal
315          POSIX minimums.  */
316       name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
317       if (name_max < 0)
318         name_max = _POSIX_NAME_MAX;
319       if (length > name_max)
320         {
321           error (0, 0, _("name `%s' has length %d; exceeds limit of %d"),
322                  start, length, name_max);
323           free (parent);
324           return 1;
325         }
326
327       if (last_elem)
328         break;
329
330       if (exists == 1)
331         {
332           free (parent);
333           parent = xstrdup (path);
334         }
335
336       *slash++ = '/';
337     }
338
339   /* `parent' is now the last existing leading directory in the whole path,
340      so it's ok to call pathconf with it as the argument.  */
341   path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
342   if (path_max < 0)
343     path_max = _POSIX_PATH_MAX;
344   free (parent);
345   if (strlen (path) > path_max)
346     {
347       error (0, 0, _("path `%s' has length %d; exceeds limit of %d"),
348              path, strlen (path), path_max);
349       return 1;
350     }
351
352   return 0;
353 }
354
355 static void
356 usage (int status)
357 {
358   if (status != 0)
359     fprintf (stderr, _("Try `%s --help' for more information.\n"),
360              program_name);
361   else
362     {
363       printf (_("Usage: %s [OPTION]... NAME...\n"), program_name);
364       printf (_("\
365 Diagnose unportable constructs in NAME.\n\
366 \n\
367   -p, --portability   check for all POSIX systems, not only this one\n\
368       --help          display this help and exit\n\
369       --version       output version information and exit\n\
370 "));
371     }
372   exit (status);
373 }