xref: /netbsd-src/usr.bin/realpath/realpath.c (revision d736f87f8e8a11d0b55ef29070a0a14c301377b4)
1*d736f87fSkre /*	$NetBSD: realpath.c,v 1.3 2023/05/25 17:24:17 kre Exp $	*/
2771c4258Skamil /*-
3771c4258Skamil  * SPDX-License-Identifier: BSD-3-Clause
4771c4258Skamil  *
5771c4258Skamil  * Copyright (c) 1991, 1993, 1994
6771c4258Skamil  *	The Regents of the University of California.  All rights reserved.
7771c4258Skamil  *
8771c4258Skamil  * Redistribution and use in source and binary forms, with or without
9771c4258Skamil  * modification, are permitted provided that the following conditions
10771c4258Skamil  * are met:
11771c4258Skamil  * 1. Redistributions of source code must retain the above copyright
12771c4258Skamil  *    notice, this list of conditions and the following disclaimer.
13771c4258Skamil  * 2. Redistributions in binary form must reproduce the above copyright
14771c4258Skamil  *    notice, this list of conditions and the following disclaimer in the
15771c4258Skamil  *    documentation and/or other materials provided with the distribution.
16771c4258Skamil  * 3. Neither the name of the University nor the names of its contributors
17771c4258Skamil  *    may be used to endorse or promote products derived from this software
18771c4258Skamil  *    without specific prior written permission.
19771c4258Skamil  *
20771c4258Skamil  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21771c4258Skamil  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22771c4258Skamil  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23771c4258Skamil  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24771c4258Skamil  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25771c4258Skamil  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26771c4258Skamil  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27771c4258Skamil  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28771c4258Skamil  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29771c4258Skamil  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30771c4258Skamil  * SUCH DAMAGE.
31771c4258Skamil  */
32771c4258Skamil 
33771c4258Skamil #include <sys/cdefs.h>
34771c4258Skamil #if !defined(lint)
35771c4258Skamil #if 0
36771c4258Skamil __FBSDID("$FreeBSD: head/bin/realpath/realpath.c 326025 2017-11-20 19:49:47Z pfg $");
37771c4258Skamil #else
38*d736f87fSkre __RCSID("$NetBSD: realpath.c,v 1.3 2023/05/25 17:24:17 kre Exp $");
39771c4258Skamil #endif
40771c4258Skamil #endif /* not lint */
41771c4258Skamil 
42771c4258Skamil #include <sys/param.h>
43245b5ea4Skre #include <sys/stat.h>
44771c4258Skamil 
45771c4258Skamil #include <err.h>
46245b5ea4Skre #include <errno.h>
47771c4258Skamil #include <stdio.h>
48245b5ea4Skre #include <stdbool.h>
49771c4258Skamil #include <stdlib.h>
50245b5ea4Skre #include <string.h>
51771c4258Skamil #include <unistd.h>
52771c4258Skamil 
53245b5ea4Skre static bool process(char *path);
54771c4258Skamil static void usage(void) __dead;
55771c4258Skamil 
56245b5ea4Skre static const char options[] = "Eeq";
57245b5ea4Skre 
58245b5ea4Skre char dot[] = ".";
59245b5ea4Skre 
60245b5ea4Skre bool eflag = false;		/* default to -E mode */
61245b5ea4Skre bool qflag = false;
62245b5ea4Skre 
63771c4258Skamil int
main(int argc,char * argv[])64771c4258Skamil main(int argc, char *argv[])
65771c4258Skamil {
66245b5ea4Skre 	char *path;
67245b5ea4Skre 	int ch, rval;
68771c4258Skamil 
69771c4258Skamil 	setprogname(argv[0]);
70771c4258Skamil 
71245b5ea4Skre 	while ((ch = getopt(argc, argv, options)) != -1) {
72771c4258Skamil 		switch (ch) {
73245b5ea4Skre 		case 'e':
74245b5ea4Skre 			eflag = true;
75245b5ea4Skre 			break;
76245b5ea4Skre 		case 'E':
77245b5ea4Skre 			eflag = false;
78245b5ea4Skre 			break;
79771c4258Skamil 		case 'q':
80245b5ea4Skre 			qflag = true;
81771c4258Skamil 			break;
82771c4258Skamil 		case '?':
83771c4258Skamil 		default:
84771c4258Skamil 			usage();
85771c4258Skamil 		}
86771c4258Skamil 	}
87771c4258Skamil 	argc -= optind;
88771c4258Skamil 	argv += optind;
89245b5ea4Skre 
90245b5ea4Skre 	path = *argv != NULL ? *argv++ : dot;
91771c4258Skamil 	rval = 0;
92771c4258Skamil 	do {
93245b5ea4Skre 		if (path[0] == '\0') {
94245b5ea4Skre 			/* ignore -q for this one */
95245b5ea4Skre 			warnx("Invalid path ''");
96771c4258Skamil 			rval = 1;
97245b5ea4Skre 			continue;
98245b5ea4Skre 		}
99245b5ea4Skre 		if (!process(path))
100245b5ea4Skre 			rval = 1;;
101771c4258Skamil 	} while ((path = *argv++) != NULL);
102771c4258Skamil 	exit(rval);
103771c4258Skamil }
104771c4258Skamil 
105245b5ea4Skre static bool
process(char * path)106245b5ea4Skre process(char *path)
107245b5ea4Skre {
108245b5ea4Skre 	char buf[PATH_MAX];
109245b5ea4Skre 	char buf2[sizeof buf];
110245b5ea4Skre 	char lbuf[PATH_MAX];
111245b5ea4Skre 	char *pp, *p, *q, *r, *s;
112245b5ea4Skre 	struct stat sb;
113245b5ea4Skre 	bool dir_reqd = false;
114245b5ea4Skre 
115245b5ea4Skre 	if ((p = realpath(path, buf)) != NULL) {
116245b5ea4Skre 		(void)printf("%s\n", p);
117245b5ea4Skre 		return true;
118245b5ea4Skre 	}
119245b5ea4Skre 
120245b5ea4Skre 	if (eflag || errno != ENOENT) {
121245b5ea4Skre 		if (!qflag)
122245b5ea4Skre 			warn("%s", path);
123245b5ea4Skre 		return false;
124245b5ea4Skre 	}
125245b5ea4Skre 
126245b5ea4Skre 	p = strrchr(path, '/');
127245b5ea4Skre 	while (p != NULL && p > &path[1] && p[1] == '\0') {
128245b5ea4Skre 		dir_reqd = true;
129245b5ea4Skre 		*p = '\0';
130245b5ea4Skre 		p = strrchr(path, '/');
131245b5ea4Skre 	}
132245b5ea4Skre 
133245b5ea4Skre 	if (p == NULL) {
134245b5ea4Skre 		p = realpath(".", buf);
135*d736f87fSkre 		if (p == NULL) {
136*d736f87fSkre 			warnx("relative path; current location unknown");
137*d736f87fSkre 			return false;
138*d736f87fSkre 		}
139245b5ea4Skre 		if ((size_t)snprintf(buf2, sizeof buf2, "%s/%s", buf, path)
140245b5ea4Skre 		    >= sizeof buf2) {
141245b5ea4Skre 			if (!qflag)
142245b5ea4Skre 				warnx("%s/%s: path too long", p, path);
143245b5ea4Skre 			return false;
144245b5ea4Skre 		}
145245b5ea4Skre 		path = buf2;
146245b5ea4Skre 		p = strrchr(path, '/');
147245b5ea4Skre 		if (p == NULL)
148245b5ea4Skre 			abort();
149245b5ea4Skre 	}
150245b5ea4Skre 
151245b5ea4Skre 	*p = '\0';
152245b5ea4Skre 	pp = ++p;
153245b5ea4Skre 
154245b5ea4Skre 	q = path; r = buf; s = buf2;
155245b5ea4Skre 	while (realpath(*q ? q : "/", r) != NULL) {
156245b5ea4Skre 		ssize_t llen;
157245b5ea4Skre 
158245b5ea4Skre 		if (strcmp(r, "/") == 0 || strcmp(r, "//") == 0)
159245b5ea4Skre 			r++;
160245b5ea4Skre 		if ((size_t)snprintf(s, sizeof buf, "%s/%s", r, pp)
161245b5ea4Skre 		    >= sizeof buf)
162245b5ea4Skre 			return false;
163245b5ea4Skre 
164245b5ea4Skre 		if (lstat(s, &sb) == -1 || !S_ISLNK(sb.st_mode)) {
165245b5ea4Skre 			(void)printf("%s\n", s);
166245b5ea4Skre 			return true;
167245b5ea4Skre 		}
168245b5ea4Skre 
169245b5ea4Skre 		q = strchr(r, '\0');
170245b5ea4Skre 		if (q >= &r[sizeof buf - 3]) {
171245b5ea4Skre 			*p = '/';
172245b5ea4Skre 			if (!qflag)
173245b5ea4Skre 				warnx("Expanded path for %s too long\n", path);
174245b5ea4Skre 			return false;
175245b5ea4Skre 		}
176245b5ea4Skre 
177245b5ea4Skre 		if ((llen = readlink(s, lbuf, sizeof lbuf - 2)) == -1) {
178245b5ea4Skre 			*p = '/';
179245b5ea4Skre 			if (!qflag)
180245b5ea4Skre 				warn("%s", path);
181245b5ea4Skre 			return false;
182245b5ea4Skre 		}
183245b5ea4Skre 		lbuf[llen] = '\0';
184245b5ea4Skre 
185245b5ea4Skre 		if (lbuf[0] == '/') {
186245b5ea4Skre 			q = lbuf;
187245b5ea4Skre 			if (dir_reqd) {
188245b5ea4Skre 				lbuf[llen++] = '/';
189245b5ea4Skre 				lbuf[llen] = '\0';
190245b5ea4Skre 			}
191245b5ea4Skre 		} else {
192245b5ea4Skre 			if (q != buf2) {
193245b5ea4Skre 				q = buf2;
194245b5ea4Skre 				r = buf;
195245b5ea4Skre 			} else {
196245b5ea4Skre 				q = buf;
197245b5ea4Skre 				r = buf2;
198245b5ea4Skre 			}
199245b5ea4Skre 
200245b5ea4Skre 			if ((size_t)snprintf(q, sizeof buf, "%s/%s%s", r, lbuf,
201245b5ea4Skre 			    (dir_reqd ? "/" : "")) >= sizeof buf) {
202245b5ea4Skre 				*p = '/';
203245b5ea4Skre 				if (!qflag)
204245b5ea4Skre 					warnx("Expanded path for %s too long\n",
205245b5ea4Skre 					    path);
206245b5ea4Skre 				return false;
207245b5ea4Skre 			}
208245b5ea4Skre 		}
209245b5ea4Skre 
210245b5ea4Skre 		s = realpath(q, r);
211245b5ea4Skre 		if (s != NULL) {
212245b5ea4Skre 			/* this case should almost never happen (race) */
213245b5ea4Skre 			(void)printf("%s\n", s);
214245b5ea4Skre 			return true;
215245b5ea4Skre 		}
216245b5ea4Skre 		if (errno != ENOENT) {
217245b5ea4Skre 			*p = '/';
218245b5ea4Skre 			if (!qflag)
219245b5ea4Skre 				warn("%s", path);
220245b5ea4Skre 			return false;
221245b5ea4Skre 		}
222245b5ea4Skre 
223245b5ea4Skre 		pp = strrchr(q, '/');
224245b5ea4Skre 		if (pp == NULL) {
225245b5ea4Skre 			/* we just put one there, where did it go? */
226245b5ea4Skre 			abort();
227245b5ea4Skre 		}
228245b5ea4Skre 		if (dir_reqd) {
229245b5ea4Skre 			*pp = '\0';
230245b5ea4Skre 			pp = strrchr(q, '/');
231245b5ea4Skre 			if (pp == NULL)
232245b5ea4Skre 				abort();
233245b5ea4Skre 		}
234245b5ea4Skre 		*pp++ = '\0';
235245b5ea4Skre 
236245b5ea4Skre 		s = q;
237245b5ea4Skre 	}
238245b5ea4Skre 
239245b5ea4Skre 	*p = '/';
240245b5ea4Skre 
241245b5ea4Skre 	if (!qflag)
242245b5ea4Skre 		warn("%s", path);
243245b5ea4Skre 	return false;
244245b5ea4Skre }
245245b5ea4Skre 
246771c4258Skamil static void
usage(void)247771c4258Skamil usage(void)
248771c4258Skamil {
249771c4258Skamil 
250245b5ea4Skre 	(void)fprintf(stderr, "usage: %s [-%s] [path ...]\n",
251245b5ea4Skre 	    getprogname(), options);
252771c4258Skamil   	exit(1);
253771c4258Skamil }
254