unit ufs_vnops;

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

interface

uses
 mqueue,
 kern_param,
 time,
 vfile,
 vmount,
 vdirent,
 vuio,
 vnode,
 vnamei,
 vnode_if,
 vfs_default,
 vfs_vnops,
 ufs,
 kern_mtx,
 kern_sx;

function  ufs_vmkdir(dmp:p_ufs_mount;name:PChar;namelen:Integer;dotdot:p_ufs_dirent;inode:DWORD):p_ufs_dirent;
procedure ufs_purge(dm:p_ufs_mount;dd:p_ufs_dirent);

function  ufs_lookup(ap:p_vop_lookup_args):Integer;
function  ufs_access(ap:p_vop_access_args):Integer;
function  ufs_getattr(ap:p_vop_getattr_args):Integer;
function  ufs_setattr(ap:p_vop_setattr_args):Integer;
function  ufs_readdir(ap:p_vop_readdir_args):Integer;
function  ufs_rread(ap:p_vop_read_args):Integer;
function  ufs_readlink(ap:p_vop_readlink_args):Integer;
function  ufs_symlink(ap:p_vop_symlink_args):Integer;
function  ufs_remove(ap:p_vop_remove_args):Integer;
function  ufs_rmdir(ap:p_vop_rmdir_args):Integer;
function  ufs_mkdir(ap:p_vop_mkdir_args):Integer;
function  ufs_reclaim(ap:p_vop_reclaim_args):Integer;

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

  vop_islocked      :nil;
  vop_lookup        :@ufs_lookup;
  vop_create        :nil;
  vop_whiteout      :nil;
  vop_mknod         :nil;
  vop_open          :nil;
  vop_close         :nil;
  vop_access        :@ufs_access;
  vop_accessx       :nil;
  vop_getattr       :@ufs_getattr;
  vop_setattr       :@ufs_setattr;
  vop_markatime     :nil;
  vop_read          :@ufs_rread;
  vop_write         :nil;
  vop_ioctl         :nil;
  vop_poll          :nil;
  vop_kqfilter      :nil;
  vop_revoke        :nil;
  vop_fsync         :nil;
  vop_remove        :@ufs_remove;
  vop_link          :nil;
  vop_rename        :nil;
  vop_mkdir         :@ufs_mkdir;
  vop_rmdir         :@ufs_rmdir;
  vop_symlink       :@ufs_symlink;
  vop_readdir       :@ufs_readdir;
  vop_readlink      :@ufs_readlink;
  vop_inactive      :nil;
  vop_reclaim       :@ufs_reclaim;
  vop_lock1         :nil;
  vop_unlock        :nil;
  vop_bmap          :nil;
  vop_strategy      :nil;
  vop_getwritemount :nil;
  vop_print         :nil;
  vop_pathconf      :@vop_stdpathconf;
  vop_advlock       :nil;
  vop_advlockasync  :nil;
  vop_advlockpurge  :nil;
  vop_reallocblks   :nil;
  vop_getpages      :nil;
  vop_putpages      :nil;
  vop_vptofh        :nil;
  vop_vptocnp       :nil;
  vop_allocate      :nil;
  vop_unp_bind      :nil;
  vop_unp_connect   :nil;
  vop_unp_detach    :nil;
 );

implementation

uses
 sysutils,
 errno,
 kern_thr,
 kern_proc,
 vfs_subr,
 subr_uio,
 vnode_pager;

const
 UFS_DEL_VNLOCKED =$01;
 UFS_DEL_NORECURSE=$02;

function VFSTOUFS(mp:p_mount):p_ufs_mount; inline;
begin
 Result:=mp^.mnt_data;
end;

function ufs_parent_dirent(de:p_ufs_dirent):p_ufs_dirent; inline;
begin
 Exit(de^.ufs_dir);
end;

function ufs_newdirent(name:PChar;namelen:Integer):p_ufs_dirent;
var
 i:Integer;
 de:p_ufs_dirent;
 d:t_dirent;
