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
smb_nt_transact_notify_change(smb_request_t * sr,struct smb_xa * xa)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
smb_notify_encode_action(struct smb_request * sr,struct smb_xa * xa,uint32_t action,char * fname)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
smb_notify_file_closed(struct smb_ofile * of)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
smb_notify_event(smb_node_t * node,uint_t action,const char * name)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
smb_notify_sr(smb_request_t * sr,uint_t action,const char * name)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