1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2002 Tim J. Robbins.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29/*
30 * pathchk -- check pathnames
31 *
32 * Check whether files could be created with the names specified on the
33 * command line. If -p is specified, check whether the pathname is portable
34 * to all POSIX systems.
35 */
36
37#include <sys/types.h>
38#include <sys/stat.h>
39
40#include <err.h>
41#include <errno.h>
42#include <limits.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <unistd.h>
47
48static int	 check(const char *);
49static int	 portable(const char *);
50static void	 usage(void);
51
52static int	 pflag;			/* Perform portability checks */
53static int	 Pflag;			/* Check for empty paths, leading '-' */
54
55int
56main(int argc, char *argv[])
57{
58	int ch, rval;
59	const char *arg;
60
61	while ((ch = getopt(argc, argv, "pP")) > 0) {
62		switch (ch) {
63		case 'p':
64			pflag = 1;
65			break;
66		case 'P':
67			Pflag = 1;
68			break;
69		default:
70			usage();
71			/*NOTREACHED*/
72		}
73	}
74	argc -= optind;
75	argv += optind;
76
77	if (argc == 0)
78		usage();
79
80	rval = 0;
81	while ((arg = *argv++) != NULL)
82		rval |= check(arg);
83
84	exit(rval);
85}
86
87static void
88usage(void)
89{
90
91	fprintf(stderr, "usage: pathchk [-Pp] pathname ...\n");
92	exit(1);
93}
94
95static int
96check(const char *path)
97{
98	struct stat sb;
99	long complen, namemax, pathmax, svnamemax;
100	int last;
101	char *end, *p, *pathd;
102
103	if ((pathd = strdup(path)) == NULL)
104		err(1, "strdup");
105
106	p = pathd;
107
108	if (Pflag && *p == '\0') {
109		warnx("%s: empty pathname", path);
110		goto bad;
111	}
112	if ((Pflag || pflag) && (*p == '-' || strstr(p, "/-") != NULL)) {
113		warnx("%s: contains a component starting with '-'", path);
114		goto bad;
115	}
116
117	if (!pflag) {
118		errno = 0;
119		namemax = pathconf(*p == '/' ? "/" : ".", _PC_NAME_MAX);
120		if (namemax == -1 && errno != 0)
121			namemax = NAME_MAX;
122	} else
123		namemax = _POSIX_NAME_MAX;
124
125	for (;;) {
126		p += strspn(p, "/");
127		complen = (long)strcspn(p, "/");
128		end = p + complen;
129		last = *end == '\0';
130		*end = '\0';
131
132		if (namemax != -1 && complen > namemax) {
133			warnx("%s: %s: component too long (limit %ld)", path,
134			    p, namemax);
135			goto bad;
136		}
137
138		if (!pflag && stat(pathd, &sb) == -1 && errno != ENOENT) {
139			warn("%s: %.*s", path, (int)(strlen(pathd) -
140			    complen - 1), pathd);
141			goto bad;
142		}
143
144		if (pflag && !portable(p)) {
145			warnx("%s: %s: component contains non-portable "
146			    "character", path, p);
147			goto bad;
148		}
149
150		if (last)
151			break;
152
153		if (!pflag) {
154			errno = 0;
155			svnamemax = namemax;
156			namemax = pathconf(pathd, _PC_NAME_MAX);
157			if (namemax == -1 && errno != 0)
158				namemax = svnamemax;
159		}
160
161		*end = '/';
162		p = end + 1;
163	}
164
165	if (!pflag) {
166		errno = 0;
167		pathmax = pathconf(path, _PC_PATH_MAX);
168		if (pathmax == -1 && errno != 0)
169			pathmax = PATH_MAX;
170	} else
171		pathmax = _POSIX_PATH_MAX;
172	if (pathmax != -1 && strlen(path) >= (size_t)pathmax) {
173		warnx("%s: path too long (limit %ld)", path, pathmax - 1);
174		goto bad;
175	}
176
177	free(pathd);
178	return (0);
179
180bad:	free(pathd);
181	return (1);
182}
183
184/*
185 * Check whether a path component contains only portable characters.
186 */
187static int
188portable(const char *path)
189{
190	static const char charset[] =
191	    "abcdefghijklmnopqrstuvwxyz"
192	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
193	    "0123456789._-";
194	long s;
195
196	s = strspn(path, charset);
197	if (path[s] != '\0')
198		return (0);
199
200	return (1);
201}
202