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