framp
framp

Reputation: 141

How to kill a shell script invoked with Popen with sudo?

I wrote a GTK GUI application in Python to just invoke a shell script and pass invocation parameters to the script from the GUI and return the script results in a text window.

Now I like to allow the user to cancel a running shell script from the GUI. The script is started with Popen. It works fine if I invoke it as a normal user (flag sudo=False in the code below). When I use sudo to invoke the script I cannot cancel it any more. I found people reporting kill will not work if I use shell=True, so I tested it with shell=False but it doesn't work either.

I stripped down my issue to the following code snippet:

import os
import signal
import subprocess
import time
import sys
import shlex

bashScriptFileName="t.sh"

f = open(bashScriptFileName,'w')

f.write("""#!/bin/bash
on_die()
{
    echo "Dying..."
    exit 0
}  

trap 'on_die' TERM

SEC=0
while true ; do
    sleep 1
    SEC=$((SEC+1))
    echo "I'm PID# $$, and I'm alive for $SEC seconds now!"
done

exit 0""")
f.close()
os.chmod(bashScriptFileName, 0755)

shell=True      # flag to enable/disable shell invocation of Popen
sudo=True       # flag to invoke command with sudo

if sudo:
    commandToExecute='sudo -S '+ bashScriptFileName
else:
    commandToExecute='./' + bashScriptFileName

if not shell:
    commandToExecute = shlex.split(commandToExecute)

print "Command: %s" % (commandToExecute)                                

proc = subprocess.Popen(commandToExecute, stdin=subprocess.PIPE, close_fds=False,shell=shell, preexec_fn=os.setsid)
print >> proc.stdin, "secretPassword"                
print 'PARENT      : Pausing before sending signal to child %s...' % proc.pid
sys.stdout.flush()
time.sleep(5)
print 'PARENT      : Signaling child %s' % proc.pid
sys.stdout.flush()
os.killpg(proc.pid, signal.SIGTERM)
time.sleep(3)
print "Done"

I actually already had problems to get the script canceled as a normal user with os.kill but then found out I have to use os.setuid and killpg to kill the running shell. I'm new to threading and it may be just a simple Linux threading misunderstanding from my side ...

Upvotes: 2

Views: 3930

Answers (2)

taksan
taksan

Reputation: 271

TL;DR;

A far more elegant way to handle this problem, is starting the process using pyexpect instead of "subprocess.Popen".

spawnProcess = pexpect.spawn(command)

Then, when you want to "kill" the process, all you have to is:

spawnProcess.sendcontrol("c")

Explanation:

Running sudo kill from os.system is a dirty hack and requires the user to type in the password again or requires you to store the root password just to allow you to kill a subprocess. In my case it's even worse, because the process is not started with sudo, it's setuid root executable, so the target user really doesn't have sudo access.

As it turns out, "CTRL+C" from the bash is NOT the same as kill -INT, as a lot of people lead you to believe. You CAN "CTRL+C" a process you start with sudo or with setuid root, but you CAN'T send SIGINT to the process. I found the explanation to this here:

https://www.reddit.com/r/linux/comments/2av912/how_does_sudo_get_signals_from_the_controlling/ciz574v

Knowing that only "CTRL+C" would really work, I devised a solution the imitates what the user would do, and it works like a charm!

Upvotes: 2

CrazyCasta
CrazyCasta

Reputation: 28362

If you started it with sudo you'll have to have root privileges to kill it. Basically, you'll have to do:

os.system("sudo kill %d"%(pid))

Also, I would suggest for security purposes that you create your bash script as root, put it wherever it's convient, not give the user write access to it, and finally setup sudo to run it without needing a password. This will negate the need to store your password in this python script.

Upvotes: 3

Related Questions