On 02/24/2012 12:12 AM, Pádraig Brady wrote:
> On 02/23/2012 09:28 PM, Eric Blake wrote:
>> On 02/23/2012 12:50 PM, har...@redhat.com wrote:
>>> From: Harald Hoyer <har...@redhat.com>
>>>
>>> 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.
>>
>> In general, I like the idea.  I will point out that we now have realpath
>> that also can compute relative locations to an arbitrary starting point,
>> so we probably ought to share the code between the two programs rather
>> than rewriting it.
> 
> I like the functionality too.
> As Eric points out, the new `realpath` util has the
> relative path generation logic in it, which should be reused.
> To demonstrate the equivalence you could implement this
> functionality modulo option handling using `realpath` like:
> 
> ln--relative() {
>   target="$1"; link_name="$2"
>   rtarget="$(realpath -m "$target" --relative-to "$(dirname "$link_name")")"
>   ln -s -v "$rtarget" "$link_name"
> }
> 
> Now given the simplicity of the above, there is the argument
> that new options within ln are not needed. We've discussed
> on and off the idea of a contrib/ folder in coreutils to
> provide generally useful but higher level commands like this.
> Worth considering at least.

Attached is a quick PoC refactoring to use existing code.

cheers,
Pádraig.
From 5196f6b86f29e28c981393f4640268c9a45eb3e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <p...@draigbrady.com>
Date: Wed, 21 Mar 2012 02:49:37 +0000
Subject: [PATCH] ln: refactor --relative to use existing code

Also add some vestigial tests and docs.
TODO: Improve the buffer handling in relpath.c.
---
 doc/coreutils.texi |    9 ++++
 src/Makefile.am    |    2 +
 src/ln.c           |   77 ++++++---------------------------
 src/realpath.c     |   96 ++---------------------------------------
 src/relpath.c      |  122 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/relpath.h      |   27 +++++++++++
 tests/Makefile.am  |    1 +
 tests/ln/relative  |   32 ++++++++++++++
 8 files changed, 210 insertions(+), 156 deletions(-)
 create mode 100644 src/relpath.c
 create mode 100644 src/relpath.h
 create mode 100755 tests/ln/relative

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 835c245..a5614d4 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -9389,6 +9389,15 @@ 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.
+@xref{realpath invocation}, which gives greater control
+over relative path generation.
+
+
 @item -s
 @itemx --symbolic
 @opindex -s
diff --git a/src/Makefile.am b/src/Makefile.am
index b124064..06ab615 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -474,9 +474,11 @@ 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
+realpath_SOURCES = realpath.c relpath.c relpath.h
 timeout_SOURCES = timeout.c operand2sig.c
 
 mv_SOURCES = mv.c remove.c $(copy_sources)
diff --git a/src/ln.c b/src/ln.c
index 77965af..aa644da 100644
--- a/src/ln.c
+++ b/src/ln.c
@@ -29,6 +29,7 @@
 #include "hash.h"
 #include "hash-triple.h"
 #include "quote.h"
+#include "relpath.h"
 #include "same.h"
 #include "yesno.h"
 #include "canonicalize.h"
@@ -131,73 +132,23 @@ convert_abs_rel (const char *from, const char *target)
   /* we use the 4*MAXPATHLEN, which should not overrun */
   char relative_from[MAXPATHLEN*4];
   char *realtarget=NULL, *realfrom=NULL;
-  int level=0, fromlevel=0, targetlevel=0;
-  int l, i, rl;
 
   realtarget = xstrdup(canonicalize_filename_mode (target, CAN_MISSING));
   realfrom = xstrdup(canonicalize_filename_mode (from, CAN_MISSING));
 
-  if ((realtarget == NULL) || (realfrom == NULL))
-    {
-      free(realtarget);
-      free(realfrom);
-      return from;
-    }
+  /* Get relative dirname.  */
+  realtarget[dir_len (realtarget)] = '\0';
 
-  /* now calculate the relative path from <from> to <target> and
-     store it in <relative_from>
-  */
-  relative_from[0] = 0;
-  rl = 0;
-
-  /* count the pathname elements of realtarget */
-  for(targetlevel=0, i = 0; realtarget[i]; i++)
-    if (realtarget[i] == '/')
-      targetlevel++;
-
-  /* count the pathname elements of realfrom */
-  for(fromlevel=0, i = 0; realfrom[i]; i++)
-    if (realfrom[i] == '/')
-      fromlevel++;
-
-  /* count the pathname elements, which are common for both paths */
-  for(level=0, i = 0;
-      realtarget[i] && (realtarget[i] == realfrom[i]);
-      i++)
-    if (realtarget[i] == '/')
-      level++;
-
-  /* add "../" to the relative_from path, until the common pathname is
-     reached */
-  for(i = level; i < targetlevel; i++)
-    {
-      if (i != level)
-       relative_from[rl++] = '/';
-      relative_from[rl++] = '.';
-      relative_from[rl++] = '.';
-    }
+  if (!relpath (realfrom, realtarget, relative_from, sizeof relative_from))
+    *relative_from = '\0';
 
