logo

utils-std

Collection of commonly available Unix tools
commit: 171eac505114e8e71450221269212aa8362ab58a
parent ff0b40aabdabc2b16c4da3f1c8e1b380584c6c06
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Sun, 28 Apr 2024 01:13:35 +0200

cmd/ln: new

Diffstat:

Acmd/ln.168++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/ln.c144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmakeless.sh1+
Atest-cmd/ln.t56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 + .