2 Copyright: (C)2015-2020 Laurent Bercot. http://skarnet.org/
3 ISC license. See http://opensource.org/licenses/ISC
5 Build-time requirements: skalibs. https://skarnet.org/software/skalibs/
6 Run-time requirements: none, if you link skalibs statically.
9 gcc -o sdnotify-wrapper -L/usr/lib/skalibs sdnotify-wrapper.c -lskarnet
10 Use /usr/lib/skalibs/libskarnet.a instead of -lskarnet to link statically.
11 Adapt gcc's -I and -L options to your skalibs installation paths.
13 Usage: if a daemon would be launched by systemd as "foobard args...",
14 launch it as "sdnotify-wrapper foobard args..." instead, and you can now
15 tell systemd that this daemon supports readiness notification.
17 Instead of using sd_notify() and having to link against the systemd
18 library, the daemon notifies readiness by writing whatever it wants
19 to a file descriptor (by default: stdout), then a newline. (Then it
20 should close that file descriptor.) The simplest way is something like
21 int notify_readiness() { write(1, "\n", 1) ; close(1) ; }
22 This mechanism is understandable by any notification readiness framework.
24 Readiness notification occurs when the newline is written, not when
25 the descriptor is closed; but since sdnotify-wrapper stops reading
26 after the first newline and will exit, any subsequent writes will
27 fail and it's best to simply close the descriptor right away.
29 sdnotify-wrapper sees the notification when it occurs and sends it
30 to systemd using the sd_notify format.
33 -d fd: the daemon will write its notification on descriptor fd.
35 -f: do not doublefork. Use if the daemon waits for children it does
36 not know it has (for instance, superservers do this). When in doubt,
37 do not use that option, or you may have a zombie hanging around.
38 -t timeout: if the daemon has not sent a notification after timeout
39 milliseconds, give up and exit; systemd will not be notified.
40 -k: keep the NOTIFY_SOCKET environment variable when execing into the
41 daemon. By default, the variable is unset: the daemon should not need it.
44 sdnotify-wrapper does not change the daemon's pid. It runs as a
45 (grand)child of the daemon.
46 If the NOTIFY_SOCKET environment variable is not set, sdnotify-wrapper
47 does nothing - it only execs into the daemon.
48 sdnotify-wrapper is more liberal than sd_notify(). It will accept
49 a relative path in NOTIFY_SOCKET.
53 #include <sys/types.h>
58 #include <sys/socket.h>
61 #include <skalibs/uint64.h>
62 #include <skalibs/types.h>
63 #include <skalibs/bytestr.h>
64 #include <skalibs/sgetopt.h>
65 #include <skalibs/strerr2.h>
66 #include <skalibs/allreadwrite.h>
67 #include <skalibs/tai.h>
68 #include <skalibs/iopause.h>
69 #include <skalibs/djbunix.h>
70 #include <skalibs/socket.h>
71 #include <skalibs/exec.h>
73 #define USAGE "sdnotify-wrapper [ -d fd ] [ -f ] [ -t timeout ] [ -k ] prog..."
74 #define dieusage() strerr_dieusage(100, USAGE)
76 #define VAR "NOTIFY_SOCKET"
78 static inline int ipc_sendto (int fd
, char const *s
, size_t len
, char const *path
)
80 struct sockaddr_un sa
;
81 size_t l
= strlen(path
) ;
82 if (l
> IPCPATH_MAX
) return (errno
= ENAMETOOLONG
, 0) ;
83 memset(&sa
, 0, sizeof sa
) ;
84 sa
.sun_family
= AF_UNIX
;
85 memcpy(sa
.sun_path
, path
, l
+1) ;
86 if (path
[0] == '@') sa
.sun_path
[0] = 0 ;
87 return sendto(fd
, s
, len
, MSG_NOSIGNAL
, (struct sockaddr
*)&sa
, sizeof sa
) >= 0 ;
90 static inline void notify_systemd (pid_t pid
, char const *socketpath
)
93 char fmt
[16 + PID_FMT
] = "READY=1\nMAINPID=" ;
94 int fd
= ipc_datagram_b() ;
95 if (fd
< 0) strerr_diefu1sys(111, "create socket") ;
96 n
+= pid_fmt(fmt
+ n
, pid
) ;
98 if (!ipc_sendto(fd
, fmt
, n
, socketpath
))
99 strerr_diefu2sys(111, "send notification message to ", socketpath
) ;
103 static inline int run_child (int fd
, unsigned int timeout
, pid_t pid
, char const *s
)
106 iopause_fd x
= { .fd
= fd
, .events
= IOPAUSE_READ
} ;
109 if (timeout
) tain_from_millisecs(&deadline
, timeout
) ;
110 else deadline
= tain_infinite_relative
;
111 tain_add_g(&deadline
, &deadline
) ;
114 int r
= iopause_g(&x
, 1, &deadline
) ;
115 if (r
< 0) strerr_diefu1sys(111, "iopause") ;
117 r
= sanitize_read(fd_read(fd
, dummy
, 4096)) ;
119 if (errno
== EPIPE
) return 1 ;
120 else strerr_diefu1sys(111, "read from parent") ;
121 else if (r
&& memchr(dummy
, '\n', r
)) break ;
124 notify_systemd(pid
, s
) ;
128 int main (int argc
, char const *const *argv
)
130 char const *s
= getenv(VAR
) ;
131 unsigned int fd
= 1 ;
132 unsigned int timeout
= 0 ;
133 int df
= 1, keep
= 0 ;
134 PROG
= "sdnotify-wrapper" ;
136 subgetopt l
= SUBGETOPT_ZERO
;
139 int opt
= subgetopt_r(argc
, argv
, "d:ft:k", &l
) ;
140 if (opt
== -1) break ;
143 case 'd' : if (!uint0_scan(l
.arg
, &fd
)) dieusage() ; break ;
144 case 'f' : df
= 0 ; break ;
145 case 't' : if (!uint0_scan(l
.arg
, &timeout
)) dieusage() ; break ;
146 case 'k' : keep
= 1 ; break ;
147 default : dieusage() ;
150 argc
-= l
.ind
; argv
+= l
.ind
;
152 if (!argc
) dieusage() ;
154 if (!s
) xexec(argv
) ;
157 pid_t parent
= getpid() ;
160 if (pipe(p
) < 0) strerr_diefu1sys(111, "pipe") ;
161 child
= df
? doublefork() : fork() ;
162 if (child
< 0) strerr_diefu1sys(111, df
? "doublefork" : "fork") ;
165 PROG
= "sdnotify-wrapper (child)" ;
167 return run_child(p
[0], timeout
, parent
, s
) ;
170 if (fd_move((int)fd
, p
[1]) < 0) strerr_diefu1sys(111, "move descriptor") ;
171 if (keep
) xexec(argv
) ;
172 else xmexec_m(argv
, VAR
, sizeof(VAR
)) ;