Home | History | Annotate | Download | only in bus
      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 */
     45 static int wds[MAX_DIRS_TO_WATCH];
     46 static char *dirs[MAX_DIRS_TO_WATCH];
     47 static int num_wds = 0;
     48 static int inotify_fd = -1;
     49 static DBusWatch *watch = NULL;
     50 static DBusLoop *loop = NULL;
     51 
     52 static dbus_bool_t
     53 _inotify_watch_callback (DBusWatch *watch, unsigned int condition, void *data)
     54 {
     55   return dbus_watch_handle (watch, condition);
     56 }
     57 
     58 static 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 
     96 static 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>
    197 static 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 
    219 static 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 
    267 out:
    268   return ret;
    269 }
    270 
    271 void
    272 bus_set_watched_dirs (BusContext *context, DBusList **directories)
    273 {
    274   if (!_init_inotify (context))
    275     return;
    276 
    277   _set_watched_dirs_internal (directories);
    278 }
    279