From cdba659e0746e0d7c6ecbcabfbb25132f5418a38 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 5 Oct 2009 21:30:33 -0600 Subject: [PATCH] linkat: support Linux 2.6.17 MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * m4/linkat.m4 (gl_FUNC_LINKAT): Default to always replacing linkat on Linux, but allow cache variable override. * lib/linkat.c (rpl_linkat): Define override. * modules/linkat (Depends-on): Add symlinkat. * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add new default. * modules/unistd (Makefile.am): Substitute it. * lib/unistd.in.h (linkat): Declare replacement. Reported by Pádraig Brady. Signed-off-by: Eric Blake --- ChangeLog | 10 +++ lib/linkat.c | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- lib/unistd.in.h | 6 +- m4/linkat.m4 | 24 ++++++- m4/unistd_h.m4 | 3 +- modules/linkat | 1 + modules/unistd | 1 + 7 files changed, 235 insertions(+), 11 deletions(-) diff --git a/ChangeLog b/ChangeLog index 078217091..03624976b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,15 @@ 2009-10-05 Eric Blake + linkat: support Linux 2.6.17 + * m4/linkat.m4 (gl_FUNC_LINKAT): Default to always replacing + linkat on Linux, but allow cache variable override. + * lib/linkat.c (rpl_linkat): Define override. + * modules/linkat (Depends-on): Add symlinkat. + * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add new default. + * modules/unistd (Makefile.am): Substitute it. + * lib/unistd.in.h (linkat): Declare replacement. + Reported by Pádraig Brady. + quotearg: port test to systems with C.UTF-8 locale * tests/test-quotearg.c (struct result_strings): Add another member, differentiating between C.ASCII and C.UTF-8 handling. diff --git a/lib/linkat.c b/lib/linkat.c index bda062756..f785d091f 100644 --- a/lib/linkat.c +++ b/lib/linkat.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include "areadlink.h" @@ -41,11 +43,13 @@ # endif #endif +#if !HAVE_LINKAT + /* Create a link. If FILE1 is a symlink, either create a hardlink to that symlink, or fake it by creating an identical symlink. */ -#if LINK_FOLLOWS_SYMLINKS == 0 -# define link_immediate link -#else +# if LINK_FOLLOWS_SYMLINKS == 0 +# define link_immediate link +# else static int link_immediate (char const *file1, char const *file2) { @@ -88,13 +92,13 @@ link_immediate (char const *file1, char const *file2) return -1; return link (file1, file2); } -#endif +# endif /* LINK_FOLLOWS_SYMLINKS == 0 */ /* Create a link. If FILE1 is a symlink, create a hardlink to the canonicalized file. */ -#if 0 < LINK_FOLLOWS_SYMLINKS -# define link_follow link -#else +# if 0 < LINK_FOLLOWS_SYMLINKS +# define link_follow link +# else static int link_follow (char const *file1, char const *file2) { @@ -159,7 +163,7 @@ link_follow (char const *file1, char const *file2) } return result; } -#endif +# endif /* 0 < LINK_FOLLOWS_SYMLINKS */ /* Create a link to FILE1, in the directory open on descriptor FD1, to FILE2, in the directory open on descriptor FD2. If FILE1 is a symlink, FLAG @@ -179,3 +183,184 @@ linkat (int fd1, char const *file1, int fd2, char const *file2, int flag) return at_func2 (fd1, file1, fd2, file2, flag ? link_follow : link_immediate); } + +#else /* HAVE_LINKAT */ + +# undef linkat + +/* Read a symlink, like areadlink, but relative to FD. */ + +static char * +areadlinkat (int fd, char const *filename) +{ + /* The initial buffer size for the link value. A power of 2 + detects arithmetic overflow earlier, but is not required. */ +# define INITIAL_BUF_SIZE 1024 + + /* Allocate the initial buffer on the stack. This way, in the common + case of a symlink of small size, we get away with a single small malloc() + instead of a big malloc() followed by a shrinking realloc(). */ + char initial_buf[INITIAL_BUF_SIZE]; + + char *buffer = initial_buf; + size_t buf_size = sizeof (initial_buf); + + while (1) + { + /* Attempt to read the link into the current buffer. */ + ssize_t link_length = readlinkat (fd, filename, buffer, buf_size); + + /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink returns -1 + with errno == ERANGE if the buffer is too small. */ + if (link_length < 0 && errno != ERANGE) + { + if (buffer != initial_buf) + { + int saved_errno = errno; + free (buffer); + errno = saved_errno; + } + return NULL; + } + + if ((size_t) link_length < buf_size) + { + buffer[link_length++] = '\0'; + + /* Return it in a chunk of memory as small as possible. */ + if (buffer == initial_buf) + { + buffer = (char *) malloc (link_length); + if (buffer == NULL) + /* errno is ENOMEM. */ + return NULL; + memcpy (buffer, initial_buf, link_length); + } + else + { + /* Shrink buffer before returning it. */ + if ((size_t) link_length < buf_size) + { + char *smaller_buffer = (char *) realloc (buffer, link_length); + + if (smaller_buffer != NULL) + buffer = smaller_buffer; + } + } + return buffer; + } + + if (buffer != initial_buf) + free (buffer); + buf_size *= 2; + if (SSIZE_MAX < buf_size || (SIZE_MAX / 2 < SSIZE_MAX && buf_size == 0)) + { + errno = ENOMEM; + return NULL; + } + buffer = (char *) malloc (buf_size); + if (buffer == NULL) + /* errno is ENOMEM. */ + return NULL; + } +} + +/* Create a link. If FILE1 is a symlink, create a hardlink to the + canonicalized file. */ + +static int +linkat_follow (int fd1, char const *file1, int fd2, char const *file2) +{ + char *name = (char *) file1; + char *target; + int result; + int i = MAXSYMLINKS; + + /* There is no realpathat. */ + while (i-- && (target = areadlinkat (fd1, name))) + { + if (IS_ABSOLUTE_FILE_NAME (target)) + { + if (name != file1) + free (name); + name = target; + } + else + { + char *dir = mdir_name (name); + if (name != file1) + free (name); + if (!dir) + { + free (target); + errno = ENOMEM; + return -1; + } + name = mfile_name_concat (dir, target, NULL); + free (dir); + free (target); + if (!name) + { + errno = ENOMEM; + return -1; + } + } + } + if (i < 0) + { + target = NULL; + errno = ELOOP; + } + if (!target && errno != EINVAL) + { + if (name != file1) + { + int saved_errno = errno; + free (name); + errno = saved_errno; + } + return -1; + } + result = linkat (fd1, name, fd2, file2, 0); + if (name != file1) + { + int saved_errno = errno; + free (name); + errno = saved_errno; + } + return result; +} + + +/* Like linkat, but guarantee that AT_SYMLINK_FOLLOW works even on + older Linux kernels. */ + +int +rpl_linkat (int fd1, char const *file1, int fd2, char const *file2, int flag) +{ + if (!flag) + return linkat (fd1, file1, fd2, file2, flag); + if (flag & ~AT_SYMLINK_FOLLOW) + { + errno = EINVAL; + return -1; + } + + /* Cache the information on whether the system call really works. */ + { + static int have_follow_really; /* 0 = unknown, 1 = yes, -1 = no */ + if (0 <= have_follow_really) + { + int result = linkat (fd1, file1, fd2, file2, flag); + if (!(result == -1 && errno == EINVAL)) + { + have_follow_really = 1; + return result; + } + have_follow_really = -1; + } + } + return linkat_follow (fd1, file1, fd2, file2); +} + +#endif /* HAVE_LINKAT */ diff --git a/lib/unistd.in.h b/lib/unistd.in.h index 8a96e792b..38e2e13f7 100644 --- a/lib/unistd.in.h +++ b/lib/unistd.in.h @@ -582,10 +582,14 @@ extern int link (const char *path1, const char *path2); #endif #if @GNULIB_LINKAT@ +# if @REPLACE_LINKAT@ +# undef linkat +# define linkat rpl_linkat +# endif /* Create a new hard link for an existing file, relative to two directories. FLAG controls whether symlinks are followed. Return 0 if successful, otherwise -1 and errno set. */ -# if !@HAVE_LINKAT@ +# if !@HAVE_LINKAT@ || @REPLACE_LINKAT@ extern int linkat (int fd1, const char *path1, int fd2, const char *path2, int flag); # endif diff --git a/m4/linkat.m4 b/m4/linkat.m4 index be68c5fc7..d6a1671b4 100644 --- a/m4/linkat.m4 +++ b/m4/linkat.m4 @@ -1,4 +1,4 @@ -# serial 1 +# serial 2 # See if we need to provide linkat replacement. dnl Copyright (C) 2009 Free Software Foundation, Inc. @@ -21,5 +21,27 @@ AC_DEFUN([gl_FUNC_LINKAT], HAVE_LINKAT=0 AC_LIBOBJ([linkat]) AC_LIBOBJ([at-func2]) + else + AC_CACHE_CHECK([whether linkat(,AT_SYMLINK_FOLLOW) works], + [gl_cv_func_linkat_follow], + [rm -rf conftest.f1 conftest.f2 + touch conftest.f1 + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include +#include +#ifdef __linux__ +/* Linux added linkat in 2.6.16, but did not add AT_SYMLINK_FOLLOW + until 2.6.18. Always replace linkat to support older kernels. */ +choke me +#endif +]], [return linkat (AT_FDCWD, "conftest.f1", AT_FDCWD, "conftest.f2", + AT_SYMLINK_FOLLOW);])], + [gl_cv_func_linkat_follow=yes], + [gl_cv_func_linkat_follow="need runtime check"]) + rm -rf conftest.f1 conftest.f2]) + if test "$gl_cv_func_linkat_follow" != yes; then + REPLACE_LINKAT=1 + AC_LIBOBJ([linkat]) + fi fi ]) diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4 index 16daed884..5aa39aeda 100644 --- a/m4/unistd_h.m4 +++ b/m4/unistd_h.m4 @@ -1,4 +1,4 @@ -# unistd_h.m4 serial 30 +# unistd_h.m4 serial 31 dnl Copyright (C) 2006-2009 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -102,6 +102,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS], REPLACE_GETPAGESIZE=0; AC_SUBST([REPLACE_GETPAGESIZE]) REPLACE_LCHOWN=0; AC_SUBST([REPLACE_LCHOWN]) REPLACE_LINK=0; AC_SUBST([REPLACE_LINK]) + REPLACE_LINKAT=0; AC_SUBST([REPLACE_LINKAT]) REPLACE_LSEEK=0; AC_SUBST([REPLACE_LSEEK]) REPLACE_READLINK=0; AC_SUBST([REPLACE_READLINK]) REPLACE_RMDIR=0; AC_SUBST([REPLACE_RMDIR]) diff --git a/modules/linkat b/modules/linkat index 8d9dec341..6b56144dc 100644 --- a/modules/linkat +++ b/modules/linkat @@ -21,6 +21,7 @@ readlink same-inode stpcpy symlink +symlinkat unistd configure.ac: diff --git a/modules/unistd b/modules/unistd index d21a20447..d299e4af0 100644 --- a/modules/unistd +++ b/modules/unistd @@ -94,6 +94,7 @@ unistd.h: unistd.in.h -e 's|@''REPLACE_GETPAGESIZE''@|$(REPLACE_GETPAGESIZE)|g' \ -e 's|@''REPLACE_LCHOWN''@|$(REPLACE_LCHOWN)|g' \ -e 's|@''REPLACE_LINK''@|$(REPLACE_LINK)|g' \ + -e 's|@''REPLACE_LINKAT''@|$(REPLACE_LINKAT)|g' \ -e 's|@''REPLACE_LSEEK''@|$(REPLACE_LSEEK)|g' \ -e 's|@''REPLACE_READLINK''@|$(REPLACE_READLINK)|g' \ -e 's|@''REPLACE_RMDIR''@|$(REPLACE_RMDIR)|g' \ -- 2.11.0