From 7e531d78bf330bfac65cf3f208c2300b607decee Mon Sep 17 00:00:00 2001 From: Trumeet Date: Sun, 16 May 2021 19:23:13 -0700 Subject: First Commit --- .gitignore | 5 + LICENSE | 339 +++++++++++++++++++++++ Makefile | 45 ++++ README.md | 33 +++ common.h | 22 ++ extmc.1 | 17 ++ main.c | 785 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ mcin.c | 425 +++++++++++++++++++++++++++++ mcin.h | 10 + md5.c | 257 ++++++++++++++++++ md5.h | 33 +++ net.c | 55 ++++ net.h | 11 + plugin/common.h | 6 + plugin/plugin.h | 77 ++++++ plugin_registry.c | 256 ++++++++++++++++++ plugin_registry.h | 31 +++ plugins.c | 143 ++++++++++ plugins.h | 45 ++++ rcon.c | 113 ++++++++ rcon.h | 29 ++ rcon_host.c | 265 ++++++++++++++++++ rcon_host.h | 20 ++ sample/Makefile | 28 ++ sample/main.c | 118 ++++++++ thpool.c | 537 +++++++++++++++++++++++++++++++++++++ thpool.h | 187 +++++++++++++ threads_util.c | 15 ++ threads_util.h | 6 + 29 files changed, 3913 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 common.h create mode 100644 extmc.1 create mode 100644 main.c create mode 100644 mcin.c create mode 100644 mcin.h create mode 100644 md5.c create mode 100644 md5.h create mode 100644 net.c create mode 100644 net.h create mode 100644 plugin/common.h create mode 100644 plugin/plugin.h create mode 100644 plugin_registry.c create mode 100644 plugin_registry.h create mode 100644 plugins.c create mode 100644 plugins.h create mode 100644 rcon.c create mode 100644 rcon.h create mode 100644 rcon_host.c create mode 100644 rcon_host.h create mode 100644 sample/Makefile create mode 100644 sample/main.c create mode 100644 thpool.c create mode 100644 thpool.h create mode 100644 threads_util.c create mode 100644 threads_util.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b8544f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.o +*.so +*.ctl +extmc +extmcctl diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c9d5441 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +# Copyright 2019 ~ 2021 YuutaW Minecraft, All Rights Reserved. +# Proprietary and confidential. +# Unauthorized copying of any parts of this file, via any medium is strictly prohibited. +# Written by Yuuta Liang , April 2021. + +CFLAGS= \ + -I.\ + -std=c99 \ + -Wall \ + -D_POSIX_C_SOURCE=200809L \ + + +LDFLAGS= \ + -ldl \ + -lpthread \ + + +OBJ=main.o thpool.o mcin.o plugins.o rcon_host.o rcon.o net.o plugin_registry.o threads_util.o md5.o + +BIN=extmc + +debug: CFLAGS += -DCONTROL_SOCKET_PATH="\"./extmc.ctl\"" -g3 -O0 -rdynamic +debug: $(BIN) + +release: CFLAGS += -DDISABLE_DEBUG +release: $(BIN) + +%.o: %.c + $(CC) -c -o $@ $< $(CFLAGS) + +$(BIN): $(OBJ) + $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) + +.PHONY: clean +clean: + $(RM) *~ *.o $(BIN) + +ifeq ($(PREFIX),) + PREFIX := /usr/local +endif + +install: $(BIN) + install -d $(DESTDIR)$(PREFIX)/bin/ + install -m 755 $(BIN) $(DESTDIR)$(PREFIX)/bin/ + ln -s $(DESTDIR)$(PREFIX)/bin/$(BIN) $(DESTDIR)$(PREFIX)/bin/extmcctl diff --git a/README.md b/README.md new file mode 100644 index 0000000..7918c3a --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# extmc + +Minecraft plugin framework based on stdout and rcon. + +## Features + +* Plugin system. +* Multithreaded. +* Almost all settings can be reloaded at runtime. + +## Requirements + +* POSIX.1-2008 compliant system (BSD support is on the way) +* C11 compiler +* GNU Make + +## Documents + +To be continued (🕊️🕊️🕊️) + +## Install + +```shell +make install +``` + +## Author + +Yuuta Liang + +## Liccense + +GPL v2 only. diff --git a/common.h b/common.h new file mode 100644 index 0000000..58c2af6 --- /dev/null +++ b/common.h @@ -0,0 +1,22 @@ +/* + * Copyright 2019 ~ 2021 YuutaW Minecraft, All Rights Reserved. + * Proprietary and confidential. + * Unauthorized copying of any parts of this file, via any medium is strictly prohibited. + * Written by Yuuta Liang , April 2021. + */ + +#ifndef _COMMON_H +#define _COMMON_H + +#include +#define _(X) gettext(X) + +#ifdef DISABLE_DEBUG +#define DEBUG(fmt) +#define DEBUGF(fmt, ...) +#else +#define DEBUG(fmt, ...) do { fprintf(stdout, fmt); } while (0) +#define DEBUGF(fmt, ...) do { fprintf(stdout, fmt, __VA_ARGS__); } while (0) +#endif + +#endif // _COMMON_H diff --git a/extmc.1 b/extmc.1 new file mode 100644 index 0000000..8b3378e --- /dev/null +++ b/extmc.1 @@ -0,0 +1,17 @@ +.\" Manpage for extmc +.\" Contact Yuuta Liang for typos. +.TH man 1 "15 May 2021" "1.0" "extmc manual page" +.SH NAME +extmc \- extmc daemon +.SH +SYNOPSIS +exmtc +.SH DESCRIPTION +A framework that reads the console output of Minecraft servers and deliver messages to plugins. extmc also provides rcon client APIs. +.SH AUTHOR +Written by Yuuta Liang . +.SH KNOWN BUGS +No known bugs. +Report bugs at https://github.com/YuutaW-Minecraft/extmc. +.SH SEE ALSO +extmcctl(1) 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(¤t_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 \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 \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 \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); + } +} diff --git a/mcin.c b/mcin.c new file mode 100644 index 0000000..9ccd45f --- /dev/null +++ b/mcin.c @@ -0,0 +1,425 @@ +#include "mcin.h" +#include "common.h" +#include "thpool.h" +#include "plugins.h" +#include "plugin_registry.h" + +#include +#include +#include +#include +#include + +static regex_t reg_master; + +static regex_t reg_player_join; +static regex_t reg_player_leave; +static regex_t reg_player_achievement; +static regex_t reg_player_challenge; +static regex_t reg_player_goal; +static regex_t reg_player_say; + +static regex_t reg_player_die[64]; + +static regex_t reg_server_stopping; +static regex_t reg_server_starting; +static regex_t reg_server_started; + +static int mcin_compile_regex(const char *regex, const int flags, regex_t *reg) +{ + int r = regcomp(reg, regex, flags); + if(r) + { + fprintf(stderr, _("Cannot compile regex: %s.\n"), regex); + } + return r; +} + +#define mcin_compile_death_msg(index, regex) \ +{ \ + r = mcin_compile_regex(regex, REG_EXTENDED, ®_player_die[index]); \ + if(r) return r; \ +} + +int mcin_init() +{ + int r = 0; + r = mcin_compile_regex(".*\\[([0-9][0-9]:[0-9][0-9]:[0-9][0-9])\\] \\[(.*)\\/(.*)\\]: (.*)\n", REG_EXTENDED, ®_master); + if(r) return r; + r = mcin_compile_regex("(.*) joined the game", REG_EXTENDED, ®_player_join); + if(r) return r; + r = mcin_compile_regex("(.*) lost connection: (.*)", REG_EXTENDED, ®_player_leave); + if(r) return r; + r = mcin_compile_regex("(.*) has made the advancement \\[(.*)\\]", REG_EXTENDED, ®_player_achievement); + if(r) return r; + r = mcin_compile_regex("(.*) has completed the challenge \\[(.*)\\]", REG_EXTENDED, ®_player_challenge); + if(r) return r; + r = mcin_compile_regex("(.*) has reached the goal \\[(.*)\\]", REG_EXTENDED, ®_player_goal); + if(r) return r; + r = mcin_compile_regex("<(.*)> (.*)", REG_EXTENDED, ®_player_say); + if(r) return r; + + r = mcin_compile_regex("Stopping server", REG_EXTENDED, ®_server_stopping); + if(r) return r; + r = mcin_compile_regex("Starting minecraft server version (.*)", REG_EXTENDED, ®_server_starting); + if(r) return r; + r = mcin_compile_regex("Done \\((.*s)\\)! For help, type \"help\"", REG_EXTENDED, ®_server_started); + if(r) return r; + + mcin_compile_death_msg(0, "(.*) was shot by (.*)"); + mcin_compile_death_msg(1, "(.*) was shot by (.*) using .*"); + mcin_compile_death_msg(2, "(.*) was pummeled by (.*)"); + mcin_compile_death_msg(3, "(.*) was pummeled by (.*) using .*"); + mcin_compile_death_msg(4, "(.*) was pricked to death"); + mcin_compile_death_msg(5, "(.*) walked into a cactus whilst trying to escape (.*)"); + mcin_compile_death_msg(6, "(.*) drowned"); + mcin_compile_death_msg(7, "(.*) drowned whilst trying to escape (.*)"); + mcin_compile_death_msg(8, "(.*) experienced kinetic energy"); + mcin_compile_death_msg(9, "(.*) experienced kinetic energy whilst trying to escape (.*)"); + mcin_compile_death_msg(10, "(.*) was blown up by (.*)"); + mcin_compile_death_msg(11, "(.*) was blown up by (.*) using .*"); + mcin_compile_death_msg(12, "(.*) was killed by \\[Intentional Game Design\\]"); + mcin_compile_death_msg(13, "(.*) hit the ground too hard"); + mcin_compile_death_msg(14, "(.*) hit the ground too hard whilst trying to escape (.*)"); + mcin_compile_death_msg(15, "(.*) fell from a high place"); + mcin_compile_death_msg(16, "(.*) fell off a ladder"); + mcin_compile_death_msg(17, "(.*) fell off some vines"); + mcin_compile_death_msg(18, "(.*) fell off some weeping vines"); + mcin_compile_death_msg(19, "(.*) fell off some twisting vines"); + mcin_compile_death_msg(20, "(.*) fell off scaffolding"); + mcin_compile_death_msg(21, "(.*) fell off while climbing"); + mcin_compile_death_msg(22, "(.*) was squashed by a falling anvil"); + mcin_compile_death_msg(23, "(.*) was squashed by a falling anvil whilst fighting (.*)"); + mcin_compile_death_msg(24, "(.*) was squashed by a falling block"); + mcin_compile_death_msg(25, "(.*) was squashed by a falling block whilst fighting (.*)"); + mcin_compile_death_msg(26, "(.*) went up in flames"); + mcin_compile_death_msg(27, "(.*) walked into fire whilst fighting (.*.)"); + mcin_compile_death_msg(28, "(.*) burned to death"); + mcin_compile_death_msg(29, "(.*) was burnt to a crisp whilst fighting (.*)"); + mcin_compile_death_msg(30, "(.*) went off with a bang"); + mcin_compile_death_msg(31, "(.*) went off with a bang due to a firework fired from .* by (.*)"); + mcin_compile_death_msg(32, "(.*) tried to swim in lava"); + mcin_compile_death_msg(33, "(.*) tried to swim in lava to escape (.*)"); + mcin_compile_death_msg(34, "(.*) was struck by lightning"); + mcin_compile_death_msg(35, "(.*) was struck by lightning whilst fighting (.*)"); + mcin_compile_death_msg(36, "(.*) discovered the floor was lava"); + mcin_compile_death_msg(37, "(.*) walked into danger zone due to (.*)"); + mcin_compile_death_msg(38, "(.*) was killed by magic"); + mcin_compile_death_msg(39, "(.*) was killed by magic whilst trying to escape (.*)"); + mcin_compile_death_msg(40, "(.*) was killed by (.*) using magic"); + mcin_compile_death_msg(41, "(.*) was killed by (.*) using .*"); + mcin_compile_death_msg(42, "(.*) was slain by (.*)"); + mcin_compile_death_msg(43, "(.*) was slain by (.*) using .*"); + mcin_compile_death_msg(44, "(.*) was fireballed by (.*)"); + mcin_compile_death_msg(45, "(.*) was fireballed by (.*) using .*"); + mcin_compile_death_msg(46, "(.*) was stung to death"); + mcin_compile_death_msg(47, "(.*) was shot by a skull from (.*)"); + mcin_compile_death_msg(48, "(.*) starved to death"); + mcin_compile_death_msg(49, "(.*) starved to death whilst fighting (.*)"); + mcin_compile_death_msg(50, "(.*) suffocated in a wall"); + mcin_compile_death_msg(51, "(.*) suffocated in a wall whilst fighting (.*)"); + mcin_compile_death_msg(52, "(.*) was squished too much"); + mcin_compile_death_msg(53, "(.*) was squished by (.*)"); + mcin_compile_death_msg(54, "(.*) was poked to death by a sweet berry bush"); + mcin_compile_death_msg(55, "(.*) was poked to death by a sweet berry bush whilst trying to escape (.*)"); + mcin_compile_death_msg(56, "(.*) was killed trying to hurt (.*)"); + mcin_compile_death_msg(57, "(.*) was killed by .* trying to hurt (.*)"); + mcin_compile_death_msg(58, "(.*) was impaled by (.*)"); + mcin_compile_death_msg(59, "(.*) was impaled by (.*) with .*"); + mcin_compile_death_msg(60, "(.*) fell out of the world"); + mcin_compile_death_msg(61, "(.*) didn't want to live in the same world as (.*)"); + mcin_compile_death_msg(62, "(.*) withered away"); + mcin_compile_death_msg(63, "(.*) withered away whilst fighting (.*)"); + + return r; +} + +void mcin_free() +{ + regfree(®_master); + regfree(®_player_join); + regfree(®_player_leave); + regfree(®_player_say); + regfree(®_player_achievement); + regfree(®_player_challenge); + regfree(®_player_goal); + for(int i = 0; i < 64; i ++) + regfree(®_player_die[i]); + regfree(®_server_stopping); + regfree(®_server_starting); + regfree(®_server_started); +} + +static bool mcin_match_one_ex(const regex_t reg, const char *str, const int required_args, const int total_args, struct plugin_call_job_args *arg) +{ + regmatch_t pmatch[6]; + int r = regexec(®, str, 6, pmatch, 0); + if(r) + { + return false; + } + if(pmatch[0].rm_so == -1) return false; + arg->id = 0; + arg->arg1 = NULL; + arg->arg2 = NULL; + arg->arg3 = NULL; + arg->arg4 = NULL; + arg->arg5 = NULL; + for(int i = 1; i < total_args + 1; i ++) + { + const regmatch_t match = pmatch[i]; + if(match.rm_so == -1 && i < required_args) + { + // Not reaching the required number of arguments. + if(arg->arg5 != NULL) + { + free(arg->arg5); + arg->arg5 = NULL; + } + if(arg->arg4 != NULL) + { + free(arg->arg4); + arg->arg4 = NULL; + } + if(arg->arg3 != NULL) + { + free(arg->arg3); + arg->arg3 = NULL; + } + if(arg->arg2 != NULL) + { + free(arg->arg2); + arg->arg2 = NULL; + } + if(arg->arg1 != NULL) + { + free(arg->arg1); + arg->arg1 = NULL; + } + return false; + } + const int length = match.rm_eo - match.rm_so; + char *substring = calloc(length + 1, sizeof(char)); + memcpy(substring, &str[match.rm_so], length); + substring[length] = '\0'; + switch(i) + { + case 1: + arg->arg1 = substring; + break; + case 2: + arg->arg2 = substring; + break; + case 3: + arg->arg3 = substring; + break; + case 4: + arg->arg4 = substring; + break; + case 5: + arg->arg5 = substring; + break; + } + } + return true; +} + +static bool mcin_match_one(const regex_t reg, const char *str, const int required_args, struct plugin_call_job_args *arg) +{ + return mcin_match_one_ex(reg, str, required_args, required_args, arg); +} + +static struct plugin_call_job_args *args_copy(const struct plugin_call_job_args *orig, int index) +{ + struct plugin_call_job_args *args = malloc(sizeof(struct plugin_call_job_args)); + args->id = index; // Index to ID resolution will happen in plugcall_*. + if(orig->arg1 == NULL) args->arg1 = NULL; + else + { + args->arg1 = calloc(strlen(orig->arg1) + 1, sizeof(char)); + strcpy(args->arg1, orig->arg1); + } + + if(orig->arg2 == NULL) args->arg2 = NULL; + else + { + args->arg2 = calloc(strlen(orig->arg2) + 1, sizeof(char)); + strcpy(args->arg2, orig->arg2); + } + if(orig->arg3 == NULL) args->arg3 = NULL; + else + { + args->arg3 = calloc(strlen(orig->arg3) + 1, sizeof(char)); + strcpy(args->arg3, orig->arg3); + } + if(orig->arg4 == NULL) args->arg4 = NULL; + else + { + args->arg4 = calloc(strlen(orig->arg4) + 1, sizeof(char)); + strcpy(args->arg4, orig->arg4); + } + if(orig->arg5 == NULL) args->arg5 = NULL; + else + { + args->arg5 = calloc(strlen(orig->arg5) + 1, sizeof(char)); + strcpy(args->arg5, orig->arg5); + } + return args; +} + +void mcin_match(const char *str, const threadpool thpool) +{ + struct plugin_call_job_args local; + local.id = 0; + local.arg1 = NULL; + local.arg2 = NULL; + local.arg3 = NULL; + local.arg4 = NULL; + local.arg5 = NULL; + regmatch_t pmatch[5]; + if(regexec(®_master, str, 5, pmatch, 0)) return; + char *temp_str = calloc(strlen(str) + 1, sizeof(char)); + for(int i = 1 /* Ignore the string itself */; i < 5; i ++) + { + const regmatch_t match = pmatch[i]; + if(match.rm_so == -1) + // Shouldn't happen if the string is valid. + goto cleanup; + if(i != 2 && i != 3 && i != 4) continue; // We don't care. + int length = match.rm_eo - match.rm_so; + for(int j = 0; j < length; j ++) + { + temp_str[j] = str[match.rm_so + j]; + } + temp_str[length] = '\0'; + + if(i == 2) /* Tag */ + if(strcmp(temp_str, "Server thread")) + goto cleanup; + if(i == 3) /* Level */ + if(strcmp(temp_str, "INFO")) + goto cleanup; + if(i == 4) // Data + break; + } + const int size = plugin_size(); + if(mcin_match_one(reg_player_join, temp_str, 1, &local)) + { + for(int i = 0; i < size; i ++) + { + if(plugin_get_by_index(i)->fc_player_join == NULL) continue; + thpool_add_work(thpool, &plugcall_player_join, args_copy(&local, i)); + } + goto cleanup; + } + if(mcin_match_one(reg_player_leave, temp_str, 2, &local)) + { + for(int i = 0; i < size; i ++) + { + if(plugin_get_by_index(i)->fc_player_leave == NULL) continue; + thpool_add_work(thpool, &plugcall_player_leave, args_copy(&local, i)); + } + goto cleanup; + } + if(mcin_match_one(reg_player_achievement, temp_str, 2, &local)) + { + for(int i = 0; i < size; i ++) + { + if(plugin_get_by_index(i)->fc_player_achievement == NULL) continue; + thpool_add_work(thpool, &plugcall_player_achievement, args_copy(&local, i)); + } + goto cleanup; + } + if(mcin_match_one(reg_player_challenge, temp_str, 2, &local)) + { + for(int i = 0; i < size; i ++) + { + if(plugin_get_by_index(i)->fc_player_challenge == NULL) continue; + thpool_add_work(thpool, &plugcall_player_challenge, args_copy(&local, i)); + } + goto cleanup; + } + if(mcin_match_one(reg_player_goal, temp_str, 2, &local)) + { + for(int i = 0; i < size; i ++) + { + if(plugin_get_by_index(i)->fc_player_goal == NULL) continue; + thpool_add_work(thpool, &plugcall_player_goal, args_copy(&local, i)); + } + goto cleanup; + } + if(mcin_match_one(reg_player_say, temp_str, 2, &local)) + { + for(int i = 0; i < size; i ++) + { + if(plugin_get_by_index(i)->fc_player_say == NULL) continue; + thpool_add_work(thpool, &plugcall_player_say, args_copy(&local, i)); + } + goto cleanup; + } + for(int i = 0; i < 64; i ++) + if(mcin_match_one_ex(reg_player_die[i], temp_str, 1, 2, &local)) + { + for(int j = 0; j < size; j ++) + { + if(plugin_get_by_index(i)->fc_player_die == NULL) continue; + thpool_add_work(thpool, &plugcall_player_die, args_copy(&local, j)); + } + goto cleanup; + } + if(mcin_match_one(reg_server_stopping, temp_str, 0, &local)) + { + for(int i = 0; i < size; i ++) + { + if(plugin_get_by_index(i)->fc_server_stopping == NULL) continue; + thpool_add_work(thpool, &plugcall_server_stopping, args_copy(&local, i)); + } + goto cleanup; + } + if(mcin_match_one(reg_server_starting, temp_str, 1, &local)) + { + for(int i = 0; i < size; i ++) + { + if(plugin_get_by_index(i)->fc_server_starting == NULL) continue; + thpool_add_work(thpool, &plugcall_server_starting, args_copy(&local, i)); + } + goto cleanup; + } + if(mcin_match_one(reg_server_started, temp_str, 1, &local)) + { + for(int i = 0; i < size; i ++) + { + if(plugin_get_by_index(i)->fc_server_started == NULL) continue; + thpool_add_work(thpool, &plugcall_server_started, args_copy(&local, i)); + } + goto cleanup; + } + goto cleanup; +cleanup: + if(local.arg5 != NULL) + { + free(local.arg5); + local.arg5 = NULL; + } + if(local.arg4 != NULL) + { + free(local.arg4); + local.arg4 = NULL; + } + if(local.arg3 != NULL) + { + free(local.arg3); + local.arg3 = NULL; + } + if(local.arg2 != NULL) + { + free(local.arg2); + local.arg2 = NULL; + } + if(local.arg1 != NULL) + { + free(local.arg1); + local.arg1 = NULL; + } + free(temp_str); +} diff --git a/mcin.h b/mcin.h new file mode 100644 index 0000000..c406d69 --- /dev/null +++ b/mcin.h @@ -0,0 +1,10 @@ +#ifndef _MCIN_H +#define _MCIN_H + +#include "thpool.h" + +int mcin_init(); +void mcin_free(); +void mcin_match(const char *str, const threadpool thpool); + +#endif // _MCIN_H diff --git a/md5.c b/md5.c new file mode 100644 index 0000000..654c508 --- /dev/null +++ b/md5.c @@ -0,0 +1,257 @@ +/* + * https://raw.githubusercontent.com/Zunawe/md5-c/eba1d664e4fbf639c105bc1a075685d83b69bde3/md5.c + * Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm + * and modified slightly to be functionally identical but condensed into control structures. + */ + +#include "md5.h" + +/* + * Constants defined by the MD5 algorithm + */ +#define A 0x67452301 +#define B 0xefcdab89 +#define C 0x98badcfe +#define D 0x10325476 + +static uint32_t S[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; + +static uint32_t K[] = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; + +/* + * Padding used to make the size (in bits) of the input congruent to 448 mod 512 + */ +static uint8_t PADDING[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* + * Initialize a context + */ +void md5Init(MD5Context *ctx){ + ctx->size = (uint64_t)0; + + ctx->buffer[0] = (uint32_t)A; + ctx->buffer[1] = (uint32_t)B; + ctx->buffer[2] = (uint32_t)C; + ctx->buffer[3] = (uint32_t)D; +} + +/* + * Add some amount of input to the context + * + * If the input fills out a block of 512 bits, apply the algorithm (md5Step) + * and save the result in the buffer. Also updates the overall size. + */ + +void md5Update(MD5Context *ctx, uint8_t *input_buffer, size_t input_len){ + uint32_t input[16]; + unsigned int offset = ctx->size % 64; + ctx->size += (uint64_t)input_len; + + // Copy each byte in input_buffer into the next space in our context input + for(unsigned int i = 0; i < input_len; ++i){ + ctx->input[offset++] = (uint8_t)*(input_buffer + i); + + // If we've filled our context input, copy it into our local array input + // then reset the offset to 0 and fill in a new buffer + // The local array input is a list of 16 32-bit words for use in the algorithm + if(offset % 64 == 0){ + for(unsigned int j = 0; j < 16; ++j){ + // Convert to little-endian + input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 | + (uint32_t)(ctx->input[(j * 4) + 2]) << 16 | + (uint32_t)(ctx->input[(j * 4) + 1]) << 8 | + (uint32_t)(ctx->input[(j * 4)]); + } + md5Step(ctx->buffer, input); + offset = 0; + } + } +} + +/* + * Pad the current input to get to 448 bytes, append the size in bits to the very end, + * and save the result of the final iteration into digest. + */ +void md5Finalize(MD5Context *ctx){ + uint32_t input[16]; + unsigned int offset = ctx->size % 64; + unsigned int padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset; + + // Fill in the padding andndo the changes to size that resulted from the update + md5Update(ctx, PADDING, padding_length); + ctx->size -= (uint64_t)padding_length; + + // Do a final update (internal to this function) + // Last two 32-bit words are the two halves of the size (converted from bytes to bits) + for(unsigned int j = 0; j < 14; ++j){ + input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 | + (uint32_t)(ctx->input[(j * 4) + 2]) << 16 | + (uint32_t)(ctx->input[(j * 4) + 1]) << 8 | + (uint32_t)(ctx->input[(j * 4)]); + } + input[14] = (uint32_t)(ctx->size * 8); + input[15] = (uint32_t)((ctx->size * 8) >> 32); + + md5Step(ctx->buffer, input); + + // Move the result into digest + // (Convert from little-endian) + for(unsigned int i = 0; i < 4; ++i){ + ctx->digest[(i * 4) + 0] = (uint8_t)((ctx->buffer[i] & 0x000000FF)); + ctx->digest[(i * 4) + 1] = (uint8_t)((ctx->buffer[i] & 0x0000FF00) >> 8); + ctx->digest[(i * 4) + 2] = (uint8_t)((ctx->buffer[i] & 0x00FF0000) >> 16); + ctx->digest[(i * 4) + 3] = (uint8_t)((ctx->buffer[i] & 0xFF000000) >> 24); + } +} + +/* + * Step on 512 bits of input with the main MD5 algorithm. + */ +void md5Step(uint32_t *buffer, uint32_t *input){ + uint32_t AA = buffer[0]; + uint32_t BB = buffer[1]; + uint32_t CC = buffer[2]; + uint32_t DD = buffer[3]; + + uint32_t E; + + unsigned int j; + + for(unsigned int i = 0; i < 64; ++i){ + switch(i / 16){ + case 0: + E = F(BB, CC, DD); + j = i; + break; + case 1: + E = G(BB, CC, DD); + j = ((i * 5) + 1) % 16; + break; + case 2: + E = H(BB, CC, DD); + j = ((i * 3) + 5) % 16; + break; + default: + E = I(BB, CC, DD); + j = (i * 7) % 16; + break; + } + + uint32_t temp = DD; + DD = CC; + CC = BB; + BB = BB + rotate_left(AA + E + K[i] + input[j], S[i]); + AA = temp; + } + + buffer[0] += AA; + buffer[1] += BB; + buffer[2] += CC; + buffer[3] += DD; +} + +/* + * Functions that will return a pointer to the hash of the provided input + */ +uint8_t* md5String(char *input){ + MD5Context ctx; + md5Init(&ctx); + md5Update(&ctx, (uint8_t *)input, strlen(input)); + md5Finalize(&ctx); + + uint8_t *result = malloc(16); + memcpy(result, ctx.digest, 16); + return result; +} + +uint8_t* md5File(FILE *file){ + char *input_buffer = malloc(1024); + size_t input_size = 0; + + MD5Context ctx; + md5Init(&ctx); + + while((input_size = fread(input_buffer, 1, 1024, file)) > 0){ + md5Update(&ctx, (uint8_t *)input_buffer, input_size); + } + + md5Finalize(&ctx); + + free(input_buffer); + + uint8_t *result = malloc(16); + memcpy(result, ctx.digest, 16); + return result; +} + +/* + * Bit-manipulation functions defined by the MD5 algorithm + */ +uint32_t F(uint32_t X, uint32_t Y, uint32_t Z){ + return (X & Y) | (~X & Z); +} + +uint32_t G(uint32_t X, uint32_t Y, uint32_t Z){ + return (X & Z) | (Y & ~Z); +} + +uint32_t H(uint32_t X, uint32_t Y, uint32_t Z){ + return X ^ Y ^ Z; +} + +uint32_t I(uint32_t X, uint32_t Y, uint32_t Z){ + return Y ^ (X | ~Z); +} + +/* + * Rotates a 32-bit word left by n bits + */ +uint32_t rotate_left(uint32_t x, uint32_t n){ + return (x << n) | (x >> (32 - n)); +} + +/* + * Printing bytes from buffers or the hash + */ +void print_bytes(void *p, size_t length){ + uint8_t *pp = (uint8_t *)p; + for(unsigned int i = 0; i < length; ++i){ + if(i && !(i % 16)){ + printf("\n"); + } + printf("%02X ", pp[i]); + } + printf("\n"); +} + +void print_hash(uint8_t *p){ + for(unsigned int i = 0; i < 16; ++i){ + printf("%02x", p[i]); + } + printf("\n"); +} diff --git a/md5.h b/md5.h new file mode 100644 index 0000000..57904f2 --- /dev/null +++ b/md5.h @@ -0,0 +1,33 @@ +/* + * https://raw.githubusercontent.com/Zunawe/md5-c/eba1d664e4fbf639c105bc1a075685d83b69bde3/md5.h + */ + +#include +#include +#include +#include + +typedef struct{ + uint64_t size; // Size of input in bytes + uint32_t buffer[4]; // Current accumulation of hash + uint8_t input[64]; // Input to be used in the next step + uint8_t digest[16]; // Result of algorithm +}MD5Context; + +void md5Init(MD5Context *ctx); +void md5Update(MD5Context *ctx, uint8_t *input, size_t input_len); +void md5Finalize(MD5Context *ctx); +void md5Step(uint32_t *buffer, uint32_t *input); + +uint8_t* md5String(char *input); +uint8_t* md5File(FILE *file); + +uint32_t F(uint32_t X, uint32_t Y, uint32_t Z); +uint32_t G(uint32_t X, uint32_t Y, uint32_t Z); +uint32_t H(uint32_t X, uint32_t Y, uint32_t Z); +uint32_t I(uint32_t X, uint32_t Y, uint32_t Z); + +uint32_t rotate_left(uint32_t x, uint32_t n); + +void print_bytes(void *p, size_t length); +void print_hash(uint8_t *p); diff --git a/net.c b/net.c new file mode 100644 index 0000000..6fa5d55 --- /dev/null +++ b/net.c @@ -0,0 +1,55 @@ +/* + * Adopted from mcrcon, Copyright (c) 2012-2020, Tiiffi . + * https://github.com/Tiiffi/mcrcon/tree/b02201d689b3032bc681b28f175fd3d83d167293 + */ + +#include "net.h" +#include "common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int net_connect(const char *host, const char *port, int *out) +{ + int sd; + struct addrinfo hints; + struct addrinfo *server_info, *p; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + int ret = getaddrinfo(host, port, &hints, &server_info); + if(ret) + { + fprintf(stderr, _("Cannot resolve host %s: %s.\n"), host, strerror(ret)); + return EX_IOERR; + } + for (p = server_info; p != NULL; p = p->ai_next) + { + sd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sd == -1) continue; + ret = connect(sd, p->ai_addr, p->ai_addrlen); + if(ret == -1) + { + close(sd); + continue; + } + break; + } + if(p == NULL) + { + fprintf(stderr, _("Cannot connect to %s:%s : %s.\n"), host, port, strerror(errno)); + freeaddrinfo(server_info); + return EX_IOERR; + } + freeaddrinfo(server_info); + *out = sd; + return 0; +} diff --git a/net.h b/net.h new file mode 100644 index 0000000..0cd06bc --- /dev/null +++ b/net.h @@ -0,0 +1,11 @@ +/* + * Adopted from mcrcon, Copyright (c) 2012-2020, Tiiffi . + * https://github.com/Tiiffi/mcrcon/tree/b02201d689b3032bc681b28f175fd3d83d167293 + */ + +#ifndef _NET_H +#define _NET_H + +int net_connect(const char *host, const char *port, int *out); + +#endif // _NET_H diff --git a/plugin/common.h b/plugin/common.h new file mode 100644 index 0000000..238a806 --- /dev/null +++ b/plugin/common.h @@ -0,0 +1,6 @@ +#ifndef _PLUGIN_COMMON_H +#define _PLUGIN_COMMON_H + +#define RCON_DATA_BUFFSIZE 4096 + +#endif // _PLUGIN_COMMON_H diff --git a/plugin/plugin.h b/plugin/plugin.h new file mode 100644 index 0000000..bfed63d --- /dev/null +++ b/plugin/plugin.h @@ -0,0 +1,77 @@ +/* + * Plugin API definition. + */ + +#ifndef _PLUGIN_H +#define _PLUGIN_H + +#include "common.h" +#include + +/* When the administrator disabled rcon. The plugin must have a way to avoid using rcon since it is non-recoverable. */ +#define EPG_RCON_DISABLED -1 + +/* 32-bit unsigned integer to indicate the version of the API. */ +extern const uint32_t epg_version; + +/* Plugin display name. */ +extern const char *epg_name; + +/* Current session handle. */ +struct epg_handle { + /* Unique ID. */ + int id; + /* Send rcon command. */ + int (*rcon_send)(int, char *); + int (*rcon_recv)(int *, char *); +}; + +/* Before the plugin is loaded. + * Return a non-zero integer to indicate an error and the plugin will be unloaded immediatedly (without calling epg_unload). + * Thread: main thread (during autoloading) or control socket (during extmcctl operations). */ +int epg_load(struct epg_handle *handle); + +/* Before the plugin is unloaded. + * Return a non-zero integer to indicate an error and the plugin will not be unloaded. + * Thread: main thread (during autoloading) or control socket (during extmcctl operations). */ +int epg_unload(struct epg_handle *handle); + +/* + * When a player joins the game. + * Thread: worker */ +int epg_player_join(struct epg_handle *handle, + char *player); + +int epg_player_leave(struct epg_handle *handle, + char *player, + char *reason); + +int epg_player_say(struct epg_handle *handle, + char *player, + char *content); + +int epg_player_die(struct epg_handle *handle, + char *player, + char *source); + +int epg_player_achievement(struct epg_handle *handle, + char *player, + char *challenge); + +int epg_player_challenge(struct epg_handle *handle, + char *player, + char *challenge); + +int epg_player_goal(struct epg_handle *handle, + char *player, + char *goal); + +int epg_server_stopping(struct epg_handle *handle); + +int epg_server_starting(struct epg_handle *handle, + char *version); + +int epg_server_started(struct epg_handle *handle, + char *took); + +#endif // _PLUGIN_H diff --git a/plugin_registry.c b/plugin_registry.c new file mode 100644 index 0000000..b6b0dbd --- /dev/null +++ b/plugin_registry.c @@ -0,0 +1,256 @@ +#include "plugins.h" +#include "plugin_registry.h" +#include "rcon_host.h" +#include "common.h" + +#include +#include +#include +#include +#include + +#define PLUGIN_ID_GEN_MAX_RETRY 1 + +static int plugin_count = 0; +static struct plugin *plugin_arr = NULL; +static pthread_key_t key_plugin; + +static int plugin_generate_id() +{ + int retry_count = 0; + goto gen; +gen: + if(retry_count > PLUGIN_ID_GEN_MAX_RETRY) return -1; + int rand_val = rand(); + for(int i = 0; i < plugin_count; i ++) + { + if(plugin_get_by_index(i)->id == rand_val) + { + retry_count ++; + goto gen; + } + } + return rand_val; +} + +static void arr_resize(int new_size) +{ + if(new_size == 0) + { + if(plugin_arr != NULL) + { + free(plugin_arr); + plugin_arr = NULL; + } + return; + } + if(plugin_arr == NULL) + plugin_arr = calloc(new_size, sizeof(struct plugin)); + else + plugin_arr = realloc(plugin_arr, new_size * sizeof(struct plugin)); +} + +int plugin_registry_init() +{ + pthread_key_create(&key_plugin, NULL); + return 0; +} + +void plugin_registry_free() +{ + plugin_count = 0; + arr_resize(0); + pthread_key_delete(key_plugin); +} + +int plugin_size() +{ + return plugin_count; +} + +static int plugin_id_to_index(int id) +{ + for(int i = 0; i < plugin_count; i ++) + { + if(plugin_arr[i].id == id) + return i; + } + return -1; +} + +struct plugin *plugin_get(int id) +{ + const int index = plugin_id_to_index(id); + if(index < 0) + return NULL; + return &plugin_arr[index]; +} + +struct plugin *plugin_get_by_index(int index) +{ + return &plugin_arr[index]; +} + +int plugin_registry_unload(int stderr_fd, int id) +{ + int r = 0; + const int index = plugin_id_to_index(id); + if(index < 0) + { + r = EPLUGINNOTFOUND; + goto cleanup; + } + struct plugin *plug = plugin_get_by_index(index); + r = plugin_unload(stderr_fd, plug); + if(r) goto cleanup; + memcpy(plug, &plugin_arr[index + 1], (plugin_count - 1 - index) * sizeof(struct plugin)); + arr_resize(-- plugin_count); + goto cleanup; +cleanup: + return r; +} + +int plugin_registry_load(int stderr_fd, const char *path, int *id) +{ + int r = 0; + const int id_gen = plugin_generate_id(); + if(id_gen <= 0) + { + r = EPLUGINEXCEED; + goto cleanup; + } + arr_resize(++ plugin_count); + r = plugin_load(stderr_fd, path, id_gen, &plugin_arr[plugin_count - 1]); + if(r) + { + arr_resize(-- plugin_count); + goto cleanup; + } + *id = id_gen; + goto cleanup; +cleanup: + return r; +} + +static int api_rcon_send_wrapper(int pkt_id, char *command) +{ + int r = 0; + const struct plugin *plug = pthread_getspecific(key_plugin); + printf(_("[rcon#%d] -> '%s' (%d)\n"), + plug->id, + command, + pkt_id); + // TODO: The plugin identity may have future usages. + r = rcon_host_send(pkt_id, command); + if(r) goto cleanup; +cleanup: + return r; +} + +static int api_rcon_recv_wrapper(int *pkt_id, char *out) +{ + int r = 0; + const struct plugin *plug = pthread_getspecific(key_plugin); + // TODO: The plugin identity may have future usages. + r = rcon_host_recv(pkt_id, out); + if(!r) + printf(_("[rcon#%d] <- %s (%d)\n"), + plug->id, + out, + *pkt_id); + if(r) goto cleanup; +cleanup: + return r; +} + +void plugcall_setup_handle(struct plugin *plugin, struct epg_handle *handle) +{ + pthread_setspecific(key_plugin, plugin); + handle->id = plugin->id; + handle->rcon_send = &api_rcon_send_wrapper; + handle->rcon_recv = &api_rcon_recv_wrapper; +} + +#define PLUGCALL_PRE(X) \ + struct epg_handle handle; \ + struct plugin_call_job_args *args = arg; \ + struct plugin *plugin = plugin_get_by_index(args->id); \ + plugcall_setup_handle(plugin, &handle); + +#define PLUGCALL_POST(X) \ + if(args->arg1 != NULL) free(args->arg1); \ + if(args->arg2 != NULL) free(args->arg2); \ + if(args->arg3 != NULL) free(args->arg3); \ + if(args->arg4 != NULL) free(args->arg4); \ + if(args->arg5 != NULL) free(args->arg5); \ + free(args); + +void plugcall_player_join(void *arg) +{ + PLUGCALL_PRE(arg) + plugin->fc_player_join(&handle, args->arg1); + PLUGCALL_POST(arg) +} + +void plugcall_player_leave(void *arg) +{ + PLUGCALL_PRE(arg) + plugin->fc_player_leave(&handle, args->arg1, args->arg2); + PLUGCALL_POST(arg) +} + +void plugcall_player_achievement(void *arg) +{ + PLUGCALL_PRE(arg) + plugin->fc_player_achievement(&handle, args->arg1, args->arg2); + PLUGCALL_POST(arg) +} + +void plugcall_player_challenge(void *arg) +{ + PLUGCALL_PRE(arg) + plugin->fc_player_challenge(&handle, args->arg1, args->arg2); + PLUGCALL_POST(arg) +} + +void plugcall_player_goal(void *arg) +{ + PLUGCALL_PRE(arg) + plugin->fc_player_goal(&handle, args->arg1, args->arg2); + PLUGCALL_POST(arg) +} + +void plugcall_player_say(void *arg) +{ + PLUGCALL_PRE(arg) + plugin->fc_player_say(&handle, args->arg1, args->arg2); + PLUGCALL_POST(arg) +} + +void plugcall_player_die(void *arg) +{ + PLUGCALL_PRE(arg) + plugin->fc_player_die(&handle, args->arg1, args->arg2); + PLUGCALL_POST(arg) +} + +void plugcall_server_stopping(void *arg) +{ + PLUGCALL_PRE(arg) + plugin->fc_server_stopping(&handle); + PLUGCALL_POST(arg) +} + +void plugcall_server_starting(void *arg) +{ + PLUGCALL_PRE(arg) + plugin->fc_server_starting(&handle, args->arg1); + PLUGCALL_POST(arg) +} + +void plugcall_server_started(void *arg) +{ + PLUGCALL_PRE(arg) + plugin->fc_server_started(&handle, args->arg1); + PLUGCALL_POST(arg) +} diff --git a/plugin_registry.h b/plugin_registry.h new file mode 100644 index 0000000..a38808e --- /dev/null +++ b/plugin_registry.h @@ -0,0 +1,31 @@ +#ifndef _PLUGIN_REGISTRY_H +#define _PLUGIN_REGISTRY_H + +#include "plugins.h" + +#define EPLUGINEXCEED 10 +#define EPLUGINNOTFOUND 74 + +int plugin_registry_init(); +void plugin_registry_free(); + +int plugin_size(); +struct plugin *plugin_get(int id); +struct plugin *plugin_get_by_index(int index); +int plugin_registry_unload(int stderr_fd, int id); +int plugin_registry_load(int stderr_fd, const char *path, int *id); + +void plugcall_setup_handle(struct plugin *plugin, struct epg_handle *handle); + +void plugcall_player_join(void *arg); +void plugcall_player_leave(void *arg); +void plugcall_player_achievement(void *arg); +void plugcall_player_challenge(void *arg); +void plugcall_player_goal(void *arg); +void plugcall_player_say(void *arg); +void plugcall_player_die(void *arg); +void plugcall_server_stopping(void *arg); +void plugcall_server_starting(void *arg); +void plugcall_server_started(void *arg); + +#endif // _PLUGIN_REGISTRY_H diff --git a/plugins.c b/plugins.c new file mode 100644 index 0000000..32a8d16 --- /dev/null +++ b/plugins.c @@ -0,0 +1,143 @@ +/* + * Copyright 2019 ~ 2021 YuutaW Minecraft, All Rights Reserved. + * Proprietary and confidential. + * Unauthorized copying of any parts of this file, via any medium is strictly prohibited. + * Written by Yuuta Liang , April 2021. + */ + +#include "plugins.h" +#include "common.h" +#include "plugin_registry.h" + +#include +#include +#include +#include +#include + +static const void *plugin_dlsym(int stderr_fd, void *handle, const bool mandatory, const char *name) +{ + const void *sym = dlsym(handle, name); + if(sym == NULL) + { + if(mandatory) + { + dprintf(stderr_fd, _("Cannot load %s, aborting: %s.\n"), name, dlerror()); + } + else + { + dprintf(stderr_fd, _("Cannot load %s, ignoring: %s.\n"), name, dlerror()); + } + return NULL; + } + return sym; +} + +int plugin_unload(int stderr_fd, struct plugin *plugin) +{ + if(plugin->fc_unload != NULL) + { + struct epg_handle hdl; + plugcall_setup_handle(plugin, &hdl); + int unload_r = plugin->fc_unload(&hdl); + if(unload_r) + { + dprintf(stderr_fd, _("Cannot unload plugin: it returned an error: %d.\n"), unload_r); + return unload_r; + } + } + if(plugin->handle != NULL) + { + dlclose(plugin->handle); + plugin->handle = NULL; + } + return 0; +} + +static int plugin_load_v1(int stderr_fd, struct plugin *out) +{ + int r = 0; + const void *sym; + sym = plugin_dlsym(stderr_fd, out->handle, true, "epg_name"); + if(sym == NULL) + { + r = 64; + goto cleanup; + } + out->name = *(char**)sym; + out->fc_load = plugin_dlsym(stderr_fd, out->handle, false, "epg_load"); + out->fc_unload = plugin_dlsym(stderr_fd, out->handle, false, "epg_unload"); + out->fc_player_join = plugin_dlsym(stderr_fd, out->handle, false, "epg_player_join"); + out->fc_player_leave = plugin_dlsym(stderr_fd, out->handle, false, "epg_player_leave"); + out->fc_player_say = plugin_dlsym(stderr_fd, out->handle, false, "epg_player_say"); + out->fc_player_die = plugin_dlsym(stderr_fd, out->handle, false, "epg_player_die"); + out->fc_player_achievement = plugin_dlsym(stderr_fd, out->handle, false, "epg_player_achievement"); + out->fc_player_challenge = plugin_dlsym(stderr_fd, out->handle, false, "epg_player_challenge"); + out->fc_player_goal = plugin_dlsym(stderr_fd, out->handle, false, "epg_player_goal"); + out->fc_server_stopping = plugin_dlsym(stderr_fd, out->handle, false, "epg_server_stopping"); + out->fc_server_starting = plugin_dlsym(stderr_fd, out->handle, false, "epg_server_starting"); + out->fc_server_started = plugin_dlsym(stderr_fd, out->handle, false, "epg_server_started"); + goto cleanup; +cleanup: + return r; +} + +int plugin_load(int stderr_fd, const char *path, const int id, struct plugin *out) +{ + int r = 0; + out->id = id; + out->path = path; + out->handle = NULL; + out->name = NULL; + out->version = 0; + out->fc_load = NULL; + out->fc_unload = NULL; + out->fc_player_join = NULL; + out->fc_player_leave = NULL; + out->fc_player_say = NULL; + out->fc_player_die = NULL; + out->fc_player_achievement = NULL; + out->fc_player_challenge = NULL; + out->fc_player_goal = NULL; + out->fc_server_stopping = NULL; + out->fc_server_starting = NULL; + out->fc_server_started = NULL; + + void *handle = dlopen(path, RTLD_LAZY); + if(handle == NULL) + { + dprintf(stderr_fd, _("Cannot load %s: %s.\n"), path, dlerror()); + r = 1; + goto cleanup; + } + out->handle = handle; + const void *sym = plugin_dlsym(stderr_fd, handle, true, "epg_version"); + if(sym == NULL) + { + r = 64; + goto cleanup; + } + out->version = *(uint32_t*)sym; + switch(out->version) + { + case 1: + r = plugin_load_v1(stderr_fd, out); + if(r) goto cleanup; + break; + default: + dprintf(stderr_fd, _("Unsupported plugin %s: Incompatible with version %u.\n"), path, out->version); + break; + } + struct epg_handle hdl; + plugcall_setup_handle(out, &hdl); + r = out->fc_load(&hdl); + if(r) + { + dprintf(stderr_fd, _("Cannot load plugin: it returned an error: %d.\n"), r); + goto cleanup; + } + goto cleanup; +cleanup: + if(r) plugin_unload(stderr_fd, out); + return r; +} diff --git a/plugins.h b/plugins.h new file mode 100644 index 0000000..bb2948d --- /dev/null +++ b/plugins.h @@ -0,0 +1,45 @@ +/* + * Copyright 2019 ~ 2021 YuutaW Minecraft, All Rights Reserved. + * Proprietary and confidential. + * Unauthorized copying of any parts of this file, via any medium is strictly prohibited. + * Written by Yuuta Liang , April 2021. + */ + +#ifndef _PLUGINS_H +#define _PLUGINS_H + +#include "plugin/plugin.h" + +struct plugin_call_job_args { + int id; + char *arg1; + char *arg2; + char *arg3; + char *arg4; + char *arg5; +}; + +struct plugin { + int id; + const char *path; + void *handle; + char *name; + uint32_t version; + int (*fc_load)(struct epg_handle *); + int (*fc_unload)(struct epg_handle *); + int (*fc_player_join)(struct epg_handle *, char *); + int (*fc_player_leave)(struct epg_handle *, char *, char *); + int (*fc_player_say)(struct epg_handle *, char *, char *); + int (*fc_player_die)(struct epg_handle *, char *, char *); + int (*fc_player_achievement)(struct epg_handle *, char *, char *); + int (*fc_player_challenge)(struct epg_handle *, char *, char *); + int (*fc_player_goal)(struct epg_handle *, char *, char *); + int (*fc_server_stopping)(struct epg_handle *); + int (*fc_server_starting)(struct epg_handle *, char *); + int (*fc_server_started)(struct epg_handle *, char *); +}; + +int plugin_load(int stderr_fd, const char *path, const int id, struct plugin *out); +int plugin_unload(int stderr_fd, struct plugin *plugin); + +#endif // _PLUGINS_H diff --git a/rcon.c b/rcon.c new file mode 100644 index 0000000..75e850d --- /dev/null +++ b/rcon.c @@ -0,0 +1,113 @@ +/* + * Adopted from mcrcon, Copyright (c) 2012-2020, Tiiffi . + * https://github.com/Tiiffi/mcrcon/tree/b02201d689b3032bc681b28f175fd3d83d167293 + */ + +#include "rcon.h" +#include "common.h" + +#include +#include +#include +#include +#include + +int rcon_send_packet(int sd, struct rc_packet *packet) +{ + int len; + int total = 0; // bytes we've sent + int bytesleft; // bytes left to send + int ret = -1; + + bytesleft = len = packet->size + sizeof(int); + + while (total < len) + { + ret = send(sd, (char *) packet + total, bytesleft, 0); + if(ret == -1) + { + fprintf(stderr, _("send(): %s.\n"), strerror(errno)); + return EX_IOERR; + } + total += ret; + bytesleft -= ret; + } + + return EX_OK; +} + +int rcon_build_packet(struct rc_packet *out, int id, int cmd, char *s1) +{ + // size + id + cmd + s1 + s2 NULL terminator + int s1_len = strlen(s1); + if (s1_len > RCON_DATA_BUFFSIZE) + { + fprintf(stderr, _("Warning: Command string too long (%d). Maximum allowed: %d.\n"), s1_len, RCON_DATA_BUFFSIZE); + return EX_DATAERR; + } + + out->size = sizeof(int) * 2 + s1_len + 2; + out->id = id; + out->cmd = cmd; + strncpy(out->data, s1, RCON_DATA_BUFFSIZE); + + return EX_OK; +} + +int rcon_recv_packet(struct rc_packet *out, int sd) +{ + int psize; + + int ret = recv(sd, (char *) &psize, sizeof(int), 0); + + if (ret == 0) + { + fprintf(stderr, _("Connection lost.\n")); + return EX_IOERR; + } + if(ret == -1) + { + fprintf(stderr, _("recv(): %d\n"), errno); + return EX_IOERR; + } + + if (ret != sizeof(int)) + { + fprintf(stderr, _("Error: recv() failed. Invalid packet size (%d).\n"), ret); + return EX_IOERR; + } + + if (psize < 10 || psize > RCON_DATA_BUFFSIZE) + { + fprintf(stderr, _("Warning: invalid packet size (%d). Must over 10 and less than %d.\n"), psize, RCON_DATA_BUFFSIZE); + + if(psize > RCON_DATA_BUFFSIZE || psize < 0) psize = RCON_DATA_BUFFSIZE; + // Former net_clean_incoming. + char tmp[psize]; + ret = recv(sd, tmp, psize, 0); + + if(ret == 0) + { + fprintf(stderr, _("Connection lost.\n")); + } + + return EX_DATAERR; + } + + out->size = psize; + + int received = 0; + while (received < psize) + { + ret = recv(sd, (char *) out + sizeof(int) + received, psize - received, 0); + if (ret == 0) // connection closed before completing receving + { + fprintf(stderr, _("Connection lost.\n")); + return EX_IOERR; + } + + received += ret; + } + + return EX_OK; +} diff --git a/rcon.h b/rcon.h new file mode 100644 index 0000000..d12814c --- /dev/null +++ b/rcon.h @@ -0,0 +1,29 @@ +/* + * Adopted from mcrcon, Copyright (c) 2012-2020, Tiiffi . + * https://github.com/Tiiffi/mcrcon/tree/b02201d689b3032bc681b28f175fd3d83d167293 + */ + +#ifndef _RCON_H +#define _RCON_H + +#include "plugin/common.h" + +#define RCON_EXEC_COMMAND 2 +#define RCON_AUTHENTICATE 3 +#define RCON_RESPONSEVALUE 0 +#define RCON_AUTH_RESPONSE 2 +#define RCON_PID 0xBADC0DE + +struct rc_packet { + int size; + int id; + int cmd; + char data[RCON_DATA_BUFFSIZE]; + // ignoring string2 for now +}; + +int rcon_send_packet(int sd, struct rc_packet *packet); +int rcon_build_packet(struct rc_packet *out, int id, int cmd, char *s1); +int rcon_recv_packet(struct rc_packet *out, int sd); + +#endif // _RCON_H diff --git a/rcon_host.c b/rcon_host.c new file mode 100644 index 0000000..a5ba9b4 --- /dev/null +++ b/rcon_host.c @@ -0,0 +1,265 @@ +#include "rcon_host.h" +#include "rcon.h" +#include "net.h" +#include "plugin/plugin.h" +#include "md5.h" + +#include +#include +#include +#include +#include +#include "common.h" +#include +#include + +static bool pthread_key_init = false; +static pthread_key_t key_rcon_fd; + +static struct rcon_host_connarg *connarg; +static uint64_t connarg_existing_hash_1; +static uint64_t connarg_existing_hash_2; + +struct rcon_thread_data { + int fd; + uint64_t connarg_hash_1; + uint64_t connarg_hash_2; +}; + +static void destructor(void *data) +{ + DEBUGF("rcon_host.c#destructor: (%p)\n", data); + struct rcon_thread_data *dat = (struct rcon_thread_data *)data; + if(dat->fd != -1) + { + DEBUGF("rcon_host.c#destructor: Closing rcon socket %d\n", dat->fd); + close(dat->fd); + } + free(dat); +} + +/* https://stackoverflow.com/a/25669375/6792243 */ +static uint64_t uint8ArrtoUint64(uint8_t *var, uint32_t lowest_pos) +{ + return (((uint64_t)var[lowest_pos+7]) << 56) | + (((uint64_t)var[lowest_pos+6]) << 48) | + (((uint64_t)var[lowest_pos+5]) << 40) | + (((uint64_t)var[lowest_pos+4]) << 32) | + (((uint64_t)var[lowest_pos+3]) << 24) | + (((uint64_t)var[lowest_pos+2]) << 16) | + (((uint64_t)var[lowest_pos+1]) << 8) | + (((uint64_t)var[lowest_pos]) << 0); +} + +static void connarg_hash_update(struct rcon_host_connarg *connarg) +{ + uint64_t hash_1 = 0; + uint64_t hash_2 = 0; + if(connarg != NULL) + { + MD5Context ctx; + md5Init(&ctx); + if(connarg->host != NULL) md5Update(&ctx, (uint8_t *)connarg->host, sizeof(char) * strlen(connarg->host)); + if(connarg->port != NULL) md5Update(&ctx, (uint8_t *)connarg->port, sizeof(char) * strlen(connarg->port)); + if(connarg->password != NULL) md5Update(&ctx, (uint8_t *)connarg->password, sizeof(char) * strlen(connarg->password)); + md5Finalize(&ctx); + hash_1 = uint8ArrtoUint64(ctx.digest, 0); + hash_2 = uint8ArrtoUint64(ctx.digest, 8); + } + connarg_existing_hash_1 = hash_1; + connarg_existing_hash_2 = hash_2; + DEBUGF("rcon_host.c#connarg_hash: %lu%lu.\n", hash_1, hash_2); +} + +static int rcon_host_clear_current_thread_socket() +{ + struct rcon_thread_data *data = pthread_getspecific(key_rcon_fd); + if(data != NULL) data->fd = -1; + return 0; +} + +static int rcon_host_get_current_thread_socket(int *out) +{ + int r = 0; + struct rcon_thread_data *data = pthread_getspecific(key_rcon_fd); + if(data == NULL) + { + DEBUG("rcon_host.c#rcon_host_get_current_thread_socket: Allocating new thread specific data.\n"); + data = malloc(sizeof(struct rcon_thread_data)); + r = pthread_setspecific(key_rcon_fd, data); + if(r) + { + fprintf(stderr, _("Cannot set thread specific data: %d\n"), r); + free(data); + data = NULL; + goto cleanup; + } + data->fd = -1; + data->connarg_hash_1 = 0; + data->connarg_hash_2 = 0; + } + DEBUGF("rcon_host.c#rcon_host_get_current_thread_socket: Hash: %lu%lu -> %lu%lu.\n", + data->connarg_hash_1, + data->connarg_hash_2, + connarg_existing_hash_1, + connarg_existing_hash_2); + + if(data->connarg_hash_1 != connarg_existing_hash_1 || data->connarg_hash_2 != connarg_existing_hash_2) + { + DEBUGF("rcon_host.c#rcon_host_get_current_thread_socket: Hash mismatch (%lu%lu -> %lu%lu). Recreating.\n", + data->connarg_hash_1, + data->connarg_hash_2, + connarg_existing_hash_1, + connarg_existing_hash_2); + if(data->fd != -1) + { + DEBUGF("rcon_host.c#rcon_host_get_current_thread_socket: Disconnecting existing socket %d.\n", data->fd); + close(data->fd); + data->fd = -1; + data->connarg_hash_1 = 0; + data->connarg_hash_2 = 0; + } + if(connarg != NULL) + { + struct rc_packet pkgt = {0, 0, 0, { 0x00 }}; + r = rcon_build_packet(&pkgt, RCON_PID, RCON_AUTHENTICATE, connarg->password); + if(r) goto cleanup; + int fd = -1; + r = net_connect(connarg->host, connarg->port, &fd); + if(r) + { + fprintf(stderr, _("Cannot connect to %s:%s: %d\n"), connarg->host, connarg->port, r); + goto cleanup; + } + r = rcon_send_packet(fd, &pkgt); + if(r) + { + close(fd); + goto cleanup; + } + r = rcon_recv_packet(&pkgt, fd); + if(r) + { + close(fd); + goto cleanup; + } + if(pkgt.id == -1) + { + fprintf(stderr, _("Incorrect rcon password.\n")); + close(fd); + r = 77; + goto cleanup; + } + data->fd = fd; + data->connarg_hash_1 = connarg_existing_hash_1; + data->connarg_hash_2 = connarg_existing_hash_2; + } + else + { + r = EPG_RCON_DISABLED; + } + DEBUGF("rcon_host.c#rcon_host_get_current_thread_socket: Socket updated: %d.\n", data->fd); + } + else if(data->fd == -1) /* Disabled */ + { + r = EPG_RCON_DISABLED; + } + *out = data->fd; + goto cleanup; +cleanup: + return r; +} + +int rcon_host_send(const int pkt_id, const char *command) +{ + int r = 0; + int fd = 0; + r = rcon_host_get_current_thread_socket(&fd); + if(r) goto cleanup; + struct rc_packet pkgt = {0, 0, 0, { 0x00 }}; + r = rcon_build_packet(&pkgt, pkt_id, RCON_EXEC_COMMAND, (char *)command); + if(r) goto cleanup; + r = rcon_send_packet(fd, &pkgt); + if(r) + { + close(fd); + rcon_host_clear_current_thread_socket(); + goto cleanup; + } + goto cleanup; +cleanup: + return r; +} + +int rcon_host_recv(int *pkt_id, char *out) +{ + int r = 0; + int fd = 0; + r = rcon_host_get_current_thread_socket(&fd); + if(r) goto cleanup; + struct rc_packet pkgt = {0, 0, 0, { 0x00 }}; + r = rcon_recv_packet(&pkgt, fd); + if(r) + { + close(fd); + rcon_host_clear_current_thread_socket(); + goto cleanup; + } + // TODO: Size issue? Memory issue? + *pkt_id = pkgt.id; + strcpy(out, pkgt.data); + goto cleanup; +cleanup: + return r; +} + +int rcon_host_init() +{ + int r = 0; + r = pthread_key_create(&key_rcon_fd, &destructor); + if(r) goto cleanup; + pthread_key_init = true; +cleanup: + if(r) rcon_host_free(); + return r; +} + +void rcon_host_free() +{ + if(pthread_key_init) + { + pthread_key_delete(key_rcon_fd); + pthread_key_init = false; + } +} + +struct rcon_host_connarg *rcon_host_getconnarg() +{ + return connarg; +} + +void rcon_host_setconnarg(struct rcon_host_connarg *arg) +{ + connarg = arg; + connarg_hash_update(arg); +} + +void rcon_host_connarg_free(struct rcon_host_connarg *arg) +{ + if(arg->host != NULL) + { + free(arg->host); + arg->host = NULL; + } + if(arg->port != NULL) + { + free(arg->port); + arg->port = NULL; + } + if(arg->password != NULL) + { + free(arg->password); + arg->password = NULL; + } + free(arg); +} diff --git a/rcon_host.h b/rcon_host.h new file mode 100644 index 0000000..c8578a4 --- /dev/null +++ b/rcon_host.h @@ -0,0 +1,20 @@ +#ifndef _RCON_HOST_H +#define _RCON_HOST_H + +struct rcon_host_connarg { + char *host; + char *port; + char *password; +}; + +int rcon_host_init(); +void rcon_host_free(); +void rcon_host_connarg_free(struct rcon_host_connarg *arg); + +int rcon_host_send(const int id, const char *command); +int rcon_host_recv(int *pkgt_id, char *out); + +void rcon_host_setconnarg(struct rcon_host_connarg *arg); +struct rcon_host_connarg *rcon_host_getconnarg(); + +#endif // _RCON_HOST_H diff --git a/sample/Makefile b/sample/Makefile new file mode 100644 index 0000000..8b9ee32 --- /dev/null +++ b/sample/Makefile @@ -0,0 +1,28 @@ +# Copyright 2019 ~ 2021 YuutaW Minecraft, All Rights Reserved. +# Proprietary and confidential. +# Unauthorized copying of any parts of this file, via any medium is strictly prohibited. +# Written by Yuuta Liang , April 2021. + +CFLAGS= \ + -I.\ + -g \ + -std=c99 \ + -Wall \ + -D_POSIX_C_SOURCE=200809L \ + + +LDFLAGS= \ + +OBJ=main.o + +BIN=libsample.so + +%.o: %.c + $(CC) -c -o $@ $< $(CFLAGS) -fpic + +$(BIN): $(OBJ) + $(CC) -shared -o $@ $^ $(CFLAGS) $(LDFLAGS) + +.PHONY: clean +clean: + $(RM) *~ *.o $(BIN) diff --git a/sample/main.c b/sample/main.c new file mode 100644 index 0000000..98f54d9 --- /dev/null +++ b/sample/main.c @@ -0,0 +1,118 @@ +/* + * Copyright 2019 ~ 2021 YuutaW Minecraft, All Rights Reserved. + * Proprietary and confidential. + * Unauthorized copying of any parts of this file, via any medium is strictly prohibited. + * Written by Yuuta Liang , April 2021. + */ + +#include "../plugin/plugin.h" + +#include +#include +#include + +const uint32_t epg_version = 1; + +const char *epg_name = "Test Plugin"; + +int epg_load(struct epg_handle *handle) +{ + printf("[%d]: Loaded.\n", handle->id); + int r = 0; + r = handle->rcon_send(11, "list uuid"); + printf("[%d]: rcon_send: %d\n", handle->id, r); + int id = -1; + char out[RCON_DATA_BUFFSIZE]; + r = handle->rcon_recv(&id, out); + printf("[%d]: rcon_recv: %d. ID: %d, out: '%s'.\n", handle->id, r, id, out); + return 0; +} + +int epg_unload(struct epg_handle *handle) +{ + printf("[%d]: Unloading.\n", handle->id); + return 0; +} + +int epg_player_join(struct epg_handle *handle, + char *player) +{ + printf("[%d]: %s joined.\n", handle->id, player); + sleep(10); + int r = 0; + r = handle->rcon_send(11, "list"); + printf("[%d]: rcon_send: %d\n", handle->id, r); + int id = -1; + char out[RCON_DATA_BUFFSIZE]; + r = handle->rcon_recv(&id, out); + printf("[%d]: rcon_recv: %d. ID: %d, out: '%s'.\n", handle->id, r, id, out); + return 0; +} + +int epg_player_leave(struct epg_handle *handle, + char *player, + char *reason) +{ + printf("[%d]: %s left: %s.\n", handle->id, player, reason); + return 0; +} + +int epg_player_say(struct epg_handle *handle, + char *player, + char *content) +{ + printf("[%d]: %s said: %s.\n", handle->id, player, content); + return 0; +} + +int epg_player_die(struct epg_handle *handle, + char *player, + char *source) +{ + printf("[%d]: %s died because of %s.\n", handle->id, player, source); + return 0; +} + +int epg_player_achievement(struct epg_handle *handle, + char *player, + char *achievement) +{ + printf("[%d]: %s achieved %s.\n", handle->id, player, achievement); + return 0; +} + +int epg_player_challenge(struct epg_handle *handle, + char *player, + char *challenge) +{ + printf("[%d]: %s made the challenge: %s.\n", handle->id, player, challenge); + return 0; +} + +int epg_player_goal(struct epg_handle *handle, + char *player, + char *goal) +{ + printf("[%d]: %s made the goal: %s.\n", handle->id, player, goal); + return 0; +} + +int epg_server_stopping(struct epg_handle *handle) +{ + printf("[%d]: Server stopped.\n", handle->id); + return 0; +} + +int epg_server_starting(struct epg_handle *handle, + char *version) +{ + printf("[%d]: Server is starting, version: %s.\n", handle->id, version); + return 0; +} + +int epg_server_started(struct epg_handle *handle, + char *took) +{ + printf("[%d]: Server started, took: %s.\n", handle->id, took); + return 0; +} diff --git a/thpool.c b/thpool.c new file mode 100644 index 0000000..213bbdb --- /dev/null +++ b/thpool.c @@ -0,0 +1,537 @@ +/* ******************************** + * Author: Johan Hanssen Seferidis + * License: MIT + * Description: Library providing a threading pool where you can add + * work. For usage, check the thpool.h file or README.md + * + *//** @file thpool.h *//* + * + ********************************/ + +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include + +#include "thpool.h" +#include "threads_util.h" + +#ifdef THPOOL_DEBUG +#define THPOOL_DEBUG 1 +#else +#define THPOOL_DEBUG 0 +#endif + +#if !defined(DISABLE_PRINT) || defined(THPOOL_DEBUG) +#define err(str) fprintf(stderr, str) +#else +#define err(str) +#endif + +static volatile int threads_keepalive; +static volatile int threads_on_hold; + + + +/* ========================== STRUCTURES ============================ */ + + +/* Binary semaphore */ +typedef struct bsem { + pthread_mutex_t mutex; + pthread_cond_t cond; + int v; +} bsem; + + +/* Job */ +typedef struct job{ + struct job* prev; /* pointer to previous job */ + void (*function)(void* arg); /* function pointer */ + void* arg; /* function's argument */ +} job; + + +/* Job queue */ +typedef struct jobqueue{ + pthread_mutex_t rwmutex; /* used for queue r/w access */ + job *front; /* pointer to front of queue */ + job *rear; /* pointer to rear of queue */ + bsem *has_jobs; /* flag as binary semaphore */ + int len; /* number of jobs in queue */ +} jobqueue; + + +/* Thread */ +typedef struct thread{ + int id; /* friendly id */ + pthread_t pthread; /* pointer to actual thread */ + struct thpool_* thpool_p; /* access to thpool */ +} thread; + + +/* Threadpool */ +typedef struct thpool_{ + thread** threads; /* pointer to threads */ + volatile int num_threads_alive; /* threads currently alive */ + volatile int num_threads_working; /* threads currently working */ + pthread_mutex_t thcount_lock; /* used for thread count etc */ + pthread_cond_t threads_all_idle; /* signal to thpool_wait */ + jobqueue jobqueue; /* job queue */ +} thpool_; + + + + + +/* ========================== PROTOTYPES ============================ */ + + +static int thread_init(thpool_* thpool_p, struct thread** thread_p, int id); +static void* thread_do(struct thread* thread_p); +static void thread_hold(int sig_id); +static void thread_destroy(struct thread* thread_p); + +static int jobqueue_init(jobqueue* jobqueue_p); +static void jobqueue_clear(jobqueue* jobqueue_p); +static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob_p); +static struct job* jobqueue_pull(jobqueue* jobqueue_p); +static void jobqueue_destroy(jobqueue* jobqueue_p); + +static void bsem_init(struct bsem *bsem_p, int value); +static void bsem_reset(struct bsem *bsem_p); +static void bsem_post(struct bsem *bsem_p); +static void bsem_post_all(struct bsem *bsem_p); +static void bsem_wait(struct bsem *bsem_p); + + + + + +/* ========================== THREADPOOL ============================ */ + + +/* Initialise thread pool */ +struct thpool_* thpool_init(int num_threads){ + + threads_on_hold = 0; + threads_keepalive = 1; + + if (num_threads < 0){ + num_threads = 0; + } + + /* Make new thread pool */ + thpool_* thpool_p; + thpool_p = (struct thpool_*)malloc(sizeof(struct thpool_)); + if (thpool_p == NULL){ + err("thpool_init(): Could not allocate memory for thread pool\n"); + return NULL; + } + thpool_p->num_threads_alive = 0; + thpool_p->num_threads_working = 0; + + /* Initialise the job queue */ + if (jobqueue_init(&thpool_p->jobqueue) == -1){ + err("thpool_init(): Could not allocate memory for job queue\n"); + free(thpool_p); + return NULL; + } + + /* Make threads in pool */ + thpool_p->threads = (struct thread**)malloc(num_threads * sizeof(struct thread *)); + if (thpool_p->threads == NULL){ + err("thpool_init(): Could not allocate memory for threads\n"); + jobqueue_destroy(&thpool_p->jobqueue); + free(thpool_p); + return NULL; + } + + pthread_mutex_init(&(thpool_p->thcount_lock), NULL); + pthread_cond_init(&thpool_p->threads_all_idle, NULL); + + /* Thread init */ + int n; + for (n=0; nthreads[n], n); +#if THPOOL_DEBUG + printf("THPOOL_DEBUG: Created thread %d in pool \n", n); +#endif + } + + /* Wait for threads to initialize */ + while (thpool_p->num_threads_alive != num_threads) {} + + return thpool_p; +} + + +/* Add work to the thread pool */ +int thpool_add_work(thpool_* thpool_p, void (*function_p)(void*), void* arg_p){ + job* newjob; + + newjob=(struct job*)malloc(sizeof(struct job)); + if (newjob==NULL){ + err("thpool_add_work(): Could not allocate memory for new job\n"); + return -1; + } + + /* add function and argument */ + newjob->function=function_p; + newjob->arg=arg_p; + + /* add job to queue */ + jobqueue_push(&thpool_p->jobqueue, newjob); + + return 0; +} + + +/* Wait until all jobs have finished */ +void thpool_wait(thpool_* thpool_p){ + pthread_mutex_lock(&thpool_p->thcount_lock); + while (thpool_p->jobqueue.len || thpool_p->num_threads_working) { + pthread_cond_wait(&thpool_p->threads_all_idle, &thpool_p->thcount_lock); + } + pthread_mutex_unlock(&thpool_p->thcount_lock); +} + + +/* Destroy the threadpool */ +void thpool_destroy(thpool_* thpool_p){ + /* No need to destory if it's NULL */ + if (thpool_p == NULL) return ; + + volatile int threads_total = thpool_p->num_threads_alive; + + /* End each thread 's infinite loop */ + threads_keepalive = 0; + + /* Give one second to kill idle threads */ + double TIMEOUT = 1.0; + time_t start, end; + double tpassed = 0.0; + time (&start); + while (tpassed < TIMEOUT && thpool_p->num_threads_alive){ + bsem_post_all(thpool_p->jobqueue.has_jobs); + time (&end); + tpassed = difftime(end,start); + } + + /* Poll remaining threads */ + while (thpool_p->num_threads_alive){ + bsem_post_all(thpool_p->jobqueue.has_jobs); + sleep(1); + } + + /* Job queue cleanup */ + jobqueue_destroy(&thpool_p->jobqueue); + /* Deallocs */ + int n; + for (n=0; n < threads_total; n++){ + thread_destroy(thpool_p->threads[n]); + } + free(thpool_p->threads); + free(thpool_p); +} + + +/* Pause all threads in threadpool */ +void thpool_pause(thpool_* thpool_p) { + int n; + for (n=0; n < thpool_p->num_threads_alive; n++){ + pthread_kill(thpool_p->threads[n]->pthread, SIGUSR1); + } +} + + +/* Resume all threads in threadpool */ +void thpool_resume(thpool_* thpool_p) { + // resuming a single threadpool hasn't been + // implemented yet, meanwhile this supresses + // the warnings + (void)thpool_p; + + threads_on_hold = 0; +} + + +int thpool_num_threads_working(thpool_* thpool_p){ + return thpool_p->num_threads_working; +} + + + + + +/* ============================ THREAD ============================== */ + + +/* Initialize a thread in the thread pool + * + * @param thread address to the pointer of the thread to be created + * @param id id to be given to the thread + * @return 0 on success, -1 otherwise. + */ +static int thread_init (thpool_* thpool_p, struct thread** thread_p, int id){ + + *thread_p = (struct thread*)malloc(sizeof(struct thread)); + if (*thread_p == NULL){ + err("thread_init(): Could not allocate memory for thread\n"); + return -1; + } + + (*thread_p)->thpool_p = thpool_p; + (*thread_p)->id = id; + + pthread_create(&(*thread_p)->pthread, NULL, (void * (*)(void *)) thread_do, (*thread_p)); + pthread_detach((*thread_p)->pthread); + return 0; +} + + +/* Sets the calling thread on hold */ +static void thread_hold(int sig_id) { + (void)sig_id; + threads_on_hold = 1; + while (threads_on_hold){ + sleep(1); + } +} + + +/* What each thread is doing +* +* In principle this is an endless loop. The only time this loop gets interuppted is once +* thpool_destroy() is invoked or the program exits. +* +* @param thread thread that will run this function +* @return nothing +*/ +static void* thread_do(struct thread* thread_p){ + + /* Set thread name for profiling and debuging */ + char thread_name[32] = {0}; + snprintf(thread_name, 32, "thread-pool-%d", thread_p->id); + thread_set_name(thread_name); + + /* Assure all threads have been created before starting serving */ + thpool_* thpool_p = thread_p->thpool_p; + + /* Register signal handler */ + struct sigaction act; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = thread_hold; + if (sigaction(SIGUSR1, &act, NULL) == -1) { + err("thread_do(): cannot handle SIGUSR1"); + } + + /* Mark thread as alive (initialized) */ + pthread_mutex_lock(&thpool_p->thcount_lock); + thpool_p->num_threads_alive += 1; + pthread_mutex_unlock(&thpool_p->thcount_lock); + + while(threads_keepalive){ + + bsem_wait(thpool_p->jobqueue.has_jobs); + + if (threads_keepalive){ + + pthread_mutex_lock(&thpool_p->thcount_lock); + thpool_p->num_threads_working++; + pthread_mutex_unlock(&thpool_p->thcount_lock); + + /* Read job from queue and execute it */ + void (*func_buff)(void*); + void* arg_buff; + job* job_p = jobqueue_pull(&thpool_p->jobqueue); + if (job_p) { + func_buff = job_p->function; + arg_buff = job_p->arg; + func_buff(arg_buff); + free(job_p); + } + + pthread_mutex_lock(&thpool_p->thcount_lock); + thpool_p->num_threads_working--; + if (!thpool_p->num_threads_working) { + pthread_cond_signal(&thpool_p->threads_all_idle); + } + pthread_mutex_unlock(&thpool_p->thcount_lock); + + } + } + pthread_mutex_lock(&thpool_p->thcount_lock); + thpool_p->num_threads_alive --; + pthread_mutex_unlock(&thpool_p->thcount_lock); + + return NULL; +} + + +/* Frees a thread */ +static void thread_destroy (thread* thread_p){ + free(thread_p); +} + + + + + +/* ============================ JOB QUEUE =========================== */ + + +/* Initialize queue */ +static int jobqueue_init(jobqueue* jobqueue_p){ + jobqueue_p->len = 0; + jobqueue_p->front = NULL; + jobqueue_p->rear = NULL; + + jobqueue_p->has_jobs = (struct bsem*)malloc(sizeof(struct bsem)); + if (jobqueue_p->has_jobs == NULL){ + return -1; + } + + pthread_mutex_init(&(jobqueue_p->rwmutex), NULL); + bsem_init(jobqueue_p->has_jobs, 0); + + return 0; +} + + +/* Clear the queue */ +static void jobqueue_clear(jobqueue* jobqueue_p){ + + while(jobqueue_p->len){ + free(jobqueue_pull(jobqueue_p)); + } + + jobqueue_p->front = NULL; + jobqueue_p->rear = NULL; + bsem_reset(jobqueue_p->has_jobs); + jobqueue_p->len = 0; + +} + + +/* Add (allocated) job to queue + */ +static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob){ + + pthread_mutex_lock(&jobqueue_p->rwmutex); + newjob->prev = NULL; + + switch(jobqueue_p->len){ + + case 0: /* if no jobs in queue */ + jobqueue_p->front = newjob; + jobqueue_p->rear = newjob; + break; + + default: /* if jobs in queue */ + jobqueue_p->rear->prev = newjob; + jobqueue_p->rear = newjob; + + } + jobqueue_p->len++; + + bsem_post(jobqueue_p->has_jobs); + pthread_mutex_unlock(&jobqueue_p->rwmutex); +} + + +/* Get first job from queue(removes it from queue) + * Notice: Caller MUST hold a mutex + */ +static struct job* jobqueue_pull(jobqueue* jobqueue_p){ + + pthread_mutex_lock(&jobqueue_p->rwmutex); + job* job_p = jobqueue_p->front; + + switch(jobqueue_p->len){ + + case 0: /* if no jobs in queue */ + break; + + case 1: /* if one job in queue */ + jobqueue_p->front = NULL; + jobqueue_p->rear = NULL; + jobqueue_p->len = 0; + break; + + default: /* if >1 jobs in queue */ + jobqueue_p->front = job_p->prev; + jobqueue_p->len--; + /* more than one job in queue -> post it */ + bsem_post(jobqueue_p->has_jobs); + + } + + pthread_mutex_unlock(&jobqueue_p->rwmutex); + return job_p; +} + + +/* Free all queue resources back to the system */ +static void jobqueue_destroy(jobqueue* jobqueue_p){ + jobqueue_clear(jobqueue_p); + free(jobqueue_p->has_jobs); +} + + + + + +/* ======================== SYNCHRONISATION ========================= */ + + +/* Init semaphore to 1 or 0 */ +static void bsem_init(bsem *bsem_p, int value) { + if (value < 0 || value > 1) { + err("bsem_init(): Binary semaphore can take only values 1 or 0"); + exit(1); + } + pthread_mutex_init(&(bsem_p->mutex), NULL); + pthread_cond_init(&(bsem_p->cond), NULL); + bsem_p->v = value; +} + + +/* Reset semaphore to 0 */ +static void bsem_reset(bsem *bsem_p) { + bsem_init(bsem_p, 0); +} + + +/* Post to at least one thread */ +static void bsem_post(bsem *bsem_p) { + pthread_mutex_lock(&bsem_p->mutex); + bsem_p->v = 1; + pthread_cond_signal(&bsem_p->cond); + pthread_mutex_unlock(&bsem_p->mutex); +} + + +/* Post to all threads */ +static void bsem_post_all(bsem *bsem_p) { + pthread_mutex_lock(&bsem_p->mutex); + bsem_p->v = 1; + pthread_cond_broadcast(&bsem_p->cond); + pthread_mutex_unlock(&bsem_p->mutex); +} + + +/* Wait on semaphore until semaphore has value 0 */ +static void bsem_wait(bsem* bsem_p) { + pthread_mutex_lock(&bsem_p->mutex); + while (bsem_p->v != 1) { + pthread_cond_wait(&bsem_p->cond, &bsem_p->mutex); + } + bsem_p->v = 0; + pthread_mutex_unlock(&bsem_p->mutex); +} diff --git a/thpool.h b/thpool.h new file mode 100644 index 0000000..af3e68d --- /dev/null +++ b/thpool.h @@ -0,0 +1,187 @@ +/********************************** + * @author Johan Hanssen Seferidis + * License: MIT + * + **********************************/ + +#ifndef _THPOOL_ +#define _THPOOL_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* =================================== API ======================================= */ + + +typedef struct thpool_* threadpool; + + +/** + * @brief Initialize threadpool + * + * Initializes a threadpool. This function will not return until all + * threads have initialized successfully. + * + * @example + * + * .. + * threadpool thpool; //First we declare a threadpool + * thpool = thpool_init(4); //then we initialize it to 4 threads + * .. + * + * @param num_threads number of threads to be created in the threadpool + * @return threadpool created threadpool on success, + * NULL on error + */ +threadpool thpool_init(int num_threads); + + +/** + * @brief Add work to the job queue + * + * Takes an action and its argument and adds it to the threadpool's job queue. + * If you want to add to work a function with more than one arguments then + * a way to implement this is by passing a pointer to a structure. + * + * NOTICE: You have to cast both the function and argument to not get warnings. + * + * @example + * + * void print_num(int num){ + * printf("%d\n", num); + * } + * + * int main() { + * .. + * int a = 10; + * thpool_add_work(thpool, (void*)print_num, (void*)a); + * .. + * } + * + * @param threadpool threadpool to which the work will be added + * @param function_p pointer to function to add as work + * @param arg_p pointer to an argument + * @return 0 on success, -1 otherwise. + */ +int thpool_add_work(threadpool, void (*function_p)(void*), void* arg_p); + + +/** + * @brief Wait for all queued jobs to finish + * + * Will wait for all jobs - both queued and currently running to finish. + * Once the queue is empty and all work has completed, the calling thread + * (probably the main program) will continue. + * + * Smart polling is used in wait. The polling is initially 0 - meaning that + * there is virtually no polling at all. If after 1 seconds the threads + * haven't finished, the polling interval starts growing exponentially + * until it reaches max_secs seconds. Then it jumps down to a maximum polling + * interval assuming that heavy processing is being used in the threadpool. + * + * @example + * + * .. + * threadpool thpool = thpool_init(4); + * .. + * // Add a bunch of work + * .. + * thpool_wait(thpool); + * puts("All added work has finished"); + * .. + * + * @param threadpool the threadpool to wait for + * @return nothing + */ +void thpool_wait(threadpool); + + +/** + * @brief Pauses all threads immediately + * + * The threads will be paused no matter if they are idle or working. + * The threads return to their previous states once thpool_resume + * is called. + * + * While the thread is being paused, new work can be added. + * + * @example + * + * threadpool thpool = thpool_init(4); + * thpool_pause(thpool); + * .. + * // Add a bunch of work + * .. + * thpool_resume(thpool); // Let the threads start their magic + * + * @param threadpool the threadpool where the threads should be paused + * @return nothing + */ +void thpool_pause(threadpool); + + +/** + * @brief Unpauses all threads if they are paused + * + * @example + * .. + * thpool_pause(thpool); + * sleep(10); // Delay execution 10 seconds + * thpool_resume(thpool); + * .. + * + * @param threadpool the threadpool where the threads should be unpaused + * @return nothing + */ +void thpool_resume(threadpool); + + +/** + * @brief Destroy the threadpool + * + * This will wait for the currently active threads to finish and then 'kill' + * the whole threadpool to free up memory. + * + * @example + * int main() { + * threadpool thpool1 = thpool_init(2); + * threadpool thpool2 = thpool_init(2); + * .. + * thpool_destroy(thpool1); + * .. + * return 0; + * } + * + * @param threadpool the threadpool to destroy + * @return nothing + */ +void thpool_destroy(threadpool); + + +/** + * @brief Show currently working threads + * + * Working threads are the threads that are performing work (not idle). + * + * @example + * int main() { + * threadpool thpool1 = thpool_init(2); + * threadpool thpool2 = thpool_init(2); + * .. + * printf("Working threads: %d\n", thpool_num_threads_working(thpool1)); + * .. + * return 0; + * } + * + * @param threadpool the threadpool of interest + * @return integer number of threads working + */ +int thpool_num_threads_working(threadpool); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/threads_util.c b/threads_util.c new file mode 100644 index 0000000..d2247a2 --- /dev/null +++ b/threads_util.c @@ -0,0 +1,15 @@ +#include "threads_util.h" + +#if defined(__linux__) +#include +#endif + +void thread_set_name(const char *name) +{ +#if defined(__linux__) + /* Use prctl instead to prevent using _GNU_SOURCE flag and implicit declaration */ + prctl(PR_SET_NAME, name); +#elif defined(__APPLE__) && defined(__MACH__) + pthread_setname_np(name); +#endif +} diff --git a/threads_util.h b/threads_util.h new file mode 100644 index 0000000..c2bf489 --- /dev/null +++ b/threads_util.h @@ -0,0 +1,6 @@ +#ifndef _THREADS_UTIL_H +#define _THREADS_UTIL_H + +void thread_set_name(const char *name); + +#endif // _THREADS_UTIL_H -- cgit v1.2.3