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 "utils.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/* Apply process resource limits */
 59static void apply_rlimits(void)
 60{
 61	struct rlimit rl;
 62
 63	rl.rlim_cur = 0;
 64	rl.rlim_max = 0;
 65	(void)setrlimit(RLIMIT_CORE, &rl);
 66
 67	rl.rlim_cur = 1024;
 68	rl.rlim_max = 1024;
 69	(void)setrlimit(RLIMIT_NOFILE, &rl);
 70}
 71
 72/* Handle a single accepted client connection */
 73static void* client_thread(void* arg)
 74{
 75	int c = (int)(intptr_t)arg;
 76
 77	sock_set_timeouts(c);
 78	(void)http_handle(c);
 79	shutdown(c, SHUT_WR);
 80	close(c);
 81
 82	release_slot();
 83	return NULL;
 84}
 85
 86/* Set close on exec on file descriptor */
 87static void fd_set_cloexec(int fd)
 88{
 89	int f = fcntl(fd, F_GETFD);
 90	if (f >= 0)
 91		(void)fcntl(fd, F_SETFD, f | FD_CLOEXEC);
 92}
 93
 94/* Apply configured socket IO timeouts */
 95static void sock_set_timeouts(int fd)
 96{
 97	struct timeval tv;
 98	tv.tv_sec = http_io_timeout;
 99	tv.tv_usec = 0;
100
101	(void)setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
102	(void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
103}
104
105/* Release one client slot back to the pool */
106static void release_slot(void)
107{
108	if (pthread_mutex_lock(&slots_lock) != 0)
109		return;
110
111	if (slots_available < max_clients)
112		slots_available++;
113
114	(void)pthread_mutex_unlock(&slots_lock);
115}
116
117/* Try to reserve one client slot
118   Returns 1=Acquired, 0=Full, -1=Failure */
119static int try_acquire_slot(void)
120{
121	int ok = 0;
122
123	if (pthread_mutex_lock(&slots_lock) != 0)
124		return -1;
125
126	if (slots_available > 0) {
127		slots_available--;
128		ok = 1;
129	}
130
131	(void)pthread_mutex_unlock(&slots_lock);
132	return ok;
133}
134
135void die(const char* s, int e)
136{
137	perror(s);
138	exit(e);
139}
140
141void run(void)
142{
143	for (;;) {
144		int c;
145
146		c = accept(sock, NULL, NULL);
147		if (c < 0) {
148			if (errno == EINTR)
149				continue;
150			continue;
151		}
152
153		fd_set_cloexec(c);
154
155		/* client cap. if full 503 and close */
156		if (try_acquire_slot() != 1) {
157			/* short 503 + retry after */
158			static const char resp[] =
159				"HTTP/1.1 503 Service Unavailable\r\n"
160				"Content-Type: text/plain\r\n"
161				"Retry-After: 1\r\n"
162				"Content-Length: 5\r\n"
163				"Connection: close\r\n"
164				"\r\n"
165				"busy\n";
166			(void)write_all(c, resp, sizeof(resp) - 1);
167
168			close(c);
169			LOG_FIELD(true, "CORE", "Connection", "Busy (503)");
170			continue;
171		}
172
173		LOG_FIELD(verbose_log, "CORE", "Connection", "Accepted");
174		pthread_t t;
175		int err = pthread_create(&t, NULL, client_thread, (void*)(intptr_t)c);
176		if (err != 0) {
177			LOG_FIELD(true, "CORE", "pthread_create FAILED", "%d", err);
178			close(c);
179			release_slot();
180			continue;
181		}
182
183		(void)pthread_detach(t);
184	}
185}
186
187void setup(void)
188{
189	signal(SIGPIPE, SIG_IGN);
190
191	config_load();
192	apply_rlimits();
193
194	if (pthread_mutex_init(&lib_lock, NULL) != 0)
195		die("pthread_mutex_init", EXIT_FAILURE);
196
197	if (pthread_mutex_init(&slots_lock, NULL) != 0)
198		die("pthread_mutex_init", EXIT_FAILURE);
199	slots_available = max_clients;
200
201	int ret = 1;
202	sock = socket(AF_INET, SOCK_STREAM, 0);
203	if (sock < 0)
204		die("socket", EXIT_FAILURE);
205	fd_set_cloexec(sock);
206
207	int yes = 1;
208	ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
209	if (ret < 0)
210		die("setsockopt", EXIT_FAILURE);
211
212	struct sockaddr_in a;
213	memset(&a, 0, sizeof(a));
214	a.sin_family = AF_INET;
215	a.sin_port = htons(server_port);
216	ret = inet_pton(AF_INET, server_addr, &a.sin_addr);
217	if (ret != 1)
218		die("inet_pton", EXIT_FAILURE);
219
220	ret = bind(sock, (struct sockaddr*)&a, sizeof(a));
221	if (ret < 0)
222		die("bind", EXIT_FAILURE);
223
224	if (scan_library(&lib, media_dir) < 0)
225		die("scan_library", EXIT_FAILURE);
226	LOG_FIELD(verbose_log, "SCAN", "Cached items", "%zu", lib.len);
227
228	ret = listen(sock, LISTEN_BACKLOG);
229	if (ret < 0)
230		die("listen", EXIT_FAILURE);
231
232	LOG_FIELD(verbose_log, "CORE", "Listening on", "%s:%d", server_addr, server_port);
233#ifdef __OpenBSD__
234	if (unveil(media_dir, "r") < 0)
235		die("unveil", EXIT_FAILURE);
236	if (unveil(NULL, NULL) < 0)
237		die("unveil", EXIT_FAILURE);
238#endif
239}
240
241int main(int argc, char* argv[])
242{
243	if (argc > 1) {
244		if (strcmp(argv[1], "-v") == 0) {
245			printf(
246				"parados v%s-%s\n"
247				"\n"
248				"This software is licensed under ISC.\n"
249				"See LICENSE for license information.\n"
250				"\n"
251				"See parados(1), parados(7), parados.conf(5)\n"
252				"for usage information\n",
253				VERSION, GIT_VER
254			);
255			return EXIT_SUCCESS;
256		}
257		else {
258			printf("Invalid command\n");
259			printf("See parados(1) for more information\n");
260			return EXIT_FAILURE;
261		}
262	}
263
264	setup();
265#ifdef __OpenBSD__
266	if (pledge("stdio inet proc rpath", NULL) < 0)
267		die("pledge", EXIT_FAILURE);
268#endif /* __OpenBSD__ */
269	run();
270	return EXIT_SUCCESS;
271}
272