aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrumeet <yuuta@yuuta.moe>2021-05-16 19:23:13 -0700
committerTrumeet <yuuta@yuuta.moe>2021-05-16 19:23:13 -0700
commit7e531d78bf330bfac65cf3f208c2300b607decee (patch)
treea430b80d94661b31c7982724bf4f24667ca297b9
downloadextmc-7e531d78bf330bfac65cf3f208c2300b607decee.tar
extmc-7e531d78bf330bfac65cf3f208c2300b607decee.tar.gz
extmc-7e531d78bf330bfac65cf3f208c2300b607decee.tar.bz2
extmc-7e531d78bf330bfac65cf3f208c2300b607decee.zip
First Commit
-rw-r--r--.gitignore5
-rw-r--r--LICENSE339
-rw-r--r--Makefile45
-rw-r--r--README.md33
-rw-r--r--common.h22
-rw-r--r--extmc.117
-rw-r--r--main.c785
-rw-r--r--mcin.c425
-rw-r--r--mcin.h10
-rw-r--r--md5.c257
-rw-r--r--md5.h33
-rw-r--r--net.c55
-rw-r--r--net.h11
-rw-r--r--plugin/common.h6
-rw-r--r--plugin/plugin.h77
-rw-r--r--plugin_registry.c256
-rw-r--r--plugin_registry.h31
-rw-r--r--plugins.c143
-rw-r--r--plugins.h45
-rw-r--r--rcon.c113
-rw-r--r--rcon.h29
-rw-r--r--rcon_host.c265
-rw-r--r--rcon_host.h20
-rw-r--r--sample/Makefile28
-rw-r--r--sample/main.c118
-rw-r--r--thpool.c537
-rw-r--r--thpool.h187
-rw-r--r--threads_util.c15
-rw-r--r--threads_util.h6
29 files changed, 3913 insertions, 0 deletions
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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.
+
+ <signature of Ty Coon>, 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 <yuuta@yuuta.moe>, 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 <yuuta@yuuta.moe>
+
+## 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 <yuuta@yuuta.moe>, April 2021.
+ */
+
+#ifndef _COMMON_H
+#define _COMMON_H
+
+#include <libintl.h>
+#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 <yuuta@yuuta.moe> for typos.
+.TH man 1 "15 May 2021" "1.0" "extmc manual page"
+.SH NAME
+extmc \- extmc daemon
+.SH
+SYNOPSIS
+exmtc <path/to/autoloading/plugins>
+.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 <yuuta@yuuta.moe>.
+.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 <limits.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <semaphore.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <inttypes.h>
+#include <limits.h>
+
+#ifndef CONTROL_SOCKET_PATH
+#define CONTROL_SOCKET_PATH "/run/extmc.ctl"
+#endif
+
+static pthread_mutex_t process_mutex = PTHREAD_MUTEX_INITIALIZER;
+static threadpool thpool = NULL;
+static bool received_sigterm = false;
+static sem_t exit_sem;
+static int ctl_fd = -1;
+
+static void exclusive_section_enter()
+{
+ pthread_mutex_lock(&process_mutex);
+}
+
+static void exclusive_section_leave()
+{
+ pthread_mutex_unlock(&process_mutex);
+}
+
+static int autoload(const char *config_path)
+{
+ FILE *file = fopen(config_path, "r");
+ if(file == NULL)
+ {
+ char buf[128];
+ int r = errno;
+ strerror_r(r, buf, 128);
+ fprintf(stderr, _("Cannot open %s: %s.\n"), config_path, buf);
+ return r;
+ }
+ int r = 0;
+ while(true)
+ {
+ char *current_path = calloc(4098, sizeof(char));
+ if(current_path == NULL)
+ {
+ r = errno;
+ fprintf(stderr, _("Cannot allocate memory: %d.\n"), r);
+ fclose(file);
+ return r;
+ }
+ for(unsigned int i = 2; i <= UINT_MAX; i ++)
+ {
+ if(fgets(&current_path[(i - 2) * 4097], 4098, file) == NULL)
+ {
+ free(current_path);
+ current_path = NULL;
+ break;
+ }
+ if(current_path[strlen(current_path) - 1] != '\n')
+ {
+ char *current_path_ext = realloc(current_path, 4098 * sizeof(char) * i);
+ if(current_path_ext == NULL)
+ {
+ r = errno;
+ fprintf(stderr, _("Cannot allocate memory: %d.\n"), r);
+ free(current_path);
+ fclose(file);
+ return r;
+ }
+ current_path = current_path_ext;
+ }
+ else
+ {
+ break;
+ }
+ }
+ if(current_path == NULL) break;
+ // Remove \n
+ current_path[strlen(current_path) - 1] = '\0';
+ if(strlen(current_path) > 0)
+ {
+ int id = 0;
+ if(!plugin_registry_load(2, current_path, &id))
+ printf(_("Autoload: loaded %s with ID %d.\n"), current_path, id);
+ }
+ free(current_path);
+ }
+ fclose(file);
+ return 0;
+}
+
+static void *main_sighandler(void *arg)
+{
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ thread_set_name("sighandler");
+ int r = 0;
+ sigset_t *set = arg;
+ int sig;
+
+ while(true)
+ {
+ r = sigwait(set, &sig);
+ if(r)
+ {
+ fprintf(stderr, _("sigwait(): %d\n"), r);
+ goto cleanup;
+ }
+ switch(sig)
+ {
+ case SIGINT:
+ case SIGTERM:
+ printf(_("Received SIGINT or SIGTERM. Exiting.\n"));
+ received_sigterm = true;
+ goto cleanup;
+ }
+ }
+ goto cleanup;
+cleanup:
+ sem_post(&exit_sem);
+ pthread_exit(NULL);
+ return NULL;
+}
+
+static void *main_loop(void *arg)
+{
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ thread_set_name("main-loop");
+ char buffer[501];
+ while(true)
+ {
+ if(fgets(buffer, 500, stdin) == NULL)
+ {
+ printf(_("Received EOF. Exiting.\n"));
+ goto cleanup;
+ }
+ exclusive_section_enter();
+ if(received_sigterm)
+ {
+ exclusive_section_leave();
+ goto cleanup;
+ }
+ mcin_match(buffer, thpool);
+ exclusive_section_leave();
+ }
+ goto cleanup;
+cleanup:
+ sem_post(&exit_sem);
+ pthread_exit(NULL);
+ return NULL;
+}
+
+static int main_handle_cmd(const int out, int argc, char **argv)
+{
+ if(argc <= 0)
+ {
+ dprintf(out, _("Invalid arguments\n"));
+ return 64;
+ }
+ if(!strcmp(argv[0], "list"))
+ {
+ if(argc != 1)
+ {
+ dprintf(out, _("list expects no arguments\n"));
+ return 64;
+ }
+ for(int i = 0; i < plugin_size(); i ++)
+ {
+ const struct plugin *plug = plugin_get_by_index(i);
+ dprintf(out, _("%d\t%s\n"), plug->id, plug->name);
+ }
+ return 0;
+ }
+ if(!strcmp(argv[0], "load"))
+ {
+ if(argc != 2)
+ {
+ dprintf(out, _("load expects one argument: load <path/to/lib.so>\n"));
+ return 64;
+ }
+ dprintf(out, _("Waiting until the processing is done.\n"));
+ exclusive_section_enter();
+ thpool_wait(thpool);
+ int id = -1;
+ int r = plugin_registry_load(out, argv[1], &id);
+ if(!r)
+ {
+ dprintf(out, _("ID: %d\n"), id);
+ }
+ exclusive_section_leave();
+ return r;
+ }
+ if(!strcmp(argv[0], "unload"))
+ {
+ if(argc != 2)
+ {
+ dprintf(out, _("unload expects one argument: unload <ID>\n"));
+ return 64;
+ }
+ char *endptr;
+ intmax_t num = strtoimax(argv[1], &endptr, 10);
+ if(strcmp(endptr, "") || (num == INTMAX_MAX && errno == ERANGE) || num > INT_MAX || num < INT_MIN)
+ {
+ dprintf(out, _("Invalid ID: %s\n"), argv[1]);
+ return 64;
+ }
+ int id = (int)num;
+ dprintf(out, _("Waiting until the processing is done.\n"));
+ int r = 0;
+ exclusive_section_enter();
+ thpool_wait(thpool);
+ struct plugin *plug = plugin_get(id);
+ if(plug == NULL)
+ {
+ r = 1;
+ dprintf(out, _("Cannot find plugin ID: %d\n"), id);
+ }
+ else
+ {
+ r = plugin_registry_unload(out, id);
+ }
+ exclusive_section_leave();
+ return r;
+ }
+ if(!strcmp(argv[0], "rcon-get"))
+ {
+ int r = 0;
+ struct rcon_host_connarg *connarg = rcon_host_getconnarg();
+ if(connarg == NULL)
+ {
+ dprintf(out, _("Rcon is disabled.\n"));
+ }
+ else
+ {
+ dprintf(out, _("Host:\t%s\nPort:\t%s\n"), connarg->host, connarg->port);
+ }
+ return r;
+ }
+ if(!strcmp(argv[0], "rcon-set"))
+ {
+ int r = 0;
+ bool disable = false;
+ if(argc != 4)
+ {
+ if(argc == 2 && !strcmp("disable", argv[1]))
+ {
+ disable = true;
+ }
+ else
+ {
+ dprintf(out, _("Usage: rcon-set <host> <port> <password>\n"));
+ dprintf(out, _("Usage: rcon-set disable\n"));
+ return 64;
+ }
+ }
+ // Always allocate a new one to make sure it is atomic.
+ struct rcon_host_connarg *newargs = NULL;
+ if(!disable)
+ {
+ newargs = malloc(sizeof(struct rcon_host_connarg));
+ if(newargs == NULL)
+ {
+ r = errno;
+ dprintf(out, _("Cannot allocate memory: %d.\n"), r);
+ return r;
+ }
+ newargs->host = NULL;
+ newargs->port = NULL;
+ newargs->password = NULL;
+ int size = 0;
+ size = strlen(argv[1]) + 1;
+ newargs->host = calloc(size, sizeof(char));
+ if(newargs->host == NULL)
+ {
+ r = errno;
+ dprintf(out, _("Cannot allocate memory: %d\n"), r);
+ rcon_host_connarg_free(newargs);
+ return r;
+ }
+ memcpy(newargs->host, argv[1], size);
+
+ size = strlen(argv[2]) + 1;
+ newargs->port = calloc(size, sizeof(char));
+ if(newargs->port == NULL)
+ {
+ r = errno;
+ dprintf(out, _("Cannot allocate memory: %d\n"), r);
+ rcon_host_connarg_free(newargs);
+ return r;
+ }
+ memcpy(newargs->port, argv[2], size);
+
+ size = strlen(argv[3]) + 1;
+ newargs->password = calloc(size, sizeof(char));
+ if(newargs->password == NULL)
+ {
+ r = errno;
+ dprintf(out, _("Cannot allocate memory: %d\n"), r);
+ rcon_host_connarg_free(newargs);
+ return r;
+ }
+ memcpy(newargs->password, argv[3], size);
+ }
+
+ struct rcon_host_connarg *connarg = rcon_host_getconnarg();
+ rcon_host_setconnarg(newargs);
+ if(connarg != NULL) rcon_host_connarg_free(connarg);
+ dprintf(out, _("Ongoing requests will not be cancelled. Existing connections will be updated when plugins make requests.\n"));
+ return r;
+ }
+ dprintf(out, "Unexpected action: '%s'\n", argv[0]);
+ return 64;
+}
+
+static void *main_ctlsocket(void *arg)
+{
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ thread_set_name("ctl-socket");
+ int r = 0;
+ char buf[1025];
+ while(true)
+ {
+ const int accept_fd = accept(ctl_fd, NULL, NULL);
+ if(accept_fd == -1)
+ {
+ r = errno;
+ strerror_r(r, buf, 128);
+ fprintf(stderr, _("Cannot accept control connection: %s\n"), buf);
+ goto cleanup;
+ }
+ const ssize_t size = read(accept_fd, buf, 1024);
+ buf[size] = '\0';
+
+ char *pch = NULL;
+ char **argv = calloc(1, sizeof(char*));
+ if(argv == NULL)
+ {
+ dprintf(accept_fd, _("Cannot allocate memory: %d.\n%d"), errno, errno);
+ close(accept_fd);
+ continue;
+ }
+ int argc = 0;
+ pch = strtok(buf, " ");
+ bool fail = false;
+ while(pch != NULL)
+ {
+ if(pch[strlen(pch) - 1] == '\n') pch[strlen(pch) - 1] = '\0';
+ argc ++;
+ char **argv_ext = realloc(argv, argc * sizeof(char*));
+ if(argv_ext == NULL)
+ {
+ dprintf(accept_fd, _("Cannot allocate memory: %d.\n%d"), errno, errno);
+ free(argv);
+ fail = true;
+ close(accept_fd);
+ break;
+ }
+ argv = argv_ext;
+ argv[argc - 1] = pch;
+ pch = strtok(NULL, " ");
+ }
+ if(fail) continue;
+ int resp = main_handle_cmd(accept_fd, argc, argv);
+ dprintf(accept_fd, "%d", resp);
+ free(argv);
+ close(accept_fd);
+ }
+
+ goto cleanup;
+cleanup:
+ pthread_exit(NULL);
+ return NULL;
+}
+
+static int setup_sem()
+{
+ int r = sem_init(&exit_sem, 0, 0);
+ if(r)
+ {
+ fprintf(stderr, "sem_init(): %s\n", strerror(errno));
+ goto cleanup;
+ }
+ goto cleanup;
+cleanup:
+ return r;
+}
+
+static int setup_sock()
+{
+ int r = 0;
+ ctl_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if(ctl_fd == -1)
+ {
+ r = errno;
+ fprintf(stderr, _("Cannot create control socket: %s\n"), strerror(r));
+ goto cleanup;
+ }
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, CONTROL_SOCKET_PATH, sizeof(addr.sun_path) - 1);
+ int unlink_r = unlink(CONTROL_SOCKET_PATH);
+ if(unlink_r && errno != ENOENT)
+ {
+ r = errno;
+ fprintf(stderr, _("unlink(): %s\n"), strerror(r));
+ goto cleanup;
+ }
+ r = bind(ctl_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un));
+ if(r)
+ {
+ r = errno;
+ fprintf(stderr, _("Cannot bind to the control socket: %s\n"), strerror(r));
+ goto cleanup;
+ }
+ r = listen(ctl_fd, 5);
+ if(r)
+ {
+ r = errno;
+ fprintf(stderr, _("Cannot listen to the control socket: %s\n"), strerror(r));
+ goto cleanup;
+ }
+ goto cleanup;
+cleanup:
+ return r;
+}
+
+int setup_sigmask(sigset_t *set)
+{
+ sigemptyset(set);
+ sigaddset(set, SIGPIPE);
+ sigaddset(set, SIGTERM);
+ sigaddset(set, SIGINT);
+ int r = pthread_sigmask(SIG_BLOCK, set, NULL);
+ if(r)
+ {
+ fprintf(stderr, _("pthread_sigmask(): %d\n"), r);
+ goto cleanup;
+ }
+ goto cleanup;
+cleanup:
+ return r;
+}
+
+static int setup_thread(pthread_t *thread, void *start_routine, void *arg)
+{
+ int r = pthread_create(thread, NULL, start_routine, arg);
+ if(r)
+ {
+ fprintf(stderr, _("Cannot setup thread: %d\n"), r);
+ goto cleanup;
+ }
+ goto cleanup;
+cleanup:
+ return r;
+}
+
+static int destroy_thread(pthread_t thread)
+{
+ int r = 0;
+ r = pthread_cancel(thread);
+ if(r && r != ESRCH)
+ {
+ fprintf(stderr, "pthread_cancel: %d.\n", r);
+ }
+ else
+ {
+ r = 0;
+ }
+ r = pthread_join(thread, NULL);
+ if(r && r != ESRCH)
+ {
+ fprintf(stderr, "pthread_join: %d.\n", r);
+ }
+ else
+ {
+ r = 0;
+ }
+ return r;
+}
+
+static int main_daemon(int argc, char **argv)
+{
+ DEBUG("main.c#main_daemon: main_daemon()\n");
+ bool sem_setup = false,
+ mcin_setup = false,
+ rcon_setup = false,
+ reg_setup = false,
+ sock_setup = false,
+ autoload_setup = false,
+ sigmask_setup = false,
+ thpool_setup = false,
+ sighandler_setup = false,
+ loop_setup = false,
+ socket_thread_setup = false;
+
+ int r = 0;
+
+ DEBUG("main.c#main_daemon: Setup semaphore...\n");
+ r = setup_sem();
+ if(r) goto cleanup;
+ else sem_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup regular expressions...\n");
+ r = mcin_init();
+ if(r) goto cleanup;
+ else mcin_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup rcon host...\n");
+ r = rcon_host_init();
+ if(r) goto cleanup;
+ else rcon_setup = true;
+
+ DEBUG("main.c#main_daemon: Loading pre-defined rcon arguments from environment variables.\n");
+ const char *connarg_env_host = getenv("RCON_HOST");
+ const char *connarg_env_port = getenv("RCON_PORT");
+ const char *connarg_env_password = getenv("RCON_PASSWORD");
+ if(!(connarg_env_host == NULL && connarg_env_port == NULL && connarg_env_password == NULL))
+ {
+ if(connarg_env_host != NULL && connarg_env_port != NULL && connarg_env_password != NULL)
+ {
+ struct rcon_host_connarg *connarg = malloc(sizeof(struct rcon_host_connarg));
+ int size = 0;
+ size = strlen(connarg_env_host) + 1;
+ connarg->host = calloc(size, sizeof(char));
+ memcpy(connarg->host, connarg_env_host, size);
+ size = strlen(connarg_env_port) + 1;
+ connarg->port = calloc(size, sizeof(char));
+ memcpy(connarg->port, connarg_env_port, size);
+ size = strlen(connarg_env_password) + 1;
+ connarg->password = calloc(size, sizeof(char));
+ memcpy(connarg->password, connarg_env_password, size);
+ rcon_host_setconnarg(connarg);
+ DEBUGF("main.c#main_daemon: Loaded rcon arguments from environment variables:\nHost:\t%s\nPort:\t%s\nPassword:\t%s\n",
+ connarg->host,
+ connarg->port,
+ connarg->password);
+ }
+ else
+ {
+ fprintf(stderr, _("Cannot load rcon settings: RCON_HOST, RCON_PORT and RCON_PASSWORD must all present.\n"));
+ r = 64;
+ goto cleanup;
+ }
+ }
+
+ DEBUG("main.c#main_daemon: Setup plugin registry...\n");
+ r = plugin_registry_init();
+ if(r) goto cleanup;
+ else reg_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup control socket...\n");
+ r = setup_sock();
+ if(r) goto cleanup;
+ else sock_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup signal masks...\n");
+ sigset_t set;
+ r = setup_sigmask(&set);
+ if(r) goto cleanup;
+ else sigmask_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup thread pool...\n");
+ int thpool_threads = 1;
+ if(getenv("THPOOL_THREADS") != NULL)
+ {
+ char *endptr;
+ uintmax_t num = strtoumax(getenv("THPOOL_THREADS"), &endptr, 10);
+ if(strcmp(endptr, "") || (num == UINTMAX_MAX && errno == ERANGE) || num > INT_MAX || num <= 0)
+ {
+ fprintf(stderr, _("Invalid THPOOL_THREADS value.\n"));
+ r = 64;
+ goto cleanup;
+ }
+ thpool_threads = (int)num;
+ }
+ DEBUGF("main.c#main_daemon: Using '%d' threads.\n", thpool_threads);
+ thpool = thpool_init(thpool_threads);
+ thpool_setup = true;
+
+ if(argc > 1)
+ {
+ DEBUG("main.c#main_daemon: Autoloading plugins at startup...\n");
+ r = autoload(argv[1]);
+ if(r) goto cleanup;
+ else autoload_setup = true;
+ }
+
+ DEBUG("main.c#main_daemon: Setup signal handler thread...\n");
+ pthread_t thread_sighandler;
+ r = setup_thread(&thread_sighandler, &main_sighandler, &set);
+ if(r) goto cleanup;
+ else sighandler_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup main loop thread...\n");
+ pthread_t thread_loop;
+ r = setup_thread(&thread_loop, &main_loop, NULL);
+ if(r) goto cleanup;
+ else loop_setup = true;
+
+ DEBUG("main.c#main_daemon: Setup control socket thread...\n");
+ pthread_t thread_ctlsocket;
+ r = setup_thread(&thread_ctlsocket, &main_ctlsocket, NULL);
+ if(r) goto cleanup;
+ else socket_thread_setup = true;
+
+ // Setup done. Enter blocking.
+
+ DEBUG("main.c#main_daemon: Main: Setup done. Waiting.\n");
+ r = sem_wait(&exit_sem);
+ if(r)
+ {
+ fprintf(stderr, "sem_wait(): %d.\n", r);
+ goto cleanup;
+ }
+
+ goto cleanup;
+cleanup:
+ DEBUG("main.c#main_daemon: Cleanup semaphore...\n");
+ if(sem_setup) sem_destroy(&exit_sem);
+ DEBUG("main.c#main_daemon: Cleanup regular expressions...\n");
+ if(mcin_setup) mcin_free();
+ DEBUG("main.c#main_daemon: Cleanup control socket thread...\n");
+ if(socket_thread_setup) destroy_thread(thread_ctlsocket);
+ DEBUG("main.c#main_daemon: Cleanup control socket...\n");
+ if(sock_setup)
+ {
+ close(ctl_fd);
+ unlink(CONTROL_SOCKET_PATH);
+ }
+ DEBUG("main.c#main_daemon: Cleanup loop thread...\n");
+ if(loop_setup) destroy_thread(thread_loop);
+ DEBUG("main.c#main_daemon: Cleanup signal handler thread...\n");
+ if(sighandler_setup) destroy_thread(thread_sighandler);
+ // Always perform thpool_wait after the main loop thread is paused or stopped.
+ DEBUG("main.c#main_daemon: Cleanup thread pool...\n");
+ if(thpool_setup)
+ {
+ thpool_wait(thpool);
+ thpool_destroy(thpool);
+ }
+ if(autoload_setup) {} // Plugins are always unloaded.
+ DEBUG("main.c#main_daemon: Unloading plugins...\n");
+ const int size = plugin_size();
+ int *plugins = calloc(size, sizeof(int));
+ for(int i = 0; i < size; i ++)
+ plugins[i] = plugin_get_by_index(i)->id;
+ for(int i = 0; i < size; i ++)
+ {
+ int unload_r = plugin_registry_unload(2, plugins[i]);
+ if(unload_r)
+ {
+ fprintf(stderr, _("Unload: %d\n"), unload_r);
+ }
+ }
+ free(plugins);
+ DEBUG("main.c#main_daemon: Cleanup rcon host...\n");
+ struct rcon_host_connarg *connarg = rcon_host_getconnarg();
+ if(connarg != NULL) rcon_host_connarg_free(connarg);
+ if(rcon_setup) { rcon_host_free(); }
+ DEBUG("main.c#main_daemon: Cleanup plugin registry...\n");
+ if(reg_setup) plugin_registry_free();
+ // Make the compiler happy: we don't need to do any cleanup for these items.
+ if(sigmask_setup) {}
+ return r;
+}
+
+static int main_ctl(int argc, char **argv)
+{
+ int r = 0;
+ struct sockaddr_un addr;
+ const int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if(fd == -1) {
+ r = errno;
+ fprintf(stderr, "%s\n", strerror(r));
+ goto cleanup;
+ }
+
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, CONTROL_SOCKET_PATH, sizeof(addr.sun_path) - 1);
+ r = connect(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un));
+ if(r == -1)
+ {
+ r = errno;
+ fprintf(stderr, "%s\n", strerror(r));
+ goto cleanup;
+ }
+ unsigned int length = 0;
+ for(int i = 1; i < argc; i ++)
+ {
+ length += strlen(argv[i]);
+ if(i != argc - 1) length ++;
+ }
+ char *dat = calloc(length, sizeof(char));
+ strcpy(dat, "");
+ for(int i = 1; i < argc; i ++)
+ {
+ strcat(dat, argv[i]);
+ if(i != argc - 1) strcat(dat, " ");
+ }
+ dprintf(fd, "%s", dat);
+
+ ssize_t num_read;
+ int arr_size = 1025;
+ char *buffer = calloc(1025, sizeof(char));
+ strcpy(buffer, "");
+ char buf[1025];
+ while((num_read = read(fd, buf, 1024)) > 0)
+ {
+ buf[num_read] = '\0';
+ arr_size += 1024;
+ buffer = realloc(buffer, arr_size * sizeof(char));
+ strcat(buffer, buf);
+ }
+ const char *last_newline = strrchr(buffer, '\n');
+ char *endptr;
+ // Try parse if the whole thing is an exit code.
+ if(last_newline == NULL)
+ {
+ intmax_t num = strtoimax(buffer, &endptr, 10);
+ if(!strcmp(endptr, "") && !(num == INTMAX_MAX && errno == ERANGE) && num <= INT_MAX && num >= INT_MIN)
+ {
+ r = (int)num;
+ buffer[0] = '\0';
+ }
+ }
+ else
+ {
+ intmax_t num = strtoimax(&buffer[(int)(last_newline - buffer) + 1], &endptr, 10);
+ if(strcmp(endptr, "") || (num == INTMAX_MAX && errno == ERANGE) || num > INT_MAX || num < INT_MIN)
+ {
+ r = 255;
+ }
+ else
+ {
+ r = (int)num;
+ buffer[(int)(last_newline - buffer) + 1] = '\0';
+ }
+ }
+ printf("%s", buffer);
+ free(buffer);
+
+cleanup:
+ if(fd != -1)
+ close(fd);
+ return r;
+}
+
+int main(int argc, char **argv)
+{
+ bool invoke_as_ctl = false;
+ if(argc > 0)
+ {
+ char *path = strrchr(argv[0], '/');
+ if(path != NULL)
+ {
+ const char *substr = &argv[0][(int)(path - argv[0]) + 1];
+ invoke_as_ctl = !strcmp(substr, "extmcctl");
+ }
+ }
+ if(invoke_as_ctl)
+ {
+ return main_ctl(argc, argv);
+ }
+ else
+ {
+ return main_daemon(argc, argv);
+ }
+}
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 <regex.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+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, &reg_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, &reg_master);
+ if(r) return r;
+ r = mcin_compile_regex("(.*) joined the game", REG_EXTENDED, &reg_player_join);
+ if(r) return r;
+ r = mcin_compile_regex("(.*) lost connection: (.*)", REG_EXTENDED, &reg_player_leave);
+ if(r) return r;
+ r = mcin_compile_regex("(.*) has made the advancement \\[(.*)\\]", REG_EXTENDED, &reg_player_achievement);
+ if(r) return r;
+ r = mcin_compile_regex("(.*) has completed the challenge \\[(.*)\\]", REG_EXTENDED, &reg_player_challenge);
+ if(r) return r;
+ r = mcin_compile_regex("(.*) has reached the goal \\[(.*)\\]", REG_EXTENDED, &reg_player_goal);
+ if(r) return r;
+ r = mcin_compile_regex("<(.*)> (.*)", REG_EXTENDED, &reg_player_say);
+ if(r) return r;
+
+ r = mcin_compile_regex("Stopping server", REG_EXTENDED, &reg_server_stopping);
+ if(r) return r;
+ r = mcin_compile_regex("Starting minecraft server version (.*)", REG_EXTENDED, &reg_server_starting);
+ if(r) return r;
+ r = mcin_compile_regex("Done \\((.*s)\\)! For help, type \"help\"", REG_EXTENDED, &reg_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(&reg_master);
+ regfree(&reg_player_join);
+ regfree(&reg_player_leave);
+ regfree(&reg_player_say);
+ regfree(&reg_player_achievement);
+ regfree(&reg_player_challenge);
+ regfree(&reg_player_goal);
+ for(int i = 0; i < 64; i ++)
+ regfree(&reg_player_die[i]);
+ regfree(&reg_server_stopping);
+ regfree(&reg_server_starting);
+ regfree(&reg_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(&reg, 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(&reg_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 <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+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 <tiiffi at gmail>.
+ * https://github.com/Tiiffi/mcrcon/tree/b02201d689b3032bc681b28f175fd3d83d167293
+ */
+
+#include "net.h"
+#include "common.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+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 <tiiffi at gmail>.
+ * 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 <stdint.h>
+
+/* 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 <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+#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 <yuuta@yuuta.moe>, April 2021.
+ */
+
+#include "plugins.h"
+#include "common.h"
+#include "plugin_registry.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <dlfcn.h>
+#include <stdbool.h>
+#include <string.h>
+
+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 <yuuta@yuuta.moe>, 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 <tiiffi at gmail>.
+ * https://github.com/Tiiffi/mcrcon/tree/b02201d689b3032bc681b28f175fd3d83d167293
+ */
+
+#include "rcon.h"
+#include "common.h"
+
+#include <sys/socket.h>
+#include <sysexits.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+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 <tiiffi at gmail>.
+ * 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 <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "common.h"
+#include <errno.h>
+#include <stdbool.h>
+
+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 <yuuta@yuuta.moe>, 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 <yuuta@yuuta.moe>, April 2021.
+ */
+
+#include "../plugin/plugin.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+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 <unistd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <errno.h>
+#include <time.h>
+
+#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; n<num_threads; n++){
+ thread_init(thpool_p, &thpool_p->threads[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 <sys/prctl.h>
+#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