summaryrefslogtreecommitdiffstats
path: root/Src/Console.c
blob: 03738bf96fb6d18bfbf7642fc6054a1a825e9b5e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/*
 * Video On Demand Samples
 *
 * Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG
 *
 * 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 2 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/>.
 *
 * You may also obtain this software under a propriety license from Microchip.
 * Please contact Microchip for further information.
 *
 */

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "Console.h"

///Our shared memory key
static int shmkey = 072162537;
//The minimum priority
static ConsolePrio_t minPrio = PRIO_LOW;

/*! \cond PRIVATE */
typedef struct
{
    ///The first process will setup the critical section and set this variable to true.
    bool initialized;
    ///If set to true, multiple processeses are synced via shared memory.
    bool processSynced;
    ///If set to true, there is an segmented print ongoing (Start, Continue, Exit).
    bool criticalSection;
    //If is in a critical segmented print, this variable will hold the prio for Start, Continue, Exit.
    ConsolePrio_t criticalSectionPrio;
    ///Handle of the shared mutex.
    pthread_mutex_t mutex;
} sharedData_t;
/*! \endcond */

/*----------------------------------------------------------*/
/*! \brief Pointer to the shared memory instance.
 */
/*----------------------------------------------------------*/
static sharedData_t *data = NULL;

void ConsoleInit( bool synchronizeProcesses )
{
    pthread_mutexattr_t attr;

    if( synchronizeProcesses )
    {
        int shmid = shmget( shmkey, sizeof( sharedData_t ), IPC_CREAT | 0666 );
        if( ( sharedData_t * )-1 == ( data = ( sharedData_t* )shmat( shmid, NULL, 0 ) ) )
        {
            data = NULL;
            fprintf( stderr, RED"ConsoleInit failed, because shared memory could not be accessed."RESETCOLOR"\n" );
            return;
        }
    }
    else
    {
        data = ( sharedData_t * )calloc( 1, sizeof( sharedData_t ) );
    }
    if( ( NULL != data ) && !data->initialized )
    {
        data->processSynced = synchronizeProcesses;
        data->initialized = true;
        data->criticalSection = false;

        pthread_mutexattr_init( &attr );
        if( synchronizeProcesses )
            pthread_mutexattr_setpshared( &attr, PTHREAD_PROCESS_SHARED );
        else
            pthread_mutexattr_setpshared( &attr, PTHREAD_PROCESS_PRIVATE );
        pthread_mutex_init( &data->mutex, &attr );
    }
}

void ConsoleDeinit( void )
{
    if( NULL != data && !data->processSynced )
    {
        free( data );
        data = NULL;
    }
}

void ConsoleSetPrio( ConsolePrio_t prio )
{
    minPrio = prio;
}

void ConsolePrintf( ConsolePrio_t prio, const char *statement, ... )
{
    int err;
    if( prio < minPrio || NULL == statement )
        return;
    if( NULL == data )
    {
        fprintf( stderr, RED"ConsolePrintf data was null"RESETCOLOR"\n" );
        return;
    }
    if( 0 != ( err = pthread_mutex_lock( &data->mutex ) ) )
    {
        fprintf( stderr, RED"ConsolePrintf, pthread_mutex_lock error: %d"RESETCOLOR"\n", err );
        return;
    }

    va_list args;
    va_start( args, statement );
    vfprintf( stderr, statement, args );
    va_end( args );

    if( 0 != ( err = pthread_mutex_unlock( &data->mutex ) ) )
    {
        fprintf( stderr, RED"ConsolePrintf, pthread_mutex_unlock error: %d"RESETCOLOR"\n", err );
        return;
    }
}

