Hello again,
On 6 Oct 2022, at 18:59, Robin Roevens robin.roevens@disroot.org wrote:
- 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 | 397 +++++++++++++++++++++++++++++++------ 1 file changed, 336 insertions(+), 61 deletions(-)
diff --git a/src/misc-progs/addonctrl.c b/src/misc-progs/addonctrl.c index 14b4b1325..1687aac19 100644 --- a/src/misc-progs/addonctrl.c +++ b/src/misc-progs/addonctrl.c @@ -10,71 +10,346 @@ #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 *enabled_path = "/etc/rc.d/rc3.d"; +const char *disabled_path = "/etc/rc.d/rc3.d/off";
+char errormsg[BUFFER_SIZE] = ""; +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;
- if ((dp = opendir(path)) != NULL) {
while(found == NULL && (entry = readdir(dp)) != NULL)
if (fnmatch(filepattern, entry->d_name, FNM_PATHNAME) == 0)
found = strdup(entry->d_name);
closedir(dp);
- }
- return found;
+}
+// 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 *filter) {
- 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;
- if (addon == NULL) {
errno = EINVAL;
return NULL;
- }
- if (asprintf(&metafile, "%s%s", metafile_prefix, addon) == -1) {
errno = ENOMEM;
return NULL;
- }
You should initialise metafile with NULL before passing it to asprintf().
asprintf() will automatically set errno, actually.
- FILE *fp = fopen(metafile,"r");
- if (fp != NULL) {
Just FYI, I like writing this check as “if (!fp)” because it is a a lot shorter...
// Get initscript(s) for addon from meta-file
while (getline(&line, &line_len, fp) != -1 && services == NULL) {
// Strip newline
char *newline = strchr(line, '\n');
if (newline) *newline = 0;
Yes, this will cut off the trailing newline.
// 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) {
// Put each service in services array
service = strtok(token, service_delim);
while (service != NULL) {
// if filter is set, only select filtered service
if ((filter != NULL && strcmp(filter, service) == 0) ||
filter == NULL) {
services = reallocarray(services ,i+1 ,sizeof (char *));
if (services != NULL)
services[i++] = strdup(service);
Technically you could check whether strdup() was successful, but I would accept this version.
else
break;
}
service = strtok(NULL, service_delim);
}
}
}
}
free(line);
Are you sure that line will always be non-NULL?
What happens if you read an empty file and getline() exists immediately without ever allocating a buffer?
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 *initscript;
Same here. Please initialise it.
- char *argv[] = {
action,
NULL
- };
- int r = 0;
- if ((r = asprintf(&initscript, "%s/%s", initd_path, service)) != -1) {
r = run(initscript, argv);
free(initscript);
- } else {
errno = ENOMEM;
- }
This could look slightly cleaner as:
…
r = asprintf(&initscript, ….); if (r > 0) { r = run(initscript, argv); }
if (initscript) free(initscript);
return r;
Maybe… I don’t know.
- 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 = 1;
- char *filename = NULL;
- if ((filename = find_file_in_dir(src_path, filepattern)) != NULL) {
if ((r = asprintf(&src, "%s/%s", src_path, filename)) != -1 &&
(r = asprintf(&dest, "%s/%s", dest_path, filename) != -1)) {
// move initscript
r = rename(src, dest);
} else {
errno = ENOMEM;
}
if (src != NULL)
free(src);
if (dest != NULL)
free(dest);
- } else {
if ((filename = find_file_in_dir(dest_path, filepattern)) == NULL)
r = 2;
- }
- if (filename != NULL)
free(filename);
- 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;
Init to NULL.
- int r = 0;
- if (asprintf(&filepattern, "S??%s", service) == -1) {
errno = ENOMEM;
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
- errno = 0;
- 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;
NULL. How does this even run without it?
- if (asprintf(&filepattern, "S??%s", service) == -1) {
errno = ENOMEM;
return;
- }
- if (find_file_in_dir(enabled_path, filepattern) != NULL)
fprintf(stdout, "%s is enabled on boot.\n", service);
- else if (find_file_in_dir(disabled_path, filepattern) != 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;
- 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);
- }
- 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
- errno = 0;
There is usually no need to reset this.
- services = get_addon_services(addon, &servicescnt, service_filter);
- if (services == NULL || *services == 0) {
if (errno != 0)
This is not reliable error checking. The function should return a defined value if an error has occurred (e.g. NULL) and an error is detected, that error should be handled.
So in this case:
services = get_addon_services(…); if (!services) { printf(“ERROR: …\”);
}
… Other code, on success…
Now, NULL is not a very good indicator because your problem might be that an error has been found and therefore the function aborted, or that there is actually no services available for this package.
You could handle this by setting errno to something like ENOENT (No such file or directory) and handle that separately:
services = get_addon_services(…); if (!services) { switch (errno) { case ENOENT: DO SOMETHING; Break;
default: printf(“ERROR: …\”); DO SOMETING ELSE }
Finally, instead of printing the errno number in the error message, you can use %m (without any argument) which will be automatically converted into something human readable.
fprintf(stderr, "\nSystem error occured. (Error: %d)\n\n", errno);
else if (strcmp(errormsg, "") != 0)
fprintf(stderr, "\n%s\n\n", errormsg);
else if (service_filter != NULL)
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);
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) {
errno = 0;
for(int i = 0; i < servicescnt; i++) {
if (initscript_action(services[i], action) != 0) {
r = 1;
if (errno != 0)
fprintf(stderr, "\nSystem error occured. (Error: %d)\n\n", errno);
break;
}
}
- } else if (strcmp(action, "enable") == 0 ||
strcmp(action, "disable") == 0) {
errno = 0;
for(int i = 0; i < servicescnt; i++) {
if (toggle_service(services[i], action) == 0) {
fprintf(stdout, "%sd service %s\n", action, services[i]);
If you want to just print something, just use printf.
} else if (errno != 0) {
r = 1;
fprintf(stderr, "\nSystem error occured. (Error: %d)\n\n", errno);
break;
} else {
r = 1;
fprintf(stderr, "\n%s\n\n", errormsg);
}
}
- } else if (strcmp(action, "boot-status") == 0) {
errno = 0;
for(int i = 0; i < servicescnt; i++) {
print_boot_status(services[i]);
if (errno != 0) {
r = 1;
fprintf(stderr, "\nSystem error occured. (Error: %d)\n\n", errno);
break;
}
}
- } else if (strcmp(action, "list-services") == 0) {
fprintf(stdout, "\nServices for addon %s:\n", addon);
for(int i = 0; i < servicescnt; i++) {
fprintf(stdout, " %s\n", services[i]);
}
fprintf(stdout, "\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;
}
Otherwise, this looks a lot better since the last version :)
-Michael
-- 2.37.3
-- Dit bericht is gescanned op virussen en andere gevaarlijke inhoud door MailScanner en lijkt schoon te zijn.