unit vnode_pager;

{$mode ObjFPC}{$H+}
{$CALLING SysV_ABI_CDecl}

interface

uses
 vnode,
 vm,
 vmparam,
 sys_vm_object;

function  vnode_pager_alloc(handle:Pointer;
                            size:vm_ooffset_t;
                            prot:vm_prot_t;
                            offset:vm_ooffset_t):vm_object_t;

function  vnode_create_vobject(vp:p_vnode;isize:vm_ooffset_t):Integer;

procedure vnode_destroy_vobject(vp:p_vnode);

procedure vnode_pager_dealloc(obj:vm_object_t);

procedure vnode_pager_setsize(vp:p_vnode;nsize:vm_ooffset_t);

implementation

uses
 vnode_if,
 vfs_subr,
 vfs_vnops,
 systm,
 kern_param,
 kern_mtx;

//

procedure vm_object_terminate(obj:vm_object_t); external;
procedure vm_pager_deallocate(obj:vm_object_t); external;
procedure vm_object_pip_wait(obj:vm_object_t;waitid:pchar); external;

//

function IDX_TO_OFF(x:QWORD):QWORD; inline;
begin
 Result:=QWORD(x) shl PAGE_SHIFT;
end;

function OFF_TO_IDX(x:QWORD):QWORD; inline;
begin
 Result:=QWORD(x) shr PAGE_SHIFT;
end;

{
 * Allocate (or lookup) pager for a vnode.
 * Handle is a vnode pointer.
 *
 * MPSAFE
 }

function vnode_pager_alloc(handle:Pointer;
                           size:vm_ooffset_t;
                           prot:vm_prot_t;
                           offset:vm_ooffset_t):vm_object_t;
label
 retry;
var
 obj:vm_object_t;
 vp:p_vnode;
begin
 {
  * Pageout to vnode, no can do yet.
  }
 if (handle=nil) then Exit(nil);

 vp:=handle;

 {
  * If the obj is being terminated, wait for it to
  * go away.
  }
retry:
 obj:=vp^.v_object;
 while (obj<>nil) do
 begin
  VM_OBJECT_LOCK(obj);
  if ((obj^.flags and OBJ_DEAD)=0) then
  begin
   break;
  end;
  vm_object_set_flag(obj, OBJ_DISCONNECTWNT);
  msleep(obj, VM_OBJECT_MTX(obj), PDROP or PVM, 'vadead', 0);
  obj:=vp^.v_object;
 end;

 Assert(vp^.v_usecount<>0, 'vnode_pager_alloc: no vnode reference');

 if (obj=nil) then
 begin
  {
   * Add an obj of the appropriate size
   }
  obj:=vm_object_allocate(OBJT_VNODE, OFF_TO_IDX(round_page(size)));

  obj^.un_pager.vnp.vnp_size:=size;
  obj^.un_pager.vnp.writemappings:=0;

  obj^.handle:=handle;

  VI_LOCK(vp);
  if (vp^.v_object<>nil) then
  begin
   // Obj has been created while we were sleeping
   VI_UNLOCK(vp);
   VM_OBJECT_LOCK(obj);
   Assert(obj^.ref_count=1, 'leaked ref %p %d');

   obj^.otype:=OBJT_DEAD;
   obj^.ref_count:=0;
   VM_OBJECT_UNLOCK(obj);
   vm_object_destroy(obj);
   goto retry;
  end;

  vp^.v_object:=obj;
  VI_UNLOCK(vp);
 end else
 begin
  Inc(obj^.ref_count);
  VM_OBJECT_UNLOCK(obj);
 end;

 vref(vp);
 Exit(obj);
end;

{ Create the VM system backing object for this vnode }
function vnode_create_vobject(vp:p_vnode;isize:vm_ooffset_t):Integer;
var
 obj:vm_object_t;
 size:QWORD;
 va:t_vattr;
