#include <string.h>
#include <unistd.h>
#include <sys/types.h>
+#include <sys/stat.h>
#include <fcntl.h>
+#include <dirent.h>
+#include <fnmatch.h>
+#include <errno.h>
#include "setuid.h"
#define BUFFER_SIZE 1024
+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";
+
+const 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";
+
+// Find a file <path> using <filepattern> 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;
+
+ 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);
+ }
+
+ closedir(dp);
+ }
+
+ return found;
+}
+
+// Reads Services metadata for <addon>.
+// Returns pointer to array of strings containing the services for <addon>,
+// sets <servicescnt> to the number of found services and
+// sets <returncode> to
+// -1 - system error occured, check errno
+// 0 - success - if returned array is NULL, there are no services for <addon>
+// 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 <service> with parameter <action>
+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 <src_path> to <dest_path>
+// Returns:
+// -1: Error during move or memory allocation. Details in errno
+// 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 = 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 <service> is enabled or disabled on boot
+// Returns:
+// -1 - System error occured. Check errno.
+// 0 - <service> is disabled on boot
+// 1 - <service> is enabled on boot
+// 2 - Runlevel suymlink for <service> 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 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;
+ 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;
}