Eric Blake <ebb9 <at> byu.net> writes:

> > I'd like to make a test release soon.  Maybe as soon as Friday.
> > If you have something that you'd like included, please speak up.
> 
> I'm nearly ready to post support for ln -P/-L, thanks to the pending addition 
> of gnulib linkat().  That would be nice to have in the release.

How does this look?


From: Eric Blake <e...@byu.net>
Date: Thu, 24 Sep 2009 11:57:11 -0600
Subject: [PATCH] ln: add -L/-P options

* src/ln.c (STAT_LIKE_LINK): Delete.
(logical): New flag.
(long_options): Add -L, -P.
(usage): Mention them.
(main): Choose between them.
(do_link): Perform correct action.
* tests/ln/misc: Move hard-to-sym portion of test...
* tests/ln/hard-to-sym: ...into new test, and add more.
* tests/Makefile.am (TESTS): Run new test.
* NEWS: Document this.
* doc/coreutils.texi (link invocation, ln invocation): Likewise.
* bootstrap.conf (gnulib_modules): Add linkat.
---
 NEWS                 |    7 ++++
 bootstrap.conf       |    1 +
 doc/coreutils.texi   |   49 +++++++++++++++++++++++++++---
 src/ln.c             |   63 ++++++++++++++++++++-----------------
 tests/Makefile.am    |    1 +
 tests/ln/hard-to-sym |   82 ++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/ln/misc        |   23 --------------
 7 files changed, 169 insertions(+), 57 deletions(-)
 create mode 100644 tests/ln/hard-to-sym

diff --git a/NEWS b/NEWS
index 1571c9c..5060502 100644
--- a/NEWS
+++ b/NEWS
@@ -32,6 +32,13 @@ GNU coreutils NEWS                                    -*- 
outline -*-
   last component (possibly via a dangling symlink) can be created,
   since mkdir will succeed in that case.

+** New features
+
+  ln now accepts the options --logical (-L) and --physical (-P),
+  added by POSIX 2008.  The default behavior is -P on systems like
+  GNU/Linux where link(2) creates hard links to symlinks, and -L on
+  BSD systems where link(2) follows symlinks.
+
 ** Improvements

   rm: rewrite to use gnulib's fts
diff --git a/bootstrap.conf b/bootstrap.conf
index d1dc128..f648e22 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -132,6 +132,7 @@ gnulib_modules="
   linebuffer
   link
   link-follow
+  linkat
   long-options
   lstat
   maintainer-makefile
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 0bfbd56..e7cf5ea 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -8753,6 +8753,11 @@ link invocation
 not specified by @acronym{POSIX}, and the @command{link} command is
 more portable in practice.

+If @var{filename} is a symbolic link, it is unspecified whether
+...@var{linkname} will be a hard link to the symbolic link or to the
+target of the symbolic link.  Use @command{ln -P} or @command{ln -L}
+specify which behavior is desired.
+
 @exitstatus


@@ -8808,8 +8813,10 @@ ln invocation
 original are indistinguishable.  Technically speaking, they share the
 same inode, and the inode contains all the information about a
 file---indeed, it is not incorrect to say that the inode @emph{is} the
-file.  On all existing implementations, you cannot make a hard link to
-a directory, and hard links cannot cross file system boundaries.  (These
+file.  Most systems prohibit making a hard link to
+a directory; on those where it is allowed, only the super-user can do
+so (and with caution, since creating a cycle will cause problems to many
+other utilities).  Hard links cannot cross file system boundaries.  (These
 restrictions are not mandated by @acronym{POSIX}, however.)

 @cindex dereferencing symbolic links
@@ -8821,9 +8828,13 @@ ln invocation
 reading, writing, and so on) are passed the symbolic link file, the
 kernel automatically @dfn{dereferences} the link and operates on the
 target of the link.  But some operations (e.g., removing) work on the
