(root)/
tar-1.35/
gnu/
readlinkat.c
       1  /* Read a symlink relative to an open directory.
       2     Copyright (C) 2009-2023 Free Software Foundation, Inc.
       3  
       4     This program is free software: you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published by
       6     the Free Software Foundation, either version 3 of the License, or
       7     (at your option) any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  /* written by Eric Blake */
      18  
      19  #include <config.h>
      20  
      21  /* Specification.  */
      22  #include <unistd.h>
      23  
      24  #include <errno.h>
      25  #include <stdlib.h>
      26  #include <string.h>
      27  #include <sys/stat.h>
      28  
      29  #if HAVE_READLINKAT
      30  
      31  # undef fstatat
      32  # undef readlinkat
      33  
      34  ssize_t
      35  rpl_readlinkat (int fd, char const *file, char *buf, size_t bufsize)
      36  {
      37  # if READLINK_TRAILING_SLASH_BUG
      38    size_t file_len = strlen (file);
      39    if (file_len && file[file_len - 1] == '/')
      40      {
      41        /* Even if FILE without the slash is a symlink to a directory,
      42           both lstat() and stat() must resolve the trailing slash to
      43           the directory rather than the symlink.  We can therefore
      44           safely use fstatat(..., 0) to distinguish between EINVAL and
      45           ENOTDIR/ENOENT, avoiding extra overhead of rpl_fstatat().  */
      46        struct stat st;
      47        if (fstatat (fd, file, &st, 0) == 0 || errno == EOVERFLOW)
      48          errno = EINVAL;
      49        return -1;
      50      }
      51  # endif /* READLINK_TRAILING_SLASH_BUG */
      52  
      53    ssize_t r = readlinkat (fd, file, buf, bufsize);
      54  
      55  # if READLINK_TRUNCATE_BUG
      56    if (r < 0 && errno == ERANGE)
      57      {
      58        /* Try again with a bigger buffer.  This is just for test cases;
      59           real code invariably discards short reads.  */
      60        char stackbuf[4032];
      61        r = readlinkat (fd, file, stackbuf, sizeof stackbuf);
      62        if (r < 0)
      63          {
      64            if (errno == ERANGE)
      65              {
      66                /* Clear the buffer, which is good enough for real code.
      67                   Thankfully, no test cases try short reads of enormous
      68                   symlinks and what would be the point anyway?  */
      69                r = bufsize;
      70                memset (buf, 0, r);
      71              }
      72          }
      73        else
      74          {
      75            if (bufsize < r)
      76              r = bufsize;
      77            memcpy (buf, stackbuf, r);
      78          }
      79      }
      80  # endif
      81  
      82    return r;
      83  }
      84  
      85  #else
      86  
      87  /* Gnulib provides a readlink stub for mingw; use it for distinction
      88     between EINVAL and ENOENT, rather than always failing with ENOSYS.  */
      89  
      90  /* POSIX 2008 says that unlike readlink, readlinkat returns 0 for
      91     success instead of the buffer length.  But this would render
      92     readlinkat worthless since readlink does not guarantee a
      93     NUL-terminated buffer.  Assume this was a bug in POSIX.  */
      94  
      95  /* Read the contents of symlink FILE into buffer BUF of size BUFSIZE, in the
      96     directory open on descriptor FD.  If possible, do it without changing
      97     the working directory.  Otherwise, resort to using save_cwd/fchdir,
      98     then readlink/restore_cwd.  If either the save_cwd or the restore_cwd
      99     fails, then give a diagnostic and exit nonzero.  */
     100  
     101  # define AT_FUNC_NAME readlinkat
     102  # define AT_FUNC_F1 readlink
     103  # define AT_FUNC_POST_FILE_PARAM_DECLS , char *buf, size_t bufsize
     104  # define AT_FUNC_POST_FILE_ARGS        , buf, bufsize
     105  # define AT_FUNC_RESULT ssize_t
     106  # include "at-func.c"
     107  # undef AT_FUNC_NAME
     108  # undef AT_FUNC_F1
     109  # undef AT_FUNC_POST_FILE_PARAM_DECLS
     110  # undef AT_FUNC_POST_FILE_ARGS
     111  # undef AT_FUNC_RESULT
     112  
     113  #endif