unit md_vnops;

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

interface

uses
 windows,
 ntapi,
 mqueue,
 vm_pmap,
 kern_param,
 time,
 vfile,
 vmount,
 vdirent,
 vuio,
 vnode,
 vnamei,
 vnode_if,
 vfilio,
 vfs_default,
 ufs,
 ufs_vnops,
 kern_mtx,
 kern_sx;

function md_mount(mp:p_ufs_mount):Integer;
function md_unmount(mp:p_ufs_mount):Integer;
function md_statfs(mp:p_mount;sbp:p_statfs):Integer;

function md_free_dirent(de:p_ufs_dirent):Integer;

function md_vmkdir(dmp:p_ufs_mount;name:PChar;namelen:Integer;dotdot:p_ufs_dirent):p_ufs_dirent;

function md_lookup(ap:p_vop_lookup_args):Integer;
function md_readdir(ap:p_vop_readdir_args):Integer;
function md_inactive(ap:p_vop_inactive_args):Integer;
function md_reclaim(ap:p_vop_reclaim_args):Integer;
function md_getattr(ap:p_vop_getattr_args):Integer;

function md_readlink(ap:p_vop_readlink_args):Integer;
function md_symlink(ap:p_vop_symlink_args):Integer;
function md_link(ap:p_vop_link_args):Integer;

function md_mkdir(ap:p_vop_mkdir_args):Integer;
function md_remove(ap:p_vop_remove_args):Integer;
function md_rmdir(ap:p_vop_rmdir_args):Integer;
function md_rename(ap:p_vop_rename_args):Integer;

function md_create(ap:p_vop_create_args):Integer;
function md_open(ap:p_vop_open_args):Integer;
function md_close(ap:p_vop_close_args):Integer;
function md_fsync(ap:p_vop_fsync_args):Integer;
function md_setattr(ap:p_vop_setattr_args):Integer;

function md_ioctl(ap:p_vop_ioctl_args):Integer;

function md_read(ap:p_vop_read_args):Integer;
function md_write(ap:p_vop_write_args):Integer;

function md_advlock(ap:p_vop_advlock_args):Integer;
function md_advlockpurge(ap:p_vop_advlockpurge_args):Integer;

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

  vop_islocked      :nil;
  vop_lookup        :@md_lookup;
  vop_create        :@md_create;
  vop_whiteout      :nil;
  vop_mknod         :nil;
  vop_open          :@md_open;
  vop_close         :@md_close;
  vop_access        :nil; //parent
  vop_accessx       :nil;
  vop_getattr       :@md_getattr;
  vop_setattr       :@md_setattr;
  vop_markatime     :nil;
  vop_read          :@md_read;
  vop_write         :@md_write;
  vop_ioctl         :@md_ioctl;
  vop_poll          :nil;
  vop_kqfilter      :nil;
  vop_revoke        :nil;
  vop_fsync         :@md_fsync;
  vop_remove        :@md_remove;
  vop_link          :@md_link;
  vop_rename        :@md_rename;
  vop_mkdir         :@md_mkdir;
  vop_rmdir         :@md_rmdir;
  vop_symlink       :@md_symlink;
  vop_readdir       :@md_readdir;
  vop_readlink      :@md_readlink;
  vop_inactive      :@md_inactive;
  vop_reclaim       :@md_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       :@md_advlock;
  vop_advlockasync  :nil;
  vop_advlockpurge  :@md_advlockpurge;
  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,
 vfcntl,
 vstat,
 vfs_subr,
 subr_uio,
 kern_thr,
 vnode_pager;

const
 UFS_SET_READONLY=(not &0222);

 FILE_DIR_ACCESS=SYNCHRONIZE or
                 FILE_LIST_DIRECTORY or
                 FILE_WRITE_DATA or
                 FILE_ADD_FILE or
                 FILE_ADD_SUBDIRECTORY or
                 FILE_TRAVERSE or
                 FILE_READ_ATTRIBUTES or
                 FILE_WRITE_ATTRIBUTES;

 FILE_SHARE_ALL=FILE_SHARE_READ or
                FILE_SHARE_WRITE or
                FILE_SHARE_DELETE;

type
 TOBJ_ATTR=packed record
  OATTR:OBJECT_ATTRIBUTES;
  UPATH:UNICODE_STRING;
 end;

 TNT_DIRENT=packed record
  Info:FILE_ID_FULL_DIR_INFORMATION;
  Name:array[0..260] of WCHAR;
 end;

 T_NT_SYMLINK=packed record
  info:REPARSE_DATA_BUFFER;
  Name:array[0..MAX_PATH*2] of WCHAR;
 end;

 T_NT_LINK=packed record
  info:FILE_LINK_INFORMATION;
  Name:array[0..MAX_PATH] of WCHAR;
 end;

 T_NT_RENAME=T_NT_LINK;

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

function INIT_UNICODE(FileName:PWideChar):UNICODE_STRING;
begin
 Result.Length       :=strlen(FileName)*SizeOf(WideChar);
 Result.MaximumLength:=Result.Length+SizeOf(WideChar);
 Result.Buffer       :=FileName;
end;

procedure INIT_OBJ(var OBJ:TOBJ_ATTR;fd:THandle;attr:ULONG;FileName:PWideChar);
begin
 OBJ.OATTR.Length:=SizeOf(OBJECT_ATTRIBUTES);

 OBJ.OATTR.RootDirectory:=fd;
 OBJ.OATTR.ObjectName   :=@OBJ.UPATH;
 OBJ.OATTR.Attributes   :=attr;

 OBJ.UPATH:=INIT_UNICODE(FileName);
end;

function ntf2px(n:Integer):Integer; inline;
begin
 Case DWORD(n) of
  STATUS_SUCCESS               :Result:=0;
  STATUS_PENDING               :Result:=EWOULDBLOCK;
  STATUS_NO_MORE_FILES         :Result:=0;
  STATUS_ACCESS_VIOLATION      :Result:=EFAULT;
  STATUS_INVALID_HANDLE        :Result:=EBADF;
  STATUS_NO_SUCH_FILE          :Result:=ENOENT;
  STATUS_END_OF_FILE           :Result:=0;
  STATUS_NO_MEMORY             :Result:=ENOMEM;
  STATUS_ACCESS_DENIED         :Result:=EACCES;
  STATUS_DISK_CORRUPT_ERROR    :Result:=EIO;
  STATUS_OBJECT_NAME_NOT_FOUND :Result:=ENOENT;
  STATUS_OBJECT_NAME_COLLISION :Result:=EEXIST;
  STATUS_OBJECT_PATH_NOT_FOUND :Result:=ENOENT;
  STATUS_OBJECT_PATH_SYNTAX_BAD:Result:=ENOTDIR;
  STATUS_SHARING_VIOLATION     :Result:=EACCES;
  STATUS_FILE_LOCK_CONFLICT    :Result:=EWOULDBLOCK;
  STATUS_LOCK_NOT_GRANTED      :Result:=EWOULDBLOCK;
  STATUS_RANGE_NOT_LOCKED      :Result:=ENOLCK;
  STATUS_DISK_FULL             :Result:=ENOSPC;
  STATUS_FILE_IS_A_DIRECTORY   :Result:=EISDIR;
  STATUS_NOT_SAME_DEVICE       :Result:=EXDEV;
  STATUS_INSUFFICIENT_RESOURCES:Result:=ENOMEM;
  STATUS_DIRECTORY_NOT_EMPTY   :Result:=ENOTEMPTY;
  STATUS_FILE_CORRUPT_ERROR    :Result:=EIO;
  STATUS_NOT_A_DIRECTORY       :Result:=ENOTDIR;
  STATUS_NAME_TOO_LONG         :Result:=ENAMETOOLONG;
  STATUS_IO_DEVICE_ERROR       :Result:=EIO;
  STATUS_TOO_MANY_LINKS        :Result:=EMLINK;
  STATUS_CANT_CROSS_RM_BOUNDARY:Result:=EXDEV;
  else
                                Result:=EINVAL;
 end;
end;

function get_unix_file_time(time:LARGE_INTEGER):timespec; inline;
begin
 Int64(time):=Int64(time)-DELTA_EPOCH_IN_UNIT;
 Result.tv_sec :=(Int64(time) div UNIT_PER_SEC);
 Result.tv_nsec:=(Int64(time) mod UNIT_PER_SEC)*NSEC_PER_UNIT;
end;

function get_win_file_time(time:timespec):LARGE_INTEGER; inline;
begin
 Int64(Result):=(time.tv_sec*UNIT_PER_SEC)+(time.tv_nsec div NSEC_PER_UNIT);
 Int64(Result):=Int64(Result)+DELTA_EPOCH_IN_UNIT;
end;

