Support the case where speed_t is simply a number, and in that case
assume that arbitrary values can be passed.  This is assumed to be the
case when all known speed_t macros equal their own value.

Try to probe for a variety of speed_t constants by trying to coax
$(CC) into emitting macro definitions (-E -dM).  If this is not
supported, use a fairly extensive list of constants as a
fallback.  This both improves the test for arbitrary speed support, as
well as allowing proper operation in the case where the constants are
not plain numbers and allows for handing enumerated speed constants
that were not known a priori when the source code was written.

A simple shell script (mostly using sed) is used to turn the list of
constants (probed and predefined) into a pair of conversion functions,
baud_to_value() and value_to_baud(); string_to_baud() is then
reimplemented as a wrapper around the latter.  Using a shell script
hopefully should make this portable.

Signed-off-by: "H. Peter Anvin" (Intel) <h...@zytor.com>
---
 src/.gitignore |   1 +
 src/local.mk   |  17 +++++-
 src/speedgen   |  85 ++++++++++++++++++++++++++++++
 src/stty.c     | 139 +++++++++++++++++--------------------------------
 src/termios.c  |  34 ++++++++++++
 5 files changed, 185 insertions(+), 91 deletions(-)
 create mode 100755 src/speedgen
 create mode 100644 src/termios.c

diff --git a/src/.gitignore b/src/.gitignore
index 55f9660c62d8..2fffa349be21 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -87,6 +87,7 @@ shred
 shuf
 sleep
 sort
+speedlist.h
 split
 stat
 stdbuf
diff --git a/src/local.mk b/src/local.mk
index 57692c0ce0ae..45dffd8bf624 100644
--- a/src/local.mk
+++ b/src/local.mk
@@ -72,7 +72,8 @@ EXTRA_DIST +=         \
   src/primes.h         \
   src/crctab.c         \
   src/tac-pipe.c       \
-  src/extract-magic
+  src/extract-magic    \
+  src/speedgen
 
 CLEANFILES += $(SCRIPTS)
 
@@ -693,6 +694,20 @@ src/version.h: Makefile
        $(AM_V_at)chmod a-w $@t
        $(AM_V_at)mv $@t $@
 
+# Target-specific termios baud rate file. This is opportunistic;
+# if cc -E doesn't support -dM, the speedgen script still includes
+# an extensive fallback list of common constants.
+CLEANFILES += src/speedlist.h
+src/speedlist.h: src/termios.c lib/config.h src/speedgen
+       $(AM_V_GEN)rm -f $@
+       $(AM_V_at)${MKDIR_P} src
+       $(AM_V_at)$(COMPILE) -E -dM $< 2>/dev/null |            \
+                 $(SHELL) $(srcdir)/src/speedgen $@t
+       $(AM_V_at)chmod a-w $@t
+       $(AM_V_at)mv $@t $@
+
+src/stty.$(OBJEXT): src/speedlist.h
+
 # Generates a list of macro invocations like:
 #   SINGLE_BINARY_PROGRAM(program_name_str, main_name)
 # once for each program list on $(single_binary_progs). Note that
diff --git a/src/speedgen b/src/speedgen
new file mode 100755
index 000000000000..f1647d9f0acf
--- /dev/null
+++ b/src/speedgen
@@ -0,0 +1,85 @@
+#!/bin/sh -e
+
+out="$1"
+tmp="$out.tmp"
+
+if [ -z "$out" ]; then
+    echo "Usage: $0 outfile" 2>&1
+    exit 1
+fi
+
+s='[[:space:]]'                        # For brevity's sake
+
+trap "rm -f '$tmp'" EXIT
+trap "rm -f '$tmp' '$out'" ERR HUP INT QUIT TERM
+
+# Fallback list of speeds that are always tested for
+defspeeds="0 50 75 110 134 150 200 300 600 1200 1800 2400 4800 7200 9600 \
+14400 19200 28800 33600 38400 57600 76800 115200 153600 230400 307200 \
+460800 500000 576000 614400 921600 1000000 1152000 1500000 \
+2000000 2500000 3000000 3500000 4000000 5000000 10000000"
+(
+    sed -n -e "s/^$s*\#$s*define$s$s*B\\([1-9][0-9]*\\)$s.*\$/\\1/p"
+    for s in $defspeeds; do echo "$s"; done
+) | sort -n | uniq > "$tmp"
+
+cat > "$out" <<'EOF'
+#ifndef SPEEDLIST_H
+# define SPEEDLIST_H 1
+
+# if 1 \
+EOF
+
+sed -e 's/^.*$/ \&\& (!defined(B&) || B& == &) \\/' < "$tmp" >> "$out"
+
+cat >> "$out" <<'EOF'
+
+#  define TERMIOS_SPEED_T_SANE 1
+
+# endif
+
+ATTRIBUTE_CONST
+static unsigned long int
+baud_to_value (speed_t speed)
+{
+# ifdef TERMIOS_SPEED_T_SANE
+  return speed;
+# else
+  switch (speed)
+    {
+EOF
+
+sed -e 's/^.*$/#  ifdef B&\n      case B&: return &;\n#  endif/' \
+    < "$tmp" >> "$out"
+
+cat >> "$out" <<'EOF'
+      default: return -1;
+    }
+# endif
+}
+
+ATTRIBUTE_CONST
+static speed_t
+value_to_baud (unsigned long int value)
+{
+# ifdef TERMIOS_SPEED_T_SANE
+  speed_t speed = value;
+  if (speed != value)
+    speed = (speed_t) -1;      /* Unrepresentable (overflow?) */
+  return speed;
+# else
+  switch (value)
+    {
+EOF
+
+sed -e 's/^.*$/#  ifdef B&\n      case &: return B&;\n#  endif/' \
+    < "$tmp" >> "$out"
+
+cat >> "$out" <<'EOF'
+      default: return (speed_t) -1;
+    }
+# endif
+}
+
+#endif
+EOF
diff --git a/src/stty.c b/src/stty.c
index 133b33cfc312..d2ea5834942b 100644
--- a/src/stty.c
+++ b/src/stty.c
@@ -2172,100 +2172,59 @@ recover_mode (char const *arg, struct termios *mode)
   return true;
 }
 
