maint: improve error messages upon failed read, write, access, close
[platform/upstream/coreutils.git] / src / ln.c
index 0ec3d3e..1aa1473 100644 (file)
--- a/src/ln.c
+++ b/src/ln.c
@@ -1,10 +1,10 @@
-/* `ln' program to create links between files.
-   Copyright (C) 86, 89, 90, 91, 1995-2004 Free Software Foundation, Inc.
+/* 'ln' program to create links between files.
+   Copyright (C) 1986-2013 Free Software Foundation, Inc.
 
-   This program is free software; you can redistribute it and/or modify
+   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, or (at your option)
-   any later version.
+   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
@@ -12,8 +12,7 @@
    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, write to the Free Software Foundation,
-   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 \f
 /* Written by Mike Parker and David MacKenzie. */
 
 #include <getopt.h>
 
 #include "system.h"
-#include "same.h"
 #include "backupfile.h"
-#include "dirname.h"
 #include "error.h"
+#include "filenamecat.h"
+#include "file-set.h"
+#include "hash.h"
+#include "hash-triple.h"
 #include "quote.h"
+#include "relpath.h"
+#include "same.h"
 #include "yesno.h"
+#include "canonicalize.h"
 
-/* The official name of this program (e.g., no `g' prefix).  */
+/* The official name of this program (e.g., no 'g' prefix).  */
 #define PROGRAM_NAME "ln"
 
-#define AUTHORS "Mike Parker", "David MacKenzie"
-
-#ifndef ENABLE_HARD_LINK_TO_SYMLINK_WARNING
-# define ENABLE_HARD_LINK_TO_SYMLINK_WARNING 0
-#endif
-
-/* 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
-
-/* Construct a string NEW_DEST by concatenating DEST, a slash, and
-   basename(SOURCE) in alloca'd memory.  Don't modify DEST or SOURCE.  */
-
-#define PATH_BASENAME_CONCAT(new_dest, dest, source)                   \
-    do                                                                 \
-      {                                                                        \
-       const char *source_base;                                        \
-       char *tmp_source;                                               \
-       size_t buf_len = strlen (source) + 1;                           \
-                                                                       \
-       tmp_source = alloca (buf_len);                                  \
-       memcpy (tmp_source, (source), buf_len);                         \
-       strip_trailing_slashes (tmp_source);                            \
-       source_base = base_name (tmp_source);                           \
-                                                                       \
-       (new_dest) = alloca (strlen ((dest)) + 1                        \
-                                     + strlen (source_base) + 1);      \
-       stpcpy (stpcpy (stpcpy ((new_dest), (dest)), "/"), source_base);\
-      }                                                                        \
-    while (0)
-
-/* The name by which the program was run, for error messages.  */
-char *program_name;
+#define AUTHORS \
+  proper_name ("Mike Parker"), \
+  proper_name ("David MacKenzie")
 
 /* FIXME: document */
 static enum backup_type backup_type;
 
-/* A pointer to the function used to make links.  This will point to either
-   `link' or `symlink'. */
-static int (*linkfunc) ();
-
 /* If true, make symbolic links; otherwise, make hard links.  */
 static bool symbolic_link;
 
+/* If true, make symbolic links relative  */
+static bool relative;
+
+/* 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;
 
@@ -99,15 +65,24 @@ 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
    directory, treat it just as if it were a directory.  Otherwise, the
-   command `ln --force --no-dereference file symlink-to-dir' deletes
+   command 'ln --force --no-dereference file symlink-to-dir' deletes
    symlink-to-dir before creating the new link.  */
 static bool dereference_dest_dir_symlinks = true;
 
