diff options
author | David Oberhollenzer <david.oberhollenzer@tele2.at> | 2018-04-12 05:30:12 +0200 |
---|---|---|
committer | David Oberhollenzer <david.oberhollenzer@tele2.at> | 2018-04-12 10:28:40 +0200 |
commit | 6cf0a254cd9e4892a941bb5ad6f8d9ed317b1617 (patch) | |
tree | ad9f1b409c5a465986078ee16b102f6fa6cfbdb3 /cmd | |
parent | e148e873e05e4ebe4d7a90cdbe8cdd72618eb7ac (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.am | 15 | ||||
-rw-r--r-- | cmd/service/disable.c | 89 | ||||
-rw-r--r-- | cmd/service/dumpscript.c | 182 | ||||
-rw-r--r-- | cmd/service/enable.c | 98 | ||||
-rw-r--r-- | cmd/service/help.c | 129 | ||||
-rw-r--r-- | cmd/service/list.c | 102 | ||||
-rw-r--r-- | cmd/service/servicecmd.c | 90 | ||||
-rw-r--r-- | cmd/service/servicecmd.h | 93 |
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 */ + |