-struct speed_map
-{
-  char const *string;          /* ASCII representation. */
-  speed_t speed;               /* Internal form. */
-  unsigned long int value;     /* Numeric value. */
-};
-
-static struct speed_map const speeds[] =
-{
-  {"0", B0, 0},
-  {"50", B50, 50},
-  {"75", B75, 75},
-  {"110", B110, 110},
-  {"134", B134, 134},
-  {"134.5", B134, 134},
-  {"150", B150, 150},
-  {"200", B200, 200},
-  {"300", B300, 300},
-  {"600", B600, 600},
-  {"1200", B1200, 1200},
-  {"1800", B1800, 1800},
-  {"2400", B2400, 2400},
-  {"4800", B4800, 4800},
-  {"9600", B9600, 9600},
-  {"19200", B19200, 19200},
-  {"38400", B38400, 38400},
-  {"exta", B19200, 19200},
-  {"extb", B38400, 38400},
-#ifdef B57600
-  {"57600", B57600, 57600},
-#endif
-#ifdef B115200
-  {"115200", B115200, 115200},
-#endif
-#ifdef B230400
-  {"230400", B230400, 230400},
-#endif
-#ifdef B460800
-  {"460800", B460800, 460800},
-#endif
-#ifdef B500000
-  {"500000", B500000, 500000},
-#endif
-#ifdef B576000
-  {"576000", B576000, 576000},
-#endif
-#ifdef B921600
-  {"921600", B921600, 921600},
-#endif
-#ifdef B1000000
-  {"1000000", B1000000, 1000000},
-#endif
-#ifdef B1152000
-  {"1152000", B1152000, 1152000},
-#endif
-#ifdef B1500000
-  {"1500000", B1500000, 1500000},
-#endif
-#ifdef B2000000
-  {"2000000", B2000000, 2000000},
-#endif
-#ifdef B2500000
-  {"2500000", B2500000, 2500000},
-#endif
-#ifdef B3000000
-  {"3000000", B3000000, 3000000},
-#endif
-#ifdef B3500000
-  {"3500000", B3500000, 3500000},
-#endif
-#ifdef B4000000
-  {"4000000", B4000000, 4000000},
-#endif
-  {nullptr, 0, 0}
-};
+/* Autogenerated conversion functions to/from speed_t */
+#include "speedlist.h"
 
 ATTRIBUTE_PURE
-static speed_t
-string_to_baud (char const *arg)
+speed_t string_to_baud (char const *arg)
 {
-  for (int i = 0; speeds[i].string != nullptr; ++i)
-    if (STREQ (arg, speeds[i].string))
-      return speeds[i].speed;
-  return (speed_t) -1;
-}
+  char *ep;
+  unsigned long value;
+  unsigned char c;
 
-ATTRIBUTE_PURE
-static unsigned long int
-baud_to_value (speed_t speed)
-{
-  for (int i = 0; speeds[i].string != nullptr; ++i)
-    if (speed == speeds[i].speed)
-      return speeds[i].value;
-  return 0;
+  value = strtoul (arg, &ep, 10);
+
+  c = *ep++;
+  if (c == '.')
+    {
+      /* Number includes a fraction. Round it to nearest-even.
+        Note in particular that 134.5 must round to 134! */
+      c = *ep++;
+      if (c)
+       {
+         c -= '0';
+         if (c > 9)
+           {
+             return (speed_t) -1; /* Garbage after otherwise valid number */
+           }
+         else if (c > 5)
+           {
+             value++;
+           }
+         else if (c == 5)
+           {
+             while ((c = *ep++) == '0')
+               ; /* Skip zeroes after .5 */
+
+             if (c >= '1' && c <= '9')
+               value++;                /* Nonzero digit, round up */
+             else
+               value += (value & 1);   /* Exactly in the middle, round even */
+           }
+       }
+    }
+  else if (c)
+    {
+      /* Not a valid number; check for legacy aliases "exta" and "extb" */
+      if (STREQ (arg, "exta"))
+       return B19200;
+      else if (STREQ (arg, "extb"))
+       return B38400;
+      else
+       return (speed_t) -1;
+    }
+
+  return value_to_baud (value);
 }
 
 static void
diff --git a/src/termios.c b/src/termios.c
new file mode 100644
index 000000000000..ae30ce8e165c
--- /dev/null
+++ b/src/termios.c
@@ -0,0 +1,34 @@
+/* termios.c -- coax out Bxxx macros from termios.h
+
+   Copyright (C) 1990-2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* This simply #includes headers which may or may not provide Bxxx
+   constant macros.  This is run through the C preprocessor and defined
+   macros are extracted.
+
+   In the case where the C preprocessor isn't capable of doing so,
+   the script this is fed through contains a pre-defined set of common
+   constants. */
+
+#include <config.h>
+
+#ifdef TERMIOS_NEEDS_XOPEN_SOURCE
+# define _XOPEN_SOURCE
+#endif
+
+#include <sys/types.h>
+#include <termios.h>
+#include <sys/ioctl.h>
-- 
2.49.0


Reply via email to