xref: /illumos-gate/usr/src/cmd/pathchk/pathchk.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  *
24  *  Copyright (c) 1994 by Sun Microsystems, Inc.
25  */
26 
27 /*
28  * Copyright 1991, 1992 by Mortice Kern Systems Inc.  All rights reserved.
29  *
30  * Standards Conformance :
31  *      P1003.2/D11.2
32  *
33  */
34 #pragma ident	"%Z%%M%	%I%	%E% SMI"
35 /*
36  * Original ident string for reference
37  * ident	"$Id: pathchk.c,v 1.29 1994/05/24 15:51:19 mark Exp $"
38  */
39 
40 #include <locale.h>
41 #include <libintl.h>
42 #include <limits.h>
43 #include <sys/stat.h>
44 #include <fcntl.h>		/* for creat() prototype */
45 #include <string.h>
46 #include <errno.h>
47 #include <stdlib.h>
48 #include <stdio.h>
49 #include <ctype.h>
50 #include <unistd.h>
51 #include <stdlib.h>
52 
53 /*
54  * These are the characters in the portable filename character set defined
55  * in POSIX P1003.2.
56  */
57 static	char	portfsset[] = \
58 	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-";
59 
60 
61 #ifndef M_FSDELIM
62 #define	M_FSDELIM(c)	((c) == '/')
63 #endif
64 
65 static char *nametoolong = "%s: component too long.\n";
66 static char *pathtoolong = "%s: pathname too long.\n";
67 static char *notsrch = "%s: Not searchable.\n";
68 static char *badchar = "%s: Nonportable character '%c' (%#02X) found.\n";
69 static char *badbyte = "%s: Nonportable byte %#02X found.\n";
70 
71 static char *pathconfprob = "pathchk: warning: \
72 			    pathconf(\"%s\", %s) returns '%s'. Using %s = %d\n";
73 
74 
75 static int printWarnings = 1;
76 
77 static int checkpathname(char *, int);
78 static void usage(void);
79 
80 /*
81  * mainline for pathchk
82  */
83 int
84 main(int argc, char **argv)
85 {
86 	int c;
87 	int errors;
88 	int pflag = 0;
89 
90 	(void) setlocale(LC_ALL, "");
91 #if !defined(TEXT_DOMAIN)
92 #define TEXT_DOMAIN "SYS_TEST"
93 #endif
94 	(void) textdomain(TEXT_DOMAIN);
95 
96 
97 	while ((c = getopt(argc, argv, "pw")) != EOF) {
98 		switch (c) {
99 		case 'p':
100 			pflag = 1;
101 			break;
102 
103 		case 'w':
104 			/* turn off warning messages */
105 			printWarnings = 0;
106 			break;
107 
108 		default:
109 			usage();
110 		}
111 	}
112 
113 	argv += optind;
114 
115 	if (*argv == 0) {
116 		usage();
117 		/* NOTREACHED */
118 	}
119 
120 	errors = 0;
121 	while (*argv) {
122 		errors += checkpathname(*argv, pflag);
123 		argv += 1;
124 	}
125 
126 	return (errors);
127 }
128 
129 /*
130  * checkPathConf(const char *, int, long *)
131  *
132  * Calls pathconf(), and returns 1 if pathconf failed, zero
133  * otherwise.  If pathconf() succeeded, then *valp contains the
134  * value returned
135  */
136 static int
137 checkPathConf(const char *path, int type, long *valp)
138 {
139 	errno = 0;
140 	*valp = pathconf(path, type);
141 	if ((*valp == -1) && (errno != 0) && (errno != EACCES)) {
142 		/*
143 		 * pathconf() is not supported on some mounted filesystems
144 		 * (e.g NFS mounts) and pathconf() is known to fail.
145 		 * So, we print a warning and use the POSIX default values.
146 		 */
147 		if (type == _PC_PATH_MAX)
148 			*valp = _POSIX_PATH_MAX;
149 		else
150 			*valp = _POSIX_NAME_MAX;
151 
152 		if (printWarnings) {
153 			(void) fprintf(stderr, gettext(pathconfprob), path,
154 				type == _PC_PATH_MAX?"_PC_PATH_MAX" :
155 				    "_PC_NAME_MAX", strerror(errno),
156 				type == _PC_PATH_MAX ? "PATH_MAX" : "NAME_MAX",
157 				    *valp);
158 		}
159 	}
160 	return ((*valp == -1) && (errno != 0));
161 }
162 
163 
164 #define	UPDATE_LIMITS(buf)\
165 {\
166 	if (pflag) {\
167 		nameMax = _POSIX_NAME_MAX;\
168 		pathMax = _POSIX_PATH_MAX;\
169 	} else if (checkPathConf((buf), _PC_PATH_MAX, &pathMax) || \
170 	    checkPathConf((buf), _PC_NAME_MAX, &nameMax)) {\
171 		(void) fprintf(stderr, gettext(notsrch), buf);\
172 		return (1);\
173 	}\
174 }
175 
176 /*
177  * checkpathname(char *pname)
178  * pathchk a single pathname.
179  */
180 int
181 checkpathname(char *path, int pflag)
182 {
183 	int		checkStat;
184 	long		nameMax;
185 	long		pathMax;
186 	char		*scomp;
187 	char		*ecomp;
188 	register char	*p;
189 
190 	p = path;
191 	checkStat = 1;
192 
193 	/*
194 	 * Get the initial NAME_MAX and PATH_MAX values
195 	 */
196 	if (M_FSDELIM(*p)) {
197 		char buf[2];
198 
199 		buf[0] = *p;
200 		buf[1] = '\0';
201 
202 		UPDATE_LIMITS(buf);
203 	} else {
204 		/*
205 		 * This is a relative pathname, initial values
206 		 * are relative to the current directory
207 		 */
208 		UPDATE_LIMITS(".");
209 	}
210 
211 	/*
212 	 * Check to make sure that the pathname doesn't exceed the
213 	 * current PATH_MAX
214 	 */
215 	if (pathMax != -1 && strlen(p) > (size_t)pathMax) {
216 		(void) fprintf(stderr, gettext(pathtoolong), path);
217 		return (1);
218 	}
219 
220 
221 	/*
222 	 * Now spin around checking all the prefixes of
223 	 * the pathname, until we hit the end of the
224 	 * argument
225 	 */
226 	while (*p != '\0') {
227 		/*
228 		 * Find the beginning of the next
229 		 * component.  Assume that
230 		 * M_FSDELIM('\0') == 0
231 		 */
232 		while (M_FSDELIM(*p))
233 			p += 1;
234 
235 		if (*p == '\0') {
236 			/*
237 			 * There were trailing fsdelim chars on
238 			 * the path provided, so we were
239 			 * finished, we just didn't know it.
240 			 */
241 			return (0);
242 		}
243 
244 		scomp = p;
245 
246 		/*
247 		 * Find the end of the current component
248 		 * and check for valid characters in the component
249 		 */
250 		 while (*p != '\0' && !M_FSDELIM(*p)) {
251 			/*
252 			 * for pflag: check for PFCS characters
253 			 * otherwise assume all characters are valid
254 			 */
255 			if (pflag && (strchr(portfsset, *p) == 0)) {
256 				if (isprint(*p)) {
257 					(void) fprintf(stderr,
258 					    gettext(badchar), path, *p, *p);
259 				} else {
260 					(void) fprintf(stderr,
261 					    gettext(badbyte), path, *p);
262 				}
263 				return (1);
264 			}
265 			p += 1;
266 		 }
267 
268 		ecomp = p;
269 
270 		/*
271 		 * Make sure that this component does not exceed
272 		 * NAME_MAX in the current prefix directory
273 		 */
274 		if ((nameMax != -1) && (ecomp - scomp > nameMax)) {
275 			(void) fprintf(stderr, gettext(nametoolong), scomp);
276 			return (1);
277 		} else if (!pflag && checkStat) {
278 			/*
279 			 * Perform the extra checks that
280 			 * are required when not just
281 			 * checking for portability.
282 			 */
283 			 struct stat sb;
284 			 char fsdelim;
285 
286 			 fsdelim = *ecomp;
287 			 *ecomp = '\0';
288 
289 			 if (stat(path, &sb) == -1) {
290 				/*
291 				 * We error out if an
292 				 * intermediate component
293 				 * is a file, when we
294 				 * were expecting a
295 				 * directory, or it is an
296 				 * unsearchable directory.
297 				 */
298 				if ((errno == ENOTDIR && fsdelim != '\0') ||
299 				    (errno == EACCES)) {
300 					(void) fprintf(stderr, gettext(notsrch),
301 						path);
302 					return (1);
303 				} else if (errno == ENOENT) {
304 					checkStat = 0;
305 				}
306 			 } else if (S_ISDIR(sb.st_mode)) {
307 				/*
308 				 * If the current prefix is a
309 				 * directory, then we need to
310 				 * update the limits for NAME_MAX
311 				 * for the next component and the suffix.
312 				 */
313 				if (checkPathConf(path, _PC_NAME_MAX,
314 				    &nameMax)) {
315 					(void) fprintf(stderr,
316 					    gettext(notsrch), path);
317 					return (1);
318 				}
319 			}
320 
321 			/*
322 			 * restore the fsdelim char that we
323 			 * stomped to produce a prefix.
324 			 */
325 			*ecomp = fsdelim;
326 		} /* if (we need to stat the path) */
327 	} /* while (more of this path to check) */
328 
329 	/*
330 	 * We successfully traversed the whole pathname
331 	 */
332 	return (0);
333 }
334 
335 void
336 usage()
337 {
338 	(void) fprintf(stderr, gettext("usage: pathchk [-p] pathname ..."));
339 	(void) fprintf(stderr, "\n");
340 	exit(2);
341 }
342