Blame lib/careadlinkat.c

Packit Service fdd496
/* Read symbolic links into a buffer without size limitation, relative to fd.
Packit Service fdd496
Packit Service fdd496
   Copyright (C) 2001, 2003-2004, 2007, 2009-2017 Free Software Foundation,
Packit Service fdd496
   Inc.
Packit Service fdd496
Packit Service fdd496
   This program is free software: you can redistribute it and/or modify
Packit Service fdd496
   it under the terms of the GNU General Public License as published by
Packit Service fdd496
   the Free Software Foundation; either version 3 of the License, or
Packit Service fdd496
   (at your option) any later version.
Packit Service fdd496
Packit Service fdd496
   This program is distributed in the hope that it will be useful,
Packit Service fdd496
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service fdd496
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service fdd496
   GNU General Public License for more details.
Packit Service fdd496
Packit Service fdd496
   You should have received a copy of the GNU General Public License
Packit Service fdd496
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
Packit Service fdd496
Packit Service fdd496
/* Written by Paul Eggert, Bruno Haible, and Jim Meyering.  */
Packit Service fdd496
Packit Service fdd496
#include <config.h>
Packit Service fdd496
Packit Service fdd496
#include "careadlinkat.h"
Packit Service fdd496
Packit Service fdd496
#include <errno.h>
Packit Service fdd496
#include <limits.h>
Packit Service fdd496
#include <string.h>
Packit Service fdd496
#include <unistd.h>
Packit Service fdd496
Packit Service fdd496
/* Define this independently so that stdint.h is not a prerequisite.  */
Packit Service fdd496
#ifndef SIZE_MAX
Packit Service fdd496
# define SIZE_MAX ((size_t) -1)
Packit Service fdd496
#endif
Packit Service fdd496
Packit Service fdd496
#ifndef SSIZE_MAX
Packit Service fdd496
# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
Packit Service fdd496
#endif
Packit Service fdd496
Packit Service fdd496
#include "allocator.h"
Packit Service fdd496
Packit Service fdd496
/* Assuming the current directory is FD, get the symbolic link value
Packit Service fdd496
   of FILENAME as a null-terminated string and put it into a buffer.
Packit Service fdd496
   If FD is AT_FDCWD, FILENAME is interpreted relative to the current
Packit Service fdd496
   working directory, as in openat.
Packit Service fdd496
Packit Service fdd496
   If the link is small enough to fit into BUFFER put it there.
Packit Service fdd496
   BUFFER's size is BUFFER_SIZE, and BUFFER can be null
Packit Service fdd496
   if BUFFER_SIZE is zero.
Packit Service fdd496
Packit Service fdd496
   If the link is not small, put it into a dynamically allocated
Packit Service fdd496
   buffer managed by ALLOC.  It is the caller's responsibility to free
Packit Service fdd496
   the returned value if it is nonnull and is not BUFFER.  A null
Packit Service fdd496
   ALLOC stands for the standard allocator.
Packit Service fdd496
Packit Service fdd496
   The PREADLINKAT function specifies how to read links.  It operates
Packit Service fdd496
   like POSIX readlinkat()
Packit Service fdd496
   <http://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html>
Packit Service fdd496
   but can assume that its first argument is the same as FD.
Packit Service fdd496
Packit Service fdd496
   If successful, return the buffer address; otherwise return NULL and
Packit Service fdd496
   set errno.  */
Packit Service fdd496
Packit Service fdd496
char *
Packit Service fdd496
careadlinkat (int fd, char const *filename,
Packit Service fdd496
              char *buffer, size_t buffer_size,
Packit Service fdd496
              struct allocator const *alloc,
Packit Service fdd496
              ssize_t (*preadlinkat) (int, char const *, char *, size_t))
