xref: /freebsd/sys/contrib/openzfs/cmd/zed/zed.d/zed-functions.sh (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
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        fi
287        # output message
288        cat "${pathname}"
289    } |
290    eval ${ZED_EMAIL_PROG} ${ZED_EMAIL_OPTS_PARSED} >/dev/null 2>&1
291    rv=$?
292    if [ "${rv}" -ne 0 ]; then
293        zed_log_err "${ZED_EMAIL_PROG##*/} exit=${rv}"
294        return 1
295    fi
296    return 0
297}
298
299
300# zed_notify_pushbullet (subject, pathname)
301#
302# Send a notification via Pushbullet <https://www.pushbullet.com/>.
303# The access token (ZED_PUSHBULLET_ACCESS_TOKEN) identifies this client to the
304# Pushbullet server.  The optional channel tag (ZED_PUSHBULLET_CHANNEL_TAG) is
305# for pushing to notification feeds that can be subscribed to; if a channel is
306# not defined, push notifications will instead be sent to all devices
307# associated with the account specified by the access token.
308#
309# Requires awk, curl, and sed executables to be installed in the standard PATH.
310#
311# References
312#   https://docs.pushbullet.com/
313#   https://www.pushbullet.com/security
314#
315# Arguments
316#   subject: notification subject
317#   pathname: pathname containing the notification message (OPTIONAL)
318#
319# Globals
320#   ZED_PUSHBULLET_ACCESS_TOKEN
321#   ZED_PUSHBULLET_CHANNEL_TAG
322#
323# Return
324#   0: notification sent
325#   1: notification failed
326#   2: not configured
327#
328zed_notify_pushbullet()
329{
330    local subject="$1"
331    local pathname="${2:-"/dev/null"}"
332    local msg_body
333    local msg_tag
334    local msg_json
335    local msg_out
336    local msg_err
337    local url="https://api.pushbullet.com/v2/pushes"
338
339    [ -n "${ZED_PUSHBULLET_ACCESS_TOKEN}" ] || return 2
340
341    [ -n "${subject}" ] || return 1
342    if [ ! -r "${pathname}" ]; then
343        zed_log_err "pushbullet cannot read \"${pathname}\""
344        return 1
345    fi
346
347    zed_check_cmd "awk" "curl" "sed" || return 1
348
349    # Escape the following characters in the message body for JSON:
350    # newline, backslash, double quote, horizontal tab, vertical tab,
351    # and carriage return.
352    #
353    msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\"");
354        gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \
355        "${pathname}")"
356
357    # Push to a channel if one is configured.
358    #
359    [ -n "${ZED_PUSHBULLET_CHANNEL_TAG}" ] && msg_tag="$(printf \
360        '"channel_tag": "%s", ' "${ZED_PUSHBULLET_CHANNEL_TAG}")"
361
362    # Construct the JSON message for pushing a note.
363    #
364    msg_json="$(printf '{%s"type": "note", "title": "%s", "body": "%s"}' \
365        "${msg_tag}" "${subject}" "${msg_body}")"
366
367    # Send the POST request and check for errors.
368    #
369    msg_out="$(curl -u "${ZED_PUSHBULLET_ACCESS_TOKEN}:" -X POST "${url}" \
370        --header "Content-Type: application/json" --data-binary "${msg_json}" \
371        2>/dev/null)"; rv=$?
372    if [ "${rv}" -ne 0 ]; then
373        zed_log_err "curl exit=${rv}"
374        return 1
375    fi
376    msg_err="$(echo "${msg_out}" \
377        | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
378    if [ -n "${msg_err}" ]; then
379        zed_log_err "pushbullet \"${msg_err}"\"
380        return 1
381    fi
382    return 0
383}
384
385
386# zed_notify_slack_webhook (subject, pathname)
387#
388# Notification via Slack Webhook <https://api.slack.com/incoming-webhooks>.
389# The Webhook URL (ZED_SLACK_WEBHOOK_URL) identifies this client to the
390# Slack channel.
391#
392# Requires awk, curl, and sed executables to be installed in the standard PATH.
393#
394# References
395#   https://api.slack.com/incoming-webhooks
396#
397# Arguments
398#   subject: notification subject
399#   pathname: pathname containing the notification message (OPTIONAL)
400#
401# Globals
402#   ZED_SLACK_WEBHOOK_URL
403#
404# Return
405#   0: notification sent
406#   1: notification failed
407#   2: not configured
408#
409zed_notify_slack_webhook()
410{
411    [ -n "${ZED_SLACK_WEBHOOK_URL}" ] || return 2
412
413    local subject="$1"
414    local pathname="${2:-"/dev/null"}"
415    local msg_body
416    local msg_tag
417    local msg_json
418    local msg_out
419    local msg_err
420    local url="${ZED_SLACK_WEBHOOK_URL}"
421
422    [ -n "${subject}" ] || return 1
423    if [ ! -r "${pathname}" ]; then
424        zed_log_err "slack webhook cannot read \"${pathname}\""
425        return 1
426    fi
427
428    zed_check_cmd "awk" "curl" "sed" || return 1
429
430    # Escape the following characters in the message body for JSON:
431    # newline, backslash, double quote, horizontal tab, vertical tab,
432    # and carriage return.
433    #
434    msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\"");
435        gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \
436        "${pathname}")"
437
438    # Construct the JSON message for posting.
439    #
440    msg_json="$(printf '{"text": "*%s*\\n%s"}' "${subject}" "${msg_body}" )"
441
442    # Send the POST request and check for errors.
443    #
444    msg_out="$(curl -X POST "${url}" \
445        --header "Content-Type: application/json" --data-binary "${msg_json}" \
446        2>/dev/null)"; rv=$?
447    if [ "${rv}" -ne 0 ]; then
448        zed_log_err "curl exit=${rv}"
449        return 1
450    fi
451    msg_err="$(echo "${msg_out}" \
452        | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
453    if [ -n "${msg_err}" ]; then
454        zed_log_err "slack webhook \"${msg_err}"\"
455        return 1
456    fi
457    return 0
458}
459
460# zed_notify_pushover (subject, pathname)
461#
462# Send a notification via Pushover <https://pushover.net/>.
463# The access token (ZED_PUSHOVER_TOKEN) identifies this client to the
464# Pushover server. The user token (ZED_PUSHOVER_USER) defines the user or
465# group to which the notification will be sent.
466#
467# Requires curl and sed executables to be installed in the standard PATH.
468#
469# References
470#   https://pushover.net/api
471#
472# Arguments
473#   subject: notification subject
474#   pathname: pathname containing the notification message (OPTIONAL)
475#
476# Globals
477#   ZED_PUSHOVER_TOKEN
478#   ZED_PUSHOVER_USER
479#
480# Return
481#   0: notification sent
482#   1: notification failed
483#   2: not configured
484#
485zed_notify_pushover()
486{
487    local subject="$1"
488    local pathname="${2:-"/dev/null"}"
489    local msg_body
490    local msg_out
491    local msg_err
492    local url="https://api.pushover.net/1/messages.json"
493
494    [ -n "${ZED_PUSHOVER_TOKEN}" ] && [ -n "${ZED_PUSHOVER_USER}" ] || return 2
495
496    if [ ! -r "${pathname}" ]; then
497        zed_log_err "pushover cannot read \"${pathname}\""
498        return 1
499    fi
500
501    zed_check_cmd "curl" "sed" || return 1
502
503    # Read the message body in.
504    #
505    msg_body="$(cat "${pathname}")"
506
507    if [ -z "${msg_body}" ]
508    then
509        msg_body=$subject
510        subject=""
511    fi
512
513    # Send the POST request and check for errors.
514    #
515    msg_out="$( \
516        curl \
517        --form-string "token=${ZED_PUSHOVER_TOKEN}" \
518        --form-string "user=${ZED_PUSHOVER_USER}" \
519        --form-string "message=${msg_body}" \
520        --form-string "title=${subject}" \
521        "${url}" \
522        2>/dev/null \
523        )"; rv=$?
524    if [ "${rv}" -ne 0 ]; then
525        zed_log_err "curl exit=${rv}"
526        return 1
527    fi
528    msg_err="$(echo "${msg_out}" \
529        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
530    if [ -n "${msg_err}" ]; then
531        zed_log_err "pushover \"${msg_err}"\"
532        return 1
533    fi
534    return 0
535}
536
537
538# zed_notify_ntfy (subject, pathname)
539#
540# Send a notification via Ntfy.sh <https://ntfy.sh/>.
541# The ntfy topic (ZED_NTFY_TOPIC) identifies the topic that the notification
542# will be sent to Ntfy.sh server. The ntfy url (ZED_NTFY_URL) defines the
543# self-hosted or provided hosted ntfy service location. The ntfy access token
544# <https://docs.ntfy.sh/publish/#access-tokens> (ZED_NTFY_ACCESS_TOKEN) reprsents an
545# access token that could be used if a topic is read/write protected. If a
546# topic can be written to publicaly, a ZED_NTFY_ACCESS_TOKEN is not required.
547#
548# Requires curl and sed executables to be installed in the standard PATH.
549#
550# References
551#   https://docs.ntfy.sh
552#
553# Arguments
554#   subject: notification subject
555#   pathname: pathname containing the notification message (OPTIONAL)
556#
557# Globals
558#   ZED_NTFY_TOPIC
559#   ZED_NTFY_ACCESS_TOKEN (OPTIONAL)
560#   ZED_NTFY_URL
561#
562# Return
563#   0: notification sent
564#   1: notification failed
565#   2: not configured
566#
567zed_notify_ntfy()
568{
569    local subject="$1"
570    local pathname="${2:-"/dev/null"}"
571    local msg_body
572    local msg_out
573    local msg_err
574
575    [ -n "${ZED_NTFY_TOPIC}" ] || return 2
576    local url="${ZED_NTFY_URL:-"https://ntfy.sh"}/${ZED_NTFY_TOPIC}"
577
578    if [ ! -r "${pathname}" ]; then
579        zed_log_err "ntfy cannot read \"${pathname}\""
580        return 1
581    fi
582
583    zed_check_cmd "curl" "sed" || return 1
584
585    # Read the message body in.
586    #
587    msg_body="$(cat "${pathname}")"
588
589    if [ -z "${msg_body}" ]
590    then
591        msg_body=$subject
592        subject=""
593    fi
594
595    # Send the POST request and check for errors.
596    #
597    if [ -n "${ZED_NTFY_ACCESS_TOKEN}" ]; then
598        msg_out="$( \
599        curl \
600        -u ":${ZED_NTFY_ACCESS_TOKEN}" \
601        -H "Title: ${subject}" \
602        -d "${msg_body}" \
603        -H "Priority: high" \
604        "${url}" \
605        2>/dev/null \
606        )"; rv=$?
607    else
608        msg_out="$( \
609        curl \
610        -H "Title: ${subject}" \
611        -d "${msg_body}" \
612        -H "Priority: high" \
613        "${url}" \
614        2>/dev/null \
615        )"; rv=$?
616    fi
617    if [ "${rv}" -ne 0 ]; then
618        zed_log_err "curl exit=${rv}"
619        return 1
620    fi
621    msg_err="$(echo "${msg_out}" \
622        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
623    if [ -n "${msg_err}" ]; then
624        zed_log_err "ntfy \"${msg_err}"\"
625        return 1
626    fi
627    return 0
628}
629
630
631# zed_notify_gotify (subject, pathname)
632#
633# Send a notification via Gotify <https://gotify.net/>.
634# The Gotify URL (ZED_GOTIFY_URL) defines a self-hosted Gotify location.
635# The Gotify application token (ZED_GOTIFY_APPTOKEN) defines a
636# Gotify application token which is associated with a message.
637# The optional Gotify priority value (ZED_GOTIFY_PRIORITY) overrides the
638# default or configured priority at the Gotify server for the application.
639#
640# Requires curl and sed executables to be installed in the standard PATH.
641#
642# References
643#   https://gotify.net/docs/index
644#
645# Arguments
646#   subject: notification subject
647#   pathname: pathname containing the notification message (OPTIONAL)
648#
649# Globals
650#   ZED_GOTIFY_URL
651#   ZED_GOTIFY_APPTOKEN
652#   ZED_GOTIFY_PRIORITY
653#
654# Return
655#   0: notification sent
656#   1: notification failed
657#   2: not configured
658#
659zed_notify_gotify()
660{
661    local subject="$1"
662    local pathname="${2:-"/dev/null"}"
663    local msg_body
664    local msg_out
665    local msg_err
666
667    [ -n "${ZED_GOTIFY_URL}" ] && [ -n "${ZED_GOTIFY_APPTOKEN}" ] || return 2
668    local url="${ZED_GOTIFY_URL}/message?token=${ZED_GOTIFY_APPTOKEN}"
669
670    if [ ! -r "${pathname}" ]; then
671        zed_log_err "gotify cannot read \"${pathname}\""
672        return 1
673    fi
674
675    zed_check_cmd "curl" "sed" || return 1
676
677    # Read the message body in.
678    #
679    msg_body="$(cat "${pathname}")"
680
681    if [ -z "${msg_body}" ]
682    then
683        msg_body=$subject
684        subject=""
685    fi
686
687    # Send the POST request and check for errors.
688    #
689    if [ -n "${ZED_GOTIFY_PRIORITY}" ]; then
690        msg_out="$( \
691        curl \
692        --form-string "title=${subject}" \
693        --form-string "message=${msg_body}" \
694        --form-string "priority=${ZED_GOTIFY_PRIORITY}" \
695        "${url}" \
696        2>/dev/null \
697        )"; rv=$?
698    else
699        msg_out="$( \
700        curl \
701        --form-string "title=${subject}" \
702        --form-string "message=${msg_body}" \
703        "${url}" \
704        2>/dev/null \
705        )"; rv=$?
706    fi
707
708    if [ "${rv}" -ne 0 ]; then
709        zed_log_err "curl exit=${rv}"
710        return 1
711    fi
712    msg_err="$(echo "${msg_out}" \
713        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
714    if [ -n "${msg_err}" ]; then
715        zed_log_err "gotify \"${msg_err}"\"
716        return 1
717    fi
718    return 0
719}
720
721
722
723# zed_rate_limit (tag, [interval])
724#
725# Check whether an event of a given type [tag] has already occurred within the
726# last [interval] seconds.
727#
728# This function obtains a lock on the statefile using file descriptor 9.
729#
730# Arguments
731#   tag: arbitrary string for grouping related events to rate-limit
732#   interval: time interval in seconds (OPTIONAL)
733#
734# Globals
735#   ZED_NOTIFY_INTERVAL_SECS
736#   ZED_RUNDIR
737#
738# Return
739#   0 if the event should be processed
740#   1 if the event should be dropped
741#
742# State File Format
743#   time;tag
744#
745zed_rate_limit()
746{
747    local tag="$1"
748    local interval="${2:-${ZED_NOTIFY_INTERVAL_SECS}}"
749    local lockfile="zed.zedlet.state.lock"
750    local lockfile_fd=9
751    local statefile="${ZED_RUNDIR}/zed.zedlet.state"
752    local time_now
753    local time_prev
754    local umask_bak
755    local rv=0
756
757    [ -n "${tag}" ] || return 0
758
759    zed_lock "${lockfile}" "${lockfile_fd}"
760    time_now="$(date +%s)"
761    time_prev="$(grep -E "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
762        | tail -1 | cut -d\; -f1)"
763
764    if [ -n "${time_prev}" ] \
765            && [ "$((time_now - time_prev))" -lt "${interval}" ]; then
766        rv=1
767    else
768        umask_bak="$(umask)"
769        umask 077
770        grep -E -v "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
771            > "${statefile}.$$"
772        echo "${time_now};${tag}" >> "${statefile}.$$"
773        mv -f "${statefile}.$$" "${statefile}"
774        umask "${umask_bak}"
775    fi
776
777    zed_unlock "${lockfile}" "${lockfile_fd}"
778    return "${rv}"
779}
780
781
782# zed_guid_to_pool (guid)
783#
784# Convert a pool GUID into its pool name (like "tank")
785# Arguments
786#   guid: pool GUID (decimal or hex)
787#
788# Return
789#   Pool name
790#
791zed_guid_to_pool()
792{
793	if [ -z "$1" ] ; then
794		return
795	fi
796
797	guid="$(printf "%u" "$1")"
798	$ZPOOL get -H -ovalue,name guid | awk '$1 == '"$guid"' {print $2; exit}'
799}
800
801# zed_exit_if_ignoring_this_event
802#
803# Exit the script if we should ignore this event, as determined by
804# $ZED_SYSLOG_SUBCLASS_INCLUDE and $ZED_SYSLOG_SUBCLASS_EXCLUDE in zed.rc.
805# This function assumes you've imported the normal zed variables.
806zed_exit_if_ignoring_this_event()
807{
808	if [ -n "${ZED_SYSLOG_SUBCLASS_INCLUDE}" ]; then
809	    eval "case ${ZEVENT_SUBCLASS} in
810	    ${ZED_SYSLOG_SUBCLASS_INCLUDE});;
811	    *) exit 0;;
812	    esac"
813	elif [ -n "${ZED_SYSLOG_SUBCLASS_EXCLUDE}" ]; then
814	    eval "case ${ZEVENT_SUBCLASS} in
815	    ${ZED_SYSLOG_SUBCLASS_EXCLUDE}) exit 0;;
816	    *);;
817	    esac"
818	fi
819}
820