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

Reply via email to