ctdb-scripts: Avoid direct /proc access
[vlendec/samba-autobuild/.git] / ctdb / config / debug_locks.sh
1 #!/bin/sh
2
3 # This script parses /proc/locks and finds the processes that are holding
4 # locks on CTDB databases.  For all those processes the script dumps a
5 # stack trace.
6 #
7 # This script can be used only if Samba is configured to use fcntl locks
8 # rather than mutex locks.
9
10 [ -n "$CTDB_BASE" ] || \
11     CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; echo "$PWD")
12
13 . "${CTDB_BASE}/functions"
14
15 # type is at least mentioned in POSIX and more is portable than which(1)
16 # shellcheck disable=SC2039
17 if ! type gstack >/dev/null 2>&1 ; then
18         gstack ()
19         {
20                 _pid="$1"
21
22                 gdb -batch --quiet -nx "/proc/${_pid}/exe" "$_pid" \
23                     -ex "thread apply all bt" 2>/dev/null |
24                         grep '^\(#\|Thread \)'
25         }
26 fi
27
28 # Load/cache database options from configuration file
29 ctdb_get_db_options
30
31 dump_stack ()
32 {
33         _pid="$1"
34
35         echo "----- Stack trace for PID=${_pid} -----"
36         _state=$(ps -p "$_pid" -o state= | cut -c 1)
37         if [ "$_state" = "D" ] ; then
38                 # Don't run gstack on a process in D state since
39                 # gstack will hang until the process exits D state.
40                 # Although it is possible for a process to transition
41                 # to D state after this check, it is unlikely because
42                 # if a process is stuck in D state then it is probably
43                 # the reason why this script was called.  Note that a
44                 # kernel stack almost certainly won't help diagnose a
45                 # deadlock... but it will probably give us someone to
46                 # blame!
47                 echo "----- Process in D state, printing kernel stack only"
48                 get_proc "${_pid}/stack"
49         else
50                 gstack "$_pid"
51         fi
52 }
53
54 dump_stacks ()
55 {
56         _pids="$1"
57
58         # Use word splitting to squash whitespace
59         # shellcheck disable=SC2086
60         _pids=$(echo $_pids | tr ' ' '\n' | sort -u)
61
62         for _pid in $_pids; do
63                 dump_stack "$_pid"
64         done
65 }
66
67 (
68     flock -n 9 || exit 1
69
70     echo "===== Start of debug locks PID=$$ ====="
71
72     # Create sed expression to convert inodes to names.
73     # Filenames don't contain dashes and we want basenames
74     # shellcheck disable=SC2035
75     sed_cmd=$(cd "$CTDB_DBDIR" &&
76                   stat -c "s#[0-9a-f]*:[0-9a-f]*:%i #%n #" *.tdb.* 2>/dev/null ;
77               cd "$CTDB_DBDIR_PERSISTENT" &&
78                   stat -c "s#[0-9a-f]*:[0-9a-f]*:%i #%n #" *.tdb.* 2>/dev/null)
79
80     # Parse /proc/locks and extract following information
81     #    pid process_name tdb_name offsets [W]
82     out=$( get_proc "locks" |
83     grep -F "POSIX  ADVISORY  WRITE" |
84     awk '{ if($2 == "->") { print $6, $7, $8, $9, "W" } else { print $5, $6, $7, $8 } }' |
85     while read pid rest ; do
86         pname=$(ps -p "$pid" -o comm=)
87         echo "$pid $pname $rest"
88     done | sed -e "$sed_cmd" | grep '\.tdb' )
89
90     if [ -n "$out" ]; then
91         # Log information about locks
92         echo "$out"
93
94         # Find processes that are waiting for locks
95         dbs=$(echo "$out" | grep "W$" | awk '{print $3}')
96         all_pids=""
97         for db in $dbs ; do
98             pids=$(echo "$out" | grep -v "W$" | grep "$db" | grep -v ctdbd | awk '{print $1}')
99             all_pids="$all_pids $pids"
100         done
101
102         dump_stacks "$all_pids"
103     fi
104
105     echo "===== End of debug locks PID=$$ ====="
106 )9>"${CTDB_SCRIPT_VARDIR}/debug_locks.lock" | script_log "ctdbd-lock"
107
108 exit 0