Compiler.h: move to util/
[ncmpc-debian.git] / src / plugin.cxx
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2018 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  */
19
20 #include "plugin.hxx"
21 #include "io/Path.hxx"
22 #include "util/Compiler.h"
23
24 #include <glib.h>
25
26 #include <algorithm>
27
28 #include <assert.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <sys/stat.h>
33 #include <sys/signal.h>
34 #include <sys/wait.h>
35
36 struct PluginCycle;
37
38 struct PluginPipe {
39         PluginCycle *cycle;
40
41         /** the pipe to the plugin process, or -1 if none is currently
42             open */
43         int fd = -1;
44         /** the GLib IO watch of #fd */
45         guint event_id;
46         /** the output of the current plugin */
47         std::string data;
48
49         ~PluginPipe() {
50                 Close();
51         }
52
53         void Close() {
54                 if (fd < 0)
55                         return;
56
57                 g_source_remove(event_id);
58                 close(fd);
59                 fd = -1;
60         }
61 };
62
63 struct PluginCycle {
64         /** the plugin list; used for traversing to the next plugin */
65         PluginList *list;
66
67         /** arguments passed to execv() */
68         char **argv;
69
70         /** caller defined callback function */
71         plugin_callback_t callback;
72         /** caller defined pointer passed to #callback */
73         void *callback_data;
74
75         /** the index of the next plugin which is going to be
76             invoked */
77         guint next_plugin = 0;
78
79         /** the pid of the plugin process, or -1 if none is currently
80             running */
81         pid_t pid = -1;
82
83         /** the stdout pipe */
84         PluginPipe pipe_stdout;
85         /** the stderr pipe */
86         PluginPipe pipe_stderr;
87
88         /** list of all error messages from failed plugins */
89         std::string all_errors;
90
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),
95                  next_plugin(0) {}
96
97         ~PluginCycle() {
98                 /* free argument list */
99                 for (unsigned i = 0; i == 0 || argv[i] != nullptr; ++i)
100                         g_free(argv[i]);
101                 g_free(argv);
102         }
103 };
104
105 static bool
106 register_plugin(PluginList *list, std::string &&path)
107 {
108         struct stat st;
109         if (stat(path.c_str(), &st) < 0)
110                 return false;
111
112         list->plugins.emplace_back(std::move(path));
113         return true;
114 }
115
116 bool
117 plugin_list_load_directory(PluginList *list, const char *path)
118 {
119         GDir *dir = g_dir_open(path, 0, nullptr);
120         if (dir == nullptr)
121                 return false;
122
123         const char *name;
124         while ((name = g_dir_read_name(dir)) != nullptr) {
125                 register_plugin(list, BuildPath(path, name));
126         }
127
128         g_dir_close(dir);
129
130         std::sort(list->plugins.begin(), list->plugins.end());
131
132         return true;
133 }
134
135 static void
136 next_plugin(PluginCycle *cycle);
137
138 static void
139 plugin_eof(PluginCycle *cycle, PluginPipe *p)
140 {
141         close(p->fd);
142         p->fd = -1;
143
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;
147
148         int status, ret = waitpid(cycle->pid, &status, 0);
149         cycle->pid = -1;
150
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";
162                 }
163
164                 /* the plugin has failed */
165                 cycle->pipe_stdout.data.clear();
166                 cycle->pipe_stderr.data.clear();
167
168                 next_plugin(cycle);
169         } else {
170                 /* success: invoke the callback */
171                 cycle->callback(std::move(cycle->pipe_stdout.data), true,
172                                 cycle->argv[0], cycle->callback_data);
173         }
174 }
175
176 static gboolean
177 plugin_data(gcc_unused GIOChannel *source,
178             gcc_unused GIOCondition condition, gpointer data)
179 {
180         auto *p = (PluginPipe *)data;
181         assert(p->fd >= 0);
182
183         PluginCycle *cycle = p->cycle;
184         assert(cycle != nullptr);
185         assert(cycle->pid > 0);
186
187         char buffer[256];
188         ssize_t nbytes = condition & G_IO_IN
189                 ? read(p->fd, buffer, sizeof(buffer))
190                 : 0;
191         if (nbytes <= 0) {
192                 plugin_eof(cycle, p);
193                 return false;
194         }
195
196         p->data.append(buffer, nbytes);
197         return true;
198 }
199
200 /**
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
205  * moment after.
206  */
207 static gboolean
208 plugin_delayed_fail(gpointer data)
209 {
210         auto *cycle = (PluginCycle *)data;
211
212         assert(cycle != nullptr);
213         assert(cycle->pipe_stdout.fd < 0);
214         assert(cycle->pipe_stderr.fd < 0);
215         assert(cycle->pid < 0);
216
217         cycle->callback(std::move(cycle->all_errors), false, nullptr,
218                         cycle->callback_data);
219
220         return false;
221 }
222
223 static void
224 plugin_fd_add(PluginCycle *cycle, PluginPipe *p, int fd)
225 {
226         p->cycle = cycle;
227         p->fd = 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),
230                                      plugin_data, p);
231         g_io_channel_unref(channel);
232 }
233
234 static int
235 start_plugin(PluginCycle *cycle, const char *plugin_path)
236 {
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());
243
244         /* set new program name, but free the one from the previous
245            plugin */
246         g_free(cycle->argv[0]);
247         cycle->argv[0] = g_path_get_basename(plugin_path);
248
249         int fds_stdout[2];
250         if (pipe(fds_stdout) < 0)
251                 return -1;
252
253         int fds_stderr[2];
254         if (pipe(fds_stderr) < 0) {
255                 close(fds_stdout[0]);
256                 close(fds_stdout[1]);
257                 return -1;
258         }
259
260         pid_t pid = fork();
261
262         if (pid < 0) {
263                 close(fds_stdout[0]);
264                 close(fds_stdout[1]);
265                 close(fds_stderr[0]);
266                 close(fds_stderr[1]);
267                 return -1;
268         }
269
270         if (pid == 0) {
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]);
277                 close(0);
278                 /* XXX close other fds? */
279
280                 execv(plugin_path, cycle->argv);
281                 _exit(1);
282         }
283
284         close(fds_stdout[1]);
285         close(fds_stderr[1]);
286
287         cycle->pid = pid;
288
289         /* XXX CLOEXEC? */
290
291         plugin_fd_add(cycle, &cycle->pipe_stdout, fds_stdout[0]);
292         plugin_fd_add(cycle, &cycle->pipe_stderr, fds_stderr[0]);
293
294         return 0;
295 }
296
297 static void
298 next_plugin(PluginCycle *cycle)
299 {
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());
305
306         if (cycle->next_plugin >= cycle->list->plugins.size()) {
307                 /* no plugins left */
308                 g_idle_add(plugin_delayed_fail, cycle);
309                 return;
310         }
311
312         const char *plugin_path = (const char *)
313                 cycle->list->plugins[cycle->next_plugin++].c_str();
314         if (start_plugin(cycle, plugin_path) < 0) {
315                 /* system error */
316                 g_idle_add(plugin_delayed_fail, cycle);
317                 return;
318         }
319 }
320
321 static char **
322 make_argv(const char*const* args)
323 {
324         unsigned num = 0;
325         while (args[num] != nullptr)
326                 ++num;
327         num += 2;
328
329         char **ret = g_new(char *, num);
330
331         /* reserve space for the program name */
332         *ret++ = nullptr;
333
334         while (*args != nullptr)
335                 *ret++ = g_strdup(*args++);
336
337         /* end of argument vector */
338         *ret++ = nullptr;
339
340         return ret - num;
341 }
342
343 PluginCycle *
344 plugin_run(PluginList *list, const char *const*args,
345            plugin_callback_t callback, void *callback_data)
346 {
347         assert(args != nullptr);
348
349         auto *cycle = new PluginCycle(*list, make_argv(args),
350                                       callback, callback_data);
351         next_plugin(cycle);
352
353         return cycle;
354 }
355
356 void
357 plugin_stop(PluginCycle *cycle)
358 {
359         if (cycle->pid > 0) {
360                 /* kill the plugin process */
361
362                 cycle->pipe_stdout.Close();
363                 cycle->pipe_stderr.Close();
364
365                 int status;
366
367                 kill(cycle->pid, SIGTERM);
368                 waitpid(cycle->pid, &status, 0);
369         }
370
371         delete cycle;
372 }