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