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. 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/ln.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 98 insertions(+), 1 deletions(-) diff --git a/src/ln.c b/src/ln.c index d984fd7..77965af 100644 --- a/src/ln.c +++ b/src/ln.c @@ -31,6 +31,7 @@ #include "quote.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 +46,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 +94,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 +125,81 @@ target_directory_operand (char const *file) return is_a_dir; } +static const char * +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; + } + + /* 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++] = '.'; + } + + /* 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++; + + /* 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); +} + /* Make a link DEST to the (usually) existing file SOURCE. Symbolic links to nonexistent files are allowed. Return true if successful. */ @@ -246,6 +326,12 @@ do_link (const char *source, const char *dest) } } + if (relative) + { + source = convert_abs_rel(source, dest); + } + + ok = ((symbolic_link ? symlink (source, dest) : linkat (AT_FDCWD, source, AT_FDCWD, dest, logical ? AT_SYMLINK_FOLLOW : 0)) @@ -367,6 +453,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\ -s, --symbolic make symbolic links instead of hard links\n\ "), stdout); fputs (_("\ @@ -429,7 +516,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 +547,9 @@ main (int argc, char **argv) case 'P': logical = false; break; + case 'r': + relative = true; + break; case 's': symbolic_link = true; break; @@ -539,6 +629,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; -- 1.7.9.1