subprocces.Popen, kill process started with sudo
Question:
I am trying to start and later kill a process that requires sudo
via a python-script. Even if the python script itself is run with sudo
and kill()
does not give any permission errors the process is not killed (and never receives SIGKILL
).
Investigating this, i found out that Popen()
returns the the process id of the sudo process, i assume at least, rather than the process i want to control. So when i correctly kill it later the underlying process keeps running. (Although if i kill the python program before killing the sudo process in python code the underlying process is also killed, so i guess there must be a way to do this manually, too).
I know it might be an option to use pgrep
or pidof
to search for the correct process, but as the processes name might not be unique it seems unnescessarly error prone (it might also occur that a process with the same name is started around the same time, so taking the latest one might not help).
Is there any solution to get reliably the pid
of the underlying process started with sudo in python?
Using Python3.
My code for conducting the tests, taken slightly modified from https://stackoverflow.com/a/43417395/1171541:
import subprocess, time
cmd = ["sudo", "testscript.sh"]
def myfunction(action, process=None):
if action === "start":
process = subprocess.Popen(cmd)
return process
if action === "stop"
# kill() and send_signal(signal.SIGTERM) do not work either
process.terminate()
process = myfunction("start")
time.sleep(5)
myfunction("stop", process);
Answers:
Okay, i can answer my own question here (which i found on https://izziswift.com/how-to-terminate-a-python-subprocess-launched-with-shelltrue/). The trick was to open the process with:
subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)
and then kill it:
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
This time i use a shell to open and use the os to kill all the processes in the process group.
There are two problems:
- sudo does not propagate SIGKILL, see
man sudo
. Solution: use the default signal SIGTERM. SIGKILL is bad practice anyway.
- sudo does not propagate signals sent from its own Progress Group ID out of fear of "accidentally kill itself", see
man sudo
. Solution: use start_new_session=True
which (indirectly) gives a different PGID.
Demonstration of issue 1:
$ sudo sleep 15 &
$ kill $! # sudo propagates the TERM signal and sleep is terminated
$ sudo sleep 15 & sudoPID=$!
$ ps xfao pid,ppid,pgid,sid,comm | grep -C 5 -e PID -e sleep -e sudo
# As documented, sudo does NOT propagate the KILL signal and sleep is STILL running!
$ kill -KILL $sudoPID
$ ps xfao pid,ppid,pgid,sid,comm | grep -C 5 -e PID -e sleep -e sudo
Demonstration of issue 2
# signal from different PGID is propagated and sleep is terminated
sudo sleep 15 & kill $!
# Signal sent from the same PGID is NOT propagated, sleep is still running!
sudo bash -c 'sleep 15 & killall sudo'
I am trying to start and later kill a process that requires sudo
via a python-script. Even if the python script itself is run with sudo
and kill()
does not give any permission errors the process is not killed (and never receives SIGKILL
).
Investigating this, i found out that Popen()
returns the the process id of the sudo process, i assume at least, rather than the process i want to control. So when i correctly kill it later the underlying process keeps running. (Although if i kill the python program before killing the sudo process in python code the underlying process is also killed, so i guess there must be a way to do this manually, too).
I know it might be an option to use pgrep
or pidof
to search for the correct process, but as the processes name might not be unique it seems unnescessarly error prone (it might also occur that a process with the same name is started around the same time, so taking the latest one might not help).
Is there any solution to get reliably the pid
of the underlying process started with sudo in python?
Using Python3.
My code for conducting the tests, taken slightly modified from https://stackoverflow.com/a/43417395/1171541:
import subprocess, time
cmd = ["sudo", "testscript.sh"]
def myfunction(action, process=None):
if action === "start":
process = subprocess.Popen(cmd)
return process
if action === "stop"
# kill() and send_signal(signal.SIGTERM) do not work either
process.terminate()
process = myfunction("start")
time.sleep(5)
myfunction("stop", process);
Okay, i can answer my own question here (which i found on https://izziswift.com/how-to-terminate-a-python-subprocess-launched-with-shelltrue/). The trick was to open the process with:
subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)
and then kill it:
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
This time i use a shell to open and use the os to kill all the processes in the process group.
There are two problems:
- sudo does not propagate SIGKILL, see
man sudo
. Solution: use the default signal SIGTERM. SIGKILL is bad practice anyway. - sudo does not propagate signals sent from its own Progress Group ID out of fear of "accidentally kill itself", see
man sudo
. Solution: usestart_new_session=True
which (indirectly) gives a different PGID.
Demonstration of issue 1:
$ sudo sleep 15 &
$ kill $! # sudo propagates the TERM signal and sleep is terminated
$ sudo sleep 15 & sudoPID=$!
$ ps xfao pid,ppid,pgid,sid,comm | grep -C 5 -e PID -e sleep -e sudo
# As documented, sudo does NOT propagate the KILL signal and sleep is STILL running!
$ kill -KILL $sudoPID
$ ps xfao pid,ppid,pgid,sid,comm | grep -C 5 -e PID -e sleep -e sudo
Demonstration of issue 2
# signal from different PGID is propagated and sleep is terminated
sudo sleep 15 & kill $!
# Signal sent from the same PGID is NOT propagated, sleep is still running!
sudo bash -c 'sleep 15 & killall sudo'