misc-progs: addonctrl: Add support for 'Services' metadata

* 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>
Reviewed-by: Michael Tremer <michael.tremer@ipfire.org>
This commit is contained in:
Robin Roevens
2022-10-12 00:01:53 +02:00
committed by Peter Müller
parent e728fba16f
commit 24168c8898

View File

@@ -10,71 +10,408 @@
#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
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 <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";
if (argc < 3) {
fprintf(stderr, "\nMissing arguments.\n\naddonctrl addon (start|stop|restart|reload|enable|disable)\n\n");
exit(1);
}
// 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;
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 <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 **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;
}