Print this page
NEX-6402 SMB2 change notify response wrong when STATUS_NOTIFY_ENUM_DIR
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-6096 Enable compile warnings re. parentheses in smbsrv
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Josef 'Jeff' Sipek <josef.sipek@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Jean McCormack <jean.mccormack@nexenta.com>
NEX-3906 Prefer that SMB change notify not tie up a worker thread
NEX-5278 SMB notify should buffer per file handle
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-4391 improve smb cancel support
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-2602 MacOS X Yosemite fails to authenticate
NEX-2107 SMB2 Notify Change fails to return for directory time change
SMB-11 SMB2 message parse & dispatch
SMB-12 SMB2 Negotiate Protocol
SMB-13 SMB2 Session Setup
SMB-14 SMB2 Logoff
SMB-15 SMB2 Tree Connect
SMB-16 SMB2 Tree Disconnect
SMB-17 SMB2 Create
SMB-18 SMB2 Close
SMB-19 SMB2 Flush
SMB-20 SMB2 Read
SMB-21 SMB2 Write
SMB-22 SMB2 Lock/Unlock
SMB-23 SMB2 Ioctl
SMB-24 SMB2 Cancel
SMB-25 SMB2 Echo
SMB-26 SMB2 Query Dir
SMB-27 SMB2 Change Notify
SMB-28 SMB2 Query Info
SMB-29 SMB2 Set Info
SMB-30 SMB2 Oplocks
SMB-53 SMB2 Create Context options
(SMB2 code review cleanup 1, 2, 3)
| Split |
Close |
| Expand all |
| Collapse all |
--- old/usr/src/uts/common/fs/smbsrv/smb_notify.c
+++ new/usr/src/uts/common/fs/smbsrv/smb_notify.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
|
↓ open down ↓ |
13 lines elided |
↑ open up ↑ |
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21
22 22 /*
23 23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24 - * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
24 + * Copyright 2016 Nexenta Systems, Inc. All rights reserved.
25 25 */
26 26
27 27 /*
28 28 * File Change Notification (FCN)
29 29 * Common parts shared by SMB1 & SMB2
30 30 */
31 31
32 32 /*
33 33 * This command notifies the client when the specified directory
34 34 * has changed, and optionally returns the names of files and
35 35 * directories that changed, and how they changed. The caller
36 36 * specifies a "Completion Filter" to select which kinds of
37 37 * changes they want to know about.
38 38 *
39 39 * When a change that's in the CompletionFilter is made to the directory,
40 40 * the command completes. The names of the files that have changed since
41 41 * the last time the command was issued are returned to the client.
42 42 * If too many files have changed since the last time the command was
43 43 * issued, then zero bytes are returned and an alternate status code
44 44 * is returned in the Status field of the response.
45 45 *
46 46 * The CompletionFilter is a mask created as the sum of any of the
47 47 * following flags:
48 48 *
49 49 * FILE_NOTIFY_CHANGE_FILE_NAME 0x00000001
50 50 * FILE_NOTIFY_CHANGE_DIR_NAME 0x00000002
51 51 * FILE_NOTIFY_CHANGE_NAME 0x00000003
52 52 * FILE_NOTIFY_CHANGE_ATTRIBUTES 0x00000004
53 53 * FILE_NOTIFY_CHANGE_SIZE 0x00000008
54 54 * FILE_NOTIFY_CHANGE_LAST_WRITE 0x00000010
55 55 * FILE_NOTIFY_CHANGE_LAST_ACCESS 0x00000020
56 56 * FILE_NOTIFY_CHANGE_CREATION 0x00000040
57 57 * FILE_NOTIFY_CHANGE_EA 0x00000080
58 58 * FILE_NOTIFY_CHANGE_SECURITY 0x00000100
59 59 * FILE_NOTIFY_CHANGE_STREAM_NAME 0x00000200
60 60 * FILE_NOTIFY_CHANGE_STREAM_SIZE 0x00000400
61 61 * FILE_NOTIFY_CHANGE_STREAM_WRITE 0x00000800
62 62 *
63 63 *
64 64 * The response contains FILE_NOTIFY_INFORMATION structures, as defined
65 65 * below. The NextEntryOffset field of the structure specifies the offset,
66 66 * in bytes, from the start of the current entry to the next entry in the
67 67 * list. If this is the last entry in the list, this field is zero. Each
68 68 * entry in the list must be longword aligned, so NextEntryOffset must be a
69 69 * multiple of four.
70 70 *
71 71 * typedef struct {
72 72 * ULONG NextEntryOffset;
73 73 * ULONG Action;
74 74 * ULONG FileNameLength;
75 75 * WCHAR FileName[1];
76 76 * } FILE_NOTIFY_INFORMATION;
77 77 *
|
↓ open down ↓ |
43 lines elided |
↑ open up ↑ |
78 78 * Where Action describes what happened to the file named FileName:
79 79 *
80 80 * FILE_ACTION_ADDED 0x00000001
81 81 * FILE_ACTION_REMOVED 0x00000002
82 82 * FILE_ACTION_MODIFIED 0x00000003
83 83 * FILE_ACTION_RENAMED_OLD_NAME 0x00000004
84 84 * FILE_ACTION_RENAMED_NEW_NAME 0x00000005
85 85 * FILE_ACTION_ADDED_STREAM 0x00000006
86 86 * FILE_ACTION_REMOVED_STREAM 0x00000007
87 87 * FILE_ACTION_MODIFIED_STREAM 0x00000008
88 + *
89 + * The internal interface between SMB1 and/or SMB2 protocol handlers
90 + * and this module has some sophistication to allow for:
91 + * (1) code sharing between SMB1 and SMB2(+)
92 + * (2) efficient handling of non-blocking scenarios
93 + * (3) long blocking calls without tying up a thread
94 + *
95 + * The interface has three calls (like a three act play)
96 + *
97 + * smb_notify_act1:
98 + * Validate parameters, setup ofile buffer.
99 + * If data already available, return it, all done.
100 + * (In the "all done" case, skip act2 & act3.)
101 + * If no data available, return a special error
102 + * ("STATUS_PENDING") to tell the caller they must
103 + * proceed with calls to act2 & act3.
104 + *
105 + * smb_notify_act2:
106 + * Arrange wakeup after event delivery or cancellation.
107 + * Return leaving the SR with no worker thread.
108 + *
109 + * smb_notify_act3:
110 + * New taskq work thread runs this after the wakeup
111 + * or cancellation arranged in act2 happens. This
112 + * returns the notification data and retires the SR.
113 + *
114 + * In the SMB2 notify handler, we call act1 during the initial
115 + * synchronous handling of the request. If that returns anything
116 + * other than STATUS_PENDING, that request is fully complete.
117 + * If act1 returns STATUS_PENDING, SMB2 calls act2 as it's
118 + * "go async" handler, which arranges to call act3 later.
119 + *
120 + * In the SMB1 notify handler there is not separate sync. & async
121 + * handler so act1 and (if necessary) act2 are both called during
122 + * the initial handling of the request.
123 + *
124 + * About notify event buffering:
125 + *
126 + * An important (and poorly documented) feature of SMB notify is
127 + * that once a notify call has happened on a given directory handle,
128 + * the system CONTINUES to post events to the notify event buffer
129 + * for the handle, even when SMB notify calls are NOT running.
130 + * When the client next comes back with a notify call, we return
131 + * any events that were posted while they were "away". This is
132 + * how clients track directory changes without missing events.
133 + *
134 + * About simultaneous notify calls:
135 + *
136 + * Note that SMB "notify" calls are destructive to events, much like
137 + * reading data from a pipe. It therefore makes little sense to
138 + * allow multiple simultaneous callers. However, we permit it
139 + * (like Windows does) as follows: When multiple notify calls
140 + * are waiting for events, the next event wakes them all, and
141 + * only the last one out clears the event buffer. They all get
142 + * whatever events are pending at the time they woke up.
143 + *
144 + * About NT_STATUS_NOTIFY_ENUM_DIR
145 + *
146 + * One more caution about NT_STATUS_NOTIFY_ENUM_DIR: Some clients
147 + * are stupid about re-reading the directory almost continuously when
148 + * there are changes happening in the directory. We want to bound
149 + * the rate of such directory re-reading, so before returning an
150 + * NT_STATUS_NOTIFY_ENUM_DIR, we delay just a little. The length
151 + * of the delay can be adjusted via smb_notify_enum_dir_delay,
152 + * though it's not expected that should need to be changed.
88 153 */
89 154
90 155 #include <smbsrv/smb_kproto.h>
91 156 #include <sys/sdt.h>
92 157
93 -static void smb_notify_sr(smb_request_t *, uint_t, const char *);
94 -static uint32_t smb_notify_encode_action(struct smb_request *,
95 - mbuf_chain_t *, uint32_t, char *);
158 +/*
159 + * Length of the short delay we impose before returning
160 + * NT_STATUS_NOTIFY_ENUM_DIR (See above)
161 + */
162 +int smb_notify_enum_dir_delay = 100; /* mSec. */
96 163
164 +static uint32_t smb_notify_get_events(smb_request_t *);
165 +static void smb_notify_cancel(smb_request_t *);
166 +static void smb_notify_wakeup(smb_request_t *);
167 +static void smb_notify_dispatch2(smb_request_t *);
168 +static void smb_notify_encode_action(smb_ofile_t *,
169 + uint32_t, const char *);
170 +
171 +
172 +/*
173 + * smb_notify_act1()
174 + *
175 + * Check for events and consume, non-blocking.
176 + * Special return STATUS_PENDING means:
177 + * No events; caller must call "act2" next.
178 + *
179 + * See overall design notes, top of file.
180 + */
97 181 uint32_t
98 -smb_notify_common(smb_request_t *sr, mbuf_chain_t *mbc,
99 - uint32_t CompletionFilter)
182 +smb_notify_act1(smb_request_t *sr, uint32_t buflen, uint32_t filter)
100 183 {
101 - smb_notify_change_req_t *nc;
184 + smb_ofile_t *of;
102 185 smb_node_t *node;
186 + smb_notify_t *nc;
103 187 uint32_t status;
104 188
105 - if (sr->fid_ofile == NULL)
189 + /*
190 + * Validate parameters
191 + */
192 + if ((of = sr->fid_ofile) == NULL)
106 193 return (NT_STATUS_INVALID_HANDLE);
107 -
108 - node = sr->fid_ofile->f_node;
194 + nc = &of->f_notify;
195 + node = of->f_node;
109 196 if (node == NULL || !smb_node_is_dir(node)) {
110 - /*
111 - * Notify change is only valid on directories.
112 - */
197 + /* Notify change is only valid on directories. */
113 198 return (NT_STATUS_INVALID_PARAMETER);
114 199 }
115 200
201 + mutex_enter(&of->f_mutex);
202 +
116 203 /*
117 - * Prepare to receive event data.
204 + * On the first FCN call with this ofile, subscribe to
205 + * events on the node. The corresponding unsubscribe
206 + * happens in smb_ofile_delete().
118 207 */
119 - nc = &sr->sr_ncr;
120 - nc->nc_flags = CompletionFilter;
121 - ASSERT(nc->nc_action == 0);
122 - ASSERT(nc->nc_fname == NULL);
123 - nc->nc_fname = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
124 -
208 + if (nc->nc_subscribed == B_FALSE) {
209 + nc->nc_subscribed = B_TRUE;
210 + smb_node_fcn_subscribe(node);
211 + /* In case this happened before we subscribed. */
212 + if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
213 + nc->nc_events |= FILE_NOTIFY_CHANGE_EV_DELETE;
214 + }
215 + /*
216 + * Windows only lets you set these on the first call,
217 + * so we may as well do the same.
218 + */
219 + nc->nc_buffer.max_bytes = buflen;
220 + nc->nc_filter = filter;
221 + }
125 222 /*
126 - * Subscribe to events on this node.
223 + * If we already have events, consume them.
127 224 */
128 - smb_node_fcn_subscribe(node, sr);
225 + sr->raw_data.max_bytes = buflen;
226 + if (nc->nc_events != 0) {
227 + status = smb_notify_get_events(sr);
228 + } else {
229 + /* Caller will come back for act2 */
230 + status = NT_STATUS_PENDING;
231 + }
129 232
233 + mutex_exit(&of->f_mutex);
234 +
130 235 /*
131 - * Wait for subscribed events to arrive.
132 - * Expect SMB_REQ_STATE_EVENT_OCCURRED
133 - * or SMB_REQ_STATE_CANCELED when signaled.
134 - * Note it's possible (though rare) to already
135 - * have SMB_REQ_STATE_CANCELED here.
236 + * See: About NT_STATUS_NOTIFY_ENUM_DIR (above)
136 237 */
137 - mutex_enter(&sr->sr_mutex);
138 - if (sr->sr_state == SMB_REQ_STATE_ACTIVE)
139 - sr->sr_state = SMB_REQ_STATE_WAITING_EVENT;
140 - while (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT) {
141 - cv_wait(&nc->nc_cv, &sr->sr_mutex);
142 - }
143 - if (sr->sr_state == SMB_REQ_STATE_EVENT_OCCURRED)
144 - sr->sr_state = SMB_REQ_STATE_ACTIVE;
145 - mutex_exit(&sr->sr_mutex);
238 + if (status == NT_STATUS_NOTIFY_ENUM_DIR &&
239 + smb_notify_enum_dir_delay > 0)
240 + delay(MSEC_TO_TICK(smb_notify_enum_dir_delay));
146 241
242 + return (status);
243 +}
244 +
245 +/*
246 + * smb_notify_act2()
247 + *
248 + * Prepare to wait for events after act1 found that none were pending.
249 + * Assume the wait may be for a very long time. (hours, days...)
250 + * Special return STATUS_PENDING means the SR will later be
251 + * scheduled again on a new worker thread, and this thread
252 + * MUST NOT touch it any longer (return SDRC_SR_KEPT).
253 + *
254 + * See overall design notes, top of file.
255 + */
256 +uint32_t
257 +smb_notify_act2(smb_request_t *sr)
258 +{
259 + smb_ofile_t *of;
260 + smb_notify_t *nc;
261 + uint32_t status;
262 +
147 263 /*
148 - * Unsubscribe from events on this node.
264 + * Sanity checks.
149 265 */
150 - smb_node_fcn_unsubscribe(node, sr);
266 + if ((of = sr->fid_ofile) == NULL)
267 + return (NT_STATUS_INVALID_HANDLE);
268 + nc = &of->f_notify;
151 269
270 + mutex_enter(&of->f_mutex);
271 +
152 272 /*
153 - * Why did we wake up?
273 + * Prepare for a potentially long wait for events.
274 + * Normally transition from ACTIVE to WAITING_FCN1.
275 + *
276 + * Note we hold both of->f_mutex, sr->sr_mutex here,
277 + * taken in that order.
154 278 */
279 + mutex_enter(&sr->sr_mutex);
155 280 switch (sr->sr_state) {
156 281 case SMB_REQ_STATE_ACTIVE:
282 + /*
283 + * This sr has no worker thread until smb_notify_act3
284 + * or smb_notify_cancel (later, via taskq_dispatch).
285 + */
286 + sr->sr_state = SMB_REQ_STATE_WAITING_FCN1;
287 + sr->cancel_method = smb_notify_cancel;
288 + sr->sr_worker = NULL;
289 + list_insert_tail(&nc->nc_waiters, sr);
290 + status = NT_STATUS_PENDING;
157 291 break;
158 - case SMB_REQ_STATE_CANCELED:
292 +
293 + case SMB_REQ_STATE_CANCELLED:
159 294 status = NT_STATUS_CANCELLED;
160 - goto out;
295 + break;
161 296 default:
162 297 status = NT_STATUS_INTERNAL_ERROR;
163 - goto out;
298 + break;
164 299 }
300 + mutex_exit(&sr->sr_mutex);
165 301
166 302 /*
167 - * We have SMB_REQ_STATE_ACTIVE.
168 - *
169 - * If we have event data, marshall it now, else just
170 - * say "many things changed". Note that when we get
171 - * action FILE_ACTION_SUBDIR_CHANGED, we don't have
172 - * any event details and only know that some subdir
173 - * changed, so just report "many things changed".
303 + * In case we missed any events before setting
304 + * state FCN1, schedule our own wakeup.
174 305 */
175 - switch (nc->nc_action) {
306 + if (status == NT_STATUS_PENDING && nc->nc_events != 0) {
307 + smb_notify_wakeup(sr);
308 + }
176 309
177 - case FILE_ACTION_ADDED:
178 - case FILE_ACTION_REMOVED:
179 - case FILE_ACTION_MODIFIED:
180 - case FILE_ACTION_RENAMED_OLD_NAME:
181 - case FILE_ACTION_RENAMED_NEW_NAME:
182 - case FILE_ACTION_ADDED_STREAM:
183 - case FILE_ACTION_REMOVED_STREAM:
184 - case FILE_ACTION_MODIFIED_STREAM:
310 + mutex_exit(&of->f_mutex);
311 +
312 + /* Note: Never NT_STATUS_NOTIFY_ENUM_DIR here. */
313 + ASSERT(status != NT_STATUS_NOTIFY_ENUM_DIR);
314 +
315 + return (status);
316 +}
317 +
318 +/*
319 + * smb_notify_act3()
320 + *
321 + * This runs via the 2nd taskq_dispatch call, after we've either
322 + * seen a change notify event, or the request has been cancelled.
323 + * Complete it here. This returns to SMB1 or SMB2 code to send
324 + * the response and free the request.
325 + *
326 + * See overall design notes, top of file.
327 + */
328 +uint32_t
329 +smb_notify_act3(smb_request_t *sr)
330 +{
331 + smb_ofile_t *of;
332 + smb_notify_t *nc;
333 + uint32_t status;
334 +
335 + of = sr->fid_ofile;
336 + ASSERT(of != NULL);
337 + nc = &of->f_notify;
338 +
339 + mutex_enter(&of->f_mutex);
340 +
341 + mutex_enter(&sr->sr_mutex);
342 + ASSERT3P(sr->sr_worker, ==, NULL);
343 + sr->sr_worker = curthread;
344 + sr->cancel_method = NULL;
345 +
346 + list_remove(&nc->nc_waiters, sr);
347 +
348 + switch (sr->sr_state) {
349 + case SMB_REQ_STATE_WAITING_FCN2:
185 350 /*
186 - * Build the reply
351 + * Got smb_notify_wakeup.
187 352 */
188 - status = smb_notify_encode_action(sr, mbc,
189 - nc->nc_action, nc->nc_fname);
353 + sr->sr_state = SMB_REQ_STATE_ACTIVE;
354 + status = 0;
190 355 break;
191 356
192 - case FILE_ACTION_SUBDIR_CHANGED:
193 - status = NT_STATUS_NOTIFY_ENUM_DIR;
357 + case SMB_REQ_STATE_CANCEL_PENDING:
358 + /*
359 + * Got smb_notify_cancel
360 + */
361 + sr->sr_state = SMB_REQ_STATE_CANCELLED;
362 + status = NT_STATUS_CANCELLED;
194 363 break;
195 -
196 - case FILE_ACTION_DELETE_PENDING:
197 - status = NT_STATUS_DELETE_PENDING;
198 - break;
199 -
200 364 default:
201 - ASSERT(0);
202 365 status = NT_STATUS_INTERNAL_ERROR;
203 366 break;
204 367 }
368 + mutex_exit(&sr->sr_mutex);
205 369
206 -out:
207 - kmem_free(nc->nc_fname, MAXNAMELEN);
208 - nc->nc_fname = NULL;
370 + if (status == 0)
371 + status = smb_notify_get_events(sr);
372 +
373 + mutex_exit(&of->f_mutex);
374 +
375 + /*
376 + * See: About NT_STATUS_NOTIFY_ENUM_DIR (above)
377 + */
378 + if (status == NT_STATUS_NOTIFY_ENUM_DIR &&
379 + smb_notify_enum_dir_delay > 0)
380 + delay(MSEC_TO_TICK(smb_notify_enum_dir_delay));
381 +
209 382 return (status);
210 383 }
211 384
212 -/*
213 - * Encode a FILE_NOTIFY_INFORMATION struct.
214 - *
215 - * We only ever put one of these in a response, so this
216 - * does not bother handling appending additional ones.
217 - */
218 385 static uint32_t
219 -smb_notify_encode_action(struct smb_request *sr, mbuf_chain_t *mbc,
220 - uint32_t action, char *fname)
386 +smb_notify_get_events(smb_request_t *sr)
221 387 {
222 - uint32_t namelen;
388 + smb_ofile_t *of;
389 + smb_notify_t *nc;
390 + uint32_t status;
391 + int len;
223 392
224 - ASSERT(FILE_ACTION_ADDED <= action &&
225 - action <= FILE_ACTION_MODIFIED_STREAM);
393 + of = sr->fid_ofile;
394 + ASSERT(of != NULL);
395 + ASSERT(MUTEX_HELD(&of->f_mutex));
396 + nc = &of->f_notify;
226 397
227 - if (fname == NULL)
228 - return (NT_STATUS_INTERNAL_ERROR);
229 - namelen = smb_wcequiv_strlen(fname);
230 - if (namelen == 0)
231 - return (NT_STATUS_INTERNAL_ERROR);
398 + DTRACE_PROBE2(notify__get__events,
399 + smb_request_t, sr,
400 + uint32_t, nc->nc_events);
232 401
233 - if (smb_mbc_encodef(mbc, "%lllU", sr,
234 - 0, /* NextEntryOffset */
235 - action, namelen, fname))
236 - return (NT_STATUS_NOTIFY_ENUM_DIR);
402 + /*
403 + * Special events which override other events
404 + */
405 + if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_CLOSED) {
406 + status = NT_STATUS_NOTIFY_CLEANUP;
407 + goto out;
408 + }
409 + if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_DELETE) {
410 + status = NT_STATUS_DELETE_PENDING;
411 + goto out;
412 + }
413 + if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_SUBDIR) {
414 + status = NT_STATUS_NOTIFY_ENUM_DIR;
415 + goto out;
416 + }
417 + if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_OVERFLOW) {
418 + status = NT_STATUS_NOTIFY_ENUM_DIR;
419 + goto out;
420 + }
237 421
238 - return (0);
239 -}
422 + /*
423 + * Normal events (FILE_NOTIFY_VALID_MASK)
424 + *
425 + * At this point there should be some, or else
426 + * some sort of bug woke us up for nothing.
427 + */
428 + if ((nc->nc_events & FILE_NOTIFY_VALID_MASK) == 0) {
429 + status = NT_STATUS_INTERNAL_ERROR;
430 + goto out;
431 + }
240 432
241 -/*
242 - * smb_notify_file_closed
243 - *
244 - * Cancel any change-notify calls on this open file.
245 - */
246 -void
247 -smb_notify_file_closed(struct smb_ofile *of)
248 -{
249 - smb_session_t *ses;
250 - smb_request_t *sr;
251 - smb_slist_t *list;
433 + /*
434 + * Many Windows clients call change notify with a
435 + * zero-length buffer, expecting all events to be
436 + * reported as _ENUM_DIR. Testing max_bytes here
437 + * because ROOM_FOR check below says "yes" if both
438 + * max_bytes and the amount we ask for are zero.
439 + */
440 + if (nc->nc_buffer.max_bytes <= 0) {
441 + status = NT_STATUS_NOTIFY_ENUM_DIR;
442 + goto out;
443 + }
252 444
253 - SMB_OFILE_VALID(of);
254 - ses = of->f_session;
255 - SMB_SESSION_VALID(ses);
256 - list = &ses->s_req_list;
445 + /*
446 + * Client gave us a non-zero output buffer, and
447 + * there was no overflow event (checked above)
448 + * so there should be some event data.
449 + */
450 + if ((len = nc->nc_buffer.chain_offset) <= 0) {
451 + status = NT_STATUS_INTERNAL_ERROR;
452 + goto out;
453 + }
257 454
258 - smb_slist_enter(list);
455 + /*
456 + * If the current SR has a smaller output buffer
457 + * then what was setup by some previous notify,
458 + * we could have more data than will fit.
459 + */
460 + if (!MBC_ROOM_FOR(&sr->raw_data, len)) {
461 + /* Would overflow caller's buffer. */
462 + status = NT_STATUS_NOTIFY_ENUM_DIR;
463 + goto out;
464 + }
259 465
260 - sr = smb_slist_head(list);
261 - while (sr) {
262 - SMB_REQ_VALID(sr);
263 - if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
264 - sr->fid_ofile == of) {
265 - smb_request_cancel(sr);
266 - }
267 - sr = smb_slist_next(list, sr);
466 + /*
467 + * Copy the event data to sr->raw_data. In the copy,
468 + * zap the NextEntryOffset in the last entry, and
469 + * trim any extra bytes at the tail.
470 + */
471 + (void) smb_mbc_copy(&sr->raw_data, &nc->nc_buffer, 0, len);
472 + (void) smb_mbc_poke(&sr->raw_data, nc->nc_last_off, "l", 0);
473 + smb_mbuf_trim(sr->raw_data.chain, len);
474 + status = 0;
475 +
476 +out:
477 + /*
478 + * If there are no other SRs waiting on this ofile,
479 + * mark all events consumed, except for those that
480 + * remain until the ofile is closed. That means
481 + * clear all bits EXCEPT: _EV_CLOSED, _EV_DELETE
482 + *
483 + * If there are other waiters (rare) all will get
484 + * the currently pending events, and then the
485 + * the last one out will clear the events.
486 + */
487 + if (list_is_empty(&nc->nc_waiters)) {
488 + nc->nc_buffer.chain_offset = 0;
489 + nc->nc_events &= (FILE_NOTIFY_CHANGE_EV_CLOSED |
490 + FILE_NOTIFY_CHANGE_EV_DELETE);
268 491 }
269 492
270 - smb_slist_exit(list);
493 + return (status);
271 494 }
272 495
496 +/*
497 + * Called by common code after a transition from
498 + * state WAITING_FCN1 to state CANCEL_PENDING.
499 + */
500 +static void
501 +smb_notify_cancel(smb_request_t *sr)
502 +{
503 + ASSERT3U(sr->sr_state, ==, SMB_REQ_STATE_CANCEL_PENDING);
504 + smb_notify_dispatch2(sr);
505 +}
273 506
274 507 /*
275 - * smb_notify_event
276 - *
277 - * Post an event to the watchers on a given node.
278 - *
279 - * This makes one exception for RENAME, where we expect a
280 - * pair of events for the {old,new} directory element names.
281 - * This only delivers an event for the "new" name.
282 - *
283 - * The event delivery mechanism does not implement delivery of
284 - * multiple events for one "NT Notify" call. One could do that,
285 - * but modern clients don't actually use the event data. They
286 - * set a max. received data size of zero, which means we discard
287 - * the data and send the special "lots changed" error instead.
288 - * Given that, there's not really any point in implementing the
289 - * delivery of multiple events. In fact, we don't even need to
290 - * implement single event delivery, but do so for completeness,
291 - * for debug convenience, and to be nice to older clients that
292 - * may actually want some event data instead of the error.
293 - *
294 - * Given that we only deliver a single event for an "NT Notify"
295 - * caller, we want to deliver the "new" name event. (The "old"
296 - * name event is less important, even ignored by some clients.)
297 - * Since we know these are delivered in pairs, we can simply
298 - * discard the "old" name event, knowing that the "new" name
299 - * event will be delivered immediately afterwards.
300 - *
301 - * So, why do event sources post the "old name" event at all?
302 - * (1) For debugging, so we see both {old,new} names here.
303 - * (2) If in the future someone decides to implement the
304 - * delivery of both {old,new} events, the changes can be
305 - * mostly isolated to this file.
508 + * Called after ofile event delivery to take a waiting smb request
509 + * from state FCN1 to state FCN2. This may be called many times
510 + * (as events are delivered) but it must (exactly once) schedule
511 + * the taskq job to run smb_notify_act3(). Only the event that
512 + * takes us from state FCN1 to FCN2 schedules the taskq job.
306 513 */
307 -void
308 -smb_notify_event(smb_node_t *node, uint_t action, const char *name)
514 +static void
515 +smb_notify_wakeup(smb_request_t *sr)
309 516 {
310 - smb_request_t *sr;
311 - smb_node_fcn_t *fcn;
517 + boolean_t do_disp = B_FALSE;
312 518
313 - SMB_NODE_VALID(node);
314 - fcn = &node->n_fcn;
519 + SMB_REQ_VALID(sr);
315 520
316 - if (action == FILE_ACTION_RENAMED_OLD_NAME)
317 - return; /* see above */
521 + mutex_enter(&sr->sr_mutex);
522 + if (sr->sr_state == SMB_REQ_STATE_WAITING_FCN1) {
523 + sr->sr_state = SMB_REQ_STATE_WAITING_FCN2;
524 + do_disp = B_TRUE;
525 + }
526 + mutex_exit(&sr->sr_mutex);
318 527
319 - mutex_enter(&fcn->fcn_mutex);
320 -
321 - sr = list_head(&fcn->fcn_watchers);
322 - while (sr) {
323 - smb_notify_sr(sr, action, name);
324 - sr = list_next(&fcn->fcn_watchers, sr);
528 + if (do_disp) {
529 + smb_notify_dispatch2(sr);
325 530 }
531 +}
326 532
327 - mutex_exit(&fcn->fcn_mutex);
533 +/*
534 + * smb_notify_dispatch2()
535 + * Schedule a 2nd taskq call to finish up a change notify request;
536 + * (smb_notify_act3) either completing it or cancelling it.
537 + */
538 +static void
539 +smb_notify_dispatch2(smb_request_t *sr)
540 +{
541 + void (*tq_func)(void *);
542 +
543 + /*
544 + * Both of these call smb_notify_act3(), returning
545 + * to version-specific code to send the response.
546 + */
547 + if (sr->session->dialect >= SMB_VERS_2_BASE)
548 + tq_func = smb2_change_notify_finish;
549 + else
550 + tq_func = smb_nt_transact_notify_finish;
551 +
552 + (void) taskq_dispatch(sr->sr_server->sv_worker_pool,
553 + tq_func, sr, TQ_SLEEP);
328 554 }
329 555
556 +
330 557 /*
331 558 * What completion filter (masks) apply to each of the
332 559 * FILE_ACTION_... events.
333 560 */
334 561 static const uint32_t
335 562 smb_notify_action_mask[] = {
336 563 0, /* not used */
337 564
338 565 /* FILE_ACTION_ADDED */
339 566 FILE_NOTIFY_CHANGE_NAME |
340 567 FILE_NOTIFY_CHANGE_LAST_WRITE,
341 568
342 569 /* FILE_ACTION_REMOVED */
343 570 FILE_NOTIFY_CHANGE_NAME |
344 571 FILE_NOTIFY_CHANGE_LAST_WRITE,
345 572
346 573 /* FILE_ACTION_MODIFIED */
347 574 FILE_NOTIFY_CHANGE_ATTRIBUTES |
348 575 FILE_NOTIFY_CHANGE_SIZE |
349 576 FILE_NOTIFY_CHANGE_LAST_WRITE |
350 577 FILE_NOTIFY_CHANGE_LAST_ACCESS |
351 578 FILE_NOTIFY_CHANGE_CREATION |
352 579 FILE_NOTIFY_CHANGE_EA |
353 580 FILE_NOTIFY_CHANGE_SECURITY,
354 581
355 582 /* FILE_ACTION_RENAMED_OLD_NAME */
356 583 FILE_NOTIFY_CHANGE_NAME |
357 584 FILE_NOTIFY_CHANGE_LAST_WRITE,
358 585
359 586 /* FILE_ACTION_RENAMED_NEW_NAME */
360 587 FILE_NOTIFY_CHANGE_NAME |
361 588 FILE_NOTIFY_CHANGE_LAST_WRITE,
362 589
363 590 /* FILE_ACTION_ADDED_STREAM */
|
↓ open down ↓ |
24 lines elided |
↑ open up ↑ |
364 591 FILE_NOTIFY_CHANGE_STREAM_NAME,
365 592
366 593 /* FILE_ACTION_REMOVED_STREAM */
367 594 FILE_NOTIFY_CHANGE_STREAM_NAME,
368 595
369 596 /* FILE_ACTION_MODIFIED_STREAM */
370 597 FILE_NOTIFY_CHANGE_STREAM_SIZE |
371 598 FILE_NOTIFY_CHANGE_STREAM_WRITE,
372 599
373 600 /* FILE_ACTION_SUBDIR_CHANGED */
374 - NODE_FLAGS_WATCH_TREE,
601 + FILE_NOTIFY_CHANGE_EV_SUBDIR,
375 602
376 603 /* FILE_ACTION_DELETE_PENDING */
377 - NODE_FLAGS_WATCH_TREE |
378 - FILE_NOTIFY_VALID_MASK,
604 + FILE_NOTIFY_CHANGE_EV_DELETE,
605 +
606 + /* FILE_ACTION_HANDLE_CLOSED */
607 + FILE_NOTIFY_CHANGE_EV_CLOSED,
379 608 };
380 609 static const int smb_notify_action_nelm =
381 610 sizeof (smb_notify_action_mask) /
382 611 sizeof (smb_notify_action_mask[0]);
383 612
384 613 /*
385 - * smb_notify_sr
614 + * smb_notify_ofile
386 615 *
387 - * Post an event to an smb request waiting on some node.
388 - *
389 - * Note that node->fcn.mutex is held. This implies a
390 - * lock order: node->fcn.mutex, then sr_mutex
616 + * Post an event to the change notify buffer for this ofile,
617 + * subject to the mask that selects subscribed event types.
618 + * If an SR is waiting for events and we've delivered some,
619 + * wake the SR.
391 620 */
392 -static void
393 -smb_notify_sr(smb_request_t *sr, uint_t action, const char *name)
621 +void
622 +smb_notify_ofile(smb_ofile_t *of, uint_t action, const char *name)
394 623 {
395 - smb_notify_change_req_t *ncr;
396 - uint32_t mask;
624 + smb_notify_t *nc;
625 + smb_request_t *sr;
626 + uint32_t filter, events;
397 627
398 - SMB_REQ_VALID(sr);
399 - ncr = &sr->sr_ncr;
628 + SMB_OFILE_VALID(of);
400 629
630 + mutex_enter(&of->f_mutex);
631 + nc = &of->f_notify;
632 +
401 633 /*
402 - * Compute the completion filter mask bits for which
403 - * we will signal waiting notify requests.
634 + * Compute the filter & event bits for this action,
635 + * which determine whether we'll post the event.
636 + * Note: always sensitive to: delete, closed.
404 637 */
638 + filter = nc->nc_filter |
639 + FILE_NOTIFY_CHANGE_EV_DELETE |
640 + FILE_NOTIFY_CHANGE_EV_CLOSED;
405 641 VERIFY(action < smb_notify_action_nelm);
406 - mask = smb_notify_action_mask[action];
642 + events = smb_notify_action_mask[action];
643 + if ((filter & events) == 0)
644 + goto unlock_out;
407 645
408 - mutex_enter(&sr->sr_mutex);
409 - if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
410 - (ncr->nc_flags & mask) != 0) {
411 - sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED;
646 + /*
647 + * OK, we're going to post this event.
648 + */
649 + switch (action) {
650 + case FILE_ACTION_ADDED:
651 + case FILE_ACTION_REMOVED:
652 + case FILE_ACTION_MODIFIED:
653 + case FILE_ACTION_RENAMED_OLD_NAME:
654 + case FILE_ACTION_RENAMED_NEW_NAME:
655 + case FILE_ACTION_ADDED_STREAM:
656 + case FILE_ACTION_REMOVED_STREAM:
657 + case FILE_ACTION_MODIFIED_STREAM:
412 658 /*
413 - * Save event data in the sr_ncr field so the
414 - * reply handler can return it.
659 + * Append this event to the buffer.
660 + * Also keep track of events seen.
415 661 */
416 - ncr->nc_action = action;
417 - if (name != NULL)
418 - (void) strlcpy(ncr->nc_fname, name, MAXNAMELEN);
419 - cv_signal(&ncr->nc_cv);
662 + smb_notify_encode_action(of, action, name);
663 + nc->nc_events |= events;
664 + break;
665 +
666 + case FILE_ACTION_SUBDIR_CHANGED:
667 + case FILE_ACTION_DELETE_PENDING:
668 + case FILE_ACTION_HANDLE_CLOSED:
669 + /*
670 + * These are "internal" events, and therefore
671 + * are not appended to the response buffer.
672 + * Just record the event flags and wakeup.
673 + */
674 + nc->nc_events |= events;
675 + break;
676 +
677 + default:
678 + ASSERT(0); /* bogus action */
679 + break;
420 680 }
421 - mutex_exit(&sr->sr_mutex);
681 +
682 + sr = list_head(&nc->nc_waiters);
683 + while (sr != NULL) {
684 + smb_notify_wakeup(sr);
685 + sr = list_next(&nc->nc_waiters, sr);
686 + }
687 +
688 +unlock_out:
689 + mutex_exit(&of->f_mutex);
690 +}
691 +
692 +/*
693 + * Encode a FILE_NOTIFY_INFORMATION struct.
694 + */
695 +static void
696 +smb_notify_encode_action(smb_ofile_t *of,
697 + uint32_t action, const char *fname)
698 +{
699 + smb_notify_t *nc = &of->f_notify;
700 + mbuf_chain_t *mbc;
701 + uint32_t namelen, totlen;
702 +
703 + ASSERT(nc != NULL);
704 + ASSERT(FILE_ACTION_ADDED <= action &&
705 + action <= FILE_ACTION_MODIFIED_STREAM);
706 + ASSERT(fname != NULL);
707 + ASSERT(MUTEX_HELD(&of->f_mutex));
708 +
709 + /* Once we've run out of room, stop trying to append. */
710 + if ((nc->nc_events & FILE_NOTIFY_CHANGE_EV_OVERFLOW) != 0)
711 + return;
712 +
713 + if (fname == NULL)
714 + return;
715 + namelen = smb_wcequiv_strlen(fname);
716 + if (namelen == 0)
717 + return;
718 +
719 + /*
720 + * Layout is: 3 DWORDS, Unicode string, pad(4).
721 + */
722 + mbc = &nc->nc_buffer;
723 + totlen = (12 + namelen + 3) & ~3;
724 + if (MBC_ROOM_FOR(mbc, totlen) == 0) {
725 + nc->nc_events |= FILE_NOTIFY_CHANGE_EV_OVERFLOW;
726 + return;
727 + }
728 +
729 + /*
730 + * Keep track of where this entry starts (nc_last_off)
731 + * because after we put all entries, we need to zap
732 + * the NextEntryOffset field in the last one.
733 + */
734 + nc->nc_last_off = mbc->chain_offset;
735 +
736 + /*
737 + * Encode this entry, then 4-byte alignment padding.
738 + *
739 + * Note that smb_mbc_encodef with a "U" code puts a
740 + * Unicode string with a null termination. We don't
741 + * want a null, but do want alignment padding. We
742 + * get that by encoding with "U.." at the end of the
743 + * encoding string, which gets us two bytes for the
744 + * Unicode NULL, and two more zeros for the "..".
745 + * We then "back up" the chain_offset (finger) so it's
746 + * correctly 4-byte aligned. We will sometimes have
747 + * written a couple more bytes than needed, but we'll
748 + * just overwrite those with the next entry. At the
749 + * end, we trim the mbuf chain to the correct length.
750 + */
751 + (void) smb_mbc_encodef(mbc, "lllU..",
752 + totlen, /* NextEntryOffset */
753 + action, namelen, fname);
754 + mbc->chain_offset = nc->nc_last_off + totlen;
422 755 }
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX