#!/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{$_} }; } }