Subject: [bug#30498] [PATCH 3/3] Use syslog for logging when running as root.

From: Ludovic Courtès <[email-masked]>
Date: Wed, 7 Mar 2018 12:04:54 +0100

* modules/shepherd/comm.scm (call-with-syslog-port, syslog-output-port):
New procedures.
* modules/shepherd.scm (main): Set 'log-output-port'
to (syslog-output-port) when running as root.
* doc/shepherd.texi (Invoking shepherd): Adjust accordingly.
---
 doc/shepherd.texi         | 18 ++++++++++---
 modules/shepherd.scm      |  2 +-
 modules/shepherd/comm.scm | 66 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 81 insertions(+), 5 deletions(-)

diff --git a/doc/shepherd.texi b/doc/shepherd.texi
index 01d8050..7946f8b 100644
--- a/doc/shepherd.texi
+++ b/doc/shepherd.texi
@@ -406,13 +406,23 @@ permissions are not as expected.
 @cindex log file
 Log output into @var{file}.
 
-The default behavior is to write to @file{/dev/kmsg} when running as
-superuser.  This special device is GNU/Linux-specific; when it does not
-exist, write to @file{/var/log/shepherd.log} instead.
-
 For unprivileged users, the default log file is
 @file{$XDG_CONFIG_HOME/shepherd/shepherd.log}.
 
+@cindex syslog
+When running as root, the default behavior is to connect to
+@file{/dev/log}, the @dfn{syslog} socket (@pxref{Overview of Syslog,,,
+libc, The GNU C Library Reference Manual}).  A syslog daemon,
+@command{syslogd}, is expected to read messages from there
+(@pxref{syslogd invocation, syslogd,, libc, GNU Inetutils}).
+
+When @file{/dev/log} is unavailable, for instance because
+@command{syslogd} is not running, as is the case during system startup
+and shutdown, @command{shepherd} falls back to the Linux kernel
+@dfn{ring buffer}, @file{/dev/kmsg}.  If @file{/dev/kmsg} is missing, as
+is the case on other operating systems, it falls back to
+@file{/dev/console}.
+
 @item --pid[=@var{file}]
 When @command{shepherd} is ready to accept connections, write its PID to @var{file} or
 to the standard output if @var{file} is omitted.
diff --git a/modules/shepherd.scm b/modules/shepherd.scm
index 39fbe14..fede338 100644
--- a/modules/shepherd.scm
+++ b/modules/shepherd.scm
@@ -168,7 +168,7 @@
                     (cond (logfile
                            (open-file logfile "al"))
                           ((zero? (getuid))
-                           (open-file "/dev/kmsg" "wl"))
+                           (syslog-output-port))
                           (else
                            (open-file (user-default-log-file) "al"))))
                    (%current-logfile-date-format
diff --git a/modules/shepherd/comm.scm b/modules/shepherd/comm.scm
index e686bfa..cbd8686 100644
--- a/modules/shepherd/comm.scm
+++ b/modules/shepherd/comm.scm
@@ -50,6 +50,7 @@
             report-command-error
 
             log-output-port
+            syslog-output-port
             start-logging
             stop-logging
             make-shepherd-output-port
@@ -216,6 +217,71 @@ on service '~a':")
   ;; 'strftime' format strings for entries in the log file.
   (make-parameter default-logfile-date-format))
 
+(define call-with-syslog-port
+  (let ((port #f))                                ;connection to /dev/log
+    (lambda (proc)
+      "Call PROC with an open output port.  The output port corresponds to
+/dev/log (aka. syslog) or, if that is unavailable, a degraded logging
+mechanism."
+      (define (call/syslog)
+        (catch 'system-error
+          (lambda ()
+            (proc port))
+          (lambda args
+            (if (memv (system-error-errno args)
+                      (list ENOTCONN ECONNREFUSED EPIPE))
+                (begin
+                  (set! port #f)
+                  (call-with-syslog-port proc))
+                (apply throw args)))))
+
+      (or (and port (not (port-closed? port)) (call/syslog))
+          (let ((sock (socket AF_UNIX SOCK_DGRAM 0)))
+            (catch 'system-error
+              (lambda ()
+                (connect sock AF_UNIX "/dev/log")
+                (setvbuf sock _IOLBF)
+                (set! port sock)
+                (call/syslog))
+              (lambda args
+                (close-port sock)
+                (if (memv (system-error-errno args)
+                          (list ENOENT ECONNREFUSED))
+                    (catch 'system-error
+                      (lambda ()
+                        (call-with-output-file "/dev/kmsg"
+                          (lambda (port)
+                            (setvbuf port _IOFBF)
+                            (proc port))))
+                      (lambda args
+                        (if (memv (system-error-errno args)
+                                  (list ENOENT EACCES EPERM))
+                            (call-with-output-file "/dev/console"
+                              (lambda (port)
+                                (setvbuf port _IONBF)
+                                (proc port)))
+                            (apply throw args))))
+                    (apply throw args)))))))))
+
+(define (syslog-output-port)
+  "Return the output port to write to syslog or /dev/kmsg, whichever is
+available."
+  (make-soft-port
+   (vector
+    (lambda (char)                                ;write char
+      (call-with-syslog-port
+       (lambda (port)
+         (write-char char port))))
+    (lambda (str)                                 ;write string
+      (call-with-syslog-port
+       (lambda (port)
+         (display str port))))
+    (const #t)                                    ;flush
+    #f                                            ;get char
+    (lambda ()                                    ;close
+      (call-with-syslog-port close-port)))
+   "w"))                                          ;output port
+
 (define %not-newline
   (char-set-complement (char-set #\newline)))
 
-- 
2.16.2