r13293: Rather a big patch I'm afraid, but this should fix bug #3347
[kai/samba.git] / source3 / smbd / notify_fam.c
1 /*
2  * FAM file notification support.
3  *
4  * Copyright (c) James Peach 2005
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 2 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, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19  */
20
21 #include "includes.h"
22
23 #ifdef HAVE_FAM_CHANGE_NOTIFY
24
25 #include <fam.h>
26
27 /* NOTE: There are multiple versions of FAM floating around the net, each with
28  * slight differences from the original SGI FAM implementation. In this file,
29  * we rely only on the SGI features and do not assume any extensions. For
30  * example, we do not look at FAMErrno, because it is not set by the original
31  * implementation.
32  *
33  * Random FAM links:
34  *      http://oss.sgi.com/projects/fam/
35  *      http://savannah.nongnu.org/projects/fam/
36  *      http://sourceforge.net/projects/bsdfam/
37  */
38
39 struct fam_req_info
40 {
41     FAMRequest      req;
42     int             generation;
43     enum FAMCodes   code;
44     enum
45     {
46         /* We are waiting for an event. */
47         FAM_REQ_MONITORING,
48         /* An event has been receive, but we haven't been able to send it back
49          * to the client yet. It is stashed in the code member.
50          */
51         FAM_REQ_FIRED
52     } state;
53 };
54
55 /* Don't initialise this until the first register request. We want a single
56  * FAM connection for each worker smbd. If we allow the master (parent) smbd to
57  * open a FAM connection, multiple processes talking on the same socket will
58  * undoubtedly create havoc.
59  */
60 static FAMConnection    global_fc;
61 static int              global_fc_generation;
62
63 #define FAM_TRACE       8
64 #define FAM_TRACE_LOW   10
65
66 #define FAM_NOTIFY_CHECK_TIMEOUT    1 /* secs */
67 #define FAM_EVENT_DRAIN             ((uint32_t)(-1))
68
69 /* Turn a FAM event code into a string. Don't rely on specific code values,
70  * because that might not work across all flavours of FAM.
71  */
72 static const char *
73 fam_event_str(enum FAMCodes code)
74 {
75     static struct { enum FAMCodes code; const char * name; } evstr[] =
76     {
77         { FAMChanged,           "FAMChanged"},
78         { FAMDeleted,           "FAMDeleted"},
79         { FAMStartExecuting,    "FAMStartExecuting"},
80         { FAMStopExecuting,     "FAMStopExecuting"},
81         { FAMCreated,           "FAMCreated"},
82         { FAMMoved,             "FAMMoved"},
83         { FAMAcknowledge,       "FAMAcknowledge"},
84         { FAMExists,            "FAMExists"},
85         { FAMEndExist,          "FAMEndExist"}
86     };
87
88     int i;
89
90     for (i = 0; i < ARRAY_SIZE(evstr); ++i) {
91         if (code == evstr[i].code)
92             return(evstr[i].name);
93     }
94
95     return("<unknown>");
96 }
97
98 static BOOL
99 fam_check_reconnect(void)
100 {
101     if (FAMCONNECTION_GETFD(&global_fc) < 0) {
102         fstring name;
103
104         global_fc_generation++;
105         snprintf(name, sizeof(name), "smbd (%lu)", (unsigned long)sys_getpid());
106
107         if (FAMOpen2(&global_fc, name) < 0) {
108             DEBUG(0, ("failed to connect to FAM service\n"));
109             return(False);
110         }
111     }
112
113     return(True);
114 }
115
116 static BOOL
117 fam_monitor_path(connection_struct *    conn,
118                  struct fam_req_info *  info,
119                  const char *           path,
120                  uint32                 flags)
121 {
122     SMB_STRUCT_STAT st;
123     pstring         fullpath;
124
125     DEBUG(FAM_TRACE, ("requesting FAM notifications for '%s'\n", path));
126
127     /* FAM needs an absolute pathname. */
128
129     /* It would be better to use reduce_name() here, but reduce_name does not
130      * actually return the reduced result. How utterly un-useful.
131      */
132     pstrcpy(fullpath, path);
133     if (!canonicalize_path(conn, fullpath)) {
134         DEBUG(0, ("failed to canonicalize path '%s'\n", path));
135         return(False);
136     }
137
138     if (*fullpath != '/') {
139         DEBUG(0, ("canonicalized path '%s' into `%s`\n", path, fullpath));
140         DEBUGADD(0, ("but expected an absolute path\n"));
141         return(False);
142     }
143
144     if (SMB_VFS_STAT(conn, path, &st) < 0) {
145         DEBUG(0, ("stat of '%s' failed: %s\n", path, strerror(errno)));
146         return(False);
147     }
148     /* Start monitoring this file or directory. We hand the state structure to
149      * both the caller and the FAM library so we can match up the caller's
150      * status requests with FAM notifications.
151      */
152     if (S_ISDIR(st.st_mode)) {
153         FAMMonitorDirectory(&global_fc, fullpath, &(info->req), info);
154     } else {
155         FAMMonitorFile(&global_fc, fullpath, &(info->req), info);
156     }
157
158     /* Grr. On IRIX, neither of the monitor functions return a status. */
159
160     /* We will stay in initialising state until we see the FAMendExist message
161      * for this file.
162      */
163     info->state = FAM_REQ_MONITORING;
164     info->generation = global_fc_generation;
165     return(True);
166 }
167
168 static BOOL
169 fam_handle_event(enum FAMCodes code, uint32 flags)
170 {
171 #define F_CHANGE_MASK (FILE_NOTIFY_CHANGE_FILE | \
172                         FILE_NOTIFY_CHANGE_ATTRIBUTES | \
173                         FILE_NOTIFY_CHANGE_SIZE | \
174                         FILE_NOTIFY_CHANGE_LAST_WRITE | \
175                         FILE_NOTIFY_CHANGE_LAST_ACCESS | \
176                         FILE_NOTIFY_CHANGE_CREATION | \
177                         FILE_NOTIFY_CHANGE_EA | \
178                         FILE_NOTIFY_CHANGE_SECURITY)
179
180 #define F_DELETE_MASK (FILE_NOTIFY_CHANGE_FILE_NAME | \
181                         FILE_NOTIFY_CHANGE_DIR_NAME)
182
183 #define F_CREATE_MASK (FILE_NOTIFY_CHANGE_FILE_NAME | \
184                         FILE_NOTIFY_CHANGE_DIR_NAME)
185
186     switch (code) {
187         case FAMChanged:
188             if (flags & F_CHANGE_MASK)
189                 return(True);
190             break;
191         case FAMDeleted:
192             if (flags & F_DELETE_MASK)
193                 return(True);
194             break;
195         case FAMCreated:
196             if (flags & F_CREATE_MASK)
197                 return(True);
198             break;
199         default:
200             /* Ignore anything else. */
201             break;
202     }
203
204     return(False);
205
206 #undef F_CHANGE_MASK
207 #undef F_DELETE_MASK
208 #undef F_CREATE_MASK
209 }
210
211 static BOOL
212 fam_pump_events(struct fam_req_info * info, uint32_t flags)
213 {
214     FAMEvent ev;
215
216     for (;;) {
217
218         /* If we are draining the event queue we must keep going until we find
219          * the correct FAMAcknowledge event or the connection drops. Otherwise
220          * we should stop when there are no more events pending.
221          */
222         if (flags != FAM_EVENT_DRAIN && !FAMPending(&global_fc)) {
223             break;
224         }
225
226         if (FAMNextEvent(&global_fc, &ev) < 0) {
227             DEBUG(0, ("failed to fetch pending FAM event\n"));
228             DEBUGADD(0, ("resetting FAM connection\n"));
229             FAMClose(&global_fc);
230             FAMCONNECTION_GETFD(&global_fc) = -1;
231             return(False);
232         }
233
234         DEBUG(FAM_TRACE_LOW, ("FAM event %s on '%s' for request %d\n",
235                     fam_event_str(ev.code), ev.filename, ev.fr.reqnum));
236
237         switch (ev.code) {
238             case FAMAcknowledge:
239                 /* FAM generates an ACK event when we cancel a monitor. We need
240                  * this to know when it is safe to free out request state
241                  * structure.
242                  */
243                 if (info->generation == global_fc_generation &&
244                     info->req.reqnum == ev.fr.reqnum &&
245                     flags == FAM_EVENT_DRAIN) {
246                     return(True);
247                 }
248
249             case FAMEndExist:
250             case FAMExists:
251                 /* Ignore these. FAM sends these enumeration events when we
252                  * start monitoring. If we are monitoring a directory, we will
253                  * get a FAMExists event for each directory entry.
254                  */
255
256                 /* TODO: we might be able to use these to implement recursive
257                  * monitoring of entire subtrees.
258                  */
259             case FAMMoved:
260                 /* These events never happen. A move or rename shows up as a
261                  * create/delete pair.
262                  */
263             case FAMStartExecuting:
264             case FAMStopExecuting:
265                 /* We might get these, but we just don't care. */
266                 break;
267
268             case FAMChanged:
269             case FAMDeleted:
270             case FAMCreated:
271                 if (info->generation != global_fc_generation) {
272                     /* Ignore this; the req number can't be matched. */
273                     break;
274                 }
275
276                 if (info->req.reqnum == ev.fr.reqnum) {
277                     /* This is the event the caller was interested in. */
278                     DEBUG(FAM_TRACE, ("handling FAM %s event on '%s'\n",
279                                 fam_event_str(ev.code), ev.filename));
280                     /* Ignore events if we are draining this request. */
281                     if (flags != FAM_EVENT_DRAIN) {
282                         return(fam_handle_event(ev.code, flags));
283                     }
284                     break;
285                 } else {
286                     /* Caller doesn't want this event. Stash the result so we
287                      * can come back to it. Unfortunately, FAM doesn't
288                      * guarantee to give us back evinfo.
289                      */
290                     struct fam_req_info * evinfo =
291                         (struct fam_req_info *)ev.userdata;
292
293                     if (evinfo) {
294                         DEBUG(FAM_TRACE, ("storing FAM %s event for winter\n",
295                                     fam_event_str(ev.code)));
296                         evinfo->state = FAM_REQ_FIRED;
297                         evinfo->code = ev.code;
298                     } else {
299                         DEBUG(2, ("received FAM %s notification for %s, "
300                                   "but userdata was unexpectedly NULL\n",
301                                   fam_event_str(ev.code), ev.filename));
302                     }
303                     break;
304                 }
305
306             default:
307                 DEBUG(0, ("ignoring unknown FAM event code %d for `%s`\n",
308                             ev.code, ev.filename));
309         }
310     }
311
312     /* No more notifications pending. */
313     return(False);
314 }
315
316 static BOOL
317 fam_test_connection(void)
318 {
319     FAMConnection fc;
320
321     /* On IRIX FAMOpen2 leaks 960 bytes in 48 blocks. It's a deliberate leak
322      * in the library and there's nothing we can do about it here.
323      */
324     if (FAMOpen2(&fc, "smbd probe") < 0)
325         return(False);
326
327     FAMClose(&fc);
328     return(True);
329 }
330
331 /* ------------------------------------------------------------------------- */
332
333 static void *
334 fam_register_notify(connection_struct * conn,
335                     char *              path,
336                     uint32              flags)
337 {
338     struct fam_req_info * info;
339
340     if (!fam_check_reconnect()) {
341         return(False);
342     }
343
344     if ((info = SMB_MALLOC_P(struct fam_req_info)) == NULL) {
345         DEBUG(0, ("malloc of %d bytes failed\n", sizeof(struct fam_req_info)));
346         return(NULL);
347     }
348
349     if (fam_monitor_path(conn, info, path, flags)) {
350         return(info);
351     } else {
352         SAFE_FREE(info);
353         return(NULL);
354     }
355 }
356
357 static BOOL
358 fam_check_notify(connection_struct *    conn,
359                  uint16_t               vuid,
360                  char *                 path,
361                  uint32_t               flags,
362                  void *                 data,
363                  time_t                 when)
364 {
365     struct fam_req_info * info;
366
367     info = (struct fam_req_info *)data;
368     SMB_ASSERT(info != NULL);
369
370     DEBUG(10, ("checking FAM events for `%s`\n", path));
371
372     if (info->state == FAM_REQ_FIRED) {
373         DEBUG(FAM_TRACE, ("handling previously fired FAM %s event\n",
374                     fam_event_str(info->code)));
375         info->state = FAM_REQ_MONITORING;
376         return(fam_handle_event(info->code, flags));
377     }
378
379     if (!fam_check_reconnect()) {
380         return(False);
381     }
382
383     if (info->generation != global_fc_generation) {
384         DEBUG(FAM_TRACE, ("reapplying stale FAM monitor to %s\n", path));
385         fam_monitor_path(conn, info, path, flags);
386         return(False);
387     }
388
389     return(fam_pump_events(info, flags));
390 }
391
392 static void
393 fam_remove_notify(void * data)
394 {
395     struct fam_req_info * info;
396
397     if ((info = (struct fam_req_info *)data) == NULL)
398         return;
399
400     /* No need to reconnect. If the FAM connection is gone, there's no need to
401      * cancel and we can safely let FAMCancelMonitor fail. If it we
402      * reconnected, then the generation check will stop us cancelling the wrong
403      * request.
404      */
405
406     if (info->generation == global_fc_generation) {
407         DEBUG(FAM_TRACE, ("removing FAM notification for request %d\n",
408                     info->req.reqnum));
409         FAMCancelMonitor(&global_fc, &(info->req));
410
411         /* Soak up all events until the FAMAcknowledge. We can't free
412          * our request state until we are sure there are no more events in
413          * flight.
414          */
415         fam_pump_events(info, FAM_EVENT_DRAIN);
416     }
417
418     SAFE_FREE(info);
419 }
420
421 struct cnotify_fns * fam_notify_init(void)
422 {
423     static struct cnotify_fns global_fam_notify =
424     {
425         fam_register_notify,
426         fam_check_notify,
427         fam_remove_notify,
428         FAM_NOTIFY_CHECK_TIMEOUT
429     };
430
431     /* TODO: rather than relying on FAM_NOTIFY_CHECK_TIMEOUT, we should have an
432      * API to push the FAM fd into the global server fd set.
433      */
434
435     FAMCONNECTION_GETFD(&global_fc) = -1;
436
437     if (!fam_test_connection()) {
438         DEBUG(0, ("FAM file change notifications not available\n"));
439         return(NULL);
440     }
441
442     DEBUG(FAM_TRACE, ("enabling FAM change notifications\n"));
443     return &global_fam_notify;
444 }
445
446 #endif /* HAVE_FAM_CHANGE_NOTIFY */