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