Collin Funk <[email protected]> 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 <[email protected]>
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