1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
25 */
26
27 /*
28 * File Change Notification (FCN)
29 * Common parts shared by SMB1 & SMB2
30 */
31
32 /*
33 * This command notifies the client when the specified directory
34 * has changed, and optionally returns the names of files and
35 * directories that changed, and how they changed. The caller
36 * specifies a "Completion Filter" to select which kinds of
37 * changes they want to know about.
38 *
39 * When a change that's in the CompletionFilter is made to the directory,
40 * the command completes. The names of the files that have changed since
41 * the last time the command was issued are returned to the client.
42 * If too many files have changed since the last time the command was
43 * issued, then zero bytes are returned and an alternate status code
44 * is returned in the Status field of the response.
45 *
46 * The CompletionFilter is a mask created as the sum of any of the
47 * following flags:
48 *
49 * FILE_NOTIFY_CHANGE_FILE_NAME 0x00000001
50 * FILE_NOTIFY_CHANGE_DIR_NAME 0x00000002
51 * FILE_NOTIFY_CHANGE_NAME 0x00000003
52 * FILE_NOTIFY_CHANGE_ATTRIBUTES 0x00000004
53 * FILE_NOTIFY_CHANGE_SIZE 0x00000008
54 * FILE_NOTIFY_CHANGE_LAST_WRITE 0x00000010
55 * FILE_NOTIFY_CHANGE_LAST_ACCESS 0x00000020
56 * FILE_NOTIFY_CHANGE_CREATION 0x00000040
57 * FILE_NOTIFY_CHANGE_EA 0x00000080
58 * FILE_NOTIFY_CHANGE_SECURITY 0x00000100
59 * FILE_NOTIFY_CHANGE_STREAM_NAME 0x00000200
60 * FILE_NOTIFY_CHANGE_STREAM_SIZE 0x00000400
61 * FILE_NOTIFY_CHANGE_STREAM_WRITE 0x00000800
62 *
63 *
64 * The response contains FILE_NOTIFY_INFORMATION structures, as defined
65 * below. The NextEntryOffset field of the structure specifies the offset,
66 * in bytes, from the start of the current entry to the next entry in the
67 * list. If this is the last entry in the list, this field is zero. Each
68 * entry in the list must be longword aligned, so NextEntryOffset must be a
69 * multiple of four.
70 *
71 * typedef struct {
72 * ULONG NextEntryOffset;
73 * ULONG Action;
74 * ULONG FileNameLength;
75 * WCHAR FileName[1];
76 * } FILE_NOTIFY_INFORMATION;
77 *
78 * Where Action describes what happened to the file named FileName:
79 *
80 * FILE_ACTION_ADDED 0x00000001
81 * FILE_ACTION_REMOVED 0x00000002
82 * FILE_ACTION_MODIFIED 0x00000003
83 * FILE_ACTION_RENAMED_OLD_NAME 0x00000004
84 * FILE_ACTION_RENAMED_NEW_NAME 0x00000005
85 * FILE_ACTION_ADDED_STREAM 0x00000006
86 * FILE_ACTION_REMOVED_STREAM 0x00000007
87 * FILE_ACTION_MODIFIED_STREAM 0x00000008
88 */
89
90 #include <smbsrv/smb_kproto.h>
91 #include <sys/sdt.h>
92
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 *);
96
97 uint32_t
smb_notify_common(smb_request_t * sr,mbuf_chain_t * mbc,uint32_t CompletionFilter)98 smb_notify_common(smb_request_t *sr, mbuf_chain_t *mbc,
99 uint32_t CompletionFilter)
100 {
101 smb_notify_change_req_t *nc;
102 smb_node_t *node;
103 uint32_t status;
104
105 if (sr->fid_ofile == NULL)
106 return (NT_STATUS_INVALID_HANDLE);
107
108 node = sr->fid_ofile->f_node;
109 if (node == NULL || !smb_node_is_dir(node)) {
110 /*
111 * Notify change is only valid on directories.
112 */
113 return (NT_STATUS_INVALID_PARAMETER);
114 }
115
116 /*
117 * Prepare to receive event data.
118 */
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
125 /*
126 * Subscribe to events on this node.
127 */
128 smb_node_fcn_subscribe(node, sr);
129
130 /*
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.
136 */
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);
146
147 /*
148 * Unsubscribe from events on this node.
149 */
150 smb_node_fcn_unsubscribe(node, sr);
151
152 /*
153 * Why did we wake up?
154 */
155 switch (sr->sr_state) {
156 case SMB_REQ_STATE_ACTIVE:
157 break;
158 case SMB_REQ_STATE_CANCELED:
159 status = NT_STATUS_CANCELLED;
160 goto out;
161 default:
162 status = NT_STATUS_INTERNAL_ERROR;
163 goto out;
164 }
165
166 /*
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".
174 */
175 switch (nc->nc_action) {
176
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:
185 /*
186 * Build the reply
187 */
188 status = smb_notify_encode_action(sr, mbc,
189 nc->nc_action, nc->nc_fname);
190 break;
191
192 case FILE_ACTION_SUBDIR_CHANGED:
193 status = NT_STATUS_NOTIFY_ENUM_DIR;
194 break;
195
196 case FILE_ACTION_DELETE_PENDING:
197 status = NT_STATUS_DELETE_PENDING;
198 break;
199
200 default:
201 ASSERT(0);
202 status = NT_STATUS_INTERNAL_ERROR;
203 break;
204 }
205
206 out:
207 kmem_free(nc->nc_fname, MAXNAMELEN);
208 nc->nc_fname = NULL;
209 return (status);
210 }
211
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 static uint32_t
smb_notify_encode_action(struct smb_request * sr,mbuf_chain_t * mbc,uint32_t action,char * fname)219 smb_notify_encode_action(struct smb_request *sr, mbuf_chain_t *mbc,
220 uint32_t action, char *fname)
221 {
222 uint32_t namelen;
223
224 ASSERT(FILE_ACTION_ADDED <= action &&
225 action <= FILE_ACTION_MODIFIED_STREAM);
226
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);
232
233 if (smb_mbc_encodef(mbc, "%lllU", sr,
234 0, /* NextEntryOffset */
235 action, namelen, fname))
236 return (NT_STATUS_NOTIFY_ENUM_DIR);
237
238 return (0);
239 }
240
241 /*
242 * smb_notify_file_closed
243 *
244 * Cancel any change-notify calls on this open file.
245 */
246 void
smb_notify_file_closed(struct smb_ofile * of)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;
252
253 SMB_OFILE_VALID(of);
254 ses = of->f_session;
255 SMB_SESSION_VALID(ses);
256 list = &ses->s_req_list;
257
258 smb_slist_enter(list);
259
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);
268 }
269
270 smb_slist_exit(list);
271 }
272
273
274 /*
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.
306 */
307 void
smb_notify_event(smb_node_t * node,uint_t action,const char * name)308 smb_notify_event(smb_node_t *node, uint_t action, const char *name)
309 {
310 smb_request_t *sr;
311 smb_node_fcn_t *fcn;
312
313 SMB_NODE_VALID(node);
314 fcn = &node->n_fcn;
315
316 if (action == FILE_ACTION_RENAMED_OLD_NAME)
317 return; /* see above */
318
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);
325 }
326
327 mutex_exit(&fcn->fcn_mutex);
328 }
329
330 /*
331 * What completion filter (masks) apply to each of the
332 * FILE_ACTION_... events.
333 */
334 static const uint32_t
335 smb_notify_action_mask[] = {
336 0, /* not used */
337
338 /* FILE_ACTION_ADDED */
339 FILE_NOTIFY_CHANGE_NAME |
340 FILE_NOTIFY_CHANGE_LAST_WRITE,
341
342 /* FILE_ACTION_REMOVED */
343 FILE_NOTIFY_CHANGE_NAME |
344 FILE_NOTIFY_CHANGE_LAST_WRITE,
345
346 /* FILE_ACTION_MODIFIED */
347 FILE_NOTIFY_CHANGE_ATTRIBUTES |
348 FILE_NOTIFY_CHANGE_SIZE |
349 FILE_NOTIFY_CHANGE_LAST_WRITE |
350 FILE_NOTIFY_CHANGE_LAST_ACCESS |
351 FILE_NOTIFY_CHANGE_CREATION |
352 FILE_NOTIFY_CHANGE_EA |
353 FILE_NOTIFY_CHANGE_SECURITY,
354
355 /* FILE_ACTION_RENAMED_OLD_NAME */
356 FILE_NOTIFY_CHANGE_NAME |
357 FILE_NOTIFY_CHANGE_LAST_WRITE,
358
359 /* FILE_ACTION_RENAMED_NEW_NAME */
360 FILE_NOTIFY_CHANGE_NAME |
361 FILE_NOTIFY_CHANGE_LAST_WRITE,
362
363 /* FILE_ACTION_ADDED_STREAM */
364 FILE_NOTIFY_CHANGE_STREAM_NAME,
365
366 /* FILE_ACTION_REMOVED_STREAM */
367 FILE_NOTIFY_CHANGE_STREAM_NAME,
368
369 /* FILE_ACTION_MODIFIED_STREAM */
370 FILE_NOTIFY_CHANGE_STREAM_SIZE |
371 FILE_NOTIFY_CHANGE_STREAM_WRITE,
372
373 /* FILE_ACTION_SUBDIR_CHANGED */
374 NODE_FLAGS_WATCH_TREE,
375
376 /* FILE_ACTION_DELETE_PENDING */
377 NODE_FLAGS_WATCH_TREE |
378 FILE_NOTIFY_VALID_MASK,
379 };
380 static const int smb_notify_action_nelm =
381 sizeof (smb_notify_action_mask) /
382 sizeof (smb_notify_action_mask[0]);
383
384 /*
385 * smb_notify_sr
386 *
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
391 */
392 static void
smb_notify_sr(smb_request_t * sr,uint_t action,const char * name)393 smb_notify_sr(smb_request_t *sr, uint_t action, const char *name)
394 {
395 smb_notify_change_req_t *ncr;
396 uint32_t mask;
397
398 SMB_REQ_VALID(sr);
399 ncr = &sr->sr_ncr;
400
401 /*
402 * Compute the completion filter mask bits for which
403 * we will signal waiting notify requests.
404 */
405 VERIFY(action < smb_notify_action_nelm);
406 mask = smb_notify_action_mask[action];
407
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;
412 /*
413 * Save event data in the sr_ncr field so the
414 * reply handler can return it.
415 */
416 ncr->nc_action = action;
417 if (name != NULL)
418 (void) strlcpy(ncr->nc_fname, name, MAXNAMELEN);
419 cv_signal(&ncr->nc_cv);
420 }
421 mutex_exit(&sr->sr_mutex);
422 }
423