In the previous post we have seen how to start a simple deamon with systemd. Now we will use the socket based activation of this init system to make our daemon start when it is needed.
Let’s start with a basic server which only prints a message.
#include <stdio.h> #include <stdlib.h> #include "CommonLib/net_socket.h" void panic(lerror *error) { lstring *buf = NULL; l_assert(error!=NULL); buf = lstring_new(); buf = lerror_fill_f(error, buf); fprintf(stderr, "%s", buf); lstring_delete(buf); abort(); } int main() { lerror *myError = NULL; TCPListenSocket *listeningSocket = NULL; TCPSocket *socket = NULL; listeningSocket = TCPListenSocket_new("0.0.0.0", "3233", &myError); if (myError!=NULL) panic(myError); puts("Hello server is accepting connections"); fflush(stdout); while(1) { socket = TCPListenSocket_accept(listeningSocket, &myError); if (myError!=NULL) panic(myError); TCPSocket_send_string(socket, "Hello from the server!", &myError); if (myError!=NULL) panic(myError); TCPSocket_destroy(socket); socket = NULL; } TCPListenSocket_destroy(listeningSocket); return 0; }
This code is based on the CommonLib library.
Now, if systemd started us, we need to get the listening file descriptor and to do so with must include the systemd header file:
#include <systemd/sd-daemon.h>
and to replace:
listeningSocket = TCPListenSocket_new("0.0.0.0", "3233", &myError); if (myError!=NULL) panic(myError);
with:
if (sd_listen_fds(0)==0) { listeningSocket = TCPListenSocket_new("0.0.0.0", "3233", &myError); if (myError!=NULL) panic(myError); } else { listeningSocket = TCPListenSocket_new_from_fd( SD_LISTEN_FDS_START + 0, "localhost:3233"); puts("Hello server received socket from SystemD"); fflush(stdout); }
The sd_listen_fds function will count the sockets passed by systemd and, if systemd started us, the file descriptor SD_LISTEN_FDS_START + 0, the first, is encapsulated in a TCPListenSocket object. The server then follows in the same way. Simple, isn’t?
Now our server is socket activatable. We must tell it to systemd writing a my_server.socket like this:
[Socket] ListenStream=3233 [Install] WantedBy=sockets.target
We must also write a my_server.service file like this:
[Unit] Description=<description here> After=network.target [Service] User=<user name here> ExecStart=<executable file name here>
Note that this service file has not the install section.
These files must be placed in our /usr/lib/systemd/system directory.
We can make systemd preallocate the socket for us with:
# systemctl start my_server.socket
If you ask your system for the running processes you will not see the daemon process.
When you will make the first connection systemd will start it.
magnificent post, quite informative. I wonder why the other specialists of this sector do not notice this. You need to continue your writing. I’m sure, you’ve an excellent readers’ base already!
Thank you for your really kind words!
I’ll continue writing as soon as I have free time.
CommonLib references and calls unfortunately make this unusable for me, if you were to present this example without that, it would be much more understandable for an ordinary systemd programmer.
Hi Robert.
I used CommonLib to make the code more understandable. Without CommonLib socket calls would be mixed with socket preallocation ones.
I think it would be easy to implement another example that doesn’t use CommonLib calls.
What to do if we would like the process to be terminated once the socket is closed