void ConsolePrintfStart( ConsolePrio_t prio, const char *statement, ... )
{
    int err;
    if( NULL == data )
    {
        fprintf( stderr, RED"ConsolePrintfStart data was null"RESETCOLOR"\n" );
        return;
    }
    if( 0 != ( err = pthread_mutex_lock( &data->mutex ) ) )
    {
        fprintf( stderr, RED"ConsolePrintfStart, pthread_mutex_lock error: %d"RESETCOLOR"\n", err );
        return;
    }
    data->criticalSection = true;
    data->criticalSectionPrio = prio;

    if( data->criticalSectionPrio >= minPrio && NULL != statement )
    {
        va_list args;
        va_start( args, statement );
        vfprintf( stderr, statement, args );
        va_end( args );
    }
}

void ConsolePrintfContinue( const char *statement, ... )
{
    if( NULL == data )
    {
        fprintf( stderr, RED"ConsolePrintfContinue data was null"RESETCOLOR"\n" );
        return;
    }
    if( !data->criticalSection )
    {
        fprintf( stderr, RED"ConsolePrintfContinue not in critical section"RESETCOLOR"\n" );
        return;
    }

    if( data->criticalSectionPrio >= minPrio && NULL != statement )
    {
        va_list args;
        va_start( args, statement );
        vfprintf( stderr, statement, args );
        va_end( args );
    }
}