begin
 size:=isize;

 if (not vn_isdisk(vp, nil)) and (vn_canvmio(vp)=FALSE) then
 begin
  Exit(0);
 end;

 obj:=vp^.v_object;
 while (obj<>nil) do
 begin
  VM_OBJECT_LOCK(obj);
  if ((obj^.flags and OBJ_DEAD)=0) then
  begin
   VM_OBJECT_UNLOCK(obj);
   Exit(0);
  end;
  VOP_UNLOCK(vp, 0);
  vm_object_set_flag(obj, OBJ_DISCONNECTWNT);
  msleep(obj, VM_OBJECT_MTX(obj), PDROP or PVM, 'vodead', 0);
  vn_lock(vp, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  //
  obj:=vp^.v_object;
 end;

 if (size=0) then
 begin
  if (vn_isdisk(vp, nil)) then
  begin
   size:=IDX_TO_OFF(High(Integer));
  end else
  begin
   if (VOP_GETATTR(vp, @va)<>0) then
   begin
    Exit(0);
   end;
   size:=va.va_size;
  end;
 end;

 obj:=vnode_pager_alloc(vp, size, 0, 0);

 {
  * Dereference the reference we just created.  This assumes
  * that the obj is associated with the vp.
  }
 VM_OBJECT_LOCK(obj);
  Dec(obj^.ref_count);
 VM_OBJECT_UNLOCK(obj);

 vrele(vp);

 Assert(vp^.v_object<>nil, 'vnode_create_vobject: nil obj');

 Exit(0);
end;

procedure vnode_destroy_vobject(vp:p_vnode);
var
 obj:vm_object_t;
begin
 obj:=vp^.v_object;
 if (obj=nil) then Exit;

 ASSERT_VOP_ELOCKED(vp, 'vnode_destroy_vobject');

 VM_OBJECT_LOCK(obj);
 if (obj^.ref_count=0) then
 begin
  {
   * vclean() may be called twice. The first time
   * removes the primary reference to the object,
   * the second time goes one further and is a
   * special-case to terminate the object.
   *
   * don't double-terminate the object
   }
  if ((obj^.flags and OBJ_DEAD)=0) then
   vm_object_terminate(obj)
  else
   VM_OBJECT_UNLOCK(obj);

 end else
 begin
  {
   * Woe to the process that tries to page now :-).
   }
  vm_pager_deallocate(obj);
  VM_OBJECT_UNLOCK(obj);
 end;
 vp^.v_object:=nil;
end;


{
 * The object must be locked.
 }
procedure vnode_pager_dealloc(obj:vm_object_t);
var
 vp:p_vnode;
 refs:Integer;
begin
 vp:=obj^.handle;

 if (vp=nil) then
 begin
  Writeln(StdErr,'vnode_pager_dealloc: pager already dealloced');
  Exit;
 end;

 VM_OBJECT_LOCK_ASSERT(obj);
 vm_object_pip_wait(obj, 'vnpdea');
 refs:=obj^.ref_count;

 obj^.handle:=nil;
 obj^.otype:=OBJT_DEAD;

 if ((obj^.flags and OBJ_DISCONNECTWNT)<>0) then
 begin
  vm_object_clear_flag(obj, OBJ_DISCONNECTWNT);
  wakeup(obj);
 end;

 ASSERT_VOP_ELOCKED(vp, 'vnode_pager_dealloc');

 if (obj^.un_pager.vnp.writemappings > 0) then
 begin
  obj^.un_pager.vnp.writemappings:=0;
  VOP_ADD_WRITECOUNT(vp, -1);
 end;
 vp^.v_object:=nil;
 //VOP_UNSET_TEXT(vp);
 VM_OBJECT_UNLOCK(obj);

 while (refs>0) do
 begin
  vunref(vp);
  Dec(refs);
 end;

 VM_OBJECT_LOCK(obj);
end;

{
 Lets the VM system know about a change in size for a file.
 We adjust our own internal size and flush any cached pages in
 the associated object that are affected by the size change.

 Note: this routine may be invoked as a result of a pager put
 operation (possibly at object termination time), so we must be careful.
}
procedure vnode_pager_setsize(vp:p_vnode;nsize:vm_ooffset_t);
var
 obj:vm_object_t;
 nobjsize:DWORD;
begin
 obj:=vp^.v_object;
 if (obj=nil) then Exit;

 nobjsize:=OFF_TO_IDX(nsize + PAGE_MASK);

 VM_OBJECT_LOCK(obj);

 if (obj^.otype=OBJT_DEAD) then
 begin
  VM_OBJECT_UNLOCK(obj);
  Exit;
 end;

 Assert(obj^.otype=OBJT_VNODE,'not vnode-backed obj %p');

 obj^.un_pager.vnp.vnp_size:=nsize;
 obj^.size:=nobjsize;

 VM_OBJECT_UNLOCK(obj);
end;


end.



