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