xref: /freebsd/sys/contrib/openzfs/cmd/zed/zed.d/zed-functions.sh (revision 071ab5a1f3cbfd29c8fbec27f7e619418adaf074)
1#!/bin/sh
2# shellcheck disable=SC2154,SC3043
3# zed-functions.sh
4#
5# ZED helper functions for use in ZEDLETs
6
7
8# Variable Defaults
9#
10: "${ZED_LOCKDIR:="/var/lock"}"
11: "${ZED_NOTIFY_INTERVAL_SECS:=3600}"
12: "${ZED_NOTIFY_VERBOSE:=0}"
13: "${ZED_RUNDIR:="/var/run"}"
14: "${ZED_SYSLOG_PRIORITY:="daemon.notice"}"
15: "${ZED_SYSLOG_TAG:="zed"}"
16
17ZED_FLOCK_FD=8
18
19
20# zed_check_cmd (cmd, ...)
21#
22# For each argument given, search PATH for the executable command [cmd].
23# Log a message if [cmd] is not found.
24#
25# Arguments
26#   cmd: name of executable command for which to search
27#
28# Return
29#   0 if all commands are found in PATH and are executable
30#   n for a count of the command executables that are not found
31#
32zed_check_cmd()
33{
34    local cmd
35    local rv=0
36
37    for cmd; do
38        if ! command -v "${cmd}" >/dev/null 2>&1; then
39            zed_log_err "\"${cmd}\" not installed"
40            rv=$((rv + 1))
41        fi
42    done
43    return "${rv}"
44}
45
46
47# zed_log_msg (msg, ...)
48#
49# Write all argument strings to the system log.
50#
51# Globals
52#   ZED_SYSLOG_PRIORITY
53#   ZED_SYSLOG_TAG
54#
55# Return
56#   nothing
57#
58zed_log_msg()
59{
60    logger -p "${ZED_SYSLOG_PRIORITY}" -t "${ZED_SYSLOG_TAG}" -- "$@"
61}
62
63
64# zed_log_err (msg, ...)
65#
66# Write an error message to the system log.  This message will contain the
67# script name, EID, and all argument strings.
68#
69# Globals
70#   ZED_SYSLOG_PRIORITY
71#   ZED_SYSLOG_TAG
72#   ZEVENT_EID
73#
74# Return
75#   nothing
76#
77zed_log_err()
78{
79    zed_log_msg "error: ${0##*/}:""${ZEVENT_EID:+" eid=${ZEVENT_EID}:"}" "$@"
80}
81
82
83# zed_lock (lockfile, [fd])
84#
85# Obtain an exclusive (write) lock on [lockfile].  If the lock cannot be
86# immediately acquired, wait until it becomes available.
87#
88# Every zed_lock() must be paired with a corresponding zed_unlock().
89#
90# By default, flock-style locks associate the lockfile with file descriptor 8.
91# The bash manpage warns that file descriptors >9 should be used with care as
92# they may conflict with file descriptors used internally by the shell.  File
93# descriptor 9 is reserved for zed_rate_limit().  If concurrent locks are held
94# within the same process, they must use different file descriptors (preferably
95# decrementing from 8); otherwise, obtaining a new lock with a given file
96# descriptor will release the previous lock associated with that descriptor.
97#
98# Arguments
99#   lockfile: pathname of the lock file; the lock will be stored in
100#     ZED_LOCKDIR unless the pathname contains a "/".
101#   fd: integer for the file descriptor used by flock (OPTIONAL unless holding
102#     concurrent locks)
103#
104# Globals
105#   ZED_FLOCK_FD
106#   ZED_LOCKDIR
107#
108# Return
109#   nothing
110#
111zed_lock()
112{
113    local lockfile="$1"
114    local fd="${2:-${ZED_FLOCK_FD}}"
115    local umask_bak
116    local err
117
118    [ -n "${lockfile}" ] || return
119    if ! expr "${lockfile}" : '.*/' >/dev/null 2>&1; then
120        lockfile="${ZED_LOCKDIR}/${lockfile}"
121    fi
122
123    umask_bak="$(umask)"
124    umask 077
125
126    # Obtain a lock on the file bound to the given file descriptor.
127    #
128    eval "exec ${fd}>> '${lockfile}'"
129    if ! err="$(flock --exclusive "${fd}" 2>&1)"; then
130        zed_log_err "failed to lock \"${lockfile}\": ${err}"
131    fi
132
133    umask "${umask_bak}"
134}
135
136
137# zed_unlock (lockfile, [fd])
138#
139# Release the lock on [lockfile].
140#
141# Arguments
142#   lockfile: pathname of the lock file
143#   fd: integer for the file descriptor used by flock (must match the file
144#     descriptor passed to the zed_lock function call)
145#
146# Globals
147#   ZED_FLOCK_FD
148#   ZED_LOCKDIR
149#
150# Return
151#   nothing
152#
153zed_unlock()
154{
155    local lockfile="$1"
156    local fd="${2:-${ZED_FLOCK_FD}}"
157    local err
158
159    [ -n "${lockfile}" ] || return
160    if ! expr "${lockfile}" : '.*/' >/dev/null 2>&1; then
161        lockfile="${ZED_LOCKDIR}/${lockfile}"
162    fi
163
164    # Release the lock and close the file descriptor.
165    if ! err="$(flock --unlock "${fd}" 2>&1)"; then
166        zed_log_err "failed to unlock \"${lockfile}\": ${err}"
167    fi
168    eval "exec ${fd}>&-"
169}
170
171
172# zed_notify (subject, pathname)
173#
174# Send a notification via all available methods.
175#
176# Arguments
177#   subject: notification subject
178#   pathname: pathname containing the notification message (OPTIONAL)
179#
180# Return
181#   0: notification succeeded via at least one method
182#   1: notification failed
183#   2: no notification methods configured
184#
185zed_notify()
186{
187    local subject="$1"
188    local pathname="$2"
189    local num_success=0
190    local num_failure=0
191
192    zed_notify_email "${subject}" "${pathname}"; rv=$?
193    [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
194    [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
195
196    zed_notify_pushbullet "${subject}" "${pathname}"; rv=$?
197    [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
198    [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
199
200    zed_notify_slack_webhook "${subject}" "${pathname}"; rv=$?
201    [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
202    [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
203
204    zed_notify_pushover "${subject}" "${pathname}"; rv=$?
205    [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
206    [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
207
208    zed_notify_ntfy "${subject}" "${pathname}"; rv=$?
209    [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
210    [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
211
212    zed_notify_gotify "${subject}" "${pathname}"; rv=$?
213    [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
214    [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
215
216    [ "${num_success}" -gt 0 ] && return 0
217    [ "${num_failure}" -gt 0 ] && return 1
218    return 2
219}
220
221
222# zed_notify_email (subject, pathname)
223#
224# Send a notification via email to the address specified by ZED_EMAIL_ADDR.
225#
226# Requires the mail executable to be installed in the standard PATH, or
227# ZED_EMAIL_PROG to be defined with the pathname of an executable capable of
228# reading a message body from stdin.
229#
230# Command-line options to the mail executable can be specified in
231# ZED_EMAIL_OPTS.  This undergoes the following keyword substitutions:
232# - @ADDRESS@ is replaced with the space-delimited recipient email address(es)
233# - @SUBJECT@ is replaced with the notification subject
234#   If @SUBJECT@ was omited here, a "Subject: ..." header will be added to notification
235#
236#
237# Arguments
238#   subject: notification subject
239#   pathname: pathname containing the notification message (OPTIONAL)
240#
241# Globals
242#   ZED_EMAIL_PROG
243#   ZED_EMAIL_OPTS
244#   ZED_EMAIL_ADDR
245#
246# Return
247#   0: notification sent
248#   1: notification failed
249#   2: not configured
250#
251zed_notify_email()
252{
253    local subject="${1:-"ZED notification"}"
254    local pathname="${2:-"/dev/null"}"
255
256    : "${ZED_EMAIL_PROG:="mail"}"
257    : "${ZED_EMAIL_OPTS:="-s '@SUBJECT@' @ADDRESS@"}"
258
259    # For backward compatibility with ZED_EMAIL.
260    if [ -n "${ZED_EMAIL}" ] && [ -z "${ZED_EMAIL_ADDR}" ]; then
261        ZED_EMAIL_ADDR="${ZED_EMAIL}"
262    fi
263    [ -n "${ZED_EMAIL_ADDR}" ] || return 2
264
265    zed_check_cmd "${ZED_EMAIL_PROG}" || return 1
266
267    [ -n "${subject}" ] || return 1
268    if [ ! -r "${pathname}" ]; then
269        zed_log_err \
270                "${ZED_EMAIL_PROG##*/} cannot read \"${pathname}\""
271        return 1
272    fi
273
274    # construct cmdline options
275    ZED_EMAIL_OPTS_PARSED="$(echo "${ZED_EMAIL_OPTS}" \
276        | sed   -e "s/@ADDRESS@/${ZED_EMAIL_ADDR}/g" \
277                -e "s/@SUBJECT@/${subject}/g")"
278
279    # pipe message to email prog
280    # shellcheck disable=SC2086,SC2248
281    {
282        # no subject passed as option?
283        if [ "${ZED_EMAIL_OPTS%@SUBJECT@*}" = "${ZED_EMAIL_OPTS}" ] ; then
284            # inject subject header
285            printf "Subject: %s\n" "${subject}"
286            # The following empty line is needed to separate the header from the
287            # body of the message. Otherwise programs like sendmail will skip
288            # everything up to the first empty line (or wont send an email at
289            # all) and will still exit with exit code 0
290            printf "\n"
291        fi
292        # output message
293        cat "${pathname}"
294    } |
295    eval ${ZED_EMAIL_PROG} ${ZED_EMAIL_OPTS_PARSED} >/dev/null 2>&1
296    rv=$?
297    if [ "${rv}" -ne 0 ]; then
298        zed_log_err "${ZED_EMAIL_PROG##*/} exit=${rv}"
299        return 1
300    fi
301    return 0
302}
303
304
305# zed_notify_pushbullet (subject, pathname)
306#
307# Send a notification via Pushbullet <https://www.pushbullet.com/>.
308# The access token (ZED_PUSHBULLET_ACCESS_TOKEN) identifies this client to the
309# Pushbullet server.  The optional channel tag (ZED_PUSHBULLET_CHANNEL_TAG) is
310# for pushing to notification feeds that can be subscribed to; if a channel is
311# not defined, push notifications will instead be sent to all devices
312# associated with the account specified by the access token.
313#
314# Requires awk, curl, and sed executables to be installed in the standard PATH.
315#
316# References
317#   https://docs.pushbullet.com/
318#   https://www.pushbullet.com/security
319#
320# Arguments
321#   subject: notification subject
322#   pathname: pathname containing the notification message (OPTIONAL)
323#
324# Globals
325#   ZED_PUSHBULLET_ACCESS_TOKEN
326#   ZED_PUSHBULLET_CHANNEL_TAG
327#
328# Return
329#   0: notification sent
330#   1: notification failed
331#   2: not configured
332#
333zed_notify_pushbullet()
334{
335    local subject="$1"
336    local pathname="${2:-"/dev/null"}"
337    local msg_body
338    local msg_tag
339    local msg_json
340    local msg_out
341    local msg_err
342    local url="https://api.pushbullet.com/v2/pushes"
343
344    [ -n "${ZED_PUSHBULLET_ACCESS_TOKEN}" ] || return 2
345
346    [ -n "${subject}" ] || return 1
347    if [ ! -r "${pathname}" ]; then
348        zed_log_err "pushbullet cannot read \"${pathname}\""
349        return 1
350    fi
351
352    zed_check_cmd "awk" "curl" "sed" || return 1
353
354    # Escape the following characters in the message body for JSON:
355    # newline, backslash, double quote, horizontal tab, vertical tab,
356    # and carriage return.
357    #
358    msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\"");
359        gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \
360        "${pathname}")"
361
362    # Push to a channel if one is configured.
363    #
364    [ -n "${ZED_PUSHBULLET_CHANNEL_TAG}" ] && msg_tag="$(printf \
365        '"channel_tag": "%s", ' "${ZED_PUSHBULLET_CHANNEL_TAG}")"
366
367    # Construct the JSON message for pushing a note.
368    #
369    msg_json="$(printf '{%s"type": "note", "title": "%s", "body": "%s"}' \
370        "${msg_tag}" "${subject}" "${msg_body}")"
371
372    # Send the POST request and check for errors.
373    #
374    msg_out="$(curl -u "${ZED_PUSHBULLET_ACCESS_TOKEN}:" -X POST "${url}" \
375        --header "Content-Type: application/json" --data-binary "${msg_json}" \
376        2>/dev/null)"; rv=$?
377    if [ "${rv}" -ne 0 ]; then
378        zed_log_err "curl exit=${rv}"
379        return 1
380    fi
381    msg_err="$(echo "${msg_out}" \
382        | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
383    if [ -n "${msg_err}" ]; then
384        zed_log_err "pushbullet \"${msg_err}"\"
385        return 1
386    fi
387    return 0
388}
389
390
391# zed_notify_slack_webhook (subject, pathname)
392#
393# Notification via Slack Webhook <https://api.slack.com/incoming-webhooks>.
394# The Webhook URL (ZED_SLACK_WEBHOOK_URL) identifies this client to the
395# Slack channel.
396#
397# Requires awk, curl, and sed executables to be installed in the standard PATH.
398#
399# References
400#   https://api.slack.com/incoming-webhooks
401#
402# Arguments
403#   subject: notification subject
404#   pathname: pathname containing the notification message (OPTIONAL)
405#
406# Globals
407#   ZED_SLACK_WEBHOOK_URL
408#
409# Return
410#   0: notification sent
411#   1: notification failed
412#   2: not configured
413#
414zed_notify_slack_webhook()
415{
416    [ -n "${ZED_SLACK_WEBHOOK_URL}" ] || return 2
417
418    local subject="$1"
419    local pathname="${2:-"/dev/null"}"
420    local msg_body
421    local msg_tag
422    local msg_json
423    local msg_out
424    local msg_err
425    local url="${ZED_SLACK_WEBHOOK_URL}"
426
427    [ -n "${subject}" ] || return 1
428    if [ ! -r "${pathname}" ]; then
429        zed_log_err "slack webhook cannot read \"${pathname}\""
430        return 1
431    fi
432
433    zed_check_cmd "awk" "curl" "sed" || return 1
434
435    # Escape the following characters in the message body for JSON:
436    # newline, backslash, double quote, horizontal tab, vertical tab,
437    # and carriage return.
438    #
439    msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\"");
440        gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \
441        "${pathname}")"
442
443    # Construct the JSON message for posting.
444    #
445    msg_json="$(printf '{"text": "*%s*\\n%s"}' "${subject}" "${msg_body}" )"
446
447    # Send the POST request and check for errors.
448    #
449    msg_out="$(curl -X POST "${url}" \
450        --header "Content-Type: application/json" --data-binary "${msg_json}" \
451        2>/dev/null)"; rv=$?
452    if [ "${rv}" -ne 0 ]; then
453        zed_log_err "curl exit=${rv}"
454        return 1
455    fi
456    msg_err="$(echo "${msg_out}" \
457        | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
458    if [ -n "${msg_err}" ]; then
459        zed_log_err "slack webhook \"${msg_err}"\"
460        return 1
461    fi
462    return 0
463}
464
465# zed_notify_pushover (subject, pathname)
466#
467# Send a notification via Pushover <https://pushover.net/>.
468# The access token (ZED_PUSHOVER_TOKEN) identifies this client to the
469# Pushover server. The user token (ZED_PUSHOVER_USER) defines the user or
470# group to which the notification will be sent.
471#
472# Requires curl and sed executables to be installed in the standard PATH.
473#
474# References
475#   https://pushover.net/api
476#
477# Arguments
478#   subject: notification subject
479#   pathname: pathname containing the notification message (OPTIONAL)
480#
481# Globals
482#   ZED_PUSHOVER_TOKEN
483#   ZED_PUSHOVER_USER
484#
485# Return
486#   0: notification sent
487#   1: notification failed
488#   2: not configured
489#
490zed_notify_pushover()
491{
492    local subject="$1"
493    local pathname="${2:-"/dev/null"}"
494    local msg_body
495    local msg_out
496    local msg_err
497    local url="https://api.pushover.net/1/messages.json"
498
499    [ -n "${ZED_PUSHOVER_TOKEN}" ] && [ -n "${ZED_PUSHOVER_USER}" ] || return 2
500
501    if [ ! -r "${pathname}" ]; then
502        zed_log_err "pushover cannot read \"${pathname}\""
503        return 1
504    fi
505
506    zed_check_cmd "curl" "sed" || return 1
507
508    # Read the message body in.
509    #
510    msg_body="$(cat "${pathname}")"
511
512    if [ -z "${msg_body}" ]
513    then
514        msg_body=$subject
515        subject=""
516    fi
517
518    # Send the POST request and check for errors.
519    #
520    msg_out="$( \
521        curl \
522        --form-string "token=${ZED_PUSHOVER_TOKEN}" \
523        --form-string "user=${ZED_PUSHOVER_USER}" \
524        --form-string "message=${msg_body}" \
525        --form-string "title=${subject}" \
526        "${url}" \
527        2>/dev/null \
528        )"; rv=$?
529    if [ "${rv}" -ne 0 ]; then
530        zed_log_err "curl exit=${rv}"
531        return 1
532    fi
533    msg_err="$(echo "${msg_out}" \
534        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
535    if [ -n "${msg_err}" ]; then
536        zed_log_err "pushover \"${msg_err}"\"
537        return 1
538    fi
539    return 0
540}
541
542
543# zed_notify_ntfy (subject, pathname)
544#
545# Send a notification via Ntfy.sh <https://ntfy.sh/>.
546# The ntfy topic (ZED_NTFY_TOPIC) identifies the topic that the notification
547# will be sent to Ntfy.sh server. The ntfy url (ZED_NTFY_URL) defines the
548# self-hosted or provided hosted ntfy service location. The ntfy access token
549# <https://docs.ntfy.sh/publish/#access-tokens> (ZED_NTFY_ACCESS_TOKEN) reprsents an
550# access token that could be used if a topic is read/write protected. If a
551# topic can be written to publicaly, a ZED_NTFY_ACCESS_TOKEN is not required.
552#
553# Requires curl and sed executables to be installed in the standard PATH.
554#
555# References
556#   https://docs.ntfy.sh
557#
558# Arguments
559#   subject: notification subject
560#   pathname: pathname containing the notification message (OPTIONAL)
561#
562# Globals
563#   ZED_NTFY_TOPIC
564#   ZED_NTFY_ACCESS_TOKEN (OPTIONAL)
565#   ZED_NTFY_URL
566#
567# Return
568#   0: notification sent
569#   1: notification failed
570#   2: not configured
571#
572zed_notify_ntfy()
573{
574    local subject="$1"
575    local pathname="${2:-"/dev/null"}"
576    local msg_body
577    local msg_out
578    local msg_err
579
580    [ -n "${ZED_NTFY_TOPIC}" ] || return 2
581    local url="${ZED_NTFY_URL:-"https://ntfy.sh"}/${ZED_NTFY_TOPIC}"
582
583    if [ ! -r "${pathname}" ]; then
584        zed_log_err "ntfy cannot read \"${pathname}\""
585        return 1
586    fi
587
588    zed_check_cmd "curl" "sed" || return 1
589
590    # Read the message body in.
591    #
592    msg_body="$(cat "${pathname}")"
593
594    if [ -z "${msg_body}" ]
595    then
596        msg_body=$subject
597        subject=""
598    fi
599
600    # Send the POST request and check for errors.
601    #
602    if [ -n "${ZED_NTFY_ACCESS_TOKEN}" ]; then
603        msg_out="$( \
604        curl \
605        -u ":${ZED_NTFY_ACCESS_TOKEN}" \
606        -H "Title: ${subject}" \
607        -d "${msg_body}" \
608        -H "Priority: high" \
609        "${url}" \
610        2>/dev/null \
611        )"; rv=$?
612    else
613        msg_out="$( \
614        curl \
615        -H "Title: ${subject}" \
616        -d "${msg_body}" \
617        -H "Priority: high" \
618        "${url}" \
619        2>/dev/null \
620        )"; rv=$?
621    fi
622    if [ "${rv}" -ne 0 ]; then
623        zed_log_err "curl exit=${rv}"
624        return 1
625    fi
626    msg_err="$(echo "${msg_out}" \
627        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
628    if [ -n "${msg_err}" ]; then
629        zed_log_err "ntfy \"${msg_err}"\"
630        return 1
631    fi
632    return 0
633}
634
635
636# zed_notify_gotify (subject, pathname)
637#
638# Send a notification via Gotify <https://gotify.net/>.
639# The Gotify URL (ZED_GOTIFY_URL) defines a self-hosted Gotify location.
640# The Gotify application token (ZED_GOTIFY_APPTOKEN) defines a
641# Gotify application token which is associated with a message.
642# The optional Gotify priority value (ZED_GOTIFY_PRIORITY) overrides the
643# default or configured priority at the Gotify server for the application.
644#
645# Requires curl and sed executables to be installed in the standard PATH.
646#
647# References
648#   https://gotify.net/docs/index
649#
650# Arguments
651#   subject: notification subject
652#   pathname: pathname containing the notification message (OPTIONAL)
653#
654# Globals
655#   ZED_GOTIFY_URL
656#   ZED_GOTIFY_APPTOKEN
657#   ZED_GOTIFY_PRIORITY
658#
659# Return
660#   0: notification sent
661#   1: notification failed
662#   2: not configured
663#
664zed_notify_gotify()
665{
666    local subject="$1"
667    local pathname="${2:-"/dev/null"}"
668    local msg_body
669    local msg_out
670    local msg_err
671
672    [ -n "${ZED_GOTIFY_URL}" ] && [ -n "${ZED_GOTIFY_APPTOKEN}" ] || return 2
673    local url="${ZED_GOTIFY_URL}/message?token=${ZED_GOTIFY_APPTOKEN}"
674
675    if [ ! -r "${pathname}" ]; then
676        zed_log_err "gotify cannot read \"${pathname}\""
677        return 1
678    fi
679
680    zed_check_cmd "curl" "sed" || return 1
681
682    # Read the message body in.
683    #
684    msg_body="$(cat "${pathname}")"
685
686    if [ -z "${msg_body}" ]
687    then
688        msg_body=$subject
689        subject=""
690    fi
691
692    # Send the POST request and check for errors.
693    #
694    if [ -n "${ZED_GOTIFY_PRIORITY}" ]; then
695        msg_out="$( \
696        curl \
697        --form-string "title=${subject}" \
698        --form-string "message=${msg_body}" \
699        --form-string "priority=${ZED_GOTIFY_PRIORITY}" \
700        "${url}" \
701        2>/dev/null \
702        )"; rv=$?
703    else
704        msg_out="$( \
705        curl \
706        --form-string "title=${subject}" \
707        --form-string "message=${msg_body}" \
708        "${url}" \
709        2>/dev/null \
710        )"; rv=$?
711    fi
712
713    if [ "${rv}" -ne 0 ]; then
714        zed_log_err "curl exit=${rv}"
715        return 1
716    fi
717    msg_err="$(echo "${msg_out}" \
718        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
719    if [ -n "${msg_err}" ]; then
720        zed_log_err "gotify \"${msg_err}"\"
721        return 1
722    fi
723    return 0
724}
725
726
727
728# zed_rate_limit (tag, [interval])
729#
730# Check whether an event of a given type [tag] has already occurred within the
731# last [interval] seconds.
732#
733# This function obtains a lock on the statefile using file descriptor 9.
734#
735# Arguments
736#   tag: arbitrary string for grouping related events to rate-limit
737#   interval: time interval in seconds (OPTIONAL)
738#
739# Globals
740#   ZED_NOTIFY_INTERVAL_SECS
741#   ZED_RUNDIR
742#
743# Return
744#   0 if the event should be processed
745#   1 if the event should be dropped
746#
747# State File Format
748#   time;tag
749#
750zed_rate_limit()
751{
752    local tag="$1"
753    local interval="${2:-${ZED_NOTIFY_INTERVAL_SECS}}"
754    local lockfile="zed.zedlet.state.lock"
755    local lockfile_fd=9
756    local statefile="${ZED_RUNDIR}/zed.zedlet.state"
757    local time_now
758    local time_prev
759    local umask_bak
760    local rv=0
761
762    [ -n "${tag}" ] || return 0
763
764    zed_lock "${lockfile}" "${lockfile_fd}"
765    time_now="$(date +%s)"
766    time_prev="$(grep -E "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
767        | tail -1 | cut -d\; -f1)"
768
769    if [ -n "${time_prev}" ] \
770            && [ "$((time_now - time_prev))" -lt "${interval}" ]; then
771        rv=1
772    else
773        umask_bak="$(umask)"
774        umask 077
775        grep -E -v "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
776            > "${statefile}.$$"
777        echo "${time_now};${tag}" >> "${statefile}.$$"
778        mv -f "${statefile}.$$" "${statefile}"
779        umask "${umask_bak}"
780    fi
781
782    zed_unlock "${lockfile}" "${lockfile_fd}"
783    return "${rv}"
784}
785
786
787# zed_guid_to_pool (guid)
788#
789# Convert a pool GUID into its pool name (like "tank")
790# Arguments
791#   guid: pool GUID (decimal or hex)
792#
793# Return
794#   Pool name
795#
796zed_guid_to_pool()
797{
798	if [ -z "$1" ] ; then
799		return
800	fi
801
802	guid="$(printf "%u" "$1")"
803	$ZPOOL get -H -ovalue,name guid | awk '$1 == '"$guid"' {print $2; exit}'
804}
805
806# zed_exit_if_ignoring_this_event
807#
808# Exit the script if we should ignore this event, as determined by
809# $ZED_SYSLOG_SUBCLASS_INCLUDE and $ZED_SYSLOG_SUBCLASS_EXCLUDE in zed.rc.
810# This function assumes you've imported the normal zed variables.
811zed_exit_if_ignoring_this_event()
812{
813	if [ -n "${ZED_SYSLOG_SUBCLASS_INCLUDE}" ]; then
814	    eval "case ${ZEVENT_SUBCLASS} in
815	    ${ZED_SYSLOG_SUBCLASS_INCLUDE});;
816	    *) exit 0;;
817	    esac"
818	elif [ -n "${ZED_SYSLOG_SUBCLASS_EXCLUDE}" ]; then
819	    eval "case ${ZEVENT_SUBCLASS} in
820	    ${ZED_SYSLOG_SUBCLASS_EXCLUDE}) exit 0;;
821	    *);;
822	    esac"
823	fi
824}
825