ln: add the --relative option
authorHarald Hoyer <harald@redhat.com>
Thu, 22 Mar 2012 20:51:20 +0000 (20:51 +0000)
committerPádraig Brady <P@draigBrady.com>
Thu, 22 Mar 2012 20:51:20 +0000 (20:51 +0000)
With the "--relative --symbolic" options, ln computes the relative
symbolic link for the user.

So, ln works just as cp, but creates relative symbolic links instead
of copying the file.

I miss this feature since the beginning of using ln.

$ tree ./
/
`-- usr
    |-- bin
    `-- lib
        `-- foo
            `-- foo

4 directories, 1 file

$ ln -s -v --relative usr/lib/foo/foo usr/bin/foo
‘usr/bin/foo’ -> ‘../lib/foo/foo’

$ tree ./
/
`-- usr
    |-- bin
    |   `-- foo -> ../lib/foo/foo
    `-- lib
        `-- foo
            `-- foo

4 directories, 2 files

$ ln -s -v --relative usr/bin/foo usr/lib/foo/link-to-foo
‘usr/lib/foo/link-to-foo’ -> ‘foo’

$ tree ./
/
`-- usr
    |-- bin
    |   `-- foo -> ../lib/foo/foo
    `-- lib
        `-- foo
            |-- link-to-foo -> foo
            `-- foo

4 directories, 3 files

* src/Makefile.am: Reference the relpath module.
* src/ln.c (usage): Mention the new option.
(do_link): Call the relative conversion if specified.
(convert_abs_rel): Perform the relative conversion
using the relpath module.
* tests/ln/relative: Add a new test.
* tests/Makefile.am: Reference the new test.
* doc/coreutils.texi: Document the new feature.
* NEWS: Mention the new feature.

NEWS
doc/coreutils.texi
src/Makefile.am
src/ln.c
tests/Makefile.am
tests/ln/relative [new file with mode: 0755]

diff --git a/NEWS b/NEWS
index 5b53eb8..c9ed185 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   dd now accepts the conv=sparse flag to attempt to create sparse
   output, by seeking rather than writing to the output file.
 
+  ln now accepts the --relative option, to generate a relative
+  symbolic link to a target, irrespective of how the target is specified.
+
   split now accepts an optional "from" argument to --numeric-suffixes,
   which changes the start number from the default of 0.
 
index 10be715..b7208aa 100644 (file)
@@ -9389,6 +9389,22 @@ 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 -r
+@itemx --relative
+@opindex -r
+@opindex --relative
+Make symbolic links relative to the link location.
+
+Example:
+
+@smallexample
+ln -srv /a/file /tmp
+'/tmp/file' -> '../a/file'
+@end smallexample
+
+@xref{realpath invocation}, which gives greater control
+over relative path generation.
+
 @item -s
 @itemx --symbolic
 @opindex -s
index 85f12d6..06ab615 100644 (file)
@@ -474,6 +474,7 @@ vdir_SOURCES = ls.c ls-vdir.c
 id_SOURCES = id.c group-list.c
 groups_SOURCES = groups.c group-list.c
 ls_SOURCES = ls.c ls-ls.c
+ln_SOURCES = ln.c relpath.c relpath.h
 chown_SOURCES = chown.c chown-core.c
 chgrp_SOURCES = chgrp.c chown-core.c
 kill_SOURCES = kill.c operand2sig.c
index d984fd7..e7ab348 100644 (file)
--- a/src/ln.c
+++ b/src/ln.c
 #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).  */
 #define PROGRAM_NAME "ln"
@@ -45,6 +47,9 @@ static enum backup_type backup_type;
 /* 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;
 
@@ -90,6 +95,7 @@ static struct option const long_options[] =
   {"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'},
   {GETOPT_HELP_OPTION_DECL},
@@ -120,6 +126,33 @@ target_directory_operand (char const *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.
    Return true if successful.  */
@@ -130,6 +163,7 @@ do_link (const char *source, const char *dest)
   struct stat source_stats;
   struct stat dest_stats;
   char *dest_backup = NULL;
+  char *rel_source = NULL;
   bool dest_lstat_ok = false;
   bool source_is_dir = false;
   bool ok;
@@ -246,6 +280,9 @@ do_link (const char *source, const char *dest)
         }
     }
 
+  if (relative)
+    source = rel_source = convert_abs_rel (source, dest);
+
   ok = ((symbolic_link ? symlink (source, dest)
          : linkat (AT_FDCWD, source, AT_FDCWD, dest,
                    logical ? AT_SYMLINK_FOLLOW : 0))
@@ -276,6 +313,7 @@ do_link (const char *source, const char *dest)
         {
           error (0, errno, _("cannot remove %s"), quote (dest));
           free (dest_backup);
+          free (rel_source);
           return false;
         }
 
@@ -322,6 +360,7 @@ do_link (const char *source, const char *dest)
     }
 
   free (dest_backup);
+  free (rel_source);
   return ok;
 }
 
@@ -367,6 +406,7 @@ Mandatory arguments to long options are mandatory for short options too.\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 (_("\
@@ -429,7 +469,7 @@ main (int argc, char **argv)
   symbolic_link = remove_existing_files = interactive = verbose
     = hard_dir_link = false;
 
-  while ((c = getopt_long (argc, argv, "bdfinst:vFLPS:T", long_options, NULL))
+  while ((c = getopt_long (argc, argv, "bdfinrst:vFLPS:T", long_options, NULL))
          != -1)
     {
       switch (c)
@@ -460,6 +500,9 @@ main (int argc, char **argv)
         case 'P':
           logical = false;
           break;
+        case 'r':
+          relative = true;
+          break;
         case 's':
           symbolic_link = true;
           break;
@@ -539,6 +582,13 @@ main (int argc, char **argv)
                  ? 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;
index c72b175..011051a 100644 (file)
@@ -420,6 +420,7 @@ TESTS =                                             \
   ln/hard-backup                               \
   ln/hard-to-sym                               \
   ln/misc                                      \
+  ln/relative                                  \
   ln/sf-1                                      \
   ln/slash-decorated-nonexistent-dest          \
   ln/target-1                                  \
diff --git a/tests/ln/relative b/tests/ln/relative
new file mode 100755 (executable)
index 0000000..cfc3469
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+# Test "ln --relative".
+
+# Copyright (C) 2012 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/>.
+
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
+print_ver_ ln
+
+mkdir -p usr/bin || framework_failure_
+mkdir -p usr/lib/foo || framework_failure_
+touch usr/lib/foo/foo || framework_failure_
+
+ln -sr usr/lib/foo/foo usr/bin/foo
+test $(readlink usr/bin/foo) = '../lib/foo/foo' || fail=1
+
+ln -sr usr/bin/foo usr/lib/foo/link-to-foo
+test $(readlink usr/lib/foo/link-to-foo) = 'foo' || fail=1
+
+Exit $fail