xref: /freebsd/sys/contrib/openzfs/cmd/zed/zed.d/zed-functions.sh (revision d0abb9a6399accc9053e2808052be00a6754ecef)
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    # shellcheck disable=SC2016
445    #
446    msg_json="$(printf '{"text": "*%s*\\n```%s```"}' "${subject}" "${msg_body}" )"
447
448    # Send the POST request and check for errors.
449    #
450    msg_out="$(curl -X POST "${url}" \
451        --header "Content-Type: application/json" --data-binary "${msg_json}" \
452        2>/dev/null)"; rv=$?
453    if [ "${rv}" -ne 0 ]; then
454        zed_log_err "curl exit=${rv}"
455        return 1
456    fi
457    msg_err="$(echo "${msg_out}" \
458        | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
459    if [ -n "${msg_err}" ]; then
460        zed_log_err "slack webhook \"${msg_err}"\"
461        return 1
462    fi
463    return 0
464}
465
466# zed_notify_pushover (subject, pathname)
467#
468# Send a notification via Pushover <https://pushover.net/>.
469# The access token (ZED_PUSHOVER_TOKEN) identifies this client to the
470# Pushover server. The user token (ZED_PUSHOVER_USER) defines the user or
471# group to which the notification will be sent.
472#
473# Requires curl and sed executables to be installed in the standard PATH.
474#
475# References
476#   https://pushover.net/api
477#
478# Arguments
479#   subject: notification subject
480#   pathname: pathname containing the notification message (OPTIONAL)
481#
482# Globals
483#   ZED_PUSHOVER_TOKEN
484#   ZED_PUSHOVER_USER
485#
486# Return
487#   0: notification sent
488#   1: notification failed
489#   2: not configured
490#
491zed_notify_pushover()
492{
493    local subject="$1"
494    local pathname="${2:-"/dev/null"}"
495    local msg_body
496    local msg_out
497    local msg_err
498    local url="https://api.pushover.net/1/messages.json"
499
500    [ -n "${ZED_PUSHOVER_TOKEN}" ] && [ -n "${ZED_PUSHOVER_USER}" ] || return 2
501
502    if [ ! -r "${pathname}" ]; then
503        zed_log_err "pushover cannot read \"${pathname}\""
504        return 1
505    fi
506
507    zed_check_cmd "curl" "sed" || return 1
508
509    # Read the message body in.
510    #
511    msg_body="$(cat "${pathname}")"
512
513    if [ -z "${msg_body}" ]
514    then
515        msg_body=$subject
516        subject=""
517    fi
518
519    # Send the POST request and check for errors.
520    #
521    msg_out="$( \
522        curl \
523        --form-string "token=${ZED_PUSHOVER_TOKEN}" \
524        --form-string "user=${ZED_PUSHOVER_USER}" \
525        --form-string "message=${msg_body}" \
526        --form-string "title=${subject}" \
527        "${url}" \
528        2>/dev/null \
529        )"; rv=$?
530    if [ "${rv}" -ne 0 ]; then
531        zed_log_err "curl exit=${rv}"
532        return 1
533    fi
534    msg_err="$(echo "${msg_out}" \
535        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
536    if [ -n "${msg_err}" ]; then
537        zed_log_err "pushover \"${msg_err}"\"
538        return 1
539    fi
540    return 0
541}
542
543
544# zed_notify_ntfy (subject, pathname)
545#
546# Send a notification via Ntfy.sh <https://ntfy.sh/>.
547# The ntfy topic (ZED_NTFY_TOPIC) identifies the topic that the notification
548# will be sent to Ntfy.sh server. The ntfy url (ZED_NTFY_URL) defines the
549# self-hosted or provided hosted ntfy service location. The ntfy access token
550# <https://docs.ntfy.sh/publish/#access-tokens> (ZED_NTFY_ACCESS_TOKEN) reprsents an
551# access token that could be used if a topic is read/write protected. If a
552# topic can be written to publicaly, a ZED_NTFY_ACCESS_TOKEN is not required.
553#
554# Requires curl and sed executables to be installed in the standard PATH.
555#
556# References
557#   https://docs.ntfy.sh
558#
559# Arguments
560#   subject: notification subject
561#   pathname: pathname containing the notification message (OPTIONAL)
562#
563# Globals
564#   ZED_NTFY_TOPIC
565#   ZED_NTFY_ACCESS_TOKEN (OPTIONAL)
566#   ZED_NTFY_URL
567#
568# Return
569#   0: notification sent
570#   1: notification failed
571#   2: not configured
572#
573zed_notify_ntfy()
574{
575    local subject="$1"
576    local pathname="${2:-"/dev/null"}"
577    local msg_body
578    local msg_out
579    local msg_err
580
581    [ -n "${ZED_NTFY_TOPIC}" ] || return 2
582    local url="${ZED_NTFY_URL:-"https://ntfy.sh"}/${ZED_NTFY_TOPIC}"
583
584    if [ ! -r "${pathname}" ]; then
585        zed_log_err "ntfy cannot read \"${pathname}\""
586        return 1
587    fi
588
589    zed_check_cmd "curl" "sed" || return 1
590
591    # Read the message body in.
592    #
593    msg_body="$(cat "${pathname}")"
594
595    if [ -z "${msg_body}" ]
596    then
597        msg_body=$subject
598        subject=""
599    fi
600
601    # Send the POST request and check for errors.
602    #
603    if [ -n "${ZED_NTFY_ACCESS_TOKEN}" ]; then
604        msg_out="$( \
605        curl \
606        -u ":${ZED_NTFY_ACCESS_TOKEN}" \
607        -H "Title: ${subject}" \
608        -d "${msg_body}" \
609        -H "Priority: high" \
610        "${url}" \
611        2>/dev/null \
612        )"; rv=$?
613    else
614        msg_out="$( \
615        curl \
616        -H "Title: ${subject}" \
617        -d "${msg_body}" \
618        -H "Priority: high" \
619        "${url}" \
620        2>/dev/null \
621        )"; rv=$?
622    fi
623    if [ "${rv}" -ne 0 ]; then
624        zed_log_err "curl exit=${rv}"
625        return 1
626    fi
627    msg_err="$(echo "${msg_out}" \
628        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
629    if [ -n "${msg_err}" ]; then
630        zed_log_err "ntfy \"${msg_err}"\"
631        return 1
632    fi
633    return 0
634}
635
636
637# zed_notify_gotify (subject, pathname)
638#
639# Send a notification via Gotify <https://gotify.net/>.
640# The Gotify URL (ZED_GOTIFY_URL) defines a self-hosted Gotify location.
641# The Gotify application token (ZED_GOTIFY_APPTOKEN) defines a
642# Gotify application token which is associated with a message.
643# The optional Gotify priority value (ZED_GOTIFY_PRIORITY) overrides the
644# default or configured priority at the Gotify server for the application.
645#
646# Requires curl and sed executables to be installed in the standard PATH.
647#
648# References
649#   https://gotify.net/docs/index
650#
651# Arguments
652#   subject: notification subject
653#   pathname: pathname containing the notification message (OPTIONAL)
654#
655# Globals
656#   ZED_GOTIFY_URL
657#   ZED_GOTIFY_APPTOKEN
658#   ZED_GOTIFY_PRIORITY
659#
660# Return
661#   0: notification sent
662#   1: notification failed
663#   2: not configured
664#
665zed_notify_gotify()
666{
667    local subject="$1"
668    local pathname="${2:-"/dev/null"}"
669    local msg_body
670    local msg_out
671    local msg_err
672
673    [ -n "${ZED_GOTIFY_URL}" ] && [ -n "${ZED_GOTIFY_APPTOKEN}" ] || return 2
674    local url="${ZED_GOTIFY_URL}/message?token=${ZED_GOTIFY_APPTOKEN}"
675
676    if [ ! -r "${pathname}" ]; then
677        zed_log_err "gotify cannot read \"${pathname}\""
678        return 1
679    fi
680
681    zed_check_cmd "curl" "sed" || return 1
682
683    # Read the message body in.
684    #
685    msg_body="$(cat "${pathname}")"
686
687    if [ -z "${msg_body}" ]
688    then
689        msg_body=$subject
690        subject=""
691    fi
692
693    # Send the POST request and check for errors.
694    #
695    if [ -n "${ZED_GOTIFY_PRIORITY}" ]; then
696        msg_out="$( \
697        curl \
698        --form-string "title=${subject}" \
699        --form-string "message=${msg_body}" \
700        --form-string "priority=${ZED_GOTIFY_PRIORITY}" \
701        "${url}" \
702        2>/dev/null \
703        )"; rv=$?
704    else
705        msg_out="$( \
706        curl \
707        --form-string "title=${subject}" \
708        --form-string "message=${msg_body}" \
709        "${url}" \
710        2>/dev/null \
711        )"; rv=$?
712    fi
713
714    if [ "${rv}" -ne 0 ]; then
715        zed_log_err "curl exit=${rv}"
716        return 1
717    fi
718    msg_err="$(echo "${msg_out}" \
719        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
720    if [ -n "${msg_err}" ]; then
721        zed_log_err "gotify \"${msg_err}"\"
722        return 1
723    fi
724    return 0
725}
726
727
728
729# zed_rate_limit (tag, [interval])
730#
731# Check whether an event of a given type [tag] has already occurred within the
732# last [interval] seconds.
733#
734# This function obtains a lock on the statefile using file descriptor 9.
735#
736# Arguments
737#   tag: arbitrary string for grouping related events to rate-limit
738#   interval: time interval in seconds (OPTIONAL)
739#
740# Globals
741#   ZED_NOTIFY_INTERVAL_SECS
742#   ZED_RUNDIR
743#
744# Return
745#   0 if the event should be processed
746#   1 if the event should be dropped
747#
748# State File Format
749#   time;tag
750#
751zed_rate_limit()
752{
753    local tag="$1"
754    local interval="${2:-${ZED_NOTIFY_INTERVAL_SECS}}"
755    local lockfile="zed.zedlet.state.lock"
756    local lockfile_fd=9
757    local statefile="${ZED_RUNDIR}/zed.zedlet.state"
758    local time_now
759    local time_prev
760    local umask_bak
761    local rv=0
762
763    [ -n "${tag}" ] || return 0
764
765    zed_lock "${lockfile}" "${lockfile_fd}"
766    time_now="$(date +%s)"
767    time_prev="$(grep -E "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
768        | tail -1 | cut -d\; -f1)"
769
770    if [ -n "${time_prev}" ] \
771            && [ "$((time_now - time_prev))" -lt "${interval}" ]; then
772        rv=1
773    else
774        umask_bak="$(umask)"
775        umask 077
776        grep -E -v "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
777            > "${statefile}.$$"
778        echo "${time_now};${tag}" >> "${statefile}.$$"
779        mv -f "${statefile}.$$" "${statefile}"
780        umask "${umask_bak}"
781    fi
782
783    zed_unlock "${lockfile}" "${lockfile_fd}"
784    return "${rv}"
785}
786
787
788# zed_guid_to_pool (guid)
789#
790# Convert a pool GUID into its pool name (like "tank")
791# Arguments
792#   guid: pool GUID (decimal or hex)
793#
794# Return
795#   Pool name
796#
797zed_guid_to_pool()
798{
799	if [ -z "$1" ] ; then
800		return
801	fi
802
803	guid="$(printf "%u" "$1")"
804	$ZPOOL get -H -ovalue,name guid | awk '$1 == '"$guid"' {print $2; exit}'
805}
806
807# zed_exit_if_ignoring_this_event
808#
809# Exit the script if we should ignore this event, as determined by
810# $ZED_SYSLOG_SUBCLASS_INCLUDE and $ZED_SYSLOG_SUBCLASS_EXCLUDE in zed.rc.
811# This function assumes you've imported the normal zed variables.
812zed_exit_if_ignoring_this_event()
813{
814	if [ -n "${ZED_SYSLOG_SUBCLASS_INCLUDE}" ]; then
815	    eval "case ${ZEVENT_SUBCLASS} in
816	    ${ZED_SYSLOG_SUBCLASS_INCLUDE});;
817	    *) exit 0;;
818	    esac"
819	elif [ -n "${ZED_SYSLOG_SUBCLASS_EXCLUDE}" ]; then
820	    eval "case ${ZEVENT_SUBCLASS} in
821	    ${ZED_SYSLOG_SUBCLASS_EXCLUDE}) exit 0;;
822	    *);;
823	    esac"
824	fi
825}
826