FPPS4/sys/fs/nullfs/null_subr.pas

373 lines
7.6 KiB
Plaintext

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.