2.1 What a daemon is
A daemon is a long-running background process with no controlling terminal — servers (sshd, httpd, named, crond) all daemonise at startup. No terminal means no stdout: daemons log through syslog instead.
2.2 syslogd and the syslog API
The syslogd daemon collects log messages (Unix-domain socket /dev/log, UDP port 514 from remote hosts, kernel via /dev/klog) and routes them per /etc/syslog.conf rules of the form facility.level → destination (file, console, remote host, user).
#include <syslog.h>
openlog("myserver", LOG_PID | LOG_CONS, LOG_DAEMON); /* ident, options, facility */
syslog(LOG_INFO, "started on port %d", port);
syslog(LOG_ERR, "accept failed: %m"); /* %m = strerror(errno) */
closelog();
Levels (priority order): LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG. Facilities: LOG_DAEMON, LOG_AUTH, LOG_MAIL, LOG_LOCAL0–7, ...
2.3 daemon_init — the canonical daemonising recipe
Every step exists for a reason; learn the reasons:
int daemon_init(const char *pname, int facility) {
pid_t pid;
if ((pid = fork()) != 0) exit(0); /* ① parent exits
→ child is not a process-group leader (needed for setsid),
and the shell thinks the command finished */
setsid(); /* ② new session: session leader,
group leader, and NO controlling terminal */
signal(SIGHUP, SIG_IGN); /* ③ ignore HUP from step ④ */
if ((pid = fork()) != 0) exit(0); /* ④ second fork: no longer a
session leader → can NEVER reacquire a terminal */
chdir("/"); /* ⑤ don't pin any filesystem */
umask(0); /* ⑥ predictable file modes */
for (i = 0; i < MAXFD; i++) close(i); /* ⑦ close inherited fds */
/* reopen 0,1,2 on /dev/null in production code */
openlog(pname, LOG_PID, facility); /* ⑧ stdout is gone — use syslog */
return 0;
}
The "double fork" question — why fork twice? — is a guaranteed viva item: the first fork enables setsid; the second guarantees the daemon (no longer session leader) cannot accidentally acquire a controlling terminal by opening a tty.
2.4 inetd — the Internet superserver
Problem: dozens of rarely-used services, each a sleeping daemon wasting a process. Solution: one daemon, inetd, listens on all their ports and launches the real program only when a connection arrives.
/etc/inetd.conf — one service per line:
# service socktype proto wait/nowait user server-program arguments
ftp stream tcp nowait root /usr/sbin/ftpd ftpd -l
telnet stream tcp nowait root /usr/sbin/telnetd telnetd
daytime stream tcp nowait root internal
tftp dgram udp wait nobody /usr/sbin/tftpd tftpd
How inetd works (the descriptor dance):
- For each line: socket → bind → (listen for TCP) → add fd to a select set.
- select returns → for stream/nowait: accept.
- fork; in the child:
dup2(connfd, 0); dup2(connfd, 1); dup2(connfd, 2);close everything else; exec the server program. - The launched program just reads stdin / writes stdout — it doesn't even know sockets exist. (Trivial "internal" services like echo/daytime inetd answers itself.)
wait vs nowait: nowait (typical TCP) = inetd accepts and keeps listening immediately — many children in parallel. wait (typical UDP, e.g. tftpd) = inetd hands the datagram socket itself to the child and stops watching that fd until the child exits — otherwise inetd and child would race for the same datagrams (one socket, no accept in UDP!). Explaining why UDP must be wait is the standard exam discriminator.
daemon_inetd: a server launched by inetd skips the whole daemon_init dance — inetd already daemonised; the program only calls openlog and serves fd 0.
Trade-off summary: inetd saves memory for infrequent services but pays fork+exec per connection — busy servers (HTTP) listen standalone. Modern descendants: xinetd, and systemd socket activation — same idea, new clothes.
2.5 Sessions, process groups and controlling terminals — the theory under daemon_init
The double-fork recipe only makes sense once the kernel's job-control objects are clear; this background paragraph is what turns a memorised recipe into an explained one.
- A process group is a set of processes that receive terminal signals together (one ^C kills a whole pipeline).
- A session is a set of process groups; a session may have one controlling terminal, and the process that established the session (the session leader) is the only one that can acquire it.
- Rules that drive the recipe:
setsid()fails if the caller is already a process-group leader (hence fork #1 — the child is guaranteed not to be one); after setsid the child is a session leader, and on SVR4-derived systems a session leader that opens a terminal device accidentally acquires it as controlling terminal (hence fork #2 — the grandchild is in the session but not its leader, and can never acquire a terminal, ever).
So the exam answer for "why two forks?" has exactly two clauses, one per fork — and each clause names the kernel rule it satisfies.
2.6 Standalone vs inetd-launched vs socket-activated — comparison table
| Criterion | Standalone daemon | inetd-launched | systemd socket activation |
|---|---|---|---|
| who owns the listening socket | the server itself | inetd; child gets it as fd 0 | systemd; passed as fd 3+ |
| process exists when idle | yes — sleeping | no | no |
| cost per connection | accept (cheap) | fork + exec (expensive) | first connection starts the service, then standalone |
| startup code needed | full daemon_init | none (daemon_inetd: just openlog) | none |
| right for | busy services (HTTP, DB) | rare, tiny services (the historic echo/daytime) | modern Linux services generally |
| config | own init script / unit | /etc/inetd.conf line | .socket + .service units |
The conceptual through-line worth writing as a closing sentence: all three answers to "who calls socket/bind/listen?" exist because descriptors survive fork and exec — the entire mechanism rests on that one Unit-1 fact.
2.7 Worked trace: one telnet connection through inetd
A timeline answer for "explain the working of inetd":
boot inetd reads inetd.conf; for telnet: socket→bind(23)→listen→FD_SET
t=0 client SYN arrives; kernel completes handshake (queue)
t=0 inetd's select wakes: listenfd 23 readable
t=0+ inetd: accept() → connfd
fork() → child:
close all fds except connfd
dup2(connfd,0); dup2(connfd,1); dup2(connfd,2); close(connfd)
setgid/setuid to the configured user ← privilege drop: inetd ran as root
exec("/usr/sbin/telnetd")
parent (inetd): close(connfd); back to select
t=0+ telnetd reads/writes fd 0/1 — believes it has plain stdin/stdout
close telnetd exits; SIGCHLD → inetd reaps (its other duty!)
Details examiners look for: the privilege-drop step (inetd must be root to bind ports < 1024, but services run as the configured user); the parent's close(connfd) (reference counting again); and SIGCHLD handling in inetd itself (a forking server must reap — Unit 2's rule applies to inetd too).
2.8 syslog message routing — a worked configuration line
# /etc/syslog.conf — selector(facility.level) action
mail.info /var/log/maillog # mail at info-or-higher
*.err /var/log/errors # any facility, err and above
daemon.* @loghost.example.com # forward to central host (UDP 514)
auth.crit root # write to root's terminal
The semantics to state precisely: a level in a selector means that level and everything more severe (mail.info matches err too); facilities exist so one daemon (syslogd) can sort everyone's messages without knowing the programs; remote forwarding is why a cracked machine's logs can still be trustworthy — they already left the building. The %m conversion (= strerror(errno)) in the syslog() format string is a small detail that regularly appears as a one-mark question.
Exam pointers
- "What is a daemon? Write and explain daemon_init()" — the §2.3 code with the reason for every numbered step; the two-fork explanation from §2.5 is where the marks concentrate.
- "Explain inetd with its configuration file" — config line fields in order, then the four-step descriptor dance, then wait vs nowait with the UDP justification.
- "Why is the UDP entry 'wait' while TCP entries are 'nowait'?" — no accept in UDP: there is only one socket carrying all datagrams; if inetd kept selecting on it while the child also read it, the two would race for datagrams. inetd must hand the socket over and look away until the child exits.
- Short answers: syslog levels in order; what LOG_PID does; why daemons chdir("/") (don't pin a mounted filesystem) and umask(0) (don't let an inherited mask corrupt file modes).
Check yourself
- Recite the eight daemon_init steps and attach a one-line reason to each — which two steps exist purely because of controlling-terminal rules?
- Why does a daemon launched by inetd skip daemon_init? Which single initialisation call does it still need?
- In the inetd trace, what would happen if the child exec'd the service without the dup2 calls?
- A service entry says
dgram udp wait. Walk through what inetd does differently versus astream tcp nowaitentry, and why. - Which selector matches more messages:
daemon.errordaemon.debug? Explain the level-threshold rule.