logo

utils-std

Collection of commonly available Unix tools
commit: 2d39eea38dda2a5febf2574929717f5d85232a55
parent 910e99b2b9f92ba209f6317a7604b4c4128bbbee
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Thu,  2 May 2024 01:28:08 +0200

cmd/{readlink,realpath}: Add commonly used short options

Diffstat:

MMakefile10+++++++++-
Mcmd/chown.c12+++---------
Acmd/readlink2++
Mcmd/readlink.132++++++++++++++++++++++++++------
Dcmd/readlink.c68--------------------------------------------------------------------
Mcmd/realpath.132+++++++++++++++++---------------
Mcmd/realpath.c220++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Alib/path.c37+++++++++++++++++++++++++++++++++++++
Alib/path.h13+++++++++++++
Mmakeless.sh5++---
Mtest-cmd/readlink.t5++---
Mtest-cmd/realpath.t45+++++++++++++++++++++++++++------------------
12 files changed, 306 insertions(+), 175 deletions(-)

diff --git a/Makefile b/Makefile @@ -6,7 +6,7 @@ include config.mk # Commands implemented as scripts SCRIPTS=cmd/yes # Commands linking to another executable -SYMLINKS=cmd/'[' cmd/chgrp +SYMLINKS=cmd/'[' cmd/chgrp cmd/readlink all: $(EXE) $(MAN1SO) @@ -125,6 +125,14 @@ cmd/tr: cmd/tr.c lib/tr_str.c lib/tr_str.h Makefile rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno} $(CC) -std=c99 $(CFLAGS) -o $@ cmd/tr.c lib/tr_str.c $(LDFLAGS) $(LDSTATIC) +cmd/chown: cmd/chown.c lib/path.c lib/path.h Makefile + rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno} + $(CC) -std=c99 $(CFLAGS) -o $@ cmd/chown.c lib/path.c $(LDFLAGS) $(LDSTATIC) + +cmd/realpath: cmd/realpath.c lib/path.c lib/path.h Makefile + rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno} + $(CC) -std=c99 $(CFLAGS) -o $@ cmd/realpath.c lib/path.c $(LDFLAGS) $(LDSTATIC) + cmd/expr.tab.c: cmd/expr.y Makefile $(YACC) -b cmd/expr cmd/expr.y diff --git a/cmd/chown.c b/cmd/chown.c @@ -9,6 +9,8 @@ #define _NETBSD_SOURCE #endif +#include "../lib/path.h" + #include <assert.h> #include <dirent.h> // fdopendir, readdir, closedir #include <errno.h> @@ -19,7 +21,7 @@ #include <stdbool.h> #include <stdio.h> // fprintf #include <stdlib.h> // abort, exit -#include <string.h> // strerror, strcmp, strrchr +#include <string.h> // strerror, strcmp #include <sys/stat.h> // fstatat, S_ISDIR #include <unistd.h> // getopt @@ -104,14 +106,6 @@ parse_group(char *str) return 0; } -static char * -static_basename(char *path) -{ - char *sep = strrchr(path, '/'); - - return (sep == NULL) ? path : sep + 1; -} - static int do_fchownat(int fd, char *name, char *acc_path, enum chown_follow_symlinks follow_symlinks) { diff --git a/cmd/readlink b/cmd/readlink @@ -0,0 +1 @@ +realpath +\ No newline at end of file diff --git a/cmd/readlink.1 b/cmd/readlink.1 @@ -9,16 +9,17 @@ .Nd print content of a symbolic link .Sh SYNOPSIS .Nm -.Op Fl n +.Op Fl f Ns | Ns Fl e +.Op Fl n Ns | Ns Fl z .Ar path .Sh DESCRIPTION -Note: For canonicalization of a path, see the +Note: For canonicalization of a path, use the .Xr realpath 1 -utility instead. +utility instead for better portability. .Pp The .Nm -utility reads the symbolic link of +utility reads the symbolic link of each .Ar path and prints it. .Pp @@ -28,9 +29,19 @@ is not a symbolic link, .Nm errors out. .Sh OPTIONS -.Bl -tag -width ee +.Bl -tag -width _n +.It Fl f +Canonicalize +.Ar path +with following every symlink, all but the last path component are required to exists. +.It Fl e +Canonicalize +.Ar path +with following every symlink, all path component must exists. .It Fl n -Do not print a trailing newline. +Do not print a trailing separator. +.It Fl z +Use NULL byte as separator instead of newlines. .El .Sh EXIT STATUS .Ex -std @@ -52,5 +63,14 @@ $ readlink foobar .Nm should be compliant with IEEE P1003.1-202x/D4 (“POSIX.1”). +.Pp +The options +.Fl f , +.Fl e +and +.Fl z +and support for multiple +.Ar path +arguments are extensions present for compatibility with existing software. .Sh AUTHORS .An Haelwenn (lanodan) Monnier Aq Mt contact+utils@hacktivis.me diff --git a/cmd/readlink.c b/cmd/readlink.c @@ -1,68 +0,0 @@ -// 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 <errno.h> -#include <limits.h> // PATH_MAX -#include <stdbool.h> -#include <stdio.h> // fprintf -#include <string.h> // strerror -#include <unistd.h> // getopt - -static void -usage() -{ - fprintf(stderr, "Usage: readlink [-n] file\n"); -} - -int -main(int argc, char *argv[]) -{ - bool newline = true; - - int c = -1; - while((c = getopt(argc, argv, ":n")) != -1) - { - switch(c) - { - case 'n': - newline = false; - break; - case ':': - fprintf(stderr, "readlink: Error: Missing operand for option: '-%c'\n", optopt); - usage(); - return 1; - case '?': - fprintf(stderr, "readlink: Error: Unrecognised option: '-%c'\n", optopt); - usage(); - return 1; - } - } - - argv += optind; - argc -= optind; - - if(argc != 1) - { - fprintf(stderr, "readlink: Expected one file as argument, got %d\n", argc); - usage(); - return 1; - } - - char buf[PATH_MAX] = ""; - if(readlink(argv[0], buf, sizeof(buf) - 1) < 0) - { - fprintf(stderr, - "readlink: Error: Failed reading symbolic link of '%s': %s\n", - argv[0], - strerror(errno)); - return 1; - } - - printf("%s", buf); - if(newline) printf("\n"); - - return 0; -} diff --git a/cmd/realpath.1 b/cmd/realpath.1 @@ -9,31 +9,21 @@ .Nd print resolved path .Sh SYNOPSIS .Nm -.Op Fl Ee -.Ar path +.Op Fl E Ns | Ns Fl e +.Op Fl n Ns | Ns Fl z +.Ar path ... .Sh DESCRIPTION The .Nm utility resolves .Pa \&. , .Pa .. , -and symlinks used in +and symlinks used in each .Ar path to print an absolute pathname. .Pp When no flags are passed, this implementation assumes the behavior of -.Fl E , -future versions might reproduce the -.Cm mksh -builtin implementation of -.Nm -which resolves -.Pa /dev/null/.. -to -.Pa /dev -similarly to the -.Fl m -option of the GNU implementation. +.Fl E . .Sh OPTIONS .Bl -tag -width ee .It Fl E @@ -42,6 +32,10 @@ Do not fail when the final path component does not exists and it's parent is a d Fail when .Ar path does not exists. +.It Fl n +Do not print a trailing separator. +.It Fl z +Use NULL byte as separator instead of newlines. .El .Sh EXIT STATUS .Ex -std @@ -51,5 +45,13 @@ does not exists. .Nm should be compliant with IEEE P1003.1-202x/D4 (“POSIX.1”). +.Pp +The options +.Fl n +and +.Fl z +and support for multiple +.Ar path +arguments are extensions present for compatibility with existing software. .Sh AUTHORS .An Haelwenn (lanodan) Monnier Aq Mt contact+utils@hacktivis.me diff --git a/cmd/realpath.c b/cmd/realpath.c @@ -5,6 +5,8 @@ #define _POSIX_C_SOURCE 200809L #define _XOPEN_SOURCE 700 // realpath is in XSI +#include "../lib/path.h" + #include <errno.h> #include <limits.h> // PATH_MAX #include <stdbool.h> @@ -13,19 +15,86 @@ #include <string.h> // strncmp(), strnlen, strerror #include <unistd.h> // getopt +static bool must_exists = false; + +static char *argv0 = NULL; +static char sep = '\n'; + +static int +print_realpath(char *path) +{ + /* flawfinder: ignore, NULL given */ + char *file = realpath(path, NULL); + if(file) + { + if(printf("%s", file) < 0) + { + fprintf(stderr, "%s: Error writing to stdtout: %s\n", argv0, strerror(errno)); + free(file); + return 1; + } + + free(file); + + return 0; + } + + if(must_exists || errno != ENOENT) + { + fprintf(stderr, "%s: Failed canonilizing \"%s\": %s\n", argv0, path, strerror(errno)); + return 1; + } + + char *child = path_split_static(path, true); + + if(child == NULL) + { + // Return as if realpath just failed + fprintf(stderr, "%s: Failed canonilizing \"%s\": %s\n", argv0, path, strerror(errno)); + return 1; + } + + errno = 0; + + /* flawfinder: ignore, NULL given */ + char *parent = realpath(path, NULL); + if(!parent) + { + fprintf(stderr, + "%s: Failed canonilizing parent of full path \"%s/%s\": %s\n", + argv0, + path, + child, + strerror(errno)); + return 1; + } + + if(printf("%s/%s", parent, child) < 0) + { + fprintf(stderr, "%s: Error writing to stdtout: %s\n", argv0, strerror(errno)); + free(parent); + return 1; + } + + free(parent); + return 0; +} + static void -usage() +usage_realpath() { - fprintf(stderr, "Usage: realpath [-E|-e] path\n"); + fprintf(stderr, "Usage: realpath [-E|-e] [-n|-z] path...\n"); } -int -main(int argc, char *argv[]) +static int +main_realpath(int argc, char *argv[]) { - bool must_exists = false; + must_exists = false; + + int offset_sep = 0; int c = -1; - while((c = getopt(argc, argv, ":Ee")) != -1) + while((c = getopt(argc, argv, ":Eemnz")) != -1) { switch(c) { @@ -35,13 +104,19 @@ main(int argc, char *argv[]) case 'e': must_exists = true; break; + case 'n': + offset_sep = 1; + break; + case 'z': + sep = '\0'; + break; case ':': fprintf(stderr, "realpath: Error: Missing operand for option: '-%c'\n", optopt); - usage(); + usage_realpath(); return 1; case '?': fprintf(stderr, "realpath: Error: Unrecognised option: '-%c'\n", optopt); - usage(); + usage_realpath(); return 1; } } @@ -49,72 +124,113 @@ main(int argc, char *argv[]) argv += optind; argc -= optind; - if(argc != 1) + if(argc == 0) { - usage(); + fprintf(stderr, "%s: Expected one file as argument, got 0\n", argv0); + usage_realpath(); return 1; } - char *path = argv[0]; - size_t path_len = strnlen(argv[0], PATH_MAX); - - if(path_len >= PATH_MAX) + for(int i = 0; i < argc; i++) { - fprintf(stderr, "realpath: Argument given is longer than {PATH_MAX}(= %d)\n", PATH_MAX); - return 1; + if(print_realpath(argv[i]) != 0) return 1; + if(i != argc - offset_sep) printf("%c", sep); } - /* flawfinder: ignore, NULL given */ - char *file = realpath(path, NULL); - if(file) + return 0; +} + +static void +usage_readlink() +{ + fprintf(stderr, "Usage: readlink [-f|-e] [-n|-z] file...\n"); +} + +static int +main_readlink(int argc, char *argv[]) +{ + must_exists = true; + + bool canonicalize = false; + + int offset_sep = 0; + + int c = -1; + while((c = getopt(argc, argv, ":femnz")) != -1) { - if(puts(file) < 0) + switch(c) { - fprintf(stderr, "realpath: puts: %s\n", strerror(errno)); + case 'f': + canonicalize = true; + must_exists = false; + break; + case 'e': + must_exists = true; + break; + case 'n': + offset_sep = 1; + break; + case 'z': + sep = '\0'; + break; + case ':': + fprintf(stderr, "readlink: Error: Missing operand for option: '-%c'\n", optopt); + usage_readlink(); + return 1; + case '?': + fprintf(stderr, "readlink: Error: Unrecognised option: '-%c'\n", optopt); + usage_readlink(); return 1; } - - return 0; } - if(errno == ENOENT && !must_exists) - { - char *child = NULL; + argv += optind; + argc -= optind; - // delete trailing slashes - for(int i = path_len - 1; i > 0 && path[i] == '/'; i--) - path[i] = 0; + if(argc == 0) + { + fprintf(stderr, "%s: Expected one file as argument, got 0\n", argv0); + usage_readlink(); + return 1; + } - for(int i = path_len - 1; i > 0; i--) - if(path[i] == '/') - { - path[i] = 0; - child = &path[i + 1]; - break; - } + for(int i = 0; i < argc; i++) + { + char *path = argv[i]; - if(child == NULL) + if(canonicalize) { - fprintf(stderr, "realpath: \"%s\": %s\n", path, strerror(errno)); - return 1; - } + if(print_realpath(path) != 0) return 1; + if(i != argc) printf("%c", sep); - errno = 0; + continue; + } - /* flawfinder: ignore, NULL given */ - char *parent = realpath(path, NULL); - if(!parent) + char buf[PATH_MAX] = ""; + if(readlink(path, buf, sizeof(buf) - 1) < 0) { - fprintf(stderr, "realpath: \"%s/%s\": %s\n", path, child, strerror(errno)); + fprintf(stderr, + "readlink: Error: Failed reading symbolic link of '%s': %s\n", + path, + strerror(errno)); return 1; } - printf("%s/%s\n", parent, child); - return 0; - } - else - { - fprintf(stderr, "realpath: \"%s\": %s\n", path, strerror(errno)); - return 1; + printf("%s", buf); + if(i != argc - offset_sep) printf("%c", sep); } + + return 0; +} + +int +main(int argc, char *argv[]) +{ + argv0 = static_basename(argv[0]); + if(strcmp(argv0, "realpath") == 0) return main_realpath(argc, argv); + if(strcmp(argv0, "readlink") == 0) return main_readlink(argc, argv); + + fprintf(stderr, "Unknown utility '%s', expected realpath or readlink\n", argv0); + + return 1; } diff --git a/lib/path.c b/lib/path.c @@ -0,0 +1,37 @@ +// utils-std: Collection of commonly available Unix tools +// SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +#include "./path.h" + +#include <string.h> // strrchr + +char * +static_basename(char *path) +{ + char *sep = strrchr(path, '/'); + + return (sep == NULL) ? path : sep + 1; +} + +char * +path_split_static(char *path, bool trim) +{ + char *child = NULL; + size_t path_len = strlen(path); + + // delete trailing slashes + if(trim) + for(int i = path_len - 1; i > 0 && path[i] == '/'; i--) + path[i] = 0; + + for(int i = path_len - 1; i > 0; i--) + if(path[i] == '/') + { + path[i] = 0; + child = &path[i + 1]; + break; + } + + return child; +} diff --git a/lib/path.h b/lib/path.h @@ -0,0 +1,13 @@ +// utils-std: Collection of commonly available Unix tools +// SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +#include <stdbool.h> +#include <stddef.h> // size_t + +// barebones basename, returns what's after the rightmost slash +char *static_basename(char *path); + +// Modifies a path in-place to given a parent (dirname-like) and a child (basename-like) +// optionally trims the tailing slashes prior to splitting +char *path_split_static(char *path, bool trim); diff --git a/makeless.sh b/makeless.sh @@ -15,7 +15,7 @@ $CC -std=c99 $CFLAGS -o cmd/base64 cmd/base64.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/basename cmd/basename.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/cat cmd/cat.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/chmod cmd/chmod.c lib/mode.c lib/symbolize_mode.c $LDFLAGS $LDSTATIC -$CC -std=c99 $CFLAGS -o cmd/chown cmd/chown.c $LDFLAGS $LDSTATIC +$CC -std=c99 $CFLAGS -o cmd/chown cmd/chown.c lib/path.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/chroot cmd/chroot.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/date cmd/date.c lib/iso_parse.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/df cmd/df.c lib/humanize.c $LDFLAGS $LDSTATIC @@ -36,8 +36,7 @@ $CC -std=c99 $CFLAGS -o cmd/nproc cmd/nproc.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/pathchk cmd/pathchk.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/printf cmd/printf.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/pwd cmd/pwd.c $LDFLAGS $LDSTATIC -$CC -std=c99 $CFLAGS -o cmd/readlink cmd/readlink.c $LDFLAGS $LDSTATIC -$CC -std=c99 $CFLAGS -o cmd/realpath cmd/realpath.c $LDFLAGS $LDSTATIC +$CC -std=c99 $CFLAGS -o cmd/realpath cmd/realpath.c lib/path.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/rm cmd/rm.c lib/consent.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/rmdir cmd/rmdir.c $LDFLAGS $LDSTATIC $CC -std=c99 $CFLAGS -o cmd/seq cmd/seq.c -lm $LDFLAGS $LDSTATIC diff --git a/test-cmd/readlink.t b/test-cmd/readlink.t @@ -14,11 +14,10 @@ $ readlink readlink: Expected one file as argument, got 0 - Usage: readlink [-n] file + Usage: readlink [-f|-e] [-n|-z] file... [1] $ readlink foo bar - readlink: Expected one file as argument, got 2 - Usage: readlink [-n] file + readlink: Error: Failed reading symbolic link of 'foo': No such file or directory [1] $ test ! -f enoent diff --git a/test-cmd/realpath.t b/test-cmd/realpath.t @@ -21,13 +21,13 @@ $ $path/realpath /var/empty/foo// /var/empty/foo $ $path/realpath /var/empty/foo/bar - realpath: "/var/empty/foo/bar": No such file or directory + realpath: Failed canonilizing parent of full path "/var/empty/foo/bar": No such file or directory [1] $ $path/realpath /var/empty/foo/bar/ - realpath: "/var/empty/foo/bar": No such file or directory + realpath: Failed canonilizing parent of full path "/var/empty/foo/bar": No such file or directory [1] $ $path/realpath /var/empty/foo/bar// - realpath: "/var/empty/foo/bar": No such file or directory + realpath: Failed canonilizing parent of full path "/var/empty/foo/bar": No such file or directory [1] $ test "$($path/realpath .)" = "$(pwd)" @@ -46,13 +46,13 @@ $ $path/realpath /var/empty/foo// /var/empty/foo $ $path/realpath /var/empty/foo/bar - realpath: "/var/empty/foo/bar": No such file or directory + realpath: Failed canonilizing parent of full path "/var/empty/foo/bar": No such file or directory [1] $ $path/realpath /var/empty/foo/bar/ - realpath: "/var/empty/foo/bar": No such file or directory + realpath: Failed canonilizing parent of full path "/var/empty/foo/bar": No such file or directory [1] $ $path/realpath /var/empty/foo/bar// - realpath: "/var/empty/foo/bar": No such file or directory + realpath: Failed canonilizing parent of full path "/var/empty/foo/bar": No such file or directory [1] @@ -66,22 +66,22 @@ $ $path/realpath -e /var/empty /var/empty $ $path/realpath -e /var/empty/foo - realpath: "/var/empty/foo": No such file or directory + realpath: Failed canonilizing "/var/empty/foo": No such file or directory [1] $ $path/realpath -e /var/empty/foo/ - realpath: "/var/empty/foo/": No such file or directory + realpath: Failed canonilizing "/var/empty/foo/": No such file or directory [1] $ $path/realpath -e /var/empty/foo// - realpath: "/var/empty/foo//": No such file or directory + realpath: Failed canonilizing "/var/empty/foo//": No such file or directory [1] $ $path/realpath -e /var/empty/foo/bar - realpath: "/var/empty/foo/bar": No such file or directory + realpath: Failed canonilizing "/var/empty/foo/bar": No such file or directory [1] $ $path/realpath -e /var/empty/foo/bar/ - realpath: "/var/empty/foo/bar/": No such file or directory + realpath: Failed canonilizing "/var/empty/foo/bar/": No such file or directory [1] $ $path/realpath -e /var/empty/foo/bar// - realpath: "/var/empty/foo/bar//": No such file or directory + realpath: Failed canonilizing "/var/empty/foo/bar//": No such file or directory [1] Non-directory @@ -94,25 +94,34 @@ Non-directory /dev/null $ $path/realpath /dev/null/ - realpath: "/dev/null/": Not a directory + realpath: Failed canonilizing "/dev/null/": Not a directory [1] $ $path/realpath -e /dev/null/ - realpath: "/dev/null/": Not a directory + realpath: Failed canonilizing "/dev/null/": Not a directory [1] $ $path/realpath -E /dev/null/ - realpath: "/dev/null/": Not a directory + realpath: Failed canonilizing "/dev/null/": Not a directory [1] $ $path/realpath /dev/null/.. - realpath: "/dev/null/..": Not a directory + realpath: Failed canonilizing "/dev/null/..": Not a directory [1] $ $path/realpath -e /dev/null/.. - realpath: "/dev/null/..": Not a directory + realpath: Failed canonilizing "/dev/null/..": Not a directory [1] $ $path/realpath -E /dev/null/.. - realpath: "/dev/null/..": Not a directory + realpath: Failed canonilizing "/dev/null/..": Not a directory [1] +Zero-separation + $ $path/realpath -z /dev/null /var/empty + /dev/null\x00/var/empty\x00 (no-eol) (esc) + +No final newline + $ $path/realpath -n /dev/null /var/empty + /dev/null + /var/empty (no-eol) + As required by "IEEE P1003.1™-202x/D4" -E and -e aren't errorneous $ $path/realpath -E -e /dev/null