(root)/
binutils-2.41/
libiberty/
lrealpath.c
       1  /* Libiberty realpath.  Like realpath, but more consistent behavior.
       2     Based on gdb_realpath from GDB.
       3  
       4     Copyright (C) 2003-2023 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  {
     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  }