summaryrefslogtreecommitdiff
path: root/lib/readlinkat.c
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2020-12-13 16:48:26 -0800
committerPaul Eggert <eggert@cs.ucla.edu>2020-12-13 17:34:14 -0800
commita2549454801d432e480fc83a32a827e72212a1fb (patch)
tree4e9a19ba46ecf2b809f4730058e273ce17c12cf5 /lib/readlinkat.c
parent13f9414bd94dbac97d2a770700bbae9077c8827f (diff)
downloadgnulib-a2549454801d432e480fc83a32a827e72212a1fb.tar.gz
readlink, readlinkat: add ERANGE portability
Fix some portability issues with Gnulib's readlink and readlinkat, notably mostly working around the ERANGE problem in AIX and HP-UX. * doc/posix-functions/readlink.texi: * doc/posix-functions/readlinkat.texi: ERANGE problem is mostly fixed now. Mention AIX problem with trailing / and EINVAL. Lessen differences between these two files. * lib/readlink.c (rpl_readlink): * lib/readlinkat.c (rpl_readlinkat): If stat ("FILE/", ...) reports EOVERFLOW, treat FILE/ as an existing directory. Mostly work around READLINK_TRUNCATE BUG. Lessen spurious differences between the readlink and readlinkat code. * lib/readlinkat.c (rpl_readlinkat): Fix bug where stat was used where fstatat was intended. * m4/readlink.m4 (gl_FUNC_READLINK): Rename gl_cv_func_readlink_works to gl_cv_func_readlink_trailing_slash to identify readlink problems more precisely. All uses changed. Guess no on AIX or HP-UX for this variable. Add check for whether readlink truncates results, and define new macro READLINK_TRUCATE_BUG accordingly. * m4/readlinkat.m4 (gl_FUNC_READLINKAT): Also check gl_cv_func_readlink_trailing_slash when deciding whether to replace readlinkat. * modules/readlinkat (Depends-on): Most dependencies are also needed if replacing readlinkat. fstatat is different, as it is needed only if replacing an existing readlinkat.
Diffstat (limited to 'lib/readlinkat.c')
-rw-r--r--lib/readlinkat.c47
1 files changed, 39 insertions, 8 deletions
diff --git a/lib/readlinkat.c b/lib/readlinkat.c
index 68ec65ebfc..7a41208ebf 100644
--- a/lib/readlinkat.c
+++ b/lib/readlinkat.c
@@ -28,10 +28,11 @@
#if HAVE_READLINKAT
+# undef fstatat
# undef readlinkat
ssize_t
-rpl_readlinkat (int fd, char const *file, char *buf, size_t len)
+rpl_readlinkat (int fd, char const *file, char *buf, size_t bufsize)
{
# if READLINK_TRAILING_SLASH_BUG
size_t file_len = strlen (file);
@@ -40,15 +41,45 @@ rpl_readlinkat (int fd, char const *file, char *buf, size_t len)
/* Even if FILE without the slash is a symlink to a directory,
both lstat() and stat() must resolve the trailing slash to
the directory rather than the symlink. We can therefore
- safely use stat() to distinguish between EINVAL and
- ENOTDIR/ENOENT, avoiding extra overhead of rpl_lstat(). */
+ safely use fstatat(..., 0) to distinguish between EINVAL and
+ ENOTDIR/ENOENT, avoiding extra overhead of rpl_fstatat(). */
struct stat st;
- if (stat (file, &st) == 0)
+ if (fstatat (fd, file, &st, 0) == 0 || errno == EOVERFLOW)
errno = EINVAL;
return -1;
}
# endif /* READLINK_TRAILING_SLASH_BUG */
- return readlinkat (fd, file, buf, len);
+
+ ssize_t r = readlinkat (fd, file, buf, bufsize);
+
+# if READLINK_TRUNCATE_BUG
+ if (r < 0 && errno == ERANGE)
+ {
+ /* Try again with a bigger buffer. This is just for test cases;
+ real code invariably discards short reads. */
+ char stackbuf[4032];
+ r = readlinkat (fd, file, stackbuf, sizeof stackbuf);
+ if (r < 0)
+ {
+ if (errno == ERANGE)
+ {
+ /* Clear the buffer, which is good enough for real code.
+ Thankfully, no test cases try short reads of enormous
+ symlinks and what would be the point anyway? */
+ r = bufsize;
+ memset (buf, 0, r);
+ }
+ }
+ else
+ {
+ if (bufsize < r)
+ r = bufsize;
+ memcpy (buf, stackbuf, r);
+ }
+ }
+# endif
+
+ return r;
}
#else
@@ -61,7 +92,7 @@ rpl_readlinkat (int fd, char const *file, char *buf, size_t len)
readlinkat worthless since readlink does not guarantee a
NUL-terminated buffer. Assume this was a bug in POSIX. */
-/* Read the contents of symlink FILE into buffer BUF of size LEN, in the
+/* Read the contents of symlink FILE into buffer BUF of size BUFSIZE, in the
directory open on descriptor FD. If possible, do it without changing
the working directory. Otherwise, resort to using save_cwd/fchdir,
then readlink/restore_cwd. If either the save_cwd or the restore_cwd
@@ -69,8 +100,8 @@ rpl_readlinkat (int fd, char const *file, char *buf, size_t len)
# define AT_FUNC_NAME readlinkat
# define AT_FUNC_F1 readlink
-# define AT_FUNC_POST_FILE_PARAM_DECLS , char *buf, size_t len
-# define AT_FUNC_POST_FILE_ARGS , buf, len
+# define AT_FUNC_POST_FILE_PARAM_DECLS , char *buf, size_t bufsize
+# define AT_FUNC_POST_FILE_ARGS , buf, bufsize
# define AT_FUNC_RESULT ssize_t
# include "at-func.c"
# undef AT_FUNC_NAME