stream.c (6102B)
1 /*
2 * Copyright (c) 2014 Joris Vink <joris@coders.se>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <sys/param.h>
18 #include <sys/stat.h>
19 #include <sys/mman.h>
20 #include <sys/queue.h>
21
22 #include <fcntl.h>
23 #include <stdint.h>
24
25 #include <kore/kore.h>
26 #include <kore/http.h>
27
28 #include "assets.h"
29
30 struct video {
31 int fd;
32 int ref;
33 off_t size;
34 char *path;
35 u_int8_t *data;
36 void *base;
37
38 TAILQ_ENTRY(video) list;
39 };
40
41 int init(int);
42 int video_stream(struct http_request *);
43
44 static void video_unmap(struct video *);
45 static int video_stream_finish(struct netbuf *);
46 static int video_mmap(struct http_request *, struct video *);
47 static int video_open(struct http_request *, struct video **);
48
49 TAILQ_HEAD(, video) videos;
50
51 int
52 init(int state)
53 {
54 if (state == KORE_MODULE_UNLOAD) {
55 kore_log(LOG_NOTICE, "not reloading module");
56 return (KORE_RESULT_ERROR);
57 }
58
59 TAILQ_INIT(&videos);
60 return (KORE_RESULT_OK);
61 }
62
63 int
64 video_stream(struct http_request *req)
65 {
66 struct video *v;
67 const char *header;
68 off_t start, end;
69 int n, err, status;
70 char *bytes, *range[3], rb[128], *ext, ctype[32];
71
72 if (!video_open(req, &v))
73 return (KORE_RESULT_OK);
74
75 if ((ext = strrchr(req->path, '.')) == NULL) {
76 v->ref--;
77 http_response(req, 400, NULL, 0);
78 return (KORE_RESULT_OK);
79 }
80
81 if (!kore_snprintf(ctype, sizeof(ctype), NULL, "video/%s", ext + 1)) {
82 v->ref--;
83 http_response(req, 500, NULL, 0);
84 return (KORE_RESULT_OK);
85 }
86
87 kore_log(LOG_NOTICE, "%p: opened %s (%s) for streaming (%jd ref:%d)",
88 (void *)req->owner, v->path, ctype, (intmax_t)v->size, v->ref);
89
90 if (http_request_header(req, "range", &header)) {
91 if ((bytes = strchr(header, '=')) == NULL) {
92 v->ref--;
93 http_response(req, 416, NULL, 0);
94 return (KORE_RESULT_OK);
95 }
96
97 bytes++;
98 n = kore_split_string(bytes, "-", range, 3);
99 if (n == 0) {
100 v->ref--;
101 http_response(req, 416, NULL, 0);
102 return (KORE_RESULT_OK);
103 }
104
105 if (n >= 1) {
106 start = kore_strtonum64(range[0], 1, &err);
107 if (err != KORE_RESULT_OK) {
108 v->ref--;
109 http_response(req, 416, NULL, 0);
110 return (KORE_RESULT_OK);
111 }
112 }
113
114 if (n > 1) {
115 end = kore_strtonum64(range[1], 1, &err);
116 if (err != KORE_RESULT_OK) {
117 v->ref--;
118 http_response(req, 416, NULL, 0);
119 return (KORE_RESULT_OK);
120 }
121 } else {
122 end = 0;
123 }
124
125 if (end == 0)
126 end = v->size;
127
128 if (start > end || start > v->size || end > v->size) {
129 v->ref--;
130 http_response(req, 416, NULL, 0);
131 return (KORE_RESULT_OK);
132 }
133
134 status = 206;
135 if (!kore_snprintf(rb, sizeof(rb), NULL,
136 "bytes %ld-%ld/%ld", start, end - 1, v->size)) {
137 v->ref--;
138 http_response(req, 500, NULL, 0);
139 return (KORE_RESULT_OK);
140 }
141
142 kore_log(LOG_NOTICE, "%p: %s sending: %jd-%jd/%jd",
143 (void *)req->owner, v->path, (intmax_t)start,
144 (intmax_t)end - 1, (intmax_t)v->size);
145 http_response_header(req, "content-range", rb);
146 } else {
147 start = 0;
148 status = 200;
149 end = v->size;
150 }
151
152 http_response_header(req, "content-type", ctype);
153 http_response_header(req, "accept-ranges", "bytes");
154 http_response_stream(req, status, v->data + start,
155 end - start, video_stream_finish, v);
156
157 return (KORE_RESULT_OK);
158 }
159
160 static int
161 video_open(struct http_request *req, struct video **out)
162 {
163 struct stat st;
164 struct video *v;
165 char fpath[MAXPATHLEN];
166
167 if (!kore_snprintf(fpath, sizeof(fpath), NULL, "videos%s", req->path)) {
168 http_response(req, 500, NULL, 0);
169 return (KORE_RESULT_ERROR);
170 }
171
172 TAILQ_FOREACH(v, &videos, list) {
173 if (!strcmp(v->path, fpath)) {
174 if (video_mmap(req, v)) {
175 *out = v;
176 return (KORE_RESULT_OK);
177 }
178
179 close(v->fd);
180 TAILQ_REMOVE(&videos, v, list);
181 kore_free(v->path);
182 kore_free(v);
183
184 http_response(req, 500, NULL, 0);
185 return (KORE_RESULT_ERROR);
186 }
187 }
188
189 v = kore_malloc(sizeof(*v));
190 v->ref = 0;
191 v->base = NULL;
192 v->data = NULL;
193 v->path = kore_strdup(fpath);
194
195 if ((v->fd = open(fpath, O_RDONLY)) == -1) {
196 kore_free(v->path);
197 kore_free(v);
198
199 if (errno == ENOENT)
200 http_response(req, 404, NULL, 0);
201 else
202 http_response(req, 500, NULL, 0);
203
204 return (KORE_RESULT_ERROR);
205 }
206
207 if (fstat(v->fd, &st) == -1) {
208 close(v->fd);
209 kore_free(v->path);
210 kore_free(v);
211
212 http_response(req, 500, NULL, 0);
213 return (KORE_RESULT_ERROR);
214 }
215
216 v->size = st.st_size;
217 if (!video_mmap(req, v)) {
218 close(v->fd);
219 kore_free(v->path);
220 kore_free(v);
221
222 http_response(req, 500, NULL, 0);
223 return (KORE_RESULT_ERROR);
224 }
225
226 *out = v;
227 TAILQ_INSERT_TAIL(&videos, v, list);
228
229 return (KORE_RESULT_OK);
230 }
231
232 static int
233 video_mmap(struct http_request *req, struct video *v)
234 {
235 if (v->base != NULL && v->data != NULL) {
236 v->ref++;
237 return (KORE_RESULT_OK);
238 }
239
240 v->base = mmap(NULL, v->size, PROT_READ, MAP_SHARED, v->fd, 0);
241 if (v->base == MAP_FAILED)
242 return (KORE_RESULT_ERROR);
243
244 v->ref++;
245 v->data = v->base;
246
247 return (KORE_RESULT_OK);
248 }
249
250 static int
251 video_stream_finish(struct netbuf *nb)
252 {
253 struct video *v = nb->extra;
254
255 v->ref--;
256 kore_log(LOG_NOTICE, "%p: video stream %s done (%zu/%zu ref:%d)",
257 (void *)nb->owner, v->path, nb->s_off, nb->b_len, v->ref);
258
259 if (v->ref == 0)
260 video_unmap(v);
261
262 return (KORE_RESULT_OK);
263 }
264
265 static void
266 video_unmap(struct video *v)
267 {
268 if (munmap(v->base, v->size) == -1) {
269 kore_log(LOG_ERR, "munmap(%s): %s", v->path, errno_s);
270 } else {
271 v->base = NULL;
272 v->data = NULL;
273 kore_log(LOG_NOTICE,
274 "unmapped %s for streaming, no refs left", v->path);
275 }
276 }