1#!/bin/sh
2
3# ########################################################################
4# This program is part of $PROJECT_NAME$
5# License: GPL License (see COPYING)
6# Authors:
7#  Baron Schwartz
8# ########################################################################
9
10# ########################################################################
11# Redirect STDERR to STDOUT; Nagios doesn't handle STDERR.
12# ########################################################################
13exec 2>&1
14
15# ########################################################################
16# Set up constants, etc.
17# ########################################################################
18STATE_OK=0
19STATE_WARNING=1
20STATE_CRITICAL=2
21STATE_UNKNOWN=3
22STATE_DEPENDENT=4
23
24# ########################################################################
25# Run the program.
26# ########################################################################
27main() {
28   # Get options
29   for o; do
30      case "${o}" in
31         -c)              shift; OPT_CRIT="${1}"; shift; ;;
32         --defaults-file) shift; OPT_DEFT="${1}"; shift; ;;
33         -H)              shift; OPT_HOST="${1}"; shift; ;;
34         -l)              shift; OPT_USER="${1}"; shift; ;;
35         -L)              shift; OPT_LOPA="${1}"; shift; ;;
36         -p)              shift; OPT_PASS="${1}"; shift; ;;
37         -P)              shift; OPT_PORT="${1}"; shift; ;;
38         -S)              shift; OPT_SOCK="${1}"; shift; ;;
39         -w)              shift; OPT_WARN="${1}"; shift; ;;
40         --version)       grep -A2 '^=head1 VERSION' "$0" | tail -n1; exit 0 ;;
41         --help)          perl -00 -ne 'm/^  Usage:/ && print' "$0"; exit 0 ;;
42         -*)              echo "Unknown option ${o}.  Try --help."; exit 1; ;;
43      esac
44   done
45   if [ -e '/usr/local/etc/nagios/mysql.cnf' ]; then
46      OPT_DEFT="${OPT_DEFT:-/usr/local/etc/nagios/mysql.cnf}"
47   fi
48   if is_not_sourced; then
49      if [ -n "$1" ]; then
50         echo "WARN spurious command-line options: $@"
51         exit 1
52      fi
53   fi
54
55   # Set the exit status in case there are any problems.
56   NOTE="UNK could not determine the PID file location."
57
58   # Set up files to hold one or more PID file locations.
59   local TEMP=$(mktemp -t "${0##*/}.XXXXXX") || exit $?
60   local FILES=$(mktemp -t "${0##*/}.XXXXXX") || exit $?
61   trap "rm -f '${TEMP}' '${FILES}' >/dev/null 2>&1" EXIT
62
63   # If any connection option was given, then try to log in to find the PID
64   # file.
65   if [ "${OPT_DEFT}${OPT_HOST}${OPT_USER}${OPT_PASS}${OPT_PORT}${OPT_SOCK}" ]; then
66      # If this fails (e.g. we can't log in), then there will be no line in the
67      # file, and later we won't change the exit code / note away from "UNK".
68      if mysql_exec "SHOW GLOBAL VARIABLES" > "${TEMP}"; then
69         get_pidfile "${TEMP}" >> "${FILES}"
70      fi
71   else
72      # Find all MySQL server instances.
73      for pid in $(_pidof mysqld); do
74         ps -p ${pid} -o pid,command | grep "${pid}" >> "${TEMP}"
75      done
76      # The ${TEMP} file may now contain lines like the following sample:
77      # 13822     /usr/sbin/mysqld --defaults-file=/var/lib/mysql/my.cnf \
78      #           --basedir=/usr --datadir=/var/lib/mysql/data/ \
79      #           --pid-file=/var/run/mysqld/mysqld.pid \
80      #           --socket=/var/run/mysqld/mysqld.sock
81      # Now the task is to read find any reference to a --pid-file or --pid_file option.
82      # We store these into the $FILES temp file.
83      while read pid command; do
84         if echo "${command}" | grep pid.file >/dev/null 2>&1; then
85            # Strip off everything up to and including --pid-file=
86            command="${command##*--pid?file=}"
87            # Strip off any options that follow this, assuming that there's not
88            # a space followed by a dash in the pidfile's path.
89            echo "${command%% -*}" >> "${FILES}"
90         fi
91      done < "${TEMP}"
92   fi
93
94   # TODO: maybe in the future we can also check whether the PID in the file is
95   # correct. TODO: maybe we should also alert on which PID is missing its
96   # pidfile.
97   MISSING=""
98   NOTE2=""
99   while read pidfile; do
100      if [ ! -e "${pidfile}" ]; then
101         MISSING=1
102         NOTE2="${NOTE2:+${NOTE2}; }missing ${pidfile}"
103      fi
104      NOTE="OK all PID files exist."
105   done < "${FILES}"
106
107   if [ "${MISSING}" ]; then
108      if [ "${OPT_CRIT}" ]; then
109         NOTE="CRIT ${NOTE2}"
110      else
111         NOTE="WARN ${NOTE2}"
112      fi
113   fi
114
115   echo $NOTE
116}
117
118# ########################################################################
119# Execute a MySQL command.
120# ########################################################################
121mysql_exec() {
122   mysql ${OPT_DEFT:+--defaults-file="${OPT_DEFT}"} \
123      ${OPT_LOPA:+--login-path="${OPT_LOPA}"} \
124      ${OPT_HOST:+-h"${OPT_HOST}"} ${OPT_PORT:+-P"${OPT_PORT}"} \
125      ${OPT_USER:+-u"${OPT_USER}"} ${OPT_PASS:+-p"${OPT_PASS}"} \
126      ${OPT_SOCK:+-S"${OPT_SOCK}"} -ss -e "$1"
127}
128
129# ########################################################################
130# A wrapper around pidof, which might not exist. The first argument is the
131# command name to match.
132# ########################################################################
133_pidof() {
134   if ! pidof "${1}" 2>/dev/null; then
135      ps axo pid,ucomm | awk -v comm="${1}" '$2 == comm { print $1 }'
136   fi
137}
138
139# ########################################################################
140# Unfortunately, MySQL 5.0 doesn't have a system variable @@pid_file, so
141# we have to use SHOW VARIABLES and a temp file.  In 5.1 and newer we
142# could have done it in a single SQL statement:
143#    SELECT IF(@@pid_file LIKE '/%', @@pid_file,
144#    CONCAT(@@basedir, @@pid_file))" >> "${FILES}"
145# The first argument is the file that contains SHOW VARIABLES.
146# ########################################################################
147get_pidfile() {
148   awk '
149      /^pid_file/ { pid_file = $2 }
150      /^basedir/  { basedir  = $2 }
151      END {
152         if ( substr(pid_file, 1, 1) != "/" ) {
153            pid_file = basedir pid_file;
154         }
155         print pid_file;
156      }
157      ' "$1"
158}
159
160# ########################################################################
161# Determine whether this program is being executed directly, or sourced/included
162# from another file.
163# ########################################################################
164is_not_sourced() {
165   [ "${0##*/}" = "pmp-check-mysql-pidfile" ] || [ "${0##*/}" = "bash" -a "$_" = "$0" ]
166}
167
168# ########################################################################
169# Execute the program if it was not included from another file.
170# This makes it possible to include without executing, and thus test.
171# ########################################################################
172if is_not_sourced; then
173   OUTPUT=$(main "$@")
174   EXITSTATUS=$STATE_UNKNOWN
175   case "${OUTPUT}" in
176      UNK*)  EXITSTATUS=$STATE_UNKNOWN;  ;;
177      OK*)   EXITSTATUS=$STATE_OK;       ;;
178      WARN*) EXITSTATUS=$STATE_WARNING;  ;;
179      CRIT*) EXITSTATUS=$STATE_CRITICAL; ;;
180   esac
181   echo "${OUTPUT}"
182   exit $EXITSTATUS
183fi
184
185# ############################################################################
186# Documentation
187# ############################################################################
188: <<'DOCUMENTATION'
189=pod
190
191=head1 NAME
192
193pmp-check-mysql-pidfile - Alert when the mysqld PID file is missing.
194
195=head1 SYNOPSIS
196
197  Usage: pmp-check-mysql-pidfile [OPTIONS]
198  Options:
199    -c CRIT         Critical threshold; makes a missing PID file critical.
200    --defaults-file FILE Only read mysql options from the given file.
201                    Defaults to /usr/local/etc/nagios/mysql.cnf if it exists.
202    -H HOST         MySQL hostname.
203    -l USER         MySQL username.
204    -L LOGIN-PATH   Use login-path to access MySQL (with MySQL client 5.6).
205    -p PASS         MySQL password.
206    -P PORT         MySQL port.
207    -S SOCKET       MySQL socket file.
208    -w WARN         Warning threshold; ignored.
209    --help          Print help and exit.
210    --version       Print version and exit.
211  Options must be given as --option value, not --option=value or -Ovalue.
212  Use perldoc to read embedded documentation with more details.
213
214=head1 DESCRIPTION
215
216This Nagios plugin checks to make sure that the MySQL PID file is not missing.
217The PID file contains the process ID of the MySQL server process, and is used by
218init scripts to start and stop the server. If it is deleted for some reason,
219then it is likely that the init script will not work correctly.  The file can be
220deleted by poorly written scripts, an accident, or a mistaken attempt to restart
221MySQL while it is already running, especially if mysqld is executed directly
222instead of using the init script.
223
224The plugin accepts the -w and -c options for compatibility with standard Nagios
225plugin conventions, but they are not based on a threshold. Instead, the plugin
226raises a warning by default, and if the -c option is given, it raises an error
227instead, regardless of the option.
228
229By default, this plugin will attempt to detect all running instances of MySQL,
230and verify the PID file's existence for each one.  It does this purely by
231examining the Unix process table with the C<ps> tool.  However, in some cases
232the process's command line does not list the path to the PID file.  If the tool
233fails to detect the MySQL server process, or if you wish to limit the check to a
234single instance in the event that there are multiple instances on a single
235server, then you can specify MySQL authentication options.  This will cause the
236plugin to skip examining the Unix processlist, log into MySQL, and examine the
237pid_file variable from SHOW VARIABLES to find the location of the PID file.
238
239=head1 PRIVILEGES
240
241This plugin executes the following commands against MySQL:
242
243=over
244
245=item *
246
247C<SELECT> the system variables C<@@pid_file> and C<@@basedir>.
248
249=back
250
251This plugin executes the following UNIX commands that may need special privileges:
252
253=over
254
255=item *
256
257ps
258
259=back
260
261The plugin should be able to either get variables from MySQL or find mysqld
262PID using C<ps> command.
263
264On BSD, if C<sysctl> option C<security.bsd.see_other_uids> is set to 0, C<ps>
265will not return mysqld PID if the plugin run from non-root user.
266
267Also an user you run the plugin from should be able to access MySQL PID file
268file, so you may want to add it into mysql unix group etc.
269
270=head1 COPYRIGHT, LICENSE, AND WARRANTY
271
272This program is copyright 2012-$CURRENT_YEAR$ Baron Schwartz, 2012-$CURRENT_YEAR$ Percona Inc.
273Feedback and improvements are welcome.
274
275THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
276WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
277MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
278
279This program is free software; you can redistribute it and/or modify it under
280the terms of the GNU General Public License as published by the Free Software
281Foundation, version 2.  You should have received a copy of the GNU General
282Public License along with this program; if not, write to the Free Software
283Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
284
285=head1 VERSION
286
287$PROJECT_NAME$ pmp-check-mysql-pidfile $VERSION$
288
289=cut
290
291DOCUMENTATION
292