Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 :
4 : Copyright (C) Andrew Tridgell 2006
5 :
6 : This program is free software; you can redistribute it and/or modify
7 : it under the terms of the GNU General Public License as published by
8 : the Free Software Foundation; either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : This program is distributed in the hope that it will be useful,
12 : but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : GNU General Public License for more details.
15 :
16 : You should have received a copy of the GNU General Public License
17 : along with this program. If not, see <http://www.gnu.org/licenses/>.
18 : */
19 :
20 : /*
21 : notify implementation using inotify
22 : */
23 :
24 : #include "includes.h"
25 : #include "system/filesys.h"
26 : #include <tevent.h>
27 : #include "ntvfs/sysdep/sys_notify.h"
28 : #include "../lib/util/dlinklist.h"
29 : #include "libcli/raw/smb.h"
30 : #include "param/param.h"
31 :
32 : #include <sys/inotify.h>
33 :
34 : /* glibc < 2.5 headers don't have these defines */
35 : #ifndef IN_ONLYDIR
36 : #define IN_ONLYDIR 0x01000000
37 : #endif
38 : #ifndef IN_MASK_ADD
39 : #define IN_MASK_ADD 0x20000000
40 : #endif
41 :
42 : struct inotify_private {
43 : struct sys_notify_context *ctx;
44 : int fd;
45 : struct inotify_watch_context *watches;
46 : };
47 :
48 : struct inotify_watch_context {
49 : struct inotify_watch_context *next, *prev;
50 : struct inotify_private *in;
51 : int wd;
52 : sys_notify_callback_t callback;
53 : void *private_data;
54 : uint32_t mask; /* the inotify mask */
55 : uint32_t filter; /* the windows completion filter */
56 : const char *path;
57 : };
58 :
59 :
60 : /*
61 : see if a particular event from inotify really does match a requested
62 : notify event in SMB
63 : */
64 0 : static bool filter_match(struct inotify_watch_context *w,
65 : struct inotify_event *e)
66 : {
67 0 : if ((e->mask & w->mask) == 0) {
68 : /* this happens because inotify_add_watch() coalesces watches on the same
69 : path, oring their masks together */
70 0 : return false;
71 : }
72 :
73 : /* SMB separates the filters for files and directories */
74 0 : if (e->mask & IN_ISDIR) {
75 0 : if ((w->filter & FILE_NOTIFY_CHANGE_DIR_NAME) == 0) {
76 0 : return false;
77 : }
78 : } else {
79 0 : if ((e->mask & IN_ATTRIB) &&
80 0 : (w->filter & (FILE_NOTIFY_CHANGE_ATTRIBUTES|
81 : FILE_NOTIFY_CHANGE_LAST_WRITE|
82 : FILE_NOTIFY_CHANGE_LAST_ACCESS|
83 : FILE_NOTIFY_CHANGE_EA|
84 : FILE_NOTIFY_CHANGE_SECURITY))) {
85 0 : return true;
86 : }
87 0 : if ((e->mask & IN_MODIFY) &&
88 0 : (w->filter & FILE_NOTIFY_CHANGE_ATTRIBUTES)) {
89 0 : return true;
90 : }
91 0 : if ((w->filter & FILE_NOTIFY_CHANGE_FILE_NAME) == 0) {
92 0 : return false;
93 : }
94 : }
95 :
96 0 : return true;
97 : }
98 :
99 :
100 :
101 : /*
102 : dispatch one inotify event
103 :
104 : the cookies are used to correctly handle renames
105 : */
106 0 : static void inotify_dispatch(struct inotify_private *in,
107 : struct inotify_event *e,
108 : uint32_t prev_cookie,
109 : struct inotify_event *e2)
110 : {
111 0 : struct inotify_watch_context *w, *next;
112 0 : struct notify_event ne;
113 :
114 : /* ignore extraneous events, such as unmount and IN_IGNORED events */
115 0 : if ((e->mask & (IN_ATTRIB|IN_MODIFY|IN_CREATE|IN_DELETE|
116 : IN_MOVED_FROM|IN_MOVED_TO)) == 0) {
117 0 : return;
118 : }
119 :
120 : /* map the inotify mask to a action. This gets complicated for
121 : renames */
122 0 : if (e->mask & IN_CREATE) {
123 0 : ne.action = NOTIFY_ACTION_ADDED;
124 0 : } else if (e->mask & IN_DELETE) {
125 0 : ne.action = NOTIFY_ACTION_REMOVED;
126 0 : } else if (e->mask & IN_MOVED_FROM) {
127 0 : if (e2 != NULL && e2->cookie == e->cookie) {
128 0 : ne.action = NOTIFY_ACTION_OLD_NAME;
129 : } else {
130 0 : ne.action = NOTIFY_ACTION_REMOVED;
131 : }
132 0 : } else if (e->mask & IN_MOVED_TO) {
133 0 : if (e->cookie == prev_cookie) {
134 0 : ne.action = NOTIFY_ACTION_NEW_NAME;
135 : } else {
136 0 : ne.action = NOTIFY_ACTION_ADDED;
137 : }
138 : } else {
139 0 : ne.action = NOTIFY_ACTION_MODIFIED;
140 : }
141 0 : ne.path = e->name;
142 :
143 : /* find any watches that have this watch descriptor */
144 0 : for (w=in->watches;w;w=next) {
145 0 : next = w->next;
146 0 : if (w->wd == e->wd && filter_match(w, e)) {
147 0 : ne.dir = w->path;
148 0 : w->callback(in->ctx, w->private_data, &ne);
149 : }
150 : }
151 :
152 : /* SMB expects a file rename to generate three events, two for
153 : the rename and the other for a modify of the
154 : destination. Strange! */
155 0 : if (ne.action != NOTIFY_ACTION_NEW_NAME ||
156 0 : (e->mask & IN_ISDIR) != 0) {
157 0 : return;
158 : }
159 :
160 0 : ne.action = NOTIFY_ACTION_MODIFIED;
161 0 : e->mask = IN_ATTRIB;
162 :
163 0 : for (w=in->watches;w;w=next) {
164 0 : next = w->next;
165 0 : if (w->wd == e->wd && filter_match(w, e) &&
166 0 : !(w->filter & FILE_NOTIFY_CHANGE_CREATION)) {
167 0 : ne.dir = w->path;
168 0 : w->callback(in->ctx, w->private_data, &ne);
169 : }
170 : }
171 : }
172 :
173 : /*
174 : called when the kernel has some events for us
175 : */
176 0 : static void inotify_handler(struct tevent_context *ev, struct tevent_fd *fde,
177 : uint16_t flags, void *private_data)
178 : {
179 0 : struct inotify_private *in = talloc_get_type(private_data,
180 : struct inotify_private);
181 0 : int bufsize = 0;
182 0 : struct inotify_event *e0, *e;
183 0 : uint32_t prev_cookie=0;
184 :
185 : /*
186 : we must use FIONREAD as we cannot predict the length of the
187 : filenames, and thus can't know how much to allocate
188 : otherwise
189 : */
190 0 : if (ioctl(in->fd, FIONREAD, &bufsize) != 0 ||
191 0 : bufsize == 0) {
192 0 : DEBUG(0,("No data on inotify fd?!\n"));
193 0 : return;
194 : }
195 :
196 0 : e0 = e = talloc_size(in, bufsize);
197 0 : if (e == NULL) return;
198 :
199 0 : if (read(in->fd, e0, bufsize) != bufsize) {
200 0 : DEBUG(0,("Failed to read all inotify data\n"));
201 0 : talloc_free(e0);
202 0 : return;
203 : }
204 :
205 : /* we can get more than one event in the buffer */
206 0 : while (bufsize >= sizeof(*e)) {
207 0 : struct inotify_event *e2 = NULL;
208 0 : bufsize -= e->len + sizeof(*e);
209 0 : if (bufsize >= sizeof(*e)) {
210 0 : e2 = (struct inotify_event *)(e->len + sizeof(*e) + (char *)e);
211 : }
212 0 : inotify_dispatch(in, e, prev_cookie, e2);
213 0 : prev_cookie = e->cookie;
214 0 : e = e2;
215 : }
216 :
217 0 : talloc_free(e0);
218 : }
219 :
220 : /*
221 : setup the inotify handle - called the first time a watch is added on
222 : this context
223 : */
224 0 : static NTSTATUS inotify_setup(struct sys_notify_context *ctx)
225 : {
226 0 : struct inotify_private *in;
227 0 : struct tevent_fd *fde;
228 :
229 0 : in = talloc(ctx, struct inotify_private);
230 0 : NT_STATUS_HAVE_NO_MEMORY(in);
231 :
232 0 : in->fd = inotify_init();
233 0 : if (in->fd == -1) {
234 0 : DEBUG(0,("Failed to init inotify - %s\n", strerror(errno)));
235 0 : talloc_free(in);
236 0 : return map_nt_error_from_unix_common(errno);
237 : }
238 0 : in->ctx = ctx;
239 0 : in->watches = NULL;
240 :
241 0 : ctx->private_data = in;
242 :
243 : /* add a event waiting for the inotify fd to be readable */
244 0 : fde = tevent_add_fd(ctx->ev, in, in->fd,
245 : TEVENT_FD_READ, inotify_handler, in);
246 0 : if (!fde) {
247 0 : if (errno == 0) {
248 0 : errno = ENOMEM;
249 : }
250 0 : DEBUG(0,("Failed to tevent_add_fd() - %s\n", strerror(errno)));
251 0 : talloc_free(in);
252 0 : return map_nt_error_from_unix_common(errno);
253 : }
254 :
255 0 : tevent_fd_set_auto_close(fde);
256 :
257 0 : return NT_STATUS_OK;
258 : }
259 :
260 :
261 : /*
262 : map from a change notify mask to a inotify mask. Remove any bits
263 : which we can handle
264 : */
265 : static const struct {
266 : uint32_t notify_mask;
267 : uint32_t inotify_mask;
268 : } inotify_mapping[] = {
269 : {FILE_NOTIFY_CHANGE_FILE_NAME, IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
270 : {FILE_NOTIFY_CHANGE_DIR_NAME, IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
271 : {FILE_NOTIFY_CHANGE_ATTRIBUTES, IN_ATTRIB|IN_MOVED_TO|IN_MOVED_FROM|IN_MODIFY},
272 : {FILE_NOTIFY_CHANGE_LAST_WRITE, IN_ATTRIB},
273 : {FILE_NOTIFY_CHANGE_LAST_ACCESS, IN_ATTRIB},
274 : {FILE_NOTIFY_CHANGE_EA, IN_ATTRIB},
275 : {FILE_NOTIFY_CHANGE_SECURITY, IN_ATTRIB}
276 : };
277 :
278 0 : static uint32_t inotify_map(struct notify_entry *e)
279 : {
280 0 : int i;
281 0 : uint32_t out=0;
282 0 : for (i=0;i<ARRAY_SIZE(inotify_mapping);i++) {
283 0 : if (inotify_mapping[i].notify_mask & e->filter) {
284 0 : out |= inotify_mapping[i].inotify_mask;
285 0 : e->filter &= ~inotify_mapping[i].notify_mask;
286 : }
287 : }
288 0 : return out;
289 : }
290 :
291 : /*
292 : destroy a watch
293 : */
294 0 : static int watch_destructor(struct inotify_watch_context *w)
295 : {
296 0 : struct inotify_private *in = w->in;
297 0 : int wd = w->wd;
298 0 : DLIST_REMOVE(w->in->watches, w);
299 :
300 : /* only rm the watch if its the last one with this wd */
301 0 : for (w=in->watches;w;w=w->next) {
302 0 : if (w->wd == wd) break;
303 : }
304 0 : if (w == NULL) {
305 0 : inotify_rm_watch(in->fd, wd);
306 : }
307 0 : return 0;
308 : }
309 :
310 :
311 : /*
312 : add a watch. The watch is removed when the caller calls
313 : talloc_free() on *handle
314 : */
315 0 : static NTSTATUS inotify_watch(struct sys_notify_context *ctx,
316 : struct notify_entry *e,
317 : sys_notify_callback_t callback,
318 : void *private_data,
319 : void *handle_p)
320 : {
321 0 : struct inotify_private *in;
322 0 : int wd;
323 0 : uint32_t mask;
324 0 : struct inotify_watch_context *w;
325 0 : uint32_t filter = e->filter;
326 0 : void **handle = (void **)handle_p;
327 :
328 : /* maybe setup the inotify fd */
329 0 : if (ctx->private_data == NULL) {
330 0 : NTSTATUS status;
331 0 : status = inotify_setup(ctx);
332 0 : NT_STATUS_NOT_OK_RETURN(status);
333 : }
334 :
335 0 : in = talloc_get_type(ctx->private_data, struct inotify_private);
336 :
337 0 : mask = inotify_map(e);
338 0 : if (mask == 0) {
339 : /* this filter can't be handled by inotify */
340 0 : return NT_STATUS_INVALID_PARAMETER;
341 : }
342 :
343 : /* using IN_MASK_ADD allows us to cope with inotify() returning the same
344 : watch descriptor for multiple watches on the same path */
345 0 : mask |= (IN_MASK_ADD | IN_ONLYDIR);
346 :
347 : /* get a new watch descriptor for this path */
348 0 : wd = inotify_add_watch(in->fd, e->path, mask);
349 0 : if (wd == -1) {
350 0 : e->filter = filter;
351 0 : return map_nt_error_from_unix_common(errno);
352 : }
353 :
354 0 : w = talloc(in, struct inotify_watch_context);
355 0 : if (w == NULL) {
356 0 : inotify_rm_watch(in->fd, wd);
357 0 : e->filter = filter;
358 0 : return NT_STATUS_NO_MEMORY;
359 : }
360 :
361 0 : w->in = in;
362 0 : w->wd = wd;
363 0 : w->callback = callback;
364 0 : w->private_data = private_data;
365 0 : w->mask = mask;
366 0 : w->filter = filter;
367 0 : w->path = talloc_strdup(w, e->path);
368 0 : if (w->path == NULL) {
369 0 : inotify_rm_watch(in->fd, wd);
370 0 : e->filter = filter;
371 0 : return NT_STATUS_NO_MEMORY;
372 : }
373 :
374 0 : (*handle) = w;
375 :
376 0 : DLIST_ADD(in->watches, w);
377 :
378 : /* the caller frees the handle to stop watching */
379 0 : talloc_set_destructor(w, watch_destructor);
380 :
381 0 : return NT_STATUS_OK;
382 : }
383 :
384 :
385 : static struct sys_notify_backend inotify = {
386 : .name = "inotify",
387 : .notify_watch = inotify_watch
388 : };
389 :
390 : /*
391 : initialise the inotify module
392 : */
393 : NTSTATUS sys_notify_inotify_init(TALLOC_CTX *);
394 68 : NTSTATUS sys_notify_inotify_init(TALLOC_CTX *ctx)
395 : {
396 : /* register ourselves as a system inotify module */
397 68 : return sys_notify_register(ctx, &inotify);
398 : }
|