From a2fe22f0de3eaa8636247f51d323b49ced45aac5 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Mon, 13 Apr 2020 23:24:31 +0200 Subject: [PATCH 01/24] Add .vimrc to repository --- .vimrc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .vimrc diff --git a/.vimrc b/.vimrc new file mode 100644 index 00000000..cd72c889 --- /dev/null +++ b/.vimrc @@ -0,0 +1,5 @@ +set tabstop=4 +set shiftwidth=4 +set softtabstop=4 +set expandtab +set nosmarttab From a1f5b77e9f859b193e0fe557982201700ee08ace Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Mon, 13 Apr 2020 23:24:31 +0200 Subject: [PATCH 02/24] mxqd: Ignore failure to scan JOB_TMPDIR_MNTDIR Avoid unneeded warning when scanning JOB_TMPDIR_MNTDIR for possible leftover job mounts. The parent directory of JOB_TMPDIR_MNTDIR is created when the first job with the --tmpdir feature is started. So it won't exist in a new server. This cleanup path is only used in the exceptional case that the usual cleanup path (via job_has_finished) didn't succeed, e.g. when mxqd was killed. The cleanup is not essential for the currently running server. --- mxqd.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mxqd.c b/mxqd.c index aef3ab0d..c1106aba 100644 --- a/mxqd.c +++ b/mxqd.c @@ -2308,10 +2308,8 @@ static void server_umount_stale_job_mountdirs(struct mxq_server *server) { int i; entries=scandir(MXQ_JOB_TMPDIR_MNTDIR,&namelist,&job_mountdirs_is_valid_name,&alphasort); - if (entries<0) { - mx_log_err("scandir %s: %m", MXQ_JOB_TMPDIR_MNTDIR); + if (entries<=0) return; - } for (i=0;id_name, &job_id)) { if (server_get_job_list_by_job_id(server, job_id) == NULL) { From f0f5ddd760bf3b200685c70bc63009cddedbd123 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Mon, 13 Apr 2020 23:24:32 +0200 Subject: [PATCH 03/24] mxq.h: Add attribute `unused` Add function attribute ((unusged)) to avoid a compiler warning when the static inline function defined in the header file is not used by the compilation unit. --- mxq.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mxq.h b/mxq.h index 418331d2..39c460db 100644 --- a/mxq.h +++ b/mxq.h @@ -60,7 +60,7 @@ # define MXQ_MAX_PENDING_JOBS_PER_GROUP 10000 #endif -static void mxq_print_generic_version(void) +__attribute__ ((unused)) static void mxq_print_generic_version(void) { printf( "%s - " MXQ_VERSIONFULL "\n" From 459c5a81ad4ba15415258abaec7ac0eee5cf385e Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Mon, 13 Apr 2020 23:24:32 +0200 Subject: [PATCH 04/24] sql: Add field group_disabled_servers --- mysql/create_tables.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql/create_tables.sql b/mysql/create_tables.sql index c3c0f9d2..d7a1850c 100644 --- a/mysql/create_tables.sql +++ b/mysql/create_tables.sql @@ -4,6 +4,7 @@ CREATE TABLE IF NOT EXISTS mxq_group ( group_status INT1 UNSIGNED NOT NULL DEFAULT 0, group_flags INT8 UNSIGNED NOT NULL DEFAULT 0, group_priority INT2 UNSIGNED NOT NULL DEFAULT 127, + group_disabled_servers VARCHAR(1000) NOT NULL DEFAULT '', user_uid INT4 UNSIGNED NOT NULL, user_name VARCHAR(256) NOT NULL, From c9f13bd80aa6303c2f6bdf6de6810b5b205ce932 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Mon, 13 Apr 2020 23:24:32 +0200 Subject: [PATCH 05/24] mxq_group.h: Add group_disabled_servers --- mxq_group.h | 1 + 1 file changed, 1 insertion(+) diff --git a/mxq_group.h b/mxq_group.h index 762b04ae..82098dad 100644 --- a/mxq_group.h +++ b/mxq_group.h @@ -14,6 +14,7 @@ struct mxq_group { uint8_t group_status; uint64_t group_flags; uint16_t group_priority; + char * group_disabled_servers; uint32_t user_uid; char * user_name; From 3961297871a9f557185fec0e617f1ddc976cee7d Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Mon, 13 Apr 2020 23:24:33 +0200 Subject: [PATCH 06/24] mxq_group.c: Add group_disabled_servers --- mxq_group.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mxq_group.c b/mxq_group.c index 34f8c7ac..5b4a7e10 100644 --- a/mxq_group.c +++ b/mxq_group.c @@ -12,13 +12,14 @@ #include "mx_util.h" #include "mx_mysql.h" -#define GROUP_FIELDS_CNT 33 +#define GROUP_FIELDS_CNT 34 #define GROUP_FIELDS \ " group_id," \ " group_name," \ " group_status," \ " group_flags," \ " group_priority," \ + " group_disabled_servers," \ " user_uid," \ " user_name," \ " user_gid," \ @@ -62,6 +63,7 @@ static int bind_result_group_fields(struct mx_mysql_bind *result, struct mxq_gro res += mx_mysql_bind_var(result, idx++, uint8, &(g->group_status)); res += mx_mysql_bind_var(result, idx++, uint64, &(g->group_flags)); res += mx_mysql_bind_var(result, idx++, uint16, &(g->group_priority)); + res += mx_mysql_bind_var(result, idx++, string, &(g->group_disabled_servers)); res += mx_mysql_bind_var(result, idx++, uint32, &(g->user_uid)); res += mx_mysql_bind_var(result, idx++, string, &(g->user_name)); @@ -105,6 +107,7 @@ static int bind_result_group_fields(struct mx_mysql_bind *result, struct mxq_gro void mxq_group_free_content(struct mxq_group *g) { + mx_free_null(g->group_disabled_servers); mx_free_null(g->group_name); mx_free_null(g->user_name); mx_free_null(g->user_group); From 106d6587be0657952fd425a65026561297a758f9 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Mon, 13 Apr 2020 23:24:33 +0200 Subject: [PATCH 07/24] web: Add field group_disabled_servers --- web/pages/mxq/mxq.in | 1 + 1 file changed, 1 insertion(+) diff --git a/web/pages/mxq/mxq.in b/web/pages/mxq/mxq.in index 8a7bca74..914abefb 100755 --- a/web/pages/mxq/mxq.in +++ b/web/pages/mxq/mxq.in @@ -392,6 +392,7 @@ group_name : $group_name group_status : $group_status_text group_flags : $o->{group_flags} group_priority : $o->{group_priority} +group_disabled_servers : $o->{group_disabled_servers} user_uid : $o->{user_uid} user_name : $o->{user_name} From 0a335e5a8dd773e5ed41869e007b425263825ca7 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Mon, 13 Apr 2020 23:24:34 +0200 Subject: [PATCH 08/24] web: Add whitespace to align output --- web/pages/mxq/mxq.in | 50 ++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/web/pages/mxq/mxq.in b/web/pages/mxq/mxq.in index 914abefb..35fc2dc5 100755 --- a/web/pages/mxq/mxq.in +++ b/web/pages/mxq/mxq.in @@ -388,39 +388,39 @@ sub group_details_raw { return <<"__EOF__"
-group_name     : $group_name
-group_status   : $group_status_text
-group_flags    : $o->{group_flags}
-group_priority : $o->{group_priority}
+group_name             : $group_name
+group_status           : $group_status_text
+group_flags            : $o->{group_flags}
+group_priority         : $o->{group_priority}
 group_disabled_servers : $o->{group_disabled_servers}
 
-user_uid       : $o->{user_uid}
-user_name      : $o->{user_name}
-user_gid       : $o->{user_gid}
-user_group     : $o->{user_group}
+user_uid               : $o->{user_uid}
+user_name              : $o->{user_name}
+user_gid               : $o->{user_gid}
+user_group             : $o->{user_group}
 
-job_command     : $job_command
-job_threads     : $o->{job_threads}
-job_memory      : $o->{job_memory} MiB
-job_time        : $o->{job_time} minutes
-job_tmpdir_size : $job_tmpdir_size
+job_command            : $job_command
+job_threads            : $o->{job_threads}
+job_memory             : $o->{job_memory} MiB
+job_time               : $o->{job_time} minutes
+job_tmpdir_size        : $job_tmpdir_size
 
-job_max_per_node      : $o->{job_max_per_node}
+job_max_per_node       : $o->{job_max_per_node}
 
-group_jobs            : $o->{group_jobs}
-group_jobs_inq        : $o->{group_jobs_inq}
-group_jobs_running    : $o->{group_jobs_running}
-group_jobs_finished   : $o->{group_jobs_finished}
-group_jobs_failed     : $o->{group_jobs_failed}
-group_jobs_cancelled  : $o->{group_jobs_cancelled}
-group_jobs_unknown    : $o->{group_jobs_unknown}
-group_jobs_restarted  : $o->{group_jobs_restarted}
+group_jobs             : $o->{group_jobs}
+group_jobs_inq         : $o->{group_jobs_inq}
+group_jobs_running     : $o->{group_jobs_running}
+group_jobs_finished    : $o->{group_jobs_finished}
+group_jobs_failed      : $o->{group_jobs_failed}
+group_jobs_cancelled   : $o->{group_jobs_cancelled}
+group_jobs_unknown     : $o->{group_jobs_unknown}
+group_jobs_restarted   : $o->{group_jobs_restarted}
 
-group_slots_running:  : $o->{group_slots_running}
+group_slots_running    : $o->{group_slots_running}
 
-group_mtime           : $o->{group_mtime}
+group_mtime            : $o->{group_mtime}
 
-group_date_end        : $o->{group_date_end}
+group_date_end         : $o->{group_date_end}
 
 stats_max_sumrss                 : $o->{stats_max_sumrss} kiB
 stats_max_maxrss                 : $o->{stats_max_maxrss}

From 1b6bf14e9ff4b4a89330addb8d7b13b120fd780f Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Mon, 13 Apr 2020 23:24:34 +0200
Subject: [PATCH 09/24] Add xmalloc.h

Add header file for the quasi-standard [1] xmalloc call.

Include x-version of strndup. Other functions (e.g. xrealloc) can be
added as needed.

The current approach implemented in mx_util is to wait and retry on
ENOMEM malloc failure. However, this overhead doesn't seem to be
justified, because it is difficult to imagine a case, where a malloc
would fail with ENOMEM at one time and a retry would succeed.

[1] https://www.gnu.org/software/libc/manual/html_node/Malloc-Examples.html
---
 xmalloc.h | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)
 create mode 100644 xmalloc.h

diff --git a/xmalloc.h b/xmalloc.h
new file mode 100644
index 00000000..40820135
--- /dev/null
+++ b/xmalloc.h
@@ -0,0 +1,26 @@
+#ifndef _XMALLOC_H
+#define _XMALLOC_H 1
+
+#include 
+#include 
+
+__attribute__ ((noreturn, unused)) static void out_of_memory() {
+    fprintf(stderr,"out of memory\n");
+    abort();
+}
+
+__attribute__ ((unused)) static void *xmalloc(size_t size) {
+    void *ptr = malloc(size);
+    if (__builtin_expect(ptr == NULL, 0))
+        out_of_memory();
+    return(ptr);
+}
+
+__attribute__ ((unused)) static char *xstrndup(const char *s, size_t n) {
+    char *ptr = strndup(s, n);
+    if (__builtin_expect(ptr == NULL, 0))
+        out_of_memory();
+    return(ptr);
+}
+
+#endif

From 2706f39011946639f6816658bdb526ae136ec8ac Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Mon, 13 Apr 2020 23:24:34 +0200
Subject: [PATCH 10/24] Add keywordset module

Add a utility module which can store a set of keywords. The set can be
created and update from a string and can be serialized into a string.

The string representation is a space-separated list of keywordis. Strings
created by this utility contain the keywords stored in the set sorted in
lexical order separated by a single space character.

Input strings used to create or update a keyword set contain keywords
separated by whitespace. Words from the string are added to the set
unless a word starts with '-' in which case it will be removed from the

Usage example:

    struct keywordset *kws = keywordset_new("xxx yyy")
    keywordset_update(kws, "-yyy aaaa")

    char *s = keywordset_get(kws):   // s now "aaaaa xxx"
    free(s);                         // caller must free()

    if (keywordset_ismember(kws,"xxx")) ... // true

    keywordset_free(kws);
---
 keywordset.c      | 133 ++++++++++++++++++++++++++++++++++++++++++++++
 keywordset.h      |  10 ++++
 test_keywordset.c |  39 ++++++++++++++
 3 files changed, 182 insertions(+)
 create mode 100644 keywordset.c
 create mode 100644 keywordset.h
 create mode 100644 test_keywordset.c

diff --git a/keywordset.c b/keywordset.c
new file mode 100644
index 00000000..589acf65
--- /dev/null
+++ b/keywordset.c
@@ -0,0 +1,133 @@
+#include "keywordset.h"
+#include 
+#include 
+#include 
+#include 
+#include "xmalloc.h"
+
+#define KEYWORDSET_INITIAL_SLOTS (4-2)
+
+struct keywordset {
+    int nr_slots;
+    int	 used;
+    char **names;
+};
+
+static int find_name(struct keywordset *kws, char *name, size_t len) {
+    int i;
+    int j;
+    for ( i = 0; i < kws->used ; i++ ) {
+        j = 0;
+        while(1) {
+            if (kws->names[i][j] == 0)
+                break;
+            if (kws->names[i][j] != name[j])
+                break;
+            j++;
+            if (j==len)
+                return i;
+        }
+    }
+    return -1;
+}
+
+static void expand(struct keywordset *kws) {
+    int new_slots=(kws->nr_slots+2)*2-2;
+    kws->names=realloc(kws->names,new_slots*sizeof(*kws->names));
+    kws->nr_slots=new_slots;
+}
+
+static void add_name(struct keywordset *kws, char *name, size_t len) {
+    int i=find_name(kws, name, len);
+    if (i>=0) {
+        free(kws->names[i]);
+        kws->names[i] = xstrndup(name, len);
+    } else {
+        if (kws->used == kws->nr_slots)
+            expand(kws);
+        kws->names[kws->used++] = xstrndup(name, len);
+    }
+}
+
+static void remove_name(struct keywordset *kws, char *name, size_t len) {
+    int i=find_name(kws, name, len);
+    if (i>=0) {
+        free(kws->names[i]);
+        memmove(&(kws->names[i]), &(kws->names[i+1]), (kws->used-i-1)*sizeof(*kws->names));
+        kws->used--;
+    }
+}
+
+void keywordset_update(struct keywordset *kws, char *input) {
+    char *c=input;
+    char *name_start;
+    int add;
+    while (*c) {
+        while (*c && isspace(*c))
+            c++;
+        if (*c == '-') {
+            add = 0;
+            c++;
+        } else
+            add = 1;
+        if (*c) {
+            name_start=c++;
+            while (*c && !isspace(*c))
+                c++;
+            if (add)
+                add_name(kws, name_start, c-name_start);
+            else
+                remove_name(kws, name_start, c-name_start);
+        }
+    }
+}
+
+struct keywordset *keywordset_new(char *input) {
+    struct keywordset *kws = xmalloc(sizeof(*kws));
+    kws->nr_slots = KEYWORDSET_INITIAL_SLOTS;
+    kws->used = 0;
+    kws->names = xmalloc(KEYWORDSET_INITIAL_SLOTS*sizeof(*kws->names));
+    if (input)
+        keywordset_update(kws, input);
+    return kws;
+}
+
+static int cmp(const void *a, const void *b) {
+    return strcmp(*(char **)a, *(char **)b);
+}
+
+char *keywordset_get(struct keywordset *kws) {
+    char **names=xmalloc(kws->used * sizeof(*names));
+    memcpy(names, kws->names, kws->used * sizeof(*names));
+    qsort(names, kws->used, sizeof(*names), cmp);
+    size_t len = 0;
+    int i;
+    for (i=0; iused; i++) {
+        len += strlen(names[i]);
+    }
+    size_t outlen = len + (kws->used >= 2 ? kws->used-1 : 0);
+    char *out=xmalloc(outlen + 1 );
+    char *p=out;
+    for ( i = 0 ; i < kws->used ; i++) {
+        p=stpcpy(p, names[i]);
+        *p++ = ' ';
+    }
+    out[outlen] = 0;
+    free(names);
+    return(out);
+}
+
+int keywordset_ismember(struct keywordset *kws, char *name) {
+    if (find_name(kws, name, strlen(name)) >= 0)
+        return 1;
+    else
+        return 0;
+}
+
+void keywordset_free(struct keywordset *kws) {
+    int i;
+    for ( i = 0 ; i < kws->used ; i++)
+        free(kws->names[i]);
+    free(kws->names);
+    free(kws);
+}
diff --git a/keywordset.h b/keywordset.h
new file mode 100644
index 00000000..f9964739
--- /dev/null
+++ b/keywordset.h
@@ -0,0 +1,10 @@
+#ifndef _KEYWORDSET_H
+#define _KEYWORDSET_H
+
+struct keywordset *keywordset_new(char *input);
+void keywordset_update(struct keywordset *kws, char *input);
+char *keywordset_get(struct keywordset *kws);
+int keywordset_ismember(struct keywordset *kwd, char *name);
+void keywordset_free(struct keywordset *kws);
+
+#endif
diff --git a/test_keywordset.c b/test_keywordset.c
new file mode 100644
index 00000000..a7b7ded3
--- /dev/null
+++ b/test_keywordset.c
@@ -0,0 +1,39 @@
+#include 
+#include 
+#include "keywordset.h"
+#include 
+
+int main() {
+    struct keywordset *kws = keywordset_new("avaritia theinternet");
+    char *s;
+
+    keywordset_update(kws, "-avaritia deadbird null void xx XX");
+
+    s=keywordset_get(kws);
+    assert(strcmp(s, "XX deadbird null theinternet void xx")==0);
+    free(s);
+
+    assert(keywordset_ismember(kws, "avaritia") == 0);
+    assert(keywordset_ismember(kws, "deadpool") == 0);
+    assert(keywordset_ismember(kws, "deadbird") == 1);
+    assert(keywordset_ismember(kws, "theinternet") == 1);
+    assert(keywordset_ismember(kws, "DEADBIRD") == 0);
+    assert(keywordset_ismember(kws, "xx") == 1);
+    assert(keywordset_ismember(kws, "XX") == 1);
+
+    keywordset_free(kws);
+
+    kws=keywordset_new(NULL);
+    s=keywordset_get(kws);
+    assert(strcmp(s, "")==0);
+    free(s);
+    keywordset_free(kws);
+
+    kws=keywordset_new("");
+    s=keywordset_get(kws);
+    assert(strcmp(s, "")==0);
+    free(s);
+    keywordset_free(kws);
+
+
+}

From 085cb76d0d5e89eafc1a5762e3f7d4fdcf8b72a4 Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Mon, 13 Apr 2020 23:24:35 +0200
Subject: [PATCH 11/24] Add keywordset to build system

---
 .gitignore |  3 +++
 Makefile   | 17 +++++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/.gitignore b/.gitignore
index e9d5b00d..7d3fa655 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,8 @@ test_mxqd_control.o
 mxq_log.o
 mx_mysql.o
 mxqd_control.o
+keywordset.o
+test_keywordset.o
 
 
 mxqsub
@@ -34,6 +36,7 @@ test_mx_util
 test_mx_log
 test_mx_mysq
 test_mxqd_control
+test_keywordset
 
 /web/pages/mxq/mxq
 web/lighttpd.conf
diff --git a/Makefile b/Makefile
index 6e14493d..330bfdfc 100644
--- a/Makefile
+++ b/Makefile
@@ -341,6 +341,10 @@ mxqd_control.h += mxqd.h
 
 mx_getopt.h += mx_getopt.h
 
+### keywordset.h -------------------------------------------------------
+
+keywordset.h += keywordset.h
+
 ########################################################################
 
 ### mx_getopt.o --------------------------------------------------------
@@ -494,6 +498,13 @@ mxqsub.o: CFLAGS += $(CFLAGS_MYSQL)
 
 clean: CLEAN += mxqsub.o
 
+### keywordset.o -----------------------------------------------------
+
+keywordset.o: $(keywordset.h)
+keywordset.o: xmalloc.h
+
+clean: CLEAN += keywordset.o
+
 ########################################################################
 
 ### mxqd ---------------------------------------------------------------
@@ -678,3 +689,9 @@ test_mxqd_control: LDLIBS += $(LDLIBS_MYSQL)
 clean: CLEAN += test_mxqd_control
 
 test: test_mxqd_control
+
+test_keywordset: $(test_keywordset.h)
+test_keywordset: test_keywordset.o
+test_keywordset: keywordset.o
+clean: CLEAN += test_keywordset.o
+test: test_keywordset

From 230988e7e3c1e360bec7972e658d9f6f7160346d Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Mon, 13 Apr 2020 23:24:35 +0200
Subject: [PATCH 12/24] mxqsub: Add option --disabled-servers

Add a new option so that specific servers can be excluded from starting
jobs for this group. E.g.:

    mxqsub --disabled-servers="dontpanic sigusr2" sleep 100
---
 mxqsub.c | 43 +++++++++++++++++++++++++++++++++++--------
 1 file changed, 35 insertions(+), 8 deletions(-)

diff --git a/mxqsub.c b/mxqsub.c
index e9900d11..5f32cb41 100644
--- a/mxqsub.c
+++ b/mxqsub.c
@@ -32,6 +32,7 @@
 #include "mx_util.h"
 #include "mx_getopt.h"
 #include "mx_mysql.h"
+#include "keywordset.h"
 
 #include "mxq.h"
 
@@ -69,9 +70,10 @@ static void print_usage(void)
     "  Scheduling is done based on the resources a job needs and\n"
     "  on the priority given to the job.\n"
     "\n"
-    "  -j, --threads=NUMBER     set number of threads       (default: 1)\n"
-    "  -m, --memory=SIZE        set amount of memory        (default: 2G)\n"
-    "      --tmpdir=SIZE        set size of MXQ_JOB_TMPDIR  (default: 0)\n"
+    "  -j, --threads=NUMBER       set number of threads         (default: 1)\n"
+    "  -m, --memory=SIZE          set amount of memory          (default: 2G)\n"
+    "  --tmpdir=SIZE              set size of MXQ_JOB_TMPDIR    (default: 0)\n"
+    "  --disabled-servers=STRING  set list of disabled servers  (default: '')\n"
     "\n"
     "        [SIZE] may be suffixed with a combination of T, G and M\n"
     "               to specify tebibytes, gibibytes and mebibytes.\n"
@@ -168,6 +170,7 @@ static int load_group_id(struct mx_mysql *mysql, struct mxq_group *g)
                 " AND job_tmpdir_size = ?"
                 " AND job_max_per_node = ?"
                 " AND group_priority = ?"
+                " AND group_disabled_servers = ?"
                 " AND group_status = 0"
                 " AND group_flags & ? = 0 "
             " ORDER BY group_id DESC"
@@ -189,7 +192,8 @@ static int load_group_id(struct mx_mysql *mysql, struct mxq_group *g)
     res += mx_mysql_statement_param_bind(stmt, 9, uint32, &(g->job_tmpdir_size));
     res += mx_mysql_statement_param_bind(stmt, 10, uint16, &(g->job_max_per_node));
     res += mx_mysql_statement_param_bind(stmt, 11, uint16, &(g->group_priority));
-    res += mx_mysql_statement_param_bind(stmt, 12, uint64, &(flags));
+    res += mx_mysql_statement_param_bind(stmt, 12, string, &(g->group_disabled_servers));
+    res += mx_mysql_statement_param_bind(stmt, 13, uint64, &(flags));
     assert(res == 0);
 
     res = mx_mysql_statement_execute(stmt, &num_rows);
@@ -253,6 +257,7 @@ static int load_group_id_by_group_id(struct mx_mysql *mysql, struct mxq_group *g
                 " AND job_tmpdir_size = ?"
                 " AND job_max_per_node = ?"
                 " AND group_priority = ?"
+                " AND group_disabled_servers = ?"
                 " AND group_status = 0"
                 " AND group_id = ?"
                 " AND group_flags & ? = 0 "
@@ -275,8 +280,9 @@ static int load_group_id_by_group_id(struct mx_mysql *mysql, struct mxq_group *g
     res += mx_mysql_statement_param_bind(stmt,  9, uint32, &(g->job_tmpdir_size));
     res += mx_mysql_statement_param_bind(stmt, 10, uint16, &(g->job_max_per_node));
     res += mx_mysql_statement_param_bind(stmt, 11, uint16, &(g->group_priority));
-    res += mx_mysql_statement_param_bind(stmt, 12, uint64, &(g->group_id));
-    res += mx_mysql_statement_param_bind(stmt, 13, uint64, &(flags));
+    res += mx_mysql_statement_param_bind(stmt, 12, string, &(g->group_disabled_servers));
+    res += mx_mysql_statement_param_bind(stmt, 13, uint64, &(g->group_id));
+    res += mx_mysql_statement_param_bind(stmt, 14, uint64, &(flags));
     assert(res == 0);
 
     res = mx_mysql_statement_execute(stmt, &num_rows);
@@ -332,6 +338,7 @@ static int load_group_id_run_or_wait(struct mx_mysql *mysql, struct mxq_group *g
                 " AND job_tmpdir_size = ?"
                 " AND job_max_per_node = ?"
                 " AND group_priority = ?"
+                " AND group_disabled_servers = ?"
                 " AND group_status = 0"
                 " AND ("
                         "group_jobs_running > 0"
@@ -358,7 +365,8 @@ static int load_group_id_run_or_wait(struct mx_mysql *mysql, struct mxq_group *g
     res += mx_mysql_statement_param_bind(stmt, 9, uint32, &(g->job_tmpdir_size));
     res += mx_mysql_statement_param_bind(stmt, 10, uint16, &(g->job_max_per_node));
     res += mx_mysql_statement_param_bind(stmt, 11, uint16, &(g->group_priority));
-    res += mx_mysql_statement_param_bind(stmt, 12, uint64, &(flags));
+    res += mx_mysql_statement_param_bind(stmt, 12, string, &(g->group_disabled_servers));
+    res += mx_mysql_statement_param_bind(stmt, 13, uint64, &(flags));
     assert(res == 0);
 
     res = mx_mysql_statement_execute(stmt, &num_rows);
@@ -415,7 +423,8 @@ static int add_group(struct mx_mysql *mysql, struct mxq_group *g)
                 " job_time = ?,"
                 " job_tmpdir_size = ?,"
                 " job_max_per_node = ?,"
-                " group_priority = ?");
+                " group_priority = ?,"
+                " group_disabled_servers = ?");
     if (!stmt) {
         mx_log_err("mx_mysql_statement_prepare(): %m");
         return -errno;
@@ -433,6 +442,7 @@ static int add_group(struct mx_mysql *mysql, struct mxq_group *g)
     res += mx_mysql_statement_param_bind(stmt, 9, uint32, &(g->job_tmpdir_size));
     res += mx_mysql_statement_param_bind(stmt,10, uint16, &(g->job_max_per_node));
     res += mx_mysql_statement_param_bind(stmt,11, uint16, &(g->group_priority));
+    res += mx_mysql_statement_param_bind(stmt,12, string, &(g->group_disabled_servers));
     assert(res == 0);
 
     res = mx_mysql_statement_execute(stmt, &num_rows);
@@ -639,6 +649,7 @@ int main(int argc, char *argv[])
     u_int16_t  arg_priority;
     char      *arg_group_name;
     u_int16_t  arg_group_priority;
+    char      *arg_disabled_servers;
     char      *arg_program_name;
     u_int16_t  arg_threads;
     u_int64_t  arg_memory;
@@ -659,6 +670,7 @@ int main(int argc, char *argv[])
     _mx_cleanup_free_ char *arg_stdout_absolute = NULL;
     _mx_cleanup_free_ char *arg_stderr_absolute = NULL;
     _mx_cleanup_free_ char *arg_args = NULL;
+    _mx_cleanup_free_ char *disabled_servers = NULL;
 
     int flags = 0;
 
@@ -713,6 +725,7 @@ int main(int argc, char *argv[])
                 MX_OPTION_OPTIONAL_ARG("mysql-default-file",  'M'),
                 MX_OPTION_OPTIONAL_ARG("mysql-default-group", 'S'),
                 MX_OPTION_REQUIRED_ARG("tmpdir", 7),
+                MX_OPTION_REQUIRED_ARG("disabled-servers",  8),
                 MX_OPTION_END
     };
 
@@ -738,6 +751,7 @@ int main(int argc, char *argv[])
     arg_jobflags       = 0;
     arg_groupid        = UINT64_UNSET;
     arg_tmpdir         = 0;
+    arg_disabled_servers = NULL;
 
     arg_mysql_default_group = getenv("MXQ_MYSQL_DEFAULT_GROUP");
     if (!arg_mysql_default_group)
@@ -942,6 +956,10 @@ int main(int argc, char *argv[])
                 }
                 break;
 
+            case 8:
+                arg_disabled_servers = optctl.optarg;
+                break;
+
         }
     }
 