function NT_FA_TO_DT(a,e:ULONG):Byte;
begin
 if ((a and FILE_ATTRIBUTE_REPARSE_POINT)<>0) and
    (e=IO_REPARSE_TAG_SYMLINK) then
 begin
  Result:=DT_LNK;
 end else
 if ((a and FILE_ATTRIBUTE_DEVICE)<>0) then
 begin
  Result:=DT_CHR; //DT_FIFO ???
 end else
 if ((a and FILE_ATTRIBUTE_DIRECTORY)<>0) then
 begin
  Result:=DT_DIR;
 end else
 begin
  Result:=DT_REG;
 end;
end;

function get_inode(i:LARGE_INTEGER):Integer; inline;
begin
 Result:=(i.LowPart+i.HighPart); //simple hash
end;

procedure fix_win_path(name:PWideChar;namelen:Integer);
begin
 if (name=nil) then Exit;
 while (namelen>0) do
 begin
  if (name[0]='/') then
  begin
   name[0]:='\';
  end;
  Inc(name);
  Dec(namelen);
 end;
end;

procedure fix_unix_path(name:PAnsiChar;namelen:Integer);
begin
 if (name=nil) then Exit;
 while (namelen>0) do
 begin
  if (name[0]='\') then
  begin
   name[0]:='/';
  end;
  Inc(name);
  Dec(namelen);
 end;
end;

function _UTF8Encode(P:PWideChar;len:SizeInt):RawByteString;
var
 i:SizeInt;
 hs:RawByteString;
begin
 Result:='';
 if (p=nil) or (len<=0) then exit;
 hs:='';
 SetLength(hs,len*3);
 i:=UnicodeToUtf8(PAnsiChar(hs),length(hs)+1,P,len);
 if (i>0) then
 begin
  SetLength(hs,i-1);
  Result:=hs;
 end;
end;

function _UTF8Decode(P:PAnsiChar;len:SizeInt):WideString;
var
 i:SizeInt;
 hs:WideString;
begin
 Result:='';
 if (p=nil) or (len<=0) then exit;
 hs:='';
 SetLength(hs,len);
 i:=Utf8ToUnicode(PWideChar(hs),length(hs)+1,P,len);
 if (i>0) then
 begin
  SetLength(hs,i-1);
  Result:=hs;
 end;
end;

function md_mount_is_valid(vp:p_vnode):Integer;
var
 mp:p_mount;
begin
 Result:=EXDEV;
 mp:=vp^.v_mount;
 if (mp=nil) then Exit;
 if (mp^.mnt_vfc<>@ufs_vfsconf) then Exit;
 if ((mp^.mnt_flag and MNT_ROOTFS)<>0) then Exit;
 Result:=0;
end;

function md_mount(mp:p_ufs_mount):Integer;
var
 w:WideString;
 OBJ:TOBJ_ATTR;
 BLK:IO_STATUS_BLOCK;
 F:Thandle;
 R:DWORD;
begin
 w:=UTF8Decode(ExpandFileName(mp^.ufs_path));
 w:='\??\'+w;

 OBJ:=Default(TOBJ_ATTR);
 INIT_OBJ(OBJ,0,OBJ_CASE_INSENSITIVE,PWideChar(w));
 BLK:=Default(IO_STATUS_BLOCK);

 R:=NtOpenFile(@F,
               FILE_DIR_ACCESS,
               @OBJ,
               @BLK,
               FILE_SHARE_ALL,
               FILE_OPEN_FOR_BACKUP_INTENT or
               FILE_SYNCHRONOUS_IO_NONALERT or
               FILE_DIRECTORY_FILE
 );

 Result:=ntf2px(R);
 if (Result<>0) then Exit;

 mp^.ufs_md_fp:=Pointer(F);
end;

function md_unmount(mp:p_ufs_mount):Integer;
var
 fd:THandle;
begin
 fd:=THandle(System.InterlockedExchange(mp^.ufs_md_fp,nil));
 if (fd<>0) then
 begin
  NtClose(fd);
 end;
 Result:=0;
end;

function mul_div_u64(m,d,v:QWORD):QWORD; sysv_abi_default; assembler; nostackframe;
asm
 movq v,%rax
 mulq m
 divq d
end;

function md_statfs(mp:p_mount;sbp:p_statfs):Integer;
var
 dmp:p_ufs_mount;
 FFF:FILE_FS_FULL_SIZE_INFORMATION;
 BLK:IO_STATUS_BLOCK;
 Sector:Int64;
 R:DWORD;
begin
 dmp:=VFSTOUFS(mp);
 if (dmp^.ufs_md_fp=nil) then Exit(0);

 FFF:=Default(FILE_FS_FULL_SIZE_INFORMATION);
 BLK:=Default(IO_STATUS_BLOCK);

 R:=NtQueryVolumeInformationFile(
           THandle(dmp^.ufs_md_fp),
           @BLK,
           @FFF,
           SizeOf(FFF),
           FileFsFullSizeInformation
          );

 Result:=ntf2px(R);
 if (Result<>0) then Exit;

 Sector:=(FFF.SectorsPerAllocationUnit*FFF.BytesPerSector);

 sbp^.f_bavail:=mul_div_u64(Sector,DEV_BSIZE,QWORD(FFF.CallerAvailableAllocationUnits));
 sbp^.f_blocks:=mul_div_u64(Sector,DEV_BSIZE,QWORD(FFF.TotalAllocationUnits          ));
 sbp^.f_bfree :=mul_div_u64(Sector,DEV_BSIZE,QWORD(FFF.ActualAvailableAllocationUnits));
end;

function md_free_dirent(de:p_ufs_dirent):Integer;
var
 s:Pointer;
 fd:THandle;
begin
 s:=System.InterlockedExchange(de^.ufs_symlink,nil);
 if (s<>nil) then
 begin
  FreeMem(s);
 end;

 if ((de^.ufs_flags and UFS_DROOT)=0) then //if not root dir
 begin
  fd:=THandle(System.InterlockedExchange(de^.ufs_md_fp,nil));
  if (fd<>0) then
  begin
   NtClose(fd);
  end;
 end;
 Result:=0;
end;

function md_open_dirent(de:p_ufs_dirent):Integer;
var
 w:WideString;
 OBJ:TOBJ_ATTR;
 BLK:IO_STATUS_BLOCK;
 F:Thandle;
 R:DWORD;
begin
 Assert(de^.ufs_dir^.ufs_md_fp<>nil);

 w:=_UTF8Decode(@de^.ufs_dirent^.d_name,de^.ufs_dirent^.d_namlen);

 OBJ:=Default(TOBJ_ATTR);
 INIT_OBJ(OBJ,THandle(de^.ufs_dir^.ufs_md_fp),0,PWideChar(w));
 BLK:=Default(IO_STATUS_BLOCK);

 R:=NtOpenFile(@F,
               FILE_DIR_ACCESS,
               @OBJ,
               @BLK,
               FILE_SHARE_ALL,
               FILE_OPEN_FOR_BACKUP_INTENT or
               FILE_SYNCHRONOUS_IO_NONALERT or
               FILE_DIRECTORY_FILE
 );

 Result:=ntf2px(R);
 if (Result<>0) then Exit;

 de^.ufs_md_fp:=Pointer(F);
end;

function md_open_dirent_file(de:p_ufs_dirent;symlink:Boolean;fdr:PHandle):Integer;
var
 w:WideString;
 OBJ:TOBJ_ATTR;
 BLK:IO_STATUS_BLOCK;
 F:Thandle;
 opt:DWORD;
 R:DWORD;
begin
 sx_assert(@de^.ufs_md_lock);

 Assert(de^.ufs_dir^.ufs_md_fp<>nil);

 w:=_UTF8Decode(@de^.ufs_dirent^.d_name,de^.ufs_dirent^.d_namlen);

 OBJ:=Default(TOBJ_ATTR);
 INIT_OBJ(OBJ,THandle(de^.ufs_dir^.ufs_md_fp),0,PWideChar(w));
 BLK:=Default(IO_STATUS_BLOCK);

 opt:=FILE_OPEN_FOR_BACKUP_INTENT or FILE_SYNCHRONOUS_IO_NONALERT;

 if (de^.ufs_dirent^.d_type=DT_DIR) then
 begin
  opt:=opt or FILE_DIRECTORY_FILE;
 end;

 if symlink then
 begin
  opt:=opt or FILE_OPEN_REPARSE_POINT;
 end;

 R:=NtOpenFile(@F,
               SYNCHRONIZE or
               FILE_CAN_DELETE or
               FILE_READ_DATA or
               //FILE_WRITE_DATA or
               FILE_READ_ATTRIBUTES or
               FILE_WRITE_ATTRIBUTES,
               @OBJ,
               @BLK,
               FILE_SHARE_ALL,
               opt
 );


 Result:=ntf2px(R);
 if (Result<>0) then Exit;

 fdr^:=F;
end;

function md_find_rel_mount_path(first:p_mount;var src:PWideChar;var len:SizeInt):Integer;
var
 mp:p_mount;
 W:WideString;

 function compare(mp:p_mount):Boolean;
 begin
  Result:=False;
  if (mp^.mnt_vfc=@ufs_vfsconf) then //current fstype
  if ((mp^.mnt_flag and MNT_ROOTFS)=0) then //host mount
  begin
   W:=UTF8Decode(ExpandFileName(mp^.mnt_stat.f_mntfromname));

   if (len>=Length(W)) then
   begin

    //compare case insensetive
    if (CompareStringW(
         LOCALE_SYSTEM_DEFAULT,
         NORM_IGNORECASE,
         PWideChar(W),
         Length(W),
         PWideChar(src),
         Length(W)
        )=2) then
    begin
     Inc(src,Length(W));
     Dec(len,Length(W));

     Result:=True;
    end; //cmp
   end; //len
  end;
 end;

begin
 Result:=ERESTART;
 mtx_lock(mountlist_mtx);

 if (first<>nil) then
 if compare(first) then
 begin
  mtx_unlock(mountlist_mtx);
  Exit(0);
 end;

 mp:=TAILQ_FIRST(@mountlist);
 while (mp<>nil) do
 begin

  if (first<>mp) then
  if compare(mp) then
  begin
   Result:=0;
   Break;
  end;

  mp:=TAILQ_NEXT(mp,@mp^.mnt_list);
 end;

 mtx_unlock(mountlist_mtx);
end;

function md_update_symlink(de:p_ufs_dirent;fd:THandle):Integer;
var
 NT_SYMLINK:T_NT_SYMLINK;
 BLK:IO_STATUS_BLOCK;

 P:PWideChar;
 len:SizeInt;

 U:RawByteString;
 nt_abs:Boolean;

 R:DWORD;

 mp:p_mount;
begin
 sx_assert(@de^.ufs_md_lock);

 if (de^.ufs_dirent^.d_type<>DT_LNK) then Exit(EINVAL);

 if (de^.ufs_symlink<>nil) then Exit(0); //cached

 NT_SYMLINK:=Default(T_NT_SYMLINK);
 BLK:=Default(IO_STATUS_BLOCK);

 R:=NtFsControlFile(
           fd,
           0,
           nil,
           nil,
           @BLK,
           FSCTL_GET_REPARSE_POINT,
           nil,
           0,
           @NT_SYMLINK,
           SizeOf(NT_SYMLINK)
          );

 Result:=ntf2px(R);
 if (Result<>0) then Exit;

 //only symlink
 if (NT_SYMLINK.info.ReparseTag<>IO_REPARSE_TAG_SYMLINK) then
 begin
  Exit(ERESTART);
 end;

 P:=@NT_SYMLINK.info.SymbolicLinkReparseBuffer.PathBuffer;
 P:=Pointer(P)+NT_SYMLINK.info.SymbolicLinkReparseBuffer.SubstituteNameOffset;
 len:=NT_SYMLINK.info.SymbolicLinkReparseBuffer.SubstituteNameLength div 2;

 if (PQWORD(P)^=$005C003F003F005C) then // "\??\"
 begin
  Inc(P,4);
  Dec(len,4);

  nt_abs:=True;
 end else
 if (PWORD(P)^=$005C) then // "\"
 begin
  //broke struct
  Dec(P,2);
  Inc(len,2);

  P[0]:=GetCurrentDir[1];
  P[1]:=':';

  nt_abs:=True;
 end else
 if (PWORD(P)[1]=$003A) then // "C:"
 begin
  nt_abs:=True;
 end else
 begin
  nt_abs:=False;
 end;

 if nt_abs then
 begin
  mp:=nil;

  if (de^.ufs_vnode<>nil) then
  begin
   mp:=de^.ufs_vnode^.v_mount;
  end;

  //find by mount points
  Result:=md_find_rel_mount_path(mp,P,len);
  if (Result<>0) then Exit;
 end;

 U:=_UTF8Encode(P,len);

 fix_unix_path(PAnsiChar(U),Length(U));

 //save to cache
 de^.ufs_symlink:=AllocMem(Length(U)+1);
 Move(PAnsiChar(U)^, de^.ufs_symlink^, Length(U));
end;

function md_update_dirent(FD:THandle;de:p_ufs_dirent;prev:PFILE_BASIC_INFORMATION):Integer;
label
 _retry,
 _exit;
var
 RL:THandle;

 FBI:FILE_BASIC_INFORMATION;
 FSI:FILE_STANDARD_INFORMATION;
 FII:FILE_INTERNAL_INFORMATION;
 BLK:IO_STATUS_BLOCK;
 R:DWORD;
begin
 Result:=0;
 if (de=nil) then Exit;

 sx_assert(@de^.ufs_md_lock);

 if (FD<>0) then
 begin
  RL:=0;
 end else
 if (de^.ufs_md_fp=nil) then
 begin
  Result:=md_open_dirent_file(de,(de^.ufs_dirent^.d_type=DT_LNK),@FD);
  if (Result<>0) then Exit;
  RL:=FD;
 end else
 begin
  FD:=THandle(de^.ufs_md_fp);
  RL:=0;
 end;

 if (prev<>nil) then
 begin
  //just copy
  FBI:=prev^;
 end else
 begin
  //load time and file type
  FBI:=Default(FILE_BASIC_INFORMATION);
  BLK:=Default(IO_STATUS_BLOCK);

  R:=NtQueryInformationFile(
      FD,
      @BLK,
      @FBI,
      SizeOf(FBI),
      FileBasicInformation
     );

  Result:=ntf2px(R);
  if (Result<>0) then goto _exit;
 end;

 de^.ufs_atime:=get_unix_file_time(FBI.LastAccessTime);
 de^.ufs_mtime:=get_unix_file_time(FBI.LastWriteTime);
 de^.ufs_ctime:=get_unix_file_time(FBI.ChangeTime);
 de^.ufs_btime:=get_unix_file_time(FBI.CreationTime);

 ////

 _retry:

 if (de^.ufs_dirent^.d_type=DT_LNK) then
 begin
  //load symlink info
  if (de^.ufs_symlink=nil) then
  begin
   Result:=md_update_symlink(de,FD);

   if (Result=ERESTART) then
   begin
    Result:=0;
    //update type
    de^.ufs_dirent^.d_type:=NT_FA_TO_DT(FBI.FileAttributes,0);
    goto _retry;
   end;
   if (Result<>0) then goto _exit;
  end;
 end else
 if (de^.ufs_dirent^.d_type<>DT_DIR) then
 begin
  //load size
  FSI:=Default(FILE_STANDARD_INFORMATION);
  BLK:=Default(IO_STATUS_BLOCK);

  R:=NtQueryInformationFile(
      FD,
      @BLK,
      @FSI,
      SizeOf(FSI),
      FileStandardInformation
     );

  Result:=ntf2px(R);
  if (Result<>0) then goto _exit;

  de^.ufs_links:=FSI.NumberOfLinks;
  de^.ufs_size :=Int64(FSI.EndOfFile);
  de^.ufs_bytes:=Int64(FSI.AllocationSize);
 end;

 ////

 if (de^.ufs_inode=0) then
 begin
  //load inode
  FII:=Default(FILE_INTERNAL_INFORMATION);
  BLK:=Default(IO_STATUS_BLOCK);

  R:=NtQueryInformationFile(
      FD,
      @BLK,
      @FII,
      SizeOf(FII),
      FileInternalInformation
     );

  Result:=ntf2px(R);
  if (Result<>0) then goto _exit;

  de^.ufs_inode:=get_inode(FII.IndexNumber);
  de^.ufs_dirent^.d_fileno:=de^.ufs_inode;
 end;

_exit:
 if (RL<>0) then
 begin
  NtClose(RL);
 end;
end;

function md_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;

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

 sx_init(@de^.ufs_md_lock, 'md_lock');
 TAILQ_INIT(@de^.ufs_dlist);

 Exit(de);
end;

function md_vmkdir(dmp:p_ufs_mount;name:PChar;namelen:Integer;dotdot:p_ufs_dirent):p_ufs_dirent;
var
 nd:p_ufs_dirent;
 error:Integer;
begin
 { Create the new directory }
 nd:=md_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 (dotdot=nil) then
 begin
  //move root handle
  nd^.ufs_flags:=UFS_DROOT;
  nd^.ufs_md_fp:=dmp^.ufs_md_fp;
 end else
 begin
  error:=md_open_dirent(nd);
  if (error<>0) then
  begin
   ufs_de_drop(nd);
   Exit(nil);
  end;

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

 sx_xlock(@nd^.ufs_md_lock);
 error:=md_update_dirent(0,nd,nil);
 sx_xunlock(@nd^.ufs_md_lock);

 if (error<>0) then
 begin
  ufs_de_drop(nd);
  Exit(nil);
 end;

 Exit(nd);
end;

function md_find_cache(dd:p_ufs_dirent;name:PChar;namelen:Integer;_type:Integer):p_ufs_dirent;
var
 de:p_ufs_dirent;
begin
 sx_assert(@dd^.ufs_md_lock);

 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 md_new_cache(dd:p_ufs_dirent;name:PChar;namelen:Integer;prev:PFILE_BASIC_INFORMATION;var nd:p_ufs_dirent):Integer;
var
 de:p_dirent;
begin
 sx_assert(@dd^.ufs_md_lock);

 Result:=0;

 nd:=md_newdirent(name, namelen);

 nd^.ufs_mode :=UFS_DEFAULT_MODE;
 nd^.ufs_dir  :=dd;

 if ((prev^.FileAttributes and FILE_ATTRIBUTE_READONLY)<>0) then
 begin
  nd^.ufs_mode:=nd^.ufs_mode and UFS_SET_READONLY;
 end;

 de:=nd^.ufs_dirent;

 de^.d_fileno:=nd^.ufs_inode;
 de^.d_type  :=NT_FA_TO_DT(prev^.FileAttributes,IO_REPARSE_TAG_SYMLINK);

 if (de^.d_type=DT_DIR) then
 begin
  nd^.ufs_links:=2;
  Result:=md_open_dirent(nd);
  if (Result<>0) then
  begin
   ufs_de_drop(nd);
   nd:=nil;
   Exit;
  end;
 end;

 sx_xlock(@nd^.ufs_md_lock);
 Result:=md_update_dirent(0,nd,prev);
 sx_unlock(@nd^.ufs_md_lock);

 if (Result<>0) then
 begin
  ufs_de_drop(nd);
  nd:=nil;
  Exit;
 end;

 //link->dir
 if (nd^.ufs_md_fp=nil) and (de^.d_type=DT_DIR) then
 begin
  nd^.ufs_links:=2;
  Result:=md_open_dirent(nd);
  if (Result<>0) then
  begin
   ufs_de_drop(nd);
   nd:=nil;
   Exit;
  end;
 end;

 TAILQ_INSERT_TAIL(@dd^.ufs_dlist,nd,@nd^.ufs_list);
 Inc(dd^.ufs_links);
 ufs_de_hold(dd);
end;

function md_lookup_dirent(dd:p_ufs_dirent;name:PChar;namelen:Integer;var nd:p_ufs_dirent):Integer;
var
 w:WideString;

 FBI:FILE_BASIC_INFORMATION;
 OBJ:TOBJ_ATTR;
 R:DWORD;
begin
 sx_assert(@dd^.ufs_md_lock);

 Result:=0;
 nd:=nil;

 w:=_UTF8Decode(name,namelen);

 OBJ:=Default(TOBJ_ATTR);
 INIT_OBJ(OBJ,THandle(dd^.ufs_md_fp),0,PWideChar(w));

 R:=NtQueryAttributesFile(@OBJ,@FBI);

 Result:=ntf2px(R);
 if (Result<>0) then Exit;

 Result:=md_new_cache(dd,name,namelen,@FBI,nd);
end;

procedure md_unlink_cache(de:p_ufs_dirent;curr_locked_ownership,parent_locked:Boolean);
label
 _start;
var
 dd,exclude_parent:p_ufs_dirent;
 curr_hold_ownership:Boolean;
begin
 if (de=nil) then Exit;

 exclude_parent:=nil;
 curr_hold_ownership:=False;

 _start:

 Assert(sx_xlocked(@de^.ufs_md_lock)=curr_locked_ownership);

 if not curr_locked_ownership then
 begin
  curr_locked_ownership:=True;
  sx_xlock(@de^.ufs_md_lock);
 end;

 dd:=System.InterlockedExchange(de^.ufs_dir,nil); //parent

 if ((de^.ufs_flags and UFS_CREATE)<>0) then
 begin
  //unlink soft
  de^.ufs_dir:=nil;
  sx_unlock(@de^.ufs_md_lock);
  ufs_de_drop(dd);
  Exit;
 end;

 if (dd<>nil) then
 begin
  ufs_de_hold(dd); //hold parent

  Assert(sx_xlocked(@dd^.ufs_md_lock)=parent_locked);

  if parent_locked then
  begin
   if (exclude_parent=nil) then
   begin
    //exclude parent lock ownership
    exclude_parent:=dd;
   end;
  end else
  begin
   parent_locked:=True;
   //relock
   sx_unlock(@de^.ufs_md_lock);
   sx_xlock (@dd^.ufs_md_lock);
   sx_xlock (@de^.ufs_md_lock);
  end;

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

  if curr_locked_ownership then
  begin
   curr_locked_ownership:=False;
   if (exclude_parent<>de) then
   begin
    sx_unlock(@de^.ufs_md_lock);
   end;
  end;

  if curr_hold_ownership then
  begin
   curr_hold_ownership:=False;
   ufs_de_drop(de); //drop prev hold
  end;

  ufs_de_drop(dd); //drop list hold

  if (TAILQ_EMPTY(@dd^.ufs_dlist) and (dd^.ufs_dir<>nil)) then
  begin
   //need to go down further

   curr_hold_ownership  :=True;  //hold parent -> hold curr
   curr_locked_ownership:=True;  //lock parent -> lock curr
   parent_locked        :=False;

   de:=dd;

   goto _start;
  end;

  //end

  if parent_locked and (exclude_parent<>dd) then
  begin
   sx_unlock(@dd^.ufs_md_lock);
  end;

  ufs_de_drop(dd); //drop parent
 end;

 if curr_locked_ownership and (exclude_parent<>de) then
 begin
  sx_unlock(@de^.ufs_md_lock);
 end;

 if curr_hold_ownership then
 begin
  ufs_de_drop(de); //drop prev hold
 end;
end;

procedure md_delete_cache(de:p_ufs_dirent);
begin
 if (de=nil) then Exit;

 sx_xlock(@de^.ufs_md_lock);

 Assert(de^.ufs_ref>0);

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

 md_unlink_cache(de,True,False);

 ufs_de_drop(de);
end;

function md_inactive(ap:p_vop_inactive_args):Integer;
begin
 Exit(0);
end;

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

 de:=ufs_relv(vp);
 if (de<>nil) then
 begin
  VI_LOCK(vp);
  md_delete_cache(de);
  VI_UNLOCK(vp);
 end;

 vnode_destroy_vobject(vp);

 Exit(0);
end;

function md_lookup(ap:p_vop_lookup_args):Integer;
var
 cnp:p_componentname;
 dvp:p_vnode;
 vpp:pp_vnode;
 de,dd:p_ufs_dirent;
 error,flags,nameiop:Integer;
 pname:PChar;
 dmp:p_ufs_mount;
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 (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;

 sx_xlock(@dd^.ufs_md_lock);

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

 if (de=nil) then
 begin
  Result:=md_lookup_dirent(dd,cnp^.cn_nameptr,cnp^.cn_namelen,de);
 end;

 sx_xunlock(@dd^.ufs_md_lock);

 if (de=nil) then
 begin
  //not found or access error

  if (Result=ENOENT) then //only not found case
  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; //Result:=md_lookup_dirent
 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;

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

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

 Exit(error);
end;

function md_readdir(ap:p_vop_readdir_args):Integer;
var
 uio:p_uio;
 dd:p_ufs_dirent;
 dt:t_dirent;
 off:Int64;
 i:Integer;

 NT_DIRENT:TNT_DIRENT;
 BLK:IO_STATUS_BLOCK;
 R:DWORD;
 restart:Boolean;
begin
 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;

 dd:=ap^.a_vp^.v_data;
 off:=0;
 restart:=True;

 sx_xlock(@dd^.ufs_md_lock);

 repeat
  NT_DIRENT:=Default(TNT_DIRENT);
  BLK:=Default(IO_STATUS_BLOCK);

  R:=NtQueryDirectoryFile(
            THandle(dd^.ufs_md_fp),
            0,
            nil,
            nil,
            @BLK,
            @NT_DIRENT,
            SizeOf(NT_DIRENT),
            FileIdFullDirectoryInformation,
            True,
            nil,
            restart
           );
  restart:=false;

  if (R=STATUS_NO_MORE_FILES) then Break;

  Result:=ntf2px(R);
  if (Result<>0) then Break;

  dt:=Default(t_dirent);

  i:=UnicodeToUtf8(@dt.d_name,
                   t_dirent.MAXNAMLEN+1,
                   @NT_DIRENT.Name,
                   NT_DIRENT.Info.FileNameLength div 2);

  if (i<=0) then
  begin
   //skip error
   Continue;
  end;

  dt.d_reclen:=SizeOf(t_dirent)-(t_dirent.MAXNAMLEN+1)+((i + 3) and (not 3)); //zero include

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

  if (off >= uio^.uio_offset) then
  begin
   dt.d_fileno:=get_inode(NT_DIRENT.Info.FileId);
   dt.d_type  :=NT_FA_TO_DT(NT_DIRENT.Info.FileAttributes,NT_DIRENT.Info.EaSize);
   dt.d_namlen:=i-1; //zero exclude

   Result:=vfs_read_dirent(ap, @dt, off);
   if (Result<>0) then break;
  end;

  Inc(off,dt.d_reclen);
 until false;

 sx_xunlock(@dd^.ufs_md_lock);
 uio^.uio_offset:=off;

 Exit(0);
end;

function md_getattr(ap:p_vop_getattr_args):Integer;
var
 vp:p_vnode;
 de:p_ufs_dirent;

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

 VI_LOCK(vp);
 sx_xlock(@de^.ufs_md_lock);

 Result:=md_update_dirent(THandle(vp^.v_un),de,nil);

 vnode_pager_setsize(vp, de^.ufs_size);

 sx_xunlock(@de^.ufs_md_lock);
 VI_UNLOCK(vp);

 if (Result<>0) then Exit;

 Result:=ufs_getattr(ap);
end;

function md_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);

 sx_xlock(@de^.ufs_md_lock);

 Result:=0;
 if (de^.ufs_symlink=nil) then //not cached
 begin
  Result:=md_update_dirent(0,de,nil);
 end;

 if (Result<>0) then
 begin
  sx_xunlock(@de^.ufs_md_lock);
  Exit;
 end;

 Result:=uiomove(de^.ufs_symlink, strlen(de^.ufs_symlink), ap^.a_uio);

 sx_xunlock(@de^.ufs_md_lock);
end;

function md_symlink(ap:p_vop_symlink_args):Integer;
const
 REPARSE_DATA_OFFSET=ptrint(@REPARSE_DATA_BUFFER(nil^).GenericReparseBuffer);
label
 _del,
 _err;
var
 len,error:Integer;
 dd:p_ufs_dirent;
 de:p_ufs_dirent;
 dmp:p_ufs_mount;

 Privilege:DWORD;
 PrivState:Pointer;

 w:WideString;

 OBJ:TOBJ_ATTR;
 BLK:IO_STATUS_BLOCK;
 NT_SYMLINK:T_NT_SYMLINK;
 FBI:FILE_BASIC_INFORMATION;

 FD:THandle;
 R:DWORD;
 del_on_close:Boolean;
begin
 error:=0;
 //error:=priv_check(curkthread, PRIV_DEVFS_SYMLINK);
 if (error<>0) then Exit(error);

 len:=strlen(ap^.a_target);
 if (len<=0) then Exit(EINVAL);
 if (len>MAX_PATH) then Exit(ENAMETOOLONG);

 //Get Privilege
 Privilege:=SE_CREATE_SYMBOLIC_LINK_PRIVILEGE;
 R:=RtlAcquirePrivilege(@Privilege,1,0,@PrivState);

 if (R<>0) then Exit(EPERM);

 dd:=ap^.a_dvp^.v_data;

 sx_xlock(@dd^.ufs_md_lock);

 w:=_UTF8Decode(ap^.a_cnp^.cn_nameptr, ap^.a_cnp^.cn_namelen);

 OBJ:=Default(TOBJ_ATTR);
 INIT_OBJ(OBJ,THandle(dd^.ufs_md_fp),0,PWideChar(w));
 BLK:=Default(IO_STATUS_BLOCK);

 R:=NtCreateFile(@FD,
                 FILE_READ_ATTRIBUTES or
                 FILE_WRITE_ATTRIBUTES or
                 FILE_CAN_DELETE or
                 SYNCHRONIZE,
                 @OBJ,
                 @BLK,
                 nil,
                 FILE_ATTRIBUTE_NORMAL,
                 0,
                 FILE_CREATE,
                 FILE_SYNCHRONOUS_IO_NONALERT or
                 FILE_OPEN_REPARSE_POINT or
                 FILE_NON_DIRECTORY_FILE {FILE_DIRECTORY_FILE}, //target dir or file ???
                 nil,
                 0);

 Result:=ntf2px(R);
 if (Result<>0) then goto _err;

 w:=_UTF8Decode(ap^.a_target,len);
 len:=Length(w);

 NT_SYMLINK:=Default(T_NT_SYMLINK);

 NT_SYMLINK.info.ReparseTag       :=IO_REPARSE_TAG_SYMLINK;
 NT_SYMLINK.info.ReparseDataLength:=(SizeOf(REPARSE_DATA_BUFFER)-REPARSE_DATA_OFFSET)+(len*4);

 NT_SYMLINK.info.SymbolicLinkReparseBuffer.PrintNameOffset     :=0;
 NT_SYMLINK.info.SymbolicLinkReparseBuffer.PrintNameLength     :=(len*SizeOf(WideChar));
 NT_SYMLINK.info.SymbolicLinkReparseBuffer.SubstituteNameOffset:=(len*SizeOf(WideChar));
 NT_SYMLINK.info.SymbolicLinkReparseBuffer.SubstituteNameLength:=(len*SizeOf(WideChar));

 if (ap^.a_target[0]<>'/') then //is relative
 begin
  NT_SYMLINK.info.SymbolicLinkReparseBuffer.Flags              :=SYMLINK_FLAG_RELATIVE;
  //only from relative
  fix_win_path(PWideChar(w),len);
 end;

 Move(PWideChar(w)^,NT_SYMLINK.Name[0]  ,len*SizeOf(WideChar));
 Move(PWideChar(w)^,NT_SYMLINK.Name[len],len*SizeOf(WideChar));

 BLK:=Default(IO_STATUS_BLOCK);

 //Need SE_CREATE_SYMBOLIC_LINK_PRIVILEGE
 R:=NtFsControlFile(FD,
                    0,
                    nil,
                    nil,
                    @BLK,
                    FSCTL_SET_REPARSE_POINT,
                    @NT_SYMLINK,
                    NT_SYMLINK.info.ReparseDataLength+REPARSE_DATA_OFFSET,
                    nil,
                    0);

 Result:=ntf2px(R);
 if (Result<>0) then
 begin
  //mark delete on close handle
  _del:
  del_on_close:=true;
  NtSetInformationFile(FD,@BLK,@del_on_close,1,FileDispositionInformation);
  NtClose(FD);
  _err:
  sx_xunlock(@dd^.ufs_md_lock);
  RtlReleasePrivilege(PrivState);
  Exit;
 end;

 FBI:=Default(FILE_BASIC_INFORMATION);
 BLK:=Default(IO_STATUS_BLOCK);

 R:=NtQueryInformationFile(
     FD,
     @BLK,
     @FBI,
     SizeOf(FBI),
     FileBasicInformation
    );

 Result:=ntf2px(R);
 if (Result<>0) then goto _del;

 NtClose(FD);

 //clear cache
 de:=md_find_cache(dd,ap^.a_cnp^.cn_nameptr,ap^.a_cnp^.cn_namelen,0);
 md_unlink_cache(de,False,True);

 //new dirent
 Result:=md_new_cache(dd,ap^.a_cnp^.cn_nameptr,ap^.a_cnp^.cn_namelen,@FBI,de);

 sx_xunlock(@dd^.ufs_md_lock);
 RtlReleasePrivilege(PrivState);

 if (de=nil) then Exit; //if new fail

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

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

function md_link(ap:p_vop_link_args):Integer;
var
 cnp:p_componentname;
 dd:p_ufs_dirent;
 de:p_ufs_dirent;

 i:Integer;
 FD:THandle;
 NT_LINK:T_NT_LINK;
 BLK:IO_STATUS_BLOCK;
 R:DWORD;
begin
 Result:=md_mount_is_valid(ap^.a_tdvp);
 if (Result<>0) then Exit;

 cnp:=ap^.a_cnp;
 dd:=ap^.a_tdvp^.v_data;
 de:=ap^.a_vp^.v_data;

 sx_xlock(@dd^.ufs_md_lock);
 sx_xlock(@de^.ufs_md_lock);

 Result:=md_open_dirent_file(de,True,@FD);

 sx_xunlock(@de^.ufs_md_lock);

 if (Result<>0) then
 begin
  sx_xunlock(@dd^.ufs_md_lock);
  Exit;
 end;

 NT_LINK:=Default(T_NT_LINK);
 NT_LINK.info.ReplaceIfExists:=false;
 NT_LINK.info.RootDirectory  :=THandle(dd^.ufs_md_fp);

 i:=Utf8ToUnicode(@NT_LINK.Name,
                  MAX_PATH,
                  cnp^.cn_nameptr,
                  cnp^.cn_namelen);

 if (i<=0) then
 begin
  sx_xunlock(@dd^.ufs_md_lock);
  NtClose(FD);
  Exit(ENAMETOOLONG);
 end;

 NT_LINK.info.FileNameLength:=(i-1)*SizeOf(WideChar); //zero exclude

 BLK:=Default(IO_STATUS_BLOCK);

 R:=NtSetInformationFile(FD,
                         @BLK,
                         @NT_LINK,
                         NT_LINK.info.FileNameLength+24,
                         FileLinkInformation);

 Result:=ntf2px(R);

 if (Result=0) then
 begin
  //clear cache
  de:=md_find_cache(dd,cnp^.cn_nameptr,cnp^.cn_namelen,0);
  md_unlink_cache(de,False,True);
 end;

 sx_xunlock(@dd^.ufs_md_lock);
 NtClose(FD);
end;

function md_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;

 w:WideString;
 OBJ:TOBJ_ATTR;
 BLK:IO_STATUS_BLOCK;
 FD:Thandle;
 R:DWORD;
begin
 dvp:=ap^.a_dvp;
 cnp:=ap^.a_cnp;
 vap:=ap^.a_vap;
 dmp:=VFSTOUFS(dvp^.v_mount);

 dd:=dvp^.v_data;

 sx_xlock(@dd^.ufs_md_lock);

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

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

 w:=_UTF8Decode(cnp^.cn_nameptr,cnp^.cn_namelen);

 OBJ:=Default(TOBJ_ATTR);
 INIT_OBJ(OBJ,THandle(dd^.ufs_md_fp),0,PWideChar(w));
 BLK:=Default(IO_STATUS_BLOCK);

 R:=NtCreateFile(@FD,
                 SYNCHRONIZE or
                 FILE_LIST_DIRECTORY or
                 FILE_READ_ATTRIBUTES or
                 FILE_WRITE_ATTRIBUTES,
                 @OBJ,
                 @BLK,
                 nil,
                 FILE_ATTRIBUTE_NORMAL,
                 FILE_SHARE_READ or
                 FILE_SHARE_WRITE,
                 FILE_CREATE,
                 FILE_DIRECTORY_FILE or
                 FILE_SYNCHRONOUS_IO_NONALERT or
                 FILE_OPEN_FOR_BACKUP_INTENT or
                 FILE_OPEN_REPARSE_POINT,
                 nil,
                 0);

 Result:=ntf2px(R);
 if (Result<>0) then
 begin
  sx_xunlock(@dd^.ufs_md_lock);
  Exit;
 end;

 NtClose(FD);

 //clear cache
 de:=md_find_cache(dd,ap^.a_cnp^.cn_nameptr,ap^.a_cnp^.cn_namelen,0);
 md_unlink_cache(de,False,True);

 de:=md_vmkdir(dmp,cnp^.cn_nameptr,cnp^.cn_namelen,dd);

 if (de=nil) then
 begin
  Exit(ENOMEM);
 end;

 de^.ufs_mode:=vap^.va_mode;

 sx_xunlock(@dd^.ufs_md_lock);

 sx_xlock(@dmp^.ufs_lock);

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

function md_remove(ap:p_vop_remove_args):Integer;
var
 dvp,vp:p_vnode;
 dd,de:p_ufs_dirent;

 FD:THandle;
 BLK:IO_STATUS_BLOCK;
 R:DWORD;

 del_on_close:Boolean;
begin
 dvp:=ap^.a_dvp;
 vp:=ap^.a_vp;

 dd:=dvp^.v_data;
 de:=vp^.v_data;

 sx_xlock(@dd^.ufs_md_lock);
 sx_xlock(@de^.ufs_md_lock);

 Result:=md_open_dirent_file(de,True,@FD);

 sx_xunlock(@de^.ufs_md_lock);

 if (Result<>0) then
 begin
  sx_xunlock(@dd^.ufs_md_lock);
  Exit;
 end;

 BLK:=Default(IO_STATUS_BLOCK);
 del_on_close:=true;

 R:=NtSetInformationFile(FD,@BLK,@del_on_close,1,FileDispositionInformation);

 Result:=ntf2px(R);
 if (Result<>0) then
 begin
  sx_xunlock(@dd^.ufs_md_lock);
  NtClose(FD);
  Exit;
 end;

 //clear cache
 md_unlink_cache(de,False,True);

 NtClose(FD); //<-deleted

 sx_xunlock(@dd^.ufs_md_lock);
end;

function md_rmdir(ap:p_vop_rmdir_args):Integer;
var
 dvp,vp:p_vnode;
 dd,de:p_ufs_dirent;

 FD:THandle;
 BLK:IO_STATUS_BLOCK;
 R:DWORD;

 del_on_close:Boolean;
begin
 dvp:=ap^.a_dvp;
 vp:=ap^.a_vp;

 dd:=dvp^.v_data;
 de:=vp^.v_data;

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

 sx_xlock(@dd^.ufs_md_lock);
 sx_xlock(@de^.ufs_md_lock);

 Result:=md_open_dirent_file(de,True,@FD);

 sx_xunlock(@de^.ufs_md_lock);

 if (Result<>0) then
 begin
  sx_xunlock(@dd^.ufs_md_lock);
  Exit;
 end;

 BLK:=Default(IO_STATUS_BLOCK);
 del_on_close:=true;

 R:=NtSetInformationFile(FD,@BLK,@del_on_close,1,FileDispositionInformation);

 Result:=ntf2px(R);
 if (Result<>0) then
 begin
  sx_xunlock(@dd^.ufs_md_lock);
  NtClose(FD);
  Exit;
 end;

 //clear cache
 md_unlink_cache(de,False,True);

 NtClose(FD); //<-deleted

 sx_xunlock(@dd^.ufs_md_lock);
end;

function md_rename(ap:p_vop_rename_args):Integer;
label
 _exit;
var
 dd_f,dd_t:p_ufs_dirent;
 de_f,de_t:p_ufs_dirent;
 cnp_t:p_componentname;

 FD:THandle;
 NT_RENAME:T_NT_RENAME;
 BLK:IO_STATUS_BLOCK;
 R:DWORD;
 i:Integer;

begin
 Result:=md_mount_is_valid(ap^.a_tdvp);
 if (Result<>0) then Exit;

 dd_f:=ap^.a_fdvp^.v_data;
 dd_t:=ap^.a_tdvp^.v_data;
 de_f:=ap^.a_fvp^.v_data;
 cnp_t:=ap^.a_tcnp;

 de_t:=nil;
 if (ap^.a_tvp<>nil) then
 begin
  de_t:=ap^.a_tvp^.v_data;
 end;

 sx_xlock(@dd_f^.ufs_md_lock);
 if (dd_f<>dd_t) then
 begin
  sx_xlock(@dd_t^.ufs_md_lock);
 end;

 sx_xlock(@de_f^.ufs_md_lock);

 Result:=md_open_dirent_file(de_f,True,@FD);
 if (Result<>0) then goto _exit;

 NT_RENAME:=Default(T_NT_RENAME);
 NT_RENAME.info.ReplaceIfExists:=True;
 NT_RENAME.info.RootDirectory  :=THandle(dd_t^.ufs_md_fp);

 i:=Utf8ToUnicode(@NT_RENAME.Name,
                  MAX_PATH,
                  cnp_t^.cn_nameptr,
                  cnp_t^.cn_namelen);

 if (i<=0) then
 begin
  Result:=ENAMETOOLONG;
  goto _exit;
 end;

 NT_RENAME.info.FileNameLength:=(i-1)*SizeOf(WideChar); //zero exclude

 BLK:=Default(IO_STATUS_BLOCK);

 R:=NtSetInformationFile(FD,
                         @BLK,
                         @NT_RENAME,
                         NT_RENAME.info.FileNameLength+24,
                         FileRenameInformation);

 Result:=ntf2px(R);

 if (Result<>0) then goto _exit;

 //clear cache
 md_unlink_cache(de_f,True ,True);
 md_unlink_cache(de_t,False,True);

 de_f:=nil;

 _exit:

 if (de_f<>nil) then
 begin
  sx_xunlock(@de_f^.ufs_md_lock);
 end;

 sx_xunlock(@dd_f^.ufs_md_lock);
 if (dd_f<>dd_t) then
 begin
  sx_xunlock(@dd_t^.ufs_md_lock);
 end;

 NtClose(FD);
end;

Function GetDesiredAccess(flags:Integer):DWORD; inline;
begin
 Result:=SYNCHRONIZE or
         FILE_READ_ATTRIBUTES or
         FILE_WRITE_ATTRIBUTES;

 if ((flags and FREAD)<>0) then
 begin
  Result:=Result or FILE_READ_DATA;
 end;

 if ((flags and FWRITE)<>0) then
 begin
  Result:=Result or FILE_WRITE_DATA or FILE_APPEND_DATA;
 end;
end;

Function GetCreationDisposition(flags:Integer):DWORD; inline;
begin
 Result:=0;
 if ((flags and O_CREAT)<>0) then
 begin
  if ((flags and O_EXCL)<>0) then
  begin
   Result:=FILE_CREATE;
  end else
  if ((flags and O_TRUNC)<>0) then
  begin
   Result:=FILE_OVERWRITE_IF;
  end else
  begin
   Result:=FILE_OPEN_IF;
  end;
 end else
 if ((flags and O_TRUNC)<>0) then
 begin
  Result:=FILE_OVERWRITE;
 end else
 begin
  Result:=FILE_OPEN;
 end;
end;

Function GetFileAttrtibute(flags,mode:Integer):DWORD; inline;
begin
 Result:=FILE_ATTRIBUTE_NORMAL;
 if ((flags and O_CREAT)<>0) and
    ((mode and S_IWUSR)=0) then
 begin
  Result:=Result or FILE_ATTRIBUTE_READONLY;
 end;
end;

Function GetCreateOptions(flags:Integer):DWORD; inline;
begin
 Result:=FILE_SYNCHRONOUS_IO_NONALERT or
         FILE_NON_DIRECTORY_FILE;
 if ((flags and (O_FSYNC or O_DSYNC))<>0) then
 begin
  Result:=Result or FILE_WRITE_THROUGH;
 end;
end;

function md_create(ap:p_vop_create_args):Integer;
var
 dvp:p_vnode;
 cnp:p_componentname;
 vap:p_vattr;
 dmp:p_ufs_mount;

 dd:p_ufs_dirent;
 nd:p_ufs_dirent;
begin
 dvp:=ap^.a_dvp;
 cnp:=ap^.a_cnp;
 vap:=ap^.a_vap;

 dd:=dvp^.v_data;
 if (dd=nil) then Exit(EPERM);

 nd:=md_newdirent(cnp^.cn_nameptr,cnp^.cn_namelen);
 if (nd=nil) then Exit(ENOMEM);

 nd^.ufs_flags:=UFS_CREATE;
 nd^.ufs_mode :=vap^.va_mode;
 nd^.ufs_dir  :=dd;

 nd^.ufs_dirent^.d_type:=DT_REG;

 //link soft
 ufs_de_hold(dd);

 dmp:=VFSTOUFS(dvp^.v_mount);
 sx_xlock(@dmp^.ufs_lock);

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

function md_open(ap:p_vop_open_args):Integer;
var
 vp:p_vnode;
 flags:Integer;

 DA,FA,CD,CO:DWORD;

 dd:p_ufs_dirent;
 de:p_ufs_dirent;
 dc:p_ufs_dirent;

 ufs_size:Int64;

 w:WideString;
 OBJ:TOBJ_ATTR;
 BLK:IO_STATUS_BLOCK;

 FD:THandle;
 R:DWORD;
begin
 vp:=ap^.a_vp;
 flags:=ap^.a_mode;

 if (vp=nil) then Exit(EPERM);

 vp^.v_un:=nil;

 case vp^.v_type of
  VREG:;
  VLNK:Exit(0);
  VDIR:Exit(0);
  else
   Exit(EOPNOTSUPP);
 end;

 de:=vp^.v_data;
 if (de=nil) then Exit(EPERM);

 dd:=de^.ufs_dir;
 if (dd=nil) then Exit(EPERM);

 sx_xlock(@dd^.ufs_md_lock);
 sx_xlock(@de^.ufs_md_lock);

 w:=_UTF8Decode(@de^.ufs_dirent^.d_name,de^.ufs_dirent^.d_namlen);

 OBJ:=Default(TOBJ_ATTR);
 INIT_OBJ(OBJ,THandle(dd^.ufs_md_fp),0,PWideChar(w));
 BLK:=Default(IO_STATUS_BLOCK);

 DA:=GetDesiredAccess(flags);
 FA:=GetFileAttrtibute(flags,de^.ufs_mode);
 CD:=GetCreationDisposition(flags);
 CO:=GetCreateOptions(flags);

 R:=NtCreateFile(@FD,
                 DA,
                 @OBJ,
                 @BLK,
                 nil,
                 FA,
                 FILE_SHARE_ALL,
                 CD,
                 CO,
                 nil,
                 0);

 Result:=ntf2px(R);
 if (Result<>0) then
 begin
  md_unlink_cache(de,True,True);
  sx_xunlock(@dd^.ufs_md_lock);
  Exit;
 end;

 Result:=md_update_dirent(FD,de,nil);
 if (Result<>0) then
 begin
  md_unlink_cache(de,True,True);
  sx_xunlock(@dd^.ufs_md_lock);
  NtClose(FD);
  Exit;
 end;

 vp^.v_un:=Pointer(FD);

 ufs_size:=de^.ufs_size;

 if ((de^.ufs_flags and UFS_CREATE)<>0) then
 begin
  if ((flags and O_EXCL)<>0) then
  begin
   //clear cache
   dc:=md_find_cache(dd,@de^.ufs_dirent^.d_name,de^.ufs_dirent^.d_namlen,0);
   md_unlink_cache(dc,False,True);
  end;
  //
  de^.ufs_flags:=de^.ufs_flags and (not UFS_CREATE);
  //link cache
  TAILQ_INSERT_TAIL(@dd^.ufs_dlist,de,@de^.ufs_list);
  Inc(dd^.ufs_links);
 end;

 sx_unlock (@de^.ufs_md_lock);
 sx_xunlock(@dd^.ufs_md_lock);

 vnode_create_vobject(vp, ufs_size);
end;

function md_close(ap:p_vop_close_args):Integer;
var
 vp:p_vnode;
 FD:THandle;
begin
 vp:=ap^.a_vp;

 FD:=THandle(System.InterlockedExchange(vp^.v_un,nil));
 if (FD<>0) then
 begin
  NtClose(FD);
 end;

 vnode_destroy_vobject(vp);

 Result:=0;
end;

function md_fsync(ap:p_vop_fsync_args):Integer;
var
 vp:p_vnode;
 FD:THandle;
 fullsync:Boolean;

 BLK:IO_STATUS_BLOCK;
begin
 vp:=ap^.a_vp;
 FD:=THandle(vp^.v_un);
 fullsync:=((ap^.a_waitfor and 2)<>0);

 if (FD=0) then Exit(EINVAL);

 BLK:=Default(IO_STATUS_BLOCK);

 //result doesn't matter
 NtFlushBuffersFile(FD,@BLK);

 if fullsync then
 begin
  //atime
  //mtime
 end;

 Result:=0;
end;

function md_setattr(ap:p_vop_setattr_args):Integer;
label
 _err;
var
 de:p_ufs_dirent;
 vap:p_vattr;
 vp:p_vnode;
 error:Integer;
 uid:uid_t;
 gid:gid_t;

 change_time,change_size:Boolean;

 FD,RL:THandle;

 FBI:FILE_BASIC_INFORMATION;
 BLK:IO_STATUS_BLOCK;
 SIZE:Int64;
 R:DWORD;

 procedure _settime(var dst:LARGE_INTEGER;var src:timespec); inline;
 begin
  if (src.tv_sec<>-1) and (src.tv_nsec<>-1) then
  begin
   dst:=get_win_file_time(src);
  end;
 end;

begin
 Result:=0;

 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;

 VI_LOCK(vp);
 sx_xlock(@de^.ufs_md_lock);

 error:=0;
 change_time:=False;
 change_size:=False;

 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;
 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;
 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;

  change_time:=True;
 end;

 if (vap^.va_size<>VNOVAL) then
 begin
  change_size:=True;
 end;

 if change_time or change_size then
 begin

  if (vp^.v_un<>nil) then
  begin
   FD:=THandle(vp^.v_un);
   RL:=0;
  end else
  if (de^.ufs_md_fp<>nil) then
  begin
   FD:=THandle(de^.ufs_md_fp);
   RL:=0;
  end else
  begin
   Result:=md_open_dirent_file(de,True,@FD);
   if (Result<>0) then Exit;
   RL:=FD;
  end;

  if change_time then
  begin
   BLK:=Default(IO_STATUS_BLOCK);

   R:=NtQueryInformationFile(
       FD,
       @BLK,
       @FBI,
       SizeOf(FBI),
       FileBasicInformation);

   Result:=ntf2px(R);
   if (Result<>0) then goto _err;

   //update
   de^.ufs_ctime:=get_unix_file_time(FBI.ChangeTime);

   _settime(FBI.LastAccessTime,de^.ufs_atime);
   _settime(FBI.LastWriteTime ,de^.ufs_mtime);
   _settime(FBI.CreationTime  ,de^.ufs_btime);

   BLK:=Default(IO_STATUS_BLOCK);

   R:=NtSetInformationFile(
       FD,
       @BLK,
       @FBI,
       SizeOf(FBI),
       FileBasicInformation);

   Result:=ntf2px(R);
   if (Result<>0) then goto _err;
  end;

  if change_size then
  begin
   SIZE:=vap^.va_size;

   R:=NtSetInformationFile(
       FD,
       @BLK,
       @SIZE,
       SizeOf(Int64),
       FileEndOfFileInformation);

   if (R<>0) then
   begin
    R:=NtSetInformationFile(
        FD,
        @BLK,
        @SIZE,
        SizeOf(Int64),
        FileAllocationInformation);
   end;

   Result:=ntf2px(R);
   if (Result<>0) then goto _err;

   de^.ufs_size:=SIZE;

   vnode_pager_setsize(vp, SIZE);
  end;

  _err:

  sx_unlock(@de^.ufs_md_lock);
  VI_UNLOCK(vp);

  if (RL<>0) then
  begin
   NtClose(RL);
  end;
 end;

end;

function md_ioctl(ap:p_vop_ioctl_args):Integer;
begin
 Result:=EOPNOTSUPP;

 case ap^.a_command of
  FIOSEEKDATA:Result:=0;
  else;
 end;

end;

type
 t_uio_cb=function(
  FileHandle   :THandle;
  Event        :THandle;
  ApcRoutine   :Pointer;
  ApcContext   :Pointer;
  IoStatusBlock:PIO_STATUS_BLOCK;
  Buffer       :Pointer;
  Length       :ULONG;
  ByteOffset   :PLARGE_INTEGER;
  Key          :PULONG
 ):DWORD; stdcall;

 t_iov_cb=procedure(iov:p_iovec);

procedure iov_null(iov:p_iovec); assembler; nostackframe;
asm
 //
end;

function md_io(vp:p_vnode;uio:p_uio;ioflag:Integer):Integer;
var
 td:p_kthread;

 de:p_ufs_dirent;
 F:Thandle;
 iocb:t_uio_cb;
 ioin:PInt64;

 append:Boolean;
 locked:Boolean;

 iov:p_iovec;
 vec:iovec;
 iol:t_iov_cb;

 OFFSET:Int64;
 BLK:IO_STATUS_BLOCK;
 R:DWORD;
begin
 Result:=0;
 de:=vp^.v_data;
 F:=THandle(vp^.v_un);

 td:=curkthread;
 if (td=nil) then Exit(-1);

 ioin:=nil;
 case uio^.uio_rw of
  UIO_READ :ioin:=@td^.td_ru.ru_inblock;
  UIO_WRITE:ioin:=@td^.td_ru.ru_oublock;
 end;

 iocb:=nil;
 case uio^.uio_rw of
  UIO_READ :iocb:=@NtReadFile;
  UIO_WRITE:iocb:=@NtWriteFile;
 end;

 iol:=nil;
 case uio^.uio_segflg of
  UIO_USERSPACE:iol:=@iov_uplift;
  UIO_SYSSPACE :iol:=@iov_null;
 end;

 append:=(uio^.uio_rw=UIO_WRITE) and ((ioflag and IO_APPEND)<>0);

 locked:=((ioflag and IO_UNIT)<>0) and (not append);

 if locked then
 begin
  sx_xlock(@de^.ufs_md_lock);
 end;

 while (uio^.uio_iovcnt>0) or (uio^.uio_resid>0) do
 begin
  iov:=uio^.uio_iov;

  vec:=iov^;
  iol(@vec);

  if (vec.iov_len=0) then
  begin
   Inc(uio^.uio_iov);
   Dec(uio^.uio_iovcnt);
   continue;
  end;

  if append then
  begin
   OFFSET:=Int64(FILE_WRITE_TO_END_OF_FILE_L);
  end else
  begin
   OFFSET:=uio^.uio_offset;
  end;

  BLK:=Default(IO_STATUS_BLOCK);

  R:=iocb(F,0,nil,nil,@BLK,vec.iov_base,vec.iov_len,@OFFSET,nil);

  if (R=STATUS_PENDING) then
  begin
   R:=NtWaitForSingleObject(F,False,nil);
   if (R=0) then
   begin
    R:=BLK.Status;
   end;
  end;

  Result:=ntf2px(R);

  System.InterlockedIncrement64(ioin^);

  if (Int64(BLK.Information)<vec.iov_len) then
  begin
   //partial
   vec.iov_len:=BLK.Information;
   if (vec.iov_len<>0) then
   begin
    Inc(iov^.iov_base  ,vec.iov_len);
    Dec(iov^.iov_len   ,vec.iov_len);
    Dec(uio^.uio_resid ,vec.iov_len);

    if not append then
    begin
     Inc(uio^.uio_offset,vec.iov_len);
    end;
   end;
   Break;
  end;

  if (Result<>0) then Break;

  Inc(iov^.iov_base  ,vec.iov_len);
  Dec(iov^.iov_len   ,vec.iov_len);
  Dec(uio^.uio_resid ,vec.iov_len);

  if not append then
  begin
   Inc(uio^.uio_offset,vec.iov_len);
  end;

  if (iov^.iov_len=0) then
  begin
   Inc(uio^.uio_iov);
   Dec(uio^.uio_iovcnt);
  end;
 end;

 if locked then
 begin
  sx_xunlock(@de^.ufs_md_lock);
 end;
end;

function md_read(ap:p_vop_read_args):Integer;
begin
 case ap^.a_vp^.v_type of
  VDIR:Exit(VOP_READDIR(ap^.a_vp, ap^.a_uio, nil, nil, nil));
  VREG:Exit(md_io(ap^.a_vp,ap^.a_uio,ap^.a_ioflag));
  else
       Exit(EBADF);
 end;
end;

function md_write(ap:p_vop_write_args):Integer;
begin
 case ap^.a_vp^.v_type of
  VREG:Exit(md_io(ap^.a_vp,ap^.a_uio,ap^.a_ioflag));
  else
       Exit(EBADF);
 end;
end;

function md_advlock(ap:p_vop_advlock_args):Integer;
var
 vp:p_vnode;
 de:p_ufs_dirent;
 fp:p_file;
 fl:p_flock;
 op:Integer;
 wf:Integer;

 offset,size:Int64;

 F:THandle;
 BLK:IO_STATUS_BLOCK;
 R:DWORD;
begin
 vp:=ap^.a_vp;
 if (vp^.v_type<>VREG) then Exit(EBADF);

 de:=vp^.v_data;
 fp:=ap^.a_id;
 fl:=ap^.a_fl;
 op:=ap^.a_op;
 wf:=ap^.a_flags;

 F:=THandle(vp^.v_un);

 case op of
  F_SETLK:;
  else
   Exit(ENOTSUP);
 end;

 case fl^.l_type of
  F_RDLCK:;
  F_UNLCK:;
  F_WRLCK:;
  else
   Exit(ENOTSUP);
 end;

 case fl^.l_whence of
  SEEK_SET:offset:=fl^.l_start;
  SEEK_CUR:offset:=fp^.f_offset+fl^.l_start;
  SEEK_END:
   begin
    sx_xlock(@de^.ufs_md_lock);

    Result:=md_update_dirent(F,de,nil);

    offset:=de^.ufs_size+fl^.l_start;

    sx_xunlock(@de^.ufs_md_lock);

    if (Result<>0) then Exit;
   end;
  else
       Exit(ENOTSUP);
 end;

 size:=fl^.l_len;
 if (size=0) then
 begin
  size:=-1;
 end;

 case fl^.l_type of
  F_RDLCK:
   begin
    R:=NtLockFile(
        F,
        0,
        nil,
        nil,
        @BLK,
        @offset,
        @size,
        0,
        ((wf and F_WAIT)=0),
        False
       );
   end;
  F_WRLCK:
   begin
    R:=NtLockFile(
        F,
        0,
        nil,
        nil,
        @BLK,
        @offset,
        @size,
        0,
        ((wf and F_WAIT)=0),
        True
       );
   end;
  F_UNLCK:
   begin
    R:=NtUnlockFile(
        F,
        @BLK,
        @offset,
        @size,
        0
       );
   end
  else;
 end;

 Result:=ntf2px(R);
end;

function md_advlockpurge(ap:p_vop_advlockpurge_args):Integer;
begin
 //Locks are automatically released on NtClose
 Result:=0;
end;


end.

