xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_notify.c (revision 5677e04907859594bfe1a95ba3bfdb1629b89dac)
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 2015 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 /*
98  * Cancel method for smb_notify_common()
99  *
100  * This request is waiting in change notify.
101  */
102 static void
103 smb_notify_cancel(smb_request_t *sr)
104 {
105 	cv_signal(&sr->sr_ncr.nc_cv);
106 }
107 
108 uint32_t
109 smb_notify_common(smb_request_t *sr, mbuf_chain_t *mbc,
110 	uint32_t CompletionFilter)
111 {
112 	smb_notify_change_req_t *nc;
113 	smb_node_t	*node;
114 	uint32_t	status;
115 
116 	if (sr->fid_ofile == NULL)
117 		return (NT_STATUS_INVALID_HANDLE);
118 
119 	node = sr->fid_ofile->f_node;
120 	if (node == NULL || !smb_node_is_dir(node)) {
121 		/*
122 		 * Notify change is only valid on directories.
123 		 */
124 		return (NT_STATUS_INVALID_PARAMETER);
125 	}
126 
127 	/*
128 	 * Prepare to receive event data.
129 	 */
130 	nc = &sr->sr_ncr;
131 	nc->nc_flags = CompletionFilter;
132 	ASSERT(nc->nc_action == 0);
133 	ASSERT(nc->nc_fname == NULL);
134 	nc->nc_fname = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
135 
136 	/*
137 	 * Subscribe to events on this node.
138 	 */
139 	smb_node_fcn_subscribe(node, sr);
140 
141 	/*
142 	 * Wait for subscribed events to arrive.
143 	 * Expect SMB_REQ_STATE_EVENT_OCCURRED
144 	 * or SMB_REQ_STATE_CANCELLED when signaled.
145 	 * Note it's possible (though rare) to already
146 	 * have SMB_REQ_STATE_CANCELLED here.
147 	 */
148 	mutex_enter(&sr->sr_mutex);
149 	if (sr->sr_state == SMB_REQ_STATE_ACTIVE) {
150 		sr->sr_state = SMB_REQ_STATE_WAITING_EVENT;
151 		sr->cancel_method = smb_notify_cancel;
152 	}
153 	while (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT) {
154 		cv_wait(&nc->nc_cv, &sr->sr_mutex);
155 	}
156 	sr->cancel_method = NULL;
157 
158 	switch (sr->sr_state) {
159 	case SMB_REQ_STATE_WAITING_EVENT:
160 	case SMB_REQ_STATE_EVENT_OCCURRED:
161 		/* normal wakeup */
162 		sr->sr_state = SMB_REQ_STATE_ACTIVE;
163 		status = 0;
164 		break;
165 
166 	case SMB_REQ_STATE_CANCEL_PENDING:
167 		/* cancelled via smb_notify_cancel */
168 		sr->sr_state = SMB_REQ_STATE_CANCELLED;
169 		status = NT_STATUS_CANCELLED;
170 		break;
171 
172 	case SMB_REQ_STATE_CANCELLED:
173 		/* cancelled before this function ran */
174 		status = NT_STATUS_CANCELLED;
175 		break;
176 
177 	default:
178 		status = NT_STATUS_INTERNAL_ERROR;
179 		break;
180 	}
181 
182 	mutex_exit(&sr->sr_mutex);
183 
184 	/*
185 	 * Unsubscribe from events on this node.
186 	 */
187 	smb_node_fcn_unsubscribe(node, sr);
188 
189 	/*
190 	 * Why did we wake up?
191 	 */
192 	if (status != 0)
193 		goto out;
194 
195 	/*
196 	 * We have SMB_REQ_STATE_ACTIVE.
197 	 *
198 	 * If we have event data, marshall it now, else just
199 	 * say "many things changed". Note that when we get
200 	 * action FILE_ACTION_SUBDIR_CHANGED, we don't have
201 	 * any event details and only know that some subdir
202 	 * changed, so just report "many things changed".
203 	 */
204 	switch (nc->nc_action) {
205 
206 	case FILE_ACTION_ADDED:
207 	case FILE_ACTION_REMOVED:
208 	case FILE_ACTION_MODIFIED:
209 	case FILE_ACTION_RENAMED_OLD_NAME:
210 	case FILE_ACTION_RENAMED_NEW_NAME:
211 	case FILE_ACTION_ADDED_STREAM:
212 	case FILE_ACTION_REMOVED_STREAM:
213 	case FILE_ACTION_MODIFIED_STREAM:
214 		/*
215 		 * Build the reply
216 		 */
217 		status = smb_notify_encode_action(sr, mbc,
218 		    nc->nc_action, nc->nc_fname);
219 		break;
220 
221 	case FILE_ACTION_SUBDIR_CHANGED:
222 		status = NT_STATUS_NOTIFY_ENUM_DIR;
223 		break;
224 
225 	case FILE_ACTION_DELETE_PENDING:
226 		status = NT_STATUS_DELETE_PENDING;
227 		break;
228 
229 	default:
230 		ASSERT(0);
231 		status = NT_STATUS_INTERNAL_ERROR;
232 		break;
233 	}
234 
235 out:
236 	kmem_free(nc->nc_fname, MAXNAMELEN);
237 	nc->nc_fname = NULL;
238 	return (status);
239 }
240 
241 /*
242  * Encode a FILE_NOTIFY_INFORMATION struct.
243  *
244  * We only ever put one of these in a response, so this
245  * does not bother handling appending additional ones.
246  */
247 static uint32_t
248 smb_notify_encode_action(struct smb_request *sr, mbuf_chain_t *mbc,
249 	uint32_t action, char *fname)
250 {
251 	uint32_t namelen;
252 
253 	ASSERT(FILE_ACTION_ADDED <= action &&
254 	    action <= FILE_ACTION_MODIFIED_STREAM);
255 
256 	if (fname == NULL)
257 		return (NT_STATUS_INTERNAL_ERROR);
258 	namelen = smb_wcequiv_strlen(fname);
259 	if (namelen == 0)
260 		return (NT_STATUS_INTERNAL_ERROR);
261 
262 	if (smb_mbc_encodef(mbc, "%lllU", sr,
263 	    0, /* NextEntryOffset */
264 	    action, namelen, fname))
265 		return (NT_STATUS_NOTIFY_ENUM_DIR);
266 
267 	return (0);
268 }
269 
270 /*
271  * smb_notify_file_closed
272  *
273  * Cancel any change-notify calls on this open file.
274  */
275 void
276 smb_notify_file_closed(struct smb_ofile *of)
277 {
278 	smb_session_t	*ses;
279 	smb_request_t	*sr;
280 	smb_slist_t	*list;
281 
282 	SMB_OFILE_VALID(of);
283 	ses = of->f_session;
284 	SMB_SESSION_VALID(ses);
285 	list = &ses->s_req_list;
286 
287 	smb_slist_enter(list);
288 
289 	sr = smb_slist_head(list);
290 	while (sr) {
291 		SMB_REQ_VALID(sr);
292 		if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
293 		    sr->fid_ofile == of) {
294 			smb_request_cancel(sr);
295 		}
296 		sr = smb_slist_next(list, sr);
297 	}
298 
299 	smb_slist_exit(list);
300 }
301 
302 
303 /*
304  * smb_notify_event
305  *
306  * Post an event to the watchers on a given node.
307  *
308  * This makes one exception for RENAME, where we expect a
309  * pair of events for the {old,new} directory element names.
310  * This only delivers an event for the "new" name.
311  *
312  * The event delivery mechanism does not implement delivery of
313  * multiple events for one "NT Notify" call.  One could do that,
314  * but modern clients don't actually use the event data.  They
315  * set a max. received data size of zero, which means we discard
316  * the data and send the special "lots changed" error instead.
317  * Given that, there's not really any point in implementing the
318  * delivery of multiple events.  In fact, we don't even need to
319  * implement single event delivery, but do so for completeness,
320  * for debug convenience, and to be nice to older clients that
321  * may actually want some event data instead of the error.
322  *
323  * Given that we only deliver a single event for an "NT Notify"
324  * caller, we want to deliver the "new" name event.  (The "old"
325  * name event is less important, even ignored by some clients.)
326  * Since we know these are delivered in pairs, we can simply
327  * discard the "old" name event, knowing that the "new" name
328  * event will be delivered immediately afterwards.
329  *
330  * So, why do event sources post the "old name" event at all?
331  * (1) For debugging, so we see both {old,new} names here.
332  * (2) If in the future someone decides to implement the
333  * delivery of both {old,new} events, the changes can be
334  * mostly isolated to this file.
335  */
336 void
337 smb_notify_event(smb_node_t *node, uint_t action, const char *name)
338 {
339 	smb_request_t	*sr;
340 	smb_node_fcn_t	*fcn;
341 
342 	SMB_NODE_VALID(node);
343 	fcn = &node->n_fcn;
344 
345 	if (action == FILE_ACTION_RENAMED_OLD_NAME)
346 		return; /* see above */
347 
348 	mutex_enter(&fcn->fcn_mutex);
349 
350 	sr = list_head(&fcn->fcn_watchers);
351 	while (sr) {
352 		smb_notify_sr(sr, action, name);
353 		sr = list_next(&fcn->fcn_watchers, sr);
354 	}
355 
356 	mutex_exit(&fcn->fcn_mutex);
357 }
358 
359 /*
360  * What completion filter (masks) apply to each of the
361  * FILE_ACTION_... events.
362  */
363 static const uint32_t
364 smb_notify_action_mask[] = {
365 	0,  /* not used */
366 
367 	/* FILE_ACTION_ADDED	 */
368 	FILE_NOTIFY_CHANGE_NAME |
369 	FILE_NOTIFY_CHANGE_LAST_WRITE,
370 
371 	/* FILE_ACTION_REMOVED	 */
372 	FILE_NOTIFY_CHANGE_NAME |
373 	FILE_NOTIFY_CHANGE_LAST_WRITE,
374 
375 	/* FILE_ACTION_MODIFIED	 */
376 	FILE_NOTIFY_CHANGE_ATTRIBUTES |
377 	FILE_NOTIFY_CHANGE_SIZE |
378 	FILE_NOTIFY_CHANGE_LAST_WRITE |
379 	FILE_NOTIFY_CHANGE_LAST_ACCESS |
380 	FILE_NOTIFY_CHANGE_CREATION |
381 	FILE_NOTIFY_CHANGE_EA |
382 	FILE_NOTIFY_CHANGE_SECURITY,
383 
384 	/* FILE_ACTION_RENAMED_OLD_NAME */
385 	FILE_NOTIFY_CHANGE_NAME |
386 	FILE_NOTIFY_CHANGE_LAST_WRITE,
387 
388 	/* FILE_ACTION_RENAMED_NEW_NAME */
389 	FILE_NOTIFY_CHANGE_NAME |
390 	FILE_NOTIFY_CHANGE_LAST_WRITE,
391 
392 	/* FILE_ACTION_ADDED_STREAM */
393 	FILE_NOTIFY_CHANGE_STREAM_NAME,
394 
395 	/* FILE_ACTION_REMOVED_STREAM */
396 	FILE_NOTIFY_CHANGE_STREAM_NAME,
397 
398 	/* FILE_ACTION_MODIFIED_STREAM */
399 	FILE_NOTIFY_CHANGE_STREAM_SIZE |
400 	FILE_NOTIFY_CHANGE_STREAM_WRITE,
401 
402 	/* FILE_ACTION_SUBDIR_CHANGED */
403 	NODE_FLAGS_WATCH_TREE,
404 
405 	/* FILE_ACTION_DELETE_PENDING */
406 	NODE_FLAGS_WATCH_TREE |
407 	FILE_NOTIFY_VALID_MASK,
408 };
409 static const int smb_notify_action_nelm =
410 	sizeof (smb_notify_action_mask) /
411 	sizeof (smb_notify_action_mask[0]);
412 
413 /*
414  * smb_notify_sr
415  *
416  * Post an event to an smb request waiting on some node.
417  *
418  * Note that node->fcn.mutex is held.  This implies a
419  * lock order: node->fcn.mutex, then sr_mutex
420  */
421 static void
422 smb_notify_sr(smb_request_t *sr, uint_t action, const char *name)
423 {
424 	smb_notify_change_req_t	*ncr;
425 	uint32_t	mask;
426 
427 	SMB_REQ_VALID(sr);
428 	ncr = &sr->sr_ncr;
429 
430 	/*
431 	 * Compute the completion filter mask bits for which
432 	 * we will signal waiting notify requests.
433 	 */
434 	VERIFY(action < smb_notify_action_nelm);
435 	mask = smb_notify_action_mask[action];
436 
437 	mutex_enter(&sr->sr_mutex);
438 	if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
439 	    (ncr->nc_flags & mask) != 0) {
440 		sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED;
441 		/*
442 		 * Save event data in the sr_ncr field so the
443 		 * reply handler can return it.
444 		 */
445 		ncr->nc_action = action;
446 		if (name != NULL)
447 			(void) strlcpy(ncr->nc_fname, name, MAXNAMELEN);
448 		cv_signal(&ncr->nc_cv);
449 	}
450 	mutex_exit(&sr->sr_mutex);
451 }
452