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
27Given a pointer to a string containing a pathname, returns a canonical
28version of the filename. Symlinks will be resolved, and ``.'' and ``..''
29components 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)
56extern 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*/
90char* 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*/
139char* 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
202char *
203lrealpath (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 (name: filename, resolved: buf);
213 if (rp == NULL)
214 rp = filename;
215 return strdup (s: 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 (name: filename);
225 if (rp == NULL)
226 return strdup (s: 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 (path: "/", _PC_PATH_MAX);
243 if (path_max > 0)
244 {
245 /* PATH_MAX is bounded. */
246 char *buf, *rp, *ret;
247 buf = (char *) malloc (size: path_max);
248 if (buf == NULL)
249 return NULL;
250 rp = realpath (name: filename, resolved: buf);
251 ret = strdup (s: rp ? rp : filename);
252 free (ptr: 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

source code of libiberty/lrealpath.c