From 15b8318e93a947d1422e9a927f699eda1085cf0d Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 22 Mar 2012 20:51:20 +0000 Subject: ln: add the --relative option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 | 3 +++ doc/coreutils.texi | 16 ++++++++++++++++ src/Makefile.am | 1 + src/ln.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- tests/Makefile.am | 1 + tests/ln/relative | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 1 deletion(-) create mode 100755 tests/ln/relative diff --git a/NEWS b/NEWS index 5b53eb8a0..c9ed18521 100644 --- 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. diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 10be7156b..b7208aa5f 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 85f12d65a..06ab61527 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/ln.c b/src/ln.c index d984fd7b4..e7ab3487e 100644 --- a/src/ln.c +++ b/src/ln.c @@ -29,8 +29,10 @@ #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; diff --git a/tests/Makefile.am b/tests/Makefile.am index c72b17575..011051aa5 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 000000000..cfc34691e --- /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 -- cgit v1.2.1