mirror of
https://https.git.savannah.gnu.org/git/bash.git
synced 2026-06-29 16:39:53 +02:00
new `jobid' loadable builtin like ash-based shells; fix for nofork command substitution when followed by an asynchronous subshell using GNU nohup; fix for nofork command substitution to move the file descriptor moved to the anonymous file out of the user-accessible range
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+31
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
@@ -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 "$@"
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user