]> git.proxmox.com Git - mirror_zfs.git/blame - cmd/zed/zed.d/zed-functions.sh
Update arc_memory_throttle() to check pageout
[mirror_zfs.git] / cmd / zed / zed.d / zed-functions.sh
CommitLineData
aded9a68
CD
1# zed-functions.sh
2#
3# ZED helper functions for use in ZEDLETs
4
5
6# Variable Defaults
7#
aded9a68 8: "${ZED_LOCKDIR:="/var/lock"}"
20967ff1
CD
9: "${ZED_NOTIFY_INTERVAL_SECS:=3600}"
10: "${ZED_NOTIFY_VERBOSE:=0}"
aded9a68
CD
11: "${ZED_RUNDIR:="/var/run"}"
12: "${ZED_SYSLOG_PRIORITY:="daemon.notice"}"
13: "${ZED_SYSLOG_TAG:="zed"}"
14
15ZED_FLOCK_FD=8
16
17
18# zed_check_cmd (cmd, ...)
19#
20# For each argument given, search PATH for the executable command [cmd].
21# Log a message if [cmd] is not found.
22#
23# Arguments
24# cmd: name of executable command for which to search
25#
26# Return
27# 0 if all commands are found in PATH and are executable
28# n for a count of the command executables that are not found
29#
30zed_check_cmd()
31{
32 local cmd
33 local rv=0
34
35 for cmd; do
36 if ! command -v "${cmd}" >/dev/null 2>&1; then
37 zed_log_err "\"${cmd}\" not installed"
38 rv=$((rv + 1))
39 fi
40 done
41 return "${rv}"
42}
43
44
45# zed_log_msg (msg, ...)
46#
47# Write all argument strings to the system log.
48#
49# Globals
50# ZED_SYSLOG_PRIORITY
51# ZED_SYSLOG_TAG
52#
53# Return
54# nothing
55#
56zed_log_msg()
57{
58 logger -p "${ZED_SYSLOG_PRIORITY}" -t "${ZED_SYSLOG_TAG}" -- "$@"
59}
60
61
62# zed_log_err (msg, ...)
63#
64# Write an error message to the system log. This message will contain the
65# script name, EID, and all argument strings.
66#
67# Globals
68# ZED_SYSLOG_PRIORITY
69# ZED_SYSLOG_TAG
70# ZEVENT_EID
71#
72# Return
73# nothing
74#
75zed_log_err()
76{
77 logger -p "${ZED_SYSLOG_PRIORITY}" -t "${ZED_SYSLOG_TAG}" -- "error:" \
78 "$(basename -- "$0"):" "${ZEVENT_EID:+"eid=${ZEVENT_EID}:"}" "$@"
79}
80
81
82# zed_lock (lockfile, [fd])
83#
84# Obtain an exclusive (write) lock on [lockfile]. If the lock cannot be
85# immediately acquired, wait until it becomes available.
86#
87# Every zed_lock() must be paired with a corresponding zed_unlock().
88#
89# By default, flock-style locks associate the lockfile with file descriptor 8.
90# The bash manpage warns that file descriptors >9 should be used with care as
91# they may conflict with file descriptors used internally by the shell. File
92# descriptor 9 is reserved for zed_rate_limit(). If concurrent locks are held
93# within the same process, they must use different file descriptors (preferably
94# decrementing from 8); otherwise, obtaining a new lock with a given file
95# descriptor will release the previous lock associated with that descriptor.
96#
97# Arguments
98# lockfile: pathname of the lock file; the lock will be stored in
99# ZED_LOCKDIR unless the pathname contains a "/".
100# fd: integer for the file descriptor used by flock (OPTIONAL unless holding
101# concurrent locks)
102#
103# Globals
104# ZED_FLOCK_FD
105# ZED_LOCKDIR
106#
107# Return
108# nothing
109#
110zed_lock()
111{
112 local lockfile="$1"
113 local fd="${2:-${ZED_FLOCK_FD}}"
114 local umask_bak
115 local err
116
117 [ -n "${lockfile}" ] || return
118 if ! expr "${lockfile}" : '.*/' >/dev/null 2>&1; then
119 lockfile="${ZED_LOCKDIR}/${lockfile}"
120 fi
121
122 umask_bak="$(umask)"
123 umask 077
124
125 # Obtain a lock on the file bound to the given file descriptor.
126 #
127 eval "exec ${fd}> '${lockfile}'"
128 err="$(flock --exclusive "${fd}" 2>&1)"
129 if [ $? -ne 0 ]; 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 #
166 err="$(flock --unlock "${fd}" 2>&1)"
167 if [ $? -ne 0 ]; then
168 zed_log_err "failed to unlock \"${lockfile}\": ${err}"
169 fi
170 eval "exec ${fd}>&-"
171}
172
173
20967ff1
CD
174# zed_notify (subject, pathname)
175#
176# Send a notification via all available methods.
177#
178# Arguments
179# subject: notification subject
180# pathname: pathname containing the notification message (OPTIONAL)
181#
182# Return
183# 0: notification succeeded via at least one method
184# 1: notification failed
185# 2: no notification methods configured
186#
187zed_notify()
188{
189 local subject="$1"
190 local pathname="$2"
191 local num_success=0
192 local num_failure=0
193
194 zed_notify_email "${subject}" "${pathname}"; rv=$?
195 [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
196 [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
197
a0d065fa
CD
198 zed_notify_pushbullet "${subject}" "${pathname}"; rv=$?
199 [ "${rv}" -eq 0 ] && num_success=$((num_success + 1))
200 [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1))
201
20967ff1
CD
202 [ "${num_success}" -gt 0 ] && return 0
203 [ "${num_failure}" -gt 0 ] && return 1
204 return 2
205}
206
207
208# zed_notify_email (subject, pathname)
209#
210# Send a notification via email to the address specified by ZED_EMAIL.
211#
212# Requires the mail executable to be installed in the standard PATH.
213#
214# Arguments
215# subject: notification subject
216# pathname: pathname containing the notification message (OPTIONAL)
217#
218# Globals
219# ZED_EMAIL
220#
221# Return
222# 0: notification sent
223# 1: notification failed
224# 2: not configured
225#
226zed_notify_email()
227{
228 local subject="$1"
229 local pathname="${2:-"/dev/null"}"
230
231 [ -n "${ZED_EMAIL}" ] || return 2
232
233 [ -n "${subject}" ] || return 1
234 if [ ! -r "${pathname}" ]; then
235 zed_log_err "mail cannot read \"${pathname}\""
236 return 1
237 fi
238
239 zed_check_cmd "mail" || return 1
240
241 mail -s "${subject}" "${ZED_EMAIL}" < "${pathname}" >/dev/null 2>&1; rv=$?
242 if [ "${rv}" -ne 0 ]; then
243 zed_log_err "mail exit=${rv}"
244 return 1
245 fi
246 return 0
247}
248
249
a0d065fa
CD
250# zed_notify_pushbullet (subject, pathname)
251#
252# Send a notification via Pushbullet <https://www.pushbullet.com/>.
253# The access token (ZED_PUSHBULLET_ACCESS_TOKEN) identifies this client to the
254# Pushbullet server. The optional channel tag (ZED_PUSHBULLET_CHANNEL_TAG) is
255# for pushing to notification feeds that can be subscribed to; if a channel is
256# not defined, push notifications will instead be sent to all devices
257# associated with the account specified by the access token.
258#
259# Requires awk, curl, and sed executables to be installed in the standard PATH.
260#
261# References
262# https://docs.pushbullet.com/
263# https://www.pushbullet.com/security
264#
265# Arguments
266# subject: notification subject
267# pathname: pathname containing the notification message (OPTIONAL)
268#
269# Globals
270# ZED_PUSHBULLET_ACCESS_TOKEN
271# ZED_PUSHBULLET_CHANNEL_TAG
272#
273# Return
274# 0: notification sent
275# 1: notification failed
276# 2: not configured
277#
278zed_notify_pushbullet()
279{
280 local subject="$1"
281 local pathname="${2:-"/dev/null"}"
282 local msg_body
283 local msg_tag
284 local msg_json
285 local msg_out
286 local msg_err
287 local url="https://api.pushbullet.com/v2/pushes"
288
289 [ -n "${ZED_PUSHBULLET_ACCESS_TOKEN}" ] || return 2
290
291 [ -n "${subject}" ] || return 1
292 if [ ! -r "${pathname}" ]; then
293 zed_log_err "pushbullet cannot read \"${pathname}\""
294 return 1
295 fi
296
297 zed_check_cmd "awk" "curl" "sed" || return 1
298
299 # Escape the following characters in the message body for JSON:
300 # newline, backslash, double quote, horizontal tab, vertical tab,
301 # and carriage return.
302 #
303 msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\"");
304 gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \
305 "${pathname}")"
306
307 # Push to a channel if one is configured.
308 #
309 [ -n "${ZED_PUSHBULLET_CHANNEL_TAG}" ] && msg_tag="$(printf \
310 '"channel_tag": "%s", ' "${ZED_PUSHBULLET_CHANNEL_TAG}")"
311
312 # Construct the JSON message for pushing a note.
313 #
314 msg_json="$(printf '{%s"type": "note", "title": "%s", "body": "%s"}' \
315 "${msg_tag}" "${subject}" "${msg_body}")"
316
317 # Send the POST request and check for errors.
318 #
319 msg_out="$(curl -u "${ZED_PUSHBULLET_ACCESS_TOKEN}:" -X POST "${url}" \
320 --header "Content-Type: application/json" --data-binary "${msg_json}" \
321 2>/dev/null)"; rv=$?
322 if [ "${rv}" -ne 0 ]; then
323 zed_log_err "curl exit=${rv}"
324 return 1
325 fi
326 msg_err="$(echo "${msg_out}" \
327 | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')"
328 if [ -n "${msg_err}" ]; then
329 zed_log_err "pushbullet \"${msg_err}"\"
330 return 1
331 fi
332 return 0
333}
334
335
aded9a68
CD
336# zed_rate_limit (tag, [interval])
337#
338# Check whether an event of a given type [tag] has already occurred within the
339# last [interval] seconds.
340#
341# This function obtains a lock on the statefile using file descriptor 9.
342#
343# Arguments
344# tag: arbitrary string for grouping related events to rate-limit
345# interval: time interval in seconds (OPTIONAL)
346#
347# Globals
20967ff1 348# ZED_NOTIFY_INTERVAL_SECS
aded9a68
CD
349# ZED_RUNDIR
350#
351# Return
352# 0 if the event should be processed
353# 1 if the event should be dropped
354#
355# State File Format
356# time;tag
357#
358zed_rate_limit()
359{
360 local tag="$1"
20967ff1 361 local interval="${2:-${ZED_NOTIFY_INTERVAL_SECS}}"
aded9a68
CD
362 local lockfile="zed.zedlet.state.lock"
363 local lockfile_fd=9
364 local statefile="${ZED_RUNDIR}/zed.zedlet.state"
365 local time_now
366 local time_prev
367 local umask_bak
368 local rv=0
369
370 [ -n "${tag}" ] || return 0
371
372 zed_lock "${lockfile}" "${lockfile_fd}"
373 time_now="$(date +%s)"
374 time_prev="$(egrep "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
375 | tail -1 | cut -d\; -f1)"
376
377 if [ -n "${time_prev}" ] \
378 && [ "$((time_now - time_prev))" -lt "${interval}" ]; then
379 rv=1
380 else
381 umask_bak="$(umask)"
382 umask 077
383 egrep -v "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \
384 > "${statefile}.$$"
385 echo "${time_now};${tag}" >> "${statefile}.$$"
386 mv -f "${statefile}.$$" "${statefile}"
387 umask "${umask_bak}"
388 fi
389
390 zed_unlock "${lockfile}" "${lockfile_fd}"
391 return "${rv}"
392}