unit vfs_default;

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

interface

uses
 sysutils,
 kern_param,
 vnode,
 vnode_if,
 vdirent,
 vuio,
 vfile,
 vmount,
 vfs_mount,
 vnamei,
 vfcntl,
 vpoll,
 kern_thr,
 kern_mtx;

function vop_eopnotsupp(ap:Pointer):Integer;
function vop_ebadf(ap:Pointer):Integer;
function vop_enotty(ap:Pointer):Integer;
function vop_einval(ap:Pointer):Integer;
function vop_enoent(ap:Pointer):Integer;
function VOP_NULL(ap:Pointer):Integer;
function vop_panic(ap:Pointer):Integer;
function vop_nolookup(ap:p_vop_lookup_args):Integer;
function vop_norename(ap:p_vop_rename_args):Integer;
function vop_nostrategy(ap:p_vop_strategy_args):Integer;

function get_next_dirent(vp:p_vnode;
                         dpp:PPointer;
                         dirbuf:PByte;
                         dirbuflen:Integer;
                         off:PQWORD;
                         cpos:PPByte;
                         len:PInteger;
                         eofflag:PInteger):Integer;

function dirent_exists(vp:p_vnode;dirname:PChar):Integer;
function vop_stdaccess(ap:p_vop_access_args):Integer;
function vop_stdaccessx(ap:p_vop_accessx_args):Integer;
function vop_stdadvlock(ap:p_vop_advlock_args):Integer;
function vop_stdadvlockasync(ap:p_vop_advlockasync_args):Integer;
function vop_stdadvlockpurge(ap:p_vop_advlockpurge_args):Integer;
function vop_stdpathconf(ap:p_vop_pathconf_args):Integer;
function lockmgr(lk:p_mtx;flags:Integer;ilk:p_mtx):Integer;
function vop_stdlock(ap:p_vop_lock1_args):Integer;
function vop_stdunlock(ap:p_vop_unlock_args):Integer;
function vop_stdislocked(ap:p_vop_islocked_args):Integer;
function vop_nopoll(ap:p_vop_poll_args):Integer;
function vop_stdpoll(ap:p_vop_poll_args):Integer;
function vop_stdgetwritemount(ap:p_vop_getwritemount_args):Integer;
function vop_stdbmap(ap:p_vop_bmap_args):Integer;
function vop_stdfsync(ap:p_vop_fsync_args):Integer;
function vop_stdgetpages(ap:p_vop_getpages_args):Integer;
function vop_stdkqfilter(ap:p_vop_kqfilter_args):Integer;
function vop_stdputpages(ap:p_vop_putpages_args):Integer;
function vop_stdvptofh(ap:p_vop_vptofh_args):Integer;
function vop_stdvptocnp(ap:p_vop_vptocnp_args):Integer;
function vop_stdallocate(ap:p_vop_allocate_args):Integer;
function vop_stdunp_bind(ap:p_vop_unp_bind_args):Integer;
function vop_stdunp_connect(ap:p_vop_unp_connect_args):Integer;
function vop_stdunp_detach(ap:p_vop_unp_detach_args):Integer;

function vfs_stdroot(mp:p_mount;flags:Integer;vpp:pp_vnode):Integer;
function vfs_stdstatfs(mp:p_mount;sbp:p_statfs):Integer;
function vfs_stdquotactl(mp:p_mount;cmds:Integer;uid:uid_t;arg:Pointer):Integer;
function vfs_stdsync(mp:p_mount;waitfor:Integer):Integer;
function vfs_stdnosync(mp:p_mount;waitfor:Integer):Integer;
function vfs_stdvget(mp:p_mount;ino:DWORD;flags:Integer;vpp:pp_vnode):Integer;
function vfs_stdfhtovp(mp:p_mount;fhp:p_fid;flags:Integer;vpp:pp_vnode) :Integer;
function vfs_stdinit(vfsp:p_vfsconf):Integer;
function vfs_stduninit(vfsp:p_vfsconf):Integer;
function vfs_stdextattrctl(mp:p_mount;cmd:Integer;filename_vp:p_vnode;attrnamespace:Integer;attrname:PChar):Integer;
function vfs_stdsysctl(mp:p_mount;op:Integer;req:Pointer):Integer;

const
 DIRENT_MINSIZE=SizeOf(t_dirent)-(t_dirent.MAXNAMLEN+1)+4;

{
 * This vnode table stores what we want to do if the filesystem doesn't
 * implement a particular VOP.
 *
 * If there is no specific entry here, we will return EOPNOTSUPP.
 *
 * Note that every filesystem has to implement either vop_access
 * or vop_accessx; failing to do so will result in immediate crash
 * due to stack overflow, as vop_stdaccess() calls vop_stdaccessx(),
 * which calls vop_stdaccess() etc.
 }