begin
 d.d_namlen:=namelen;
 i:=sizeof(t_ufs_dirent) + GENERIC_DIRSIZ(@d);
 de:=AllocMem(i);
 de^.ufs_dirent:=p_dirent(de + 1);
 de^.ufs_dirent^.d_namlen:=namelen;
 de^.ufs_dirent^.d_reclen:=GENERIC_DIRSIZ(@d);

 Move(name^, de^.ufs_dirent^.d_name, namelen);

 de^.ufs_dirent^.d_name[namelen]:=#0;

 vfs_timestamp(@de^.ufs_ctime);
 de^.ufs_mtime  :=de^.ufs_ctime;
 de^.ufs_atime  :=de^.ufs_ctime;
 de^.ufs_btime  :=de^.ufs_ctime;

 de^.ufs_links  :=1;
 de^.ufs_ref    :=1;

 TAILQ_INIT(@de^.ufs_dlist);

 Exit(de);
end;

function _ufs_rmdir(dm:p_ufs_mount;de:p_ufs_dirent):Integer; forward;

{
 * The caller needs to hold the dm for the duration of the call since
 * dm^.dm_lock may be temporary dropped.
 }
procedure ufs_delete(dm:p_ufs_mount;de:p_ufs_dirent;flags:Integer);
var
 dd,dd2:p_ufs_dirent;
 vp:p_vnode;
begin
 if (dm=nil) or (de=nil) then Exit;

 Assert((de^.ufs_flags and UFS_DOOMED)=0,'ufs_delete doomed dirent');
 de^.ufs_flags:=de^.ufs_flags or UFS_DOOMED;

 if ((flags and UFS_DEL_NORECURSE)=0) then
 begin
  dd:=ufs_parent_dirent(de);
 end else
 begin
  dd:=nil;
 end;

 if (dd<>nil) then
 begin
  ufs_de_hold(dd);
 end;

 mtx_lock(ufs_interlock);
 vp:=de^.ufs_vnode;
 if (vp<>nil) then
 begin
  VI_LOCK(vp);
  mtx_unlock(ufs_interlock);
  vholdl(vp);

  sx_unlock(@dm^.ufs_lock);

  if ((flags and UFS_DEL_VNLOCKED)=0) then
   vn_lock(vp, LK_EXCLUSIVE or LK_INTERLOCK or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%})
  else
   VI_UNLOCK(vp);

  vgone(vp);

  if ((flags and UFS_DEL_VNLOCKED)=0) then
  begin
   VOP_UNLOCK(vp, 0);
  end;

  vdrop(vp);
  sx_xlock(@dm^.ufs_lock);
 end else
 begin
  mtx_unlock(ufs_interlock);
 end;

 if (de^.ufs_symlink<>nil) then
 begin
  FreeMem(de^.ufs_symlink);
  de^.ufs_symlink:=nil;
 end;

 if (de^.ufs_inode > UFS_ROOTINO) then
 begin
  ufs_free_cdp_inode(de^.ufs_inode);
  de^.ufs_inode:=0;
 end;

 dd2:=de^.ufs_dir;
 if (dd2<>nil) then
 begin
  de^.ufs_dir:=nil;
  Dec(dd2^.ufs_links);
  TAILQ_REMOVE(@dd2^.ufs_dlist,de,@de^.ufs_list);
 end;

 if (dd<>nil) then
 begin
  if ufs_de_drop(dd) then
  begin
   //
  end else
  if ((flags and UFS_DEL_NORECURSE)=0) then
  begin
   _ufs_rmdir(dm, de);
  end;
 end;

 ufs_de_drop(de);
end;

function _ufs_dir_status(dm:p_ufs_mount;de:p_ufs_dirent):Integer;
var
 de_dot,de_dotdot:p_ufs_dirent;
begin
 Result:=0;
 if (dm=nil) or (de=nil) then Exit;

 sx_assert(@dm^.ufs_lock);

 if (de^.ufs_dirent^.d_type<>DT_DIR) then Exit(ENOTDIR);

 if ((de^.ufs_flags and UFS_DOOMED)<>0) or (de=dm^.ufs_rootdir) then
 begin
  Exit(EBUSY);
 end;

 de_dot:=TAILQ_FIRST(@de^.ufs_dlist);
 Assert(de_dot<>nil, 'ufs_rmdir: . missing');

 de_dotdot:=TAILQ_NEXT(de_dot,@de_dot^.ufs_list);
 Assert(de_dotdot<>nil, 'ufs_rmdir: .. missing');

 { Exit if the directory is not empty. }
 if (TAILQ_NEXT(de_dotdot,@de_dotdot^.ufs_list)<>nil) then
 begin
  Exit(ENOTEMPTY);
 end;