@@ -997,6 +1015,14 @@ int main(int argc, char *argv[])
     arg_args = mx_strvec_to_str(argv);
     assert(arg_args);
 
+    if ( arg_disabled_servers != NULL ) {
+        struct keywordset *kws = keywordset_new(arg_disabled_servers);
+        disabled_servers = keywordset_get(kws);
+        keywordset_free(kws);
+    } else {
+        disabled_servers = mx_strdup_forever("");
+    }
+
     /******************************************************************/
 
     memset(&job,   0, sizeof(job));
@@ -1013,6 +1039,7 @@ int main(int argc, char *argv[])
     group.job_memory      = arg_memory;
     group.job_time        = arg_time;
     group.job_tmpdir_size = arg_tmpdir;
+    group.group_disabled_servers = disabled_servers;
 
     group.job_max_per_node = arg_max_per_node;
 

From a041fcdf9f42d0103713a9a7e2379b89859af3d6 Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Mon, 13 Apr 2020 23:24:35 +0200
Subject: [PATCH 13/24] Makefile: Let mxqsub depend on keywordset

---
 Makefile | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Makefile b/Makefile
index 330bfdfc..aac50c4c 100644
--- a/Makefile
+++ b/Makefile
@@ -494,6 +494,7 @@ mxqsub.o: $(mxq.h)
 mxqsub.o: $(mxq_group.h)
 mxqsub.o: $(mxq_job.h)
 mxqsub.o: $(mx_util.h)
