logo

utils-std

Collection of commonly available Unix tools
commit: a49b8b759cad50d7b325cd9c66bd5d0b0473bf2f
parent 955e8d080f4a2b518fac93bd437fae1882788138
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Fri, 19 Apr 2024 07:45:43 +0200

cmd/pathchk: new

Diffstat:

Acmd/pathchk.168++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/pathchk.c153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcoreutils.txt2+-
Mlsb_commands.txt2+-
Mposix_utilities.txt2+-
Atest-cmd/pathchk.t39+++++++++++++++++++++++++++++++++++++++
6 files changed, 263 insertions(+), 3 deletions(-)

diff --git a/cmd/pathchk.1 b/cmd/pathchk.1 @@ -0,0 +1,68 @@ +.\" 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-04-19 +.Dt PATHCHK 1 +.Os +.Sh NAME +.Nm pathchk +.Nd check pathname validity +.Sh SYNOPSIS +.Nm +.Op Fl pP +.Ar pathname ... +.Sh DESCRIPTION +.Nm +checks each +.Ar pathname +for their validity and portability. +.Pp +By default, +.Nm +does the following checks, based on the underlying filesystem: +.Bl -bullet +.It +Isn't longer than PATH_MAX +.It +Doesn't have any path component longer than NAME_MAX +.It +Is reachable to the current user +.It +Doesn't contains invalid bytes +.El +.Pp +Note that non-existing path components aren't seen as an error by +.Nm . +.Sh OPTIONS +.Bl -tag -width ee +.It Fl p +Do not perform checks against the underlying filesystem, meaning: +.Bl -bullet -compact +.It +_POSIX_PATH_MAX is used instead of PATH_MAX +.It +_POSIX_NAME_MAX is used instead of NAME_MAX +.It +Directories aren't checked for user reachability +.It +POSIX Portable Character Set is the only reference for invalid byte sequences +.El +.It Fl P +Perform the following additional checks: +.Bl -bullet -compact +.It +Doesn't contains a path component whose first character is the <hyphen-minus> character +.It +Isn't empty +.El +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr test 1 +.Sh STANDARDS +.Nm +should be compliant with +IEEE P1003.1-202x/D4 (“POSIX.1”). +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact+utils@hacktivis.me diff --git a/cmd/pathchk.c b/cmd/pathchk.c @@ -0,0 +1,153 @@ +// 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 <assert.h> +#include <errno.h> +#include <limits.h> // PATH_MAX +#include <stdbool.h> +#include <stdio.h> // fprintf +#include <string.h> // strerror +#include <sys/stat.h> // lstat +#include <unistd.h> // getopt + +// POSIX Portable Character Set +// Returns 0 on success, or the first invalid character found +static char +str_pcs(char *str) +{ + for(size_t i = 0; i < strlen(str); i++) + { + char c = str[i]; + if(c >= 0x07 && c <= 0x0D) continue; + if(c >= 0x20 && c <= 0x7E) continue; + + return c; + } + + return 0; +} + +static void +usage() +{ + fprintf(stderr, "Usage: pathchk [-p] [-P] pathname...\n"); +} + +int +main(int argc, char *argv[]) +{ + bool opt_P = false, opt_p = false; + size_t path_max = PATH_MAX; + size_t name_max = NAME_MAX; + + int c = -1; + while((c = getopt(argc, argv, ":pP")) != -1) + { + switch(c) + { + case 'P': + opt_P = true; + break; + case 'p': + opt_p = true; + path_max = _POSIX_PATH_MAX; + name_max = _POSIX_NAME_MAX; + 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; + } + + int err = 0; + + for(int i = 0; i < argc; i++) + { + char *path = argv[i]; + size_t len = strlen(path); + + if(opt_P && len == 0) + { + fprintf(stderr, "pathchk: Operand number %d is empty\n", i); + err = 1; + } + + if(len > path_max) + { + fprintf(stderr, + "pathchk: Path (%zd octets) is over the maximum size (%zd octets): %s\n", + len, + path_max, + path); + err = 1; + } + + char *p = strtok(path, "/"); + do + { + if(p == NULL || p[0] == 0) break; + if(p[0] == '-' && opt_P) + { + fprintf(stderr, "pathchk: Path component starts with an hyphen: %s\n", p); + err = 1; + } + size_t name_len = strlen(p); + if(name_len > name_max) + { + fprintf(stderr, + "pathchk: Path component (%zd octets) is over the maximum size (%zd octets): %s\n", + name_len, + name_max, + p); + err = 1; + } + } while((p = strtok(NULL, "/")) != NULL); + + if(!opt_p) + { + assert(errno == 0); + /* flawfinder: ignore, doesn't do any other filesystem interaction afterwards */ + if(access(path, F_OK) < 0 && errno != ENOENT) + { + fprintf(stderr, + "pathchk: Error checking while checking '%s' against filesystem: %s\n", + path, + strerror(errno)); + errno = 0; + err = 1; + } + } + else + { + char c_pcs = str_pcs(path); + if(c_pcs != 0) + { + fprintf(stderr, + "pathchk: Error non-portable character '%c' (0x%02x) found in: %s\n", + c_pcs, + c_pcs, + path); + err = 1; + } + } + } + + return err; +} diff --git a/coreutils.txt b/coreutils.txt @@ -55,7 +55,7 @@ nproc: ? numfmt: Maybe, consider humanize(1) od: Todo paste: Todo -pathchk: ? +pathchk: Done pinky: No pr: No. Considered obsolete printenv: No, the output sucks diff --git a/lsb_commands.txt b/lsb_commands.txt @@ -93,7 +93,7 @@ od: Todo passwd: out of scope paste: Todo patch: out of scope -pathchk: ? +pathchk: Done pax: out of scope pidof: ? pr: No. Considered obsolete diff --git a/posix_utilities.txt b/posix_utilities.txt @@ -92,7 +92,7 @@ nohup: done od paste patch: no -pathchk +pathchk: done pax: no pr: no printf diff --git a/test-cmd/pathchk.t b/test-cmd/pathchk.t @@ -0,0 +1,39 @@ +#!/usr/bin/env cram +# SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 + + $ export PATH="$TESTDIR/../cmd:$PATH" + + $ test "$(command -v pathchk)" = "$TESTDIR/../cmd/pathchk" + + $ pathchk "$(printf "foo/%*s/bar" "$(getconf NAME_MAX "$PWD")" | tr ' ' _)" + $ pathchk "$(printf "foo/s%*s/bar" "$(getconf NAME_MAX "$PWD")" | tr ' ' _)" + pathchk: Path component \([0-9]+ octets\) is over the maximum size \([0-9]+ octets\): s_+ (re) + [1] + + $ pathchk -p "$(printf "foo/%*s/bar" "$(getconf _POSIX_NAME_MAX)" | tr ' ' _)" + $ pathchk -p "$(printf "foo/s%*s/bar" "$(getconf _POSIX_NAME_MAX)" | tr ' ' _)" + pathchk: Path component \([0-9]+ octets\) is over the maximum size \([0-9]+ octets\): s_+ (re) + [1] + + $ pathchk "$(printf "s%*s" "$(getconf PATH_MAX "$PWD")" | tr ' ' /)" + pathchk: Path \([0-9]+ octets\) is over the maximum size \([0-9]+ octets\): s/+ (re) + [1] + + $ pathchk -p "$(printf "%*s" "$(getconf _POSIX_PATH_MAX)" | tr ' ' /)" + $ pathchk -p "$(printf "s%*s" "$(getconf _POSIX_PATH_MAX)" | tr ' ' /)" + pathchk: Path \([0-9]+ octets\) is over the maximum size \([0-9]+ octets\): s/+ (re) + [1] + + $ pathchk /dev/null/foo + $ pathchk /../foo + $ pathchk -P ./-foo + pathchk: Path component starts with an hyphen: -foo + [1] + $ pathchk -P ./bar/-foo + pathchk: Path component starts with an hyphen: -foo + [1] + + $ pathchk -p "foo$(printf '\01')bar" + pathchk: Error non-portable character '\x01' (0x01) found in: foo\x01bar (esc) + [1]