summaryrefslogtreecommitdiffstats
path: root/source3/modules/onefs_notify.c
blob: 3455afd4ab42cd54bba82617d80273466fdd9d15 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
/*
 * Unix SMB/CIFS implementation.
 *
 * Support for change notify using OneFS's file event notification system
 *
 * Copyright (C) Andrew Tridgell, 2006
 * Copyright (C) Steven Danneman, 2008
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

/* Implement handling of change notify requests on files and directories using
 * Isilon OneFS's "ifs event" file notification system.
 *
 * The structure of this file is based off the implementation of change notify
 * using the inotify system calls in smbd/notify_inotify.c */

/* TODO: We could reduce the number of file descriptors used by merging
 * multiple watch requests on the same directory into the same
 * onefs_notify_watch_context.  To do this we'd need mux/demux routines that
 * when receiving an event on that watch context would check it against the
 * CompletionFilter and WatchTree of open SMB requests, and return notify
 * events back to the proper SMB requests */

#include "includes.h"
#include "onefs.h"

#include <ifs/ifs_types.h>
#include <ifs/ifs_syscalls.h>
#include <isi_util/syscalls.h>

#include <sys/event.h>

#define ONEFS_IFS_EVENT_MAX_NUM 8
#define ONEFS_IFS_EVENT_MAX_BYTES (ONEFS_IFS_EVENT_MAX_NUM * \
				   sizeof(struct ifs_event))

struct onefs_notify_watch_context {
	struct sys_notify_context *ctx;
	int watch_fd;
	ino_t watch_lin;
	const char *path;
	int ifs_event_fd;
	uint32_t ifs_filter; /* the ifs event mask */
	uint32_t smb_filter; /* the windows completion filter */
	void (*callback)(struct sys_notify_context *ctx,
			 void *private_data,
			 struct notify_event *ev);
	void *private_data;
};

/**
 * Conversion map from a SMB completion filter to an IFS event mask.
 */
static const struct {
	uint32_t smb_filter;
	uint32_t ifs_filter;
} onefs_notify_conv[] = {
	{FILE_NOTIFY_CHANGE_FILE_NAME,
	    NOTE_CREATE | NOTE_DELETE | NOTE_RENAME_FROM | NOTE_RENAME_TO},
	{FILE_NOTIFY_CHANGE_DIR_NAME,
	    NOTE_CREATE | NOTE_DELETE | NOTE_RENAME_FROM | NOTE_RENAME_TO},
	{FILE_NOTIFY_CHANGE_ATTRIBUTES,
	    NOTE_CREATE | NOTE_DELETE | NOTE_RENAME_FROM | NOTE_RENAME_TO |
	    NOTE_ATTRIB},
	{FILE_NOTIFY_CHANGE_SIZE,
	    NOTE_SIZE | NOTE_EXTEND},
	{FILE_NOTIFY_CHANGE_LAST_WRITE,
	    NOTE_WRITE | NOTE_ATTRIB},
	/* OneFS doesn't set atime by default, but we can somewhat fake it by
	 * notifying for other events that imply ACCESS */
	{FILE_NOTIFY_CHANGE_LAST_ACCESS,
	    NOTE_WRITE | NOTE_ATTRIB},
	/* We don't have an ifs_event for the setting of create time, but we
	 * can fake by notifying when a "new" file is created via rename */
	{FILE_NOTIFY_CHANGE_CREATION,
	    NOTE_RENAME_TO},
	{FILE_NOTIFY_CHANGE_SECURITY,
	    NOTE_SECURITY},
	/* Unsupported bits
	FILE_NOTIFY_CHANGE_EA		 (no EAs in OneFS)
	FILE_NOTIFY_CHANGE_STREAM_NAME	 (no ifs_event equivalent)
	FILE_NOTIFY_CHANGE_STREAM_SIZE	 (no ifs_event equivalent)
	FILE_NOTIFY_CHANGE_STREAM_WRITE	 (no ifs_event equivalent) */
};

#define ONEFS_NOTIFY_UNSUPPORTED  (FILE_NOTIFY_CHANGE_LAST_ACCESS | \
				   FILE_NOTIFY_CHANGE_CREATION    | \
				   FILE_NOTIFY_CHANGE_EA          | \
				   FILE_NOTIFY_CHANGE_STREAM_NAME | \
				   FILE_NOTIFY_CHANGE_STREAM_SIZE | \
				   FILE_NOTIFY_CHANGE_STREAM_WRITE)