-link file itself, rather than on its target.  The owner, group, and
-mode of a symlink are not significant to file access performed through
-the link.  @xref{Symbolic Links,,,
+link file itself, rather than on its target.  The owner and group of a
+symlink are not significant to file access performed through
+the link, but do have implications on deleting a symbolic link from a
+directory with the restricted deletion bit set.  On the GNU system,
+the mode of a symlink has no significance and cannot be changed, but
+on some BSD systems, the mode can be changed and will affect whether
+the symlink will be traversed in file name resolution.  @xref{Symbolic Links,,,
 libc, The GNU C Library Reference Manual}.

 Symbolic links can contain arbitrary strings; a @dfn{dangling symlink}
@@ -8878,6 +8889,14 @@ ln invocation
 @cindex prompting, and @command{ln}
 Prompt whether to remove existing destination files.

+...@item -L
+...@itemx --logical
+...@opindex -L
+...@opindex --logical
+If @option{-s} is not in effect, and the source file is a symbolic
+link, create the hard link to the file referred to by the symbolic
+link, rather than the symbolic link itself.
+
 @item -n
 @itemx --no-dereference
 @opindex -n
@@ -8899,6 +8918,17 @@ ln invocation
 This option is weaker than the @option{--no-target-directory}
 (@option{-T}) option, so it has no effect if both options are given.

+...@item -P
+...@itemx --physical
+...@opindex -P
+...@opindex --physical
+If @option{-s} is not in effect, and the source file is a symbolic
+link, create the hard link to the symbolic link itself.  On platforms
+where this is not supported by the kernel, this option creates a
+symbolic link with identical contents; since symbolic link contents
+cannot be edited, any path resolution performed through either link
+will be the same as if a hard link had been created.
+
 @item -s
 @itemx --symbolic
 @opindex -s
@@ -8920,6 +8950,15 @@ ln invocation

 @end table

+...@cindex hard links to symbolic links
+...@cindex symbolic links and @command{ln}
+If @option{-L} and @option{-P} are both given, the last one takes
+precedence.  If @option{-s} is also given, @option{-L} and @option{-P}
+are silently ignored.  If neither option is given, then this
+implementation defaults to @option{-P} if the system @code{link} supports
+hard links to symbolic links (such as the GNU system), and @option{-L}
+if @code{link} follows symbolic links (such as on BSD).
+
 @exitstatus

 Examples:
diff --git a/src/ln.c b/src/ln.c
index 0c35338..0cc3d6f 100644
--- a/src/ln.c
+++ b/src/ln.c
@@ -39,26 +39,15 @@
   proper_name ("Mike Parker"), \
   proper_name ("David MacKenzie")

-/* In being careful not even to try to make hard links to directories,
-   we have to know whether link(2) follows symlinks.  If it does, then
-   we have to *stat* the `source' to see if the resulting link would be
-   to a directory.  Otherwise, we have to use *lstat* so that we allow
-   users to make hard links to symlinks-that-point-to-directories.  */
-
-#if LINK_FOLLOWS_SYMLINKS
-# define STAT_LIKE_LINK(File, Stat_buf) \
-  stat (File, Stat_buf)
-#else
-# define STAT_LIKE_LINK(File, Stat_buf) \
-  lstat (File, Stat_buf)
-#endif
-
 /* FIXME: document */
 static enum backup_type backup_type;

 /* If true, make symbolic links; otherwise, make hard links.  */
 static bool symbolic_link;

+/* If true, hard links are logical rather than physical.  */
+static bool logical = !!LINK_FOLLOWS_SYMLINKS;
+
 /* If true, ask the user before removing existing files.  */
 static bool interactive;

@@ -71,7 +60,7 @@ static bool verbose;
 /* If true, allow the superuser to *attempt* to make hard links
    to directories.  However, it appears that this option is not useful
    in practice, since even the superuser is prohibited from hard-linking
-   directories on most (all?) existing systems.  */
+   directories on most existing systems (Solaris being an exception).  */
 static bool hard_dir_link;

 /* If nonzero, and the specified destination is a symbolic link to a
@@ -99,6 +88,8 @@ static struct option const long_options[] =
   {"interactive", no_argument, NULL, 'i'},
   {"suffix", required_argument, NULL, 'S'},
   {"target-directory", required_argument, NULL, 't'},
+  {"logical", no_argument, NULL, 'L'},
+  {"physical", no_argument, NULL, 'P'},
   {"symbolic", no_argument, NULL, 's'},
   {"verbose", no_argument, NULL, 'v'},
   {GETOPT_HELP_OPTION_DECL},
@@ -143,18 +134,15 @@ do_link (const char *source, const char *dest)
   bool source_is_dir = false;
   bool ok;

-  /* Use stat here instead of lstat.
-     On SVR4, link does not follow symlinks, so this check disallows
-     making hard links to symlinks that point to directories.  Big deal.
-     On other systems, link follows symlinks, so this check is right.
-
-     FIXME - POSIX 2008 added the AT_SYMLINK_FOLLOW flag to linkat so
-     that we can specify either behavior, via the new options -L
-     (hard-link to symlinks) and -P (hard-link to the referent).  Once
-     gnulib has a decent implementation, we should use it here.  */
   if (!symbolic_link)
     {
-      if (STAT_LIKE_LINK (source, &source_stats) != 0)
+       /* Which stat to use depends on whether linkat will follow the
+          symlink.  We can't use the shorter
+          (logical ? stat : lstat) (source, &source_stats)
+          since stat might be a function-like macro.  */
+      if ((logical ? stat (source, &source_stats)
+           : lstat (source, &source_stats))
+          != 0)
         {
           error (0, errno, _("accessing %s"), quote (source));
           return false;
@@ -258,7 +246,9 @@ do_link (const char *source, const char *dest)
         }
     }

-  ok = ((symbolic_link ? symlink (source, dest) : link (source, dest))
+  ok = ((symbolic_link ? symlink (source, dest)
+         : linkat (AT_FDCWD, source, AT_FDCWD, dest,
+                   logical ? AT_SYMLINK_FOLLOW : 0))
         == 0);

   /* If the attempt to create a link failed and we are removing or
@@ -289,7 +279,9 @@ do_link (const char *source, const char *dest)
           return false;
         }

-      ok = ((symbolic_link ? symlink (source, dest) : link (source, dest))
+      ok = ((symbolic_link ? symlink (source, dest)
+             : linkat (AT_FDCWD, source, AT_FDCWD, dest,
+                       logical ? AT_SYMLINK_FOLLOW : 0))
             == 0);
     }

@@ -370,9 +362,11 @@ Mandatory arguments to long options
   -f, --force                 remove existing destination files\n\
 "), stdout);
       fputs (_("\
+  -i, --interactive           prompt whether to remove destinations\n\
+  -L, --logical               make hard links to symbolic link references\n\
   -n, --no-dereference        treat destination that is a symlink to a\n\
                                 directory as if it were a normal file\n\
-  -i, --interactive           prompt whether to remove destinations\n\
+  -P, --physical              make hard links directly to symblic links\n\
   -s, --symbolic              make symbolic links instead of hard links\n\
 "), stdout);
       fputs (_("\
@@ -391,6 +385,11 @@ The version control method may
 the VERSION_CONTROL environment variable.  Here are the values:\n\
 \n\
 "), stdout);
+      printf (_("\
+Using -s ignores -L and -P.  Otherwise, the last option specified controls\n\
+behavior when the source is a symbolic link, defaulting to %s.\n\
+\n\
+"), LINK_FOLLOWS_SYMLINKS ? "-L" : "-P");
       fputs (_("\
   none, off       never make backups (even if --backup is given)\n\
   numbered, t     make numbered backups\n\
@@ -430,7 +429,7 @@ main (int argc, char **argv)
   symbolic_link = remove_existing_files = interactive = verbose
     = hard_dir_link = false;

-  while ((c = getopt_long (argc, argv, "bdfinst:vFS:T", long_options, NULL))
+  while ((c = getopt_long (argc, argv, "bdfinst:vFLPS:T", long_options, NULL))
          != -1)
     {
       switch (c)
@@ -452,9 +451,15 @@ main (int argc, char **argv)
           remove_existing_files = false;
           interactive = true;
           break;
+        case 'L':
+          logical = true;
+          break;
         case 'n':
           dereference_dest_dir_symlinks = false;
           break;
+        case 'P':
+          logical = false;
+          break;
         case 's':
           symbolic_link = true;
           break;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 63e60c8..2acad6b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -344,6 +344,7 @@ TESTS =                                             \
   install/trap                                 \
   ln/backup-1                                  \
   ln/hard-backup                               \
+  ln/hard-to-sym                               \
   ln/misc                                      \
   ln/sf-1                                      \
   ln/slash-decorated-nonexistent-dest          \
diff --git a/tests/ln/hard-to-sym b/tests/ln/hard-to-sym
new file mode 100644
index 0000000..510b57a
--- /dev/null
+++ b/tests/ln/hard-to-sym
@@ -0,0 +1,82 @@
+#!/bin/sh
+# Tests for ln -L/-P.
+
+# 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/>.
+
+if test "$VERBOSE" = yes; then
+  set -x
+  ln --version
+fi
+
+. $srcdir/test-lib.sh
+
+fail=0
+
+# ===================================================
+# ensure -s silently overrides -L, -P
+touch a || framework_failure
+ln -L -s a symlink1 || fail=1
+ln -P -s symlink1 symlink2 || fail=1
+ln -s -L -P symlink2 symlink3 || fail=1
+
+# ===================================================
+# ensure that -L follows symlinks, and overrides -P
+ln -P -L symlink3 hard-to-a || fail=1
+ls=`ls -lG hard-to-a`x
+case "$ls" in
+  *'hard-to-ax') ;;
+  *'hard-to-a -> '*x) fail=1 ;;
+  *) framework_failure ;;
+esac
+
+# ===================================================
+# ensure that -P links (or at least duplicates) symlinks, and overrides -L
+ln -L -P symlink3 hard-to-3 || fail=1
+ls=`ls -lG hard-to-3`x
+case "$ls" in
+  *'hard-to-3 -> symlink2x') ;;
+  *'hard-to-3x') fail=1 ;;
+  *'hard-to-3 -> '*x) fail=1 ;;
+  *) framework_failure ;;
+esac
+
+# ===================================================
+# Create a hard link to a dangling symlink.
+ln -s /no-such-dir || framework_failure
+ln -L no-such-dir hard-to-dangle 2>err && fail=1
+case `cat err` in
+  *' accessing `no-such-dir'\':*) ;;
+  *) fail=1 ;;
+esac
+ln -P no-such-dir hard-to-dangle || fail=1
+
+# ===================================================
+# Create a hard link to a symlink to a directory.
+mkdir d || framework_failure
+ln -s d link-to-dir || framework_failure
+ln -L link-to-dir hard-to-dir-link 2>err && fail=1
+case `cat err` in
+  *': `link-to-dir'\'': hard link not allowed for directory'*) ;;
+  *) fail=1 ;;
+esac
+ln -P link-to-dir/ hard-to-dir-link 2>err && fail=1
+case `cat err` in
+  *': `link-to-dir/'\'': hard link not allowed for directory'*) ;;
+  *) fail=1 ;;
+esac
+ln -P link-to-dir hard-to-dir-link || fail=1
+
+Exit $fail
diff --git a/tests/ln/misc b/tests/ln/misc
index f13bd7b..d42d68a 100755
--- a/tests/ln/misc
+++ b/tests/ln/misc
@@ -108,29 +108,6 @@ ln b b~ || framework_failure
 ln -f --b=simple a b || fail=1

 # ===================================================
-# determine if link(2) follows symlinks on this system
-touch a || framework_failure
-ln -s a symlink || framework_failure
-ln symlink hard-to-sym > /dev/null 2>&1 || framework_failure
-ls=`ls -lG hard-to-sym`x
-case "$ls" in
-  *'hard-to-symx') link_follows_symlink=yes ;;
-  *'hard-to-sym -> ax') link_follows_symlink=no ;;
-  *) framework_failure ;;
-esac
-
-if test $link_follows_symlink = no; then
-  # Create a hard link to a dangling symlink.
-  # This is not portable.  At least sunos4.1.4 and OpenBSD 2.3 fail this test.
-  # They get this:
-  # ln: cannot create hard link `hard-to-dangle' to `no-such-dir': \
-  #   No such file or directory
-  #
-  ln -s /no-such-dir || fail=1
-  ln no-such-dir hard-to-dangle > /dev/null 2>&1 || fail=1
-fi
-rm -rf a symlink hard-to-sym hard-to-dangle
-# ===================================================

 # Make sure ln can make simple backups.
 # This was fixed in 4.0.34.  Broken in 4.0r.
-- 
1.6.1.2






Reply via email to