bug#79232: tsort doesn't support -w added by POSIX 2024.

Collin Funk <[email protected]>
Newsgroups gmane.comp.gnu.core-utils.bugs
Message-ID <[email protected]>
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
0001-tsort-implement-w-as-required-by-POSIX-2024.patch (text/x-patch, 6.9 KB)
From 96a822612454ace9f4369857c9cb223f835a27f7 Mon Sep 17 00:00:00 2001
Message-ID: <96a822612454ace9f4369857c9cb223f835a27f7.1755146320.git.collin.funk1@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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.