Skip to content

Instantly share code, notes, and snippets.

@geraldlai
Created February 5, 2019 23:21
Show Gist options
  • Save geraldlai/fc87a7fa4ae92ff07683adb20249e406 to your computer and use it in GitHub Desktop.
Save geraldlai/fc87a7fa4ae92ff07683adb20249e406 to your computer and use it in GitHub Desktop.
snuff: kill processes gracefully
#!/usr/bin/perl
# snuff: kill processes gracefully
# Author: Gerald Lai
# License: 0BSD
use warnings;
use strict;
use Getopt::Long qw(GetOptions);
Getopt::Long::Configure(qw(posix_default no_ignore_case pass_through bundling));
use Pod::Usage qw(pod2usage);
use Time::HiRes qw(usleep);
my %OPT = (grace => 1);
GetOptions( \%OPT,
"grace|t=f",
"single|s",
"quiet|q",
"help|h|?" => \&help,
"man" => \&man,
) or pod2usage(
-message => "snuff: Error parsing command arguments.\n",
-verbose => 0, # SYNOPSIS only
-exitval => 22,
-output => \*STDERR,
);
# no input
(@ARGV == 0) and pod2usage(
-message => "snuff: No PIDs given.\n",
-verbose => 0, # SYNOPSIS only
-exitval => 22,
-output => \*STDERR,
);
$OPT{grace} = 1 if $OPT{grace} < 0;
$OPT{grace} *= 1e6;
my @snuff;
for my $pidarg (@ARGV)
{
next unless $pidarg =~ /^\d+$/;
my %psinfo;
my @pids = $pidarg;
# obtain list of child PIDs
unless ($OPT{single})
{
# take snapshot of process table
my %pspid;
for (qx(/bin/ps -A -o ppid= -o pid= -o etime= -o args=))
{
chomp;
my ($ppid, $pid, $etime, @cmd) = split;
push @{ $pspid{$ppid} }, $pid;
$psinfo{$pid} = " # [$etime] @cmd";
}
# build dependency list
my @search = $pidarg; # begin with PID as parent
while (@search)
{
my @temp = @search;
@search = ();
# search for children and prepend them to PID snuff list
for my $ppid (@temp)
{
my $children_ref = $pspid{$ppid};
push @search, @{ $children_ref } if defined($children_ref);
}
unshift @pids, @search;
}
}
# snuff by systematically sending sequence of signals
for my $pid (@pids)
{
my $pidtxt = $pid;
$pidtxt .= $psinfo{$pid} if defined($psinfo{$pid});
print STDERR "\n" unless $OPT{quiet};
next unless kill 0 => $pid;
push @snuff, $pid;
print STDERR "[SIGTERM] kill -15 $pidtxt\n" unless $OPT{quiet};
kill TERM => $pid;
usleep $OPT{grace};
next unless kill 0 => $pid;
print STDERR "[SIGINT] kill -2 $pidtxt\n" unless $OPT{quiet};
kill INT => $pid;
usleep $OPT{grace};
next unless kill 0 => $pid;
print STDERR "[SIGHUP] kill -1 $pidtxt\n" unless $OPT{quiet};
kill HUP => $pid;
usleep $OPT{grace};
next unless kill 0 => $pid;
print STDERR "[SIGKILL] kill -9 $pidtxt\n" unless $OPT{quiet};
kill KILL => $pid;
}
}
# print PIDs in snuffed order
if (@snuff && !$OPT{quiet})
{
print STDERR "\n";
print "@snuff\n";
}
exit (@snuff ? 0 : 1);
# SYNOPSIS
sub help {
pod2usage(
-verbose => 1,
-exitval => 1,
-output => \*STDERR,
);
}
# man page of POD
sub man {
pod2usage(
-verbose => 2,
-exitval => 1,
-output => \*STDOUT,
);
}
__END__
=head1 NAME
snuff - Kill Unix processes gracefully
=head1 SYNOPSIS
=over
=item snuff
[ -t, --grace <secs> ] Grace period before sending next signal. Default 1.
[ -s, --single ] Do not snuff child processes. Default disabled.
[ -q, --quiet ] Be non-verbose about snuffing processes. Default disabled.
[ -h ] [ --man ] Command usage / manpage.
pid1 [ pid2 ... ] Specify one or more PIDs.
=back
=head1 DEPENDENCIES
ps(1) -- C</bin/ps>
No non-core modules are required.
=head1 AUTHOR
Gerald Lai
=cut
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment