How do I launch a background process through a wrapper that'll undo the SIGINT ignore? - linux

I want to launch a background process from bash in such a way that I can then send SIGINT signals to it (eg: via kill or htop). How do I do that?
By default, when a process is launched in the backround, SIGINT and SIGQUIT are ignored (see this question).

If the process is execed (=isn't just a subshell but is based on binary), you can run it through a wrapper that'll undo the SIGINT/SIGQUIT ignore:
reset_sigint.c:
#include <signal.h>
#include <unistd.h>
int main(int C, char**V)
{
sigaction(SIGINT,&(struct sigaction){.sa_handler=SIG_DFL}, 0);
execvp(V[1],V+1);
return 127;
}
To prevent the SIGINT/SIGQUIT ignore more generically for any process run as a command in the shell, you can run it with set -m on
sh -c 'set -m; ( sleep 10 )& ps -p $! -o ignored'
#compare with: sh -c '( sleep 10 )& ps -p $! -o ignored'
but that'll run the command in a separate process group as a (possibly undesirable) sideffect.

Related

Switch user without creating an intermediate process

I'm able to use sudo or su to execute a command as another user. By combining with exec, I'm able to replace the current process with sudo or su, and a child process running the command. But I want to replace the current process with the command running as another user. How do I do that?
Testing with sleep inf as the command, and someguy as the user:
exec su someguy -c 'sleep inf'
This gives me from pstree:
bash───su───sleep
And
exec sudo -u someguy sleep inf
gives
bash───sudo───sleep
In both cases I just want the sleep command, with bash as the parent.
I expect I could do this from C with something some sequence of setuid() and exec().
The difference between sudo sleep and exec sudo sleep is that in the second command sudo process replaces bash image and calling shell process exits when sleep exits
pstree -p $$
bash(8765)───pstree(8943)
((sleep 1; pstree -p $$ )&); sudo -u user sleep 2
bash(8765)───sudo(8897)───sleep(8899)
((sleep 1; pstree -p $$ )&); exec sudo -u user sleep 2
sudo(8765)───sleep(8993)
however the fact that sudo or su fork a new process depends on design and their implementation (some sources found here).
From sudo man page :
Process model
When sudo runs a command, it calls fork(2), sets up the execution environment as described above, and calls the execve system call in the child process. The main sudo
process waits until the command has completed, then passes the command's exit status to the security policy's close function and exits. If an I/O logging plugin is config-
ured or if the security policy explicitly requests it, a new pseudo-terminal (“pty”) is created and a second sudo process is used to relay job control signals between the
user's existing pty and the new pty the command is being run in. This extra process makes it possible to, for example, suspend and resume the command. Without it, the com-
mand would be in what POSIX terms an “orphaned process group” and it would not receive any job control signals. As a special case, if the policy plugin does not define a
close function and no pty is required, sudo will execute the command directly instead of calling fork(2) first. The sudoers policy plugin will only define a close function
when I/O logging is enabled, a pty is required, or the pam_session or pam_setcred options are enabled. Note that pam_session and pam_setcred are enabled by default on sys-
tems using PAM.
I do not share the observation and the conclusions. See below:
I created two shellscripts:
$ cat just_sudo.sh
#!/bin/bash
sudo sleep inf
$ cat exec_sudo.sh
#!/bin/bash
exec sudo sleep inf
So, one with an exec, one without. If I do a pstree to see the starting situation, I get:
$ pstree $$
bash───pstree
$ echo $$
17250
This gives me the baseline. Next I launched both scripts:
$ bash just_sudo.sh &
[1] 1218
$ bash exec_sudo.sh &
[2] 1220
And then, pstree gives:
$ pstree $$
bash─┬─bash───sleep
├─pstree
└─sleep
the first being the just_sudo, the second is the exec_sudo. Both run as root:
$ ps -ef | grep sleep
root 1219 1218 0 14:01 pts/4 00:00:00 sleep inf
root 1220 17250 0 14:01 pts/4 00:00:00 sleep inf
once again the first is the just_sudo and the second the exec_sudo. You can see that the parent-PID for the sleep in the exec_sudo is the interactive shell from which the scripts are launched and the PID is 1220, which was the PID we saw when the script was launched in the background.
If you use two terminal windows and do not put it in the background, this will work also:
terminal 1 terminal 2
$ echo $$
16053 $ pstree 16053
bash
$ sudo sleep inf
$ pstree 16053
bash───sleep
^C
$ exec sudo sleep inf
$ pstree 16053
sleep
^C
( window is closed )
So, on my linux system, the behavior is not as you suggest.The only way that the sudo may remain in the process-tree is if it runs in the existing tty (so without an exec), or if it is invoked with a pseudo-terminal, for example as exec sudoedit.
I am not sure if this can be done using sudo or su. But you can easily achieve this using a simple c program. I will be showing a very bare minimal one with hard coded command and user id, but you can always customize it to your liking
test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
int runAs(int gid, int uid, char *command[]) {
setgid(gid);
setuid(uid);
char *args[]={"sleep","inf",NULL};
execvp(args[0],args);
}
int main(int argc, char *argv[] )
{
runAs(1000, 65534, argv);
return 0;
}
Note: on my machine 1000 is the uid/gid of vagrant user and group. 65534 is uid and gid of nobody user and group
build.sh
#!/bin/bash
sudo gcc test.c -o sosu
sudo chown root:root sosu
sudo chmod u+s sosu
Now time for a test
$ pstree $$ -p
bash(23251)───pstree(28627)
$ ./sosu
Now from another terminal
$ pstree -p 23251
bash(23251)───sleep(28687)
$ ps aux | grep [2]8687
nobody 28687 0.0 0.0 7288 700 pts/0 S+ 11:40 0:00 sleep inf
As you can see the process is run as nobody and its a child of bash
In order to free a command, you have to give him std io:
For this,you could either close all stdin, stdout and stderr or let them point elsewhere.
Try this:
su - someguy -c 'exec nohup sleep 60 >/tmp/sleep.log 2>/tmp/sleep.err <<<"" &'
Note:
su - someguy -c 'exec nohup sleep 60 &'
Is enough, and
su - someguy -c 'exec sleep 60 >/tmp/sleep.log 2>/tmp/sleep.err <<<"" &'
Will work too.
Consider having a look at man nohup
Note 2: Under bash, you could use:
su - someguy -c 'exec sleep 60 & disown -h'
... And read help disown or man bash.
Little demo showing how to close all IOs:
su - someguy -c 'exec 0<&- ; exec 1>&- ; exec 2>&- ; exec sleep 60 &'
quick test:
pstree $(ps -C sleep ho pid)
sleep

Don't show the output of kill command in a Linux bash script [duplicate]

How can you suppress the Terminated message that comes up after you kill a
process in a bash script?
I tried set +bm, but that doesn't work.
I know another solution involves calling exec 2> /dev/null, but is that
reliable? How do I reset it back so that I can continue to see stderr?
In order to silence the message, you must be redirecting stderr at the time the message is generated. Because the kill command sends a signal and doesn't wait for the target process to respond, redirecting stderr of the kill command does you no good. The bash builtin wait was made specifically for this purpose.
Here is very simple example that kills the most recent background command. (Learn more about $! here.)
kill $!
wait $! 2>/dev/null
Because both kill and wait accept multiple pids, you can also do batch kills. Here is an example that kills all background processes (of the current process/script of course).
kill $(jobs -rp)
wait $(jobs -rp) 2>/dev/null
I was led here from bash: silently kill background function process.
The short answer is that you can't. Bash always prints the status of foreground jobs. The monitoring flag only applies for background jobs, and only for interactive shells, not scripts.
see notify_of_job_status() in jobs.c.
As you say, you can redirect so standard error is pointing to /dev/null but then you miss any other error messages. You can make it temporary by doing the redirection in a subshell which runs the script. This leaves the original environment alone.
(script 2> /dev/null)
which will lose all error messages, but just from that script, not from anything else run in that shell.
You can save and restore standard error, by redirecting a new filedescriptor to point there:
exec 3>&2 # 3 is now a copy of 2
exec 2> /dev/null # 2 now points to /dev/null
script # run script with redirected stderr
exec 2>&3 # restore stderr to saved
exec 3>&- # close saved version
But I wouldn't recommend this -- the only upside from the first one is that it saves a sub-shell invocation, while being more complicated and, possibly even altering the behavior of the script, if the script alters file descriptors.
EDIT:
For more appropriate answer check answer given by Mark Edgar
Solution: use SIGINT (works only in non-interactive shells)
Demo:
cat > silent.sh <<"EOF"
sleep 100 &
kill -INT $!
sleep 1
EOF
sh silent.sh
http://thread.gmane.org/gmane.comp.shells.bash.bugs/15798
Maybe detach the process from the current shell process by calling disown?
The Terminated is logged by the default signal handler of bash 3.x and 4.x. Just trap the TERM signal at the very first of child process:
#!/bin/sh
## assume script name is test.sh
foo() {
trap 'exit 0' TERM ## here is the key
while true; do sleep 1; done
}
echo before child
ps aux | grep 'test\.s[h]\|slee[p]'
foo &
pid=$!
sleep 1 # wait trap is done
echo before kill
ps aux | grep 'test\.s[h]\|slee[p]'
kill $pid ## no need to redirect stdin/stderr
sleep 1 # wait kill is done
echo after kill
ps aux | grep 'test\.s[h]\|slee[p]'
Is this what we are all looking for?
Not wanted:
$ sleep 3 &
[1] 234
<pressing enter a few times....>
$
$
[1]+ Done sleep 3
$
Wanted:
$ (set +m; sleep 3 &)
<again, pressing enter several times....>
$
$
$
$
$
As you can see, no job end message. Works for me in bash scripts as well, also for killed background processes.
'set +m' disables job control (see 'help set') for the current shell. So if you enter your command in a subshell (as done here in brackets) you will not influence the job control settings of the current shell. Only disadvantage is that you need to get the pid of your background process back to the current shell if you want to check whether it has terminated, or evaluate the return code.
This also works for killall (for those who prefer it):
killall -s SIGINT (yourprogram)
suppresses the message... I was running mpg123 in background mode.
It could only silently be killed by sending a ctrl-c (SIGINT) instead of a SIGTERM (default).
disown did exactly the right thing for me -- the exec 3>&2 is risky for a lot of reasons -- set +bm didn't seem to work inside a script, only at the command prompt
Had success with adding 'jobs 2>&1 >/dev/null' to the script, not certain if it will help anyone else's script, but here is a sample.
while true; do echo $RANDOM; done | while read line
do
echo Random is $line the last jobid is $(jobs -lp)
jobs 2>&1 >/dev/null
sleep 3
done
Another way to disable job notifications is to place your command to be backgrounded in a sh -c 'cmd &' construct.
#!/bin/bash
# ...
pid="`sh -c 'sleep 30 & echo ${!}' | head -1`"
kill "$pid"
# ...
# or put several cmds in sh -c '...' construct
sh -c '
sleep 30 &
pid="${!}"
sleep 5
kill "${pid}"
'
I found that putting the kill command in a function and then backgrounding the function suppresses the termination output
function killCmd() {
kill $1
}
killCmd $somePID &
Simple:
{ kill $! } 2>/dev/null
Advantage? can use any signal
ex:
{ kill -9 $PID } 2>/dev/null

background jobs change to daemon without nohup/disown?

a strange thing to me
a script while.sh,it's content is:
while [ 1 ];do
sleep 1
echo `date`
done
run as $while.sh >& while.log & (without nohup or disown or setsid or double fork())
exit and login again can see this process is still exist,it's ppid is 1 and it's tty is ?
my system is rhel6(rhel5 is the same, bash
in centos5.x it must use nohup or disown or do double fork() in code
what happen in rhel6
Is the huponexit shell option set?
$ shopt
...
huponexit off
Bash will send a SIGHUP signal to its jobs if it receives a SIGHUP itself, but it won't signal them when it exits normally unless you enable this option.
For what it's worth this is disabled on both RHEL6 and RHEL5, at least on the systems I just tested. I tried this command:
$ sleep 1000 &
It was not killed when I logged out and logged back in unless I deliberately enabled shopt -s huponexit.

How to Kill Current Command When Bash Script is Killed

I current have a script that looks like this.
# code
mplayer "$vid"
# more code
The problem is that if this script is killed the mplayer process lives. I wondering how I could make it so that killing the script would kill mplayer as well.
I can't use exec because I need to run commands after mplayer.
exec mplayer "$vid"
The only possible solution I can think of is to spawn it in the background and wait until it finishes manually. That way I can get it's PID and kill it when the script gets killed, not exactly elegant. I was wondering what the "proper" or best way of doing this is.
I was able to test the prctl idea I posted about in a comment and it seems to work. You will need to compile this:
#include "sys/prctl.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
int main(int argc, char ** argv){
prctl(PR_SET_PDEATHSIG, atoi(argv[1]),0,0,0);
char * argv0 = strdup(argv[2]);
char * slashptr = strrchr(argv0, '/');
if(slashptr){
argv0 = slashptr + 1;
}
return execvp(argv0, &(argv[2]));
}
Let's say you have compiled the above to an executable named "prun" and it is in your path. Let's say your script is called "foo.sh" and it is also in your path. Make a wrapper script that calls
prun 15 foo.sh
foo.sh should get SIGTERM when the wrapper script is terminated for any reason, even SIGKILL.
Note: this is a linux only solution and the c source code presented is without detailed checking of arguments
Thanks to Mux for the lead. It appears that there is no way to do this in bash except for manually catching signals. Here is a final working (overly commented) version.
trap : SIGTERM SIGINT # Trap these two (killing) signals. These will cause wait
# to return a value greater than 128 immediately after received.
mplayer "$vid" & # Start in background (PID gets put in `$!`)
pid=$!
wait $pid # Wait for mplayer to finish.
[ $? -gt 128 ] && { kill $pid ; exit 128; } ; # If a signal was recieved
# kill mplayer and exit.
Refrences:
- traps: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html
(Updated) I think I understand what you are looking for now:
You can accomplish this by spawning a new terminal to run your script:
gnome-terminal -x /path_to_dir_of_your_script/your_script_name
(or use xterm -e or konsole -e instead of gnome-terminal -x, depending on what system you are on)
So now whenever your script ends / exits (I assume you have exit 0 or exit 1 in certain parts of the script), the newly spawned terminal will also exit since the script is finished - this will in turn also kill any applications spawned under that new terminal.
For example, I just tested the above command with this script:
#!/bin/bash
gedit &
pid=$!
echo "$pid"
sleep 5
exit 0
As you can see, there are no explicit calls to kill the new gedit process, but the application (gedit) closes as soon as the script exits anyway.
(Previous answer: alternatively, if you were simply asking about how to kill a process) Here's a short example of how you can accomplish that with kill.
#!/bin/bash
gedit &
pid=$!
echo "$pid"
sleep 5
kill -s SIGKILL $pid
Unless I misunderstood your question, you can get the PID of the spawned process right away instead of waiting until it finishes.
Well, you can simply kill the process group instead, this way the whole process tree will be killed, first find out the group id
ps x -o "%p %r %c" | grep <name>
And then use kill like so:
kill -TERM -<gid>
Note the dash before the process group id. Or a one-liner:
kill -TERM -$(pgrep <name>)
Perhaps use command substitution to run mplayer "$vid" in a subshell:
$(mplayer "$vid")
I tested it this way:
tesh.sh:
#!/bin/sh
$vid = "..."
$(mplayer "$vid")
% test.sh
In a separate terminal:
% pkill test.sh
In the orginal terminal, mplayer stops, printing to stderr
Terminated
MPlayer interrupted by signal 13 in module: av_sync

Sleep in a while loop gets its own pid

I have a bash script that does some parallel processing in a loop. I don't want the parallel process to spike the CPU, so I use a sleep command. Here's a simplified version.
(while true;do sleep 99999;done)&
So I execute the above line from a bash prompt and get something like:
[1] 12345
Where [1] is the job number and 12345 is the process ID (pid) of the while loop. I do a kill 12345 and get:
[1]+ Terminated ( while true; do
sleep 99999;
done )
It looks like the entire script was terminated. However, I do a ps aux|grep sleep and find the sleep command is still going strong but with its own pid! I can kill the sleep and everything seems fine. However, if I were to kill the sleep first, the while loop starts a new sleep pid. This is such a surprise to me since the sleep is not parallel to the while loop. The loop itself is a single path of execution.
So I have two questions:
Why did the sleep command get its own process ID?
How do I easily kill the while loop and the sleep?
Sleep gets its own PID because it is a process running and just waiting. Try which sleep to see where it is.
You can use ps -uf to see the process tree on your system. From there you can determine what the PPID (parent PID) of the shell (the one running the loop) of the sleep is.
Because "sleep" is a process, not a build-in function or similar
You could do the following:
(while true;do sleep 99999;done)&
whilepid=$!
kill -- -$whilepid
The above code kills the process group, because the PID is specified as a negative number (e.g. -123 instead of 123). In addition, it uses the variable $!, which stores the PID of the most recently executed process.
Note:
When you execute any process in background on interactive mode (i.e. using the command line prompt) it creates a new process group, which is what is happening to you. That way, it's relatively easy to "kill 'em all", because you just have to kill the whole process group. However, when the same is done within a script, it doesn't create any new group, because all new processes belong to the script PID, even if they are executed in background (jobs control is disabled by default). To enable jobs control in a script, you just have to put the following at the beginning of the script:
#!/bin/bash
set -m
Have you tried doing kill %1, where 1 is the number you get after launching the command in background?
I did it right now after launching (while true;do sleep 99999;done)& and it correctly terminated it.
"ps --ppid" selects all processes with the specified parent pid, eg:
$ (while true;do sleep 99999;done)&
[1] 12345
$ ppid=12345 ; kill -9 $ppid $(ps --ppid $ppid -o pid --no-heading)
You can kill the process group.
To find the process group of your process run:
ps --no-headers -o "%r" -p 15864
Then kill the process group using:
kill -- -[PGID]
You can do it all in one command. Let's try it out:
$ (while true;do sleep 99999;done)&
[1] 16151
$ kill -- -$(ps --no-headers -o "%r" -p 16151)
[1]+ Terminated ( while true; do
sleep 99999;
done )
To kill the while loop and the sleep using $! you can also use a trap signal handler inside the subshell.
(trap 'kill ${!}; exit' TERM; while true; do sleep 99999 & wait ${!}; done)&
kill -TERM ${!}

Resources