+mxqsub.o: $(keywordset.h)
 mxqsub.o: CFLAGS += $(CFLAGS_MYSQL)
 
 clean: CLEAN += mxqsub.o
@@ -535,6 +536,7 @@ mxqsub: mx_getopt.o
 mxqsub: mx_util.o
 mxqsub: mx_log.o
 mxqsub: mx_mysql.o
+mxqsub: keywordset.o
 mxqsub: LDLIBS += $(LDLIBS_MYSQL)
 
 build: mxqsub

From f86710d493e5126ff1f2f685a2fb1c2c356ffa5e Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Mon, 13 Apr 2020 23:24:35 +0200
Subject: [PATCH 14/24] Add command "mxqset"

Add a new command which can be used to modify an existing group:

    mxqset group 123 --closed
    mxqset group 123 --open
    mxqset group 123 --disabled-servers=dontpanic
    mxqset group 123 --update-disabled-servers="sigusr sigusr2 -dontpanic"
    mxqset group 123 --disabled-servers=

The flags open and closed can be set from mxqadmin as well
(`mxqadmin --close=123`) , but the synopsis, in which the options take
the groupid as a value is difficult to expand to options, which take a
value by themself.
---
 mxqset.c | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 200 insertions(+)
 create mode 100644 mxqset.c

