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:
Chet Ramey
2026-01-23 16:39:00 -05:00
parent c4b56ed9ac
commit b805bbec80
9 changed files with 305 additions and 2 deletions
+7 -2
View 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
+196
View File
@@ -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 */
};
+12
View File
@@ -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 "$@"
)