xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_notify.c (revision a90cf9f29973990687fa61de9f1f6ea22e924e40)
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
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
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
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
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
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