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 */
25
26 /*
27 * File Change Notification (FCN)
28 */
29
30 /*
31 * SMB: nt_transact_notify_change
32 *
33 * Client Setup Words Description
34 * ================================== =================================
35 *
36 * ULONG CompletionFilter; Specifies operation to monitor
37 * USHORT Fid; Fid of directory to monitor
38 * BOOLEAN WatchTree; TRUE = watch all subdirectories too
39 * UCHAR Reserved; MBZ
40 *
41 * This command notifies the client when the directory specified by Fid is
42 * modified. It also returns the name(s) of the file(s) that changed. The
43 * command completes once the directory has been modified based on the
44 * supplied CompletionFilter. The command is a "single shot" and therefore
45 * needs to be reissued to watch for more directory changes.
46 *
47 * A directory file must be opened before this command may be used. Once
48 * the directory is open, this command may be used to begin watching files
49 * and subdirectories in the specified directory for changes. The first
50 * time the command is issued, the MaxParameterCount field in the transact
51 * header determines the size of the buffer that will be used at the server
52 * to buffer directory change information between issuances of the notify
53 * change commands.
54 *
55 * When a change that is in the CompletionFilter is made to the directory,
56 * the command completes. The names of the files that have changed since
57 * the last time the command was issued are returned to the client. The
58 * ParameterCount field of the response indicates the number of bytes that
59 * are being returned. If too many files have changed since the last time
60 * the command was issued, then zero bytes are returned and an alternate
61 * status code is returned in the Status field of the response.
62 *
63 * The CompletionFilter is a mask created as the sum of any of the
64 * following flags:
65 *
66 * FILE_NOTIFY_CHANGE_FILE_NAME 0x00000001
67 * FILE_NOTIFY_CHANGE_DIR_NAME 0x00000002
68 * FILE_NOTIFY_CHANGE_NAME 0x00000003
69 * FILE_NOTIFY_CHANGE_ATTRIBUTES 0x00000004
70 * FILE_NOTIFY_CHANGE_SIZE 0x00000008
71 * FILE_NOTIFY_CHANGE_LAST_WRITE 0x00000010
72 * FILE_NOTIFY_CHANGE_LAST_ACCESS 0x00000020
73 * FILE_NOTIFY_CHANGE_CREATION 0x00000040
74 * FILE_NOTIFY_CHANGE_EA 0x00000080
75 * FILE_NOTIFY_CHANGE_SECURITY 0x00000100
76 * FILE_NOTIFY_CHANGE_STREAM_NAME 0x00000200
77 * FILE_NOTIFY_CHANGE_STREAM_SIZE 0x00000400
78 * FILE_NOTIFY_CHANGE_STREAM_WRITE 0x00000800
79 *
80 * Server Response Description
81 * ================================== ================================
82 * ParameterCount # of bytes of change data
83 * Parameters[ ParameterCount ] FILE_NOTIFY_INFORMATION
84 * structures
85 *
86 * The response contains FILE_NOTIFY_INFORMATION structures, as defined
87 * below. The NextEntryOffset field of the structure specifies the offset,
88 * in bytes, from the start of the current entry to the next entry in the
89 * list. If this is the last entry in the list, this field is zero. Each
90 * entry in the list must be longword aligned, so NextEntryOffset must be a
91 * multiple of four.
92 *
93 * typedef struct {
94 * ULONG NextEntryOffset;
95 * ULONG Action;
96 * ULONG FileNameLength;
97 * WCHAR FileName[1];
98 * } FILE_NOTIFY_INFORMATION;
99 *
100 * Where Action describes what happened to the file named FileName:
101 *
102 * FILE_ACTION_ADDED 0x00000001
103 * FILE_ACTION_REMOVED 0x00000002
104 * FILE_ACTION_MODIFIED 0x00000003
105 * FILE_ACTION_RENAMED_OLD_NAME 0x00000004
106 * FILE_ACTION_RENAMED_NEW_NAME 0x00000005
107 * FILE_ACTION_ADDED_STREAM 0x00000006
108 * FILE_ACTION_REMOVED_STREAM 0x00000007
109 * FILE_ACTION_MODIFIED_STREAM 0x00000008
110 */
111
112 #include <smbsrv/smb_kproto.h>
113 #include <sys/sdt.h>
114
115 static void smb_notify_change_daemon(smb_thread_t *, void *);
116
117 static boolean_t smb_notify_initialized = B_FALSE;
118 static smb_slist_t smb_ncr_list;
119 static smb_slist_t smb_nce_list;
120 static smb_thread_t smb_thread_notify_daemon;
121
122 /*
123 * smb_notify_init
124 *
125 * This function is not multi-thread safe. The caller must make sure only one
126 * thread makes the call.
127 */
128 int
smb_notify_init(void)129 smb_notify_init(void)
130 {
131 int rc;
132
133 if (smb_notify_initialized)
134 return (0);
135
136 smb_slist_constructor(&smb_ncr_list, sizeof (smb_request_t),
137 offsetof(smb_request_t, sr_ncr.nc_lnd));
138
139 smb_slist_constructor(&smb_nce_list, sizeof (smb_request_t),
140 offsetof(smb_request_t, sr_ncr.nc_lnd));
141
142 smb_thread_init(&smb_thread_notify_daemon,
143 "smb_notify_change_daemon", smb_notify_change_daemon, NULL);
144
145 rc = smb_thread_start(&smb_thread_notify_daemon);
146 if (rc) {
147 smb_thread_destroy(&smb_thread_notify_daemon);
148 smb_slist_destructor(&smb_ncr_list);
149 smb_slist_destructor(&smb_nce_list);
150 return (rc);
151 }
152
153 smb_notify_initialized = B_TRUE;
154
155 return (0);
156 }
157
158 /*
159 * smb_notify_fini
160 *
161 * This function is not multi-thread safe. The caller must make sure only one
162 * thread makes the call.
163 */
164 void
smb_notify_fini(void)165 smb_notify_fini(void)
166 {
167 if (!smb_notify_initialized)
168 return;
169
170 smb_thread_stop(&smb_thread_notify_daemon);
171 smb_thread_destroy(&smb_thread_notify_daemon);
172 smb_slist_destructor(&smb_ncr_list);
173 smb_slist_destructor(&smb_nce_list);
174 smb_notify_initialized = B_FALSE;
175 }
176
177 /*
178 * smb_nt_transact_notify_change
179 *
180 * This function is responsible for processing NOTIFY CHANGE requests.
181 * Requests are stored in a global queue. This queue is processed when
182 * a monitored directory is changed or client cancels one of its already
183 * sent requests.
184 */
185 smb_sdrc_t
smb_nt_transact_notify_change(struct smb_request * sr,struct smb_xa * xa)186 smb_nt_transact_notify_change(struct smb_request *sr, struct smb_xa *xa)
187 {
188 uint32_t CompletionFilter;
189 unsigned char WatchTree;
190 smb_node_t *node;
191
192 if (smb_mbc_decodef(&xa->req_setup_mb, "lwb",
193 &CompletionFilter, &sr->smb_fid, &WatchTree) != 0)
194 return (SDRC_NOT_IMPLEMENTED);
195
196 smbsr_lookup_file(sr);
197 if (sr->fid_ofile == NULL) {
198 smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
199 return (SDRC_ERROR);
200 }
201
202 node = sr->fid_ofile->f_node;
203
204 if (!smb_node_is_dir(node)) {
205 /*
206 * Notify change requests are only valid on directories.
207 */
208 smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY, 0, 0);
209 return (SDRC_ERROR);
210 }
211
212 mutex_enter(&sr->sr_mutex);
213 switch (sr->sr_state) {
214 case SMB_REQ_STATE_ACTIVE:
215 node->waiting_event++;
216 node->flags |= NODE_FLAGS_NOTIFY_CHANGE;
217 if ((node->flags & NODE_FLAGS_CHANGED) == 0) {
218 sr->sr_ncr.nc_node = node;
219 sr->sr_ncr.nc_flags = CompletionFilter;
220 if (WatchTree)
221 sr->sr_ncr.nc_flags |= NODE_FLAGS_WATCH_TREE;
222
223 sr->sr_keep = B_TRUE;
224 sr->sr_state = SMB_REQ_STATE_WAITING_EVENT;
225
226 smb_slist_insert_tail(&smb_ncr_list, sr);
227
228 /*
229 * Monitor events system-wide.
230 *
231 * XXX: smb_node_ref() and smb_node_release()
232 * take &node->n_lock. May need alternate forms
233 * of these routines if node->n_lock is taken
234 * around calls to smb_fem_fcn_install() and
235 * smb_fem_fcn_uninstall().
236 */
237
238 smb_fem_fcn_install(node);
239
240 mutex_exit(&sr->sr_mutex);
241 return (SDRC_SR_KEPT);
242 } else {
243 /* node already changed, reply immediately */
244 if (--node->waiting_event == 0)
245 node->flags &=
246 ~(NODE_FLAGS_NOTIFY_CHANGE |
247 NODE_FLAGS_CHANGED);
248 mutex_exit(&sr->sr_mutex);
249 return (SDRC_SUCCESS);
250 }
251
252 case SMB_REQ_STATE_CANCELED:
253 mutex_exit(&sr->sr_mutex);
254 smbsr_error(sr, NT_STATUS_CANCELLED, 0, 0);
255 return (SDRC_ERROR);
256
257 default:
258 ASSERT(0);
259 mutex_exit(&sr->sr_mutex);
260 return (SDRC_SUCCESS);
261 }
262 }
263
264 /*
265 * smb_reply_notify_change_request
266 *
267 * This function sends appropriate response to an already queued NOTIFY CHANGE
268 * request. If node is changed (reply == NODE_FLAGS_CHANGED), a normal reply is
269 * sent.
270 * If client cancels the request or session dropped, an NT_STATUS_CANCELED
271 * is sent in reply.
272 */
273
274 void
smb_reply_notify_change_request(smb_request_t * sr)275 smb_reply_notify_change_request(smb_request_t *sr)
276 {
277 smb_node_t *node;
278 smb_srqueue_t *srq;
279 int total_bytes, n_setup, n_param, n_data;
280 int param_off, param_pad, data_off, data_pad;
281 struct smb_xa *xa;
282 smb_error_t err;
283
284 SMB_REQ_VALID(sr);
285 srq = sr->session->s_srqueue;
286 smb_srqueue_waitq_to_runq(srq);
287
288 xa = sr->r_xa;
289 node = sr->sr_ncr.nc_node;
290
291 if (--node->waiting_event == 0) {
292 node->flags &= ~(NODE_FLAGS_NOTIFY_CHANGE | NODE_FLAGS_CHANGED);
293 smb_fem_fcn_uninstall(node);
294 }
295
296 mutex_enter(&sr->sr_mutex);
297 switch (sr->sr_state) {
298
299 case SMB_REQ_STATE_EVENT_OCCURRED:
300 sr->sr_state = SMB_REQ_STATE_ACTIVE;
301
302 /* many things changed */
303
304 (void) smb_mbc_encodef(&xa->rep_data_mb, "l", 0L);
305
306 /* setup the NT transact reply */
307
308 n_setup = MBC_LENGTH(&xa->rep_setup_mb);
309 n_param = MBC_LENGTH(&xa->rep_param_mb);
310 n_data = MBC_LENGTH(&xa->rep_data_mb);
311
312 n_setup = (n_setup + 1) / 2; /* Convert to setup words */
313 param_pad = 1; /* must be one */
314 param_off = param_pad + 32 + 37 + (n_setup << 1) + 2;
315 /* Pad to 4 bytes */
316 data_pad = (4 - ((param_off + n_param) & 3)) % 4;
317 /* Param off from hdr */
318 data_off = param_off + n_param + data_pad;
319 total_bytes = param_pad + n_param + data_pad + n_data;
320
321 (void) smbsr_encode_result(sr, 18+n_setup, total_bytes,
322 "b3.llllllllbCw#.C#.C",
323 18 + n_setup, /* wct */
324 n_param, /* Total Parameter Bytes */
325 n_data, /* Total Data Bytes */
326 n_param, /* Total Parameter Bytes this buffer */
327 param_off, /* Param offset from header start */
328 0, /* Param displacement */
329 n_data, /* Total Data Bytes this buffer */
330 data_off, /* Data offset from header start */
331 0, /* Data displacement */
332 n_setup, /* suwcnt */
333 &xa->rep_setup_mb, /* setup[] */
334 total_bytes, /* Total data bytes */
335 param_pad,
336 &xa->rep_param_mb,
337 data_pad,
338 &xa->rep_data_mb);
339 break;
340
341 case SMB_REQ_STATE_CANCELED:
342 err.status = NT_STATUS_CANCELLED;
343 err.errcls = ERRDOS;
344 err.errcode = ERROR_OPERATION_ABORTED;
345 smbsr_set_error(sr, &err);
346
347 (void) smb_mbc_encodef(&sr->reply, "bwbw",
348 (short)0, 0L, (short)0, 0L);
349 sr->smb_wct = 0;
350 sr->smb_bcc = 0;
351 break;
352 default:
353 ASSERT(0);
354 }
355 mutex_exit(&sr->sr_mutex);
356
357 /* Setup the header */
358 (void) smb_mbc_poke(&sr->reply, 0, SMB_HEADER_ED_FMT,
359 sr->first_smb_com,
360 sr->smb_rcls,
361 sr->smb_reh,
362 sr->smb_err,
363 sr->smb_flg | SMB_FLAGS_REPLY,
364 sr->smb_flg2,
365 sr->smb_pid_high,
366 sr->smb_sig,
367 sr->smb_tid,
368 sr->smb_pid,
369 sr->smb_uid,
370 sr->smb_mid);
371
372 if (sr->session->signing.flags & SMB_SIGNING_ENABLED)
373 smb_sign_reply(sr, NULL);
374
375 /* send the reply */
376 DTRACE_PROBE1(ncr__reply, struct smb_request *, sr)
377 (void) smb_session_send(sr->session, 0, &sr->reply);
378 smbsr_cleanup(sr);
379
380 mutex_enter(&sr->sr_mutex);
381 sr->sr_state = SMB_REQ_STATE_COMPLETED;
382 mutex_exit(&sr->sr_mutex);
383 smb_srqueue_runq_exit(srq);
384 smb_request_free(sr);
385 }
386
387 /*
388 * smb_process_session_notify_change_queue
389 *
390 * This function traverses notify change request queue and sends
391 * cancel replies to all of requests that are related to a specific
392 * session.
393 */
394 void
smb_process_session_notify_change_queue(smb_session_t * session,smb_tree_t * tree)395 smb_process_session_notify_change_queue(
396 smb_session_t *session,
397 smb_tree_t *tree)
398 {
399 smb_request_t *sr;
400 smb_request_t *tmp;
401 boolean_t sig = B_FALSE;
402
403 smb_slist_enter(&smb_ncr_list);
404 smb_slist_enter(&smb_nce_list);
405 sr = smb_slist_head(&smb_ncr_list);
406 while (sr) {
407 ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
408 tmp = smb_slist_next(&smb_ncr_list, sr);
409 if ((sr->session == session) &&
410 (tree == NULL || sr->tid_tree == tree)) {
411 mutex_enter(&sr->sr_mutex);
412 switch (sr->sr_state) {
413 case SMB_REQ_STATE_WAITING_EVENT:
414 smb_slist_obj_move(
415 &smb_nce_list,
416 &smb_ncr_list,
417 sr);
418 smb_srqueue_waitq_enter(
419 sr->session->s_srqueue);
420 sr->sr_state = SMB_REQ_STATE_CANCELED;
421 sig = B_TRUE;
422 break;
423 default:
424 ASSERT(0);
425 break;
426 }
427 mutex_exit(&sr->sr_mutex);
428 }
429 sr = tmp;
430 }
431 smb_slist_exit(&smb_nce_list);
432 smb_slist_exit(&smb_ncr_list);
433 if (sig)
434 smb_thread_signal(&smb_thread_notify_daemon);
435 }
436
437 /*
438 * smb_process_file_notify_change_queue
439 *
440 * This function traverses notify change request queue and sends
441 * cancel replies to all of requests that are related to the
442 * specified file.
443 */
444 void
smb_process_file_notify_change_queue(struct smb_ofile * of)445 smb_process_file_notify_change_queue(struct smb_ofile *of)
446 {
447 smb_request_t *sr;
448 smb_request_t *tmp;
449 boolean_t sig = B_FALSE;
450
451 smb_slist_enter(&smb_ncr_list);
452 smb_slist_enter(&smb_nce_list);
453 sr = smb_slist_head(&smb_ncr_list);
454 while (sr) {
455 ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
456 tmp = smb_slist_next(&smb_ncr_list, sr);
457 if (sr->fid_ofile == of) {
458 mutex_enter(&sr->sr_mutex);
459 switch (sr->sr_state) {
460 case SMB_REQ_STATE_WAITING_EVENT:
461 smb_slist_obj_move(&smb_nce_list,
462 &smb_ncr_list, sr);
463 smb_srqueue_waitq_enter(
464 sr->session->s_srqueue);
465 sr->sr_state = SMB_REQ_STATE_CANCELED;
466 sig = B_TRUE;
467 break;
468 default:
469 ASSERT(0);
470 break;
471 }
472 mutex_exit(&sr->sr_mutex);
473 }
474 sr = tmp;
475 }
476 smb_slist_exit(&smb_nce_list);
477 smb_slist_exit(&smb_ncr_list);
478 if (sig)
479 smb_thread_signal(&smb_thread_notify_daemon);
480 }
481
482 /*
483 * smb_reply_specific_cancel_request
484 *
485 * This function searches global request list for a specific request. If found,
486 * moves the request to event queue and kicks the notify change daemon.
487 */
488
489 void
smb_reply_specific_cancel_request(struct smb_request * zsr)490 smb_reply_specific_cancel_request(struct smb_request *zsr)
491 {
492 smb_request_t *sr;
493 smb_request_t *tmp;
494 boolean_t sig = B_FALSE;
495
496 smb_slist_enter(&smb_ncr_list);
497 smb_slist_enter(&smb_nce_list);
498 sr = smb_slist_head(&smb_ncr_list);
499 while (sr) {
500 ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
501 tmp = smb_slist_next(&smb_ncr_list, sr);
502 if ((sr->session == zsr->session) &&
503 (sr->smb_uid == zsr->smb_uid) &&
504 (sr->smb_pid == zsr->smb_pid) &&
505 (sr->smb_tid == zsr->smb_tid) &&
506 (sr->smb_mid == zsr->smb_mid)) {
507 mutex_enter(&sr->sr_mutex);
508 switch (sr->sr_state) {
509 case SMB_REQ_STATE_WAITING_EVENT:
510 smb_slist_obj_move(&smb_nce_list,
511 &smb_ncr_list, sr);
512 smb_srqueue_waitq_enter(
513 sr->session->s_srqueue);
514 sr->sr_state = SMB_REQ_STATE_CANCELED;
515 sig = B_TRUE;
516 break;
517 default:
518 ASSERT(0);
519 break;
520 }
521 mutex_exit(&sr->sr_mutex);
522 }
523 sr = tmp;
524 }
525 smb_slist_exit(&smb_nce_list);
526 smb_slist_exit(&smb_ncr_list);
527 if (sig)
528 smb_thread_signal(&smb_thread_notify_daemon);
529 }
530
531 /*
532 * smb_process_node_notify_change_queue
533 *
534 * This function searches notify change request queue and sends
535 * 'NODE MODIFIED' reply to all requests which are related to a
536 * specific node.
537 * WatchTree flag: We handle this flag in a special manner just
538 * for DAVE clients. When something is changed, we notify all
539 * requests which came from DAVE clients on the same volume which
540 * has been modified. We don't care about the tree that they wanted
541 * us to monitor. any change in any part of the volume will lead
542 * to notifying all notify change requests from DAVE clients on the
543 * different parts of the volume hierarchy.
544 */
545 void
smb_process_node_notify_change_queue(smb_node_t * node)546 smb_process_node_notify_change_queue(smb_node_t *node)
547 {
548 smb_request_t *sr;
549 smb_request_t *tmp;
550 smb_node_t *nc_node;
551 boolean_t sig = B_FALSE;
552
553 ASSERT(node->n_magic == SMB_NODE_MAGIC);
554
555 if (!(node->flags & NODE_FLAGS_NOTIFY_CHANGE))
556 return;
557
558 node->flags |= NODE_FLAGS_CHANGED;
559
560 smb_slist_enter(&smb_ncr_list);
561 smb_slist_enter(&smb_nce_list);
562 sr = smb_slist_head(&smb_ncr_list);
563 while (sr) {
564 ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
565 tmp = smb_slist_next(&smb_ncr_list, sr);
566
567 nc_node = sr->sr_ncr.nc_node;
568 if (nc_node == node) {
569 mutex_enter(&sr->sr_mutex);
570 switch (sr->sr_state) {
571 case SMB_REQ_STATE_WAITING_EVENT:
572 smb_slist_obj_move(&smb_nce_list,
573 &smb_ncr_list, sr);
574 smb_srqueue_waitq_enter(
575 sr->session->s_srqueue);
576 sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED;
577 sig = B_TRUE;
578 break;
579 default:
580 ASSERT(0);
581 break;
582 }
583 mutex_exit(&sr->sr_mutex);
584 }
585 sr = tmp;
586 }
587 smb_slist_exit(&smb_nce_list);
588 smb_slist_exit(&smb_ncr_list);
589 if (sig)
590 smb_thread_signal(&smb_thread_notify_daemon);
591 }
592
593 /*
594 * smb_notify_change_daemon
595 *
596 * This function processes notify change event list and send appropriate
597 * responses to the requests. This function executes in the system as an
598 * indivdual thread.
599 */
600 static void
smb_notify_change_daemon(smb_thread_t * thread,void * arg)601 smb_notify_change_daemon(smb_thread_t *thread, void *arg)
602 {
603 _NOTE(ARGUNUSED(arg))
604
605 smb_request_t *sr;
606 smb_request_t *tmp;
607 list_t sr_list;
608
609 list_create(&sr_list, sizeof (smb_request_t),
610 offsetof(smb_request_t, sr_ncr.nc_lnd));
611
612 while (smb_thread_continue(thread)) {
613
614 while (smb_slist_move_tail(&sr_list, &smb_nce_list)) {
615 sr = list_head(&sr_list);
616 while (sr) {
617 ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
618 tmp = list_next(&sr_list, sr);
619 list_remove(&sr_list, sr);
620 smb_reply_notify_change_request(sr);
621 sr = tmp;
622 }
623 }
624 }
625 list_destroy(&sr_list);
626 }
627