Collin Funk <collin.fu...@gmail.com> writes: > I'll have a look at implementing this behavior. But figured it was worth > sending on bug-coreutils for tracking like the rest of the missing POSIX > 2024 features.
I have attached a proposed patch. I followed the POSIX recommendation of using the exit status 0 - 124 for the number of cycles and 125 for program errors (e.g. closing standard output). But I have hidden the 125 exit status behind POSIXLY_CORRECT. To avoid breaking something like this: tsort input-file if test $? -eq 1; then echo do something fi However, that means that when POSIXLY_CORRECT is not defined the exit status is ambiguous as shown in the following example: # Input with a cycle. $ printf 'a b\nb a\n' | ./src/tsort tsort: -: input contains a loop: tsort: a tsort: b a b $ echo $? 1 # Program error. $ echo 'a a' | ./src/tsort > /dev/full tsort: write error: No space left on device $ echo $? 1 If we can agree on that behavior, I'll probably move tests/misc/tsort.pl to a separate tsort directory in tests. And then add a shell script to test the behavior of POSIXLY_CORRECT on the exit status using /dev/full. Collin
>From 96a822612454ace9f4369857c9cb223f835a27f7 Mon Sep 17 00:00:00 2001 Message-ID: <96a822612454ace9f4369857c9cb223f835a27f7.1755146320.git.collin.fu...@gmail.com> From: Collin Funk <collin.fu...@gmail.com> Date: Wed, 13 Aug 2025 21:27:29 -0700 Subject: [PATCH] tsort: implement -w as required by POSIX 2024 * NEWS: Mention the new option. * doc/coreutils.texi (Exit status): Mention tsort in the list of programs which deviates from the normal conventions. (tsort invocation): Document the option. Document the possible exit statuses. * src/tsort.c: Include getopt.h. (cycle_count, long_options): New variables. (MAX_CYCLES): New enum constant. (usage): Add the option to the help message. (tsort): Count the number of cycles to a maximum of MAX_CYCLES. Use it as the exit status if -w is in use. (main): If POSIXLY_CORRECT always use the 125 exit status to indicate a program error. If not, allow the ambiguous 1 to indicate a program error or cycle count of 1. * tests/misc/tsort.pl (@Tests): Add a simple test case. --- NEWS | 4 ++++ doc/coreutils.texi | 32 +++++++++++++++++++++++++--- src/tsort.c | 52 ++++++++++++++++++++++++++++++++++++++++++--- tests/misc/tsort.pl | 4 ++++ 4 files changed, 86 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index bfde1e62d..f10b2096c 100644 --- a/NEWS +++ b/NEWS @@ -84,6 +84,10 @@ GNU coreutils NEWS -*- outline -*- the same as realpath with no options. The corresponding long option is --canonicalize. + tsort now supports the -w option to set the exit status to the number + of cycles in the input, as required by POSIX 2024. The corresponding + long option is --count-cycles. + ** Improvements 'factor' is now much faster at identifying large prime numbers, diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 4f54770ec..18110c4dd 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -1537,7 +1537,7 @@ @node Exit status @command{chroot}, @command{env}, @command{expr}, @command{ls}, @command{nice}, @command{nohup}, @command{numfmt}, @command{printenv}, @command{runcon}, @command{sort}, @command{stdbuf}, @command{test}, -@command{timeout}, @command{tty}. +@command{timeout}, @command{tsort}, @command{tty}. @node Floating point @section Floating point numbers @@ -6280,10 +6280,36 @@ @node tsort invocation @code{parse_options} may be placed anywhere in the list as long as it precedes @code{main}. -The only options are @option{--help} and @option{--version}. @xref{Common +The program accepts the following options. Also see @ref{Common options}. -@exitstatus +@table @samp + +@item -w +@itemx --count-cycles +@opindex -w +@opindex --count-cycles +Set the exit status to the number of cycles found in the input up to a +maximum of 124. + +@end table + + +@cindex exit status of @command{tsort} +Exit status: + +@vindex POSIXLY_CORRECT +@display +0 if no error occurred. +1 if an error occurred and the @env{POSIXLY_CORRECT} environment +variable is not set. +125 an error occurred when invoked using @option{-w} option or the +@env{POSIXLY_CORRECT} environment variable set. +@end display + +When invoked using the @option{-w} option, the exit status can be any +value between 0 and 124 corresponding to the number cycles found in the +input. @menu * tsort background:: Where tsort came from. diff --git a/src/tsort.c b/src/tsort.c index 2377f7082..9ea0d17b5 100644 --- a/src/tsort.c +++ b/src/tsort.c @@ -23,6 +23,7 @@ #include <config.h> #include <sys/types.h> +#include <getopt.h> #include "system.h" #include "assure.h" @@ -71,6 +72,20 @@ static struct item *loop = nullptr; /* The number of strings to sort. */ static size_t n_strings = 0; +/* Number of cycles or -1 if they should not be counted. */ +static int cycle_count = -1; + +/* Only count to 124 cycles, as recommended by POSIX 2024. */ +enum { MAX_CYCLES = 124 }; + +static struct option const long_options[] = +{ + {"count-cycles", no_argument, nullptr, 'w'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {nullptr, 0, nullptr, 0} +}; + void usage (int status) { @@ -86,7 +101,8 @@ Write totally ordered list consistent with the partial ordering in FILE.\n\ emit_stdin_note (); fputs (_("\ -\n\ + -w, --count-cycles set the exit status to the number of cycles in\n\ + the input\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); @@ -513,6 +529,9 @@ tsort (char const *file) error (0, 0, _("%s: input contains a loop:"), quotef (file)); ok = false; + if (0 <= cycle_count && cycle_count < MAX_CYCLES) + ++cycle_count; + /* Print the loop and remove a relation to break it. */ do walk_tree (root, detect_loop); @@ -521,10 +540,10 @@ tsort (char const *file) } if (fclose (stdin) != 0) - error (EXIT_FAILURE, errno, "%s", + error (0 <= cycle_count ? MAX_CYCLES + 1 : EXIT_FAILURE, errno, "%s", is_stdin ? _("standard input") : quotef (file)); - exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); + exit (ok ? EXIT_SUCCESS : 0 <= cycle_count ? cycle_count : EXIT_FAILURE); } int @@ -536,8 +555,35 @@ main (int argc, char **argv) bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); + /* If POSIXLY_CORRECT is defined differentiate between program errors + and a possible cycle count of 1. */ + int exit_internal_failure = (getenv ("POSIXLY_CORRECT") + ? MAX_CYCLES + 1 : EXIT_FAILURE); + initialize_exit_failure (exit_internal_failure); atexit (close_stdout); + while (true) + { + int c = getopt_long (argc, argv, "w", long_options, nullptr); + + if (c == -1) + break; + + switch (c) + { + case 'w': + cycle_count = 0; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + parse_gnu_standard_options_only (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version, true, usage, AUTHORS, (char const *) nullptr); diff --git a/tests/misc/tsort.pl b/tests/misc/tsort.pl index f1ca28a08..b497fd9a4 100755 --- a/tests/misc/tsort.pl +++ b/tests/misc/tsort.pl @@ -31,6 +31,10 @@ my @Tests = ['cycle-2', {IN => {f => "t x\nt s\ns t\n"}}, {OUT => "s\nt\nx\n"}, {EXIT => 1}, {ERR => "tsort: f: input contains a loop:\ntsort: s\ntsort: t\n"} ], + ['cycle-3', '-w', {IN => {f => "a a\na b\na c\nc a\nb a"}}, + {OUT => "a\nc\nb\n"}, {EXIT => 2}, + {ERR => "tsort: f: input contains a loop:\ntsort: a\ntsort: b\n" + . "tsort: f: input contains a loop:\ntsort: a\ntsort: c\n"} ], ['posix-1', {IN => "a b c c d e\ng g\nf g e f\nh h\n"}, {OUT => "a\nc\nd\nh\nb\ne\nf\ng\n"}], -- 2.50.1