Code

plugin: fix crash
[ncmpc.git] / src / plugin.c
index 79515d8f079cbee7e54b3d552ea2a87315fbb05a..f9049ecfa77458285c07da66f4946f542a1b4959 100644 (file)
@@ -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
  * 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 <assert.h>
 #include <stdlib.h>
 #include <sys/signal.h>
 #include <sys/wait.h>
 
+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)