X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fplugin.c;h=f9049ecfa77458285c07da66f4946f542a1b4959;hb=36e3f4589afc5bf3d17c0584719a86a577340fa0;hp=79515d8f079cbee7e54b3d552ea2a87315fbb05a;hpb=041b59e39d320fff89e02a853cd162ba88b909cf;p=ncmpc.git diff --git a/src/plugin.c b/src/plugin.c index 79515d8..f9049ec 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -1,6 +1,6 @@ -/* - * (c) 2004-2008 The Music Player Daemon Project - * http://www.musicpd.org/ +/* ncmpc (Ncurses MPD Client) + * (c) 2004-2017 The Music Player Daemon Project + * Project homepage: http://musicpd.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -11,12 +11,14 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "plugin.h" +#include "Compiler.h" #include #include @@ -26,6 +28,18 @@ #include #include +struct plugin_pipe { + struct plugin_cycle *cycle; + + /** the pipe to the plugin process, or -1 if none is currently + open */ + int fd; + /** the GLib IO watch of #fd */ + guint event_id; + /** the output of the current plugin */ + GString *data; +}; + struct plugin_cycle { /** the plugin list; used for traversing to the next plugin */ struct plugin_list *list; @@ -45,52 +59,58 @@ struct plugin_cycle { /** the pid of the plugin process, or -1 if none is currently running */ pid_t pid; - /** the pipe to the plugin process, or -1 if none is currently - open */ - int fd; - /** the GLib channel of #fd */ - GIOChannel *channel; - /** the GLib IO watch of #channel */ - guint event_id; - /** the output of the current plugin */ - GString *data; + /** the stdout pipe */ + struct plugin_pipe pipe_stdout; + /** the stderr pipe */ + struct plugin_pipe pipe_stderr; + + /** list of all error messages from failed plugins */ + GString *all_errors; }; static bool register_plugin(struct plugin_list *list, char *path) { - int ret; struct stat st; - - ret = stat(path, &st); - if (ret < 0) + if (stat(path, &st) < 0) return false; g_ptr_array_add(list->plugins, path); return true; } +static gint +plugin_compare_func_alpha(gconstpointer plugin1, gconstpointer plugin2) +{ + return strcmp(* (char * const *) plugin1, * (char * const *) plugin2); +} + +static void +plugin_list_sort(struct plugin_list *list, GCompareFunc compare_func) +{ + g_ptr_array_sort(list->plugins, + compare_func); +} + bool plugin_list_load_directory(struct plugin_list *list, const char *path) { - GDir *dir; - const char *name; - char *plugin; - bool ret; - - dir = g_dir_open(path, 0, NULL); + GDir *dir = g_dir_open(path, 0, NULL); if (dir == NULL) return false; + const char *name; while ((name = g_dir_read_name(dir)) != NULL) { - plugin = g_build_filename(path, name, NULL); - ret = register_plugin(list, plugin); - if (!ret) + char *plugin = g_build_filename(path, name, NULL); + if (!register_plugin(list, plugin)) g_free(plugin); } g_dir_close(dir); + + plugin_list_sort(list, plugin_compare_func_alpha); + return true; } @@ -105,51 +125,64 @@ static void next_plugin(struct plugin_cycle *cycle); static void -plugin_eof(struct plugin_cycle *cycle) +plugin_eof(struct plugin_cycle *cycle, struct plugin_pipe *p) { - int ret, status; + close(p->fd); + p->fd = -1; - g_io_channel_unref(cycle->channel); - close(cycle->fd); - cycle->fd = -1; + /* Only if both pipes are have EOF status we are done */ + if (cycle->pipe_stdout.fd != -1 || cycle->pipe_stderr.fd != -1) + return; - ret = waitpid(cycle->pid, &status, 0); + int status, ret = waitpid(cycle->pid, &status, 0); cycle->pid = -1; if (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { + /* If we encountered an error other than service unavailable + * (69), log it for later. If all plugins fail, we may get + * some hints for debugging.*/ + if (cycle->pipe_stderr.data->len > 0 && + WEXITSTATUS(status) != 69) + g_string_append_printf(cycle->all_errors, + "*** %s ***\n\n%s\n", + cycle->argv[0], + cycle->pipe_stderr.data->str); + /* the plugin has failed */ - g_string_free(cycle->data, TRUE); - cycle->data = NULL; + g_string_free(cycle->pipe_stdout.data, TRUE); + cycle->pipe_stdout.data = NULL; + g_string_free(cycle->pipe_stderr.data, TRUE); + cycle->pipe_stderr.data = NULL; next_plugin(cycle); } else { /* success: invoke the callback */ - cycle->callback(cycle->data, cycle->callback_data); + cycle->callback(cycle->pipe_stdout.data, true, + cycle->argv[0], cycle->callback_data); } } static gboolean -plugin_data(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, gpointer data) +plugin_data(gcc_unused GIOChannel *source, + gcc_unused GIOCondition condition, gpointer data) { - struct plugin_cycle *cycle = data; - char buffer[256]; - ssize_t nbytes; + struct plugin_pipe *p = data; + assert(p->fd >= 0); + struct plugin_cycle *cycle = p->cycle; assert(cycle != NULL); - assert(cycle->fd >= 0); assert(cycle->pid > 0); - assert(source == cycle->channel); - nbytes = condition & G_IO_IN - ? read(cycle->fd, buffer, sizeof(buffer)) + char buffer[256]; + ssize_t nbytes = condition & G_IO_IN + ? read(p->fd, buffer, sizeof(buffer)) : 0; if (nbytes <= 0) { - plugin_eof(cycle); + plugin_eof(cycle, p); return FALSE; } - g_string_append_len(cycle->data, buffer, nbytes); + g_string_append_len(p->data, buffer, nbytes); return TRUE; } @@ -166,48 +199,70 @@ plugin_delayed_fail(gpointer data) struct plugin_cycle *cycle = data; assert(cycle != NULL); - assert(cycle->fd < 0); + assert(cycle->pipe_stdout.fd < 0); + assert(cycle->pipe_stderr.fd < 0); assert(cycle->pid < 0); - assert(cycle->data == NULL); - cycle->callback(NULL, cycle->callback_data); + cycle->callback(cycle->all_errors, false, NULL, cycle->callback_data); return FALSE; } +static void +plugin_fd_add(struct plugin_cycle *cycle, struct plugin_pipe *p, int fd) +{ + p->cycle = cycle; + p->fd = fd; + p->data = g_string_new(NULL); + GIOChannel *channel = g_io_channel_unix_new(fd); + p->event_id = g_io_add_watch(channel, G_IO_IN|G_IO_HUP, + plugin_data, p); + g_io_channel_unref(channel); +} + static int start_plugin(struct plugin_cycle *cycle, const char *plugin_path) { - int ret, fds[2]; - pid_t pid; - assert(cycle != NULL); assert(cycle->pid < 0); - assert(cycle->fd < 0); - assert(cycle->data == NULL); + assert(cycle->pipe_stdout.fd < 0); + assert(cycle->pipe_stderr.fd < 0); + assert(cycle->pipe_stdout.data == NULL); + assert(cycle->pipe_stderr.data == NULL); /* set new program name, but free the one from the previous plugin */ g_free(cycle->argv[0]); cycle->argv[0] = g_path_get_basename(plugin_path); - ret = pipe(fds); - if (ret < 0) + int fds_stdout[2]; + if (pipe(fds_stdout) < 0) return -1; - pid = fork(); + int fds_stderr[2]; + if (pipe(fds_stderr) < 0) { + close(fds_stdout[0]); + close(fds_stdout[1]); + return -1; + } + + pid_t pid = fork(); if (pid < 0) { - close(fds[0]); - close(fds[1]); + close(fds_stdout[0]); + close(fds_stdout[1]); + close(fds_stderr[0]); + close(fds_stderr[1]); return -1; } if (pid == 0) { - dup2(fds[1], 1); - dup2(fds[1], 1); - close(fds[0]); - close(fds[1]); + dup2(fds_stdout[1], 1); + dup2(fds_stderr[1], 2); + close(fds_stdout[0]); + close(fds_stdout[1]); + close(fds_stderr[0]); + close(fds_stderr[1]); close(0); /* XXX close other fds? */ @@ -215,17 +270,15 @@ start_plugin(struct plugin_cycle *cycle, const char *plugin_path) _exit(1); } - close(fds[1]); + close(fds_stdout[1]); + close(fds_stderr[1]); cycle->pid = pid; - cycle->fd = fds[0]; - cycle->data = g_string_new(NULL); /* XXX CLOEXEC? */ - cycle->channel = g_io_channel_unix_new(cycle->fd); - cycle->event_id = g_io_add_watch(cycle->channel, G_IO_IN|G_IO_HUP, - plugin_data, cycle); + plugin_fd_add(cycle, &cycle->pipe_stdout, fds_stdout[0]); + plugin_fd_add(cycle, &cycle->pipe_stderr, fds_stderr[0]); return 0; } @@ -233,25 +286,23 @@ start_plugin(struct plugin_cycle *cycle, const char *plugin_path) static void next_plugin(struct plugin_cycle *cycle) { - const char *plugin_path; - int ret = -1; - assert(cycle->pid < 0); - assert(cycle->fd < 0); - assert(cycle->data == NULL); + assert(cycle->pipe_stdout.fd < 0); + assert(cycle->pipe_stderr.fd < 0); + assert(cycle->pipe_stdout.data == NULL); + assert(cycle->pipe_stderr.data == NULL); if (cycle->next_plugin >= cycle->list->plugins->len) { /* no plugins left */ - g_timeout_add(0, plugin_delayed_fail, cycle); + g_idle_add(plugin_delayed_fail, cycle); return; } - plugin_path = g_ptr_array_index(cycle->list->plugins, - cycle->next_plugin++); - ret = start_plugin(cycle, plugin_path); - if (ret < 0) { + const char *plugin_path = g_ptr_array_index(cycle->list->plugins, + cycle->next_plugin++); + if (start_plugin(cycle, plugin_path) < 0) { /* system error */ - g_timeout_add(0, plugin_delayed_fail, cycle); + g_idle_add(plugin_delayed_fail, cycle); return; } } @@ -260,20 +311,17 @@ static char ** make_argv(const char*const* args) { unsigned num = 0; - char **ret; - while (args[num] != NULL) ++num; num += 2; - ret = g_new(char*, num); + char **ret = g_new(char *, num); /* reserve space for the program name */ *ret++ = NULL; - do { + while (*args != NULL) *ret++ = g_strdup(*args++); - } while (*args != NULL); /* end of argument vector */ *ret++ = NULL; @@ -295,23 +343,31 @@ plugin_run(struct plugin_list *list, const char *const*args, cycle->callback_data = callback_data; cycle->next_plugin = 0; cycle->pid = -1; - cycle->fd = -1; - cycle->data = NULL; + cycle->pipe_stdout.fd = -1; + cycle->pipe_stderr.fd = -1; + cycle->pipe_stdout.data = NULL; + cycle->pipe_stderr.data = NULL; + cycle->all_errors = g_string_new(NULL); next_plugin(cycle); return cycle; } +static void +plugin_fd_remove(struct plugin_pipe *p) +{ + if (p->fd >= 0) { + g_source_remove(p->event_id); + close(p->fd); + } +} + void plugin_stop(struct plugin_cycle *cycle) { - if (cycle->fd >= 0) { - /* close the pipe to the plugin */ - g_source_remove(cycle->event_id); - g_io_channel_unref(cycle->channel); - close(cycle->fd); - } + plugin_fd_remove(&cycle->pipe_stdout); + plugin_fd_remove(&cycle->pipe_stderr); if (cycle->pid > 0) { /* kill the plugin process */ @@ -321,9 +377,13 @@ plugin_stop(struct plugin_cycle *cycle) waitpid(cycle->pid, &status, 0); } - if (cycle->data != NULL) - /* free data that has been received */ - g_string_free(cycle->data, TRUE); + /* free data that has been received */ + if (cycle->pipe_stdout.data != NULL) + g_string_free(cycle->pipe_stdout.data, TRUE); + if (cycle->pipe_stderr.data != NULL) + g_string_free(cycle->pipe_stderr.data, TRUE); + if (cycle->all_errors != NULL) + g_string_free(cycle->all_errors, TRUE); /* free argument list */ for (guint i = 0; i == 0 || cycle->argv[i] != NULL; ++i)