Branch
Hash :
7b089321
Author :
Date :
2025-01-01T09:24:36
maint: run 'make update-copyright'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
/* readlinkat wrapper to return the link name in malloc'd storage.
Unlike xreadlinkat, only call exit on failure to change directory.
Copyright (C) 2001, 2003-2007, 2009-2025 Free Software Foundation, Inc.
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
along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* Written by Jim Meyering <jim@meyering.net>
and Eric Blake <ebb9@byu.net>. */
#include <config.h>
#include "areadlink.h"
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if HAVE_READLINKAT
/* SYMLINK_MAX is used only for an initial memory-allocation sanity
check, so it's OK to guess too small on hosts where there is no
arbitrary limit to symbolic link length. */
# ifndef SYMLINK_MAX
# define SYMLINK_MAX 1024
# endif
# define MAXSIZE (SIZE_MAX < SSIZE_MAX ? SIZE_MAX : SSIZE_MAX)
/* Call readlinkat to get the symbolic link value of FILE, relative to FD.
SIZE is a hint as to how long the link is expected to be;
typically it is taken from st_size. It need not be correct.
Return a pointer to that NUL-terminated string in malloc'd storage.
If readlinkat fails, malloc fails, or if the link value is longer
than SSIZE_MAX, return NULL (caller may use errno to diagnose).
However, failure to change directory during readlinkat will issue
a diagnostic and exit. */
char *
areadlinkat_with_size (int fd, char const *file, size_t size)
{
/* Some buggy file systems report garbage in st_size. Defend
against them by ignoring outlandish st_size values in the initial
memory allocation. */
size_t symlink_max = SYMLINK_MAX;
size_t INITIAL_LIMIT_BOUND = 8 * 1024;
size_t initial_limit = (symlink_max < INITIAL_LIMIT_BOUND
? symlink_max + 1
: INITIAL_LIMIT_BOUND);
enum { stackbuf_size = 128 };
/* The initial buffer size for the link value. */
size_t buf_size = (size == 0 ? stackbuf_size
: size < initial_limit ? size + 1 : initial_limit);
while (1)
{
ssize_t r;
size_t link_length;
char stackbuf[stackbuf_size];
char *buf = stackbuf;
char *buffer = NULL;
if (! (size == 0 && buf_size == stackbuf_size))
{
buf = buffer = malloc (buf_size);
if (!buffer)
/* We can assume errno == ENOMEM here, since all platforms that have
readlinkat() have a POSIX compliant malloc(). */
return NULL;
}
r = readlinkat (fd, file, buf, buf_size);
link_length = r;
if (r < 0)
{
free (buffer);
return NULL;
}
if (link_length < buf_size)
{
buf[link_length] = 0;
if (!buffer)
{
buffer = malloc (link_length + 1);
if (buffer)
return memcpy (buffer, buf, link_length + 1);
}
else if (link_length + 1 < buf_size)
{
/* Shrink BUFFER before returning it. */
char *shrinked_buffer = realloc (buffer, link_length + 1);
if (shrinked_buffer != NULL)
buffer = shrinked_buffer;
}
return buffer;
}
free (buffer);
if (buf_size <= MAXSIZE / 2)
buf_size *= 2;
else if (buf_size < MAXSIZE)
buf_size = MAXSIZE;
else
{
errno = ENOMEM;
return NULL;
}
}
}
#else /* !HAVE_READLINKAT */
/* It is more efficient to change directories only once and call
areadlink_with_size, rather than repeatedly call the replacement
readlinkat. */
# define AT_FUNC_NAME areadlinkat_with_size
# define AT_FUNC_F1 areadlink_with_size
# define AT_FUNC_POST_FILE_PARAM_DECLS , size_t size
# define AT_FUNC_POST_FILE_ARGS , size
# define AT_FUNC_RESULT char *
# define AT_FUNC_FAIL NULL
# include "at-func.c"
# undef AT_FUNC_NAME
# undef AT_FUNC_F1
# undef AT_FUNC_POST_FILE_PARAM_DECLS
# undef AT_FUNC_POST_FILE_ARGS
# undef AT_FUNC_RESULT
# undef AT_FUNC_FAIL
#endif /* !HAVE_READLINKAT */