aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--toys/posix/ln.c36
1 files changed, 30 insertions, 6 deletions
diff --git a/toys/posix/ln.c b/toys/posix/ln.c
index d8f4ce62..04b4f292 100644
--- a/toys/posix/ln.c
+++ b/toys/posix/ln.c
@@ -46,19 +46,43 @@ void ln_main(void)
for (i=0; i<toys.optc; i++) {
int rc;
- char *try = toys.optargs[i];
+ char *oldnew, *try = toys.optargs[i];
if (S_ISDIR(buf.st_mode)) new = xmprintf("%s/%s", dest, basename(try));
else new = dest;
- // Silently unlink the existing target (if any)
- if (toys.optflags & FLAG_f) unlink(new);
+
+ // 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).
+ oldnew = new;
+ if (toys.optflags & FLAG_f) {
+ new = xmprintf("%s_XXXXXX", new);
+ rc = mkstemp(new);
+ if (rc >= 0) {
+ close(rc);
+ if (unlink(new)) perror_msg("unlink '%s'", new);
+ }
+ }
rc = (toys.optflags & FLAG_s) ? symlink(try, new) : link(try, new);
+ if (toys.optflags & FLAG_f) {
+ if (!rc) {
+ int temp;
+
+ rc = rename(new, oldnew);
+ temp = errno;
+ if (rc && unlink(new)) perror_msg("unlink '%s'", new);
+ errno = temp;
+ }
+ free(new);
+ new = oldnew;
+ }
if (rc)
- perror_exit("cannot create %s link from '%s' to '%s'",
+ perror_msg("cannot create %s link from '%s' to '%s'",
(toys.optflags & FLAG_s) ? "symbolic" : "hard", try, new);
- else if (toys.optflags & FLAG_v)
- fprintf(stderr, "'%s' -> '%s'\n", new, try);
+ else
+ if (toys.optflags & FLAG_v) fprintf(stderr, "'%s' -> '%s'\n", new, try);
+
if (new != dest) free(new);
}
}