Code

plugin: show plugin error messages on the screen
[ncmpc.git] / src / plugin.c
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2009 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_cycle {
31         /** the plugin list; used for traversing to the next plugin */
32         struct plugin_list *list;
34         /** arguments passed to execv() */
35         char **argv;
37         /** caller defined callback function */
38         plugin_callback_t callback;
39         /** caller defined pointer passed to #callback */
40         void *callback_data;
42         /** the index of the next plugin which is going to be
43             invoked */
44         guint next_plugin;
46         /** the pid of the plugin process, or -1 if none is currently
47             running */
48         pid_t pid;
49         /** the pipe to the plugin process, or -1 if none is currently
50             open */
51         int fd;
52         /** the GLib channel of #fd */
53         GIOChannel *channel;
54         /** the GLib IO watch of #channel */
55         guint event_id;
57         /** the output of the current plugin */
58         GString *data;
59 };
61 static bool
62 register_plugin(struct plugin_list *list, char *path)
63 {
64         int ret;
65         struct stat st;
67         ret = stat(path, &st);
68         if (ret < 0)
69                 return false;
71         g_ptr_array_add(list->plugins, path);
72         return true;
73 }
75 static gint 
76 plugin_compare_func_alpha(gconstpointer plugin1, gconstpointer plugin2)
77 {
78         return strcmp(* (char * const *) plugin1, * (char * const *) plugin2);
79 }
81 static void
82 plugin_list_sort(struct plugin_list *list, GCompareFunc compare_func)
83 {
84         g_ptr_array_sort(list->plugins,
85                         compare_func);
86 }
88 bool
89 plugin_list_load_directory(struct plugin_list *list, const char *path)
90 {
91         GDir *dir;
92         const char *name;
93         char *plugin;
94         bool ret;
96         dir = g_dir_open(path, 0, NULL);
97         if (dir == NULL)
98                 return false;
100         while ((name = g_dir_read_name(dir)) != NULL) {
101                 plugin = g_build_filename(path, name, NULL);
102                 ret = register_plugin(list, plugin);
103                 if (!ret)
104                         g_free(plugin);
105         }
107         g_dir_close(dir);
109         plugin_list_sort(list, plugin_compare_func_alpha);
111         return true;
114 void plugin_list_deinit(struct plugin_list *list)
116         for (guint i = 0; i < list->plugins->len; ++i)
117                 free(g_ptr_array_index(list->plugins, i));
118         g_ptr_array_free(list->plugins, TRUE);
121 static void
122 next_plugin(struct plugin_cycle *cycle);
124 static void
125 plugin_eof(struct plugin_cycle *cycle)
127         int ret, status;
129         g_io_channel_unref(cycle->channel);
130         close(cycle->fd);
131         cycle->fd = -1;
133         ret = waitpid(cycle->pid, &status, 0);
134         cycle->pid = -1;
136         if (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
137                 /* the plugin has failed */
138                 g_string_free(cycle->data, TRUE);
139                 cycle->data = NULL;
141                 next_plugin(cycle);
142         } else {
143                 /* success: invoke the callback */
144                 cycle->callback(cycle->data, cycle->callback_data);
145         }
148 static gboolean
149 plugin_data(G_GNUC_UNUSED GIOChannel *source,
150             G_GNUC_UNUSED GIOCondition condition, gpointer data)
152         struct plugin_cycle *cycle = data;
153         char buffer[256];
154         ssize_t nbytes;
156         assert(cycle != NULL);
157         assert(cycle->fd >= 0);
158         assert(cycle->pid > 0);
159         assert(source == cycle->channel);
161         nbytes = condition & G_IO_IN
162                 ? read(cycle->fd, buffer, sizeof(buffer))
163                 : 0;
164         if (nbytes <= 0) {
165                 plugin_eof(cycle);
166                 return FALSE;
167         }
169         g_string_append_len(cycle->data, buffer, nbytes);
170         return TRUE;
173 /**
174  * This is a timer callback which calls the plugin callback "some time
175  * later".  This solves the problem that plugin_run() may fail
176  * immediately, leaving its return value in an undefined state.
177  * Instead, install a timer which calls the plugin callback in the
178  * moment after.
179  */
180 static gboolean
181 plugin_delayed_fail(gpointer data)
183         struct plugin_cycle *cycle = data;
185         assert(cycle != NULL);
186         assert(cycle->fd < 0);
187         assert(cycle->pid < 0);
188         assert(cycle->data == NULL);
190         cycle->callback(NULL, cycle->callback_data);
192         return FALSE;
195 static int
196 start_plugin(struct plugin_cycle *cycle, const char *plugin_path)
198         int ret, fds[2];
199         pid_t pid;
201         assert(cycle != NULL);
202         assert(cycle->pid < 0);
203         assert(cycle->fd < 0);
204         assert(cycle->data == NULL);
206         /* set new program name, but free the one from the previous
207            plugin */
208         g_free(cycle->argv[0]);
209         cycle->argv[0] = g_path_get_basename(plugin_path);
211         ret = pipe(fds);
212         if (ret < 0)
213                 return -1;
215         pid = fork();
217         if (pid < 0) {
218                 close(fds[0]);
219                 close(fds[1]);
220                 return -1;
221         }
223         if (pid == 0) {
224                 dup2(fds[1], 1);
225                 dup2(fds[1], 2);
226                 close(fds[0]);
227                 close(fds[1]);
228                 close(0);
229                 /* XXX close other fds? */
231                 execv(plugin_path, cycle->argv);
232                 _exit(1);
233         }
235         close(fds[1]);
237         cycle->pid = pid;
238         cycle->fd = fds[0];
239         cycle->data = g_string_new(NULL);
241         /* XXX CLOEXEC? */
243         cycle->channel = g_io_channel_unix_new(cycle->fd);
244         cycle->event_id = g_io_add_watch(cycle->channel, G_IO_IN|G_IO_HUP,
245                                          plugin_data, cycle);
247         return 0;
250 static void
251 next_plugin(struct plugin_cycle *cycle)
253         const char *plugin_path;
254         int ret = -1;
256         assert(cycle->pid < 0);
257         assert(cycle->fd < 0);
258         assert(cycle->data == NULL);
260         if (cycle->next_plugin >= cycle->list->plugins->len) {
261                 /* no plugins left */
262                 g_timeout_add(0, plugin_delayed_fail, cycle);
263                 return;
264         }
266         plugin_path = g_ptr_array_index(cycle->list->plugins,
267                                         cycle->next_plugin++);
268         ret = start_plugin(cycle, plugin_path);
269         if (ret < 0) {
270                 /* system error */
271                 g_timeout_add(0, plugin_delayed_fail, cycle);
272                 return;
273         }
276 static char **
277 make_argv(const char*const* args)
279         unsigned num = 0;
280         char **ret;
282         while (args[num] != NULL)
283                 ++num;
284         num += 2;
286         ret = g_new(char*, num);
288         /* reserve space for the program name */
289         *ret++ = NULL;
291         do {
292                 *ret++ = g_strdup(*args++);
293         } while (*args != NULL);
295         /* end of argument vector */
296         *ret++ = NULL;
298         return ret - num;
301 struct plugin_cycle *
302 plugin_run(struct plugin_list *list, const char *const*args,
303            plugin_callback_t callback, void *callback_data)
305         struct plugin_cycle *cycle = g_new(struct plugin_cycle, 1);
307         assert(args != NULL);
309         cycle->list = list;
310         cycle->argv = make_argv(args);
311         cycle->callback = callback;
312         cycle->callback_data = callback_data;
313         cycle->next_plugin = 0;
314         cycle->pid = -1;
315         cycle->fd = -1;
316         cycle->data = NULL;
318         next_plugin(cycle);
320         return cycle;
323 void
324 plugin_stop(struct plugin_cycle *cycle)
326         if (cycle->fd >= 0) {
327                 /* close the pipe to the plugin */
328                 g_source_remove(cycle->event_id);
329                 g_io_channel_unref(cycle->channel);
330                 close(cycle->fd);
331         }
333         if (cycle->pid > 0) {
334                 /* kill the plugin process */
335                 int status;
337                 kill(cycle->pid, SIGTERM);
338                 waitpid(cycle->pid, &status, 0);
339         }
341         if (cycle->data != NULL)
342                 /* free data that has been received */
343                 g_string_free(cycle->data, TRUE);
345         /* free argument list */
346         for (guint i = 0; i == 0 || cycle->argv[i] != NULL; ++i)
347                 g_free(cycle->argv[i]);
348         g_free(cycle->argv);
350         g_free(cycle);