Killing All Processes for a Given User
Problem
You want an easy way to kill all the running processes of a user whose processes get out of control.
Solution
You can send a Unix signal (including the deadly SIGTERM or the even deadlier SIGKILL) from Ruby with the Process.kill method. But how to get the list of processes for a given user? The simplest way is to call out to the unix ps command and parse the output. Running ps -u#{username} gives us the processes for a particular user.
#!/usr/bin/ruby -w # banish.rb def signal_all(username, signal) lookup_uid(username) killed = 0 %x{ps -u#{username}}.each_with_index do |proc, i| next if i == 0 # Skip the header provided by ps pid = proc.split[0].to_i begin Process.kill(signal, pid) rescue SystemCallError => e raise e unless e.errno == Errno::ESRCH end killed += 1 end return killed end
There are a couple things to look out for here.
- ps dumps a big error message if we pass in the name of a nonexistent user. It would look better if we could handle that error ourselves. Thats what the call to lookup_uid will do.
- ps prints out a header as its first line. We want to skip that line because it doesn represent a process.
- Killing a process also kills all of its children. This can be a problem if the child process shows up later in the ps list: killing it again will raise a SystemCallError. We deal with that possibility by catching and ignoring that particular SystemCallError. We still count the process as "killed," though.
Heres the implementation of lookup_id:
def lookup_uid(username) require etc begin user = Etc.getpwnam(username) rescue ArgumentError raise ArgumentError, "No such user: #{username}" end return user.uid end
Now all that remains is the command-line interface:
require optparse signal = "SIGHUP" opts = OptionParser.new do |opts| opts.banner = "Usage: #{__FILE__} [-9] [USERNAME]" opts.on("-9", "--with-extreme-prejudice", "Send an uncatchable kill signal.") { signal = "SIGKILL" } end opts.parse!(ARGV) if ARGV.size != 1 $stderr.puts opts.banner exit end username = ARGV[0] if username == "root" $stderr.puts "Sorry, killing all of roots processes would bring down the system." exit end puts "Killed #{signal_all(username, signal)} process(es)."
As root, you can do some serious damage with this tool:
$ ./banish.rb peon 5 process(es) killed
Discussion
The main problem with banish.rb as written is that it depends on an external program. Whats worse, it depends on parsing the human-readable output of an external program. For a quick script this is fine, but this would be more reliable as a self-contained program.
You can get a Ruby interface to the Unix process table by installing the sysproctable library. This makes it easy to treat the list of currently running processes as a Ruby data structure. Heres an alternate implementation of signal_all that uses sys-proctable instead of invoking a separate program. Note that, unlike the other implementation, this one actually uses the return value of lookup_uid:
def signal_all(username, signal) uid = lookup_uid(username) require sys/proctable killed = 0 Sys::ProcTable.ps.each do |proc| if proc.uid == uid begin Process.kill(signal, proc.pid) rescue SystemCallError => e raise e unless e.errno == Errno::ESRCH end killed += 1 end end return killed end
See Also
- sys-proctable is in the RAA at http://raa.ruby-lang.org/project/sys-proctable/; its one of the sysutils packages: see http://rubyforge.org/projects/sysutils for the others
- To write an equivalent program for Windows, youd either use WMIthrough Rubys win32ole standard library, or install a native binary of GNUs ps and use win32-process
About the Authors
Lucas Carlson is a professional Ruby programmer who specializes in Rails web development. He has authored a half-dozen libraries and contributed to many others, including Rails and RedCloth. He lives in Portland, Oregon and maintains a web site at http://rufy.com/. Leonard Richardson has been programming since he was eight years old. Recently, the quality of his code has improved somewhat. He is responsible for libraries in many languages, including Rubyful Soup. A California native, he now works in New York and maintains a web site at http://www.crummy.com/. |
Colophon
The animal on the cover of Ruby Cookbook is a side-striped jackal (Canis adustus), found mostly in central and southern Africa. These jackals avoid the open, preferring thickly wooded areas on the edge of savannas and forests. They occasionally make their way into cities. Side-striped jackals are rare but not considered endangered. There are reserves for these jackals at the Serengeti National Park in Tanzania and at the Akagera National Park in Rwanda. Side-striped jackals are about 15 inches tall and weigh between 16 and 26 pounds. This jackal has a light grey coat with a white stripe from shoulder to hip, and a white-tipped tail. The diet of side-striped jackals consists largely of wild fruits, small mammals, and insects. They also eat carrion and are adept scavengers; they will follow a lion or other big cat to a kill. The jackals usually live singly or in pairs, but they sometimes gather in family units of up to six members. Their lifespan is about 10 to 12 years. Jackals have been an object of superstition because of their association with carrion and death, and because of their eerie nocturnal noises: they hoot, yap, and make a kind of screaming yell. Perhaps because jackals were often found prowling and hunting the edges of the desert near cemeteries, the ancient Egyptian god of embalming and gatekeeper of the path of the dead, Anubis, was depicted as a jackalheaded man. Anubis served as a psychopomp, conducting souls to the underworld, where he weighed their hearts on a scale to determine whether they would be admitted to the underworld or cast to the crocodile-headed demon, Ammit. The cover image is from Lydekkers Royal History. The cover font is Adobe ITC Garamond. The text font is Linotype Birka; the heading font is Adobe Myriad Condensed; and the code font is LucasFonts TheSans Mono Condensed. |