1/* -*- mode: C; c-file-style: "gnu" -*- */
2/* dir-watch-inotify.c  OS specific directory change notification for message bus
3 *
4 * Copyright (C) 2003 Red Hat, Inc.
5 *           (c) 2006 Mandriva
6 *
7 * Licensed under the Academic Free License version 2.1
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 */
24
25#include <config.h>
26
27#include <stdlib.h>
28#include <unistd.h>
29#include <fcntl.h>
30#include <sys/inotify.h>
31#include <sys/types.h>
32#include <signal.h>
33#include <errno.h>
34
35#include <dbus/dbus-internals.h>
36#include <dbus/dbus-list.h>
37#include <dbus/dbus-watch.h>
38#include "dir-watch.h"
39
40#define MAX_DIRS_TO_WATCH 128
41#define INOTIFY_EVENT_SIZE (sizeof(struct inotify_event))
42#define INOTIFY_BUF_LEN (1024 * (INOTIFY_EVENT_SIZE + 16))
43
44/* use a static array to avoid handling OOM */
45static int wds[MAX_DIRS_TO_WATCH];
46static char *dirs[MAX_DIRS_TO_WATCH];
47static int num_wds = 0;
48static int inotify_fd = -1;
49static DBusWatch *watch = NULL;
50static DBusLoop *loop = NULL;
51
52static dbus_bool_t
53_inotify_watch_callback (DBusWatch *watch, unsigned int condition, void *data)
54{
55  return dbus_watch_handle (watch, condition);
56}
57
58static dbus_bool_t
59_handle_inotify_watch (DBusWatch *passed_watch, unsigned int flags, void *data)
60{
61  char buffer[INOTIFY_BUF_LEN];
62  ssize_t ret = 0;
63  int i = 0;
64  pid_t pid;
65  dbus_bool_t have_change = FALSE;
66
67  ret = read (inotify_fd, buffer, INOTIFY_BUF_LEN);
68  if (ret < 0)
69    _dbus_verbose ("Error reading inotify event: '%s'\n", _dbus_strerror(errno));
70  else if (!ret)
71    _dbus_verbose ("Error reading inotify event: buffer too small\n");
72
73  while (i < ret)
74    {
75      struct inotify_event *ev;
76      pid = _dbus_getpid ();
77
78      ev = (struct inotify_event *) &buffer[i];
79      i += INOTIFY_EVENT_SIZE + ev->len;
80#ifdef DBUS_ENABLE_VERBOSE_MODE
81      if (ev->len)
82        _dbus_verbose ("event name: '%s'\n", ev->name);
83      _dbus_verbose ("inotify event: wd=%d mask=%u cookie=%u len=%u\n", ev->wd, ev->mask, ev->cookie, ev->len);
84#endif
85      _dbus_verbose ("Sending SIGHUP signal on reception of a inotify event\n");
86      have_change = TRUE;
87    }
88  if (have_change)
89    (void) kill (pid, SIGHUP);
90
91  return TRUE;
92}
93
94#include <stdio.h>
95
96static void
97_set_watched_dirs_internal (DBusList **directories)
98{
99  int new_wds[MAX_DIRS_TO_WATCH];
100  char *new_dirs[MAX_DIRS_TO_WATCH];
101  DBusList *link;
102  int i, j, wd;
103
104  for (i = 0; i < MAX_DIRS_TO_WATCH; i++)
105    {
106      new_wds[i] = -1;
107      new_dirs[i] = NULL;
108    }
109
110  i = 0;
111  link = _dbus_list_get_first_link (directories);
112  while (link != NULL)
113    {
114      new_dirs[i++] = (char *)link->data;
115      link = _dbus_list_get_next_link (directories, link);
116    }
117
118  /* Look for directories in both the old and new sets, if
119   * we find one, move its data into the new set.
120   */
121  for (i = 0; new_dirs[i]; i++)
122    {
123      for (j = 0; j < num_wds; j++)
124        {
125          if (dirs[j] && strcmp (new_dirs[i], dirs[j]) == 0)
126            {
127              new_wds[i] = wds[j];
128              new_dirs[i] = dirs[j];
129              wds[j] = -1;
130              dirs[j] = NULL;
131              break;
132            }
133        }
134    }
135
136  /* Any directories we find in "wds" with a nonzero fd must
137   * not be in the new set, so perform cleanup now.
138   */
139  for (j = 0; j < num_wds; j++)
140    {
141      if (wds[j] != -1)
142        {
143          inotify_rm_watch (inotify_fd, wds[j]);
144          dbus_free (dirs[j]);
145          wds[j] = -1;
146          dirs[j] = NULL;
147        }
148    }
149
150  for (i = 0; new_dirs[i]; i++)
151    {
152      if (new_wds[i] == -1)
153        {
154          /* FIXME - less lame error handling for failing to add a watch; we may need to sleep. */
155          wd = inotify_add_watch (inotify_fd, new_dirs[i], IN_CLOSE_WRITE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM);
156          if (wd < 0)
157            {
158              /* Not all service directories need to exist. */
159              if (errno != ENOENT)
160                {
161                  _dbus_warn ("Cannot setup inotify for '%s'; error '%s'\n", new_dirs[i], _dbus_strerror (errno));
162                  goto out;
163                }
164              else
165                {
166                  new_wds[i] = -1;
167                  new_dirs[i] = NULL;
168                  continue;
169                }
170            }
171          new_wds[i] = wd;
172          new_dirs[i] = _dbus_strdup (new_dirs[i]);
173          if (!new_dirs[i])
174            {
175              /* FIXME have less lame handling for OOM, we just silently fail to
176               * watch.  (In reality though, the whole OOM handling in dbus is stupid
177               * but we won't go into that in this comment =) )
178               */
179              inotify_rm_watch (inotify_fd, wd);
180              new_wds[i] = -1;
181            }
182        }
183    }
184
185  num_wds = i;
186
187  for (i = 0; i < MAX_DIRS_TO_WATCH; i++)
188    {
189      wds[i] = new_wds[i];
190      dirs[i] = new_dirs[i];
191    }
192
193 out:;
194}
195
196#include <stdio.h>
197static void
198_shutdown_inotify (void *data)
199{
200  DBusList *empty = NULL;
201
202  if (inotify_fd == -1)
203    return;
204
205  _set_watched_dirs_internal (&empty);
206
207  close (inotify_fd);
208  inotify_fd = -1;
209  if (watch != NULL)
210    {
211      _dbus_loop_remove_watch (loop, watch, _inotify_watch_callback, NULL);
212      _dbus_watch_unref (watch);
213      _dbus_loop_unref (loop);
214    }
215  watch = NULL;
216  loop = NULL;
217}
218
219static int
220_init_inotify (BusContext *context)
221{
222  int ret = 0;
223
224  if (inotify_fd == -1)
225    {
226#ifdef HAVE_INOTIFY_INIT1
227      inotify_fd = inotify_init1 (IN_CLOEXEC);
228      /* This ensures we still run on older Linux kernels.
229       * https://bugs.freedesktop.org/show_bug.cgi?id=23957
230       */
231      if (inotify_fd < 0)
232        inotify_fd = inotify_init ();
233#else
234      inotify_fd = inotify_init ();
235#endif
236      if (inotify_fd <= 0)
237        {
238          _dbus_warn ("Cannot initialize inotify\n");
239          goto out;
240        }
241      loop = bus_context_get_loop (context);
242      _dbus_loop_ref (loop);
243
244      watch = _dbus_watch_new (inotify_fd, DBUS_WATCH_READABLE, TRUE,
245                               _handle_inotify_watch, NULL, NULL);
246
247      if (watch == NULL)
248        {
249          _dbus_warn ("Unable to create inotify watch\n");
250          goto out;
251        }
252
253      if (!_dbus_loop_add_watch (loop, watch, _inotify_watch_callback,
254                                 NULL, NULL))
255        {
256          _dbus_warn ("Unable to add reload watch to main loop");
257          _dbus_watch_unref (watch);
258          watch = NULL;
259          goto out;
260        }
261
262      _dbus_register_shutdown_func (_shutdown_inotify, NULL);
263    }
264
265  ret = 1;
266
267out:
268  return ret;
269}
270
271void
272bus_set_watched_dirs (BusContext *context, DBusList **directories)
273{
274  if (!_init_inotify (context))
275    return;
276
277  _set_watched_dirs_internal (directories);
278}
279