SCCSID  equ     "@(#)mknode.asm 6.13 92/02/20"
TITLE   MKNODE - Node maker
NAME    MKNODE
;
; FAT workers for APIs that make a new directory entry AND
; Low level routines for making a new local file system node
;  This file contains swappable code only.
;
;       Global          Local
;       DOS_OpenCreate
;       DOS_MkDir
;       DOS_SetFSInfo
;       DOS_Rename
;                       BuildDir
;       NewDir
;       SetDotEnt
;       MakeNode
;       NewEntry
;
;   MODIFICATION HISTORY:
;    - 11/27/95 - MLM defect 72417
;                 moved from PTDA to TCB:
;                 Attrib, Creating, Curr_Dir_End, DevPt, DESTSTART, DirStart,
;                 DirSec, EntFree, LastEnt, Name1, NoSetDir,
;                 PathNameType, Ren_WFP, Sattrib, ptda_pSrchBuf, VolID,
;                 WFP_Start, WFP_Path_End

.xlist
include fnofs.inc
include dosseg.inc
include basemaca.inc
include osmaca.inc
include oldmaca.inc
include osconst.inc
.xcref
include buffer.inc
include dossymc.inc
include chardd.inc
include devhdr.inc
include dirent.inc
include dpb.inc
include error.inc
include extattr.inc
include filesys.inc
include fsc.inc
include mi.inc
include tkexport.inc
include mft.inc
include sft.inc
include selxport.inc
include protmode.inc
include vpb.inc
include curdir.inc
include find.inc
include inbuf.inc
include argframe.inc
include opncreat.inc
include fatea.inc
include filemode.inc
include pgexport.inc
include vmexport.inc
include oplock.inc
include codelock.inc
.cref
.list
        ModName , FNO_MKNODE_ASM

        ReferFlat       _fLWDaemon,DWORD

                                                ;72417 removed PTDA entries now
                                                ;      in TCB

        ReferTask       CurrTCB,WORD            ; Current thread control block
        ReferTask       ExtErr_Locus,BYTE       ; Extended Error Locus

        ReferGDT        GDT_VPB,WORD
        ReferGlobal     initfatchkdsk,BYTE  ; init chkdsk of fat boot partition ;#61135

        EXTRNFAR        Date16

        EXTRN32         CodeLockProc
        EXTRN32         CodeUnlockProc
        EXTRN32         _SELConvertToLaddr
        EXTRN32         _BufFlushFATDir

        EXTRN           Allocate:NEAR
        EXTRN           DeleteVID:NEAR
        EXTRN           DiskUpdateFileSize:NEAR
        EXTRN           DOS_Search_First:NEAR
        EXTRN           DoSetFEAInfo:NEAR
        EXTRN           EAHandleToOffset:NEAR
        EXTRN           EASerialize:NEAR
        EXTRN           EAUnserialize:NEAR
        EXTRN           FigRec:NEAR
        EXTRN           FillSFTFromDirEnt:NEAR
        EXTRN           FillDeviceSFT:NEAR
        EXTRN           fValidFATDisk:NEAR
        EXTRN           GetBufBack:NEAR
        EXTRN           GetEADataInfo:NEAR
        EXTRN           GETENT:NEAR
        EXTRN           GETENTRY:NEAR
        EXTRN           GetEOF:NEAR
        EXTRN           GetPath:NEAR
        EXTRN           GetPathNoSet:NEAR
        EXTRN           IsEOF:NEAR
        EXTRN           LockMFT:NEAR
        EXTRN           MatchAttributes:NEAR
        EXTRN           NextEntry:NEAR
        EXTRN           SetDirSrch:NEAR
        EXTRN           SET_BUF_AS_DIR:NEAR
        EXTRN           SetupFatEA:NEAR
        EXTRN           StartSrch:NEAR
        EXTRN           ReleaseEA:NEAR
        EXTRN           UnlockMFT:NEAR
        EXTRN           UpdateVID:NEAR
        EXTRN           UseCache:NEAR

        EXTRNFAR        AddShare
        EXTRNFAR        FlushBuf
        EXTRNFAR        FNotify_Path_Wake
        EXTRNFAR        fValidFATDisk
        EXTRNFAR        fVolumeWriteable
        EXTRNFAR        GetBuffr
        EXTRNFAR        ProbeBuff
        EXTRNFAR        RlseBuffr
        EXTRNFAR        RemoveShare
        EXTRNFAR        ShSU
        EXTRNFAR        UCase
        EXTRNFAR        UnpackName
        EXTRNFAR        Volume_Access_Lock
        EXTRNFAR        Volume_Access_Unlock
        EXTRNFAR        Volume_Alloc_Lock
        EXTRNFAR        Volume_Alloc_Unlock
        EXTRNFAR        WaitVPB
        EXTRNFAR        WakeVPB


        EXTRN32         CodeLockProc
        EXTRN32         CodeUnlockProc

ifdef FSSTRICT
        EXTRN           IsSFT:NEAR
endif


        FARCODE HIGH4
FSSWAPCODE SEGMENT
        ASSUME  SS:TASKAREA

        PUBLIC  _StartNODEGROUP
_StartNODEGROUP label   byte

;***    DOS_OpenCreate - Try to open the specified file or create it
;
;
;       We have three possible actions in this routine:
;
;           OPEN a local object
;               - Local file or character device:  We set up the SFT
;               - Direct Device:  We fill in the SFT from the meagre info
;
;           REPLACE a local object
;               - Local file: We truncate it and set its new size
;               - Local device: We just open it and ignore the size
;
;           CREATE a local file
;               - We make a directory entry for the file
;
;
;       ENTRY   SS:BX   -> Argument frame on stack (See OpenStruc in argframe.inc)
;               (ES:DI) -> SFT
;               [WFP_START] Points to WFP string ("d:\" must be first 3
;                       chars, NUL terminated)
;               [CURR_DIR_END] Points to end of Current dir part of string
;                       ( = -1 if current dir not involved, else Points
;                        to first char after last "\" of current dir part)
;               [WFP_Path_End] Points one byte after last "\" in WFP_Start
;               [ThisCDS] Points to CDS being used
;                       (Low word = -1 if NUL CDS (Net direct request))
;               [TCBThisSFT] Points to SFT to fill in if file found
;                       sf_fsi.sfi_mode field set
;                       sf_flags field set
;               [TCBThishVPB] set up with the hVPB
;               [Sattrib] Is attribute of search, determines what files can
;                       be found
;
;       EXIT    sf_ref_count is NOT altered
;               es:di -> sft
;               CARRY CLEAR
;                   ThisSFT filled in.
;                   CX = Action taken (FILE_EXIST, FILE_CREATE, FILE_REPLACE)
;                   BX = 1 if need eas are there for this file
;                               ( in case of FILE_EXIST only)
;                       ELSE BX = 0
;               CARRY SET
;                   AX is error code
;                       error_file_not_found
;                               Last element of path not found
;                       error_path_not_found
;                               Bad path (not in curr dir part if present)
;                       error_invalid_access
;                               Bad sharing mode and/or bad access mode
;                       ERROR_ACCESS_DENIED
;                               Attempt to open read only file for writting, or
;                               open a directory
;
;       USES    AX, BX, CX, DX, DI, SI, ES, Flags
;
;       ALGORITHM
;
;           Adjust sfi_mode if opening for exec
;           if (open_direct)
;               FillDirectBlockDeviceSFT()  /* Set up SFT for block device */
;           else {
;               if (open for exec)
;                   treat as "read-only"
;               LockMFT()                   /* Useful for CREATEs and REPLACEs */
;               Volume_Access_Lock(SHARE)   /* To prevent races with CREATEs */
;               if (!fValidFATDisk())
;                   goto exit:
;               GetPath()
;               if (found) && (! directory) {
;                   if (OPEN)
;                       Adjust sharing mode for files
;                       OPEN file or device
;                   else if (REPLACE) {
;                       Check file attributes
;                       if (attributes indicate no replace)
;                           jump to exit (error failed)
;                       remember existing file size
;                       if new size > old size then
;                          DiskUpdateFileSize(new size)
;                          if allocation fails
;                          return error
;                       DoSetFEAInfo(EASDONTEXIST)
;                       if error
;                          if old size < new size
;                             DiskUpdateFileSize(old size) - won't fail
;                          return
;                       else
;                          if new size < old size
;                             DiskUpdateFileSize(new size) - won't fail
;                       Set new attribute for file
;                       set ST_PWRITE and ST_SWRITE in sfi_tstamp
;                   }
;                   else
;                       if (device) && (CREATE)
;                           OPEN the device, say it was CREATEd
;                       else
;                           jump to exit(error failed)
;               }
;               else {      /* Not found */
;                   if (CREATE) {
;                       AttribOk()
;                       MakeNode()
;                       set ST_PWRITE and ST_SWRITE in sfi_tstamp
;                   }
;                   else
;                       jump to exit(error failed)
;               }
;               exit: Volume_Access_Unlock()
;               UnlockMFT()
;           }
;           return any error indications
;
Procedure   DOS_OpenCreate,hybrid
    ASSUME  DS:nothing, ES:NOTHING, SS:TASKAREA
        PMONLY
        TaskContext <ds>

LocalVar    dopc_Action,WORD            ; Action taken
LocalVar    dopc_OpenFlag,WORD          ; OpenFlag of action if exist/not exist
LocalVar    dopc_FileAttrib,WORD        ; Attribute of file (for CREATE or REPLACE)
LocalVar    dopc_EABuf,DWORD            ; Pointer to user EA buffer (0 if none)
LocalVar    dopc_SFTPtr,DWORD           ; SFT for this operation
LocalVar    dopc_NewFileSize,DWORD      ; For REPLACE, new size of file
LocalVar    dopc_OldFileSize,DWORD      ; For REPLACE, existing size of file
LocalVar    dopc_filechange,WORD        ; For REPLACE, flags after old - new size
LocalVar    dopc_DirEntryOff,WORD       ; Offset in buffer of directory entry
LocalVar    dopc_DirBufSec,DWORD        ; Sector of directory sector for object
LocalVar    dopc_retcode,WORD           ; return code (not always used)
LocalVar    dopc_needeas,WORD           ; return code (not always used)
LocalVar    dopc_fakeEAOP,<size EAOP> ; used to eliminate EA's in replace
LocalVar    dopc_fakeFEAlist,<size FEAlist> ;pointed to by dopc_fakeEAOP

        CodeLock        NEWNODE_GROUP,dopc120

        mov     ax,TASKAREA:[bx].opn3s_Flag         ; AX = Open flag
        mov     dx,TASKAREA:[bx].opn3s_Attrs        ; DX = Open Attributes
        mov     cx,TASKAREA:[bx].opn3s_EABufPtr.Segmt
        mov     si,TASKAREA:[bx].opn3s_EABufPtr.Offst   ; CX:SI -> EABuf
;
; Initialize dopc_Action (assume object exists), OpenFlag, FileAttrib,
; EABuf and SFTPtr.
;
        EnterProc <file_exist,ax,dx,cx,si,es,di>
        mov     [dopc_needeas],0        ; assume no NEED EAs

        test    es:[di].sf_flags,SF_FCB             ; Is this SFT for an FCB?
        jz      dopc5                               ; No. COntinue.

CpuMode 386
        pushad
        SaveReg <ds>
        SaveReg <es>

        mov     ax,es
        movzx   eax,ax
        FlatContext <ds,es>
        CCALL32  _SELConvertToLaddr,<eax>
        RestoreReg <es>
IFDEF FSSTRICT
        cmp     eax,-1                          ; error ?
        jne     short dopc0
        InternalError   <DOS_Opencreate: invalid sft address?>
ENDIF
dopc0:  mov     es:[di].sf_fsd.sfdFAT_pSFT,eax
        RestoreReg <ds>
        popad
CpuMode reset
    ASSUME DS:NOTHING, ES:NOTHING

;
; Initialize dopc_NewFileSize.
;
dopc5:  mov     si,TASKAREA:[bx].opn3s_Size.loword
        mov     bx,TASKAREA:[bx].opn3s_Size.hiword   ; BX:SI = file size
        mov     [dopc_NewFileSize].HighWord,BX
        mov     [dopc_NewFileSize].LowWord,SI
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBNoSetDir,0
        UnSetBP_CurrTCB
                                        ;72417 end
        mov     ax,es:[di].sf_fsi.sfi_mode
;
; We now perform different actions depending on whether this is a direct OPEN
; or not.
;
        test    ax,open_direct
        jz      dopc10                  ; This is not a direct OPEN
;
; This is a direct open. We fill up the SFT with the meagre information
; that we have.
;
        call    FillDirectBlockDeviceSFT
        les     di,[dopc_SFTPtr]
        jmp     dopc90                  ; All done with OPEN of direct device.

;
; See if the object exists. It could be either a device, a file or a
; directory.
;
; Lock the Volume to prevent races between CREATE/MKDIR/RENAME/RMDIR.
; Lock the MFT to prevent races during CREATEs.
;
dopc10: SaveReg <BP>
        test    es:[di].sf_flags,DEVID_DEVICE ; Character device?
        jnz     dopc15                  ; No locking of volume for devices
        mov     cx,VPB_ACC_SHARE
        call    LockMFT
        CALLFAR Volume_Access_Lock      ; Prevent races during CREATEs
dopc15: call    fValidFATDisk           ; Valid FAT FS disk?
        LJZ     dopcEr                  ; No. AX=error code. Return error.
        call    GetPath
        RestoreReg <BP>
        jnc     dopc_Found              ; Found the object. Carry on processing.
        LJNZ    dopc_bad_path           ; Bad pathname. Return error.
        cmp     cl,80H                  ; Object not found && No meta chars?
        LJZ     dopc_NotFound           ; Yes. Process for object not found.
        jmp     dopc_bad_file           ; Meta chars. Return error.


;** Object was found. Could be a file, device or directory.
;
; For directories, we return error since we cannot OPEN or CREATE them.
;
;   [CurBuf+2]:BX Points into [CurBuf] to start of entry
;
dopc_Found:
        LJZ     dopc_bad_access             ; Found a directory. Return error.
;
; The file or device in the path exists.
;
; If an OPEN or REPLACE was requested we go ahead and OPEN it in order to
; get a valid SFT for it. Then, if the REPLACE bit is set, we can either
; truncate the file or REPLACE it (depending on the new size specified)
;
; If, however, neither REPLACE nor OPEN was specified, then it indicates that
; we have to FAIL the operation. This is true EXCEPT for devices, when it
; is all right to CREATE an existing object.
; So, for devices, if the user specified that a CREATE was OK, then we go
; ahead and simulate a CREATE by doing an OPEN and returning an action of
; FILE_CREATE.
;
        les     di,[dopc_SFTPtr]        ; ES:DI -> SFT
        test    [dopc_OpenFlag],(exist_open+exist_replace) ; Open or Replace requested?
        jnz     dopc18                  ; OK. Go and do operation.
        test    es:[di].sf_flags,DEVID_DEVICE
        LJZ     dopc_chk_open_fail_1    ; Not a device. FAIL with "open_failed"
        test    [dopc_OpenFlag],(nexist_create) ; Create requested?
        LJZ     dopc_bad_access         ; No Create. FAIL this operation.
        mov     [dopc_Action],file_create   ; Say that we did a CREATE
        jmp     short dopc30            ; Do an OPEN

;
; We are going to OPEN and, maybe, replace the file or device.
;
dopc18: test    es:[di].sf_flags,DEVID_DEVICE   ; Character device?
        jnz     dopc30                      ; Devices don't have attributes
;
; Save the sector number of the directory sector in the LocalVar. We will use
; it later to get the buffer back.
;
        mov     si,TASKAREA:[CurrTCB]
        les     di,TASKAREA:[si].TCBCurBuf      ; ES:DI -> directory buffer
        mov     cx,es:[di].buf_sector.HighWord
        mov     [dopc_DirBufSec].HighWord,cx
        mov     cx,es:[di].buf_sector.LowWord
        mov     [dopc_DirBufSec].LowWord,cx ; Save dir sector for later
        mov     cx,bx                       ; CX -> directory entry in buffer
        sub     cx,di                       ; CX = Offset of entry in buffer
        mov     [dopc_DirEntryOff],cx       ; Save offset of dir entry for now
;
; We are going to OPEN the file or device.
; For a device, we set up the SFT and call the device driver to do an OPEN
; For a file, we have to check the attributes to make sure they are consistent.
;
; TASKAREA:SI -> Current TCB
;
        mov     AL,ES:[BX].dir_attr
        test    AL,attr_volume_id           ; can't open volume ids
        LJNZ    dopc_bad_access
        test    AL,attr_read_only           ; check write on read only
        jz      dopc30
;
; The file is marked READ-ONLY.  We verify that the open mode allows access to
; the read-only file.  Unfortunately, with FCB's  we cannot
; determine at the OPEN time if such access is allowed.  Thus, we defer such
; processing until the actual write operation:
;
; If FCB, then we change the mode to be read_only.
; If not open for read then error.
;
        les     di,[dopc_SFTPtr]            ; ES:DI -> SFT
        MOV     CX,es:[di].sf_fsi.sfi_mode
        TEST    es:[di].sf_flags,SF_FCB     ; is it FCB?
        JZ      dopc25                      ; no
        AND     CX,NOT open_access          ; clear access
    .errnz      open_for_read
        MOV     es:[di].sf_fsi.sfi_mode,CX
        JMP     SHORT dopc30
;
; The SFT is normal.  See if the requested access is open_for_read
;
dopc25: AND     CL,open_access              ; remove extras
        CMP     CL,open_for_read            ; is it open for read?
        JZ      dopc30
        JMP     dopc_bad_access
;
; All done, restore registers and fill the SFT.
;
; BX -> directory entry ("fake" entry if device)
; ES:DI -> SFT
;
dopc30: les     di,[dopc_SFTPtr]            ; ES:DI -> SFT
        test    es:[di].sf_flags,DEVID_DEVICE ; Character device?
        jz      dopc35                      ; Not a device. Set SFT for file.
        call    FillDeviceSFT               ; Fill up SFT for device
        jmp     dopcSRet                    ; Success. All done.
;
; It is a file. Fill up the SFT from the directory entry.
;
dopc35: SaveReg <bp>
        mov     bp,CurrTCB
        mov     bp,TASKAREA:[bp].TCBThishVPB        ; BP = hVPB
        call    FillSFTFromDirEnt
        RestoreReg <bp>
;
; Check to see if user requested a REPLACE operation. In this case, we
; either need to truncate, shrink or grow the file.
;
dopc40: test    [dopc_OpenFlag],exist_replace  ; Do we need to re-size or truncate?
        LJZ     dopcSRet                            ; No. All done.

;
; The user wants us to REPLACE the existing file.
;
; We have to:
;       - If this is a pipe, do nothing
;       - Delete any existing EAs (release the clusters)
;       - Set any new Extended Attributes
;       - Update the size (may truncate it).
;       - Change the attributes if necessary.
;       - set sfi_tstamp indicate we want last write time stamp updated
;
        les     di,[dopc_SFTPtr]        ; ES:DI -> SFT
        test    es:[di].sf_flags,SF_PIPE
        LJNZ    dopcRet                 ; No further action for a pipe
        CALLFAR fVolumeWriteable        ; Can we write to the volume?
        LJC     dopcRet                 ;   NO, exit with error
        mov     [dopc_Action],file_replace
        CALLFAR RlseBuffr               ; Release buffer for now
        call    BlanksBeforeDot         ; check for blanks before dot
        LJC     dopcRet
;
; We must ensure that the "replace" is allowed on the existing file.
; To be correct as well as consistent with the create_new part of the call,
; as well as realdos, we must:
;   1) fail attempts to replace a read-only file with ERROR_ACCESS_DENIED
;   2) if the existing file has dir, sys or hidden set, and we don't, fail
;      the replace with ERROR_ACCESS_DENIED
;
; ES:DI -> SFT
;
        mov     cx,[dopc_FileAttrib]    ; Get new attributes
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     TASKAREA:[bp].TCBAttrib,cx    ; Argument for MatchAttributes
        UnSetBP_CurrTCB
                                        ;72417 end
        mov     ch,es:[di].sf_fsi.sfi_DOSattr ; CH = file's attributes
        test    ch,attr_read_only
        LJNZ    dopc_bad_access         ; Cannot replace read-only file. Error.
        call    MatchAttributes
        LJNZ    dopc_bad_access         ; Bad attribute. Return error.
        cmp     cl,ch                   ; Attrib already correct?
        je      dopc41
        test    [dopc_FileAttrib],NOT attr_changeable
        LJNZ    dopc_bad_access
