commit 9f73103
uint
·
2026-01-31 04:18:48 +0000 UTC
parent 088c85d
make endpoint handling more thread safe, add /rescan endpoint
5 files changed,
+147,
-14
+118,
-14
1@@ -1,6 +1,7 @@
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <limits.h>
5+#include <pthread.h>
6 #include <stddef.h>
7 #include <stdint.h>
8 #include <stdio.h>
9@@ -9,6 +10,7 @@
10 #include <strings.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13+#include <time.h>
14 #include <unistd.h>
15
16 #include "config.h"
17@@ -21,6 +23,7 @@
18
19 static int cors_build(char* out, size_t outsz, const char* hdr, int preflight);
20 static const struct item* find_item(uint64_t id);
21+static int item_path_for_id(char out[4096], uint64_t id, const struct user* u);
22 static const char* mime_from_path(const char* path);
23 static int parse_hex64(const char* s, uint64_t* out);
24 static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end);
25@@ -34,6 +37,7 @@ static int stat_item(const struct item* it, struct stat* st);
26 static int stream_file(int c, const struct item* it, const char* hdr, int head_only);
27
28 extern struct library lib;
29+extern pthread_rwlock_t lib_lock;
30
31 static int cors_origin_allowed(const char* origin)
32 {
33@@ -129,6 +133,40 @@ static const struct item* find_item(uint64_t id)
34 return NULL;
35 }
36
37+static int item_path_for_id(char out[4096], uint64_t id, const struct user* u)
38+{
39+ int ret = -1;
40+
41+ if (!out)
42+ return -1;
43+
44+ out[0] = '\0';
45+
46+ pthread_rwlock_rdlock(&lib_lock);
47+
48+ const struct item* it = find_item(id);
49+ if (!it) {
50+ ret = 1; /* not found */
51+ goto out;
52+ }
53+
54+ if (u && !user_allows_path(u, it->path)) {
55+ ret = 1; /* not found */
56+ goto out;
57+ }
58+
59+ if (snprintf(out, 4096, "%s", it->path) >= 4096) {
60+ ret = -1;
61+ goto out;
62+ }
63+
64+ ret = 0;
65+
66+out:
67+ pthread_rwlock_unlock(&lib_lock);
68+ return ret; /* 0 ok, 1 not found, -1 error */
69+}
70+
71 static const char* mime_from_path(const char* path)
72 {
73 const char* dot = strrchr(path, '.');
74@@ -615,6 +653,9 @@ int http_handle(int c)
75
76 if (strcmp(path, "/library") == 0) {
77 LOG(verbose_log, "HTTP", "Route /library");
78+
79+ pthread_rwlock_rdlock(&lib_lock);
80+
81 struct json j;
82 struct library view;
83 memset(&view, 0, sizeof(view));
84@@ -626,6 +667,7 @@ int http_handle(int c)
85 if (lib.len > 0) {
86 view.items = calloc(lib.len, sizeof(*view.items));
87 if (!view.items) {
88+ pthread_rwlock_unlock(&lib_lock);
89 reply_text(c, hdr, HTTP_500, "server error\n");
90 return -1;
91 }
92@@ -643,10 +685,15 @@ int http_handle(int c)
93 if (u)
94 free(view.items);
95
96+ pthread_rwlock_unlock(&lib_lock);
97+
98 LOG(verbose_log, "JSON", "Encode FAILED");
99 reply_text(c, hdr, HTTP_500, "json encode failed\n");
100 return -1;
101 }
102+
103+ pthread_rwlock_unlock(&lib_lock);
104+
105 LOG(verbose_log, "JSON", "Encoded bytes %zu bytes", j.len);
106
107 reply_json(c, hdr, HTTP_200, j.buf, j.len, !head_only);
108@@ -658,6 +705,52 @@ int http_handle(int c)
109 return 0;
110 }
111
112+ if (strcmp(path, "/rescan") == 0) {
113+ LOG(verbose_log, "HTTP", "Route /rescan");
114+
115+ if (users.len == 0) {
116+ LOG(true, "HTTP", "Rescan forbidden Auth disabled");
117+ reply_text(c, hdr, HTTP_403, "Forbidden\n");
118+ return 0;
119+ }
120+
121+ LOG(true, "SCAN", "Rescan requested %s", u->name);
122+
123+ struct timespec t0;
124+ struct timespec t1;
125+ clock_gettime(CLOCK_MONOTONIC, &t0);
126+
127+ size_t before = 0;
128+ size_t after = 0;
129+
130+ pthread_rwlock_wrlock(&lib_lock);
131+
132+ before = lib.len;
133+ LOG(verbose_log, "SCAN", "Rescan begin %s (%zu items)", media_dir, before);
134+
135+ int ok = scan_library_rescan(&lib, media_dir);
136+
137+ after = lib.len;
138+
139+ pthread_rwlock_unlock(&lib_lock);
140+
141+ clock_gettime(CLOCK_MONOTONIC, &t1);
142+
143+ long ms = (t1.tv_sec - t0.tv_sec) * 1000L +
144+ (t1.tv_nsec - t0.tv_nsec) / 1000000L;
145+
146+ if (ok < 0) {
147+ LOG(true, "SCAN", "Rescan FAILED %s (%ld ms)", media_dir, ms);
148+ reply_text(c, hdr, HTTP_500, "rescan failed\n");
149+ return -1;
150+ }
151+
152+ LOG(true, "SCAN", "Rescan OK %zu -> %zu (%ld ms)", before, after, ms);
153+
154+ reply_text(c, hdr, HTTP_200, "ok\n");
155+ return 0;
156+ }
157+
158 if (strncmp(path, "/stream/", 8) == 0) {
159 LOG(verbose_log, "HTTP", "Route /stream");
160
161@@ -667,17 +760,22 @@ int http_handle(int c)
162 return 0;
163 }
164
165- const struct item* it = find_item(id);
166- if (!it) {
167+ char rel[4096];
168+ int fr = item_path_for_id(rel, id, u);
169+ if (fr == 1) {
170 reply_text(c, hdr, HTTP_404, "not found\n");
171 return 0;
172 }
173- if (u && !user_allows_path(u, it->path)) {
174- reply_text(c, hdr, HTTP_404, "not found\n");
175- return 0;
176+ if (fr < 0) {
177+ reply_text(c, hdr, HTTP_500, "server error\n");
178+ return -1;
179 }
180
181- return stream_file(c, it, hdr, head_only);
182+ struct item tmp;
183+ tmp.id = id;
184+ tmp.path = rel;
185+
186+ return stream_file(c, &tmp, hdr, head_only);
187 }
188
189 if (strncmp(path, "/meta/", 6) == 0) {
190@@ -689,26 +787,32 @@ int http_handle(int c)
191 return 0;
192 }
193
194- const struct item* it = find_item(id);
195- if (!it) {
196+ char rel[4096];
197+ int fr = item_path_for_id(rel, id, u);
198+ if (fr == 1) {
199 reply_text(c, hdr, HTTP_404, "not found\n");
200 return 0;
201 }
202- if (u && !user_allows_path(u, it->path)) {
203- reply_text(c, hdr, HTTP_404, "not found\n");
204- return 0;
205+
206+ if (fr < 0) {
207+ reply_text(c, hdr, HTTP_500, "server error\n");
208+ return -1;
209 }
210
211+ struct item tmp;
212+ tmp.id = id;
213+ tmp.path = rel;
214+
215 struct stat st;
216- if (stat_item(it, &st) < 0) {
217+ if (stat_item(&tmp, &st) < 0) {
218 reply_text(c, hdr, HTTP_404, "not found\n");
219 return 0;
220 }
221
222- const char* type = mime_from_path(it->path);
223+ const char* type = mime_from_path(rel);
224
225 struct json j;
226- if (json_meta(&j, it, (size_t)st.st_size, type) < 0) {
227+ if (json_meta(&j, &tmp, (size_t)st.st_size, type) < 0) {
228 reply_text(c, hdr, HTTP_500, "json failed\n");
229 return -1;
230 }
+1,
-0
1@@ -5,6 +5,7 @@
2 #define HTTP_204 "HTTP/1.1 204 No Content\r\n"
3 #define HTTP_206 "HTTP/1.1 206 Partial Content\r\n"
4 #define HTTP_400 "HTTP/1.1 400 Bad Request\r\n"
5+#define HTTP_403 "HTTP/1.1 403 Forbidden\r\n"
6 #define HTTP_404 "HTTP/1.1 404 Not Found\r\n"
7 #define HTTP_405 "HTTP/1.1 405 Method Not Allowed\r\n"
8 #define HTTP_416 "HTTP/1.1 416 Range Not Satisfiable\r\n"
+1,
-0
1@@ -17,6 +17,7 @@ struct library {
2
3 void scan_library_free(struct library* l);
4 int scan_library(struct library* l, const char* root);
5+int scan_library_rescan(struct library* l, const char* root);
6
7 #endif /* SCAN_H */
8
+1,
-0
1@@ -50,6 +50,7 @@ void setup(void);
2 static sem_t slots;
3 static int sock;
4 struct library lib;
5+pthread_rwlock_t lib_lock = PTHREAD_RWLOCK_INITIALIZER;
6
7 static void apply_rlimits(void)
8 {
+26,
-0
1@@ -1,3 +1,4 @@
2+#include <pthread.h>
3 #include <stdbool.h>
4 #include <stdint.h>
5 #include <stdio.h>
6@@ -142,3 +143,28 @@ void scan_library_free(struct library* l)
7 l->cap = 0;
8 }
9
10+int scan_library_rescan(struct library* l, const char* root)
11+{
12+ struct library new;
13+ struct library old;
14+
15+ if (!l || !root)
16+ return -1;
17+
18+ memset(&new, 0, sizeof(new));
19+
20+ if (scan_library(&new, root) < 0) {
21+ scan_library_free(&new);
22+ return -1;
23+ }
24+
25+ /* swap */
26+ old = *l;
27+ *l = new;
28+
29+ /* free old */
30+ scan_library_free(&old);
31+
32+ return 0;
33+}
34+