[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
linkat (was: mingw and same-inode)
From: |
Eric Blake |
Subject: |
linkat (was: mingw and same-inode) |
Date: |
Wed, 23 Sep 2009 22:19:59 -0600 |
User-agent: |
Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.23) Gecko/20090812 Thunderbird/2.0.0.23 Mnenhy/0.7.6.666 |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
According to Eric Blake on 9/23/2009 3:10 PM:
> I'm currently testing these two patches, as mingw prerequisites before I can
> get linkat() working. In particular, mingw is lousy at SAME_INODE, since all
> three of [fl]stat produce st_ino == 0 for all files (then again, mingw never
> claimed POSIX compliance!). Code was always taking the identical-file path,
> even for distinct files.
>
> I'm also preparing a followup patch for coreutils usage of SAME_INODE.
> Thoughts before I apply this?
And with this, linkat() now works. Tested on Linux (both before and after
native linkat was added), Solaris 8-10, OpenBSD, cygwin 1.5, cygwin 1.7,
and mingw. The only failures were on cygwin 1.7, but I have pending
patches to fix cygwin there, so I'm not worried about it. I'm pushing
this series, and will start work on implementing 'ln -P/-L' in coreutils
tomorrow.
Eric Blake (3):
fchdir: another mingw fix
I finally figured out the last piece of portability that was getting in
the way of openat and friends from working reliably on mingw. Up till
now, fchdir(open(".",O_RDONLY)) worked, but not
fchdir(open("..",O_RDONLY)), because canonicalize_file_name didn't know
what to do with the directory. But since fchdir only needs the canonical
names of directories (not regular files), and since getcwd() works on
mingw to give a canonical name, I was able to find an alternate approach
that breaks the dependency on canonicalize-lgpl altogether.
dirname: add library-safe mdir_name
I wanted to use dirname in at_func2, but without the penalty of
xalloc_die. This patch is sufficient for that task, although we may want
to consider a followup that splits dirname into a lighter-weight module
(possibly with portions of dirname.h under LGPL), or at least split .o
files (so that using just mdir_name from dirname.o doesn't force the
linker to require xalloc_die).
linkat: new module
I've posted previews of at_func2 before [1], but have refined it over the
past couple of weeks. renameat will have to wait a bit longer for fixing
rpl_rename first [2]. On (older) Linux and Solaris, linkat(,0) uses
native link, and linkat(,AT_SYMLINK_FOLLOW) manually crawls the readlink
chain (and does a better job than Solaris' /usr/xpg4/bin/link, which stops
after only one iteration, even if that is still a symlink). On BSD,
linkat(,0) fakes the hardlink by creating an identical symlink (a trick
from coreutils' copy.c), and linkat(,AT_SYMLINK_FOLLOW) uses native link.
Although st_nlink and st_ino are wrong, there's not much else the user
can distinguish (as there is no way to modify symlink contents). I
suppose I should do a followup patch to use lchmod/lchown to more fully
match the attributes of the cloned symlink.
[1] http://lists.gnu.org/archive/html/bug-gnulib/2009-09/msg00097.html
[2] http://lists.gnu.org/archive/html/bug-gnulib/2009-09/msg00092.html
- --
Don't work too hard, make some time for fun as well!
Eric Blake address@hidden
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
iEYEARECAAYFAkq6828ACgkQ84KuGfSFAYCqpQCgpVEygy1V1bqTnGGgcStzMGSK
v3sAoKmVtCzN4/ll2DdJkyGA5711bBFc
=nx6R
-----END PGP SIGNATURE-----
>From 657fb89d3276465fcf756392f10aa268ca451136 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 23 Sep 2009 20:33:40 -0600
Subject: [PATCH 1/3] fchdir: another mingw fix
canonicalize_file_name does not understand drive letters or
backslash. The only reason openat required it was to make
fchdir get the canonical name of a directory. But we can do
the same trick with chdir and getcwd. With this fix,
fchdir(open("..",O_RDONLY)) finally does the right thing on mingw.
* modules/fchdir (Depends-on): Drop canonicalize-lgpl.
* lib/fchdir.c (get_name): New helper method; skips canonicalize
on mingw (where it has not yet been ported), and make it optional
elsewhere.
(_gl_register_fd): Use it.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 7 +++++++
lib/fchdir.c | 42 +++++++++++++++++++++++++++++++++++++++++-
modules/fchdir | 1 -
3 files changed, 48 insertions(+), 2 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index bed42c5..ee180ac 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
2009-09-23 Eric Blake <address@hidden>
+ fchdir: another mingw fix
+ * modules/fchdir (Depends-on): Drop canonicalize-lgpl.
+ * lib/fchdir.c (get_name): New helper method; skips canonicalize
+ on mingw (where it has not yet been ported), and make it optional
+ elsewhere.
+ (_gl_register_fd): Use it.
+
same-inode: make SAME_INODE tri-state, to port to mingw
* NEWS: Mention this change.
* lib/same-inode.h (same-inode.h): Recognize mingw limitation of
diff --git a/lib/fchdir.c b/lib/fchdir.c
index 677d442..54bbc62 100644
--- a/lib/fchdir.c
+++ b/lib/fchdir.c
@@ -33,6 +33,14 @@
# define REPLACE_OPEN_DIRECTORY 0
#endif
+#ifndef HAVE_CANONICALIZE_FILE_NAME
+# if GNULIB_CANONICALIZE || GNULIB_CANONICALIZE_LGPL
+# define HAVE_CANONICALIZE_FILE_NAME 1
+# else
+# define HAVE_CANONICALIZE_FILE_NAME 0
+# endif
+#endif
+
/* This replacement assumes that a directory is not renamed while opened
through a file descriptor.
@@ -78,6 +86,38 @@ ensure_dirs_slot (size_t fd)
return true;
}
+/* Return the canonical name of DIR in malloc'd storage. */
+static char *
+get_name (char const *dir)
+{
+ char *result;
+ if (REPLACE_OPEN_DIRECTORY || !HAVE_CANONICALIZE_FILE_NAME)
+ {
+ /* The function canonicalize_file_name has not yet been ported
+ to mingw, with all its drive letter and backslash quirks.
+ Fortunately, getcwd is reliable in this case, but we ensure
+ we can get back to where we started before using it. Treat
+ "." as a special case, as it is frequently encountered. */
+ char *cwd = getcwd (NULL, 0);
+ int saved_errno;
+ if (dir[0] == '.' && dir[1] == '\0')
+ return cwd;
+ if (chdir (cwd))
+ return NULL;
+ result = chdir (dir) ? NULL : getcwd (NULL, 0);
+ saved_errno = errno;
+ chdir (cwd);
+ free (cwd);
+ errno = saved_errno;
+ }
+ else
+ {
+ /* Avoid changing the directory. */
+ result = canonicalize_file_name (dir);
+ }
+ return result;
+}
+
/* Hook into the gnulib replacements for open() and close() to keep track
of the open file descriptors. */
@@ -108,7 +148,7 @@ _gl_register_fd (int fd, const char *filename)
|| (fstat (fd, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)))
{
if (!ensure_dirs_slot (fd)
- || (dirs[fd].name = canonicalize_file_name (filename)) == NULL)
+ || (dirs[fd].name = get_name (filename)) == NULL)
{
int saved_errno = errno;
close (fd);
diff --git a/modules/fchdir b/modules/fchdir
index 69ac3c3..5bae7d6 100644
--- a/modules/fchdir
+++ b/modules/fchdir
@@ -6,7 +6,6 @@ lib/fchdir.c
m4/fchdir.m4
Depends-on:
-canonicalize-lgpl
close
dirent
dirfd
--
1.6.5.rc1
>From c6dc1761b3e928d2de0a6116cd933b3147ffd7d8 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 9 Sep 2009 06:24:28 -0600
Subject: [PATCH 2/3] dirname: add library-safe mdir_name
A library-safe dir_name is nice, especially alongside
mfile_name_concat. Someday, we should rearrange the .o
files so that linking in mdir_name does not suck in
xalloc_die, but for now, the only planned client of
mdir_name (at-func2) is already using xalloc_die.
* lib/dirname.h (mdir_name): New prototype.
* lib/dirname.c (dir_name): Move guts...
(mdir_name): ...to new function that avoids xalloc_die.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 5 +++++
lib/dirname.c | 28 ++++++++++++++++++++++------
lib/dirname.h | 3 ++-
3 files changed, 29 insertions(+), 7 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index ee180ac..0ba1bbe 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
2009-09-23 Eric Blake <address@hidden>
+ dirname: add library-safe mdir_name
+ * lib/dirname.h (mdir_name): New prototype.
+ * lib/dirname.c (dir_name): Move guts...
+ (mdir_name): ...to new function that avoids xalloc_die.
+
fchdir: another mingw fix
* modules/fchdir (Depends-on): Drop canonicalize-lgpl.
* lib/fchdir.c (get_name): New helper method; skips canonicalize
diff --git a/lib/dirname.c b/lib/dirname.c
index c27e5b5..20dcaf5 100644
--- a/lib/dirname.c
+++ b/lib/dirname.c
@@ -1,7 +1,7 @@
/* dirname.c -- return all but the last element in a file name
- Copyright (C) 1990, 1998, 2000, 2001, 2003, 2004, 2005, 2006 Free Software
- Foundation, Inc.
+ Copyright (C) 1990, 1998, 2000, 2001, 2003, 2004, 2005, 2006, 2009
+ Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,6 +20,7 @@
#include "dirname.h"
+#include <stdlib.h>
#include <string.h>
#include "xalloc.h"
@@ -57,9 +58,9 @@ dir_len (char const *file)
since it has different meanings in different environments.
In some environments the builtin `dirname' modifies its argument.
- Return the leading directories part of FILE, allocated with xmalloc.
+ Return the leading directories part of FILE, allocated with malloc.
Works properly even if there are trailing slashes (by effectively
- ignoring them). Unlike POSIX dirname(), FILE cannot be NULL.
+ ignoring them). Return NULL on failure.
If lstat (FILE) would succeed, then { chdir (dir_name (FILE));
lstat (base_name (FILE)); } will access the same file. Likewise,
@@ -68,17 +69,32 @@ dir_len (char const *file)
to "foo" in the same directory FILE was in. */
char *
-dir_name (char const *file)
+mdir_name (char const *file)
{
size_t length = dir_len (file);
bool append_dot = (length == 0
|| (FILE_SYSTEM_DRIVE_PREFIX_CAN_BE_RELATIVE
&& length == FILE_SYSTEM_PREFIX_LEN (file)
&& file[2] != '\0' && ! ISSLASH (file[2])));
- char *dir = xmalloc (length + append_dot + 1);
+ char *dir = malloc (length + append_dot + 1);
+ if (!dir)
+ return NULL;
memcpy (dir, file, length);
if (append_dot)
dir[length++] = '.';
dir[length] = '\0';
return dir;
}
+
+/* Just like mdir_name, above, except, rather than
+ returning NULL upon malloc failure, here, we report the
+ "memory exhausted" condition and exit. */
+
+char *
+dir_name (char const *file)
+{
+ char *result = mdir_name (file);
+ if (!result)
+ xalloc_die ();
+ return result;
+}
diff --git a/lib/dirname.h b/lib/dirname.h
index f592350..90a1f0c 100644
--- a/lib/dirname.h
+++ b/lib/dirname.h
@@ -1,6 +1,6 @@
/* Take file names apart into directory and base names.
- Copyright (C) 1998, 2001, 2003-2006 Free Software Foundation, Inc.
+ Copyright (C) 1998, 2001, 2003-2006, 2009 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -59,6 +59,7 @@
# define IS_RELATIVE_FILE_NAME(F) (! IS_ABSOLUTE_FILE_NAME (F))
char *base_name (char const *file);
+char *mdir_name (char const *file);
char *dir_name (char const *file);
size_t base_len (char const *file);
size_t dir_len (char const *file);
--
1.6.5.rc1
>From c4194dcc56767f8f96bc005088b292f519b13910 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 8 Sep 2009 20:47:40 -0600
Subject: [PATCH 3/3] linkat: new module
* modules/linkat: New file.
* lib/at-func2.c (at_func2): Likewise.
* lib/linkat.c (linkat): Likewise.
* m4/linkat.m4 (gl_FUNC_LINKAT): Likewise.
* lib/openat-priv.h (at_func2): Add declaration.
* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witnesses.
* modules/unistd (Makefile.am): Substitute them.
* lib/unistd.in.h (linkat): Declare it.
* MODULES.html.sh (systems lacking POSIX:2008): Mention module.
* doc/posix-functions/linkat.texi (linkat): Likewise.
* doc/posix-functions/link.texi (link): Tweak wording.
* tests/test-link.c (main): Move guts...
* tests/test-link.h (test_link): ...into new file.
* modules/linkat-tests: New test.
* tests/test-linkat.c: Likewise.
* modules/link-tests (Files): Ship new file.
(Depends-on): Add stdbool.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 19 ++
MODULES.html.sh | 4 +-
doc/posix-functions/link.texi | 4 +
doc/posix-functions/linkat.texi | 10 +-
lib/at-func2.c | 282 +++++++++++++++++++++++++++++++
lib/linkat.c | 181 ++++++++++++++++++++
lib/openat-priv.h | 5 +
lib/unistd.in.h | 15 ++
m4/linkat.m4 | 25 +++
m4/unistd_h.m4 | 4 +-
modules/link-tests | 2 +
modules/linkat | 40 +++++
modules/linkat-tests | 16 ++
modules/unistd | 2 +
tests/test-link.c | 111 +------------
tests/test-link.h | 137 +++++++++++++++
tests/test-linkat.c | 352 +++++++++++++++++++++++++++++++++++++++
17 files changed, 1095 insertions(+), 114 deletions(-)
create mode 100644 lib/at-func2.c
create mode 100644 lib/linkat.c
create mode 100644 m4/linkat.m4
create mode 100644 modules/linkat
create mode 100644 modules/linkat-tests
create mode 100644 tests/test-link.h
create mode 100644 tests/test-linkat.c
diff --git a/ChangeLog b/ChangeLog
index 0ba1bbe..855e69d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,24 @@
2009-09-23 Eric Blake <address@hidden>
+ linkat: new module
+ * modules/linkat: New file.
+ * lib/at-func2.c (at_func2): Likewise.
+ * lib/linkat.c (linkat): Likewise.
+ * m4/linkat.m4 (gl_FUNC_LINKAT): Likewise.
+ * lib/openat-priv.h (at_func2): Add declaration.
+ * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witnesses.
+ * modules/unistd (Makefile.am): Substitute them.
+ * lib/unistd.in.h (linkat): Declare it.
+ * MODULES.html.sh (systems lacking POSIX:2008): Mention module.
+ * doc/posix-functions/linkat.texi (linkat): Likewise.
+ * doc/posix-functions/link.texi (link): Tweak wording.
+ * tests/test-link.c (main): Move guts...
+ * tests/test-link.h (test_link): ...into new file.
+ * modules/linkat-tests: New test.
+ * tests/test-linkat.c: Likewise.
+ * modules/link-tests (Files): Ship new file.
+ (Depends-on): Add stdbool.
+
dirname: add library-safe mdir_name
* lib/dirname.h (mdir_name): New prototype.
* lib/dirname.c (dir_name): Move guts...
diff --git a/MODULES.html.sh b/MODULES.html.sh
index ad00ac8..42cb57c 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2276,9 +2276,11 @@ func_all_modules ()
func_module iconv_open
func_module inet_ntop
func_module inet_pton
+ func_module link
+ func_module linkat
+ func_module listen
func_module locale
func_module lseek
- func_module listen
func_module lstat
func_module malloc-posix
func_module mbsnrtowcs
diff --git a/doc/posix-functions/link.texi b/doc/posix-functions/link.texi
index c785371..c06f0a6 100644
--- a/doc/posix-functions/link.texi
+++ b/doc/posix-functions/link.texi
@@ -19,4 +19,8 @@ link
Portability problems not fixed by Gnulib:
@itemize
address@hidden
+When the first argument is a symlink, some platforms create a
+hard-link to what the symlink referenced, rather than to the symlink
+itself. Use @samp{linkat} to force a particular behavior.
@end itemize
diff --git a/doc/posix-functions/linkat.texi b/doc/posix-functions/linkat.texi
index 1c08c7e..62fc43d 100644
--- a/doc/posix-functions/linkat.texi
+++ b/doc/posix-functions/linkat.texi
@@ -4,16 +4,16 @@ linkat
POSIX specification:
@url{http://www.opengroup.org/onlinepubs/9699919799/functions/linkat.html}
-Gnulib module: ---
+Gnulib module: linkat
Portability problems fixed by Gnulib:
@itemize
address@hidden itemize
-
-Portability problems not fixed by Gnulib:
address@hidden
@item
This function is missing on some platforms:
glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, Interix
3.5, BeOS.
@end itemize
+
+Portability problems not fixed by Gnulib:
address@hidden
address@hidden itemize
diff --git a/lib/at-func2.c b/lib/at-func2.c
new file mode 100644
index 0000000..a19b60b
--- /dev/null
+++ b/lib/at-func2.c
@@ -0,0 +1,282 @@
+/* Define an at-style functions like linkat or renameat.
+ Copyright (C) 2006, 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* written by Jim Meyering and Eric Blake */
+
+#include <config.h>
+
+#include "openat-priv.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
+#include "filenamecat.h"
+#include "openat.h"
+#include "same-inode.h"
+#include "save-cwd.h"
+
+/* Call FUNC to operate on a pair of files, where FILE1 is relative to FD1,
+ and FILE2 is relative to FD2. If possible, do it without changing the
+ working directory. Otherwise, resort to using save_cwd/fchdir,
+ FUNC, restore_cwd (up to two times). If either the save_cwd or the
+ restore_cwd fails, then give a diagnostic and exit nonzero. */
+int
+at_func2 (int fd1, char const *file1,
+ int fd2, char const *file2,
+ int (*func) (char const *file1, char const *file2))
+{
+ struct saved_cwd saved_cwd;
+ int saved_errno;
+ int err;
+ char *file1_alt;
+ char *file2_alt;
+ struct stat st1;
+ struct stat st2;
+
+ /* There are 16 possible scenarios, based on whether an fd is
+ AT_FDCWD or real, and whether a file is absolute or relative:
+
+ fd1 file1 fd2 file2 action
+ 0 cwd abs cwd abs direct call
+ 1 cwd abs cwd rel direct call
+ 2 cwd abs fd abs direct call
+ 3 cwd abs fd rel chdir to fd2
+ 4 cwd rel cwd abs direct call
+ 5 cwd rel cwd rel direct call
+ 6 cwd rel fd abs direct call
+ 7 cwd rel fd rel convert file1 to abs, then case 3
+ 8 fd abs cwd abs direct call
+ 9 fd abs cwd rel direct call
+ 10 fd abs fd abs direct call
+ 11 fd abs fd rel chdir to fd2
+ 12 fd rel cwd abs chdir to fd1
+ 13 fd rel cwd rel convert file2 to abs, then case 12
+ 14 fd rel fd abs chdir to fd1
+ 15a fd1 rel fd1 rel chdir to fd1
+ 15b fd1 rel fd2 rel chdir to fd1, then case 7
+
+ Try some optimizations to reduce fd to AT_FDCWD, or to at least
+ avoid converting an absolute name or doing a double chdir. */
+
+ if ((fd1 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file1))
+ && (fd2 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file2)))
+ return func (file1, file2); /* Case 0-2, 4-6, 8-10. */
+
+ /* If /proc/self/fd works, we don't need any stat or chdir. */
+ {
+ char proc_buf1[OPENAT_BUFFER_SIZE];
+ char *proc_file1 = ((fd1 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file1))
+ ? (char *) file1
+ : openat_proc_name (proc_buf1, fd1, file1));
+ if (proc_file1)
+ {
+ char proc_buf2[OPENAT_BUFFER_SIZE];
+ char *proc_file2 = ((fd2 == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file2))
+ ? (char *) file2
+ : openat_proc_name (proc_buf2, fd2, file2));
+ if (proc_file2)
+ {
+ int proc_result = func (proc_file1, proc_file2);
+ int proc_errno = errno;
+ if (proc_file1 != proc_buf1 && proc_file1 != file1)
+ free (proc_file1);
+ if (proc_file2 != proc_buf2 && proc_file2 != file2)
+ free (proc_file2);
+ /* If the syscall succeeds, or if it fails with an unexpected
+ errno value, then return right away. Otherwise, fall through
+ and resort to using save_cwd/restore_cwd. */
+ if (0 <= proc_result)
+ return proc_result;
+ if (! EXPECTED_ERRNO (proc_errno))
+ {
+ errno = proc_errno;
+ return proc_result;
+ }
+ }
+ else if (proc_file1 != proc_buf1 && proc_file1 != file1)
+ free (proc_buf1);
+ }
+ }
+
+ /* Cases 3, 7, 11-15 remain. Time to normalize directory fds, if
+ possible. */
+ if (IS_ABSOLUTE_FILE_NAME (file1))
+ fd1 = AT_FDCWD; /* Case 11 reduced to 3. */
+ else if (IS_ABSOLUTE_FILE_NAME (file2))
+ fd2 = AT_FDCWD; /* Case 14 reduced to 12. */
+
+ /* Cases 3, 7, 12, 13, 15 remain. */
+
+ if (fd1 == AT_FDCWD) /* Cases 3, 7. */
+ {
+ if (stat (".", &st1) == -1 || fstat (fd2, &st2) == -1)
+ return -1;
+ if (!S_ISDIR (st2.st_mode))
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+ if (SAME_INODE (st1, st2) == 1) /* Reduced to cases 1, 5. */
+ return func (file1, file2);
+ }
+ else if (fd2 == AT_FDCWD) /* Cases 12, 13. */
+ {
+ if (stat (".", &st2) == -1 || fstat (fd1, &st1) == -1)
+ return -1;
+ if (!S_ISDIR (st1.st_mode))
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+ if (SAME_INODE (st1, st2) == 1) /* Reduced to cases 4, 5. */
+ return func (file1, file2);
+ }
+ else if (fd1 != fd2) /* Case 15b. */
+ {
+ if (fstat (fd1, &st1) == -1 || fstat (fd2, &st2) == -1)
+ return -1;
+ if (!S_ISDIR (st1.st_mode) || !S_ISDIR (st2.st_mode))
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+ if (SAME_INODE (st1, st2) == 1) /* Reduced to case 15a. */
+ {
+ fd2 = fd1;
+ if (stat (".", &st1) == 0 && SAME_INODE (st1, st2) == 1)
+ return func (file1, file2); /* Further reduced to case 5. */
+ }
+ }
+ else /* Case 15a. */
+ {
+ if (fstat (fd1, &st1) == -1)
+ return -1;
+ if (!S_ISDIR (st1.st_mode))
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+ if (stat (".", &st2) == 0 && SAME_INODE (st1, st2) == 1)
+ return func (file1, file2); /* Reduced to case 5. */
+ }
+
+ /* Cases 3, 7, 12, 13, 15a, 15b remain. With all reductions in
+ place, it is time to start changing directories. */
+
+ if (save_cwd (&saved_cwd) != 0)
+ openat_save_fail (errno);
+
+ if (fd1 != AT_FDCWD && fd2 != AT_FDCWD && fd1 != fd2) /* Case 15b. */
+ {
+ if (fchdir (fd1) != 0)
+ {
+ saved_errno = errno;
+ free_cwd (&saved_cwd);
+ errno = saved_errno;
+ return -1;
+ }
+ fd1 = AT_FDCWD; /* Reduced to case 7. */
+ }
+
+ /* Cases 3, 7, 12, 13, 15a remain. Convert one relative name to
+ absolute, if necessary. */
+
+ file1_alt = (char *) file1;
+ file2_alt = (char *) file2;
+
+ if (fd1 == AT_FDCWD && !IS_ABSOLUTE_FILE_NAME (file1)) /* Case 7. */
+ {
+ /* It would be nicer to use:
+ file1_alt = file_name_concat (xgetcwd (), file1, NULL);
+ but libraries should not call xalloc_die. */
+ char *cwd = getcwd (NULL, 0);
+ if (!cwd)
+ {
+ saved_errno = errno;
+ free_cwd (&saved_cwd);
+ errno = saved_errno;
+ return -1;
+ }
+ file1_alt = mfile_name_concat (cwd, file1, NULL);
+ if (!file1_alt)
+ {
+ saved_errno = errno;
+ free (cwd);
+ free_cwd (&saved_cwd);
+ errno = saved_errno;
+ return -1;
+ }
+ free (cwd); /* Reduced to case 3. */
+ }
+ else if (fd2 == AT_FDCWD && !IS_ABSOLUTE_FILE_NAME (file2)) /* Case 13. */
+ {
+ char *cwd = getcwd (NULL, 0);
+ if (!cwd)
+ {
+ saved_errno = errno;
+ free_cwd (&saved_cwd);
+ errno = saved_errno;
+ return -1;
+ }
+ file2_alt = mfile_name_concat (cwd, file2, NULL);
+ if (!file2_alt)
+ {
+ saved_errno = errno;
+ free (cwd);
+ free_cwd (&saved_cwd);
+ errno = saved_errno;
+ return -1;
+ }
+ free (cwd); /* Reduced to case 12. */
+ }
+
+ /* Cases 3, 12, 15a remain. Change to the correct directory. */
+ if (fchdir (fd1 == AT_FDCWD ? fd2 : fd1) != 0)
+ {
+ saved_errno = errno;
+ free_cwd (&saved_cwd);
+ if (file1 != file1_alt)
+ free (file1_alt);
+ else if (file2 != file2_alt)
+ free (file2_alt);
+ errno = saved_errno;
+ return -1;
+ }
+
+ /* Finally safe to perform the user's function, then clean up. */
+
+ err = func (file1_alt, file2_alt);
+ saved_errno = (err < 0 ? errno : 0);
+
+ if (file1 != file1_alt)
+ free (file1_alt);
+ else if (file2 != file2_alt)
+ free (file2_alt);
+
+ if (restore_cwd (&saved_cwd) != 0)
+ openat_restore_fail (errno);
+
+ free_cwd (&saved_cwd);
+
+ if (saved_errno)
+ errno = saved_errno;
+ return err;
+}
+#undef CALL_FUNC
+#undef FUNC_RESULT
diff --git a/lib/linkat.c b/lib/linkat.c
new file mode 100644
index 0000000..bda0627
--- /dev/null
+++ b/lib/linkat.c
@@ -0,0 +1,181 @@
+/* Create a hard link relative to open directories.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* written by Eric Blake */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/stat.h>
+
+#include "areadlink.h"
+#include "dirname.h"
+#include "filenamecat.h"
+#include "openat-priv.h"
+
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+#ifndef MAXSYMLINKS
+# ifdef SYMLOOP_MAX
+# define MAXSYMLINKS SYMLOOP_MAX
+# else
+# define MAXSYMLINKS 20
+# endif
+#endif
+
+/* 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
+static int
+link_immediate (char const *file1, char const *file2)
+{
+ char *target = areadlink (file1);
+ if (target)
+ {
+ /* A symlink cannot be modified in-place. Therefore, creating
+ an identical symlink behaves like a hard link to a symlink,
+ except for incorrect st_ino and st_nlink. However, we must
+ be careful of EXDEV. */
+ struct stat st1;
+ struct stat st2;
+ char *dir = mdir_name (file2);
+ if (!dir)
+ {
+ free (target);
+ errno = ENOMEM;
+ return -1;
+ }
+ if (lstat (file1, &st1) == 0 && stat (dir, &st2) == 0)
+ {
+ if (st1.st_dev == st2.st_dev)
+ {
+ int result = symlink (target, file2);
+ int saved_errno = errno;
+ free (target);
+ free (dir);
+ errno = saved_errno;
+ return result;
+ }
+ free (target);
+ free (dir);
+ errno = EXDEV;
+ return -1;
+ }
+ free (target);
+ free (dir);
+ }
+ if (errno == ENOMEM)
+ return -1;
+ return link (file1, file2);
+}
+#endif
+
+/* 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
+static int
+link_follow (char const *file1, char const *file2)
+{
+ char *name = (char *) file1;
+ char *target;
+ int result;
+ int i = MAXSYMLINKS;
+
+ /* Using realpath or canonicalize_file_name is too heavy-handed: we
+ don't need an absolute name, and we don't need to resolve
+ intermediate symlinks, just the basename of each iteration. */
+ while (i-- && (target = areadlink (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 = link (name, file2);
+ if (name != file1)
+ {
+ int saved_errno = errno;
+ free (name);
+ errno = saved_errno;
+ }
+ return result;
+}
+#endif
+
+/* 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
+ controls whether to dereference FILE1 first. If possible, do it without
+ changing the working directory. Otherwise, resort to using
+ save_cwd/fchdir, then rename/restore_cwd. If either the save_cwd or
+ the restore_cwd fails, then give a diagnostic and exit nonzero. */
+
+int
+linkat (int fd1, char const *file1, int fd2, char const *file2, int flag)
+{
+ if (flag & ~AT_SYMLINK_FOLLOW)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ return at_func2 (fd1, file1, fd2, file2,
+ flag ? link_follow : link_immediate);
+}
diff --git a/lib/openat-priv.h b/lib/openat-priv.h
index fa286b5..53016a1 100644
--- a/lib/openat-priv.h
+++ b/lib/openat-priv.h
@@ -36,4 +36,9 @@ char *openat_proc_name (char buf[OPENAT_BUFFER_SIZE], int fd,
char const *file);
|| (Errno) == ENOSYS /* Solaris 8 */ \
|| (Errno) == EOPNOTSUPP /* FreeBSD */)
+/* Wrapper function shared among linkat and renameat. */
+int at_func2 (int fd1, char const *file1,
+ int fd2, char const *file2,
+ int (*func) (char const *file1, char const *file2));
+
#endif /* _GL_HEADER_OPENAT_PRIV */
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 600f224..8a96e79 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -581,6 +581,21 @@ extern int link (const char *path1, const char *path2);
link (path1, path2))
#endif
+#if @GNULIB_LINKAT@
+/* 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 address@hidden@
+extern int linkat (int fd1, const char *path1, int fd2, const char *path2,
+ int flag);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef linkat
+# define link(f1,path1,f2,path2,f) \
+ (GL_LINK_WARNING ("linkat is unportable - " \
+ "use gnulib module linkat for portability"), \
+ linkat (f1, path1, f2, path2,f))
+#endif
#if @GNULIB_LSEEK@
# if @REPLACE_LSEEK@
diff --git a/m4/linkat.m4 b/m4/linkat.m4
new file mode 100644
index 0000000..be68c5f
--- /dev/null
+++ b/m4/linkat.m4
@@ -0,0 +1,25 @@
+# serial 1
+# See if we need to provide linkat replacement.
+
+dnl Copyright (C) 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,
+dnl with or without modifications, as long as this notice is preserved.
+
+# Written by Eric Blake.
+
+AC_DEFUN([gl_FUNC_LINKAT],
+[
+ AC_REQUIRE([gl_FUNC_OPENAT])
+ AC_REQUIRE([gl_FUNC_LINK])
+ AC_REQUIRE([gl_FUNC_LINK_FOLLOWS_SYMLINK])
+ AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
+ AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+ AC_CHECK_FUNCS_ONCE([linkat symlink])
+ AC_CHECK_HEADERS_ONCE([sys/param.h])
+ if test $ac_cv_func_linkat = no; then
+ HAVE_LINKAT=0
+ AC_LIBOBJ([linkat])
+ AC_LIBOBJ([at-func2])
+ fi
+])
diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index 2334582..16daed8 100644
--- a/m4/unistd_h.m4
+++ b/m4/unistd_h.m4
@@ -1,4 +1,4 @@
-# unistd_h.m4 serial 29
+# unistd_h.m4 serial 30
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,
@@ -52,6 +52,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
GNULIB_GETUSERSHELL=0; AC_SUBST([GNULIB_GETUSERSHELL])
GNULIB_LCHOWN=0; AC_SUBST([GNULIB_LCHOWN])
GNULIB_LINK=0; AC_SUBST([GNULIB_LINK])
+ GNULIB_LINKAT=0; AC_SUBST([GNULIB_LINKAT])
GNULIB_LSEEK=0; AC_SUBST([GNULIB_LSEEK])
GNULIB_PIPE2=0; AC_SUBST([GNULIB_PIPE2])
GNULIB_READLINK=0; AC_SUBST([GNULIB_READLINK])
@@ -79,6 +80,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
HAVE_GETPAGESIZE=1; AC_SUBST([HAVE_GETPAGESIZE])
HAVE_GETUSERSHELL=1; AC_SUBST([HAVE_GETUSERSHELL])
HAVE_LINK=1; AC_SUBST([HAVE_LINK])
+ HAVE_LINKAT=1; AC_SUBST([HAVE_LINKAT])
HAVE_PIPE2=1; AC_SUBST([HAVE_PIPE2])
HAVE_READLINK=1; AC_SUBST([HAVE_READLINK])
HAVE_READLINKAT=1; AC_SUBST([HAVE_READLINKAT])
diff --git a/modules/link-tests b/modules/link-tests
index ca61deb..d8e7b1a 100644
--- a/modules/link-tests
+++ b/modules/link-tests
@@ -1,8 +1,10 @@
Files:
+tests/test-link.h
tests/test-link.c
Depends-on:
errno
+stdbool
sys_stat
configure.ac:
diff --git a/modules/linkat b/modules/linkat
new file mode 100644
index 0000000..8d9dec3
--- /dev/null
+++ b/modules/linkat
@@ -0,0 +1,40 @@
+Description:
+linkat(): create a hard link, relative to two directories
+
+Files:
+lib/at-func2.c
+lib/linkat.c
+m4/linkat.m4
+
+Depends-on:
+areadlink
+dirname
+errno
+extensions
+fcntl-h
+filenamecat
+openat
+link
+link-follow
+lstat
+readlink
+same-inode
+stpcpy
+symlink
+unistd
+
+configure.ac:
+gl_FUNC_LINKAT
+gl_UNISTD_MODULE_INDICATOR([linkat])
+
+Makefile.am:
+
+Include:
+<fcntl.h>
+<unistd.h>
+
+License:
+GPL
+
+Maintainer:
+Jim Meyering, Eric Blake
diff --git a/modules/linkat-tests b/modules/linkat-tests
new file mode 100644
index 0000000..9fb6505
--- /dev/null
+++ b/modules/linkat-tests
@@ -0,0 +1,16 @@
+Files:
+tests/test-linkat.c
+
+Depends-on:
+areadlink-with-size
+filenamecat
+progname
+same-inode
+xgetcwd
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-linkat
+check_PROGRAMS += test-linkat
+test_linkat_LDADD = $(LDADD) @LIBINTL@
diff --git a/modules/unistd b/modules/unistd
index 875efb0..d21a204 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -45,6 +45,7 @@ unistd.h: unistd.in.h
-e 's|@''GNULIB_GETUSERSHELL''@|$(GNULIB_GETUSERSHELL)|g' \
-e 's|@''GNULIB_LCHOWN''@|$(GNULIB_LCHOWN)|g' \
-e 's|@''GNULIB_LINK''@|$(GNULIB_LINK)|g' \
+ -e 's|@''GNULIB_LINKAT''@|$(GNULIB_LINKAT)|g' \
-e 's|@''GNULIB_LSEEK''@|$(GNULIB_LSEEK)|g' \
-e 's|@''GNULIB_PIPE2''@|$(GNULIB_PIPE2)|g' \
-e 's|@''GNULIB_READLINK''@|$(GNULIB_READLINK)|g' \
@@ -71,6 +72,7 @@ unistd.h: unistd.in.h
-e 's|@''HAVE_GETPAGESIZE''@|$(HAVE_GETPAGESIZE)|g' \
-e 's|@''HAVE_GETUSERSHELL''@|$(HAVE_GETUSERSHELL)|g' \
-e 's|@''HAVE_LINK''@|$(HAVE_LINK)|g' \
+ -e 's|@''HAVE_LINKAT''@|$(HAVE_LINKAT)|g' \
-e 's|@''HAVE_PIPE2''@|$(HAVE_PIPE2)|g' \
-e 's|@''HAVE_READLINK''@|$(HAVE_READLINK)|g' \
-e 's|@''HAVE_READLINKAT''@|$(HAVE_READLINKAT)|g' \
diff --git a/tests/test-link.c b/tests/test-link.c
index e09a0bb..a77ffe7 100644
--- a/tests/test-link.c
+++ b/tests/test-link.c
@@ -20,6 +20,7 @@
#include <errno.h>
#include <fcntl.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -40,117 +41,13 @@
#define BASE "test-link.t"
+#include "test-link.h"
+
int
main (int argc, char **argv)
{
- int fd;
- int ret;
-
/* Remove any garbage left from previous partial runs. */
ASSERT (system ("rm -rf " BASE "*") == 0);
- /* Create first file. */
- fd = open (BASE "a", O_CREAT | O_EXCL | O_WRONLY, 0600);
- ASSERT (0 <= fd);
- ASSERT (write (fd, "hello", 5) == 5);
- ASSERT (close (fd) == 0);
-
- /* Not all file systems support link. Mingw doesn't have reliable
- st_nlink on hard links, but our implementation does fail with
- EPERM on poor file systems, and we can detect the inferior stat()
- via st_ino. Cygwin 1.5.x copies rather than links files on those
- file systems, but there, st_nlink and st_ino are reliable. */
- ret = link (BASE "a", BASE "b");
- if (!ret)
- {
- struct stat st;
- ASSERT (stat (BASE "b", &st) == 0);
- if (st.st_ino && st.st_nlink != 2)
- {
- ASSERT (unlink (BASE "b") == 0);
- errno = EPERM;
- ret = -1;
- }
- }
- if (ret == -1)
- {
- /* If the device does not support hard links, errno is
- EPERM on Linux, EOPNOTSUPP on FreeBSD. */
- switch (errno)
- {
- case EPERM:
- case EOPNOTSUPP:
- fputs ("skipping test: "
- "hard links are not supported on this file system\n", stderr);
- ASSERT (unlink (BASE "a") == 0);
- return 77;
- default:
- perror ("link");
- return 1;
- }
- }
- ASSERT (ret == 0);
-
- /* Now, for some behavior tests. Modify the contents of 'b', and
- ensure that 'a' can see it, both while 'b' exists and after. */
- fd = open (BASE "b", O_APPEND | O_WRONLY);
- ASSERT (0 <= fd);
- ASSERT (write (fd, "world", 5) == 5);
- ASSERT (close (fd) == 0);
- {
- char buf[11] = { 0 };
- fd = open (BASE "a", O_RDONLY);
- ASSERT (0 <= fd);
- ASSERT (read (fd, buf, 10) == 10);
- ASSERT (strcmp (buf, "helloworld") == 0);
- ASSERT (close (fd) == 0);
- ASSERT (unlink (BASE "b") == 0);
- fd = open (BASE "a", O_RDONLY);
- ASSERT (0 <= fd);
- ASSERT (read (fd, buf, 10) == 10);
- ASSERT (strcmp (buf, "helloworld") == 0);
- ASSERT (close (fd) == 0);
- }
-
- /* Test for various error conditions. Assumes hard links to
- directories are not permitted. */
- ASSERT (mkdir (BASE "d", 0700) == 0);
- errno = 0;
- ASSERT (link (BASE "a", ".") == -1);
- ASSERT (errno == EEXIST || errno == EINVAL);
- errno = 0;
- ASSERT (link (BASE "a", BASE "a") == -1);
- ASSERT (errno == EEXIST);
- ASSERT (link (BASE "a", BASE "b") == 0);
- errno = 0;
- ASSERT (link (BASE "a", BASE "b") == -1);
- ASSERT (errno == EEXIST);
- errno = 0;
- ASSERT (link (BASE "a", BASE "d") == -1);
- ASSERT (errno == EEXIST);
- errno = 0;
- ASSERT (link (BASE "c", BASE "e") == -1);
- ASSERT (errno == ENOENT);
- errno = 0;
- ASSERT (link (BASE "a", BASE "c/.") == -1);
- ASSERT (errno == ENOENT);
- errno = 0;
- ASSERT (link (BASE "a/", BASE "c") == -1);
- ASSERT (errno == ENOTDIR);
- errno = 0;
- ASSERT (link (BASE "a", BASE "c/") == -1);
- ASSERT (errno == ENOTDIR || errno == ENOENT);
- errno = 0;
- ASSERT (link (BASE "d", BASE "c") == -1);
- ASSERT (errno == EPERM || errno == EACCES);
-
- /* Clean up. */
- ASSERT (unlink (BASE "a") == 0);
- ASSERT (unlink (BASE "b") == 0);
- errno = 0;
- ASSERT (unlink (BASE "c") == -1);
- ASSERT (errno == ENOENT);
- ASSERT (rmdir (BASE "d") == 0);
-
- return 0;
+ return test_link (link, true);
}
diff --git a/tests/test-link.h b/tests/test-link.h
new file mode 100644
index 0000000..9ce1894
--- /dev/null
+++ b/tests/test-link.h
@@ -0,0 +1,137 @@
+/* Test of link() function.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+/* This file is designed to test both link(a,b) and
+ linkat(AT_FDCWD,a,AT_FDCWD,b). FUNC is the function to test.
+ Assumes that BASE and ASSERT are already defined, and that
+ appropriate headers are already included. If PRINT, warn before
+ skipping symlink tests with status 77. */
+
+static int
+test_link (int (*func) (char const *, char const *), bool print)
+{
+ int fd;
+ int ret;
+
+ /* Create first file. */
+ fd = open (BASE "a", O_CREAT | O_EXCL | O_WRONLY, 0600);
+ ASSERT (0 <= fd);
+ ASSERT (write (fd, "hello", 5) == 5);
+ ASSERT (close (fd) == 0);
+
+ /* Not all file systems support link. Mingw doesn't have reliable
+ st_nlink on hard links, but our implementation does fail with
+ EPERM on poor file systems, and we can detect the inferior stat()
+ via st_ino. Cygwin 1.5.x copies rather than links files on those
+ file systems, but there, st_nlink and st_ino are reliable. */
+ ret = func (BASE "a", BASE "b");
+ if (!ret)
+ {
+ struct stat st;
+ ASSERT (stat (BASE "b", &st) == 0);
+ if (st.st_ino && st.st_nlink != 2)
+ {
+ ASSERT (unlink (BASE "b") == 0);
+ errno = EPERM;
+ ret = -1;
+ }
+ }
+ if (ret == -1)
+ {
+ /* If the device does not support hard links, errno is
+ EPERM on Linux, EOPNOTSUPP on FreeBSD. */
+ switch (errno)
+ {
+ case EPERM:
+ case EOPNOTSUPP:
+ if (print)
+ fputs ("skipping test: "
+ "hard links not supported on this file system\n",
+ stderr);
+ ASSERT (unlink (BASE "a") == 0);
+ return 77;
+ default:
+ perror ("link");
+ return 1;
+ }
+ }
+ ASSERT (ret == 0);
+
+ /* Now, for some behavior tests. Modify the contents of 'b', and
+ ensure that 'a' can see it, both while 'b' exists and after. */
+ fd = open (BASE "b", O_APPEND | O_WRONLY);
+ ASSERT (0 <= fd);
+ ASSERT (write (fd, "world", 5) == 5);
+ ASSERT (close (fd) == 0);
+ {
+ char buf[11] = { 0 };
+ fd = open (BASE "a", O_RDONLY);
+ ASSERT (0 <= fd);
+ ASSERT (read (fd, buf, 10) == 10);
+ ASSERT (strcmp (buf, "helloworld") == 0);
+ ASSERT (close (fd) == 0);
+ ASSERT (unlink (BASE "b") == 0);
+ fd = open (BASE "a", O_RDONLY);
+ ASSERT (0 <= fd);
+ ASSERT (read (fd, buf, 10) == 10);
+ ASSERT (strcmp (buf, "helloworld") == 0);
+ ASSERT (close (fd) == 0);
+ }
+
+ /* Test for various error conditions. Assumes hard links to
+ directories are not permitted. */
+ ASSERT (mkdir (BASE "d", 0700) == 0);
+ errno = 0;
+ ASSERT (func (BASE "a", ".") == -1);
+ ASSERT (errno == EEXIST || errno == EINVAL);
+ errno = 0;
+ ASSERT (func (BASE "a", BASE "a") == -1);
+ ASSERT (errno == EEXIST);
+ ASSERT (func (BASE "a", BASE "b") == 0);
+ errno = 0;
+ ASSERT (func (BASE "a", BASE "b") == -1);
+ ASSERT (errno == EEXIST);
+ errno = 0;
+ ASSERT (func (BASE "a", BASE "d") == -1);
+ ASSERT (errno == EEXIST);
+ errno = 0;
+ ASSERT (func (BASE "c", BASE "e") == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func (BASE "a", BASE "c/.") == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func (BASE "a/", BASE "c") == -1);
+ ASSERT (errno == ENOTDIR);
+ errno = 0;
+ ASSERT (func (BASE "a", BASE "c/") == -1);
+ ASSERT (errno == ENOTDIR || errno == ENOENT);
+ errno = 0;
+ ASSERT (func (BASE "d", BASE "c") == -1);
+ ASSERT (errno == EPERM || errno == EACCES);
+
+ /* Clean up. */
+ ASSERT (unlink (BASE "a") == 0);
+ ASSERT (unlink (BASE "b") == 0);
+ errno = 0;
+ ASSERT (unlink (BASE "c") == -1);
+ ASSERT (errno == ENOENT);
+ ASSERT (rmdir (BASE "d") == 0);
+
+ return 0;
+}
diff --git a/tests/test-linkat.c b/tests/test-linkat.c
new file mode 100644
index 0000000..afb1799
--- /dev/null
+++ b/tests/test-linkat.c
@@ -0,0 +1,352 @@
+/* Tests of linkat.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "areadlink.h"
+#include "filenamecat.h"
+#include "same-inode.h"
+#include "xgetcwd.h"
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (stderr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+#define BASE "test-linkat.t"
+
+#include "test-link.h"
+
+static int dfd1 = AT_FDCWD;
+static int dfd2 = AT_FDCWD;
+static int flag = AT_SYMLINK_FOLLOW;
+
+/* Wrapper to test linkat like link. */
+static int
+do_link (char const *name1, char const *name2)
+{
+ return linkat (dfd1, name1, dfd2, name2, flag);
+}
+
+/* Wrapper to see if two symlinks act the same. */
+static void
+check_same_link (char const *name1, char const *name2)
+{
+ struct stat st1;
+ struct stat st2;
+ char *contents1;
+ char *contents2;
+ ASSERT (lstat (name1, &st1) == 0);
+ ASSERT (lstat (name2, &st2) == 0);
+ contents1 = areadlink_with_size (name1, st1.st_size);
+ contents2 = areadlink_with_size (name2, st2.st_size);
+ ASSERT (contents1);
+ ASSERT (contents2);
+ ASSERT (strcmp (contents1, contents2) == 0);
+ if (!LINK_FOLLOWS_SYMLINKS)
+ ASSERT (SAME_INODE (st1, st2));
+ free (contents1);
+ free (contents2);
+}
+
+int
+main ()
+{
+ int i;
+ int dfd;
+ char *cwd;
+ int result;
+
+ /* Clean up any trash from prior testsuite runs. */
+ ASSERT (system ("rm -rf " BASE "*") == 0);
+
+ /* Test basic link functionality, without mentioning symlinks. */
+ {
+ result = test_link (do_link, false);
+ dfd1 = open (".", O_RDONLY);
+ ASSERT (0 <= dfd1);
+ ASSERT (test_link (do_link, false) == result);
+ dfd2 = dfd1;
+ ASSERT (test_link (do_link, false) == result);
+ dfd1 = AT_FDCWD;
+ ASSERT (test_link (do_link, false) == result);
+ flag = 0;
+ ASSERT (test_link (do_link, false) == result);
+ dfd1 = dfd2;
+ ASSERT (test_link (do_link, false) == result);
+ dfd2 = AT_FDCWD;
+ ASSERT (test_link (do_link, false) == result);
+ ASSERT (close (dfd1) == 0);
+ dfd1 = AT_FDCWD;
+ ASSERT (test_link (do_link, false) == result);
+ }
+
+ /* Create locations to manipulate. */
+ ASSERT (mkdir (BASE "sub1", 0700) == 0);
+ ASSERT (mkdir (BASE "sub2", 0700) == 0);
+ dfd = creat (BASE "00", 0600);
+ ASSERT (0 <= dfd);
+ ASSERT (close (dfd) == 0);
+ cwd = xgetcwd ();
+
+ dfd = open (BASE "sub1", O_RDONLY);
+ ASSERT (0 <= dfd);
+ ASSERT (chdir (BASE "sub2") == 0);
+
+ /* There are 16 possible scenarios, based on whether an fd is
+ AT_FDCWD or real, whether a file is absolute or relative, coupled
+ with whether flag is set for 32 iterations.
+
+ To ensure that we test all of the code paths (rather than
+ triggering early normalization optimizations), we use a loop to
+ repeatedly rename a file in the parent directory, use an fd open
+ on subdirectory 1, all while executing in subdirectory 2; all
+ relative names are thus given with a leading "../". Finally, the
+ last scenario (two relative paths given, neither one AT_FDCWD)
+ has two paths, based on whether the two fds are equivalent, so we
+ do the other variant after the loop. */
+ for (i = 0; i < 32; i++)
+ {
+ int flag = (i & 0x10 ? AT_SYMLINK_FOLLOW : 0);
+ int fd1 = (i & 8) ? dfd : AT_FDCWD;
+ char *file1 = file_name_concat ((i & 4) ? ".." : cwd, BASE "xx", NULL);
+ int fd2 = (i & 2) ? dfd : AT_FDCWD;
+ char *file2 = file_name_concat ((i & 1) ? ".." : cwd, BASE "xx", NULL);
+
+ ASSERT (sprintf (strchr (file1, '\0') - 2, "%02d", i) == 2);
+ ASSERT (sprintf (strchr (file2, '\0') - 2, "%02d", i + 1) == 2);
+ ASSERT (linkat (fd1, file1, fd2, file2, flag) == 0);
+ ASSERT (unlinkat (fd1, file1, 0) == 0);
+ free (file1);
+ free (file2);
+ }
+ dfd2 = open ("..", O_RDONLY);
+ ASSERT (0 <= dfd2);
+ ASSERT (linkat (dfd, "../" BASE "32", dfd2, BASE "33", 0) == 0);
+ ASSERT (linkat (dfd, "../" BASE "33", dfd2, BASE "34",
+ AT_SYMLINK_FOLLOW) == 0);
+ ASSERT (close (dfd2) == 0);
+
+ /* Now we change back to the parent directory, and set dfd to ".",
+ in order to test behavior on symlinks. */
+ ASSERT (chdir ("..") == 0);
+ ASSERT (close (dfd) == 0);
+ if (symlink (BASE "sub1", BASE "link1"))
+ {
+ ASSERT (unlink (BASE "32") == 0);
+ ASSERT (unlink (BASE "33") == 0);
+ ASSERT (unlink (BASE "34") == 0);
+ ASSERT (rmdir (BASE "sub1") == 0);
+ ASSERT (rmdir (BASE "sub2") == 0);
+ free (cwd);
+ fputs ("skipping test: symlinks not supported on this filesystem\n",
+ stderr);
+ return result;
+ }
+ dfd = open (".", O_RDONLY);
+ ASSERT (0 <= dfd);
+ ASSERT (symlink (BASE "34", BASE "link2") == 0);
+ ASSERT (symlink (BASE "link3", BASE "link3") == 0);
+ ASSERT (symlink (BASE "nowhere", BASE "link4") == 0);
+
+ /* Link cannot overwrite existing files. */
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "sub1", 0) == -1);
+ ASSERT (errno == EEXIST);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1/", dfd, BASE "sub1", 0) == -1);
+ ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "sub1/", 0) == -1);
+ ASSERT (errno == EEXIST);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "sub1",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1/", dfd, BASE "sub1",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "sub1/",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link2", 0) == -1);
+ ASSERT (errno == EEXIST);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link2",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link3", 0) == -1);
+ ASSERT (errno == EEXIST || errno == ELOOP);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link3",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES
+ || errno == ELOOP);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link2", dfd, BASE "link3", 0) == -1);
+ ASSERT (errno == EEXIST || errno == ELOOP);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link2", dfd, BASE "link3",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == EEXIST || errno == ELOOP);
+
+ /* AT_SYMLINK_FOLLOW only follows first argument, not second. */
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link4", 0) == -1);
+ ASSERT (errno == EEXIST);
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link4",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == EEXIST || errno == EPERM || errno == EACCES);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "34", dfd, BASE "link4", 0) == -1);
+ ASSERT (errno == EEXIST);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "34", dfd, BASE "link4", AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == EEXIST);
+
+ /* Trailing slash handling. */
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link2/", dfd, BASE "link5", 0) == -1);
+ ASSERT (errno == ENOTDIR);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link2/", dfd, BASE "link5",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == ENOTDIR);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link3/", dfd, BASE "link5", 0) == -1);
+ ASSERT (errno == ELOOP);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link3/", dfd, BASE "link5",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == ELOOP);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link4/", dfd, BASE "link5", 0) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link4/", dfd, BASE "link5",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == ENOENT);
+
+ /* Check for hard links to symlinks. */
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link5", 0) == 0);
+ check_same_link (BASE "link1", BASE "link5");
+ ASSERT (unlink (BASE "link5") == 0);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link1", dfd, BASE "link5",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == EPERM || errno == EACCES);
+ ASSERT (linkat (dfd, BASE "link2", dfd, BASE "link5", 0) == 0);
+ check_same_link (BASE "link2", BASE "link5");
+ ASSERT (unlink (BASE "link5") == 0);
+ ASSERT (linkat (dfd, BASE "link2", dfd, BASE "file", AT_SYMLINK_FOLLOW) ==
0);
+ errno = 0;
+ ASSERT (areadlink (BASE "file") == NULL);
+ ASSERT (errno == EINVAL);
+ ASSERT (unlink (BASE "file") == 0);
+ ASSERT (linkat (dfd, BASE "link3", dfd, BASE "link5", 0) == 0);
+ check_same_link (BASE "link3", BASE "link5");
+ ASSERT (unlink (BASE "link5") == 0);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link3", dfd, BASE "link5",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == ELOOP);
+ ASSERT (linkat (dfd, BASE "link4", dfd, BASE "link5", 0) == 0);
+ check_same_link (BASE "link4", BASE "link5");
+ ASSERT (unlink (BASE "link5") == 0);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link4", dfd, BASE "link5",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == ENOENT);
+
+ /* Check that symlink to symlink to file is followed all the way. */
+ ASSERT (symlink (BASE "link2", BASE "link5") == 0);
+ ASSERT (linkat (dfd, BASE "link5", dfd, BASE "link6", 0) == 0);
+ check_same_link (BASE "link5", BASE "link6");
+ ASSERT (unlink (BASE "link6") == 0);
+ ASSERT (linkat (dfd, BASE "link5", dfd, BASE "file", AT_SYMLINK_FOLLOW) ==
0);
+ errno = 0;
+ ASSERT (areadlink (BASE "file") == NULL);
+ ASSERT (errno == EINVAL);
+ ASSERT (unlink (BASE "link5") == 0);
+ ASSERT (symlink (BASE "link3", BASE "link5") == 0);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link5", dfd, BASE "file",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == ELOOP);
+ ASSERT (unlink (BASE "link5") == 0);
+ ASSERT (symlink (BASE "link4", BASE "link5") == 0);
+ errno = 0;
+ ASSERT (linkat (dfd, BASE "link5", dfd, BASE "file",
+ AT_SYMLINK_FOLLOW) == -1);
+ ASSERT (errno == ENOENT);
+
+ /* Now for some real fun with directory crossing. */
+ ASSERT (symlink (cwd, BASE "sub1/link") == 0);
+ ASSERT (symlink (".././/" BASE "sub1/link/" BASE "link2",
+ BASE "sub2/link") == 0);
+ ASSERT (close (dfd) == 0);
+ dfd = open (BASE "sub1", O_RDONLY);
+ ASSERT (0 <= dfd);
+ dfd2 = open (BASE "sub2", O_RDONLY);
+ ASSERT (0 < dfd2);
+ ASSERT (linkat (dfd, "../" BASE "sub2/link", dfd2, "./..//" BASE "sub1/file",
+ AT_SYMLINK_FOLLOW) == 0);
+ errno = 0;
+ ASSERT (areadlink (BASE "sub1/file") == NULL);
+ ASSERT (errno == EINVAL);
+
+ /* Cleanup. */
+ ASSERT (close (dfd) == 0);
+ ASSERT (close (dfd2) == 0);
+ ASSERT (unlink (BASE "sub1/file") == 0);
+ ASSERT (unlink (BASE "sub1/link") == 0);
+ ASSERT (unlink (BASE "sub2/link") == 0);
+ ASSERT (unlink (BASE "32") == 0);
+ ASSERT (unlink (BASE "33") == 0);
+ ASSERT (unlink (BASE "34") == 0);
+ ASSERT (rmdir (BASE "sub1") == 0);
+ ASSERT (rmdir (BASE "sub2") == 0);
+ ASSERT (unlink (BASE "link1") == 0);
+ ASSERT (unlink (BASE "link2") == 0);
+ ASSERT (unlink (BASE "link3") == 0);
+ ASSERT (unlink (BASE "link4") == 0);
+ ASSERT (unlink (BASE "link5") == 0);
+ ASSERT (unlink (BASE "file") == 0);
+ free (cwd);
+ return result;
+}
--
1.6.5.rc1