1 /*
2 harvid -- http ardour video daemon
3
4 Copyright (C) 2008-2014 Robin Gareus <robin@gareus.org>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <string.h>
23 #include <getopt.h>
24 #include <math.h>
25 #include <sys/stat.h>
26 #include <libgen.h> // basename
27 #include <locale.h>
28
29 #include "daemon_log.h"
30 #include "daemon_util.h"
31 #include "socket_server.h"
32
33 #include <harvid.h>
34 #include "image_format.h"
35 #include "enums.h"
36
37 #include "ffcompat.h"
38
39 #ifndef HAVE_WINDOWS
40 #include <arpa/inet.h> // inet_addr
41 #include <sys/mman.h> // memlock
42 #endif
43
44 #ifndef DEFAULT_PORT
45 #define DEFAULT_PORT 1554
46 #endif
47
48 char *str_escape(const char *string, int inlength, const char esc); // defined in fileindex.c
49
50 extern int debug_level;
51 extern int debug_section;
52
53 char *program_name;
54 int want_quiet = 0;
55 int want_verbose = 0;
56 int cfg_daemonize = 0;
57 int cfg_syslog = 0;
58 int cfg_memlock = 0;
59 int cfg_timeout = 0;
60 int cfg_usermask = USR_INDEX;
61 int cfg_adminmask = ADM_FLUSHCACHE;
62 char *cfg_logfile = NULL;
63 char *cfg_chroot = NULL;
64 char *cfg_username = NULL;
65 char *cfg_groupname = NULL;
66 int initial_cache_size = 128;
67 int max_decoder_threads = 8;
68 unsigned short cfg_port = DEFAULT_PORT;
69 unsigned int cfg_host = 0; /* = htonl(INADDR_ANY) */
70
printversion(void)71 static void printversion (void) {
72 printf ("harvid %s\n", ICSVERSION);
73 printf ("Compiled with %s %s %s\n\n", LIBAVFORMAT_IDENT, LIBAVCODEC_IDENT, LIBAVUTIL_IDENT);
74 printf ("Copyright (C) GPL 2002-2013 Robin Gareus <robin@gareus.org>\n");
75 }
76
usage(int status)77 static void usage (int status) {
78 printf ("%s - http ardour video server\n\n", basename(program_name));
79 printf ("Usage: %s [OPTION] [document-root]\n", program_name);
80 printf ("\n"
81 "Options:\n"
82 " -A <cmdlist>, --admin <cmdlist>\n"
83 " space separated list of allowed admin commands.\n"
84 " An exclamation-mark before a command disables it.\n"
85 " default: 'flush_cache';\n"
86 " available: flush_cache, purge_cache, shutdown\n"
87 " -c <path>, --chroot <path>\n"
88 " change system root - jails server to this path\n"
89 " -C <frames> set initial frame-cache size (default: 128)\n"
90 " -D, --daemonize fork into background and detach from TTY\n"
91 " -g <name>, --groupname <name>\n"
92 " assume this user-group\n"
93 " -h, --help display this help and exit\n"
94 " -F <feat>, --features <feat>\n"
95 " space separated list of optional features.\n"
96 " An exclamation-mark before a features disables it.\n"
97 " default: 'index';\n"
98 " available: index, seek, flatindex, keepraw\n"
99 " -l <path>, --logfile <path>\n"
100 " specify file for log messages\n"
101 " -M, --memlock attempt to lock memory (prevent cache paging)\n"
102 " -p <num>, --port <num> TCP port to listen on (default %i)\n"
103 " -P <listenaddr> IP address to listen on (default 0.0.0.0)\n"
104 " -q, --quiet, --silent inhibit usual output (may be used thrice)\n"
105 " -s, --syslog send messages to syslog\n"
106 " -t <thread-limit> set maximum decoder-threads (default: 8)\n"
107 " -T <sec>, --timeout <secs>\n"
108 " set a timeout after which the server will\n"
109 " terminate if no new request arrives\n"
110 " -u <name>, --username <name>\n"
111 " server will act as this user\n"
112 " -v, --verbose print more information (may be used twice)\n"
113 " -V, --version print version information and exit\n"
114 "\n"
115 "The default document-root (if unspecified) is the system root: / or C:\\.\n"
116 "\n"
117 "If both syslog and logfile are given that last specified option will be used.\n"
118 "\n"
119 "--verbose and --quiet are additive. The default is to print warnings\n"
120 "and above only. Available log-levels are 'mute', 'critical, 'error',\n"
121 "'warning' and 'info'.\n"
122 "\n"
123 "The --features option allows one to enable or disable /seek and /index\n"
124 "http-handlers and change their respone: The 'flatindex' option concerns\n"
125 "/index&flatindex=1 which recursively indexes all video file. It is disabled\n"
126 "by defaults since a recursive search of the default docroot / can takes a\n"
127 "very long time.\n"
128 "When requesting png or jpeg images harvid decodes a raw RGB frame and then\n"
129 "encodes it again. If 'keepraw' feature is enabled, both the raw RGB and\n"
130 "encoded image are kept in cache. The default is to invaldate the RGB frame\n"
131 "after encoding the image.\n"
132 "\n"
133 "Examples:\n"
134 "harvid -A '!flush_cache purge_cache shutdown' -C 256 /tmp/\n"
135 "\n"
136 "sudo harvid -c /tmp/ -u nobody -g nogroup /\n"
137 "\n"
138 "Report bugs to <robin@gareus.org> or https://github.com/x42/harvid/issues\n"
139 "Website http://x42.github.com/harvid/\n"
140 , DEFAULT_PORT
141 );
142 exit (status);
143 }
144
145 static struct option const long_options[] =
146 {
147 {"admin", required_argument, 0, 'A'},
148 {"chroot", required_argument, 0, 'c'},
149 {"cache-size", required_argument, 0, 'C'},
150 {"debug", required_argument, 0, 'd'},
151 {"daemonize", no_argument, 0, 'D'},
152 {"groupname", required_argument, 0, 'g'},
153 {"help", no_argument, 0, 'h'},
154 {"features", required_argument, 0, 'F'},
155 {"logfile", required_argument, 0, 'l'},
156 {"memlock", no_argument, 0, 'M'},
157 {"port", required_argument, 0, 'p'},
158 {"listenip", required_argument, 0, 'P'},
159 {"quiet", no_argument, 0, 'q'},
160 {"silent", no_argument, 0, 'q'},
161 {"syslog", no_argument, 0, 's'},
162 {"timeout", required_argument, 0, 'T'},
163 {"username", required_argument, 0, 'u'},
164 {"verbose", no_argument, 0, 'v'},
165 {"version", no_argument, 0, 'V'},
166 {NULL, 0, NULL, 0}
167 };
168
169
170 /* Set all the option flags according to the switches specified.
171 Return the index of the first non-option argument. */
decode_switches(int argc,char ** argv)172 static int decode_switches (int argc, char **argv) {
173 int c;
174 while ((c = getopt_long (argc, argv,
175 "A:" /* admin */
176 "c:" /* chroot-dir */
177 "C:" /* initial cache size */
178 "d:" /* debug */
179 "D" /* daemonize */
180 "g:" /* setGroup */
181 "h" /* help */
182 "F:" /* interaction */
183 "l:" /* logfile */
184 "M" /* memlock */
185 "p:" /* port */
186 "P:" /* IP */
187 "q" /* quiet or silent */
188 "s" /* syslog */
189 "t:" /* threads */
190 "T:" /* timeout */
191 "u:" /* setUser */
192 "v" /* verbose */
193 "V", /* version */
194 long_options, (int *) 0)) != EOF)
195 {
196 switch (c) {
197 case 'q': /* --quiet, --silent */
198 want_quiet = 1;
199 want_verbose = 0;
200 if (debug_level == DLOG_CRIT)
201 debug_level = DLOG_EMERG;
202 else if (debug_level == DLOG_ERR)
203 debug_level = DLOG_CRIT;
204 else
205 debug_level=DLOG_ERR;
206 break;
207 case 'v': /* --verbose */
208 if (debug_level == DLOG_INFO) want_verbose = 1;
209 debug_level=DLOG_INFO;
210 break;
211
212 case 'A': /* --admin */
213 if (strstr(optarg, "shutdown")) cfg_adminmask|=ADM_SHUTDOWN;
214 if (strstr(optarg, "purge_cache")) cfg_adminmask|=ADM_PURGECACHE;
215 if (strstr(optarg, "flush_cache")) cfg_adminmask|=ADM_FLUSHCACHE;
216 if (strstr(optarg, "!shutdown")) cfg_adminmask&=~ADM_SHUTDOWN;
217 if (strstr(optarg, "!purge_cache")) cfg_adminmask&=~ADM_PURGECACHE;
218 if (strstr(optarg, "!flush_cache")) cfg_adminmask&=~ADM_FLUSHCACHE;
219 break;
220 case 'c': /* --chroot */
221 cfg_chroot = optarg;
222 break;
223 case 'C':
224 initial_cache_size = atoi(optarg);
225 if (initial_cache_size < 2 || initial_cache_size > 65535)
226 initial_cache_size = 128;
227 break;
228 case 'd': /* --debug */
229 if (strstr(optarg, "SRV")) debug_section|=DEBUG_SRV;
230 if (strstr(optarg, "HTTP")) debug_section|=DEBUG_HTTP;
231 if (strstr(optarg, "CON")) debug_section|=DEBUG_CON;
232 if (strstr(optarg, "DCTL")) debug_section|=DEBUG_DCTL;
233 if (strstr(optarg, "ICS")) debug_section|=DEBUG_ICS;
234 #ifdef NDEBUG
235 fprintf(stderr, "harvid was built with NDEBUG. '-d' has no affect.\n");
236 #endif
237 break;
238 case 'D': /* --daemonize */
239 cfg_daemonize = 1;
240 break;
241 case 'F': /* --features */
242 if (strstr(optarg, "index")) cfg_usermask |= USR_INDEX;
243 if (strstr(optarg, "seek")) cfg_usermask |= USR_WEBSEEK;
244 if (strstr(optarg, "flatindex")) cfg_usermask |= USR_FLATINDEX;
245 if (strstr(optarg, "keepraw")) cfg_usermask |= USR_KEEPRAW;
246 if (strstr(optarg, "!index")) cfg_usermask &= ~USR_INDEX;
247 if (strstr(optarg, "!seek")) cfg_usermask |= USR_WEBSEEK;
248 if (strstr(optarg, "!flatindex")) cfg_usermask &= ~USR_FLATINDEX;
249 if (strstr(optarg, "!keepraw")) cfg_usermask &= ~USR_KEEPRAW;
250 break;
251 case 'g': /* --group */
252 cfg_groupname = optarg;
253 break;
254 case 'l': /* --logfile */
255 cfg_syslog = 0;
256 if (cfg_logfile) free(cfg_logfile);
257 cfg_logfile = strdup(optarg);
258 break;
259 case 'M': /* --memlock */
260 cfg_memlock = 1;
261 break;
262 case 'P': /* --listenip */
263 cfg_host = inet_addr (optarg);
264 break;
265 case 'p': /* --port */
266 {int pn = atoi(optarg);
267 if (pn > 0 && pn < 65536)
268 cfg_port = (unsigned short) atoi(optarg);
269 }
270 break;
271 case 's': /* --syslog */
272 cfg_syslog = 1;
273 if (cfg_logfile) free(cfg_logfile);
274 cfg_logfile = NULL;
275 break;
276 case 't':
277 max_decoder_threads = atoi(optarg);
278 if (max_decoder_threads < 2 || max_decoder_threads > 128)
279 max_decoder_threads = 8;
280 break;
281 case 'T': /* --timeout */
282 cfg_timeout = atoi(optarg);
283 break;
284 case 'u': /* --username */
285 cfg_username = optarg;
286 break;
287 case 'V':
288 printversion();
289 exit(0);
290 case 'h':
291 usage (0);
292 default:
293 usage (1);
294 }
295 }
296 return optind;
297 }
298
299 // -=-=-=-=-=-=- main -=-=-=-=-=-=-
300 #include "ffcompat.h"
301
302 void *dc = NULL; // decoder control
303 void *vc = NULL; // video frame cache
304 void *ic = NULL; // encoded image cache
305
main(int argc,char ** argv)306 int main (int argc, char **argv) {
307 program_name = argv[0];
308 struct stat sb;
309 uid_t cfg_uid;
310 gid_t cfg_gid;
311 #ifdef HAVE_WINDOWS
312 char *docroot = "C:\\";
313 #else
314 char *docroot = "/";
315 #endif
316 debug_level = DLOG_WARNING;
317 int exitstatus = 0;
318
319 // TODO read rc file
320
321 int i = decode_switches (argc, argv);
322
323 if ((i+1)== argc) docroot = argv[i];
324 else if (docroot && i==argc) ; // use default
325 else usage(1);
326
327 // TODO read additional rc file (from options)
328
329 if (cfg_daemonize && !cfg_logfile && !cfg_syslog) {
330 dlog(DLOG_WARNING, "daemonizing without log file or syslog.\n");
331 }
332
333 if (cfg_daemonize || cfg_syslog || cfg_logfile) {
334 /* ffdecoder prints to stdout */
335 want_verbose = 0;
336 want_quiet = 1;
337 }
338
339 /* initialize */
340
341 if (cfg_logfile || cfg_syslog) dlog_open(cfg_logfile);
342
343 /* resolve before doing chroot() */
344 cfg_uid = resolve_uid(cfg_username);
345 cfg_gid = resolve_gid(cfg_groupname);
346
347 if (cfg_chroot) {
348 if (do_chroot(cfg_chroot)) {exitstatus = -1; goto errexit;}
349 }
350
351 #ifdef HAVE_WINDOWS
352 if (strlen (docroot) == 0) {
353 dlog(DLOG_INFO, "No document-root, allowing all drive-letters\n");
354 } else
355 #endif
356 if (stat(docroot, &sb) || !S_ISDIR(sb.st_mode)) {
357 dlog(DLOG_CRIT, "document-root is not a directory\n");
358 exitstatus = -1;
359 goto errexit;
360 }
361
362 if (cfg_daemonize) {
363 if (daemonize()) {exitstatus = -1; goto errexit;}
364 }
365
366 ff_initialize();
367
368 vcache_create(&vc);
369 vcache_resize(&vc, initial_cache_size);
370 icache_create(&ic);
371 icache_resize(ic, initial_cache_size*4);
372 dctrl_create(&dc, max_decoder_threads, initial_cache_size);
373
374 if (cfg_memlock) {
375 #ifndef HAVE_WINDOWS
376 if (mlockall(MCL_CURRENT|MCL_FUTURE)) {
377 dlog(LOG_WARNING, "failed to lock memory.\n");
378 }
379 #else
380 dlog(LOG_WARNING, "memory locking is not available on windows.\n");
381 #endif
382 }
383
384 char const* const current_c_locale = setlocale (LC_NUMERIC, 0);
385 if (strcmp ("C", current_c_locale) != 0) {
386 dlog(DLOG_INFO, "Setting locate to 'C' for portable numerics.\n");
387 setlocale (LC_NUMERIC, "C");
388 }
389
390 /* all systems go */
391
392 dlog(DLOG_INFO, "Initialization complete. Starting server.\n");
393 exitstatus = start_tcp_server(cfg_host, cfg_port, docroot, cfg_uid, cfg_gid, cfg_timeout, NULL);
394
395 /* cleanup */
396
397 ff_cleanup();
398 dctrl_destroy(&dc);
399 vcache_destroy(&vc);
400 icache_destroy(&ic);
401 errexit:
402 dlog_close();
403 return(exitstatus);
404 }
405
406 // -=-=-=-=-=-=- video server callbacks -=-=-=-=-=-=-
407 /*
408 * these are called from protocol_handler() in httprotocol.c
409 */
410 #include "httprotocol.h"
411 #include "ics_handler.h"
412 #include "htmlconst.h"
413
414 #define HPSIZE 4096 // max size of homepage in bytes.
hdl_homepage_html(CONN * c)415 char *hdl_homepage_html (CONN *c) {
416 char *msg = malloc(HPSIZE * sizeof(char));
417 int off = 0;
418 off+=snprintf(msg+off, HPSIZE-off, DOCTYPE HTMLOPEN);
419 off+=snprintf(msg+off, HPSIZE-off, "<title>harvid</title></head>\n");
420 off+=snprintf(msg+off, HPSIZE-off, HTMLBODY);
421 off+=snprintf(msg+off, HPSIZE-off, CENTERDIV);
422 off+=snprintf(msg+off, HPSIZE-off, "<div style=\"text-align:center;\"><h1>Harvid - HTTP Ardour Video Daemon</h1></div>\n");
423 off+=snprintf(msg+off, HPSIZE-off, "<div style=\"float:left;margin:0 2em;\"><h2>Built-in handlers</h2>\n");
424 off+=snprintf(msg+off, HPSIZE-off, "<ul>");
425 if (cfg_usermask & USR_INDEX) {
426 off+=snprintf(msg+off, HPSIZE-off, "<li><a href=\"index/\">File Index</a></li>\n");
427 }
428 off+=snprintf(msg+off, HPSIZE-off, "<li><a href=\"status/\">Server Status</a></li>\n");
429 off+=snprintf(msg+off, HPSIZE-off, "<li><a href=\"rc/\">Server Config</a></li>\n");
430 off+=snprintf(msg+off, HPSIZE-off, "<li><a href=\"version/\">Server Version</a></li>\n");
431 off+=snprintf(msg+off, HPSIZE-off, "</ul></div>");
432
433 if (cfg_adminmask)
434 off+=snprintf(msg+off, HPSIZE-off, "<div style=\"float:right;margin:0 2em;\"><h2>Admin Tasks:</h2><ul>\n");
435 if (cfg_adminmask&ADM_FLUSHCACHE)
436 off+=snprintf(msg+off, HPSIZE-off, "<li><a href=\"admin/flush_cache\">Flush Cache</a></li>\n");
437 if (cfg_adminmask&ADM_PURGECACHE)
438 off+=snprintf(msg+off, HPSIZE-off, "<li><a href=\"admin/purge_cache\">Purge Cache</a></li>\n");
439 if (cfg_adminmask&ADM_SHUTDOWN)
440 off+=snprintf(msg+off, HPSIZE-off, "<li><a href=\"admin/shutdown\">Server Shutdown</a></li>\n");
441 if (cfg_adminmask)
442 off+=snprintf(msg+off, HPSIZE-off, "</ul>\n</div>\n");
443 off+=snprintf(msg+off, HPSIZE-off, "<div style=\"clear:both;\"></div><hr/>\n");
444 off+=snprintf(msg+off, HPSIZE-off, "<p style=\"text-align:justify;\">The default request handler decodes images and requires a <code>?frame=NUM&file=PATH</code> URL query or post parameters. Video frames are counted starting at zero. Default options are <code>w=0&h=0&format=png</code> which serves the image pre-scaled to its effective size as png.</p>\n");
445 off+=snprintf(msg+off, HPSIZE-off, "<p>The <code>/info</code> request handler requires a <code>?file=PATH</code> query parameter and optionally takes a <code>format</code> (default is html). All other handlers (/status, /rc, /version, /admin/) take no arguments.</p>\n");
446 off+=snprintf(msg+off, HPSIZE-off, "<p>Available query parameters: <code>frame</code>, <code>w</code>, <code>h</code>, <code>file</code>, <code>format</code>.</p>\n");
447 off+=snprintf(msg+off, HPSIZE-off, "<p>Frame (frame-number), w (width) and h (height) are unsigned integers.</p>\n");
448 off+=snprintf(msg+off, HPSIZE-off, "<p>Supported image output pixel formats:</p>\n");
449 off+=snprintf(msg+off, HPSIZE-off, "<ul>\n<li><em>Encoded</em>: jpg, jpeg, png, ppm</li>\n");
450 off+=snprintf(msg+off, HPSIZE-off, "<li><em>Raw RGB</em>: rgb, bgr, rgba, argb, bgra</li>\n");
451 off+=snprintf(msg+off, HPSIZE-off, "<li><em>Raw YUV</em>: yuv, yuv420, yuv440, yuv422, uyv422</li>\n</ul>\n");
452 off+=snprintf(msg+off, HPSIZE-off, "<p>Available info output formats:</p>\n");
453 off+=snprintf(msg+off, HPSIZE-off, "<ul>\n<li><em>Human Readable</em>: html, xhtml</li>\n");
454 off+=snprintf(msg+off, HPSIZE-off, "<li><em>Machine Readable</em>: json, csv, plain</li>\n</ul>\n");
455 off+=snprintf(msg+off, HPSIZE-off, "<p style=\"text-align:justify;\">The jpg (and jpeg) <em>format</em> parameter can be postfixed number to specify the jpeg quality. e.g. <code>&format=jpeg90</code>. The default is 75. Note that 'jpg' is just an alias for 'jpeg', and 'html' is an alias for 'xhtml'.</p>\n");
456 off+=snprintf(msg+off, HPSIZE-off, "<p style=\"text-align:justify;\">If either only <em>width</em> or <em>height</em> is specified with a value greater than 15, the other is calculated according to the movie's effective aspect-ratio. However the minimum size is 16x16, requesting geometries smaller than 16x16 will return the image in its original size.</p>\n");
457 off+=snprintf(msg+off, HPSIZE-off, "<p style=\"text-align:center\"><a href=\"http://x42.github.com/harvid/\">harvid @ GitHub</a></p>\n");
458 off+=snprintf(msg+off, HPSIZE-off, "</div>\n");
459 off+=snprintf(msg+off, HPSIZE-off, HTMLFOOTER, c->d->local_addr, c->d->local_port);
460 off+=snprintf(msg+off, HPSIZE-off, "\n</body>\n</html>");
461 return msg;
462 }
463
hdl_server_status_html(CONN * c)464 char *hdl_server_status_html (CONN *c) {
465 size_t ss = 1024;
466 size_t off = 0;
467 char *sm = malloc(ss * sizeof(char));
468 raprintf(sm, off, ss, DOCTYPE HTMLOPEN);
469 raprintf(sm, off, ss, "<title>harvid status</title>\n");
470 raprintf(sm, off, ss, "<style type=\"text/css\">\ntd.left{text-align:left;} td.line{border-bottom: 1px solid black;} td.dline{border-top: 3px double black;} td h3 {margin: 1em 0 0 0;}\n</style>\n");
471 raprintf(sm, off, ss, "</head>\n");
472 raprintf(sm, off, ss, HTMLBODY);
473 raprintf(sm, off, ss, "<h2>harvid status</h2>\n");
474 raprintf(sm, off, ss, "<!--status: ok, online.-->\n"); // ardour3 reads this
475 raprintf(sm, off, ss, "<p>Concurrent connections: (current / max-seen / limit) %d / %d / %d</p>\n", c->d->num_clients, c->d->max_clients, MAXCONNECTIONS);
476 #ifdef USAGE_FREQUENCY_STATISTICS
477 time_t i;
478 const time_t n = time(NULL);
479 double avg1, avg5, avgA;
480 avg1 = avg5 = avgA = 0.0;
481 for (i = 0; i < FREQ_LEN; ++i) { avgA += c->d->req_stats[i]; }
482 for (i = 0; i < 60; ++i) { avg1 += c->d->req_stats[(n-i)%FREQ_LEN]; }
483 for (i = 0; i < 300; ++i) { avg5 += c->d->req_stats[(n-i)%FREQ_LEN]; }
484 avgA /= (double) FREQ_LEN;
485 avg1 /= 60.0;
486 avg5 /= 300.0;
487 long int uptime = (long int) (n - c->d->stat_start);
488 raprintf(sm, off, ss, "<p>Requests/sec: (1min avg / 5min avg / 1h avg / all time) %.2f / %.2f / %.2f / %.3f</p>\n",
489 avg1, avg5, avgA, c->d->stat_count / difftime(n, c->d->stat_start));
490 raprintf(sm, off, ss, "<p>Total requests: %d, uptime: %ld day%s, %02ld:%02ld:%02ld</p>\n",
491 c->d->stat_count, uptime / 86400, (uptime / 86400) == 1 ? "": "s", (uptime % 86400) / 3600, (uptime % 3600) / 60, uptime %60);
492 #endif
493 dctrl_info_html(dc, &sm, &off, &ss, 2);
494 vcache_info_html(vc, &sm, &off, &ss, 0);
495 icache_info_html(ic, &sm, &off, &ss, 2);
496 raprintf(sm, off, ss, HTMLFOOTER, c->d->local_addr, c->d->local_port);
497 raprintf(sm, off, ss, "</body>\n</html>");
498 return (sm);
499 }
500
501 #define NFOSIZ (256)
file_info_json(CONN * c,ics_request_args * a,VInfo * ji)502 static char *file_info_json (CONN *c, ics_request_args *a, VInfo *ji) {
503 char *im = malloc(NFOSIZ * sizeof(char));
504 int off = 0;
505 off+=snprintf(im+off, NFOSIZ-off, "{");
506 //off+=snprintf(im+off, NFOSIZ-off, "\"geometry\":[%i,%i],", ji->movie_width, ji->movie_height);
507 off+=snprintf(im+off, NFOSIZ-off, "\"width\":%i", ji->movie_width);
508 off+=snprintf(im+off, NFOSIZ-off, ",\"height\":%i", ji->movie_height);
509 off+=snprintf(im+off, NFOSIZ-off, ",\"aspect\":%.3f", ji->movie_aspect);
510 off+=snprintf(im+off, NFOSIZ-off, ",\"framerate\":%.3f", timecode_rate_to_double(&ji->framerate));
511 off+=snprintf(im+off, NFOSIZ-off, ",\"duration\":%"PRId64, ji->frames);
512 off+=snprintf(im+off, NFOSIZ-off, "}");
513 jvi_free(ji);
514 return (im);
515 }
516
file_info_csv(CONN * c,ics_request_args * a,VInfo * ji)517 static char *file_info_csv (CONN *c, ics_request_args *a, VInfo *ji) {
518 char *im = malloc(NFOSIZ * sizeof(char));
519 int off = 0;
520 off+=snprintf(im+off, NFOSIZ-off, "1"); // FORMAT VERSION
521 off+=snprintf(im+off, NFOSIZ-off, ",%i", ji->movie_width);
522 off+=snprintf(im+off, NFOSIZ-off, ",%i", ji->movie_height);
523 off+=snprintf(im+off, NFOSIZ-off, ",%f", ji->movie_aspect);
524 off+=snprintf(im+off, NFOSIZ-off, ",%.3f", timecode_rate_to_double(&ji->framerate));
525 off+=snprintf(im+off, NFOSIZ-off, ",%"PRId64, ji->frames);
526 off+=snprintf(im+off, NFOSIZ-off, "\n");
527 jvi_free(ji);
528 return (im);
529 }
530
531 #define FIHSIZ 8192
file_info_html(CONN * c,ics_request_args * a,VInfo * ji)532 static char *file_info_html (CONN *c, ics_request_args *a, VInfo *ji) {
533 char *im = malloc(FIHSIZ * sizeof(char));
534 int off = 0;
535 char smpte[14], *tmp;
536 timecode_framenumber_to_string(smpte, &ji->framerate, ji->frames);
537
538 off+=snprintf(im+off, FIHSIZ-off, DOCTYPE HTMLOPEN);
539 off+=snprintf(im+off, FIHSIZ-off, "<title>harvid file info</title></head>\n");
540 off+=snprintf(im+off, FIHSIZ-off, HTMLBODY);
541 off+=snprintf(im+off, FIHSIZ-off, CENTERDIV);
542 off+=snprintf(im+off, FIHSIZ-off, "<h2>File info</h2>\n\n");
543 tmp = url_escape(a->file_qurl, 0);
544 if (cfg_usermask & USR_WEBSEEK) {
545 off+=snprintf(im+off, FIHSIZ-off, "<p>File: <a href=\"/seek?frame=0&file=%s\">%s</a></p><ul>\n", tmp, a->file_qurl); free(tmp);
546 } else {
547 off+=snprintf(im+off, FIHSIZ-off, "<p>File: <a href=\"/?frame=0&file=%s\">%s</a></p><ul>\n", tmp, a->file_qurl); free(tmp);
548 }
549 off+=snprintf(im+off, FIHSIZ-off, "<li>Geometry: %ix%i</li>\n", ji->movie_width, ji->movie_height);
550 off+=snprintf(im+off, FIHSIZ-off, "<li>Aspect-Ratio: %.3f</li>\n", ji->movie_aspect);
551 off+=snprintf(im+off, FIHSIZ-off, "<li>Framerate: %.2f</li>\n", timecode_rate_to_double(&ji->framerate));
552 off+=snprintf(im+off, FIHSIZ-off, "<li>Duration: %s</li>\n", smpte);
553 off+=snprintf(im+off, FIHSIZ-off, "<li>Duration: %.2f sec</li>\n", (double)ji->frames/timecode_rate_to_double(&ji->framerate));
554 off+=snprintf(im+off, FIHSIZ-off, "<li>Duration: %"PRId64" frames</li>\n", ji->frames);
555 off+=snprintf(im+off, FIHSIZ-off, "</ul>\n</div>\n");
556 off+=snprintf(im+off, FIHSIZ-off, HTMLFOOTER, c->d->local_addr, c->d->local_port);
557 off+=snprintf(im+off, FIHSIZ-off, "</body>\n</html>");
558 jvi_free(ji);
559 return (im);
560 }
561
file_info_raw(CONN * c,ics_request_args * a,VInfo * ji)562 static char *file_info_raw (CONN *c, ics_request_args *a, VInfo *ji) {
563 char *im = malloc(NFOSIZ * sizeof(char));
564 int off = 0;
565 char smpte[14];
566 timecode_framenumber_to_string(smpte, &ji->framerate, ji->frames);
567
568 off+=snprintf(im+off, NFOSIZ-off, "1\n"); // FORMAT VERSION
569 off+=snprintf(im+off, NFOSIZ-off, "%.3f\n", timecode_rate_to_double(&ji->framerate)); // fps
570 off+=snprintf(im+off, NFOSIZ-off, "%"PRId64"\n", ji->frames); // duration
571 off+=snprintf(im+off, NFOSIZ-off, "0.0\n"); // start-offset TODO
572 off+=snprintf(im+off, NFOSIZ-off, "%f\n", ji->movie_aspect);
573 jvi_free(ji);
574 return (im);
575 }
576
hdl_file_info(CONN * c,ics_request_args * a)577 char *hdl_file_info (CONN *c, ics_request_args *a) {
578 VInfo ji;
579 unsigned short vid;
580 int err = 0;
581 vid = dctrl_get_id(vc, dc, a->file_name);
582 jvi_init(&ji);
583 if ((err=dctrl_get_info(dc, vid, &ji))) {
584 if (err == 503) {
585 httperror(c->fd, 503, "Service Temporarily Unavailable", "<p>No decoder is available. The server is currently busy or overloaded.</p>");
586 } else {
587 httperror(c->fd, 500, "Service Unavailable", "<p>No decoder is available: File is invalid (no video track, unknown codec, invalid geometry,..)</p>");
588 }
589 return NULL;
590 }
591 switch (a->render_fmt) {
592 case OUT_PLAIN:
593 return file_info_raw(c, a, &ji);
594 case OUT_JSON:
595 return file_info_json(c, a, &ji);
596 case OUT_CSV:
597 return file_info_csv(c, a, &ji);
598 default:
599 return file_info_html(c, a, &ji);
600 }
601 }
602
603 /////////////
604
605 #define SINFOSIZ 2048
hdl_server_info(CONN * c,ics_request_args * a)606 char *hdl_server_info (CONN *c, ics_request_args *a) {
607 char *info = malloc(SINFOSIZ * sizeof(char));
608 int off = 0;
609 switch (a->render_fmt) {
610 case OUT_PLAIN:
611 off+=snprintf(info+off, SINFOSIZ-off, "%s\n", c->d->docroot);
612 off+=snprintf(info+off, SINFOSIZ-off, "%s\n", c->d->local_addr);
613 off+=snprintf(info+off, SINFOSIZ-off, "%d\n", c->d->local_port);
614 off+=snprintf(info+off, SINFOSIZ-off, "%d\n", initial_cache_size);
615 break;
616 case OUT_JSON:
617 {
618 char *tmp;
619 off+=snprintf(info+off, SINFOSIZ-off, "{");
620 tmp = str_escape(c->d->docroot, 0, '\\');
621 off+=snprintf(info+off, SINFOSIZ-off, "\"docroot\":\"%s\"", tmp); free(tmp);
622 off+=snprintf(info+off, SINFOSIZ-off, ",\"listenaddr\":\"%s\"", c->d->local_addr);
623 off+=snprintf(info+off, SINFOSIZ-off, ",\"listenport\":%d", c->d->local_port);
624 off+=snprintf(info+off, SINFOSIZ-off, ",\"cachesize\":%d", initial_cache_size);
625 off+=snprintf(info+off, SINFOSIZ-off, ",\"infohandlers\":[\"/info\", \"/rc\", \"/status\", \"/version\"%s\"",
626 cfg_usermask & USR_INDEX ? ",\"index\"":"");
627 off+=snprintf(info+off, SINFOSIZ-off, ",\"admintasks\":[\"/check\"%s%s%s]",
628 (cfg_adminmask & ADM_FLUSHCACHE) ? ",\"/flush_cache\"" : "",
629 (cfg_adminmask & ADM_PURGECACHE) ? ",\"/purge_cache\"" : "",
630 (cfg_adminmask & ADM_SHUTDOWN) ? ",\"/shutdown\"" : ""
631 );
632 off+=snprintf(info+off, SINFOSIZ-off, "}");
633 }
634 break;
635 case OUT_CSV:
636 {
637 char *tmp = str_escape(c->d->docroot, 0, '"');
638 off+=snprintf(info+off, SINFOSIZ-off, "\"%s\"", tmp); free(tmp);
639 off+=snprintf(info+off, SINFOSIZ-off, ",%s", c->d->local_addr);
640 off+=snprintf(info+off, SINFOSIZ-off, ",%d", c->d->local_port);
641 off+=snprintf(info+off, SINFOSIZ-off, ",%d", initial_cache_size);
642 off+=snprintf(info+off, SINFOSIZ-off, ",\"/info /rc /status /version%s\"",
643 cfg_usermask & USR_INDEX ? " index":"");
644 off+=snprintf(info+off, SINFOSIZ-off, ",\"/check%s%s%s\"",
645 (cfg_adminmask & ADM_FLUSHCACHE) ? " /flush_cache" : "",
646 (cfg_adminmask & ADM_PURGECACHE) ? " /purge_cache" : "",
647 (cfg_adminmask & ADM_SHUTDOWN) ? " /shutdown" : ""
648 );
649 off+=snprintf(info+off, SINFOSIZ-off, "\n");
650 }
651 break;
652 default: // HTML
653 off+=snprintf(info+off, SINFOSIZ-off, DOCTYPE HTMLOPEN);
654 off+=snprintf(info+off, SINFOSIZ-off, "<title>harvid server info</title></head>\n");
655 off+=snprintf(info+off, SINFOSIZ-off, HTMLBODY);
656 off+=snprintf(info+off, SINFOSIZ-off, CENTERDIV);
657 off+=snprintf(info+off, SINFOSIZ-off, "<h2>harvid server info</h2>\n\n");
658 off+=snprintf(info+off, SINFOSIZ-off, "<ul>\n");
659 off+=snprintf(info+off, SINFOSIZ-off, "<li>Docroot: %s</li>\n", c->d->docroot);
660 off+=snprintf(info+off, SINFOSIZ-off, "<li>ListenAddr: %s</li>\n", c->d->local_addr);
661 off+=snprintf(info+off, SINFOSIZ-off, "<li>ListenPort: %d</li>\n", c->d->local_port);
662 off+=snprintf(info+off, SINFOSIZ-off, "<li>CacheSize: %d</li>\n", initial_cache_size);
663 off+=snprintf(info+off, SINFOSIZ-off, "<li>File Index: %s</li>\n", cfg_usermask & USR_INDEX ? "Yes" : "No");
664 off+=snprintf(info+off, SINFOSIZ-off, "<li>Admin-task(s): /check%s%s%s</li>\n",
665 (cfg_adminmask & ADM_FLUSHCACHE) ? " /flush_cache" : "",
666 (cfg_adminmask & ADM_PURGECACHE) ? " /purge_cache" : "",
667 (cfg_adminmask & ADM_SHUTDOWN) ? " /shutdown" : ""
668 );
669 #ifndef NDEBUG // possibly sensitive information
670 off+=snprintf(info+off, SINFOSIZ-off, "<li>Memlock: %s</li>\n", cfg_memlock ? "Yes" : "No");
671 off+=snprintf(info+off, SINFOSIZ-off, "<li>Daemonized: %s</li>\n", cfg_daemonize ? "Yes" : "No");
672 off+=snprintf(info+off, SINFOSIZ-off, "<li>Chroot: %s</li>\n", cfg_chroot ? cfg_chroot : "-");
673 off+=snprintf(info+off, SINFOSIZ-off, "<li>SetUid/Gid: %s/%s</li>\n",
674 cfg_username ? cfg_username : "-", cfg_groupname ? cfg_groupname : "-");
675 off+=snprintf(info+off, SINFOSIZ-off, "<li>Log: %s</li>\n",
676 cfg_syslog ? "(syslog)" : (cfg_logfile ? cfg_logfile : "(stdout)"));
677 off+=snprintf(info+off, SINFOSIZ-off, "<li>Loglevel: %s</li>\n", dlog_level_name(debug_level));
678 off+=snprintf(info+off, SINFOSIZ-off, "<li>AVLog (stdout): %s</li>\n",
679 want_quiet ? "quiet" : want_verbose ? "verbose" :"error");
680 #endif
681 off+=snprintf(info+off, SINFOSIZ-off, "</ul>\n</div>\n");
682 off+=snprintf(info+off, SINFOSIZ-off, HTMLFOOTER, c->d->local_addr, c->d->local_port);
683 off+=snprintf(info+off, SINFOSIZ-off, "</body>\n</html>");
684 break;
685 }
686 return info;
687 }
688
hdl_server_version(CONN * c,ics_request_args * a)689 char *hdl_server_version (CONN *c, ics_request_args *a) {
690 char *info = malloc(SINFOSIZ * sizeof(char));
691 int off = 0;
692 switch (a->render_fmt) {
693 case OUT_PLAIN:
694 off+=snprintf(info+off, SINFOSIZ-off, "%s\n", SERVERVERSION);
695 off+=snprintf(info+off, SINFOSIZ-off, "%s %s %s\n", LIBAVFORMAT_IDENT, LIBAVCODEC_IDENT, LIBAVUTIL_IDENT);
696 break;
697 case OUT_JSON:
698 off+=snprintf(info+off, SINFOSIZ-off, "{");
699 off+=snprintf(info+off, SINFOSIZ-off, "\"version\":\"%s\"", ICSVERSION);
700 off+=snprintf(info+off, SINFOSIZ-off, ",\"os\":\"%s\"", ICSARCH);
701 #ifdef NDEBUG
702 off+=snprintf(info+off, SINFOSIZ-off, ",\"debug\":false");
703 #else
704 off+=snprintf(info+off, SINFOSIZ-off, ",\"debug\":true");
705 #endif
706 off+=snprintf(info+off, SINFOSIZ-off, ",\"ffmpeg\":[\"%s\",\"%s\",\"%s\"]",
707 LIBAVFORMAT_IDENT, LIBAVCODEC_IDENT, LIBAVUTIL_IDENT);
708 off+=snprintf(info+off, SINFOSIZ-off, "}");
709 break;
710 case OUT_CSV:
711 off+=snprintf(info+off, SINFOSIZ-off, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
712 ICSVERSION, SERVERVERSION, LIBAVFORMAT_IDENT, LIBAVCODEC_IDENT, LIBAVUTIL_IDENT);
713 break;
714 default: // HTML
715 off+=snprintf(info+off, SINFOSIZ-off, DOCTYPE HTMLOPEN);
716 off+=snprintf(info+off, SINFOSIZ-off, "<title>harvid server version</title></head>\n");
717 off+=snprintf(info+off, SINFOSIZ-off, HTMLBODY);
718 off+=snprintf(info+off, SINFOSIZ-off, CENTERDIV);
719 off+=snprintf(info+off, SINFOSIZ-off, "<h2>harvid server version</h2>\n\n");
720 off+=snprintf(info+off, SINFOSIZ-off, "<ul>\n");
721 off+=snprintf(info+off, SINFOSIZ-off, "<li>Version: %s</li>\n", ICSVERSION);
722 off+=snprintf(info+off, SINFOSIZ-off, "<li>Operating System: %s</li>\n", ICSARCH);
723 #ifdef NDEBUG
724 off+=snprintf(info+off, SINFOSIZ-off, "<li>Debug enabled: No</li>\n");
725 #else
726 off+=snprintf(info+off, SINFOSIZ-off, "<li>Debug enabled: Yes</li>\n");
727 #endif
728 off+=snprintf(info+off, SINFOSIZ-off, "<li>ffmpeg: %s %s %s</li>\n", LIBAVFORMAT_IDENT, LIBAVCODEC_IDENT, LIBAVUTIL_IDENT);
729 off+=snprintf(info+off, SINFOSIZ-off, "\n</ul>\n</div>\n");
730 off+=snprintf(info+off, SINFOSIZ-off, HTMLFOOTER, c->d->local_addr, c->d->local_port);
731 off+=snprintf(info+off, SINFOSIZ-off, "</body>\n</html>");
732 break;
733 }
734 return info;
735 }
736
737 /////////////
738
hdl_decode_frame(int fd,httpheader * h,ics_request_args * a)739 int hdl_decode_frame(int fd, httpheader *h, ics_request_args *a) {
740 VInfo ji;
741 unsigned short vid;
742 void *cptr = NULL;
743 uint8_t *optr = NULL;
744 size_t olen = 0;
745 uint8_t *bptr = NULL;
746 int err = 0;
747
748 vid = dctrl_get_id(vc, dc, a->file_name);
749 jvi_init(&ji);
750
751 if (a->frame < 0) a->frame = 0; // return error instead?
752 if (a->out_width < 0 || a->out_width > 16384) a->out_width = 0;
753 if (a->out_height < 0 || a->out_height > 16384) a->out_height = 0;
754
755 /* get canonical output width/height and corresponding buffersize */
756 if ((err=dctrl_get_info_scale(dc, vid, &ji, a->out_width, a->out_height, a->decode_fmt)) || ji.buffersize < 1) {
757 if (err == 503) {
758 dlog(DLOG_WARNING, "VID: no decoder available (server overload).\n", fd);
759 httperror(fd, 503, "Service Temporarily Unavailable", "<p>No decoder is available. The server is currently busy or overloaded.</p>");
760 } else {
761 dlog(DLOG_WARNING, "VID: no decoder available (invalid file or unsupported codec).\n", fd);
762 httperror(fd, 500, "Service Unavailable", "<p>No decoder is available: File is invalid (no video track, unknown codec, invalid geometry,..)</p>");
763 }
764 return 0;
765 }
766
767 /* try encoded cache if a->render_fmt != FMT_RAW */
768 if (a->render_fmt != FMT_RAW) {
769 optr = icache_get_buffer(ic, vid, a->frame, a->render_fmt, a->misc_int, ji.out_width, ji.out_height, &olen, &cptr);
770 }
771
772 if (olen == 0) {
773 /* get frame from cache - or decode it into the cache */
774 bptr = vcache_get_buffer(vc, dc, vid, a->frame, ji.out_width, ji.out_height, a->decode_fmt, &cptr, &err);
775
776 if (!bptr) {
777 dlog(DLOG_ERR, "VID: error decoding video file for fd:%d err:%d\n", fd, err);
778 if (err == 503) {
779 httperror(fd, 503, "Service Temporarily Unavailable", "<p>Video cache is unavailable. The server is currently busy or overloaded.</p>");
780 } else {
781 httperror(fd, 500, "Service Unavailable", "<p>No decoder or cache is available: File is invalid (no video track, unknown codec, invalid geometry,..)</p>");
782 }
783 return 0;
784 }
785
786 switch (a->render_fmt) {
787 case FMT_RAW:
788 olen = ji.buffersize;
789 optr = bptr;
790 break;
791 default:
792 olen = format_image(&optr, a->render_fmt, a->misc_int, &ji, bptr);
793 break;
794 }
795 }
796
797 if(olen > 0 && optr) {
798 debugmsg(DEBUG_ICS, "VID: sending %li bytes to fd:%d.\n", (long int) olen, fd);
799 switch (a->render_fmt) {
800 case FMT_RAW:
801 h->ctype = "image/raw";
802 break;
803 case FMT_JPG:
804 h->ctype = "image/jpeg";
805 break;
806 case FMT_PNG:
807 h->ctype = "image/png";
808 break;
809 case FMT_PPM:
810 h->ctype = "image/ppm";
811 break;
812 default:
813 h->ctype = "image/unknown";
814 }
815 http_tx(fd, 200, h, olen, optr);
816
817 if (bptr && a->render_fmt != FMT_RAW) {
818 /* image was read from raw frame cache end encoded just now */
819 if (icache_add_buffer(ic, vid, a->frame, a->render_fmt, a->misc_int, ji.out_width, ji.out_height, optr, olen)) {
820 /* image was not added to image cache -> unreference the buffer */
821 free(optr);
822 } else if (! (cfg_usermask & USR_KEEPRAW)) {
823 /* delete raw frame when encoded frame was cached */
824 vcache_invalidate_buffer(vc, cptr);
825 }
826 }
827 } else {
828 dlog(DLOG_ERR, "VID: error formatting image for fd:%d\n", fd);
829 httperror(fd, 500, NULL, NULL);
830 }
831
832 if (bptr)
833 vcache_release_buffer(vc, cptr);
834 else
835 icache_release_buffer(ic, cptr);
836
837 jvi_free(&ji);
838 return (0);
839 }
840
hdl_clear_cache()841 void hdl_clear_cache() {
842 vcache_clear(vc, -1);
843 icache_clear(ic);
844 }
845
hdl_purge_cache()846 void hdl_purge_cache() {
847 vcache_clear(vc, -1);
848 icache_clear(ic);
849 dctrl_cache_clear(vc, dc, 2, -1);
850 }
851
852 // vim:sw=2 sts=2 ts=8 et:
853