diff --git a/src/misc-progs/addonctrl.c b/src/misc-progs/addonctrl.c index 14b4b1325..8eb7fbfa5 100644 --- a/src/misc-progs/addonctrl.c +++ b/src/misc-progs/addonctrl.c @@ -10,71 +10,408 @@ #include #include #include +#include #include +#include +#include +#include #include "setuid.h" #define BUFFER_SIZE 1024 -int main(int argc, char *argv[]) { - char command[BUFFER_SIZE]; +const char *initd_path = "/etc/rc.d/init.d"; +const char *enabled_path = "/etc/rc.d/rc3.d"; +const char *disabled_path = "/etc/rc.d/rc3.d/off"; - if (!(initsetuid())) - exit(1); +const char *usage = + "Usage\n" + " addonctrl (start|stop|restart|reload|enable|disable|status|boot-status|list-services) []\n" + "\n" + "Options:\n" + " \t\tName of the addon to control\n" + " \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"; - if (argc < 3) { - fprintf(stderr, "\nMissing arguments.\n\naddonctrl addon (start|stop|restart|reload|enable|disable)\n\n"); - exit(1); - } +// Find a file using as glob pattern. +// Returns the found filename or NULL if not found +char *find_file_in_dir(const char *path, const char *filepattern) +{ + struct dirent *entry; + DIR *dp; + char *found = NULL; - const char* name = argv[1]; + dp = opendir(path); + if (dp) { + entry = readdir(dp); + while(!found && entry) { + if (fnmatch(filepattern, entry->d_name, FNM_PATHNAME) == 0) + found = strdup(entry->d_name); + else + entry = readdir(dp); + } - if (strlen(name) > 32) { - fprintf(stderr, "\nString to large.\n\naddonctrl addon (start|stop|restart|reload|enable|disable)\n\n"); - exit(1); - } + closedir(dp); + } - // 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; + return found; +} + +// Reads Services metadata for . +// Returns pointer to array of strings containing the services for , +// sets to the number of found services and +// sets to +// -1 - system error occured, check errno +// 0 - success - if returned array is NULL, there are no services for +// 1 - addon was not found +char **get_addon_services(const char *addon, int *servicescnt, const char *filter, int *returncode) { + 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 = NULL; + size_t line_len = 0; + int i = 0; + char *metafile = NULL; + + *returncode = 0; + + if (!addon) { + errno = EINVAL; + *returncode = 1; + return NULL; + } + + *returncode = asprintf(&metafile, "%s%s", metafile_prefix, addon); + if (*returncode == -1) + return NULL; + + FILE *fp = fopen(metafile,"r"); + if (!fp) { + if (errno == ENOENT) { + *returncode = 1; + } else { + *returncode = -1; + } + return NULL; + } + + // Get initscript(s) for addon from meta-file + while (getline(&line, &line_len, fp) != -1 && !services) { + // Strip newline + char *newline = strchr(line, '\n'); + if (newline) *newline = 0; + + // Split line in key and values; Check for required key. + token = strtok(line, keyvalue_delim); + if (!token || strcmp(token, metadata_key) != 0) + continue; + + // Get values for matched key. Stop if no values are present. + token = strtok(NULL, keyvalue_delim); + if (!token) + break; + + // Split values and put each service in services array + service = strtok(token, service_delim); + while (service) { + if (!filter || strcmp(filter, service) == 0) { + services = reallocarray(services, i+1 ,sizeof (char *)); + if (!services) { + *returncode = -1; + break; + } + + services[i] = strdup(service); + if (!services[i++]) { + *returncode = -1; + break; + } + } + + service = strtok(NULL, service_delim); + } + } + + if (line) free(line); + fclose(fp); + free(metafile); + + *servicescnt = i; + + return services; +} + +// Calls initscript with parameter +int initscript_action(const char *service, const char *action) { + char *initscript = NULL; + char *argv[] = { + action, + NULL + }; + int r = 0; + + r = asprintf(&initscript, "%s/%s", initd_path, service); + if (r != -1) + r = run(initscript, argv); + + if (initscript) free(initscript); + + return r; +} + +// Move an initscript with filepattern from to +// Returns: +// -1: Error during move or memory allocation. Details in errno +// 0: Success +// 1: file was not moved, but is already in +// 2: file does not exist in either in or +int move_initscript_by_pattern(const char *src_path, const char *dest_path, const char *filepattern) { + char *src = NULL; + char *dest = NULL; + int r = 2; + char *filename = NULL; + + filename = find_file_in_dir(src_path, filepattern); + if (filename) { + // Move file + r = asprintf(&src, "%s/%s", src_path, filename); + if (r != -1) { + r = asprintf(&dest, "%s/%s", dest_path, filename); + if (r != -1) + r = rename(src, dest); + } + + if (src) free(src); + if (dest) free(dest); + } else { + // check if file is already in dest + filename = find_file_in_dir(dest_path, filepattern); + if (filename) + r = 1; + } + + if (filename) free(filename); + + return r; +} + +// Enable/Disable addon service(s) by moving initscript symlink from/to disabled_path +// Returns: +// -1 - System error occured. Check errno. +// 0 - Success +// 1 - Service was already enabled/disabled +// 2 - Service has no valid runlevel symlink +int toggle_service(const char *service, const char *action) { + const char *src_path, *dest_path; + char *filepattern = NULL; + int r = 0; + + if (asprintf(&filepattern, "S??%s", service) == -1) + return -1; + + 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 + r = mkdir(disabled_path, S_IRWXU + S_IRGRP + S_IXGRP + S_IROTH + S_IXOTH); + if (r != -1 || errno == EEXIST) + r = move_initscript_by_pattern(src_path, dest_path, filepattern); + + free(filepattern); + + return r; +} + +// Return whether is enabled or disabled on boot +// Returns: +// -1 - System error occured. Check errno. +// 0 - is disabled on boot +// 1 - is enabled on boot +// 2 - Runlevel suymlink for was not found +int get_boot_status(char *service) { + char *filepattern = NULL; + char *filename = NULL; + int r = 2; + + if (asprintf(&filepattern, "S??%s", service) == -1) + return -1; + + filename = find_file_in_dir(enabled_path, filepattern); + if (filename) + r = 1; + else { + filename = find_file_in_dir(disabled_path, filepattern); + if (filename) + r = 0; + else + r = 2; + } + + if (filename) free(filename); + free(filepattern); + + return r; +} + +int main(int argc, char *argv[]) { + char **services = NULL; + int servicescnt = 0; + char *addon = argv[1]; + char *action = argv[2]; + char *service_filter = NULL; + int r = 0; + + if (!(initsetuid())) + exit(1); + + if (argc < 3) { + fprintf(stderr, "\nMissing arguments.\n\n%s\n\n", usage); + exit(1); + } + + // Ignore filter when list of services is requested + if (argc == 4 && strcmp(action, "list-services") != 0) + service_filter = argv[3]; + + if (strlen(addon) > 32) { + fprintf(stderr, "\nString too 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 + int rc = 0; + services = get_addon_services(addon, &servicescnt, service_filter, &rc); + if (!services) { + switch (rc) { + case -1: + fprintf(stderr, "\nSystem error occured. (Error: %m)\n\n"); + break; + + case 0: + if (service_filter) + 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); + else + fprintf(stderr, "\nAddon '%s' has no services.\n\n", addon); + break; + + case 1: + fprintf(stderr, "\nAddon '%s' not found.\n\n%s\n\n", addon, usage); + break; + } + exit(1); + } + + // Handle requested action + if (strcmp(action, "start") == 0 || + strcmp(action, "stop") == 0 || + strcmp(action, "restart") == 0 || + strcmp(action, "reload") == 0 || + strcmp(action, "status") == 0) { + + for(int i = 0; i < servicescnt; i++) { + if (initscript_action(services[i], action) < 0) { + r = 1; + fprintf(stderr, "\nSystem error occured. (Error: %m)\n\n"); + break; + } + } + + } else if (strcmp(action, "enable") == 0 || + strcmp(action, "disable") == 0) { + + for(int i = 0; i < servicescnt; i++) { + switch (r = toggle_service(services[i], action)) { + case -1: + fprintf(stderr, "\nSystem error occured. (Error: %m)\n\n"); + break; + + case 0: + printf("%sd service %s\n", action, services[i]); + break; + + case 1: + fprintf(stderr, "Service %s is already %sd. Skipping...\n", services[i], action); + break; + + case 2: + fprintf(stderr, "\nUnable to %s service %s. (Service has no valid runlevel symlink).\n\n", action, services[i]); + break; + } + + // Break from for loop in case of a system error. + if (r == -1) { + r = 1; + break; + } + } + + } else if (strcmp(action, "boot-status") == 0) { + // Print boot status for each service + for(int i = 0; i < servicescnt; i++) { + switch (get_boot_status(services[i])) { + case -1: + r = 1; + fprintf(stderr, "\nSystem error occured. (Error: %m)\n\n"); + break; + + case 0: + printf("%s is disabled on boot.\n", services[i]); + break; + + case 1: + printf("%s is enabled on boot.\n", services[i]); + break; + + case 2: + printf("%s is not available for boot. (Service has no valid symlink in either %s or %s).\n", services[i], enabled_path, disabled_path); + break; + } + + // Break from for loop in case of an error + if (r == 1) { + break; + } + } + + } else if (strcmp(action, "list-services") == 0) { + // List all services for addon + printf("\nServices for addon %s:\n", addon); + for(int i = 0; i < servicescnt; i++) { + printf(" %s\n", services[i]); + } + printf("\n"); + + } else { + fprintf(stderr, "\nBad argument given.\n\n%s\n\n", usage); + r = 1; + } + + // Cleanup + for(int i = 0; i < servicescnt; i++) + free(services[i]); + free(services); + + return r; }