const
 default_vnodeops:vop_vector=(
  vop_default       :nil                   ;
  vop_bypass        :@VOP_EOPNOTSUPP       ;

  vop_islocked      :@vop_stdislocked      ;
  vop_lookup        :@vop_nolookup         ;
  vop_create        :nil                   ;
  vop_whiteout      :nil                   ;
  vop_mknod         :nil                   ;
  vop_open          :@VOP_NULL             ;
  vop_close         :@VOP_NULL             ;
  vop_access        :@vop_stdaccess        ;
  vop_accessx       :@vop_stdaccessx       ;
  vop_getattr       :nil                   ;
  vop_setattr       :nil                   ;
  vop_markatime     :nil                   ;
  vop_read          :nil                   ;
  vop_write         :nil                   ;
  vop_ioctl         :@VOP_ENOTTY           ;
  vop_poll          :@vop_nopoll           ;
  vop_kqfilter      :@vop_stdkqfilter      ;
  vop_revoke        :@VOP_PANIC            ;
  vop_fsync         :@VOP_NULL             ;
  vop_remove        :nil                   ;
  vop_link          :nil                   ;
  vop_rename        :@vop_norename         ;
  vop_mkdir         :nil                   ;
  vop_rmdir         :nil                   ;
  vop_symlink       :nil                   ;
  vop_readdir       :nil                   ;
  vop_readlink      :@VOP_EINVAL           ;
  vop_inactive      :@VOP_NULL             ;
  vop_reclaim       :nil                   ;
  vop_lock1         :@vop_stdlock          ;
  vop_unlock        :@vop_stdunlock        ;
  vop_bmap          :@vop_stdbmap          ;
  vop_strategy      :@vop_nostrategy       ;
  vop_getwritemount :@vop_stdgetwritemount ;
  vop_print         :nil                   ;
  vop_pathconf      :@VOP_EINVAL           ;
  vop_advlock       :@vop_stdadvlock       ;
  vop_advlockasync  :@vop_stdadvlockasync  ;
  vop_advlockpurge  :@vop_stdadvlockpurge  ;
  vop_reallocblks   :nil                   ;
  vop_getpages      :@vop_stdgetpages      ;
  vop_putpages      :@vop_stdputpages      ;
  vop_vptofh        :@vop_stdvptofh        ;
  vop_vptocnp       :@vop_stdvptocnp       ;
  vop_allocate      :@vop_stdallocate      ;
  vop_unp_bind      :@vop_stdunp_bind      ;
  vop_unp_connect   :@vop_stdunp_connect   ;
  vop_unp_detach    :@vop_stdunp_detach    ;
);

implementation

uses
 errno,
 vfs_subr,
 vfs_vnops,
 vsys_generic;

{
 * Series of placeholder functions for various error returns for
 * VOPs.
 }

function vop_eopnotsupp(ap:Pointer):Integer;
begin
 Exit(EOPNOTSUPP);
end;

function vop_ebadf(ap:Pointer):Integer;
begin
 Exit(EBADF);
end;

function vop_enotty(ap:Pointer):Integer;
begin
 Exit(ENOTTY);
end;

function vop_einval(ap:Pointer):Integer;
begin
 Exit(EINVAL);
end;

function vop_enoent(ap:Pointer):Integer;
begin
 Exit(ENOENT);
end;

function VOP_NULL(ap:Pointer):Integer;
begin
 Exit(0);
end;

{
 * Helper function to panic on some bad VOPs in some filesystems.
 }
function vop_panic(ap:Pointer):Integer;
begin
 Assert(false,'filesystem goof: vop_panic[%s]');
 Exit(ENOENT);
end;

{
 * vop_std<something> and vop_no<something> are default functions for use by
 * filesystems that need the 'default reasonable' implementation for a
 * particular operation.
 *
 * The documentation for the operations they implement exists (if it exists)
 * in the VOP_<SOMETHING>(9) manpage (all uppercase).
 }

{
 * Default vop for filesystems that do not support name lookup
 }
function vop_nolookup(ap:p_vop_lookup_args):Integer;
begin
 ap^.a_vpp^:=nil;
 Exit(ENOTDIR);
end;

{
 * vop_norename:
 *
 * Handle unlock and reference counting for arguments of vop_rename
 * for filesystems that do not implement rename operation.
 }
function vop_norename(ap:p_vop_rename_args):Integer;
begin
 vop_rename_fail(ap);
 Exit(EOPNOTSUPP);
end;

{
 * vop_nostrategy:
 *
 * Strategy routine for VFS devices that have none.
 *
 * BIO_ERROR and B_INVAL must be cleared prior to calling any strategy
 * routine.  Typically this is done for a BIO_READ strategy call.
 * Typically B_INVAL is assumed to already be clear prior to a write
 * and should not be cleared manually unless you just made the buffer
 * invalid.  BIO_ERROR should be cleared either way.
 }