-  /* set l to the next uncommon pathname element in realfrom */
-  for(l = 1, i = 1; i < level; i++)
-    for(l++; realfrom[l] && realfrom[l] != '/'; l++);
-  /* skip next '/' */
-  l++;
+  free (realtarget);
+  free (realfrom);
 
-  /* append the uncommon rest of realfrom to the relative_from path */
-  for(i = level; i <= fromlevel; i++)
-    {
-      if(rl)
-       relative_from[rl++] = '/';
-      while(realfrom[l] && realfrom[l] != '/')
-       relative_from[rl++] = realfrom[l++];
-      l++;
-    }
-  relative_from[rl] = 0;
-
-  free(realtarget);
-  free(realfrom);
-
-  return xstrdup(relative_from);
+  if (*relative_from)
+    return xstrdup(relative_from);
+  else
+    return from;
 }
 
 /* Make a link DEST to the (usually) existing file SOURCE.
@@ -327,9 +278,7 @@ do_link (const char *source, const char *dest)
     }
 
   if (relative)
-    {
-      source = convert_abs_rel(source, dest);
-    }
+    source = convert_abs_rel(source, dest);
 
 
   ok = ((symbolic_link ? symlink (source, dest)
@@ -453,7 +402,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 softlink relative to LINK_NAME\n\
+  -r, --relative              create symbolic links relative to link 
location\n\
   -s, --symbolic              make symbolic links instead of hard links\n\
 "), stdout);
       fputs (_("\
diff --git a/src/realpath.c b/src/realpath.c
index 206f800..62576ff 100644
--- a/src/realpath.c
+++ b/src/realpath.c
@@ -21,6 +21,7 @@
 #include <stdio.h>
 #include <sys/types.h>
 
+#include "relpath.h"
 #include "system.h"
 #include "canonicalize.h"
 #include "error.h"
@@ -136,97 +137,6 @@ path_prefix (const char *prefix, const char *path)
   return (!*prefix && (*path == '/' || !*path));
 }
 
-/* Return the length of the longest common prefix
-   of canonical PATH1 and PATH2, ensuring only full path components
-   are matched.  Return 0 on no match.  */
-static int _GL_ATTRIBUTE_PURE
-path_common_prefix (const char *path1, const char *path2)
-{
-  int i = 0;
-  int ret = 0;
-
-  /* We already know path1[0] and path2[0] are '/'.  Special case
-     '//', which is only present in a canonical name on platforms
-     where it is distinct.  */
-  if ((path1[1] == '/') != (path2[1] == '/'))
-    return 0;
-
-  while (*path1 && *path2)
-    {
-      if (*path1 != *path2)
-        break;
-      if (*path1 == '/')
-        ret = i + 1;
-      path1++;
-      path2++;
-      i++;
-    }
-
-  if (!*path1 && !*path2)
-    ret = i;
-  if (!*path1 && *path2 == '/')
-    ret = i;
-  if (!*path2 && *path1 == '/')
-    ret = i;
-
-  return ret;
-}
-
-/* Output the relative representation if requested.  */
-static bool
-relpath (const char *can_fname)
-{
-  if (can_relative_to)
-    {
-      /* Enforce --relative-base.  */
-      if (can_relative_base && !path_prefix (can_relative_base, can_fname))
-        return false;
-
-      /* Skip the prefix common to --relative-to and path.  */
-      int common_index = path_common_prefix (can_relative_to, can_fname);
-      if (!common_index)
-        return false;
-
-      const char *relto_suffix = can_relative_to + common_index;
-      const char *fname_suffix = can_fname + common_index;
-
-      /* skip over extraneous '/'.  */
-      if (*relto_suffix == '/')
-        relto_suffix++;
-      if (*fname_suffix == '/')
-        fname_suffix++;
-
-      /* Replace remaining components of --relative-to with '..', to get
-         to a common directory.  Then output the remainder of fname.  */
-      if (*relto_suffix)
-        {
-          fputs ("..", stdout);
-          for (; *relto_suffix; ++relto_suffix)
-            {
-              if (*relto_suffix == '/')
-                fputs ("/..", stdout);
-            }
-
-          if (*fname_suffix)
-            {
-              putchar ('/');
-              fputs (fname_suffix, stdout);
-            }
-        }
-      else
-        {
-          if (*fname_suffix)
-            fputs (fname_suffix, stdout);
-          else
-            putchar ('.');
-        }
-
-      return true;
-    }
-
-  return false;
-}
-
 static bool
 isdir (const char *path)
 {
@@ -247,7 +157,9 @@ process_path (const char *fname, int can_mode)
       return false;
     }
 
-  if (!relpath (can_fname))
+  if (!can_relative_to
+      || (can_relative_base && !path_prefix (can_relative_base, can_fname))
+      || (can_relative_to && !relpath (can_fname, can_relative_to, NULL, 0)))
     fputs (can_fname, stdout);
 
   putchar (use_nuls ? '\0' : '\n');
diff --git a/src/relpath.c b/src/relpath.c
new file mode 100644
index 0000000..eb8389f
--- /dev/null
+++ b/src/relpath.c
@@ -0,0 +1,122 @@
+/* relpath - print the relative path
+   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/>.  */
+
+/* Written by Pádraig Brady.  */
+
+#include <config.h>
+#include <assert.h>
+
+#include "system.h"
+#include "relpath.h"
+
+
+/* Return the length of the longest common prefix
+   of canonical PATH1 and PATH2, ensuring only full path components
+   are matched.  Return 0 on no match.  */
+static int _GL_ATTRIBUTE_PURE
+path_common_prefix (const char *path1, const char *path2)
+{
+  int i = 0;
+  int ret = 0;
+
+  /* We already know path1[0] and path2[0] are '/'.  Special case
+     '//', which is only present in a canonical name on platforms
+     where it is distinct.  */
+  if ((path1[1] == '/') != (path2[1] == '/'))
+    return 0;
+
+  while (*path1 && *path2)
+    {
+      if (*path1 != *path2)
+        break;
+      if (*path1 == '/')
+        ret = i + 1;
+      path1++;
+      path2++;
+      i++;
+    }
+
+  if (!*path1 && !*path2)
+    ret = i;
+  if (!*path1 && *path2 == '/')
+    ret = i;
+  if (!*path2 && *path1 == '/')
+    ret = i;
+
+  return ret;
+}
+
+static char *
+buffer_or_output (const char* str, char *buf)
+{
+  if (buf)
+    buf = stpcpy (buf, str);
+  else
+    fputs (str, stdout);
+
+  return buf;
+}
+
+/* Output the relative representation if possible.
+   If BUF is non NULL, output is to that buffer rather than stdout.  */
+bool
+relpath (const char *can_fname, const char *can_reldir, char *buf, size_t len)
+{
+  char *cbuf = buf;
+
+  /* Skip the prefix common to --relative-to and path.  */
+  int common_index = path_common_prefix (can_reldir, can_fname);
+  if (!common_index)
+    return false;
+
+  const char *relto_suffix = can_reldir + common_index;
+  const char *fname_suffix = can_fname + common_index;
+
+  /* skip over extraneous '/'.  */
+  if (*relto_suffix == '/')
+    relto_suffix++;
+  if (*fname_suffix == '/')
+    fname_suffix++;
+
+  /* Replace remaining components of --relative-to with '..', to get
+     to a common directory.  Then output the remainder of fname.  */
+  if (*relto_suffix)
+    {
+      cbuf = buffer_or_output ("..", cbuf);
+      for (; *relto_suffix; ++relto_suffix)
+        {
+          if (*relto_suffix == '/')
+            cbuf = buffer_or_output ("/..", cbuf);
+        }
+
+      if (*fname_suffix)
+        {
+          cbuf = buffer_or_output ("/", cbuf);
+          cbuf = buffer_or_output (fname_suffix, cbuf);
+        }
+    }
+  else
+    {
+      if (*fname_suffix)
+        cbuf = buffer_or_output (fname_suffix, cbuf);
+      else
+        cbuf = buffer_or_output (".", cbuf);
+    }
+
+  assert (cbuf <= (buf + len)); /* TODO: enforce this better.  */
+
+  return true;
+}
diff --git a/src/relpath.h b/src/relpath.h
new file mode 100644
index 0000000..6582490
--- /dev/null
+++ b/src/relpath.h
@@ -0,0 +1,27 @@
+/* relpath - print the relative path
+   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/>.  */
+
+/* Written by Pádraig Brady.  */
+
+#ifndef _RELPATH_H
+# define _RELPATH_H
+
+# include <stdbool.h>
+
+bool
+relpath (const char *can_fname, const char *can_reldir, char *buf, size_t len);
+
+#endif
diff --git a/tests/Makefile.am b/tests/Makefile.am
index c72b175..011051a 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -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
index 0000000..cfc3469
--- /dev/null
+++ b/tests/ln/relative
@@ -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
-- 
1.7.6.4

Reply via email to