Simple Server - Poll() Version Main function

The full source code for this version of Simple Server can be found on my GitHub page

  1#include <poll.h>
  2#include <sys/select.h>
  3#include <sys/signal.h>
  4#include <sys/wait.h>
  5
  6#include <errno.h>
  7#include <magic.h>
  8#include <signal.h>
  9#include <stdio.h>
 10#include <stdlib.h>
 11#include <unistd.h>
 12
 13#include "./flags/flags.h"
 14#include "./sockets/socket.h"
 15#include "./client_conn/do_read.h"
 16#include "./client_conn/do_write.h"
 17#include "client_conn/connections.h"
 18#include "debug/debug.h"
 19
 20#define INITIAL_SIZE 32
 21
 22/* load global flags variables */
 23extern const uint32_t app_flags;
 24extern FILE *log_ptr;
 25
 26static volatile sig_atomic_t running = 1;
 27
 28static void
 29shutdown_server()
 30{
 31	DBG("Shutdown_server() called!\n");
 32	running = 0;
 33}
 34
 35/**
 36 * @brief Configure SIGCHLD disposition to auto-reap children
 37 *
 38 * Sets SIGCHLD to SIG_IGN so no handler function runs on child exit, and
 39 * sets the SA_NOCLDWAIT flag, which makes the kernel responsible for reaping
 40 * terminated child processes. Because no handler runs, the parent's main
 41 * loop is never interrupted by per-child-exit signals, which previously
 42 * degraded throughput under load.
 43 *
 44 * @note Child exit status is discarded (not collected via wait())
 45 * @note Exits program on failure to install the disposition
 46 */
 47static void
 48setup_signal_handler()
 49{
 50	struct sigaction sa = {0};
 51	sigemptyset(&sa.sa_mask);
 52
 53	sa.sa_flags = 0;
 54	sa.sa_handler = shutdown_server;
 55	if (sigaction(SIGTERM, &sa, NULL) == -1) {
 56		perror("sigaction SIGTERM. ");
 57		exit(EXIT_FAILURE);
 58	}
 59	if (sigaction(SIGINT, &sa, NULL) == -1) {
 60		perror("sigaction SIGINT. ");
 61		exit(EXIT_FAILURE);
 62	}
 63
 64	DBG("Signal handlers setup\n");
 65}
 66
 67/**
 68 * @brief Server entry point — initialises subsystems and runs the poll() event
 69 * loop.
 70 *
 71 * Startup sequence:
 72 *   1. Install SIGCHLD disposition (auto-reap via SA_NOCLDWAIT).
 73 *   2. Parse command-line flags (port, document root, debug mode).
 74 *   3. Optionally daemonise (unless -d flag is set).
 75 *   4. Open the libmagic MIME-type database.
 76 *   5. Create IPv4 and IPv6 listening sockets (both non-blocking).
 77 *   6. Allocate the parallel pollfd / Client arrays (INITIAL_SIZE=32 slots,
 78 *      indices 0-1 reserved for the listeners).
 79 *
 80 * Event loop:
 81 *   - poll() blocks indefinitely (timeout = -1).
 82 *   - EINTR is retried silently; any other poll() error is fatal.
 83 *   - Listener slots (i < N_LISTENERS=2): POLLIN triggers accept_new_conn().
 84 *   - Client slots: CLOSING state → close_conn() + index decrement.
 85 *   - POLLERR / POLLHUP / POLLNVAL → close_conn() + index decrement.
 86 *   - POLLIN + READING state → do_read().
 87 *   - POLLOUT + SENDING_HEADER or SENDING_BODY state → do_write().
 88 *
 89 * @param argc  Argument count from the shell
 90 * @param argv  Argument vector from the shell
 91 * @return      EXIT_SUCCESS on clean shutdown (currently unreachable);
 92 *              exits via exit() on fatal errors
 93 */
 94int
 95main(int argc, char *argv[])
 96{
 97	DBG("Loaded in debug mode\n");
 98
 99	setup_signal_handler();
100
101	if (setFlags(argc, argv) < 0) {
102		printf("incorrect flags\nWrite some nice message here\n");
103	}
104
105// There is a difference between build DEBUG and terminal display debug...should
106// rename it verbose...
107#ifndef DEBUG
108	if (!(app_flags & V_FLAG)) {
109		/*
110		 * We are setting nochdir to -1 so that the daemon runs in the
111		 * current working directory. Otherwise entering the correct url
112		 * for a page is a pain.
113		 *
114		 * nullfd is set to 1 to direct stdin, stdout and stderr output
115		 * do /dev/null
116		 */
117		daemon(-1, 0);
118	}
119#endif
120
121	/* initialize magic */
122	magic_t magic = magic_open(MAGIC_MIME_TYPE);
123	if (magic_load(magic, NULL) != 0) {
124		magic_close(magic);
125
126		return (EXIT_FAILURE);
127	}
128
129	printf("Simple Server %d\n", getpid());
130
131	int fd_size = INITIAL_SIZE;
132	int fd_count = 0;
133
134	struct pollfd *pfds = malloc(sizeof(*pfds) * fd_size);
135	Client *clients = malloc(sizeof(Client) * fd_size);
136
137	if (!pfds || !clients) {
138		perror("malloc");
139		exit(EXIT_FAILURE);
140	}
141
142	int num_listeners = 0;
143	int listener_v4 = -1;
144	int listener_v6 = -1;
145
146	int use_v4 = (app_flags & B4_FLAG) || !(app_flags & B6_FLAG);
147	int use_v6 = (app_flags & B6_FLAG) || !(app_flags & B4_FLAG);
148
149	if (use_v4)
150		listener_v4 = get_listener_v4();
151
152	if (use_v6)
153		listener_v6 = get_listener_v6();
154
155	if (-1 != listener_v4) {
156		pfds[num_listeners].fd = listener_v4;
157		pfds[num_listeners].events = POLLIN;
158		pfds[num_listeners].revents = 0;
159		num_listeners++;
160	}
161
162	if (-1 != listener_v6) {
163		pfds[num_listeners].fd = listener_v6;
164		pfds[num_listeners].events = POLLIN;
165		pfds[num_listeners].revents = 0;
166		num_listeners++;
167	}
168
169	if (num_listeners == 0) {
170		fprintf(stderr, "no listeners could be created; exiting\n");
171		exit(EXIT_FAILURE);
172	}
173
174	fd_count = num_listeners;
175
176	// Let's get this baby spinnin!
177	while (running) {
178		DBG("Entering first for loop\n");
179		int poll_count = poll(pfds, fd_count, -1);
180		if (-1 == poll_count) {
181			if (errno == EINTR) {
182				continue;
183			}
184			perror("poll");
185			exit(EXIT_FAILURE);
186		}
187
188		for (int i = 0; i < fd_count; i++) {
189			if (i < num_listeners) {
190				if (pfds[i].revents & POLLIN) {
191					DBG("New connection found\n");
192
193					accept_new_conn(pfds[i].fd,
194					    &pfds,
195					    &clients,
196					    &fd_count,
197					    &fd_size);
198				}
199				continue; // We don't want to treat a listener
200					  // as a client!
201			}
202
203			short revents = pfds[i].revents;
204			if (revents == 0) {
205				continue;
206			}
207
208			// Check if error and close if so
209			if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
210				close_conn(i, &fd_count, pfds, clients);
211				i--; // close_con will replace current value of
212				     // i, decrement to get the new value
213				continue;
214			}
215
216			if ((revents & POLLIN) &&
217			    (READING == clients[i].state)) {
218				DBG("Incoming from client detected\n");
219				// handle read for new request
220				do_read(i, clients, pfds, magic);
221			}
222
223			if (revents & POLLOUT &&
224			    ((SENDING_HEADER == clients[i].state) ||
225				(SENDING_BODY == clients[i].state))) {
226				// handle writing
227
228				do_write(i, clients);
229			}
230
231			if (CLOSING == clients[i].state) {
232				DBG("Closing connection\n");
233				close_conn(i, &fd_count, pfds, clients);
234				i--;
235				continue;
236			}
237		}
238
239		if (log_ptr) {
240			fflush(log_ptr);
241		}
242	}
243
244	for (int i = num_listeners; i < fd_count; i++) {
245		close_conn(i, &fd_count, pfds, clients);
246		i--;
247	}
248	close(listener_v4);
249	close(listener_v6);
250
251	magic_close(magic);
252	free(pfds);
253	free(clients);
254
255	if (log_ptr) {
256		fflush(log_ptr);
257		fclose(log_ptr);
258	}
259
260	exit(EXIT_SUCCESS);
261}