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 * 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 * 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 * 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