aboutsummaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c785
1 files changed, 785 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..8fbd6ab
--- /dev/null
+++ b/main.c
@@ -0,0 +1,785 @@
+#include "thpool.h"
+#include "plugins.h"
+#include "plugin_registry.h"
+#include "mcin.h"
+#include "common.h"
+#include "rcon_host.h"
+#include "threads_util.h"
+
+#include <limits.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <semaphore.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <inttypes.h>
+#include <limits.h>
+
+#ifndef CONTROL_SOCKET_PATH
+#define CONTROL_SOCKET_PATH "/run/extmc.ctl"
+#endif
+
+static pthread_mutex_t process_mutex = PTHREAD_MUTEX_INITIALIZER;
+static threadpool thpool = NULL;
+static bool received_sigterm = false;
+static sem_t exit_sem;
+static int ctl_fd = -1;
+
+static void exclusive_section_enter()
+{
+ pthread_mutex_lock(&process_mutex);
+}
+
+static void exclusive_section_leave()
+{
+ pthread_mutex_unlock(&process_mutex);
+}
+
+static int autoload(const char *config_path)
+{
+ FILE *file = fopen(config_path, "r");
+ if(file == NULL)
+ {
+ char buf[128];
+ int r = errno;
+ strerror_r(r, buf, 128);
+ fprintf(stderr, _("Cannot open %s: %s.\n"), config_path, buf);
+ return r;
+ }
+ int r = 0;
+ while(true)
+ {
+ char *current_path = calloc(4098, sizeof(char));
+ if(current_path == NULL)
+ {
+ r = errno;
+ fprintf(stderr, _("Cannot allocate memory: %d.\n"), r);
+ fclose(file);
+ return r;
+ }
+ for(unsigned int i = 2; i <= UINT_MAX; i ++)
+ {
+ if(fgets(&current_path[(i - 2) * 4097], 4098, file) == NULL)
+ {
+ free(current_path);
+ current_path = NULL;
+ break;
+ }
+ if(current_path[strlen(current_path) - 1] != '\n')
+ {
+ char *current_path_ext = realloc(current_path, 4098 * sizeof(char) * i);
+ if(current_path_ext == NULL)
+ {
+ r = errno;
+ fprintf(stderr, _("Cannot allocate memory: %d.\n"), r);
+ free(current_path);
+ fclose(file);
+ return r;
+ }
+ current_path = current_path_ext;
+ }
+ else
+ {
+ break;
+ }
+ }
+ if(current_path == NULL) break;
+ // Remove \n
+ current_path[strlen(current_path) - 1] = '\0';
+ if(strlen(current_path) > 0)
+ {
+ int id = 0;
+ if(!plugin_registry_load(2, current_path, &id))
+ printf(_("Autoload: loaded %s with ID %d.\n"), current_path, id);
+ }
+ free(current_path);
+ }
+ fclose(file);
+ return 0;
+}
+
+static void *main_sighandler(void *arg)
+{
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ thread_set_name("sighandler");
+ int r = 0;
+ sigset_t *set = arg;
+ int sig;
+
+ while(true)
+ {
+ r = sigwait(set, &sig);
+ if(r)
+ {
+ fprintf(stderr, _("sigwait(): %d\n"), r);
+ goto cleanup;
+ }
+ switch(sig)
+ {
+ case SIGINT:
+ case SIGTERM:
+ printf(_("Received SIGINT or SIGTERM. Exiting.\n"));
+ received_sigterm = true;
+ goto cleanup;
+ }
+ }
+ goto cleanup;
+cleanup:
+ sem_post(&exit_sem);
+ pthread_exit(NULL);
+ return NULL;
+}
+
+static void *main_loop(void *arg)
+{
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ thread_set_name("main-loop");
+ char buffer[501];
+ while(true)
+ {
+ if(fgets(buffer, 500, stdin) == NULL)
+ {
+ printf(_("Received EOF. Exiting.\n"));
+ goto cleanup;
+ }
+ exclusive_section_enter();
+ if(received_sigterm)
+ {
+ exclusive_section_leave();
+ goto cleanup;
+ }
+ mcin_match(buffer, thpool);
+ exclusive_section_leave();
+ }
+ goto cleanup;
+cleanup:
+ sem_post(&exit_sem);
+ pthread_exit(NULL);
+ return NULL;
+}
+
+static int main_handle_cmd(const int out, int argc, char **argv)
+{
+ if(argc <= 0)
+ {
+ dprintf(out, _("Invalid arguments\n"));
+ return 64;
+ }
+ if(!strcmp(argv[0], "list"))
+ {
+ if(argc != 1)
+ {
+ dprintf(out, _("list expects no arguments\n"));
+ return 64;
+ }
+ for(int i = 0; i < plugin_size(); i ++)
+ {
+ const struct plugin *plug = plugin_get_by_index(i);
+ dprintf(out, _("%d\t%s\n"), plug->id, plug->name);
+ }
+ return 0;
+ }
+ if(!strcmp(argv[0], "load"))
+ {
+ if(argc != 2)
+ {
+ dprintf(out, _("load expects one argument: load <path/to/lib.so>\n"));
+ return 64;
+ }
+ dprintf(out, _("Waiting until the processing is done.\n"));
+ exclusive_section_enter();
+ thpool_wait(thpool);
+ int id = -1;
+ int r = plugin_registry_load(out, argv[1], &id);
+ if(!r)
+ {
+ dprintf(out, _("ID: %d\n"), id);
+ }
+ exclusive_section_leave();
+ return r;
+ }
+ if(!strcmp(argv[0], "unload"))
+ {
+ if(argc != 2)
+ {
+ dprintf(out, _("unload expects one argument: unload <ID>\n"));
+ return 64;
+ }
+ char *endptr;
+ intmax_t num = strtoimax(argv[1], &endptr, 10);
+ if(strcmp(endptr, "") || (num == INTMAX_MAX && errno == ERANGE) || num > INT_MAX || num < INT_MIN)
+ {
+ dprintf(out, _("Invalid ID: %s\n"), argv[1]);
+ return 64;
+ }
+ int id = (int)num;
+ dprintf(out, _("Waiting until the processing is done.\n"));
+ int r = 0;
+ exclusive_section_enter();
+ thpool_wait(thpool);
+ struct plugin *plug = plugin_get(id);
+ if(plug == NULL)
+ {
+ r = 1;
+ dprintf(out, _("Cannot find plugin ID: %d\n"), id);
+ }
+ else
+ {
+ r = plugin_registry_unload(out, id);
+ }
+ exclusive_section_leave();
+ return r;
+ }
+ if(!strcmp(argv[0], "rcon-get"))
+ {
+ int r = 0;
+ struct rcon_host_connarg *connarg = rcon_host_getconnarg();
+ if(connarg == NULL)
+ {
+ dprintf(out, _("Rcon is disabled.\n"));
+ }
+ else
+ {
+ dprintf(out, _("Host:\t%s\nPort:\t%s\n"), connarg->host, connarg->port);
+ }
+ return r;
+ }
+ if(!strcmp(argv[0], "rcon-set"))
+ {
+ int r = 0;
+ bool disable = false;
+ if(argc != 4)
+ {
+ if(argc == 2 && !strcmp("disable", argv[1]))
+ {
+ disable = true;
+ }
+ else
+ {
+ dprintf(out, _("Usage: rcon-set <host> <port> <password>\n"));
+ dprintf(out, _("Usage: rcon-set disable\n"));
+ return 64;
+ }
+ }
+ // Always allocate a new one to make sure it is atomic.
+ struct rcon_host_connarg *newargs = NULL;
+ if(!disable)
+ {
+ newargs = malloc(sizeof(struct rcon_host_connarg));
+ if(newargs == NULL)
+ {
+ r = errno;
+ dprintf(out, _("Cannot allocate memory: %d.\n"), r);
+ return r;
+ }
+ newargs->host = NULL;
+ newargs->port = NULL;
+ newargs->password = NULL;
+ int size = 0;
+ size = strlen(argv[1]) + 1;
+ newargs->host = calloc(size, sizeof(char));
+ if(newargs->host == NULL)
+ {
+ r = errno;
+ dprintf(out, _("Cannot allocate memory: %d\n"), r);
+ rcon_host_connarg_free(newargs);
+ return r;
+ }
+ memcpy(newargs->host, argv[1], size);
+
+ size = strlen(argv[2]) + 1;
+ newargs->port = calloc(size, sizeof(char));
+ if(newargs->port == NULL)
+ {
+ r = errno;
+ dprintf(out, _("Cannot allocate memory: %d\n"), r);
+ rcon_host_connarg_free(newargs);
+ return r;
+ }
+ memcpy(newargs->port, argv[2], size);
+
+ size = strlen(argv[3]) + 1;
+ newargs->password = calloc(size, sizeof(char));
+ if(newargs->password == NULL)
+ {
+ r = errno;
+ dprintf(out, _("Cannot allocate memory: %d\n"), r);
+ rcon_host_connarg_free(newargs);
+ return r;
+ }
+ memcpy(newargs->password, argv[3], size);
+ }
+
+ struct rcon_host_connarg *connarg = rcon_host_getconnarg();
+ rcon_host_setconnarg(newargs);
+ if(connarg != NULL) rcon_host_connarg_free(connarg);
+ dprintf(out, _("Ongoing requests will not be cancelled. Existing connections will be updated when plugins make requests.\n"));
+ return r;
+ }
+ dprintf(out, "Unexpected action: '%s'\n", argv[0]);
+ return 64;
+}
+
+static void *main_ctlsocket(void *arg)
+{
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ thread_set_name("ctl-socket");
+ int r = 0;
+ char buf[1025];
+ while(true)
+ {
+ const int accept_fd = accept(ctl_fd, NULL, NULL);
+ if(accept_fd == -1)
+ {
+ r = errno;
+ strerror_r(r, buf, 128);
+ fprintf(stderr, _("Cannot accept control connection: %s\n"), buf);
+ goto cleanup;
+ }
+ const ssize_t size = read(accept_fd, buf, 1024);
+ buf[size] = '\0';
+
+ char *pch = NULL;
+ char **argv = calloc(1, sizeof(char*));
+ if(argv == NULL)
+ {
+ dprintf(accept_fd, _("Cannot allocate memory: %d.\n%d"), errno, errno);
+ close(accept_fd);
+ continue;
+ }
+ int argc = 0;
+ pch = strtok(buf, " ");
+ bool fail = false;
+ while(pch != NULL)
+ {
+ if(pch[strlen(pch) - 1] == '\n') pch[strlen(pch) - 1] = '\0';
+ argc ++;
+ char **argv_ext = realloc(argv, argc * sizeof(char*));
+ if(argv_ext == NULL)
+ {
+ dprintf(accept_fd, _("Cannot allocate memory: %d.\n%d"), errno, errno);
+ free(argv);
+ fail = true;
+ close(accept_fd);
+ break;
+ }
+ argv = argv_ext;
+ argv[argc - 1] = pch;
+ pch = strtok(NULL, " ");
+ }
+ if(fail) continue;
+ int resp = main_handle_cmd(accept_fd, argc, argv);
+ dprintf(accept_fd, "%d", resp);
+ free(argv);
+ close(accept_fd);
+ }
+
+ goto cleanup;
+cleanup:
+ pthread_exit(NULL);
+ return NULL;
+}
+
+static int setup_sem()
+{
+ int r = sem_init(&exit_sem, 0, 0);
+ if(r)
+ {
+ fprintf(stderr, "sem_init(): %s\n", strerror(errno));
+ goto cleanup;
+ }
+ goto cleanup;
+cleanup:
+ return r;
+}
+
+static int setup_sock()
+{
+ int r = 0;
+ ctl_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if(ctl_fd == -1)
+ {
+ r = errno;
+ fprintf(stderr, _("Cannot create control socket: %s\n"), strerror(r));
+ goto cleanup;
+ }
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, CONTROL_SOCKET_PATH, sizeof(addr.sun_path) - 1);
+ int unlink_r = unlink(CONTROL_SOCKET_PATH);
+ if(unlink_r && errno != ENOENT)
+ {
+ r = errno;
+ fprintf(stderr, _("unlink(): %s\n"), strerror(r));
+ goto cleanup;
+ }
+ r = bind(ctl_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un));
+ if(r)
+ {
+ r = errno;
+ fprintf(stderr, _("Cannot bind to the control socket: %s\n"), strerror(r));
+ goto cleanup;
+ }
+ r = listen(ctl_fd, 5);
+ if(r)
+ {
+ r = errno;
+ fprintf(stderr, _("Cannot listen to the control socket: %s\n"), strerror(r));
+ goto cleanup;
+ }
+ goto cleanup;
+cleanup:
+ return r;
+}
+
+int setup_sigmask(sigset_t *set)
+{
+ sigemptyset(set);
+ sigaddset(set, SIGPIPE);
+ sigaddset(set, SIGTERM);
+ sigaddset(set, SIGINT);
+ int r = pthread_sigmask(SIG_BLOCK, set, NULL);
+ if(r)
+ {
+ fprintf(stderr, _("pthread_sigmask(): %d\n"), r);
+ goto cleanup;
+ }
+ goto cleanup;
+cleanup:
+ return r;
+}
+
+static int setup_thread(pthread_t *thread, void *start_routine, void *arg)
+{
+ int r = pthread_create(thread, NULL, start_routine, arg);
+ if(r)
+ {
+ fprintf(stderr, _("Cannot setup thread: %d\n"), r);
+ goto cleanup;
+ }
+ goto cleanup;
+cleanup:
+ return r;
+}
+
+static int destroy_thread(pthread_t thread)
+{
+ int r = 0;
+ r = pthread_cancel(thread);
+ if(r && r != ESRCH)
+ {
+ fprintf(stderr, "pthread_cancel: %d.\n", r);
+ }
+ else
+ {
+ r = 0;
+ }
+ r = pthread_join(thread, NULL);
+ if(r && r != ESRCH)
+ {
+ fprintf(stderr, "pthread_join: %d.\n", r);
+ }
+ else
+ {
+ r = 0;
+ }
+ return r;
+}
+
+static int main_daemon(int argc, char **argv)
+{
+ DEBUG("main.c#main_daemon: main_daemon()\n");
+ bool sem_setup = false,
+ mcin_setup = false,
+ rcon_setup = false,
+ reg_setup = false,
+ sock_setup = false,
+ autoload_setup = false,
+ sigmask_setup = false,
+ thpool_setup = false,
+ sighandler_setup = false,
+ loop_setup = false,
+ socket_thread_setup = false;
+
+ int r = 0;
+
+ DEBUG("main.c#main_daemon: Setup semaphore...\n");
+ r = setup_sem();
+ if(r) goto cleanup;
+ else sem_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup regular expressions...\n");
+ r = mcin_init();
+ if(r) goto cleanup;
+ else mcin_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup rcon host...\n");
+ r = rcon_host_init();
+ if(r) goto cleanup;
+ else rcon_setup = true;
+
+ DEBUG("main.c#main_daemon: Loading pre-defined rcon arguments from environment variables.\n");
+ const char *connarg_env_host = getenv("RCON_HOST");
+ const char *connarg_env_port = getenv("RCON_PORT");
+ const char *connarg_env_password = getenv("RCON_PASSWORD");
+ if(!(connarg_env_host == NULL && connarg_env_port == NULL && connarg_env_password == NULL))
+ {
+ if(connarg_env_host != NULL && connarg_env_port != NULL && connarg_env_password != NULL)
+ {
+ struct rcon_host_connarg *connarg = malloc(sizeof(struct rcon_host_connarg));
+ int size = 0;
+ size = strlen(connarg_env_host) + 1;
+ connarg->host = calloc(size, sizeof(char));
+ memcpy(connarg->host, connarg_env_host, size);
+ size = strlen(connarg_env_port) + 1;
+ connarg->port = calloc(size, sizeof(char));
+ memcpy(connarg->port, connarg_env_port, size);
+ size = strlen(connarg_env_password) + 1;
+ connarg->password = calloc(size, sizeof(char));
+ memcpy(connarg->password, connarg_env_password, size);
+ rcon_host_setconnarg(connarg);
+ DEBUGF("main.c#main_daemon: Loaded rcon arguments from environment variables:\nHost:\t%s\nPort:\t%s\nPassword:\t%s\n",
+ connarg->host,
+ connarg->port,
+ connarg->password);
+ }
+ else
+ {
+ fprintf(stderr, _("Cannot load rcon settings: RCON_HOST, RCON_PORT and RCON_PASSWORD must all present.\n"));
+ r = 64;
+ goto cleanup;
+ }
+ }
+
+ DEBUG("main.c#main_daemon: Setup plugin registry...\n");
+ r = plugin_registry_init();
+ if(r) goto cleanup;
+ else reg_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup control socket...\n");
+ r = setup_sock();
+ if(r) goto cleanup;
+ else sock_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup signal masks...\n");
+ sigset_t set;
+ r = setup_sigmask(&set);
+ if(r) goto cleanup;
+ else sigmask_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup thread pool...\n");
+ int thpool_threads = 1;
+ if(getenv("THPOOL_THREADS") != NULL)
+ {
+ char *endptr;
+ uintmax_t num = strtoumax(getenv("THPOOL_THREADS"), &endptr, 10);
+ if(strcmp(endptr, "") || (num == UINTMAX_MAX && errno == ERANGE) || num > INT_MAX || num <= 0)
+ {
+ fprintf(stderr, _("Invalid THPOOL_THREADS value.\n"));
+ r = 64;
+ goto cleanup;
+ }
+ thpool_threads = (int)num;
+ }
+ DEBUGF("main.c#main_daemon: Using '%d' threads.\n", thpool_threads);
+ thpool = thpool_init(thpool_threads);
+ thpool_setup = true;
+
+ if(argc > 1)
+ {
+ DEBUG("main.c#main_daemon: Autoloading plugins at startup...\n");
+ r = autoload(argv[1]);
+ if(r) goto cleanup;
+ else autoload_setup = true;
+ }
+
+ DEBUG("main.c#main_daemon: Setup signal handler thread...\n");
+ pthread_t thread_sighandler;
+ r = setup_thread(&thread_sighandler, &main_sighandler, &set);
+ if(r) goto cleanup;
+ else sighandler_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup main loop thread...\n");
+ pthread_t thread_loop;
+ r = setup_thread(&thread_loop, &main_loop, NULL);
+ if(r) goto cleanup;
+ else loop_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup control socket thread...\n");
+ pthread_t thread_ctlsocket;
+ r = setup_thread(&thread_ctlsocket, &main_ctlsocket, NULL);
+ if(r) goto cleanup;
+ else socket_thread_setup = true;
+
+ // Setup done. Enter blocking.
+
+ DEBUG("main.c#main_daemon: Main: Setup done. Waiting.\n");
+ r = sem_wait(&exit_sem);
+ if(r)
+ {
+ fprintf(stderr, "sem_wait(): %d.\n", r);
+ goto cleanup;
+ }
+
+ goto cleanup;
+cleanup:
+ DEBUG("main.c#main_daemon: Cleanup semaphore...\n");
+ if(sem_setup) sem_destroy(&exit_sem);
+ DEBUG("main.c#main_daemon: Cleanup regular expressions...\n");
+ if(mcin_setup) mcin_free();
+ DEBUG("main.c#main_daemon: Cleanup control socket thread...\n");
+ if(socket_thread_setup) destroy_thread(thread_ctlsocket);
+ DEBUG("main.c#main_daemon: Cleanup control socket...\n");
+ if(sock_setup)
+ {
+ close(ctl_fd);
+ unlink(CONTROL_SOCKET_PATH);
+ }
+ DEBUG("main.c#main_daemon: Cleanup loop thread...\n");
+ if(loop_setup) destroy_thread(thread_loop);
+ DEBUG("main.c#main_daemon: Cleanup signal handler thread...\n");
+ if(sighandler_setup) destroy_thread(thread_sighandler);
+ // Always perform thpool_wait after the main loop thread is paused or stopped.
+ DEBUG("main.c#main_daemon: Cleanup thread pool...\n");
+ if(thpool_setup)
+ {
+ thpool_wait(thpool);
+ thpool_destroy(thpool);
+ }
+ if(autoload_setup) {} // Plugins are always unloaded.
+ DEBUG("main.c#main_daemon: Unloading plugins...\n");
+ const int size = plugin_size();
+ int *plugins = calloc(size, sizeof(int));
+ for(int i = 0; i < size; i ++)
+ plugins[i] = plugin_get_by_index(i)->id;
+ for(int i = 0; i < size; i ++)
+ {
+ int unload_r = plugin_registry_unload(2, plugins[i]);
+ if(unload_r)
+ {
+ fprintf(stderr, _("Unload: %d\n"), unload_r);
+ }
+ }
+ free(plugins);
+ DEBUG("main.c#main_daemon: Cleanup rcon host...\n");
+ struct rcon_host_connarg *connarg = rcon_host_getconnarg();
+ if(connarg != NULL) rcon_host_connarg_free(connarg);
+ if(rcon_setup) { rcon_host_free(); }
+ DEBUG("main.c#main_daemon: Cleanup plugin registry...\n");
+ if(reg_setup) plugin_registry_free();
+ // Make the compiler happy: we don't need to do any cleanup for these items.
+ if(sigmask_setup) {}
+ return r;
+}
+
+static int main_ctl(int argc, char **argv)
+{
+ int r = 0;
+ struct sockaddr_un addr;
+ const int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if(fd == -1) {
+ r = errno;
+ fprintf(stderr, "%s\n", strerror(r));
+ goto cleanup;
+ }
+
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, CONTROL_SOCKET_PATH, sizeof(addr.sun_path) - 1);
+ r = connect(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un));
+ if(r == -1)
+ {
+ r = errno;
+ fprintf(stderr, "%s\n", strerror(r));
+ goto cleanup;
+ }
+ unsigned int length = 0;
+ for(int i = 1; i < argc; i ++)
+ {
+ length += strlen(argv[i]);
+ if(i != argc - 1) length ++;
+ }
+ char *dat = calloc(length, sizeof(char));
+ strcpy(dat, "");
+ for(int i = 1; i < argc; i ++)
+ {
+ strcat(dat, argv[i]);
+ if(i != argc - 1) strcat(dat, " ");
+ }
+ dprintf(fd, "%s", dat);
+
+ ssize_t num_read;
+ int arr_size = 1025;
+ char *buffer = calloc(1025, sizeof(char));
+ strcpy(buffer, "");
+ char buf[1025];
+ while((num_read = read(fd, buf, 1024)) > 0)
+ {
+ buf[num_read] = '\0';
+ arr_size += 1024;
+ buffer = realloc(buffer, arr_size * sizeof(char));
+ strcat(buffer, buf);
+ }
+ const char *last_newline = strrchr(buffer, '\n');
+ char *endptr;
+ // Try parse if the whole thing is an exit code.
+ if(last_newline == NULL)
+ {
+ intmax_t num = strtoimax(buffer, &endptr, 10);
+ if(!strcmp(endptr, "") && !(num == INTMAX_MAX && errno == ERANGE) && num <= INT_MAX && num >= INT_MIN)
+ {
+ r = (int)num;
+ buffer[0] = '\0';
+ }
+ }
+ else
+ {
+ intmax_t num = strtoimax(&buffer[(int)(last_newline - buffer) + 1], &endptr, 10);
+ if(strcmp(endptr, "") || (num == INTMAX_MAX && errno == ERANGE) || num > INT_MAX || num < INT_MIN)
+ {
+ r = 255;
+ }
+ else
+ {
+ r = (int)num;
+ buffer[(int)(last_newline - buffer) + 1] = '\0';
+ }
+ }
+ printf("%s", buffer);
+ free(buffer);
+
+cleanup:
+ if(fd != -1)
+ close(fd);
+ return r;
+}
+
+int main(int argc, char **argv)
+{
+ bool invoke_as_ctl = false;
+ if(argc > 0)
+ {
+ char *path = strrchr(argv[0], '/');
+ if(path != NULL)
+ {
+ const char *substr = &argv[0][(int)(path - argv[0]) + 1];
+ invoke_as_ctl = !strcmp(substr, "extmcctl");
+ }
+ }
+ if(invoke_as_ctl)
+ {
+ return main_ctl(argc, argv);
+ }
+ else
+ {
+ return main_daemon(argc, argv);
+ }
+}