diff --git a/mxqset.c b/mxqset.c
new file mode 100644
index 00000000..891e9439
--- /dev/null
+++ b/mxqset.c
@@ -0,0 +1,200 @@
+#define _GNU_SOURCE
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "mx_mysql.h"
+#include "mxq.h"
+#include "mxq_group.h"
+#include "keywordset.h"
+#include "stdarg.h"
+
+static __attribute__ ((noreturn)) void die(char *msg, ...) {
+    va_list ap;
+
+    va_start(ap, msg);
+    fprintf(stderr, "%s: ",program_invocation_short_name);
+    vfprintf(stderr, msg, ap);
+    va_end(ap);
+    exit(1);
+}
+
+static void verify_group_permission(struct mx_mysql *mysql , long unsigned groupid, uid_t client_uid) {
+    struct mx_mysql_stmt *stmt = NULL;
+    unsigned long long num_rows;
+    unsigned uid;
+
+    stmt = mx_mysql_statement_prepare(mysql,"SELECT user_uid FROM mxq_group WHERE group_id = ? LIMIT 1");
+    if (!stmt)
+        die("%m\n");
+    mx_mysql_statement_param_bind(stmt, 0, uint64, &(groupid));
+    if (mx_mysql_statement_execute(stmt, &num_rows) < 0)
+        die("%m\n");
+    if (num_rows < 1)
+        die("no such group %ld\n", groupid);
+    mx_mysql_statement_result_bind(stmt, 0, uint32, &uid);
+    if (mx_mysql_statement_fetch(stmt) < 0)
+        die("%m\n");
+    if ( client_uid != 0 && client_uid != uid )
+        die("no permission to access this group\n");
+    mx_mysql_statement_close(&stmt);
+}
+
+enum OPEN_CLOSED  {
+    OPEN_CLOSED_UNSET = 0,
+    OPEN_CLOSED_CLOSED,
+    OPEN_CLOSED_OPEN,
+};
+
+static void update_closed_flag(struct mx_mysql *mysql, long unsigned groupid, enum OPEN_CLOSED open_closed) {
+
+    struct mx_mysql_stmt *stmt = NULL;
+    unsigned long long num_rows;
+
+    uint64_t mask = ~MXQ_GROUP_FLAG_CLOSED;
+    uint64_t value = open_closed == OPEN_CLOSED_CLOSED ? MXQ_GROUP_FLAG_CLOSED : 0;
+
+    stmt = mx_mysql_statement_prepare(mysql,
+            "UPDATE mxq_group SET group_flags = ( group_flags & ?) | ? WHERE group_id = ?");
+    if (!stmt)
+        die("%m\n");
+    mx_mysql_statement_param_bind(stmt, 0, uint64, &(mask));
+    mx_mysql_statement_param_bind(stmt, 1, uint64, &(value));
+    mx_mysql_statement_param_bind(stmt, 2, uint64, &(groupid));
+    if (mx_mysql_statement_execute(stmt, &num_rows) < 0)
+        die("%m\n");
+    if (num_rows < 1)
+        die("no such group %ld\n", groupid);
+    mx_mysql_statement_close(&stmt);
+}
+
+static void update_disabled_server(struct mx_mysql *mysql, long unsigned groupid, char *set_disabled_server, char *update_disabled_server) {
+
+    struct mx_mysql_stmt *stmt = NULL;
+    unsigned long long num_rows;
+    char *group_disabled_servers = NULL;
+    struct keywordset *disabled_server;
+
+    if (set_disabled_server)
+        disabled_server = keywordset_new(set_disabled_server);
+    else {
+        stmt = mx_mysql_statement_prepare(mysql,
+                "SELECT group_disabled_servers FROM mxq_group WHERE group_id = ? LIMIT 1");
+        if (!stmt)
+            die("%m\n");
+        mx_mysql_statement_param_bind(stmt, 0, uint64, &groupid);
+        if (mx_mysql_statement_execute(stmt, &num_rows) < 0)
+            die("%m\n");
+        if (num_rows < 1)
+            die("no such group %ld\n", groupid);
+        mx_mysql_statement_result_bind(stmt, 0, string, &group_disabled_servers);
+        if (mx_mysql_statement_fetch(stmt) < 0)
+            die("%m\n");
+        mx_mysql_statement_close(&stmt);
+        disabled_server = keywordset_new(group_disabled_servers);
+        free(group_disabled_servers);
+    }
+
+    if (update_disabled_server != NULL)
+        keywordset_update(disabled_server, update_disabled_server);
+
+    group_disabled_servers = keywordset_get(disabled_server);
+
+    stmt = mx_mysql_statement_prepare(mysql,
+            "UPDATE mxq_group SET group_disabled_servers = ? WHERE group_id = ?");
+     if (!stmt)
+        die("%m\n");
+    mx_mysql_statement_param_bind(stmt, 0, string, &group_disabled_servers);
+    mx_mysql_statement_param_bind(stmt, 1, uint64, &groupid);
+    if (mx_mysql_statement_execute(stmt, &num_rows) < 0)
+        die("%m\n");
+    if (num_rows < 1)
+        die("no such group %ld\n", groupid);
+    mx_mysql_statement_close(&stmt);
+    free(group_disabled_servers);
+    keywordset_free(disabled_server);
+}
+
+struct opts {
+    enum OPEN_CLOSED open_closed;
+    char *set_disabled_server;
+    char *update_disabled_server;
+};
+
+static error_t parser (int key, char *arg, struct argp_state *state) {
+    struct opts *opts = state->input;
+    switch (key) {
+        case 10:
+            opts->open_closed = OPEN_CLOSED_CLOSED;
+            return 0;
+        case 11:
+            opts->open_closed = OPEN_CLOSED_OPEN;
+            return 0;
+        case 12:
+            opts->set_disabled_server = arg;
+            return 0;
+        case 13:
+            opts->update_disabled_server = arg;
+            return 0;
+    }
+    return ARGP_ERR_UNKNOWN;
+}
+
+static const struct  argp_option options[] = {
+    {"closed",                  10, NULL,   0, NULL},
+    {"open",                    11, NULL,   0, NULL},
+    {"disabled-servers",        12, "",     0, NULL},
+    {"update-disabled-servers", 13, "",     0, NULL},
+    {0}
+};
+
+static const struct argp argp = { options, parser, NULL, NULL };
+
+static __attribute__ ((noreturn)) void exit_usage(char *argv0) {
+    fprintf(stderr,
+"usage: %s group GID [group-options]\n"
+"\n"
+"group options:\n"
+"\n"
+"    --open\n"
+"    --closed\n"
+"    --disabled-servers=STRING\n"
+"    --update-disabled-servers=STRING\n"
+        ,program_invocation_short_name);
+    exit(EX_USAGE);
+}
+
+int main(int argc, char **argv) {
+
+    char *argv0=argv[0];
+    struct mx_mysql *mysql = NULL;
+    int groupid;
+    uid_t uid = getuid();
+
+    if (argc<3 || strcmp(argv[1],"group") != 0)
+        exit_usage(argv0);
+    groupid=atoi(argv[2]);
+
+    int sts;
+    struct opts opts={0};
+
+    sts=argp_parse (&argp, argc-3, &argv[3], ARGP_PARSE_ARGV0|ARGP_SILENT, NULL, &opts);
+    if (sts)
+        exit_usage(argv0);
+
+    assert(mx_mysql_initialize(&mysql) == 0);
+    mx_mysql_option_set_default_file(mysql, MXQ_MYSQL_DEFAULT_FILE);
+    mx_mysql_option_set_default_group(mysql, MXQ_MYSQL_DEFAULT_GROUP);
+    assert(mx_mysql_connect_forever(&mysql) == 0);
+
+    verify_group_permission(mysql, groupid, uid);
+
+    if ( opts.open_closed != OPEN_CLOSED_UNSET)
+        update_closed_flag(mysql, groupid, opts.open_closed);
+    if ( opts.set_disabled_server != NULL || opts.update_disabled_server != NULL)
+        update_disabled_server(mysql, groupid, opts.set_disabled_server, opts.update_disabled_server);
+
+    mx_mysql_finish(&mysql);
+}