end;

function _ufs_rmdir(dm:p_ufs_mount;de:p_ufs_dirent):Integer;
label
 next;
var
 dd,de_dot,de_dotdot:p_ufs_dirent;
begin
 Result:=0;
 if (dm=nil) or (de=nil) then Exit;

 sx_assert(@dm^.ufs_lock);

 Assert(de^.ufs_dirent^.d_type=DT_DIR,'ufs_rmdir: de is not a directory');

 if ((de^.ufs_flags and UFS_DOOMED)<>0) or (de=dm^.ufs_rootdir) then
 begin
  Exit(EBUSY);
 end;

 dd       :=nil;
 de_dot   :=nil;
 de_dotdot:=nil;

 de_dot:=TAILQ_FIRST(@de^.ufs_dlist);
 if (de_dot=nil) then
 begin
  goto next;
 end;

 de_dotdot:=TAILQ_NEXT(de_dot,@de_dot^.ufs_list);
 if (de_dotdot=nil) then
 begin
  goto next;
 end;

 { Exit if the directory is not empty. }
 if (TAILQ_NEXT(de_dotdot,@de_dotdot^.ufs_list)<>nil) then
 begin
  Exit(ENOTEMPTY);
 end;

 dd:=ufs_parent_dirent(de);

 next:

 Assert(de_dot^.ufs_dir=de);
 Assert(de_dotdot^.ufs_dir=de);
 Assert(de^.ufs_dir=dd);

 ufs_de_hold(dd);
 ufs_delete(dm, de_dot   ,UFS_DEL_NORECURSE);
 ufs_delete(dm, de_dotdot,UFS_DEL_NORECURSE);
 ufs_delete(dm, de       ,UFS_DEL_NORECURSE);
 ufs_de_drop(dd);
end;

procedure ufs_purge(dm:p_ufs_mount;dd:p_ufs_dirent);
var
 de:p_ufs_dirent;
begin
 if (dm=nil) or (dd=nil) then Exit;

 sx_assert(@dm^.ufs_lock);

 ufs_de_hold(dd);

 repeat
  de:=TAILQ_LAST(@dd^.ufs_dlist);
  if (de=nil) then break;

  if ((de^.ufs_flags and (UFS_DOT or UFS_DOTDOT))<>0) then
   ufs_delete(dm, de, UFS_DEL_NORECURSE)
  else
  if (de^.ufs_dirent^.d_type=DT_DIR) then
   ufs_purge(dm, de)
  else
   ufs_delete(dm, de, UFS_DEL_NORECURSE);

 until false;

 if ((dd^.ufs_flags and UFS_DOOMED)=0) then
  ufs_delete(dm, dd, UFS_DEL_NORECURSE);

 ufs_de_drop(dd);
end;

function ufs_vmkdir(dmp:p_ufs_mount;name:PChar;namelen:Integer;dotdot:p_ufs_dirent;inode:DWORD):p_ufs_dirent;
var
 nd,de:p_ufs_dirent;
begin
 { Create the new directory }
 nd:=ufs_newdirent(name, namelen);

 nd^.ufs_dirent^.d_type:=DT_DIR;
 nd^.ufs_mode :=UFS_DEFAULT_MODE;
 nd^.ufs_links:=2;
 nd^.ufs_dir  :=dotdot;

 if (inode<>0) then
  nd^.ufs_inode:=inode
 else
  nd^.ufs_inode:=ufs_alloc_cdp_inode;

 if (dotdot=nil) then
 begin
  nd^.ufs_flags:=UFS_DROOT;
 end else
 begin
  {
   * '.' and '..' are always the two first entries in the
   * de_dlist list.
   *
   * Create the '.' entry in the new directory.
   }
  de:=ufs_newdirent('.', 1);
  de^.ufs_dirent^.d_type:=DT_DIR;
  de^.ufs_flags:=de^.ufs_flags or UFS_DOT;
  TAILQ_INSERT_TAIL(@nd^.ufs_dlist,de,@de^.ufs_list);
  de^.ufs_dir:=nd;

  { Create the '..' entry in the new directory. }
  de:=ufs_newdirent('..', 2);
  de^.ufs_dirent^.d_type:=DT_DIR;
  de^.ufs_flags:=de^.ufs_flags or UFS_DOTDOT;
  TAILQ_INSERT_TAIL(@nd^.ufs_dlist,de,@de^.ufs_list);
  de^.ufs_dir:=nd;

  sx_assert(@dmp^.ufs_lock);
  TAILQ_INSERT_TAIL(@dotdot^.ufs_dlist,nd,@nd^.ufs_list);
  Inc(dotdot^.ufs_links);
 end;

 Exit(nd);
