* Addonctrl will now check in addon metadata for the exact initscript names (Services). If more than one initscript is defined for an addon, the requested action will be performed on all listed initscripts. * Added posibility to perform action on a specific initscript of an addon instead of on all initscripts of the addon. * New action 'list-services' to display a list of services related to an addon. * New action 'boot-status' to display wether service(s) are enabled to start on boot or not. * More error checking and cleaner error reporting to user * General cleanup and code restructuring to avoid code duplication * Updated and made usage instructions more verbose.
Fixes: Bug#12935 Signed-off-by: Robin Roevens robin.roevens@disroot.org --- src/misc-progs/addonctrl.c | 384 +++++++++++++++++++++++++++++++------ 1 file changed, 323 insertions(+), 61 deletions(-)
diff --git a/src/misc-progs/addonctrl.c b/src/misc-progs/addonctrl.c index 14b4b1325..277bd1809 100644 --- a/src/misc-progs/addonctrl.c +++ b/src/misc-progs/addonctrl.c @@ -10,71 +10,333 @@ #include <string.h> #include <unistd.h> #include <sys/types.h> +#include <sys/stat.h> #include <fcntl.h> +#include <dirent.h> +#include <errno.h> #include "setuid.h"
#define BUFFER_SIZE 1024
+const char *enabled_path = "/etc/rc.d/rc3.d"; +const char *disabled_path = "/etc/rc.d/rc3.d/off"; + +char errormsg[BUFFER_SIZE] = ""; +char *usage = + "Usage\n" + " addonctrl <addon> (start|stop|restart|reload|enable|disable|status|boot-status|list-services) [<service>]\n" + "\n" + "Options:\n" + " <addon>\t\tName of the addon to control\n" + " <service>\t\tSpecific service of the addon to control (optional)\n" + " \t\t\tBy default the requested action is performed on all related services. See also 'list-services'.\n" + " start\t\t\tStart service(s) of the addon\n" + " stop\t\t\tStop service(s) of the addon\n" + " restart\t\tRestart service(s) of the addon\n" + " enable\t\tEnable service(s) of the addon to start at boot\n" + " disable\t\tDisable service(s) of the addon to start at boot\n" + " status\t\tDisplay current state of addon service(s)\n" + " boot-status\t\tDisplay wether service(s) is enabled on boot or not\n" + " list-services\t\tDisplay a list of services related to the addon"; + +// Check if <text> matches <pattern>. Wildcard '?' matches any single character. +// Returns 1 when found, 0 when not found +int match(const char *pattern, const char *text) { + if (pattern[0] == '\0' && *text == '\0') + return 1; + if (*text != '\0' && (pattern[0]=='?' || pattern[0]==*text)) + return match(pattern+1, text+1); + return 0; +} + +// Find a file <path> using <filepattern> allowing the '?' wildcard. +// Returns the found filename or NULL if not found +char* find_file_in_dir(const char *path, const char *filepattern) +{ + static struct dirent *entry; + DIR *dp; + int found = 0; + + if ((dp = opendir(path)) != NULL) { + while(found == 0 && (entry = readdir(dp)) != NULL) + found = match(filepattern, entry->d_name); + + closedir(dp); + } + + if (! found) { + return NULL; + } + + return entry->d_name; +} + +// Reads Services metadata for <addon>. +// Returns pointer to array of strings containing the services for <addon> +// and sets <servicescnt> to the number of found services +char **get_addon_services(const char *addon, int *servicescnt) { + const char *metafile_prefix = "/opt/pakfire/db/installed/meta-"; + const char *metadata_key = "Services"; + const char *keyvalue_delim = ":"; + const char *service_delim = " "; + char *token; + char **services = NULL; + char *service; + char line[BUFFER_SIZE]; + int i = 0; + char *metafile = malloc((strlen(metafile_prefix) + strlen(addon) + 1) * sizeof(char)); + + sprintf(metafile, "%s%s", metafile_prefix, addon); + FILE *fp = fopen(metafile,"r"); + if ( fp ) { + // Get initscript(s) for addon from meta-file + while (!feof(fp) && services == NULL) { + if (fgets(line, BUFFER_SIZE - 1, fp) != NULL) { + // Strip newline + char *newline = strchr(line, '\n'); + if (newline) *newline = 0; + + // Parse key/value and look for required key. + token = strtok(line, keyvalue_delim); + if (token != NULL && strcmp(token, metadata_key) == 0) { + token = strtok(NULL, keyvalue_delim); + if (token != NULL) { + services = malloc((strlen(token) + 1) * sizeof (char *)); + + // Put each service in services array + service = strtok(token, service_delim); + while (service != NULL) { + services[i] = malloc((strlen(service) + 1) * sizeof (char)); + strcpy(services[i++], service); + + service = strtok(NULL, service_delim); + } + } + } + } else { + snprintf(errormsg, BUFFER_SIZE - 1, "Could not read '%s' metadata for addon '%s'.", metadata_key, addon); + } + } + fclose(fp); + } else { + snprintf(errormsg, BUFFER_SIZE - 1, "Addon '%s' not found.\n\n%s", addon, usage); + } + + free (metafile); + *servicescnt = i; + return services; +} + +// Calls initscript <service> with parameter <action> +int initscript_action(const char *service, const char *action) { + const char *initd_path = "/etc/rc.d/init.d"; + char *command = malloc((strlen(initd_path) + 1 + strlen(service) + 1) * sizeof(char)); + int r = 0; + + sprintf(command, "%s/%s %s", initd_path, service, action); + r = safe_system(command); + free(command); + + return r; +} + +// Move an initscript with filepattern from <src_path> to <dest_path> +// Returns: +// -1: Error during move. Details in errno (returned by C rename function) +// 0: Success +// 1: file was not moved, but is already in <dest_path> +// 2: file does not exist in either in <src_path> or <dest_path> +int move_initscript_by_pattern(const char *src_path, const char *dest_path, const char *filepattern) { + char *src, *dest; + int r = 1; + char *filename = find_file_in_dir(src_path, filepattern); + + if ( filename != NULL ) { + src = malloc((strlen(src_path) + 1 + strlen(filename) + 1) * sizeof(char)); + dest = malloc((strlen(dest_path) + 1 + strlen(filename) + 1) * sizeof(char)); + sprintf(src, "%s/%s", src_path, filename); + sprintf(dest, "%s/%s", dest_path, filename); + + r = rename(src, dest); + + free(src); + free(dest); + } else { + filename = find_file_in_dir(dest_path, filepattern); + if (filename == NULL) + r = 2; + } + + return r; +} + +// Enable/Disable addon service(s) by moving initscript symlink from/to disabled_path +int toggle_service(const char *service, const char *action) { + const char *src_path, *dest_path; + char *filepattern = malloc((3 + strlen(service) + 1) * sizeof(char)); + int r = 0; + + sprintf(filepattern, "S??%s", service); + + if (strcmp(action, "enable") == 0) { + src_path = disabled_path; + dest_path = enabled_path; + } else { + src_path = enabled_path; + dest_path = disabled_path; + } + + // Ensure disabled_path exists + if (mkdir(disabled_path, S_IRWXU + S_IRGRP + S_IXGRP + S_IROTH + S_IXOTH) == -1 && errno != EEXIST) { + r = 1; + snprintf(errormsg, BUFFER_SIZE -1, "Error creating %s. (Error: %d)", disabled_path, errno); + } else { + r = move_initscript_by_pattern(src_path, dest_path, filepattern); + if (r == -1 ) { + r = 1; + snprintf(errormsg, BUFFER_SIZE - 1, "Could not %s %s. (Error: %d)", action, service, errno); + } else if (r == 1) { + snprintf(errormsg, BUFFER_SIZE - 1, "Service %s is already %sd. Skipping...", service, action); + } else if (r == 2) { + snprintf(errormsg, BUFFER_SIZE - 1, "Unable to %s service %s. (Service has no valid symlink in %s).", action, service, src_path); + } + } + + free(filepattern); + + return r; +} + +// Print to stdout wether <service> is enabled or disabled on boot +// Prints <service> as Not available when initscript is not found +// in either enabled_path or disabled_path. +void print_boot_status(char *service) { + char *filepattern = malloc((3 + strlen(service) + 1) * sizeof(char)); + sprintf(filepattern, "S??%s", service); + char *enabled = find_file_in_dir(enabled_path, filepattern); + char *disabled = find_file_in_dir(disabled_path, filepattern); + + if (enabled != NULL) + fprintf(stdout, "%s is enabled on boot.\n", service); + else if (disabled != NULL) + fprintf(stdout, "%s is disabled on boot.\n", service); + else + fprintf(stdout, "%s is not available for boot. (Service has no valid symlink in either %s or %s).\n", service, enabled_path, disabled_path); + + free(filepattern); +} + int main(int argc, char *argv[]) { - char command[BUFFER_SIZE]; - - if (!(initsetuid())) - exit(1); - - if (argc < 3) { - fprintf(stderr, "\nMissing arguments.\n\naddonctrl addon (start|stop|restart|reload|enable|disable)\n\n"); - exit(1); - } - - const char* name = argv[1]; - - if (strlen(name) > 32) { - fprintf(stderr, "\nString to large.\n\naddonctrl addon (start|stop|restart|reload|enable|disable)\n\n"); - exit(1); - } - - // Check if the input argument is valid - if (!is_valid_argument_alnum(name)) { - fprintf(stderr, "Invalid add-on name: %s\n", name); - exit(2); - } - - sprintf(command, "/opt/pakfire/db/installed/meta-%s", name); - FILE *fp = fopen(command,"r"); - if ( fp ) { - fclose(fp); - } else { - fprintf(stderr, "\nAddon '%s' not found.\n\naddonctrl addon (start|stop|restart|reload|status|enable|disable)\n\n", name); - exit(1); - } - - if (strcmp(argv[2], "start") == 0) { - snprintf(command, BUFFER_SIZE - 1, "/etc/rc.d/init.d/%s start", name); - safe_system(command); - } else if (strcmp(argv[2], "stop") == 0) { - snprintf(command, BUFFER_SIZE - 1, "/etc/rc.d/init.d/%s stop", name); - safe_system(command); - } else if (strcmp(argv[2], "restart") == 0) { - snprintf(command, BUFFER_SIZE - 1, "/etc/rc.d/init.d/%s restart", name); - safe_system(command); - } else if (strcmp(argv[2], "reload") == 0) { - snprintf(command, BUFFER_SIZE - 1, "/etc/rc.d/init.d/%s reload", name); - safe_system(command); - } else if (strcmp(argv[2], "status") == 0) { - snprintf(command, BUFFER_SIZE - 1, "/etc/rc.d/init.d/%s status", name); - safe_system(command); - } else if (strcmp(argv[2], "enable") == 0) { - snprintf(command, BUFFER_SIZE - 1, "mv -f /etc/rc.d/rc3.d/off/S??%s /etc/rc.d/rc3.d" , name); - safe_system(command); - } else if (strcmp(argv[2], "disable") == 0) { - snprintf(command, BUFFER_SIZE - 1, "mkdir -p /etc/rc.d/rc3.d/off"); - safe_system(command); - snprintf(command, BUFFER_SIZE - 1, "mv -f /etc/rc.d/rc3.d/S??%s /etc/rc.d/rc3.d/off" , name); - safe_system(command); - } else { - fprintf(stderr, "\nBad argument given.\n\naddonctrl addon (start|stop|restart|reload|enable|disable)\n\n"); - exit(1); - } - - return 0; + char **services = NULL; + char **services_ptr = NULL; + int servicescnt = 0; + char *addon = argv[1]; + char *action = argv[2]; + char *service_filter = NULL; + int actioned = 0; + int r = 0; + + if (!(initsetuid())) + exit(1); + + if (argc < 3) { + fprintf(stderr, "\nMissing arguments.\n\n%s\n\n", usage); + exit(1); + } + + if (argc == 4) + service_filter = argv[3]; + + if (strlen(addon) > 32) { + fprintf(stderr, "\nString to large.\n\n%s\n\n", usage); + exit(1); + } + + // Check if the input argument is valid + if (!is_valid_argument_alnum(addon)) { + fprintf(stderr, "Invalid add-on name: %s.\n", addon); + exit(2); + } + + // Get initscript name(s) from addon metadata + services_ptr = get_addon_services(addon, &servicescnt); + services = services_ptr; + if (services == NULL || *services == 0) { + if (strcmp(errormsg, "") != 0) + fprintf(stderr, "\n%s\n\n", errormsg); + else + fprintf(stderr, "\nAddon '%s' has no services.\n\n", addon); + exit(1); + } + + if (strcmp(action, "start") == 0 || + strcmp(action, "stop") == 0 || + strcmp(action, "restart") == 0 || + strcmp(action, "reload") == 0 || + strcmp(action, "status") == 0) { + + while (*services != 0) { + if ((service_filter != NULL && strcmp(service_filter, *services) == 0) || + service_filter == NULL) { + if (initscript_action(*services, action) != 0) + r = 1; + ++actioned; + } + ++services; + } + + } else if (strcmp(action, "enable") == 0 || + strcmp(action, "disable") == 0) { + + while (*services != 0) { + if ((service_filter != NULL && strcmp(service_filter, *services) == 0) || + service_filter == NULL) { + if (toggle_service(*services, action) == 0) { + fprintf(stdout, "%sd service %s\n", action, *services); + } + else { + r = 1; + fprintf(stderr, "\n%s\n\n", errormsg); + } + + ++actioned; + } + ++services; + } + + } else if (strcmp(action, "boot-status") == 0) { + while(*services != 0) { + if ((service_filter != NULL && strcmp(service_filter, *services) == 0) || + service_filter == NULL) { + print_boot_status(*services); + ++actioned; + } + ++services; + } + + } else if (strcmp(action, "list-services") == 0) { + fprintf(stdout, "\nServices for addon %s:\n", addon); + while (*services != 0) { + fprintf(stdout, " %s\n", *services); + ++actioned; + ++services; + } + fprintf(stdout, "\n"); + + } else { + fprintf(stderr, "\nBad argument given.\n\n%s\n\n", usage); + r = 1; + } + + if (r == 0 && service_filter != NULL && actioned == 0) { + fprintf(stderr, "\nNo service %s found for addon %s. Use 'list-services' to get a list of available services\n\n%s\n\n", service_filter, addon, usage); + r = 1; + } + + // Cleanup + for(int i = 0; i < servicescnt; i++) + free(services_ptr[i]); + free(services_ptr); + + return r; }