From efcee783e4d576898130ccbf1cb2d7d6bf1b8420 Mon Sep 17 00:00:00 2001 From: Eric Blake 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 100755 tests/ln/hard-to-sym diff --git a/NEWS b/NEWS index 1571c9c0d..506050203 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 d1dc128e8..f648e226a 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 0bfbd5689..b10cc100c 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -8753,6 +8753,11 @@ On a @acronym{GNU} system, this command acts like @samp{ln --directory 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} +to specify which behavior is desired. + @exitstatus @@ -8808,8 +8813,10 @@ A @dfn{hard link} is another name for an existing file; the link and the 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 @@ refers to a different file, by name. When most operations (opening, 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 @@ Remove existing destination files. @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 @@ just like a directory. 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 file name 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 @@ Print the name of each file after linking it successfully. @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 0c35338f2..4f75c1919 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 are mandatory for short options too.\n\ -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 symbolic links\n\ -s, --symbolic make symbolic links instead of hard links\n\ "), stdout); fputs (_("\ @@ -391,6 +385,11 @@ The version control method may be selected via the --backup option or through\n\ 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 63e60c8c5..2acad6bd6 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 100755 index 000000000..510b57abf --- /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 . + +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 f13bd7bf3..d42d68a27 100755 --- a/tests/ln/misc +++ b/tests/ln/misc @@ -107,29 +107,6 @@ touch a b || framework_failure 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.