;
; Attributes are OK. Proceed with the "replace" operation.
; first, save existing file size.
; ES:DI -> SFT
; At this time, Our SFT does not contain the latest info for this file.
; There may be somebody else who has done some writes to the file. So, we
; need to update our sft from other SFTs for this file.
;
dopc41: mov     ax,3                    ; update our sft from other sfts
        CALLFAR ShSU
        mov     ax,es:[di].sf_fsi.sfi_size.loword   ; remember the file size
        mov     dopc_OldFileSize.loword,ax
        mov     dx,es:[di].sf_fsi.sfi_size.hiword   ; remember the file size
        mov     dopc_OldFileSize.hiword,dx

; DX:AX = current size of file

        sub     ax,dopc_NewFileSize.loword
        sbb     dx,dopc_NewFileSize.hiword          ; (DX:AX) = existing file size - new file size
        pushf
        pop     dopc_filechange                     ; save flags (CY if growing file)
        jnc     dopc42                              ; new file size <= existing size

; file is growing, so try to allocate it.

        mov     dx,[dopc_NewFileSize].HighWord
        mov     ax,[dopc_NewFileSize].LowWord   ; DX:AX = new file size
        call    DiskUpdateFileSize      ; Update the file size to the new one
        mov     ax,ERROR_DISK_FULL      ; Insufficient space for user's request
        LJC     dopcRet                 ; Some error changing file size. Return.
;
; Now see if the user specified an EABuf to set for the file.
;
dopc42: mov     bx,[dopc_EABuf].Segmt   ; BX = RPL stamped selector
        mov     si,[dopc_EABuf].Offst
        test    bx,not SEL_RPLMASK
        jnz     dopc43          ; EAs supplied--branch to set 'em up
;
; We're replacing a file, and the EA pointer was null.  So we want the file
; being replaced to have an empty EA list.  In order to do this, it is
; necessary to 'fake out' the EA routines.  Set up a valid EAOP struct,
; whose FEAlist has a length of MINFEALISTSIZE, and whose fpFEAList
; points to the said FEAlist.
;
        lea             cx,ss:[dopc_fakeFEAlist]
        mov             [dopc_fakeEAOP].eaop_fpFEAlist.Offst,cx         ;set FEAList ptr
        mov             [dopc_fakeEAOP].eaop_fpFEAlist.Segmt,ss         ;of the EAOP struct
        mov             WORD PTR [dopc_fakeFEAlist].feal_cblist.loword,MINFEALISTSIZE
        mov             WORD PTR [dopc_fakeFEAlist].feal_cblist.hiword,0
        lea             si,ss:[dopc_fakeEAOP]   ;make it look like user called DosOpen(2)
        mov             bx,ss                                   ;with EABuf as we have just set it up
        mov             [dopc_EABuf].Offst,si
        mov             [dopc_EABuf].Segmt,ss

dopc43:
        mov     cx,size EAOP            ; Size of EAOP buffer containing pointers
        mov     dh,ACCTYPE_RW           ; Want READ/WRITE access to buffer
        CALLFAR ProbeBuff               ; Verify that buffer is valid
    ASSUME DS:NOTHING, ES:NOTHING
        LJC     dopc100                 ; Buffer invalid. Return error.
;
; The EAOP given is valid.  So set up EA's for the file.
;
        les     di,[dopc_SFTPtr]        ; ES:DI -> SFT
        mov     bx,[dopc_EABuf].Segmt
        mov     si,[dopc_EABuf].Offst   ; BX:SI -> EABuf
    ASSUME DS:NOTHING, ES:NOTHING
        mov     ax,EASDONTEXIST         ; EAs for the file/dir don't exist (replace them)
        call    DoSetFEAInfo
        TaskContext DS                  ; Set up DS for next call
        LJC     dopc100                 ; Error setting EAs. Return.

;
; if new size < old size
;    DiskUpdateFileSize(new size) - shouldn't fail
; if new size fails, we're out of luck.  EAs remain updated.
;
        push    dopc_filechange
        popf
        jc      dopc50
        CALLFAR RlseBuffr
        mov     dx,[dopc_NewFileSize].HighWord
        mov     ax,[dopc_NewFileSize].LowWord   ; DX:AX = new file size
        call    DiskUpdateFileSize      ; Update the file size to the new one
        mov     ax,ERROR_DISK_FULL      ; Insufficient space for user's request
        LJC     dopcRet                 ; Some error changing file size. Return.

; New size worked. This affects last write time stamp (for FAT)

dopc50: les     di,[dopc_SFTPtr]        ; ES:DI -> SFT
        or      es:[di].sf_fsi.sfi_tstamp,ST_PWRITE or ST_SWRITE ; stamp last write
;
; Change attribute if necessary.
;
        mov     cx,[dopc_FileAttrib]    ; Get new attribute
        cmp     cl, es:[di].sf_fsi.sfi_DOSattr  ; Attrib already correct?
        LJZ     dopcSRet                        ; Yes, return sucess ('NC')
;
; The attribute for the file is legal, and is not the same as the old one.
; We have to update the directory entry for this object to include the new
; attribute.
;
        SaveReg <cx>                    ; Save new file attribute
        mov     bx,[dopc_DirEntryOff]
        mov     cx,[dopc_DirBufSec].HighWord
        mov     dx,[dopc_DirBufSec].LowWord
        SaveReg <BP>
        mov     bp,es:[di].sf_fsi.sfi_hVPB
        VPBContext ES,BP
        call    GetBufBack              ; Get directory sector back into buffer
        RestoreReg <BP>
        RestoreReg <cx>                 ; CX = new file attribute
        LJC     dopc_bad_access         ; Error re-reading sector into buffer
;
; [CurBuf+2]:BX points to directory entry
; CL = new file attribute
;
        mov     di,TASKAREA:[CurrTCB]
        les     di,TASKAREA:[DI].TCBCurBuf
        and     BYTE PTR ES:[BX].dir_attr,NOT attr_changeable
        or      BYTE PTR ES:[BX].dir_attr,CL
        or      ES:[DI.buf_flags],BUF_DIRTY
        CALLFAR RlseBuffr
        push    bp
        mov     bp,CurrTCB
        mov     ax,TASKAREA:[bp].TCBThishVPB
        pop     bp
        CALLFAR FlushBuf
        jnc     dopcSRet
        jmp     dopc_bad_file

;* Object does not exist.
;
; There is no such file. We can only try to CREATE it (if the user said
; that we should).
;
dopc_NotFound:
        test    TASKAREA:[dopc_OpenFlag],nexist_create
        LJZ     dopc_chk_open_fail_2    ; Return error. open_failed
        CALLFAR fVolumeWriteable        ; Can we write to the volume?
        jc      dopcRet                 ;   NO, exit with error
        call    BlanksBeforeDot         ; check for blanks before dot
        jc      dopcRet
        SaveReg <BX>
        mov     bx,[dopc_FileAttrib]
        call    AttribOk                ; BX = attribute to create
        RestoreReg <AX>                 ; AX -> directory entry in CurBuf
        mov     ExtErr_Locus,ERRLOC_UNK
        LJC      dopc_bad_access        ; Bogus attributes. Return error.
        xchg    ax,bx                   ; BX -> dir entry, AL = attribute to create
        mov     [dopc_Action],file_create
        SaveReg <bp>
        mov     di,[dopc_EABuf].Segmt
        mov     dx,[dopc_EABuf].Offst   ; DI:DX -> EABuf (0 if none)
        call    MakeNode
        RestoreReg <bp>                 ; BP -> LocalVar frame
        jc      dopcRet                 ; If error, return
;
        sub     bx,di                   ; offset of directory entry within sec.
        mov     [dopc_DirEntryOff],bx   ; store the offset for later use
        mov     bx,TASKAREA:CurrTCB
        les     bx,TASKAREA:[bx].TCBCurBuf          ;
        mov     cx,es:[bx.buf_sector].LowWord ; store the directory sector
        mov     [dopc_DirBufSec].LowWord,cx ; for later use
        mov     cx, es:[bx.buf_sector].HighWord;
        mov     [dopc_DirBufSec].HighWord,cx
;
; Set the size of the file to what the user requested. Allocate space on the
; disk if necessary. Update the SFT size and firFILEclus fields.
;
        CALLFAR RlseBuffr

        mov     dx,[dopc_NewFileSize].HighWord
        mov     ax,[dopc_NewFileSize].LowWord   ; DX:AX = new file size
        call    DiskUpdateFileSize      ; Update the file size to the new one
        jc      dopcUpdFail             ; if error (disk full) delete the
                                        ; newly made entry
; success return ; check for file_exist open, in that case we need to return
; the existense (or non-) of NEED EAS

dopcSRet:xor    ax,ax                   ; default : no need eas, NC