From 42c1f4417d5949561b67d33a7ee3a42e4fdd56e6 Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Mon, 13 Apr 2020 23:24:36 +0200
Subject: [PATCH 15/24] Add mxqset to build system

---
 .gitignore |  2 ++
 Makefile   | 25 +++++++++++++++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/.gitignore b/.gitignore
index 7d3fa655..4599a3ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@ mx_mysql.o
 mxqd_control.o
 keywordset.o
 test_keywordset.o
+mxqset.o
 
 
 mxqsub
@@ -32,6 +33,7 @@ mxqdump
 mxqkill
 mxqd
 mxqps
+mxqset
 test_mx_util
 test_mx_log
 test_mx_mysq
diff --git a/Makefile b/Makefile
index aac50c4c..e8ec5d61 100644
--- a/Makefile
+++ b/Makefile
@@ -416,6 +416,16 @@ mxqadmin.o: CFLAGS += $(CFLAGS_MYSQL)
 
 clean: CLEAN += mxqadmin.o
 
+### mxqset.o ----------------------------------------------------------
+
+mxqsset.o: $(mx_mysql.h)
+mxqsset.o: $(keywordset.h)
+mxqkill.o: $(mxq.h)
+mxqkill.o: $(mxq_group.h)
+mxqset.o: CFLAGS += $(CFLAGS_MYSQL)
+
+clean: CLEAN += mxqsset.o
+
 ### mxqkill.o ----------------------------------------------------------
 
 mxqkill.o: $(mx_log.h)
