xref: /netbsd-src/usr.bin/realpath/realpath.c (revision 7d62b00eb9ad855ffcd7da46b41e23feb5476fac)
1 /*	$NetBSD: realpath.c,v 1.2 2022/07/21 09:47:31 kre Exp $	*/
2 /*-
3  * SPDX-License-Identifier: BSD-3-Clause
4  *
5  * Copyright (c) 1991, 1993, 1994
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <sys/cdefs.h>
34 #if !defined(lint)
35 #if 0
36 __FBSDID("$FreeBSD: head/bin/realpath/realpath.c 326025 2017-11-20 19:49:47Z pfg $");
37 #else
38 __RCSID("$NetBSD: realpath.c,v 1.2 2022/07/21 09:47:31 kre Exp $");
39 #endif
40 #endif /* not lint */
41 
42 #include <sys/param.h>
43 #include <sys/stat.h>
44 
45 #include <err.h>
46 #include <errno.h>
47 #include <stdio.h>
48 #include <stdbool.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52 
53 static bool process(char *path);
54 static void usage(void) __dead;
55 
56 static const char options[] = "Eeq";
57 
58 char dot[] = ".";
59 
60 bool eflag = false;		/* default to -E mode */
61 bool qflag = false;
62 
63 int
64 main(int argc, char *argv[])
65 {
66 	char *path;
67 	int ch, rval;
68 
69 	setprogname(argv[0]);
70 
71 	while ((ch = getopt(argc, argv, options)) != -1) {
72 		switch (ch) {
73 		case 'e':
74 			eflag = true;
75 			break;
76 		case 'E':
77 			eflag = false;
78 			break;
79 		case 'q':
80 			qflag = true;
81 			break;
82 		case '?':
83 		default:
84 			usage();
85 		}
86 	}
87 	argc -= optind;
88 	argv += optind;
89 
90 	path = *argv != NULL ? *argv++ : dot;
91 	rval = 0;
92 	do {
93 		if (path[0] == '\0') {
94 			/* ignore -q for this one */
95 			warnx("Invalid path ''");
96 			rval = 1;
97 			continue;
98 		}
99 		if (!process(path))
100 			rval = 1;;
101 	} while ((path = *argv++) != NULL);
102 	exit(rval);
103 }
104 
105 static bool
106 process(char *path)
107 {
108 	char buf[PATH_MAX];
109 	char buf2[sizeof buf];
110 	char lbuf[PATH_MAX];
111 	char *pp, *p, *q, *r, *s;
112 	struct stat sb;
113 	bool dir_reqd = false;
114 
115 	if ((p = realpath(path, buf)) != NULL) {
116 		(void)printf("%s\n", p);
117 		return true;
118 	}
119 
120 	if (eflag || errno != ENOENT) {
121 		if (!qflag)
122 			warn("%s", path);
123 		return false;
124 	}
125 
126 	p = strrchr(path, '/');
127 	while (p != NULL && p > &path[1] && p[1] == '\0') {
128 		dir_reqd = true;
129 		*p = '\0';
130 		p = strrchr(path, '/');
131 	}
132 
133 	if (p == NULL) {
134 		p = realpath(".", buf);
135 		if ((size_t)snprintf(buf2, sizeof buf2, "%s/%s", buf, path)
136 		    >= sizeof buf2) {
137 			if (!qflag)
138 				warnx("%s/%s: path too long", p, path);
139 			return false;
140 		}
141 		path = buf2;
142 		p = strrchr(path, '/');
143 		if (p == NULL)
144 			abort();
145 	}
146 
147 	*p = '\0';
148 	pp = ++p;
149 
150 	q = path; r = buf; s = buf2;
151 	while (realpath(*q ? q : "/", r) != NULL) {
152 		ssize_t llen;
153 
154 		if (strcmp(r, "/") == 0 || strcmp(r, "//") == 0)
155 			r++;
156 		if ((size_t)snprintf(s, sizeof buf, "%s/%s", r, pp)
157 		    >= sizeof buf)
158 			return false;
159 
160 		if (lstat(s, &sb) == -1 || !S_ISLNK(sb.st_mode)) {
161 			(void)printf("%s\n", s);
162 			return true;
163 		}
164 
165 		q = strchr(r, '\0');
166 		if (q >= &r[sizeof buf - 3]) {
167 			*p = '/';
168 			if (!qflag)
169 				warnx("Expanded path for %s too long\n", path);
170 			return false;
171 		}
172 
173 		if ((llen = readlink(s, lbuf, sizeof lbuf - 2)) == -1) {
174 			*p = '/';
175 			if (!qflag)
176 				warn("%s", path);
177 			return false;
178 		}
179 		lbuf[llen] = '\0';
180 
181 		if (lbuf[0] == '/') {
182 			q = lbuf;
183 			if (dir_reqd) {
184 				lbuf[llen++] = '/';
185 				lbuf[llen] = '\0';
186 			}
187 		} else {
188 			if (q != buf2) {
189 				q = buf2;
190 				r = buf;
191 			} else {
192 				q = buf;
193 				r = buf2;
194 			}
195 
196 			if ((size_t)snprintf(q, sizeof buf, "%s/%s%s", r, lbuf,
197 			    (dir_reqd ? "/" : "")) >= sizeof buf) {
198 				*p = '/';
199 				if (!qflag)
200 					warnx("Expanded path for %s too long\n",
201 					    path);
202 				return false;
203 			}
204 		}
205 
206 		s = realpath(q, r);
207 		if (s != NULL) {
208 			/* this case should almost never happen (race) */
209 			(void)printf("%s\n", s);
210 			return true;
211 		}
212 		if (errno != ENOENT) {
213 			*p = '/';
214 			if (!qflag)
215 				warn("%s", path);
216 			return false;
217 		}
218 
219 		pp = strrchr(q, '/');
220 		if (pp == NULL) {
221 			/* we just put one there, where did it go? */
222 			abort();
223 		}
224 		if (dir_reqd) {
225 			*pp = '\0';
226 			pp = strrchr(q, '/');
227 			if (pp == NULL)
228 				abort();
229 		}
230 		*pp++ = '\0';
231 
232 		s = q;
233 	}
234 
235 	*p = '/';
236 
237 	if (!qflag)
238 		warn("%s", path);
239 	return false;
240 }
241 
242 static void
243 usage(void)
244 {
245 
246 	(void)fprintf(stderr, "usage: %s [-%s] [path ...]\n",
247 	    getprogname(), options);
248   	exit(1);
249 }
250