1 /* $NetBSD: smbfs_kq.c,v 1.21 2008/04/28 20:24:02 martin Exp $ */
4 * Copyright (c) 2003, 2008 The NetBSD Foundation, Inc.
7 * This code is derived from software contributed to The NetBSD Foundation
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: smbfs_kq.c,v 1.21 2008/04/28 20:24:02 martin Exp $");
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/namei.h>
38 #include <sys/kernel.h>
41 #include <sys/fcntl.h>
42 #include <sys/mount.h>
43 #include <sys/unistd.h>
44 #include <sys/vnode.h>
45 #include <sys/lockf.h>
47 #include <sys/kthread.h>
49 #include <sys/dirent.h>
51 #include <machine/limits.h>
54 #include <uvm/uvm_extern.h>
56 #include <netsmb/smb.h>
57 #include <netsmb/smb_conn.h>
58 #include <netsmb/smb_subr.h>
59 #include <netsmb/smb_rq.h>
61 #include <fs/smbfs/smbfs.h>
62 #include <fs/smbfs/smbfs_node.h>
63 #include <fs/smbfs/smbfs_subr.h>
65 #include <miscfs/genfs/genfs.h>
68 * The maximum of outstanding SMB requests is 65536, since the
69 * message id is 16bit. Don't consume all. If there is more
70 * than 30k directory notify requests, fall back to polling mode.
72 #define DNOTIFY_MAX 30000
75 SLIST_ENTRY(kevq
) kev_link
; /* link on kevlist */
76 SLIST_ENTRY(kevq
) k_link
; /* link on poll/dn list */
81 #define KEVQ_BUSY 0x01 /* currently being processed */
82 #define KEVQ_WANT 0x02 /* want to change this entry */
83 #define KEVQ_DNOT 0x04 /* kevent using NT directory change notify */
84 struct timespec omtime
; /* old modification time */
85 struct timespec octime
; /* old change time */
86 nlink_t onlink
; /* old number of references to file */
87 struct smb_rq
*rq
; /* request structure */
90 static struct lwp
*smbkql
; /* the kevent handler */
91 static struct smb_cred smbkq_scred
;
93 static kmutex_t smbkq_lock
;
94 /* guard access to k*evlist */
95 static SLIST_HEAD(, kevq
) kevlist
= SLIST_HEAD_INITIALIZER(kevlist
);
96 static SLIST_HEAD(, kevq
) kplist
= SLIST_HEAD_INITIALIZER(kplist
);
97 static SLIST_HEAD(, kevq
) kdnlist
= SLIST_HEAD_INITIALIZER(kdnlist
);
99 static int dnot_num
= 0; /* number of active dir notifications */
100 static u_int32_t kevs
;
102 static void smbfskq_dirnotify(void *);
105 * This routine periodically checks server for change
106 * of any of the watched files every SMBFS_MINATTRTIME/2 seconds.
107 * Only changes in size, modification time, change time and nlinks
108 * are being checked, everything else is ignored.
109 * Directory events are watched via NT DIRECTORY CHANGE NOTIFY
110 * if the server supports it.
112 * The routine only calls VOP_GETATTR() when it's likely it would get
113 * some new data, i.e. when the vnode expires from attrcache. This
114 * should give same result as periodically running stat(2) from userland,
115 * while keeping CPU/network usage low, and still provide proper kevent
117 * The poller thread is created when first vnode is added to watch list,
118 * and exits when the watch list is empty. The overhead of thread creation
119 * isn't really important, neither speed of attach and detach of knote.
123 smbfs_kqpoll(void *arg
)
134 mutex_enter(&smbkq_lock
);
136 /* check all entries on poll list for changes */
137 SLIST_FOREACH(ke
, &kplist
, k_link
) {
138 /* skip if still in attrcache */
139 if (smbfs_attr_cachelookup(ke
->vp
, &attr
) != ENOENT
)
143 * Mark entry busy, release lock and check
146 ke
->flags
|= KEVQ_BUSY
;
147 mutex_exit(&smbkq_lock
);
149 /* save v_size, smbfs_getattr() updates it */
150 osize
= ke
->vp
->v_size
;
152 error
= VOP_GETATTR(ke
->vp
, &attr
, l
->l_cred
);
154 /* relock and proceed with next */
155 mutex_enter(&smbkq_lock
);
159 /* following is a bit fragile, but about best
161 if (ke
->vp
->v_type
!= VDIR
&& attr
.va_size
!= osize
) {
162 int extended
= (attr
.va_size
> osize
);
163 VN_KNOTE(ke
->vp
, NOTE_WRITE
164 | (extended
? NOTE_EXTEND
: 0));
165 ke
->omtime
= attr
.va_mtime
;
166 } else if (attr
.va_mtime
.tv_sec
!= ke
->omtime
.tv_sec
167 || attr
.va_mtime
.tv_nsec
!= ke
->omtime
.tv_nsec
) {
168 VN_KNOTE(ke
->vp
, NOTE_WRITE
);
169 ke
->omtime
= attr
.va_mtime
;
172 if (attr
.va_ctime
.tv_sec
!= ke
->octime
.tv_sec
173 || attr
.va_ctime
.tv_nsec
!= ke
->octime
.tv_nsec
) {
174 VN_KNOTE(ke
->vp
, NOTE_ATTRIB
);
175 ke
->octime
= attr
.va_ctime
;
178 if (attr
.va_nlink
!= ke
->onlink
) {
179 VN_KNOTE(ke
->vp
, NOTE_LINK
);
180 ke
->onlink
= attr
.va_nlink
;
183 mutex_enter(&smbkq_lock
);
184 ke
->flags
&= ~KEVQ_BUSY
;
185 if (ke
->flags
& KEVQ_WANT
) {
186 ke
->flags
&= ~KEVQ_WANT
;
191 /* Exit if there are no more kevents to watch for */
197 /* only wake periodically if poll list is nonempty */
198 needwake
= !SLIST_EMPTY(&kplist
);
200 /* wait a while before checking for changes again */
201 if (SLIST_EMPTY(&kdnlist
)) {
202 error
= mtsleep(smbkql
, PSOCK
, "smbkqidl",
203 needwake
? (SMBFS_ATTRTIMO
* hz
/ 2) : 0,
208 /* woken up, check if any pending notifications */
209 while (!SLIST_EMPTY(&kdnlist
)) {
213 ke
= SLIST_FIRST(&kdnlist
);
214 SLIST_REMOVE_HEAD(&kdnlist
, k_link
);
215 SLIST_NEXT(ke
, k_link
) = NULL
;
218 /* drop lock while processing */
219 mutex_exit(&smbkq_lock
);
222 * Skip fetch if not yet setup.
224 if (__predict_false(ke
->rq
== NULL
))
227 error
= smbfs_smb_nt_dirnotify_fetch(ke
->rq
,
229 ke
->rq
= NULL
; /* rq deallocated by now */
232 * if there is error, switch to
233 * polling for this one
235 ke
->flags
&= KEVQ_DNOT
;
236 SLIST_INSERT_HEAD(&kplist
, ke
, k_link
);
240 VN_KNOTE(ke
->vp
, hint
);
243 /* reissue the notify request */
244 (void) smbfs_smb_nt_dirnotify_setup(
246 &ke
->rq
, &smbkq_scred
,
247 smbfskq_dirnotify
, ke
);
249 /* reacquire the lock */
250 mutex_enter(&smbkq_lock
);
254 mutex_exit(&smbkq_lock
);
260 smbfskq_dirnotify(void *arg
)
262 struct kevq
*ke
= arg
;
264 if (SLIST_NEXT(ke
, k_link
)) {
265 /* already on notify list */
269 SLIST_INSERT_HEAD(&kdnlist
, ke
, k_link
);
274 filt_smbfsdetach(struct knote
*kn
)
276 struct kevq
*ke
= (struct kevq
*)kn
->kn_hook
;
277 struct vnode
*vp
= ke
->vp
;
278 struct smb_rq
*rq
= NULL
;
280 mutex_enter(&vp
->v_interlock
);
281 SLIST_REMOVE(&ke
->vp
->v_klist
, kn
, knote
, kn_selnext
);
282 mutex_exit(&vp
->v_interlock
);
284 /* Remove the vnode from watch list */
285 mutex_enter(&smbkq_lock
);
287 /* the handler does something to it, wait */
288 while (ke
->flags
& KEVQ_BUSY
) {
289 ke
->flags
|= KEVQ_WANT
;
290 mtsleep(ke
, PSOCK
, "smbkqdw", 0, &smbkq_lock
);
293 if (ke
->usecount
> 1) {
294 /* keep, other kevents need this */
298 if (ke
->flags
& KEVQ_DNOT
) {
302 /* If on dirnotify list, remove */
303 if (SLIST_NEXT(ke
, k_link
))
304 SLIST_REMOVE(&kdnlist
, ke
, kevq
, k_link
);
306 SLIST_REMOVE(&kplist
, ke
, kevq
, k_link
);
307 SLIST_REMOVE(&kevlist
, ke
, kevq
, kev_link
);
308 kmem_free(ke
, sizeof(*ke
));
312 mutex_exit(&smbkq_lock
);
314 /* If there was request still pending, cancel it now */
316 smb_iod_removerq(rq
);
319 * Explicitly cancel the request, so that server can
320 * free directory change notify resources.
322 smbfs_smb_ntcancel(SSTOCP(rq
->sr_share
), rq
->sr_mid
,
331 filt_smbfsread(struct knote
*kn
, long hint
)
333 struct kevq
*ke
= (struct kevq
*)kn
->kn_hook
;
334 struct vnode
*vp
= ke
->vp
;
337 if (hint
== NOTE_REVOKE
) {
339 * filesystem is gone, so set the EOF flag and schedule
340 * the knote for deletion.
342 KASSERT(mutex_owned(&vp
->v_interlock
));
343 kn
->kn_flags
|= (EV_EOF
| EV_ONESHOT
);
347 /* There is no size info for directories */
349 mutex_enter(&vp
->v_interlock
);
351 KASSERT(mutex_owned(&vp
->v_interlock
));
353 if (vp
->v_type
== VDIR
) {
355 * This is kind of hackish, since we need to
356 * set the flag when we are called with the hint
357 * to make confirming call from kern_event.c
358 * succeed too, but need to unset it afterwards
359 * so that the directory wouldn't stay flagged
361 * XXX perhaps just fail for directories?
363 if (hint
& NOTE_WRITE
) {
364 kn
->kn_fflags
|= NOTE_WRITE
;
365 rv
= (1 * sizeof(struct dirent
));
366 } else if (hint
== 0 && (kn
->kn_fflags
& NOTE_WRITE
)) {
367 kn
->kn_fflags
&= ~NOTE_WRITE
;
368 rv
= (1 * sizeof(struct dirent
));
372 kn
->kn_data
= vp
->v_size
- ((file_t
*)kn
->kn_obj
)->f_offset
;
373 rv
= (kn
->kn_data
!= 0);
376 mutex_enter(&vp
->v_interlock
);
383 filt_smbfsvnode(struct knote
*kn
, long hint
)
385 struct kevq
*ke
= (struct kevq
*)kn
->kn_hook
;
386 struct vnode
*vp
= ke
->vp
;
391 KASSERT(mutex_owned(&vp
->v_interlock
));
392 kn
->kn_flags
|= EV_EOF
;
393 if ((kn
->kn_sfflags
& hint
) != 0)
394 kn
->kn_fflags
|= hint
;
397 mutex_enter(&vp
->v_interlock
);
398 fflags
= kn
->kn_fflags
;
399 mutex_exit(&vp
->v_interlock
);
402 KASSERT(mutex_owned(&vp
->v_interlock
));
403 if ((kn
->kn_sfflags
& hint
) != 0)
404 kn
->kn_fflags
|= hint
;
405 fflags
= kn
->kn_fflags
;
409 return (kn
->kn_fflags
!= 0);
412 static const struct filterops smbfsread_filtops
=
413 { 1, NULL
, filt_smbfsdetach
, filt_smbfsread
};
414 static const struct filterops smbfsvnode_filtops
=
415 { 1, NULL
, filt_smbfsdetach
, filt_smbfsvnode
};
418 smbfs_kqfilter(void *v
)
420 struct vop_kqfilter_args
/* {
424 struct vnode
*vp
= ap
->a_vp
;
425 struct knote
*kn
= ap
->a_kn
;
426 struct kevq
*ke
, *ken
;
429 struct lwp
*l
= curlwp
; /* XXX */
431 struct smb_vc
*vcp
= SSTOVC(VTOSMB(vp
)->n_mount
->sm_share
);
434 switch (kn
->kn_filter
) {
436 kn
->kn_fop
= &smbfsread_filtops
;
439 kn
->kn_fop
= &smbfsvnode_filtops
;
445 /* Find out if we can use directory change notify for this file */
446 dnot
= (vp
->v_type
== VDIR
447 && (SMB_CAPS(vcp
) & SMB_CAP_NT_SMBS
)
448 && dnot_num
< DNOTIFY_MAX
);
451 * Put the vnode to watched list.
456 * Fetch current attributes. It's only needed when the vnode
457 * is not watched yet, but we need to do this without lock
458 * held. This is likely cheap due to attrcache, so do it now.
460 memset(&attr
, 0, sizeof(attr
));
461 (void) VOP_GETATTR(vp
, &attr
, l
->l_cred
);
463 /* ensure the handler is running */
464 /* XXX this is unreliable. */
466 mutex_init(&smbkq_lock
, MUTEX_DEFAULT
, IPL_NONE
);
470 error
= kthread_create(PRI_NONE
, 0, NULL
, smbfs_kqpoll
,
471 NULL
, &smbkql
, "smbkq");
472 smb_makescred(&smbkq_scred
, smbkql
, smbkql
->l_cred
);
480 * Allocate new kev. It's more probable it will be needed,
481 * and the malloc is cheaper than scanning possibly
482 * large kevlist list second time after malloc.
484 ken
= kmem_alloc(sizeof(*ken
), KM_SLEEP
);
486 /* Check the list and insert new entry */
487 mutex_enter(&smbkq_lock
);
488 SLIST_FOREACH(ke
, &kevlist
, kev_link
) {
494 /* already watched, so just bump usecount */
496 kmem_free(ken
, sizeof(*ken
));
499 memset(ken
, 0, sizeof(*ken
));
503 ke
->flags
= (dnot
) ? KEVQ_DNOT
: 0;
504 ke
->omtime
= attr
.va_mtime
;
505 ke
->octime
= attr
.va_ctime
;
506 ke
->onlink
= attr
.va_nlink
;
512 * Add kevent to list of 'need attend' kevnets.
513 * The handler will pick it up and setup request
517 SLIST_INSERT_HEAD(&kdnlist
, ke
, k_link
);
521 /* add to poll list */
522 SLIST_INSERT_HEAD(&kplist
, ke
, k_link
);
525 SLIST_INSERT_HEAD(&kevlist
, ke
, kev_link
);
527 /* kick the handler */
531 mutex_enter(&vp
->v_interlock
);
532 SLIST_INSERT_HEAD(&vp
->v_klist
, kn
, kn_selnext
);
534 mutex_exit(&vp
->v_interlock
);
536 mutex_exit(&smbkq_lock
);