From 41fd5a5f2113341ee99cbecbc950b525b7cb0ae8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Stelmach?= Date: Wed, 27 Mar 2024 11:23:39 +0100 Subject: [PATCH] tizen: Add support for relative symlinks to ln(1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This is GPL-2 licensed implementation of --relative option for ln(1) available upstream since version 8.16. Change-Id: I119ac263961e09ec755793fa0803447e8105adf8 Signed-off-by: Łukasz Stelmach --- src/ln.c | 100 +++++++++++++++++++++++++++++++++++++++++-- tests/ln/Makefile.am | 2 +- tests/ln/Makefile.in | 2 +- tests/ln/relative | 57 ++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 6 deletions(-) create mode 100755 tests/ln/relative diff --git a/src/ln.c b/src/ln.c index fae3708..97c3268 100644 --- a/src/ln.c +++ b/src/ln.c @@ -29,6 +29,7 @@ #include "filenamecat.h" #include "quote.h" #include "yesno.h" +#include "canonicalize.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "ln" @@ -62,6 +63,9 @@ static enum backup_type backup_type; /* If true, make symbolic links; otherwise, make hard links. */ static bool symbolic_link; +/* If true, make symbolic link relative to link location */ +static bool relative_link; + /* If true, ask the user before removing existing files. */ static bool interactive; @@ -91,6 +95,7 @@ static struct option const long_options[] = {"no-target-directory", no_argument, NULL, 'T'}, {"force", no_argument, NULL, 'f'}, {"interactive", no_argument, NULL, 'i'}, + {"relative", no_argument, NULL, 'r'}, {"suffix", required_argument, NULL, 'S'}, {"target-directory", required_argument, NULL, 't'}, {"symbolic", no_argument, NULL, 's'}, @@ -130,6 +135,7 @@ target_directory_operand (char const *file) static bool do_link (const char *source, const char *dest) { + char relative_source[PATH_MAX]; struct stat source_stats; struct stat dest_stats; char *dest_backup = NULL; @@ -243,8 +249,90 @@ do_link (const char *source, const char *dest) } } - ok = ((symbolic_link ? symlink (source, dest) : link (source, dest)) - == 0); + if (!symbolic_link) + { + ok = (link (source, dest) == 0); + } + else + { + if (relative_link) + { + char *canon_source; + char *canon_dest; + char *s, *sp = NULL; + char *d, *dp = NULL; + char *dest_base; + size_t relative_len, relative_start; + + s = canon_source = canonicalize_filename_mode(source, CAN_ALL_BUT_LAST); + if (!s) + { + error(0, errno, "cannot canonicalize souce file name: %s", quote(source)); + return false; + } + + d = canon_dest = canonicalize_filename_mode(dest, CAN_ALL_BUT_LAST); + if (!d) + { + error(0, errno, "cannot canonicalize destination file name: %s", quote(source)); + free(canon_source); + return false; + } + if (*s != '/' || *d != '/') + { + /* Canonical names should both start with '/'. */ + error(0, EINVAL, "canonical names don't match"); + free(canon_source); + free(canon_dest); + return false; + } + + dest_base = base_name(canon_dest); + + s++; d++; + sp = strchr(s, '/'); + dp = strchr(d, '/'); + while (strncmp(s, d, sp - s) == 0) + { + d = dp + 1; + s = sp + 1; + sp = strchr(s, '/'); + dp = strchr(d, '/'); + } + + relative_start = s - canon_source; + memset(relative_source, 0, PATH_MAX); + relative_len = 0; + + while ((relative_len < (PATH_MAX - 1 - 3)) && (strncmp(d, dest_base, dp - d) != 0)) + { + d = dp + 1; + dp = strchr(d , '/'); + + strncat(relative_source, "../", PATH_MAX - 1 - relative_len); + relative_len += 3; + } + + if (relative_len >= (PATH_MAX - 1 - 3)) + { + error(0, ENAMETOOLONG, "destination too deep"); + free(dest_base); + free(canon_source); + free(canon_dest); + return false; + } + strncat(relative_source, canon_source + relative_start, PATH_MAX - 1 - relative_len); + + source = relative_source; + + free(dest_base); + free(canon_source); + free(canon_dest); + } + + ok = (symlink (source, dest) == 0); + } + /* If the attempt to create a link failed and we are removing or backing up destinations, unlink the destination and try again. @@ -351,6 +439,7 @@ Mandatory arguments to long options are mandatory for short options too.\n\ -n, --no-dereference treat destination that is a symlink to a\n\ directory as if it were a normal file\n\ -i, --interactive prompt whether to remove destinations\n\ + -r, --relative make symbolic links relative to link location\n\ -s, --symbolic make symbolic links instead of hard links\n\ "), stdout); fputs (_("\ @@ -406,9 +495,9 @@ main (int argc, char **argv) backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); symbolic_link = remove_existing_files = interactive = verbose - = hard_dir_link = false; + = hard_dir_link = relative_link = false; - while ((c = getopt_long (argc, argv, "bdfinst:vFS:T", long_options, NULL)) + while ((c = getopt_long (argc, argv, "bdfinrst:vFS:T", long_options, NULL)) != -1) { switch (c) @@ -433,6 +522,9 @@ main (int argc, char **argv) case 'n': dereference_dest_dir_symlinks = false; break; + case 'r': + relative_link = true; + break; case 's': symbolic_link = true; break; diff --git a/tests/ln/Makefile.am b/tests/ln/Makefile.am index 7ae15a7..a15417e 100644 --- a/tests/ln/Makefile.am +++ b/tests/ln/Makefile.am @@ -1,5 +1,5 @@ ## Process this file with automake to produce Makefile.in -*-Makefile-*-. -TESTS = hard-backup target-1 sf-1 misc backup-1 +TESTS = hard-backup target-1 sf-1 misc backup-1 relative EXTRA_DIST = $(TESTS) TESTS_ENVIRONMENT = \ CU_TEST_NAME=`basename $(abs_srcdir)`,$$tst \ diff --git a/tests/ln/Makefile.in b/tests/ln/Makefile.in index 9ad7daf..18c5c13 100644 --- a/tests/ln/Makefile.in +++ b/tests/ln/Makefile.in @@ -471,7 +471,7 @@ sysconfdir = @sysconfdir@ target_alias = @target_alias@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ -TESTS = hard-backup target-1 sf-1 misc backup-1 +TESTS = hard-backup target-1 sf-1 misc backup-1 relative EXTRA_DIST = $(TESTS) TESTS_ENVIRONMENT = \ CU_TEST_NAME=`basename $(abs_srcdir)`,$$tst \ diff --git a/tests/ln/relative b/tests/ln/relative new file mode 100755 index 0000000..4231531 --- /dev/null +++ b/tests/ln/relative @@ -0,0 +1,57 @@ +#!/bin/sh +# Test "ln --relative". + +# Copyright (C) 2024 Samsung Electronics + +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +if test "$VERBOSE" = yes; then + set -x + ln --version +fi + +pwd=`pwd` +t0=`echo "$0"|sed 's,.*/,,'`.tmp; tmp=$t0/$$ +trap 'status=$?; cd "$pwd" && chmod -R u+rwx $t0 && rm -rf $t0 && exit $status' 0 +trap '(exit $?); exit $?' 1 2 13 15 + +framework_failure=0 +mkdir -p $tmp/a || framework_failure=1 +mkdir -p $tmp/z || framework_failure=1 +touch $tmp/b || framework_failure=1 +touch $tmp/a/b || framework_failure=1 +cd $tmp || framework_failure=1 + +if test $framework_failure = 1; then + echo "$0: failure in testing framework" 1>&2 + (exit 1); exit 1 +fi + +fail=0 + +ln -sr ./a/b ./y +test $(readlink y) = 'a/b' || fail=1 + +ln -sr ./a/b ./z/y +test $(readlink z/y) = '../a/b' || fail=1 + +ln -sr ./b ./z/x +test $(readlink z/x) = '../b' || fail=1 + +ln -sr ./b ./x +test $(readlink x) = 'b' || fail=1 + +(exit $fail); exit $fail -- 2.34.1