xref: /netbsd-src/external/gpl3/binutils/dist/libiberty/lrealpath.c (revision cb63e24e8d6aae7ddac1859a9015f48b1d8bd90e)
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 */
get_full_path_name(const char * filename)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 */
get_final_path_name(HANDLE fh)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 *
lrealpath(const char * filename)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