bool arcan_event_blacklisted(const char* idstr) { /* idstr comes from a trusted context, won't exceed stack size */ char buf[strlen(idstr) + sizeof("bl_")]; snprintf(buf, COUNT_OF(buf), "bl_%s", idstr); char* res = arcan_db_appl_val(dbhandle, ARCAN_TBL, "bl_"); bool rv = res && strcmp(res, "block") == 0; arcan_mem_free(res); return rv; }
int main(int argc, char* argv[]) { settings.in_monitor = getenv("ARCAN_MONITOR_FD") != NULL; bool windowed = false; bool fullscreen = false; bool conservative = false; bool nosound = false; unsigned char debuglevel = 0; int scalemode = ARCAN_VIMAGE_NOPOW2; int width = -1; int height = -1; /* only used when monitor mode is activated, where we want some * of the global paths etc. accessible, but not *all* of them */ char* monitor_arg = "LOG"; /* * if we crash in the Lua VM, switch to this app and have it * adopt our external connections */ char* fallback = NULL; char* hookscript = NULL; char* dbfname = NULL; int ch; srand( time(0) ); /* VIDs all have a randomized base to provoke crashes in poorly written scripts, * only -g will make their base and sequence repeatable */ while ((ch = getopt_long(argc, argv, "w:h:mx:y:fsW:d:Sq:a:p:b:B:M:O:t:H:g1:2:V", longopts, NULL)) >= 0){ switch (ch) { case '?' : usage(); exit(EXIT_SUCCESS); break; case 'w' : width = strtol(optarg, NULL, 10); break; case 'h' : height = strtol(optarg, NULL, 10); break; case 'm' : conservative = true; break; case 'f' : fullscreen = true; break; case 's' : windowed = true; break; case 'W' : platform_video_setsynch(optarg); break; case 'd' : dbfname = strdup(optarg); break; case 'S' : nosound = true; break; case 'q' : settings.timedump = strtol(optarg, NULL, 10); break; case 'p' : arcan_override_namespace(optarg, RESOURCE_APPL_SHARED); break; case 'b' : fallback = strdup(optarg); break; case 'V' : fprintf(stdout, "%s\nshmif-%" PRIu64"\nluaapi-%d:%d\n", ARCAN_BUILDVERSION, arcan_shmif_cookie(), LUAAPI_VERSION_MAJOR, LUAAPI_VERSION_MINOR ); exit(EXIT_SUCCESS); break; case 'H' : hookscript = strdup( optarg ); break; #ifndef _WIN32 case 'M' : settings.monitor_counter = settings.monitor = abs( (int)strtol(optarg, NULL, 10) ); break; case 'O' : monitor_arg = strdup( optarg ); break; #endif case 't' : arcan_override_namespace(optarg, RESOURCE_SYS_APPLBASE); arcan_override_namespace(optarg, RESOURCE_SYS_APPLSTORE); break; case 'B' : arcan_override_namespace(optarg, RESOURCE_SYS_BINS); break; case 'g' : debuglevel++; srand(0xdeadbeef); break; break; case '1' : stdout_redirected = true; if (freopen(optarg, "a", stdout) == NULL); break; case '2' : stderr_redirected = true; if (freopen(optarg, "a", stderr) == NULL); break; default: break; } } if (optind >= argc){ arcan_warning("Couldn't start, missing 'applname' argument. \n" "Consult the manpage (man arcan) for additional details\n"); usage(); exit(EXIT_SUCCESS); } /* probe system, load environment variables, ... */ arcan_set_namespace_defaults(); arcan_ffunc_initlut(); #ifdef DISABLE_FRAMESERVERS arcan_override_namespace("", RESOURCE_SYS_BINS); #endif const char* err_msg; if (!arcan_verifyload_appl(argv[optind], &err_msg)){ arcan_warning("arcan_verifyload_appl(), " "failed to load (%s), reason: %s.", argv[optind], err_msg); if (fallback){ arcan_warning("trying to load fallback application (%s)\n", fallback); if (!arcan_verifyload_appl(fallback, &err_msg)){ arcan_warning("fallback application failed to load (%s), giving up.\n", err_msg); goto error; } } else goto error; } if (!arcan_verify_namespaces(false)){ arcan_warning("namespace verification failed, status:\n"); goto error; } if (debuglevel > 1) arcan_verify_namespaces(true); #ifndef _WIN32 /* pipe to file, socket or launch script based on monitor output, * format will be LUA tables with the exception of each cell ending with * #ENDSAMPLE . The block will be sampled, parsed and should return a table * pushed through the sample() function in the LUA space */ if (settings.in_monitor){ settings.mon_infd = strtol( getenv("ARCAN_MONITOR_FD"), NULL, 10); } else if (settings.monitor > 0){ extern arcan_benchdata benchdata; benchdata.bench_enabled = true; if (strncmp(monitor_arg, "LOG:", 4) == 0){ settings.mon_outf = fopen(&monitor_arg[4], "w+"); if (NULL == settings.mon_outf) arcan_fatal("couldn't open log output (%s) for writing\n", monitor_arg[4]); fcntl(fileno(settings.mon_outf), F_SETFD, FD_CLOEXEC); } else { int pair[2]; pid_t p1; if (pipe(pair) == 0) ; if ( (p1 = fork()) == 0){ close(pair[1]); /* double-fork to get away from parent */ if (fork() != 0) exit(EXIT_SUCCESS); /* * set the descriptor of the inherited pipe as an envvariable, * this will have the program be launched with in_monitor set to true * the monitor args will then be ignored and appname replaced with * the monitorarg */ char monfd_buf[8] = {0}; snprintf(monfd_buf, 8, "%d", pair[0]); setenv("ARCAN_MONITOR_FD", monfd_buf, 1); argv[optind] = strdup(monitor_arg); execv(argv[0], argv); exit(EXIT_FAILURE); } else { /* don't terminate just because the pipe gets broken (i.e. dead monitor) */ close(pair[0]); settings.mon_outf = fdopen(pair[1], "w"); signal(SIGPIPE, SIG_IGN); } } fullscreen = false; } #else if (!stdout_redirected){ } #endif /* * try to open the specified database, * if that fails, warn, try to create an empty * database and if that fails, give up. */ if (!dbfname) dbfname = arcan_expand_resource("arcandb.sqlite", RESOURCE_APPL_SHARED); dbhandle = arcan_db_open(dbfname, arcan_appl_id()); if (!dbhandle) { arcan_warning("Couldn't open database (requested: %s)," "trying to create a new one.\n", dbfname); FILE* fpek = fopen(dbfname, "w+"); /* case of non-write:able dbpath */ if (!fpek){ arcan_mem_free(dbfname); dbfname = arcan_expand_resource("arcandb.sqlite", RESOURCE_APPL_TEMP); if (!dbfname) goto error; fpek = fopen(dbfname, "w+"); } if (fpek){ fclose(fpek); dbhandle = arcan_db_open(dbfname, arcan_appl_id()); } if (!dbhandle){ arcan_warning("Couldn't create database, using in-mem fallback\n"); dbhandle = arcan_db_open(":memory:", arcan_appl_id()); } if (!dbhandle){ arcan_warning("In memory db fallback failed, giving up\n"); goto error; } } /* either use previous explicit dimensions (if found and cached) * or revert to platform default or store last */ if (-1 == width){ char* dbw = arcan_db_appl_val(dbhandle, "arcan", "width"); if (dbw){ width = (uint16_t) strtoul(dbw, NULL, 10); arcan_mem_free(dbw); } else width = 0; } else{ char buf[6] = {0}; snprintf(buf, sizeof(buf), "%d", width); arcan_db_appl_kv(dbhandle, "arcan", "width", buf); } if (-1 == height){ char* dbh = arcan_db_appl_val(dbhandle, "arcan", "height"); if (dbh){ height = (uint16_t) strtoul(dbh, NULL, 10); arcan_mem_free(dbh); } else height = 0; } else{ char buf[6] = {0}; snprintf(buf, sizeof(buf), "%d", height); arcan_db_appl_kv(dbhandle, "arcan", "height", buf); } arcan_video_default_scalemode(scalemode); if (windowed) fullscreen = false; /* grab video, (necessary) */ if (arcan_video_init(width, height, 32, fullscreen, windowed, conservative, arcan_appl_id()) != ARCAN_OK){ arcan_fatal("Error; Couldn't initialize video system," "try other windowing options (-f, -w, ...)\n"); } /* defined in warning.c for arcan_fatal, we avoid the use of an * atexit() as this can be routed through abort() or similar * functions but some video platforms are extremely volatile * if we don't initiate a shutdown (egl-dri for one) */ extern void(*arcan_fatal_hook)(void); arcan_fatal_hook = arcan_video_shutdown; errno = 0; /* grab audio, (possible to live without) */ if (ARCAN_OK != arcan_audio_setup(nosound)) arcan_warning("Warning: No audio devices could be found.\n"); arcan_math_init(); /* setup device polling, cleanup, ... */ arcan_evctx* def = arcan_event_defaultctx(); arcan_event_init( def ); #ifdef ARCAN_LED arcan_led_init(); #endif /* * system integration note here, this could essentially provide * a side-channel into debugdata as the path can be expanded to * the debug directory, thus a compromised frameserver could possibly * leak crash-dumps etc. outside the sandbox. If this is a concern, * change this behavior or define a different logpath in the env. */ if (getenv("ARCAN_FRAMESERVER_LOGDIR") == NULL){ char* lpath = arcan_expand_resource("", RESOURCE_SYS_DEBUG); setenv("ARCAN_FRAMESERVER_LOGDIR", lpath, 1); arcan_mem_free(lpath); } if (hookscript){ char* tmphook = arcan_expand_resource(hookscript, RESOURCE_APPL_SHARED); free(hookscript); hookscript = NULL; if (tmphook){ data_source src = arcan_open_resource(tmphook); if (src.fd != BADFD){ map_region reg = arcan_map_resource(&src, false); if (reg.ptr){ hookscript = strdup(reg.ptr); arcan_release_map(reg); } arcan_release_resource(&src); } free(tmphook); } } #ifndef _WIN32 system_page_size = sysconf(_SC_PAGE_SIZE); #endif /* * fallback implementation resides here and a little further down * in the "if adopt" block. Use verifyload to reconfigure application * namespace and scripts to run, then recoverexternal will cleanup * audio/video/event and invoke adopt() in the script */ bool adopt = false, in_recover = false; int jumpcode = setjmp(arcanmain_recover_state); if (jumpcode == 1){ arcan_db_close(&dbhandle); dbhandle = arcan_db_open(dbfname, arcan_appl_id()); if (!dbhandle) goto error; adopt = true; } else if (jumpcode == 2){ if (in_recover){ arcan_warning("Double-Failure (main appl + adopt appl), giving up.\n"); goto error; } if (!fallback){ arcan_warning("Lua VM instance failed and no " "fallback defined, giving up.\n"); goto error; } const char* errmsg; arcan_lua_shutdown(settings.lua); if (!arcan_verifyload_appl(fallback, &errmsg)){ arcan_warning("Lua VM error fallback, failure loading (%s), reason: %s\n", fallback, errmsg); goto error; } if (!arcan_verify_namespaces(false)) goto error; in_recover = true; adopt = true; } /* setup VM, map arguments and possible overrides */ settings.lua = arcan_lua_alloc(); arcan_lua_mapfunctions(settings.lua, debuglevel); bool inp_file; const char* inp = arcan_appl_basesource(&inp_file); if (!inp){ arcan_warning("main(), No main script found for (%s)\n", arcan_appl_id()); goto error; } char* msg = arcan_lua_main(settings.lua, inp, inp_file); if (msg != NULL){ #ifdef ARCAN_LUA_NOCOLOR arcan_warning("\nParsing error in %s:\n%s\n", arcan_appl_id(), msg); #else arcan_warning("\n\x1b[1mParsing error in (\x1b[33m%s\x1b[39m):\n" "\x1b[35m%s\x1b[22m\x1b[39m\n\n", arcan_appl_id(), msg); #endif goto error; } free(msg); /* entry point follows the name of the appl, * hand over execution and begin event loop */ if (argc > optind) arcan_lua_pushargv(settings.lua, argv + optind + 1); if (!arcan_lua_callvoidfun(settings.lua, "", false)) arcan_fatal("couldn't load appl, missing %s function\n", arcan_appl_id() ? arcan_appl_id() : ""); if (hookscript) arcan_lua_dostring(settings.lua, hookscript); if (adopt){ int saved, truncated; arcan_video_recoverexternal(false, &saved, &truncated, arcan_lua_adopt, settings.lua); arcan_warning("switching applications, %d adopted.\n", saved); } arcan_evctx* evctx = arcan_event_defaultctx(); bool done = false; int exit_code = EXIT_SUCCESS; arcan_event ev; while (!done) { /* these can populate event queue, but only to a certain limit */ arcan_video_pollfeed(); arcan_audio_refresh(); float frag = arcan_event_process(evctx, on_clock_pulse); while (1 == arcan_event_poll(evctx, &ev)){ switch (ev.category){ case EVENT_VIDEO: if (ev.vid.kind == EVENT_VIDEO_EXPIRE) arcan_video_deleteobject(ev.vid.source); break; /* this event category is never propagated to the scripting engine itself */ case EVENT_SYSTEM: if (ev.sys.kind == EVENT_SYSTEM_EXIT){ exit_code = ev.sys.errcode; done = true; goto out; } goto error; default: break; } arcan_lua_pushevent(settings.lua, &ev); } platform_video_synch(settings.tick_count, frag, preframe, postframe); } out: free(hookscript); arcan_lua_callvoidfun(settings.lua, "shutdown", false); #ifdef ARCAN_LED arcan_led_shutdown(); #endif arcan_video_shutdown(); arcan_event_deinit(arcan_event_defaultctx()); arcan_mem_free(dbfname); if (dbhandle) arcan_db_close(&dbhandle); return exit_code; error: if (debuglevel > 1){ arcan_warning("fatal: main loop failed, arguments: \n"); for (size_t i = 0; i < argc; i++) arcan_warning("%s ", argv[i]); arcan_warning("\n\n"); } arcan_mem_free(dbfname); arcan_video_shutdown(); arcan_event_deinit(arcan_event_defaultctx()); /* for cases where we have a broken namespace, also dump * the current namespace state as that might help troubleshoot */ arcan_verify_namespaces(true); return EXIT_FAILURE; }