r14111: Work around additional typedefs in the gamin implementation
[Samba/nascimento.git] / source3 / smbd / notify_fam.c
blob3b6be77acab34f47e4021035c267635fd8b78e46
1 /*
2 * FAM file notification support.
4 * Copyright (c) James Peach 2005
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.
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.
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
21 #include "includes.h"
23 #ifdef HAVE_FAM_CHANGE_NOTIFY
25 #include <fam.h>
27 #if !defined(HAVE_FAM_H_FAMCODES_TYPEDEF)
28 /* Gamin provides this typedef which means we can't use 'enum FAMCodes' as per
29 * every other FAM implementation. Phooey.
31 typedef enum FAMCodes FAMCodes;
32 #endif
34 /* NOTE: There are multiple versions of FAM floating around the net, each with
35 * slight differences from the original SGI FAM implementation. In this file,
36 * we rely only on the SGI features and do not assume any extensions. For
37 * example, we do not look at FAMErrno, because it is not set by the original
38 * implementation.
40 * Random FAM links:
41 * http://oss.sgi.com/projects/fam/
42 * http://savannah.nongnu.org/projects/fam/
43 * http://sourceforge.net/projects/bsdfam/
46 struct fam_req_info
48 FAMRequest req;
49 int generation;
50 FAMCodes code;
51 enum
53 /* We are waiting for an event. */
54 FAM_REQ_MONITORING,
55 /* An event has been receive, but we haven't been able to send it back
56 * to the client yet. It is stashed in the code member.
58 FAM_REQ_FIRED
59 } state;
62 /* Don't initialise this until the first register request. We want a single
63 * FAM connection for each worker smbd. If we allow the master (parent) smbd to
64 * open a FAM connection, multiple processes talking on the same socket will
65 * undoubtedly create havoc.
67 static FAMConnection global_fc;
68 static int global_fc_generation;
70 #define FAM_TRACE 8
71 #define FAM_TRACE_LOW 10
73 #define FAM_EVENT_DRAIN ((uint32_t)(-1))
75 static void * fam_register_notify(connection_struct * conn,
76 char * path,
77 uint32 flags);
79 static BOOL fam_check_notify(connection_struct * conn,
80 uint16_t vuid,
81 char * path,
82 uint32_t flags,
83 void * data,
84 time_t when);
86 static void fam_remove_notify(void * data);
88 static struct cnotify_fns global_fam_notify =
90 fam_register_notify,
91 fam_check_notify,
92 fam_remove_notify,
93 -1,
97 /* Turn a FAM event code into a string. Don't rely on specific code values,
98 * because that might not work across all flavours of FAM.
100 static const char *
101 fam_event_str(FAMCodes code)
103 static const struct { FAMCodes code; const char * name; } evstr[] =
105 { FAMChanged, "FAMChanged"},
106 { FAMDeleted, "FAMDeleted"},
107 { FAMStartExecuting, "FAMStartExecuting"},
108 { FAMStopExecuting, "FAMStopExecuting"},
109 { FAMCreated, "FAMCreated"},
110 { FAMMoved, "FAMMoved"},
111 { FAMAcknowledge, "FAMAcknowledge"},
112 { FAMExists, "FAMExists"},
113 { FAMEndExist, "FAMEndExist"}
116 int i;
118 for (i = 0; i < ARRAY_SIZE(evstr); ++i) {
119 if (code == evstr[i].code)
120 return(evstr[i].name);
123 return("<unknown>");
126 static BOOL
127 fam_check_reconnect(void)
129 if (FAMCONNECTION_GETFD(&global_fc) < 0) {
130 fstring name;
132 global_fc_generation++;
133 snprintf(name, sizeof(name), "smbd (%lu)", (unsigned long)sys_getpid());
135 if (FAMOpen2(&global_fc, name) < 0) {
136 DEBUG(0, ("failed to connect to FAM service\n"));
137 return(False);
141 global_fam_notify.notification_fd = FAMCONNECTION_GETFD(&global_fc);
142 return(True);
145 static BOOL
146 fam_monitor_path(connection_struct * conn,
147 struct fam_req_info * info,
148 const char * path,
149 uint32 flags)
151 SMB_STRUCT_STAT st;
152 pstring fullpath;
154 DEBUG(FAM_TRACE, ("requesting FAM notifications for '%s'\n", path));
156 /* FAM needs an absolute pathname. */
158 /* It would be better to use reduce_name() here, but reduce_name does not
159 * actually return the reduced result. How utterly un-useful.
161 pstrcpy(fullpath, path);
162 if (!canonicalize_path(conn, fullpath)) {
163 DEBUG(0, ("failed to canonicalize path '%s'\n", path));
164 return(False);
167 if (*fullpath != '/') {
168 DEBUG(0, ("canonicalized path '%s' into `%s`\n", path, fullpath));
169 DEBUGADD(0, ("but expected an absolute path\n"));
170 return(False);
173 if (SMB_VFS_STAT(conn, path, &st) < 0) {
174 DEBUG(0, ("stat of '%s' failed: %s\n", path, strerror(errno)));
175 return(False);
177 /* Start monitoring this file or directory. We hand the state structure to
178 * both the caller and the FAM library so we can match up the caller's
179 * status requests with FAM notifications.
181 if (S_ISDIR(st.st_mode)) {
182 FAMMonitorDirectory(&global_fc, fullpath, &(info->req), info);
183 } else {
184 FAMMonitorFile(&global_fc, fullpath, &(info->req), info);
187 /* Grr. On IRIX, neither of the monitor functions return a status. */
189 /* We will stay in initialising state until we see the FAMendExist message
190 * for this file.
192 info->state = FAM_REQ_MONITORING;
193 info->generation = global_fc_generation;
194 return(True);
197 static BOOL
198 fam_handle_event(const FAMCodes code, uint32 flags)
200 #define F_CHANGE_MASK (FILE_NOTIFY_CHANGE_FILE | \
201 FILE_NOTIFY_CHANGE_ATTRIBUTES | \
202 FILE_NOTIFY_CHANGE_SIZE | \
203 FILE_NOTIFY_CHANGE_LAST_WRITE | \
204 FILE_NOTIFY_CHANGE_LAST_ACCESS | \
205 FILE_NOTIFY_CHANGE_CREATION | \
206 FILE_NOTIFY_CHANGE_EA | \
207 FILE_NOTIFY_CHANGE_SECURITY)
209 #define F_DELETE_MASK (FILE_NOTIFY_CHANGE_FILE_NAME | \
210 FILE_NOTIFY_CHANGE_DIR_NAME)
212 #define F_CREATE_MASK (FILE_NOTIFY_CHANGE_FILE_NAME | \
213 FILE_NOTIFY_CHANGE_DIR_NAME)
215 switch (code) {
216 case FAMChanged:
217 if (flags & F_CHANGE_MASK)
218 return(True);
219 break;
220 case FAMDeleted:
221 if (flags & F_DELETE_MASK)
222 return(True);
223 break;
224 case FAMCreated:
225 if (flags & F_CREATE_MASK)
226 return(True);
227 break;
228 default:
229 /* Ignore anything else. */
230 break;
233 return(False);
235 #undef F_CHANGE_MASK
236 #undef F_DELETE_MASK
237 #undef F_CREATE_MASK
240 static BOOL
241 fam_pump_events(struct fam_req_info * info, uint32_t flags)
243 FAMEvent ev;
245 for (;;) {
247 /* If we are draining the event queue we must keep going until we find
248 * the correct FAMAcknowledge event or the connection drops. Otherwise
249 * we should stop when there are no more events pending.
251 if (flags != FAM_EVENT_DRAIN && !FAMPending(&global_fc)) {
252 break;
255 if (FAMNextEvent(&global_fc, &ev) < 0) {
256 DEBUG(0, ("failed to fetch pending FAM event\n"));
257 DEBUGADD(0, ("resetting FAM connection\n"));
258 FAMClose(&global_fc);
259 FAMCONNECTION_GETFD(&global_fc) = -1;
260 return(False);
263 DEBUG(FAM_TRACE_LOW, ("FAM event %s on '%s' for request %d\n",
264 fam_event_str(ev.code), ev.filename, ev.fr.reqnum));
266 switch (ev.code) {
267 case FAMAcknowledge:
268 /* FAM generates an ACK event when we cancel a monitor. We need
269 * this to know when it is safe to free out request state
270 * structure.
272 if (info->generation == global_fc_generation &&
273 info->req.reqnum == ev.fr.reqnum &&
274 flags == FAM_EVENT_DRAIN) {
275 return(True);
278 case FAMEndExist:
279 case FAMExists:
280 /* Ignore these. FAM sends these enumeration events when we
281 * start monitoring. If we are monitoring a directory, we will
282 * get a FAMExists event for each directory entry.
285 /* TODO: we might be able to use these to implement recursive
286 * monitoring of entire subtrees.
288 case FAMMoved:
289 /* These events never happen. A move or rename shows up as a
290 * create/delete pair.
292 case FAMStartExecuting:
293 case FAMStopExecuting:
294 /* We might get these, but we just don't care. */
295 break;
297 case FAMChanged:
298 case FAMDeleted:
299 case FAMCreated:
300 if (info->generation != global_fc_generation) {
301 /* Ignore this; the req number can't be matched. */
302 break;
305 if (info->req.reqnum == ev.fr.reqnum) {
306 /* This is the event the caller was interested in. */
307 DEBUG(FAM_TRACE, ("handling FAM %s event on '%s'\n",
308 fam_event_str(ev.code), ev.filename));
309 /* Ignore events if we are draining this request. */
310 if (flags != FAM_EVENT_DRAIN) {
311 return(fam_handle_event(ev.code, flags));
313 break;
314 } else {
315 /* Caller doesn't want this event. Stash the result so we
316 * can come back to it. Unfortunately, FAM doesn't
317 * guarantee to give us back evinfo.
319 struct fam_req_info * evinfo =
320 (struct fam_req_info *)ev.userdata;
322 if (evinfo) {
323 DEBUG(FAM_TRACE, ("storing FAM %s event for winter\n",
324 fam_event_str(ev.code)));
325 evinfo->state = FAM_REQ_FIRED;
326 evinfo->code = ev.code;
327 } else {
328 DEBUG(2, ("received FAM %s notification for %s, "
329 "but userdata was unexpectedly NULL\n",
330 fam_event_str(ev.code), ev.filename));
332 break;
335 default:
336 DEBUG(0, ("ignoring unknown FAM event code %d for `%s`\n",
337 ev.code, ev.filename));
341 /* No more notifications pending. */
342 return(False);
345 static BOOL
346 fam_test_connection(void)
348 FAMConnection fc;
350 /* On IRIX FAMOpen2 leaks 960 bytes in 48 blocks. It's a deliberate leak
351 * in the library and there's nothing we can do about it here.
353 if (FAMOpen2(&fc, "smbd probe") < 0)
354 return(False);
356 FAMClose(&fc);
357 return(True);
360 /* ------------------------------------------------------------------------- */
362 static void *
363 fam_register_notify(connection_struct * conn,
364 char * path,
365 uint32 flags)
367 struct fam_req_info * info;
369 if (!fam_check_reconnect()) {
370 return(False);
373 if ((info = SMB_MALLOC_P(struct fam_req_info)) == NULL) {
374 DEBUG(0, ("malloc of %d bytes failed\n", sizeof(struct fam_req_info)));
375 return(NULL);
378 if (fam_monitor_path(conn, info, path, flags)) {
379 return(info);
380 } else {
381 SAFE_FREE(info);
382 return(NULL);
386 static BOOL
387 fam_check_notify(connection_struct * conn,
388 uint16_t vuid,
389 char * path,
390 uint32_t flags,
391 void * data,
392 time_t when)
394 struct fam_req_info * info;
396 info = (struct fam_req_info *)data;
397 SMB_ASSERT(info != NULL);
399 DEBUG(10, ("checking FAM events for `%s`\n", path));
401 if (info->state == FAM_REQ_FIRED) {
402 DEBUG(FAM_TRACE, ("handling previously fired FAM %s event\n",
403 fam_event_str(info->code)));
404 info->state = FAM_REQ_MONITORING;
405 return(fam_handle_event(info->code, flags));
408 if (!fam_check_reconnect()) {
409 return(False);
412 if (info->generation != global_fc_generation) {
413 DEBUG(FAM_TRACE, ("reapplying stale FAM monitor to %s\n", path));
414 fam_monitor_path(conn, info, path, flags);
415 return(False);
418 return(fam_pump_events(info, flags));
421 static void
422 fam_remove_notify(void * data)
424 struct fam_req_info * info;
426 if ((info = (struct fam_req_info *)data) == NULL)
427 return;
429 /* No need to reconnect. If the FAM connection is gone, there's no need to
430 * cancel and we can safely let FAMCancelMonitor fail. If it we
431 * reconnected, then the generation check will stop us cancelling the wrong
432 * request.
435 if (info->generation == global_fc_generation) {
436 DEBUG(FAM_TRACE, ("removing FAM notification for request %d\n",
437 info->req.reqnum));
438 FAMCancelMonitor(&global_fc, &(info->req));
440 /* Soak up all events until the FAMAcknowledge. We can't free
441 * our request state until we are sure there are no more events in
442 * flight.
444 fam_pump_events(info, FAM_EVENT_DRAIN);
447 SAFE_FREE(info);
450 struct cnotify_fns * fam_notify_init(void)
452 FAMCONNECTION_GETFD(&global_fc) = -1;
454 if (!fam_test_connection()) {
455 DEBUG(0, ("FAM file change notifications not available\n"));
456 return(NULL);
459 DEBUG(FAM_TRACE, ("enabling FAM change notifications\n"));
460 return &global_fam_notify;
463 #endif /* HAVE_FAM_CHANGE_NOTIFY */