Coverage Report

Created: 2025-06-24 06:45

/src/binutils-gdb/libiberty/lrealpath.c
Line
Count
Source (jump to first uncovered line)
1
/* Libiberty realpath.  Like realpath, but more consistent behavior.
2
   Based on gdb_realpath from GDB.
3
4
   Copyright (C) 2003-2025 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
26
{
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
26
#if defined(REALPATH_LIMIT)
210
26
  {
211
26
    char buf[REALPATH_LIMIT];
212
26
    const char *rp = realpath (filename, buf);
213
26
    if (rp == NULL)
214
0
      rp = filename;
215
26
    return strdup (rp);
216
26
  }
217
0
#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
0
#if defined(HAVE_CANONICALIZE_FILE_NAME)
223
0
  {
224
0
    char *rp = canonicalize_file_name (filename);
225
0
    if (rp == NULL)
226
0
      return strdup (filename);
227
0
    else
228
0
      return rp;
229
0
  }
230
0
#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
0
#if defined (HAVE_REALPATH) && defined (HAVE_UNISTD_H)
240
0
  {
241
    /* Find out the max path size.  */
242
0
    long path_max = pathconf ("/", _PC_PATH_MAX);
243
0
    if (path_max > 0)
244
0
      {
245
  /* PATH_MAX is bounded.  */
246
0
  char *buf, *rp, *ret;
247
0
  buf = (char *) malloc (path_max);
248
0
  if (buf == NULL)
249
0
    return NULL;
250
0
  rp = realpath (filename, buf);
251
0
  ret = strdup (rp ? rp : filename);
252
0
  free (buf);
253
0
  return ret;
254
0
      }
255
0
  }
256
0
#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
0
}