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)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.