aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/lib.c29
-rw-r--r--lib/lib.h1
-rwxr-xr-xtests/ln.test10
-rw-r--r--toys/posix/ln.c27
4 files changed, 58 insertions, 9 deletions
diff --git a/lib/lib.c b/lib/lib.c
index 03c1db69..3bfee2ef 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -216,6 +216,7 @@ int mkpath(char *dir)
}
// Split a path into linked list of components, tracking head and tail of list.
+// Assigns head of list to *list, returns address of ->next entry to extend list
// Filters out // entries with no contents.
struct string_list **splitpath(char *path, struct string_list **list)
{
@@ -1028,6 +1029,34 @@ char *fileunderdir(char *file, char *dir)
return rc ? s2 : 0;
}
+// return (malloced) relative path to get from "from" to "to"
+char *relative_path(char *from, char *to)
+{
+ char *s, *ret = 0;
+ int i, j, k;
+
+ if (!(from = xabspath(from, -1))) return 0;
+ if (!(to = xabspath(to, -1))) goto error;
+
+ // skip common directories from root
+ for (i = j = 0; from[i] && from[i] == to[i]; i++) if (to[i] == '/') j = i+1;
+
+ // count remaining destination directories
+ for (i = j, k = 0; from[i]; i++) if (from[i] == '/') k++;
+
+ if (!k) ret = xstrdup(to+j);
+ else {
+ s = ret = xmprintf("%*c%s", 3*k, ' ', to+j);
+ while (k--) memcpy(s+3*k, "../", 3);
+ }
+
+error:
+ free(from);
+ free(to);
+
+ return ret;
+}
+
// Execute a callback for each PID that matches a process name from a list.
void names_to_pid(char **names, int (*callback)(pid_t pid, char *name),
int scripts)
diff --git a/lib/lib.h b/lib/lib.h
index b48d320e..f3eacb9d 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -402,6 +402,7 @@ mode_t string_to_mode(char *mode_str, mode_t base);
void mode_to_string(mode_t mode, char *buf);
char *getbasename(char *name);
char *fileunderdir(char *file, char *dir);
+char *relative_path(char *from, char *to);
void names_to_pid(char **names, int (*callback)(pid_t pid, char *name),
int scripts);
diff --git a/tests/ln.test b/tests/ln.test
index c326c2e1..6a5787f7 100755
--- a/tests/ln.test
+++ b/tests/ln.test
@@ -55,6 +55,7 @@ rm -rf file dir slink
testing "-t" "ln -st . one/two three && readlink two three" "one/two\nthree\n" \
"" ""
+rm -f two three
touch file1 file2 && mkdir dir
testing "create_multiple_hardlinks" "ln file* dir/ &&
@@ -78,3 +79,12 @@ testing "create_hardlink_and_remove_sourcefile" "ln file hlink &&
[ file -ef hlink ] && rm file && [ -f hlink ] && echo 'yes'" \
"yes\n" "" ""
rm -f file hlink
+
+mkdir -p one/two
+ln -s . circular
+mkdir -p three
+echo hello > three/four
+testing "ln -r" \
+ "ln -sr circular/three/../three/four one/two/five && cat one/two/five" \
+ "hello\n" "" ""
+rm -rf one three circular
diff --git a/toys/posix/ln.c b/toys/posix/ln.c
index 306f0cac..3cd5c7b8 100644
--- a/toys/posix/ln.c
+++ b/toys/posix/ln.c
@@ -4,7 +4,7 @@
*
* See http://opengroup.org/onlinepubs/9699919799/utilities/ln.html
-USE_LN(NEWTOY(ln, "<1t:Tvnfs", TOYFLAG_BIN))
+USE_LN(NEWTOY(ln, "<1rt:Tvnfs", TOYFLAG_BIN))
config LN
bool "ln"
@@ -18,6 +18,7 @@ config LN
-s Create a symbolic link
-f Force the creation of the link, even if TO already exists
-n Symlink at TO treated as file
+ -r Create relative symlink from -> to
-t Create links in DIR
-T TO always treated as file, max 2 arguments
-v Verbose
@@ -34,30 +35,37 @@ void ln_main(void)
{
char *dest = TT.t ? TT.t : toys.optargs[--toys.optc], *new;
struct stat buf;
- int i;
+ int i, rc;
+
+ if (FLAG(T) && toys.optc>1) help_exit("Max 2 arguments");
// With one argument, create link in current directory.
if (!toys.optc) {
toys.optc++;
- dest=".";
+ dest = ".";
}
- if (FLAG(T) && toys.optc>1) help_exit("Max 2 arguments");
// Is destination a directory?
if (!((FLAG(n)||FLAG(T)) ? lstat : stat)(dest, &buf)) {
- i = S_ISDIR(buf.st_mode);
-
- if ((FLAG(T) && i) || (!i && (toys.optc>1 || TT.t)))
+ if ((i = S_ISDIR(buf.st_mode)) ? FLAG(T) : (toys.optc>1 || TT.t))
error_exit("'%s' %s a directory", dest, i ? "is" : "not");
} else buf.st_mode = 0;
for (i=0; i<toys.optc; i++) {
- int rc;
- char *oldnew, *try = toys.optargs[i];
+ char *oldnew = 0, *try = toys.optargs[i];
if (S_ISDIR(buf.st_mode)) new = xmprintf("%s/%s", dest, basename(try));
else new = dest;
+ if (FLAG(r)) {
+ try = relative_path(new, try);
+ if (!try) {
+ if (new != dest) free(new);
+ continue;
+ }
+ toys.optflags |= FLAG_s;
+ }
+
// Force needs to unlink the existing target (if any). Do that by creating
// a temp version and renaming it over the old one, so we can retain the
// old file in cases we can't replace it (such as hardlink between mounts).
@@ -88,6 +96,7 @@ void ln_main(void)
FLAG(s) ? "symbolic" : "hard", try, new);
else if (FLAG(v)) fprintf(stderr, "'%s' -> '%s'\n", new, try);
+ if (try != toys.optargs[i]) free(try);
if (new != dest) free(new);
}
}