dopcRet:PUSHF
        mov     [dopc_retcode],ax       ; save the return code
        CALLFAR RlseBuffr
        les     di,[dopc_SFTPtr]        ; ES:DI -> SFT
        test    es:[di].sf_flags,DEVID_DEVICE
        jnz     dopc85                  ; Don't unlock MFT or Volume for device

ifdef FSSTRICT                          ; verify no one has trashed TCBThisSFT
    ASSUME DS:NOTHING
        SaveReg <ax,bx>
        mov     bx,[CurrTCB]
        mov     ax,es                   ; ax:di -> sft
        cmp     ax,TASKAREA:[bx].TCBThisSFT.Segmt
        jne     dopc60
        cmp     di,TASKAREA:[bx].TCBThisSFT.Offst
        je      dopc70

dopc60: InternalError <dopc: TCBThisSFT trashed>

dopc70: RestoreReg <bx,ax>
endif

        POPF                            ; get the old flags
        PUSHF                           ; put them back on the stack
        SaveReg <ES>
                                        ; b790587
        jc      dopc115                 ; if error, don't look for need EAs
        cmp     [dopc_Action],file_exist; Did the file exist?
        jne     dopc80                  ; no, no need to look for NEED EAs
;
; NeedEASPresent can fail if the EA code cannot be locked down for some
; reason.  Return error code to caller.
;
        call    NeedEASPresent          ; ax = 1 if there are need eas else 0
        jnc     dopc75

        mov     [dopc_retcode],ax       ; error - out of mem or EA file trashed
        RestoreReg  <es>
        pop     ax                      ; get rid of the old flags
        PUSHF                           ; save the new flags with CY set
        SaveReg <es>
        jmp     short dopc80

dopc75: mov     [dopc_needeas],ax       ; save the need EAs indicator
dopc80: mov     cx,VPB_ACC_SHARE
        CALLFAR Volume_Access_Unlock    ; Destroys ES.
        call    UnlockMFT
        RestoreReg <ES>                 ; ES:DI -> SFT
dopc85: mov     ax,[dopc_retcode]
        POPF                            ; Restore any error indication and code.
;
; Return to the caller with the action taken in CX and any error indications
;
dopc90: mov     cx,[dopc_Action]        ; Return any action taken in CX
        mov     bx,[dopc_needeas]       ; (bx) = need EAs present flag
        CodeUnlock      NEWNODE_GROUP
        LeaveProc
        ret

dopc_chk_open_fail_2:
        MOV     AX,ERROR_OPEN_FAILED
        jmp     short dopcEr

dopc_bad_file:
        MOV     AX,ERROR_FILE_NOT_FOUND
        jmp     short dopcEr

dopc_bad_access:
        MOV     AX,ERROR_ACCESS_DENIED
        jmp     short dopcEr

dopc_chk_open_fail_1:
        MOV     AX,ERROR_OPEN_FAILED
        jmp     short dopcEr

dopc_file_exists:
        MOV     AX,ERROR_FILE_EXISTS
        jmp     short dopcEr


dopc_bad_path:
        MOV     AX,ERROR_PATH_NOT_FOUND
dopcEr: STC
        jmp     dopcRet

; control comes here in case of update size failure for creation of a new file.
; this means there is no disk space and hence we have to delete the directory
; entry we just made
                                        ; assume diskful and delete the entry
dopcUpdFail:    mov     bx,[dopc_DirEntryOff]
        mov     cx,[dopc_DirBufSec].HighWord
        mov     dx,[dopc_DirBufSec].LowWord
        push    bp
        mov     bp,CurrTCB
        mov     bp,TASKAREA:[bp].TCBThishVPB        ;
        VPBContext es,bp                ; es already has VPBContext
        call    GetBufBack              ; get back the directory buffer
        pop     bp
        jc      dopcNoDel
        mov     si,CurrTCB
        mov     es, word ptr TASKAREA:[si].TCBCurBuf.Segmt
        or      byte ptr es:[di.buf_flags],BUF_DIRTY
                                        ;di already points to beginbuf.
        mov     byte ptr es:[bx],DIRENT_DELETED ; mark the newly created entry
                                        ; as deleted
dopcNoDel: mov  ax,ERROR_DISK_FULL      ; If error, assume insufficient space
                                        ; on disk
        stc
        jmp     dopcRet

;
; replace failed.  we may have grown file.  if we did, resize it to its
; original size.
;
dopc100: push    dopc_filechange         ; flags = CY if file has been grown
        popf
        jnc     dopc110
        push    ax
        CALLFAR RlseBuffr       ; release dir buffer since we will be
                                ; taking an alloc lock in diskupdatesize
        mov     dx,[dopc_OldFileSize].HighWord
        mov     ax,[dopc_OldFileSize].LowWord   ; DX:AX = previous file size
        call    DiskUpdateFileSize      ; Update the file size to the previous one
        pop     ax

; if call to DiskUpdateFileSize fails, we're out of luck.  file has been
; grown and will stay at larger size.

dopc110: stc
        jmp dopcRet

; if error was "disk full", flush dirty directory entry... in case user decides
; to replace this diskette with a different one

dopc115: cmp     [dopc_retcode],ERROR_DISK_FULL; Disk full error?
        jne     dopc117         ; no, keep dirty buffers
        SaveReg <DI>
        push    bp
        mov     bp,CurrTCB
        mov     ax,TASKAREA:[bp].TCBThishVPB
        pop     bp
        CALLFAR FlushBuf
        RestoreReg <DI>
dopc117: jmp     dopc80


dopc120: mov    ax,ERROR_NOT_ENOUGH_MEMORY
        stc
        ret

EndProc DOS_OpenCreate

BREAK <DOS_MkDir - Make a directory entry>

;***    DOS_MkDir - Make a new directory
;
;       A new directory is created and the . and .. entries are filled in.
;
;       ENTRY   [WFP_Start] Points to WFP string ("d:/" must be first 3
;                       chars, NUL terminated)
;               [Curr_Dir_End] Points to end of Current dir part of string
;                       ( = -1 if current dir not involved, else Points
;                        to first char after last "/" of current dir part)
;               [ThisCDS] Points to CDS being used
;                       (Low word = -1 if NUL CDS (Net direct request))
;               BX:SI -> Extended Attribute (EA) structure (NULL means none)
;               DS     = TASKAREA
;               [cMeta] indicates no metas
;
;       EXIT    if 'NC' - success
;               if 'C' - error
;                   AX is error code
;                       ERROR_PATH_NOT_FOUND
;                               Bad path (not in curr dir part if present)
;                       ERROR_BAD_CURR_DIr
;                               Bad path in current directory part of path
;                       ERROR_ACCESS_DENIEd
;                               Already exists, device name
;
;       USES    ax,bx,cx,dx,si,di,es,flags,bp
;
;       EFFECTS Switches Context
;
;       DOS_MkDir:
;          Volume_Access_Lock(file_op)
;          AddShare(dir)
;          if (error)
;             Volume_Access_Unlock(file_op)
;             return
;          MakeNode
;          if (error) {
;             Volume_Access_Unlock(file_op)
;             RemoveShare
;             return
;          }
;          RlseBuffr
;          NewDir
;          if (error)
;             jump to ERROR
;          mark buf dirty
;          set up . and .. entries
;          RlseBuffr
;          GetBuffr
;          if (error)
;             jump to ERROR
;          dir size = zero
;          mark buf dirty
;          RlseBuffr
;          FlushBuf
;          Volume_Access_Unlock(file_op)
;          RemoveShare
;          return
;
;       ERROR:
;          repeat
;             GetBuffr
;          until (no error)
;          mark dir deleted
;          mark buffer dirty
;          FlushBuf
;          Volume_Access_Unlock(file_op)
;          RemoveShare
;          return

Procedure   DOS_MkDir,faronly
        TaskContext <ds>,,noassume
    ASSUME      DS:TASKAREA, ES:NOTHING
        PMONLY

        CodeLock        NEWNODE_GROUP
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        test    TASKAREA:[bp].TCBPathNameType,PNType_LOCDEV OR PNType_PSDEV
        UnSetBP_CurrTCB
                                        ;72417 end
        MOV     AX,ERROR_ACCESS_DENIED
        LJNZ    dmdx                    ; Cannot make a device into a directory
        CALLFAR fVolumeWriteable        ; Can we write to the volume?
        LJC     dmdx                    ;   NO, exit with error
        call    BlanksBeforeDot         ; check for blanks before dot
        LJC     dmdx
;
;  We first get access to the volume.  Then we set up the sft and add the
;  path to the sharer.
;
        SaveReg <BX,SI>
        mov     cx,VPB_ACC_SHARE        ; share volume
        CALLFAR Volume_Access_Lock      ; get access to volume
        mov     ax,sharing_deny_both    ; sharing mode
        mov     cx,opnobreak            ; don't break opbatch or oplock
        CALLFAR AddShare                ; allocate SFT and enter in sharing
        RestoreReg <DX,DI>              ; DI:DX -> EABuf (0 if none)
        mov     ax,ERROR_ACCESS_DENIED
        LJC     dmd95                   ; error, go unlock volume and return
;
; Call MakeNode to make a node for the new directory.
;
        mov     AX,attr_directory
        call    MakeNode
        LJC     dmd80           ; error, go unlock volume and remove sharing
;
; [CurBuf+2]:BX -> dir entry
; [CurBuf+2]:SI -> dir_firstfile
;
        SegAssert SS,<DS>,"DOS_Mkdir2"
        mov     bp,CurrTCB
        mov     bp,TASKAREA:[bp].TCBThishVPB
        VPBContext es,bp        ; Makenode zaps this

        mov     DI,CurrTCB
        LDS     DI,TASKAREA:[DI].TCBCurBuf  ; (DS:DI) -> CurBuf
    ASSUME      DS:NOTHING

        SUB     SI,DI           ; (SI) = offset to dir_firstfile in buffer
        PUSH    SI                      ; Pointer to dir_firstfile
        PUSH    word ptr [DI.buf_sector]
        PUSH    word ptr [DI.buf_sector].hiword ; Sector of new node
        CALLFAR RlseBuffr
;
; Set up . and .. entries.
;
        mov     cx,VPB_ALLOC_SHARE
        CALLFAR Volume_Alloc_Lock       ; Get sharing lock for allocating clusters
        TaskContext DS
                                        ;72417 moved from PTDA to TCB
        mov     bx,CurrTCB
        PUSH    TASKAREA:[bx].TCBDirStart  ; Parent for .. entry
        XOR     AX,AX
        MOV     TASKAREA:[bx].TCBDirStart,AX   ; Null directory
                                        ;72417 end
        call    NewDir
        mov     cx,VPB_ALLOC_SHARE
        CALLFAR Volume_Alloc_Unlock     ; Release lock
        LJC     NODEEXISTSPOPDEL        ; No room
        mov     DI,CurrTCB
        LES     DI,TASKAREA:[DI].TCBCurBuf
ifdef FSSTRICT
        TEST    ES:[DI.buf_flags],BUF_ISDIR
        jnz     dmd10
        InternalError <DOS_MkDir - buffer is not dir buffer>
dmd10:
endif
        OR      ES:[DI.buf_flags],BUF_DIRTY
        ADD     DI,BUFINSIZ             ; Point at buffer
ifdef FSSTRICT
        SaveReg <di,cx,di,bp,ds>
        mov     DI,CurrTCB
        mov     bp,TASKAREA:[di].TCBThishVPB
        VPBContext ds,bp
        MOV     CX,DS:[bp].vpb_fsi.vpi_cbSector
        RestoreReg <ds,bp,di>
    assume ds:TASKAREA
        xor ax,ax
        repe scasb
        jz dmd20
        InternalError <DOS_MkDir - buffer is not zeroed>
dmd20:  RestoreReg <cx,di>
endif
        MOV     AX,202EH                ; ". "
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     DX,[bp].TCBDirStart     ; Point at itself
        UnSetBP_CurrTCB
                                        ;72417 end
        call    SetDotEnt
        MOV     AX,2E2EH                ; ".."
        POP     DX                      ; Parent
        call    SetDotEnt
        CALLFAR RlseBuffr

        mov     bp,CurrTCB
        mov     [bp].TCBAllowed,ALLOWED_FAIL + ALLOWED_RETRY
        mov     bp,TASKAREA:[bp].TCBThishVPB
        VPBContext es,bp
        POP     CX
        POP     DX                      ; CX:DX = Entry sector
        XOR     AL,AL                   ; Pre read
        CALLFAR GetBuffr
        LJC     NODEEXISTSP
                                        ;72417 moved from PTDA to TCB
        mov     DI,CurrTCB
        MOV     DX,TASKAREA:[DI].TCBDirStart
                                        ;72417 end
        LDS     DI,TASKAREA:[DI].TCBCurBuf
    ASSUME      DS:NOTHING
;
; Set flags to dir and size to zero.  Mark buffer dirty and flush to disk.
;
        OR      [DI.buf_flags],BUF_ISDIR
        POP     SI                      ; dir_firstfile pointer
        ADD     SI,DI
        MOV     [SI],DX
        XOR     DX,DX
        MOV     [SI+2],DX               ; Zero size
        MOV     [SI+4],DX
        OR      [DI.buf_flags],BUF_DIRTY        ; Dirty buffer
        CALLFAR RlseBuffr

        TaskContext DS
        MOV     AX,BP
        CALLFAR FlushBuf
ifdef FSSTRICT
        SaveReg <ds,es>
        pushf
        pusha
                                ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     [bp].TCBNoSetDir,0
        MOV     [bp].TCBSattrib,attr_directory+attr_system+attr_hidden
        UnSetBP_CurrTCB
                                ;72417 end
                                ; Dir calls can find these
        call    GetPath
        JC      dmd40           ; Path not found
        CALLFAR RlseBuffr
        TaskContext DS
        TaskContext ES
                                ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        lea     di,TASKAREA:[di].TCBName1
                                ;72417 end
        MOV     AL,'*'
        stosb
        XOR     AL,AL
        STOSB                   ; Nul terminate it
        call    StartSrch       ; Set search
        call    GETENTRY        ; Get start of directory
        JC      dmd40           ; jump if error
        mov     si,CurrTCB
        mov     ds,word ptr TASKAREA:[si].TCBCurBuf.Segmt
    ASSUME      DS:NOTHING
        MOV     SI,BX                   ; DS:SI -> sector with files for this dir
        LODSW
        CMP     AX,(' ' SHL 8) OR '.'   ; First entry '.'?
        JNZ     dmd40                   ; Nope
        ADD     SI,(SIZE dir_entry) - 2 ; Next entry
        LODSW
        CMP     AX,('.' SHL 8) OR '.'   ; Second entry '..'?
        JNZ     dmd40                   ; Nope
        jmp short dmd50

dmd40:  InternalError <DOS_Mkdir - first or second entry is not . or ..>