end;

function ufs_find(dd:p_ufs_dirent;name:PChar;namelen:Integer;_type:Integer):p_ufs_dirent;
var
 de:p_ufs_dirent;
begin
 de:=TAILQ_FIRST(@dd^.ufs_dlist);
 while (de<>nil) do
 begin
  if (namelen<>de^.ufs_dirent^.d_namlen) then
  begin
   de:=TAILQ_NEXT(de,@de^.ufs_list);
   continue;
  end;
  if (_type<>0) and (_type<>de^.ufs_dirent^.d_type) then
  begin
   de:=TAILQ_NEXT(de,@de^.ufs_list);
   continue;
  end;

  if (CompareByte(name^, de^.ufs_dirent^.d_name, namelen)<>0) then
  begin
   de:=TAILQ_NEXT(de,@de^.ufs_list);
   continue;
  end;
  break;
 end;

 Exit(de);
end;

function ufs_lookupx(ap:p_vop_lookup_args;dm_unlock:PBoolean):Integer;
var
 cnp:p_componentname;
 dvp:p_vnode;
 vpp:pp_vnode;
 de,dd:p_ufs_dirent;
 error,flags,nameiop,dvplocked:Integer;
 pname:PChar;
begin
 cnp:=ap^.a_cnp;
 vpp:=ap^.a_vpp;
 dvp:=ap^.a_dvp;
 pname:=cnp^.cn_nameptr;
 flags:=cnp^.cn_flags;
 nameiop:=cnp^.cn_nameiop;
 dd:=dvp^.v_data;
 vpp^:=nil;

 if ((flags and ISLASTCN)<>0) and (nameiop=RENAME) then
 begin
  Exit(EOPNOTSUPP);
 end;

 if (dvp^.v_type<>VDIR) then
 begin
  Exit(ENOTDIR);
 end;

 //If read-only and op is not CREATE|LOOKUP, will return EROFS.
 if ((flags and ISLASTCN)<>0) and
     ((p_mount(dvp^.v_mount)^.mnt_flag and MNT_RDONLY)<>0) and
     (nameiop <> CREATE) and
     (nameiop <> LOOKUP) then
 begin
  Exit(EROFS);
 end;

 if (((flags and ISDOTDOT)<>0) and ((dvp^.v_vflag and VV_ROOT)<>0)) then
 begin
  Exit(EIO);
 end;

 error:=VOP_ACCESS(dvp, VEXEC);
 if (error<>0) then
 begin
  Exit(error);
 end;

 if (cnp^.cn_namelen=1) and (pname^='.') then
 begin
  if ((flags and ISLASTCN)<>0) and (nameiop<>LOOKUP) then
  begin
   Exit(EINVAL);
  end;

  vpp^:=dvp;
  VREF(dvp);

  Exit(0);
 end;

 if ((flags and ISDOTDOT)<>0) then
 begin
  if ((flags and ISLASTCN)<>0) and (nameiop<>LOOKUP) then
  begin
   Exit(EINVAL);
  end;

  de:=ufs_parent_dirent(dd);

  if (de=nil) then Exit(ENOENT);

  dvplocked:=VOP_ISLOCKED(dvp);
  VOP_UNLOCK(dvp, 0);

  error:=ufs_allocv(de, dvp^.v_mount, cnp^.cn_lkflags and LK_TYPE_MASK, vpp); //sx_xunlock
  dm_unlock^:=false;

  vn_lock(dvp, dvplocked or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});

  Exit(error);
 end;

 de:=ufs_find(dd, cnp^.cn_nameptr, cnp^.cn_namelen, 0);

 if (de=nil) then
 begin
  //not found

  Case nameiop of
   CREATE,
   RENAME:
    begin

     //If read-only and op is CREATE|RENAME, will return EROFS.
     if ((flags and ISLASTCN)<>0) and
         ((p_mount(dvp^.v_mount)^.mnt_flag and MNT_RDONLY)<>0) then
     begin
      Exit(EROFS);
     end;

     if ((flags and (LOCKPARENT or WANTPARENT))<>0) and
        ((flags and ISLASTCN)<>0) then
     begin
      cnp^.cn_flags:=cnp^.cn_flags or SAVENAME;
      Exit(EJUSTRETURN);
     end;

    end;
   else;
  end;

  Exit(ENOENT);
 end;

 if (cnp^.cn_nameiop=DELETE) and ((flags and ISLASTCN)<>0) then
 begin
  error:=VOP_ACCESS(dvp, VWRITE);
  if (error<>0) then
  begin
   Exit(error);
  end;

  if (vpp^=dvp) then
  begin
   VREF(dvp);
   vpp^:=dvp;
   Exit(0);
  end;
 end;

 error:=ufs_allocv(de, dvp^.v_mount, cnp^.cn_lkflags and LK_TYPE_MASK, vpp); //sx_xunlock
 dm_unlock^:=false;

 Exit(error);
