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&amp;file=PATH</code> URL query or post parameters. Video frames are counted starting at zero. Default options are <code>w=0&amp;h=0&amp;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&amp;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&amp;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