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    [ "${num_success}" -gt 0 ] && return 0
213    [ "${num_failure}" -gt 0 ] && return 1
214    return 2
215}
216
217
218# zed_notify_email (subject, pathname)
219#
220# Send a notification via email to the address specified by ZED_EMAIL_ADDR.
221#
222# Requires the mail executable to be installed in the standard PATH, or
223# ZED_EMAIL_PROG to be defined with the pathname of an executable capable of
224# reading a message body from stdin.
225#
226# Command-line options to the mail executable can be specified in
227# ZED_EMAIL_OPTS.  This undergoes the following keyword substitutions:
228# - @ADDRESS@ is replaced with the space-delimited recipient email address(es)
229# - @SUBJECT@ is replaced with the notification subject
230#   If @SUBJECT@ was omited here, a "Subject: ..." header will be added to notification
231#
232#
233# Arguments
234#   subject: notification subject
235#   pathname: pathname containing the notification message (OPTIONAL)
236#
237# Globals
238#   ZED_EMAIL_PROG
239#   ZED_EMAIL_OPTS
240#   ZED_EMAIL_ADDR
241#
242# Return
243#   0: notification sent
244#   1: notification failed
245#   2: not configured
246#
247zed_notify_email()
248{
249    local subject="${1:-"ZED notification"}"
250    local pathname="${2:-"/dev/null"}"
251
252    : "${ZED_EMAIL_PROG:="mail"}"
253    : "${ZED_EMAIL_OPTS:="-s '@SUBJECT@' @ADDRESS@"}"
254
255    # For backward compatibility with ZED_EMAIL.
256    if [ -n "${ZED_EMAIL}" ] && [ -z "${ZED_EMAIL_ADDR}" ]; then
257        ZED_EMAIL_ADDR="${ZED_EMAIL}"
258    fi
259    [ -n "${ZED_EMAIL_ADDR}" ] || return 2
260
261    zed_check_cmd "${ZED_EMAIL_PROG}" || return 1
262
263    [ -n "${subject}" ] || return 1
264    if [ ! -r "${pathname}" ]; then
265        zed_log_err \
266                "${ZED_EMAIL_PROG##*/} cannot read \"${pathname}\""
267        return 1
268    fi
269
270    # construct cmdline options
271    ZED_EMAIL_OPTS_PARSED="$(echo "${ZED_EMAIL_OPTS}" \
272        | sed   -e "s/@ADDRESS@/${ZED_EMAIL_ADDR}/g" \
273                -e "s/@SUBJECT@/${subject}/g")"
274
275    # pipe message to email prog
276    # shellcheck disable=SC2086,SC2248
277    {
278        # no subject passed as option?
279        if [ "${ZED_EMAIL_OPTS%@SUBJECT@*}" = "${ZED_EMAIL_OPTS}" ] ; then
280            # inject subject header
281            printf "Subject: %s\n" "${subject}"
282        fi
283        # output message
284        cat "${pathname}"
285    } |
286    eval ${ZED_EMAIL_PROG} ${ZED_EMAIL_OPTS_PARSED} >/dev/null 2>&1
287    rv=$?
288    if [ "${rv}" -ne 0 ]; then
289        zed_log_err "${ZED_EMAIL_PROG##*/} exit=${rv}"
290        return 1
291    fi
292    return 0
293}
294
295
296# zed_notify_pushbullet (subject, pathname)
297#
298# Send a notification via Pushbullet <https://www.pushbullet.com/>.
299# The access token (ZED_PUSHBULLET_ACCESS_TOKEN) identifies this client to the
300# Pushbullet server.  The optional channel tag (ZED_PUSHBULLET_CHANNEL_TAG) is
301# for pushing to notification feeds that can be subscribed to; if a channel is
302# not defined, push notifications will instead be sent to all devices
303# associated with the account specified by the access token.
304#
305# Requires awk, curl, and sed executables to be installed in the standard PATH.
306#
307# References
308#   https://docs.pushbullet.com/
309#   https://www.pushbullet.com/security
310#
311# Arguments
312#   subject: notification subject
313#   pathname: pathname containing the notification message (OPTIONAL)
314#
315# Globals
316#   ZED_PUSHBULLET_ACCESS_TOKEN
317#   ZED_PUSHBULLET_CHANNEL_TAG
318#
319# Return
320#   0: notification sent
321#   1: notification failed
322#   2: not configured
323#
324zed_notify_pushbullet()
325{
326    local subject="$1"
327    local pathname="${2:-"/dev/null"}"
328    local msg_body
329    local msg_tag
330    local msg_json
331    local msg_out
332    local msg_err
333    local url="https://api.pushbullet.com/v2/pushes"
334
335    [ -n "${ZED_PUSHBULLET_ACCESS_TOKEN}" ] || return 2
336
337    [ -n "${subject}" ] || return 1
338    if [ ! -r "${pathname}" ]; then
339        zed_log_err "pushbullet cannot read \"${pathname}\""
340        return 1
341    fi
342
343    zed_check_cmd "awk" "curl" "sed" || return 1
344
345    # Escape the following characters in the message body for JSON:
346    # newline, backslash, double quote, horizontal tab, vertical tab,
347    # and carriage return.
348    #
349    msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\"");
350        gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \
351        "${pathname}")"
352
353    # Push to a channel if one is configured.
354    #
355    [ -n "${ZED_PUSHBULLET_CHANNEL_TAG}" ] && msg_tag="$(printf \
356        '"channel_tag": "%s", ' "${ZED_PUSHBULLET_CHANNEL_TAG}")"
357
358    # Construct the JSON message for pushing a note.
359    #
360    msg_json="$(printf '{%s"type": "note", "title": "%s", "body": "%s"}' \
361        "${msg_tag}" "${subject}" "${msg_body}")"
362
363    # Send the POST request and check for errors.
364    #
365    msg_out="$(curl -u "${ZED_PUSHBULLET_ACCESS_TOKEN}:" -X POST "${url}" \
366        --header "Content-Type: application/json" --data-binary "${msg_json}" \
367        2>/dev/null)"; rv=$?
368    if [ "${rv}" -ne 0 ]; then
369        zed_log_err "curl exit=${rv}"
370        return 1
371    fi
372    msg_err="$(echo "${msg_out}" \
373        | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
374    if [ -n "${msg_err}" ]; then
375        zed_log_err "pushbullet \"${msg_err}"\"
376        return 1
377    fi
378    return 0
379}
380
381
382# zed_notify_slack_webhook (subject, pathname)
383#
384# Notification via Slack Webhook <https://api.slack.com/incoming-webhooks>.
385# The Webhook URL (ZED_SLACK_WEBHOOK_URL) identifies this client to the
386# Slack channel.
387#
388# Requires awk, curl, and sed executables to be installed in the standard PATH.
389#
390# References
391#   https://api.slack.com/incoming-webhooks
392#
393# Arguments
394#   subject: notification subject
395#   pathname: pathname containing the notification message (OPTIONAL)
396#
397# Globals
398#   ZED_SLACK_WEBHOOK_URL
399#
400# Return
401#   0: notification sent
402#   1: notification failed
403#   2: not configured
404#
405zed_notify_slack_webhook()
406{
407    [ -n "${ZED_SLACK_WEBHOOK_URL}" ] || return 2
408
409    local subject="$1"
410    local pathname="${2:-"/dev/null"}"
411    local msg_body
412    local msg_tag
413    local msg_json
414    local msg_out
415    local msg_err
416    local url="${ZED_SLACK_WEBHOOK_URL}"
417
418    [ -n "${subject}" ] || return 1
419    if [ ! -r "${pathname}" ]; then
420        zed_log_err "slack webhook cannot read \"${pathname}\""
421        return 1
422    fi
423
424    zed_check_cmd "awk" "curl" "sed" || return 1
425
426    # Escape the following characters in the message body for JSON:
427    # newline, backslash, double quote, horizontal tab, vertical tab,
428    # and carriage return.
429    #
430    msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\"");
431        gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \
432        "${pathname}")"
433
434    # Construct the JSON message for posting.
435    #
436    msg_json="$(printf '{"text": "*%s*\\n%s"}' "${subject}" "${msg_body}" )"
437
438    # Send the POST request and check for errors.
439    #
440    msg_out="$(curl -X POST "${url}" \
441        --header "Content-Type: application/json" --data-binary "${msg_json}" \
442        2>/dev/null)"; rv=$?
443    if [ "${rv}" -ne 0 ]; then
444        zed_log_err "curl exit=${rv}"
445        return 1
446    fi
447    msg_err="$(echo "${msg_out}" \
448        | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
449    if [ -n "${msg_err}" ]; then
450        zed_log_err "slack webhook \"${msg_err}"\"
451        return 1
452    fi
453    return 0
454}
455
456# zed_notify_pushover (subject, pathname)
457#
458# Send a notification via Pushover <https://pushover.net/>.
459# The access token (ZED_PUSHOVER_TOKEN) identifies this client to the
460# Pushover server. The user token (ZED_PUSHOVER_USER) defines the user or
461# group to which the notification will be sent.
462#
463# Requires curl and sed executables to be installed in the standard PATH.
464#
465# References
466#   https://pushover.net/api
467#
468# Arguments
469#   subject: notification subject
470#   pathname: pathname containing the notification message (OPTIONAL)
471#
472# Globals
473#   ZED_PUSHOVER_TOKEN
474#   ZED_PUSHOVER_USER
475#
476# Return
477#   0: notification sent
478#   1: notification failed
479#   2: not configured
480#
481zed_notify_pushover()
482{
483    local subject="$1"
484    local pathname="${2:-"/dev/null"}"
485    local msg_body
486    local msg_out
487    local msg_err
488    local url="https://api.pushover.net/1/messages.json"
489
490    [ -n "${ZED_PUSHOVER_TOKEN}" ] && [ -n "${ZED_PUSHOVER_USER}" ] || return 2
491
492    if [ ! -r "${pathname}" ]; then
493        zed_log_err "pushover cannot read \"${pathname}\""
494        return 1
495    fi
496
497    zed_check_cmd "curl" "sed" || return 1
498
499    # Read the message body in.
500    #
501    msg_body="$(cat "${pathname}")"
502
503    if [ -z "${msg_body}" ]
504    then
505        msg_body=$subject
506        subject=""
507    fi
508
509    # Send the POST request and check for errors.
510    #
511    msg_out="$( \
512        curl \
513        --form-string "token=${ZED_PUSHOVER_TOKEN}" \
514        --form-string "user=${ZED_PUSHOVER_USER}" \
515        --form-string "message=${msg_body}" \
516        --form-string "title=${subject}" \
517        "${url}" \
518        2>/dev/null \
519        )"; rv=$?
520    if [ "${rv}" -ne 0 ]; then
521        zed_log_err "curl exit=${rv}"
522        return 1
523    fi
524    msg_err="$(echo "${msg_out}" \
525        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
526    if [ -n "${msg_err}" ]; then
527        zed_log_err "pushover \"${msg_err}"\"
528        return 1
529    fi
530    return 0
531}
532
533
534# zed_notify_ntfy (subject, pathname)
535#
536# Send a notification via Ntfy.sh <https://ntfy.sh/>.
537# The ntfy topic (ZED_NTFY_TOPIC) identifies the topic that the notification
538# will be sent to Ntfy.sh server. The ntfy url (ZED_NTFY_URL) defines the
539# self-hosted or provided hosted ntfy service location. The ntfy access token
540# <https://docs.ntfy.sh/publish/#access-tokens> (ZED_NTFY_ACCESS_TOKEN) reprsents an
541# access token that could be used if a topic is read/write protected. If a
542# topic can be written to publicaly, a ZED_NTFY_ACCESS_TOKEN is not required.
543#
544# Requires curl and sed executables to be installed in the standard PATH.
545#
546# References
547#   https://docs.ntfy.sh
548#
549# Arguments
550#   subject: notification subject
551#   pathname: pathname containing the notification message (OPTIONAL)
552#
553# Globals
554#   ZED_NTFY_TOPIC
555#   ZED_NTFY_ACCESS_TOKEN (OPTIONAL)
556#   ZED_NTFY_URL
557#
558# Return
559#   0: notification sent
560#   1: notification failed
561#   2: not configured
562#
563zed_notify_ntfy()
564{
565    local subject="$1"
566    local pathname="${2:-"/dev/null"}"
567    local msg_body
568    local msg_out
569    local msg_err
570
571    [ -n "${ZED_NTFY_TOPIC}" ] || return 2
572    local url="${ZED_NTFY_URL:-"https://ntfy.sh"}/${ZED_NTFY_TOPIC}"
573
574    if [ ! -r "${pathname}" ]; then
575        zed_log_err "ntfy cannot read \"${pathname}\""
576        return 1
577    fi
578
579    zed_check_cmd "curl" "sed" || return 1
580
581    # Read the message body in.
582    #
583    msg_body="$(cat "${pathname}")"
584
585    if [ -z "${msg_body}" ]
586    then
587        msg_body=$subject
588        subject=""
589    fi
590
591    # Send the POST request and check for errors.
592    #
593    if [ -n "${ZED_NTFY_ACCESS_TOKEN}" ]; then
594        msg_out="$( \
595        curl \
596        -u ":${ZED_NTFY_ACCESS_TOKEN}" \
597        -H "Title: ${subject}" \
598        -d "${msg_body}" \
599        -H "Priority: high" \
600        "${url}" \
601        2>/dev/null \
602        )"; rv=$?
603    else
604        msg_out="$( \
605        curl \
606        -H "Title: ${subject}" \
607        -d "${msg_body}" \
608        -H "Priority: high" \
609        "${url}" \
610        2>/dev/null \
611        )"; rv=$?
612    fi
613    if [ "${rv}" -ne 0 ]; then
614        zed_log_err "curl exit=${rv}"
615        return 1
616    fi
617    msg_err="$(echo "${msg_out}" \
618        | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')"
619    if [ -n "${msg_err}" ]; then
620        zed_log_err "ntfy \"${msg_err}"\"
621        return 1
622    fi
623    return 0
624}
625
626
627
628# zed_rate_limit (tag, [interval])
629#
630# Check whether an event of a given type [tag] has already occurred within the
631# last [interval] seconds.
632#
633# This function obtains a lock on the statefile using file descriptor 9.
634#
635# Arguments
636#   tag: arbitrary string for grouping related events to rate-limit
637#   interval: time interval in seconds (OPTIONAL)
638#
639# Globals
640#   ZED_NOTIFY_INTERVAL_SECS
641#   ZED_RUNDIR
642#
643# Return
644#   0 if the event should be processed
645#   1 if the event should be dropped
646#
647# State File Format
648#   time;tag
649#
650zed_rate_limit()
651{
652    local tag="$1"
653    local interval="${2:-${ZED_NOTIFY_INTERVAL_SECS}}"
654    local lockfile="zed.zedlet.state.lock"
655    local lockfile_fd=9
656    local statefile="${ZED_RUNDIR}/zed.zedlet.state"
657    local time_now
658    local time_prev
659    local umask_bak
660    local rv=0
661
662    [ -n "${tag}" ] || return 0
663
664    zed_lock "${lockfile}" "${lockfile_fd}"
665    time_now="$(date +%s)"
666    time_prev="$(grep -E "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
667        | tail -1 | cut -d\; -f1)"
668
669    if [ -n "${time_prev}" ] \
670            && [ "$((time_now - time_prev))" -lt "${interval}" ]; then
671        rv=1
672    else
673        umask_bak="$(umask)"
674        umask 077
675        grep -E -v "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
676            > "${statefile}.$$"
677        echo "${time_now};${tag}" >> "${statefile}.$$"
678        mv -f "${statefile}.$$" "${statefile}"
679        umask "${umask_bak}"
680    fi
681
682    zed_unlock "${lockfile}" "${lockfile_fd}"
683    return "${rv}"
684}
685
686
687# zed_guid_to_pool (guid)
688#
689# Convert a pool GUID into its pool name (like "tank")
690# Arguments
691#   guid: pool GUID (decimal or hex)
692#
693# Return
694#   Pool name
695#
696zed_guid_to_pool()
697{
698	if [ -z "$1" ] ; then
699		return
700	fi
701
702	guid="$(printf "%u" "$1")"
703	$ZPOOL get -H -ovalue,name guid | awk '$1 == '"$guid"' {print $2; exit}'
704}
705
706# zed_exit_if_ignoring_this_event
707#
708# Exit the script if we should ignore this event, as determined by
709# $ZED_SYSLOG_SUBCLASS_INCLUDE and $ZED_SYSLOG_SUBCLASS_EXCLUDE in zed.rc.
710# This function assumes you've imported the normal zed variables.
711zed_exit_if_ignoring_this_event()
712{
713	if [ -n "${ZED_SYSLOG_SUBCLASS_INCLUDE}" ]; then
714	    eval "case ${ZEVENT_SUBCLASS} in
715	    ${ZED_SYSLOG_SUBCLASS_INCLUDE});;
716	    *) exit 0;;
717	    esac"
718	elif [ -n "${ZED_SYSLOG_SUBCLASS_EXCLUDE}" ]; then
719	    eval "case ${ZEVENT_SUBCLASS} in
720	    ${ZED_SYSLOG_SUBCLASS_EXCLUDE}) exit 0;;
721	    *);;
722	    esac"
723	fi
724}
725