careadlinkat.c 4.69 KB
Newer Older
1 2
/* Read symbolic links into a buffer without size limitation, relative to fd.

Paul Eggert's avatar
Paul Eggert committed
3 4
   Copyright (C) 2001, 2003-2004, 2007, 2009-2019 Free Software Foundation,
   Inc.
5 6 7 8 9 10 11 12 13 14 15 16

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
Paul Eggert's avatar
Paul Eggert committed
17
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
18 19 20 21 22 23 24 25 26 27

/* Written by Paul Eggert, Bruno Haible, and Jim Meyering.  */

#include <config.h>

#include "careadlinkat.h"

#include <errno.h>
#include <limits.h>
#include <string.h>
Paul Eggert's avatar
Paul Eggert committed
28
#include <unistd.h>
29 30 31 32 33 34 35 36 37 38

/* Define this independently so that stdint.h is not a prerequisite.  */
#ifndef SIZE_MAX
# define SIZE_MAX ((size_t) -1)
#endif

#ifndef SSIZE_MAX
# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
#endif

Paul Eggert's avatar
Paul Eggert committed
39 40
#include "allocator.h"

41 42 43 44 45 46 47 48 49 50 51 52 53 54
/* Assuming the current directory is FD, get the symbolic link value
   of FILENAME as a null-terminated string and put it into a buffer.
   If FD is AT_FDCWD, FILENAME is interpreted relative to the current
   working directory, as in openat.

   If the link is small enough to fit into BUFFER put it there.
   BUFFER's size is BUFFER_SIZE, and BUFFER can be null
   if BUFFER_SIZE is zero.

   If the link is not small, put it into a dynamically allocated
   buffer managed by ALLOC.  It is the caller's responsibility to free
   the returned value if it is nonnull and is not BUFFER.  A null
   ALLOC stands for the standard allocator.

Paul Eggert's avatar
Paul Eggert committed
55 56 57 58
   The PREADLINKAT function specifies how to read links.  It operates
   like POSIX readlinkat()
   <http://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html>
   but can assume that its first argument is the same as FD.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

   If successful, return the buffer address; otherwise return NULL and
   set errno.  */

char *
careadlinkat (int fd, char const *filename,
              char *buffer, size_t buffer_size,
              struct allocator const *alloc,
              ssize_t (*preadlinkat) (int, char const *, char *, size_t))
{
  char *buf;
  size_t buf_size;
  size_t buf_size_max =
    SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX;
  char stack_buf[1024];

Paul Eggert's avatar
Paul Eggert committed
75
  if (! alloc)
Paul Eggert's avatar
Paul Eggert committed
76
    alloc = &stdlib_allocator;
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104

  if (! buffer_size)
    {
      /* Allocate the initial buffer on the stack.  This way, in the
         common case of a symlink of small size, we get away with a
         single small malloc() instead of a big malloc() followed by a
         shrinking realloc().  */
      buffer = stack_buf;
      buffer_size = sizeof stack_buf;
    }

  buf = buffer;
  buf_size = buffer_size;

  do
    {
      /* Attempt to read the link into the current buffer.  */
      ssize_t link_length = preadlinkat (fd, filename, buf, buf_size);
      size_t link_size;
      if (link_length < 0)
        {
          /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink returns -1
             with errno == ERANGE if the buffer is too small.  */
          int readlinkat_errno = errno;
          if (readlinkat_errno != ERANGE)
            {
              if (buf != buffer)
                {
Paul Eggert's avatar
Paul Eggert committed
105
                  alloc->free (buf);
106 107 108 109 110 111 112 113 114 115 116 117 118 119
                  errno = readlinkat_errno;
                }
              return NULL;
            }
        }

      link_size = link_length;

      if (link_size < buf_size)
        {
          buf[link_size++] = '\0';

          if (buf == stack_buf)
            {
Paul Eggert's avatar
Paul Eggert committed
120
              char *b = (char *) alloc->allocate (link_size);
121
              buf_size = link_size;
122 123 124 125 126
              if (! b)
                break;
              memcpy (b, buf, link_size);
              buf = b;
            }
Paul Eggert's avatar
Paul Eggert committed
127
          else if (link_size < buf_size && buf != buffer && alloc->reallocate)
128 129
            {
              /* Shrink BUF before returning it.  */
Paul Eggert's avatar
Paul Eggert committed
130
              char *b = (char *) alloc->reallocate (buf, link_size);
131 132 133 134 135 136 137 138
              if (b)
                buf = b;
            }

          return buf;
        }

      if (buf != buffer)
Paul Eggert's avatar
Paul Eggert committed
139
        alloc->free (buf);
140 141 142 143 144

      if (buf_size <= buf_size_max / 2)
        buf_size *= 2;
      else if (buf_size < buf_size_max)
        buf_size = buf_size_max;
145 146 147 148 149
      else if (buf_size_max < SIZE_MAX)
        {
          errno = ENAMETOOLONG;
          return NULL;
        }
150 151
      else
        break;
Paul Eggert's avatar
Paul Eggert committed
152
      buf = (char *) alloc->allocate (buf_size);
153 154 155
    }
  while (buf);

Paul Eggert's avatar
Paul Eggert committed
156
  if (alloc->die)
157
    alloc->die (buf_size);
158 159 160
  errno = ENOMEM;
  return NULL;
}