commit: e860f5632e9e06e1e627ae070a793e20936970dd
parent 673f62ec770c01988df49e848896beb9eb501a02
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Sat, 23 Mar 2024 05:09:52 +0100
cmd/realpath: New
Diffstat:
4 files changed, 294 insertions(+), 1 deletion(-)
diff --git a/cmd/realpath.1 b/cmd/realpath.1
@@ -0,0 +1,55 @@
+.\" 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-03-24
+.Dt REALPATH 1
+.Os
+.Sh NAME
+.Nm realpath
+.Nd print resolved path
+.Sh SYNOPSIS
+.Nm
+.Op Fl Ee
+.Ar path
+.Sh DESCRIPTION
+The
+.Nm
+utility resolves
+.Pa \&. ,
+.Pa .. ,
+and symlinks used in
+.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.
+.Sh OPTIONS
+.Bl -tag -width ee
+.It Fl E
+Do not fail when the final path component does not exists and it's parent is a directory.
+.It Fl e
+Fail when
+.Ar path
+does not exists.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr realpath 3
+.Sh STANDARDS
+.Nm
+should be compliant with
+IEEE P1003.1-202x/D4 (“POSIX.1”).
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me
diff --git a/cmd/realpath.c b/cmd/realpath.c
@@ -0,0 +1,119 @@
+// 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
+#define _XOPEN_SOURCE 700 // realpath is in XSI
+
+#include <errno.h>
+#include <limits.h> // PATH_MAX
+#include <stdbool.h>
+#include <stdio.h> // fprintf(), puts()
+#include <stdlib.h> // realpath()
+#include <string.h> // strncmp(), strnlen, strerror
+#include <unistd.h> // getopt
+
+void
+usage()
+{
+ fprintf(stderr, "Usage: realpath [-E|-e] path\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c = -1;
+ bool must_exists = false;
+
+ while((c = getopt(argc, argv, ":Ee")) != -1)
+ {
+ switch(c)
+ {
+ case 'E':
+ must_exists = false;
+ break;
+ case 'e':
+ must_exists = true;
+ break;
+ case ':':
+ fprintf(stderr, "realpath: Error: Missing operand for option: '-%c'\n", optopt);
+ usage();
+ return 1;
+ case '?':
+ fprintf(stderr, "realpath: Error: Unrecognised option: '-%c'\n", optopt);
+ usage();
+ return 1;
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if(argc != 1)
+ {
+ usage();
+ return 1;
+ }
+
+ char *path = argv[0];
+ size_t path_len = strnlen(argv[0], PATH_MAX);
+
+ if(path_len >= PATH_MAX)
+ {
+ fprintf(stderr, "realpath: Argument given is longer than {PATH_MAX}(= %d)\n", PATH_MAX);
+ return 1;
+ }
+
+ /* flawfinder: ignore */
+ char *file = realpath(path, NULL);
+ if(file)
+ {
+ if(puts(file) < 0)
+ {
+ fprintf(stderr, "realpath: puts: %s\n", strerror(errno));
+ return 1;
+ }
+
+ return 0;
+ }
+
+ if(errno == ENOENT && !must_exists)
+ {
+ char *child = NULL;
+
+ // delete trailing slashes
+ 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;
+ }
+
+ if(child == NULL)
+ {
+ fprintf(stderr, "realpath: \"%s\": %s\n", path, strerror(errno));
+ return 1;
+ }
+
+ errno = 0;
+
+ char *parent = realpath(path, NULL);
+ if(!parent)
+ {
+ fprintf(stderr, "realpath: \"%s/%s\": %s\n", path, child, strerror(errno));
+ return 1;
+ }
+
+ printf("%s/%s\n", parent, child);
+ return 0;
+ }
+ else
+ {
+ fprintf(stderr, "realpath: \"%s\": %s\n", path, strerror(errno));
+ return 1;
+ }
+}
diff --git a/coreutils.txt b/coreutils.txt
@@ -63,7 +63,7 @@ printf: Todo
ptx: No
pwd: Done
readlink: ?
-realpath: Todo
+realpath: Done
rm: Todo
rmdir: No
runcon: No. (SELinux-specific util, wtf)
diff --git a/test-cmd/realpath.t b/test-cmd/realpath.t
@@ -0,0 +1,119 @@
+#!/usr/bin/env cram
+# SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+# SPDX-License-Identifier: MPL-2.0
+
+ $ export path="$TESTDIR/../cmd"
+
+
+ $ test "$($path/realpath .)" = "$(pwd)"
+ $ $path/realpath /
+ /
+ $ $path/realpath /var
+ /var
+ $ $path/realpath /var/
+ /var
+ $ $path/realpath /var/empty
+ /var/empty
+ $ $path/realpath /var/empty/foo
+ /var/empty/foo
+ $ $path/realpath /var/empty/foo/
+ /var/empty/foo
+ $ $path/realpath /var/empty/foo//
+ /var/empty/foo
+ $ $path/realpath /var/empty/foo/bar
+ realpath: "/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
+ [1]
+ $ $path/realpath /var/empty/foo/bar//
+ realpath: "/var/empty/foo/bar": No such file or directory
+ [1]
+
+ $ test "$($path/realpath .)" = "$(pwd)"
+ $ $path/realpath /
+ /
+ $ $path/realpath /var
+ /var
+ $ $path/realpath /var/
+ /var
+ $ $path/realpath /var/empty
+ /var/empty
+ $ $path/realpath /var/empty/foo
+ /var/empty/foo
+ $ $path/realpath /var/empty/foo/
+ /var/empty/foo
+ $ $path/realpath /var/empty/foo//
+ /var/empty/foo
+ $ $path/realpath /var/empty/foo/bar
+ realpath: "/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
+ [1]
+ $ $path/realpath /var/empty/foo/bar//
+ realpath: "/var/empty/foo/bar": No such file or directory
+ [1]
+
+
+ $ test "$($path/realpath -e .)" = "$(pwd)"
+ $ $path/realpath -e /
+ /
+ $ $path/realpath -e /var
+ /var
+ $ $path/realpath -e /var/
+ /var
+ $ $path/realpath -e /var/empty
+ /var/empty
+ $ $path/realpath -e /var/empty/foo
+ realpath: "/var/empty/foo": No such file or directory
+ [1]
+ $ $path/realpath -e /var/empty/foo/
+ realpath: "/var/empty/foo/": No such file or directory
+ [1]
+ $ $path/realpath -e /var/empty/foo//
+ realpath: "/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
+ [1]
+ $ $path/realpath -e /var/empty/foo/bar/
+ realpath: "/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
+ [1]
+
+Non-directory
+
+ $ $path/realpath /dev/null
+ /dev/null
+ $ $path/realpath -e /dev/null
+ /dev/null
+ $ $path/realpath -E /dev/null
+ /dev/null
+
+ $ $path/realpath /dev/null/
+ realpath: "/dev/null/": Not a directory
+ [1]
+ $ $path/realpath -e /dev/null/
+ realpath: "/dev/null/": Not a directory
+ [1]
+ $ $path/realpath -E /dev/null/
+ realpath: "/dev/null/": Not a directory
+ [1]
+
+ $ $path/realpath /dev/null/..
+ realpath: "/dev/null/..": Not a directory
+ [1]
+ $ $path/realpath -e /dev/null/..
+ realpath: "/dev/null/..": Not a directory
+ [1]
+ $ $path/realpath -E /dev/null/..
+ realpath: "/dev/null/..": Not a directory
+ [1]
+
+As required by "IEEE P1003.1™-202x/D4" -E and -e aren't errorneous
+
+ $ $path/realpath -E -e /dev/null
+ /dev/null