Code

plugin: pass the plugin name to the callback
[ncmpc.git] / src / plugin.c
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2010 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
4  
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
20 #include "plugin.h"
22 #include <assert.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <sys/signal.h>
28 #include <sys/wait.h>
30 struct plugin_pipe {
31         /** the pipe to the plugin process, or -1 if none is currently
32             open */
33         int fd;
34         /** the GLib channel of #fd */
35         GIOChannel *channel;
36         /** the GLib IO watch of #channel */
37         guint event_id;
38         /** the output of the current plugin */
39         GString *data;
40 };
42 struct plugin_cycle {
43         /** the plugin list; used for traversing to the next plugin */
44         struct plugin_list *list;
46         /** arguments passed to execv() */
47         char **argv;
49         /** caller defined callback function */
50         plugin_callback_t callback;
51         /** caller defined pointer passed to #callback */
52         void *callback_data;
54         /** the index of the next plugin which is going to be
55             invoked */
56         guint next_plugin;
58         /** the pid of the plugin process, or -1 if none is currently
59             running */
60         pid_t pid;
62         /** the stdout pipe */
63         struct plugin_pipe pipe_stdout;
64         /** the stderr pipe */
65         struct plugin_pipe pipe_stderr;
67         /** list of all error messages from failed plugins */
68         GString *all_errors;
69 };
71 static bool
72 register_plugin(struct plugin_list *list, char *path)
73 {
74         int ret;
75         struct stat st;
77         ret = stat(path, &st);
78         if (ret < 0)
79                 return false;
81         g_ptr_array_add(list->plugins, path);
82         return true;
83 }
85 static gint 
86 plugin_compare_func_alpha(gconstpointer plugin1, gconstpointer plugin2)
87 {
88         return strcmp(* (char * const *) plugin1, * (char * const *) plugin2);
89 }
91 static void
92 plugin_list_sort(struct plugin_list *list, GCompareFunc compare_func)
93 {
94         g_ptr_array_sort(list->plugins,
95                         compare_func);
96 }
98 bool
99 plugin_list_load_directory(struct plugin_list *list, const char *path)
101         GDir *dir;
102         const char *name;
103         char *plugin;
104         bool ret;
106         dir = g_dir_open(path, 0, NULL);
107         if (dir == NULL)
108                 return false;
110         while ((name = g_dir_read_name(dir)) != NULL) {
111                 plugin = g_build_filename(path, name, NULL);
112                 ret = register_plugin(list, plugin);
113                 if (!ret)
114                         g_free(plugin);
115         }
117         g_dir_close(dir);
119         plugin_list_sort(list, plugin_compare_func_alpha);
121         return true;
124 void plugin_list_deinit(struct plugin_list *list)
126         for (guint i = 0; i < list->plugins->len; ++i)
127                 free(g_ptr_array_index(list->plugins, i));
128         g_ptr_array_free(list->plugins, TRUE);
131 static void
132 next_plugin(struct plugin_cycle *cycle);
134 static void
135 plugin_eof(struct plugin_cycle *cycle, struct plugin_pipe *p)
137         int ret, status;
139         g_io_channel_unref(p->channel);
140         close(p->fd);
141         p->fd = -1;
143         /* Only if both pipes are have EOF status we are done */
144         if (cycle->pipe_stdout.fd != -1 || cycle->pipe_stderr.fd != -1)
145                 return;
147         ret = waitpid(cycle->pid, &status, 0);
148         cycle->pid = -1;
150         if (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
151                 /* If we encountered an error other than service unavailable
152                  * (69), log it for later. If all plugins fail, we may get
153                  * some hints for debugging.*/
154                 if (cycle->pipe_stderr.data->len > 0 &&
155                     WEXITSTATUS(status) != 69)
156                         g_string_append_printf(cycle->all_errors,
157                                                "*** %s ***\n\n%s\n",
158                                                cycle->argv[0],
159                                                cycle->pipe_stderr.data->str);
161                 /* the plugin has failed */
162                 g_string_free(cycle->pipe_stdout.data, TRUE);
163                 cycle->pipe_stdout.data = NULL;
164                 g_string_free(cycle->pipe_stderr.data, TRUE);
165                 cycle->pipe_stderr.data = NULL;
167                 next_plugin(cycle);
168         } else {
169                 /* success: invoke the callback */
170                 cycle->callback(cycle->pipe_stdout.data, true,
171                                 cycle->argv[0], cycle->callback_data);
172         }
175 static gboolean
176 plugin_data(G_GNUC_UNUSED GIOChannel *source,
177             G_GNUC_UNUSED GIOCondition condition, gpointer data)
179         struct plugin_cycle *cycle = data;
180         struct plugin_pipe *p = NULL;
181         char buffer[256];
182         ssize_t nbytes;
184         assert(cycle != NULL);
185         assert(cycle->pid > 0);
186         if (source == cycle->pipe_stdout.channel)
187                 p = &cycle->pipe_stdout;
188         else if (source == cycle->pipe_stderr.channel)
189                 p = &cycle->pipe_stderr;
190         assert(p != NULL);
191         assert(p->fd >= 0);
193         nbytes = condition & G_IO_IN ? read(p->fd, buffer, sizeof(buffer)) : 0;
194         if (nbytes <= 0) {
195                 plugin_eof(cycle, p);
196                 return FALSE;
197         }
199         g_string_append_len(p->data, buffer, nbytes);
200         return TRUE;
203 /**
204  * This is a timer callback which calls the plugin callback "some time
205  * later".  This solves the problem that plugin_run() may fail
206  * immediately, leaving its return value in an undefined state.
207  * Instead, install a timer which calls the plugin callback in the
208  * moment after.
209  */
210 static gboolean
211 plugin_delayed_fail(gpointer data)
213         struct plugin_cycle *cycle = data;
215         assert(cycle != NULL);
216         assert(cycle->pipe_stdout.fd < 0);
217         assert(cycle->pipe_stderr.fd < 0);
218         assert(cycle->pid < 0);
220         cycle->callback(cycle->all_errors, false, NULL, cycle->callback_data);
222         return FALSE;
225 static void
226 plugin_fd_add(struct plugin_cycle *cycle, struct plugin_pipe *p, int fd)
228         p->fd = fd;
229         p->data = g_string_new(NULL);
230         p->channel = g_io_channel_unix_new(fd);
231         p->event_id = g_io_add_watch(p->channel, G_IO_IN|G_IO_HUP,
232                                      plugin_data, cycle);
235 static int
236 start_plugin(struct plugin_cycle *cycle, const char *plugin_path)
238         int ret, fds_stdout[2], fds_stderr[2];
239         pid_t pid;
241         assert(cycle != NULL);
242         assert(cycle->pid < 0);
243         assert(cycle->pipe_stdout.fd < 0);
244         assert(cycle->pipe_stderr.fd < 0);
245         assert(cycle->pipe_stdout.data == NULL);
246         assert(cycle->pipe_stderr.data == NULL);
248         /* set new program name, but free the one from the previous
249            plugin */
250         g_free(cycle->argv[0]);
251         cycle->argv[0] = g_path_get_basename(plugin_path);
253         ret = pipe(fds_stdout);
254         if (ret < 0)
255                 return -1;
256         ret = pipe(fds_stderr);
257         if (ret < 0) {
258                 close(fds_stdout[0]);
259                 close(fds_stdout[1]);
260                 return -1;
261         }
263         pid = fork();
265         if (pid < 0) {
266                 close(fds_stdout[0]);
267                 close(fds_stdout[1]);
268                 close(fds_stderr[0]);
269                 close(fds_stderr[1]);
270                 return -1;
271         }
273         if (pid == 0) {
274                 dup2(fds_stdout[1], 1);
275                 dup2(fds_stderr[1], 2);
276                 close(fds_stdout[0]);
277                 close(fds_stdout[1]);
278                 close(fds_stderr[0]);
279                 close(fds_stderr[1]);
280                 close(0);
281                 /* XXX close other fds? */
283                 execv(plugin_path, cycle->argv);
284                 _exit(1);
285         }
287         close(fds_stdout[1]);
288         close(fds_stderr[1]);
290         cycle->pid = pid;
292         /* XXX CLOEXEC? */
294         plugin_fd_add(cycle, &cycle->pipe_stdout, fds_stdout[0]);
295         plugin_fd_add(cycle, &cycle->pipe_stderr, fds_stderr[0]);
297         return 0;
300 static void
301 next_plugin(struct plugin_cycle *cycle)
303         const char *plugin_path;
304         int ret = -1;
306         assert(cycle->pid < 0);
307         assert(cycle->pipe_stdout.fd < 0);
308         assert(cycle->pipe_stderr.fd < 0);
309         assert(cycle->pipe_stdout.data == NULL);
310         assert(cycle->pipe_stderr.data == NULL);
312         if (cycle->next_plugin >= cycle->list->plugins->len) {
313                 /* no plugins left */
314                 g_timeout_add(0, plugin_delayed_fail, cycle);
315                 return;
316         }
318         plugin_path = g_ptr_array_index(cycle->list->plugins,
319                                         cycle->next_plugin++);
320         ret = start_plugin(cycle, plugin_path);
321         if (ret < 0) {
322                 /* system error */
323                 g_timeout_add(0, plugin_delayed_fail, cycle);
324                 return;
325         }
328 static char **
329 make_argv(const char*const* args)
331         unsigned num = 0;
332         char **ret;
334         while (args[num] != NULL)
335                 ++num;
336         num += 2;
338         ret = g_new(char*, num);
340         /* reserve space for the program name */
341         *ret++ = NULL;
343         while (*args != NULL)
344                 *ret++ = g_strdup(*args++);
346         /* end of argument vector */
347         *ret++ = NULL;
349         return ret - num;
352 struct plugin_cycle *
353 plugin_run(struct plugin_list *list, const char *const*args,
354            plugin_callback_t callback, void *callback_data)
356         struct plugin_cycle *cycle = g_new(struct plugin_cycle, 1);
358         assert(args != NULL);
360         cycle->list = list;
361         cycle->argv = make_argv(args);
362         cycle->callback = callback;
363         cycle->callback_data = callback_data;
364         cycle->next_plugin = 0;
365         cycle->pid = -1;
366         cycle->pipe_stdout.fd = -1;
367         cycle->pipe_stderr.fd = -1;
368         cycle->pipe_stdout.data = NULL;
369         cycle->pipe_stderr.data = NULL;
371         cycle->all_errors = g_string_new(NULL);
372         next_plugin(cycle);
374         return cycle;
377 static void
378 plugin_fd_remove(struct plugin_pipe *p)
380         if (p->fd >= 0) {
381                 g_source_remove(p->event_id);
382                 g_io_channel_unref(p->channel);
383                 close(p->fd);
384         }
387 void
388 plugin_stop(struct plugin_cycle *cycle)
390         plugin_fd_remove(&cycle->pipe_stdout);
391         plugin_fd_remove(&cycle->pipe_stderr);
393         if (cycle->pid > 0) {
394                 /* kill the plugin process */
395                 int status;
397                 kill(cycle->pid, SIGTERM);
398                 waitpid(cycle->pid, &status, 0);
399         }
401         /* free data that has been received */
402         if (cycle->pipe_stdout.data != NULL)
403                 g_string_free(cycle->pipe_stdout.data, TRUE);
404         if (cycle->pipe_stderr.data != NULL)
405                 g_string_free(cycle->pipe_stderr.data, TRUE);
406         if (cycle->all_errors != NULL)
407                 g_string_free(cycle->all_errors, TRUE);
409         /* free argument list */
410         for (guint i = 0; i == 0 || cycle->argv[i] != NULL; ++i)
411                 g_free(cycle->argv[i]);
412         g_free(cycle->argv);
414         g_free(cycle);