function vop_nostrategy(ap:p_vop_strategy_args):Integer;
begin
 Writeln('No strategy for buffer at %p');
 //ap^.a_bp^.b_ioflags:=ap^.a_bp^.b_ioflags or BIO_ERROR;
 //ap^.a_bp^.b_error:=EOPNOTSUPP;
 //bufdone(ap^.a_bp);
 Exit(EOPNOTSUPP);
end;

function get_next_dirent(vp:p_vnode;
                         dpp:PPointer;
                         dirbuf:PByte;
                         dirbuflen:Integer;
                         off:PQWORD;
                         cpos:PPByte;
                         len:PInteger;
                         eofflag:PInteger):Integer;
var
 error, reclen:Integer;
 uio:t_uio;
 iov:iovec;
 dp:p_dirent;
begin
 Assert(VOP_ISLOCKED(vp)<>0,'vp %p is not locked');
 Assert(vp^.v_type=VDIR,'vp %p is not a directory');

 if (len^=0) then
 begin
  iov.iov_base:=dirbuf;
  iov.iov_len:=dirbuflen;

  uio.uio_iov   :=@iov;
  uio.uio_iovcnt:=1;
  uio.uio_offset:=off^;
  uio.uio_resid :=dirbuflen;
  uio.uio_segflg:=UIO_SYSSPACE;
  uio.uio_rw    :=UIO_READ;
  uio.uio_td    :=curkthread;

  eofflag^:=0;


  //error:=mac_vnode_check_readdir(td^.td_ucred, vp);
  //if (error=0) then

  error:=VOP_READDIR(vp, @uio, eofflag, nil, nil);
  if (error<>0) then Exit(error);

  off^:=uio.uio_offset;

  cpos^:=dirbuf;
  len^:=(dirbuflen - uio.uio_resid);

  if (len^=0) then
   Exit(ENOENT);
 end;

 dp:=p_dirent(cpos^);
 reclen:=dp^.d_reclen;
 dpp^:=dp;

 { check for malformed directory.. }
 if (reclen < DIRENT_MINSIZE) then
  Exit(EINVAL);

 cpos^:=cpos^+reclen;
 len^:=len^-reclen;

 Exit(0);
end;

{
 * Check if a named file exists in a given directory vnode.
 }
function dirent_exists(vp:p_vnode;dirname:PChar):Integer;
label
 _out;
var
 dirbuf,cpos:PByte;
 error, eofflag, dirbuflen, len, found:Integer;
 off:QWORD;
 dp:p_dirent;
 va:t_vattr;
begin
 Assert(VOP_ISLOCKED(vp)<>0, 'vp %p is not locked');
 Assert(vp^.v_type=VDIR, 'vp %p is not a directory');

 found:=0;

 error:=VOP_GETATTR(vp, @va);
 if (error<>0) then
  Exit(found);

 dirbuflen:=DEV_BSIZE;
 if (dirbuflen < va.va_blocksize) then
  dirbuflen:=va.va_blocksize;

 dirbuf:=AllocMem(dirbuflen);

 off:=0;
 len:=0;
 repeat
  error:=get_next_dirent(vp, @dp, dirbuf, dirbuflen, @off, @cpos, @len, @eofflag);
  if (error<>0) then
   goto _out;

  if (dp^.d_type<>DT_WHT) and
     (Strcomp(PChar(@dp^.d_name),PChar(dirname))=0) then
  begin
   found:=1;
   goto _out;
  end;
 until not ((len>0) or (eofflag=0));

_out:
 FreeMem(dirbuf);
 Exit(found);
end;

function vop_stdaccess(ap:p_vop_access_args):Integer;
begin
 Assert((ap^.a_accmode and (not (VEXEC or VWRITE or VREAD or VADMIN or VAPPEND)))=0, ('invalid bit in accmode'));

 Exit(VOP_ACCESSX(ap^.a_vp, ap^.a_accmode));
end;

function vop_stdaccessx(ap:p_vop_accessx_args):Integer;
var
 error:Integer;
 accmode:accmode_t;
begin
 accmode:=ap^.a_accmode;

 error:=vfs_unixify_accmode(@accmode);
 if (error<>0) then
  Exit(error);

 if (accmode=0) then
  Exit(0);

 Exit(VOP_ACCESS(ap^.a_vp, accmode));
end;

{
 * Advisory record locking support
 }
function vop_stdadvlock(ap:p_vop_advlock_args):Integer;
var
 vp:p_vnode;
 vattr:t_vattr;
 error:Integer;
