xref: /freebsd-src/sys/contrib/openzfs/cmd/zed/zed.d/zed-functions.sh (revision e92ffd9b626833ebdbf2742c8ffddc6cd94b963e)
1eda14cbcSMatt Macy#!/bin/sh
2*e92ffd9bSMartin Matuska# shellcheck disable=SC2154,SC3043
3eda14cbcSMatt Macy# zed-functions.sh
4eda14cbcSMatt Macy#
5eda14cbcSMatt Macy# ZED helper functions for use in ZEDLETs
6eda14cbcSMatt Macy
7eda14cbcSMatt Macy
8eda14cbcSMatt Macy# Variable Defaults
9eda14cbcSMatt Macy#
10eda14cbcSMatt Macy: "${ZED_LOCKDIR:="/var/lock"}"
11eda14cbcSMatt Macy: "${ZED_NOTIFY_INTERVAL_SECS:=3600}"
12eda14cbcSMatt Macy: "${ZED_NOTIFY_VERBOSE:=0}"
13eda14cbcSMatt Macy: "${ZED_RUNDIR:="/var/run"}"
14eda14cbcSMatt Macy: "${ZED_SYSLOG_PRIORITY:="daemon.notice"}"
15eda14cbcSMatt Macy: "${ZED_SYSLOG_TAG:="zed"}"
16eda14cbcSMatt Macy
17eda14cbcSMatt MacyZED_FLOCK_FD=8
18eda14cbcSMatt Macy
19eda14cbcSMatt Macy
20eda14cbcSMatt Macy# zed_check_cmd (cmd, ...)
21eda14cbcSMatt Macy#
22eda14cbcSMatt Macy# For each argument given, search PATH for the executable command [cmd].
23eda14cbcSMatt Macy# Log a message if [cmd] is not found.
24eda14cbcSMatt Macy#
25eda14cbcSMatt Macy# Arguments
26eda14cbcSMatt Macy#   cmd: name of executable command for which to search
27eda14cbcSMatt Macy#
28eda14cbcSMatt Macy# Return
29eda14cbcSMatt Macy#   0 if all commands are found in PATH and are executable
30eda14cbcSMatt Macy#   n for a count of the command executables that are not found
31eda14cbcSMatt Macy#
32eda14cbcSMatt Macyzed_check_cmd()
33eda14cbcSMatt Macy{
34eda14cbcSMatt Macy    local cmd
35eda14cbcSMatt Macy    local rv=0
36eda14cbcSMatt Macy
37eda14cbcSMatt Macy    for cmd; do
38eda14cbcSMatt Macy        if ! command -v "${cmd}" >/dev/null 2>&1; then
39eda14cbcSMatt Macy            zed_log_err "\"${cmd}\" not installed"
40eda14cbcSMatt Macy            rv=$((rv + 1))
41eda14cbcSMatt Macy        fi
42eda14cbcSMatt Macy    done
43eda14cbcSMatt Macy    return "${rv}"
44eda14cbcSMatt Macy}
45eda14cbcSMatt Macy
46eda14cbcSMatt Macy
47eda14cbcSMatt Macy# zed_log_msg (msg, ...)
48eda14cbcSMatt Macy#
49eda14cbcSMatt Macy# Write all argument strings to the system log.
50eda14cbcSMatt Macy#
51eda14cbcSMatt Macy# Globals
52eda14cbcSMatt Macy#   ZED_SYSLOG_PRIORITY
53eda14cbcSMatt Macy#   ZED_SYSLOG_TAG
54eda14cbcSMatt Macy#
55eda14cbcSMatt Macy# Return
56eda14cbcSMatt Macy#   nothing
57eda14cbcSMatt Macy#
58eda14cbcSMatt Macyzed_log_msg()
59eda14cbcSMatt Macy{
60eda14cbcSMatt Macy    logger -p "${ZED_SYSLOG_PRIORITY}" -t "${ZED_SYSLOG_TAG}" -- "$@"
61eda14cbcSMatt Macy}
62eda14cbcSMatt Macy
63eda14cbcSMatt Macy
64eda14cbcSMatt Macy# zed_log_err (msg, ...)
65eda14cbcSMatt Macy#
66eda14cbcSMatt Macy# Write an error message to the system log.  This message will contain the
67eda14cbcSMatt Macy# script name, EID, and all argument strings.
68eda14cbcSMatt Macy#
69eda14cbcSMatt Macy# Globals
70eda14cbcSMatt Macy#   ZED_SYSLOG_PRIORITY
71eda14cbcSMatt Macy#   ZED_SYSLOG_TAG
72eda14cbcSMatt Macy#   ZEVENT_EID
73eda14cbcSMatt Macy#
74eda14cbcSMatt Macy# Return
75eda14cbcSMatt Macy#   nothing
76eda14cbcSMatt Macy#
77eda14cbcSMatt Macyzed_log_err()
78eda14cbcSMatt Macy{
79eda14cbcSMatt Macy    logger -p "${ZED_SYSLOG_PRIORITY}" -t "${ZED_SYSLOG_TAG}" -- "error:" \
80dae17134SMartin Matuska        "${0##*/}:""${ZEVENT_EID:+" eid=${ZEVENT_EID}:"}" "$@"
81eda14cbcSMatt Macy}
82eda14cbcSMatt Macy
83eda14cbcSMatt Macy
84eda14cbcSMatt Macy# zed_lock (lockfile, [fd])
85eda14cbcSMatt Macy#
86eda14cbcSMatt Macy# Obtain an exclusive (write) lock on [lockfile].  If the lock cannot be
87eda14cbcSMatt Macy# immediately acquired, wait until it becomes available.
88eda14cbcSMatt Macy#
89eda14cbcSMatt Macy# Every zed_lock() must be paired with a corresponding zed_unlock().
90eda14cbcSMatt Macy#
91eda14cbcSMatt Macy# By default, flock-style locks associate the lockfile with file descriptor 8.
92eda14cbcSMatt Macy# The bash manpage warns that file descriptors >9 should be used with care as
93eda14cbcSMatt Macy# they may conflict with file descriptors used internally by the shell.  File
94eda14cbcSMatt Macy# descriptor 9 is reserved for zed_rate_limit().  If concurrent locks are held
95eda14cbcSMatt Macy# within the same process, they must use different file descriptors (preferably
96eda14cbcSMatt Macy# decrementing from 8); otherwise, obtaining a new lock with a given file
97eda14cbcSMatt Macy# descriptor will release the previous lock associated with that descriptor.
98eda14cbcSMatt Macy#
99eda14cbcSMatt Macy# Arguments
100eda14cbcSMatt Macy#   lockfile: pathname of the lock file; the lock will be stored in
101eda14cbcSMatt Macy#     ZED_LOCKDIR unless the pathname contains a "/".
102eda14cbcSMatt Macy#   fd: integer for the file descriptor used by flock (OPTIONAL unless holding
103eda14cbcSMatt Macy#     concurrent locks)
104eda14cbcSMatt Macy#
105eda14cbcSMatt Macy# Globals
106eda14cbcSMatt Macy#   ZED_FLOCK_FD
107eda14cbcSMatt Macy#   ZED_LOCKDIR
108eda14cbcSMatt Macy#
109eda14cbcSMatt Macy# Return
110eda14cbcSMatt Macy#   nothing
111eda14cbcSMatt Macy#
112eda14cbcSMatt Macyzed_lock()
113eda14cbcSMatt Macy{
114eda14cbcSMatt Macy    local lockfile="$1"
115eda14cbcSMatt Macy    local fd="${2:-${ZED_FLOCK_FD}}"
116eda14cbcSMatt Macy    local umask_bak
117eda14cbcSMatt Macy    local err
118eda14cbcSMatt Macy
119eda14cbcSMatt Macy    [ -n "${lockfile}" ] || return
120eda14cbcSMatt Macy    if ! expr "${lockfile}" : '.*/' >/dev/null 2>&1; then
121eda14cbcSMatt Macy        lockfile="${ZED_LOCKDIR}/${lockfile}"
122eda14cbcSMatt Macy    fi
123eda14cbcSMatt Macy
124eda14cbcSMatt Macy    umask_bak="$(umask)"
125eda14cbcSMatt Macy    umask 077
126eda14cbcSMatt Macy
127eda14cbcSMatt Macy    # Obtain a lock on the file bound to the given file descriptor.
128eda14cbcSMatt Macy    #
1293ff01b23SMartin Matuska    eval "exec ${fd}>> '${lockfile}'"
13016038816SMartin Matuska    if ! err="$(flock --exclusive "${fd}" 2>&1)"; then
131eda14cbcSMatt Macy        zed_log_err "failed to lock \"${lockfile}\": ${err}"
132eda14cbcSMatt Macy    fi
133eda14cbcSMatt Macy
134eda14cbcSMatt Macy    umask "${umask_bak}"
135eda14cbcSMatt Macy}
136eda14cbcSMatt Macy
137eda14cbcSMatt Macy
138eda14cbcSMatt Macy# zed_unlock (lockfile, [fd])
139eda14cbcSMatt Macy#
140eda14cbcSMatt Macy# Release the lock on [lockfile].
141eda14cbcSMatt Macy#
142eda14cbcSMatt Macy# Arguments
143eda14cbcSMatt Macy#   lockfile: pathname of the lock file
144eda14cbcSMatt Macy#   fd: integer for the file descriptor used by flock (must match the file
145eda14cbcSMatt Macy#     descriptor passed to the zed_lock function call)
146eda14cbcSMatt Macy#
147eda14cbcSMatt Macy# Globals
148eda14cbcSMatt Macy#   ZED_FLOCK_FD
149eda14cbcSMatt Macy#   ZED_LOCKDIR
150eda14cbcSMatt Macy#
151eda14cbcSMatt Macy# Return
152eda14cbcSMatt Macy#   nothing
153eda14cbcSMatt Macy#
154eda14cbcSMatt Macyzed_unlock()
155eda14cbcSMatt Macy{
156eda14cbcSMatt Macy    local lockfile="$1"
157eda14cbcSMatt Macy    local fd="${2:-${ZED_FLOCK_FD}}"
158eda14cbcSMatt Macy    local err
159eda14cbcSMatt Macy
160eda14cbcSMatt Macy    [ -n "${lockfile}" ] || return
161eda14cbcSMatt Macy    if ! expr "${lockfile}" : '.*/' >/dev/null 2>&1; then
162eda14cbcSMatt Macy        lockfile="${ZED_LOCKDIR}/${lockfile}"
163eda14cbcSMatt Macy    fi
164eda14cbcSMatt Macy
165eda14cbcSMatt Macy    # Release the lock and close the file descriptor.
16616038816SMartin Matuska    if ! err="$(flock --unlock "${fd}" 2>&1)"; then
167eda14cbcSMatt Macy        zed_log_err "failed to unlock \"${lockfile}\": ${err}"
168eda14cbcSMatt Macy    fi
169eda14cbcSMatt Macy    eval "exec ${fd}>&-"
170eda14cbcSMatt Macy}
171eda14cbcSMatt Macy
172eda14cbcSMatt Macy
173eda14cbcSMatt Macy# zed_notify (subject, pathname)
174eda14cbcSMatt Macy#
175eda14cbcSMatt Macy# Send a notification via all available methods.
176eda14cbcSMatt Macy#
177eda14cbcSMatt Macy# Arguments
178eda14cbcSMatt Macy#   subject: notification subject
179eda14cbcSMatt Macy#   pathname: pathname containing the notification message (OPTIONAL)
180eda14cbcSMatt Macy#
181eda14cbcSMatt Macy# Return
182eda14cbcSMatt Macy#   0: notification succeeded via at least one method
183eda14cbcSMatt Macy#   1: notification failed
184eda14cbcSMatt Macy#   2: no notification methods configured
185eda14cbcSMatt Macy#
186eda14cbcSMatt Macyzed_notify()
187eda14cbcSMatt Macy{
188eda14cbcSMatt Macy    local subject="$1"
189eda14cbcSMatt Macy    local pathname="$2"
190eda14cbcSMatt Macy    local num_success=0
191eda14cbcSMatt Macy    local num_failure=0
192eda14cbcSMatt Macy
193eda14cbcSMatt Macy    zed_notify_email "${subject}" "${pathname}"; rv=$?
194eda14cbcSMatt Macy    [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
195eda14cbcSMatt Macy    [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
196eda14cbcSMatt Macy
197eda14cbcSMatt Macy    zed_notify_pushbullet "${subject}" "${pathname}"; rv=$?
198eda14cbcSMatt Macy    [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
199eda14cbcSMatt Macy    [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
200eda14cbcSMatt Macy
201eda14cbcSMatt Macy    zed_notify_slack_webhook "${subject}" "${pathname}"; rv=$?
202eda14cbcSMatt Macy    [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
203eda14cbcSMatt Macy    [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
204eda14cbcSMatt Macy
20516038816SMartin Matuska    zed_notify_pushover "${subject}" "${pathname}"; rv=$?
20616038816SMartin Matuska    [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
20716038816SMartin Matuska    [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
20816038816SMartin Matuska
209eda14cbcSMatt Macy    [ "${num_success}" -gt 0 ] && return 0
210eda14cbcSMatt Macy    [ "${num_failure}" -gt 0 ] && return 1
211eda14cbcSMatt Macy    return 2
212eda14cbcSMatt Macy}
213eda14cbcSMatt Macy
214eda14cbcSMatt Macy
215eda14cbcSMatt Macy# zed_notify_email (subject, pathname)
216eda14cbcSMatt Macy#
217eda14cbcSMatt Macy# Send a notification via email to the address specified by ZED_EMAIL_ADDR.
218eda14cbcSMatt Macy#
219eda14cbcSMatt Macy# Requires the mail executable to be installed in the standard PATH, or
220eda14cbcSMatt Macy# ZED_EMAIL_PROG to be defined with the pathname of an executable capable of
221eda14cbcSMatt Macy# reading a message body from stdin.
222eda14cbcSMatt Macy#
223eda14cbcSMatt Macy# Command-line options to the mail executable can be specified in
224eda14cbcSMatt Macy# ZED_EMAIL_OPTS.  This undergoes the following keyword substitutions:
225eda14cbcSMatt Macy# - @ADDRESS@ is replaced with the space-delimited recipient email address(es)
226eda14cbcSMatt Macy# - @SUBJECT@ is replaced with the notification subject
227eda14cbcSMatt Macy#
228eda14cbcSMatt Macy# Arguments
229eda14cbcSMatt Macy#   subject: notification subject
230eda14cbcSMatt Macy#   pathname: pathname containing the notification message (OPTIONAL)
231eda14cbcSMatt Macy#
232eda14cbcSMatt Macy# Globals
233eda14cbcSMatt Macy#   ZED_EMAIL_PROG
234eda14cbcSMatt Macy#   ZED_EMAIL_OPTS
235eda14cbcSMatt Macy#   ZED_EMAIL_ADDR
236eda14cbcSMatt Macy#
237eda14cbcSMatt Macy# Return
238eda14cbcSMatt Macy#   0: notification sent
239eda14cbcSMatt Macy#   1: notification failed
240eda14cbcSMatt Macy#   2: not configured
241eda14cbcSMatt Macy#
242eda14cbcSMatt Macyzed_notify_email()
243eda14cbcSMatt Macy{
244eda14cbcSMatt Macy    local subject="$1"
245eda14cbcSMatt Macy    local pathname="${2:-"/dev/null"}"
246eda14cbcSMatt Macy
247eda14cbcSMatt Macy    : "${ZED_EMAIL_PROG:="mail"}"
248eda14cbcSMatt Macy    : "${ZED_EMAIL_OPTS:="-s '@SUBJECT@' @ADDRESS@"}"
249eda14cbcSMatt Macy
250eda14cbcSMatt Macy    # For backward compatibility with ZED_EMAIL.
251eda14cbcSMatt Macy    if [ -n "${ZED_EMAIL}" ] && [ -z "${ZED_EMAIL_ADDR}" ]; then
252eda14cbcSMatt Macy        ZED_EMAIL_ADDR="${ZED_EMAIL}"
253eda14cbcSMatt Macy    fi
254eda14cbcSMatt Macy    [ -n "${ZED_EMAIL_ADDR}" ] || return 2
255eda14cbcSMatt Macy
256eda14cbcSMatt Macy    zed_check_cmd "${ZED_EMAIL_PROG}" || return 1
257eda14cbcSMatt Macy
258eda14cbcSMatt Macy    [ -n "${subject}" ] || return 1
259eda14cbcSMatt Macy    if [ ! -r "${pathname}" ]; then
260eda14cbcSMatt Macy        zed_log_err \
261dae17134SMartin Matuska                "${ZED_EMAIL_PROG##*/} cannot read \"${pathname}\""
262eda14cbcSMatt Macy        return 1
263eda14cbcSMatt Macy    fi
264eda14cbcSMatt Macy
265eda14cbcSMatt Macy    ZED_EMAIL_OPTS="$(echo "${ZED_EMAIL_OPTS}" \
266eda14cbcSMatt Macy        | sed   -e "s/@ADDRESS@/${ZED_EMAIL_ADDR}/g" \
267eda14cbcSMatt Macy                -e "s/@SUBJECT@/${subject}/g")"
268eda14cbcSMatt Macy
269*e92ffd9bSMartin Matuska    # shellcheck disable=SC2086,SC2248
2702617128aSMartin Matuska    eval ${ZED_EMAIL_PROG} ${ZED_EMAIL_OPTS} < "${pathname}" >/dev/null 2>&1
271eda14cbcSMatt Macy    rv=$?
272eda14cbcSMatt Macy    if [ "${rv}" -ne 0 ]; then
273dae17134SMartin Matuska        zed_log_err "${ZED_EMAIL_PROG##*/} exit=${rv}"
274eda14cbcSMatt Macy        return 1
275eda14cbcSMatt Macy    fi
276eda14cbcSMatt Macy    return 0
277eda14cbcSMatt Macy}
278eda14cbcSMatt Macy
279eda14cbcSMatt Macy
280eda14cbcSMatt Macy# zed_notify_pushbullet (subject, pathname)
281eda14cbcSMatt Macy#
282eda14cbcSMatt Macy# Send a notification via Pushbullet <https://www.pushbullet.com/>.
283eda14cbcSMatt Macy# The access token (ZED_PUSHBULLET_ACCESS_TOKEN) identifies this client to the
284eda14cbcSMatt Macy# Pushbullet server.  The optional channel tag (ZED_PUSHBULLET_CHANNEL_TAG) is
285eda14cbcSMatt Macy# for pushing to notification feeds that can be subscribed to; if a channel is
286eda14cbcSMatt Macy# not defined, push notifications will instead be sent to all devices
287eda14cbcSMatt Macy# associated with the account specified by the access token.
288eda14cbcSMatt Macy#
289eda14cbcSMatt Macy# Requires awk, curl, and sed executables to be installed in the standard PATH.
290eda14cbcSMatt Macy#
291eda14cbcSMatt Macy# References
292eda14cbcSMatt Macy#   https://docs.pushbullet.com/
293eda14cbcSMatt Macy#   https://www.pushbullet.com/security
294eda14cbcSMatt Macy#
295eda14cbcSMatt Macy# Arguments
296eda14cbcSMatt Macy#   subject: notification subject
297eda14cbcSMatt Macy#   pathname: pathname containing the notification message (OPTIONAL)
298eda14cbcSMatt Macy#
299eda14cbcSMatt Macy# Globals
300eda14cbcSMatt Macy#   ZED_PUSHBULLET_ACCESS_TOKEN
301eda14cbcSMatt Macy#   ZED_PUSHBULLET_CHANNEL_TAG
302eda14cbcSMatt Macy#
303eda14cbcSMatt Macy# Return
304eda14cbcSMatt Macy#   0: notification sent
305eda14cbcSMatt Macy#   1: notification failed
306eda14cbcSMatt Macy#   2: not configured
307eda14cbcSMatt Macy#
308eda14cbcSMatt Macyzed_notify_pushbullet()
309eda14cbcSMatt Macy{
310eda14cbcSMatt Macy    local subject="$1"
311eda14cbcSMatt Macy    local pathname="${2:-"/dev/null"}"
312eda14cbcSMatt Macy    local msg_body
313eda14cbcSMatt Macy    local msg_tag
314eda14cbcSMatt Macy    local msg_json
315eda14cbcSMatt Macy    local msg_out
316eda14cbcSMatt Macy    local msg_err
317eda14cbcSMatt Macy    local url="https://api.pushbullet.com/v2/pushes"
318eda14cbcSMatt Macy
319eda14cbcSMatt Macy    [ -n "${ZED_PUSHBULLET_ACCESS_TOKEN}" ] || return 2
320eda14cbcSMatt Macy
321eda14cbcSMatt Macy    [ -n "${subject}" ] || return 1
322eda14cbcSMatt Macy    if [ ! -r "${pathname}" ]; then
323eda14cbcSMatt Macy        zed_log_err "pushbullet cannot read \"${pathname}\""
324eda14cbcSMatt Macy        return 1
325eda14cbcSMatt Macy    fi
326eda14cbcSMatt Macy
327eda14cbcSMatt Macy    zed_check_cmd "awk" "curl" "sed" || return 1
328eda14cbcSMatt Macy
329eda14cbcSMatt Macy    # Escape the following characters in the message body for JSON:
330eda14cbcSMatt Macy    # newline, backslash, double quote, horizontal tab, vertical tab,
331eda14cbcSMatt Macy    # and carriage return.
332eda14cbcSMatt Macy    #
333eda14cbcSMatt Macy    msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\"");
334eda14cbcSMatt Macy        gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \
335eda14cbcSMatt Macy        "${pathname}")"
336eda14cbcSMatt Macy
337eda14cbcSMatt Macy    # Push to a channel if one is configured.
338eda14cbcSMatt Macy    #
339eda14cbcSMatt Macy    [ -n "${ZED_PUSHBULLET_CHANNEL_TAG}" ] && msg_tag="$(printf \
340eda14cbcSMatt Macy        '"channel_tag": "%s", ' "${ZED_PUSHBULLET_CHANNEL_TAG}")"
341eda14cbcSMatt Macy
342eda14cbcSMatt Macy    # Construct the JSON message for pushing a note.
343eda14cbcSMatt Macy    #
344eda14cbcSMatt Macy    msg_json="$(printf '{%s"type": "note", "title": "%s", "body": "%s"}' \
345eda14cbcSMatt Macy        "${msg_tag}" "${subject}" "${msg_body}")"
346eda14cbcSMatt Macy
347eda14cbcSMatt Macy    # Send the POST request and check for errors.
348eda14cbcSMatt Macy    #
349eda14cbcSMatt Macy    msg_out="$(curl -u "${ZED_PUSHBULLET_ACCESS_TOKEN}:" -X POST "${url}" \
350eda14cbcSMatt Macy        --header "Content-Type: application/json" --data-binary "${msg_json}" \
351eda14cbcSMatt Macy        2>/dev/null)"; rv=$?
352eda14cbcSMatt Macy    if [ "${rv}" -ne 0 ]; then
353eda14cbcSMatt Macy        zed_log_err "curl exit=${rv}"
354eda14cbcSMatt Macy        return 1
355eda14cbcSMatt Macy    fi
356eda14cbcSMatt Macy    msg_err="$(echo "${msg_out}" \
357eda14cbcSMatt Macy        | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
358eda14cbcSMatt Macy    if [ -n "${msg_err}" ]; then
359eda14cbcSMatt Macy        zed_log_err "pushbullet \"${msg_err}"\"
360eda14cbcSMatt Macy        return 1
361eda14cbcSMatt Macy    fi
362eda14cbcSMatt Macy    return 0
363eda14cbcSMatt Macy}
364eda14cbcSMatt Macy
365eda14cbcSMatt Macy
366eda14cbcSMatt Macy# zed_notify_slack_webhook (subject, pathname)
367eda14cbcSMatt Macy#
368eda14cbcSMatt Macy# Notification via Slack Webhook <https://api.slack.com/incoming-webhooks>.
369eda14cbcSMatt Macy# The Webhook URL (ZED_SLACK_WEBHOOK_URL) identifies this client to the
370eda14cbcSMatt Macy# Slack channel.
371eda14cbcSMatt Macy#
372eda14cbcSMatt Macy# Requires awk, curl, and sed executables to be installed in the standard PATH.
373eda14cbcSMatt Macy#
374eda14cbcSMatt Macy# References
375eda14cbcSMatt Macy#   https://api.slack.com/incoming-webhooks
376eda14cbcSMatt Macy#
377eda14cbcSMatt Macy# Arguments
378eda14cbcSMatt Macy#   subject: notification subject
379eda14cbcSMatt Macy#   pathname: pathname containing the notification message (OPTIONAL)
380eda14cbcSMatt Macy#
381eda14cbcSMatt Macy# Globals
382eda14cbcSMatt Macy#   ZED_SLACK_WEBHOOK_URL
383eda14cbcSMatt Macy#
384eda14cbcSMatt Macy# Return
385eda14cbcSMatt Macy#   0: notification sent
386eda14cbcSMatt Macy#   1: notification failed
387eda14cbcSMatt Macy#   2: not configured
388eda14cbcSMatt Macy#
389eda14cbcSMatt Macyzed_notify_slack_webhook()
390eda14cbcSMatt Macy{
391eda14cbcSMatt Macy    [ -n "${ZED_SLACK_WEBHOOK_URL}" ] || return 2
392eda14cbcSMatt Macy
393eda14cbcSMatt Macy    local subject="$1"
394eda14cbcSMatt Macy    local pathname="${2:-"/dev/null"}"
395eda14cbcSMatt Macy    local msg_body
396eda14cbcSMatt Macy    local msg_tag
397eda14cbcSMatt Macy    local msg_json
398eda14cbcSMatt Macy    local msg_out
399eda14cbcSMatt Macy    local msg_err
400eda14cbcSMatt Macy    local url="${ZED_SLACK_WEBHOOK_URL}"
401eda14cbcSMatt Macy
402eda14cbcSMatt Macy    [ -n "${subject}" ] || return 1
403eda14cbcSMatt Macy    if [ ! -r "${pathname}" ]; then
404eda14cbcSMatt Macy        zed_log_err "slack webhook cannot read \"${pathname}\""
405eda14cbcSMatt Macy        return 1
406eda14cbcSMatt Macy    fi
407eda14cbcSMatt Macy
408eda14cbcSMatt Macy    zed_check_cmd "awk" "curl" "sed" || return 1
409eda14cbcSMatt Macy
410eda14cbcSMatt Macy    # Escape the following characters in the message body for JSON:
411eda14cbcSMatt Macy    # newline, backslash, double quote, horizontal tab, vertical tab,
412eda14cbcSMatt Macy    # and carriage return.
413eda14cbcSMatt Macy    #
414eda14cbcSMatt Macy    msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\"");
415eda14cbcSMatt Macy        gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \
416eda14cbcSMatt Macy        "${pathname}")"
417eda14cbcSMatt Macy
418eda14cbcSMatt Macy    # Construct the JSON message for posting.
419eda14cbcSMatt Macy    #
420eda14cbcSMatt Macy    msg_json="$(printf '{"text": "*%s*\n%s"}' "${subject}" "${msg_body}" )"
421eda14cbcSMatt Macy
422eda14cbcSMatt Macy    # Send the POST request and check for errors.
423eda14cbcSMatt Macy    #
424eda14cbcSMatt Macy    msg_out="$(curl -X POST "${url}" \
425eda14cbcSMatt Macy        --header "Content-Type: application/json" --data-binary "${msg_json}" \
426eda14cbcSMatt Macy        2>/dev/null)"; rv=$?
427eda14cbcSMatt Macy    if [ "${rv}" -ne 0 ]; then
428eda14cbcSMatt Macy        zed_log_err "curl exit=${rv}"
429eda14cbcSMatt Macy        return 1
430eda14cbcSMatt Macy    fi
431eda14cbcSMatt Macy    msg_err="$(echo "${msg_out}" \
432eda14cbcSMatt Macy        | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
433eda14cbcSMatt Macy    if [ -n "${msg_err}" ]; then
434eda14cbcSMatt Macy        zed_log_err "slack webhook \"${msg_err}"\"
435eda14cbcSMatt Macy        return 1
436eda14cbcSMatt Macy    fi
437eda14cbcSMatt Macy    return 0
438eda14cbcSMatt Macy}
439eda14cbcSMatt Macy
44016038816SMartin Matuska# zed_notify_pushover (subject, pathname)
44116038816SMartin Matuska#
44216038816SMartin Matuska# Send a notification via Pushover <https://pushover.net/>.
44316038816SMartin Matuska# The access token (ZED_PUSHOVER_TOKEN) identifies this client to the
44416038816SMartin Matuska# Pushover server. The user token (ZED_PUSHOVER_USER) defines the user or
44516038816SMartin Matuska# group to which the notification will be sent.
44616038816SMartin Matuska#
44716038816SMartin Matuska# Requires curl and sed executables to be installed in the standard PATH.
44816038816SMartin Matuska#
44916038816SMartin Matuska# References
45016038816SMartin Matuska#   https://pushover.net/api
45116038816SMartin Matuska#
45216038816SMartin Matuska# Arguments
45316038816SMartin Matuska#   subject: notification subject
45416038816SMartin Matuska#   pathname: pathname containing the notification message (OPTIONAL)
45516038816SMartin Matuska#
45616038816SMartin Matuska# Globals
45716038816SMartin Matuska#   ZED_PUSHOVER_TOKEN
45816038816SMartin Matuska#   ZED_PUSHOVER_USER
45916038816SMartin Matuska#
46016038816SMartin Matuska# Return
46116038816SMartin Matuska#   0: notification sent
46216038816SMartin Matuska#   1: notification failed
46316038816SMartin Matuska#   2: not configured
46416038816SMartin Matuska#
46516038816SMartin Matuskazed_notify_pushover()
46616038816SMartin Matuska{
46716038816SMartin Matuska    local subject="$1"
46816038816SMartin Matuska    local pathname="${2:-"/dev/null"}"
46916038816SMartin Matuska    local msg_body
47016038816SMartin Matuska    local msg_out
47116038816SMartin Matuska    local msg_err
47216038816SMartin Matuska    local url="https://api.pushover.net/1/messages.json"
47316038816SMartin Matuska
47416038816SMartin Matuska    [ -n "${ZED_PUSHOVER_TOKEN}" ] && [ -n "${ZED_PUSHOVER_USER}" ] || return 2
47516038816SMartin Matuska
47616038816SMartin Matuska    if [ ! -r "${pathname}" ]; then
47716038816SMartin Matuska        zed_log_err "pushover cannot read \"${pathname}\""
47816038816SMartin Matuska        return 1
47916038816SMartin Matuska    fi
48016038816SMartin Matuska
48116038816SMartin Matuska    zed_check_cmd "curl" "sed" || return 1
48216038816SMartin Matuska
48316038816SMartin Matuska    # Read the message body in.
48416038816SMartin Matuska    #
48516038816SMartin Matuska    msg_body="$(cat "${pathname}")"
48616038816SMartin Matuska
48716038816SMartin Matuska    if [ -z "${msg_body}" ]
48816038816SMartin Matuska    then
48916038816SMartin Matuska        msg_body=$subject
49016038816SMartin Matuska        subject=""
49116038816SMartin Matuska    fi
49216038816SMartin Matuska
49316038816SMartin Matuska    # Send the POST request and check for errors.
49416038816SMartin Matuska    #
49516038816SMartin Matuska    msg_out="$( \
49616038816SMartin Matuska        curl \
49716038816SMartin Matuska        --form-string "token=${ZED_PUSHOVER_TOKEN}" \
49816038816SMartin Matuska        --form-string "user=${ZED_PUSHOVER_USER}" \
49916038816SMartin Matuska        --form-string "message=${msg_body}" \
50016038816SMartin Matuska        --form-string "title=${subject}" \
50116038816SMartin Matuska        "${url}" \
50216038816SMartin Matuska        2>/dev/null \
50316038816SMartin Matuska        )"; rv=$?
50416038816SMartin Matuska    if [ "${rv}" -ne 0 ]; then
50516038816SMartin Matuska        zed_log_err "curl exit=${rv}"
50616038816SMartin Matuska        return 1
50716038816SMartin Matuska    fi
50816038816SMartin Matuska    msg_err="$(echo "${msg_out}" \
50916038816SMartin Matuska        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
51016038816SMartin Matuska    if [ -n "${msg_err}" ]; then
51116038816SMartin Matuska        zed_log_err "pushover \"${msg_err}"\"
51216038816SMartin Matuska        return 1
51316038816SMartin Matuska    fi
51416038816SMartin Matuska    return 0
51516038816SMartin Matuska}
51616038816SMartin Matuska
51716038816SMartin Matuska
518eda14cbcSMatt Macy# zed_rate_limit (tag, [interval])
519eda14cbcSMatt Macy#
520eda14cbcSMatt Macy# Check whether an event of a given type [tag] has already occurred within the
521eda14cbcSMatt Macy# last [interval] seconds.
522eda14cbcSMatt Macy#
523eda14cbcSMatt Macy# This function obtains a lock on the statefile using file descriptor 9.
524eda14cbcSMatt Macy#
525eda14cbcSMatt Macy# Arguments
526eda14cbcSMatt Macy#   tag: arbitrary string for grouping related events to rate-limit
527eda14cbcSMatt Macy#   interval: time interval in seconds (OPTIONAL)
528eda14cbcSMatt Macy#
529eda14cbcSMatt Macy# Globals
530eda14cbcSMatt Macy#   ZED_NOTIFY_INTERVAL_SECS
531eda14cbcSMatt Macy#   ZED_RUNDIR
532eda14cbcSMatt Macy#
533eda14cbcSMatt Macy# Return
534eda14cbcSMatt Macy#   0 if the event should be processed
535eda14cbcSMatt Macy#   1 if the event should be dropped
536eda14cbcSMatt Macy#
537eda14cbcSMatt Macy# State File Format
538eda14cbcSMatt Macy#   time;tag
539eda14cbcSMatt Macy#
540eda14cbcSMatt Macyzed_rate_limit()
541eda14cbcSMatt Macy{
542eda14cbcSMatt Macy    local tag="$1"
543eda14cbcSMatt Macy    local interval="${2:-${ZED_NOTIFY_INTERVAL_SECS}}"
544eda14cbcSMatt Macy    local lockfile="zed.zedlet.state.lock"
545eda14cbcSMatt Macy    local lockfile_fd=9
546eda14cbcSMatt Macy    local statefile="${ZED_RUNDIR}/zed.zedlet.state"
547eda14cbcSMatt Macy    local time_now
548eda14cbcSMatt Macy    local time_prev
549eda14cbcSMatt Macy    local umask_bak
550eda14cbcSMatt Macy    local rv=0
551eda14cbcSMatt Macy
552eda14cbcSMatt Macy    [ -n "${tag}" ] || return 0
553eda14cbcSMatt Macy
554eda14cbcSMatt Macy    zed_lock "${lockfile}" "${lockfile_fd}"
555eda14cbcSMatt Macy    time_now="$(date +%s)"
556eda14cbcSMatt Macy    time_prev="$(grep -E "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
557eda14cbcSMatt Macy        | tail -1 | cut -d\; -f1)"
558eda14cbcSMatt Macy
559eda14cbcSMatt Macy    if [ -n "${time_prev}" ] \
560eda14cbcSMatt Macy            && [ "$((time_now - time_prev))" -lt "${interval}" ]; then
561eda14cbcSMatt Macy        rv=1
562eda14cbcSMatt Macy    else
563eda14cbcSMatt Macy        umask_bak="$(umask)"
564eda14cbcSMatt Macy        umask 077
565eda14cbcSMatt Macy        grep -E -v "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
566eda14cbcSMatt Macy            > "${statefile}.$$"
567eda14cbcSMatt Macy        echo "${time_now};${tag}" >> "${statefile}.$$"
568eda14cbcSMatt Macy        mv -f "${statefile}.$$" "${statefile}"
569eda14cbcSMatt Macy        umask "${umask_bak}"
570eda14cbcSMatt Macy    fi
571eda14cbcSMatt Macy
572eda14cbcSMatt Macy    zed_unlock "${lockfile}" "${lockfile_fd}"
573eda14cbcSMatt Macy    return "${rv}"
574eda14cbcSMatt Macy}
575eda14cbcSMatt Macy
576eda14cbcSMatt Macy
577eda14cbcSMatt Macy# zed_guid_to_pool (guid)
578eda14cbcSMatt Macy#
579eda14cbcSMatt Macy# Convert a pool GUID into its pool name (like "tank")
580eda14cbcSMatt Macy# Arguments
581eda14cbcSMatt Macy#   guid: pool GUID (decimal or hex)
582eda14cbcSMatt Macy#
583eda14cbcSMatt Macy# Return
584eda14cbcSMatt Macy#   Pool name
585eda14cbcSMatt Macy#
586eda14cbcSMatt Macyzed_guid_to_pool()
587eda14cbcSMatt Macy{
588eda14cbcSMatt Macy	if [ -z "$1" ] ; then
589eda14cbcSMatt Macy		return
590eda14cbcSMatt Macy	fi
591eda14cbcSMatt Macy
59216038816SMartin Matuska	guid="$(printf "%u" "$1")"
59316038816SMartin Matuska	$ZPOOL get -H -ovalue,name guid | awk '$1 == '"$guid"' {print $2; exit}'
594eda14cbcSMatt Macy}
595eda14cbcSMatt Macy
596eda14cbcSMatt Macy# zed_exit_if_ignoring_this_event
597eda14cbcSMatt Macy#
598eda14cbcSMatt Macy# Exit the script if we should ignore this event, as determined by
599eda14cbcSMatt Macy# $ZED_SYSLOG_SUBCLASS_INCLUDE and $ZED_SYSLOG_SUBCLASS_EXCLUDE in zed.rc.
600eda14cbcSMatt Macy# This function assumes you've imported the normal zed variables.
601eda14cbcSMatt Macyzed_exit_if_ignoring_this_event()
602eda14cbcSMatt Macy{
603eda14cbcSMatt Macy	if [ -n "${ZED_SYSLOG_SUBCLASS_INCLUDE}" ]; then
604eda14cbcSMatt Macy	    eval "case ${ZEVENT_SUBCLASS} in
605eda14cbcSMatt Macy	    ${ZED_SYSLOG_SUBCLASS_INCLUDE});;
606eda14cbcSMatt Macy	    *) exit 0;;
607eda14cbcSMatt Macy	    esac"
608eda14cbcSMatt Macy	elif [ -n "${ZED_SYSLOG_SUBCLASS_EXCLUDE}" ]; then
609eda14cbcSMatt Macy	    eval "case ${ZEVENT_SUBCLASS} in
610eda14cbcSMatt Macy	    ${ZED_SYSLOG_SUBCLASS_EXCLUDE}) exit 0;;
611eda14cbcSMatt Macy	    *);;
612eda14cbcSMatt Macy	    esac"
613eda14cbcSMatt Macy	fi
614eda14cbcSMatt Macy}
615