end;

function ufs_lookup(ap:p_vop_lookup_args):Integer;
var
 dmp:p_ufs_mount;
 dm_unlock:Boolean;
begin
 dmp:=VFSTOUFS(ap^.a_dvp^.v_mount);

 dm_unlock:=True;
 sx_xlock(@dmp^.ufs_lock);

 Result:=ufs_lookupx(ap, @dm_unlock);

 if (dm_unlock) then
 begin
  sx_xunlock(@dmp^.ufs_lock);
 end;
end;

function ufs_access(ap:p_vop_access_args):Integer;
var
 vp:p_vnode;
 de:p_ufs_dirent;
 accmode:accmode_t;
 error:Integer;
begin
 vp:=ap^.a_vp;

 de:=vp^.v_data;

 accmode:=ap^.a_accmode;

 if ((accmode and VWRITE)<>0) and
    ((p_mount(vp^.v_mount)^.mnt_flag and MNT_RDONLY)<>0) then
 begin
  case vp^.v_type of
   VREG,
   VDIR,
   VLNK:
   Exit(EROFS);
  else;
  end;
 end;

 error:=vaccess(vp^.v_type, de^.ufs_mode, de^.ufs_uid, de^.ufs_gid, accmode, nil);

 if (error=0) then
 begin
  Exit(0);
 end;

 if (error<>EACCES) then
 begin
  Exit(error);
 end;

 if ((p_proc.p_flag and P_CONTROLT)=0) then
 begin
  Exit(error);
 end;

 Exit(error);
end;

function ufs_getattr(ap:p_vop_getattr_args):Integer;
var
 vp:p_vnode;
 vap:p_vattr;
 de:p_ufs_dirent;

begin
 vp:=ap^.a_vp;
 vap:=ap^.a_vap;
 de:=vp^.v_data;

 vap^.va_uid :=de^.ufs_uid;
 vap^.va_gid :=de^.ufs_gid;
 vap^.va_mode:=de^.ufs_mode;

 case vp^.v_type of
  VLNK:
   begin
    vap^.va_size :=strlen(de^.ufs_symlink);
    vap^.va_bytes:=0;
   end;
  VDIR:
   begin
    vap^.va_size :=DEV_BSIZE;
    vap^.va_bytes:=DEV_BSIZE;
   end;
  else
   begin
    vap^.va_size :=de^.ufs_size;
    vap^.va_bytes:=de^.ufs_bytes;
   end;
 end;

 vap^.va_blocksize:=DEV_BSIZE;
 vap^.va_type:=vp^.v_type;

 vap^.va_atime    :=de^.ufs_atime;
 vap^.va_mtime    :=de^.ufs_mtime;
 vap^.va_ctime    :=de^.ufs_ctime;
 vap^.va_birthtime:=de^.ufs_btime;

 vap^.va_gen    :=0;
 vap^.va_flags  :=0;
 vap^.va_filerev:=0;
 vap^.va_nlink  :=de^.ufs_links;
 vap^.va_fileid :=de^.ufs_inode;

 Exit(0);
