1 /* $NetBSD: ntp_realpath.c,v 1.2 2024/08/18 20:47:13 christos Exp $ */
2
3 /*
4 * ntp_realpath.c - get real path for a file
5 * Juergen Perlinger (perlinger@ntp.org) for the NTP project.
6 * Feb 11, 2014 for the NTP project.
7 *
8 * This is a butchered version of FreeBSD's implementation of 'realpath()',
9 * and the following copyright applies:
10 *----------------------------------------------------------------------
11 */
12
13 /*-
14 * SPDX-License-Identifier: BSD-3-Clause
15 *
16 * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
17 *
18 * Redistribution and use in source and binary forms, with or without
19 * modification, are permitted provided that the following conditions
20 * are met:
21 * 1. Redistributions of source code must retain the above copyright
22 * notice, this list of conditions and the following disclaimer.
23 * 2. Redistributions in binary form must reproduce the above copyright
24 * notice, this list of conditions and the following disclaimer in the
25 * documentation and/or other materials provided with the distribution.
26 * 3. The names of the authors may not be used to endorse or promote
27 * products derived from this software without specific prior written
28 * permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
31 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
34 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * SUCH DAMAGE.
41 */
42
43 #ifdef HAVE_CONFIG_H
44 #include <config.h>
45 #endif
46 #include "ntp_stdlib.h"
47
48 /* ================================================================== */
49 #if defined(SYS_WINNT)
50 /* ================================================================== */
51
52 #include <stdlib.h>
53
54 /* On Windows, we assume 2k for a file path is enough. */
55 #define NTP_PATH_MAX 2048
56
57 static char *
realpath1(const char * path,char * resolved)58 realpath1(const char *path, char *resolved)
59 {
60 /* Items in the device name space get passed back AS IS. Everything
61 * else is fed through '_fullpath()', which is probably the closest
62 * counterpart to what 'realpath()' is expected to do on Windows...
63 */
64 char * retval = NULL;
65
66 if (!strncmp(path, "\\\\.\\", 4)) {
67 if (strlcpy(resolved, path, NTP_PATH_MAX) >= NTP_PATH_MAX)
68 errno = ENAMETOOLONG;
69 else
70 retval = resolved;
71 } else if ((retval = _fullpath(resolved, path, NTP_PATH_MAX)) == NULL) {
72 errno = ENAMETOOLONG;
73 }
74 return retval;
75 }
76
77 /* ================================================================== */
78 #elif !defined(HAVE_FUNC_POSIX_REALPATH)
79 /* ================================================================== */
80
81 #include <sys/stat.h>
82 #include <errno.h>
83 #include <stdlib.h>
84 #include <string.h>
85 #include <unistd.h>
86 #include <fcntl.h>
87
88 /* The following definitions are to avoid system settings with excessive
89 * values for maxmimum path length and symlink chains/loops. Adjust with
90 * care, if that's ever needed: some buffers are on the stack!
91 */
92 #define NTP_PATH_MAX 1024
93 #define NTP_MAXSYMLINKS 16
94
95 /*
96 * Find the real name of path, by removing all ".", ".." and symlink
97 * components. Returns (resolved) on success, or (NULL) on failure,
98 * in which case the path which caused trouble is left in (resolved).
99 */
100 static char *
realpath1(const char * path,char * resolved)101 realpath1(const char *path, char *resolved)
102 {
103 struct stat sb;
104 char *p, *q;
105 size_t left_len, resolved_len, next_token_len;
106 unsigned symlinks;
107 ssize_t slen;
108 char left[NTP_PATH_MAX], next_token[NTP_PATH_MAX], link_tgt[NTP_PATH_MAX];
109
110 symlinks = 0;
111 if (path[0] == '/') {
112 resolved[0] = '/';
113 resolved[1] = '\0';
114 if (path[1] == '\0')
115 return (resolved);
116 resolved_len = 1;
117 left_len = strlcpy(left, path + 1, sizeof(left));
118 } else {
119 if (getcwd(resolved, NTP_PATH_MAX) == NULL) {
120 resolved[0] = '.';
121 resolved[1] = '\0';
122 return (NULL);
123 }
124 resolved_len = strlen(resolved);
125 left_len = strlcpy(left, path, sizeof(left));
126 }
127 if (left_len >= sizeof(left) || resolved_len >= NTP_PATH_MAX) {
128 errno = ENAMETOOLONG;
129 return (NULL);
130 }
131
132 /*
133 * Iterate over path components in `left'.
134 */
135 while (left_len != 0) {
136 /*
137 * Extract the next path component and adjust `left'
138 * and its length.
139 */
140 p = strchr(left, '/');
141
142 next_token_len = p != NULL ? (size_t)(p - left) : left_len;
143 memcpy(next_token, left, next_token_len);
144 next_token[next_token_len] = '\0';
145
146 if (p != NULL) {
147 left_len -= next_token_len + 1;
148 memmove(left, p + 1, left_len + 1);
149 } else {
150 left[0] = '\0';
151 left_len = 0;
152 }
153
154 if (resolved[resolved_len - 1] != '/') {
155 if (resolved_len + 1 >= NTP_PATH_MAX) {
156 errno = ENAMETOOLONG;
157 return (NULL);
158 }
159 resolved[resolved_len++] = '/';
160 resolved[resolved_len] = '\0';
161 }
162 if ('\0' == next_token[0]) {
163 /* Handle consequential slashes. */
164 continue;
165 } else if (strcmp(next_token, ".") == 0) {
166 continue;
167 } else if (strcmp(next_token, "..") == 0) {
168 /*
169 * Strip the last path component except when we have
170 * single "/"
171 */
172 if (resolved_len > 1) {
173 resolved[resolved_len - 1] = '\0';
174 q = strrchr(resolved, '/') + 1;
175 *q = '\0';
176 resolved_len = q - resolved;
177 }
178 continue;
179 }
180
181 /*
182 * Append the next path component and lstat() it.
183 */
184 resolved_len = strlcat(resolved, next_token, NTP_PATH_MAX);
185 if (resolved_len >= NTP_PATH_MAX) {
186 errno = ENAMETOOLONG;
187 return (NULL);
188 }
189 if (lstat(resolved, &sb) != 0)
190 return (NULL);
191 if (S_ISLNK(sb.st_mode)) {
192 if (++symlinks > NTP_MAXSYMLINKS) {
193 errno = ELOOP;
194 return (NULL);
195 }
196 slen = readlink(resolved, link_tgt, sizeof(link_tgt));
197 if (slen <= 0 || slen >= (ssize_t)sizeof(link_tgt)) {
198 if (slen < 0) {
199 /* keep errno from readlink(2) call */
200 } else if (slen == 0) {
201 errno = ENOENT;
202 } else {
203 errno = ENAMETOOLONG;
204 }
205 return (NULL);
206 }
207 link_tgt[slen] = '\0';
208 if (link_tgt[0] == '/') {
209 resolved[1] = '\0';
210 resolved_len = 1;
211 } else {
212 /* Strip the last path component. */
213 q = strrchr(resolved, '/') + 1;
214 *q = '\0';
215 resolved_len = q - resolved;
216 }
217
218 /*
219 * If there are any path components left, then
220 * append them to link_tgt. The result is placed
221 * in `left'.
222 */
223 if (p != NULL) {
224 if (link_tgt[slen - 1] != '/') {
225 if (slen + 1 >= (ssize_t)sizeof(link_tgt)) {
226 errno = ENAMETOOLONG;
227 return (NULL);
228 }
229 link_tgt[slen] = '/';
230 link_tgt[slen + 1] = 0;
231 }
232 left_len = strlcat(link_tgt, left,
233 sizeof(link_tgt));
234 if (left_len >= sizeof(link_tgt)) {
235 errno = ENAMETOOLONG;
236 return (NULL);
237 }
238 }
239 left_len = strlcpy(left, link_tgt, sizeof(left));
240 } else if (!S_ISDIR(sb.st_mode) && p != NULL) {
241 errno = ENOTDIR;
242 return (NULL);
243 }
244 }
245
246 /*
247 * Remove trailing slash except when the resolved pathname
248 * is a single "/".
249 */
250 if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
251 resolved[resolved_len - 1] = '\0';
252 return (resolved);
253 }
254
255 /* ================================================================== */
256 #endif /* !defined(SYS_WINNT) && !defined(HAVE_POSIX_REALPATH) */
257 /* ================================================================== */
258
259 char *
ntp_realpath(const char * path)260 ntp_realpath(const char * path)
261 {
262 # if defined(HAVE_FUNC_POSIX_REALPATH)
263
264 return realpath(path, NULL);
265
266 # else
267
268 char *res = NULL, *m = NULL;
269 if (path == NULL)
270 errno = EINVAL;
271 else if (path[0] == '\0')
272 errno = ENOENT;
273 else if ((m = malloc(NTP_PATH_MAX)) == NULL)
274 errno = ENOMEM; /* MSVCRT malloc does not set this... */
275 else if ((res = realpath1(path, m)) == NULL)
276 free(m);
277 else
278 res = realloc(res, strlen(res) + 1);
279 return (res);
280
281 # endif
282 }
283