cc0c49037de6d21bfc44d3c6a6070ed9f7e9c268
[profile/ivi/pulseaudio-module-murphy-ivi.git] / augment / module-dir-watch.c
1 /***
2   This file is part of PulseAudio.
3
4   Copyright 2012 Ismo Puustinen
5
6   PulseAudio is free software; you can redistribute it and/or modify
7   it under the terms of the GNU Lesser General Public License as published
8   by the Free Software Foundation; either version 2.1 of the License,
9   or (at your option) any later version.
10
11   PulseAudio is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with PulseAudio; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19   USA.
20 ***/
21
22 #include <pulsecore/pulsecore-config.h>
23
24 #include <stdint.h>
25
26 #ifdef __linux__
27 #define HAVE_INOTIFY
28 #endif
29
30 #ifdef HAVE_INOTIFY
31 #include <sys/inotify.h>
32 #endif
33
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <limits.h>
38 #include <errno.h>
39
40 #include <sys/types.h>
41
42 #include <pulse/xmalloc.h>
43 #include <pulse/proplist.h>
44
45 #include <pulsecore/client.h>
46 #include <pulsecore/macro.h>
47 #include <pulsecore/module.h>
48 #include <pulsecore/core-util.h>
49 #include <pulsecore/core-error.h>
50 #include <pulsecore/log.h>
51 #include <pulsecore/llist.h>
52
53 #include "module-dir-watch-symdef.h"
54
55 PA_MODULE_AUTHOR("Ismo Puustinen");
56 PA_MODULE_DESCRIPTION("Directory watch module");
57 PA_MODULE_VERSION(PACKAGE_VERSION);
58 PA_MODULE_LOAD_ONCE(TRUE);
59 PA_MODULE_USAGE("");
60
61 struct userdata {
62     pa_core *core;
63     pa_module *module;
64     pa_hook_slot *client_watch_slot;
65     pa_hook_slot *client_unlink_slot;
66 #ifdef HAVE_INOTIFY
67     pa_hashmap *paths_to_clients;
68     int inotify_fd;
69     pa_io_event *inotify_io;
70 #endif
71 };
72
73 #ifdef HAVE_INOTIFY
74 struct client_id {
75     PA_LLIST_FIELDS(struct client_id);
76     uint32_t id;
77 };
78
79 struct client_data {
80     PA_LLIST_HEAD(struct client_id, ids);
81     int i;
82     char *directory;
83     int wd;
84 };
85 #endif
86
87 static void fire(
88         struct userdata *u,
89         pa_client *c,
90         const char *action,
91         const char *dir,
92         const char *fn) {
93     pa_proplist *list;
94
95     pa_assert(u);
96     pa_assert(fn);
97     pa_assert(action);
98
99     list = pa_proplist_new();
100
101     pa_proplist_sets(list, "action", action);
102     pa_proplist_sets(list, "directory", dir);
103     pa_proplist_sets(list, "file", fn);
104
105     pa_client_send_event(c, "dir_watch_event", list);
106
107     pa_proplist_free(list);
108 }
109
110 static void fire_created(
111         struct userdata *u,
112         pa_client *c,
113         char *dir,
114         char *fn) {
115
116     fire(u, c, "create", dir, fn);
117 }
118
119 static void fire_deleted(
120         struct userdata *u,
121         pa_client *c,
122         char *dir,
123         char *fn) {
124
125     fire(u, c, "create", dir, fn);
126 }
127
128 static void fire_modified(
129         struct userdata *u,
130         pa_client *c,
131         char *dir,
132         char *fn) {
133
134     fire(u, c, "modify", dir, fn);
135 }
136
137 static void fire_attributed(
138         struct userdata *u,
139         pa_client *c,
140         char *dir,
141         char *fn) {
142
143     fire(u, c, "attribute", dir, fn);
144 }
145
146 #ifdef HAVE_INOTIFY
147 static void client_data_free(struct client_data *cd, struct userdata *u) {
148
149     pa_assert(cd);
150
151     pa_xfree(cd->directory);
152     while(cd->ids) {
153         struct client_id *id = cd->ids;
154         PA_LLIST_REMOVE(struct client_id, cd->ids, id);
155         pa_xfree(id);
156     }
157
158     pa_xfree(cd);
159 }
160
161 static void dir_watch_inotify_cb(
162         pa_mainloop_api *a,
163         pa_io_event *e,
164         int fd,
165         pa_io_event_flags_t events,
166         void *userdata) {
167     ssize_t r;
168     struct inotify_event *event;
169     int type = 0;
170     uint8_t eventbuf[2 * (sizeof(struct inotify_event) + NAME_MAX + 1)];
171     struct userdata *u = userdata;
172
173     pa_log_debug("> inotify_cb");
174
175     while (TRUE) {
176
177         r = pa_read(fd, &eventbuf, sizeof(eventbuf), &type);
178
179         if (r <= 0) {
180             if (r < 0 && errno == EAGAIN)
181                 break;
182
183             goto fail;
184         }
185
186         event = (struct inotify_event *) &eventbuf;
187
188         while (r > 0) {
189             size_t len;
190
191             if ((size_t) r < sizeof(struct inotify_event)) {
192                 pa_log("read() too short.");
193                 goto fail;
194             }
195
196             len = sizeof(struct inotify_event) + event->len;
197
198             if ((size_t) r < len) {
199                 goto fail;
200             }
201
202             if (event->len > 0) {
203                 struct client_data *cd = pa_hashmap_get(u->paths_to_clients, (const void *)NULL + event->wd);
204                 struct client_id *id;
205
206                 if (!cd) {
207                     goto fail;
208                 }
209
210                 PA_LLIST_FOREACH(id, cd->ids) {
211
212                     pa_client *c = pa_idxset_get_by_index(u->core->clients, id->id);
213
214                     if (!c) {
215                         pa_log_error("client not found!");
216                         continue;
217                     }
218
219                     if (event->mask & IN_MODIFY) {
220                         pa_log_debug("File %s modified", event->name);
221                         fire_modified(u, c, cd->directory, event->name);
222                     }
223                     if (event->mask & IN_CREATE) {
224                         pa_log_debug("File %s created", event->name);
225                         fire_created(u, c, cd->directory, event->name);
226                     }
227                     if (event->mask & IN_ATTRIB) {
228                         pa_log_debug("File %s attribute change", event->name);
229                         fire_attributed(u, c, cd->directory, event->name);
230                     }
231                     if (event->mask & IN_DELETE) {
232                         pa_log_debug("File %s deleted", event->name);
233                         fire_deleted(u, c, cd->directory, event->name);
234                     }
235                 }
236             }
237
238             event = (struct inotify_event*) ((uint8_t*) event + len);
239             r -= len;
240         }
241     }
242
243     return;
244
245 fail:
246     if (u->inotify_io) {
247         a->io_free(u->inotify_io);
248         u->inotify_io = NULL;
249     }
250
251     if (u->inotify_fd >= 0) {
252         pa_close(u->inotify_fd);
253         u->inotify_fd = -1;
254     }
255 }
256 #endif
257
258 static int add_directory(const char *directory, pa_client *c, struct userdata *u) {
259 #ifdef HAVE_INOTIFY
260     struct client_data *cd;
261     int wd;
262
263     wd = inotify_add_watch(u->inotify_fd, directory, IN_CREATE|IN_DELETE|IN_MODIFY|IN_ATTRIB);
264     if (wd < 0) {
265         pa_log_error("Failed to add directory %s to watch list", directory);
266         goto fail;
267     }
268
269     cd = pa_hashmap_get(u->paths_to_clients, (const void *)NULL + wd);
270
271     if (cd) {
272         struct client_id *id = pa_xnew0(struct client_id, 1);
273         id->id = c->index;
274
275         PA_LLIST_PREPEND(struct client_id, cd->ids, id);
276     }
277     else {
278         struct client_id *id = pa_xnew0(struct client_id, 1);
279         id->id = c->index;
280
281         cd = pa_xnew0(struct client_data, 1);
282         cd->directory = pa_xstrdup(directory);
283         cd->wd = wd;
284         PA_LLIST_HEAD_INIT(struct client_id, cd->ids);
285
286         PA_LLIST_PREPEND(struct client_id, cd->ids, id);
287
288         pa_hashmap_put(u->paths_to_clients, (const void *)0 + wd, (void *) cd);
289     }
290 #endif
291     return 0;
292
293 fail:
294     return -1;
295 }
296
297 static pa_hook_result_t client_unlink_cb(
298         pa_core *core,
299         pa_client *c,
300         struct userdata *u) {
301 #ifdef HAVE_INOTIFY
302     void *state;
303     struct client_data *cd;
304     struct client_id *id;
305     pa_bool_t found = FALSE;
306
307     PA_HASHMAP_FOREACH(cd, u->paths_to_clients, state) {
308         PA_LLIST_FOREACH(id, cd->ids) {
309             if (id->id == c->index) {
310                 PA_LLIST_REMOVE(struct client_id, cd->ids, id);
311                 pa_xfree(id);
312                 found = TRUE;
313                 break;
314             }
315         }
316
317         if (!found)
318             continue;
319
320         if (!cd->ids) {
321             /* no-one is interested in the directory anymore */
322             inotify_rm_watch(u->inotify_fd, cd->wd);
323             client_data_free(cd, NULL);
324         }
325         break;
326     }
327 #endif
328
329     return PA_HOOK_OK;
330 }
331
332 static pa_hook_result_t client_watch_cb(
333         pa_core *core,
334         pa_client *c,
335         struct userdata *u) {
336     const char *type, *directory;
337
338     pa_log("received directory watch event");
339
340     type = pa_proplist_gets(c->proplist, "type");
341
342     if (!type || strcmp(type, "directory-watch") != 0) {
343         /* not for us */
344         return PA_HOOK_OK;
345     }
346
347     directory = pa_proplist_gets(c->proplist, "dir-watch.directory");
348
349     if (!directory) {
350         goto fail;
351     }
352
353     if (add_directory(directory, c, u) < 0) {
354         goto fail;
355     }
356
357 fail:
358     /* error, kill the client */
359     pa_client_kill(c);
360     return PA_HOOK_OK;
361 }
362
363 void pa__done(pa_module *m) {
364     struct userdata *u;
365
366     pa_assert(m);
367     pa_assert(m->userdata);
368
369     u = m->userdata;
370
371 #ifdef HAVE_INOTIFY
372     if (u->inotify_io)
373         m->core->mainloop->io_free(u->inotify_io);
374
375     if (u->inotify_fd >= 0)
376         pa_close(u->inotify_fd);
377
378
379     if (u->paths_to_clients)
380         pa_hashmap_free(u->paths_to_clients, (pa_free_cb_t) client_data_free);
381 #endif
382
383     pa_xfree(u);
384 }
385
386 int pa__init(pa_module *m) {
387     struct userdata *u;
388
389     pa_log_debug("Init directory watch module");
390
391     pa_assert(m);
392
393     u = pa_xnew0(struct userdata, 1);
394     u->module = m;
395     u->core = m->core;
396     u->client_watch_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CLIENT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) client_watch_cb, u);
397     u->client_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CLIENT_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) client_unlink_cb, u);
398
399     m->userdata = u;
400
401 #ifdef HAVE_INOTIFY
402     u->paths_to_clients = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
403
404     u->inotify_fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
405     if (u->inotify_fd < 0) {
406         goto fail;
407     }
408
409     u->inotify_io = u->core->mainloop->io_new(u->core->mainloop, u->inotify_fd, PA_IO_EVENT_INPUT, dir_watch_inotify_cb, u);
410     if (!u->inotify_io) {
411         goto fail;
412     }
413 #endif
414
415     return 0;
416
417  fail:
418     pa__done(m);
419
420     return -1;
421 }