commit: 171eac505114e8e71450221269212aa8362ab58a
parent ff0b40aabdabc2b16c4da3f1c8e1b380584c6c06
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Sun, 28 Apr 2024 01:13:35 +0200
cmd/ln: new
Diffstat:
A | cmd/ln.1 | 68 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | cmd/ln.c | 144 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | makeless.sh | 1 | + |
A | test-cmd/ln.t | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 269 insertions(+), 0 deletions(-)
diff --git a/cmd/ln.1 b/cmd/ln.1
@@ -0,0 +1,68 @@
+.\" utils-std: Collection of commonly available Unix tools
+.\" Copyright 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" SPDX-License-Identifier: MPL-2.0
+.Dd 2024-04-28
+.Dt LN 1
+.Os
+.Sh NAME
+.Nm ln
+.Nd create hard links and symbolic links
+.Sh SYNOPSIS
+.Nm
+.Op Fl f
+.Op Fl L Ns | Ns Fl P
+.Ar source...
+.Ar target
+.Nm
+.Fl s
+.Op Fl f
+.Ar reference...
+.Ar target
+.Sh DESCRIPTION
+.Nm
+create links at
+.Ar target
+for each given
+.Ar source
+or
+.Ar reference .
+When multiple
+.Ar source
+or
+.Ar reference
+are given,
+.Ar target
+is expected to be a directory which
+.Nm
+will create links into.
+.Pp
+Should be noted that
+.Ar reference
+is kept as is and therefore is always relative to
+.Ar target
+rather than the current directory.
+.Sh OPTIONS
+.Bl -tag -width __
+.It Fl f
+Forcefully create links by removing existing entries.
+.It Fl L
+If
+.Ar source
+is a symbolic link, dereference it.
+.It Fl P
+If
+.Ar source
+is a symbolic link, hard link it.
+This is the default.
+.It Fl s
+Create symbolic links instead of hard links.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh STANDARDS
+.Nm
+should be compliant with the
+.St -p1003.1-2008
+specification.
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact+utils@hacktivis.me
diff --git a/cmd/ln.c b/cmd/ln.c
@@ -0,0 +1,144 @@
+// utils-std: Collection of commonly available Unix tools
+// SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: MPL-2.0
+
+#define _POSIX_C_SOURCE 200809L
+
+#include "../lib/bitmasks.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h> // linkat, AT_SYMLINK_FOLLOW
+#include <limits.h> // PATH_MAX
+#include <stdbool.h>
+#include <stdio.h> // fprintf
+#include <string.h> // strerror
+#include <sys/stat.h>
+#include <unistd.h> // getopt, symlink, link
+
+static bool opt_s = false, force = true;
+static int link_flags = 0;
+
+static int
+do_link(char *src, char *dest)
+{
+ assert(errno == 0);
+ struct stat dest_stat;
+ if(lstat(dest, &dest_stat) < 0)
+ {
+ if(errno != ENOENT)
+ {
+ fprintf(stderr, "ln: Failed getting status for target '%s': %s\n", dest, strerror(errno));
+ return -1;
+ }
+ errno = 0;
+ }
+ else if(force)
+ {
+ if(unlink(dest) < 0)
+ {
+ fprintf(stderr, "ln: Unlinking '%s' before replacing failed: %s\n", dest, strerror(errno));
+ return -1;
+ }
+ }
+ else
+ {
+ fprintf(stderr, "ln: Error: Destination '%s' already exists\n", dest);
+ return -1;
+ }
+
+ if(opt_s)
+ {
+ if(symlink(src, dest) < 0)
+ {
+ fprintf(stderr, "ln: Failed creating symlink '%s': %s\n", dest, strerror(errno));
+ return -1;
+ }
+ }
+ else
+ {
+ if(linkat(AT_FDCWD, src, AT_FDCWD, dest, link_flags) < 0)
+ {
+ fprintf(stderr,
+ "ln: Failed creating hard link from '%s' to '%s': %s\n",
+ src,
+ dest,
+ strerror(errno));
+ return -1;
+ }
+ }
+
+ assert(errno == 0);
+
+ return 0;
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "\
+Usage: ln [-f] [-L|-P] source... target\n\
+ ln -s [-f] reference... target\n\
+");
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c = -1;
+ while((c = getopt(argc, argv, ":fsLP")) != -1)
+ {
+ switch(c)
+ {
+ case 'f':
+ force = true;
+ break;
+ case 's':
+ opt_s = true;
+ break;
+ case 'L':
+ FIELD_SET(link_flags, AT_SYMLINK_FOLLOW);
+ break;
+ case 'P':
+ FIELD_CLR(link_flags, AT_SYMLINK_FOLLOW);
+ break;
+ case '?':
+ fprintf(stderr, "ln: Unknown option '-%c'\n", optopt);
+ usage();
+ break;
+ }
+ }
+
+ assert(errno == 0);
+
+ argc -= optind;
+ argv += optind;
+
+ if(argc == 2)
+ {
+ if(do_link(argv[0], argv[1]) < 0) return 1;
+ }
+ else
+ {
+ char *dest = argv[argc - 1];
+ char target[PATH_MAX] = "";
+
+ for(int i = 0; i < argc - 1; i++)
+ {
+ char *src = argv[i];
+
+ char *src_sep = strrchr(src, '/');
+ char *src_basename = src_sep != NULL ? src_sep + 1 : src;
+
+ if(snprintf(target, PATH_MAX, "%s/%s", dest, src_basename) < 0)
+ {
+ fprintf(stderr, "ln: Failed joining destination '%s' and target '%s'\n", dest, src);
+ return 1;
+ }
+
+ if(do_link(src, target) < 0) return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/makeless.sh b/makeless.sh
@@ -24,6 +24,7 @@ $CC -std=c99 $CFLAGS -o cmd/env cmd/env.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/false cmd/false.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/id cmd/id.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/link cmd/link.c $LDFLAGS $LDSTATIC
+$CC -std=c99 $CFLAGS -o cmd/ln cmd/ln.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/logname cmd/logname.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/mkdir cmd/mkdir.c lib/mode.c $LDFLAGS $LDSTATIC
$CC -std=c99 $CFLAGS -o cmd/mkfifo cmd/mkfifo.c lib/mode.c $LDFLAGS $LDSTATIC
diff --git a/test-cmd/ln.t b/test-cmd/ln.t
@@ -0,0 +1,56 @@
+#!/usr/bin/env cram
+# SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+ $ export PATH="$TESTDIR/../cmd:$PATH"
+
+ $ test "$(command -v ln)" = "$TESTDIR/../cmd/ln"
+
+ $ ln hard_enoent_src hard_enoent_dest
+ ln: Failed creating hard link from 'hard_enoent_src' to 'hard_enoent_dest': No such file or directory
+ [1]
+
+ $ ln -s hard_enoent_ref1 hard_enoent_ref2 hard_enoent_dest
+ ln: Failed creating symlink 'hard_enoent_dest/hard_enoent_ref1': No such file or directory
+ [1]
+
+ $ touch hard_file_src
+ $ ln hard_file_src hard_file_dest
+ $ test -f hard_file_dest
+ $ rm hard_file_src hard_file_dest
+
+ $ touch hard_file_src1 ./hard_file_src2
+ $ mkdir hard_dir_dest
+ $ ln hard_file_src1 ./hard_file_src2 hard_dir_dest
+ $ test -f hard_dir_dest/hard_file_src1
+ $ test -f hard_dir_dest/hard_file_src2
+ $ rm hard_file_src1 hard_file_src2
+ $ rm -r hard_dir_dest
+
+ $ ln -s sym_enoent_ref sym_enoent_dest
+ $ readlink sym_enoent_dest
+ sym_enoent_ref
+ $ rm sym_enoent_dest
+
+ $ ln -s sym_enoent_ref1 sym_enoent_ref2 sym_enoent_dest
+ ln: Failed creating symlink 'sym_enoent_dest/sym_enoent_ref1': No such file or directory
+ [1]
+
+ $ mkdir sym_dir_slash
+ $ ln -s sym_enoent_ref1 ./sym_enoent_ref2 sym_dir_slash/
+ $ readlink sym_dir_slash/sym_enoent_ref1
+ sym_enoent_ref1
+ $ readlink sym_dir_slash/sym_enoent_ref2
+ ./sym_enoent_ref2
+ $ rm -r sym_dir_slash
+
+ $ mkdir sym_dir_noslash
+ $ ln -s sym_enoent_ref1 ./sym_enoent_ref2 sym_dir_noslash
+ $ readlink sym_dir_noslash/sym_enoent_ref1
+ sym_enoent_ref1
+ $ readlink sym_dir_noslash/sym_enoent_ref2
+ ./sym_enoent_ref2
+ $ rm -r sym_dir_noslash
+
+ $ find
+ .