Skip to content

Instantly share code, notes, and snippets.

@philpennock
Last active November 11, 2021 00:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save philpennock/6f6de681305ad65612d0809e29eef54f to your computer and use it in GitHub Desktop.
Save philpennock/6f6de681305ad65612d0809e29eef54f to your computer and use it in GitHub Desktop.
ps_byjail: ps(1) wrapper for FreeBSD, showing which jail each process is in
#!/usr/bin/env perl
use strict;
use warnings;
#ps axdro user,pid,ppid,pgid,jid,stat,time,%cpu,%mem,vsz,rss,command ::
# $F[2] is PPID
# 0 means in-kernel process, all "real" processes (except init) have a positive integer as PPID
# Skip those where parent is this script
# $F[1] is PID
# Skip this script
#
# with 'j' flags were in $F[6] but we're putting them in $F[5]
# $F[5] has flags, which include 'J' for in-jail processes
# ax for all processes
# d for hierarchy, combined with r for sort-by-CPU between siblings
# o to control fields
my $PS = 'ps axdr';
my $PS_o = 'user,pid,ppid,pgid,jid,stat,time,%cpu,%mem,vsz,rss,command';
my $byj = {};
my @hdrs = ();
my $jnames = { 0 => "<nojail>" };
my %ps_takes_param = map {; "-$_" => 1 } qw/O o G J M N p t U/;
my %ps_inhibit_PS_o = map {; "-$_" => 1 } qw/O o/;
# if argv lists jail numbers/ids, restrict to those; signal not restricting
# by leaving this undef (below is set undef if empty after parsing argv):
my $only_jails = {};
my ($COL_pid, $COL_ppid, $COL_jid, $COL_stat);
my %COL_set = (
'pid' => \$COL_pid,
'ppid' => \$COL_ppid,
'jid' => \$COL_jid,
'stat' => \$COL_stat,
);
sub extract_ps_fields {
my $opt = $_[1];
my @f = split /(?:\s|,)/, $_[0];
@f = ('pid', @f, qw/tt stat time command/) if $opt eq '-O';
for (my $i = 0; $i < $#f; $i++) {
my $o = lc ((split /=/, $f[$i])[0]);
if (exists $COL_set{$o}) {
${$COL_set{$o}} = $i;
#print "DBG: $o ${$COL_set{$o}}\n";
}
}
my $bad = 0;
foreach my $k (sort keys %COL_set) {
next if defined ${$COL_set{$k}};
print STDERR "Missing: ps field '$k'\n";
$bad = 1;
}
exit 1 if $bad;
}
my $swallow = undef;
my $want_PS_o = 1;
foreach my $i (@ARGV) {
# full parsing would handle -abcXfoo where -X takes foo, we assume caller
# is splitting things differently
if (defined $swallow) {
extract_ps_fields($i, $swallow) if exists $ps_inhibit_PS_o{$swallow};
$swallow = undef;
$PS .= " $i";
next;
}
if ($i =~ /^-/) {
$PS .= " $i";
$swallow = $i if exists $ps_takes_param{$i};
$want_PS_o = 0 if exists $ps_inhibit_PS_o{$i};
next;
}
#print "DBG: only-jails: $i\n";
$only_jails->{$i} = 1;
$only_jails->{0} = 1 if lc($i) eq 'nojail';
}
if ($want_PS_o) {
$PS .= " -o $PS_o";
extract_ps_fields($PS_o, "-o");
}
#print "DBG: PS: $PS\n";
$only_jails = undef unless scalar %{$only_jails};
foreach (`jls -n`) {
/\bjid=(\S++).*\bname=(\S++)/ or next;
$jnames->{$1} = $2;
#print "DBG: <$1> <$2>\n" unless defined $only_jails;
}
foreach (`$PS`) {
my @F = split(' ');
if ($F[$COL_pid] eq "PID") { push @hdrs, $_; next };
next if $F[$COL_ppid] eq "0" && $F[$COL_pid] ne "1";
next if $F[$COL_pid] eq $$ or $F[$COL_ppid] eq $$;
#print "DBG: new jid: $F[$COL_jid]\n" unless exists $byj->{$F[$COL_jid]};
$byj->{$F[$COL_jid]} = [] unless exists $byj->{$F[$COL_jid]};
push @{$byj->{$F[$COL_jid]}}, $_;
}
#print "DBG: jail: $_\n" foreach sort keys %$byj;
map { s/^(.+)$/\e[33m$1\e[0m/ } @hdrs; # make headers yellow
foreach my $j (sort {$a <=> $b} keys %$byj) {
if (defined $only_jails) {
next unless exists $only_jails->{$j} or exists $only_jails->{$jnames->{$j}};
};
print "\e[32mJail: \e[1m$j\t$jnames->{$j}\e[0m\n";
print foreach @hdrs;
print foreach @{$byj->{$j}};
print "\n";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment