1 /* Libiberty realpath. Like realpath, but more consistent behavior. 2 Based on gdb_realpath from GDB. 3 4 Copyright (C) 2003-2024 Free Software Foundation, Inc. 5 6 This file is part of the libiberty library. 7 8 This program is free software; you can redistribute it and/or modify 9 it under the terms of the GNU General Public License as published by 10 the Free Software Foundation; either version 2 of the License, or 11 (at your option) any later version. 12 13 This program is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with this program; if not, write to the Free Software 20 Foundation, Inc., 51 Franklin Street - Fifth Floor, 21 Boston, MA 02110-1301, USA. */ 22 23 /* 24 25 @deftypefn Replacement {const char*} lrealpath (const char *@var{name}) 26 27 Given a pointer to a string containing a pathname, returns a canonical 28 version of the filename. Symlinks will be resolved, and ``.'' and ``..'' 29 components will be simplified. The returned value will be allocated using 30 @code{malloc}, or @code{NULL} will be returned on a memory allocation error. 31 32 @end deftypefn 33 34 */ 35 36 #include "config.h" 37 #include "ansidecl.h" 38 #include "libiberty.h" 39 40 #ifdef HAVE_LIMITS_H 41 #include <limits.h> 42 #endif 43 #ifdef HAVE_STDLIB_H 44 #include <stdlib.h> 45 #endif 46 #ifdef HAVE_UNISTD_H 47 #include <unistd.h> 48 #endif 49 #ifdef HAVE_STRING_H 50 #include <string.h> 51 #endif 52 53 /* On GNU libc systems the declaration is only visible with _GNU_SOURCE. */ 54 #if defined(HAVE_CANONICALIZE_FILE_NAME) \ 55 && defined(NEED_DECLARATION_CANONICALIZE_FILE_NAME) 56 extern char *canonicalize_file_name (const char *); 57 #endif 58 59 #if defined(HAVE_REALPATH) 60 # if defined (PATH_MAX) 61 # define REALPATH_LIMIT PATH_MAX 62 # else 63 # if defined (MAXPATHLEN) 64 # define REALPATH_LIMIT MAXPATHLEN 65 # endif 66 # endif 67 #else 68 /* cygwin has realpath, so it won't get here. */ 69 # if defined (_WIN32) 70 # define WIN32_LEAN_AND_MEAN 71 # include <windows.h> /* for GetFullPathName/GetFinalPathNameByHandle/ 72 CreateFile/CloseHandle */ 73 # define WIN32_REPLACE_SLASHES(_ptr, _len) \ 74 for (unsigned i = 0; i != (_len); ++i) \ 75 if ((_ptr)[i] == '\\') (_ptr)[i] = '/'; 76 77 # define WIN32_UNC_PREFIX "//?/UNC/" 78 # define WIN32_UNC_PREFIX_LEN (sizeof(WIN32_UNC_PREFIX)-1) 79 # define WIN32_IS_UNC_PREFIX(ptr) \ 80 (0 == memcmp(ptr, WIN32_UNC_PREFIX, WIN32_UNC_PREFIX_LEN)) 81 82 # define WIN32_NON_UNC_PREFIX "//?/" 83 # define WIN32_NON_UNC_PREFIX_LEN (sizeof(WIN32_NON_UNC_PREFIX)-1) 84 # define WIN32_IS_NON_UNC_PREFIX(ptr) \ 85 (0 == memcmp(ptr, WIN32_NON_UNC_PREFIX, WIN32_NON_UNC_PREFIX_LEN)) 86 87 /* Get full path name without symlinks resolution. 88 It also converts all forward slashes to back slashes. 89 */ 90 char* get_full_path_name(const char *filename) { 91 DWORD len; 92 char *buf, *ptr, *res; 93 94 /* determining the required buffer size. 95 from the man: `If the lpBuffer buffer is too small to contain 96 the path, the return value is the size, in TCHARs, of the buffer 97 that is required to hold the path _and_the_terminating_null_character_` 98 */ 99 len = GetFullPathName(filename, 0, NULL, NULL); 100 101 if ( len == 0 ) 102 return strdup(filename); 103 104 buf = (char *)malloc(len); 105 106 /* no point to check the result again */ 107 len = GetFullPathName(filename, len, buf, NULL); 108 buf[len] = 0; 109 110 /* replace slashes */ 111 WIN32_REPLACE_SLASHES(buf, len); 112 113 /* calculate offset based on prefix type */ 114 len = WIN32_IS_UNC_PREFIX(buf) 115 ? (WIN32_UNC_PREFIX_LEN - 2) 116 : WIN32_IS_NON_UNC_PREFIX(buf) 117 ? WIN32_NON_UNC_PREFIX_LEN 118 : 0 119 ; 120 121 ptr = buf + len; 122 if ( WIN32_IS_UNC_PREFIX(buf) ) { 123 ptr[0] = '/'; 124 ptr[1] = '/'; 125 } 126 127 res = strdup(ptr); 128 129 free(buf); 130 131 return res; 132 } 133 134 # if _WIN32_WINNT >= 0x0600 135 136 /* Get full path name WITH symlinks resolution. 137 It also converts all forward slashes to back slashes. 138 */ 139 char* get_final_path_name(HANDLE fh) { 140 DWORD len; 141 char *buf, *ptr, *res; 142 143 /* determining the required buffer size. 144 from the man: `If the function fails because lpszFilePath is too 145 small to hold the string plus the terminating null character, 146 the return value is the required buffer size, in TCHARs. This 147 value _includes_the_size_of_the_terminating_null_character_`. 148 but in my testcase I have path with 26 chars, the function 149 returns 26 also, ie without the trailing zero-char... 150 */ 151 len = GetFinalPathNameByHandle( 152 fh 153 ,NULL 154 ,0 155 ,FILE_NAME_NORMALIZED | VOLUME_NAME_DOS 156 ); 157 158 if ( len == 0 ) 159 return NULL; 160 161 len += 1; /* for zero-char */ 162 buf = (char *)malloc(len); 163 164 /* no point to check the result again */ 165 len = GetFinalPathNameByHandle( 166 fh 167 ,buf 168 ,len 169 ,FILE_NAME_NORMALIZED | VOLUME_NAME_DOS 170 ); 171 buf[len] = 0; 172 173 /* replace slashes */ 174 WIN32_REPLACE_SLASHES(buf, len); 175 176 /* calculate offset based on prefix type */ 177 len = WIN32_IS_UNC_PREFIX(buf) 178 ? (WIN32_UNC_PREFIX_LEN - 2) 179 : WIN32_IS_NON_UNC_PREFIX(buf) 180 ? WIN32_NON_UNC_PREFIX_LEN 181 : 0 182 ; 183 184 ptr = buf + len; 185 if ( WIN32_IS_UNC_PREFIX(buf) ) { 186 ptr[0] = '/'; 187 ptr[1] = '/'; 188 } 189 190 res = strdup(ptr); 191 192 free(buf); 193 194 return res; 195 } 196 197 # endif // _WIN32_WINNT >= 0x0600 198 199 # endif // _WIN32 200 #endif 201 202 char * 203 lrealpath (const char *filename) 204 { 205 /* Method 1: The system has a compile time upper bound on a filename 206 path. Use that and realpath() to canonicalize the name. This is 207 the most common case. Note that, if there isn't a compile time 208 upper bound, you want to avoid realpath() at all costs. */ 209 #if defined(REALPATH_LIMIT) 210 { 211 char buf[REALPATH_LIMIT]; 212 const char *rp = realpath (filename, buf); 213 if (rp == NULL) 214 rp = filename; 215 return strdup (rp); 216 } 217 #endif /* REALPATH_LIMIT */ 218 219 /* Method 2: The host system (i.e., GNU) has the function 220 canonicalize_file_name() which malloc's a chunk of memory and 221 returns that, use that. */ 222 #if defined(HAVE_CANONICALIZE_FILE_NAME) 223 { 224 char *rp = canonicalize_file_name (filename); 225 if (rp == NULL) 226 return strdup (filename); 227 else 228 return rp; 229 } 230 #endif 231 232 /* Method 3: Now we're getting desperate! The system doesn't have a 233 compile time buffer size and no alternative function. Query the 234 OS, using pathconf(), for the buffer limit. Care is needed 235 though, some systems do not limit PATH_MAX (return -1 for 236 pathconf()) making it impossible to pass a correctly sized buffer 237 to realpath() (it could always overflow). On those systems, we 238 skip this. */ 239 #if defined (HAVE_REALPATH) && defined (HAVE_UNISTD_H) 240 { 241 /* Find out the max path size. */ 242 long path_max = pathconf ("/", _PC_PATH_MAX); 243 if (path_max > 0) 244 { 245 /* PATH_MAX is bounded. */ 246 char *buf, *rp, *ret; 247 buf = (char *) malloc (path_max); 248 if (buf == NULL) 249 return NULL; 250 rp = realpath (filename, buf); 251 ret = strdup (rp ? rp : filename); 252 free (buf); 253 return ret; 254 } 255 } 256 #endif 257 258 /* The MS Windows method */ 259 #if defined (_WIN32) 260 { 261 char *res; 262 263 /* For Windows Vista and greater */ 264 #if _WIN32_WINNT >= 0x0600 265 266 /* For some reason the function receives just empty `filename`, but not NULL. 267 What should we do in that case? 268 According to `strdup()` implementation 269 (https://elixir.bootlin.com/glibc/latest/source/string/strdup.c) 270 it will alloc 1 byte even for empty but non NULL string. 271 OK, will use `strdup()` for that case. 272 */ 273 if ( 0 == strlen(filename) ) 274 return strdup(filename); 275 276 HANDLE fh = CreateFile( 277 filename 278 ,FILE_READ_ATTRIBUTES 279 ,FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE 280 ,NULL 281 ,OPEN_EXISTING 282 ,FILE_FLAG_BACKUP_SEMANTICS 283 ,NULL 284 ); 285 286 if ( fh == INVALID_HANDLE_VALUE ) { 287 res = get_full_path_name(filename); 288 } else { 289 res = get_final_path_name(fh); 290 CloseHandle(fh); 291 292 if ( !res ) 293 res = get_full_path_name(filename); 294 } 295 296 #else 297 298 /* For Windows XP */ 299 res = get_full_path_name(filename); 300 301 #endif // _WIN32_WINNT >= 0x0600 302 303 return res; 304 } 305 #endif // _WIN32 306 } 307