master uint/parados / server / parados.c
  1/*            parados
  2	the simple home media server
  3
  4	See parados(1), parados(7), parados.conf(5)
  5	for usage information
  6
  7	This software is licensed under ISC.
  8	Check LICENCE for more details.
  9*/
 10
 11#include <errno.h>
 12#include <fcntl.h>
 13#include <signal.h>
 14#include <stdarg.h>
 15#include <stdbool.h>
 16#include <stddef.h>
 17#include <stdint.h>
 18#include <stdio.h>
 19#include <stdlib.h>
 20#include <string.h>
 21#include <sys/resource.h>
 22#include <sys/socket.h>
 23#include <sys/time.h>
 24#include <time.h>
 25#include <unistd.h>
 26
 27#include <arpa/inet.h>
 28#include <netinet/in.h>
 29#include <pthread.h>
 30
 31#include "config.h"
 32#include "http.h"
 33#include "log.h"
 34#include "scan.h"
 35#include "util.h"
 36
 37#ifndef GIT_VER
 38#define GIT_VER "unknown"
 39#endif /* GIT_VER */
 40
 41static void apply_rlimits(void);
 42static void* client_thread(void* arg);
 43static void fd_set_cloexec(int fd);
 44static void sock_set_timeouts(int fd);
 45static void release_slot(void);
 46static int try_acquire_slot(void);
 47
 48void die(const char* s, int e);
 49void run(void);
 50void setup(void);
 51
 52static int sock;
 53struct library lib;
 54pthread_mutex_t lib_lock;
 55static pthread_mutex_t slots_lock;
 56static int slots_available;
 57
 58/**
 59 * @brief Apply process resource limits
 60 */
 61static void apply_rlimits(void)
 62{
 63	struct rlimit rl;
 64
 65	rl.rlim_cur = 0;
 66	rl.rlim_max = 0;
 67	(void)setrlimit(RLIMIT_CORE, &rl);
 68
 69	rl.rlim_cur = 1024;
 70	rl.rlim_max = 1024;
 71	(void)setrlimit(RLIMIT_NOFILE, &rl);
 72}
 73
 74/**
 75 * @brief Handle a single accepted client connection
 76 *
 77 * @param arg Client socket cast through void*
 78 */
 79static void* client_thread(void* arg)
 80{
 81	int c = (int)(intptr_t)arg;
 82
 83	sock_set_timeouts(c);
 84	(void)http_handle(c);
 85	shutdown(c, SHUT_WR);
 86	close(c);
 87
 88	release_slot();
 89	return NULL;
 90}
 91
 92/**
 93 * @brief Set close on exec on file descriptor
 94 *
 95 * @param fd File descriptor
 96 */
 97static void fd_set_cloexec(int fd)
 98{
 99	int f = fcntl(fd, F_GETFD);
100	if (f >= 0)
101		(void)fcntl(fd, F_SETFD, f | FD_CLOEXEC);
102}
103
104/**
105 * @brief Apply configured socket IO timeouts
106 *
107 * @param fd File descriptor
108 */
109static void sock_set_timeouts(int fd)
110{
111	struct timeval tv;
112	tv.tv_sec = http_io_timeout;
113	tv.tv_usec = 0;
114
115	(void)setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
116	(void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
117}
118
119/**
120 * @brief Release one client slot back to the pool
121 */
122static void release_slot(void)
123{
124	if (pthread_mutex_lock(&slots_lock) != 0)
125		return;
126
127	if (slots_available < max_clients)
128		slots_available++;
129
130	(void)pthread_mutex_unlock(&slots_lock);
131}
132
133/**
134 * @brief Try to reserve one client slot
135 *
136 * @return 1=Acquired, 0=Full, -1=Failure
137 */
138static int try_acquire_slot(void)
139{
140	int ok = 0;
141
142	if (pthread_mutex_lock(&slots_lock) != 0)
143		return -1;
144
145	if (slots_available > 0) {
146		slots_available--;
147		ok = 1;
148	}
149
150	(void)pthread_mutex_unlock(&slots_lock);
151	return ok;
152}
153
154void die(const char* s, int e)
155{
156	perror(s);
157	exit(e);
158}
159
160void run(void)
161{
162	for (;;) {
163		int c;
164
165		c = accept(sock, NULL, NULL);
166		if (c < 0) {
167			if (errno == EINTR)
168				continue;
169			continue;
170		}
171
172		fd_set_cloexec(c);
173
174		/* client cap. if full 503 and close */
175		if (try_acquire_slot() != 1) {
176			/* short 503 + retry after */
177			static const char resp[] =
178				"HTTP/1.1 503 Service Unavailable\r\n"
179				"Content-Type: text/plain\r\n"
180				"Retry-After: 1\r\n"
181				"Content-Length: 5\r\n"
182				"Connection: close\r\n"
183				"\r\n"
184				"busy\n";
185			(void)write_all(c, resp, sizeof(resp) - 1);
186
187			close(c);
188			LOG(true, "CORE", "Connection         Busy (503)");
189			continue;
190		}
191
192		LOG(verbose_log, "CORE", "Connection         Accepted");
193		pthread_t t;
194		int err = pthread_create(&t, NULL, client_thread, (void*)(intptr_t)c);
195		if (err != 0) {
196			LOG(true, "CORE", "pthread_create FAILED  %d", err);
197			close(c);
198			release_slot();
199			continue;
200		}
201
202		(void)pthread_detach(t);
203	}
204}
205
206void setup(void)
207{
208	signal(SIGPIPE, SIG_IGN);
209
210	config_load();
211	apply_rlimits();
212
213	if (pthread_mutex_init(&lib_lock, NULL) != 0)
214		die("pthread_mutex_init", EXIT_FAILURE);
215
216	if (pthread_mutex_init(&slots_lock, NULL) != 0)
217		die("pthread_mutex_init", EXIT_FAILURE);
218	slots_available = max_clients;
219
220	int ret = 1;
221	sock = socket(AF_INET, SOCK_STREAM, 0);
222	if (sock < 0)
223		die("socket", EXIT_FAILURE);
224	fd_set_cloexec(sock);
225
226	int yes = 1;
227	ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
228	if (ret < 0)
229		die("setsockopt", EXIT_FAILURE);
230
231	struct sockaddr_in a;
232	memset(&a, 0, sizeof(a));
233	a.sin_family = AF_INET;
234	a.sin_port = htons(server_port);
235	ret = inet_pton(AF_INET, server_addr, &a.sin_addr);
236	if (ret != 1)
237		die("inet_pton", EXIT_FAILURE);
238
239	ret = bind(sock, (struct sockaddr*)&a, sizeof(a));
240	if (ret < 0)
241		die("bind", EXIT_FAILURE);
242
243	if (scan_library(&lib, media_dir) < 0)
244		die("scan_library", EXIT_FAILURE);
245	LOG(verbose_log, "SCAN", "Cached items       %zu", lib.len);
246
247	ret = listen(sock, LISTEN_BACKLOG);
248	if (ret < 0)
249		die("listen", EXIT_FAILURE);
250
251	LOG(verbose_log, "CORE", "Listening on       %s:%d", server_addr, server_port);
252#ifdef __OpenBSD__
253	if (unveil(media_dir, "r") < 0)
254		die("unveil", EXIT_FAILURE);
255	if (unveil(NULL, NULL) < 0)
256		die("unveil", EXIT_FAILURE);
257#endif
258}
259
260int main(int argc, char* argv[])
261{
262	if (argc > 1) {
263		if (strcmp(argv[1], "-v") == 0) {
264			printf(
265				"parados v%s-%s\n"
266				"\n"
267				"This software is licensed under ISC.\n"
268				"See LICENSE for license information.\n"
269				"\n"
270				"See parados(1), parados(7), parados.conf(5)\n"
271				"for usage information\n",
272				VERSION, GIT_VER
273			);
274			return EXIT_SUCCESS;
275		}
276		else {
277			printf("Invalid command\n");
278			printf("See parados(1) for more information\n");
279			return EXIT_FAILURE;
280		}
281	}
282
283	setup();
284#ifdef __OpenBSD__
285	if (pledge("stdio inet proc rpath", NULL) < 0)
286		die("pledge", EXIT_FAILURE);
287#endif /* __OpenBSD__ */
288	run();
289	return EXIT_SUCCESS;
290}
291