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