From a911476f59fc3c332a5f91548f2954e04032c3a6 Mon Sep 17 00:00:00 2001 From: Nicholas De Nova Date: Thu, 29 Sep 2016 20:10:58 -0500 Subject: [PATCH] Add readlink(1). --- utils/.gitignore | 1 + utils/Makefile | 6 +++ utils/readlink.1 | 36 ++++++++++++++ utils/readlink.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 utils/readlink.1 create mode 100644 utils/readlink.c diff --git a/utils/.gitignore b/utils/.gitignore index 8a5d2ffe..cdcca689 100644 --- a/utils/.gitignore +++ b/utils/.gitignore @@ -35,6 +35,7 @@ passwd ps pstree pwd +readlink realpath rm rmdir diff --git a/utils/Makefile b/utils/Makefile index 53f10fe3..fa75644a 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -47,6 +47,7 @@ passwd \ ps \ pstree \ pwd \ +readlink \ realpath \ rm \ rmdir \ @@ -72,6 +73,9 @@ BINARIES=\ $(BINARIES_EXCEPT_INSTALL) \ xinstall +MANPAGES=\ +readlink.1 + all: $(BINARIES) .PHONY: all install clean @@ -80,6 +84,8 @@ install: all mkdir -p $(DESTDIR)$(BINDIR) install $(BINARIES_EXCEPT_INSTALL) $(DESTDIR)$(BINDIR) install xinstall $(DESTDIR)$(BINDIR)/install + mkdir -p $(DESTDIR)$(MANDIR)/man1 + cp $(MANPAGES) $(DESTDIR)$(MANDIR)/man1 %: %.c $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $< -o $@ diff --git a/utils/readlink.1 b/utils/readlink.1 new file mode 100644 index 00000000..14025c5d --- /dev/null +++ b/utils/readlink.1 @@ -0,0 +1,36 @@ +.Dd September 29, 2016 +.Dt READLINK 1 +.Os +.Sh NAME +.Nm readlink +.Nd print symbolic link target +.Sh SYNOPSIS +.Nm +.Op Fl fn +.Ar path +.Sh DESCRIPTION +.Nm +prints the target of the symbolic link specified by +.Ar path . +.Pp +The options are as follows: +.Bl -tag -width "12345678" +.It Fl f , Fl \-canonicalize +Print the absolute path of +.Ar path +(which need not be a symbolic link) with all symbolic links expanded, +and no +.Pa \&. +and +.Pa .. +path components. +.It Fl n , Fl \-no-newline +Do not append a newline. +.El +.Sh EXIT STATUS +.Nm +will exit with 0 on success and non-zero otherwise. +.Sh SEE ALSO +.Xr readlink 1 , +.Xr readlink 3 , +.Xr realpath 3 diff --git a/utils/readlink.c b/utils/readlink.c new file mode 100644 index 00000000..580a73c6 --- /dev/null +++ b/utils/readlink.c @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2016 Nicholas De Nova. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * readlink.c + * Print symbolic link target. + */ + +#include +#include +#include +#include +#include +#include +#include + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + { + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } + } +} + +int main(int argc, char* argv[]) +{ + bool append_newline = true; + bool canonicalize = false; + + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' || !arg[1] ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + char c; + while ( (c = *++arg) ) + switch ( c ) + { + case 'f': canonicalize = true; break; + case 'n': append_newline = false; break; + default: + errx(1, "unknown option -- '%c'", c); + } + } + else if ( !strcmp(arg, "--canonicalize") ) + canonicalize = true; + else if ( !strcmp(arg, "--no-newline") ) + append_newline = false; + else + errx(1, "unknown option: %s", arg); + } + + compact_arguments(&argc, &argv); + + if ( argc < 2 ) + errx(1, "missing path operand"); + else if ( argc > 2 ) + errx(1, "unexpected extra operand"); + + char* buffer; + if ( canonicalize ) + { + buffer = realpath(argv[1], NULL); + if ( !buffer ) + err(1, "%s", argv[1]); + } + else + { + size_t buffer_size = 4096; + while ( true ) + { + buffer = malloc(buffer_size); + if ( !buffer ) + err(1, "malloc"); + + ssize_t path_size = readlink(argv[1], buffer, buffer_size); + if ( path_size < 0 ) + err(1, "%s", argv[1]); + + if ( (size_t) path_size == buffer_size ) + { + free(buffer); + if ( SIZE_MAX / 2 < buffer_size ) + { + errno = ENOMEM; + err(1, "malloc"); + return 1; + } + buffer_size *= 2; + continue; + } + + buffer[path_size] = '\0'; + break; + } + } + + fputs(buffer, stdout); + if ( append_newline ) + putchar('\n'); + + if ( ferror(stdout) || fflush(stdout) == EOF ) + err(1, "stdout"); + + return 0; +}