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.
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 /** the pipe to the plugin process, or -1 if none is currently
33 open */
34 int fd;
35 /** the GLib channel of #fd */
36 GIOChannel *channel;
37 /** the GLib IO watch of #channel */
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 int ret;
76 struct stat st;
78 ret = stat(path, &st);
79 if (ret < 0)
80 return false;
82 g_ptr_array_add(list->plugins, path);
83 return true;
84 }
86 static gint
87 plugin_compare_func_alpha(gconstpointer plugin1, gconstpointer plugin2)
88 {
89 return strcmp(* (char * const *) plugin1, * (char * const *) plugin2);
90 }
92 static void
93 plugin_list_sort(struct plugin_list *list, GCompareFunc compare_func)
94 {
95 g_ptr_array_sort(list->plugins,
96 compare_func);
97 }
99 bool
100 plugin_list_load_directory(struct plugin_list *list, const char *path)
101 {
102 GDir *dir;
103 const char *name;
104 char *plugin;
105 bool ret;
107 dir = g_dir_open(path, 0, NULL);
108 if (dir == NULL)
109 return false;
111 while ((name = g_dir_read_name(dir)) != NULL) {
112 plugin = g_build_filename(path, name, NULL);
113 ret = register_plugin(list, plugin);
114 if (!ret)
115 g_free(plugin);
116 }
118 g_dir_close(dir);
120 plugin_list_sort(list, plugin_compare_func_alpha);
122 return true;
123 }
125 void plugin_list_deinit(struct plugin_list *list)
126 {
127 for (guint i = 0; i < list->plugins->len; ++i)
128 free(g_ptr_array_index(list->plugins, i));
129 g_ptr_array_free(list->plugins, TRUE);
130 }
132 static void
133 next_plugin(struct plugin_cycle *cycle);
135 static void
136 plugin_eof(struct plugin_cycle *cycle, struct plugin_pipe *p)
137 {
138 int ret, status;
140 g_io_channel_unref(p->channel);
141 close(p->fd);
142 p->fd = -1;
144 /* Only if both pipes are have EOF status we are done */
145 if (cycle->pipe_stdout.fd != -1 || cycle->pipe_stderr.fd != -1)
146 return;
148 ret = waitpid(cycle->pid, &status, 0);
149 cycle->pid = -1;
151 if (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
152 /* If we encountered an error other than service unavailable
153 * (69), log it for later. If all plugins fail, we may get
154 * some hints for debugging.*/
155 if (cycle->pipe_stderr.data->len > 0 &&
156 WEXITSTATUS(status) != 69)
157 g_string_append_printf(cycle->all_errors,
158 "*** %s ***\n\n%s\n",
159 cycle->argv[0],
160 cycle->pipe_stderr.data->str);
162 /* the plugin has failed */
163 g_string_free(cycle->pipe_stdout.data, TRUE);
164 cycle->pipe_stdout.data = NULL;
165 g_string_free(cycle->pipe_stderr.data, TRUE);
166 cycle->pipe_stderr.data = NULL;
168 next_plugin(cycle);
169 } else {
170 /* success: invoke the callback */
171 cycle->callback(cycle->pipe_stdout.data, true,
172 cycle->argv[0], cycle->callback_data);
173 }
174 }
176 static gboolean
177 plugin_data(gcc_unused GIOChannel *source,
178 gcc_unused GIOCondition condition, gpointer data)
179 {
180 struct plugin_cycle *cycle = data;
181 struct plugin_pipe *p = NULL;
182 char buffer[256];
183 ssize_t nbytes;
185 assert(cycle != NULL);
186 assert(cycle->pid > 0);
187 if (source == cycle->pipe_stdout.channel)
188 p = &cycle->pipe_stdout;
189 else if (source == cycle->pipe_stderr.channel)
190 p = &cycle->pipe_stderr;
191 assert(p != NULL);
192 assert(p->fd >= 0);
194 nbytes = condition & G_IO_IN ? read(p->fd, buffer, sizeof(buffer)) : 0;
195 if (nbytes <= 0) {
196 plugin_eof(cycle, p);
197 return FALSE;
198 }
200 g_string_append_len(p->data, buffer, nbytes);
201 return TRUE;
202 }
204 /**
205 * This is a timer callback which calls the plugin callback "some time
206 * later". This solves the problem that plugin_run() may fail
207 * immediately, leaving its return value in an undefined state.
208 * Instead, install a timer which calls the plugin callback in the
209 * moment after.
210 */
211 static gboolean
212 plugin_delayed_fail(gpointer data)
213 {
214 struct plugin_cycle *cycle = data;
216 assert(cycle != NULL);
217 assert(cycle->pipe_stdout.fd < 0);
218 assert(cycle->pipe_stderr.fd < 0);
219 assert(cycle->pid < 0);
221 cycle->callback(cycle->all_errors, false, NULL, cycle->callback_data);
223 return FALSE;
224 }
226 static void
227 plugin_fd_add(struct plugin_cycle *cycle, struct plugin_pipe *p, int fd)
228 {
229 p->fd = fd;
230 p->data = g_string_new(NULL);
231 p->channel = g_io_channel_unix_new(fd);
232 p->event_id = g_io_add_watch(p->channel, G_IO_IN|G_IO_HUP,
233 plugin_data, cycle);
234 }
236 static int
237 start_plugin(struct plugin_cycle *cycle, const char *plugin_path)
238 {
239 int ret, fds_stdout[2], fds_stderr[2];
240 pid_t pid;
242 assert(cycle != NULL);
243 assert(cycle->pid < 0);
244 assert(cycle->pipe_stdout.fd < 0);
245 assert(cycle->pipe_stderr.fd < 0);
246 assert(cycle->pipe_stdout.data == NULL);
247 assert(cycle->pipe_stderr.data == NULL);
249 /* set new program name, but free the one from the previous
250 plugin */
251 g_free(cycle->argv[0]);
252 cycle->argv[0] = g_path_get_basename(plugin_path);
254 ret = pipe(fds_stdout);
255 if (ret < 0)
256 return -1;
257 ret = pipe(fds_stderr);
258 if (ret < 0) {
259 close(fds_stdout[0]);
260 close(fds_stdout[1]);
261 return -1;
262 }
264 pid = fork();
266 if (pid < 0) {
267 close(fds_stdout[0]);
268 close(fds_stdout[1]);
269 close(fds_stderr[0]);
270 close(fds_stderr[1]);
271 return -1;
272 }
274 if (pid == 0) {
275 dup2(fds_stdout[1], 1);
276 dup2(fds_stderr[1], 2);
277 close(fds_stdout[0]);
278 close(fds_stdout[1]);
279 close(fds_stderr[0]);
280 close(fds_stderr[1]);
281 close(0);
282 /* XXX close other fds? */
284 execv(plugin_path, cycle->argv);
285 _exit(1);
286 }
288 close(fds_stdout[1]);
289 close(fds_stderr[1]);
291 cycle->pid = pid;
293 /* XXX CLOEXEC? */
295 plugin_fd_add(cycle, &cycle->pipe_stdout, fds_stdout[0]);
296 plugin_fd_add(cycle, &cycle->pipe_stderr, fds_stderr[0]);
298 return 0;
299 }
301 static void
302 next_plugin(struct plugin_cycle *cycle)
303 {
304 const char *plugin_path;
305 int ret = -1;
307 assert(cycle->pid < 0);
308 assert(cycle->pipe_stdout.fd < 0);
309 assert(cycle->pipe_stderr.fd < 0);
310 assert(cycle->pipe_stdout.data == NULL);
311 assert(cycle->pipe_stderr.data == NULL);
313 if (cycle->next_plugin >= cycle->list->plugins->len) {
314 /* no plugins left */
315 g_timeout_add(0, plugin_delayed_fail, cycle);
316 return;
317 }
319 plugin_path = g_ptr_array_index(cycle->list->plugins,
320 cycle->next_plugin++);
321 ret = start_plugin(cycle, plugin_path);
322 if (ret < 0) {
323 /* system error */
324 g_timeout_add(0, plugin_delayed_fail, cycle);
325 return;
326 }
327 }
329 static char **
330 make_argv(const char*const* args)
331 {
332 unsigned num = 0;
333 char **ret;
335 while (args[num] != NULL)
336 ++num;
337 num += 2;
339 ret = g_new(char*, num);
341 /* reserve space for the program name */
342 *ret++ = NULL;
344 while (*args != NULL)
345 *ret++ = g_strdup(*args++);
347 /* end of argument vector */
348 *ret++ = NULL;
350 return ret - num;
351 }
353 struct plugin_cycle *
354 plugin_run(struct plugin_list *list, const char *const*args,
355 plugin_callback_t callback, void *callback_data)
356 {
357 struct plugin_cycle *cycle = g_new(struct plugin_cycle, 1);
359 assert(args != NULL);
361 cycle->list = list;
362 cycle->argv = make_argv(args);
363 cycle->callback = callback;
364 cycle->callback_data = callback_data;
365 cycle->next_plugin = 0;
366 cycle->pid = -1;
367 cycle->pipe_stdout.fd = -1;
368 cycle->pipe_stderr.fd = -1;
369 cycle->pipe_stdout.data = NULL;
370 cycle->pipe_stderr.data = NULL;
372 cycle->all_errors = g_string_new(NULL);
373 next_plugin(cycle);
375 return cycle;
376 }
378 static void
379 plugin_fd_remove(struct plugin_pipe *p)
380 {
381 if (p->fd >= 0) {
382 g_source_remove(p->event_id);
383 g_io_channel_unref(p->channel);
384 close(p->fd);
385 }
386 }
388 void
389 plugin_stop(struct plugin_cycle *cycle)
390 {
391 plugin_fd_remove(&cycle->pipe_stdout);
392 plugin_fd_remove(&cycle->pipe_stderr);
394 if (cycle->pid > 0) {
395 /* kill the plugin process */
396 int status;
398 kill(cycle->pid, SIGTERM);
399 waitpid(cycle->pid, &status, 0);
400 }
402 /* free data that has been received */
403 if (cycle->pipe_stdout.data != NULL)
404 g_string_free(cycle->pipe_stdout.data, TRUE);
405 if (cycle->pipe_stderr.data != NULL)
406 g_string_free(cycle->pipe_stderr.data, TRUE);
407 if (cycle->all_errors != NULL)
408 g_string_free(cycle->all_errors, TRUE);
410 /* free argument list */
411 for (guint i = 0; i == 0 || cycle->argv[i] != NULL; ++i)
412 g_free(cycle->argv[i]);
413 g_free(cycle->argv);
415 g_free(cycle);
416 }