unit null_subr; {$mode ObjFPC}{$H+} {$CALLING SysV_ABI_CDecl} interface uses vmount, vnode, vfs_default, nullfs; { * null layer cache: * Each cache entry holds a reference to the lower vnode * along with a pointer to the alias vnode. When an * entry is added the lower vnode is VREF'd. When the * alias is removed the lower vnode is vrele'd. } function nullfs_init(vfsp:p_vfsconf):Integer; function nullfs_uninit(vfsp:p_vfsconf):Integer; function null_hashget(mp:p_mount;lowervp:p_vnode):p_vnode; function null_hashins(mp:p_mount;xp:p_null_node):p_vnode; procedure null_destroy_proto(vp:p_vnode;xp:Pointer); procedure null_insmntque_dtr(vp:p_vnode;xp:Pointer); function null_nodeget(mp:p_mount;lowervp:p_vnode;vpp:pp_vnode):Integer; procedure null_hashrem(xp:p_null_node); implementation uses hamt, mqueue, errno, vfs_subr, vfs_vnops, dead_vnops, vnode_if, kern_mtx; // var null_vnodeops:vop_vector; external; // var null_node_hashtbl:TSTUB_HAMT32; null_hashmtx:mtx; function MOUNTTONULLMOUNT(mp:p_mount):p_null_mount; inline; begin Result:=mp^.mnt_data; end; function VTONULL(vp:p_vnode):p_null_node; inline; begin Result:=vp^.v_data; end; function NULLTOV(xp:p_null_node):p_vnode; inline; begin Result:=xp^.null_vnode; end; function vfs_hash_index(vp:p_vnode):DWORD; begin if (vp=nil) then Exit(0); Result:=(vp^.v_hash + p_mount(vp^.v_mount)^.mnt_hashseed); end; function NULL_NHASH(vp:p_vnode;force:Boolean):Pointer; var data:PPointer; begin Result:=nil; data:=HAMT_search32(@null_node_hashtbl,vfs_hash_index(vp)); if (data<>nil) then begin Result:=data^; end else if force then begin Result:=AllocMem(SizeOf(LIST_HEAD)); if (Result=nil) then Exit; data:=HAMT_insert32(@null_node_hashtbl,vfs_hash_index(vp),Result); if (data=nil) then begin FreeMem(Result); Result:=nil; end else if (data^<>Result) then begin FreeMem(Result); Result:=data^; end; end; end; { * Initialise cache headers } function nullfs_init(vfsp:p_vfsconf):Integer; begin FillChar(null_node_hashtbl,SizeOf(null_node_hashtbl),0); mtx_init(null_hashmtx, 'nullhs'); Exit(0); end; procedure free_hash_data_cb(data,userdata:Pointer); register; begin if (data<>nil) then begin FreeMem(data); end; end; function nullfs_uninit(vfsp:p_vfsconf):Integer; begin mtx_destroy(null_hashmtx); HAMT_clear32(@null_node_hashtbl,@free_hash_data_cb,nil); Exit(0); end; { * Exita VREF'ed alias for lower vnode if already exists, else 0. * Lower vnode should be locked on entry and will be left locked on exit. } function null_hashget(mp:p_mount;lowervp:p_vnode):p_vnode; var hd:Pointer; a:p_null_node; vp:p_vnode; begin ASSERT_VOP_LOCKED(lowervp, 'nil_hashget'); { * Find hash base, and then search the (two-way) linked * list looking for a nil_node structure which is referencing * the lower vnode. If found, the increment the nil_node * reference count (but NOT the lower vnode's VREF counter). } mtx_lock(null_hashmtx); hd:=NULL_NHASH(lowervp,false); if (hd=nil) then begin mtx_unlock(null_hashmtx); Exit(nil); end; a:=LIST_FIRST(hd); while (a<>nil) do begin if (a^.null_lowervp=lowervp) and (NULLTOV(a)^.v_mount=mp) then begin { * Since we have the lower node locked the nullfs * node can not be in the process of recycling. If * it had been recycled before we grabed the lower * lock it would not have been found on the hash. } vp:=NULLTOV(a); vref(vp); mtx_unlock(null_hashmtx); Exit(vp); end; a:=LIST_NEXT(a,@a^.null_hash); end; mtx_unlock(null_hashmtx); Exit(nil); end; { * Act like nil_hashget, but add passed null_node to hash if no existing * node found. } function null_hashins(mp:p_mount;xp:p_null_node):p_vnode; var hd:Pointer; oxp:p_null_node; ovp:p_vnode; begin mtx_lock(null_hashmtx); hd:=NULL_NHASH(xp^.null_lowervp,true); if (hd=nil) then begin mtx_unlock(null_hashmtx); Exit(nil); end; oxp:=LIST_FIRST(hd); while (oxp<>nil) do begin if (oxp^.null_lowervp=xp^.null_lowervp) and (NULLTOV(oxp)^.v_mount=mp) then begin { * See nil_hashget for a description of this * operation. } ovp:=NULLTOV(oxp); vref(ovp); mtx_unlock(null_hashmtx); Exit(ovp); end; oxp:=LIST_NEXT(oxp,@oxp^.null_hash); end; LIST_INSERT_HEAD(hd, xp,@xp^.null_hash); mtx_unlock(null_hashmtx); Exit(nil); end; procedure null_destroy_proto(vp:p_vnode;xp:Pointer); begin lockmgr(@vp^.v_lock, LK_EXCLUSIVE, nil); VI_LOCK(vp); vp^.v_data :=nil; vp^.v_vnlock:=@vp^.v_lock; vp^.v_op :=@dead_vnodeops; VI_UNLOCK(vp); vgone(vp); vput(vp); FreeMem(xp); end; procedure null_insmntque_dtr(vp:p_vnode;xp:Pointer); begin vput(p_null_node(xp)^.null_lowervp); null_destroy_proto(vp, xp); end; { * Make a new or get existing nullfs node. * Vp is the alias vnode, lowervp is the lower vnode. * * The lowervp assumed to be locked and having 'spare' reference. This routine * vrele lowervp if nullfs node was taken from hash. Otherwise it 'transfers' * the caller's 'spare' reference to created nullfs vnode. } function null_nodeget(mp:p_mount;lowervp:p_vnode;vpp:pp_vnode):Integer; var xp:p_null_node; vp:p_vnode; error:Integer; begin ASSERT_VOP_LOCKED(lowervp, 'lowervp'); if (lowervp<>nil) then begin Assert(lowervp^.v_usecount >= 1,'Unreferenced vnode %p'); end; { Lookup the hash firstly. } vpp^:=null_hashget(mp, lowervp); if (vpp^<>nil) then begin vrele(lowervp); Exit(0); end; { * The insmntque1() call below requires the exclusive lock on * the nullfs vnode. Upgrade the lock now if hash failed to * provide ready to use vnode. } if (lowervp<>nil) then if (VOP_ISLOCKED(lowervp)<>LK_EXCLUSIVE) then begin Assert((MOUNTTONULLMOUNT(mp)^.nullm_flags and NULLM_CACHE)<>0,'lowervp %p is not excl locked and cache is disabled'); vn_lock(lowervp, LK_UPGRADE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%}); if ((lowervp^.v_iflag and VI_DOOMED)<>0) then begin vput(lowervp); Exit(ENOENT); end; end; { * We do not serialize vnode creation, instead we will check for * duplicates later, when adding new vnode to hash. * Note that duplicate can only appear in hash if the lowervp is * locked LK_SHARED. * * Do the MALLOC before the getnewvnode since doing so afterward * might cause a bogus v_data pointer to get dereferenced * elsewhere if MALLOC should block. } xp:=AllocMem(sizeof(t_null_node)); error:=getnewvnode('nil', mp, @null_vnodeops, @vp); if (error<>0) then begin vput(lowervp); FreeMem(xp); Exit(error); end; xp^.null_vnode :=vp; xp^.null_lowervp:=lowervp; xp^.null_flags :=0; vp^.v_data :=xp; if (lowervp=nil) then begin vp^.v_type :=VDIR; vp^.v_vnlock:=@vp^.v_lock; end else begin vp^.v_type :=lowervp^.v_type; vp^.v_vnlock:=lowervp^.v_vnlock; end; error:=insmntque1(vp, mp, @null_insmntque_dtr, xp); if (error<>0) then Exit(error); { * Atomically insert our new node into the hash or vget existing * if someone else has beaten us to it. } vpp^:=null_hashins(mp, xp); if (vpp^<>nil) then begin vrele(lowervp); null_destroy_proto(vp, xp); Exit(0); end; vpp^:=vp; Exit(0); end; { * Remove node from hash. } procedure null_hashrem(xp:p_null_node); var hd:Pointer; idx:DWORD; data:PPointer; begin mtx_lock(null_hashmtx); LIST_REMOVE(xp,@xp^.null_hash); if (xp^.null_lowervp^.v_mount<>nil) then begin idx:=vfs_hash_index(xp^.null_lowervp); data:=HAMT_search32(@null_node_hashtbl,idx); if (data<>nil) then begin hd:=data^; if LIST_EMPTY(hd) then if HAMT_delete32(@null_node_hashtbl,idx,nil) then begin FreeMem(hd); end; end; end; mtx_unlock(null_hashmtx); end; end.