diff options
Diffstat (limited to 'src/main.c')
-rw-r--r-- | src/main.c | 611 |
1 files changed, 611 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..795c2a7b --- /dev/null +++ b/src/main.c @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * File: main.c + * Author: "Fulup Ar Foll" + * + * Created on 05 December 2015, 15:38 + */ + +#include "local-def.h" + +#include <syslog.h> +#include <setjmp.h> +#include <signal.h> +#include <getopt.h> + +static sigjmp_buf exitpoint; // context save for set/longjmp +static sigjmp_buf restartpoint; // context save for set/longjmp + +/*---------------------------------------------------------- + | printversion + | print version and copyright + +--------------------------------------------------------- */ + static void printVersion (void) { + + fprintf (stderr,"\n----------------------------------------- \n"); + fprintf (stderr,"| AFB [Application Framework Binder] version=%s |\n", AJQ_VERSION); + fprintf (stderr,"----------------------------------------- \n"); + fprintf (stderr,"| Copyright(C) 2015 Fulup Ar Foll /IoT.bzh [fulup -at- iot.bzh]\n"); + fprintf (stderr,"| AFB comes with ABSOLUTELY NO WARRANTY.\n"); + fprintf (stderr,"| Licence [what ever makes you happy] until you fix bugs by yourself :)\n\n"); + exit (0); + } // end printVersion + + +// Define command line option + #define SET_VERBOSE 101 + #define SET_BACKGROUND 105 + #define SET_FORGROUND 106 + #define KILL_PREV_EXIT 107 + #define KILL_PREV_REST 108 + #define SET_FAKE_MOD 109 + + #define SET_TCP_PORT 120 + #define SET_ROOT_DIR 121 + #define SET_ROOT_BASE 122 + #define SET_ROOT_API 123 + + #define SET_CACHE_TO 130 + #define SET_cardid 131 + #define SET_PID_FILE 132 + #define SET_SESSION_DIR 133 + #define SET_CONFIG_FILE 134 + #define SET_CONFIG_SAVE 135 + #define SET_CONFIG_EXIT 138 + + #define SET_SMACK 140 + #define SET_PLUGINS 141 + + #define DISPLAY_VERSION 150 + #define DISPLAY_HELP 151 + + +// Supported option +static AFB_options cliOptions [] = { + {SET_VERBOSE ,0,"verbose" , "Verbose Mode"}, + + {SET_FORGROUND ,0,"foreground" , "Get all in foreground mode"}, + {SET_BACKGROUND ,0,"daemon" , "Get all in background mode"}, + {KILL_PREV_EXIT ,0,"kill" , "Kill active process if any and exit"}, + {KILL_PREV_REST ,0,"restart" , "Kill active process if any and restart"}, + + {SET_TCP_PORT ,1,"port" , "HTTP listening TCP port [default 1234]"}, + {SET_ROOT_DIR ,1,"rootdir" , "HTTP Root Directory [default $HOME/.AFB"}, + {SET_ROOT_BASE ,1,"rootbase" , "Angular Base Root URL [default /"}, + {SET_ROOT_API ,1,"rootapi" , "HTML Root API URL [default /api"}, + + {SET_CACHE_TO ,1,"cache-eol" , "Client cache end of live [default 3600s]"}, + {SET_cardid ,1,"setuid" , "Change user id [default don't change]"}, + {SET_PID_FILE ,1,"pidfile" , "PID file path [default none]"}, + {SET_SESSION_DIR ,1,"sessiondir" , "Sessions file path [default rootdir/sessions]"}, + {SET_CONFIG_FILE ,1,"config" , "Config Filename [default rootdir/sessions/configs/default.AFB]"}, + {SET_CONFIG_SAVE ,0,"save" , "Save config on disk [default no]"}, + {SET_CONFIG_EXIT ,0,"saveonly" , "Save config on disk and then exit"}, + + {SET_SMACK ,1,"smack" , "Set Smack Label [default=demo"}, + {SET_PLUGINS ,1,"mods" , "Enable module [default=all"}, + + {DISPLAY_VERSION ,0,"version" , "Display version and copyright"}, + {DISPLAY_HELP ,0,"help" , "Display this help"}, + {0, 0, 0} + }; + +/*---------------------------------------------------------- + | signalQuit + | return to intitial exitpoint on order to close backend + | before exiting. + +--------------------------------------------------------- */ +void signalQuit (int signum) +{ + if (verbose) printf ("INF:signalQuit received signal to quit\n"); + longjmp (exitpoint, signum); +} + +/*---------------------------------------------------------- + | timeout signalQuit + | + +--------------------------------------------------------- */ +void signalFail (int signum) { + + sigset_t sigset; + + // unlock timeout signal to allow a new signal to come + sigemptyset (&sigset); + sigaddset (&sigset, SIGABRT); + sigprocmask (SIG_UNBLOCK, &sigset, 0); + + fprintf (stderr, "%s ERR:getAllBlock acquisition timeout\n",configTime()); + syslog (LOG_ERR, "Daemon fail and restart [please report bug]"); + longjmp (restartpoint, signum); +} + + +/*---------------------------------------------------------- + | printHelp + | print information from long option array + +--------------------------------------------------------- */ + + static void printHelp(char *name) { + int ind; + char command[20]; + + fprintf (stderr,"%s:\nallowed options\n", name); + for (ind=0; cliOptions [ind].name != NULL;ind++) + { + // display options + if (cliOptions [ind].has_arg == 0 ) + { + fprintf (stderr," --%-15s %s\n", cliOptions [ind].name, cliOptions[ind].help); + } else { + sprintf(command,"%s=xxxx", cliOptions [ind].name); + fprintf (stderr," --%-15s %s\n", command, cliOptions[ind].help); + } + } + fprintf (stderr,"Example:\n %s\\\n --verbose --port=1234 --smack=xxxx --mods=alsa:dbus\n", name); +} // end printHelp + +/*---------------------------------------------------------- + | writePidFile + | write a file in /var/run/AFB with pid + +--------------------------------------------------------- */ +static int writePidFile (AFB_config *config, int pid) { + FILE *file; + + // if no pid file configure just return + if (config->pidfile == NULL) return 0; + + // open pid file in write mode + file = fopen(config->pidfile,"w"); + if (file == NULL) { + fprintf (stderr,"%s ERR:writePidFile fail to open [%s]\n",configTime(), config->pidfile); + return -1; + } + + // write pid in file and close + fprintf (file, "%d\n", pid); + fclose (file); + return 0; +} + +/*---------------------------------------------------------- + | readPidFile + | read file in /var/run/AFB with pid + +--------------------------------------------------------- */ +static int readPidFile (AFB_config *config) { + int pid; + FILE *file; + int status; + + if (config->pidfile == NULL) return -1; + + // open pid file in write mode + file = fopen(config->pidfile,"r"); + if (file == NULL) { + fprintf (stderr,"%s ERR:readPidFile fail to open [%s]\n",configTime(), config->pidfile); + return -1; + } + + // write pid in file and close + status = fscanf (file, "%d\n", &pid); + fclose (file); + + // never kill pid 0 + if (status != 1) return -1; + + return (pid); +} + +/*---------------------------------------------------------- + | closeSession + | try to close everything before leaving + +--------------------------------------------------------- */ +static void closeSession (AFB_session *session) { + + +} + + +/*---------------------------------------------------------- + | listenLoop + | Main listening HTTP loop + +--------------------------------------------------------- */ +static void listenLoop (AFB_session *session) { + AFB_ERROR err; + + if (signal (SIGABRT, signalFail) == SIG_ERR) { + fprintf (stderr, "%s ERR: main fail to install Signal handler\n", configTime()); + return; + } + + // ------ Start httpd server + if (session->config->httpdPort > 0) { + + err = httpdStart (session); + if (err != AFB_SUCCESS) return; + + // infinite loop + httpdLoop(session); + + fprintf (stderr, "hoops returned from infinite loop [report bug]\n"); + } +} + + +/*--------------------------------------------------------- + | main + | Parse option and launch action + +--------------------------------------------------------- */ + +int main(int argc, char *argv[]) { + AFB_session *session; + char* programName = argv [0]; + int optionIndex = 0; + int optc, ind, consoleFD; + int pid, nbcmd, status; + AFB_config cliconfig; // temp structure to store CLI option before file config upload + + // ------------- Build session handler & init config ------- + session = configInit (); + memset (&cliconfig,0,sizeof(cliconfig)); + + // GNU CLI getopts nterface. + struct option ggcOption; + struct option *gnuOptions; + + // ------------------ Process Command Line ----------------------- + + // if no argument print help and return + if (argc < 2) { + printHelp(programName); + return (-1); + } + + // build GNU getopt info from cliOptions + nbcmd = sizeof (cliOptions) / sizeof (AFB_options); + gnuOptions = malloc (sizeof (ggcOption) * nbcmd); + for (ind=0; ind < nbcmd;ind++) { + gnuOptions [ind].name = cliOptions[ind].name; + gnuOptions [ind].has_arg = cliOptions[ind].has_arg; + gnuOptions [ind].flag = 0; + gnuOptions [ind].val = cliOptions[ind].val; + } + + // get all options from command line + while ((optc = getopt_long (argc, argv, "vsp?", gnuOptions, &optionIndex)) + != EOF) + { + switch (optc) + { + case SET_VERBOSE: + verbose = 1; + break; + + case SET_TCP_PORT: + if (optarg == 0) goto needValueForOption; + if (!sscanf (optarg, "%d", &cliconfig.httpdPort)) goto notAnInteger; + break; + + case SET_ROOT_DIR: + if (optarg == 0) goto needValueForOption; + cliconfig.rootdir = optarg; + break; + + case SET_ROOT_BASE: + if (optarg == 0) goto needValueForOption; + cliconfig.rootbase = optarg; + break; + + case SET_ROOT_API: + if (optarg == 0) goto needValueForOption; + cliconfig.rootapi = optarg; + break; + + case SET_SMACK: + if (optarg == 0) goto needValueForOption; + fprintf (stderr, "Not Implemented yet\n"); + cliconfig.smack = optarg; + break; + + case SET_PLUGINS: + if (optarg == 0) goto needValueForOption; + fprintf (stderr, "Not Implemented yet\n"); + cliconfig.plugins = optarg; + break; + + case SET_PID_FILE: + if (optarg == 0) goto needValueForOption; + cliconfig.pidfile = optarg; + break; + + case SET_SESSION_DIR: + if (optarg == 0) goto needValueForOption; + cliconfig.sessiondir = optarg; + break; + + case SET_CONFIG_FILE: + if (optarg == 0) goto needValueForOption; + cliconfig.configfile = optarg; + break; + + case SET_CACHE_TO: + if (optarg == 0) goto needValueForOption; + if (!sscanf (optarg, "%d", &cliconfig.cacheTimeout)) goto notAnInteger; + break; + + case SET_CONFIG_EXIT: + if (optarg != 0) goto noValueForOption; + session->configsave = 1; + session->forceexit = 1; + break; + + case SET_CONFIG_SAVE: + if (optarg != 0) goto noValueForOption; + session->configsave = 1; + break; + + case SET_cardid: + if (optarg == 0) goto needValueForOption; + if (!sscanf (optarg, "%d", &cliconfig.setuid)) goto notAnInteger; + break; + + case SET_FAKE_MOD: + if (optarg != 0) goto noValueForOption; + session->fakemod = 1; + break; + + case SET_FORGROUND: + if (optarg != 0) goto noValueForOption; + session->foreground = 1; + break; + + case SET_BACKGROUND: + if (optarg != 0) goto noValueForOption; + session->background = 1; + break; + + case KILL_PREV_REST: + if (optarg != 0) goto noValueForOption; + session->killPrevious = 1; + break; + + case KILL_PREV_EXIT: + if (optarg != 0) goto noValueForOption; + session->killPrevious = 2; + break; + + case DISPLAY_VERSION: + if (optarg != 0) goto noValueForOption; + printVersion(); + goto normalExit; + + case DISPLAY_HELP: + default: + printHelp(programName); + goto normalExit; + + } + } + // Create session config + configInit (/* session & config are initialized globally */); + + // if exist merge config file with CLI arguments + configLoadFile (session, &cliconfig); + + // ------------------ sanity check ---------------------------------------- + if ((session->background) && (session->foreground)) { + fprintf (stderr, "%s ERR: cannot select foreground & background at the same time\n",configTime()); + exit (-1); + } + + // ------------------ Some useful default values ------------------------- + if ((session->background == 0) && (session->foreground == 0)) session->foreground=1; + + // open syslog if ever needed + openlog("AGB-log", 0, LOG_DAEMON); + + // -------------- Try to kill any previsou process if asked --------------------- + if (session->killPrevious) { + pid = readPidFile (session->config); // enforce commandline option + switch (pid) { + case -1: + fprintf (stderr, "%s ERR:main --kill ignored no PID file [%s]\n",configTime(), session->config->pidfile); + break; + case 0: + fprintf (stderr, "%s ERR:main --kill ignored no active AFB process\n",configTime()); + break; + default: + status = kill (pid,SIGINT ); + if (status == 0) { + if (verbose) printf ("%s INF:main signal INTR sent to pid:%d \n", configTime(), pid); + } else { + // try kill -9 + status = kill (pid,9); + if (status != 0) fprintf (stderr, "%s ERR:main failled to killed pid=%d \n",configTime(), pid); + } + } // end switch pid + + if (session->killPrevious >= 2) goto normalExit; + } // end killPrevious + + + // ------------------ clean exit on CTR-C signal ------------------------ + if (signal (SIGINT, signalQuit) == SIG_ERR) { + fprintf (stderr, "%s Quit Signal received.",configTime()); + return (-1); + } + + // save exitpoint context when returning from longjmp closeSession and exit + status = setjmp (exitpoint); // return !+ when coming from longjmp + if (status != 0) { + if (verbose) printf ("INF:main returning from longjump after signal [%d]\n", status); + closeSession (session); + goto exitOnSignal; + } + + // let's run this program with a low priority + status=nice (20); + + + // ------------------ Finaly Process Commands ----------------------------- + + + + // if --save then store config on disk upfront + if (session->configsave) configStoreFile (session); + if (session->forceexit) exit (0); + + if (session->config->setuid) { + int err; + + err = setuid(session->config->setuid); + if (err) fprintf (stderr, "Fail to change program cardid error=%s", strerror(err)); + } + + // let's not take the risk to run as ROOT + if (getuid() == 0) status=setuid(65534); // run as nobody + + // check session dir and create if it does not exist + if (sessionCheckdir (session) != AFB_SUCCESS) goto errSessiondir; + if (verbose) fprintf (stderr, "AFB:notice Init config done\n"); + + + + // ---- run in foreground mode -------------------- + if (session->foreground) { + + if (verbose) fprintf (stderr,"AFB:notice Foreground mode\n"); + + // write a pid file for --kill-previous and --raise-debug option + status = writePidFile (session->config, getpid()); + if (status == -1) goto errorPidFile; + + // enter listening loop in foreground + listenLoop(session); + goto exitInitLoop; + } // end foreground + + + // --------- run in background mode ----------- + if (session->background) { + + // if (status != 0) goto errorCommand; + if (verbose) printf ("AFB: Entering background mode\n"); + + // open /dev/console to redirect output messAFBes + consoleFD = open(session->config->console, O_WRONLY | O_APPEND | O_CREAT , 0640); + if (consoleFD < 0) goto errConsole; + + // fork process when running background mode + pid = fork (); + + // son process get all data in standalone mode + if (pid == 0) { + + printf ("\nAFB: background mode [pid:%d console:%s]\n", getpid(),session->config->console); + if (verbose) printf ("AFB:info use '%s --restart --rootdir=%s # [--pidfile=%s] to restart daemon\n", programName,session->config->rootdir, session->config->pidfile); + + // redirect default I/O on console + close (2); status=dup(consoleFD); // redirect stderr + close (1); status=dup(consoleFD); // redirect stdout + close (0); // no need for stdin + close (consoleFD); + + setsid(); // allow father process to fully exit + sleep (2); // allow main to leave and release port + + fprintf (stderr, "----------------------------\n"); + fprintf (stderr, "%s INF:main background pid=%d\n", configTime(), getpid()); + fflush (stderr); + + // if everything look OK then look forever + syslog (LOG_ERR, "AFB: Entering infinite loop in background mode"); + + // should normally never return from this loop + listenLoop(session); + syslog (LOG_ERR, "AFB:FAIL background infinite loop exited check [%s]\n", session->config->console); + + goto exitInitLoop; + } + + // if fail nothing much to do + if (pid == -1) goto errorFork; + + // fork worked and we are in father process + status = writePidFile (session->config, pid); + if (status == -1) goto errorPidFile; + + // we are in father process, we don't need this one + exit (0); + + } // end background-foreground + +normalExit: + closeSession (session); // try to close everything before leaving + if (verbose) printf ("\n---- Application Framework Binder Normal End ------\n"); + exit (0); + +// ------------- Fatal ERROR display error and quit ------------- +errorPidFile: + fprintf (stderr,"\nERR:main Failled to write pid file [%s]\n\n", session->config->pidfile); + exit (-1); + +errorFork: + fprintf (stderr,"\nERR:main Failled to fork son process\n\n"); + exit (-1); + +needValueForOption: + fprintf (stderr,"\nERR:main option [--%s] need a value i.e. --%s=xxx\n\n" + ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name); + exit (-1); + +noValueForOption: + fprintf (stderr,"\nERR:main option [--%s] don't take value\n\n" + ,gnuOptions[optionIndex].name); + exit (-1); + +notAnInteger: + fprintf (stderr,"\nERR:main option [--%s] requirer an interger i.e. --%s=9\n\n" + ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name); + exit (-1); + +exitOnSignal: + fprintf (stderr,"\n%s INF:main pid=%d received exit signal (Hopefully crtl-C or --kill-previous !!!)\n\n" + ,configTime(), getpid()); + exit (-1); + +errConsole: + fprintf (stderr,"\nERR:cannot open /dev/console (use --foreground)\n\n"); + exit (-1); + +errSessiondir: + fprintf (stderr,"\nERR:cannot read/write session dir\n\n"); + exit (-1); + +errSoundCard: + fprintf (stderr,"\nERR:fail to probe sound cards\n\n"); + exit (-1); + +exitInitLoop: + // try to unlink pid file if any + if (session->background && session->config->pidfile != NULL) unlink (session->config->pidfile); + exit (-1); + +}; /* END main() */ + |