@@ -579,6 +589,21 @@ clean: CLEAN += mxqadmin
 install:: mxqadmin
 	$(call quiet-installforuser,$(SUID_MODE),$(UID_CLIENT),$(GID_CLIENT),mxqadmin,${DESTDIR}${BINDIR}/mxqadmin)
 
+### mxqset ---------------------------------------------------------------
+
+mxqset: mx_mysql.o
+mxqset: mx_log.o
+mxqset: mx_util.o
+mxqset: keywordset.o
+mxqset: LDLIBS += $(LDLIBS_MYSQL)
+
+build: mxqset
+
+clean: CLEAN += mxqset
+
+install:: mxqset
+	$(call quiet-installforuser,$(SUID_MODE),$(UID_CLIENT),$(GID_CLIENT),mxqset,${DESTDIR}${BINDIR}/mxqset)
+
 ### mxqkill ------------------------------------------------------------
 
 mxqkill: mx_log.o

From 3c7e9c117554c94b5bfb0f271c6f970353cbd364 Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Mon, 13 Apr 2020 23:24:36 +0200
Subject: [PATCH 16/24] mxqdump: Show disabled servers

---
 mxqdump.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/mxqdump.c b/mxqdump.c
index 8a39e9e2..d3aff47f 100644
--- a/mxqdump.c
+++ b/mxqdump.c
@@ -135,6 +135,7 @@ static int print_group(struct mxq_group *g)
         " idle_sec=%lu"
         " job_command=%s"
         " group_name=%s"
