Last active
November 30, 2022 15:11
-
-
Save jirutka/9f7624a54b6f44b31c1dce3bea11708f to your computer and use it in GitHub Desktop.
Find processes that use (maps into memory) files which have been deleted or replaced on disk.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
#---help--- | |
# Usage: procs-using-deleted-files [options] | |
# | |
# Find processes that use (maps into memory) files which have been deleted | |
# or replaced on disk. If /proc/$PID/map_files is accessible, then it omits | |
# files that have been replaced by the same files (i.e. content is the same). | |
# | |
# Options: | |
# -e PATT... Case pattern (POSIX shell's "case") specifying paths to exclude | |
# when looking for modified mapped files. | |
# -H Print heading. | |
# -v... Verbosity (may be repeated up to 3 times). | |
# -h Show this message and exit. | |
# | |
# Source: <https://gist.github.com/jirutka/9f7624a54b6f44b31c1dce3bea11708f> | |
#---help--- | |
set -eu | |
# Set pipefail if supported. | |
if ( set -o pipefail 2>/dev/null ); then | |
set -o pipefail | |
fi | |
PROGNAME='procs-using-deleted-files' | |
help() { | |
sed -En '/^#---help---/,/^#---help---/p' "$0" | sed -E 's/^# ?//; 1d;$d;' | |
exit ${1:-0} | |
} | |
# Returns 0 if $1 matches "case" pattern $2, otherwise returns 1. | |
case_match() { | |
[ "$2" ] || return 1 | |
eval "case \"$1\" in $2) return 0;; *) return 1;; esac" | |
} | |
# Prints paths of files mapped by the process with PID $1 that have been | |
# deleted or replaced. You may specify "case" pattern $2 to exclude certain | |
# paths (e.g. "/dev/*|/home/*"). Returns 1 if no files was found. | |
changed_map_files() { | |
local pid="$1" | |
local exclude="${2:-}" | |
if [ ! -r /proc/$pid/maps ]; then | |
echo "$PROGNAME: could not read /proc/$pid/maps" >&2 | |
return 2 | |
fi | |
cat /proc/$pid/maps \ | |
| sed -En 's|^([a-f0-9-]+) .* {4,}(/.*) \(deleted\)$|\1 \2|p' \ | |
| grep -v '[[:cntrl:]"$\`]' \ | |
| sort -k 2 \ | |
| uniq -f 1 \ | |
| while read maddr filepath; do | |
map_file="/proc/$pid/map_files/$maddr" | |
# Skip excluded path. | |
case_match "$filepath" "$exclude" && continue | |
# Skip path that is not a file. | |
[ -e "$filepath" ] && [ -f "$filepath" ] || continue | |
# Don't compare file's content if /proc/$pid/map_files | |
# is not accessible (i.e. on Grsecurity kernel). | |
if [ -x "${map_file%/*}" ]; then | |
[ -e "$map_file" ] || continue | |
# Skip file which content has not been changed. | |
[ -e "$filepath" ] \ | |
&& ! cmp -s "$map_file" "$filepath" 2>/dev/null \ | |
|| continue | |
fi | |
echo "$filepath" | |
done | grep . | |
} | |
# Prints executable of the process with PID $1. | |
proc_exe() { | |
local exe | |
exe=$(readlink /proc/$1/exe) | |
exe=${exe% (deleted)} | |
exe=${exe%.apk-new} # special case for apk (Alpine Linux) | |
printf '%s\n' "$exe" | |
} | |
exclude_maps='' | |
print_header='no' | |
verbosity=0 | |
while getopts ':e:Hhv' OPTION; do | |
case "$OPTION" in | |
H) print_header='yes';; | |
e) exclude_maps="${exclude_maps:+"$exclude_maps|"}$OPTARG";; | |
v) verbosity=$(( $verbosity + 1 ));; | |
h) echo "Usage: $0 [-H] [-v] [-h]" >&2; exit 0;; | |
\?) echo "$PROGNAME: unrecognized option -$OPTARG" >&2; exit 100;; | |
esac | |
done | |
# Sanitize exclude pattern. | |
if [ "$exclude_maps" ]; then | |
exclude_maps=$(printf %s "$exclude_maps" | sed 's|[();]|\\&|g') | |
fi | |
if [ "$print_header" = yes ]; then | |
printf 'PID' | |
[ $verbosity -ge 1 ] && printf '\tEXE\tCMDLINE' | |
[ $verbosity -ge 2 ] && printf '\tMAPPED FILES' | |
printf '\n' | |
fi | |
for proc in /proc/[0-9]*; do | |
pid=${proc#/proc/} | |
# Skip kernel threads. | |
[ -e $proc/exe ] || continue | |
if files=$(changed_map_files $pid "$exclude_maps"); then | |
if [ $verbosity -ge 0 ]; then | |
printf '%d' $pid | |
fi | |
if [ $verbosity -ge 1 ]; then | |
printf '\t%s\t%s' \ | |
"$(proc_exe $pid)" \ | |
"$(cat $proc/cmdline | tr '\0\t' ' ')" | |
fi | |
if [ $verbosity -ge 2 ]; then | |
printf '\t' | |
printf '%s ' $files | |
fi | |
printf '\n' | |
fi | |
done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment