1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2009 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
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;
112 }
114 void plugin_list_deinit(struct plugin_list *list)
115 {
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);
119 }
121 static void
122 next_plugin(struct plugin_cycle *cycle);
124 static void
125 plugin_eof(struct plugin_cycle *cycle)
126 {
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 }
146 }
148 static gboolean
149 plugin_data(G_GNUC_UNUSED GIOChannel *source,
150 G_GNUC_UNUSED GIOCondition condition, gpointer data)
151 {
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;
171 }
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)
182 {
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;
193 }
195 static int
196 start_plugin(struct plugin_cycle *cycle, const char *plugin_path)
197 {
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], 1);
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;
248 }
250 static void
251 next_plugin(struct plugin_cycle *cycle)
252 {
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 }
274 }
276 static char **
277 make_argv(const char*const* args)
278 {
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;
299 }
301 struct plugin_cycle *
302 plugin_run(struct plugin_list *list, const char *const*args,
303 plugin_callback_t callback, void *callback_data)
304 {
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;
321 }
323 void
324 plugin_stop(struct plugin_cycle *cycle)
325 {
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);
351 }