xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_nt_transact_notify_change.c (revision 694c35faa87b858ecdadfe4fc592615f4eefbb07)
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 2012 Nexenta Systems, Inc.  All rights reserved.
25  */
26 
27 /*
28  * File Change Notification (FCN)
29  */
30 
31 /*
32  * SMB: nt_transact_notify_change
33  *
34  *  Client Setup Words                 Description
35  *  ================================== =================================
36  *
37  *  ULONG CompletionFilter;            Specifies operation to monitor
38  *  USHORT Fid;                        Fid of directory to monitor
39  *  BOOLEAN WatchTree;                 TRUE = watch all subdirectories too
40  *  UCHAR Reserved;                    MBZ
41  *
42  * This command notifies the client when the directory specified by Fid is
43  * modified.  It also returns the name(s) of the file(s) that changed.  The
44  * command completes once the directory has been modified based on the
45  * supplied CompletionFilter.  The command is a "single shot" and therefore
46  * needs to be reissued to watch for more directory changes.
47  *
48  * A directory file must be opened before this command may be used.  Once
49  * the directory is open, this command may be used to begin watching files
50  * and subdirectories in the specified directory for changes.  The first
51  * time the command is issued, the MaxParameterCount field in the transact
52  * header determines the size of the buffer that will be used at the server
53  * to buffer directory change information between issuances of the notify
54  * change commands.
55  *
56  * When a change that is in the CompletionFilter is made to the directory,
57  * the command completes.  The names of the files that have changed since
58  * the last time the command was issued are returned to the client.  The
59  * ParameterCount field of the response indicates the number of bytes that
60  * are being returned.  If too many files have changed since the last time
61  * the command was issued, then zero bytes are returned and an alternate
62  * status code is returned in the Status field of the response.
63  *
64  * The CompletionFilter is a mask created as the sum of any of the
65  * following flags:
66  *
67  * FILE_NOTIFY_CHANGE_FILE_NAME        0x00000001
68  * FILE_NOTIFY_CHANGE_DIR_NAME         0x00000002
69  * FILE_NOTIFY_CHANGE_NAME             0x00000003
70  * FILE_NOTIFY_CHANGE_ATTRIBUTES       0x00000004
71  * FILE_NOTIFY_CHANGE_SIZE             0x00000008
72  * FILE_NOTIFY_CHANGE_LAST_WRITE       0x00000010
73  * FILE_NOTIFY_CHANGE_LAST_ACCESS      0x00000020
74  * FILE_NOTIFY_CHANGE_CREATION         0x00000040
75  * FILE_NOTIFY_CHANGE_EA               0x00000080
76  * FILE_NOTIFY_CHANGE_SECURITY         0x00000100
77  * FILE_NOTIFY_CHANGE_STREAM_NAME      0x00000200
78  * FILE_NOTIFY_CHANGE_STREAM_SIZE      0x00000400
79  * FILE_NOTIFY_CHANGE_STREAM_WRITE     0x00000800
80  *
81  *  Server Response                    Description
82  *  ================================== ================================
83  *  ParameterCount                     # of bytes of change data
84  *  Parameters[ ParameterCount ]       FILE_NOTIFY_INFORMATION
85  *                                      structures
86  *
87  * The response contains FILE_NOTIFY_INFORMATION structures, as defined
88  * below.  The NextEntryOffset field of the structure specifies the offset,
89  * in bytes, from the start of the current entry to the next entry in the
90  * list.  If this is the last entry in the list, this field is zero.  Each
91  * entry in the list must be longword aligned, so NextEntryOffset must be a
92  * multiple of four.
93  *
94  * typedef struct {
95  *     ULONG NextEntryOffset;
96  *     ULONG Action;
97  *     ULONG FileNameLength;
98  *     WCHAR FileName[1];
99  * } FILE_NOTIFY_INFORMATION;
100  *
101  * Where Action describes what happened to the file named FileName:
102  *
103  * FILE_ACTION_ADDED            0x00000001
104  * FILE_ACTION_REMOVED          0x00000002
105  * FILE_ACTION_MODIFIED         0x00000003
106  * FILE_ACTION_RENAMED_OLD_NAME 0x00000004
107  * FILE_ACTION_RENAMED_NEW_NAME 0x00000005
108  * FILE_ACTION_ADDED_STREAM     0x00000006
109  * FILE_ACTION_REMOVED_STREAM   0x00000007
110  * FILE_ACTION_MODIFIED_STREAM  0x00000008
111  */
112 
113 #include <smbsrv/smb_kproto.h>
114 #include <sys/sdt.h>
115 
116 /*
117  * We add this flag to the CompletionFilter (see above) when the
118  * client sets WatchTree.  Must not overlap FILE_NOTIFY_VALID_MASK.
119  */
120 #define	NODE_FLAGS_WATCH_TREE		0x10000000
121 #if (NODE_FLAGS_WATCH_TREE & FILE_NOTIFY_VALID_MASK)
122 #error "NODE_FLAGS_WATCH_TREE"
123 #endif
124 
125 static void smb_notify_sr(smb_request_t *, uint_t, const char *);
126 
127 static int smb_notify_encode_action(struct smb_request *, struct smb_xa *,
128 	uint32_t, char *);
129 
130 /*
131  * smb_nt_transact_notify_change
132  *
133  * Handle and SMB NT transact NOTIFY CHANGE request.
134  * Basically, wait until "something has changed", and either
135  * return information about what changed, or return a special
136  * error telling the client "many things changed".
137  *
138  * The implementation uses a per-node list of waiting notify
139  * requests like this one, each with a blocked worker thead.
140  * Later, FEM and/or smbsrv events wake these threads, which
141  * then send the reply to the client.
142  */
143 smb_sdrc_t
144 smb_nt_transact_notify_change(smb_request_t *sr, struct smb_xa *xa)
145 {
146 	uint32_t		CompletionFilter;
147 	unsigned char		WatchTree;
148 	smb_error_t		err;
149 	int			rc;
150 	smb_node_t		*node;
151 
152 	if (smb_mbc_decodef(&xa->req_setup_mb, "lwb",
153 	    &CompletionFilter, &sr->smb_fid, &WatchTree) != 0) {
154 		smbsr_error(sr, NT_STATUS_INVALID_PARAMETER, 0, 0);
155 		return (SDRC_ERROR);
156 	}
157 	CompletionFilter &= FILE_NOTIFY_VALID_MASK;
158 	if (WatchTree)
159 		CompletionFilter |= NODE_FLAGS_WATCH_TREE;
160 
161 	smbsr_lookup_file(sr);
162 	if (sr->fid_ofile == NULL) {
163 		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
164 		return (SDRC_ERROR);
165 	}
166 
167 	node = sr->fid_ofile->f_node;
168 	if (node == NULL || !smb_node_is_dir(node)) {
169 		/*
170 		 * Notify change requests are only valid on directories.
171 		 */
172 		smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY, 0, 0);
173 		return (SDRC_ERROR);
174 	}
175 
176 	/*
177 	 * Prepare to receive event data.
178 	 */
179 	sr->sr_ncr.nc_flags = CompletionFilter;
180 	ASSERT(sr->sr_ncr.nc_action == 0);
181 	ASSERT(sr->sr_ncr.nc_fname == NULL);
182 	sr->sr_ncr.nc_fname = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
183 
184 	/*
185 	 * Subscribe to events on this node.
186 	 */
187 	smb_node_fcn_subscribe(node, sr);
188 
189 	/*
190 	 * Wait for subscribed events to arrive.
191 	 * Expect SMB_REQ_STATE_EVENT_OCCURRED
192 	 * or SMB_REQ_STATE_CANCELED when signaled.
193 	 * Note it's possible (though rare) to already
194 	 * have SMB_REQ_STATE_CANCELED here.
195 	 */
196 	mutex_enter(&sr->sr_mutex);
197 	if (sr->sr_state == SMB_REQ_STATE_ACTIVE)
198 		sr->sr_state = SMB_REQ_STATE_WAITING_EVENT;
199 	while (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT) {
200 		cv_wait(&sr->sr_ncr.nc_cv, &sr->sr_mutex);
201 	}
202 	if (sr->sr_state == SMB_REQ_STATE_EVENT_OCCURRED)
203 		sr->sr_state = SMB_REQ_STATE_ACTIVE;
204 	mutex_exit(&sr->sr_mutex);
205 
206 	/*
207 	 * Unsubscribe from events on this node.
208 	 */
209 	smb_node_fcn_unsubscribe(node, sr);
210 
211 	/*
212 	 * Build the reply
213 	 */
214 
215 	switch (sr->sr_state) {
216 
217 	case SMB_REQ_STATE_ACTIVE:
218 		/*
219 		 * If we have event data, marshall it now, else just
220 		 * say "many things changed". Note that when we are
221 		 * woken by a WatchTree event (action == 0) then we
222 		 * don't have true event details, and only know the
223 		 * directory under which something changed.  In that
224 		 * case we just say "many things changed".
225 		 */
226 		if (sr->sr_ncr.nc_action != 0 && 0 ==
227 		    smb_notify_encode_action(sr, xa,
228 		    sr->sr_ncr.nc_action, sr->sr_ncr.nc_fname)) {
229 			rc = SDRC_SUCCESS;
230 			break;
231 		}
232 		/*
233 		 * This error says "many things changed".
234 		 */
235 		err.status = NT_STATUS_NOTIFY_ENUM_DIR;
236 		err.errcls   = ERRDOS;
237 		err.errcode  = ERROR_NOTIFY_ENUM_DIR;
238 		smbsr_set_error(sr, &err);
239 		rc = SDRC_ERROR;
240 		break;
241 
242 	case SMB_REQ_STATE_CANCELED:
243 		err.status   = NT_STATUS_CANCELLED;
244 		err.errcls   = ERRDOS;
245 		err.errcode  = ERROR_OPERATION_ABORTED;
246 		smbsr_set_error(sr, &err);
247 		rc = SDRC_ERROR;
248 		break;
249 
250 	default:
251 		ASSERT(0);
252 		err.status   = NT_STATUS_INTERNAL_ERROR;
253 		err.errcls   = ERRDOS;
254 		err.errcode  = ERROR_INTERNAL_ERROR;
255 		smbsr_set_error(sr, &err);
256 		rc = SDRC_ERROR;
257 		break;
258 	}
259 
260 	if (sr->sr_ncr.nc_fname != NULL) {
261 		kmem_free(sr->sr_ncr.nc_fname, MAXNAMELEN);
262 		sr->sr_ncr.nc_fname = NULL;
263 	}
264 
265 	return (rc);
266 }
267 
268 /*
269  * Encode a FILE_NOTIFY_INFORMATION struct.
270  *
271  * We only ever put one of these in a response, so this
272  * does not bother handling appending additional ones.
273  */
274 static int
275 smb_notify_encode_action(struct smb_request *sr, struct smb_xa *xa,
276 	uint32_t action, char *fname)
277 {
278 	uint32_t namelen;
279 	int rc;
280 
281 	if (action < FILE_ACTION_ADDED ||
282 	    action > FILE_ACTION_MODIFIED_STREAM)
283 		return (-1);
284 
285 	namelen = smb_ascii_or_unicode_strlen(sr, fname);
286 	if (namelen == 0)
287 		return (-1);
288 
289 	rc = smb_mbc_encodef(&xa->rep_data_mb, "%lllu", sr,
290 	    0, /* NextEntryOffset */
291 	    action, namelen, fname);
292 	return (rc);
293 }
294 
295 /*
296  * smb_notify_file_closed
297  *
298  * Cancel any change-notify calls on this open file.
299  */
300 void
301 smb_notify_file_closed(struct smb_ofile *of)
302 {
303 	smb_session_t	*ses;
304 	smb_request_t	*sr;
305 	smb_slist_t	*list;
306 
307 	SMB_OFILE_VALID(of);
308 	ses = of->f_session;
309 	SMB_SESSION_VALID(ses);
310 	list = &ses->s_req_list;
311 
312 	smb_slist_enter(list);
313 
314 	sr = smb_slist_head(list);
315 	while (sr) {
316 		SMB_REQ_VALID(sr);
317 		if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
318 		    sr->fid_ofile == of) {
319 			smb_request_cancel(sr);
320 		}
321 		sr = smb_slist_next(list, sr);
322 	}
323 
324 	smb_slist_exit(list);
325 }
326 
327 
328 /*
329  * smb_notify_event
330  *
331  * Post an event to the watchers on a given node.
332  *
333  * This makes one exception for RENAME, where we expect a
334  * pair of events for the {old,new} directory element names.
335  * This only delivers an event for the "new" name.
336  *
337  * The event delivery mechanism does not implement delivery of
338  * multiple events for one "NT Notify" call.  One could do that,
339  * but modern clients don't actually use the event data.  They
340  * set a max. received data size of zero, which means we discard
341  * the data and send the special "lots changed" error instead.
342  * Given that, there's not really any point in implementing the
343  * delivery of multiple events.  In fact, we don't even need to
344  * implement single event delivery, but do so for completeness,
345  * for debug convenience, and to be nice to older clients that
346  * may actually want some event data instead of the error.
347  *
348  * Given that we only deliver a single event for an "NT Notify"
349  * caller, we want to deliver the "new" name event.  (The "old"
350  * name event is less important, even ignored by some clients.)
351  * Since we know these are delivered in pairs, we can simply
352  * discard the "old" name event, knowing that the "new" name
353  * event will be delivered immediately afterwards.
354  *
355  * So, why do event sources post the "old name" event at all?
356  * (1) For debugging, so we see both {old,new} names here.
357  * (2) If in the future someone decides to implement the
358  * delivery of both {old,new} events, the changes can be
359  * mostly isolated to this file.
360  */
361 void
362 smb_notify_event(smb_node_t *node, uint_t action, const char *name)
363 {
364 	smb_request_t	*sr;
365 	smb_node_fcn_t	*fcn;
366 
367 	SMB_NODE_VALID(node);
368 	fcn = &node->n_fcn;
369 
370 	if (action == FILE_ACTION_RENAMED_OLD_NAME)
371 		return; /* see above */
372 
373 	mutex_enter(&fcn->fcn_mutex);
374 
375 	sr = list_head(&fcn->fcn_watchers);
376 	while (sr) {
377 		smb_notify_sr(sr, action, name);
378 		sr = list_next(&fcn->fcn_watchers, sr);
379 	}
380 
381 	mutex_exit(&fcn->fcn_mutex);
382 }
383 
384 /*
385  * What completion filter (masks) apply to each of the
386  * FILE_ACTION_... events.
387  */
388 static const uint32_t
389 smb_notify_action_mask[] = {
390 	/* 0: Special, used by smb_node_notify_parents() */
391 	NODE_FLAGS_WATCH_TREE,
392 
393 	/* FILE_ACTION_ADDED	 */
394 	FILE_NOTIFY_CHANGE_NAME,
395 
396 	/* FILE_ACTION_REMOVED	 */
397 	FILE_NOTIFY_CHANGE_NAME,
398 
399 	/* FILE_ACTION_MODIFIED	 */
400 	FILE_NOTIFY_CHANGE_ATTRIBUTES |
401 	FILE_NOTIFY_CHANGE_SIZE |
402 	FILE_NOTIFY_CHANGE_LAST_WRITE |
403 	FILE_NOTIFY_CHANGE_LAST_ACCESS |
404 	FILE_NOTIFY_CHANGE_CREATION |
405 	FILE_NOTIFY_CHANGE_EA |
406 	FILE_NOTIFY_CHANGE_SECURITY,
407 
408 	/* FILE_ACTION_RENAMED_OLD_NAME */
409 	FILE_NOTIFY_CHANGE_NAME,
410 
411 	/* FILE_ACTION_RENAMED_NEW_NAME */
412 	FILE_NOTIFY_CHANGE_NAME,
413 
414 	/* FILE_ACTION_ADDED_STREAM */
415 	FILE_NOTIFY_CHANGE_STREAM_NAME,
416 
417 	/* FILE_ACTION_REMOVED_STREAM */
418 	FILE_NOTIFY_CHANGE_STREAM_NAME,
419 
420 	/* FILE_ACTION_MODIFIED_STREAM */
421 	FILE_NOTIFY_CHANGE_STREAM_SIZE |
422 	FILE_NOTIFY_CHANGE_STREAM_WRITE,
423 };
424 static const int smb_notify_action_nelm =
425 	sizeof (smb_notify_action_mask) /
426 	sizeof (smb_notify_action_mask[0]);
427 
428 /*
429  * smb_notify_sr
430  *
431  * Post an event to an smb request waiting on some node.
432  *
433  * Note that node->fcn.mutex is held.  This implies a
434  * lock order: node->fcn.mutex, then sr_mutex
435  */
436 static void
437 smb_notify_sr(smb_request_t *sr, uint_t action, const char *name)
438 {
439 	smb_notify_change_req_t	*ncr;
440 	uint32_t	mask;
441 
442 	SMB_REQ_VALID(sr);
443 	ncr = &sr->sr_ncr;
444 
445 	/*
446 	 * Compute the completion filter mask bits for which
447 	 * we will signal waiting notify requests.
448 	 */
449 	if (action >= smb_notify_action_nelm) {
450 		ASSERT(0);
451 		return;
452 	}
453 	mask = smb_notify_action_mask[action];
454 
455 	mutex_enter(&sr->sr_mutex);
456 	if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
457 	    (ncr->nc_flags & mask) != 0) {
458 		sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED;
459 		/*
460 		 * Save event data in the sr_ncr field so the
461 		 * reply handler can return it.
462 		 */
463 		ncr->nc_action = action;
464 		if (name != NULL)
465 			(void) strlcpy(ncr->nc_fname, name, MAXNAMELEN);
466 		cv_signal(&ncr->nc_cv);
467 	}
468 	mutex_exit(&sr->sr_mutex);
469 }
470