+/* This is a set of destination name/inode/dev triples for hard links
+   created by ln.  Use this data structure to avoid data loss via a
+   sequence of commands like this:
+   rm -rf a b c; mkdir a b c; touch a/f b/f; ln -f a/f b/f c && rm -r a b */
+static Hash_table *dest_set;
+
+/* Initial size of the dest_set hash table.  */
+enum { DEST_INFO_INITIAL_CAPACITY = 61 };
+
 static struct option const long_options[] =
 {
   {"backup", optional_argument, NULL, 'b'},
@@ -118,9 +93,11 @@ 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'},
+  {"relative", no_argument, NULL, 'r'},
   {"symbolic", no_argument, NULL, 's'},
   {"verbose", no_argument, NULL, 'v'},
-  {"version-control", required_argument, NULL, 'V'}, /* Deprecated. FIXME. */
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
@@ -134,77 +111,109 @@ static struct option const long_options[] =
 static bool
 target_directory_operand (char const *file)
 {
-  char const *b = base_name (file);
+  char const *b = last_component (file);
   size_t blen = strlen (b);
   bool looks_like_a_dir = (blen == 0 || ISSLASH (b[blen - 1]));
   struct stat st;
-  int err = ((dereference_dest_dir_symlinks ? stat : lstat) (file, &st) == 0
-            ? 0 : errno);
+  int stat_result =
+    (dereference_dest_dir_symlinks ? stat (file, &st) : lstat (file, &st));
+  int err = (stat_result == 0 ? 0 : errno);
   bool is_a_dir = !err && S_ISDIR (st.st_mode);
   if (err && err != ENOENT)
-    error (EXIT_FAILURE, err, _("accessing %s"), quote (file));
+    error (EXIT_FAILURE, err, _("failed to access %s"), quote (file));
   if (is_a_dir < looks_like_a_dir)
     error (EXIT_FAILURE, err, _("target %s is not a directory"), quote (file));
   return is_a_dir;
 }
 
+/* Return FROM represented as relative to the dir of TARGET.
+   The result is malloced.  */
+
+static char *
+convert_abs_rel (const char *from, const char *target)
+{
+  char *realtarget = canonicalize_filename_mode (target, CAN_MISSING);
+  char *realfrom = canonicalize_filename_mode (from, CAN_MISSING);
+
+  /* Write to a PATH_MAX buffer.  */
+  char *relative_from = xmalloc (PATH_MAX);
+
+  /* Get dirname to generate paths relative to.  */
+  realtarget[dir_len (realtarget)] = '\0';
+
+  if (!relpath (realfrom, realtarget, relative_from, PATH_MAX))
+    {
+      free (relative_from);
+      relative_from = NULL;
+    }
+
+  free (realtarget);
+  free (realfrom);
+
+  return relative_from ? relative_from : xstrdup (from);
+}
+
 /* Make a link DEST to the (usually) existing file SOURCE.
    Symbolic links to nonexistent files are allowed.
-   If DEST_IS_DIR, put the link to SOURCE in the DEST directory.
    Return true if successful.  */
 
 static bool
-do_link (const char *source, const char *dest, bool dest_is_dir)
+do_link (const char *source, const char *dest)
 {
   struct stat source_stats;
   struct stat dest_stats;
   char *dest_backup = NULL;
-  bool lstat_ok = false;
+  char *rel_source = NULL;
+  bool dest_lstat_ok = false;
+  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.  */
   if (!symbolic_link)
     {
-      if (STAT_LIKE_LINK (source, &source_stats) != 0)
-       {
-         error (0, errno, _("accessing %s"), quote (source));
-         return false;
-       }
-
-      if (ENABLE_HARD_LINK_TO_SYMLINK_WARNING
-         && S_ISLNK (source_stats.st_mode))
-       {
-         error (0, 0, _("%s: warning: making a hard link to a symbolic link\
- is not portable"),
-                quote (source));
-       }
-
-      if (!hard_dir_link && S_ISDIR (source_stats.st_mode))
-       {
-         error (0, 0, _("%s: hard link not allowed for directory"),
-                quote (source));
-         return false;
-       }
+       /* 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, _("failed to access %s"), quote (source));
+          return false;
+        }
+
+      if (S_ISDIR (source_stats.st_mode))
+        {
+          source_is_dir = true;
+          if (! hard_dir_link)
+            {
+              error (0, 0, _("%s: hard link not allowed for directory"),
+                     quote (source));
+              return false;
+            }
+        }
     }
 
-  if (dest_is_dir)
+  if (remove_existing_files || interactive || backup_type != no_backups)
     {
-      /* Treat DEST as a directory; build the full filename.  */
-      char *new_dest;
-      PATH_BASENAME_CONCAT (new_dest, dest, source);
-      dest = new_dest;
+      dest_lstat_ok = (lstat (dest, &dest_stats) == 0);
+      if (!dest_lstat_ok && errno != ENOENT)
+        {
+          error (0, errno, _("failed to access %s"), quote (dest));
+          return false;
+        }
     }
 
-  if (remove_existing_files || interactive || backup_type != no_backups)
+  /* If the current target was created as a hard link to another
+     source file, then refuse to unlink it.  */
+  if (dest_lstat_ok
+      && dest_set != NULL
+      && seen_file (dest_set, dest, &dest_stats))
     {
-      lstat_ok = (lstat (dest, &dest_stats) == 0);
-      if (!lstat_ok && errno != ENOENT)
-       {
-         error (0, errno, _("accessing %s"), quote (dest));
-         return false;
-       }
+      error (0, 0,
+             _("will not overwrite just-created %s with %s"),
+             quote_n (0, dest), quote_n (1, source));
+      return false;
     }
 
   /* If --force (-f) has been specified without --backup, then before
@@ -212,125 +221,154 @@ do_link (const char *source, const char *dest, bool dest_is_dir)
      (with --backup, it just renames any existing destination file)
      But if the source and destination are the same, don't remove
      anything and fail right here.  */
-  if (remove_existing_files
-      && lstat_ok
-      /* Allow `ln -sf --backup k k' to succeed in creating the
-        self-referential symlink, but don't allow the hard-linking
-        equivalent: `ln -f k k' (with or without --backup) to get
-        beyond this point, because the error message you'd get is
-        misleading.  */
+  if ((remove_existing_files
+       /* Ensure that "ln --backup f f" fails here, with the
+          "... same file" diagnostic, below.  Otherwise, subsequent
+          code would give a misleading "file not found" diagnostic.
+          This case is different than the others handled here, since
+          the command in question doesn't use --force.  */
+       || (!symbolic_link && backup_type != no_backups))
+      && dest_lstat_ok
+      /* Allow 'ln -sf --backup k k' to succeed in creating the
+         self-referential symlink, but don't allow the hard-linking
+         equivalent: 'ln -f k k' (with or without --backup) to get
+         beyond this point, because the error message you'd get is
+         misleading.  */
       && (backup_type == no_backups || !symbolic_link)
       && (!symbolic_link || stat (source, &source_stats) == 0)
       && SAME_INODE (source_stats, dest_stats)
       /* The following detects whether removing DEST will also remove
-        SOURCE.  If the file has only one link then both are surely
-        the same link.  Otherwise check whether they point to the same
-        name in the same directory.  */
+         SOURCE.  If the file has only one link then both are surely
+         the same link.  Otherwise check whether they point to the same
+         name in the same directory.  */
       && (source_stats.st_nlink == 1 || same_name (source, dest)))
     {
       error (0, 0, _("%s and %s are the same file"),
-            quote_n (0, source), quote_n (1, dest));
+             quote_n (0, source), quote_n (1, dest));
       return false;
     }
 
-  if (lstat_ok)
+  if (dest_lstat_ok)
     {
       if (S_ISDIR (dest_stats.st_mode))
-       {
-         error (0, 0, _("%s: cannot overwrite directory"), quote (dest));
-         return false;
-       }
+        {
+          error (0, 0, _("%s: cannot overwrite directory"), quote (dest));
+          return false;
+        }
       if (interactive)
-       {
-         fprintf (stderr, _("%s: replace %s? "), program_name, quote (dest));
-         if (!yesno ())
-           return true;
-       }
+        {
+          fprintf (stderr, _("%s: replace %s? "), program_name, quote (dest));
+          if (!yesno ())
+            return true;
+          remove_existing_files = true;
+        }
 
       if (backup_type != no_backups)
-       {
-         char *tmp_backup = find_backup_file_name (dest, backup_type);
-         size_t buf_len = strlen (tmp_backup) + 1;
-         dest_backup = alloca (buf_len);
-         memcpy (dest_backup, tmp_backup, buf_len);
-         free (tmp_backup);
-         if (rename (dest, dest_backup))
-           {
-             if (errno != ENOENT)
-               {
-                 error (0, errno, _("cannot backup %s"), quote (dest));
-                 return false;
-               }
-             else
-               dest_backup = NULL;
-           }
-       }
+        {
+          dest_backup = find_backup_file_name (dest, backup_type);
+          if (rename (dest, dest_backup) != 0)
+            {
+              int rename_errno = errno;
+              free (dest_backup);
+              dest_backup = NULL;
+              if (rename_errno != ENOENT)
+                {
+                  error (0, rename_errno, _("cannot backup %s"), quote (dest));
+                  return false;
+                }
+            }
+        }
     }
 
-  if (verbose)
-    {
-      printf ((symbolic_link
-              ? _("create symbolic link %s to %s")
-              : _("create hard link %s to %s")),
-             quote_n (0, dest), quote_n (1, source));
-      if (dest_backup)
-       printf (_(" (backup: %s)"), quote (dest_backup));
-      putchar ('\n');
-    }
+  if (relative)
+    source = rel_source = convert_abs_rel (source, dest);
 
-  if ((*linkfunc) (source, dest) == 0)
-    return true;
+  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
      backing up destinations, unlink the destination and try again.
 
-     POSIX 1003.1-2004 requires that ln -f A B must unlink B even on
-     failure (e.g., when A does not exist).  This is counterintuitive,
-     and we submitted a defect report
-     <http://www.opengroup.org/sophocles/show_mail.tpl?source=L&listname=austin-review-l&id=1795>
-     (2004-06-24).  If the committee does not fix the standard we'll
-     have to change the behavior of ln -f, at least if POSIXLY_CORRECT
-     is set.  In the meantime ln -f A B will not unlink B unless the
-     attempt to link A to B failed because B already existed.
+     On the surface, POSIX describes an algorithm that states that
+     'ln -f A B' will call unlink() on B before ever attempting
+     link() on A.  But strictly following this has the counterintuitive
+     effect of losing the contents of B, if A does not exist.
+     Fortunately, POSIX 2008 clarified that an application is free
+     to fail early if it can prove that continuing onwards cannot
+     succeed, so we are justified in trying link() before blindly
+     removing B, thus sometimes calling link() a second time during
+     a successful 'ln -f A B'.
 
      Try to unlink DEST even if we may have backed it up successfully.
      In some unusual cases (when DEST and DEST_BACKUP are hard-links
      that refer to the same file), rename succeeds and DEST remains.
-     If we didn't remove DEST in that case, the subsequent LINKFUNC
+     If we didn't remove DEST in that case, the subsequent symlink or link
      call would fail.  */
 
-  if (errno == EEXIST && (remove_existing_files || dest_backup))
+  if (!ok && errno == EEXIST && (remove_existing_files || dest_backup))
     {
       if (unlink (dest) != 0)
-       {
-         error (0, errno, _("cannot remove %s"), quote (dest));
-         return false;
-       }
-
-      if (linkfunc (source, dest) == 0)
-       return true;
+        {
+          error (0, errno, _("cannot remove %s"), quote (dest));
+          free (dest_backup);
+          free (rel_source);
+          return false;
+        }
+
+      ok = ((symbolic_link ? symlink (source, dest)
+             : linkat (AT_FDCWD, source, AT_FDCWD, dest,
+                       logical ? AT_SYMLINK_FOLLOW : 0))
+            == 0);
     }
 
-  error (0, errno,
-        (symbolic_link
-         ? _("creating symbolic link %s to %s")
-         : _("creating hard link %s to %s")),
-        quote_n (0, dest), quote_n (1, source));
-
-  if (dest_backup)
+  if (ok)
+    {
+      /* Right after creating a hard link, do this: (note dest name and
+         source_stats, which are also the just-linked-destinations stats) */
+      record_file (dest_set, dest, &source_stats);
+
+      if (verbose)
+        {
+          if (dest_backup)
+            printf ("%s ~ ", quote (dest_backup));
+          printf ("%s %c> %s\n", quote_n (0, dest), (symbolic_link ? '-' : '='),
+                  quote_n (1, source));
+        }
+    }
+  else
     {
-      if (rename (dest_backup, dest))
-       error (0, errno, _("cannot un-backup %s"), quote (dest));
+      error (0, errno,
+             (symbolic_link
+              ? (errno != ENAMETOOLONG && *source
+                 ? _("failed to create symbolic link %s")
+                 : _("failed to create symbolic link %s -> %s"))
+              : (errno == EMLINK && !source_is_dir
+                 ? _("failed to create hard link to %.0s%s")
+                 : (errno == EDQUOT || errno == EEXIST || errno == ENOSPC
+                    || errno == EROFS)
+                 ? _("failed to create hard link %s")
+                 : _("failed to create hard link %s => %s"))),
+             quote_n (0, dest), quote_n (1, source));
+
+      if (dest_backup)
+        {
+          if (rename (dest_backup, dest) != 0)
+            error (0, errno, _("cannot un-backup %s"), quote (dest));
+        }
     }
-  return false;
+
+  free (dest_backup);
+  free (rel_source);
+  return ok;
 }
 
 void
 usage (int status)
 {
   if (status != EXIT_SUCCESS)
-    fprintf (stderr, _("Try `%s --help' for more information.\n"),
-            program_name);
+    emit_try_help ();
   else
     {
       printf (_("\
@@ -339,18 +377,20 @@ Usage: %s [OPTION]... [-T] TARGET LINK_NAME   (1st form)\n\
   or:  %s [OPTION]... TARGET... DIRECTORY     (3rd form)\n\
   or:  %s [OPTION]... -t DIRECTORY TARGET...  (4th form)\n\
 "),
-             program_name, program_name, program_name, program_name);
+              program_name, program_name, program_name, program_name);
       fputs (_("\
 In the 1st form, create a link to TARGET with the name LINK_NAME.\n\
 In the 2nd form, create a link to TARGET in the current directory.\n\
 In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.\n\
 Create hard links by default, symbolic links with --symbolic.\n\
-When creating hard links, each TARGET must exist.\n\
-\n\
-"), stdout);
-      fputs (_("\
-Mandatory arguments to long options are mandatory for short options too.\n\
+By default, each destination (name of new link) should not already exist.\n\
+When creating hard links, each TARGET must exist.  Symbolic links\n\
+can hold arbitrary text; if later resolved, a relative link is\n\
+interpreted in relation to its parent directory.\n\
 "), stdout);
+
+      emit_mandatory_arg_note ();
+
       fputs (_("\
       --backup[=CONTROL]      make a backup of each existing destination file\n\
   -b                          like --backup but does not accept an argument\n\
@@ -360,23 +400,26 @@ Mandatory arguments to long options are mandatory for short options too.\n\
   -f, --force                 remove existing destination files\n\
 "), stdout);
       fputs (_("\
-  -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\
+  -L, --logical               dereference TARGETs that are symbolic links\n\
+  -n, --no-dereference        treat LINK_NAME as a normal file if\n\
+                                it is a symbolic link to a directory\n\
+  -P, --physical              make hard links directly to symbolic links\n\
+  -r, --relative              create symbolic links relative to link location\n\
   -s, --symbolic              make symbolic links instead of hard links\n\
 "), stdout);
       fputs (_("\
   -S, --suffix=SUFFIX         override the usual backup suffix\n\
   -t, --target-directory=DIRECTORY  specify the DIRECTORY in which to create\n\
                                 the links\n\
-  -T, --no-target-directory   treat LINK_NAME as a normal file\n\
-  -v, --verbose               print name of each file before linking\n\
+  -T, --no-target-directory   treat LINK_NAME as a normal file always\n\
+  -v, --verbose               print name of each linked file\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
       fputs (_("\
 \n\
-The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\
+The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\
 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\
@@ -387,7 +430,12 @@ the VERSION_CONTROL environment variable.  Here are the values:\n\
   existing, nil   numbered if numbered backups exist, simple otherwise\n\
   simple, never   always make simple backups\n\
 "), stdout);
-      printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+      printf (_("\
+\n\
+Using -s ignores -L and -P.  Otherwise, the last option specified controls\n\
+behavior when a TARGET is a symbolic link, defaulting to %s.\n\
+"), LINK_FOLLOWS_SYMLINKS ? "-L" : "-P");
+      emit_ancillary_info ();
     }
   exit (status);
 }
@@ -400,18 +448,18 @@ main (int argc, char **argv)
   bool make_backups = false;
   char *backup_suffix_string;
   char *version_control_string = NULL;
-  char *target_directory = NULL;
+  char const *target_directory = NULL;
   bool no_target_directory = false;
   int n_files;
   char **file;
 
   initialize_main (&argc, &argv);
-  program_name = argv[0];
+  set_program_name (argv[0]);
   setlocale (LC_ALL, "");
   bindtextdomain (PACKAGE, LOCALEDIR);
   textdomain (PACKAGE);
 
-  atexit (close_stdout);
+  atexit (close_stdin);
 
   /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless
      we'll actually use backup_suffix_string.  */
@@ -420,76 +468,74 @@ main (int argc, char **argv)
   symbolic_link = remove_existing_files = interactive = verbose
     = hard_dir_link = false;
 
-  while ((c = getopt_long (argc, argv, "bdfinst:vFS:TV:", long_options, NULL))
-        != -1)
+  while ((c = getopt_long (argc, argv, "bdfinrst:vFLPS:T", long_options, NULL))
+         != -1)
     {
       switch (c)
-       {
-       case 'V':  /* FIXME: this is deprecated.  Remove it in 2001.  */
-         error (0, 0,
-                _("warning: --version-control (-V) is obsolete;  support for\
- it\nwill be removed in some future release.  Use --backup=%s instead."
-                  ), optarg);
-         /* Fall through.  */
-
-       case 'b':
-         make_backups = true;
-         if (optarg)
-           version_control_string = optarg;
-         break;
-       case 'd':
-       case 'F':
-         hard_dir_link = true;
-         break;
-       case 'f':
-         remove_existing_files = true;
-         interactive = false;
-         break;
-       case 'i':
-         remove_existing_files = false;
-         interactive = true;
-         break;
-       case 'n':
-         dereference_dest_dir_symlinks = false;
-         break;
-       case 's':
-#ifdef S_ISLNK
-         symbolic_link = true;
-#else
-         error (EXIT_FAILURE, 0,
-                _("symbolic links are not supported on this system"));
-#endif
-         break;
-       case 't':
-         if (target_directory)
-           error (EXIT_FAILURE, 0, _("multiple target directories specified"));
-         else
-           {
-             struct stat st;
-             if (stat (optarg, &st) != 0)
-               error (EXIT_FAILURE, errno, _("accessing %s"), quote (optarg));
-             if (! S_ISDIR (st.st_mode))
-               error (EXIT_FAILURE, 0, _("target %s is not a directory"),
-                      quote (optarg));
-           }
-         target_directory = optarg;
-         break;
-       case 'T':
-         no_target_directory = true;
-         break;
-       case 'v':
-         verbose = true;
-         break;
-       case 'S':
-         make_backups = true;
-         backup_suffix_string = optarg;
-         break;
-       case_GETOPT_HELP_CHAR;
-       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
-       default:
-         usage (EXIT_FAILURE);
-         break;
-       }
+        {
+        case 'b':
+          make_backups = true;
+          if (optarg)
+            version_control_string = optarg;
+          break;
+        case 'd':
+        case 'F':
+          hard_dir_link = true;
+          break;
+        case 'f':
+          remove_existing_files = true;
+          interactive = false;
+          break;
+        case 'i':
+          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 'r':
+          relative = true;
+          break;
+        case 's':
+          symbolic_link = true;
+          break;
+        case 't':
+          if (target_directory)
+            error (EXIT_FAILURE, 0, _("multiple target directories specified"));
+          else
+            {
+              struct stat st;
+              if (stat (optarg, &st) != 0)
+                error (EXIT_FAILURE, errno, _("failed to access %s"),
+                       quote (optarg));
+              if (! S_ISDIR (st.st_mode))
+                error (EXIT_FAILURE, 0, _("target %s is not a directory"),
+                       quote (optarg));
+            }
+          target_directory = optarg;
+          break;
+        case 'T':
+          no_target_directory = true;
+          break;
+        case 'v':
+          verbose = true;
+          break;
+        case 'S':
+          make_backups = true;
+          backup_suffix_string = optarg;
+          break;
+        case_GETOPT_HELP_CHAR;
+        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+        default:
+          usage (EXIT_FAILURE);
+          break;
+        }
     }
 
   n_files = argc - optind;
@@ -504,52 +550,85 @@ main (int argc, char **argv)
   if (no_target_directory)
     {
       if (target_directory)
-       error (EXIT_FAILURE, 0,
-              _("Cannot combine --target-directory "
-                "and --no-target-directory"));
+        error (EXIT_FAILURE, 0,
+               _("cannot combine --target-directory "
+                 "and --no-target-directory"));
       if (n_files != 2)
-       {
-         if (n_files < 2)
-           error (0, 0,
-                  _("missing destination file operand after %s"),
-                  quote (file[0]));
-         else
-           error (0, 0, _("extra operand %s"), quote (file[2]));
-         usage (EXIT_FAILURE);
-       }
+        {
+          if (n_files < 2)
+            error (0, 0,
+                   _("missing destination file operand after %s"),
+                   quote (file[0]));
+          else
+            error (0, 0, _("extra operand %s"), quote (file[2]));
+          usage (EXIT_FAILURE);
+        }
     }
   else if (!target_directory)
     {
       if (n_files < 2)
-       target_directory = ".";
+        target_directory = ".";
       else if (2 <= n_files && target_directory_operand (file[n_files - 1]))
-       target_directory = file[--n_files];
+        target_directory = file[--n_files];
       else if (2 < n_files)
-       error (EXIT_FAILURE, 0, _("target %s is not a directory"),
-              quote (file[n_files - 1]));
+        error (EXIT_FAILURE, 0, _("target %s is not a directory"),
+               quote (file[n_files - 1]));
     }
 
-  if (symbolic_link)
-    linkfunc = symlink;
-  else
-    linkfunc = link;
-
   if (backup_suffix_string)
     simple_backup_suffix = xstrdup (backup_suffix_string);
 
   backup_type = (make_backups
-                ? xget_version (_("backup type"), version_control_string)
-                : no_backups);
+                 ? xget_version (_("backup type"), version_control_string)
+                 : no_backups);
+
+  if (relative && !symbolic_link)
+    {
+        error (EXIT_FAILURE, 0,
+               _("cannot do --relative without --symbolic"));
+    }
+
 
   if (target_directory)
     {
       int i;
+
+      /* Create the data structure we'll use to record which hard links we
+         create.  Used to ensure that ln detects an obscure corner case that
+         might result in user data loss.  Create it only if needed.  */
+      if (2 <= n_files
+          && remove_existing_files
+          /* Don't bother trying to protect symlinks, since ln clobbering
+             a just-created symlink won't ever lead to real data loss.  */
+          && ! symbolic_link
+          /* No destination hard link can be clobbered when making
+             numbered backups.  */
+          && backup_type != numbered_backups)
+
+        {
+          dest_set = hash_initialize (DEST_INFO_INITIAL_CAPACITY,
+                                      NULL,
+                                      triple_hash,
+                                      triple_compare,
+                                      triple_free);
+          if (dest_set == NULL)
+            xalloc_die ();
+        }
+
       ok = true;
       for (i = 0; i < n_files; ++i)
-       ok &= do_link (file[i], target_directory, true);
+        {
+          char *dest_base;
+          char *dest = file_name_concat (target_directory,
+                                         last_component (file[i]),
+                                         &dest_base);
+          strip_trailing_slashes (dest_base);
+          ok &= do_link (file[i], dest);
+          free (dest);
+        }
     }
   else
-    ok = do_link (file[0], file[1], false);
+    ok = do_link (file[0], file[1]);
 
   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
 }