end;

function ufs_setattr(ap:p_vop_setattr_args):Integer;
var
 de:p_ufs_dirent;
 vap:p_vattr;
 vp:p_vnode;
 c,error:Integer;
 uid:uid_t;
 gid:gid_t;
begin
 vap:=ap^.a_vap;
 vp:=ap^.a_vp;

 if ((p_mount(vp^.v_mount)^.mnt_flag and MNT_RDONLY)<>0) and
    (
     (vap^.va_flags       <>VNOVAL) or
     (vap^.va_uid         <>VNOVAL) or
     (vap^.va_gid         <>VNOVAL) or
     (vap^.va_atime.tv_sec<>VNOVAL) or
     (vap^.va_mtime.tv_sec<>VNOVAL) or
     (vap^.va_mode        <>VNOVAL)
    ) then
 begin
  Exit(EROFS);
 end;

 if (vap^.va_type     <>VNON) or
    (vap^.va_nlink    <>VNOVAL) or
    (vap^.va_fsid     <>VNOVAL) or
    (vap^.va_fileid   <>VNOVAL) or
    (vap^.va_blocksize<>VNOVAL) or
    ((vap^.va_flags   <>VNOVAL) and (vap^.va_flags<>0)) or
    (vap^.va_rdev     <>VNOVAL) or
    (vap^.va_bytes    <>VNOVAL) or
    (vap^.va_gen      <>VNOVAL) then
 begin
  Exit(EINVAL);
 end;

 de:=vp^.v_data;

 error:=0;
 c:=0;

 if (vap^.va_uid=VNOVAL) then
  uid:=de^.ufs_uid
 else
  uid:=vap^.va_uid;

 if (vap^.va_gid=VNOVAL) then
  gid:=de^.ufs_gid
 else
  gid:=vap^.va_gid;

 if (uid<>de^.ufs_uid) or (gid<>de^.ufs_gid) then
 begin
  //if ((ap^.a_cred^.cr_uid<>de^.de_uid) or uid<>de^.de_uid or
  //    (gid<>de^.de_gid and !groupmember(gid, ap^.a_cred))) then
  //begin
  // error:=priv_check(td, PRIV_VFS_CHOWN);
  // if (error<>) then
  //  Exit(error);
  //end;
  de^.ufs_uid:=uid;
  de^.ufs_gid:=gid;
  c:=1;
 end;

 if (vap^.va_mode<>VNOVAL) then
 begin
  //if (ap^.a_cred^.cr_uid<>de^.de_uid) then
  //begin
  // error:=priv_check(td, PRIV_VFS_ADMIN);
  // if (error<>0) then
  //  Exit(error);
  //end;
  de^.ufs_mode:=vap^.va_mode;
  c:=1;
 end;

 if (vap^.va_atime.tv_sec<>VNOVAL) or
    (vap^.va_mtime.tv_sec<>VNOVAL) or
    (vap^.va_birthtime.tv_sec<>VNOVAL) then
 begin
  { See the comment in ufs_vnops::ufs_setattr(). }

  error:=VOP_ACCESS(vp, VADMIN);
  if (error<>0) then
  begin
   if ((vap^.va_vaflags and VA_UTIMES_NULL)=0) then Exit(error);
   error:=VOP_ACCESS(vp, VWRITE);
   if (error<>0) then Exit(error);
  end;

  if (vap^.va_atime.tv_sec<>VNOVAL) then
  begin
   de^.ufs_atime:=vap^.va_atime;
  end;
  if (vap^.va_mtime.tv_sec<>VNOVAL) then
  begin
   de^.ufs_mtime:=vap^.va_mtime;
  end;
  if (vap^.va_birthtime.tv_sec<>VNOVAL) then
  begin
   de^.ufs_btime:=vap^.va_birthtime;
  end;
  c:=1;
 end;

 if (c<>0) then
 begin
  vfs_timestamp(@de^.ufs_mtime);
 end;
 Exit(0);
end;

function ufs_readdir(ap:p_vop_readdir_args):Integer;
var
 error:Integer;
 uio:p_uio;
 dp:p_dirent;
 dd:p_ufs_dirent;
 de:p_ufs_dirent;
 dmp:p_ufs_mount;
 mp:p_mount;
 off:Int64;
 tmp_ncookies:PInteger;