Packit Service fdd496
{
Packit Service fdd496
  char *buf;
Packit Service fdd496
  size_t buf_size;
Packit Service fdd496
  size_t buf_size_max =
Packit Service fdd496
    SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX;
Packit Service fdd496
  char stack_buf[1024];
Packit Service fdd496
Packit Service fdd496
  if (! alloc)
Packit Service fdd496
    alloc = &stdlib_allocator;
Packit Service fdd496
Packit Service fdd496
  if (! buffer_size)
Packit Service fdd496
    {
Packit Service fdd496
      /* Allocate the initial buffer on the stack.  This way, in the
Packit Service fdd496
         common case of a symlink of small size, we get away with a
Packit Service fdd496
         single small malloc() instead of a big malloc() followed by a
Packit Service fdd496
         shrinking realloc().  */
Packit Service fdd496
      buffer = stack_buf;
Packit Service fdd496
      buffer_size = sizeof stack_buf;
Packit Service fdd496
    }
Packit Service fdd496
Packit Service fdd496
  buf = buffer;
Packit Service fdd496
  buf_size = buffer_size;
Packit Service fdd496
Packit Service fdd496
  do
Packit Service fdd496
    {
Packit Service fdd496
      /* Attempt to read the link into the current buffer.  */
Packit Service fdd496
      ssize_t link_length = preadlinkat (fd, filename, buf, buf_size);
Packit Service fdd496
      size_t link_size;
Packit Service fdd496
      if (link_length < 0)
Packit Service fdd496
        {
Packit Service fdd496
          /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink returns -1
Packit Service fdd496
             with errno == ERANGE if the buffer is too small.  */
Packit Service fdd496
          int readlinkat_errno = errno;
Packit Service fdd496
          if (readlinkat_errno != ERANGE)
Packit Service fdd496
            {
Packit Service fdd496
              if (buf != buffer)
Packit Service fdd496
                {
Packit Service fdd496
                  alloc->free (buf);
Packit Service fdd496
                  errno = readlinkat_errno;
Packit Service fdd496
                }
Packit Service fdd496
              return NULL;
Packit Service fdd496
            }
Packit Service fdd496
        }
Packit Service fdd496
Packit Service fdd496
      link_size = link_length;
Packit Service fdd496
Packit Service fdd496
      if (link_size < buf_size)
Packit Service fdd496
        {
Packit Service fdd496
          buf[link_size++] = '\0';
Packit Service fdd496
Packit Service fdd496
          if (buf == stack_buf)
Packit Service fdd496
            {
Packit Service fdd496
              char *b = (char *) alloc->allocate (link_size);
Packit Service fdd496
              buf_size = link_size;
Packit Service fdd496
              if (! b)
Packit Service fdd496
                break;
Packit Service fdd496
              memcpy (b, buf, link_size);
Packit Service fdd496
              buf = b;
Packit Service fdd496
            }
Packit Service fdd496
          else if (link_size < buf_size && buf != buffer && alloc->reallocate)
Packit Service fdd496
            {
Packit Service fdd496
              /* Shrink BUF before returning it.  */
Packit Service fdd496
              char *b = (char *) alloc->reallocate (buf, link_size);
Packit Service fdd496
              if (b)
Packit Service fdd496
                buf = b;
Packit Service fdd496
            }
Packit Service fdd496
Packit Service fdd496
          return buf;
Packit Service fdd496
        }
Packit Service fdd496
Packit Service fdd496
      if (buf != buffer)
Packit Service fdd496
        alloc->free (buf);
Packit Service fdd496
Packit Service fdd496
      if (buf_size <= buf_size_max / 2)
Packit Service fdd496
        buf_size *= 2;
Packit Service fdd496
      else if (buf_size < buf_size_max)
Packit Service fdd496
        buf_size = buf_size_max;
Packit Service fdd496
      else if (buf_size_max < SIZE_MAX)
Packit Service fdd496
        {
Packit Service fdd496
          errno = ENAMETOOLONG;
Packit Service fdd496
          return NULL;
Packit Service fdd496
        }
Packit Service fdd496
      else
Packit Service fdd496
        break;
Packit Service fdd496
      buf = (char *) alloc->allocate (buf_size);
Packit Service fdd496
    }
Packit Service fdd496
  while (buf);
Packit Service fdd496
Packit Service fdd496
  if (alloc->die)
Packit Service fdd496
    alloc->die (buf_size);
Packit Service fdd496
  errno = ENOMEM;
Packit Service fdd496
  return NULL;
Packit Service fdd496
}