1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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/cdefs.h>
38__FBSDID("$FreeBSD$");
39
40#include <sys/types.h>
41#include <sys/stat.h>
42
43#include <err.h>
44#include <errno.h>
45#include <limits.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <unistd.h>
50
51static int	 check(const char *);
52static int	 portable(const char *);
53static void	 usage(void);
54
55static int	 pflag;			/* Perform portability checks */
56static int	 Pflag;			/* Check for empty paths, leading '-' */
57
58int
59main(int argc, char *argv[])
60{
61	int ch, rval;
62	const char *arg;
63
64	while ((ch = getopt(argc, argv, "pP")) > 0) {
65		switch (ch) {
66		case 'p':
67			pflag = 1;
68			break;
69		case 'P':
70			Pflag = 1;
71			break;
72		default:
73			usage();
74			/*NOTREACHED*/
75		}
76	}
77	argc -= optind;
78	argv += optind;
79
80	if (argc == 0)
81		usage();
82
83	rval = 0;
84	while ((arg = *argv++) != NULL)
85		rval |= check(arg);
86
87	exit(rval);
88}
89
90static void
91usage(void)
92{
93
94	fprintf(stderr, "usage: pathchk [-Pp] pathname ...\n");
95	exit(1);
96}
97
98static int
99check(const char *path)
100{
101	struct stat sb;
102	long complen, namemax, pathmax, svnamemax;
103	int last;
104	char *end, *p, *pathd;
105
106	if ((pathd = strdup(path)) == NULL)
107		err(1, "strdup");
108
109	p = pathd;
110
111	if (Pflag && *p == '\0') {
112		warnx("%s: empty pathname", path);
113		goto bad;
114	}
115	if ((Pflag || pflag) && (*p == '-' || strstr(p, "/-") != NULL)) {
116		warnx("%s: contains a component starting with '-'", path);
117		goto bad;
118	}
119
120	if (!pflag) {
121		errno = 0;
122		namemax = pathconf(*p == '/' ? "/" : ".", _PC_NAME_MAX);
123		if (namemax == -1 && errno != 0)
124			namemax = NAME_MAX;
125	} else
126		namemax = _POSIX_NAME_MAX;
127
128	for (;;) {
129		p += strspn(p, "/");
130		complen = (long)strcspn(p, "/");
131		end = p + complen;
132		last = *end == '\0';
133		*end = '\0';
134
135		if (namemax != -1 && complen > namemax) {
136			warnx("%s: %s: component too long (limit %ld)", path,
137			    p, namemax);
138			goto bad;
139		}
140
141		if (!pflag && stat(pathd, &sb) == -1 && errno != ENOENT) {
142			warn("%s: %.*s", path, (int)(strlen(pathd) -
143			    complen - 1), pathd);
144			goto bad;
145		}
146
147		if (pflag && !portable(p)) {
148			warnx("%s: %s: component contains non-portable "
149			    "character", path, p);
150			goto bad;
151		}
152
153		if (last)
154			break;
155
156		if (!pflag) {
157			errno = 0;
158			svnamemax = namemax;
159			namemax = pathconf(pathd, _PC_NAME_MAX);
160			if (namemax == -1 && errno != 0)
161				namemax = svnamemax;
162		}
163
164		*end = '/';
165		p = end + 1;
166	}
167
168	if (!pflag) {
169		errno = 0;
170		pathmax = pathconf(path, _PC_PATH_MAX);
171		if (pathmax == -1 && errno != 0)
172			pathmax = PATH_MAX;
173	} else
174		pathmax = _POSIX_PATH_MAX;
175	if (pathmax != -1 && strlen(path) >= (size_t)pathmax) {
176		warnx("%s: path too long (limit %ld)", path, pathmax - 1);
177		goto bad;
178	}
179
180	free(pathd);
181	return (0);
182
183bad:	free(pathd);
184	return (1);
185}
186
187/*
188 * Check whether a path component contains only portable characters.
189 */
190static int
191portable(const char *path)
192{
193	static const char charset[] =
194	    "abcdefghijklmnopqrstuvwxyz"
195	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
196	    "0123456789._-";
197	long s;
198
199	s = strspn(path, charset);
200	if (path[s] != '\0')
201		return (0);
202
203	return (1);
204}
205