1 /*
2  * Copyright 2003-2020, Björn Ståhl
3  * License: 3-Clause BSD, see COPYING file in arcan source repository.
4  * Reference: http://arcan-fe.com
5  */
6 
7 #include <stdlib.h>
8 #include <unistd.h>
9 #include <stdio.h>
10 #include <stdint.h>
11 #include <inttypes.h>
12 #include <stdbool.h>
13 #include <pthread.h>
14 #include <setjmp.h>
15 
16 #include <string.h>
17 #include <signal.h>
18 #include <fcntl.h>
19 
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/time.h>
23 
24 #include <sys/resource.h>
25 #include <sys/types.h>
26 #include <sys/socket.h>
27 #include <sys/wait.h>
28 
29 #include <math.h>
30 #include <limits.h>
31 #include <errno.h>
32 #include <time.h>
33 #include <ctype.h>
34 
35 #include <sqlite3.h>
36 
37 #include "getopt.h"
38 #include "arcan_math.h"
39 #include "arcan_general.h"
40 #include "arcan_shmif.h"
41 #include "arcan_event.h"
42 #include "arcan_audio.h"
43 #include "arcan_video.h"
44 #include "arcan_img.h"
45 #include "arcan_frameserver.h"
46 #include "arcan_lua.h"
47 #include "../platform/video_platform.h"
48 #include "arcan_led.h"
49 #include "arcan_db.h"
50 #include "arcan_videoint.h"
51 #include "arcan_conductor.h"
52 
53 /*
54  * list of scripts to inject
55  */
56 static struct arcan_strarr arr_hooks = {0};
57 
58 /*
59  * The arcanmain recover state is used either at the volition of the
60  * running script (see system_collapse) or in wrapping a failing pcall.
61  * This allows a simpler recovery script to adopt orphaned frameservers.
62  */
63 jmp_buf arcanmain_recover_state;
64 struct arcan_luactx* main_lua_context;
65 
66 static const struct option longopts[] = {
67 	{ "help",         no_argument,       NULL, '?'},
68 	{ "sync-strat",   required_argument, NULL, 'W'},
69 	{ "width",        required_argument, NULL, 'w'},
70 	{ "height",       required_argument, NULL, 'h'},
71 	{ "fullscreen",   no_argument,       NULL, 'f'},
72 	{ "windowed",     no_argument,       NULL, 's'},
73 	{ "debug",        no_argument,       NULL, 'g'},
74 	{ "binpath",      required_argument, NULL, 'B'},
75 	{ "libpath",      required_argument, NULL, 'L'},
76 	{ "rpath",        required_argument, NULL, 'p'},
77 	{ "applpath" ,    required_argument, NULL, 't'},
78 	{ "conservative", no_argument,       NULL, 'm'},
79 	{ "fallback",     required_argument, NULL, 'b'},
80 	{ "database",     required_argument, NULL, 'd'},
81 	{ "scalemode",    required_argument, NULL, 'r'},
82 	{ "nosound",      no_argument,       NULL, 'S'},
83 	{ "hook",         required_argument, NULL, 'H'},
84 #ifdef ARCAN_LWA
85 	{ "pipe-stdout",  no_argument,       NULL, '1'},
86 #endif
87 	{ "pipe-stdin",   no_argument,       NULL, '0'},
88 	{ "monitor",      required_argument, NULL, 'M'},
89 	{ "monitor-out",  required_argument, NULL, 'O'},
90 	{ "version",      no_argument,       NULL, 'V'},
91 	{ NULL,           no_argument,       NULL,  0 }
92 };
93 
vplatform_usage()94 static void vplatform_usage()
95 {
96 	const char** cur = platform_video_envopts();
97 	if (*cur){
98 	printf("Video platform configuration options:\n");
99 	printf("(use ARCAN_VIDEO_XXX=val for env, or "
100 		"arcan_db add_appl_kv arcan video_xxx for db)\n");
101 	printf("\tignore_dirty - always update regardless of 'dirty' state\n");
102 	while(1){
103 		const char* a = *cur++;
104 		if (!a) break;
105 		const char* b = *cur++;
106 		if (!b) break;
107 		printf("\t%s - %s\n", a, b);
108 	}
109 	printf("\n");
110 	}
111 
112 	cur = agp_envopts();
113 	printf("Graphics platform configuration options:\n");
114 	printf("(use ARCAN_GRAPHICS_XXX=val for env, graphics_xxx=val for db)\n");
115 	if (*cur){
116 	while(1){
117 		const char* a = *cur++;
118 		if (!a) break;
119 		const char* b = *cur++;
120 		if (!b) break;
121 		printf("\t%s - %s\n", a, b);
122 	}
123 	}
124 	printf("\n");
125 }
126 
usage()127 static void usage()
128 {
129 printf("Usage: arcan [-whfmWMOqspTBtHbdgaSV] applname "
130 	"[appl specific arguments]\n\n"
131 "-w\t--width       \tdesired initial canvas width (auto: 0)\n"
132 "-h\t--height      \tdesired initial canvas height (auto: 0)\n"
133 "-f\t--fullscreen  \ttoggle fullscreen mode ON (default: off)\n"
134 "-m\t--conservative\ttoggle conservative memory management (default: off)\n"
135 "-W\t--sync-strat  \tspecify video synchronization strategy (see below)\n"
136 "-M\t--monitor     \tenable monitor session (arg: samplerate, ticks/sample)\n"
137 "-O\t--monitor-out \tLOG:fname or applname\n"
138 "-s\t--windowed    \ttoggle borderless window mode\n"
139 #ifdef DISABLE_FRAMESERVERS
140 "-B\t--binpath     \tno-op, frameserver support was disabled compile-time\n"
141 #else
142 "-B\t--binpath     \tchange default searchpath for arcan_frameserver/afsrv*\n"
143 #endif
144 "-L\t--libpath     \tchange library search patch\n"
145 #ifdef ARCAN_LWA
146 "-1\t--pipe-stdout \t(for pipe-mode) negotiate an initial connection\n"
147 #endif
148 "-0\t--pipe-stdin  \t(for pipe-mode) accept requests for an initial connection\n"
149 "-p\t--rpath       \tchange default searchpath for shared resources\n"
150 "-t\t--applpath    \tchange default searchpath for applications\n"
151 "-T\t--scriptpath  \tchange default searchpath for builtin (system) scripts\n"
152 "-H\t--hook        \trun a post-appl() script (shared/sys-script namespaces)\n"
153 "-b\t--fallback    \tset a recovery/fallback application if appname crashes\n"
154 "-d\t--database    \tsqlite database (default: arcandb.sqlite)\n"
155 "-g\t--debug       \ttoggle debug output (events, coredumps, etc.)\n"
156 "-S\t--nosound     \tdisable audio output\n"
157 "-V\t--version     \tdisplay a version string then exit\n\n");
158 
159 	const char** cur = arcan_conductor_synchopts();
160 	if (*cur){
161 	printf("Video platform synchronization options (-W strat):\n");
162 	while(1){
163 		const char* a = *cur++;
164 		if (!a) break;
165 		const char* b = *cur++;
166 		if (!b) break;
167 		printf("\t%s - %s\n", a, b);
168 	}
169 	printf("\n");
170 	}
171 
172 	vplatform_usage();
173 
174 /* built-in envopts for _event.c, should really be moved there */
175 	printf("Input platform configuration options:\n");
176 	cur = platform_event_envopts();
177 	if (*cur){
178 	while(1){
179 		const char* a = *cur++;
180 		if (!a) break;
181 		const char* b = *cur++;
182 		if (!b) break;
183 		printf("\t%s - %s\n", a, b);
184 	}
185 	printf("\n");
186 	}
187 }
188 
189 /*
190  * current several namespaces are (legacy) specified relative to the old
191  * resources namespace, since those are expanded in set_namespace_defaults
192  * where the command-line switch isn't available, we have to generate these
193  * dependent namespaces overrides as well.
194  */
override_resspaces(const char * respath)195 static void override_resspaces(const char* respath)
196 {
197 	size_t len = strlen(respath);
198 	if (len == 0 || !arcan_isdir(respath)){
199 		arcan_warning("-p argument ignored, invalid path specified.\n");
200 		return;
201 	}
202 
203 	char debug_dir[ len + sizeof("/logs") ];
204 	char state_dir[ len + sizeof("/savestates") ];
205 	char font_dir[ len + sizeof("/fonts") ];
206 
207 	snprintf(debug_dir, sizeof(debug_dir), "%s/logs", respath);
208 	snprintf(state_dir, sizeof(state_dir), "%s/savestates", respath);
209 	snprintf(font_dir, sizeof(font_dir), "%s/fonts", respath);
210 
211 	arcan_override_namespace(respath, RESOURCE_APPL_SHARED);
212 	arcan_override_namespace(debug_dir, RESOURCE_SYS_DEBUG);
213 	arcan_override_namespace(state_dir, RESOURCE_APPL_STATE);
214 	arcan_override_namespace(font_dir, RESOURCE_SYS_FONT);
215 }
216 
appl_user_warning(const char * name,const char * err_msg)217 void appl_user_warning(const char* name, const char* err_msg)
218 {
219 	arcan_warning("\x1b[1mCouldn't load application \x1b[33m(%s): \x1b[31m%s\x1b[39m\n",
220 		name, err_msg);
221 
222 	if (!name || !strlen(name))
223 		arcan_warning("\x1b[1m\tthe appl-name argument is empty\x1b[22m\n");
224 	else {
225 		if (name[0] == '.')
226 			arcan_warning("\x1b[32m\ttried to load "
227 	"relative to current directory\x1b[22m\x1b[39m\n");
228 		else if (name[0] == '/')
229 			arcan_warning("\x1b[32m\ttried to load "
230 	"from absolute path. \x1b[39m\x1b[22m\n");
231 		else{
232 			char* space = arcan_expand_resource("", RESOURCE_SYS_APPLBASE);
233 			arcan_warning("\x1b[32m\ttried to load "
234 	"appl relative to base: \x1b[33m %s\x1b[22m\x1b[39m\n", space);
235 		}
236 	}
237 }
238 
fatal_shutdown()239 static void fatal_shutdown()
240 {
241 	arcan_audio_shutdown();
242 	arcan_video_shutdown(false);
243 }
244 
245 /* invoked from the conductor when it has processed a monotonic tick */
246 static struct {
247 	bool in_monitor;
248 	int monitor, monitor_counter;
249 	int mon_infd;
250 	FILE* mon_outf;
251 } settings = {0};
252 
main_cycle()253 static void main_cycle()
254 {
255 	if (settings.monitor && !settings.in_monitor){
256 		if (--settings.monitor_counter == 0){
257 			static int mc;
258 			char buf[8];
259 			snprintf(buf, 8, "%d", mc++);
260 			settings.monitor_counter = settings.monitor;
261 			arcan_lua_statesnap(settings.mon_outf, buf, true);
262 		}
263 	}
264 
265 	if (settings.in_monitor)
266 		arcan_lua_stategrab(main_lua_context, "sample", settings.mon_infd);
267 }
268 
add_hookscript(const char * instr)269 static void add_hookscript(const char* instr)
270 {
271 /* convert to filesystem path */
272 	char* expand = arcan_expand_resource(instr, RESOURCE_SYS_SCRIPTS);
273 
274 	if (!expand){
275 		arcan_warning("-H, couldn't expand hook-script: %s\n", instr ? instr : "");
276 		return;
277 	}
278 
279 /* open for reading */
280 	data_source src = arcan_open_resource(expand);
281 	if (src.fd == BADFD){
282 		arcan_warning("-H, couldn't open hook-script: %s\n", expand);
283 		free(expand);
284 		return;
285 	}
286 
287 /* get a memory mapping */
288 	map_region reg = arcan_map_resource(&src, false);
289 	if (!reg.ptr){
290 		arcan_release_resource(&src);
291 		return;
292 	}
293 
294 /* and copy the final script into the array */
295 	if (arr_hooks.count + 1 >= arr_hooks.limit)
296 		arcan_mem_growarr(&arr_hooks);
297 
298 /* to "reuse" the string, take the ugly approach of string-in-string,
299  * alloc_mem is fatal unless explicitly set NONFATAL */
300 	size_t dlen = strlen(reg.ptr);
301 	size_t blen = strlen(instr);
302 	char* comp = arcan_alloc_mem(dlen + blen + 2,
303 		ARCAN_MEM_STRINGBUF, ARCAN_MEM_BZERO, ARCAN_MEMALIGN_NATURAL);
304 	memcpy(comp, instr, blen);
305 	memcpy(&comp[blen+1], reg.ptr, dlen);
306 
307 	arr_hooks.data[arr_hooks.count++] = comp;
308 
309 	arcan_release_map(reg);
310 	arcan_release_resource(&src);
311 }
312 
313 /*
314  * Needed to be able to shift entry points on a per- target basis in cmake
315  * (lwa/normal/sdl and other combinations all mess around with main as an
316  * entry point.
317  *
318  * This really needs a severe refactor to distinguish between the phases:
319  * 1. argument parsing => state block
320  * 2. engine-main init
321  * 3. longjmp based recovery stages and partial engine re-init
322  */
323 #ifndef MAIN_REDIR
324 #define MAIN_REDIR main
325 #endif
326 
MAIN_REDIR(int argc,char * argv[])327 int MAIN_REDIR(int argc, char* argv[])
328 {
329 /* needed first as device_init might fork and need some shared mem */
330 	system_page_size = sysconf(_SC_PAGE_SIZE);
331 	arcan_conductor_enable_watchdog();
332 
333 /*
334  * these are, in contrast to normal video_init/event_init, only set once
335  * and not rerun on appl- switch etc. typically no-ops but may be used to
336  * setup priv- sep style scenarios at a point where fork()/suid() shouldn't
337  * be much of an issue
338  */
339 	platform_device_init();
340 	platform_video_preinit();
341 	platform_event_preinit();
342 	arcan_log_destination(stderr, 0);
343 
344 /*
345  * Protect against launching the main arcan instance from an environment where
346  * there already is indication of a connection destination. This is not
347  * entirely sufficient for wayland either as that implementation has a default
348  * display that it tries to open if no env was set - but fringe enough to not
349  * bother with.
350  */
351 #if defined(ARCAN_EGL_DRI) && !defined(ARCAN_LWA)
352 	if ((getenv("DISPLAY") || getenv("WAYLAND_DISPLAY"))){
353 		arcan_warning("%s running, switching to "
354 			"arcan_sdl\n", getenv("DISPLAY") ? "Xorg" : "Wayland");
355 		execvp("arcan_sdl", argv);
356 		arcan_warning("exec arcan_sdl failed, error code: %d\n", errno);
357 #if defined(ARCAN_HYBRID_SDL)
358 		arcan_warning("check that your build was made with -DHYBRID_SDL=ON\n");
359 #endif
360 		exit(EXIT_FAILURE);
361 	}
362 #endif
363 
364 #if !defined(ARCAN_LWA) && !defined(ARCAN_HEADLESS)
365 	if (getenv("ARCAN_CONNPATH")){
366 		execvp("arcan_lwa", argv);
367 		exit(EXIT_FAILURE);
368 	}
369 #endif
370 
371 	arcan_mem_growarr(&arr_hooks);
372 
373 	settings.in_monitor = getenv("ARCAN_MONITOR_FD") != NULL;
374 	bool windowed = false;
375 	bool fullscreen = false;
376 	bool conservative = false;
377 	bool nosound = false;
378 	bool stdin_connpoint = false;
379 
380 	unsigned char debuglevel = 0;
381 
382 	int scalemode = ARCAN_VIMAGE_NOPOW2;
383 	int width = -1;
384 	int height = -1;
385 
386 /* only used when monitor mode is activated, where we want some
387  * of the global paths etc. accessible, but not *all* of them */
388 	char* monitor_arg = "LOG";
389 
390 /*
391  * if we crash in the Lua VM, switch to this app and have it
392  * adopt our external connections
393  */
394 	char* fallback = NULL;
395 	char* dbfname = NULL;
396 	int ch;
397 
398 /* initialize conductor by setting the default profile, we need to
399  * do this early as it affects buffer management and so on */
400 	arcan_conductor_setsynch("vsynch");
401 
402 /*
403  * accumulation string-list for filenames provided via -H, can't
404  * add them to the hookscripts array immediately as the namespaces
405  * has not been resolved yet
406  */
407 	struct arcan_strarr tmplist = {0};
408 
409 	while ((ch = getopt_long(argc, argv,
410 		"w:h:mx:y:fsW:d:Sq:a:p:b:B:L:M:O:t:T:H:g01V", longopts, NULL)) >= 0){
411 	switch (ch) {
412 	case '?' :
413 		usage();
414 		exit(EXIT_SUCCESS);
415 	break;
416 	case 'w' : width = strtol(optarg, NULL, 10); break;
417 	case 'h' : height = strtol(optarg, NULL, 10); break;
418 	case 'm' : conservative = true; break;
419 	case 'f' : fullscreen = true; break;
420 	case 's' : windowed = true; break;
421 	case 'W' : arcan_conductor_setsynch(optarg); break;
422 	case 'd' : dbfname = strdup(optarg); break;
423 	case 'S' : nosound = true; break;
424 #ifdef ARCAN_LWA
425 /* send the connection point we will try to connect through, and update the
426  * env that SHMIF_ uses to get the behavior of connection looping */
427 	case '1' :{
428 		char cbuf[PP_SHMPAGE_SHMKEYLIM+1];
429 		snprintf(cbuf, sizeof(cbuf), "apipe%d", (int) getpid());
430 		setenv("ARCAN_CONNFL", "16", 1); /* SHMIF_CONNECT_LOOP */
431 		setenv("ARCAN_CONNPATH", strdup(cbuf), 1);
432 		puts(cbuf);
433 		fflush(stdout);
434 		arcan_warning("requesting pipe-connection via %s\n", cbuf);
435 	}
436 	break;
437 #endif
438 
439 #ifndef ARCAN_BUILDVERSION
440 #define ARCAN_BUILDVERSION "build-less"
441 #endif
442 
443 /* a prealloc:ed connection primitive is needed, defer this to when we have
444  * enough resources and context allocated to be able to do so. This does not
445  * survive appl-switching */
446 	case '0' : stdin_connpoint = true; break;
447 	case 'p' : override_resspaces(optarg); break;
448 	case 'T' : arcan_override_namespace(optarg, RESOURCE_SYS_SCRIPTS); break;
449 	case 'b' : fallback = strdup(optarg); break;
450 	case 'V' : fprintf(stdout, "%s\nshmif-%" PRIu64"\nluaapi-%d:%d\n",
451 		ARCAN_BUILDVERSION, arcan_shmif_cookie(), LUAAPI_VERSION_MAJOR,
452 		LUAAPI_VERSION_MINOR
453 		);
454 		exit(EXIT_SUCCESS);
455 	break;
456 	case 'H' :
457 		if (tmplist.count + 1 >= tmplist.limit)
458 			arcan_mem_growarr(&tmplist);
459 		tmplist.data[tmplist.count++] = strdup(optarg);
460 	break;
461 	case 'M' : settings.monitor_counter = settings.monitor =
462 		abs( (int)strtol(optarg, NULL, 10) ); break;
463 	case 'O' : monitor_arg = strdup( optarg ); break;
464 	case 't' :
465 		arcan_override_namespace(optarg, RESOURCE_SYS_APPLBASE);
466 		arcan_override_namespace(optarg, RESOURCE_SYS_APPLSTORE);
467 	break;
468 	case 'B' :
469 		arcan_override_namespace(optarg, RESOURCE_SYS_BINS);
470 	break;
471 	case 'L' :
472 		arcan_override_namespace(optarg, RESOURCE_SYS_LIBS);
473 	break;
474 	case 'g' :
475 		debuglevel++;
476 	break;
477 	default:
478 		break;
479 	}
480 	}
481 
482 	if (optind >= argc){
483 		arcan_warning("Couldn't start, missing 'applname' argument. \n"
484 			"Consult the manpage (man arcan) for additional details\n");
485 		usage();
486 		exit(EXIT_SUCCESS);
487 	}
488 
489 /* probe system, load environment variables, ... */
490 	arcan_set_namespace_defaults();
491 	arcan_ffunc_initlut();
492 #ifdef DISABLE_FRAMESERVERS
493 	arcan_override_namespace("", RESOURCE_SYS_BINS);
494 #endif
495 
496 	const char* err_msg;
497 
498 	if (!arcan_verifyload_appl(argv[optind], &err_msg)){
499 		appl_user_warning(argv[optind], err_msg);
500 
501 		if (fallback && strcmp(fallback, ":self") != 0){
502 			arcan_warning("trying to load fallback appl (%s)\n", fallback);
503 			if (!arcan_verifyload_appl(fallback, &err_msg)){
504 				arcan_warning("fallback application failed to load (%s), giving up.\n",
505 					err_msg);
506 				goto error;
507 			}
508 		}
509 		else
510 			goto error;
511 	}
512 
513 	if (!arcan_verify_namespaces(false)){
514 		arcan_warning("\x1b[32mCouldn't verify filesystem namespaces.\x1b[39m\n");
515 /* with debuglevel, we have separate reporting. */
516 		if (!debuglevel){
517 			arcan_warning("\x1b[33mLook through the following list and note the "
518 				"entries marked broken, \nCheck the manpage for config and environment "
519 				"variables, or try the arguments: \n"
520 				"\t <system-scripts> : -T path/to/arcan/data/scripts\n"
521 				"\t <application-shared> : -p any/valid/user/path\n"
522 				"\x1b[39m\n"
523 			);
524 			arcan_verify_namespaces(true);
525 		}
526 		goto error;
527 	}
528 
529 	if (debuglevel > 1)
530 		arcan_verify_namespaces(true);
531 
532 /* namespaces are resolved, time to figure out the hookscripts */
533 	if (tmplist.count){
534 		for (size_t i = 0; i < tmplist.count; i++){
535 			if (tmplist.data[i])
536 				add_hookscript(tmplist.data[i]);
537 		}
538 		arcan_mem_freearr(&tmplist);
539 	}
540 
541 /* pipe to file, socket or launch script based on monitor output,
542  * format will be LUA tables with the exception of each cell ending with
543  * #ENDSAMPLE . The block will be sampled, parsed and should return a table
544  * pushed through the sample() function in the LUA space */
545 	if (settings.in_monitor){
546 		settings.mon_infd = strtol( getenv("ARCAN_MONITOR_FD"), NULL, 10);
547 	}
548 	else if (settings.monitor > 0){
549 		extern arcan_benchdata benchdata;
550 		benchdata.bench_enabled = true;
551 
552 		if (strncmp(monitor_arg, "LOG:", 4) == 0){
553 			settings.mon_outf = fopen(&monitor_arg[4], "w+");
554 			if (NULL == settings.mon_outf)
555 				arcan_fatal("couldn't open log output (%s) for writing\n",
556 					monitor_arg[4]);
557 			fcntl(fileno(settings.mon_outf), F_SETFD, FD_CLOEXEC);
558 		}
559 		else {
560 			int pair[2];
561 
562 			pid_t p1;
563 			if (pipe(pair) == 0)
564 				;
565 
566 			if ( (p1 = fork()) == 0){
567 				close(pair[1]);
568 
569 /* double-fork to get away from parent */
570 				if (fork() != 0)
571 					exit(EXIT_SUCCESS);
572 
573 /*
574  * set the descriptor of the inherited pipe as an envvariable,
575  * this will have the program be launched with in_monitor set to true
576  * the monitor args will then be ignored and appname replaced with
577  * the monitorarg
578  */
579 				char monfd_buf[8] = {0};
580 				snprintf(monfd_buf, 8, "%d", pair[0]);
581 				setenv("ARCAN_MONITOR_FD", monfd_buf, 1);
582 				argv[optind] = strdup(monitor_arg);
583 
584 				execv(argv[0], argv);
585 				exit(EXIT_FAILURE);
586 			}
587 			else {
588 /* don't terminate just because the pipe gets broken (i.e. dead monitor) */
589 				close(pair[0]);
590 				settings.mon_outf = fdopen(pair[1], "w");
591 			}
592 		}
593 
594 		fullscreen = false;
595 	}
596 
597 /* two main sources for sigpipe, one being monitor and the other being
598  * lua- layer open_nonblock calls, neither has any use for it so mask */
599 	sigaction(SIGPIPE, &(struct sigaction){
600 		.sa_handler = SIG_IGN, .sa_flags = 0}, 0);
601 
602 	struct arcan_dbh* dbhandle = NULL;
603 /* fallback to whatever is the platform database- storepath */
604 	if (dbfname || (dbfname = platform_dbstore_path()))
605 		dbhandle = arcan_db_open(dbfname, arcan_appl_id());
606 
607 	if (!dbhandle){
608 		arcan_warning("Couldn't open/create database (%s), "
609 			"fallback to :memory:\n", dbfname);
610 		dbhandle = arcan_db_open(":memory:", arcan_appl_id());
611 	}
612 
613 	if (!dbhandle){
614 		arcan_warning("In memory db fallback failed, giving up\n");
615 		goto error;
616 	}
617 	arcan_db_set_shared(dbhandle);
618 	const char* target_appl = NULL;
619 	dbhandle = arcan_db_get_shared(&target_appl);
620 
621 /* either use previous explicit dimensions (if found and cached)
622  * or revert to platform default or store last */
623 	if (-1 == width){
624 		char* dbw = arcan_db_appl_val(dbhandle, target_appl, "width");
625 		if (dbw){
626 			width = (uint16_t) strtoul(dbw, NULL, 10);
627 			arcan_mem_free(dbw);
628 		}
629 		else
630 			width = 0;
631 	}
632 	else{
633 		char buf[6] = {0};
634 		snprintf(buf, sizeof(buf), "%d", width);
635 		arcan_db_appl_kv(dbhandle, target_appl, "width", buf);
636 	}
637 
638 	if (-1 == height){
639 		char* dbh = arcan_db_appl_val(dbhandle, target_appl, "height");
640 		if (dbh){
641 			height = (uint16_t) strtoul(dbh, NULL, 10);
642 			arcan_mem_free(dbh);
643 		}
644 		else
645 			height = 0;
646 	}
647 	else{
648 		char buf[6] = {0};
649 		snprintf(buf, sizeof(buf), "%d", height);
650 		arcan_db_appl_kv(dbhandle, target_appl, "height", buf);
651 	}
652 
653 	arcan_video_default_scalemode(scalemode);
654 
655 	if (windowed)
656 		fullscreen = false;
657 
658 /* grab video, (necessary) */
659 	arcan_evctx* evctx = arcan_event_defaultctx();
660 	arcan_event_init(evctx);
661 
662 	if (arcan_video_init(width, height, 32, fullscreen, windowed,
663 		conservative, arcan_appl_id()) != ARCAN_OK){
664 		printf("Error: couldn't initialize video subsystem. Check permissions, "
665 			" try other video platform options (-f, -w, -h)\n");
666 		vplatform_usage();
667 		arcan_fatal("Video platform initialization failed\n");
668 	}
669 
670 /* defined in warning.c for arcan_fatal, we avoid the use of an atexit() as
671  * this can be routed through abort() or similar functions but some video
672  * platforms are extremely volatile if we don't initiate a shutdown (egl-dri
673  * for one) */
674 	extern void(*arcan_fatal_hook)(void);
675 	arcan_fatal_hook = fatal_shutdown;
676 
677 	errno = 0;
678 /* grab audio, (possible to live without) */
679 	if (ARCAN_OK != arcan_audio_setup(nosound))
680 		arcan_warning("Warning: No audio devices could be found.\n");
681 
682 	arcan_math_init();
683 	arcan_img_init();
684 
685 /* setup device polling, cleanup, ... */
686 	arcan_led_init();
687 
688 /*
689  * fallback implementation resides here and a little further down in the "if
690  * adopt" block. Use verifyload to reconfigure application namespace and
691  * scripts to run, then recoverexternal will cleanup audio/video/event and
692  * invoke adopt() in the script
693  */
694 	bool adopt = false, in_recover = false;
695 	int jumpcode = setjmp(arcanmain_recover_state);
696 	int saved, truncated;
697 
698 	if (jumpcode == ARCAN_LUA_SWITCH_APPL ||
699 			jumpcode == ARCAN_LUA_SWITCH_APPL_NOADOPT){
700 
701 /* close down the database and reinitialize with the name of the new appl */
702 		arcan_db_close(&dbhandle);
703 		arcan_db_set_shared(NULL);
704 
705 		dbhandle = arcan_db_open(dbfname, arcan_appl_id());
706 		if (!dbhandle)
707 			goto error;
708 
709 		arcan_db_set_shared(dbhandle);
710 		arcan_lua_cbdrop();
711 		arcan_lua_shutdown(main_lua_context);
712 
713 /* mask off errors so shutdowns etc. won't queue new events that enter
714  * the event queue and gets exposed to the new appl */
715 		arcan_event_maskall(evctx);
716 
717 /* with the 'no adopt' we can just safely flush the video stack and all
718  * connections will be closed and freed */
719 		if (jumpcode == ARCAN_LUA_SWITCH_APPL_NOADOPT){
720 			int lastctxc = arcan_video_popcontext();
721 			int lastctxa;
722 			while( lastctxc != (lastctxa = arcan_video_popcontext()) )
723 				lastctxc = lastctxa;
724 
725 /* rescan so devices seem to be added again */
726 			platform_event_rescan_idev(evctx);
727 		}
728 		else{
729 /* for adopt the rescan is deferred (adopt messes with the eventqueu) */
730 			arcan_video_recoverexternal(true, &saved, &truncated, NULL, NULL);
731 			adopt = true;
732 		}
733 		arcan_event_clearmask(evctx);
734 	}
735 /* fallback recovery with adoption */
736 	else if (jumpcode == ARCAN_LUA_RECOVERY_SWITCH){
737 		if (in_recover || !arcan_conductor_valid_cycle()){
738 			arcan_warning("Double-Failure (main appl + adopt appl), giving up.\n");
739 			goto error;
740 		}
741 
742 /*
743  * There is another edge condition here, and that is if some non-user triggered
744  * event happens on reload after in_recover has been cleared. The last resort
745  * from a livelock then is to have some crash-recovery timeout and counter.
746  * This is also not water tight if the client has a long load/init stage BUT
747  * with the ANR requirement on 10s so that sets an upper limit, use half that.
748  */
749 		static uint64_t last_recover;
750 		static uint8_t recover_counter;
751 		if (!last_recover)
752 			last_recover = arcan_timemillis();
753 
754 		if (last_recover && arcan_timemillis() - last_recover < 5000){
755 			recover_counter++;
756 			if (recover_counter > 5){
757 				arcan_warning("Script handover / recover safety timeout exceeded.\n");
758 				goto error;
759 			}
760 		}
761 		else {
762 			recover_counter = 0;
763 		}
764 
765 		if (!fallback){
766 			arcan_warning("Lua VM failed with no fallback defined, (see -b arg).\n");
767 			goto error;
768 		}
769 
770 		arcan_event_maskall(evctx);
771 		arcan_video_recoverexternal(true, &saved, &truncated, NULL, NULL);
772 		arcan_event_clearmask(evctx);
773 		platform_video_recovery();
774 
775 		const char* errmsg;
776 		arcan_lua_cbdrop();
777 		arcan_lua_shutdown(main_lua_context);
778 		if (strcmp(fallback, ":self") == 0)
779 			fallback = argv[optind];
780 
781 		if (!arcan_verifyload_appl(fallback, &errmsg)){
782 			arcan_warning("Lua VM error fallback, failure loading (%s), reason: %s\n",
783 				fallback, errmsg);
784 			goto error;
785 		}
786 
787 		if (!arcan_verify_namespaces(false))
788 			goto error;
789 
790 /* to track if we get a crash in the fallback application and not get stuck
791  * in an endless loop */
792 		in_recover = true;
793 		adopt = true;
794 	}
795 	else if (jumpcode == ARCAN_LUA_RECOVERY_FATAL_IGNORE){
796 		goto run_loop;
797 	}
798 
799 /* setup VM, map arguments and possible overrides */
800 	main_lua_context = arcan_lua_alloc();
801 	arcan_lua_mapfunctions(main_lua_context, debuglevel);
802 
803 	bool inp_file;
804 	const char* inp = arcan_appl_basesource(&inp_file);
805 	if (!inp){
806 		arcan_warning("main(), No main script found for (%s)\n", arcan_appl_id());
807 		goto error;
808 	}
809 
810 	char* msg = arcan_lua_main(main_lua_context, inp, inp_file);
811 	if (msg != NULL){
812 		arcan_warning("\n\x1b[1mParsing error in (\x1b[33m%s\x1b[39m):\n"
813 			"\x1b[35m%s\x1b[22m\x1b[39m\n\n", arcan_appl_id(), msg);
814 		goto error;
815 	}
816 	free(msg);
817 
818 	if (!arcan_lua_callvoidfun(main_lua_context, "", false, (const char**)
819 		(argc > optind ? (argv + optind + 1) : NULL))){
820 		arcan_warning("\n\x1b[1mCouldn't load (\x1b[33m%s\x1b[39m):"
821 			"\x1b[35m missing '%s' function\x1b[22m\x1b[39m\n\n", arcan_appl_id(), arcan_appl_id());
822 		goto error;
823 	}
824 
825 /* mark that we are in hook so a script can know that is being used as a hook-
826  * scripts and not as embedded by the appl itself */
827 	arcan_lua_setglobalint(main_lua_context, "IN_HOOK", 1);
828 	for (size_t i = 0; i < arr_hooks.count; i++){
829 		if (arr_hooks.data[i]){
830 			const char* name = arr_hooks.data[i];
831 			const char* src = &name[strlen(name)+1];
832 			arcan_lua_dostring(main_lua_context, src, name);
833 		}
834 	}
835 	arcan_lua_setglobalint(main_lua_context, "IN_HOOK", 0);
836 
837 	if (adopt){
838 		arcan_lua_setglobalint(main_lua_context, "CLOCK", evctx->c_ticks);
839 		arcan_lua_adopt(main_lua_context);
840 
841 /* re-issue video recovery here as adopt CLEARS THE EVENTQUEUE, this is quite a
842  * complex pattern as the work in selectively recovering the event-queue after
843  * an error is much too dangerous */
844 		platform_video_recovery();
845 		platform_event_rescan_idev(evctx);
846 
847 		in_recover = false;
848 	}
849 	else if (stdin_connpoint){
850 		char cbuf[PP_SHMPAGE_SHMKEYLIM+1];
851 /* read desired connection point from stdin, strip trailing \n */
852 		if (fgets(cbuf, sizeof(cbuf), stdin)){
853 			size_t len = strlen(cbuf);
854 			cbuf[len-1] = '\0';
855 			for (const char* c = cbuf; *c; c++)
856 				if (!isalnum(*c)){
857 					arcan_warning("-1, %s failed (only [a->Z0-9] accepted)\n", cbuf);
858 					goto error;
859 				}
860 			if (!arcan_lua_launch_cp(main_lua_context, cbuf, NULL)){
861 				arcan_fatal("-1, couldn't setup connection point (%s)\n", cbuf);
862 				goto error;
863 			}
864 		}
865 		else{
866 			arcan_warning("-1, couldn't read a valid connection point from stdin\n");
867 			goto error;
868 		}
869 	}
870 	bool done = false;
871 	int exit_code = 0;
872 
873 run_loop:
874 	exit_code = arcan_conductor_run(main_cycle);
875 
876 	arcan_lua_callvoidfun(main_lua_context, "shutdown", false, NULL);
877 	arcan_mem_freearr(&arr_hooks);
878 	arcan_led_shutdown();
879 	arcan_event_deinit(evctx, true);
880 	arcan_audio_shutdown();
881 	arcan_video_shutdown(exit_code != 256);
882 	arcan_mem_free(dbfname);
883 	if (dbhandle){
884 		arcan_db_close(&dbhandle);
885 		arcan_db_set_shared(NULL);
886 	}
887 
888 	return exit_code == 256 ? EXIT_SUCCESS : exit_code;
889 
890 error:
891 	if (debuglevel > 1){
892 		arcan_warning("fatal: main loop failed, arguments: \n");
893 		for (size_t i = 0; i < argc; i++)
894 			arcan_warning("%s ", argv[i]);
895 		arcan_warning("\n\n");
896 
897 		arcan_verify_namespaces(true);
898 	}
899 
900 /* first write the reason as 'last words' if we are running in lwa as that
901  * needs to be done BEFORE video_shutdown as that closes the context */
902 	const char* crashmsg = arcan_lua_crash_source(main_lua_context);
903 #ifdef ARCAN_LWA
904 	arcan_shmif_last_words(arcan_shmif_primary(SHMIF_INPUT), crashmsg);
905 #endif
906 
907 /* now we can shutdown the subsystems themselves */
908 	arcan_event_deinit(evctx, true);
909 	arcan_mem_free(dbfname);
910 	arcan_audio_shutdown();
911 	arcan_video_shutdown(false);
912 
913 /* and finally write the reason to stdout as well now that native platforms
914  * have restored the context to having valid stdout/stderr */
915 	if (crashmsg){
916 		arcan_warning(
917 			"\n\x1b[1mImproper API use from Lua script\n"
918 			":\n\t\x1b[32m%s\x1b[39m\n", crashmsg
919 		);
920 
921 		arcan_warning("version:\n\t%s\n\tshmif-%" PRIu64"\n\tluaapi-%d:%d\n",
922 		ARCAN_BUILDVERSION, arcan_shmif_cookie(), LUAAPI_VERSION_MAJOR,
923 		LUAAPI_VERSION_MINOR
924 		);
925 	}
926 
927 	return EXIT_FAILURE;
928 }
929