begin
 tmp_ncookies:=nil;

 if (ap^.a_vp^.v_type<>VDIR) then
 begin
  Exit(ENOTDIR);
 end;

 uio:=ap^.a_uio;
 if (uio^.uio_offset < 0) then
 begin
  Exit(EINVAL);
 end;

 if (ap^.a_ncookies<>nil) then
 begin
  tmp_ncookies:=ap^.a_ncookies;
  ap^.a_ncookies^:=0;
  ap^.a_ncookies:=nil;
 end;

 mp:=ap^.a_vp^.v_mount;
 dmp:=VFSTOUFS(mp);

 error:=0;
 de:=ap^.a_vp^.v_data;
 off:=0;

 sx_xlock(@dmp^.ufs_lock);

 dd:=TAILQ_FIRST(@de^.ufs_dlist);
 while (dd<>nil) do
 begin
  dp:=dd^.ufs_dirent;

  if (dp^.d_reclen > uio^.uio_resid) then break;

  dp^.d_fileno:=dd^.ufs_inode;

  if (off >= uio^.uio_offset) then
  begin
   error:=vfs_read_dirent(ap, dp, off);
   if (error<>0) then break;
  end;

  Inc(off,dp^.d_reclen);
  dd:=TAILQ_NEXT(dd,@dd^.ufs_list);
 end;

 sx_xunlock(@dmp^.ufs_lock);
 uio^.uio_offset:=off;

 {
  * Restore ap^.a_ncookies if it wasn't originally nil in the first
  * place.
  }
 if (tmp_ncookies<>nil) then
  ap^.a_ncookies:=tmp_ncookies;

 Exit(error);
end;

function ufs_rread(ap:p_vop_read_args):Integer;
begin
 if (ap^.a_vp^.v_type<>VDIR) then
 begin
  Exit(EINVAL);
 end;

 Exit(VOP_READDIR(ap^.a_vp, ap^.a_uio, nil, nil, nil));
end;

function ufs_readlink(ap:p_vop_readlink_args):Integer;
var
 de:p_ufs_dirent;
begin
 de:=ap^.a_vp^.v_data;
 if (de^.ufs_dirent^.d_type<>DT_LNK) then Exit(EINVAL);

 Exit(uiomove(de^.ufs_symlink, strlen(de^.ufs_symlink), ap^.a_uio));
end;

function ufs_symlink(ap:p_vop_symlink_args):Integer;
var
 i,error:Integer;
 dd:p_ufs_dirent;
 de:p_ufs_dirent;
 dmp:p_ufs_mount;
begin
 error:=0;
 //error:=priv_check(curkthread, PRIV_DEVFS_SYMLINK);
 if (error<>0) then Exit(error);

 dmp:=VFSTOUFS(ap^.a_dvp^.v_mount);

 sx_xlock(@dmp^.ufs_lock);

 dd:=ap^.a_dvp^.v_data;
 de:=ufs_find(dd, ap^.a_cnp^.cn_nameptr, ap^.a_cnp^.cn_namelen, 0);

 if (de<>nil) then
 begin
  sx_xunlock(@dmp^.ufs_lock);
  Exit(EEXIST);
 end;

 de:=ufs_newdirent(ap^.a_cnp^.cn_nameptr, ap^.a_cnp^.cn_namelen);
 de^.ufs_flags:=0;
 de^.ufs_uid  :=0;
 de^.ufs_gid  :=0;
 de^.ufs_mode :=UFS_DEFAULT_MODE;
 de^.ufs_inode:=ufs_alloc_cdp_inode;
 de^.ufs_dir  :=dd;
 de^.ufs_dirent^.d_type:=DT_LNK;

 i:=strlen(ap^.a_target) + 1;
 de^.ufs_symlink:=AllocMem(i);
 Move(ap^.a_target^, de^.ufs_symlink^, i);

 TAILQ_INSERT_TAIL(@dd^.ufs_dlist,de,@de^.ufs_list);

 Exit(ufs_allocv(de, ap^.a_dvp^.v_mount, LK_EXCLUSIVE, ap^.a_vpp)); //sx_xunlock
