1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2018 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.
21 #include "io/Path.hxx"
33 #include <sys/signal.h>
41 /** the pipe to the plugin process, or -1 if none is currently
44 /** the GLib IO watch of #fd */
46 /** the output of the current plugin */
57 g_source_remove(event_id);
64 /** the plugin list; used for traversing to the next plugin */
67 /** arguments passed to execv() */
70 /** caller defined callback function */
71 plugin_callback_t callback;
72 /** caller defined pointer passed to #callback */
75 /** the index of the next plugin which is going to be
77 guint next_plugin = 0;
79 /** the pid of the plugin process, or -1 if none is currently
83 /** the stdout pipe */
84 PluginPipe pipe_stdout;
85 /** the stderr pipe */
86 PluginPipe pipe_stderr;
88 /** list of all error messages from failed plugins */
89 std::string all_errors;
91 PluginCycle(PluginList &_list, char **_argv,
92 plugin_callback_t _callback, void *_callback_data)
93 :list(&_list), argv(_argv),
94 callback(_callback), callback_data(_callback_data),
98 /* free argument list */
99 for (unsigned i = 0; i == 0 || argv[i] != nullptr; ++i)
106 register_plugin(PluginList *list, std::string &&path)
109 if (stat(path.c_str(), &st) < 0)
112 list->plugins.emplace_back(std::move(path));
117 plugin_list_load_directory(PluginList *list, const char *path)
119 GDir *dir = g_dir_open(path, 0, nullptr);
124 while ((name = g_dir_read_name(dir)) != nullptr) {
125 register_plugin(list, BuildPath(path, name));
130 std::sort(list->plugins.begin(), list->plugins.end());
136 next_plugin(PluginCycle *cycle);
139 plugin_eof(PluginCycle *cycle, PluginPipe *p)
144 /* Only if both pipes are have EOF status we are done */
145 if (cycle->pipe_stdout.fd != -1 || cycle->pipe_stderr.fd != -1)
148 int status, ret = waitpid(cycle->pid, &status, 0);
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.empty() &&
156 WEXITSTATUS(status) != 69) {
157 cycle->all_errors += "*** ";
158 cycle->all_errors += cycle->argv[0];
159 cycle->all_errors += " ***\n\n";
160 cycle->all_errors += cycle->pipe_stderr.data;
161 cycle->all_errors += "\n";
164 /* the plugin has failed */
165 cycle->pipe_stdout.data.clear();
166 cycle->pipe_stderr.data.clear();
170 /* success: invoke the callback */
171 cycle->callback(std::move(cycle->pipe_stdout.data), true,
172 cycle->argv[0], cycle->callback_data);
177 plugin_data(gcc_unused GIOChannel *source,
178 gcc_unused GIOCondition condition, gpointer data)
180 auto *p = (PluginPipe *)data;
183 PluginCycle *cycle = p->cycle;
184 assert(cycle != nullptr);
185 assert(cycle->pid > 0);
188 ssize_t nbytes = condition & G_IO_IN
189 ? read(p->fd, buffer, sizeof(buffer))
192 plugin_eof(cycle, p);
196 p->data.append(buffer, nbytes);
201 * This is a timer callback which calls the plugin callback "some time
202 * later". This solves the problem that plugin_run() may fail
203 * immediately, leaving its return value in an undefined state.
204 * Instead, install a timer which calls the plugin callback in the
208 plugin_delayed_fail(gpointer data)
210 auto *cycle = (PluginCycle *)data;
212 assert(cycle != nullptr);
213 assert(cycle->pipe_stdout.fd < 0);
214 assert(cycle->pipe_stderr.fd < 0);
215 assert(cycle->pid < 0);
217 cycle->callback(std::move(cycle->all_errors), false, nullptr,
218 cycle->callback_data);
224 plugin_fd_add(PluginCycle *cycle, PluginPipe *p, int fd)
228 GIOChannel *channel = g_io_channel_unix_new(fd);
229 p->event_id = g_io_add_watch(channel, GIOCondition(G_IO_IN|G_IO_HUP),
231 g_io_channel_unref(channel);
235 start_plugin(PluginCycle *cycle, const char *plugin_path)
237 assert(cycle != nullptr);
238 assert(cycle->pid < 0);
239 assert(cycle->pipe_stdout.fd < 0);
240 assert(cycle->pipe_stderr.fd < 0);
241 assert(cycle->pipe_stdout.data.empty());
242 assert(cycle->pipe_stderr.data.empty());
244 /* set new program name, but free the one from the previous
246 g_free(cycle->argv[0]);
247 cycle->argv[0] = g_path_get_basename(plugin_path);
250 if (pipe(fds_stdout) < 0)
254 if (pipe(fds_stderr) < 0) {
255 close(fds_stdout[0]);
256 close(fds_stdout[1]);
263 close(fds_stdout[0]);
264 close(fds_stdout[1]);
265 close(fds_stderr[0]);
266 close(fds_stderr[1]);
271 dup2(fds_stdout[1], 1);
272 dup2(fds_stderr[1], 2);
273 close(fds_stdout[0]);
274 close(fds_stdout[1]);
275 close(fds_stderr[0]);
276 close(fds_stderr[1]);
278 /* XXX close other fds? */
280 execv(plugin_path, cycle->argv);
284 close(fds_stdout[1]);
285 close(fds_stderr[1]);
291 plugin_fd_add(cycle, &cycle->pipe_stdout, fds_stdout[0]);
292 plugin_fd_add(cycle, &cycle->pipe_stderr, fds_stderr[0]);
298 next_plugin(PluginCycle *cycle)
300 assert(cycle->pid < 0);
301 assert(cycle->pipe_stdout.fd < 0);
302 assert(cycle->pipe_stderr.fd < 0);
303 assert(cycle->pipe_stdout.data.empty());
304 assert(cycle->pipe_stderr.data.empty());
306 if (cycle->next_plugin >= cycle->list->plugins.size()) {
307 /* no plugins left */
308 g_idle_add(plugin_delayed_fail, cycle);
312 const char *plugin_path = (const char *)
313 cycle->list->plugins[cycle->next_plugin++].c_str();
314 if (start_plugin(cycle, plugin_path) < 0) {
316 g_idle_add(plugin_delayed_fail, cycle);
322 make_argv(const char*const* args)
325 while (args[num] != nullptr)
329 char **ret = g_new(char *, num);
331 /* reserve space for the program name */
334 while (*args != nullptr)
335 *ret++ = g_strdup(*args++);
337 /* end of argument vector */
344 plugin_run(PluginList *list, const char *const*args,
345 plugin_callback_t callback, void *callback_data)
347 assert(args != nullptr);
349 auto *cycle = new PluginCycle(*list, make_argv(args),
350 callback, callback_data);
357 plugin_stop(PluginCycle *cycle)
359 if (cycle->pid > 0) {
360 /* kill the plugin process */
362 cycle->pipe_stdout.Close();
363 cycle->pipe_stderr.Close();
367 kill(cycle->pid, SIGTERM);
368 waitpid(cycle->pid, &status, 0);