dmd50:  CALLFAR RlseBuffr
        popa
        popf
        RestoreReg <es,ds>
endif
        pushf               ; save flushbuf return
;
; release volume and remove path from sharing.
;
        mov     cx,VPB_ACC_SHARE
        CALLFAR Volume_Access_Unlock
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT          ; (ES:DI) -> SFT
        CALLFAR RemoveShare                 ; remove from sharing and free sft
        popf
        MOV     AX,ERROR_ACCESS_DENIED
        CodeUnlock      NEWNODE_GROUP
        ret

;
; We had a problem.  If we can get the buffer, mark the entry deleted and
; flush to disk.  Also, free any clusters allocated in the EA chain.
; Release the volume and remove from sharing.
;
; AX = error code from NewDir
;
NODEEXISTSPOPDEL:
        POP     DX                      ; Parent
        POP     CX
        POP     DX                      ; CX:DX = Entry sector

        mov     bp,CurrTCB
        mov     [bp].TCBAllowed,ALLOWED_FAIL + ALLOWED_RETRY
        mov     bp,TASKAREA:[bp].TCBThishVPB
        VPBContext es,bp

        SaveReg <AX>
        XOR     AL,AL                   ; Pre read
        CALLFAR GetBuffr
        RestoreReg <AX>                 ; AX = error code from NewDir
        JC      NODEEXISTSP
        mov     DI,CurrTCB
        LDS     DI,TASKAREA:[DI].TCBCurBuf
    ASSUME      DS:NOTHING
        OR      [DI.buf_flags],BUF_ISDIR
        POP     SI                              ; dir_firstfile pointer
;
; SI is index into buffer of directory entry
; DS:DI -> buffer
; AX = error code to return
;
; We need to mark this directory entry as deleted, and to free any clusters
; allocated for EAs.
;
        ADD     SI,DI                   ; DS:SI -> dir_firstfile for object
        SUB     SI,dir_firstfile                ;Point back to start of dir entry
        MOV     BYTE PTR [SI],DIRENT_DELETED    ; Free the entry
        OR      [DI.buf_flags],BUF_DIRTY        ; Dirty buffer
        mov     bx,[si].dir_EAhandle    ; BX = EA handle
        TaskContext DS
        CALLFAR RlseBuffr
;
; See if we had allocated an EA chain. If so, we need to free it since this
; operation failed.
;

        or      bx,bx                   ; Are ther any EAs to delete?
        jz      dmd70                   ; No EAs to delete

        SaveReg <ax,si,ds>              ; preserve all used registers
        mov     si,bp                   ; es:si -> VPB
        call    ReleaseEA
        RestoreReg <ds,si,ax>

dmd70:  SaveReg <ax>
        MOV     AX,BP
        CALLFAR FlushBuf
        RestoreReg <AX>                 ; AX = error code from NewDir

;
; MakeNode returned an error. If it is ERROR_FILE_EXISTS, we translate it to
; ERROR_ACCESS_DENIED. Otherwise, we leave it as is.
;
dmd80:  cmp     AX,ERROR_FILE_EXISTS
        jne     dmd90                   ; Don't translate it.

dmd85:  MOV     AX,ERROR_ACCESS_DENIED
dmd90:  TaskContext DS                  ; B731705
        mov     di,CurrTCB
        les     di,TASKAREA:[di].TCBThisSFT      ; (ES:DI) -> SFT
        CALLFAR RemoveShare             ; remove from sharing and free sft
dmd95:  mov     cx, VPB_ACC_SHARE       ; shared access
        CALLFAR Volume_Access_Unlock    ; release volume
dmdx:   STC
        CodeUnlock      NEWNODE_GROUP
        ret

NODEEXISTSP:
        POP     SI                      ; Clean stack
        JMP     dmd85

EndProc DOS_MkDir

