diff --git a/src/plugin.c b/src/plugin.c
index 88cc305044737d3ed0680588c40e230476dfa277..64e61ab73d2b1c31ade1bce65fcd8c5cf3664083 100644 (file)
--- a/src/plugin.c
+++ b/src/plugin.c
/* ncmpc (Ncurses MPD Client)
- * (c) 2004-2009 The Music Player Daemon Project
+ * (c) 2004-2010 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
-
+ *
* This program is distributed in the hope that it will be useful,
* 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.,
* 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 {
+ /** 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;
+};
+
struct plugin_cycle {
/** the plugin list; used for traversing to the next plugin */
struct plugin_list *list;
/** 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
+static gint
plugin_compare_func_alpha(gconstpointer plugin1, gconstpointer plugin2)
{
return strcmp(* (char * const *) plugin1, * (char * const *) plugin2);
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);
}
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;
+ g_io_channel_unref(p->channel);
+ 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;
-
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))
+ struct plugin_pipe *p = NULL;
+ if (source == cycle->pipe_stdout.channel)
+ p = &cycle->pipe_stdout;
+ else if (source == cycle->pipe_stderr.channel)
+ p = &cycle->pipe_stderr;
+ assert(p != NULL);
+ assert(p->fd >= 0);
+
+ 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;
}
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->fd = fd;
+ p->data = g_string_new(NULL);
+ p->channel = g_io_channel_unix_new(fd);
+ p->event_id = g_io_add_watch(p->channel, G_IO_IN|G_IO_HUP,
+ plugin_data, cycle);
+}
+
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;
+
+ int fds_stderr[2];
+ if (pipe(fds_stderr) < 0) {
+ close(fds_stdout[0]);
+ close(fds_stdout[1]);
return -1;
+ }
- pid = fork();
+ 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], 2);
- 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? */
_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;
}
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 */
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);
return;
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;
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);
+ g_io_channel_unref(p->channel);
+ 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 */
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)