logo

utils-std

Collection of commonly available Unix tools
commit: e860f5632e9e06e1e627ae070a793e20936970dd
parent 673f62ec770c01988df49e848896beb9eb501a02
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Sat, 23 Mar 2024 05:09:52 +0100

cmd/realpath: New

Diffstat:

Acmd/realpath.155+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/realpath.c119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcoreutils.txt2+-
Atest-cmd/realpath.t119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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