+        " disabled_servers=%s"
         "\n",
         g->user_name,
         g->user_uid,
@@ -163,7 +164,8 @@ static int print_group(struct mxq_group *g)
         g->stats_run_sec,
         g->stats_idle_sec,
         g->job_command,
-        g->group_name);
+        g->group_name,
+        g->group_disabled_servers);
 }
 
 static int print_job(struct mxq_group *g, struct mxq_job *j)

From d7abbb21587141dd216fa0d184f99f7b3634c67e Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Mon, 13 Apr 2020 23:24:36 +0200
Subject: [PATCH 17/24] mxqd: Only start jobs we are qualified for

Check, whether this server is qualified to start jobs from a group.
Lazy-evaluate qualification criteria and cache the result.
Don't start jobs we are not qualified for.

For now, the only qualification criteria is, that our short or fully
qualified hostname is not included the the excluded_servers set of the
group.

This can later be expanded to additional criteria (e.g. hostconfig or
processor flags).
---
 mxqd.c         | 34 ++++++++++++++++++++++++++++++++++
 mxqd.h         |  4 ++++
 mxqd_control.c |  3 +++
 3 files changed, 41 insertions(+)

diff --git a/mxqd.c b/mxqd.c
index c1106aba..ff211eac 100644
--- a/mxqd.c
+++ b/mxqd.c
@@ -43,6 +43,7 @@
 #include "mxq.h"
 
 #include "mxqd_control.h"
+#include "keywordset.h"
 
 #ifndef MXQ_INITIAL_PATH
 #  define MXQ_INITIAL_PATH      "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
@@ -546,6 +547,16 @@ int server_init(struct mxq_server *server, int argc, char *argv[])
     }
 
     server->hostname = arg_hostname;
+    {
+        char *dot=index(arg_hostname,'.');
+        if (dot) {
+            server->hostname_short = mx_malloc_forever(dot-arg_hostname+1);
+            strncpy(server->hostname_short, arg_hostname, dot-arg_hostname);
+            server->hostname_short[dot-arg_hostname] = 0;
+        } else
+            server->hostname_short = mx_strdup_forever(arg_hostname);
+    }
+
     server->daemon_name = arg_daemon_name;
     server->initial_path = arg_initial_path;
     server->initial_tmpdir = arg_initial_tmpdir;
@@ -1277,6 +1288,26 @@ unsigned long start_job(struct mxq_group_list *glist)
 
 /**********************************************************************/
 
+static int server_is_qualified(struct mxq_server *server, struct mxq_group *group) {
+    int is_qualified = 1;
+    if (*group->group_disabled_servers != 0) {
+        struct keywordset *kws = keywordset_new(group->group_disabled_servers);
+        if ( keywordset_ismember(kws, server->hostname_short)
+                || keywordset_ismember(kws, server->hostname) )
+            is_qualified = 0;
+        keywordset_free(kws);
+    }
+    return (is_qualified);
+}
+
+static int server_is_qualified_cached(struct mxq_server *server, struct mxq_group_list *glist) {
+    if (!glist->server_is_qualified_evaluated) {
+        glist->server_is_qualified = server_is_qualified(server, &glist->group);
+        glist->server_is_qualified_evaluated = 1;
+    }
+    return glist->server_is_qualified;
+}
+
 unsigned long start_user(struct mxq_user_list *ulist, long slots_to_start)
 {
     struct mxq_server *server;
@@ -1319,6 +1350,8 @@ unsigned long start_user(struct mxq_user_list *ulist, long slots_to_start)
         if (df_scratch/1024/1024/1024 < group->job_tmpdir_size + 20) {
             continue;
         }
+        if (!server_is_qualified_cached(server, glist))
+            continue;
 
         mx_log_info("  group=%s(%d):%lu slots_to_start=%ld slots_per_job=%lu :: trying to start job for group.",
                 group->user_name, group->user_uid, group->group_id, slots_to_start, glist->slots_per_job);
@@ -1472,6 +1505,7 @@ void server_free(struct mxq_server *server)
     mx_free_null(server->finished_jobsdir);
     mx_flock_free(server->flock);
     mx_free_null(server->supgid);
+    mx_free_null(server->hostname_short);
 
     mx_log_finish();
 }
diff --git a/mxqd.h b/mxqd.h
index 5c9ce7e3..87aa2236 100644
--- a/mxqd.h
+++ b/mxqd.h
@@ -43,6 +43,9 @@ struct mxq_group_list {
     unsigned long global_threads_running;
     unsigned long global_slots_running;
 
+    int        server_is_qualified_evaluated;
+    int        server_is_qualified;
+
     short orphaned;
 };
 
@@ -95,6 +98,7 @@ struct mxq_server {
     unsigned long long int starttime;
     char *host_id;
     char *hostname;
+    char *hostname_short;
     char *daemon_name;
     char *pidfilename;
     char *finished_jobsdir;
diff --git a/mxqd_control.c b/mxqd_control.c
index 36814e26..bbbd8549 100644
--- a/mxqd_control.c
+++ b/mxqd_control.c
@@ -103,6 +103,9 @@ static void _group_list_init(struct mxq_group_list *glist)
     glist->slots_max  = slots_max;
     glist->memory_max = memory_max;
 
+    glist->server_is_qualified_evaluated = 0;
+    glist->server_is_qualified = 0;
+
     glist->orphaned = 0;
 }
 