/**
 * Convert Windows/SMB filter/flags to IFS event filter.
 *
 * @param[in] smb_filter Windows Completion Filter sent in the SMB
 *
 * @return ifs event filter mask
 */
static uint32_t
onefs_notify_smb_filter_to_ifs_filter(uint32_t smb_filter)
{
	int i;
	uint32_t ifs_filter = 0x0;

	for (i=0;i<ARRAY_SIZE(onefs_notify_conv);i++) {
		if (onefs_notify_conv[i].smb_filter & smb_filter) {
			ifs_filter |= onefs_notify_conv[i].ifs_filter;
		}
	}

	return ifs_filter;
}

/**
 * Convert IFS filter/flags to a Windows notify action.
 *
 * Returns Win notification actions, types (1-5).
 *
 * @param[in] smb_filter Windows Completion Filter sent in the SMB
 * @param[in] ifs_filter Returned from the kernel in the ifs_event
 *
 * @return 0 if there are no more relevant flags.
 */
static int
onefs_notify_ifs_filter_to_smb_action(uint32_t smb_filter, uint32_t ifs_filter)
{
	/* Handle Windows special cases, before modifying events bitmask */

	/* Special case 1: win32api->MoveFile needs to send a modified
	 * notification on the new file, if smb_filter == ATTRIBUTES.
	 * Here we handle the case where two separate ATTR & NAME notifications
	 * have been registered.  We handle the case where both bits are set in
	 * the same registration in onefs_notify_dispatch() */
	if ((smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) &&
		!(smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
		(ifs_filter & NOTE_FILE) && (ifs_filter & NOTE_RENAME_TO))
	{
		return NOTIFY_ACTION_MODIFIED;
	}

	/* Special case 2: Writes need to send a modified
	 * notification on the file, if smb_filter = ATTRIBUTES. */
	if ((smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) &&
		(ifs_filter & NOTE_FILE) && (ifs_filter & NOTE_WRITE))
	{
		return NOTIFY_ACTION_MODIFIED;
	}

	/* Loop because some events may be filtered out. Eventually all
	 * relevant events will be taken care of and returned. */
	while (1) {
		if (ifs_filter & NOTE_CREATE) {
			ifs_filter &= ~NOTE_CREATE;
			if ((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
				(ifs_filter & NOTE_FILE))
				return NOTIFY_ACTION_ADDED;
			if ((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
				(ifs_filter & NOTE_DIRECTORY))
				return NOTIFY_ACTION_ADDED;
		}
		else if (ifs_filter & NOTE_DELETE) {
			ifs_filter &= ~NOTE_DELETE;
			if ((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
				(ifs_filter & NOTE_FILE))
				return NOTIFY_ACTION_REMOVED;
			if ((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
				(ifs_filter & NOTE_DIRECTORY))
				return NOTIFY_ACTION_REMOVED;
		}
		else if (ifs_filter & NOTE_WRITE) {
			ifs_filter &= ~NOTE_WRITE;
			if ((smb_filter & FILE_NOTIFY_CHANGE_LAST_WRITE) ||
				(smb_filter & FILE_NOTIFY_CHANGE_LAST_ACCESS))
				return NOTIFY_ACTION_MODIFIED;
		}
		else if ((ifs_filter & NOTE_SIZE) || (ifs_filter & NOTE_EXTEND)) {
			ifs_filter &= ~NOTE_SIZE;
			ifs_filter &= ~NOTE_EXTEND;

			/* TODO: Do something on NOTE_DIR? */
			if ((smb_filter & FILE_NOTIFY_CHANGE_SIZE) &&
			    (ifs_filter & NOTE_FILE))
				return NOTIFY_ACTION_MODIFIED;
		}
		else if (ifs_filter & NOTE_ATTRIB) {
			ifs_filter &= ~NOTE_ATTRIB;
			/* NOTE_ATTRIB needs to be converted to a
			 * LAST_WRITE as well, because we need to send
			 * LAST_WRITE when the mtime changes. Looking into
			 * better alternatives as this causes extra LAST_WRITE
			 * notifications. We also return LAST_ACCESS as a
			 * modification to attribs implies this. */
			if ((smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) ||
				(smb_filter & FILE_NOTIFY_CHANGE_LAST_WRITE) ||
				(smb_filter & FILE_NOTIFY_CHANGE_LAST_ACCESS))
				return NOTIFY_ACTION_MODIFIED;
		}
		else if (ifs_filter & NOTE_LINK) {
			ifs_filter &= ~NOTE_LINK;
			/* NOTE_LINK will send out NO notifications */
		}
		else if (ifs_filter & NOTE_REVOKE) {
			ifs_filter &= ~NOTE_REVOKE;
			/* NOTE_REVOKE will send out NO notifications */
		}
		else if (ifs_filter & NOTE_RENAME_FROM)  {
			ifs_filter &= ~NOTE_RENAME_FROM;

			if (((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
				 (ifs_filter & NOTE_FILE)) ||
				((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
				 (ifs_filter & NOTE_DIRECTORY))) {
				/* Check if this is a RENAME, not a MOVE */
				if (ifs_filter & NOTE_RENAME_SAMEDIR) {
					/* Remove the NOTE_RENAME_SAMEDIR, IFF
					 * RENAME_TO is not in this event */
					if (!(ifs_filter & NOTE_RENAME_TO))
						ifs_filter &=
						    ~NOTE_RENAME_SAMEDIR;
					return NOTIFY_ACTION_OLD_NAME;
				}
				return NOTIFY_ACTION_REMOVED;
			}
		}
		else if (ifs_filter & NOTE_RENAME_TO) {
			ifs_filter &= ~NOTE_RENAME_TO;

			if (((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
				 (ifs_filter & NOTE_FILE)) ||
				((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
				 (ifs_filter & NOTE_DIRECTORY))) {
				/* Check if this is a RENAME, not a MOVE */
				if (ifs_filter & NOTE_RENAME_SAMEDIR) {
					/* Remove the NOTE_RENAME_SAMEDIR, IFF
					 * RENAME_FROM is not in this event */
					if (!(ifs_filter & NOTE_RENAME_FROM))
						ifs_filter &=
						    ~NOTE_RENAME_SAMEDIR;
					return NOTIFY_ACTION_NEW_NAME;
				}
				return NOTIFY_ACTION_ADDED;
			}
			/* RAW-NOTIFY shows us that a rename triggers a
			 * creation time change */
			if ((smb_filter & FILE_NOTIFY_CHANGE_CREATION) &&
				(ifs_filter & NOTE_FILE))
				return NOTIFY_ACTION_MODIFIED;
		}
		else if (ifs_filter & NOTE_SECURITY) {
			ifs_filter &= ~NOTE_SECURITY;

			if (smb_filter & FILE_NOTIFY_CHANGE_SECURITY)
				return NOTIFY_ACTION_MODIFIED;
		} else {
			/* No relevant flags found */
			return 0;
		}
	}
}

/**
 * Retrieve a directory path of a changed file, relative to the watched dir
 *
 * @param[in] wc watch context for the returned event
 * @param[in] e ifs_event notification returned from the kernel
 * @param[out] path name relative to the watched dir. This is talloced
 *		    off of wc and needs to be freed by the caller.
 *
 * @return true on success
 *
 * TODO: This function currently doesn't handle path names with multiple
 * encodings.  enc_get_lin_path() should be used in the future to convert
 * each path segment's encoding to UTF-8
 */
static bool
get_ifs_event_path(struct onefs_notify_watch_context *wc, struct ifs_event *e,
		   char **path)
{
	char *path_buf = NULL;
	size_t pathlen = 0;
	int error = 0;

	SMB_ASSERT(path);

	/* Lookup the path from watch_dir through our parent dir */
	if (e->namelen > 0) {
		error = lin_get_path(wc->watch_lin,
				     e->parent_lin,
				     HEAD_SNAPID,
				     e->parent_parent_lin,
				     e->parent_name_hash,
				     &pathlen, &path_buf);
		if (!error) {
			/* Only add slash if a path exists in path_buf from
			 * lin_get_path call. Windows does not expect a
			 * leading '/' */
			if (pathlen > 0)
				*path = talloc_asprintf(wc, "%s/%s",
							path_buf, e->name);
			else
				*path = talloc_asprintf(wc, "%s", e->name);
			SAFE_FREE(path_buf);
		}
	}

	/* If ifs_event didn't return a name, or we errored out of our intial
	 * path lookup, try again using the lin of the changed file */
	if (!(*path)) {
		error = lin_get_path(wc->watch_lin,
				     e->lin,
				     HEAD_SNAPID,
				     e->parent_lin,
				     e->name_hash,
				     &pathlen, &path_buf);
		if (error) {
			/* It's possible that both the lin and the parent lin
			 * are invalid (or not given) -- we will skip these
			 * events. */
			DEBUG(3,("Path lookup failed. LINS are invalid: "
				 "e->lin: 0x%llu, e->parent_lin: 0x%llu, "
				 "e->parent_parent_lin: 0x%llu\n",
				 e->lin, e->parent_lin, e->parent_parent_lin));
			SAFE_FREE(path_buf);
			return false;
		} else {
			*path = talloc_asprintf(wc, "%s", path_buf);
			DEBUG(5, ("Using path from event LIN = %s\n",
			    path_buf));
			SAFE_FREE(path_buf);
		}
	}

	/* Replacement of UNIX slashes with WIN slashes is handled at a
	 * higher layer. */

	return true;
}

/**
 * Dispatch one OneFS notify event to the general Samba code
 *
 * @param[in] wc watch context for the returned event
 * @param[in] e event returned from the kernel
 *
 * @return nothing
 */
static void
onefs_notify_dispatch(struct onefs_notify_watch_context *wc,
		      struct ifs_event *e)
{
	char *path = NULL;
	struct notify_event ne;

	DEBUG(5, ("Retrieved ifs event from kernel: lin=%#llx, ifs_events=%#x, "
		 "parent_lin=%#llx, namelen=%d, name=\"%s\"\n",
		 e->lin, e->events, e->parent_lin, e->namelen, e->name));

	/* Check validity of event returned from kernel */
	if (e->lin == 0) {
		/* The lin == 0 specifies 1 of 2 cases:
		 * 1) We are out of events. The kernel has a limited
		 *    amount (somewhere near 90000)
		 * 2) Split nodes have merged back and had data written
		 *    to them -- thus we've missed some of those events.  */
		DEBUG(3, ("We've missed some kernel ifs events!\n"));

		/* Alert higher level to the problem so it returns catch-all
		 * response to the client */
		ne.path = NULL;
		ne.action = 0;
		wc->callback(wc->ctx, wc->private_data, &ne);
	}

	if (e->lin == wc->watch_lin) {
		/* Windows doesn't report notifications on root
		 * watched directory */
		/* TODO: This should be abstracted out to the general layer
		 * instead of being handled in every notify provider */
		DEBUG(5, ("Skipping notification on root of the watched "
			 "path.\n"));
		return;
	}

	/* Retrieve the full path for the ifs event name */
	if(!get_ifs_event_path(wc, e, &path)) {
		DEBUG(3, ("Failed to convert the ifs_event lins to a path. "
			 "Skipping this event\n"));
		return;
	}

	if (!strncmp(path, ".ifsvar", 7)) {
		/* Skip notifications on file if its in ifs configuration
		 * directory */
		goto clean;
	}

	ne.path = path;

	/* Convert ifs event mask to an smb action mask */
	ne.action = onefs_notify_ifs_filter_to_smb_action(wc->smb_filter,
							  e->events);

	DEBUG(5, ("Converted smb_filter=%#x, ifs_events=%#x, to "
		  "ne.action = %d, for ne.path = %s\n",
		  wc->smb_filter, e->events, ne.action, ne.path));

	if (!ne.action)
		goto clean;

	/* Return notify_event to higher level */
	wc->callback(wc->ctx, wc->private_data, &ne);

        /* SMB expects a file rename/move to generate three actions, a
	 * rename_from/delete on the from file, a rename_to/create on the to
	 * file, and a modify for the rename_to file. If we have two separate
	 * notifications registered for ATTRIBUTES and FILENAME, this case will
	 * be handled by separate ifs_events in
	 * onefs_notify_ifs_filter_to_smb_action(). If both bits are registered
	 * in the same notification, we must send an extra MODIFIED action
	 * here. */
	if ((wc->smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) &&
	    (wc->smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
	    (e->events & NOTE_FILE) && (e->events & NOTE_RENAME_TO))
	{
		ne.action = NOTIFY_ACTION_MODIFIED;
		wc->callback(wc->ctx, wc->private_data, &ne);
	}

	/* FALLTHROUGH */
clean:
	talloc_free(path);
	return;
}

/**
 * Callback when the kernel has some events for us
 *
 * Read events off ifs event fd and pass them to our dispatch function
 *
 * @param ev context of all tevents registered in the smbd
 * @param fde tevent struct specific to one ifs event channel
 * @param flags tevent flags passed when we added our ifs event channel fd to
 *		the main loop
 * @param private_data onefs_notify_watch_context specific to this ifs event
 *		       channel
 *
 * @return nothing
 */
static void
onefs_notify_handler(struct event_context *ev,
		     struct fd_event *fde,
		     uint16_t flags,
		     void *private_data)
{
	struct onefs_notify_watch_context *wc = NULL;
	struct ifs_event ifs_events[ONEFS_IFS_EVENT_MAX_NUM];
	ssize_t nread = 0;
	int count = 0;
	int i = 0;

	wc = talloc_get_type(private_data, struct onefs_notify_watch_context);

	/* Read as many ifs events from the notify channel as we can */
	nread = sys_read(wc->ifs_event_fd, &ifs_events,
			 ONEFS_IFS_EVENT_MAX_BYTES);
	if (nread == 0) {
		DEBUG(0,("No data found while reading ifs event fd?!\n"));
		return;
	}
	if (nread < 0) {
		DEBUG(0,("Failed to read ifs event data: %s\n",
			strerror(errno)));
		return;
	}

	count = nread / sizeof(struct ifs_event);

	DEBUG(5, ("Got %d notification events in %d bytes.\n", count, nread));

	/* Dispatch ifs_events one-at-a-time to higher level */
	for (i=0; i < count; i++) {
		onefs_notify_dispatch(wc, &ifs_events[i]);
	}
}

/**
 * Destroy the ifs event channel
 *
 * This is called from talloc_free() when the generic Samba notify layer frees
 * the onefs_notify_watch_context.
 *
 * @param[in] wc pointer to watch context which is being destroyed
 *
 * return 0 on success
*/
static int
onefs_watch_destructor(struct onefs_notify_watch_context *wc)
{
	/* The ifs_event_fd will re de-registered from the event loop by
	 * another destructor triggered from the freeing of this wc */
	close(wc->ifs_event_fd);
	return 0;
}

/**
 * Register a single change notify watch request.
 *
 * Open an event listener on a directory to watch for modifications. This
 * channel is closed by a destructor when the caller calls talloc_free()
 * on *handle.
 *
 * @param[in] vfs_handle handle given to most VFS operations
 * @param[in] ctx context (conn and tevent) for this connection
 * @param[in] e filter and path of client's notify request
 * @param[in] callback function to call when file notification event is received
 *		       from the kernel, passing that event up to Samba's
 *		       generalized change notify layer
 * @param[in] private_data opaque data given to us by the general change notify
 *			   layer which must be returned in the callback function
 * @param[out] handle_p handle returned to generalized change notify layer used
 *			to close the event channel when notification is
 *			cancelled
 *
 * @return NT_STATUS_OK unless error
 */
NTSTATUS
onefs_notify_watch(vfs_handle_struct *vfs_handle,
		   struct sys_notify_context *ctx,
		   struct notify_entry *e,
		   void (*callback)(struct sys_notify_context *ctx,
				    void *private_data,
				    struct notify_event *ev),
		   void *private_data,
		   void *handle_p)
{
	int ifs_event_fd = -1;
	uint32_t ifs_filter = 0;
	uint32_t smb_filter = e->filter;
	bool watch_tree = !!e->subdir_filter;
	struct onefs_notify_watch_context *wc = NULL;
	void **handle = (void **)handle_p;
	SMB_STRUCT_STAT sbuf;
	NTSTATUS status = NT_STATUS_UNSUCCESSFUL;

	/* Fallback to default Samba implementation if kernel CN is disabled */
	if (!lp_kernel_change_notify(vfs_handle->conn->params)) {
		(*handle) = NULL;
		return NT_STATUS_OK;
	}

	/* The OneFS open path should always give us a valid fd on a directory*/
	SMB_ASSERT(e->dir_fd >= 0);

	/* Always set e->filter to 0 so we don't fallback on the default change
	 * notify backend. It's not cluster coherent or cross-protocol so we
	 * can't guarantee correctness using it. */
	e->filter = 0;
	e->subdir_filter = 0;

	/* Snapshots do not currently allow event listeners. See Isilon
	 * bug 33476 for an example of .snapshot debug spew that can occur. */
	if (e->dir_id.extid != HEAD_SNAPID)
		return NT_STATUS_INVALID_PARAMETER;

	/* Convert Completion Filter mask to IFS Event mask */
	ifs_filter = onefs_notify_smb_filter_to_ifs_filter(smb_filter);

	if (smb_filter & ONEFS_NOTIFY_UNSUPPORTED) {
		/* One or more of the filter bits could not be fully handled by
		 * the ifs_event system. To be correct, if we cannot service a
		 * bit in the completion filter we should return
		 * NT_STATUS_NOT_IMPLEMENTED to let the client know that they
		 * won't be receiving all the notify events that they asked for.
		 * Unfortunately, WinXP clients cache this error message, stop
		 * trying to send any notify requests at all, and instead return
		 * NOT_IMPLEMENTED to all requesting apps without ever sending a
		 * message to us. Thus we lie, and say we can service all bits,
		 * but simply don't return actions for the filter bits we can't
		 * detect or fully implement. */
		DEBUG(3,("One or more of the Windows completion filter bits "
			 "for \"%s\" could not be fully handled by the "
			 "ifs_event system. The failed bits are "
			 "smb_filter=%#x\n",
			 e->path, smb_filter & ONEFS_NOTIFY_UNSUPPORTED));
	}

	if (ifs_filter == 0) {
		/* None of the filter bits given are supported by the ifs_event
		 * system. Don't create a kernel notify channel, but mock
		 * up a fake handle for the caller. */
		DEBUG(3,("No bits in the Windows completion filter could be "
		         "translated to ifs_event mask for \"%s\", "
			 "smb_filter=%#x\n", e->path, smb_filter));
		(*handle) = NULL;
		return NT_STATUS_OK;
	}

	/* Register an ifs event channel for this watch request */
	ifs_event_fd = ifs_create_listener(watch_tree ?
					   EVENT_RECURSIVE :
					   EVENT_CHILDREN,
					   ifs_filter,
					   e->dir_fd);
	if (ifs_event_fd < 0) {
		DEBUG(0,("Failed to create listener for %s with \"%s\". "
			"smb_filter=0x%x, ifs_filter=0x%x, watch_tree=%u\n",
			strerror(errno), e->path, smb_filter, ifs_filter,
			watch_tree));
		return map_nt_error_from_unix(errno);
	}

	/* Create a watch context to track this change notify request */
	wc = talloc(ctx, struct onefs_notify_watch_context);
	if (wc == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto err;
	}

	/* Get LIN for directory */
	if (sys_fstat(e->dir_fd, &sbuf)) {
		DEBUG(0, ("stat on directory fd failed: %s\n",
			  strerror(errno)));
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	if (sbuf.st_ino == 0) {
		DEBUG(0, ("0 LIN found!\n"));
		goto err;
	}

	wc->ctx = ctx;
	wc->watch_fd = e->dir_fd;
	wc->watch_lin = sbuf.st_ino;
	wc->ifs_event_fd = ifs_event_fd;
	wc->ifs_filter = ifs_filter;
	wc->smb_filter = smb_filter;
	wc->callback = callback;
	wc->private_data = private_data;
	wc->path = talloc_strdup(wc, e->path);
	if (wc->path == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto err;
	}

	(*handle) = wc;

	/* The caller frees the handle to stop watching */
	talloc_set_destructor(wc, onefs_watch_destructor);

	/* Add a tevent waiting for the ifs event fd to be readable */
	event_add_fd(ctx->ev, wc, wc->ifs_event_fd, EVENT_FD_READ,
		     onefs_notify_handler, wc);

	DEBUG(10, ("Watching for changes on \"%s\" smb_filter=0x%x, "
		   "ifs_filter=0x%x, watch_tree=%d, ifs_event_fd=%d, "
		   "dir_fd=%d, dir_lin=0x%llx\n",
		   e->path, smb_filter, ifs_filter, watch_tree,
		   ifs_event_fd, e->dir_fd, sbuf.st_ino));

	return NT_STATUS_OK;

err:
	talloc_free(wc);
	SMB_ASSERT(ifs_event_fd >= 0);
	close(ifs_event_fd);
	return status;
}