xref: /netbsd-src/external/gpl3/gdb/dist/libiberty/lrealpath.c (revision 7e120ff03ede3fe64e2c8620c01465d528502ddb)
198b9484cSchristos /* Libiberty realpath.  Like realpath, but more consistent behavior.
298b9484cSchristos    Based on gdb_realpath from GDB.
398b9484cSchristos 
4*7e120ff0Schristos    Copyright (C) 2003-2024 Free Software Foundation, Inc.
598b9484cSchristos 
698b9484cSchristos    This file is part of the libiberty library.
798b9484cSchristos 
898b9484cSchristos    This program is free software; you can redistribute it and/or modify
998b9484cSchristos    it under the terms of the GNU General Public License as published by
1098b9484cSchristos    the Free Software Foundation; either version 2 of the License, or
1198b9484cSchristos    (at your option) any later version.
1298b9484cSchristos 
1398b9484cSchristos    This program is distributed in the hope that it will be useful,
1498b9484cSchristos    but WITHOUT ANY WARRANTY; without even the implied warranty of
1598b9484cSchristos    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1698b9484cSchristos    GNU General Public License for more details.
1798b9484cSchristos 
1898b9484cSchristos    You should have received a copy of the GNU General Public License
1998b9484cSchristos    along with this program; if not, write to the Free Software
2098b9484cSchristos    Foundation, Inc., 51 Franklin Street - Fifth Floor,
2198b9484cSchristos    Boston, MA 02110-1301, USA.  */
2298b9484cSchristos 
2398b9484cSchristos /*
2498b9484cSchristos 
2598b9484cSchristos @deftypefn Replacement {const char*} lrealpath (const char *@var{name})
2698b9484cSchristos 
2798b9484cSchristos Given a pointer to a string containing a pathname, returns a canonical
2898b9484cSchristos version of the filename.  Symlinks will be resolved, and ``.'' and ``..''
2998b9484cSchristos components will be simplified.  The returned value will be allocated using
3098b9484cSchristos @code{malloc}, or @code{NULL} will be returned on a memory allocation error.
3198b9484cSchristos 
3298b9484cSchristos @end deftypefn
3398b9484cSchristos 
3498b9484cSchristos */
3598b9484cSchristos 
3698b9484cSchristos #include "config.h"
3798b9484cSchristos #include "ansidecl.h"
3898b9484cSchristos #include "libiberty.h"
3998b9484cSchristos 
4098b9484cSchristos #ifdef HAVE_LIMITS_H
4198b9484cSchristos #include <limits.h>
4298b9484cSchristos #endif
4398b9484cSchristos #ifdef HAVE_STDLIB_H
4498b9484cSchristos #include <stdlib.h>
4598b9484cSchristos #endif
4698b9484cSchristos #ifdef HAVE_UNISTD_H
4798b9484cSchristos #include <unistd.h>
4898b9484cSchristos #endif
4998b9484cSchristos #ifdef HAVE_STRING_H
5098b9484cSchristos #include <string.h>
5198b9484cSchristos #endif
5298b9484cSchristos 
5398b9484cSchristos /* On GNU libc systems the declaration is only visible with _GNU_SOURCE.  */
5498b9484cSchristos #if defined(HAVE_CANONICALIZE_FILE_NAME) \
5598b9484cSchristos     && defined(NEED_DECLARATION_CANONICALIZE_FILE_NAME)
5698b9484cSchristos extern char *canonicalize_file_name (const char *);
5798b9484cSchristos #endif
5898b9484cSchristos 
5998b9484cSchristos #if defined(HAVE_REALPATH)
6098b9484cSchristos # if defined (PATH_MAX)
6198b9484cSchristos #  define REALPATH_LIMIT PATH_MAX
6298b9484cSchristos # else
6398b9484cSchristos #  if defined (MAXPATHLEN)
6498b9484cSchristos #   define REALPATH_LIMIT MAXPATHLEN
6598b9484cSchristos #  endif
6698b9484cSchristos # endif
6798b9484cSchristos #else
6898b9484cSchristos   /* cygwin has realpath, so it won't get here.  */
6998b9484cSchristos # if defined (_WIN32)
7098b9484cSchristos #  define WIN32_LEAN_AND_MEAN
71*7e120ff0Schristos #  include <windows.h> /* for GetFullPathName/GetFinalPathNameByHandle/
72*7e120ff0Schristos                           CreateFile/CloseHandle */
73*7e120ff0Schristos #  define WIN32_REPLACE_SLASHES(_ptr, _len) \
74*7e120ff0Schristos      for (unsigned i = 0; i != (_len); ++i) \
75*7e120ff0Schristos        if ((_ptr)[i] == '\\') (_ptr)[i] = '/';
76*7e120ff0Schristos 
77*7e120ff0Schristos #  define WIN32_UNC_PREFIX "//?/UNC/"
78*7e120ff0Schristos #  define WIN32_UNC_PREFIX_LEN (sizeof(WIN32_UNC_PREFIX)-1)
79*7e120ff0Schristos #  define WIN32_IS_UNC_PREFIX(ptr) \
80*7e120ff0Schristos   (0 == memcmp(ptr, WIN32_UNC_PREFIX, WIN32_UNC_PREFIX_LEN))
81*7e120ff0Schristos 
82*7e120ff0Schristos #  define WIN32_NON_UNC_PREFIX "//?/"
83*7e120ff0Schristos #  define WIN32_NON_UNC_PREFIX_LEN (sizeof(WIN32_NON_UNC_PREFIX)-1)
84*7e120ff0Schristos #  define WIN32_IS_NON_UNC_PREFIX(ptr) \
85*7e120ff0Schristos   (0 == memcmp(ptr, WIN32_NON_UNC_PREFIX, WIN32_NON_UNC_PREFIX_LEN))
86*7e120ff0Schristos 
87*7e120ff0Schristos /* Get full path name without symlinks resolution.
88*7e120ff0Schristos    It also converts all forward slashes to back slashes.
89*7e120ff0Schristos */
90*7e120ff0Schristos char* get_full_path_name(const char *filename) {
91*7e120ff0Schristos   DWORD len;
92*7e120ff0Schristos   char *buf, *ptr, *res;
93*7e120ff0Schristos 
94*7e120ff0Schristos   /* determining the required buffer size.
95*7e120ff0Schristos      from the man: `If the lpBuffer buffer is too small to contain
96*7e120ff0Schristos      the path, the return value is the size, in TCHARs, of the buffer
97*7e120ff0Schristos      that is required to hold the path _and_the_terminating_null_character_`
98*7e120ff0Schristos   */
99*7e120ff0Schristos   len = GetFullPathName(filename, 0, NULL, NULL);
100*7e120ff0Schristos 
101*7e120ff0Schristos   if ( len == 0 )
102*7e120ff0Schristos     return strdup(filename);
103*7e120ff0Schristos 
104*7e120ff0Schristos   buf = (char *)malloc(len);
105*7e120ff0Schristos 
106*7e120ff0Schristos   /* no point to check the result again */
107*7e120ff0Schristos   len = GetFullPathName(filename, len, buf, NULL);
108*7e120ff0Schristos   buf[len] = 0;
109*7e120ff0Schristos 
110*7e120ff0Schristos   /* replace slashes */
111*7e120ff0Schristos   WIN32_REPLACE_SLASHES(buf, len);
112*7e120ff0Schristos 
113*7e120ff0Schristos   /* calculate offset based on prefix type */
114*7e120ff0Schristos   len = WIN32_IS_UNC_PREFIX(buf)
115*7e120ff0Schristos     ? (WIN32_UNC_PREFIX_LEN - 2)
116*7e120ff0Schristos     : WIN32_IS_NON_UNC_PREFIX(buf)
117*7e120ff0Schristos       ? WIN32_NON_UNC_PREFIX_LEN
118*7e120ff0Schristos       : 0
119*7e120ff0Schristos   ;
120*7e120ff0Schristos 
121*7e120ff0Schristos   ptr = buf + len;
122*7e120ff0Schristos   if ( WIN32_IS_UNC_PREFIX(buf) ) {
123*7e120ff0Schristos     ptr[0] = '/';
124*7e120ff0Schristos     ptr[1] = '/';
125*7e120ff0Schristos   }
126*7e120ff0Schristos 
127*7e120ff0Schristos   res = strdup(ptr);
128*7e120ff0Schristos 
129*7e120ff0Schristos   free(buf);
130*7e120ff0Schristos 
131*7e120ff0Schristos   return res;
132*7e120ff0Schristos }
133*7e120ff0Schristos 
134*7e120ff0Schristos # if _WIN32_WINNT >= 0x0600
135*7e120ff0Schristos 
136*7e120ff0Schristos /* Get full path name WITH symlinks resolution.
137*7e120ff0Schristos    It also converts all forward slashes to back slashes.
138*7e120ff0Schristos */
139*7e120ff0Schristos char* get_final_path_name(HANDLE fh) {
140*7e120ff0Schristos   DWORD len;
141*7e120ff0Schristos   char *buf, *ptr, *res;
142*7e120ff0Schristos 
143*7e120ff0Schristos   /* determining the required buffer size.
144*7e120ff0Schristos      from the  man: `If the function fails because lpszFilePath is too
145*7e120ff0Schristos      small to hold the string plus the terminating null character,
146*7e120ff0Schristos      the return value is the required buffer size, in TCHARs. This
147*7e120ff0Schristos      value _includes_the_size_of_the_terminating_null_character_`.
148*7e120ff0Schristos      but in my testcase I have path with 26 chars, the function
149*7e120ff0Schristos      returns 26 also, ie without the trailing zero-char...
150*7e120ff0Schristos   */
151*7e120ff0Schristos   len = GetFinalPathNameByHandle(
152*7e120ff0Schristos      fh
153*7e120ff0Schristos     ,NULL
154*7e120ff0Schristos     ,0
155*7e120ff0Schristos     ,FILE_NAME_NORMALIZED | VOLUME_NAME_DOS
156*7e120ff0Schristos   );
157*7e120ff0Schristos 
158*7e120ff0Schristos   if ( len == 0 )
159*7e120ff0Schristos     return NULL;
160*7e120ff0Schristos 
161*7e120ff0Schristos   len += 1; /* for zero-char */
162*7e120ff0Schristos   buf = (char *)malloc(len);
163*7e120ff0Schristos 
164*7e120ff0Schristos   /* no point to check the result again */
165*7e120ff0Schristos   len = GetFinalPathNameByHandle(
166*7e120ff0Schristos      fh
167*7e120ff0Schristos     ,buf
168*7e120ff0Schristos     ,len
169*7e120ff0Schristos     ,FILE_NAME_NORMALIZED | VOLUME_NAME_DOS
170*7e120ff0Schristos   );
171*7e120ff0Schristos   buf[len] = 0;
172*7e120ff0Schristos 
173*7e120ff0Schristos   /* replace slashes */
174*7e120ff0Schristos   WIN32_REPLACE_SLASHES(buf, len);
175*7e120ff0Schristos 
176*7e120ff0Schristos   /* calculate offset based on prefix type */
177*7e120ff0Schristos   len = WIN32_IS_UNC_PREFIX(buf)
178*7e120ff0Schristos     ? (WIN32_UNC_PREFIX_LEN - 2)
179*7e120ff0Schristos     : WIN32_IS_NON_UNC_PREFIX(buf)
180*7e120ff0Schristos       ? WIN32_NON_UNC_PREFIX_LEN
181*7e120ff0Schristos       : 0
182*7e120ff0Schristos   ;
183*7e120ff0Schristos 
184*7e120ff0Schristos   ptr = buf + len;
185*7e120ff0Schristos   if ( WIN32_IS_UNC_PREFIX(buf) ) {
186*7e120ff0Schristos     ptr[0] = '/';
187*7e120ff0Schristos     ptr[1] = '/';
188*7e120ff0Schristos   }
189*7e120ff0Schristos 
190*7e120ff0Schristos   res = strdup(ptr);
191*7e120ff0Schristos 
192*7e120ff0Schristos   free(buf);
193*7e120ff0Schristos 
194*7e120ff0Schristos   return res;
195*7e120ff0Schristos }
196*7e120ff0Schristos 
197*7e120ff0Schristos # endif // _WIN32_WINNT >= 0x0600
198*7e120ff0Schristos 
199*7e120ff0Schristos # endif // _WIN32
20098b9484cSchristos #endif
20198b9484cSchristos 
20298b9484cSchristos char *
20398b9484cSchristos lrealpath (const char *filename)
20498b9484cSchristos {
20598b9484cSchristos   /* Method 1: The system has a compile time upper bound on a filename
20698b9484cSchristos      path.  Use that and realpath() to canonicalize the name.  This is
20798b9484cSchristos      the most common case.  Note that, if there isn't a compile time
20898b9484cSchristos      upper bound, you want to avoid realpath() at all costs.  */
20998b9484cSchristos #if defined(REALPATH_LIMIT)
21098b9484cSchristos   {
21198b9484cSchristos     char buf[REALPATH_LIMIT];
21298b9484cSchristos     const char *rp = realpath (filename, buf);
21398b9484cSchristos     if (rp == NULL)
21498b9484cSchristos       rp = filename;
21598b9484cSchristos     return strdup (rp);
21698b9484cSchristos   }
21798b9484cSchristos #endif /* REALPATH_LIMIT */
21898b9484cSchristos 
21998b9484cSchristos   /* Method 2: The host system (i.e., GNU) has the function
22098b9484cSchristos      canonicalize_file_name() which malloc's a chunk of memory and
22198b9484cSchristos      returns that, use that.  */
22298b9484cSchristos #if defined(HAVE_CANONICALIZE_FILE_NAME)
22398b9484cSchristos   {
22498b9484cSchristos     char *rp = canonicalize_file_name (filename);
22598b9484cSchristos     if (rp == NULL)
22698b9484cSchristos       return strdup (filename);
22798b9484cSchristos     else
22898b9484cSchristos       return rp;
22998b9484cSchristos   }
23098b9484cSchristos #endif
23198b9484cSchristos 
23298b9484cSchristos   /* Method 3: Now we're getting desperate!  The system doesn't have a
23398b9484cSchristos      compile time buffer size and no alternative function.  Query the
23498b9484cSchristos      OS, using pathconf(), for the buffer limit.  Care is needed
23598b9484cSchristos      though, some systems do not limit PATH_MAX (return -1 for
23698b9484cSchristos      pathconf()) making it impossible to pass a correctly sized buffer
23798b9484cSchristos      to realpath() (it could always overflow).  On those systems, we
23898b9484cSchristos      skip this.  */
23998b9484cSchristos #if defined (HAVE_REALPATH) && defined (HAVE_UNISTD_H)
24098b9484cSchristos   {
24198b9484cSchristos     /* Find out the max path size.  */
24298b9484cSchristos     long path_max = pathconf ("/", _PC_PATH_MAX);
24398b9484cSchristos     if (path_max > 0)
24498b9484cSchristos       {
24598b9484cSchristos 	/* PATH_MAX is bounded.  */
24698b9484cSchristos 	char *buf, *rp, *ret;
24798b9484cSchristos 	buf = (char *) malloc (path_max);
24898b9484cSchristos 	if (buf == NULL)
24998b9484cSchristos 	  return NULL;
25098b9484cSchristos 	rp = realpath (filename, buf);
25198b9484cSchristos 	ret = strdup (rp ? rp : filename);
25298b9484cSchristos 	free (buf);
25398b9484cSchristos 	return ret;
25498b9484cSchristos       }
25598b9484cSchristos   }
25698b9484cSchristos #endif
25798b9484cSchristos 
258*7e120ff0Schristos   /* The MS Windows method */
25998b9484cSchristos #if defined (_WIN32)
26098b9484cSchristos   {
261*7e120ff0Schristos     char *res;
26298b9484cSchristos 
263*7e120ff0Schristos     /* For Windows Vista and greater */
264*7e120ff0Schristos #if _WIN32_WINNT >= 0x0600
265*7e120ff0Schristos 
266*7e120ff0Schristos     /* For some reason the function receives just empty `filename`, but not NULL.
267*7e120ff0Schristos        What should we do in that case?
268*7e120ff0Schristos        According to `strdup()` implementation
269*7e120ff0Schristos          (https://elixir.bootlin.com/glibc/latest/source/string/strdup.c)
270*7e120ff0Schristos        it will alloc 1 byte even for empty but non NULL string.
271*7e120ff0Schristos        OK, will use `strdup()` for that case.
272*7e120ff0Schristos     */
273*7e120ff0Schristos     if ( 0 == strlen(filename) )
27498b9484cSchristos       return strdup(filename);
275*7e120ff0Schristos 
276*7e120ff0Schristos     HANDLE fh = CreateFile(
277*7e120ff0Schristos        filename
278*7e120ff0Schristos       ,FILE_READ_ATTRIBUTES
279*7e120ff0Schristos       ,FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE
280*7e120ff0Schristos       ,NULL
281*7e120ff0Schristos       ,OPEN_EXISTING
282*7e120ff0Schristos       ,FILE_FLAG_BACKUP_SEMANTICS
283*7e120ff0Schristos       ,NULL
284*7e120ff0Schristos     );
285*7e120ff0Schristos 
286*7e120ff0Schristos     if ( fh == INVALID_HANDLE_VALUE ) {
287*7e120ff0Schristos       res = get_full_path_name(filename);
288*7e120ff0Schristos     } else {
289*7e120ff0Schristos       res = get_final_path_name(fh);
290*7e120ff0Schristos       CloseHandle(fh);
291*7e120ff0Schristos 
292*7e120ff0Schristos       if ( !res )
293*7e120ff0Schristos         res = get_full_path_name(filename);
294*7e120ff0Schristos     }
295*7e120ff0Schristos 
296*7e120ff0Schristos #else
297*7e120ff0Schristos 
298*7e120ff0Schristos     /* For Windows XP */
299*7e120ff0Schristos     res = get_full_path_name(filename);
300*7e120ff0Schristos 
301*7e120ff0Schristos #endif // _WIN32_WINNT >= 0x0600
302*7e120ff0Schristos 
303*7e120ff0Schristos     return res;
304*7e120ff0Schristos   }
305*7e120ff0Schristos #endif // _WIN32
30698b9484cSchristos }
307