xref: /netbsd-src/usr.bin/realpath/realpath.c (revision d736f87f8e8a11d0b55ef29070a0a14c301377b4)
1 /*	$NetBSD: realpath.c,v 1.3 2023/05/25 17:24:17 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.3 2023/05/25 17:24:17 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
main(int argc,char * argv[])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
process(char * path)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 (p == NULL) {
136 			warnx("relative path; current location unknown");
137 			return false;
138 		}
139 		if ((size_t)snprintf(buf2, sizeof buf2, "%s/%s", buf, path)
140 		    >= sizeof buf2) {
141 			if (!qflag)
142 				warnx("%s/%s: path too long", p, path);
143 			return false;
144 		}
145 		path = buf2;
146 		p = strrchr(path, '/');
147 		if (p == NULL)
148 			abort();
149 	}
150 
151 	*p = '\0';
152 	pp = ++p;
153 
154 	q = path; r = buf; s = buf2;
155 	while (realpath(*q ? q : "/", r) != NULL) {
156 		ssize_t llen;
157 
158 		if (strcmp(r, "/") == 0 || strcmp(r, "//") == 0)
159 			r++;
160 		if ((size_t)snprintf(s, sizeof buf, "%s/%s", r, pp)
161 		    >= sizeof buf)
162 			return false;
163 
164 		if (lstat(s, &sb) == -1 || !S_ISLNK(sb.st_mode)) {
165 			(void)printf("%s\n", s);
166 			return true;
167 		}
168 
169 		q = strchr(r, '\0');
170 		if (q >= &r[sizeof buf - 3]) {
171 			*p = '/';
172 			if (!qflag)
173 				warnx("Expanded path for %s too long\n", path);
174 			return false;
175 		}
176 
177 		if ((llen = readlink(s, lbuf, sizeof lbuf - 2)) == -1) {
178 			*p = '/';
179 			if (!qflag)
180 				warn("%s", path);
181 			return false;
182 		}
183 		lbuf[llen] = '\0';
184 
185 		if (lbuf[0] == '/') {
186 			q = lbuf;
187 			if (dir_reqd) {
188 				lbuf[llen++] = '/';
189 				lbuf[llen] = '\0';
190 			}
191 		} else {
192 			if (q != buf2) {
193 				q = buf2;
194 				r = buf;
195 			} else {
196 				q = buf;
197 				r = buf2;
198 			}
199 
200 			if ((size_t)snprintf(q, sizeof buf, "%s/%s%s", r, lbuf,
201 			    (dir_reqd ? "/" : "")) >= sizeof buf) {
202 				*p = '/';
203 				if (!qflag)
204 					warnx("Expanded path for %s too long\n",
205 					    path);
206 				return false;
207 			}
208 		}
209 
210 		s = realpath(q, r);
211 		if (s != NULL) {
212 			/* this case should almost never happen (race) */
213 			(void)printf("%s\n", s);
214 			return true;
215 		}
216 		if (errno != ENOENT) {
217 			*p = '/';
218 			if (!qflag)
219 				warn("%s", path);
220 			return false;
221 		}
222 
223 		pp = strrchr(q, '/');
224 		if (pp == NULL) {
225 			/* we just put one there, where did it go? */
226 			abort();
227 		}
228 		if (dir_reqd) {
229 			*pp = '\0';
230 			pp = strrchr(q, '/');
231 			if (pp == NULL)
232 				abort();
233 		}
234 		*pp++ = '\0';
235 
236 		s = q;
237 	}
238 
239 	*p = '/';
240 
241 	if (!qflag)
242 		warn("%s", path);
243 	return false;
244 }
245 
246 static void
usage(void)247 usage(void)
248 {
249 
250 	(void)fprintf(stderr, "usage: %s [-%s] [path ...]\n",
251 	    getprogname(), options);
252   	exit(1);
253 }
254