
* Don't run test script on non-Linux. * Verify that reported fds do indeed exist also in parent, to avoid false negatives on some systems (namely CentOS). Co-authored-by: Andy Pan <panjf2000@gmail.com>
80 lines
2.2 KiB
Tcl
Executable File
80 lines
2.2 KiB
Tcl
Executable File
#!/usr/bin/env tclsh
|
|
#
|
|
# This script detects file descriptors that have leaked from a parent process.
|
|
#
|
|
# Our goal is to detect file descriptors that were opened by the parent and
|
|
# not cleaned up prior to exec(), but not file descriptors that were inherited
|
|
# from the grandparent which the parent knows nothing about. To do that, we
|
|
# look up every potential leak and try to match it against open files by the
|
|
# grandparent process.
|
|
|
|
# Get PID of parent process
|
|
proc get_parent_pid {_pid} {
|
|
set fd [open "/proc/$_pid/status" "r"]
|
|
set content [read $fd]
|
|
close $fd
|
|
|
|
if {[regexp {\nPPid:\s+(\d+)} $content _ ppid]} {
|
|
return $ppid
|
|
}
|
|
|
|
error "failed to get parent pid"
|
|
}
|
|
|
|
# Read symlink to get info about the specified fd of the specified process.
|
|
# The result can be the file name or an arbitrary string that identifies it.
|
|
# When not able to read, an empty string is returned.
|
|
proc get_fdlink {_pid fd} {
|
|
if { [catch {set fdlink [file readlink "/proc/$_pid/fd/$fd"]} err] } {
|
|
return ""
|
|
}
|
|
return $fdlink
|
|
}
|
|
|
|
# Linux only
|
|
set os [exec uname]
|
|
if {$os != "Linux"} {
|
|
puts "Only Linux is supported."
|
|
exit 0
|
|
}
|
|
|
|
if {![info exists env(LEAKED_FDS_FILE)]} {
|
|
puts "Missing LEAKED_FDS_FILE environment variable."
|
|
exit 0
|
|
}
|
|
|
|
set outfile $::env(LEAKED_FDS_FILE)
|
|
set parent_pid [get_parent_pid [pid]]
|
|
set grandparent_pid [get_parent_pid $parent_pid]
|
|
set leaked_fds {}
|
|
|
|
# Look for fds that were directly inherited from our parent but not from
|
|
# our grandparent (tcl)
|
|
foreach fd [glob -tails -directory "/proc/self/fd" *] {
|
|
# Ignore stdin/stdout/stderr
|
|
if {$fd == 0 || $fd == 1 || $fd == 2} {
|
|
continue
|
|
}
|
|
|
|
set fdlink [get_fdlink "self" $fd]
|
|
if {$fdlink == ""} {
|
|
continue
|
|
}
|
|
|
|
# We ignore fds that existed in the grandparent, or fds that don't exist
|
|
# in our parent (Sentinel process).
|
|
if {[get_fdlink $grandparent_pid $fd] == $fdlink ||
|
|
[get_fdlink $parent_pid $fd] != $fdlink} {
|
|
continue
|
|
}
|
|
|
|
lappend leaked_fds [list $fd $fdlink]
|
|
}
|
|
|
|
# Produce report only if we found leaks
|
|
if {[llength $leaked_fds] > 0} {
|
|
set fd [open $outfile "w"]
|
|
puts $fd [join $leaked_fds "\n"]
|
|
close $fd
|
|
}
|