1/* areadlinkat.c -- readlinkat wrapper to return malloc'd link name 2 Unlike xreadlinkat, only call exit on failure to change directory. 3 4 Copyright (C) 2001, 2003-2007, 2009-2010 Free Software Foundation, Inc. 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 18 19/* Written by Jim Meyering <jim@meyering.net>, 20 and Bruno Haible <bruno@clisp.org>, 21 and Eric Blake <ebb9@byu.net>. */ 22 23#include <config.h> 24 25/* Specification. */ 26#include "areadlink.h" 27 28#include <errno.h> 29#include <limits.h> 30#include <stdint.h> 31#include <stdlib.h> 32#include <string.h> 33#include <unistd.h> 34 35#ifndef SSIZE_MAX 36# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2)) 37#endif 38 39#if HAVE_READLINKAT 40 41/* The initial buffer size for the link value. A power of 2 42 detects arithmetic overflow earlier, but is not required. */ 43enum { 44 INITIAL_BUF_SIZE = 1024 45}; 46 47/* Call readlinkat to get the symbolic link value of FILENAME relative to FD. 48 Return a pointer to that NUL-terminated string in malloc'd storage. 49 If readlinkat fails, return NULL and set errno (although failure to 50 change directory will issue a diagnostic and exit). 51 If realloc fails, or if the link value is longer than SIZE_MAX :-), 52 return NULL and set errno to ENOMEM. */ 53 54char * 55areadlinkat (int fd, char const *filename) 56{ 57 /* Allocate the initial buffer on the stack. This way, in the common 58 case of a symlink of small size, we get away with a single small malloc() 59 instead of a big malloc() followed by a shrinking realloc(). */ 60 char initial_buf[INITIAL_BUF_SIZE]; 61 62 char *buffer = initial_buf; 63 size_t buf_size = sizeof initial_buf; 64 65 while (1) 66 { 67 /* Attempt to read the link into the current buffer. */ 68 ssize_t link_length = readlinkat (fd, filename, buffer, buf_size); 69 70 /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink returns -1 71 with errno == ERANGE if the buffer is too small. */ 72 if (link_length < 0 && errno != ERANGE) 73 { 74 if (buffer != initial_buf) 75 { 76 int saved_errno = errno; 77 free (buffer); 78 errno = saved_errno; 79 } 80 return NULL; 81 } 82 83 if ((size_t) link_length < buf_size) 84 { 85 buffer[link_length++] = '\0'; 86 87 /* Return it in a chunk of memory as small as possible. */ 88 if (buffer == initial_buf) 89 { 90 buffer = (char *) malloc (link_length); 91 if (buffer == NULL) 92 /* errno is ENOMEM. */ 93 return NULL; 94 memcpy (buffer, initial_buf, link_length); 95 } 96 else 97 { 98 /* Shrink buffer before returning it. */ 99 if ((size_t) link_length < buf_size) 100 { 101 char *smaller_buffer = (char *) realloc (buffer, link_length); 102 103 if (smaller_buffer != NULL) 104 buffer = smaller_buffer; 105 } 106 } 107 return buffer; 108 } 109 110 if (buffer != initial_buf) 111 free (buffer); 112 buf_size *= 2; 113 if (SSIZE_MAX < buf_size || (SIZE_MAX / 2 < SSIZE_MAX && buf_size == 0)) 114 { 115 errno = ENOMEM; 116 return NULL; 117 } 118 buffer = (char *) malloc (buf_size); 119 if (buffer == NULL) 120 /* errno is ENOMEM. */ 121 return NULL; 122 } 123} 124 125#else /* !HAVE_READLINKAT */ 126 127/* It is more efficient to change directories only once and call 128 areadlink, rather than repeatedly call the replacement 129 readlinkat. */ 130 131# define AT_FUNC_NAME areadlinkat 132# define AT_FUNC_F1 areadlink 133# define AT_FUNC_POST_FILE_PARAM_DECLS /* empty */ 134# define AT_FUNC_POST_FILE_ARGS /* empty */ 135# define AT_FUNC_RESULT char * 136# define AT_FUNC_FAIL NULL 137# include "at-func.c" 138# undef AT_FUNC_NAME 139# undef AT_FUNC_F1 140# undef AT_FUNC_POST_FILE_PARAM_DECLS 141# undef AT_FUNC_POST_FILE_ARGS 142# undef AT_FUNC_RESULT 143# undef AT_FUNC_FAIL 144 145#endif /* !HAVE_READLINKAT */ 146