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 bool
76 plugin_list_load_directory(struct plugin_list *list, const char *path)
77 {
78 GDir *dir;
79 const char *name;
80 char *plugin;
81 bool ret;
83 dir = g_dir_open(path, 0, NULL);
84 if (dir == NULL)
85 return false;
87 while ((name = g_dir_read_name(dir)) != NULL) {
88 plugin = g_build_filename(path, name, NULL);
89 ret = register_plugin(list, plugin);
90 if (!ret)
91 g_free(plugin);
92 }
94 g_dir_close(dir);
95 return true;
96 }
98 void plugin_list_deinit(struct plugin_list *list)
99 {
100 for (guint i = 0; i < list->plugins->len; ++i)
101 free(g_ptr_array_index(list->plugins, i));
102 g_ptr_array_free(list->plugins, TRUE);
103 }
105 static void
106 next_plugin(struct plugin_cycle *cycle);
108 static void
109 plugin_eof(struct plugin_cycle *cycle)
110 {
111 int ret, status;
113 g_io_channel_unref(cycle->channel);
114 close(cycle->fd);
115 cycle->fd = -1;
117 ret = waitpid(cycle->pid, &status, 0);
118 cycle->pid = -1;
120 if (ret < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
121 /* the plugin has failed */
122 g_string_free(cycle->data, TRUE);
123 cycle->data = NULL;
125 next_plugin(cycle);
126 } else {
127 /* success: invoke the callback */
128 cycle->callback(cycle->data, cycle->callback_data);
129 }
130 }
132 static gboolean
133 plugin_data(G_GNUC_UNUSED GIOChannel *source,
134 G_GNUC_UNUSED GIOCondition condition, gpointer data)
135 {
136 struct plugin_cycle *cycle = data;
137 char buffer[256];
138 ssize_t nbytes;
140 assert(cycle != NULL);
141 assert(cycle->fd >= 0);
142 assert(cycle->pid > 0);
143 assert(source == cycle->channel);
145 nbytes = condition & G_IO_IN
146 ? read(cycle->fd, buffer, sizeof(buffer))
147 : 0;
148 if (nbytes <= 0) {
149 plugin_eof(cycle);
150 return FALSE;
151 }
153 g_string_append_len(cycle->data, buffer, nbytes);
154 return TRUE;
155 }
157 /**
158 * This is a timer callback which calls the plugin callback "some time
159 * later". This solves the problem that plugin_run() may fail
160 * immediately, leaving its return value in an undefined state.
161 * Instead, install a timer which calls the plugin callback in the
162 * moment after.
163 */
164 static gboolean
165 plugin_delayed_fail(gpointer data)
166 {
167 struct plugin_cycle *cycle = data;
169 assert(cycle != NULL);
170 assert(cycle->fd < 0);
171 assert(cycle->pid < 0);
172 assert(cycle->data == NULL);
174 cycle->callback(NULL, cycle->callback_data);
176 return FALSE;
177 }
179 static int
180 start_plugin(struct plugin_cycle *cycle, const char *plugin_path)
181 {
182 int ret, fds[2];
183 pid_t pid;
185 assert(cycle != NULL);
186 assert(cycle->pid < 0);
187 assert(cycle->fd < 0);
188 assert(cycle->data == NULL);
190 /* set new program name, but free the one from the previous
191 plugin */
192 g_free(cycle->argv[0]);
193 cycle->argv[0] = g_path_get_basename(plugin_path);
195 ret = pipe(fds);
196 if (ret < 0)
197 return -1;
199 pid = fork();
201 if (pid < 0) {
202 close(fds[0]);
203 close(fds[1]);
204 return -1;
205 }
207 if (pid == 0) {
208 dup2(fds[1], 1);
209 dup2(fds[1], 1);
210 close(fds[0]);
211 close(fds[1]);
212 close(0);
213 /* XXX close other fds? */
215 execv(plugin_path, cycle->argv);
216 _exit(1);
217 }
219 close(fds[1]);
221 cycle->pid = pid;
222 cycle->fd = fds[0];
223 cycle->data = g_string_new(NULL);
225 /* XXX CLOEXEC? */
227 cycle->channel = g_io_channel_unix_new(cycle->fd);
228 cycle->event_id = g_io_add_watch(cycle->channel, G_IO_IN|G_IO_HUP,
229 plugin_data, cycle);
231 return 0;
232 }
234 static void
235 next_plugin(struct plugin_cycle *cycle)
236 {
237 const char *plugin_path;
238 int ret = -1;
240 assert(cycle->pid < 0);
241 assert(cycle->fd < 0);
242 assert(cycle->data == NULL);
244 if (cycle->next_plugin >= cycle->list->plugins->len) {
245 /* no plugins left */
246 g_timeout_add(0, plugin_delayed_fail, cycle);
247 return;
248 }
250 plugin_path = g_ptr_array_index(cycle->list->plugins,
251 cycle->next_plugin++);
252 ret = start_plugin(cycle, plugin_path);
253 if (ret < 0) {
254 /* system error */
255 g_timeout_add(0, plugin_delayed_fail, cycle);
256 return;
257 }
258 }
260 static char **
261 make_argv(const char*const* args)
262 {
263 unsigned num = 0;
264 char **ret;
266 while (args[num] != NULL)
267 ++num;
268 num += 2;
270 ret = g_new(char*, num);
272 /* reserve space for the program name */
273 *ret++ = NULL;
275 do {
276 *ret++ = g_strdup(*args++);
277 } while (*args != NULL);
279 /* end of argument vector */
280 *ret++ = NULL;
282 return ret - num;
283 }
285 struct plugin_cycle *
286 plugin_run(struct plugin_list *list, const char *const*args,
287 plugin_callback_t callback, void *callback_data)
288 {
289 struct plugin_cycle *cycle = g_new(struct plugin_cycle, 1);
291 assert(args != NULL);
293 cycle->list = list;
294 cycle->argv = make_argv(args);
295 cycle->callback = callback;
296 cycle->callback_data = callback_data;
297 cycle->next_plugin = 0;
298 cycle->pid = -1;
299 cycle->fd = -1;
300 cycle->data = NULL;
302 next_plugin(cycle);
304 return cycle;
305 }
307 void
308 plugin_stop(struct plugin_cycle *cycle)
309 {
310 if (cycle->fd >= 0) {
311 /* close the pipe to the plugin */
312 g_source_remove(cycle->event_id);
313 g_io_channel_unref(cycle->channel);
314 close(cycle->fd);
315 }
317 if (cycle->pid > 0) {
318 /* kill the plugin process */
319 int status;
321 kill(cycle->pid, SIGTERM);
322 waitpid(cycle->pid, &status, 0);
323 }
325 if (cycle->data != NULL)
326 /* free data that has been received */
327 g_string_free(cycle->data, TRUE);
329 /* free argument list */
330 for (guint i = 0; i == 0 || cycle->argv[i] != NULL; ++i)
331 g_free(cycle->argv[i]);
332 g_free(cycle->argv);
334 g_free(cycle);
335 }