tizen: Add support for relative symlinks to ln(1) 70/308570/1 accepted/tizen/base/20240407.010004 accepted/tizen/base/asan/20240422.015409 accepted/tizen/base/toolchain/20240424.234804 accepted/tizen/base/x/20240410.211419 accepted/tizen/base/x/asan/20240412.063431
authorŁukasz Stelmach <l.stelmach@samsung.com>
Wed, 27 Mar 2024 10:23:39 +0000 (11:23 +0100)
committerŁukasz Stelmach <l.stelmach@samsung.com>
Wed, 27 Mar 2024 12:25:30 +0000 (13:25 +0100)
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 <l.stelmach@samsung.com>
src/ln.c
tests/ln/Makefile.am
tests/ln/Makefile.in
tests/ln/relative [new file with mode: 0755]

index fae370807b340f21d025aac7493a2d36912d1e3b..97c32683e88d219bebcfa3530d513c2e3d4d98e4 100644 (file)
--- 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;
index 7ae15a75b16b7e02afb26086ed5ef477ffdb8bab..a15417eb527e6e6bd3d07881bb1a8bc8e1d014cb 100644 (file)
@@ -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 \
index 9ad7dafbaabc270c67c7bae91335634a9bb96281..18c5c135b2ec9dbd9331d57311d3aa8103be5bec 100644 (file)
@@ -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 (executable)
index 0000000..4231531
--- /dev/null
@@ -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