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