From 2c6199f55a2753714a97cafac408e3451dfe7aa3 Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Mon, 13 Apr 2020 23:24:37 +0200
Subject: [PATCH 18/24] Makefile: Let mxqd depend on keywordset

---
 Makefile | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Makefile b/Makefile
index e8ec5d61..8863ec9b 100644
--- a/Makefile
+++ b/Makefile
@@ -486,6 +486,7 @@ mxqd.o: $(mxq_daemon.h)
 mxqd.o: $(mxq_group.h)
 mxqd.o: $(mxq_job.h)
 mxqd.o: $(mx_mysql.h)
+mxqd.o: $(keywordset.h)
 mxqd.o: CFLAGS += $(CFLAGS_MYSQL)
 mxqd.o: CFLAGS += $(CFLAGS_MXQ_INITIAL_PATH)
 mxqd.o: CFLAGS += $(CFLAGS_MXQ_INITIAL_TMPDIR)
@@ -531,6 +532,7 @@ mxqd: mxq_group.o
 mxqd: mxq_job.o
 mxqd: mx_mysql.o
 mxqd: mxqd_control.o
+mxqd: keywordset.o
 mxqd: LDLIBS += $(LDLIBS_MYSQL)
 
 build: mxqd

From c737da47edbe5309b035f4cb0b18ea5d2c7f596b Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Mon, 13 Apr 2020 23:24:37 +0200
Subject: [PATCH 19/24] MXQ bump version

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 8863ec9b..6aca8321 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 MXQ_VERSION_MAJOR = 0
 MXQ_VERSION_MINOR = 26
-MXQ_VERSION_PATCH = 1
+MXQ_VERSION_PATCH = 2
 MXQ_VERSION_EXTRA = "beta"
 MXQ_VERSIONDATE = 2016
 

From b04d9399d40f3b6cd7dc6bfc472dde3a2d634f21 Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Tue, 14 Apr 2020 10:28:39 +0200
Subject: [PATCH 20/24] fixup! Add keywordset module

---
 keywordset.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/keywordset.c b/keywordset.c
index 589acf65..2f3e4fd4 100644
--- a/keywordset.c
+++ b/keywordset.c
@@ -9,7 +9,7 @@
 
 struct keywordset {
     int nr_slots;
-    int	 used;
+    int used;
     char **names;
 };
 

From 4f7c7814fbc4b419ec26b427993b94e563018bc8 Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Tue, 14 Apr 2020 11:56:28 +0200
Subject: [PATCH 21/24] xmalloc: Add xrealloc

---
 xmalloc.h | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/xmalloc.h b/xmalloc.h
index 40820135..2c906af4 100644
--- a/xmalloc.h
+++ b/xmalloc.h
@@ -16,6 +16,13 @@ __attribute__ ((unused)) static void *xmalloc(size_t size) {
     return(ptr);
 }
 
+_attribute__ ((unused)) static void *xrealloc(void *ptr, size_t size) {
+    void *ptr = realloc(ptr, size);
+    if (__builtin_expect(ptr == NULL, 0))
+        out_of_memory();
+    return(ptr);
+}
+
 __attribute__ ((unused)) static char *xstrndup(const char *s, size_t n) {
     char *ptr = strndup(s, n);
     if (__builtin_expect(ptr == NULL, 0))

From 6affb1d9e71fe1db589d5f0dc8ab911b22756c0d Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Tue, 14 Apr 2020 11:56:54 +0200
Subject: [PATCH 22/24] keywordset: Use xrealloc

---
 keywordset.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/keywordset.c b/keywordset.c
index 2f3e4fd4..4206213a 100644
--- a/keywordset.c
+++ b/keywordset.c
@@ -33,7 +33,7 @@ static int find_name(struct keywordset *kws, char *name, size_t len) {
 
 static void expand(struct keywordset *kws) {
     int new_slots=(kws->nr_slots+2)*2-2;
-    kws->names=realloc(kws->names,new_slots*sizeof(*kws->names));
+    kws->names=xrealloc(kws->names,new_slots*sizeof(*kws->names));
     kws->nr_slots=new_slots;
 }
 

From 17613e50dea103ae81391a4ab37c2a697b1dba4e Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Tue, 14 Apr 2020 11:59:39 +0200
Subject: [PATCH 23/24] fixup! xmalloc: Add xrealloc

---
 xmalloc.h | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/xmalloc.h b/xmalloc.h
index 2c906af4..a92156c9 100644
--- a/xmalloc.h
+++ b/xmalloc.h
@@ -16,11 +16,11 @@ __attribute__ ((unused)) static void *xmalloc(size_t size) {
     return(ptr);
 }
 
-_attribute__ ((unused)) static void *xrealloc(void *ptr, size_t size) {
-    void *ptr = realloc(ptr, size);
-    if (__builtin_expect(ptr == NULL, 0))
+__attribute__ ((unused)) static void *xrealloc(void *ptr, size_t size) {
+    void *outptr = realloc(ptr, size);
+    if (__builtin_expect(outptr == NULL, 0))
         out_of_memory();
-    return(ptr);
+    return(outptr);
 }
 
 __attribute__ ((unused)) static char *xstrndup(const char *s, size_t n) {

From 85c3109b91c42654336a95a952b9e0b308486d0e Mon Sep 17 00:00:00 2001
From: Donald Buczek 
Date: Tue, 14 Apr 2020 12:27:47 +0200
Subject: [PATCH 24/24] xmalloc: Remove poor micro optimization

The optimizing compiler predicts that pointer != NULL is more likely
than pointer == NULL anyway. The generated code by "gcc -O2" is the
same with or without __builtin_expect builtin.

However, probably based on special knowledge of malloc either because of
its __attribute__ ((malloc)) declaration or hard-coded, the optimization
without the __builtin_expect builtin seems to be a bit more stronger:
The function out_of_memory() is put into a .text.unlikely code segment,
as if had attribute ((cold)) had been declared for it.

Remove __builtin_expect.
---
 xmalloc.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/xmalloc.h b/xmalloc.h
index a92156c9..23705bdd 100644
--- a/xmalloc.h
+++ b/xmalloc.h
@@ -11,21 +11,21 @@ __attribute__ ((noreturn, unused)) static void out_of_memory() {
 
 __attribute__ ((unused)) static void *xmalloc(size_t size) {
     void *ptr = malloc(size);
-    if (__builtin_expect(ptr == NULL, 0))
+    if (ptr == NULL)
         out_of_memory();
     return(ptr);
 }
 
 __attribute__ ((unused)) static void *xrealloc(void *ptr, size_t size) {
     void *outptr = realloc(ptr, size);
-    if (__builtin_expect(outptr == NULL, 0))
+    if (outptr == NULL)
         out_of_memory();
     return(outptr);
 }
 
 __attribute__ ((unused)) static char *xstrndup(const char *s, size_t n) {
     char *ptr = strndup(s, n);
-    if (__builtin_expect(ptr == NULL, 0))
+    if (ptr == NULL)
         out_of_memory();
     return(ptr);
 }