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:
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