end;

function ufs_remove(ap:p_vop_remove_args):Integer;
var
 dvp,vp:p_vnode;
 de:p_ufs_dirent;
 dmp:p_ufs_mount;
begin
 dvp:=ap^.a_dvp;
 vp:=ap^.a_vp;
 dmp:=VFSTOUFS(vp^.v_mount);

 ASSERT_VOP_ELOCKED(dvp, 'ufs_remove');
 ASSERT_VOP_ELOCKED(vp,  'ufs_remove');

 sx_xlock(@dmp^.ufs_lock);
 de:=vp^.v_data;

 if (de^.ufs_dirent^.d_type=DT_DIR) then
 begin
  Result:=_ufs_dir_status(dmp, de);
  if (Result<>0) then
  begin
   sx_xunlock(@dmp^.ufs_lock);
   Exit;
  end;
 end;

 VOP_UNLOCK(vp, 0);

 if (dvp<>vp) then
 begin
  VOP_UNLOCK(dvp, 0);
 end;

 if (de^.ufs_dirent^.d_type=DT_DIR) then
 begin
  _ufs_rmdir(dmp, de);
 end else
 begin
  ufs_delete(dmp, de, UFS_DEL_NORECURSE);
 end;

 sx_xunlock(@dmp^.ufs_lock);

 if (dvp<>vp) then
 begin
  vn_lock(dvp, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 end;

 vn_lock(vp, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});

 Exit(0);
end;

function ufs_rmdir(ap:p_vop_rmdir_args):Integer;
var
 dvp,vp:p_vnode;
 de:p_ufs_dirent;
 dmp:p_ufs_mount;
begin
 dvp:=ap^.a_dvp;
 vp:=ap^.a_vp;
 dmp:=VFSTOUFS(vp^.v_mount);

 ASSERT_VOP_ELOCKED(dvp, 'ufs_remove');
 ASSERT_VOP_ELOCKED(vp,  'ufs_remove');

 sx_xlock(@dmp^.ufs_lock);

 de:=vp^.v_data;

 Result:=_ufs_dir_status(dmp, de);
 if (Result<>0) then
 begin
  sx_xunlock(@dmp^.ufs_lock);
  Exit;
 end;

 VOP_UNLOCK(vp, 0);

 if (dvp<>vp) then
 begin
  VOP_UNLOCK(dvp, 0);
 end;

 _ufs_rmdir(dmp, de);

 sx_xunlock(@dmp^.ufs_lock);

 if (dvp<>vp) then
 begin
  vn_lock(dvp, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 end;

 vn_lock(vp, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});

 Exit(0);
end;

function ufs_mkdir(ap:p_vop_mkdir_args):Integer;
var
 dvp:p_vnode;
 cnp:p_componentname;
 vap:p_vattr;
 dmp:p_ufs_mount;
 dd:p_ufs_dirent;
 de:p_ufs_dirent;
begin
 dvp:=ap^.a_dvp;
 cnp:=ap^.a_cnp;
 vap:=ap^.a_vap;
 dmp:=VFSTOUFS(dvp^.v_mount);

 //priv_check

 sx_xlock(@dmp^.ufs_lock);

 dd:=dvp^.v_data;

 de:=ufs_find(dd, cnp^.cn_nameptr, cnp^.cn_namelen, 0);

 if (de<>nil) then
 begin
  sx_xunlock(@dmp^.ufs_lock);
  Exit(EEXIST);
 end;

 de:=ufs_vmkdir(dmp,ap^.a_cnp^.cn_nameptr,ap^.a_cnp^.cn_namelen,dd,0);

 if (de=nil) then
 begin
  sx_xunlock(@dmp^.ufs_lock);
  Exit(ENOMEM);
 end;

 de^.ufs_mode :=vap^.va_mode;

 Exit(ufs_allocv(de, ap^.a_dvp^.v_mount, LK_EXCLUSIVE, ap^.a_vpp)); //sx_xunlock
end;

function ufs_reclaim(ap:p_vop_reclaim_args):Integer;
var
 vp:p_vnode;
begin
 vp:=ap^.a_vp;

 ufs_relv(vp);

 vnode_destroy_vobject(vp);

 Exit(0);
end;

end.

