From 04997468ec9e43ee4c0dac9da405b0b79829756b Mon Sep 17 00:00:00 2001 From: hja Date: Thu, 29 Oct 2026 15:14:29 +0000 Subject: [PATCH 1/4] fix: [BUG] -t | -w suppress warning message, but help text is still shown (#312) Resolves #312 --- arg-20f1a0cd.o.tmp | 0 arg-6f8e8082.o.tmp | 0 src/arg.c | 21 ++++++++++++++------- src/main.c | 4 ++++ 4 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 arg-20f1a0cd.o.tmp create mode 100644 arg-6f8e8082.o.tmp diff --git a/arg-20f1a0cd.o.tmp b/arg-20f1a0cd.o.tmp new file mode 100644 index 00000000..e69de29b diff --git a/arg-6f8e8082.o.tmp b/arg-6f8e8082.o.tmp new file mode 100644 index 00000000..e69de29b diff --git a/src/arg.c b/src/arg.c index 44aec7ee..fbb58be0 100644 --- a/src/arg.c +++ b/src/arg.c @@ -33,6 +33,8 @@ #include "lsof.h" #include +extern int SoftErr; + /* * Local definitions */ @@ -587,7 +589,7 @@ int enter_efsys(struct lsof_context *ctx, /* context */ (void)fprintf(stderr, "%s: -e not followed by a file system path: \"%s\"\n", Pn, e); - return (1); + SoftErr = 1; return (1); } if (!(ec = mkstrcpy(e, (MALLOC_S *)NULL))) { (void)fprintf(stderr, "%s: no space for -e string: ", Pn); @@ -597,8 +599,10 @@ int enter_efsys(struct lsof_context *ctx, /* context */ if (rdlnk) path = ec; else { - if (!(path = Readlink(ctx, ec))) + if (!(path = Readlink(ctx, ec))) { + SoftErr = 1; return (1); + } } /* * Remove terminating `/' characters from paths longer than one. @@ -734,6 +738,7 @@ static int enter_fd_lst(struct lsof_context *ctx, /* context */ excl ? "exclude" : "include", FdlTy ? "exclude" : "include", buf); } + SoftErr = 1; return (1); } } @@ -869,10 +874,12 @@ int enter_dir(struct lsof_context *ctx, /* context */ if (!Fwarn) (void)fprintf(stderr, "%s: +d not followed by a directory path\n", Pn); - return (1); + SoftErr = 1; return (1); } - if (!(dn = Readlink(ctx, d))) + if (!(dn = Readlink(ctx, d))) { + SoftErr = 1; return (1); + } if (statsafely(ctx, dn, &sb)) { if (!Fwarn) { en = errno; @@ -884,7 +891,7 @@ int enter_dir(struct lsof_context *ctx, /* context */ (void)free((FREE_P *)dn); dn = (char *)NULL; } - return (1); + SoftErr = 1; return (1); } if ((sb.st_mode & S_IFMT) != S_IFDIR) { if (!Fwarn) { @@ -895,7 +902,7 @@ int enter_dir(struct lsof_context *ctx, /* context */ (void)free((FREE_P *)dn); dn = (char *)NULL; } - return (1); + SoftErr = 1; return (1); } #if defined(HASSPECDEVD) @@ -1114,7 +1121,7 @@ int enter_dir(struct lsof_context *ctx, /* context */ "%s: WARNING: no files found in directory: ", Pn); safestrprt(d, stderr, 1); } - return (1); + SoftErr = 1; return (1); } return (0); } diff --git a/src/main.c b/src/main.c index a82bbac4..466f03f1 100644 --- a/src/main.c +++ b/src/main.c @@ -103,6 +103,8 @@ static void select_default_fields(void) { * main() - main function for lsof */ +int SoftErr = 0; + int main(int argc, char *argv[]) { enum ExitStatus rv; int gopt_rv; @@ -1178,6 +1180,8 @@ int main(int argc, char *argv[]) { select_default_fields(); } + if (err && Fwarn && SoftErr && !Fhelp && !fh && !version && !DChelp) + Error(ctx); if (DChelp || err || Fhelp || fh || version) usage(ctx, err ? 1 : 0, fh, version); /* From 0a0f08261d99bb936a10bb44616a48fb99409210 Mon Sep 17 00:00:00 2001 From: hja Date: Mon, 2 Nov 2026 07:04:01 +0000 Subject: [PATCH 2/4] chore: remove accidentally committed .tmp files --- arg-20f1a0cd.o.tmp | 0 arg-6f8e8082.o.tmp | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 arg-20f1a0cd.o.tmp delete mode 100644 arg-6f8e8082.o.tmp diff --git a/arg-20f1a0cd.o.tmp b/arg-20f1a0cd.o.tmp deleted file mode 100644 index e69de29b..00000000 diff --git a/arg-6f8e8082.o.tmp b/arg-6f8e8082.o.tmp deleted file mode 100644 index e69de29b..00000000 From 5613b462d009ce75f9f3f5d6c9bd24e71bead1bd Mon Sep 17 00:00:00 2001 From: hja Date: Mon, 2 Nov 2026 07:43:28 +0000 Subject: [PATCH 3/4] ci: trigger CI run From dce470b1bdc04fb3db858d9d18afe3b68db0265a Mon Sep 17 00:00:00 2001 From: hja Date: Mon, 2 Nov 2026 19:34:41 +0000 Subject: [PATCH 4/4] Replace global soft-error flag with named argument result Preserve hard-error precedence through argument helpers and keep warning-level failures local to argument processing. Add regression coverage for suppressed warnings, explicit help, option ordering, and mixed FD-list errors. --- Makefile.am | 1 + lib/common.h | 7 ++++ src/arg.c | 37 +++++++++---------- src/main.c | 37 ++++++++++++++----- tests/case-20-soft-errors.bash | 66 ++++++++++++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 26 deletions(-) create mode 100755 tests/case-20-soft-errors.bash diff --git a/Makefile.am b/Makefile.am index 75086ca3..1fb45a7a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -130,6 +130,7 @@ DIALECT_NEUTRAL_TESTS = tests/case-00-hello.bash \ tests/case-20-exit-status.bash \ tests/case-20-fd-only-inclusion.bash \ tests/case-20-handle-missing-files.bash \ + tests/case-20-soft-errors.bash \ tests/case-20-offset-field.bash \ tests/case-20-repeat-count.bash \ tests/case-21-exit-Q-status.bash \ diff --git a/lib/common.h b/lib/common.h index 9db3c54f..1be025ab 100644 --- a/lib/common.h +++ b/lib/common.h @@ -590,6 +590,13 @@ enum ExitStatus { # define LSOF_SEARCH_FAILURE \ (FsearchErr ? LSOF_EXIT_ERROR : LSOF_EXIT_SUCCESS) +/* + * Argument helper results: 0 = success, 1 = hard error, + * SOFT_ERR = warning-level failure. Hard errors take precedence + * when combining results. + */ +enum { SOFT_ERR = 2 }; + /* * Structure definitions */ diff --git a/src/arg.c b/src/arg.c index eed277de..e47977b2 100644 --- a/src/arg.c +++ b/src/arg.c @@ -33,8 +33,6 @@ #include "lsof.h" #include -extern int SoftErr; - /* * Local definitions */ @@ -599,7 +597,7 @@ int enter_efsys(struct lsof_context *ctx, /* context */ (void)fprintf(stderr, "%s: -e not followed by a file system path: \"%s\"\n", Pn, e); - SoftErr = 1; return (1); + return (SOFT_ERR); } if (!(ec = mkstrcpy(e, (MALLOC_S *)NULL))) { (void)fprintf(stderr, "%s: no space for -e string: ", Pn); @@ -610,9 +608,8 @@ int enter_efsys(struct lsof_context *ctx, /* context */ path = ec; else { if (!(path = Readlink(ctx, ec))) { - SoftErr = 1; (void)free((FREE_P *)ec); - return (1); + return (SOFT_ERR); } if (Readlink_op == ec) Readlink_op = (char *)NULL; @@ -656,6 +653,7 @@ int enter_fd(struct lsof_context *ctx, /* context */ { char c, *cp1, *cp2, *dash; int err, excl, hi, lo; + int result, soft_err = 0; char *fc; /* * Check for non-empty list and make a copy. @@ -696,11 +694,17 @@ int enter_fd(struct lsof_context *ctx, /* context */ if (ckfd_range(ctx, cp1, dash, cp2, &lo, &hi)) err = 1; else { - if (enter_fd_lst(ctx, (char *)NULL, lo, hi, excl)) + result = enter_fd_lst(ctx, (char *)NULL, lo, hi, excl); + if (result == SOFT_ERR) + soft_err = 1; + else if (result) err = 1; } } else { - if (enter_fd_lst(ctx, cp1, 0, 0, excl)) + result = enter_fd_lst(ctx, cp1, 0, 0, excl); + if (result == SOFT_ERR) + soft_err = 1; + else if (result) err = 1; } } @@ -709,7 +713,7 @@ int enter_fd(struct lsof_context *ctx, /* context */ cp1 = cp2 + 1; } (void)free((FREE_P *)fc); - return (err); + return (err ? 1 : (soft_err ? SOFT_ERR : 0)); } /* @@ -752,8 +756,7 @@ static int enter_fd_lst(struct lsof_context *ctx, /* context */ excl ? "exclude" : "include", FdlTy ? "exclude" : "include", buf); } - SoftErr = 1; - return (1); + return (SOFT_ERR); } } /* @@ -888,12 +891,10 @@ int enter_dir(struct lsof_context *ctx, /* context */ if (!Fwarn) (void)fprintf(stderr, "%s: +d not followed by a directory path\n", Pn); - SoftErr = 1; return (1); - } - if (!(dn = Readlink(ctx, d))) { - SoftErr = 1; - return (1); + return (SOFT_ERR); } + if (!(dn = Readlink(ctx, d))) + return (SOFT_ERR); if (statsafely(ctx, dn, &sb)) { if (!Fwarn) { en = errno; @@ -905,7 +906,7 @@ int enter_dir(struct lsof_context *ctx, /* context */ (void)free((FREE_P *)dn); dn = (char *)NULL; } - SoftErr = 1; return (1); + return (SOFT_ERR); } if ((sb.st_mode & S_IFMT) != S_IFDIR) { if (!Fwarn) { @@ -916,7 +917,7 @@ int enter_dir(struct lsof_context *ctx, /* context */ (void)free((FREE_P *)dn); dn = (char *)NULL; } - SoftErr = 1; return (1); + return (SOFT_ERR); } #if defined(HASSPECDEVD) @@ -1141,7 +1142,7 @@ int enter_dir(struct lsof_context *ctx, /* context */ "%s: WARNING: no files found in directory: ", Pn); safestrprt(d, stderr, 1); } - SoftErr = 1; return (1); + return (SOFT_ERR); } return (0); } diff --git a/src/main.c b/src/main.c index d22297f4..a7a9dcb9 100644 --- a/src/main.c +++ b/src/main.c @@ -103,14 +103,14 @@ static void select_default_fields(void) { * main() - main function for lsof */ -int SoftErr = 0; - int main(int argc, char *argv[]) { enum ExitStatus rv; int gopt_rv; int ad, c, i, n, se1, se2, ss; + int arg_result; char *cp; int err = 0; + int soft_err = 0; enum ExitStatus ev = LSOF_EXIT_SUCCESS; int fh = 0; char *fmtr = (char *)NULL; @@ -357,20 +357,29 @@ int main(int argc, char *argv[]) { #endif /* defined(HASNCACHE) */ case 'd': if (GOp == '+') { - if (enter_dir(ctx, GOv, 0)) + arg_result = enter_dir(ctx, GOv, 0); + if (arg_result == SOFT_ERR) + soft_err = 1; + else if (arg_result) err = 1; else { Selflags |= SELNM; xover = 1; } } else { - if (enter_fd(ctx, GOv)) + arg_result = enter_fd(ctx, GOv); + if (arg_result == SOFT_ERR) + soft_err = 1; + else if (arg_result) err = 1; } break; case 'D': if (GOp == '+') { - if (enter_dir(ctx, GOv, 1)) + arg_result = enter_dir(ctx, GOv, 1); + if (arg_result == SOFT_ERR) + soft_err = 1; + else if (arg_result) err = 1; else { Selflags |= SELNM; @@ -390,7 +399,10 @@ int main(int argc, char *argv[]) { #if defined(HASEOPT) case 'e': - if (enter_efsys(ctx, GOv, ((GOp == '+') ? 1 : 0))) + arg_result = enter_efsys(ctx, GOv, ((GOp == '+') ? 1 : 0)); + if (arg_result == SOFT_ERR) + soft_err = 1; + else if (arg_result) err = 1; break; #endif /* defined(HASEOPT) */ @@ -1183,10 +1195,19 @@ int main(int argc, char *argv[]) { select_default_fields(); } - if (err && Fwarn && SoftErr && !Fhelp && !fh && !version && !DChelp) - Error(ctx); + /* + * Preserve usage output for unsuppressed soft failures. + * Hard errors and explicit help requests always reach usage(). + */ + if (soft_err && !Fwarn) + err = 1; if (DChelp || err || Fhelp || fh || version) usage(ctx, err ? 1 : 0, fh, version); + /* + * Suppressing warnings does not make the failed argument valid. + */ + if (soft_err) + Error(ctx); /* * Reduce the size of Suid[], if necessary. */ diff --git a/tests/case-20-soft-errors.bash b/tests/case-20-soft-errors.bash new file mode 100755 index 00000000..0fd6ea25 --- /dev/null +++ b/tests/case-20-soft-errors.bash @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Warning-level argument failures must not hide unrelated usage errors. +source tests/common.bash + +work=$(mktemp -d "${TMPDIR:-/tmp}/lsof-soft-errors.XXXXXX") || exit 1 +trap 'rm -rf "$work"' EXIT +touch "$work/file" || exit 1 +missing=$work/missing + +check_output() { + local expected=$1 + shift + local output status expected_status=1 + [[ $expected == help ]] && expected_status=0 + output=$("$lsof" "$@" 2>&1) + status=$? + if [[ $status -ne $expected_status ]]; then + echo "unexpected exit status $status: $*" + return 1 + fi + case $expected in + quiet) + [[ -z $output ]] && return 0 + ;; + warning) + [[ $output == *WARNING:* && $output == *usage:* ]] && return 0 + ;; + diagnostic) + [[ $output == *WARNING:* && $output != *usage:* ]] && return 0 + ;; + usage|help) + [[ $output == *usage:* ]] && return 0 + ;; + esac + echo "expected $expected output: $*" + echo "$output" + return 1 +} + +{ + for option in +d +D; do + for path in "$missing" "$work/file"; do + check_output warning "$option" "$path" || exit 1 + for flag in -w -t; do + check_output quiet "$flag" "$option" "$path" || exit 1 + # A later flag suppresses usage, not an already printed warning. + check_output diagnostic "$option" "$path" "$flag" || exit 1 + # Illegal FD ranges are portable hard argument errors. + check_output usage "$flag" -d 3-1 "$option" "$path" || exit 1 + check_output usage "$flag" "$option" "$path" -d 3-1 || exit 1 + check_output help "$flag" "$option" "$path" -h || exit 1 + done + done + done + check_output quiet -w +d "$missing" +D "$missing" || exit 1 + check_output usage -w +D "$missing" +w || exit 1 + # Preserve soft results through enter_fd(), including range entries. + for list in '0,^1' '0,^1-2'; do + check_output quiet -w -d "$list" || exit 1 + check_output usage -d "$list" || exit 1 + done + # Hard errors must win in either order within one FD list. + check_output usage -w -d '0,^1,3-1' || exit 1 + check_output usage -w -d '0,3-1,^1' || exit 1 + echo "soft-error argument tests passed" +} > "$report" 2>&1