;**     DOS_SetFSInfo - DosSetFSInfo worker for FAT
;
;       ENTRY   bx = infolevel (assumed to be 2 currently)
;                 for infolevel 2:
;                   ax:dx          -> label (uppercased, blank padded, etc)
;                   cx             =  label length (for testing null label)
;                   [WFP_Start]    -> "<drv>:\*.*"
;                   [Curr_Dir_End] -> end of current directory in WFP_Start or -1
;                   [WFP_Path_End] -> char after last \ in WFP_Start
;                   [ThisCDS]      -> CDS
;                   [TCBThishVPB]  =  hVPB
;
;       EXIT    if 'NC' - success
;               if 'C' - error
;                 ax = error code
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       NOTE    may block
;
;       ALGORITHM
;
;       if (volume not writeable)
;         return error
;       Volume_Alloc_Lock(EXCL)
;       search for volume label in root dir
;       if (label not found)
;         if (caller's label length == 0) {  /* deleting non-exist label */
;           rc = no error
;           goto dsf160
;         } else {
;           find free slot in root dir
;           if (not found) {
;             rc = ERROR_CANNOT_MAKE
;             goto dsf160
;           }
;         }
;
;       if (caller's label length == 0)
;         delete label
;       else {
;         copy label into dir entry
;         set date/time
;       }
;
;       mark buffer dirty
;       flush buffer
;       if (error)
;         rc = ERROR_WRITE_FAULT
;       else {
;         update volume label in VPB
;         rc = no error
;       }
;
;dsf160:Volume_Alloc_Unlock(EXCL)
;       return rc

Procedure DOS_SetFSInfo,faronly
    assume ds:nothing,es:nothing

LocalVar    dsf_pVid,DWORD              ; pointer to label
LocalVar    dsf_cbvid,WORD              ; length of label

        CodeLock        NEWNODE_GROUP

        EnterProc <ax,dx, cx>

; We have a good drive name. Make sure we can write to it.

        CALLFAR fVolumeWriteable
        LJC     dsf70                   ; not writeable, go error exit
;
; Ensure that disk is formatted for FAT FS.
;
        CALLFAR fValidFATDisk           ; Valid format for FAT FS?
        STC                             ; Assume not valid.

        LJZ     dsf70                   ; No. AX = error code. Return error.


; If we were to allow two processes to set the VID, there can be a race
; condition as there are several independent blocks in the code below.
; There can also be race conditions with this code and CREATE/MKDIR. So,
; to solve this problem, we take the volume alloc lock (as is done by
; CREATE and MKDIR) to serialize the allocation of a directory entry.
; This serialization is not required for deleting VIDs, but we take it
; anyway since it makes the code easier and since deleting volume labels
; is not a common operation.
;
        mov     cx,VPB_ALLOC_EXCL
        CALLFAR Volume_Alloc_Lock

;

;
; Try to find either the existing VID or a blank spot in the root dir
;
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBSattrib,attr_volume_id  ; look for volids only
        UnSetBP_CurrTCB
                                        ;72417 end
        TaskContext <ds>,,noassume      ; ds = TASKAREA
        SaveReg <bp>
        call    GetPath                 ; go find the path
        RestoreReg <bp>
        jc      dsf10                   ; can't find label

; found a label

        cmp     dsf_cbvid,0             ; does caller want it removed?
        jne     dsf20                   ; no, go set new label

; remove existing label
; CurBuf+2:bx point to dir entry

        mov     di,CurrTCB
        mov     es,word ptr TASKAREA:[di].TCBCurBuf+2   ; es:bx -> dir entry
        mov     es:[bx].dir_name,0E5h   ; delete it
        jmp     dsf40                   ; go update directory

; no label found. if caller passed in null label (to remove label), then
; we're done.

dsf10:  cmp     dsf_cbvid,0             ; null label?
        LJE     dsf60                   ; yes, go exit (carry cleared)

; find free root entry to store label

        SaveReg <bp>
        mov     bp,CurrTCB
        mov     bp,TASKAREA:[bp].TCBThishVPB
        VPBContext es,bp                ; es:bp -> VPB
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBDirStart,0      ; searching in root
        UnSetBP_CurrTCB
                                        ;72417 end
        call    BuildDir                ; find free entry
        RestoreReg <bp>
        mov     ax,ERROR_CANNOT_MAKE    ; assume error
        jc      dsf60                   ; error, go exit

; found an existing label or an empty entry
; caller wants to set new label
; CurBuf+2:bx point to dir entry

dsf20:  mov     di,CurrTCB
        mov     es,word ptr TASKAREA:[di].TCBCurBuf+2
        SaveReg <bx>
        mov     bx,word ptr TASKAREA:[di].TCBCurBuf
ifdef FSSTRICT
        test    ES:[BX.buf_flags],BUF_ISFAT ;Buffer dirty
        jz      dsfdbg
        InternalError <DOS_SetFSInfo: bad buffer flag>
dsfdbg:
endif
        RestoreReg <bx>
        mov     di,bx                   ; es:di point to dir entry
        xor     eax,eax                 ; zero fill
        mov     cx,size dir_entry SHR 2 ; size of entry to be cleared
        .errnz  size dir_entry AND 3
        rep     stosd
        mov     es:[bx].dir_attr,attr_volume_id ; mark entry as vol label

        lds     si,dsf_pVid             ; ds:si -> label
        lea     di,[bx].dir_name        ; es:di -> name in dir entry
        lodsb                           ; al = first byte
        cmp     al,0E5h                 ; allow 0E5h as 1st char
        jne     dsf30
        mov     al,5
dsf30:  stosb                           ; store first byte in dir name
        .errnz  (MAXLABLEN - 11)
        movsd                           ; copy label into dir entry
        movsd
        movsw

; set date/time

        CALLFAR Date16
        mov     es:[bx].dir_date,ax     ; set date
        mov     es:[bx].dir_time,dx     ; set time

; dirty buffer and flush label to disk

dsf40:  mov     di,CurrTCB
        lds     si,TASKAREA:[di].TCBCurBuf
        or      ds:[si].buf_flags,BUF_DIRTY     ; mark buffer dirty

        CALLFAR RlseBuffr
        mov     ax,TASKAREA:[di].TCBThishVPB    ; ax = hVPB
        CALLFAR FlushBuf
        mov     ax,ERROR_WRITE_FAULT    ; in case of error
        jc      dsf60                   ; if error, don't update VPB

; we updated the volume label on disk, now update the copy in VPB

        lea     bx,HIGH4CODE:DeleteVID  ; assume delete label
        cmp     dsf_cbvid,0             ; null label ?
        je      dsf50                   ; yes, go delete label
        lea     bx,HIGH4CODE:UpdateVID  ; update label
        lds     si,dsf_pVid             ; ds:si -> label
dsf50:  SaveReg <bp>
        mov     bp,CurrTCB
        mov     bp,TASKAREA:[bp].TCBThishVPB    ; bp = hVPB
        call    bx                      ; NEAR call to UpdateVID or DeleteVID
        RestoreReg <bp>
        clc                             ; no error

dsf60:  mov     cx,VPB_ALLOC_EXCL
        CALLFAR Volume_Alloc_Unlock     ; Free up the VPB

dsf70:  LeaveProc
        CodeUnlock      NEWNODE_GROUP
        RETP

EndProc DOS_SetFSInfo


;**     DOS_Rename - move/rename the specified file(s) or sub-directory
;
;       ENTRY   [WFP_Start] Points to SOURCE WFP string ("d:/" must be first
;                       3 chars, NUL terminated)
;               [Curr_Dir_End] Points to end of SOURCE Current dir part
;                       ( = -1 if current dir not involved, else Points
;                        to first char after last "/" of current dir part)
;               [Ren_WFP] Points to DEST WFP string ("d:/" must be first 3
;                       chars, NUL terminated)
;               [ThisCDS] Points to CDS being used
;                       (Low word = -1 if NUL CDS (Net direct request))
;               [Sattrib] Is attribute of search, determines what files
;                       can be found
;               [cMeta] indicates no metas
;               dh = 1 if src=dest, else 0
;
;               The volume *MUST* have been EXCLUSIVELY locked for allocation.
;
;       EXIT    Releases the volume alloc lock set by FSD_Move
;               if 'NC' - success
;               if 'C' - error
;                   AX is error code
;                       ERROR_FILE_NOT_FOUND
;                               No match for source, or dest path invalid
;                       ERROR_NOT_SAME_DEVICE
;                               Source and dest are on different devices
;                       ERROR_ACCESS_DENIED
;                               Device name given, Destination exists.
;                               NOTE: In third case some renames may have
;                                been done if metas.
;                       ERROR_PATH_NOT_FOUND
;                               Bad path (not in curr dir part if present)
;                               path too long ( more than PMPATHLEN chars)
;                               SOURCE ONLY
;                       ERROR_BAD_CURR_DIR
;                               Bad path in current directory part of path
;                               SOURCE ONLY
;                       ERROR_SHARING_VIOLATION
;                               Deny both access required, generates an INT 24.
;                       ERROR_CIRCULARITY_REQESTEd
;                               Resulting directory move would have caused a
;                               cirularity
;                       ERROR_DIRECTORY_IN_CDs
;                               Directory cannot be renamed/moved because it
;                               is in use in a CDS
;                       ERROR_BAD_PARENT_POINTER
;                               When trying to update the parent pointer of the
;                               moved directory . or .. were not found.
;                       ERROR_PATH_BUSY
;                               Source is a directory and is a prefix
;                               of some CDS.
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags,bp
;
;       EFFECTS Switches context
;
;       PSEUDOCODE
;          if (source and target not on same drive)
;             return error
;          fVolumeWriteable
;          DOS_Search_First(source)
;          if (source string doesn't exist)
;             return error
;          RlseBuffr
;          if src = dest
;             return no error
;          flag = dir_attr
;          GetPathNoSet(dest)
;          if (found)
;             RlseBuffr
;             return error
;          if (bad path)
;             return error
;          RlseBuffr
;          if (moving dir/file from one dir to another)
;             NewEntry  ; make a directory entry
;             mark buffer dirty
;             RlseBuffr
;             copy source file info into new node
;             if (moving dir) {
;                SetDirSrch
;                StartSrch
;                GETENTRY    ; get first entry (.)
;                update . and .. entries
;                RlseBuffr
;             }
;             GET_SOURCE
;             free source
;          }
;          else (renaming file/dir within directory) {
;             GET_SOURCE
;             copy new name into old entry
;          }
;          mark buffer dirty
;          RlseBuffr
;          FNotify_Path_Wake ; source
;          if (dest != src)
;              FNotify_Path_Wake ; destination
;          FlushBuf
;          if (Volume alloc lock to be unlocked)
;            Volume_Alloc_Unlock(EXCL)
;
;   Change history
;   --------------
;   69544  08/03/93  LRN  changed code at label 'dor280' to 'pop bp' instead
;                         of 'pop ax'
;
Procedure   DOS_Rename,faronly
    ASSUME      DS:nothing, ES:NOTHING
        PMONLY
LocalVar    dr_srceqdest,BYTE   ; src = dest?
LocalVar    dr_fvolunlock,BYTE  ; 1 if we need to release volume alloc lock
LocalVar    dr_ren_type,WORD    ; renaming dir or file

        CodeLock        NEWNODE_GROUP

        EnterProc <dx>
        mov     dr_fvolunlock,1 ; We have to unlock the volume for allocation

        TaskContext <ds>

; If (target drive != source drive) then fail

        MOV     [ExtErr_Locus],ERRLOC_DISK
                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        MOV     DI,TASKAREA:[si].TCBRen_WFP
        MOV     SI,TASKAREA:[si].TCBWFP_Start
                                        ;72417 end
;
; Make sure that the source and destination are on the same drive. If not,
; we fail the rename operation.
;
        mov     al,byte ptr [di]        ; pick up drive letter of new name.
        CALLFAR UCase                   ; convert it to upper case.
        mov     ah,al                   ; save it for the compare.
        mov     al,byte ptr [si]        ; pick up drive letter of old name.
        CALLFAR UCase                   ; convert it to upper case.
        cmp     al,ah                   ; are they both on the same drive?
        je      dor05                   ; probably, go see if writeable volume.

        mov     ax,ERROR_NOT_SAME_DEVICE ; no, go report
        jmp     short dor10              ; the error.

;
; Check to make sure we can write on this volume.
;
dor05:  CALLFAR fVolumeWriteable        ; Can we write to the volume?
        jc      dor10                   ;   NO, exit with error
;
; Check that source exists and path is good.
;
        push    bp                      ; DOS_Search_First uses bp
        mov     bp,CurrTCB
        mov     [bp].TCBDMAAdd.Segmt,ss
                                        ;72417 moved from PTDA to TCB
        push    TASKAREA:[bp].TCB_pSrchBuf
                                        ;72417 end
        pop     [bp].TCBDMAAdd.Offst
        call    DOS_Search_First        ; Sets [NoSetDir] to 1, [CurBuf+2]:BX
                                        ;    points to entry, [Name1] = source
        pop     bp
        JNC     dor20                   ; continue if source string found

        CALLFAR RlseBuffr
        CMP     AX,ERROR_NO_MORE_FILES  ; set the correct error
        JNZ     dor10
        MOV     AX,ERROR_FILE_NOT_FOUND

dor10:  jmp     dor290                  ; no buffers locked

; Source path and file string found
;
; [ThisCDS], [ThishVPB] are set and will remain correct
; throughout the RENAME since it is known at this point that the source
; and destination are both on the same device.
;
; [Sattrib] is also set.
; [ptda_pSrchBuf] points to dir entry

dor20:  CALLFAR RlseBuffr
        cmp     dr_srceqdest,1          ; src = dest?
        LJE     dor265                  ; yes, go return
        mov     dr_ren_type,VPB_ACC_SHARE  ; assume source is a file
                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        mov     si,TASKAREA:[si].TCB_pSrchBuf
                                        ;72417 end
        test    [si.fbs_dir_entry.dir_attr],attr_directory ; is source a dir?
        jz      dor35                   ; no, don't change ren_type

; quick test to verify this is indeed a directory (it is possible to have a
; file with a directory attribute on a corrupted disk) so we don't further
; mess up the disk.

        mov     cx,[si.fbs_dir_entry.dir_size_l] ; cx = size low word
        or      cx,[si.fbs_dir_entry.dir_size_h] ; cx = 0 if directory
        jz      dor30

ifdef FSSTRICT
        InternalError <DOS_Rename: bad attribute>
else
        mov     ax,ERROR_ACCESS_DENIED
        jmp     dor290
endif

dor30:  mov     dr_ren_type,VPB_ACC_EXCL ; source is a directory
;
; Now check that the destination path exists.
;
                                        ;72417 moved from PTDA to TCB
dor35:  mov     si,CurrTCB
        push    TASKAREA:[si].TCBWFP_Start             ; save source string pointer
        push    ax
        MOV     ax,TASKAREA:[si].TCBRen_WFP            ; Swap source and destination ptrs
        MOV     TASKAREA:[si].TCBWFP_Start,ax          ;  because GetPath is passed parameters
                                        ;  using PTDA variable WFP_Start
        MOV     TASKAREA:[si].TCBCurr_Dir_End,-1       ; No current dir on dest
                                        ; set for root

; (A rename is like a CREATE_NEW as far as the destination is concerned.)

        MOV     BYTE PTR TASKAREA:[si].TCBCreating,0FFH
        mov     si,ax
        pop     ax
                                        ;72417 end

; path cache must be turned off before calling GetPath so that some important
; side effect (either global var or buffers; not sure) will occur. This needs
; to be documented.

                                        ; [WFP_Start] -> destination path
        push    bp
        call    GetPathNoSet
        pop     bp
                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        pop     TASKAREA:[si].TCBWFP_Start    ; make this point to source string
                                        ;72417 end

;   If this GetPath fails due to file not found, we know all renames will work
;   since no files match the destination name.  If GetPath fails for any other
;   reason, the rename will fails on a path not found, or whatever (also fails
;   if a device or directory is found).  If the GetPath succeeds, the rename
;   fails.  The destination filename is in [NAME1] and the explicit source
;   name is at ptda_pSrchBuf in the directory entry part.
;
;   Summary of return codes from GetPath
;   ====================================
;
;   C && Z && (CL==00H)   bad name in path
;   C && Z && (CL==80H)   path OK but final name bad
;   C && Z && (CL==81H)   path OK but final name had metas in it.
;   C && NZ               file in middle of path or bad name in path or
;                         bad attribute mismatch or path too long or malformed
;   NC                    always means that the buffer TCBCurBuf is locked
;   NC && (AH==10000000B) if sign bit set the AH is a device ID not a file
;   NC && Z               directory found
;   NC && NZ              file found.

        JC      dor60                   ; Failed - go check why.

        CALLFAR RlseBuffr
        MOV     AX,ERROR_ACCESS_DENIED
dor40:  stc
dor50:  jmp     dor265

; If the destination path does not exist then error

dor60:  MOV     AX,ERROR_PATH_NOT_FOUND
        JNZ     dor50                   ; CY set

        mov     si,CurrTCB
        cmp     [si].TCBFailErr,0
        JNZ     dor40                   ; Search for dest failed because
                                        ;       user FAILed to hard error
        OR      CL,CL                   ; bad path?
        jz      dor40                   ; yes, go return error

;
; At this point, the source exists, the dest doesn't, and both paths are OK.
;
dor70:  CALLFAR RlseBuffr               ; release dest buffer
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     AX,[bp].TCBDirStart
        MOV     [bp].TCBDESTSTART,AX    ; save dir starting cluster #
        UnSetBP_CurrTCB
                                        ;72417 end
                                        ; this is used to switch to the
                                        ; destination dir and for the update
                                        ; of the parent pointer for dir
                                        ; rename/move operations.
        test    dr_ren_type,VPB_ACC_EXCL   ; directory?
        jnz     dor160                  ; yes, go do cds and circularity test
;
; Compare DirStart of dest and source. If they're the same then it's a
; simple rename (no need to move).
;
                                        ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        mov     di,TASKAREA:[di].TCB_pSrchBuf
                                        ;72417 end
        CMP     AX,WORD PTR [di.find_buf_DirStart]   ; DirStart of source
        jnz     dor170                  ; jump for file move operation
        jmp     dor190                  ; Jump for simple file rename

;   Directory Rename/Move Operation

; if the source parent == target parent then this is a "simple" directory
; rename operation that does not require moving the directory and hence
; changing parents.

                                        ;72417 moved from PTDA to TCB
dor160: mov     di,CurrTCB
        MOV     AX,TASKAREA:[di].TCBDESTSTART     ; DirStart of dest
        mov     di,TASKAREA:[di].TCB_pSrchBuf
                                        ;72417 end
        CMP     AX,WORD PTR [di.find_buf_DirStart]   ; DirStart of source
        LJZ     dor190                  ; jump if simple directory rename

; Move Directory From One Directory to Another
; --------------------------------------------
; Make a new node for the rename, in the target directory
; copy the source to new node
; delete the old (source) node
; update the parent pointer
; DI = ptda_pSrchBuf

dor170: mov     al,[di.fbs_dir_entry.dir_attr]  ; set attribute the same
        xor     ah,ah
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     [bp].TCBAttrib,AX       ;  as the source.
        UnSetBP_CurrTCB
                                        ;72417 end
        push    bp
        xor     di,di
        mov     dx,di                   ; DI:DX -> EABuf (No EAs to set here)
        call    NewEntry
        pop     bp
        mov     dr_fvolunlock,0         ; NewEntry always unlocks the volume
        LJC     dor265

; A zero length entry with the correct new name has now been made at
;   [CurBuf+2]:BX.  This is an exit condition of NewEntry above.

        mov     DI,CurrTCB
        LES     DI,TASKAREA:[DI].TCBCurBuf

        Assert  ISCURBUF,<ES,DI>,"Got_Dest"

        OR      ES:[DI.buf_flags],BUF_DIRTY ; mark buffer as dirty in advance
        MOV     DI,BX                   ; passed by NewEntry
        lea     di,[di].dir_attr        ; skip name

        SegAssert SS,<DS>,"DOS_Rename1"
        ASSUME  DS:TASKAREA

; Note the source file/directory entry is saved at ptda_pSrchBuf by SearchFirst and
; SearchNext routines (see find.inc for definition of buffer layout)
;
; Copy the source file/directory information to the null node we created
; earlier (mknode).   This has the effect of the move.   Once we have
; done the copy we will delete the source entry and the move (rename) is almost
; complete.

                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        mov     si,TASKAREA:[si].TCB_pSrchBuf
                                        ;72417 end
        lea     SI,TASKAREA:[si].fbs_dir_entry.dir_attr
        MOV     CX,((SIZE dir_entry) - dir_attr) SHR 2
        .errnz  ((SIZE dir_entry) - dir_attr) AND 2
        .erre   ((SIZE dir_entry) - dir_attr) AND 1
        REP MOVSD
        MOVSB
        CALLFAR RlseBuffr

;   If the thing we moved was a directory, then we must update its
;   parent pointer to point to the new parent directory.

;   If source was file then do not update its parent pointer (it does not have
;   one).

        test    dr_ren_type,VPB_ACC_EXCL ; directory renamed?
        jz      dor180                  ; no, skip updating parent pointer

;   Update Parent Pointer
;   =====================
;
;   Get ready to read the directory that has moved.   Note that the directory
;   itself has not moved.   There is a new directory entry in the new parent
;   that points to the same directoy file as before.   What we have to do
;   is read that directory file and update its ".." entry with the new parent
;   since the new node is in a different parent than it was before.

                                        ;72417 moved from PTDA to TCB
        mov     bx,CurrTCB
        mov     bx,TASKAREA:[bx].TCB_pSrchBuf
                                        ;72417 end
        mov     bx,WORD PTR[bx.fbs_dir_entry.dir_firstfile] ; get dir file cluster #
        push    bp
        mov     bp,CurrTCB              ; get vpb pointer
        mov     bp,TASKAREA:[bp].TCBThishVPB
        VPBContext es,bp

;   SetDirSrch Requires (es:bp) to point to the VPB
;                       (bx) = cluster number of dir

        call    SetDirSrch              ; Reset search to start of dir
        pop     bp
        LJC     dor265                  ; jump if failed

        push    bp
        call    StartSrch               ; position at start of directory
        call    GETENTRY                ; get the first entry
        pop     bp
        LJC     dor265                  ; jump if failed to get entry

;   GETENTRY leaves:-
;   [CurBuf+2]:BX = Pointer to next directory entry in CurBuf
;   No consistency check is perform to check that . and .. entries are first
;   since os/2 fat file system assumes that the disk image is correct at all
;   times.

                                      ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        mov     cx,TASKAREA:[di].TCBDESTSTART    ; (cx) = new parent pointer
                                      ;72471 end
        lds     di,TASKAREA:[di].TCBCurBuf ; (ds:di) pointer to buffer
    ASSUME      DS:NOTHING            ; (bx) offset to first entry, see GETENTRY
        MOV     SI,BX                 ; (ds:si) pointer to . directory entry
        lea     SI,[si].(SIZE dir_entry) ; (ds:si) pointer to .. directory entry

;   (cx) == [DESTSTART]   the new parent pointer
;   (ds:dir_firstfile[si])    will contain the old parent pointer
;   (ds:di) pointer to buffer

        mov     ds:dir_firstfile[si],cx     ; update the parent pointer
                                      ; (DS:DI) pointer to buffer
        OR      DS:[DI.buf_flags],BUF_DIRTY ; mark buffer dirty and release it
        CALLFAR RlseBuffr

        TaskContext <ds>,di

; Get back to the source entry so that we can remove it (mark as deleted).

dor180: push    bp
        CALL    GET_SOURCE
        pop     bp
        LJC     dor265

        mov     di,CurrTCB
        mov     es,word ptr TASKAREA:[di].TCBCurBuf.Segmt
        MOV     DI,BX                   ; passed by GET_SOURCE
        MOV     AL,DIRENT_DELETED
        STOSB                           ; "free" the source
        JMP     SHORT dor200

; Simple Rename
; =============
;
; In the case of the simple rename we are not having to move the file or dir
; from one directory to another.   All we have to do is change its name
; in the current directory - hence the term simple rename.
;
; since we have so support the "?" meta character renames for MS-DOS FCB type
; compatibility.   We must now reposition the file system back to the
; source file so that we can loop around again to do the find_next

dor190: push bp
        CALL    GET_SOURCE              ; Get the source back
        pop     bp
        LJC     dor265

; just copy the new name into the old directory entry

; [TCBCurBuf+2]:BX points to the entry

        mov     di,CurrTCB
        mov     es,word ptr TASKAREA:[di].TCBCurBuf.Segmt
        MOV     DI,BX                   ; (ES:DI) -> directory entry name
        SegAssert SS,<DS>,"DOS_Rename2"
        ASSUME  DS:TASKAREA
                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        lea     si,TASKAREA:[si].TCBName1    ; (DS:SI) -> new name (asciiz)
                                        ;72417 end
        CALLFAR UnpackName              ; put new name in dir entry form

;   The buffer is marked as dirty so that should a flush buffer be performed
;   or this buffer is freed it will get written back to disk.
;   [TCBCurBuf+2]:BX points to the entry

dor200: mov     di,CurrTCB
        mov     di,word ptr TASKAREA:[di].TCBCurBuf

        Assert  ISCURBUF,<ES,DI>,"dor200"

        mov     al,ES:[BX.dir_attr]     ; AL = attribute
        OR      ES:[DI.buf_flags],BUF_DIRTY
        CALLFAR RlseBuffr
        SegAssert SS,<DS>,"DOS_Rename3"

        mov     ah,fFNY_DELETE          ; this is for ren/del of source
        CALLFAR FNotify_Path_Wake       ; source

                                        ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        push    TASKAREA:[di].TCBWFP_Start  ; save source string pointer
        MOV     ax,TASKAREA:[di].TCBRen_WFP ; Swap source and destination ptrs
        MOV     TASKAREA:[di].TCBWFP_Start,ax          ;
        mov     di,ax
                                        ;72417 end

        cmp     dr_srceqdest,1          ; src = dest?
        je      dor263                  ; Yes. Don't call notify again.
        mov     ah,fFNY_NODELETE        ; this is not for ren/delete
        CALLFAR FNotify_Path_Wake       ; destination

                                        ;72417 moved from PTDA to TCB
dor263: mov     ax,bp
        mov     bp,CurrTCB
        pop     [bp].TCBWFP_Start       ; make this point to source string
        mov     bp,ax
                                        ;72417 end

; commit renamed file to disk

    assume ds:nothing,es:nothing
dor260: push    bp
        mov     bp,CurrTCB
        mov     ax,TASKAREA:[bp].TCBThishVPB
        SaveReg <ax>
        CALLFAR FlushBuf
        RestoreReg <ax>
        mov     bp,CurrTCB
        cmp     [bp].TCBFailErr,0
        JNZ     dor280                  ; User FAILed to hard error
        pop     bp

        SaveReg <ax,bx>
        mov     bx,ax
        call    UseCache                ; Can we use the cache for this vol ?
        RestoreReg <bx,ax>
        jc      dor264

CpuMode 386
        SaveReg <ds,es,edi,esi,ebx,eax>
        FlatContext <ds,es>
        movzx   eax,ax                  ; EAX = hVPB
        CCALL32  _BufFlushFATDir,<eax>  ; flush FAT and Directory
        RestoreReg <eax,ebx,esi,edi,es,ds>
        ASSUME  DS:TASKAREA
CpuMode reset
dor264: clc

dor265: pushf
        cmp     dr_fvolunlock,0
        je      dor270                  ; Volume already unlocked
        mov     cx,VPB_ALLOC_EXCL
        CALLFAR Volume_Alloc_Unlock
dor270: popf                            ; Restore any error indication
        LeaveProc
        CodeUnlock      NEWNODE_GROUP
        ret

dor280: pop     bp                      ; saved bp (69544)...formerly popped ax
        MOV     AX,ERROR_PATH_NOT_FOUND
dor290: stc
        jmp short dor265

EndProc DOS_Rename


Break   <BuildDir, NewDir -- Allocate directory entries>

;***    BuildDir - Allocate a directory entry
;***    NewDir - Grow a newly created directory
;
;       Grow directory if no free entries and not root
;
;       ENTRY   ES:BP points to VPB
;               [ThisSFT] Set if using NewDir entry point
;               [EntFree] possible free directory entry, set during search
;               [LASTENT] current last valid entry number in directory if
;                        no free entries.
;               [DIRSTART] Points to first cluster of dir (0 means root)
;               The VPB **MUST** have been locked for allocation to prevent
;
;       EXIT    'C' if failure
;               'NC' if success
;                   If a new dir [DIRSTART],[CLUSFAC],[ClusNum],[DIRSEC] set
;                   [CurBuf+2]:BX points to free entry
;
;       EFFECTS Switches Context
;

Procedure   BuildDir,near,local
        SegAssert SS,<DS>
        ASSUME DS:TASKAREA, ES:NOTHING

        chkvselhd es,bp

ifdef FSSTRICT
        test    es:[bp].vpb_fsd.vpdFAT_alloc,-1
        jnz     bldd                            ; Lock has been taken
        InternalError <BuildDir: VPB not locked for allocation>
bldd:
endif   ; FSSTRICT

                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     AX,[bp].TCBEntFree
        UnSetBP_CurrTCB
                                        ;72471 end
        CMP     AX,-1                           ; free entry found?
        JZ      bldd2                           ; no, must grow dir.
bldd0:  call    GETENT
        jc      bldd3
;
; EntFree was set during the search.  Check that the entry is still
; free; some other process may have already created a file there!
;
bldd1:  push    ds
        push    bp
        mov     bp,CurrTCB
        mov     ds,word ptr [bp].TCBCurBuf+2
        pop     bp
        mov     al,ds:[bx]                      ; get first byte of name
        pop     ds
        or      al,al                           ; never allocated?
        jz      bldd3                           ; yes (carry clear from or)
        cmp     al,DIRENT_DELETED               ; freed entry?
        je      bldd3                           ; yes (carry clear from cmp)
        call    NextEntry                       ; try the next one

; bugbug 28-jul-88 peterwu - dir search should restart at beginning
; If EntFree entry is no longer free, and we don't find free entries between
; EntFree and EOD (end of directory), we should re-scan from beginning of
; directory to EntFree (a file may have been deleted recently) before growing
; root. We could also just start searching from the beginning of the
; directory if EntFree is no longer free, but there's a much larger chance of
; finding a free entry between EntFree and EOD so it's good to look there
; first (better performance).

        jc      bldd2
        jmp     bldd1

                                        ;72417 moved from PTDA to TCB
bldd2:  SetBP_CurrTCB
        CMP     [bp].TCBDirStart,0              ; root directory?
        UnSetBP_CurrTCB
                                        ;72417 end
        JNZ     NewDir                          ; no, grow it
        STC                                     ; Can't grow root
bldd3:  ret

;**     NewDir - Grow a newly created directory
;
;
Entry   NewDir
                                        ;72417 moved from PTDA to TCB
        mov     bx,CurrTCB
        MOV     BX,TASKAREA:[bx].TCBDirStart
                                        ;72417 end
        OR      BX,BX
        JZ      NULLDIR
        call    GetEOF
        jc      bldd3
NULLDIR: MOV     CX,1
        call    Allocate
        jc      bldd3

                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     DX,[bp].TCBDirStart
        UnSetBP_CurrTCB
        OR      DX,DX
        JNZ     ADDINGDIR
        call    SetDirSrch
        jc      bldd3
        SetBP_CurrTCB
        MOV     [bp].TCBLastEnt,-1
        MOV     DX,WORD PTR [bp].TCBDirSec
        MOV     SI,WORD PTR [bp].TCBDirSec+2          ; SI:DX -> starting sector
        UnSetBP_CurrTCB
                                        ;72471 end
        JMP     SHORT GOTDIRREC
ADDINGDIR:
        PUSH    BX
        mov     bx,CurrTCB
        mov     bx,TASKAREA:[bx].TCBClusNum
        call    IsEOF
        POP     BX
        JB      NOTFIRSTGROW
        push    bp
        mov     bp,CurrTCB
        mov     [bp].TCBClusNum,BX
        pop     bp
NOTFIRSTGROW:
        MOV     DX,BX
        XOR     BL,BL
        call    FigRec
GOTDIRREC:
        chkvselhd es,bp
        MOV     CL,ES:[bp].vpb_fsd.vpdFAT_cluster_mask
        INC     CL
        XOR     CH,CH
;
; SI:DX = sector number
;
ZERODIR:
        PUSH    CX
        push    bp
        mov     bp,CurrTCB
        mov     [bp].TCBAllowed,ALLOWED_FAIL + ALLOWED_RETRY
        pop     bp
        mov     al,01h              ; don't pre-read
        push    si                  ; Save high word of sector number
        xchg    si,cx               ; CX:DX = sector number
        CALLFAR GetBuffr
        pop     si                  ; SI:DX = sector number
        JNC     GET_SSIZE
        POP     CX
        ret

GET_SSIZE:
        chkvselhd es,bp
        MOV     CX,ES:[bp].vpb_fsi.vpi_cbSector
        mov     DI,CurrTCB
        LES     DI,TASKAREA:[DI].TCBCurBuf
        OR      ES:[DI.buf_flags],BUF_ISDIR
        PUSH    DI
        ADD     DI,BUFINSIZ
        XOR     AX,AX
        SHR     CX,1
        REP STOSW
        JNC     EVENZ
        STOSB
EVENZ:
        POP     DI
        OR      ES:[DI.buf_flags],BUF_DIRTY
        CALLFAR RlseBuffr

        VPBContext es,bp

        POP     CX
        ADD     DX,1                ; INC does not affect CARRY
        ADC     SI,0                ; SI:DX = next sector
        LOOP    ZERODIR
                                    ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     AX,[bp].TCBLastEnt
        UnSetBP_CurrTCB
                                    ;72471 end
        INC     AX
        jmp     bldd0                   ; go back to point to entry

EndProc BuildDir


;***    SetDotEnt - set up a . or .. directory entry for a directory.
;
;       ENTRY   ES:DI point to the beginning of a directory entry.
;               AX contains ". " or ".."
;               DX contains first cluster of entry
;               [TCBThisSFT] contains pointer to SFT for entry
;               (used to get time and date of creation)
;
;       EXIT    "." or ".." entry made in directory
;
Procedure   SetDotEnt,NEAR,local
        SegAssert SS,<DS>
        ASSUME DS:TASKAREA, ES:NOTHING
        SaveReg <DS>
;
; Fill in name field
;
        STOSW
        MOV     CX,4
        MOV     EAX,"    "
        STOSD
        STOSD
        STOSB
;
; Set up attribute
;
        MOV     AL,attr_directory
    .errnz      dir_attr-(dir_name+11)
        STOSB
;
; Initialize time and date of creation
;
        ADD     DI,10
        mov     si,CurrTCB
        lds     si,TASKAREA:[si].TCBThisSFT
    ASSUME DS:NOTHING
        MOV     AX,[SI.sf_fsi.sfi_mtime]
    .errnz      dir_time-(dir_attr+1+10)
        STOSW
        MOV     AX,[SI.sf_fsi.sfi_mdate]
    .errnz      dir_date-(dir_time+2)
        STOSW
;
; Set up first cluster field
;
        MOV     AX,DX
    .errnz      dir_firstfile-(dir_date+2)
        STOSW
;
; 0 file size
;
        XOR     EAX,EAX
    .errnz      dir_size_l-(dir_firstfile+2)
    .errnz      (size dir_entry)-(dir_size_l+4)
        STOSD
        RestoreReg <DS>             ; Restore DS of entry
        ret
EndProc SetDotEnt


Break   <MakeNode -- CREATE A NEW NODE>

;***    MakeNode - Make a new node (directory entry)
;
;       MakeNode is called to make a new directory entry - this may be
;       either a file or a directory. It is assumed that the entry to
;       be made does not represent a device. The caller is responsible
;       for detecting that case, and doing different work for it, since
;       it is not possible to "CREATE" a device in this way.
;       If the entry exists, then an error is returned. This means that
;       MakeNode **CANNOT** be called to truncate an existing file.
;
;
;       ENTRY   AX - attribute to create
;               DI:DX -> EABuf (0 => no EAs to set) (See EAOPStruc in EXTATTR.INC)
;                   NOTE that the buffer has not been validated yet.
;               [WFP_START] Points to WFP string ("d:/" must be first 3
;                       chars, NUL terminated)
;               [CURR_DIR_END] Points to end of Current dir part of string
;                       ( = -1 if current dir not involved, else Points
;                        to first char after last "/" of current dir part)
;               [ThisCDS] Points to CDS being used
;               [ThisSFT] Points to SFT to fill in
;                       sf_fsi.sfi_mode field set
;                       sf_flags field set
;
;       EXIT    Sets EXTERR_LOCUS = errLOC_Disk or errLOC_Unk via GetPathNoset
;               'C'if error
;                   ERROR_ACCESS_DENIED - Attribute mismatch/node exists as dir
;                   ERROR_CANNOT_MAKE - new node could not be created
;                   ERROR_FILE_EXISTS - file exists and truncation not ok
;                   ERROR_PATH_NOT_FOUND - bad path
;                       SI return from GetPath maintained
;
;               'NC' if success
;                   [CurBuf+2]:BX Points to entry
;                   [CurBuf+2]:DI Points to entry.dir_firstfile
;                   [ThisSFT] is filled in
;                       sf_fsi.sfi_mode = unchanged.
;                   Attribute byte in directory entry is AL of input
;
;       USES    AX, BX, CX, DX, DI, SI, ES, BP, Flags
;
;       PSEUDOCODE
;           if (!ValidFATDisk)
;               return (not dos disk)
;           GetPathNoSet
;           if (found)
;               if (not volume id or directory)
;                   InternalError
;               else
;                   Return error
;           else    /* Can CREATE it */
;               call    NewEntry /* to create a new entry */
;
; NOTE: We are taking an EXCLUSIVE volume alloc lock in this procedure.
;       we only need a share lock for the duration of builddir call (while
;       allocating disk clusters). But we are taking EXCL. lock to avoid
;       RACES BETWEEN TWO CREATES IN THE SAME SUBDIRECTORY.
;       We have to take the lock before the call to GetPath since GetPath
;       sets some variables that are used later, and if many processes are
;       making new entries in the same directory, we get race conditions in
;       BuildDir.
;
Procedure   MakeNode,NEAR,LOCAL
        SegAssert SS,<DS>
        ASSUME DS:TASKAREA, ES:NOTHING

                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     BYTE PTR [bp].TCBCreating,0FFH    ; Creating
        MOV     [bp].TCBNoSetDir,0
        MOV     [bp].TCBSattrib,AX
        UnSetBP_CurrTCB
                                        ;72417 end
;
; Verify that this is a valid FAT disk
;
        call    fValidFATDisk           ; Valid format for FAT FS? Trashes AX!!
        jz      mkn50                   ; No. AX = error code. Return error.

;
; Take the volume alloc lock here so that we prevent races with CREATE/MKDIR/
; RENAME. This is necessary since GetPath sets up variables whose indications
; may change - so we take the lock up front so that only one process can be
; allocating a new directory entry on this volume at any one time.
;
        mov     cx,VPB_ALLOC_EXCL
        CALLFAR Volume_Alloc_Lock

        SaveReg <DI,DX>
        call    GetPathNoSet
        RestoreReg <DX,DI>              ; DI:DX -> EABuf
;
; (ES:BP) -> VPB
; [CurBuf+2]:BX -> into CurBuf to start of entry
; [CurBuf+2]:SI -> into CurBuf to dir_firstfile field
        jnc     mkn60                   ; File existed
        jnz     mkn10                   ; Path bad
        cmp     cl,80H                  ; Check CL return from GETPATH
        jnz     mkn10                   ; Name simply not found, and no metas

        call    NewEntry                ; NewEntry releases the Volume Alloc Lock
        ret
;
; Various error paths. We make sure we unlock the volume alloc lock that we
; took at the beginning.
;
mkn10:  mov     ax,ERROR_PATH_NOT_FOUND ; bad path
        jmp     short mkn40             ; go return error

mkn20:  mov     ax,ERROR_ACCESS_DENIED  ; Attribute mismatch/exists as dir
        jmp     short mkn40             ; go return error

mkn30:  CALLFAR RlseBuffr               ; file exists and truncation not ok
        mov     ax,ERROR_FILE_EXISTS

mkn40:  mov     cx,VPB_ALLOC_EXCL
        CALLFAR Volume_Alloc_Unlock

mkn50:  stc
        ret
;
; The node exists.  It may be either a directory or file. This is considered
; an error.
;
;   'Z' => directory
;
; If it exists and we are not creating a volume label or a directory, then
; it is considered an InternalError. This is because we assume that all the
; callers take care of the case where the file exists. We will assemble this
; code conditionally to catch bugs in the file system.
;
; Otherwise, we return an error.
;
IFDEF FSSTRICT
mkn60:  jz      mkn20                   ; It is a directory. Return error.
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        test    byte ptr [bp].TCBAttrib,(attr_volume_id+attr_directory)
        UnSetBP_CurrTCB
                                        ;72417 end
        jnz     mkn20       ; Cannot already exist if making DIR or Volume ID
InternalError   <MakeNode: File Exists>
ELSE
mkn60:  jmp     short mkn30
ENDIF

EndProc MakeNode


;***    NewEntry - Make a new directory entry
;
;       It is assumed that the object does not already exist.
;
;       ENTRY   DI:DX -> EABuf (0 => no EAs) (See EAOP Struc in EXTATTR.INC)
;                   NOTE that the buffer has not been validated yet.
;               [ThisSFT] contains pointer to SFT
;               [ThishVPB] contains hVPB
;               [LASTENT] current last valid entry number in directory if no
;                       free entries
;               [VOLID] set if a volume ID was found during search
;               [Attrib] Contains attributes for new file
;               [DIRSTART] Points to first cluster of dir (0 means root)
;               [Name1] contains the name to create (asciiz)
;               The VPB **MUST** have been locked EXCLUSIVE for allocation
;               to prevent races.
;
;       EXIT    'C' if error
;                   AX = error code
;                   Can't grow dir, atts didn't match, attempt to make 2nd
;                   vol ID, user FAILed to I 24
;               'NC' if success
;                   [CurBuf+2]:BX points to start of directory entry
;                   [CurBuf+2]:SI points to dir_firstfile of directory entry
;                   Ref count on VPB incremented
;                   SFT filled in for object
;               Volume Alloc Lock is released on return
;
;       USES    AX, CX, DX, DI, ES, BP, Flags
;
;       ALGORITHM
;
;           BuildDir()
;           if error
;               jump to errorexit
;           Fill up directory entry
;           FillSFTFromDirEnt()
;           RlseBuffr()
;           Volume_Alloc_Unlock(EXCL)
;           if (EABuf) {
;               ProbeBuff()
;               if (error)
;                   jump to errorexit2
;               SetFEAInfo()
;               if (error)
;                   jump to errorexit2
;           }
;           FlushBuf()
;           FNotify_Path_Wake()
;           GetBufBack()
;           return (any error)
;
;           errorexit2: GetBufBack()
;                       Mark entry as DELETED
;                       FlushBuf()
;           errorexit:  Volume_Alloc_Unlock(EXCL)
;                       return (error)
;
Procedure   NewEntry,near,local
        SegAssert SS,<DS>
        ASSUME DS:TASKAREA, ES:NOTHING

LocalVar    ne_EABufPtr,DWORD           ; Pointer to EABuf
LocalVar    ne_DirEntOff,WORD           ; Offset of directory entry in sector
LocalVar    ne_DirBufSec,DWORD          ; Sector of directory entry
LocalVar    ne_DirEntOff,WORD           ; Offset of directory entry in buffer
LocalVar    ne_BufhVPB,WORD             ; hVPB of buffer with directory entry
LocalVar    ne_SavOrigin,WORD           ; Saved DirStart to handle REOPENs of
                                        ; EA file

        EnterProc <DI,DX>               ; Save EABuf pointer in local variable
        mov     di,CurrTCB
        CMP     TASKAREA:[di].TCBFailErr,0
        LJNZ    ne_err          ; User FAILed, node might exist
        SaveReg <BP>            ; Save pointer to stack frame
        mov     BP,TASKAREA:[DI].TCBThishVPB
        VPBContext es,bp
    ASSUME      ES:NOTHING
        CALL    BuildDir        ; Try to build dir
        RestoreReg <BP>         ; BP points to stack frame
        LJC     ne_err          ; Error building dir. Return.
;
; [CurBuf+2]:BX points to free entry in directory
;
                                ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        TEST    BYTE PTR [bp].TCBAttrib,attr_volume_id
        UnSetBP_CurrTCB
        JZ      NOTVOLID
        SetBP_CurrTCB
        CMP     BYTE PTR [bp].TCBVolID,0
        UnSetBP_CurrTCB
        LJNZ    ne_err          ; Can't create a second volume ID
NOTVOLID:
        mov     di,CurrTCB
        mov     es,word ptr TASKAREA:[di].TCBCurBuf+2
        MOV     DI,BX           ; ES:DI -> Free directory entry
        TaskContext DS
        mov     si,CurrTCB
        lea     SI,TASKAREA:[si].TCBName1    ; DS:SI -> Name1
        CALLFAR UnpackName      ; Move name into dir entry
        SetBP_CurrTCB
        MOV     AL, byte ptr [bp].TCBAttrib
        UnSetBP_CurrTCB
                                ;72417 end
    .errnz      dir_attr-(dir_name+11)
        STOSB                   ; Attributes
        XOR     EAX,EAX
        STOSD
        STOSD
        STOSW                   ; Zero pad
        CALLFAR Date16
        XCHG    AX,DX
    .errnz      dir_time-(dir_attr+1+2*5)
        STOSW                   ; dir_time
        XCHG    AX,DX
    .errnz      dir_date-(dir_time+2)
        STOSW                   ; dir_date
        XOR     AX,AX           ; High word of AX is 0
    .errnz      dir_firstfile-(dir_date+2)
        STOSW                   ; Zero dir_firstfile and size
    .errnz      dir_size_l-(dir_firstfile+2)
        STOSD
    .errnz      (size dir_entry)-(dir_size_l+4)
;
; Save the sector number of the buffer and the offset of the directory entry
; in the sector for later use, and possible return to the caller.
;
        mov     si,CurrTCB
        mov     si,TASKAREA:word ptr [si].TCBCurBuf
ifdef FSSTRICT
        test    ES:[si.buf_flags],BUF_ISFAT ;Buffer for FAT
        jz      nedbg
        InternalError <NewEntry: bad buffer flag>
nedbg:
endif
        OR      ES:[SI.buf_flags],BUF_DIRTY
        MOV     AX,ES:[SI.buf_hVPB]
        mov     DX,word ptr ES:[SI.buf_sector]
        mov     CX,word ptr ES:[SI.buf_sector]+2
        mov     [ne_BufhVPB],ax
        mov     ax,bx                   ; AX -> directory entry in CurBuf
        sub     ax,si                   ; AX = Relative offset into sector of entry
        mov     [ne_DirBufSec].HighWord,cx
        mov     [ne_DirBufSec].LowWord,dx
        mov     [ne_DirEntOff],ax       ; Save these for later use (maybe).
;
; We have made the node. Now we go and fill the SFT from the directory entry.
;
; (CurBuf+2):BX -> directory entry for object

        SaveReg <bp>
        mov     bp,CurrTCB
        mov     bp,TASKAREA:[bp].TCBThishVPB        ; BP = hVPB
        call    FillSFTFromDirEnt
        RestoreReg <bp>
;
; We now check to see if there are any Extended Attributes to set up for
; this object. If there are, we try to set them.
;
        CALLFAR RlseBuffr               ; Release the buffer for now

;
; Release the volume alloc lock since it is now safe to allow other processes
; in to allocate stuff on the volume. This also prevents a deadlock case where
; we try to create an EA file on the volume if we are setting EAs.
;
        mov     cx,VPB_ALLOC_EXCL
        CALLFAR Volume_Alloc_Unlock
;
; The user selector has been RPL stamped by SCI. We therefore test to see if
; it is "zero" by testing with NOT RPL_MASK
;
        test    [ne_EABufPtr].Segmt,NOT SEL_RPLMASK
        jz      ne70                    ; No EAs to set up. Go flush buffers.
;
; Check to make sure that the pointer to EABuf supplied by the user is a
; valid pointer and the buffer is big enough (i.e. size of EAOP).
;
        mov     bx,[ne_EABufPtr].Segmt
        mov     si,[ne_EABufPtr].Offst
        mov     cx,size EAOP
        mov     dh,ACCTYPE_RW
        CALLFAR ProbeBuff               ; Verify that buffer is valid
    ASSUME DS:NOTHING, ES:NOTHING
        jc      ne99                    ; Buffer invalid. Remove directory entry.
;
; Buffer is big enough and pointer is valid. Set the EAlist.
;
        mov     si,TASKAREA:[CurrTCB]
        les     di,TASKAREA:[si].TCBThisSFT ; ES:DI -> SFT
        mov     bx,[ne_EABufPtr].Segmt
        mov     si,[ne_EABufPtr].Offst  ; BX:SI -> EABuf
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     ax,[bp].TCBDirStart     ; #72012, save dirstart in case
        UnSetBP_CurrTCB
                                        ;72417 end
        mov     [ne_SavOrigin],ax       ; when creating the EA file we
                                        ; encounter a problem.
        mov     ax,EASDONTEXIST         ; EAs for the file/dir don't exist
        call    DoSetFEAInfo            ; 'C' if error; 'NC' if successful.
        jc      ne99                    ; Error. Remove directory entry.
        mov     ax,[ne_SavOrigin]       ; Restore DirStart.
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        mov     [bp].TCBDirStart,ax
        UnSetBP_CurrTCB
                                        ;72417 end
;
; Flush the buffers so that everything is written out to disk.
;
ne70:   mov     ax,[ne_BufhVPB]         ; AX = hVPB to flush
        CALLFAR FlushBuf                ; Flush out dir. sector and FATs
        push    ax
                                        ;72417 moved from PTDA to TCB
        SetBP_CurrTCB
        MOV     AL,byte ptr [bp].TCBAttrib ; AL = New entry attribute
        UnSetBP_CurrTCB
                                        ;72417 end
        mov     ah,fFNY_NODELETE        ; AH = not for del/ren operations
        CALLFAR FNotify_Path_Wake
        pop     ax
        jc      ne90                    ; Error doing Flush. Can't do much more.
;
; For the return, we get the directory sector back into the buffer and return
; the pointers to the buffer.
;
        mov     bx,[ne_DirEntOff]
        mov     dx,[ne_DirBufSec].LowWord
        mov     cx,[ne_DirBufSec].HighWord
        SaveReg <BP>
        mov     bp,CurrTCB
                                        ;72417 moved from PTDA to TCB
        test    byte ptr [bp].TCBAttrib,attr_volume_id
                                        ;72417 end
        mov     BP,TASKAREA:[bp].TCBThishVPB
        jz      ne80
;
; pad volume id with blanks and update vpb. assumption is that volid is
; legal (up to 11 bytes, no '.').
;
        SaveReg <cx,si>
        TaskContext <ES>,di
                                        ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        lea     di,TASKAREA:[di].TCBName1  ; (ES:DI) -> Name1
                                        ;72417 end
        mov     cx,11
        mov     al,0                    ; look for end of volid
        repne scasb
        mov     al,' '                  ; fill out rest of 11 bytes with blanks
        rep stosb
                                        ;72417 moved from PTDA to TCB
        mov     si,CurrTCB
        lea     si,TASKAREA:[si].TCBName1
                                        ;72417 end
        call    UpdateVID
        RestoreReg <si,cx>

ne80:   VPBContext es,bp                ; ES:BP -> VPB
        TaskContext DS                  ; Restore DS = TASKAREA for GetBufBack
        call    GetBufBack
        RestoreReg <BP>                 ; BP points to stack frame
ne90:   LeaveProc
        ret

;
; Come here if we have created a directory entry, but encountered an error
; setting the EAs for the entry. We mark the directory entry as deleted and
; return 'C' to the caller to say that the operation failed.
;
    ASSUME DS:NOTHING, ES:NOTHING
ne99:   SaveReg <AX>                    ; Save error code
        mov     bx,[ne_DirEntOff]
        mov     dx,[ne_DirBufSec].LowWord
        mov     cx,[ne_DirBufSec].HighWord
        SaveReg <BP>                    ; Save stack frame pointer
;       mov     bp,CurrTCB              ; Instead of getting the CurrTCB now    #83102
;       SaveReg <BP>                    ;  get it after done with hVPB          #83102
;       mov     BP,TASKAREA:[bp].TCBThishVPB  #81850<- used to get hvpb via TCB #83102
        mov     bp,[ne_BufhVPB]         ;     #81850 get hVPB from stack input
        VPBContext es,bp                ; ES:BP -> VPB
        TaskContext DS                  ; Restore DS = TASKAREA for GetBufBack
        call    GetBufBack              ; Get back the directory entry
;       RestoreReg <BP>                 ; BP -> Current TCB: don't have to      #83102
                                        ;  restore anymore, we'll get it from
                                        ;  scratch since we haven't yet
        mov     bp,CurrTCB              ; Put CurrTCB in BP                     #83102
        mov     es,TASKAREA:[bp].TCBCurBuf.Segmt    ; ES:BX -> directory entry
        mov     es:[bx].dir_name,DIRENT_DELETED     ; Mark entry as deleted
        mov     si,TASKAREA:word ptr [bp].TCBCurBuf ; CP20 B722527
        or      es:[si.buf_flags],BUF_DIRTY         ; CP20 B722527
        RestoreReg <BP>                 ; BP points to stack frame
        mov     ax,[ne_BufhVPB]         ; AX = hVPB to flush
        CALLFAR FlushBuf
        RestoreReg <AX>                 ; AX = error code
        STC                             ; Got an error. Return with 'C'.
        jmp     short ne90              ; Return error to caller

;
; We got an error trying to create the entry. We return ERROR_CANNOT_MAKE
; (since this is what has been returned all along if a create fails).
;
; We release the volume alloc lock before we return.
;
ne_err: mov     cx,VPB_ALLOC_EXCL
        CALLFAR Volume_Alloc_Unlock
        STC                             ; Got an error. Return with 'C'.
        mov     ax,ERROR_CANNOT_MAKE    ; Error code if CREATE failed
        jmp     short ne90

EndProc NewEntry


;**     AttribOk - detect and report bogus attribute bytes
;
;       AttribOk will report whether a given attribute byte is legal
;       or not.  As a side effect, if attr_volume_id is set, the entire
;       attribute byte will be set to attr_volume_id.  (ie other bits
;       will be turned off, except for the archive bit.)
;
;
;       AttribOk supports attribute testing/adjustment for use in file
;       CREATION.  Attribute testing support for directory searching is
;       provided by SrchAttribOk.
;
;       ENTRY   BX      = attribute word, BH ignored.
;                         (reserved byte checking must be done elsewhere)
;
;       EXIT    Carry Clear
;                       BL = attribute byte passed in, unless attr_volume_id
;                            was set, in which case
;                            BL = attr_volume_id + attr_archive
;
;                            Also, attr_archive will be turned on.
;
;               Carry Set
;                       Bogus attribute byte
;
;       USES    BL, flags
;
Procedure AttribOk,,LOCAL

        TEST    BL,NOT (attr_all OR attr_ignore OR attr_volume_id)
                                        ; Mask out any meaningless bits
        JNZ     AttOk2                  ; Bogus attribute bit

        TEST    BL,attr_volume_id
        JZ      AttOk1                  ; not vol id, don't set attr_volume_id

        MOV     BL,attr_volume_id
AttOk1: OR      BL,attr_archive         ; File changed
        TEST    BL,attr_directory + attr_device
        jnz     AttOk2
        ret                             ; attribute byte ok, adjusted for vol-id
AttOk2: STC                             ; attribute byte is bogus
        ret

EndProc   AttribOk


;***    FillDirectBlockDeviceSFT - Open and set up an SFT for a block device
;
;       FillDirectBlockDeviceSFT sets up the SFT for a direct device OPEN.
;
;       ENTRY   ES:DI -> SFT
;               AL = Value to place in sf_flags fields
;               The following fields in the SFT have been set up:
;                   sf_fsi.sfi_mode
;                   sf_ref_count = 0
;                   sf_flags = 0
;
;       EXIT    'C' if error
;                   Sector count too large
;               'NC' if success
;                   SFT set up for device
;                   Ref count on VPB incremented
;
;       USES    AX, CX, DX, DS, ES, Flags
;
Procedure   FillDirectBlockDeviceSFT,NEAR,LOCAL
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

        SaveReg <BP>
;
; Open a direct device.  We need to do the following:
;     Fill in SFT (not via FillSFTFromDirEnt)
;     Increment VPB ref count
;
; Fill in relevant portion of SFT
;
        mov     es:[di].sf_ref_count,1          ; we now own this SFT
;
; sf_fsi.sfi_mode is already filled in
; sf_fsi.sfi_DOSattr is ignored
; sf_flags:   Local file
;             NoDate is ignored in DOS_CLOSE
;             Not pipe
;             DIRECT
;             File (as opposed to char device)
;             Dirty bit is ignored in DOS_CLOSE
;             Bits 0-5 are block dev #
;
        mov     bp,CurrTCB
        push    es                      ; save seg of SFT
        mov     bp,[bp].TCBThishVPB     ; get volume for which
                                        ; the open is intended
        VPBContext es,bp                ; (ES:BP) -> VPB
        push    es                      ; save seg of VPB
        les     si,es:[bp].vpb_fsi.vpi_pDPB     ; (ES:SI) -> DPB
        mov     al,es:[si].dpb_drive    ; get low 6 bits of drive #
        pop     es                      ; (ES:BP) -> VPB
        pop     ds                      ; (DS:DI) -> SFT
    ASSUME DS:NOTHING
        xor     ah,ah                   ; no upper bits
        mov     [di].sf_flags,ax        ; set up flags for later DASD detection
        mov     [di].sf_fsi.sfi_hVPB,bp ; volume of handle is current volume
;
; sf_devptr is not used on local files
; sf_fsd.sfdFAT_firclus is not used in direct access files
;
        xor     bx,bx
        mov     [di].sf_fsi.sfi_ctime,bx ; time/date are not relevant
        mov     [di].sf_fsi.sfi_cdate,bx ; 0, however, is a reasonable value
        mov     [di].sf_fsi.sfi_atime,bx ; time/date are not relevant
        mov     [di].sf_fsi.sfi_adate,bx ; 0, however, is a reasonable value
        mov     [di].sf_fsi.sfi_mtime,bx ; time/date are not relevant
        mov     [di].sf_fsi.sfi_mdate,bx ; 0, however, is a reasonable value

        mov     word ptr [di].sf_fsi.sfi_position,bx
        mov     word ptr [di].sf_fsi.sfi_position.Segmt,bx

;       Calculate size of media.  The VPB has the total number of sectors.  If
;       the multiplication (# of xectors * sector size) overflows, we fail

        mov     ax,es:[bp].vpb_fsi.vpi_totsec.hiword    ; (ax) = high sector count
        mul     es:[bp].vpb_fsi.vpi_cbSector    ; (dx:ax) = high byte count
        or      dx,dx                           ; did we overflow?
        stc
        jnz     odbd10                          ; yes, go fail

        mov     bx,ax                           ; (bx) = high byte count
        mov     ax,es:[bp].vpb_fsi.vpi_totsec.loword    ; (ax) = low sector count
        mul     es:[bp].vpb_fsi.vpi_cbSector    ; (dx:ax) = low byte count
        add     dx,bx                           ; (dx:ax) = total byte count
        jc      odbd10                          ; fail, overflow


;       (DX:AX)     number of bytes on device
;       Carry clear.

        mov     word ptr [di].sf_fsi.sfi_size.loword,ax
        mov     word ptr [di].sf_fsi.sfi_size.hiword,dx
;
; sf_fsd.sfdFAT_cluspos is ignored
; sf_fsd.sfdFAT_lstclus is ignored
; sf_fsd.sfdFAT_dirsec is ignored
; sf_fsd.sfdFAT_dirpos is ignored
; sf_fsd.sfdFAT_name is ignored
; sf sharing info is already set up by MFT_Enter above.
;

odbd10: TaskContext ds
        RestoreReg <BP>
        ret
EndProc FillDirectBlockDeviceSFT

;***    NeedEASPresent - are there any need eas for this file ?
;
;       ENTRY   es:di -> sft of file
;
;       EXIT    NC:     AX = 1 if there are need eas present
;                          = 0 no need eas (or no eas for this volume)
;               CY: error, ax = code
;
;       USES    AX, DS, ES, Flags
;
;       ALGORITHM
;               check for the presence of the eas for this file
;               if there are no eas return (ax, 0)
;               if the ea file is not open , open it by calling
;                       SetupFATEA
;               Get logical Cluster from ea handle
;               call GetEADataInfo with cluster and ea handle
;               return ((no of need eas >0 ?))
;

Procedure NeedEASPresent,near,LOCAL
        assume ds:nothing, es:nothing

LocalVar        nep_sftptr,DWORD        ; for saving and restoring TCBThisSFT
LocalVar        nep_eahandle,WORD       ; EA handle for the file in question
LocalVar        nep_hVPB,WORD

ifdef FSSTRICT
        call    IsSFT
        LJC     nep91                   ; error locking code into memory
endif
        mov     ax,es:[di].sf_fsd.sfdFAT_EAHandle   ; (ax) = EA handle
        or      ax,ax                   ; are there eas for this file ?
        LJZ     nep91                   ;   no, we are done

        SaveReg <bx,cx,dx,si,di>        ; save registers
        mov     bx,es:[di].sf_fsi.sfi_hVPB ; bx = hVPB

        EnterProc <es,di, ax, bx>       ; init local vars

        mov     ax,EA_READ              ; open file only if present
        call    SetupFatEA              ; make sure the EA file is open
        mov     ax,0                    ; assume no need EAs
;
; BUGBUG 19-Jul-89 peterwu - need to check error type
;
;#61135 LJC     nep85                   ; error, that means no need EAs
        LJC     nep97                   ; error, that means no need EAs  ;#61135
;
; get the ea cluster no from the ea handle and then get the no of need eas
;
        mov     bx,nep_hVPB             ; (bx) = hVPB
        VPBContext ds,bx                ; ds:bx -> VPB
        mov     ax,EA_READ              ; read serialization

        CodeLock FATEA_GROUP,nep85

        call    EASerialize             ; get read serialization

        mov     bx,CurrTCB              ; ss:bx -> TCB
        push    TASKAREA:[bx].TCBThisSFT.Offst ; save TCBThisSFT
        push    TASKAREA:[bx].TCBThisSFT.Segmt

        mov     di,nep_hVPB
        VPBContext es,di                ; es:di -> vpb
;
; Set TCBThisSFT to EA sft
;
        mov     dx,es:[di].vpb_fsd.vpdFAT_pEASFT.Offst  ;
        mov     TASKAREA:[bx].TCBThisSFT.Offst,dx       ;
        mov     dx,es:[di].vpb_fsd.vpdFAT_pEASFT.Segmt  ;
        mov     TASKAREA:[bx].TCBThisSFT.Segmt,dx       ; ThisSFT -> EA file

        xor     cx,cx                   ; (cx) = 0, only get cluster offset
        mov     ax,[nep_eahandle]       ; (ax) = EA handle
        call    EAHandleToOffset        ; (cx) = cluster offset of EAData
        jc      nep95                   ; error, that means need eas present

        mov     di,nep_hVPB
        VPBContext es,di                ; es:di -> vpb
        mov     ax,[nep_eahandle]       ; (ax) = EA handle
        call    GetEADataInfo           ; (bx) = number of NEED EAs
        jc      nep95                   ; error, that means need eas present

        xor     ax,ax
        cmp     ax,bx                   ; set carry if bx != 0
        adc     ax,ax                   ; ax = 1 if carry set

nep80:  SaveReg <ax>
        mov     bx,nep_hVPB             ; (bx) = hVPB
        VPBContext ds,bx                ; ds:bx -> VPB
        mov     ax,EA_READ              ; prepare to exit read serialization
        call    EAUnserialize           ; release read serialization

        CodeUnlock FATEA_GROUP

        RestoreReg <ax>

        mov     bx,CurrTCB              ; ss:bx -> TCB
        pop     TASKAREA:[bx].TCBThisSFT.Segmt    ; restore TCBThisSFT
        pop     TASKAREA:[bx].TCBThisSFT.Offst
        clc

nep85:  LeaveProc
        RestoreReg <di,si,dx,cx,bx>

nep91:  ret

;
; We got an error trying to access the EAs. In this case, it is safest to
; say that we have need EAs so that we don't allow someone to access the
; file unless they have the right bit set in the EXE header.
;
nep95:  mov     ax,1                        ; Indicate that need EAs present
        jmp     short nep80

;
; We need to check if this is at init time before autocheck has run.
; We know we donot need the eas so we can ignore the errors from
; SetUpFatEa.  We will key off of two issues: init time and fat dirty bit.
;
nep97:
        push    ds                                                                 ;#61135
        DosContext ds                                                              ;#61135
        cmp     initfatchkdsk,1         ; If trying to chkdsk a fat boot partition ;#61135
        pop     ds                                                                 ;#61135
        je      nep99                   ; Init time                                ;#61135
        stc                                                                        ;#61135
        jmp     nep85                                                              ;#61135
nep99:  clc                                                                        ;#61135
        jmp     nep85                                                              ;#61135

EndProc NeedEASPresent


;***    BlanksBeforeDot - check for blanks immediately before dot
;
;       This routine checks for blanks immediately preceding the dot.
;       If NewFiles is set, a file with blanks before the dot cannot
;       be created.  This is because both "foo.txt" and  "foo  .txt"
;       can exist on a non-FAT volume.  If a program tries to create
;       both of these files on a FAT volume (i.e. during a copy), they'll
;       both get copied to the same place "foo.txt" because of the
;       FAT FS disk format (blank filled).
;
;       INPUT:  [CurrTCB]
;               [TCBNewFlags]
;               [WFP_Path_End]
;
;       OUTPUT: NC - no blanks immediately preceding dot OR NEWFILES not set
;               CY - blanks immediately preceding dot AND NEWFILES set
;
;       USES: CX, flags
;
Procedure BlanksBeforeDot,NEAR,local
    assume ds:nothing, es:nothing

        push    bx
        mov     bx,CurrTCB
        test    TASKAREA:[bx].TCBNewFlags,TCBNEWFILES ; newfiles?
        pop     bx
        jz      bbd30                       ; no, go return (carry clear)

        SaveReg <es,di>
        TaskContext <es>,di
                                            ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        mov     di,TASKAREA:[di].TCBWFP_Path_End ; es:di -> last component of name
                                            ;72417 end
        mov     al,0                        ; '.' is not a valid DBCS lead or trailing byte
        mov     cx,MAXPNAME
        repne   scasb                       ; look for end of name
        sub     cx,MAXPNAME
        not     cx                          ; cx = length of name
                                            ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        mov     di,TASKAREA:[di].TCBWFP_Path_End   ; es:di -> last component of name
                                            ;72417 end
        mov     al,'.'                      ; '.' is not a valid DBCS lead or trailing byte
        repne   scasb                       ; look for '.' in name
        clc
        jnz     bbd20                       ; not found, go return no error
        cmp     byte ptr es:[di-2],' '      ; blanks immediately preceding dot?
        clc
        jne     bbd20                       ; no, go return
        stc
        mov     ax,ERROR_INVALID_NAME
bbd20:  RestoreReg <di,es>
bbd30:  ret

EndProc BlanksBeforeDot


;***    Get_Source - Re-find the source
;
;       ENTRY   ptda_pSrchBuf points to source info
;               ds = taskarea
;
;       EXIT    [CurBuf] set
;               [CurBuf+2]:BX points to entry
;               Carry set if error (currently user FAILed hard error)
;
;       USES    ax,bx,cx,dx,si,di,es,flags, bp

Procedure GET_SOURCE,near,local
    ASSUME      DS:TASKAREA, ES:NOTHING
        SegAssert SS,<DS>,"GET_SOURCE"

                                            ;72417 moved from PTDA to TCB
        mov     bx,CurrTCB
        mov     bx,TASKAREA:[bx].TCB_pSrchBuf
                                            ;72417 end
        MOV     BX,WORD PTR [bx.find_buf_DirStart]      ; DirStart

        mov     bp,CurrTCB
        mov     bp,TASKAREA:[bp].TCBThishVPB
        VPBContext es,bp

        call    SetDirSrch
        jc      gesx

        call    StartSrch
                                            ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        mov     di,TASKAREA:[di].TCB_pSrchBuf
                                            ;72417 end
        MOV     AX,WORD PTR [di.find_buf_LastEnt]       ; Lastent
        call    GETENT
gesx:   ret
EndProc GET_SOURCE

        PUBLIC  _EndNODEGROUP
_EndNODEGROUP   label   byte

FSSWAPCODE  ENDS
        END

