libstdc++: Use 202100L as feature test check for C++23
[official-gcc.git] / libiberty / lrealpath.c
blob2578288e8acef49d0f4ac624fdc81dfbff7befce
1 /* Libiberty realpath. Like realpath, but more consistent behavior.
2 Based on gdb_realpath from GDB.
4 Copyright (C) 2003-2023 Free Software Foundation, Inc.
6 This file is part of the libiberty library.
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.
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.
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. */
25 @deftypefn Replacement {const char*} lrealpath (const char *@var{name})
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.
32 @end deftypefn
36 #include "config.h"
37 #include "ansidecl.h"
38 #include "libiberty.h"
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
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
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] = '/';
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))
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))
87 /* Get full path name without symlinks resolution.
88 It also converts all forward slashes to back slashes.
90 char* get_full_path_name(const char *filename) {
91 DWORD len;
92 char *buf, *ptr, *res;
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_`
99 len = GetFullPathName(filename, 0, NULL, NULL);
101 if ( len == 0 )
102 return strdup(filename);
104 buf = (char *)malloc(len);
106 /* no point to check the result again */
107 len = GetFullPathName(filename, len, buf, NULL);
108 buf[len] = 0;
110 /* replace slashes */
111 WIN32_REPLACE_SLASHES(buf, len);
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
121 ptr = buf + len;
122 if ( WIN32_IS_UNC_PREFIX(buf) ) {
123 ptr[0] = '/';
124 ptr[1] = '/';
127 res = strdup(ptr);
129 free(buf);
131 return res;
134 # if _WIN32_WINNT >= 0x0600
136 /* Get full path name WITH symlinks resolution.
137 It also converts all forward slashes to back slashes.
139 char* get_final_path_name(HANDLE fh) {
140 DWORD len;
141 char *buf, *ptr, *res;
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...
151 len = GetFinalPathNameByHandle(
153 ,NULL
155 ,FILE_NAME_NORMALIZED | VOLUME_NAME_DOS
158 if ( len == 0 )
159 return NULL;
161 len += 1; /* for zero-char */
162 buf = (char *)malloc(len);
164 /* no point to check the result again */
165 len = GetFinalPathNameByHandle(
167 ,buf
168 ,len
169 ,FILE_NAME_NORMALIZED | VOLUME_NAME_DOS
171 buf[len] = 0;
173 /* replace slashes */
174 WIN32_REPLACE_SLASHES(buf, len);
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
184 ptr = buf + len;
185 if ( WIN32_IS_UNC_PREFIX(buf) ) {
186 ptr[0] = '/';
187 ptr[1] = '/';
190 res = strdup(ptr);
192 free(buf);
194 return res;
197 # endif // _WIN32_WINNT >= 0x0600
199 # endif // _WIN32
200 #endif
202 char *
203 lrealpath (const char *filename)
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)
211 char buf[REALPATH_LIMIT];
212 const char *rp = realpath (filename, buf);
213 if (rp == NULL)
214 rp = filename;
215 return strdup (rp);
217 #endif /* REALPATH_LIMIT */
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)
224 char *rp = canonicalize_file_name (filename);
225 if (rp == NULL)
226 return strdup (filename);
227 else
228 return rp;
230 #endif
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)
241 /* Find out the max path size. */
242 long path_max = pathconf ("/", _PC_PATH_MAX);
243 if (path_max > 0)
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;
256 #endif
258 /* The MS Windows method */
259 #if defined (_WIN32)
261 char *res;
263 /* For Windows Vista and greater */
264 #if _WIN32_WINNT >= 0x0600
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.
273 if ( 0 == strlen(filename) )
274 return strdup(filename);
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
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);
292 if ( !res )
293 res = get_full_path_name(filename);
296 #else
298 /* For Windows XP */
299 res = get_full_path_name(filename);
301 #endif // _WIN32_WINNT >= 0x0600
303 return res;
305 #endif // _WIN32