blob: 7338be03b86d4db9020937a7c1fa8a53e1047402 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007-2017 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
/*
* Agent main module.
*/
#include <tcf/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <signal.h>
#include <tcf/framework/mdep-threads.h>
#include <tcf/framework/asyncreq.h>
#include <tcf/framework/events.h>
#include <tcf/framework/errors.h>
#include <tcf/framework/trace.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/exceptions.h>
#include <tcf/framework/channel_tcp.h>
#include <tcf/framework/plugins.h>
#include <tcf/services/discovery.h>
#include <tcf/http/http.h>
#include <tcf/main/test.h>
#include <tcf/main/cmdline.h>
#include <tcf/main/services.h>
#include <tcf/main/framework.h>
#include <tcf/main/gdb-rsp.h>
#include <tcf/main/server.h>
#include <tcf/main/main_hooks.h>
#ifndef ENABLE_SignalHandlers
# define ENABLE_SignalHandlers 1
#endif
#ifndef DEFAULT_SERVER_URL
# define DEFAULT_SERVER_URL "TCP:"
#endif
/* Hook before all TCF initialization. This hook can add local variables. */
#ifndef PRE_INIT_HOOK
#define PRE_INIT_HOOK do {} while(0)
#endif
/* Hook before any TCF threads are created. This hook can do
* initialization that will affect all threads and call most basic TCF
* functions, like post_event. */
#ifndef PRE_THREADING_HOOK
#define PRE_THREADING_HOOK do {} while(0)
#endif
/* Hook before becoming a daemon process. This hook can output
* banners and other information. */
#ifndef PRE_DAEMON_HOOK
#define PRE_DAEMON_HOOK do {} while(0)
#endif
/* Hook to add help text. */
#ifndef HELP_TEXT_HOOK
#define HELP_TEXT_HOOK
#endif
/* Hook for illegal option case. This hook allows for handling off
* additional options. */
#ifndef ILLEGAL_OPTION_HOOK
#define ILLEGAL_OPTION_HOOK do {} while(0)
#endif
/* Signal handler. This hook extends behavior when process
* exits due to received signal. */
#ifndef SIGNAL_HANDLER_HOOK
#define SIGNAL_HANDLER_HOOK do {} while(0)
#endif
/* Hook run immediatly after option parsing loop. */
#ifndef POST_OPTION_HOOK
#define POST_OPTION_HOOK do {} while(0)
#endif
/* Hook for USAGE string */
#ifndef USAGE_STRING_HOOK
#define USAGE_STRING_HOOK \
"Usage: agent [OPTION]...", \
"Start Target Communication Framework agent."
#endif
/* Hook before exiting main(). This hook can cleanup temporary
* files, etc. */
#ifndef PRE_EXIT_HOOK
#define PRE_EXIT_HOOK do {} while(0)
#endif
static const char * progname;
static unsigned int idle_timeout;
static unsigned int idle_count;
static void check_idle_timeout(void * args) {
if (list_is_empty(&client_connection_root)) {
idle_count++;
if (idle_count > idle_timeout) {
trace(LOG_ALWAYS, "No connections for %d seconds, shutting down", idle_timeout);
cancel_event_loop();
return;
}
}
post_event_with_delay(check_idle_timeout, NULL, 1000000);
}
static void channel_closed(Channel *c) {
/* Reset idle_count if there are short lived connections */
idle_count = 0;
}
#if ENABLE_SignalHandlers
static void shutdown_event(void * args) {
cancel_event_loop();
}
static void signal_handler(int sig) {
SIGNAL_HANDLER_HOOK;
if (sig == SIGINT || sig == SIGTERM) {
exit_event_loop();
}
else if (is_dispatch_thread()) {
signal(sig, SIG_DFL);
raise(sig);
}
else {
post_event(shutdown_event, NULL);
}
}
#if defined(_POSIX_C_SOURCE) && !defined(__MINGW32__)
static void * signal_handler_thread(void * arg) {
int sig = 0;
sigset_t * set = (sigset_t *)arg;
sigwait(set, &sig);
exit_event_loop();
return NULL;
}
#endif
#if defined(_WIN32) || defined(__CYGWIN__)
static LONG NTAPI VectoredExceptionHandler(PEXCEPTION_POINTERS x) {
if (is_dispatch_thread()) {
DWORD exception_code = x->ExceptionRecord->ExceptionCode;
if (exception_code == EXCEPTION_IN_PAGE_ERROR) {
int error = ERR_OTHER;
if (x->ExceptionRecord->NumberParameters >= 3) {
ULONG status = (ULONG)x->ExceptionRecord->ExceptionInformation[2];
if (status != 0) error = set_nt_status_errno(status);
}
str_exception(error, "In page error");
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
static BOOL CtrlHandler(DWORD ctrl) {
switch(ctrl) {
case CTRL_C_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_SHUTDOWN_EVENT:
exit_event_loop();
return TRUE;
}
return FALSE;
}
#endif
static void ini_signal_handlers(void) {
#if defined(_POSIX_C_SOURCE) && !defined(__MINGW32__)
pthread_t thread;
static sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
if (sigprocmask(SIG_BLOCK, &set, NULL) < 0) check_error(errno);
check_error(pthread_create(&thread, NULL, &signal_handler_thread, (void *)&set));
#else
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
#endif
signal(SIGABRT, signal_handler);
signal(SIGILL, signal_handler);
#if defined(_WIN32) || defined(__CYGWIN__)
SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE);
AddVectoredExceptionHandler(1, VectoredExceptionHandler);
#endif
}
#endif /* ENABLE_SignalHandlers */
#if !defined(_WRS_KERNEL)
static const char * help_text[] = {
USAGE_STRING_HOOK,
" -d run in daemon mode (output is sent to system logger)",
#if ENABLE_Cmdline
" -i run in interactive mode",
#endif
" -L<file> enable logging, use -L- to send log to stderr",
#if ENABLE_Trace
" -l<level> set log level, the level is comma separated list of:",
"@",
#endif
" -s<url> set agent listening port and protocol, default is " DEFAULT_SERVER_URL,
" -S print server properties in Json format to stdout",
#if ENABLE_GdbRemoteSerialProtocol
" -g<port> start GDB Remote Serial Protocol server at the specified TCP port",
#endif
" -I<idle-seconds> exit if there are no connections for the specified time",
#if ENABLE_Plugins
" -P<dir> set agent plugins directory name",
#endif
#if ENABLE_HttpServer
" -H<dir> add HTML directory name",
#endif
#if ENABLE_SSL
" -c generate SSL certificate and exit",
#endif
HELP_TEXT_HOOK
NULL
};
static void show_help(void) {
const char ** p = help_text;
while (*p != NULL) {
if (**p == '@') {
#if ENABLE_Trace
struct trace_mode * tm = trace_mode_table;
while (tm->mode != 0) {
fprintf(stderr, " %-12s %s (%#x)\n", tm->name, tm->description, tm->mode);
tm++;
}
#endif
p++;
}
else {
fprintf(stderr, "%s\n", *p++);
}
}
}
#endif
#if defined(_WRS_KERNEL)
int tcf(void);
int tcf(void) {
#else
int main(int argc, char ** argv) {
int c;
int ind;
int daemon = 0;
const char * log_name = NULL;
const char * log_level = NULL;
#endif
int interactive = 0;
int print_server_properties = 0;
const char * url = DEFAULT_SERVER_URL;
TCFBroadcastGroup * bcg;
Protocol * proto;
PRE_INIT_HOOK;
ini_framework();
PRE_THREADING_HOOK;
#if !defined(_WRS_KERNEL)
#if ENABLE_RCBP_TEST
for (ind = 1; ind < argc; ind++) {
char * s = argv[ind];
if (*s++ != '-') break;
if (*s++ == 't') {
test_proc();
exit(0);
}
}
#endif
#endif
#if ENABLE_SignalHandlers
ini_signal_handlers();
#endif
#if defined(_WRS_KERNEL)
progname = "tcf";
open_log_file("-");
log_mode = 0;
#else
progname = argv[0];
/* Parse arguments */
for (ind = 1; ind < argc; ind++) {
char * s = argv[ind];
if (*s++ != '-') break;
while (s && (c = *s++) != '\0') {
switch (c) {
case 'i':
interactive = 1;
break;
case 'd':
#if defined(_WIN32) || defined(__CYGWIN__)
/* For Windows the only way to detach a process is to
* create a new process, so we patch the -d option to
* -D for the second time we get invoked so we don't
* keep on creating new processes forever. */
s[-1] = 'D';
daemon = 2;
break;
case 'D':
#endif
daemon = 1;
break;
case 'c':
generate_ssl_certificate();
exit(0);
break;
case 'S':
print_server_properties = 1;
break;
case 'h':
show_help();
exit(0);
case 'I':
#if ENABLE_Trace
case 'l':
#endif
case 'L':
case 's':
#if ENABLE_GdbRemoteSerialProtocol
case 'g':
#endif
#if ENABLE_Plugins
case 'P':
#endif
#if ENABLE_HttpServer
case 'H':
#endif
if (*s == '\0') {
if (++ind >= argc) {
fprintf(stderr, "%s: error: no argument given to option '%c'\n", progname, c);
exit(1);
}
s = argv[ind];
}
switch (c) {
case 'I':
idle_timeout = strtol(s, 0, 0);
break;
#if ENABLE_Trace
case 'l':
log_level = s;
parse_trace_mode(log_level, &log_mode);
break;
#endif
case 'L':
log_name = s;
break;
case 's':
url = s;
break;
#if ENABLE_GdbRemoteSerialProtocol
case 'g':
if (ini_gdb_rsp(s) < 0) {
fprintf(stderr, "Cannot create GDB server: %s\n", errno_to_str(errno));
exit(1);
}
break;
#endif
#if ENABLE_Plugins
case 'P':
plugins_path = s;
break;
#endif
#if ENABLE_HttpServer
case 'H':
{
struct stat st;
char * fnm = canonicalize_file_name(s);
if (fnm == NULL || stat(fnm, &st) < 0) {
fprintf(stderr, "%s: invalid option '-H %s': %s\n", progname, s, errno_to_str(errno));
free(fnm);
exit(1);
}
add_http_directory(fnm);
free(fnm);
}
break;
#endif
}
s = NULL;
break;
default:
ILLEGAL_OPTION_HOOK;
fprintf(stderr, "%s: error: illegal option '%c'\n", progname, c);
show_help();
exit(1);
}
}
}
POST_OPTION_HOOK;
if (daemon) {
#if defined(_WIN32) || defined(__CYGWIN__)
become_daemon(daemon > 1 ? argv : NULL);
#else
become_daemon();
#endif
}
open_log_file(log_name);
#endif
bcg = broadcast_group_alloc();
proto = protocol_alloc();
/* The static services must be initialised before the plugins */
#if ENABLE_Cmdline
if (interactive) ini_cmdline_handler(interactive, proto);
#else
if (interactive) fprintf(stderr, "Warning: This version does not support interactive mode.\n");
#endif
ini_services(proto, bcg);
#if !defined(_WRS_KERNEL)
/* Reparse log level in case initialization cause additional
* levels to be registered */
if (log_level != NULL && parse_trace_mode(log_level, &log_mode) != 0) {
fprintf(stderr, "Cannot parse log level: %s\n", log_level);
exit(1);
}
#endif
if (ini_server(url, proto, bcg) < 0) {
fprintf(stderr, "Cannot create listening port: %s\n", errno_to_str(errno));
exit(1);
}
discovery_start();
if (print_server_properties) {
ChannelServer * s;
char * server_properties;
assert(!list_is_empty(&channel_server_root));
s = servlink2channelserverp(channel_server_root.next);
server_properties = channel_peer_to_json(s->ps);
printf("Server-Properties: %s\n", server_properties);
fflush(stdout);
trace(LOG_ALWAYS, "Server-Properties: %s", server_properties);
loc_free(server_properties);
}
PRE_DAEMON_HOOK;
#if !defined(_WRS_KERNEL)
if (daemon) close_out_and_err();
#endif
if (idle_timeout != 0) {
add_channel_close_listener(channel_closed);
check_idle_timeout(NULL);
}
/* Process events - must run on the initial thread since ptrace()
* returns ECHILD otherwise, thinking we are not the owner. */
run_event_loop();
discovery_stop();
#if ENABLE_Plugins
plugins_destroy();
#endif /* ENABLE_Plugins */
PRE_EXIT_HOOK;
fprintf(stderr, "\n");
return 0;
}