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