Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

Unit 3: Daemon Processes, syslog & the inetd Superserver

Lesson 10 of 15 in the free Network Programming notes on Siksha Sarovar, written by Rohit Jangra.

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):

  1. For each line: socket → bind → (listen for TCP) → add fd to a select set.
  2. select returns → for stream/nowait: accept.
  3. fork; in the child: dup2(connfd, 0); dup2(connfd, 1); dup2(connfd, 2); close everything else; exec the server program.
  4. 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

CriterionStandalone daemoninetd-launchedsystemd socket activation
who owns the listening socketthe server itselfinetd; child gets it as fd 0systemd; passed as fd 3+
process exists when idleyes — sleepingnono
cost per connectionaccept (cheap)fork + exec (expensive)first connection starts the service, then standalone
startup code neededfull daemon_initnone (daemon_inetd: just openlog)none
right forbusy services (HTTP, DB)rare, tiny services (the historic echo/daytime)modern Linux services generally
configown 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

  1. Recite the eight daemon_init steps and attach a one-line reason to each — which two steps exist purely because of controlling-terminal rules?
  2. Why does a daemon launched by inetd skip daemon_init? Which single initialisation call does it still need?
  3. In the inetd trace, what would happen if the child exec'd the service without the dup2 calls?
  4. A service entry says dgram udp wait. Walk through what inetd does differently versus a stream tcp nowait entry, and why.
  5. Which selector matches more messages: daemon.err or daemon.debug? Explain the level-threshold rule.