aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorDavid Oberhollenzer <david.oberhollenzer@tele2.at>2018-04-12 05:30:12 +0200
committerDavid Oberhollenzer <david.oberhollenzer@tele2.at>2018-04-12 10:28:40 +0200
commit6cf0a254cd9e4892a941bb5ad6f8d9ed317b1617 (patch)
treead9f1b409c5a465986078ee16b102f6fa6cfbdb3 /cmd
parente148e873e05e4ebe4d7a90cdbe8cdd72618eb7ac (diff)
Build system and directory structure cleanup
Signed-off-by: David Oberhollenzer <david.oberhollenzer@tele2.at>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/Makemodule.am15
-rw-r--r--cmd/service/disable.c89
-rw-r--r--cmd/service/dumpscript.c182
-rw-r--r--cmd/service/enable.c98
-rw-r--r--cmd/service/help.c129
-rw-r--r--cmd/service/list.c102
-rw-r--r--cmd/service/servicecmd.c90
-rw-r--r--cmd/service/servicecmd.h93
8 files changed, 797 insertions, 1 deletions
diff --git a/cmd/Makemodule.am b/cmd/Makemodule.am
index 40c0933..f0ae3f9 100644
--- a/cmd/Makemodule.am
+++ b/cmd/Makemodule.am
@@ -15,5 +15,18 @@ killall5_CPPFLAGS = $(AM_CPPFLAGS)
killall5_CFLAGS = $(AM_CFLAGS)
killall5_LDFLAGS = $(AM_LDFLAGS)
-sbin_PROGRAMS += reboot shutdown
+SRVHEADERS = cmd/service/servicecmd.h
+
+service_SOURCES = cmd/service/servicecmd.c cmd/service/help.c
+service_SOURCES += cmd/service/enable.c cmd/service/disable.c
+service_SOURCES += cmd/service/dumpscript.c cmd/service/list.c
+service_SOURCES += $(SRVHEADERS)
+service_CPPFLAGS = $(AM_CPPFLAGS)
+service_CFLAGS = $(AM_CFLAGS)
+service_LDFLAGS = $(AM_LDFLAGS)
+service_LDADD = libinit.a
+
+EXTRA_DIST += $(SRVHEADERS)
+
+sbin_PROGRAMS += service reboot shutdown
helper_PROGRAMS += killall5
diff --git a/cmd/service/disable.c b/cmd/service/disable.c
new file mode 100644
index 0000000..8a6f9f3
--- /dev/null
+++ b/cmd/service/disable.c
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * Copyright (C) 2018 - David Oberhollenzer
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "servicecmd.h"
+
+static int cmd_disable(int argc, char **argv)
+{
+ int ret = EXIT_FAILURE;
+ char *linkname, *ptr;
+ struct stat sb;
+
+ if (check_arguments(argv[0], argc, 2, 3))
+ return EXIT_FAILURE;
+
+ for (ptr = argv[1]; isalnum(*ptr) || *ptr == '_'; ++ptr)
+ ;
+
+ if (*ptr != '\0') {
+ fprintf(stderr, "Invalid service name '%s'\n", argv[1]);
+ tell_read_help(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (argc == 3) {
+ ret = asprintf(&linkname, "%s/%s@%s",
+ SVCDIR, argv[1], argv[2]);
+ } else {
+ ret = asprintf(&linkname, "%s/%s", SVCDIR, argv[1]);
+ }
+
+ if (ret < 0) {
+ perror("asprintf");
+ return EXIT_FAILURE;
+ }
+
+ if (lstat(linkname, &sb)) {
+ fprintf(stderr, "lstat %s: %s\n", linkname, strerror(errno));
+ goto out;
+ }
+
+ if ((sb.st_mode & S_IFMT) != S_IFLNK) {
+ fprintf(stderr, "error: '%s' is not a symlink!", linkname);
+ goto out;
+ }
+
+ if (unlink(linkname)) {
+ fprintf(stderr, "removing %s: %s\n",
+ linkname, strerror(errno));
+ goto out;
+ }
+
+ ret = EXIT_SUCCESS;
+out:
+ free(linkname);
+ return ret;
+}
+
+static command_t disable = {
+ .cmd = "disable",
+ .usage = "<name> [argument]",
+ .s_desc = "disable a service",
+ .l_desc = "This disables a service by removing the coresponding "
+ "symlink in " SVCDIR ".",
+ .run_cmd = cmd_disable,
+};
+
+REGISTER_COMMAND(disable)
diff --git a/cmd/service/dumpscript.c b/cmd/service/dumpscript.c
new file mode 100644
index 0000000..880631f
--- /dev/null
+++ b/cmd/service/dumpscript.c
@@ -0,0 +1,182 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * Copyright (C) 2018 - David Oberhollenzer
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "servicecmd.h"
+#include "service.h"
+
+static service_t *try_load(const char *directory, const char *filename)
+{
+ int dirfd, type;
+ struct stat sb;
+ service_t *svc;
+
+ dirfd = open(directory, O_RDONLY | O_DIRECTORY);
+
+ if (dirfd < 0) {
+ perror(directory);
+ return NULL;
+ }
+
+ if (fstatat(dirfd, filename, &sb, AT_SYMLINK_NOFOLLOW)) {
+ fprintf(stderr, "stat %s/%s: %s\n",
+ directory, filename, strerror(errno));
+ close(dirfd);
+ return NULL;
+ }
+
+ type = (sb.st_mode & S_IFMT);
+
+ if (type != S_IFREG && type != S_IFLNK)
+ return NULL;
+
+ svc = rdsvc(dirfd, filename);
+ close(dirfd);
+ return svc;
+}
+
+enum {
+ NEED_QUOTES = 0x01,
+ NEED_ESCAPE = 0x02,
+};
+
+static int check_str(const char *str)
+{
+ int ret = 0;
+
+ while (*str != '\0') {
+ if (isspace(*str))
+ ret |= NEED_QUOTES;
+
+ if (!isascii(*str) || !isprint(*str))
+ ret |= NEED_ESCAPE | NEED_QUOTES;
+
+ if (*str == '\\' || *str == '"' || *str == '\n')
+ ret |= NEED_ESCAPE | NEED_QUOTES;
+
+ ++str;
+ }
+
+ return ret;
+}
+
+static void print_str(const char *str)
+{
+ int flags = check_str(str);
+
+ if (flags & NEED_QUOTES)
+ fputc('"', stdout);
+
+ if (flags & NEED_ESCAPE) {
+ while (*str != '\0') {
+ switch (*str) {
+ case '\a': fputs("\\a", stdout); break;
+ case '\b': fputs("\\b", stdout); break;
+ case '\f': fputs("\\f", stdout); break;
+ case '\r': fputs("\\r", stdout); break;
+ case '\t': fputs("\\t", stdout); break;
+ case '\n': fputs("\\n", stdout); break;
+ case '\\':
+ case '"':
+ fprintf(stdout, "\\%c", *str);
+ break;
+ default:
+ if (!isascii(*str) || !isprint(*str)) {
+ fprintf(stdout, "\\x%02X", *str);
+ } else {
+ fputc(*str, stdout);
+ }
+ break;
+ }
+ ++str;
+ }
+ } else {
+ fputs(str, stdout);
+ }
+
+ if (flags & NEED_QUOTES)
+ fputc('"', stdout);
+}
+
+static int cmd_dumpscript(int argc, char **argv)
+{
+ char *filename, *ptr;
+ service_t *svc;
+ size_t len;
+ exec_t *e;
+ int i;
+
+ if (check_arguments(argv[0], argc, 2, 3))
+ return EXIT_FAILURE;
+
+ for (len = 1, i = 1; i < argc; ++i)
+ len += strlen(argv[i]) + 1;
+
+ filename = alloca(len);
+ filename[0] = '\0';
+
+ for (i = 1; i < argc; ++i) {
+ if (i > 1)
+ strcat(filename, "@");
+ strcat(filename, argv[i]);
+ }
+
+ svc = try_load(SVCDIR, filename);
+
+ if (svc == NULL) {
+ fprintf(stderr, "Could not load service '%s'\n", filename);
+ return EXIT_FAILURE;
+ }
+
+ fprintf(stdout, "#\n# commands executed for serice '%s'\n#\n",
+ filename);
+
+ for (e = svc->exec; e != NULL; e = e->next) {
+ ptr = e->args;
+
+ for (i = 0; i < e->argc; ++i) {
+ if (i)
+ fputc(' ', stdout);
+
+ print_str(ptr);
+ ptr += strlen(ptr) + 1;
+ }
+
+ putchar('\n');
+ }
+
+ delsvc(svc);
+ return EXIT_SUCCESS;
+}
+
+static command_t dumpscript = {
+ .cmd = "dumpscript",
+ .usage = "<name> [arguments]",
+ .s_desc = "print commands executed for a service",
+ .l_desc = "This parses a service file from " SVCDIR " and "
+ "produces a pseudo-shell-script containing the "
+ "exact commands after argument expansion that init "
+ "runs when starting the service.",
+ .run_cmd = cmd_dumpscript,
+};
+
+REGISTER_COMMAND(dumpscript)
diff --git a/cmd/service/enable.c b/cmd/service/enable.c
new file mode 100644
index 0000000..5d4195e
--- /dev/null
+++ b/cmd/service/enable.c
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * Copyright (C) 2018 - David Oberhollenzer
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "servicecmd.h"
+
+static int cmd_enable(int argc, char **argv)
+{
+ char *target, *linkname, *ptr;
+ int ret = EXIT_FAILURE;
+ struct stat sb;
+
+ if (check_arguments(argv[0], argc, 2, 3))
+ return EXIT_FAILURE;
+
+ for (ptr = argv[1]; isalnum(*ptr) || *ptr == '_'; ++ptr)
+ ;
+
+ if (*ptr != '\0') {
+ fprintf(stderr, "Invalid service name '%s'\n", argv[1]);
+ tell_read_help(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (asprintf(&target, "%s/%s", TEMPLATEDIR, argv[1]) < 0) {
+ perror("asprintf");
+ return EXIT_FAILURE;
+ }
+
+ if (stat(target, &sb)) {
+ fprintf(stderr, "%s: %s\n", target, strerror(errno));
+ goto out_tgt;
+ }
+
+ if ((sb.st_mode & S_IFMT) != S_IFREG) {
+ fprintf(stderr, "%s: must be a regular file\n", target);
+ goto out_tgt;
+ }
+
+ if (argc == 3) {
+ ret = asprintf(&linkname, "%s/%s@%s",
+ SVCDIR, argv[1], argv[2]);
+ } else {
+ ret = asprintf(&linkname, "%s/%s", SVCDIR, argv[1]);
+ }
+
+ if (ret < 0) {
+ perror("asprintf");
+ goto out_tgt;
+ }
+
+ if (symlink(target, linkname)) {
+ fprintf(stderr, "creating symlink '%s' -> '%s: %s\n",
+ linkname, target, strerror(errno));
+ goto out;
+ }
+
+ ret = EXIT_SUCCESS;
+out:
+ free(linkname);
+out_tgt:
+ free(target);
+ return ret;
+}
+
+static command_t enable = {
+ .cmd = "enable",
+ .usage = "<name> [argument]",
+ .s_desc = "enable a service",
+ .l_desc = "This marks a service as enabled by creating a symlink in "
+ SVCDIR " pointing to the template file in " TEMPLATEDIR ". "
+ "An optional argument can be supplied to parameterize the "
+ "template.",
+ .run_cmd = cmd_enable,
+};
+
+REGISTER_COMMAND(enable)
diff --git a/cmd/service/help.c b/cmd/service/help.c
new file mode 100644
index 0000000..4fa9951
--- /dev/null
+++ b/cmd/service/help.c
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * Copyright (C) 2018 - David Oberhollenzer
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+#include "servicecmd.h"
+
+extern char *__progname;
+
+static void pretty_print(const char *str, int padd, int len)
+{
+ int i, brk;
+next:
+ while (isspace(*str) && *str != '\n')
+ ++str;
+
+ if (!(*str))
+ return;
+
+ if (*str == '\n') {
+ fputc('\n', stdout);
+ ++str;
+ len = padd;
+ goto next;
+ }
+
+ for (i = 0, brk = 0; str[i]; ++i) {
+ if (str[i] == '<' || str[i] == '[')
+ ++brk;
+ if (str[i] == '>' || str[i] == ']')
+ --brk;
+ if (!brk && isspace(str[i]))
+ break;
+ }
+
+ if ((len + i) < 80) {
+ fwrite(str, 1, i, stdout);
+ str += i;
+ len += i;
+
+ if ((len + 1) < 80) {
+ fputc(' ', stdout);
+ ++len;
+ goto next;
+ }
+ }
+
+ printf("\n%*s", padd, "");
+ len = padd;
+ goto next;
+}
+
+static void print_cmd_usage(const char *cmd, const char *usage)
+{
+ int padd;
+
+ padd = printf("Usage: %s %s ", __progname, cmd);
+
+ if ((strlen(usage) + padd) < 80) {
+ fputs(usage, stdout);
+ return;
+ }
+
+ pretty_print(usage, padd, padd);
+}
+
+static int cmd_help(int argc, char **argv)
+{
+ const char *help;
+ command_t *cmd;
+
+ if (argc < 2)
+ usage(EXIT_SUCCESS);
+
+ if (argc > 2) {
+ fprintf(stderr, "Too many arguments\n\n"
+ "Usage: %s help <command>", __progname);
+ return EXIT_FAILURE;
+ }
+
+ for (cmd = commands; cmd != NULL; cmd = cmd->next) {
+ if (strcmp(cmd->cmd, argv[1]) != 0)
+ continue;
+
+ print_cmd_usage(cmd->cmd, cmd->usage);
+ fputs("\n\n", stdout);
+
+ help = cmd->l_desc ? cmd->l_desc : cmd->s_desc;
+
+ if (islower(*help)) {
+ fputc(toupper(*(help++)), stdout);
+ pretty_print(help, 0, 1);
+ } else {
+ pretty_print(help, 0, 0);
+ }
+
+ fputc('\n', stdout);
+ return EXIT_SUCCESS;
+ }
+
+ fprintf(stderr, "Unknown command '%s'\n\n"
+ "Try `%s help' for a list of available commands\n",
+ argv[1], __progname);
+ return EXIT_FAILURE;
+}
+
+static command_t help = {
+ .cmd = "help",
+ .usage = "<command>",
+ .s_desc = "print a help text for a command",
+ .l_desc = "Print a help text for a specified command. If no command "
+ "is specified, print a generic help text and a list of "
+ "available commands.",
+ .run_cmd = cmd_help,
+};
+
+REGISTER_COMMAND(help)
diff --git a/cmd/service/list.c b/cmd/service/list.c
new file mode 100644
index 0000000..b9e342a
--- /dev/null
+++ b/cmd/service/list.c
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * Copyright (C) 2018 - David Oberhollenzer
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+#include "servicecmd.h"
+#include "service.h"
+#include "config.h"
+
+static void print_services(service_t *svc)
+{
+ const char *ptr;
+ int i;
+
+ for (; svc != NULL; svc = svc->next) {
+ printf("Name: %s\n", svc->name);
+ printf("\tDescrption: %s\n", svc->desc);
+ printf("\tType: %s\n", svc_type_to_string(svc->type));
+ printf("\tTarget: %s\n", svc_target_to_string(svc->target));
+
+ if (svc->type == SVC_RESPAWN && svc->rspwn_limit > 0)
+ printf("\tRespawn limit: %d\n", svc->rspwn_limit);
+
+ ptr = svc->before;
+ if (ptr != NULL && svc->num_before > 0) {
+ fputs("\tMust be run before:\n", stdout);
+
+ for (i = 0; i < svc->num_before; ++i) {
+ printf("\t\t%s\n", ptr);
+ ptr += strlen(ptr) + 1;
+ }
+ }
+
+ ptr = svc->after;
+ if (ptr != NULL && svc->num_after > 0) {
+ fputs("\tMust be run after:\n", stdout);
+
+ for (i = 0; i < svc->num_after; ++i) {
+ printf("\t\t%s\n", ptr);
+ ptr += strlen(ptr) + 1;
+ }
+ }
+ }
+}
+
+static int cmd_list(int argc, char **argv)
+{
+ int i, ret = EXIT_SUCCESS;
+ service_list_t list;
+
+ if (check_arguments(argv[0], argc, 1, 2))
+ return EXIT_FAILURE;
+
+ if (svcscan(SVCDIR, &list)) {
+ fprintf(stderr, "Error while reading services from %s\n",
+ SVCDIR);
+ ret = EXIT_FAILURE;
+ }
+
+ if (argc == 2) {
+ i = svc_target_from_string(argv[1]);
+
+ if (i == -1) {
+ fprintf(stderr, "Unknown target `%s'\n", argv[1]);
+ tell_read_help(argv[0]);
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ print_services(list.targets[i]);
+ } else {
+ for (i = 0; i < TGT_MAX; ++i)
+ print_services(list.targets[i]);
+ }
+out:
+ del_svc_list(&list);
+ return ret;
+}
+
+static command_t list = {
+ .cmd = "list",
+ .usage = "[target]",
+ .s_desc = "print a list of currently enabled services",
+ .l_desc = "Print a list of currently enabled services. If an "
+ "optional target is specified, print services for this "
+ "target.",
+ .run_cmd = cmd_list,
+};
+
+REGISTER_COMMAND(list)
diff --git a/cmd/service/servicecmd.c b/cmd/service/servicecmd.c
new file mode 100644
index 0000000..84e6a32
--- /dev/null
+++ b/cmd/service/servicecmd.c
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * Copyright (C) 2018 - David Oberhollenzer
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "servicecmd.h"
+#include "service.h"
+#include "config.h"
+#include "util.h"
+
+
+command_t *commands;
+
+extern char *__progname;
+
+
+void usage(int status)
+{
+ FILE *stream = (status == EXIT_SUCCESS ? stdout : stderr);
+ int padd = 0, len;
+ command_t *cmd;
+
+ fprintf(stream, "usage: %s <command> [args...]\n\n"
+ "Available commands:\n\n", __progname);
+
+ for (cmd = commands; cmd != NULL; cmd = cmd->next) {
+ len = strlen(cmd->cmd);
+
+ padd = len > padd ? len : padd;
+ }
+
+ for (cmd = commands; cmd != NULL; cmd = cmd->next) {
+ fprintf(stream, "%*s - %s\n",
+ (int)padd + 1, cmd->cmd, cmd->s_desc);
+ }
+
+ fprintf(stream, "\nTry `%s help <command>' for more information "
+ "on a specific command\n", __progname);
+
+ exit(status);
+}
+
+void tell_read_help(const char *cmd)
+{
+ fprintf(stderr, "Try `%s help %s' for more information.\n",
+ __progname, cmd);
+}
+
+int check_arguments(const char *cmd, int argc, int minc, int maxc)
+{
+ if (argc >= minc && argc <= maxc)
+ return 0;
+
+ fprintf(stderr, "Too %s arguments for `%s'\n",
+ argc > maxc ? "many" : "few", cmd);
+ tell_read_help(cmd);
+ return -1;
+}
+
+int main(int argc, char **argv)
+{
+ command_t *cmd;
+
+ if (argc < 2)
+ usage(EXIT_SUCCESS);
+
+ for (cmd = commands; cmd != NULL; cmd = cmd->next) {
+ if (!strcmp(cmd->cmd, argv[1])) {
+ return cmd->run_cmd(argc - 1, argv + 1);
+ }
+ }
+
+ fprintf(stderr, "Unknown command '%s'\n\n", argv[1]);
+ usage(EXIT_FAILURE);
+}
diff --git a/cmd/service/servicecmd.h b/cmd/service/servicecmd.h
new file mode 100644
index 0000000..b839799
--- /dev/null
+++ b/cmd/service/servicecmd.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * Copyright (C) 2018 - David Oberhollenzer
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+#ifndef SERVICECMD_H
+#define SERVICECMD_H
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "util.h"
+
+/*
+ Describes a command that can be launched by passing its name as
+ second command line argument to the main() function (i.e. immediately
+ after the actual program name).
+
+ Short and long descriptions can be provided to print out help text.
+
+ The main() function calls into a callback in this structure to execute
+ the command.
+*/
+typedef struct command_t {
+ struct command_t *next;
+
+ const char *cmd; /* command name */
+ const char *usage; /* list of possible arguments */
+ const char *s_desc; /* short description used by help */
+ const char *l_desc; /* long description used by help */
+
+ /*
+ Semantics are the same as for main(). Called from main()
+ function with first argument (i.e. top level program name)
+ removed.
+ */
+ int (*run_cmd)(int argc, char **argv);
+} command_t;
+
+/* Global list of available commands */
+extern command_t *commands;
+
+/*
+ Implemented in servicecmd.c. Prints program usage message and
+ terminates with the given exit status.
+*/
+void usage(int status) NORETURN;
+
+/*
+ Write a message to stderr that advises the user how to consult the
+ help text for a specific command.
+*/
+void tell_read_help(const char *cmd);
+
+/*
+ Check if the argument count is within specified bounds (minc and maxc
+ inclusive). If it is, return 0.
+
+ If it isn't, complain about a wrong number of arguments for a
+ command (cmd), tell the user to consult the help text and return -1.
+*/
+int check_arguments(const char *cmd, int argc, int minc, int maxc);
+
+/*
+ To implement a new command, add a global, static instance of a
+ command_t (or derived) structure to a C file and pass it to this
+ macro to have it automatically registered on program startup.
+*/
+#define REGISTER_COMMAND(cmd) \
+ static void __attribute__((constructor)) register_##cmd(void) \
+ { \
+ command_t *c = (command_t *)&cmd; \
+ \
+ c->next = commands; \
+ commands = c; \
+ }
+
+#endif /* SERVICECMD_H */
+