Merge "Optional autogen.sh flag --enable-kdbus-transport added allowing to compile...
[platform/upstream/dbus.git] / bus / dir-watch-inotify.c
index 0f3c155..49ebc72 100644 (file)
  * 
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
 
+/* Be careful, this file is not Linux-only: QNX also uses it */
+
 #include <config.h>
 
-#define _GNU_SOURCE
 #include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
+/* QNX's inotify is broken, and requires stdint.h to be manually included first */
+#include <stdint.h>
 #include <sys/inotify.h>
 #include <sys/types.h>
 #include <signal.h>
 #include <errno.h>
 
 #include <dbus/dbus-internals.h>
+#include <dbus/dbus-list.h>
 #include <dbus/dbus-watch.h>
 #include "dir-watch.h"
 
 #define MAX_DIRS_TO_WATCH 128
+#define INOTIFY_EVENT_SIZE (sizeof(struct inotify_event))
+#define INOTIFY_BUF_LEN (1024 * (INOTIFY_EVENT_SIZE + 16))
 
 /* use a static array to avoid handling OOM */
 static int wds[MAX_DIRS_TO_WATCH];
+static char *dirs[MAX_DIRS_TO_WATCH];
 static int num_wds = 0;
 static int inotify_fd = -1;
 static DBusWatch *watch = NULL;
 static DBusLoop *loop = NULL;
 
 static dbus_bool_t
-_inotify_watch_callback (DBusWatch *watch, unsigned int condition, void *data)
+_handle_inotify_watch (DBusWatch *passed_watch, unsigned int flags, void *data)
 {
-  return dbus_watch_handle (watch, condition);
+  char buffer[INOTIFY_BUF_LEN];
+  ssize_t ret = 0;
+  int i = 0;
+
+  ret = read (inotify_fd, buffer, INOTIFY_BUF_LEN);
+  if (ret < 0)
+    _dbus_verbose ("Error reading inotify event: '%s'\n", _dbus_strerror(errno));
+  else if (!ret)
+    _dbus_verbose ("Error reading inotify event: buffer too small\n");
+  else
+    {
+      _dbus_verbose ("Sending SIGHUP signal on reception of %ld inotify event(s)\n", (long) ret);
+      (void) kill (_dbus_getpid (), SIGHUP);
+    }
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+  while (i < ret)
+    {
+      struct inotify_event *ev;
+
+      ev = (struct inotify_event *) &buffer[i];
+      i += INOTIFY_EVENT_SIZE + ev->len;
+      if (ev->len)
+        _dbus_verbose ("event name: '%s'\n", ev->name);
+      _dbus_verbose ("inotify event: wd=%d mask=%u cookie=%u len=%u\n", ev->wd, ev->mask, ev->cookie, ev->len);
+    }
+#endif
+
+  return TRUE;
 }
 
