Code

plugin: fix crash
[ncmpc.git] / src / plugin.c
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2017 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.
9  *
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.
14  *
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"
21 #include "Compiler.h"
23 #include <assert.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <sys/stat.h>
28 #include <sys/signal.h>
29 #include <sys/wait.h>
31 struct plugin_pipe {
32         struct plugin_cycle *cycle;
34         /** the pipe to the plugin process, or -1 if none is currently
35             open */
36         int fd;
37         /** the GLib IO watch of #fd */
38         guint event_id;
39         /** the output of the current plugin */
40         GString *data;
41 };
43 struct plugin_cycle {
44         /** the plugin list; used for traversing to the next plugin */
45         struct plugin_list *list;
47         /** arguments passed to execv() */
48         char **argv;
50         /** caller defined callback function */
51         plugin_callback_t callback;
52         /** caller defined pointer passed to #callback */
53         void *callback_data;
55         /** the index of the next plugin which is going to be
56             invoked */
57         guint next_plugin;
59         /** the pid of the plugin process, or -1 if none is currently
60             running */
61         pid_t pid;
63         /** the stdout pipe */
64         struct plugin_pipe pipe_stdout;
65         /** the stderr pipe */
66         struct plugin_pipe pipe_stderr;
68         /** list of all error messages from failed plugins */
69         GString *all_errors;
70 };
72 static bool
73 register_plugin(struct plugin_list *list, char *path)
74 {
75         struct stat st;
76         if (stat(path, &st) < 0)
77                 return false;
79         g_ptr_array_add(list->plugins, path);
80         return true;
81 }
83 static gint
84 plugin_compare_func_alpha(gconstpointer plugin1, gconstpointer plugin2)
85 {
86         return strcmp(* (char * const *) plugin1, * (char * const *) plugin2);
87 }
89 static void
90 plugin_list_sort(struct plugin_list *list, GCompareFunc compare_func)
91 {
92         g_ptr_array_sort(list->plugins,
93                         compare_func);
94 }
96 bool
97 plugin_list_load_directory(struct plugin_list *list, const char *path)
98 {
99         GDir *dir = g_dir_open(path, 0, NULL);
100         if (dir == NULL)
101                 return false;
103         const char *name;
104         while ((name = g_dir_read_name(dir)) != NULL) {
105                 char *plugin = g_build_filename(path, name, NULL);
106                 if (!register_plugin(list, plugin))
107                         g_free(plugin);
108         }
110         g_dir_close(dir);
112         plugin_list_sort(list, plugin_compare_func_alpha);
114         return true;
117 void plugin_list_deinit(struct plugin_list *list)
119         for (guint i = 0; i < list->plugins->len; ++i)
120                 free(g_ptr_array_index(list->plugins, i));
121         g_ptr_array_free(list->plugins, TRUE);
124 static void
125 next_plugin(struct plugin_cycle *cycle);
127 static void
128 plugin_eof(struct plugin_cycle *cycle, struct plugin_pipe *p)
130         close(p->fd);
131         p->fd = -1;
133         /* Only if both pipes are have EOF status we are done */
134         if (cycle->pipe_stdout.fd != -1 || cycle->pipe_stderr.fd != -1)
135                 return;
137         int status, ret = waitpid(cycle->pid, &status, 0);
138         cycle->pid = -1;
140         if (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
141                 /* If we encountered an error other than service unavailable
142                  * (69), log it for later. If all plugins fail, we may get
143                  * some hints for debugging.*/
144                 if (cycle->pipe_stderr.data->len > 0 &&
145                     WEXITSTATUS(status) != 69)
146                         g_string_append_printf(cycle->all_errors,
147                                                "*** %s ***\n\n%s\n",
148                                                cycle->argv[0],
149                                                cycle->pipe_stderr.data->str);
151                 /* the plugin has failed */
152                 g_string_free(cycle->pipe_stdout.data, TRUE);
153                 cycle->pipe_stdout.data = NULL;
154                 g_string_free(cycle->pipe_stderr.data, TRUE);
155                 cycle->pipe_stderr.data = NULL;
157                 next_plugin(cycle);
158         } else {
159                 /* success: invoke the callback */
160                 cycle->callback(cycle->pipe_stdout.data, true,
161                                 cycle->argv[0], cycle->callback_data);
162         }
165 static gboolean
166 plugin_data(gcc_unused GIOChannel *source,
167             gcc_unused GIOCondition condition, gpointer data)
169         struct plugin_pipe *p = data;
170         assert(p->fd >= 0);
172         struct plugin_cycle *cycle = p->cycle;
173         assert(cycle != NULL);
174         assert(cycle->pid > 0);
176         char buffer[256];
177         ssize_t nbytes = condition & G_IO_IN
178                 ? read(p->fd, buffer, sizeof(buffer))
179                 : 0;
180         if (nbytes <= 0) {
181                 plugin_eof(cycle, p);
182                 return FALSE;
183         }
185         g_string_append_len(p->data, buffer, nbytes);
186         return TRUE;
189 /**
190  * This is a timer callback which calls the plugin callback "some time
191  * later".  This solves the problem that plugin_run() may fail
192  * immediately, leaving its return value in an undefined state.
193  * Instead, install a timer which calls the plugin callback in the
194  * moment after.
195  */
196 static gboolean
197 plugin_delayed_fail(gpointer data)
199         struct plugin_cycle *cycle = data;
201         assert(cycle != NULL);
202         assert(cycle->pipe_stdout.fd < 0);
203         assert(cycle->pipe_stderr.fd < 0);
204         assert(cycle->pid < 0);
206         cycle->callback(cycle->all_errors, false, NULL, cycle->callback_data);
208         return FALSE;
211 static void
212 plugin_fd_add(struct plugin_cycle *cycle, struct plugin_pipe *p, int fd)
214         p->cycle = cycle;
215         p->fd = fd;
216         p->data = g_string_new(NULL);
217         GIOChannel *channel = g_io_channel_unix_new(fd);
218         p->event_id = g_io_add_watch(channel, G_IO_IN|G_IO_HUP,
219                                      plugin_data, p);
220         g_io_channel_unref(channel);
223 static int
224 start_plugin(struct plugin_cycle *cycle, const char *plugin_path)
226         assert(cycle != NULL);
227         assert(cycle->pid < 0);
228         assert(cycle->pipe_stdout.fd < 0);
229         assert(cycle->pipe_stderr.fd < 0);
230         assert(cycle->pipe_stdout.data == NULL);
231         assert(cycle->pipe_stderr.data == NULL);
233         /* set new program name, but free the one from the previous
234            plugin */
235         g_free(cycle->argv[0]);
236         cycle->argv[0] = g_path_get_basename(plugin_path);
238         int fds_stdout[2];
239         if (pipe(fds_stdout) < 0)
240                 return -1;
242         int fds_stderr[2];
243         if (pipe(fds_stderr) < 0) {
244                 close(fds_stdout[0]);
245                 close(fds_stdout[1]);
246                 return -1;
247         }
249         pid_t pid = fork();
251         if (pid < 0) {
252                 close(fds_stdout[0]);
253                 close(fds_stdout[1]);
254                 close(fds_stderr[0]);
255                 close(fds_stderr[1]);
256                 return -1;
257         }
259         if (pid == 0) {
260                 dup2(fds_stdout[1], 1);
261                 dup2(fds_stderr[1], 2);
262                 close(fds_stdout[0]);
263                 close(fds_stdout[1]);
264                 close(fds_stderr[0]);
265                 close(fds_stderr[1]);
266                 close(0);
267                 /* XXX close other fds? */
269                 execv(plugin_path, cycle->argv);
270                 _exit(1);
271         }
273         close(fds_stdout[1]);
274         close(fds_stderr[1]);
276         cycle->pid = pid;
278         /* XXX CLOEXEC? */
280         plugin_fd_add(cycle, &cycle->pipe_stdout, fds_stdout[0]);
281         plugin_fd_add(cycle, &cycle->pipe_stderr, fds_stderr[0]);
283         return 0;
286 static void
287 next_plugin(struct plugin_cycle *cycle)
289         assert(cycle->pid < 0);
290         assert(cycle->pipe_stdout.fd < 0);
291         assert(cycle->pipe_stderr.fd < 0);
292         assert(cycle->pipe_stdout.data == NULL);
293         assert(cycle->pipe_stderr.data == NULL);
295         if (cycle->next_plugin >= cycle->list->plugins->len) {
296                 /* no plugins left */
297                 g_idle_add(plugin_delayed_fail, cycle);
298                 return;
299         }
301         const char *plugin_path = g_ptr_array_index(cycle->list->plugins,
302                                                     cycle->next_plugin++);
303         if (start_plugin(cycle, plugin_path) < 0) {
304                 /* system error */
305                 g_idle_add(plugin_delayed_fail, cycle);
306                 return;
307         }
310 static char **
311 make_argv(const char*const* args)
313         unsigned num = 0;
314         while (args[num] != NULL)
315                 ++num;
316         num += 2;
318         char **ret = g_new(char *, num);
320         /* reserve space for the program name */
321         *ret++ = NULL;
323         while (*args != NULL)
324                 *ret++ = g_strdup(*args++);
326         /* end of argument vector */
327         *ret++ = NULL;
329         return ret - num;
332 struct plugin_cycle *
333 plugin_run(struct plugin_list *list, const char *const*args,
334            plugin_callback_t callback, void *callback_data)
336         struct plugin_cycle *cycle = g_new(struct plugin_cycle, 1);
338         assert(args != NULL);
340         cycle->list = list;
341         cycle->argv = make_argv(args);
342         cycle->callback = callback;
343         cycle->callback_data = callback_data;
344         cycle->next_plugin = 0;
345         cycle->pid = -1;
346         cycle->pipe_stdout.fd = -1;
347         cycle->pipe_stderr.fd = -1;
348         cycle->pipe_stdout.data = NULL;
349         cycle->pipe_stderr.data = NULL;
351         cycle->all_errors = g_string_new(NULL);
352         next_plugin(cycle);
354         return cycle;
357 static void
358 plugin_fd_remove(struct plugin_pipe *p)
360         if (p->fd >= 0) {
361                 g_source_remove(p->event_id);
362                 close(p->fd);
363         }
366 void
367 plugin_stop(struct plugin_cycle *cycle)
369         plugin_fd_remove(&cycle->pipe_stdout);
370         plugin_fd_remove(&cycle->pipe_stderr);
372         if (cycle->pid > 0) {
373                 /* kill the plugin process */
374                 int status;
376                 kill(cycle->pid, SIGTERM);
377                 waitpid(cycle->pid, &status, 0);
378         }
380         /* free data that has been received */
381         if (cycle->pipe_stdout.data != NULL)
382                 g_string_free(cycle->pipe_stdout.data, TRUE);
383         if (cycle->pipe_stderr.data != NULL)
384                 g_string_free(cycle->pipe_stderr.data, TRUE);
385         if (cycle->all_errors != NULL)
386                 g_string_free(cycle->all_errors, TRUE);
388         /* free argument list */
389         for (guint i = 0; i == 0 || cycle->argv[i] != NULL; ++i)
390                 g_free(cycle->argv[i]);
391         g_free(cycle->argv);
393         g_free(cycle);