1*eabc0478Schristos /* $NetBSD: ntp_realpath.c,v 1.2 2024/08/18 20:47:13 christos Exp $ */ 2897be3a4Schristos 3897be3a4Schristos /* 4897be3a4Schristos * ntp_realpath.c - get real path for a file 5897be3a4Schristos * Juergen Perlinger (perlinger@ntp.org) for the NTP project. 6897be3a4Schristos * Feb 11, 2014 for the NTP project. 7897be3a4Schristos * 8897be3a4Schristos * This is a butchered version of FreeBSD's implementation of 'realpath()', 9897be3a4Schristos * and the following copyright applies: 10897be3a4Schristos *---------------------------------------------------------------------- 11897be3a4Schristos */ 12897be3a4Schristos 13897be3a4Schristos /*- 14897be3a4Schristos * SPDX-License-Identifier: BSD-3-Clause 15897be3a4Schristos * 16897be3a4Schristos * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru> 17897be3a4Schristos * 18897be3a4Schristos * Redistribution and use in source and binary forms, with or without 19897be3a4Schristos * modification, are permitted provided that the following conditions 20897be3a4Schristos * are met: 21897be3a4Schristos * 1. Redistributions of source code must retain the above copyright 22897be3a4Schristos * notice, this list of conditions and the following disclaimer. 23897be3a4Schristos * 2. Redistributions in binary form must reproduce the above copyright 24897be3a4Schristos * notice, this list of conditions and the following disclaimer in the 25897be3a4Schristos * documentation and/or other materials provided with the distribution. 26897be3a4Schristos * 3. The names of the authors may not be used to endorse or promote 27897be3a4Schristos * products derived from this software without specific prior written 28897be3a4Schristos * permission. 29897be3a4Schristos * 30897be3a4Schristos * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 31897be3a4Schristos * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 32897be3a4Schristos * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33897be3a4Schristos * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 34897be3a4Schristos * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 35897be3a4Schristos * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 36897be3a4Schristos * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 37897be3a4Schristos * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 38897be3a4Schristos * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 39897be3a4Schristos * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40897be3a4Schristos * SUCH DAMAGE. 41897be3a4Schristos */ 42897be3a4Schristos 43897be3a4Schristos #ifdef HAVE_CONFIG_H 44897be3a4Schristos #include <config.h> 45897be3a4Schristos #endif 46897be3a4Schristos #include "ntp_stdlib.h" 47897be3a4Schristos 48897be3a4Schristos /* ================================================================== */ 49897be3a4Schristos #if defined(SYS_WINNT) 50897be3a4Schristos /* ================================================================== */ 51897be3a4Schristos 52897be3a4Schristos #include <stdlib.h> 53897be3a4Schristos 54897be3a4Schristos /* On Windows, we assume 2k for a file path is enough. */ 55897be3a4Schristos #define NTP_PATH_MAX 2048 56897be3a4Schristos 57897be3a4Schristos static char * 58897be3a4Schristos realpath1(const char *path, char *resolved) 59897be3a4Schristos { 60897be3a4Schristos /* Items in the device name space get passed back AS IS. Everything 61897be3a4Schristos * else is fed through '_fullpath()', which is probably the closest 62897be3a4Schristos * counterpart to what 'realpath()' is expected to do on Windows... 63897be3a4Schristos */ 64897be3a4Schristos char * retval = NULL; 65897be3a4Schristos 66897be3a4Schristos if (!strncmp(path, "\\\\.\\", 4)) { 67897be3a4Schristos if (strlcpy(resolved, path, NTP_PATH_MAX) >= NTP_PATH_MAX) 68897be3a4Schristos errno = ENAMETOOLONG; 69897be3a4Schristos else 70897be3a4Schristos retval = resolved; 71897be3a4Schristos } else if ((retval = _fullpath(resolved, path, NTP_PATH_MAX)) == NULL) { 72897be3a4Schristos errno = ENAMETOOLONG; 73897be3a4Schristos } 74897be3a4Schristos return retval; 75897be3a4Schristos } 76897be3a4Schristos 77897be3a4Schristos /* ================================================================== */ 78897be3a4Schristos #elif !defined(HAVE_FUNC_POSIX_REALPATH) 79897be3a4Schristos /* ================================================================== */ 80897be3a4Schristos 81897be3a4Schristos #include <sys/stat.h> 82897be3a4Schristos #include <errno.h> 83897be3a4Schristos #include <stdlib.h> 84897be3a4Schristos #include <string.h> 85897be3a4Schristos #include <unistd.h> 86897be3a4Schristos #include <fcntl.h> 87897be3a4Schristos 88897be3a4Schristos /* The following definitions are to avoid system settings with excessive 89897be3a4Schristos * values for maxmimum path length and symlink chains/loops. Adjust with 90897be3a4Schristos * care, if that's ever needed: some buffers are on the stack! 91897be3a4Schristos */ 92897be3a4Schristos #define NTP_PATH_MAX 1024 93897be3a4Schristos #define NTP_MAXSYMLINKS 16 94897be3a4Schristos 95897be3a4Schristos /* 96897be3a4Schristos * Find the real name of path, by removing all ".", ".." and symlink 97897be3a4Schristos * components. Returns (resolved) on success, or (NULL) on failure, 98897be3a4Schristos * in which case the path which caused trouble is left in (resolved). 99897be3a4Schristos */ 100897be3a4Schristos static char * 101897be3a4Schristos realpath1(const char *path, char *resolved) 102897be3a4Schristos { 103897be3a4Schristos struct stat sb; 104897be3a4Schristos char *p, *q; 105897be3a4Schristos size_t left_len, resolved_len, next_token_len; 106897be3a4Schristos unsigned symlinks; 107897be3a4Schristos ssize_t slen; 108897be3a4Schristos char left[NTP_PATH_MAX], next_token[NTP_PATH_MAX], link_tgt[NTP_PATH_MAX]; 109897be3a4Schristos 110897be3a4Schristos symlinks = 0; 111897be3a4Schristos if (path[0] == '/') { 112897be3a4Schristos resolved[0] = '/'; 113897be3a4Schristos resolved[1] = '\0'; 114897be3a4Schristos if (path[1] == '\0') 115897be3a4Schristos return (resolved); 116897be3a4Schristos resolved_len = 1; 117897be3a4Schristos left_len = strlcpy(left, path + 1, sizeof(left)); 118897be3a4Schristos } else { 119897be3a4Schristos if (getcwd(resolved, NTP_PATH_MAX) == NULL) { 120897be3a4Schristos resolved[0] = '.'; 121897be3a4Schristos resolved[1] = '\0'; 122897be3a4Schristos return (NULL); 123897be3a4Schristos } 124897be3a4Schristos resolved_len = strlen(resolved); 125897be3a4Schristos left_len = strlcpy(left, path, sizeof(left)); 126897be3a4Schristos } 127897be3a4Schristos if (left_len >= sizeof(left) || resolved_len >= NTP_PATH_MAX) { 128897be3a4Schristos errno = ENAMETOOLONG; 129897be3a4Schristos return (NULL); 130897be3a4Schristos } 131897be3a4Schristos 132897be3a4Schristos /* 133897be3a4Schristos * Iterate over path components in `left'. 134897be3a4Schristos */ 135897be3a4Schristos while (left_len != 0) { 136897be3a4Schristos /* 137897be3a4Schristos * Extract the next path component and adjust `left' 138897be3a4Schristos * and its length. 139897be3a4Schristos */ 140897be3a4Schristos p = strchr(left, '/'); 141897be3a4Schristos 142897be3a4Schristos next_token_len = p != NULL ? (size_t)(p - left) : left_len; 143897be3a4Schristos memcpy(next_token, left, next_token_len); 144897be3a4Schristos next_token[next_token_len] = '\0'; 145897be3a4Schristos 146897be3a4Schristos if (p != NULL) { 147897be3a4Schristos left_len -= next_token_len + 1; 148897be3a4Schristos memmove(left, p + 1, left_len + 1); 149897be3a4Schristos } else { 150897be3a4Schristos left[0] = '\0'; 151897be3a4Schristos left_len = 0; 152897be3a4Schristos } 153897be3a4Schristos 154897be3a4Schristos if (resolved[resolved_len - 1] != '/') { 155897be3a4Schristos if (resolved_len + 1 >= NTP_PATH_MAX) { 156897be3a4Schristos errno = ENAMETOOLONG; 157897be3a4Schristos return (NULL); 158897be3a4Schristos } 159897be3a4Schristos resolved[resolved_len++] = '/'; 160897be3a4Schristos resolved[resolved_len] = '\0'; 161897be3a4Schristos } 162897be3a4Schristos if ('\0' == next_token[0]) { 163897be3a4Schristos /* Handle consequential slashes. */ 164897be3a4Schristos continue; 165897be3a4Schristos } else if (strcmp(next_token, ".") == 0) { 166897be3a4Schristos continue; 167897be3a4Schristos } else if (strcmp(next_token, "..") == 0) { 168897be3a4Schristos /* 169897be3a4Schristos * Strip the last path component except when we have 170897be3a4Schristos * single "/" 171897be3a4Schristos */ 172897be3a4Schristos if (resolved_len > 1) { 173897be3a4Schristos resolved[resolved_len - 1] = '\0'; 174897be3a4Schristos q = strrchr(resolved, '/') + 1; 175897be3a4Schristos *q = '\0'; 176897be3a4Schristos resolved_len = q - resolved; 177897be3a4Schristos } 178897be3a4Schristos continue; 179897be3a4Schristos } 180897be3a4Schristos 181897be3a4Schristos /* 182897be3a4Schristos * Append the next path component and lstat() it. 183897be3a4Schristos */ 184897be3a4Schristos resolved_len = strlcat(resolved, next_token, NTP_PATH_MAX); 185897be3a4Schristos if (resolved_len >= NTP_PATH_MAX) { 186897be3a4Schristos errno = ENAMETOOLONG; 187897be3a4Schristos return (NULL); 188897be3a4Schristos } 189897be3a4Schristos if (lstat(resolved, &sb) != 0) 190897be3a4Schristos return (NULL); 191897be3a4Schristos if (S_ISLNK(sb.st_mode)) { 192897be3a4Schristos if (++symlinks > NTP_MAXSYMLINKS) { 193897be3a4Schristos errno = ELOOP; 194897be3a4Schristos return (NULL); 195897be3a4Schristos } 196897be3a4Schristos slen = readlink(resolved, link_tgt, sizeof(link_tgt)); 197897be3a4Schristos if (slen <= 0 || slen >= (ssize_t)sizeof(link_tgt)) { 198897be3a4Schristos if (slen < 0) { 199897be3a4Schristos /* keep errno from readlink(2) call */ 200897be3a4Schristos } else if (slen == 0) { 201897be3a4Schristos errno = ENOENT; 202897be3a4Schristos } else { 203897be3a4Schristos errno = ENAMETOOLONG; 204897be3a4Schristos } 205897be3a4Schristos return (NULL); 206897be3a4Schristos } 207897be3a4Schristos link_tgt[slen] = '\0'; 208897be3a4Schristos if (link_tgt[0] == '/') { 209897be3a4Schristos resolved[1] = '\0'; 210897be3a4Schristos resolved_len = 1; 211897be3a4Schristos } else { 212897be3a4Schristos /* Strip the last path component. */ 213897be3a4Schristos q = strrchr(resolved, '/') + 1; 214897be3a4Schristos *q = '\0'; 215897be3a4Schristos resolved_len = q - resolved; 216897be3a4Schristos } 217897be3a4Schristos 218897be3a4Schristos /* 219897be3a4Schristos * If there are any path components left, then 220897be3a4Schristos * append them to link_tgt. The result is placed 221897be3a4Schristos * in `left'. 222897be3a4Schristos */ 223897be3a4Schristos if (p != NULL) { 224897be3a4Schristos if (link_tgt[slen - 1] != '/') { 225897be3a4Schristos if (slen + 1 >= (ssize_t)sizeof(link_tgt)) { 226897be3a4Schristos errno = ENAMETOOLONG; 227897be3a4Schristos return (NULL); 228897be3a4Schristos } 229897be3a4Schristos link_tgt[slen] = '/'; 230897be3a4Schristos link_tgt[slen + 1] = 0; 231897be3a4Schristos } 232897be3a4Schristos left_len = strlcat(link_tgt, left, 233897be3a4Schristos sizeof(link_tgt)); 234897be3a4Schristos if (left_len >= sizeof(link_tgt)) { 235897be3a4Schristos errno = ENAMETOOLONG; 236897be3a4Schristos return (NULL); 237897be3a4Schristos } 238897be3a4Schristos } 239897be3a4Schristos left_len = strlcpy(left, link_tgt, sizeof(left)); 240897be3a4Schristos } else if (!S_ISDIR(sb.st_mode) && p != NULL) { 241897be3a4Schristos errno = ENOTDIR; 242897be3a4Schristos return (NULL); 243897be3a4Schristos } 244897be3a4Schristos } 245897be3a4Schristos 246897be3a4Schristos /* 247897be3a4Schristos * Remove trailing slash except when the resolved pathname 248897be3a4Schristos * is a single "/". 249897be3a4Schristos */ 250897be3a4Schristos if (resolved_len > 1 && resolved[resolved_len - 1] == '/') 251897be3a4Schristos resolved[resolved_len - 1] = '\0'; 252897be3a4Schristos return (resolved); 253897be3a4Schristos } 254897be3a4Schristos 255897be3a4Schristos /* ================================================================== */ 256897be3a4Schristos #endif /* !defined(SYS_WINNT) && !defined(HAVE_POSIX_REALPATH) */ 257897be3a4Schristos /* ================================================================== */ 258897be3a4Schristos 259897be3a4Schristos char * 260897be3a4Schristos ntp_realpath(const char * path) 261897be3a4Schristos { 262897be3a4Schristos # if defined(HAVE_FUNC_POSIX_REALPATH) 263897be3a4Schristos 264897be3a4Schristos return realpath(path, NULL); 265897be3a4Schristos 266897be3a4Schristos # else 267897be3a4Schristos 268897be3a4Schristos char *res = NULL, *m = NULL; 269897be3a4Schristos if (path == NULL) 270897be3a4Schristos errno = EINVAL; 271897be3a4Schristos else if (path[0] == '\0') 272897be3a4Schristos errno = ENOENT; 273897be3a4Schristos else if ((m = malloc(NTP_PATH_MAX)) == NULL) 274897be3a4Schristos errno = ENOMEM; /* MSVCRT malloc does not set this... */ 275897be3a4Schristos else if ((res = realpath1(path, m)) == NULL) 276897be3a4Schristos free(m); 277897be3a4Schristos else 278897be3a4Schristos res = realloc(res, strlen(res) + 1); 279897be3a4Schristos return (res); 280897be3a4Schristos 281897be3a4Schristos # endif 282897be3a4Schristos } 283