03b0336a692329ebd3e6ccc5e85f473c2d39a746
[openwrt/openwrt.git] / package / system / procd / files / procd.sh
1 # procd API:
2 #
3 # procd_open_service(name, [script]):
4 # Initialize a new procd command message containing a service with one or more instances
5 #
6 # procd_close_service()
7 # Send the command message for the service
8 #
9 # procd_open_instance([name]):
10 # Add an instance to the service described by the previous procd_open_service call
11 #
12 # procd_set_param(type, [value...])
13 # Available types:
14 # command: command line (array).
15 # respawn info: array with 3 values $fail_threshold $restart_timeout $max_fail
16 # env: environment variable (passed to the process)
17 # data: arbitrary name/value pairs for detecting config changes (table)
18 # file: configuration files (array)
19 # netdev: bound network device (detects ifindex changes)
20 # limits: resource limits (passed to the process)
21 # user: $username to run service as
22 # group: $groupname to run service as
23 # pidfile: file name to write pid into
24 # stdout: boolean whether to redirect commands stdout to syslog (default: 0)
25 # stderr: boolean whether to redirect commands stderr to syslog (default: 0)
26 # facility: syslog facility used when logging to syslog (default: daemon)
27 #
28 # No space separation is done for arrays/tables - use one function argument per command line argument
29 #
30 # procd_close_instance():
31 # Complete the instance being prepared
32 #
33 # procd_running(service, [instance]):
34 # Checks if service/instance is currently running
35 #
36 # procd_kill(service, [instance]):
37 # Kill a service instance (or all instances)
38 #
39 # procd_send_signal(service, [instance], [signal])
40 # Send a signal to a service instance (or all instances)
41 #
42
43 . "$IPKG_INSTROOT/usr/share/libubox/jshn.sh"
44
45 PROCD_RELOAD_DELAY=1000
46 _PROCD_SERVICE=
47
48 procd_lock() {
49 local basescript=$(readlink "$initscript")
50 local service_name="$(basename ${basescript:-$initscript})"
51
52 flock -n 1000 &> /dev/null
53 if [ "$?" != "0" ]; then
54 exec 1000>"$IPKG_INSTROOT/var/lock/procd_${service_name}.lock"
55 flock 1000
56 if [ "$?" != "0" ]; then
57 logger "warning: procd flock for $service_name failed"
58 fi
59 fi
60 }
61
62 _procd_call() {
63 local old_cb
64
65 json_set_namespace procd old_cb
66 "$@"
67 json_set_namespace $old_cb
68 }
69
70 _procd_wrapper() {
71 procd_lock
72 while [ -n "$1" ]; do
73 eval "$1() { _procd_call _$1 \"\$@\"; }"
74 shift
75 done
76 }
77
78 _procd_ubus_call() {
79 local cmd="$1"
80
81 [ -n "$PROCD_DEBUG" ] && json_dump >&2
82 ubus call service "$cmd" "$(json_dump)"
83 json_cleanup
84 }
85
86 _procd_open_service() {
87 local name="$1"
88 local script="$2"
89
90 _PROCD_SERVICE="$name"
91 _PROCD_INSTANCE_SEQ=0
92
93 json_init
94 json_add_string name "$name"
95 [ -n "$script" ] && json_add_string script "$script"
96 json_add_object instances
97 }
98
99 _procd_close_service() {
100 json_close_object
101 _procd_open_trigger
102 service_triggers
103 _procd_close_trigger
104 type service_data >/dev/null 2>&1 && {
105 _procd_open_data
106 service_data
107 _procd_close_data
108 }
109 _procd_ubus_call ${1:-set}
110 }
111
112 _procd_add_array_data() {
113 while [ "$#" -gt 0 ]; do
114 json_add_string "" "$1"
115 shift
116 done
117 }
118
119 _procd_add_array() {
120 json_add_array "$1"
121 shift
122 _procd_add_array_data "$@"
123 json_close_array
124 }
125
126 _procd_add_table_data() {
127 while [ -n "$1" ]; do
128 local var="${1%%=*}"
129 local val="${1#*=}"
130 [ "$1" = "$val" ] && val=
131 json_add_string "$var" "$val"
132 shift
133 done
134 }
135
136 _procd_add_table() {
137 json_add_object "$1"
138 shift
139 _procd_add_table_data "$@"
140 json_close_object
141 }
142
143 _procd_open_instance() {
144 local name="$1"; shift
145
146 _PROCD_INSTANCE_SEQ="$(($_PROCD_INSTANCE_SEQ + 1))"
147 name="${name:-instance$_PROCD_INSTANCE_SEQ}"
148 json_add_object "$name"
149 [ -n "$TRACE_SYSCALLS" ] && json_add_boolean trace "1"
150 }
151
152 _procd_open_trigger() {
153 let '_procd_trigger_open = _procd_trigger_open + 1'
154 [ "$_procd_trigger_open" -gt 1 ] && return
155 json_add_array "triggers"
156 }
157
158 _procd_close_trigger() {
159 let '_procd_trigger_open = _procd_trigger_open - 1'
160 [ "$_procd_trigger_open" -lt 1 ] || return
161 json_close_array
162 }
163
164 _procd_open_data() {
165 let '_procd_data_open = _procd_data_open + 1'
166 [ "$_procd_data_open" -gt 1 ] && return
167 json_add_object "data"
168 }
169
170 _procd_close_data() {
171 let '_procd_data_open = _procd_data_open - 1'
172 [ "$_procd_data_open" -lt 1 ] || return
173 json_close_object
174 }
175
176 _procd_open_validate() {
177 json_select ..
178 json_add_array "validate"
179 }
180
181 _procd_close_validate() {
182 json_close_array
183 json_select triggers
184 }
185
186 _procd_add_jail() {
187 json_add_object "jail"
188 json_add_string name "$1"
189
190 shift
191
192 for a in $@; do
193 case $a in
194 log) json_add_boolean "log" "1";;
195 ubus) json_add_boolean "ubus" "1";;
196 udebug) json_add_boolean "udebug" "1";;
197 procfs) json_add_boolean "procfs" "1";;
198 sysfs) json_add_boolean "sysfs" "1";;
199 ronly) json_add_boolean "ronly" "1";;
200 requirejail) json_add_boolean "requirejail" "1";;
201 netns) json_add_boolean "netns" "1";;
202 userns) json_add_boolean "userns" "1";;
203 cgroupsns) json_add_boolean "cgroupsns" "1";;
204 esac
205 done
206 json_add_object "mount"
207 json_close_object
208 json_close_object
209 }
210
211 _procd_add_jail_mount() {
212 local _json_no_warning=1
213
214 json_select "jail"
215 [ $? = 0 ] || return
216 json_select "mount"
217 [ $? = 0 ] || {
218 json_select ..
219 return
220 }
221 for a in $@; do
222 json_add_string "$a" "0"
223 done
224 json_select ..
225 json_select ..
226 }
227
228 _procd_add_jail_mount_rw() {
229 local _json_no_warning=1
230
231 json_select "jail"
232 [ $? = 0 ] || return
233 json_select "mount"
234 [ $? = 0 ] || {
235 json_select ..
236 return
237 }
238 for a in $@; do
239 json_add_string "$a" "1"
240 done
241 json_select ..
242 json_select ..
243 }
244
245 _procd_set_param() {
246 local type="$1"; shift
247
248 case "$type" in
249 env|data|limits)
250 _procd_add_table "$type" "$@"
251 ;;
252 command|netdev|file|respawn|watch|watchdog)
253 _procd_add_array "$type" "$@"
254 ;;
255 error)
256 json_add_array "$type"
257 json_add_string "" "$@"
258 json_close_array
259 ;;
260 nice|term_timeout)
261 json_add_int "$type" "$1"
262 ;;
263 reload_signal)
264 json_add_int "$type" $(kill -l "$1")
265 ;;
266 pidfile|user|group|seccomp|capabilities|facility|\
267 extroot|overlaydir|tmpoverlaysize)
268 json_add_string "$type" "$1"
269 ;;
270 stdout|stderr|no_new_privs)
271 json_add_boolean "$type" "$1"
272 ;;
273 esac
274 }
275
276 _procd_add_timeout() {
277 [ "$PROCD_RELOAD_DELAY" -gt 0 ] && json_add_int "" "$PROCD_RELOAD_DELAY"
278 return 0
279 }
280
281 _procd_add_interface_trigger() {
282 json_add_array
283 _procd_add_array_data "$1"
284 shift
285
286 json_add_array
287 _procd_add_array_data "if"
288
289 json_add_array
290 _procd_add_array_data "eq" "interface" "$1"
291 shift
292 json_close_array
293
294 json_add_array
295 _procd_add_array_data "run_script" "$@"
296 json_close_array
297
298 json_close_array
299 _procd_add_timeout
300 json_close_array
301 }
302
303 _procd_add_reload_interface_trigger() {
304 local script=$(readlink "$initscript")
305 local name=$(basename ${script:-$initscript})
306
307 _procd_open_trigger
308 _procd_add_interface_trigger "interface.*" $1 /etc/init.d/$name reload
309 _procd_close_trigger
310 }
311
312 _procd_add_data_trigger() {
313 json_add_array
314 _procd_add_array_data "service.data.update"
315
316 json_add_array
317 _procd_add_array_data "if"
318
319 json_add_array
320 _procd_add_array_data "eq" "name" "$1"
321 shift
322 json_close_array
323
324 json_add_array
325 _procd_add_array_data "run_script" "$@"
326 json_close_array
327
328 json_close_array
329 _procd_add_timeout
330 json_close_array
331 }
332
333 _procd_add_reload_data_trigger() {
334 local script=$(readlink "$initscript")
335 local name=$(basename ${script:-$initscript})
336
337 _procd_open_trigger
338 _procd_add_data_trigger $1 /etc/init.d/$name reload
339 _procd_close_trigger
340 }
341
342 _procd_add_config_trigger() {
343 json_add_array
344 _procd_add_array_data "$1"
345 shift
346
347 json_add_array
348 _procd_add_array_data "if"
349
350 json_add_array
351 _procd_add_array_data "eq" "package" "$1"
352 shift
353 json_close_array
354
355 json_add_array
356 _procd_add_array_data "run_script" "$@"
357 json_close_array
358
359 json_close_array
360 _procd_add_timeout
361 json_close_array
362 }
363
364 _procd_add_mount_trigger() {
365 json_add_array
366 _procd_add_array_data "$1"
367 local action="$2"
368 local multi=0
369 shift ; shift
370
371 json_add_array
372 _procd_add_array_data "if"
373
374 if [ "$2" ]; then
375 json_add_array
376 _procd_add_array_data "or"
377 multi=1
378 fi
379
380 while [ "$1" ]; do
381 json_add_array
382 _procd_add_array_data "eq" "target" "$1"
383 shift
384 json_close_array
385 done
386
387 [ $multi = 1 ] && json_close_array
388
389 json_add_array
390 _procd_add_array_data "run_script" /etc/init.d/$name $action
391 json_close_array
392
393 json_close_array
394 _procd_add_timeout
395 json_close_array
396 }
397
398 _procd_add_action_mount_trigger() {
399 local action="$1"
400 shift
401 local mountpoints="$(procd_get_mountpoints "$@")"
402 [ "${mountpoints//[[:space:]]}" ] || return 0
403 local script=$(readlink "$initscript")
404 local name=$(basename ${script:-$initscript})
405
406 _procd_open_trigger
407 _procd_add_mount_trigger mount.add $action "$mountpoints"
408 _procd_close_trigger
409 }
410
411 procd_get_mountpoints() {
412 (
413 __procd_check_mount() {
414 local cfg="$1"
415 local path="${2%%/}/"
416 local target
417 config_get target "$cfg" target
418 target="${target%%/}/"
419 [ "$path" != "${path##$target}" ] && echo "${target%%/}"
420 }
421 local mpath
422 config_load fstab
423 for mpath in "$@"; do
424 config_foreach __procd_check_mount mount "$mpath"
425 done
426 ) | sort -u
427 }
428
429 _procd_add_restart_mount_trigger() {
430 _procd_add_action_mount_trigger restart "$@"
431 }
432
433 _procd_add_reload_mount_trigger() {
434 _procd_add_action_mount_trigger reload "$@"
435 }
436
437 _procd_add_raw_trigger() {
438 json_add_array
439 _procd_add_array_data "$1"
440 shift
441 local timeout=$1
442 shift
443
444 json_add_array
445 json_add_array
446 _procd_add_array_data "run_script" "$@"
447 json_close_array
448 json_close_array
449
450 json_add_int "" "$timeout"
451
452 json_close_array
453 }
454
455 _procd_add_reload_trigger() {
456 local script=$(readlink "$initscript")
457 local name=$(basename ${script:-$initscript})
458 local file
459
460 _procd_open_trigger
461 for file in "$@"; do
462 _procd_add_config_trigger "config.change" "$file" /etc/init.d/$name reload
463 done
464 _procd_close_trigger
465 }
466
467 _procd_add_validation() {
468 _procd_open_validate
469 $@
470 _procd_close_validate
471 }
472
473 _procd_append_param() {
474 local type="$1"; shift
475 local _json_no_warning=1
476
477 json_select "$type"
478 [ $? = 0 ] || {
479 _procd_set_param "$type" "$@"
480 return
481 }
482 case "$type" in
483 env|data|limits)
484 _procd_add_table_data "$@"
485 ;;
486 command|netdev|file|respawn|watch|watchdog)
487 _procd_add_array_data "$@"
488 ;;
489 error)
490 json_add_string "" "$@"
491 ;;
492 esac
493 json_select ..
494 }
495
496 _procd_close_instance() {
497 local respawn_vals
498 _json_no_warning=1
499 if json_select respawn ; then
500 json_get_values respawn_vals
501 if [ -z "$respawn_vals" ]; then
502 local respawn_threshold=$(uci_get system.@service[0].respawn_threshold)
503 local respawn_timeout=$(uci_get system.@service[0].respawn_timeout)
504 local respawn_retry=$(uci_get system.@service[0].respawn_retry)
505 _procd_add_array_data ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}
506 fi
507 json_select ..
508 fi
509
510 json_close_object
511 }
512
513 _procd_add_instance() {
514 _procd_open_instance
515 _procd_set_param command "$@"
516 _procd_close_instance
517 }
518
519 procd_running() {
520 local service="$1"
521 local instance="${2:-*}"
522 [ "$instance" = "*" ] || instance="'$instance'"
523
524 json_init
525 json_add_string name "$service"
526 local running=$(_procd_ubus_call list | jsonfilter -l 1 -e "@['$service'].instances[$instance].running")
527
528 [ "$running" = "true" ]
529 }
530
531 _procd_kill() {
532 local service="$1"
533 local instance="$2"
534
535 json_init
536 [ -n "$service" ] && json_add_string name "$service"
537 [ -n "$instance" ] && json_add_string instance "$instance"
538 _procd_ubus_call delete
539 }
540
541 _procd_send_signal() {
542 local service="$1"
543 local instance="$2"
544 local signal="$3"
545
546 case "$signal" in
547 [A-Z]*) signal="$(kill -l "$signal" 2>/dev/null)" || return 1;;
548 esac
549
550 json_init
551 json_add_string name "$service"
552 [ -n "$instance" -a "$instance" != "*" ] && json_add_string instance "$instance"
553 [ -n "$signal" ] && json_add_int signal "$signal"
554 _procd_ubus_call signal
555 }
556
557 _procd_status() {
558 local service="$1"
559 local instance="$2"
560 local data state
561 local n_running=0
562 local n_stopped=0
563 local n_total=0
564
565 json_init
566 [ -n "$service" ] && json_add_string name "$service"
567
568 data=$(_procd_ubus_call list | jsonfilter -e '@["'"$service"'"]')
569 [ -z "$data" ] && { echo "inactive"; return 3; }
570
571 data=$(echo "$data" | jsonfilter -e '$.instances')
572 if [ -z "$data" ]; then
573 [ -z "$instance" ] && { echo "active with no instances"; return 0; }
574 data="[]"
575 fi
576
577 [ -n "$instance" ] && instance="\"$instance\"" || instance='*'
578
579 for state in $(jsonfilter -s "$data" -e '$['"$instance"'].running'); do
580 n_total=$((n_total + 1))
581 case "$state" in
582 false) n_stopped=$((n_stopped + 1)) ;;
583 true) n_running=$((n_running + 1)) ;;
584 esac
585 done
586
587 if [ $n_total -gt 0 ]; then
588 if [ $n_running -gt 0 ] && [ $n_stopped -eq 0 ]; then
589 echo "running"
590 return 0
591 elif [ $n_running -gt 0 ]; then
592 echo "running ($n_running/$n_total)"
593 return 0
594 else
595 echo "not running"
596 return 5
597 fi
598 else
599 echo "unknown instance $instance"
600 return 4
601 fi
602 }
603
604 procd_open_data() {
605 local name="$1"
606 json_set_namespace procd __procd_old_cb
607 json_add_object data
608 }
609
610 procd_close_data() {
611 json_close_object
612 json_set_namespace $__procd_old_cb
613 }
614
615 _procd_set_config_changed() {
616 local package="$1"
617
618 json_init
619 json_add_string type config.change
620 json_add_object data
621 json_add_string package "$package"
622 json_close_object
623
624 ubus call service event "$(json_dump)"
625 }
626
627 procd_add_mdns_service() {
628 local service proto port txt_count=0
629 service=$1; shift
630 proto=$1; shift
631 port=$1; shift
632 json_add_object "${service}_$port"
633 json_add_string "service" "_$service._$proto.local"
634 json_add_int port "$port"
635 for txt in "$@"; do
636 [ -z "$txt" ] && continue
637 txt_count=$((txt_count+1))
638 [ $txt_count -eq 1 ] && json_add_array txt
639 json_add_string "" "$txt"
640 done
641 [ $txt_count -gt 0 ] && json_select ..
642
643 json_select ..
644 }
645
646 procd_add_mdns() {
647 procd_open_data
648 json_add_object "mdns"
649 procd_add_mdns_service "$@"
650 json_close_object
651 procd_close_data
652 }
653
654 uci_validate_section()
655 {
656 local _package="$1"
657 local _type="$2"
658 local _name="$3"
659 local _result
660 local _error
661 shift; shift; shift
662 _result=$(/sbin/validate_data "$_package" "$_type" "$_name" "$@" 2> /dev/null)
663 _error=$?
664 eval "$_result"
665 [ "$_error" = "0" ] || $(/sbin/validate_data "$_package" "$_type" "$_name" "$@" 1> /dev/null)
666 return $_error
667 }
668
669 uci_load_validate() {
670 local _package="$1"
671 local _type="$2"
672 local _name="$3"
673 local _function="$4"
674 local _option
675 local _result
676 shift; shift; shift; shift
677 for _option in "$@"; do
678 eval "local ${_option%%:*}"
679 done
680 uci_validate_section "$_package" "$_type" "$_name" "$@"
681 _result=$?
682 [ -n "$_function" ] || return $_result
683 eval "$_function \"\$_name\" \"\$_result\""
684 }
685
686 _procd_wrapper \
687 procd_open_service \
688 procd_close_service \
689 procd_add_instance \
690 procd_add_raw_trigger \
691 procd_add_config_trigger \
692 procd_add_interface_trigger \
693 procd_add_mount_trigger \
694 procd_add_reload_trigger \
695 procd_add_reload_data_trigger \
696 procd_add_reload_interface_trigger \
697 procd_add_action_mount_trigger \
698 procd_add_reload_mount_trigger \
699 procd_add_restart_mount_trigger \
700 procd_open_trigger \
701 procd_close_trigger \
702 procd_open_instance \
703 procd_close_instance \
704 procd_open_validate \
705 procd_close_validate \
706 procd_add_jail \
707 procd_add_jail_mount \
708 procd_add_jail_mount_rw \
709 procd_set_param \
710 procd_append_param \
711 procd_add_validation \
712 procd_set_config_changed \
713 procd_kill \
714 procd_send_signal