void ConsolePrintfExit( const char *statement, ... )
{
    int err;
    if( NULL == data )
    {
        fprintf( stderr, RED"ConsolePrintfExit data was null"RESETCOLOR"\n" );
        return;
    }
    if( !data->criticalSection )
    {
        fprintf( stderr, RED"ConsolePrintfExit not in critical section"RESETCOLOR"\n" );
        return;
    }
    if( data->criticalSectionPrio >= minPrio && NULL != statement )
    {
        va_list args;
        va_start( args, statement );
        vfprintf( stderr, statement, args );
        va_end( args );
    }
    data->criticalSection = false;
    if( 0 != ( err = pthread_mutex_unlock( &data->mutex ) ) )
    {
        fprintf( stderr, RED"ConsolePrintfExit, pthread_mutex_unlock error: %d"RESETCOLOR"\n", err );
    }
}
quot; , "Get all in background mode"}, {SET_TCP_PORT ,1,"port" , "HTTP listening TCP port [default 1234]"}, {SET_ROOT_DIR ,1,"rootdir" , "Root Directory [default $HOME/.AFB]"}, {SET_ROOT_HTTP ,1,"roothttp" , "HTTP Root Directory [default rootdir]"}, {SET_ROOT_BASE ,1,"rootbase" , "Angular Base Root URL [default /opa]"}, {SET_ROOT_API ,1,"rootapi" , "HTML Root API URL [default /api]"}, {SET_ALIAS ,1,"alias" , "Muliple url map outside of rootdir [eg: --alias=/icons:/usr/share/icons]"}, {SET_APITIMEOUT ,1,"apitimeout" , "Binding API timeout in seconds [default 10]"}, {SET_CNTXTIMEOUT ,1,"cntxtimeout" , "Client Session Context Timeout [default 900]"}, {SET_CACHE_TIMEOUT,1,"cache-eol" , "Client cache end of live [default 3600]"}, {SET_SESSION_DIR ,1,"sessiondir" , "Sessions file path [default rootdir/sessions]"}, {SET_LDPATH ,1,"ldpaths" , "Load bindingss from dir1:dir2:... [default = "BINDING_INSTALL_DIR"]"}, {SET_AUTH_TOKEN ,1,"token" , "Initial Secret [default=no-session, --token="" for session without authentication]"}, {DISPLAY_VERSION ,0,"version" , "Display version and copyright"}, {DISPLAY_HELP ,0,"help" , "Display this help"}, {SET_MODE ,1,"mode" , "set the mode: either local, remote or global"}, {SET_READYFD ,1,"readyfd" , "set the #fd to signal when ready"}, {DBUS_CLIENT ,1,"dbus-client" , "bind to an afb service through dbus"}, {DBUS_SERVICE ,1,"dbus-server" , "provides an afb service through dbus"}, {WS_CLIENT ,1,"ws-client" , "bind to an afb service through websocket"}, {WS_SERVICE ,1,"ws-server" , "provides an afb service through websockets"}, {SO_BINDING ,1,"binding" , "load the binding of path"}, {SET_SESSIONMAX ,1,"session-max" , "max count of session simultaneously [default 10]"}, {0, 0, NULL, NULL} }; /*---------------------------------------------------------- | printversion | print version and copyright +--------------------------------------------------------- */ static void printVersion (FILE *file) { fprintf(file, "\n----------------------------------------- \n"); fprintf(file, " AFB [Application Framework Binder] version=%s |\n", AFB_VERSION); fprintf(file, " \n"); fprintf(file, " Copyright (C) 2015, 2016 \"IoT.bzh\" [fulup -at- iot.bzh]\n"); fprintf(file, " AFB comes with ABSOLUTELY NO WARRANTY.\n"); fprintf(file, " Licence Apache 2\n\n"); exit (0); } /*---------------------------------------------------------- | printHelp | print information from long option array +--------------------------------------------------------- */ static void printHelp(FILE *file, const char *name) { int ind; char command[50]; fprintf (file, "%s:\nallowed options\n", name); for (ind=0; cliOptions [ind].name != NULL;ind++) { // display options if (cliOptions [ind].has_arg == 0 ) { fprintf (file, " --%-15s %s\n", cliOptions [ind].name, cliOptions[ind].help); } else { sprintf(command, "%s=xxxx", cliOptions [ind].name); fprintf (file, " --%-15s %s\n", command, cliOptions[ind].help); } } fprintf (file, "Example:\n %s\\\n --verbose --port=1234 --token='azerty' --ldpaths=build/bindings:/usr/lib64/agl/bindings\n", name); } // load config from disk and merge with CLI option static void config_set_default (struct afb_config * config) { // default HTTP port if (config->httpdPort == 0) config->httpdPort = 1234; // default binding API timeout if (config->apiTimeout == 0) config->apiTimeout = DEFLT_API_TIMEOUT; // default AUTH_TOKEN if (config->token == NULL) config->token = DEFLT_AUTH_TOKEN; // cache timeout default one hour if (config->cacheTimeout == 0) config->cacheTimeout = DEFLT_CACHE_TIMEOUT; // cache timeout default one hour if (config->cntxTimeout == 0) config->cntxTimeout = DEFLT_CNTX_TIMEOUT; // max count of sessions if (config->nbSessionMax == 0) config->nbSessionMax = CTX_NBCLIENTS; if (config->rootdir == NULL) { config->rootdir = getenv("AFBDIR"); if (config->rootdir == NULL) { config->rootdir = malloc (512); strncpy (config->rootdir, getenv("HOME"),512); strncat (config->rootdir, "/.AFB",512); } // if directory does not exist createit mkdir (config->rootdir, O_RDWR | S_IRWXU | S_IRGRP); } // if no Angular/HTML5 rootbase let's try '/' as default if (config->roothttp == NULL) config->roothttp = "."; if (config->rootbase == NULL) config->rootbase = "/opa"; if (config->rootapi == NULL) config->rootapi = "/api"; if (config->ldpaths == NULL) config->ldpaths = BINDING_INSTALL_DIR; // if no session dir create a default path from rootdir if (config->sessiondir == NULL) { config->sessiondir = malloc (512); strncpy (config->sessiondir, config->rootdir, 512); strncat (config->sessiondir, "/sessions",512); } // if no config dir create a default path from sessiondir if (config->console == NULL) { config->console = malloc (512); strncpy (config->console, config->sessiondir, 512); strncat (config->console, "/AFB-console.out",512); } } /*--------------------------------------------------------- | main | Parse option and launch action +--------------------------------------------------------- */ static void add_item(struct afb_config *config, int kind, char *value) { struct afb_config_item *item = malloc(sizeof *item); if (item == NULL) { ERROR("out of memory"); exit(1); } item->kind = kind; item->value = value; item->previous = config->items; config->items = item; } static void parse_arguments(int argc, char *argv[], struct afb_config *config) { char* programName = argv [0]; int optionIndex = 0; int optc, ind; int nbcmd; struct option *gnuOptions; // ------------------ Process Command Line ----------------------- // if no argument print help and return if (argc < 2) { printHelp(stderr, programName); exit(1); } // build GNU getopt info from cliOptions nbcmd = sizeof (cliOptions) / sizeof (AFB_options); gnuOptions = malloc (sizeof (*gnuOptions) * (unsigned)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: verbosity++; break; case SET_TCP_PORT: if (optarg == 0) goto needValueForOption; if (!sscanf (optarg, "%d", &config->httpdPort)) goto notAnInteger; break; case SET_APITIMEOUT: if (optarg == 0) goto needValueForOption; if (!sscanf (optarg, "%d", &config->apiTimeout)) goto notAnInteger; break; case SET_CNTXTIMEOUT: if (optarg == 0) goto needValueForOption; if (!sscanf (optarg, "%d", &config->cntxTimeout)) goto notAnInteger; break; case SET_ROOT_DIR: if (optarg == 0) goto needValueForOption; config->rootdir = optarg; INFO("Forcing Rootdir=%s",config->rootdir); break; case SET_ROOT_HTTP: if (optarg == 0) goto needValueForOption; config->roothttp = optarg; INFO("Forcing Root HTTP=%s",config->roothttp); break; case SET_ROOT_BASE: if (optarg == 0) goto needValueForOption; config->rootbase = optarg; INFO("Forcing Rootbase=%s",config->rootbase); break; case SET_ROOT_API: if (optarg == 0) goto needValueForOption; config->rootapi = optarg; INFO("Forcing Rootapi=%s",config->rootapi); break; case SET_ALIAS: if (optarg == 0) goto needValueForOption; if ((unsigned)config->aliascount < sizeof (config->aliasdir) / sizeof (config->aliasdir[0])) { config->aliasdir[config->aliascount].url = strsep(&optarg,":"); if (optarg == NULL) { ERROR("missing ':' in alias %s, ignored", config->aliasdir[config->aliascount].url); } else { config->aliasdir[config->aliascount].path = optarg; INFO("Alias url=%s path=%s", config->aliasdir[config->aliascount].url, config->aliasdir[config->aliascount].path); config->aliascount++; } } else { ERROR("Too many aliases [max:%d] %s ignored", MAX_ALIAS, optarg); } break; case SET_AUTH_TOKEN: if (optarg == 0) goto needValueForOption; config->token = optarg; break; case SET_LDPATH: if (optarg == 0) goto needValueForOption; config->ldpaths = optarg; break; case SET_SESSION_DIR: if (optarg == 0) goto needValueForOption; config->sessiondir = optarg; break; case SET_CACHE_TIMEOUT: if (optarg == 0) goto needValueForOption; if (!sscanf (optarg, "%d", &config->cacheTimeout)) goto notAnInteger; break; case SET_SESSIONMAX: if (optarg == 0) goto needValueForOption; if (!sscanf (optarg, "%d", &config->nbSessionMax)) goto notAnInteger; break; case SET_FORGROUND: if (optarg != 0) goto noValueForOption; config->background = 0; break; case SET_BACKGROUND: if (optarg != 0) goto noValueForOption; config->background = 1; break; case SET_MODE: if (optarg == 0) goto needValueForOption; if (!strcmp(optarg, "local")) config->mode = AFB_MODE_LOCAL; else if (!strcmp(optarg, "remote")) config->mode = AFB_MODE_REMOTE; else if (!strcmp(optarg, "global")) config->mode = AFB_MODE_GLOBAL; else goto badMode; break; case SET_READYFD: if (optarg == 0) goto needValueForOption; if (!sscanf (optarg, "%u", &config->readyfd)) goto notAnInteger; break; case DBUS_CLIENT: case DBUS_SERVICE: case WS_CLIENT: case WS_SERVICE: case SO_BINDING: if (optarg == 0) goto needValueForOption; add_item(config, optc, optarg); break; case DISPLAY_VERSION: if (optarg != 0) goto noValueForOption; printVersion(stdout); break; case DISPLAY_HELP: default: printHelp(stdout, programName); exit(0); } } free(gnuOptions); config_set_default (config); return; needValueForOption: ERROR("AFB-daemon option [--%s] need a value i.e. --%s=xxx" ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name); exit (1); notAnInteger: ERROR("AFB-daemon option [--%s] requirer an interger i.e. --%s=9" ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name); exit (1); noValueForOption: ERROR("AFB-daemon option [--%s] don't take value" ,gnuOptions[optionIndex].name); exit (1); badMode: ERROR("AFB-daemon option [--%s] only accepts local, global or remote." ,gnuOptions[optionIndex].name); exit (1); } /*---------------------------------------------------------- | closeSession | try to close everything before leaving +--------------------------------------------------------- */ static void closeSession (int status, void *data) { /* struct afb_config *config = data; */ } /*---------------------------------------------------------- | daemonize | set the process in background +--------------------------------------------------------- */ static void daemonize(struct afb_config *config) { int consoleFD; int pid; // open /dev/console to redirect output messAFBes consoleFD = open(config->console, O_WRONLY | O_APPEND | O_CREAT , 0640); if (consoleFD < 0) { ERROR("AFB-daemon cannot open /dev/console (use --foreground)"); exit (1); } // fork process when running background mode pid = fork (); // if fail nothing much to do if (pid == -1) { ERROR("AFB-daemon Failed to fork son process"); exit (1); } // if in father process, just leave if (pid != 0) _exit (0); // son process get all data in standalone mode NOTICE("background mode [pid:%d console:%s]", getpid(),config->console); // redirect default I/O on console close (2); dup(consoleFD); // redirect stderr close (1); dup(consoleFD); // redirect stdout close (0); // no need for stdin close (consoleFD); #if 0 setsid(); // allow father process to fully exit sleep (2); // allow main to leave and release port #endif } /*--------------------------------------------------------- | http server | Handles the HTTP server +--------------------------------------------------------- */ static int init_http_server(struct afb_hsrv *hsrv, struct afb_config * config) { int idx, dfd; dfd = afb_common_rootdir_get_fd(); if (!afb_hsrv_add_handler(hsrv, config->rootapi, afb_hswitch_websocket_switch, NULL, 20)) return 0; if (!afb_hsrv_add_handler(hsrv, config->rootapi, afb_hswitch_apis, NULL, 10)) return 0; for (idx = 0; idx < config->aliascount; idx++) if (!afb_hsrv_add_alias (hsrv, config->aliasdir[idx].url, dfd, config->aliasdir[idx].path, 0, 0)) return 0; if (!afb_hsrv_add_alias(hsrv, "", dfd, config->roothttp, -10, 1)) return 0; if (!afb_hsrv_add_handler(hsrv, config->rootbase, afb_hswitch_one_page_api_redirect, NULL, -20)) return 0; return 1; } static struct afb_hsrv *start_http_server(struct afb_config * config) { int rc; struct afb_hsrv *hsrv; if (afb_hreq_init_download_path("/tmp")) { /* TODO: sessiondir? */ ERROR("unable to set the tmp directory"); return NULL; } hsrv = afb_hsrv_create(); if (hsrv == NULL) { ERROR("memory allocation failure"); return NULL; } if (!afb_hsrv_set_cache_timeout(hsrv, config->cacheTimeout) || !init_http_server(hsrv, config)) { ERROR("initialisation of httpd failed"); afb_hsrv_put(hsrv); return NULL; } NOTICE("Waiting port=%d rootdir=%s", config->httpdPort, config->rootdir); NOTICE("Browser URL= http:/*localhost:%d", config->httpdPort); rc = afb_hsrv_start(hsrv, (uint16_t) config->httpdPort, 15); if (!rc) { ERROR("starting of httpd failed"); afb_hsrv_put(hsrv); return NULL; } return hsrv; } static void start_items(struct afb_config_item *item) { if (item != NULL) { /* keeps the order */ start_items(item->previous); switch(item->kind) { case DBUS_CLIENT: if (afb_api_dbus_add_client(item->value) < 0) { ERROR("can't start the afb-dbus client of path %s",item->value); exit(1); } break; case DBUS_SERVICE: if (afb_api_dbus_add_server(item->value) < 0) { ERROR("can't start the afb-dbus service of path %s",item->value); exit(1); } break; case WS_CLIENT: if (afb_api_ws_add_client(item->value) < 0) { ERROR("can't start the afb-websocket client of path %s",item->value); exit(1); } break; case WS_SERVICE: if (afb_api_ws_add_server(item->value) < 0) { ERROR("can't start the afb-websocket service of path %s",item->value); exit(1); } break; case SO_BINDING: if (afb_api_so_add_binding(item->value) < 0) { ERROR("can't start the binding of path %s",item->value); exit(1); } break; default: ERROR("unexpected internal error"); exit(1); } /* frre the item */ free(item); } } /*--------------------------------------------------------- | main | Parse option and launch action +--------------------------------------------------------- */ int main(int argc, char *argv[]) { struct afb_hsrv *hsrv; struct afb_config *config; struct sd_event *eventloop; LOGAUTH("afb-daemon"); // ------------- Build session handler & init config ------- config = calloc (1, sizeof (struct afb_config)); on_exit(closeSession, config); parse_arguments(argc, argv, config); // ------------------ sanity check ---------------------------------------- if (config->httpdPort <= 0) { ERROR("no port is defined"); exit (1); } afb_api_so_set_timeout(config->apiTimeout); if (config->ldpaths) { if (afb_api_so_add_pathset(config->ldpaths) < 0) { ERROR("initialisation of bindings within %s failed", config->ldpaths); exit(1); } } start_items(config->items); config->items = NULL; ctxStoreInit(config->nbSessionMax, config->cntxTimeout, config->token, afb_apis_count()); if (!afb_hreq_init_cookie(config->httpdPort, config->rootapi, DEFLT_CNTX_TIMEOUT)) { ERROR("initialisation of cookies failed"); exit (1); } if (afb_sig_handler_init() < 0) { ERROR("failed to initialise signal handlers"); return 1; } if (afb_common_rootdir_set(config->rootdir) < 0) { ERROR("failed to set common root directory"); return 1; } if (afb_thread_init(3, 1, 20) < 0) { ERROR("failed to initialise threading"); return 1; } // let's run this program with a low priority nice (20); // ------------------ Finaly Process Commands ----------------------------- // let's not take the risk to run as ROOT //if (getuid() == 0) goto errorNoRoot; DEBUG("Init config done"); // --------- run ----------- if (config->background) { // --------- in background mode ----------- INFO("entering background mode"); daemonize(config); } else { // ---- in foreground mode -------------------- INFO("entering foreground mode"); } /* ignore any SIGPIPE */ signal(SIGPIPE, SIG_IGN); /* start the HTTP server */ hsrv = start_http_server(config); if (hsrv == NULL) exit(1); /* start the services */ if (afb_apis_start_all_services(1) < 0) exit(1); if (config->readyfd != 0) { static const char readystr[] = "READY=1"; write(config->readyfd, readystr, sizeof(readystr) - 1); close(config->readyfd); } // infinite loop eventloop = afb_common_get_event_loop(); for(;;) sd_event_run(eventloop, 30000000); WARNING("hoops returned from infinite loop [report bug]"); return 0; }