]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
new `jobid' loadable builtin like ash-based shells; fix for nofork command substituti... devel
authorChet Ramey <chet.ramey@case.edu>
Fri, 23 Jan 2026 21:39:00 +0000 (16:39 -0500)
committerChet Ramey <chet.ramey@case.edu>
Fri, 23 Jan 2026 21:39:00 +0000 (16:39 -0500)
CWRU/CWRU.chlog
MANIFEST
Makefile.in
examples/loadables/Makefile.in
examples/loadables/jobid.c [new file with mode: 0644]
examples/scripts/nohup.sh [new file with mode: 0644]
include/shtty.h
lib/sh/shtty.c
subst.c

index 25c19cffa2943270ac1b2d413e6c5312b1708ca2..17d982b04bb8ee537ad00f09d7956d1072ec27a0 100644 (file)
@@ -12596,3 +12596,26 @@ lib/readline/complete.c
          returned multiple matches, but an immediate subsequent completion
          attempt returned a single match, insert the single match
          From a report by Adam Purkrt <adampurkrt78@gmail.com> in 5/2025
+
+                                  1/22
+                                  ----
+examples/loadables/jobid.c
+       - jobid: new builtin, like the NetBSD sh builtin
+
+subst.c
+       - function_substitute: unwind-protect stdin_redirected, in case the
+         nofork comsub causes it to be set (since it can run with pipe input
+         and output).
+         From a report by Hany Salem <hanysalemtx@gmail.com>
+
+                                  1/23
+                                  ----
+subst.c
+       - function_substitute: dup the file descriptor returned by anonopen
+         using move_to_high_fd to get it out of the way of user-accessible
+         fds.
+         Report from Stephane Chazelas <stephane@chazelas.org>
+
+lib/sh/shtty.c,include/shtty.h
+       - ttseteol, ttfd_seteol, tt_seteol: new functions to set the tty's
+         EOL character to something other than a newline
index aebb98c0ed00c80b9879674d3f1212b51b99a8a5..8744a844936d103f43d28dd6b4500bb4ac7303e8 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -785,6 +785,7 @@ examples/loadables/getconf.c        f
 examples/loadables/fdflags.c   f
 examples/loadables/finfo.c     f
 examples/loadables/fltexpr.c   f
+examples/loadables/jobid.c     f
 examples/loadables/cat.c       f
 examples/loadables/chmod.c     f
 examples/loadables/csv.c       f
index 6855719791ba90904558644aa4f0040f6c097b6a..f233a4a5cc80cb41b0244ed33f5f3d0d2f0269d6 100644 (file)
@@ -179,6 +179,10 @@ UBSAN_XLDFLAGS = -fsanitize=undefined -fsanitize=local-bounds -fsanitize=vptr
 GCOV_XCFLAGS = -fprofile-arcs -ftest-coverage
 GCOV_XLDFLAGS = -fprofile-arcs -ftest-coverage
 
+COVERAGE_XCFLAGS = -g --coverage -fprofile-arcs -ftest-coverage
+COVERAGE_XCLDAGS = -g --coverage -fprofile-arcs -ftest-coverage
+COVERAGE_OUT = doc/coverage
+
 # these need CC=clang
 LSAN_CC = clang
 LSAN_XCFLAGS = -fsanitize=leak -fno-common -fno-omit-frame-pointer -fno-optimize-sibling-calls
@@ -692,6 +696,33 @@ profiling-tests:   ${Program}
        @test "X$$PROFILE_FLAGS" == "X" && { echo "profiling-tests: must be built with profiling enabled" >&2; exit 1; }
        @${MAKE} $(BASH_MAKEFLAGS) tests TESTSCRIPT=run-gprof
 
+# from gnulib via groff
+init-coverage: clean
+       lcov --directory . --zerocounters
+
+coverage-tests: build-coverage $(TESTS_SUPPORT)
+       @-test -d tests || mkdir tests
+       @-test -d $(COVERAGE_OUT) || mkdir $(COVERAGE_OUT)
+       @cp $(TESTS_SUPPORT) tests
+       @( cd $(srcdir)/tests && \
+               BUILD_DIR=$(BUILD_DIR) PATH=$(BUILD_DIR)/tests:$$PATH THIS_SH=$(THIS_SH) $(SHELL) ${TESTSCRIPT} )
+       
+build-coverage: init-coverage
+       $(MAKE) $(BASH_MAKEFLAGS) ADDON_CFLAGS='$(COVERAGE_XCFLAGS)' \
+               ADDON_LDFLAGS='$(COVERAGE_XLDFLAGS)'
+
+run-coverage: build-coverage coverage-tests
+       lcov --directory . --output-file $(COVERAGE_OUT)/$(PACKAGE).info --capture
+
+# external requirement: genhtml
+gen-coverage:
+       genhtml --output-directory $(COVERAGE_OUT) \
+               $(COVERAGE_OUT)/$(PACKAGE).info \
+               --frames --legend \
+               --title "$(PACKAGE_NAME)"
+
+coverage: init-coverage build-coverage gen-coverage
+
 version.h:  $(SOURCES) config.h Makefile patchlevel.h
        $(SHELL) $(SUPPORT_SRC)mkversion.sh -b -S ${topdir} -s $(RELSTATUS) -d $(Version) -o newversion.h \
                && mv newversion.h version.h
index afb5f12b0cd2b812a8dbb02649efe594bb419139..6e8b635b87bab737ffba836e6f95946aba9f41f1 100644 (file)
@@ -103,7 +103,7 @@ INC = -I. -I.. -I$(topdir) -I$(topdir)/lib -I$(topdir)/builtins -I${srcdir} \
 ALLPROG = print truefalse sleep finfo logname basename dirname fdflags \
          tty pathchk tee head mkdir rmdir mkfifo mktemp printenv id whoami \
          uname sync push ln unlink realpath strftime mypid setpgid seq rm \
-         accept csv dsv cut stat getconf kv strptime chmod fltexpr
+         accept csv dsv cut stat getconf kv strptime chmod fltexpr jobid
 OTHERPROG = necho hello cat pushd asort
 
 SUBDIRS = perl
@@ -256,6 +256,9 @@ asort:      asort.o
 fltexpr:       fltexpr.o
        $(SHOBJ_LD) $(SHOBJ_LDFLAGS) $(SHOBJ_XLDFLAGS) -o $@ fltexpr.o $(SHOBJ_LIBS) -lm
 
+jobid: jobid.o
+       $(SHOBJ_LD) $(SHOBJ_LDFLAGS) $(SHOBJ_XLDFLAGS) -o $@ jobid.o $(SHOBJ_LIBS)
+
 
 # pushd is a special case.  We use the same source that the builtin version
 # uses, with special compilation options.
@@ -322,7 +325,7 @@ OBJS = print.o truefalse.o accept.o sleep.o finfo.o getconf.o logname.o \
        basename.o dirname.o tty.o pathchk.o tee.o head.o rmdir.o necho.o \
        hello.o cat.o csv.o dsv.o kv.o cut.o printenv.o id.o whoami.o uname.o \
        sync.o push.o mkdir.o mktemp.o realpath.o strftime.o setpgid.o stat.o \
-       fdflags.o seq.o asort.o strptime.o chmod.o
+       fdflags.o seq.o asort.o strptime.o chmod.o fltexpr.o jobid.o
 
 ${OBJS}:       ${BUILD_DIR}/config.h
 
@@ -364,3 +367,5 @@ fdflags.o: fdflags.c
 seq.o: seq.c
 asort.o: asort.c
 strptime.o: strptime.c
+fltexpr.o: fltexpr.c
+jobid.o: jobid.c
diff --git a/examples/loadables/jobid.c b/examples/loadables/jobid.c
new file mode 100644 (file)
index 0000000..90c5858
--- /dev/null
@@ -0,0 +1,196 @@
+/* See Makefile for compilation details. */
+
+/*
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash.
+   Bash 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.
+
+   Bash 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 Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+
+#include "bashtypes.h"
+#include <signal.h>
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include <stdio.h>
+
+#include "loadables.h"
+#include "jobs.h"
+#include "execute_cmd.h"
+
+static void
+printprocs (int job)
+{
+  PROCESS *p;
+
+  p = jobs[job]->pipe;
+  do
+    {
+      printf ("%ld", (long)p->pid);
+      p = p->next;
+      putchar (p == jobs[job]->pipe ? '\n' : ' ');
+    }
+  while (p != jobs[job]->pipe);
+}
+
+static int
+printinfo (int job, int pgrp, int jobid, int lproc)
+{
+  if (pgrp)
+    {
+      printf ("%ld\n", (long)jobs[job]->pgrp);
+      return (jobs[job]->pgrp == shell_pgrp);
+    }
+  else if (jobid)              /* caller validates job */
+    {
+      printf ("%%%d\n", job + 1);
+      return 0;
+    }
+  else if (lproc)
+    {
+      PROCESS *p;
+
+      for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next)
+        ;
+      printf ("%ld\n", (long)p->pid);
+      return 0;
+    }
+  else
+    printprocs (job);
+  return 0;
+}
+
+int
+jobid_builtin (WORD_LIST *list)
+{
+  WORD_LIST *l;
+  int any_failed, opt, job, alljobs;
+  int pgrp, jobid, lproc;
+  sigset_t set, oset;
+
+  pgrp = jobid = lproc = alljobs = 0;
+  reset_internal_getopt ();
+  while ((opt = internal_getopt (list, "agjp")) != -1)
+    {
+      switch (opt)
+       {
+         case 'a': alljobs = 1; break;
+         case 'g': pgrp = 1; break;
+         case 'j': jobid = 1 ; break;
+         case 'p': lproc = 1; break;
+         CASE_HELPOPT;
+         default:
+           builtin_usage ();
+           return (EX_USAGE);
+       }
+    }
+
+  list = loptend;
+  if ((pgrp + jobid + lproc) > 1)
+    {
+      builtin_usage ();
+      return (EX_USAGE);
+    }
+
+  any_failed = 0;
+
+  /* The -a option means all jobs; JOBSPEC arguments ignored */
+  if (alljobs)
+    {
+      if (jobs == 0)
+       return 0;
+      BLOCK_CHILD (set, oset);
+      for (job = 0; job < js.j_jobslots; job++)
+       {
+         if (INVALID_JOB (job))
+           continue;
+         any_failed += printinfo (job, pgrp, jobid, lproc);
+       }
+      UNBLOCK_CHILD (oset);
+      return (sh_chkwrite (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS));
+    }
+
+  /* No JOBSPECs means current job */
+  if (list == 0)
+    {
+      BLOCK_CHILD (set, oset);
+      job = get_job_spec (list);       /* current job */
+      if ((job == NO_JOB) || jobs == 0 || INVALID_JOB (job))
+       {
+         sh_badjob ("%%");
+         any_failed++;
+       }
+      else
+       any_failed = printinfo (job, pgrp, jobid, lproc);
+      UNBLOCK_CHILD (oset);
+      return (sh_chkwrite (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS));
+    }
+
+  /* Otherwise we print info about each JOBSPEC argument */
+  BLOCK_CHILD (set, oset);
+  for (l = list; l; l = l->next)
+    {
+      job = get_job_spec (l);
+      if ((job == NO_JOB) || jobs == 0 || INVALID_JOB (job))
+       {
+         sh_badjob (l->word->word);
+         any_failed++;
+       }
+      else
+        any_failed += printinfo (job, pgrp, jobid, lproc);
+    }
+  UNBLOCK_CHILD (oset);
+
+  return (sh_chkwrite (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS));
+}
+
+char *jobid_doc[] = {
+       "Print information about each JOBSPEC.",
+       "",
+       "JOBSPEC is any string that can be used to refer to a job. If JOBSPEC",
+       "is omitted, use the current job.",
+       "",
+       "With no options, print the process IDs of the processes in each",
+       "JOBSPEC on a single line.",
+       "",
+       "The '-a' option prints information about each job, and any JOBSPEC",
+       "arguments are ignored.",
+       "",
+       "The '-g' option prints the process group for each JOBSPEC. The 'j' option",
+       "prints the job identifier for each JOBSPEC using \"%N\" notation, where",
+       "N is the job number. The 'p' option prints the process ID of the job's",
+       "process group leader (often the same as 'g'). Only one of these three",
+       "options may be used at a time.",
+       "",
+       "The return value is 2 if an invalid option was supplied, or more than",
+       "one valid option was supplied; 1 if the 'g' option is supplied and one of",
+       "the jobs is not in a separate process group; and 0 otherwise.",
+       (char *)NULL
+};
+
+/* The standard structure describing a builtin command.  bash keeps an array
+   of these structures.  The flags must include BUILTIN_ENABLED so the
+   builtin can be used. */
+struct builtin jobid_struct = {
+       "jobid",                /* builtin name */
+       jobid_builtin,          /* function implementing the builtin */
+       BUILTIN_ENABLED,        /* initial flags for builtin */
+       jobid_doc,              /* array of long documentation strings. */
+       "jobid [-a] [-g|-j|-p] [jobspec...]",   /* usage synopsis; becomes short_doc */
+       0                       /* reserved for internal use */
+};
diff --git a/examples/scripts/nohup.sh b/examples/scripts/nohup.sh
new file mode 100644 (file)
index 0000000..299e4a0
--- /dev/null
@@ -0,0 +1,12 @@
+(
+       if [ -t 1 ]; then
+               exec 1>>nohup.out || exec 1>>~/nohup.out
+       fi
+       if [ -t 2 ]; then
+               exec 2>&1
+       fi
+
+       trap '' SIGHUP
+
+       exec "$@"
+)
index d1fac33af27b8e80d5aff59a71a1eb1576ab0f2c..e68f4193e8bab043ec7a471c39a5bcf211e241d0 100644 (file)
@@ -85,6 +85,7 @@ extern int tt_setnoecho (TTYSTRUCT *);
 extern int tt_seteightbit (TTYSTRUCT *);
 extern int tt_setnocanon (TTYSTRUCT *);
 extern int tt_setcbreak (TTYSTRUCT *);
+extern int tt_seteol (TTYSTRUCT *, int);
 
 /* These functions are all generally mutually exclusive.  If you call
    more than one (bracketed with calls to ttsave and ttrestore, of
@@ -101,6 +102,8 @@ extern int ttfd_nocanon (int, TTYSTRUCT *);
 
 extern int ttfd_cbreak (int, TTYSTRUCT *);
 
+extern int ttfd_seteol (int, TTYSTRUCT *, int);
+
 /* These functions work with fd 0 and the TTYSTRUCT saved with ttsave () */
 extern int ttonechar (void);
 extern int ttnoecho (void);
@@ -108,5 +111,6 @@ extern int tteightbit (void);
 extern int ttnocanon (void);
 
 extern int ttcbreak (void);
+extern int ttseteol (int);
 
 #endif
index 027941ca30232a35fa13b5a55acaf4c81d4c13e3..fb4a98e516918a05ffa1655e3d83545e07d3db0a 100644 (file)
@@ -308,3 +308,32 @@ ttcbreak (void)
   tt = ttin;
   return (ttfd_cbreak (0, &tt));
 }
+
+int
+tt_seteol (TTYSTRUCT *ttp, int c)
+{
+#if defined (TERMIOS_TTY_DRIVER) || defined (TERMIO_TTY_DRIVER)
+  ttp->c_cc[VEOL] = c;
+#endif
+
+  return 0;
+}
+
+int
+ttfd_seteol (int fd, TTYSTRUCT *ttp, int c)
+{
+  if (tt_seteol (ttp, c) < 0)
+    return -1;
+  return (ttsetattr (fd, ttp));
+}
+
+int
+ttseteol (int c)
+{
+  TTYSTRUCT tt;
+
+  if (ttsaved == 0)
+    return -1;
+  tt = ttin;
+  return (ttfd_seteol (0, &tt, c));
+}
diff --git a/subst.c b/subst.c
index 320cae171e6545a68a6786e3c6b400acd037f829..c960fafc5c90f4dcfaf4e1c81593f3ab88abd3d6 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -7060,6 +7060,7 @@ function_substitute (char *string, int quoted, int flags)
          sys_error ("%s", _("function_substitute: cannot open anonymous file for output"));
          exp_jump_to_top_level (DISCARD);              /* XXX */
        }
+      afd = move_to_high_fd (afd, 1, -1);
     }
 
   gs = sh_getopt_save_istate ();
@@ -7082,6 +7083,7 @@ function_substitute (char *string, int quoted, int flags)
   unwind_protect_pointer (current_builtin);
   unwind_protect_pointer (currently_executing_command);
   unwind_protect_int (eof_encountered);
+  unwind_protect_int (stdin_redirected);
   add_unwind_protect (uw_pop_var_context, 0);
   add_unwind_protect (uw_maybe_restore_getopt_state, gs);