Skip to content

Instantly share code, notes, and snippets.

@Ropid
Created May 7, 2026 21:10
Show Gist options
  • Select an option

  • Save Ropid/0d9a753d8c69c21e4e25cc6819eadec9 to your computer and use it in GitHub Desktop.

Select an option

Save Ropid/0d9a753d8c69c21e4e25cc6819eadec9 to your computer and use it in GitHub Desktop.
list running programs that are using deleted files
#!/bin/perl
use strict;
use warnings FATAL => 'all';
use 5.020;
# use Data::Dumper qw'Dumper';
# use List::Util qw'any none';
use POSIX qw'isatty';
my ($program_name) = $0 =~ m{(?:.*/)?(.*)};
## search paths
my $whitelist = qr{
^/usr/ |
^/opt/
}x;
my $blacklist = qr{
[.]cache$ |
\Q.cache (deleted)\E$ |
/gschemas[.]compiled$ |
^/usr/share/locale/
}x;
## hash with ANSI color escape codes; empty values if not running in a terminal
my %C;
for ("r", "a0".."a7", "b0".."b7") {
$C{$_} = "";
}
if (isatty *STDOUT) {
for (keys %C) {
$C{$_} = s/(.)(.)/\e[${1};3${2}m/r =~ tr/ab/01/r;
}
$C{r} = "\e[m";
}
sub usage {
print STDERR << "EOF";
Usage: $program_name [options]
Finds services and programs that need to be restarted because of stale
file handles after a system update.
Options:
verbose Print details.
EOF
}
## returns list with removed duplicates
sub uniq {
my %seen;
return grep { not $seen{$_}++ } @_;
}
## see sub 'usage' for parameter descriptions
my $verbose;
for (@ARGV) {
if ($_ and $_ eq substr("verbose", 0, length $_)) {
$verbose = 1;
} else {
usage;
exit 1;
}
}
if ($< != 0) {
if (not exec 'sudo', $0, @ARGV) {
say STDERR "$program_name: Error! This program needs to run as root.";
exit 1;
}
}
#if ($< != 0) {
#say STDERR "$program_name: Error! This program needs to run as root.";
#exit 1;
#}
## holds info for each process; keys are PIDs
my %procs;
## use external 'lsof' to find processes that are using deleted files with
## file-names matching $whitelist and $blacklist
$_ = qx'lsof +L1 -Fpcun 2> /dev/null; lsof -dDEL -Fpcun 2> /dev/null';
for (split m/^(?=p)/m) {
my ($p, $c, $u) = (/^p(.*)/m, /^c(.*)/m, /^u(.*)/m);
my @n;
for (/^n(.*)/mg) {
#push @n, $_ if /$whitelist/ and not /$blacklist/;
push @n, $_ =~ s{.*/}{}r if /$whitelist/ and not /$blacklist/;
}
if (@n) {
$procs{$p} = {
PID => $p,
UID => $u,
NAME => \@n,
COMMAND => $c
};
}
}
## prints details for a list of PIDs as recorded in %procs
sub details {
my $pids = shift;
for (map { $procs{$_} } sort { $a <=> $b } @$pids) {
printf "$C{a4}%6d $C{a3}%-15s$C{r} %s\n",
$_->{PID},
$_->{COMMAND},
(join ", ", @{ $_->{NAME} });
}
}
if (%procs) {
print << "END";
$C{b4}--------------------------------------------------------$C{r}
$C{b7} Services and programs that might need to be restarted: $C{r}
$C{b4}--------------------------------------------------------$C{r}
END
## Use external 'ps' to find unit names for the processes.
## Create PID lists hashed by unit names with the regular user's processes
## split off into a separate hash.
## Assumes regular users are UID>=1000.
my %units; my %units1000;
$_ = join " ", keys %procs;
$_ = qx{ps -o pid=,unit= -p $_ 2> /dev/null};
while (/^ *(\d+) *(.*)/mg) {
my $ref = ($procs{$1}->{UID} < 1000) ? \%units : \%units1000;
push @{ $ref->{$2} }, $1;
}
# my %units; my %units1000;
# for (values %procs) {
# my $ref = ($$_{UID} < 1000) ? \%units : \%units1000;
# push @{ $$ref{$$_{UNIT}} }, $$_{PID};
# }
## print system units
for (sort keys %units) {
if ($verbose) {
say "$C{b4}> $C{b1}$_$C{r}:";
details \@{ $units{$_} };
# say " ", join ", ", uniq map { @{$_} } map { $procs{$_}->{NAME} } @{$units{$_}};
} else {
say "$C{b4}> $C{b3}$_$C{r}";
}
}
## print user's units
for (sort keys %units1000) {
say "$C{b4}> $C{b2}$_$C{r}:";
# details \@{ (sort { $a cmp $b } @{ $units1000{$_} }) };
details \@{ $units1000{$_} };
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment