From 5196f6b86f29e28c981393f4640268c9a45eb3e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?=
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 to and
- store it in
- */
- 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
#include
+#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 . */
+
+/* Written by Pádraig Brady. */
+
+#include
+#include
+
+#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 . */
+
+/* Written by Pádraig Brady. */
+
+#ifndef _RELPATH_H
+# define _RELPATH_H
+
+# include
+
+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 .
+
+. "${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