-static dbus_bool_t
-_handle_inotify_watch (DBusWatch *watch, unsigned int flags, void *data)
+#include <stdio.h>
+
+static void
+_set_watched_dirs_internal (DBusList **directories)
 {
-  struct inotify_event ev;
-  size_t res;
-  pid_t pid;
+  int new_wds[MAX_DIRS_TO_WATCH];
+  char *new_dirs[MAX_DIRS_TO_WATCH];
+  DBusList *link;
+  int i, j, wd;
 
-  res = read (inotify_fd, &ev, sizeof(ev));
+  for (i = 0; i < MAX_DIRS_TO_WATCH; i++)
+    {
+      new_wds[i] = -1;
+      new_dirs[i] = NULL;
+    }
 
-  if (res > 0)
+  i = 0;
+  link = _dbus_list_get_first_link (directories);
+  while (link != NULL)
     {
-      pid = getpid ();
-      _dbus_verbose ("Sending SIGHUP signal on reception of a inotify event\n");
-      (void) kill (pid, SIGHUP);
+      new_dirs[i++] = (char *)link->data;
+      link = _dbus_list_get_next_link (directories, link);
     }
-  else if (res < 0 && errno == EBADF)
+
+  /* Look for directories in both the old and new sets, if
+   * we find one, move its data into the new set.
+   */
+  for (i = 0; new_dirs[i]; i++)
     {
-      if (watch != NULL)
-       {
-         _dbus_loop_remove_watch (loop, watch, _inotify_watch_callback, NULL);
-          _dbus_watch_unref (watch);
-         watch = NULL;
-       }
-      pid = getpid ();
-      _dbus_verbose ("Sending SIGHUP signal since inotify fd has been closed\n");
-      (void) kill (pid, SIGHUP);
+      for (j = 0; j < num_wds; j++)
+        {
+          if (dirs[j] && strcmp (new_dirs[i], dirs[j]) == 0)
+            {
+              new_wds[i] = wds[j];
+              new_dirs[i] = dirs[j];
+              wds[j] = -1;
+              dirs[j] = NULL;
+              break;
+            }
+        }
     }
 
-  return TRUE;
-}
-void
-bus_watch_directory (const char *dir, BusContext *context)
-{
-  int wd;
-
-  _dbus_assert (dir != NULL);
-
-  if (inotify_fd == -1) {
-     inotify_fd = inotify_init ();
-     if (inotify_fd <= 0) {
-      _dbus_warn ("Cannot initialize inotify\n");
-      goto out;
-     } 
-     loop = bus_context_get_loop (context);
-
-     watch = _dbus_watch_new (inotify_fd, DBUS_WATCH_READABLE, TRUE,
-                              _handle_inotify_watch, NULL, NULL);
-
-       if (watch == NULL)
-          {
-            _dbus_warn ("Unable to create inotify watch\n");
-           goto out;
-         }
-
-       if (!_dbus_loop_add_watch (loop, watch, _inotify_watch_callback,
-                                   NULL, NULL))
-          {
-            _dbus_warn ("Unable to add reload watch to main loop");
-           _dbus_watch_unref (watch);
-           watch = NULL;
-            goto out;
-         }
-  }
-
-
-  if (num_wds >= MAX_DIRS_TO_WATCH )
+  /* Any directories we find in "wds" with a nonzero fd must
+   * not be in the new set, so perform cleanup now.
+   */
+  for (j = 0; j < num_wds; j++)
+    {
+      if (wds[j] != -1)
+        {
+          inotify_rm_watch (inotify_fd, wds[j]);
+          dbus_free (dirs[j]);
+          wds[j] = -1;
+          dirs[j] = NULL;
+        }
+    }
+
+  for (i = 0; new_dirs[i]; i++)
     {
-      _dbus_warn ("Cannot watch config directory '%s'. Already watching %d directories\n", dir, MAX_DIRS_TO_WATCH);
-      goto out;
+      if (new_wds[i] == -1)
+        {
+          /* FIXME - less lame error handling for failing to add a watch; we may need to sleep. */
+          wd = inotify_add_watch (inotify_fd, new_dirs[i], IN_CLOSE_WRITE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM);
+          if (wd < 0)
+            {
+              /* Not all service directories need to exist. */
+              if (errno != ENOENT)
+                {
+                  _dbus_warn ("Cannot setup inotify for '%s'; error '%s'\n", new_dirs[i], _dbus_strerror (errno));
+                  goto out;
+                }
+              else
+                {
+                  new_wds[i] = -1;
+                  new_dirs[i] = NULL;
+                  continue;
+                }
+            }
+          new_wds[i] = wd;
+          new_dirs[i] = _dbus_strdup (new_dirs[i]);
+          if (!new_dirs[i])
+            {
+              /* FIXME have less lame handling for OOM, we just silently fail to
+               * watch.  (In reality though, the whole OOM handling in dbus is stupid
+               * but we won't go into that in this comment =) )
+               */
+              inotify_rm_watch (inotify_fd, wd);
+              new_wds[i] = -1;
+            }
+        }
     }
 
-  wd = inotify_add_watch (inotify_fd, dir, IN_MODIFY);
-  if (wd < 0)
+  num_wds = i;
+
+  for (i = 0; i < MAX_DIRS_TO_WATCH; i++)
     {
-      _dbus_warn ("Cannot setup inotify for '%s'; error '%s'\n", dir, _dbus_strerror (errno));
-      goto out;
+      wds[i] = new_wds[i];
+      dirs[i] = new_dirs[i];
     }
 
-  wds[num_wds++] = wd;
-  _dbus_verbose ("Added watch on config directory '%s'\n", dir);
+ out:;
+}
+
+#include <stdio.h>
+static void
+_shutdown_inotify (void *data)
+{
+  DBusList *empty = NULL;
+
+  if (inotify_fd == -1)
+    return;
+
+  _set_watched_dirs_internal (&empty);
 
- out:
-  ;
+  if (watch != NULL)
+    {
+      _dbus_loop_remove_watch (loop, watch);
+      _dbus_watch_invalidate (watch);
+      _dbus_watch_unref (watch);
+      _dbus_loop_unref (loop);
+    }
+  watch = NULL;
+  loop = NULL;
+
+  close (inotify_fd);
+  inotify_fd = -1;
 }
 
-void 
-bus_drop_all_directory_watches (void)
+static int
+_init_inotify (BusContext *context)
 {
-  int i;
-  _dbus_verbose ("Dropping all watches on config directories\n");
-  for (i = 0; i < num_wds; i++)
+  int ret = 0;
+
+  if (inotify_fd == -1)
     {
-      if (inotify_rm_watch(inotify_fd, wds[i]) != 0)
-       {
-         _dbus_verbose ("Error closing fd %d for config directory watch\n", wds[i]);
-       }
+#ifdef HAVE_INOTIFY_INIT1
+      inotify_fd = inotify_init1 (IN_CLOEXEC);
+      /* This ensures we still run on older Linux kernels.
+       * https://bugs.freedesktop.org/show_bug.cgi?id=23957
+       */
+      if (inotify_fd < 0)
+        inotify_fd = inotify_init ();
+#else
+      inotify_fd = inotify_init ();
+#endif
+      if (inotify_fd <= 0)
+        {
+          _dbus_warn ("Cannot initialize inotify\n");
+          goto out;
+        }
+      loop = bus_context_get_loop (context);
+      _dbus_loop_ref (loop);
+
+      watch = _dbus_watch_new (inotify_fd, DBUS_WATCH_READABLE, TRUE,
+                               _handle_inotify_watch, NULL, NULL);
+
+      if (watch == NULL)
+        {
+          _dbus_warn ("Unable to create inotify watch\n");
+          goto out;
+        }
+
+      if (!_dbus_loop_add_watch (loop, watch))
+        {
+          _dbus_warn ("Unable to add reload watch to main loop");
+          _dbus_watch_unref (watch);
+          watch = NULL;
+          goto out;
+        }
+
+      if (!_dbus_register_shutdown_func (_shutdown_inotify, NULL))
+      {
+          _dbus_warn ("Unable to register shutdown func");
+          _dbus_watch_unref (watch);
+          watch = NULL;
+          goto out;
+      }
     }
-  
-  num_wds = 0;
+
+  ret = 1;
+
+out:
+  return ret;
+}
+
+void
+bus_set_watched_dirs (BusContext *context, DBusList **directories)
+{
+  if (!_init_inotify (context))
+    return;
+
+  _set_watched_dirs_internal (directories);
 }