1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
# GNU Shepherd --- Test the #:pid-file option of 'make-forkexec-constructor'.
# Copyright © 2016, 2019-2020, 2022-2025 Ludovic Courtès <ludo@gnu.org>
#
# This file is part of the GNU Shepherd.
#
# The GNU Shepherd is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at
# your option) any later version.
#
# The GNU Shepherd is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with the GNU Shepherd. If not, see <https://www.gnu.org/licenses/>.
shepherd --version
herd --version
socket="t-socket-$$"
conf="t-conf-$$"
log="t-log-$$"
pid="t-pid-$$"
stamp="t-stamp-$$"
service_pid="t-service-pid-$$"
herd="herd -s $socket"
trap "cat $log || true; rm -f $socket $conf $service_pid $stamp $log;
test -f $pid && kill \`cat $pid\` || true; rm -f $pid" EXIT
cat > "$conf"<<EOF
(use-modules (ice-9 match))
(define %command
'("$SHELL" "-c" "echo \$\$ > $PWD/$service_pid ; exec sleep 600"))
(define %daemon-command
;; Emulate a daemon by forking and exiting right away.
(quasiquote ("$GUILE" "-c"
,(object->string '(when (zero? (primitive-fork))
(format #t "daemon running as PID ~a~%"
(getpid))
(force-output)
(call-with-output-file "$PWD/$service_pid"
(lambda (port)
(display (getpid) port)))
(sleep 600))))))
(define %daemon-command-successful
;; Purposefully introduce a delay between the time the PID file
;; is created and the time it actually contains a valid PID. This
;; simulates PID files not created atomically, as is the case with
;; wpa_supplicant 2.7 for instance.
(quasiquote ("$GUILE" "-c"
,(object->string '(when (zero? (primitive-fork))
(call-with-output-file "$PWD/$service_pid"
(lambda (port)
(usleep 1500000)
(display (getpid) port)))
(sleep 600))))))
(define nonexistent-pid-file
;; Nonexistent PID file in a writable directory, so unlink(2) fails
;; with ENOENT rather than EROFS as is the case in a container with
;; a read-only root file system.
"$PWD/$$/does-not-exist")
(register-services
(list
(service
;; A service that never produces its PID file, yet leaves a process
;; behind it.
'(test)
#:start (make-forkexec-constructor %command
#:pid-file nonexistent-pid-file
;; Low-end ARMv7 machines are
;; slow enough that creating
;; $service_pid could take
;; up to 4 seconds or so.
#:pid-file-timeout 6)
#:stop (make-kill-destructor)
#:respawn? #f)
(service
;; Same one, but actually produces the PID file.
'(test-works)
#:start (make-forkexec-constructor %daemon-command-successful
#:pid-file "$PWD/$service_pid"
#:pid-file-timeout 6)
#:stop (make-kill-destructor)
#:respawn? #f)
(service
;; This one "daemonizes", fails to create a PID file, but leaves
;; a child process behind it.
'(test-daemonizes)
#:start (make-forkexec-constructor %daemon-command
#:pid-file nonexistent-pid-file
#:pid-file-timeout 6)
#:stop (make-kill-destructor)
#:respawn? #f)))
;; Start it upfront. This ensures the whole machinery works even
;; when called in a non-suspendable context (continuation barrier).
(start-service (lookup-service 'test-works))
;; Note that the config file has been evaluated.
(call-with-output-file "$PWD/$stamp" (const #t))
EOF
rm -f "$pid"
shepherd -I -s "$socket" -c "$conf" -l "$log" --pid="$pid" &
# Wait till it's ready.
while ! test -f "$pid" ; do sleep 0.3 ; done
shepherd_pid="`cat $pid`"
# The config file is evaluated asynchronously, so wait until it's been loaded.
until test -f "$stamp" ; do sleep 0.3 ; done
# This service should already be running.
$herd status test-works | grep running
test -f "$service_pid"
kill -0 `cat "$service_pid"`
$herd stop test-works
rm "$service_pid"
# The service is expected to fail to start.
if $herd start test
then false; else true; fi
# Make sure it is marked as stopped.
$herd status test | grep stopped
test -f "$service_pid"
# Make sure it did not leave a process behind it.
if kill -0 `cat "$service_pid"`
then false; else true; fi
# Now a service that "daemonizes" but fails to start.
rm -f "$service_pid"
if $herd start test-daemonizes
then false; else true; fi
$herd status test-daemonizes | grep stopped
# Make sure it did not leave its child process behind it.
test -f "$service_pid"
if kill -0 `cat "$service_pid"`
then false; else true; fi
# Now start the service that works.
$herd start test-works
$herd status test-works | grep running
test -f "$service_pid"
kill -0 "`cat $service_pid`"
known_pid="`$herd status test-works | grep PID \
| sed -es'/.*PID: \([0-9]\+\)$/\1/g'`"
test `cat $service_pid` -eq $known_pid
$herd stop test-works
if kill -0 `cat "$service_pid"`
then false; else true; fi
|