2
* Unix SMB/CIFS implementation.
4
* Support for change notify using OneFS's file event notification system
6
* Copyright (C) Andrew Tridgell, 2006
7
* Copyright (C) Steven Danneman, 2008
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 3 of the License, or
12
* (at your option) any later version.
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.
19
* You should have received a copy of the GNU General Public License
20
* along with this program; if not, see <http://www.gnu.org/licenses/>.
23
/* Implement handling of change notify requests on files and directories using
24
* Isilon OneFS's "ifs event" file notification system.
26
* The structure of this file is based off the implementation of change notify
27
* using the inotify system calls in smbd/notify_inotify.c */
29
/* TODO: We could reduce the number of file descriptors used by merging
30
* multiple watch requests on the same directory into the same
31
* onefs_notify_watch_context. To do this we'd need mux/demux routines that
32
* when receiving an event on that watch context would check it against the
33
* CompletionFilter and WatchTree of open SMB requests, and return notify
34
* events back to the proper SMB requests */
39
#include <ifs/ifs_types.h>
40
#include <ifs/ifs_syscalls.h>
41
#include <isi_util/syscalls.h>
43
#include <sys/event.h>
45
#define ONEFS_IFS_EVENT_MAX_NUM 8
46
#define ONEFS_IFS_EVENT_MAX_BYTES (ONEFS_IFS_EVENT_MAX_NUM * \
47
sizeof(struct ifs_event))
49
struct onefs_notify_watch_context {
50
struct sys_notify_context *ctx;
55
uint32_t ifs_filter; /* the ifs event mask */
56
uint32_t smb_filter; /* the windows completion filter */
57
void (*callback)(struct sys_notify_context *ctx,
59
struct notify_event *ev);
64
* Conversion map from a SMB completion filter to an IFS event mask.
69
} onefs_notify_conv[] = {
70
{FILE_NOTIFY_CHANGE_FILE_NAME,
71
NOTE_CREATE | NOTE_DELETE | NOTE_RENAME_FROM | NOTE_RENAME_TO},
72
{FILE_NOTIFY_CHANGE_DIR_NAME,
73
NOTE_CREATE | NOTE_DELETE | NOTE_RENAME_FROM | NOTE_RENAME_TO},
74
{FILE_NOTIFY_CHANGE_ATTRIBUTES,
75
NOTE_CREATE | NOTE_DELETE | NOTE_RENAME_FROM | NOTE_RENAME_TO |
77
{FILE_NOTIFY_CHANGE_SIZE,
78
NOTE_SIZE | NOTE_EXTEND},
79
{FILE_NOTIFY_CHANGE_LAST_WRITE,
80
NOTE_WRITE | NOTE_ATTRIB},
81
/* OneFS doesn't set atime by default, but we can somewhat fake it by
82
* notifying for other events that imply ACCESS */
83
{FILE_NOTIFY_CHANGE_LAST_ACCESS,
84
NOTE_WRITE | NOTE_ATTRIB},
85
/* We don't have an ifs_event for the setting of create time, but we
86
* can fake by notifying when a "new" file is created via rename */
87
{FILE_NOTIFY_CHANGE_CREATION,
89
{FILE_NOTIFY_CHANGE_SECURITY,
92
FILE_NOTIFY_CHANGE_EA (no EAs in OneFS)
93
FILE_NOTIFY_CHANGE_STREAM_NAME (no ifs_event equivalent)
94
FILE_NOTIFY_CHANGE_STREAM_SIZE (no ifs_event equivalent)
95
FILE_NOTIFY_CHANGE_STREAM_WRITE (no ifs_event equivalent) */
98
#define ONEFS_NOTIFY_UNSUPPORTED (FILE_NOTIFY_CHANGE_LAST_ACCESS | \
99
FILE_NOTIFY_CHANGE_CREATION | \
100
FILE_NOTIFY_CHANGE_EA | \
101
FILE_NOTIFY_CHANGE_STREAM_NAME | \
102
FILE_NOTIFY_CHANGE_STREAM_SIZE | \
103
FILE_NOTIFY_CHANGE_STREAM_WRITE)
106
* Convert Windows/SMB filter/flags to IFS event filter.
108
* @param[in] smb_filter Windows Completion Filter sent in the SMB
110
* @return ifs event filter mask
113
onefs_notify_smb_filter_to_ifs_filter(uint32_t smb_filter)
116
uint32_t ifs_filter = 0x0;
118
for (i=0;i<ARRAY_SIZE(onefs_notify_conv);i++) {
119
if (onefs_notify_conv[i].smb_filter & smb_filter) {
120
ifs_filter |= onefs_notify_conv[i].ifs_filter;
128
* Convert IFS filter/flags to a Windows notify action.
130
* Returns Win notification actions, types (1-5).
132
* @param[in] smb_filter Windows Completion Filter sent in the SMB
133
* @param[in] ifs_filter Returned from the kernel in the ifs_event
135
* @return 0 if there are no more relevant flags.
138
onefs_notify_ifs_filter_to_smb_action(uint32_t smb_filter, uint32_t ifs_filter)
140
/* Handle Windows special cases, before modifying events bitmask */
142
/* Special case 1: win32api->MoveFile needs to send a modified
143
* notification on the new file, if smb_filter == ATTRIBUTES.
144
* Here we handle the case where two separate ATTR & NAME notifications
145
* have been registered. We handle the case where both bits are set in
146
* the same registration in onefs_notify_dispatch() */
147
if ((smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) &&
148
!(smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
149
(ifs_filter & NOTE_FILE) && (ifs_filter & NOTE_RENAME_TO))
151
return NOTIFY_ACTION_MODIFIED;
154
/* Special case 2: Writes need to send a modified
155
* notification on the file, if smb_filter = ATTRIBUTES. */
156
if ((smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) &&
157
(ifs_filter & NOTE_FILE) && (ifs_filter & NOTE_WRITE))
159
return NOTIFY_ACTION_MODIFIED;
162
/* Loop because some events may be filtered out. Eventually all
163
* relevant events will be taken care of and returned. */
165
if (ifs_filter & NOTE_CREATE) {
166
ifs_filter &= ~NOTE_CREATE;
167
if ((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
168
(ifs_filter & NOTE_FILE))
169
return NOTIFY_ACTION_ADDED;
170
if ((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
171
(ifs_filter & NOTE_DIRECTORY))
172
return NOTIFY_ACTION_ADDED;
174
else if (ifs_filter & NOTE_DELETE) {
175
ifs_filter &= ~NOTE_DELETE;
176
if ((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
177
(ifs_filter & NOTE_FILE))
178
return NOTIFY_ACTION_REMOVED;
179
if ((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
180
(ifs_filter & NOTE_DIRECTORY))
181
return NOTIFY_ACTION_REMOVED;
183
else if (ifs_filter & NOTE_WRITE) {
184
ifs_filter &= ~NOTE_WRITE;
185
if ((smb_filter & FILE_NOTIFY_CHANGE_LAST_WRITE) ||
186
(smb_filter & FILE_NOTIFY_CHANGE_LAST_ACCESS))
187
return NOTIFY_ACTION_MODIFIED;
189
else if ((ifs_filter & NOTE_SIZE) || (ifs_filter & NOTE_EXTEND)) {
190
ifs_filter &= ~NOTE_SIZE;
191
ifs_filter &= ~NOTE_EXTEND;
193
/* TODO: Do something on NOTE_DIR? */
194
if ((smb_filter & FILE_NOTIFY_CHANGE_SIZE) &&
195
(ifs_filter & NOTE_FILE))
196
return NOTIFY_ACTION_MODIFIED;
198
else if (ifs_filter & NOTE_ATTRIB) {
199
ifs_filter &= ~NOTE_ATTRIB;
200
/* NOTE_ATTRIB needs to be converted to a
201
* LAST_WRITE as well, because we need to send
202
* LAST_WRITE when the mtime changes. Looking into
203
* better alternatives as this causes extra LAST_WRITE
204
* notifications. We also return LAST_ACCESS as a
205
* modification to attribs implies this. */
206
if ((smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) ||
207
(smb_filter & FILE_NOTIFY_CHANGE_LAST_WRITE) ||
208
(smb_filter & FILE_NOTIFY_CHANGE_LAST_ACCESS))
209
return NOTIFY_ACTION_MODIFIED;
211
else if (ifs_filter & NOTE_LINK) {
212
ifs_filter &= ~NOTE_LINK;
213
/* NOTE_LINK will send out NO notifications */
215
else if (ifs_filter & NOTE_REVOKE) {
216
ifs_filter &= ~NOTE_REVOKE;
217
/* NOTE_REVOKE will send out NO notifications */
219
else if (ifs_filter & NOTE_RENAME_FROM) {
220
ifs_filter &= ~NOTE_RENAME_FROM;
222
if (((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
223
(ifs_filter & NOTE_FILE)) ||
224
((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
225
(ifs_filter & NOTE_DIRECTORY))) {
226
/* Check if this is a RENAME, not a MOVE */
227
if (ifs_filter & NOTE_RENAME_SAMEDIR) {
228
/* Remove the NOTE_RENAME_SAMEDIR, IFF
229
* RENAME_TO is not in this event */
230
if (!(ifs_filter & NOTE_RENAME_TO))
232
~NOTE_RENAME_SAMEDIR;
233
return NOTIFY_ACTION_OLD_NAME;
235
return NOTIFY_ACTION_REMOVED;
238
else if (ifs_filter & NOTE_RENAME_TO) {
239
ifs_filter &= ~NOTE_RENAME_TO;
241
if (((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
242
(ifs_filter & NOTE_FILE)) ||
243
((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
244
(ifs_filter & NOTE_DIRECTORY))) {
245
/* Check if this is a RENAME, not a MOVE */
246
if (ifs_filter & NOTE_RENAME_SAMEDIR) {
247
/* Remove the NOTE_RENAME_SAMEDIR, IFF
248
* RENAME_FROM is not in this event */
249
if (!(ifs_filter & NOTE_RENAME_FROM))
251
~NOTE_RENAME_SAMEDIR;
252
return NOTIFY_ACTION_NEW_NAME;
254
return NOTIFY_ACTION_ADDED;
256
/* RAW-NOTIFY shows us that a rename triggers a
257
* creation time change */
258
if ((smb_filter & FILE_NOTIFY_CHANGE_CREATION) &&
259
(ifs_filter & NOTE_FILE))
260
return NOTIFY_ACTION_MODIFIED;
262
else if (ifs_filter & NOTE_SECURITY) {
263
ifs_filter &= ~NOTE_SECURITY;
265
if (smb_filter & FILE_NOTIFY_CHANGE_SECURITY)
266
return NOTIFY_ACTION_MODIFIED;
268
/* No relevant flags found */
275
* Retrieve a directory path of a changed file, relative to the watched dir
277
* @param[in] wc watch context for the returned event
278
* @param[in] e ifs_event notification returned from the kernel
279
* @param[out] path name relative to the watched dir. This is talloced
280
* off of wc and needs to be freed by the caller.
282
* @return true on success
284
* TODO: This function currently doesn't handle path names with multiple
285
* encodings. enc_get_lin_path() should be used in the future to convert
286
* each path segment's encoding to UTF-8
289
get_ifs_event_path(struct onefs_notify_watch_context *wc, struct ifs_event *e,
292
char *path_buf = NULL;
298
/* Lookup the path from watch_dir through our parent dir */
299
if (e->namelen > 0) {
300
error = lin_get_path(wc->watch_lin,
303
e->parent_parent_lin,
305
&pathlen, &path_buf);
307
/* Only add slash if a path exists in path_buf from
308
* lin_get_path call. Windows does not expect a
311
*path = talloc_asprintf(wc, "%s/%s",
314
*path = talloc_asprintf(wc, "%s", e->name);
319
/* If ifs_event didn't return a name, or we errored out of our intial
320
* path lookup, try again using the lin of the changed file */
322
error = lin_get_path(wc->watch_lin,
327
&pathlen, &path_buf);
329
/* It's possible that both the lin and the parent lin
330
* are invalid (or not given) -- we will skip these
332
DEBUG(3,("Path lookup failed. LINS are invalid: "
333
"e->lin: 0x%llu, e->parent_lin: 0x%llu, "
334
"e->parent_parent_lin: 0x%llu\n",
335
e->lin, e->parent_lin, e->parent_parent_lin));
339
*path = talloc_asprintf(wc, "%s", path_buf);
340
DEBUG(5, ("Using path from event LIN = %s\n",
346
/* Replacement of UNIX slashes with WIN slashes is handled at a
353
* Dispatch one OneFS notify event to the general Samba code
355
* @param[in] wc watch context for the returned event
356
* @param[in] e event returned from the kernel
361
onefs_notify_dispatch(struct onefs_notify_watch_context *wc,
365
struct notify_event ne;
367
DEBUG(5, ("Retrieved ifs event from kernel: lin=%#llx, ifs_events=%#x, "
368
"parent_lin=%#llx, namelen=%d, name=\"%s\"\n",
369
e->lin, e->events, e->parent_lin, e->namelen, e->name));
371
/* Check validity of event returned from kernel */
373
/* The lin == 0 specifies 1 of 2 cases:
374
* 1) We are out of events. The kernel has a limited
375
* amount (somewhere near 90000)
376
* 2) Split nodes have merged back and had data written
377
* to them -- thus we've missed some of those events. */
378
DEBUG(3, ("We've missed some kernel ifs events!\n"));
380
/* Alert higher level to the problem so it returns catch-all
381
* response to the client */
384
wc->callback(wc->ctx, wc->private_data, &ne);
387
if (e->lin == wc->watch_lin) {
388
/* Windows doesn't report notifications on root
389
* watched directory */
390
/* TODO: This should be abstracted out to the general layer
391
* instead of being handled in every notify provider */
392
DEBUG(5, ("Skipping notification on root of the watched "
397
/* Retrieve the full path for the ifs event name */
398
if(!get_ifs_event_path(wc, e, &path)) {
399
DEBUG(3, ("Failed to convert the ifs_event lins to a path. "
400
"Skipping this event\n"));
404
if (!strncmp(path, ".ifsvar", 7)) {
405
/* Skip notifications on file if its in ifs configuration
412
/* Convert ifs event mask to an smb action mask */
413
ne.action = onefs_notify_ifs_filter_to_smb_action(wc->smb_filter,
416
DEBUG(5, ("Converted smb_filter=%#x, ifs_events=%#x, to "
417
"ne.action = %d, for ne.path = %s\n",
418
wc->smb_filter, e->events, ne.action, ne.path));
423
/* Return notify_event to higher level */
424
wc->callback(wc->ctx, wc->private_data, &ne);
426
/* SMB expects a file rename/move to generate three actions, a
427
* rename_from/delete on the from file, a rename_to/create on the to
428
* file, and a modify for the rename_to file. If we have two separate
429
* notifications registered for ATTRIBUTES and FILENAME, this case will
430
* be handled by separate ifs_events in
431
* onefs_notify_ifs_filter_to_smb_action(). If both bits are registered
432
* in the same notification, we must send an extra MODIFIED action
434
if ((wc->smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) &&
435
(wc->smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
436
(e->events & NOTE_FILE) && (e->events & NOTE_RENAME_TO))
438
ne.action = NOTIFY_ACTION_MODIFIED;
439
wc->callback(wc->ctx, wc->private_data, &ne);
449
* Callback when the kernel has some events for us
451
* Read events off ifs event fd and pass them to our dispatch function
453
* @param ev context of all tevents registered in the smbd
454
* @param fde tevent struct specific to one ifs event channel
455
* @param flags tevent flags passed when we added our ifs event channel fd to
457
* @param private_data onefs_notify_watch_context specific to this ifs event
463
onefs_notify_handler(struct event_context *ev,
464
struct fd_event *fde,
468
struct onefs_notify_watch_context *wc = NULL;
469
struct ifs_event ifs_events[ONEFS_IFS_EVENT_MAX_NUM];
474
wc = talloc_get_type(private_data, struct onefs_notify_watch_context);
476
/* Read as many ifs events from the notify channel as we can */
477
nread = sys_read(wc->ifs_event_fd, &ifs_events,
478
ONEFS_IFS_EVENT_MAX_BYTES);
480
DEBUG(0,("No data found while reading ifs event fd?!\n"));
484
DEBUG(0,("Failed to read ifs event data: %s\n",
489
count = nread / sizeof(struct ifs_event);
491
DEBUG(5, ("Got %d notification events in %d bytes.\n", count, nread));
493
/* Dispatch ifs_events one-at-a-time to higher level */
494
for (i=0; i < count; i++) {
495
onefs_notify_dispatch(wc, &ifs_events[i]);
500
* Destroy the ifs event channel
502
* This is called from talloc_free() when the generic Samba notify layer frees
503
* the onefs_notify_watch_context.
505
* @param[in] wc pointer to watch context which is being destroyed
507
* return 0 on success
510
onefs_watch_destructor(struct onefs_notify_watch_context *wc)
512
/* The ifs_event_fd will re de-registered from the event loop by
513
* another destructor triggered from the freeing of this wc */
514
close(wc->ifs_event_fd);
519
* Register a single change notify watch request.
521
* Open an event listener on a directory to watch for modifications. This
522
* channel is closed by a destructor when the caller calls talloc_free()
525
* @param[in] vfs_handle handle given to most VFS operations
526
* @param[in] ctx context (conn and tevent) for this connection
527
* @param[in] e filter and path of client's notify request
528
* @param[in] callback function to call when file notification event is received
529
* from the kernel, passing that event up to Samba's
530
* generalized change notify layer
531
* @param[in] private_data opaque data given to us by the general change notify
532
* layer which must be returned in the callback function
533
* @param[out] handle_p handle returned to generalized change notify layer used
534
* to close the event channel when notification is
537
* @return NT_STATUS_OK unless error
540
onefs_notify_watch(vfs_handle_struct *vfs_handle,
541
struct sys_notify_context *ctx,
542
struct notify_entry *e,
543
void (*callback)(struct sys_notify_context *ctx,
545
struct notify_event *ev),
549
int ifs_event_fd = -1;
550
uint32_t ifs_filter = 0;
551
uint32_t smb_filter = e->filter;
552
bool watch_tree = !!e->subdir_filter;
553
struct onefs_notify_watch_context *wc = NULL;
554
void **handle = (void **)handle_p;
555
SMB_STRUCT_STAT sbuf;
556
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
558
/* Fallback to default Samba implementation if kernel CN is disabled */
559
if (!lp_kernel_change_notify(vfs_handle->conn->params)) {
564
/* The OneFS open path should always give us a valid fd on a directory*/
565
SMB_ASSERT(e->dir_fd >= 0);
567
/* Always set e->filter to 0 so we don't fallback on the default change
568
* notify backend. It's not cluster coherent or cross-protocol so we
569
* can't guarantee correctness using it. */
571
e->subdir_filter = 0;
573
/* Snapshots do not currently allow event listeners. See Isilon
574
* bug 33476 for an example of .snapshot debug spew that can occur. */
575
if (e->dir_id.extid != HEAD_SNAPID)
576
return NT_STATUS_INVALID_PARAMETER;
578
/* Convert Completion Filter mask to IFS Event mask */
579
ifs_filter = onefs_notify_smb_filter_to_ifs_filter(smb_filter);
581
if (smb_filter & ONEFS_NOTIFY_UNSUPPORTED) {
582
/* One or more of the filter bits could not be fully handled by
583
* the ifs_event system. To be correct, if we cannot service a
584
* bit in the completion filter we should return
585
* NT_STATUS_NOT_IMPLEMENTED to let the client know that they
586
* won't be receiving all the notify events that they asked for.
587
* Unfortunately, WinXP clients cache this error message, stop
588
* trying to send any notify requests at all, and instead return
589
* NOT_IMPLEMENTED to all requesting apps without ever sending a
590
* message to us. Thus we lie, and say we can service all bits,
591
* but simply don't return actions for the filter bits we can't
592
* detect or fully implement. */
593
DEBUG(3,("One or more of the Windows completion filter bits "
594
"for \"%s\" could not be fully handled by the "
595
"ifs_event system. The failed bits are "
597
e->path, smb_filter & ONEFS_NOTIFY_UNSUPPORTED));
600
if (ifs_filter == 0) {
601
/* None of the filter bits given are supported by the ifs_event
602
* system. Don't create a kernel notify channel, but mock
603
* up a fake handle for the caller. */
604
DEBUG(3,("No bits in the Windows completion filter could be "
605
"translated to ifs_event mask for \"%s\", "
606
"smb_filter=%#x\n", e->path, smb_filter));
611
/* Register an ifs event channel for this watch request */
612
ifs_event_fd = ifs_create_listener(watch_tree ?
617
if (ifs_event_fd < 0) {
618
DEBUG(0,("Failed to create listener for %s with \"%s\". "
619
"smb_filter=0x%x, ifs_filter=0x%x, watch_tree=%u\n",
620
strerror(errno), e->path, smb_filter, ifs_filter,
622
return map_nt_error_from_unix(errno);
625
/* Create a watch context to track this change notify request */
626
wc = talloc(ctx, struct onefs_notify_watch_context);
628
status = NT_STATUS_NO_MEMORY;
632
/* Get LIN for directory */
633
if (sys_fstat(e->dir_fd, &sbuf)) {
634
DEBUG(0, ("stat on directory fd failed: %s\n",
636
status = map_nt_error_from_unix(errno);
640
if (sbuf.st_ino == 0) {
641
DEBUG(0, ("0 LIN found!\n"));
646
wc->watch_fd = e->dir_fd;
647
wc->watch_lin = sbuf.st_ino;
648
wc->ifs_event_fd = ifs_event_fd;
649
wc->ifs_filter = ifs_filter;
650
wc->smb_filter = smb_filter;
651
wc->callback = callback;
652
wc->private_data = private_data;
653
wc->path = talloc_strdup(wc, e->path);
654
if (wc->path == NULL) {
655
status = NT_STATUS_NO_MEMORY;
661
/* The caller frees the handle to stop watching */
662
talloc_set_destructor(wc, onefs_watch_destructor);
664
/* Add a tevent waiting for the ifs event fd to be readable */
665
event_add_fd(ctx->ev, wc, wc->ifs_event_fd, EVENT_FD_READ,
666
onefs_notify_handler, wc);
668
DEBUG(10, ("Watching for changes on \"%s\" smb_filter=0x%x, "
669
"ifs_filter=0x%x, watch_tree=%d, ifs_event_fd=%d, "
670
"dir_fd=%d, dir_lin=0x%llx\n",
671
e->path, smb_filter, ifs_filter, watch_tree,
672
ifs_event_fd, e->dir_fd, sbuf.st_ino));
678
SMB_ASSERT(ifs_event_fd >= 0);