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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
|
# GNU Shepherd --- Test system logging service (syslog).
# Copyright © 2024-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-$$"
logger="$PWD/t-syslog-logger-$$.scm"
kmsg="$PWD/t-syslog-kmsg-$$"
syslog_file="$PWD/t-syslog-$$"
syslog_auth_file="$PWD/t-syslog-auth-$$"
syslog_debug_file="$PWD/t-syslog-debug-$$"
syslog_dir="$PWD/$t-syslog-dir-$$"
syslog_remote_file="$syslog_dir/t-syslog-remote-$$"
syslog_socket="$PWD/t-syslog-socket-$$"
herd="herd -s $socket"
trap "zcat $log.* || true; cat $log || true;
rm -f $socket $conf $log* $logger $kmsg $syslog_socket;
rm -f $syslog_file* $syslog_auth_file* $syslog_debug_file* $syslog_remote_file*;
rmdir $syslog_dir;
test -f $pid && kill \`cat $pid\` || true; rm -f $pid" EXIT
cat > "$conf" <<EOF
(use-modules (shepherd service system-log)
(shepherd service log-rotation)
(shepherd service timer)
(shepherd endpoints)
(srfi srfi-19))
(define (message-destination message)
(pk 'message-destination->
(cond ((and=> (system-log-message-sender message)
(lambda (address)
;; On the Hurd, 'sender' is set even for
;; AF_UNIX connections.
(not (= AF_UNIX (sockaddr:fam address)))))
(list "$syslog_remote_file"))
((= (system-log-message-facility message)
(system-log-facility authorization/private))
(list "$syslog_auth_file"))
((= (system-log-message-facility message)
(system-log-facility mail))
'()) ;too much mail: discard these messages
((= (system-log-message-priority message)
(system-log-priority debug))
(list "$syslog_debug_file" "$syslog_file"))
(else
(list "$syslog_file")))))
(define today
(time-utc->date (current-time time-utc)))
(define past-month
(if (= 1 (date-month today))
12
(- (date-month today) 1)))
(define %endpoints
(list (endpoint (make-socket-address AF_UNIX "$syslog_socket")
#:style SOCK_DGRAM)
(endpoint (make-socket-address AF_INET INADDR_LOOPBACK 9898)
#:style SOCK_DGRAM)))
(register-services
(list (system-log-service %endpoints
#:message-destination message-destination
#:kernel-log-file "$kmsg")
(log-rotation-service
;; Arrange so that it does not trigger automatically.
(calendar-event #:months (list past-month))
#:rotation-size-threshold 0)
(service
'(logger)
#:requirement '(syslogd)
#:start (make-forkexec-constructor '("$logger"))
#:stop (make-kill-destructor)
#:respawn? #f)))
EOF
cat > "$logger" <<EOF
#!$GUILE --no-auto-compile
!#
;; -*- coding: utf-8 -*-
(use-modules (rnrs bytevectors))
(display "starting logger\n")
(let ((sock (socket AF_UNIX SOCK_DGRAM 0)))
(connect sock AF_UNIX "$syslog_socket")
(set-port-encoding! sock "UTF-8")
(display "<86> Jun 29 10:45:54 sudo: pam_unix(sudo:session): session opened for user root\n" sock)
(display "<85>Jul 14 12:32:50 sudo: pam_unix(sudo:auth): authentication failure; logname= uid=1000\n" sock)
(display "<81>Jul 14 12:33:01 sudo: ludo : 3 incorrect password attempts ; TTY=pts/34\n" sock)
(display "<31>Jul 14 12:18:28 ntpd[427]: new interface(s) found: waking up resolver\n" sock)
(display "<15>Jul 14 12:18:28 utf8[42]: checking we can print a λ and a 😃\n" sock)
(display "<38>Jul 14 12:47:33 elogind[286]: Power key pressed short.\n" sock)
(display "<30>Jul 14 12:47:33 NetworkManager[319]: <info> [1720954053.6685] manager: sleep: sleep requested\n" sock)
(display "<20>Jul 18 22:22:22 exim[42]: too much mail in your inbox\n" sock))
(let ((sock (socket AF_UNIX SOCK_DGRAM 0)))
(connect sock AF_UNIX "$syslog_socket")
(set-port-encoding! sock "ISO-8859-1")
(display "<14>Feb 26 12:12:12 latin[1]: latin1 garbage: ça alors, étrange !\n" sock))
(let ((sock (socket AF_INET SOCK_DGRAM 0))
(address (make-socket-address AF_INET INADDR_LOOPBACK 9898)))
(sendto sock
(string->utf8
"<31>Aug 4 10:05:55 mtp-probe: checking bus 1, device 111: \"/sys/devices/pci0000:00/0000:00:14.0/usb1/1-8\"\n")
address))
EOF
chmod +x "$logger"
# The empty line is intentional to test that it is ignored
cat > "$kmsg" <<EOF
<6>[370383.514474] usb 1-2: USB disconnect, device number 57
EOF
file_descriptor_count ()
{
ls -l /proc/"$(cat $pid)"/fd/[0-9]* | wc -l
}
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
# Trigger startup of the finalizer thread, which creates a couple of pipes.
# That way, those extra file descriptors won't influence the comparison with
# INITIAL_FD_COUNT done at the end.
$herd eval root '(gc)'
initial_fd_count=$(file_descriptor_count)
$herd start logger
until $herd status logger | grep stopped; do sleep 0.3; done
grep "starting logger" "$log"
# Parent directories of log files should be created if needed.
test -d "$syslog_dir"
grep "sudo:.* session opened" "$syslog_auth_file"
grep "sudo:.* authentication failure" "$syslog_auth_file"
grep "3 incorrect password attempts" "$syslog_auth_file"
grep "ntpd\[427\]: new interface" "$syslog_debug_file"
grep "ntpd\[427\]: new interface" "$syslog_file" # this one in both files
grep "a λ and a 😃" "$syslog_debug_file"
grep "a λ and a 😃" "$syslog_file" # in both files too
grep "elogind\[286\]: Power key pressed short" "$syslog_file"
grep "USB disconnect, device number 57" "$syslog_file"
grep "NetworkManager\[319\]: .*sleep" "$syslog_file"
grep "mtp-probe:" "$syslog_remote_file"
grep "latin1 garbage: .*alors.*trange" "$syslog_file"
test $(wc -l < "$syslog_auth_file") -eq 3
test $(wc -l < "$syslog_debug_file") -eq 2
test $(wc -l < "$syslog_remote_file") -eq 1
test $(wc -l < "$syslog_file") -eq 6 # empty line from kmsg is ignored
for file in "$syslog_file" "$syslog_auth_file" "$syslog_debug_file" \
"$syslog_remote_file"
do
cat "$file"
done
# The 'status' command should display the socket and kernel log file.
$herd status system-log | grep "$syslog_socket"
$herd status system-log | grep "$kmsg"
for file in "$syslog_file" "$syslog_auth_file" "$syslog_debug_file" \
"$syslog_remote_file"
do
$herd status system-log | grep "Log files: .*$file"
done
# Check the "Recent messages" part of 'herd status'.
$herd status system-log
$herd status system-log -n 12 | grep "sudo: pam_unix"
$herd status system-log -n 12 | grep "USB disconnect"
$herd status system-log -n 12 | grep "mtp-probe: "
# End of file has been reached on $kmsg so it should be closed and no longer
# be polled.
until grep "Closing .*$kmsg" "$log"; do sleep 0.2; done
# Ensure logs can be rotated.
$herd start log-rotation
$herd trigger log-rotation
for file in "$syslog_file" "$syslog_auth_file" "$syslog_debug_file" \
"$syslog_remote_file"
do
$herd files log-rotation | grep "$file"
# Rotation happens asynchronously so wait for a while.
until test -f "$file.1.gz"; do sleep 0.2; done
gunzip < "$file.1.gz"
rm "$file.1.gz"
test -f "$file" # this one should have been recreated
done
$herd stop system-log
$herd eval root '(gc)'
if test -d "/proc/$$/fd" # GNU/Hurd lacks /proc/*/fd.
then
# At this point, shepherd should be back to INITIAL_FD_COUNT.
# Since the logger's own ports are closed asynchronously, when the service
# sends it the 'terminate message, retry a few times.
i=0
while test $i -lt 20
do
ls -l "/proc/$(cat $pid)/fd"
if test $(file_descriptor_count) -le $initial_fd_count
then
break
else
sleep 0.5 # wait and retry
i=$(expr $i + 1)
fi
done
test $(file_descriptor_count) -le $initial_fd_count
fi
# Remove the logs, start it again, and ensure it's working.
rm -f "$syslog_file" "$syslog_auth_file" "$syslog_debug_file"
$herd enable logger
$herd start logger
until $herd status logger | grep stopped; do sleep 0.3; done
for file in "$syslog_file" "$syslog_auth_file" "$syslog_debug_file"
do
test -f "$file"
done
$herd stop system-log
# Check that an inaccessible #:kernel-log-file does not prevent 'system-log'
# from starting.
cat > "$conf" <<EOF
(use-modules (shepherd service system-log)
(shepherd endpoints)
(srfi srfi-19))
(define %endpoints
(list (endpoint (make-socket-address AF_UNIX "$syslog_socket")
#:style SOCK_DGRAM)))
(register-services
(list (system-log-service %endpoints
#:kernel-log-file "/does/not/exist")))
EOF
$herd load root "$conf"
$herd start system-log
$herd status system-log | grep "$syslog_socket"
if $herd status system-log | grep "/does/not/exist"; then false; else true; fi
grep "Dismissing kernel log.*/does/not/exist" "$log"
$herd stop root
# Local Variables:
# coding: utf-8
# End:
|