summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Blake <ebb9@byu.net>2009-10-05 21:30:33 -0600
committerEric Blake <ebb9@byu.net>2009-10-05 21:34:28 -0600
commitcdba659e0746e0d7c6ecbcabfbb25132f5418a38 (patch)
treebdccd092a9742d8001c7783a9b58da4b9a0e47e7
parent38742aee633001eca07469834ce365df511359ca (diff)
downloadgnulib-cdba659e0746e0d7c6ecbcabfbb25132f5418a38.tar.gz
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. Signed-off-by: Eric Blake <ebb9@byu.net>
-rw-r--r--ChangeLog10
-rw-r--r--lib/linkat.c201
-rw-r--r--lib/unistd.in.h6
-rw-r--r--m4/linkat.m424
-rw-r--r--m4/unistd_h.m43
-rw-r--r--modules/linkat1
-rw-r--r--modules/unistd1
7 files changed, 235 insertions, 11 deletions
diff --git a/ChangeLog b/ChangeLog
index 0782170917..03624976b5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
2009-10-05 Eric Blake <ebb9@byu.net>
+ 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 bda0627569..f785d091f8 100644
--- a/lib/linkat.c
+++ b/lib/linkat.c
@@ -23,6 +23,8 @@
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
#include <sys/stat.h>
#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 8a96e792b0..38e2e13f70 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 be68c5fc79..d6a1671b46 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 <fcntl.h>
+#include <unistd.h>
+#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 16daed884c..5aa39aeda2 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 8d9dec3417..6b56144dcf 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 d21a20447c..d299e4af09 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' \