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 channel of #fd */
38 GIOChannel *channel;
39 /** the GLib IO watch of #channel */
40 guint event_id;
41 /** the output of the current plugin */
42 GString *data;
43 };
45 struct plugin_cycle {
46 /** the plugin list; used for traversing to the next plugin */
47 struct plugin_list *list;
49 /** arguments passed to execv() */
50 char **argv;
52 /** caller defined callback function */
53 plugin_callback_t callback;
54 /** caller defined pointer passed to #callback */
55 void *callback_data;
57 /** the index of the next plugin which is going to be
58 invoked */
59 guint next_plugin;
61 /** the pid of the plugin process, or -1 if none is currently
62 running */
63 pid_t pid;
65 /** the stdout pipe */
66 struct plugin_pipe pipe_stdout;
67 /** the stderr pipe */
68 struct plugin_pipe pipe_stderr;
70 /** list of all error messages from failed plugins */
71 GString *all_errors;
72 };
74 static bool
75 register_plugin(struct plugin_list *list, char *path)
76 {
77 struct stat st;
78 if (stat(path, &st) < 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)
100 {
101 GDir *dir = g_dir_open(path, 0, NULL);
102 if (dir == NULL)
103 return false;
105 const char *name;
106 while ((name = g_dir_read_name(dir)) != NULL) {
107 char *plugin = g_build_filename(path, name, NULL);
108 if (!register_plugin(list, plugin))
109 g_free(plugin);
110 }
112 g_dir_close(dir);
114 plugin_list_sort(list, plugin_compare_func_alpha);
116 return true;
117 }
119 void plugin_list_deinit(struct plugin_list *list)
120 {
121 for (guint i = 0; i < list->plugins->len; ++i)
122 free(g_ptr_array_index(list->plugins, i));
123 g_ptr_array_free(list->plugins, TRUE);
124 }
126 static void
127 next_plugin(struct plugin_cycle *cycle);
129 static void
130 plugin_eof(struct plugin_cycle *cycle, struct plugin_pipe *p)
131 {
132 g_io_channel_unref(p->channel);
133 close(p->fd);
134 p->fd = -1;
136 /* Only if both pipes are have EOF status we are done */
137 if (cycle->pipe_stdout.fd != -1 || cycle->pipe_stderr.fd != -1)
138 return;
140 int status, ret = waitpid(cycle->pid, &status, 0);
141 cycle->pid = -1;
143 if (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
144 /* If we encountered an error other than service unavailable
145 * (69), log it for later. If all plugins fail, we may get
146 * some hints for debugging.*/
147 if (cycle->pipe_stderr.data->len > 0 &&
148 WEXITSTATUS(status) != 69)
149 g_string_append_printf(cycle->all_errors,
150 "*** %s ***\n\n%s\n",
151 cycle->argv[0],
152 cycle->pipe_stderr.data->str);
154 /* the plugin has failed */
155 g_string_free(cycle->pipe_stdout.data, TRUE);
156 cycle->pipe_stdout.data = NULL;
157 g_string_free(cycle->pipe_stderr.data, TRUE);
158 cycle->pipe_stderr.data = NULL;
160 next_plugin(cycle);
161 } else {
162 /* success: invoke the callback */
163 cycle->callback(cycle->pipe_stdout.data, true,
164 cycle->argv[0], cycle->callback_data);
165 }
166 }
168 static gboolean
169 plugin_data(gcc_unused GIOChannel *source,
170 gcc_unused GIOCondition condition, gpointer data)
171 {
172 struct plugin_pipe *p = data;
173 assert(p->fd >= 0);
175 struct plugin_cycle *cycle = p->cycle;
176 assert(cycle != NULL);
177 assert(cycle->pid > 0);
179 char buffer[256];
180 ssize_t nbytes = condition & G_IO_IN
181 ? read(p->fd, buffer, sizeof(buffer))
182 : 0;
183 if (nbytes <= 0) {
184 plugin_eof(cycle, p);
185 return FALSE;
186 }
188 g_string_append_len(p->data, buffer, nbytes);
189 return TRUE;
190 }
192 /**
193 * This is a timer callback which calls the plugin callback "some time
194 * later". This solves the problem that plugin_run() may fail
195 * immediately, leaving its return value in an undefined state.
196 * Instead, install a timer which calls the plugin callback in the
197 * moment after.
198 */
199 static gboolean
200 plugin_delayed_fail(gpointer data)
201 {
202 struct plugin_cycle *cycle = data;
204 assert(cycle != NULL);
205 assert(cycle->pipe_stdout.fd < 0);
206 assert(cycle->pipe_stderr.fd < 0);
207 assert(cycle->pid < 0);
209 cycle->callback(cycle->all_errors, false, NULL, cycle->callback_data);
211 return FALSE;
212 }
214 static void
215 plugin_fd_add(struct plugin_cycle *cycle, struct plugin_pipe *p, int fd)
216 {
217 p->cycle = cycle;
218 p->fd = fd;
219 p->data = g_string_new(NULL);
220 p->channel = g_io_channel_unix_new(fd);
221 p->event_id = g_io_add_watch(p->channel, G_IO_IN|G_IO_HUP,
222 plugin_data, cycle);
223 }
225 static int
226 start_plugin(struct plugin_cycle *cycle, const char *plugin_path)
227 {
228 assert(cycle != NULL);
229 assert(cycle->pid < 0);
230 assert(cycle->pipe_stdout.fd < 0);
231 assert(cycle->pipe_stderr.fd < 0);
232 assert(cycle->pipe_stdout.data == NULL);
233 assert(cycle->pipe_stderr.data == NULL);
235 /* set new program name, but free the one from the previous
236 plugin */
237 g_free(cycle->argv[0]);
238 cycle->argv[0] = g_path_get_basename(plugin_path);
240 int fds_stdout[2];
241 if (pipe(fds_stdout) < 0)
242 return -1;
244 int fds_stderr[2];
245 if (pipe(fds_stderr) < 0) {
246 close(fds_stdout[0]);
247 close(fds_stdout[1]);
248 return -1;
249 }
251 pid_t pid = fork();
253 if (pid < 0) {
254 close(fds_stdout[0]);
255 close(fds_stdout[1]);
256 close(fds_stderr[0]);
257 close(fds_stderr[1]);
258 return -1;
259 }
261 if (pid == 0) {
262 dup2(fds_stdout[1], 1);
263 dup2(fds_stderr[1], 2);
264 close(fds_stdout[0]);
265 close(fds_stdout[1]);
266 close(fds_stderr[0]);
267 close(fds_stderr[1]);
268 close(0);
269 /* XXX close other fds? */
271 execv(plugin_path, cycle->argv);
272 _exit(1);
273 }
275 close(fds_stdout[1]);
276 close(fds_stderr[1]);
278 cycle->pid = pid;
280 /* XXX CLOEXEC? */
282 plugin_fd_add(cycle, &cycle->pipe_stdout, fds_stdout[0]);
283 plugin_fd_add(cycle, &cycle->pipe_stderr, fds_stderr[0]);
285 return 0;
286 }
288 static void
289 next_plugin(struct plugin_cycle *cycle)
290 {
291 assert(cycle->pid < 0);
292 assert(cycle->pipe_stdout.fd < 0);
293 assert(cycle->pipe_stderr.fd < 0);
294 assert(cycle->pipe_stdout.data == NULL);
295 assert(cycle->pipe_stderr.data == NULL);
297 if (cycle->next_plugin >= cycle->list->plugins->len) {
298 /* no plugins left */
299 g_timeout_add(0, plugin_delayed_fail, cycle);
300 return;
301 }
303 const char *plugin_path = g_ptr_array_index(cycle->list->plugins,
304 cycle->next_plugin++);
305 if (start_plugin(cycle, plugin_path) < 0) {
306 /* system error */
307 g_timeout_add(0, plugin_delayed_fail, cycle);
308 return;
309 }
310 }
312 static char **
313 make_argv(const char*const* args)
314 {
315 unsigned num = 0;
316 while (args[num] != NULL)
317 ++num;
318 num += 2;
320 char **ret = g_new(char *, num);
322 /* reserve space for the program name */
323 *ret++ = NULL;
325 while (*args != NULL)
326 *ret++ = g_strdup(*args++);
328 /* end of argument vector */
329 *ret++ = NULL;
331 return ret - num;
332 }
334 struct plugin_cycle *
335 plugin_run(struct plugin_list *list, const char *const*args,
336 plugin_callback_t callback, void *callback_data)
337 {
338 struct plugin_cycle *cycle = g_new(struct plugin_cycle, 1);
340 assert(args != NULL);
342 cycle->list = list;
343 cycle->argv = make_argv(args);
344 cycle->callback = callback;
345 cycle->callback_data = callback_data;
346 cycle->next_plugin = 0;
347 cycle->pid = -1;
348 cycle->pipe_stdout.fd = -1;
349 cycle->pipe_stderr.fd = -1;
350 cycle->pipe_stdout.data = NULL;
351 cycle->pipe_stderr.data = NULL;
353 cycle->all_errors = g_string_new(NULL);
354 next_plugin(cycle);
356 return cycle;
357 }
359 static void
360 plugin_fd_remove(struct plugin_pipe *p)
361 {
362 if (p->fd >= 0) {
363 g_source_remove(p->event_id);
364 g_io_channel_unref(p->channel);
365 close(p->fd);
366 }
367 }
369 void
370 plugin_stop(struct plugin_cycle *cycle)
371 {
372 plugin_fd_remove(&cycle->pipe_stdout);
373 plugin_fd_remove(&cycle->pipe_stderr);
375 if (cycle->pid > 0) {
376 /* kill the plugin process */
377 int status;
379 kill(cycle->pid, SIGTERM);
380 waitpid(cycle->pid, &status, 0);
381 }
383 /* free data that has been received */
384 if (cycle->pipe_stdout.data != NULL)
385 g_string_free(cycle->pipe_stdout.data, TRUE);
386 if (cycle->pipe_stderr.data != NULL)
387 g_string_free(cycle->pipe_stderr.data, TRUE);
388 if (cycle->all_errors != NULL)
389 g_string_free(cycle->all_errors, TRUE);
391 /* free argument list */
392 for (guint i = 0; i == 0 || cycle->argv[i] != NULL; ++i)
393 g_free(cycle->argv[i]);
394 g_free(cycle->argv);
396 g_free(cycle);
397 }