begin
 vp:=ap^.a_vp;
 if (ap^.a_fl^.l_whence=SEEK_END) then
 begin
  {
   * The NFSv4 server must avoid doing a vn_lock() here, since it
   * can deadlock the nfsd threads, due to a LOR.  Fortunately
   * the NFSv4 server always uses SEEK_SET and this code is
   * only required for the SEEK_END case.
   }
  vn_lock(vp, LK_SHARED or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  error:=VOP_GETATTR(vp, @vattr);
  VOP_UNLOCK(vp, 0);
  if (error<>0) then
   Exit(error);
 end else
  vattr.va_size:=0;

 Exit(EOPNOTSUPP);
 //Exit(lf_advlock(ap, @(vp^.v_lockf), vattr.va_size));
end;

function vop_stdadvlockasync(ap:p_vop_advlockasync_args):Integer;
var
 vp:p_vnode;
 vattr:t_vattr;
 error:Integer;
begin
 vp:=ap^.a_vp;
 if (ap^.a_fl^.l_whence=SEEK_END) then
 begin
  { The size argument is only needed for SEEK_END. }
  vn_lock(vp, LK_SHARED or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  error:=VOP_GETATTR(vp, @vattr);
  VOP_UNLOCK(vp, 0);
  if (error<>0) then
  begin
   Exit(error);
  end;
 end else
 begin
  vattr.va_size:=0;
 end;

 Exit(EOPNOTSUPP);
 //Exit(lf_advlockasync(ap, @(vp^.v_lockf), vattr.va_size));
end;

function vop_stdadvlockpurge(ap:p_vop_advlockpurge_args):Integer;
//var
// vp:p_vnode;
begin
 //vp:=ap^.a_vp;

 Exit(EOPNOTSUPP);
 //lf_purgelocks(vp, @vp^.v_lockf);
 //Exit(0);
end;

{
 * vop_stdpathconf:
 *
 * Standard implementation of POSIX pathconf, to get information about limits
 * for a filesystem.
 * Override per filesystem for the case where the filesystem has smaller
 * limits.
 }
function vop_stdpathconf(ap:p_vop_pathconf_args):Integer;
begin
 case (ap^.a_name) of
  _PC_NAME_MAX:
   begin
    ap^.a_retval^:=NAME_MAX;
    Exit(0);
   end;
  _PC_PATH_MAX:
   begin
    ap^.a_retval^:=PATH_MAX;
    Exit(0);
   end;
  _PC_LINK_MAX:
   begin
    ap^.a_retval^:=LINK_MAX;
    Exit(0);
   end;
  _PC_MAX_CANON:
   begin
    ap^.a_retval^:=MAX_CANON;
    Exit(0);
   end;
  _PC_MAX_INPUT:
   begin
    ap^.a_retval^:=MAX_INPUT;
    Exit(0);
   end;
  _PC_PIPE_BUF:
   begin
    ap^.a_retval^:=PIPE_BUF;
    Exit(0);
   end;
  _PC_CHOWN_RESTRICTED:
   begin
    ap^.a_retval^:=1;
    Exit(0);
   end;
  _PC_VDISABLE:
   begin
    ap^.a_retval^:=_POSIX_VDISABLE;
    Exit(0);
   end;
  else
   Exit(EINVAL);
 end;
 { NOTREACHED }
end;

function lockmgr(lk:p_mtx;flags:Integer;ilk:p_mtx):Integer;
var
 op:Integer;
begin
 Result:=0;

 if ((flags and LK_INTERLOCK)<>0) then
 begin
  mtx_unlock(ilk^);
 end;

 op:=(flags and LK_TYPE_MASK);
 case op of
  LK_SHARED,    //read  lock
  LK_EXCLUSIVE: //write lock
   begin
    if ((flags and LK_NOWAIT)<>0) then //trylock
    begin
     if not mtx_trylock(lk^) then
     begin
      Result:=1;
     end;
    end else
    begin
     mtx_lock(lk^);
    end;
   end;
  LK_RELEASE:   //unlock
   begin
    mtx_unlock(lk^);
   end;
  LK_UPGRADE  :;//read ->write
  LK_DOWNGRADE:;//write->read
  else
   Assert(false,'TODO');
 end;

end;

{
 * Standard lock, unlock and islocked functions.
 }
function vop_stdlock(ap:p_vop_lock1_args):Integer;
var
 vp:p_vnode;
begin
 vp:=ap^.a_vp;

 //Writeln('vop_std  lock:',HexStr(ap^.a_vp^.v_vnlock));

 Result:=lockmgr(vp^.v_vnlock,ap^.a_flags,VI_MTX(vp));

 //Exit(_lockmgr_args(vp^.v_vnlock, ap^.a_flags, VI_MTX(vp),
 //    LK_WMESG_DEFAULT, LK_PRIO_DEFAULT, LK_TIMO_DEFAULT, ap^.a_file,
 //    ap^.a_line));
end;

{ See above. }
function vop_stdunlock(ap:p_vop_unlock_args):Integer;
var
 vp:p_vnode;
begin
 vp:=ap^.a_vp;

 //Writeln('vop_stdunlock:',HexStr(ap^.a_vp^.v_vnlock));

 Result:=lockmgr(vp^.v_vnlock,ap^.a_flags or LK_RELEASE,VI_MTX(vp));

 //Exit(lockmgr(vp^.v_vnlock, ap^.a_flags or LK_RELEASE, VI_MTX(vp)));
end;

{ See above. }
function vop_stdislocked(ap:p_vop_islocked_args):Integer;
begin

 //Writeln('vop_stdislocked:',HexStr(ap^.a_vp^.v_vnlock),':',mtx_owned(ap^.a_vp^.v_vnlock^));

 if mtx_owned(ap^.a_vp^.v_vnlock^) then
  Exit(LK_EXCLUSIVE)
 else
  Exit(0);

 //Exit(lockstatus(ap^.a_vp^.v_vnlock));
end;

{
 * Exittrue for select/poll.
 }
function vop_nopoll(ap:p_vop_poll_args):Integer;
begin
 Exit(poll_no_poll(ap^.a_events));
end;

{
 * Implement poll for local filesystems that support it.
 }
function vop_stdpoll(ap:p_vop_poll_args):Integer;
begin
 if ((ap^.a_events and (not POLLSTANDARD))<>0) then
  Exit(vn_pollrecord(ap^.a_vp, ap^.a_events));
 Exit(ap^.a_events and (POLLIN or POLLOUT or POLLRDNORM or POLLWRNORM));
end;

{
 * Exit our mount point, as we will take charge of the writes.
 }
function vop_stdgetwritemount(ap:p_vop_getwritemount_args):Integer;
var
 mp:p_mount;
begin
 {
  * XXX Since this is called unlocked we may be recycled while
  * attempting to ref the mount.  If this is the case or mountpoint
  * will be set to nil.  We only have to prevent this call from
  * returning with a ref to an incorrect mountpoint.  It is not
  * harmful to Exitwith a ref to our previous mountpoint.
  }
 mp:=ap^.a_vp^.v_mount;
 if (mp<>nil) then
 begin
  vfs_ref(mp);
  if (mp<>ap^.a_vp^.v_mount) then
  begin
   vfs_rel(mp);
   mp:=nil;
  end;
 end;
 (ap^.a_mpp)^:=mp;
 Exit(0);
end;

{ XXX Needs good comment and VOP_BMAP(9) manpage }
function vop_stdbmap(ap:p_vop_bmap_args):Integer;
begin
 //if (ap^.a_bop<>nil) then
 // ap^.a_bop^:=@ap^.a_vp^.v_bufobj;
 //if (ap^.a_bnp<>nil) then
 // ap^.a_bnp^:=ap^.a_bn * btodb(ap^.a_vp^.v_mount^.mnt_stat.f_iosize);
 //if (ap^.a_runp<>nil) then
 // ap^.a_runp^:=0;
 //if (ap^.a_runb<>nil) then
 // ap^.a_runb^:=0;
 Exit(0);
end;

function vop_stdfsync(ap:p_vop_fsync_args):Integer;
var
 vp:p_vnode;
 //struct buf *bp;
 //struct bufobj *bo;
 //struct buf *nbp;
 error:Integer;
 maxretry:Integer;
begin
 vp:=ap^.a_vp;
 error:=0;
 maxretry:=1000;     { large, arbitrarily chosen }

{
 bo:=@vp^.v_bufobj;
 BO_LOCK(bo);

loop1:
 {
  * MARK/SCAN initialization to avoid infinite loops.
  }
 TAILQ_FOREACH(bp, @bo^.bo_dirty.bv_hd, b_bobufs)
 begin
  bp^.b_vflags:=bp^.b_vflags and (not BV_SCANNED);
  bp^.b_error:=0;
 end;

 {
  * Flush all dirty buffers associated with a vnode.
  }
loop2:
 TAILQ_FOREACH_SAFE(bp, @bo^.bo_dirty.bv_hd, b_bobufs, nbp)
 begin
  if ((bp^.b_vflags and BV_SCANNED)<>0)
   continue;
  bp^.b_vflags:=bp^.b_vflags or BV_SCANNED;
  if (BUF_LOCK(bp, LK_EXCLUSIVE or LK_NOWAIT, nil)) then
  begin
   if (ap^.a_waitfor<>MNT_WAIT)
    continue;
   if (BUF_LOCK(bp,
       LK_EXCLUSIVE or LK_INTERLOCK or LK_SLEEPFAIL,
       BO_MTX(bo))<>0) then
   begin
    BO_LOCK(bo);
    goto loop1;
   end;
   BO_LOCK(bo);
  end;
  BO_UNLOCK(bo);
  Assert(bp^.b_bufobj=bo,
      ('bp %p wrong b_bufobj %p should be %p',
      bp, bp^.b_bufobj, bo));
  if ((bp^.b_flags and B_DELWRI)=0) then
   panic('fsync: not dirty');
  if ((vp^.v_object<>nil) and (bp^.b_flags and B_CLUSTEROK)) then
  begin
   vfs_bio_awrite(bp);
  end else
  begin
   bremfree(bp);
   bawrite(bp);
  end;
  BO_LOCK(bo);
  goto loop2;
 end;

 {
  * If synchronous the caller expects us to completely resolve all
  * dirty buffers in the system.  Wait for in-progress I/O to
  * complete (which could include background bitmap writes), then
  * retry if dirty blocks still exist.
  }
 if (ap^.a_waitfor=MNT_WAIT) then
 begin
  bufobj_wwait(bo, 0, 0);
  if (bo^.bo_dirty.bv_cnt > 0) then
  begin
   {
    * If we are unable to write any of these buffers
    * then we fail now rather than trying endlessly
    * to write them out.
    }
   TAILQ_FOREACH(bp, @bo^.bo_dirty.bv_hd, b_bobufs)
    if ((error:=bp^.b_error)=0) then
     continue;
   if (error=0) and (--maxretry >= 0) then
    goto loop1;
   error:=EAGAIN;
  end;
 end;
 BO_UNLOCK(bo);
 if (error=EAGAIN) then
  vprint('fsync: giving up on dirty', vp);
 }

 Exit(error);
end;

{ XXX Needs good comment and more info in the manpage (VOP_GETPAGES(9)). }
function vop_stdgetpages(ap:p_vop_getpages_args):Integer;
begin
 Exit(EOPNOTSUPP);
 //Exit(vnode_pager_generic_getpages(ap^.a_vp, ap^.a_m, ap^.a_count, ap^.a_reqpage));
end;

function vop_stdkqfilter(ap:p_vop_kqfilter_args):Integer;
begin
 Exit(vfs_kqfilter(ap));
end;

{ XXX Needs good comment and more info in the manpage (VOP_PUTPAGES(9)). }
function vop_stdputpages(ap:p_vop_putpages_args):Integer;
begin
 Exit(EOPNOTSUPP);
 //Exit(vnode_pager_generic_putpages(ap^.a_vp, ap^.a_m, ap^.a_count, ap^.a_sync, ap^.a_rtvals));
end;

function vop_stdvptofh(ap:p_vop_vptofh_args):Integer;
begin
 Exit(EOPNOTSUPP);
end;

function vop_stdvptocnp(ap:p_vop_vptocnp_args):Integer;
label
 _out;
var
 vp:p_vnode;
 dvp:pp_vnode;
 buf:PChar;
 buflen:PInteger;
 dirbuf, cpos:PByte;
 i, error, eofflag, dirbuflen, flags, locked, len, covered:Integer;
 off:QWORD;
 fileno:DWORD;
 va:t_vattr;
 nd:t_nameidata;
 td:p_kthread;
 dp:p_dirent;
 mvp:p_vnode;
begin
 vp:=ap^.a_vp;
 dvp:=ap^.a_vpp;
 buf:=ap^.a_buf;
 buflen:=ap^.a_buflen;

 i:=buflen^;
 error:=0;
 covered:=0;
 td:=curkthread;

 if (vp^.v_type<>VDIR) then
  Exit(ENOENT);

 error:=VOP_GETATTR(vp, @va);
 if (error<>0) then
  Exit(error);

 VREF(vp);
 locked:=VOP_ISLOCKED(vp);
 VOP_UNLOCK(vp, 0);
 NDINIT_ATVP(@nd, LOOKUP, FOLLOW or LOCKLEAF, UIO_SYSSPACE, '..', vp, td);
 flags:=FREAD;
 error:=vn_open_cred(@nd, @flags, 0, VN_OPEN_NOAUDIT, nil);
 if (error<>0) then
 begin
  vn_lock(vp, locked or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  Exit(error);
 end;
 NDFREE(@nd, NDF_ONLY_PNBUF);

 mvp:=nd.ni_vp;
 dvp^:=mvp;

 if (vp^.v_mount<>dvp^^.v_mount) and
    ((dvp^^.v_vflag and VV_ROOT)<>0) and
    ((p_mount(dvp^^.v_mount)^.mnt_flag and MNT_UNION)<>0) then
 begin
  dvp^:=p_mount(dvp^^.v_mount)^.mnt_vnodecovered;
  VREF(mvp);
  VOP_UNLOCK(mvp, 0);
  vn_close(mvp, FREAD);
  VREF(dvp^);
  vn_lock(dvp^, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  covered:=1;
 end;

 fileno:=va.va_fileid;

 dirbuflen:=DEV_BSIZE;
 if (dirbuflen < va.va_blocksize) then
  dirbuflen:=va.va_blocksize;

 dirbuf:=AllocMem(dirbuflen);

 if (dvp^^.v_type<>VDIR) then
 begin
  error:=ENOENT;
  goto _out;
 end;

 off:=0;
 len:=0;
 repeat
  { call VOP_READDIR of parent }
  error:=get_next_dirent(dvp^, @dp, dirbuf, dirbuflen, @off, @cpos, @len, @eofflag);
  if (error<>0) then
   goto _out;

  if (dp^.d_type<>DT_WHT) and
     (dp^.d_fileno=fileno) then
  begin
   if (covered<>0) then
   begin
    VOP_UNLOCK(dvp^, 0);
    vn_lock(mvp, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
    if (dirent_exists(mvp, dp^.d_name)=0) then
    begin
     error:=ENOENT;
     VOP_UNLOCK(mvp, 0);
     vn_lock(dvp^, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
     goto _out;
    end;
    VOP_UNLOCK(mvp, 0);
    vn_lock(dvp^, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
   end;
   Dec(i,dp^.d_namlen);

   if (i < 0) then
   begin
    error:=ENOMEM;
    goto _out;
   end;
   if (dp^.d_namlen=1) and (dp^.d_name[0]='.') then
   begin
    error:=ENOENT;
   end else
   begin
    Move(dp^.d_name, (buf + i)^, dp^.d_namlen);
    error:=0;
   end;
   goto _out;
  end;
 until not ((len>0) or (eofflag=0));
 error:=ENOENT;

_out:
 FreeMem(dirbuf);
 if (error=0) then
 begin
  buflen^:=i;
  vref(dvp^);
 end;
 if (covered<>0) then
 begin
  vput(dvp^);
  vrele(mvp);
 end else
 begin
  VOP_UNLOCK(mvp, 0);
  vn_close(mvp, FREAD);
 end;
 vn_lock(vp, locked or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 Exit(error);
end;

function vop_stdallocate(ap:p_vop_allocate_args):Integer;
label
 _out;
var
 aiov:iovec;
 vattr:t_vattr;
 vap:p_vattr;
 auio:t_uio;
 fsize, len, cur, offset:QWORD;
 buf:PByte;
 td:p_kthread;
 vp:p_vnode;
 iosize:QWORD;
 error:Integer;
begin
 buf:=nil;
 error:=0;
 td:=curkthread;
 vap:=@vattr;
 vp:=ap^.a_vp;
 len:=ap^.a_len^;
 offset:=ap^.a_offset^;

 error:=VOP_GETATTR(vp, vap);
 if (error<>0) then
  goto _out;
 fsize:=vap^.va_size;
 iosize:=vap^.va_blocksize;
 if (iosize=0) then
  iosize:=BLKDEV_IOSIZE;
 if (iosize > MAXPHYS) then
  iosize:=MAXPHYS;
 buf:=AllocMem(iosize);

 if (offset + len > vap^.va_size) then
 begin
  {
   * Test offset + len against the filesystem's maxfilesize.
   }
  VATTR_null(vap);
  vap^.va_size:=offset + len;
  error:=VOP_SETATTR(vp, vap);
  if (error<>0) then
   goto _out;
  VATTR_null(vap);
  vap^.va_size:=fsize;
  error:=VOP_SETATTR(vp, vap);
  if (error<>0) then
   goto _out;
 end;

 repeat
  {
   * Read and write back anything below the nominal file
   * size.  There's currently no way outside the filesystem
   * to know whether this area is sparse or not.
   }
  cur:=iosize;
  if ((offset mod iosize)<>0) then
   Dec(cur,(offset mod iosize));
  if (cur > len) then
   cur:=len;
  if (offset < fsize) then
  begin
   aiov.iov_base  :=buf;
   aiov.iov_len   :=cur;
   auio.uio_iov   :=@aiov;
   auio.uio_iovcnt:=1;
   auio.uio_offset:=offset;
   auio.uio_resid :=cur;
   auio.uio_segflg:=UIO_SYSSPACE;
   auio.uio_rw    :=UIO_READ;
   auio.uio_td    :=td;
   error:=VOP_READ(vp, @auio, 0);
   if (error<>0) then
    break;
   if (auio.uio_resid > 0) then
   begin
    FillChar((buf + cur - auio.uio_resid)^,auio.uio_resid,0);
   end;
  end else
  begin
   FillChar(buf^,cur,0);
  end;

  aiov.iov_base  :=buf;
  aiov.iov_len   :=cur;
  auio.uio_iov   :=@aiov;
  auio.uio_iovcnt:=1;
  auio.uio_offset:=offset;
  auio.uio_resid :=cur;
  auio.uio_segflg:=UIO_SYSSPACE;
  auio.uio_rw    :=UIO_WRITE;
  auio.uio_td    :=td;

  error:=VOP_WRITE(vp, @auio, 0);
  if (error<>0) then
   break;

  Dec(len,cur);
  Inc(offset,cur);
  if (len=0) then
   break;
  //if (should_yield())
  // break;
 until false;

 _out:
 ap^.a_len^:=len;
 ap^.a_offset^:=offset;
 FreeMem(buf);
 Exit(error);
end;

function vop_stdunp_bind(ap:p_vop_unp_bind_args):Integer;
begin
 ap^.a_vp^.v_socket:=ap^.a_socket;
 Exit(0);
end;

function vop_stdunp_connect(ap:p_vop_unp_connect_args):Integer;
begin
 ap^.a_socket^:=ap^.a_vp^.v_socket;
 Exit(0);
end;

function vop_stdunp_detach(ap:p_vop_unp_detach_args):Integer;
begin
 ap^.a_vp^.v_un:=nil;
 Exit(0);
end;

{
 * vfs default ops
 * used to fill the vfs function table to get reasonable default Exitvalues.
 }
function vfs_stdroot(mp:p_mount;flags:Integer;vpp:pp_vnode):Integer;
begin
 Exit(EOPNOTSUPP);
end;

function vfs_stdstatfs(mp:p_mount;sbp:p_statfs):Integer;
begin
 Exit(EOPNOTSUPP);
end;

function vfs_stdquotactl(mp:p_mount;cmds:Integer;uid:uid_t;arg:Pointer):Integer;
begin
 Exit(EOPNOTSUPP);
end;

function vfs_stdsync(mp:p_mount;waitfor:Integer):Integer;
label
 loop;
var
 vp,mvp:p_vnode;
 error,lockreq,allerror:Integer;
begin
 error:=0;
 lockreq:=0;
 allerror:=0;

 lockreq:=LK_EXCLUSIVE or LK_INTERLOCK;
 if (waitfor<>MNT_WAIT) then
 begin
  lockreq:=lockreq or LK_NOWAIT;
 end;
 {
  * Force stale buffer cache information to be flushed.
  }
loop:
 vp:=__mnt_vnode_first_all(@mvp,mp);
 while (vp<>nil) do
 begin
  //if (vp^.v_bufobj.bo_dirty.bv_cnt=0) then
  //begin
  // VI_UNLOCK(vp);
  // continue;
  //end;
  error:=vget(vp, lockreq);
  if (error<>0) then
  begin
   if (error=ENOENT) then
   begin
    //MNT_VNODE_FOREACH_ALL_ABORT(mp, mvp);
    MNT_ILOCK(mp);
    __mnt_vnode_markerfree_all(@mvp,mp);
    //MNT_VNODE_FOREACH_ALL_ABORT(mp, mvp);
    goto loop;
   end;
   continue;
  end;
  error:=VOP_FSYNC(vp, waitfor);
  if (error<>0) then
   allerror:=error;
  vput(vp);

  vp:=__mnt_vnode_next_all(@mvp,mp)
 end;
 Exit(allerror);
end;

function vfs_stdnosync(mp:p_mount;waitfor:Integer):Integer;
begin
 Exit(0);
end;

function vfs_stdvget(mp:p_mount;ino:DWORD;flags:Integer;vpp:pp_vnode):Integer;
begin
 Exit(EOPNOTSUPP);
end;

function vfs_stdfhtovp(mp:p_mount;fhp:p_fid;flags:Integer;vpp:pp_vnode) :Integer;
begin
 Exit(EOPNOTSUPP);
end;

function vfs_stdinit(vfsp:p_vfsconf):Integer;
begin
 Exit(0);
end;

function vfs_stduninit(vfsp:p_vfsconf):Integer;
begin
 Exit(0);
end;

function vfs_stdextattrctl(mp:p_mount;cmd:Integer;filename_vp:p_vnode;attrnamespace:Integer;attrname:PChar):Integer;
begin
 if (filename_vp<>nil) then
  VOP_UNLOCK(filename_vp, 0);
 Exit(EOPNOTSUPP);
end;

function vfs_stdsysctl(mp:p_mount;op:Integer;req:Pointer):Integer;
begin
 Exit(EOPNOTSUPP);
end;

{ end of vfs default ops }


end.

