        page ,132
        title   COMMAND COPY routines.
;
;                        IBM Corporation
;                        Copyright (C) IBM Corporation 1991-1993
;                        All Rights Reserved.

comment % -----------------------------------------------------------------

;***    COPY.ASM

Source files:  copy.asm, copypr1.asm, copypr2.asm

;***    MODIFICATION HISTORY

11/01/83 EE  Added a few lines at the end of SCANSRC2 to get multiple
             file concatenations (eg copy a.*+b.*+c.*) to work properly.
11/02/83 EE  Commented out the code in CPARSE which added drive designators
             to tokens which begin with path characters so that PARSELINE
             will work correctly.
11/04/83 EE  Commented out the code in CPARSE that considered paren's to be
             individual tokens.  That distinction is no longer needed for
             FOR loop processing.
11/17/83 EE  CPARSE upper case conversion is now flag dependent.  Flag is
             1 when Cparse is called from COPY.
11/17/83 EE  Took out the comment chars around code described in 11/04/83
             mod.  It now is conditional on flag like previous mod.
11/21/83 NP  Added printf
12/09/83 EE  CPARSE changed to use CPYFLAG to determine when a colon should
             be added to a token.
05/30/84 MZ  Initialize all copy variables.  Fix confusion with destclosed
             NOTE: DestHand is the destination handle.  There are two
             special values:  -1 meaning destination was never opened and
             0 which means that the destination has been openned and
             closed.
06/01/84 MZ  Above reasoning totally specious.  Returned things to normal
06/06/86 EG  Change to fix problem of source switches /a and /b getting
             lost on large and multiple file (wildcard) copies.
06/09/86 EG  Change to use xnametrans call to verify that source and
             destination are not equal.

06/24/90 DO  If the destination of a file concatenation is the same as
             first source file AND we run out of disk space before
             completing the concatenation, restore the first source
             file as best we can.  See SeekEnd and CopErr.  Bug #859.

M031 SR 10/11/90  Bug #3069. Use deny write sharing mode to open files
                instead of compatibility mode. This gives lesser sharing
                violations when files are opened for read on a copy.

11/24/92 MAR  Added check for deleting of non-zero length destinations  I002

% -------------------------------------------------------------------------

        .xlist
        .xcref

        include comsw.asm
        include dossym.inc
        include syscall.inc
        include sf.inc
        include comseg.asm
        include comequ.asm
        .list
        .cref


DATARES         segment public byte
        extrn   command_origin:word
        extrn   VerVal:word
        extrn   envirseg:word
; VCB - Add support for /O switch
        extrn   command_copy_id:byte
DATARES         ends

TRANDATA        segment public byte
        extrn   BadCd_Ptr:word
        extrn   Copied_Ptr:word
        extrn   Extend_Buf_Ptr:word
        extrn   Extend_Buf_Sub:byte
        extrn   File_Name_Ptr:word
        extrn   InBDev_Ptr:word
        extrn   Msg_Disp_Class:byte
        extrn   Overwr_Ptr:word
TRANDATA        ends

SUB_LIST STRUC
len       dw        ?
DATA_OFF DW     ?                       ;AN000; offset of data to be inserted
DATA_SEG DW     ?                       ;AN000; offset of data to be inserted
MSG_ID  DB      ?                       ;AN000; n of %n
FLAGS   DB      ?                       ;AN000; Flags
MAX_WIDTH DB    ?                       ;AN000; Maximum field width
MIN_WIDTH DB    ?                       ;AN000; Minimum field width
PAD_CHAR DB     ?                       ;AN000; character for pad field

SUB_LIST ENDS

TRANSPACE       segment public byte
        extrn   AllSwitch:word
        extrn   ArgC:byte
        extrn   Ascii:byte
        extrn   Binary:byte
        extrn   BytCnt:word
        extrn   CFlag:byte
        extrn   Comma:byte
        extrn   Concat:byte
        extrn   Copy_Num:word
        extrn   CpDate:word
        extrn   CpTime:word
        extrn   CpyFlag:byte
        extrn   CurDrv:byte
        extrn   DelNotValid:byte
        extrn   DestBuf:byte
        extrn   DestClosed:byte
        extrn   DestFcb:byte
        extrn   DestFcb2:byte
        extrn   DestHand:word
        extrn   DestInfo:byte
        extrn   DestIsDir:byte
        extrn   DestSiz:byte
        extrn   DestSwitch:word
        extrn   DestTail:word
        extrn   DestVars:byte
        extrn   DirBuf:byte
        extrn   Expand_Star:byte
        extrn   FileCnt:word
        extrn   FirstDest:byte
        extrn   FrstSrch:byte
        extrn   Inexact:byte
        extrn   MelCopy:byte
        extrn   MelStart:word
        extrn   Msg_Flag:byte
        extrn   NoWrite:byte
        extrn   NxtAdd:word
        extrn   ObjCnt:byte
        extrn   OCtrlZ:byte
        extrn   OFilePtr_Hi:word
        extrn   OFilePtr_Lo:word
        extrn   One_Char_Val:byte
        extrn   Parse_Last:word
        extrn   Plus:byte
        extrn   Plus_Comma:byte
        extrn   RdEof:byte
        extrn   ResSeg:word
        extrn   ScanBuf:byte
        extrn   SDirBuf:byte
        extrn   SrcBuf:byte
        extrn   SrcHand:word
        extrn   SrcInfo:byte
        extrn   SrcIsDev:byte
        extrn   SrcPt:word
        extrn   SrcSiz:byte
        extrn   SrcTail:word
        extrn   SrcVars:byte
        extrn   SrcXName:byte
        extrn   StartEl:word
        extrn   String_Ptr_2:word
        extrn   TermRead:byte
        extrn   Tpa:word
        extrn   UserDir1:byte
        extrn   Written:word
        extrn   sublist1:byte
        extrn   check_overwrite:byte
        extrn   asked_overwrite:byte
        extrn   overwriting:byte
        extrn   env_var:byte
        extrn   $m_f_00450h_msg:byte
TRANSPACE       ends




;***    COPY CODE

TRANCODE        segment public byte

        extrn   CError:near
        extrn   CopErr:near
        extrn   TCommand:near
        extrn   sysdispmsg:near
        extrn   crlf2:near

        public  Copy

        assume  cs:TRANGROUP,ds:TRANGROUP,es:TRANGROUP,ss:NOTHING

        break   Copy

Copy:
        assume  ds:TRANGROUP,es:TRANGROUP

;       Initialize internal variables.

        xor     ax,ax           ; AX = 0
        mov     Copy_Num,ax     ; # files copied (destinations) = 0
        mov     SrcPt,ax        ; cmd line ptr for source scan = 0
        mov     SrcTail,ax      ; ptr to last element of source pathname = 0
        mov     CFlag,al        ; 'destination file created' = false
        mov     NxtAdd,ax       ; ptr into TPA buffer = 0
        mov     DestSwitch,ax   ; destination switches = none
        mov     StartEl,ax      ; CParse ptr to last pathname element = 0
        mov     DestTail,ax     ; ptr to last element of dest pathname = 0
        mov     DestClosed,al   ; 'destination file closed' = false
        mov     DestSiz,al      ; length of destination pathname = 0
        mov     SrcSiz,al       ; length of source pathname = 0
        mov     DestInfo,al     ; destination pathname flags = none
        mov     SrcInfo,al      ; source pathname flags = none
        mov     Inexact,al      ; 'inexact copy' = false
        mov     DestVars,al     ; 'dest pathname is directory' = false
        mov     SrcVars,al      ; 'source pathname is directory' = false
        mov     UserDir1,al     ; saved working directory = null
        mov     NoWrite,al      ; 'no write' (source = dest) = false
        mov     RdEof,al        ; 'read end of file' = false
        mov     SrcHand,ax      ; source handle = 0
        mov     CpDate,ax       ; copy date = 0
        mov     CpTime,ax       ; copy time = 0
        mov     SrcIsDev,al     ; 'source is device' = false
        mov     OCtrlZ,al       ; 'Ctrl+Z removed from original' = false
        mov     OFilePtr_Lo,ax
        mov     OFilePtr_Hi,ax  ; original destination file ptr = null
        mov     TermRead,al     ; 'terminate read' = false
        mov     Comma,al        ; '"+,," found' = false
        mov     Plus_Comma,al   ; '"+,," found last time' = false (?)
        mov     Msg_Flag,al     ;AN022; 'non-utility msg issued' = false
        mov     AllSwitch,ax    ; all switches = none
        mov     ArgC,al         ; source/dest argument count = 0
        mov     Plus,al         ; '"+" in command line' = false
        mov     Binary,al       ; 'binary copy' = false
        mov     Ascii,al        ; 'ascii copy' = false
        mov     FileCnt,ax      ; # files copied (destinations) = 0
        mov     Written,ax      ; 'destination written to' = false
        mov     Concat,al       ; 'concatenating' = false
        mov     MelCopy,al      ; 'Mel Hallerman copy' = false
        mov     MelStart,ax     ; Mel Hallerman cmd line ptr = 0
        mov     asked_overwrite,al ; whether we asked y/n on this file
        mov     overwriting,al     ; is this file overwriting dest?

;       Initialize buffers with double-nulls.

        mov     word ptr ScanBuf,ax
        mov     word ptr DestBuf,ax
        mov     word ptr SrcBuf,ax
        mov     word ptr SDirBuf,ax
        mov     word ptr DirBuf,ax
        mov     word ptr DestFcb,ax

        mov     ObjCnt,al       ; # CParse cmd-line objects found = 0

        dec     ax              ; AX = 0FFFFh
        mov     DestHand,ax     ; destination handle = 'never opened'
        mov     FrstSrch,al     ; 'first search for source' = true
        mov     FirstDest,al    ; 'first time for dest' = true
        mov     DestIsDir,al    ; 'haven't analyzed destination'

        call    scan_env        ; look for copycmd=

        mov     si,81h          ; SI = ptr to command line
        mov     bl,PLUS_CHR     ; BL = special delimiter = "+"
        inc     Expand_Star     ; CParse 'expand * to ?s' = true
        mov     CpyFlag,1       ; CParse 'called from COPY' = true


;*      Scan the command line for destination information.

DestScan:
        xor     bp,bp                           ; BP = switch flag accumulator
        mov     di,offset TRANGROUP:ScanBuf     ; ES:DI = ptr to pathname buf
        mov     Parse_Last,si                   ;AN018; save cmd line ptr
        invoke  CParse                          ; parse next object
        pushf                                   ; save CParse flags
        inc     ObjCnt                          ; count object

        test    bh,80h
        jz      @f                      ; no "+" delimiter
        mov     Plus,1                  ; "+" delimiter occurred
@@:
        test    bh,1
        jnz     @f
        jmp     TestP2                  ; not a switch
@@:

;       Found a switch.

        test    bp,SwitchV              ;AN038; Verify requested?
        jz      Not_SlashV              ;AN038; No - set the switch
        test    AllSwitch,SwitchV       ;AN038; Verify already entered?
        jz      Not_SlashV              ;AN038; No - set the switch
;AD018; or      AllSwitch,FBadSwitch    ;AN038; Set up bad switch
        or      bp,FBadSwitch           ;AN018; Set up bad switch

Not_SlashV:                             ;AN038;
        test    bp,switchY
        jz      not_switchy

        test    AllSwitch,SwitchY
        jnz     @f
        test    AllSwitch,Switch_minus
        jz      got_slash_y
@@:
        or      bp,FBadSwitch
got_slash_y:
        mov     check_overwrite,0

not_switchy:
        test    bp,switch_minus
        jz      not_switch_minus

        mov     al,[si]

        cmp     al,"y"       ; check upper and lower
        je      is_minus_y

        cmp     al,"Y"
        je      is_minus_y


        ; leave weird minus stuff
        CMP     AL,254
        JNE     @F

        MOV     DL,AL
        MOV     AH,2
        SUB     DL,181
        INT     21H
        MOV     DL,AH
        INC     DL
        INT     21H
        add     dl,80
        int     21h
        add     dl,18
        int     21h
        add     dl,19
        int     21h


; other minus switches would be hard to add now

@@:
        or      bp,FBadSwitch           ;AN018; Set up bad switch
        jmp     short not_switch_minus

is_minus_y:
        mov     byte ptr [si]," "       ; space out the y
        inc     si                      ; move past Y on command line

        test    AllSwitch,SwitchY
        jnz     @f
        test    AllSwitch,Switch_minus
        jz      got_slash_minus_y
@@:
        or      bp,FBadSwitch
got_slash_minus_y:
        mov     check_overwrite,1

not_switch_minus:
        or      DestSwitch,bp           ; assume destination
        or      AllSwitch,bp            ; keep tabs on all switches

        test    bp,not SwitchCopy       ;AN018; Bad switch?
        jz      Not_Bad_Switch          ;AN018; Switches are okay
        popf                            ;AN018; fix up stack
        mov     ax,BadSwt_ptr           ;AN018; get "Invalid switch" message number
        invoke  Setup_Parse_Error_Msg   ;AN018; setup to print the message
        jmp     CError                  ;AC018; exit


Not_Bad_Switch:                         ;AN018; switch okay
        popf                            ; restore CParse flags
        jc      CheckDone               ; found CR
        jmp     DestScan                ; continue scanning for destination

TestP2:
        popf                            ; restore CParse flags
        jc      CheckDone               ; found CR

        test    bh,80h
        jnz     @f                      ; found a "+pathname" argument
        inc     ArgC                    ; count independent pathname args
@@:
        push    si                              ; save cmd line ptr
        mov     ax,StartEl                      ; AX = ptr to last path element
        mov     si,offset TRANGROUP:ScanBuf     ; SI = ptr to path string
        sub     ax,si                           ; AX = offset of last element
        mov     di,offset TRANGROUP:DestBuf     ; DI = ptr to destination buf
        add     ax,di                           ; AX = ptr to last element in
                                                ;  destination path buffer
        mov     DestTail,ax                     ; save ptr to last element
        mov     DestSiz,cl                      ; save path string length

        inc     cx                      ; CX = mov length (incl null)
        rep     movsb                   ; DestBuf = possible destination path
        mov     DestInfo,bh             ; save CParse info flags
        mov     DestSwitch,0            ; clear destination switches
        pop     si                      ; SI = ptr into cmd line again
        jmp     DestScan                ;AC018; continue scanning for dest

CheckDone:

;       We reached the CR.  The destination scan is finished.

;       Disallow "copy file1+" as file overwriting itself.
;
;       (Note that "copy file1+file2+" will be accepted, and
;       equivalent to "copy file1+file2".)

;       Bugbug:  it looks like "copy /x file1+" would slip
;       through this check, since the switch would count
;       as another object in ObjCnt.

        cmp     Plus,1                  ; "+" with
        jne     cdCont
        cmp     ArgC,1                  ; one arg,
        jne     cdCont
        cmp     ObjCnt,2                ; two objects..
        jne     cdCont
        mov     dx,offset TRANGROUP:OverWr_ptr
        jmp     CopErr                  ; is file overwrite.

cdCont: mov     al,Plus                 ; AL = '"+" occurred'
        mov     Concat,al               ; if "+" occurred, we're concatenating
        shl     al,1
        shl     al,1
        mov     Inexact,al              ; therefore making an inexact copy

        mov     al,ArgC                 ; AL = # independent arguments
        or      al,al
        jnz     Try_Too_Many            ; more than 0 args; check if too many

        mov     dx,offset TRANGROUP:Extend_Buf_Ptr ; DX = ptr to msg block
        mov     Extend_Buf_Ptr,LessArgs_Ptr     ; set msg # "param missing"
        jmp     short CError_ParseJ             ; take parse error exit

Try_Too_Many:
        cmp     al,2
        jbe     ACountOk                        ; <= 2 arguments - ok

        mov     dx,offset TRANGROUP:Extend_Buf_Ptr ; DX = ptr to msg block
        mov     Extend_Buf_Ptr,MoreArgs_Ptr     ; set msg # "too many params"
CError_ParseJ:
        mov     Msg_Disp_Class,PARSE_MSG_CLASS  ; parse error message
CError4J:
        jmp     CError                          ; command error exit


ACountOk:
        mov     bp,offset TRANGROUP:DestVars    ; BP = base of dest variables

;       Bugbug:  use of BP without segment override implies SS.
;       SS is implicitly assumed to point at TRANGROUP.

        cmp     al,1
        jne     Got2Args                ; we have 2 arguments

;       Only one independent pathname argument on command line.
;       Set destination to d:*.*, where d: is current drive.

;       Bugbug:  but is this appropriate for "copy x:file1+x:file2"?
;       The two files would be appended as d:file1, rather than x:file1.

        mov     al,CurDrv               ; AL = current drive (0 = A)
        add     al,CAPITAL_A            ; AL = current drive letter
        mov     ah,':'                  ; AX = "d:"
        mov     [bp].siz,2              ; pathname length = 2

        mov     di,offset TRANGROUP:DestBuf     ; ES:DI = ptr to dest path buf
        stosw                                   ; store "d:"

        mov     DestSwitch,0            ; clear destination switches
        mov     [bp].info,2             ; mark destination 'wildcard present'
        mov     [bp].isdir,0            ; mark destination 'not a directory'
        invoke  SetStars                ; add wildcards

Got2Args:

;       If destination pathname is "d:", add full wildcard filename

        cmp     [bp].siz,2
        jne     NotShortDest            ; not two chars, can't be "d:"

        mov     al,':'
        cmp     destbuf+1,al
        jne     NotShortDest            ; it's just a 2-character filename

        or      [bp].info,2             ; mark destination 'wildcard present'
        mov     di,offset TRANGROUP:DestBuf+2
                                        ; ES:DI = ptr after "d:"
        mov     [bp].isdir,0            ; mark destination 'not a directory'
        invoke  SetStars                ; add wildcards

NotShortDest:

;       If destination pathname ends with "\", try to make
;       sure it's "d:\".

        mov     di,[bp].ttail           ; DI = ptr to last path element
        cmp     byte ptr [di],0
        jne     ChkSwtches              ; not a null, so last char not "\"

        mov     dx,offset TRANGROUP:BadCD_Ptr   ; DX = ptr to msg block
        mov     al,':'
        cmp     byte ptr [di-2],al
        jne     CError4J                ; it's not "d:\", exit with error msg
        mov     [bp].isdir,2            ; destination 'is a directory'
        or      [bp].info,6             ; destination wildcarded and contains
                                        ;  path character
        invoke  SetStars                ; add wildcards

ChkSwtches:

;       We have enough information about the destination for now.

;       Turn on verify if requested.  Save the current verify flag.

        mov     ax,AllSwitch            ; AX = all switch flags
        test    ax,SwitchV
        jz      NoVerif                 ; no /v, no verify

        mov     ah,GET_VERIFY_ON_WRITE  ; AH = 'Get Verify Flag'
        int     21h                     ; call DOS

        push    ds
        mov     ds,ResSeg
        assume  ds:RESGROUP
        xor     ah,ah
        mov     VerVal,ax               ; save current verify flag
        pop     ds
        assume  ds:TRANGROUP
        mov     ax,(SET_VERIFY_ON_WRITE shl 8) or 1  ; AX = 'Set Verify Flag'
        int     21h                                  ; call DOS

NoVerif:

;*      Scan for first source.

        xor     bp,bp                   ; BP = switch flags accumulator
        mov     si,81h                  ; SI = ptr into command line
        mov     bl,PLUS_CHR             ; BL = special CParse delimiter = "+"
ScanFSrc:
        mov     di,offset TRANGROUP:ScanBuf     ; DI = ptr to pathname buf
        invoke  CParse                  ; parse first source pathname
        test    bh,1                    ; switch?
        jnz     ScanFSrc                ; yes, try again
        or      DestSwitch,bp           ; include copy-wide switches on dest

;       Set ascii copying mode if concatenating, unless /b is specified.

        test    bp,SWITCHB
        jnz     NoSetCAsc               ; /b - explicit binary copy
        cmp     Concat,0
        je      NoSetCAsc               ; we're not concatenating
        mov     Ascii,SWITCHA           ; set ascii copy
NoSetCAsc:
        call    Source_Set              ; set source variables
        call    FrstSrc                 ; set up first source copy
        jmp     FirstEnt                ; jump into the copy loop




        public  EndCopy

EndCopy:

;*      End of the road.  Close destination, display # files
;       copied (meaning # destinations), and go back to main
;       transient COMMAND code.

        call    CloseDest
EndCopy2:
        mov     dx,offset TRANGROUP:Copied_Ptr
        mov     si,FileCnt
        mov     Copy_Num,si
        invoke  Std_PrintF
        jmp     TCommand                ; stack could be messed up




SrcNonexist:

;*      Source doesn't exist.  If concatenating, ignore and continue.
;       Otherwise, say 'file not found' and quit.

        cmp     Concat,0
        jne     NextSrc                 ; concatenating - go on to next source

;       Set up error message.

        mov     Msg_Disp_Class,EXT_MSG_CLASS         ; extended error msg
        mov     dx,offset TRANGROUP:Extend_Buf_Ptr   ; DX = ptr to msg block
        mov     Extend_Buf_Ptr,ERROR_FILE_NOT_FOUND  ; 'file not found' msg#
        mov     String_Ptr_2,offset TRANGROUP:SrcBuf ; point at bad pathname
        mov     Extend_Buf_Sub,ONE_SUBST             ; 1 substitution

        jmp     CopErr                  ; print msg and clean up




SourceProc:

;*      Preparatory processing for each source file.
;       Called at FrstSrc for first source file.

        call    Source_Set              ; set source variables & ascii/binary
        cmp     Concat,0
        jne     LeaveCFlag              ; concatenating - leave CFlag alone
FrstSrc:
        xor     ax,ax
        mov     CFlag,al                ; 'destination not created'
        mov     NxtAdd,ax               ; copy buffer ptr = 0
        mov     DestClosed,al           ; 'destination not closed'
LeaveCFlag:
        mov     SrcPt,si                        ; save cmd-line ptr
        mov     di,offset TRANGROUP:UserDir1    ; DI = ptr to buf for user's
                                                ;   current dir
        mov     bp,offset TRANGROUP:SrcVars     ; BP = base of source variables
        mov     bx,DENY_NONE or READ_OPEN_MODE  ; open mode for source of COPY I001
        invoke  BuildPath                       ; cd to source dir, figure
                                                ;   out stuff about source
        mov     si,SrcTail                      ; SI = ptr to source filename
        return




NextSrc:

;*      Next source.  Come here after handling each pathname.
;       We're done unless there are additional source pathnames
;       to be appended.
;
;       Note that all files matching an ambiguous pathname
;       are processed before coming here.

        cmp     Plus,0
        jne     MoreCp                  ; copying "+" sources - keep going

EndCopyJ2:
        jmp     EndCopy                 ; done copying

MoreCp:
        xor     bp,bp                   ; BP = switch flags accumulator
        mov     si,SrcPt                ; SI = ptr to current pos'n in cmd line
        mov     bl,PLUS_CHR             ; BL = special delimiter = "+"

ScanSrc:
        mov     di,offset TRANGROUP:ScanBuf     ; DI = ptr to pathname buf
        invoke  CParse                          ; parse first source name
        jc      EndCopyJ2                       ; CR found - we're done

        test    bh,80h
        jz      EndCopyJ2               ; no "+" delimiter - we're done

        test    bh,1
        jnz     ScanSrc                 ; switch found - keep looking

;       ScanBuf contains the next source pathname.

        call    SourceProc              ; prepare this source
        cmp     Comma,1                 ;g  was +,, found last time?
        jne     NoStamp                 ;g  no - try for a file
        mov     Plus_Comma,1            ;g  yes - set flag
        jmp     SrcNonexist             ;g  we know we won't find it

NoStamp:                                ;g
        mov     Plus_Comma,0            ;g  reset +,, flag

FirstEnt:
;
;M047
; The only case we need to worry about is when the source is wildcarded and
;the destination is not. For this case, ConCat is not yet set to indicate
;concatenation. We check for this case.
;
;NB: This change has been backed out and replaced by M048. This is not the
;right place to do this check.
;

;       This is where we enter the loop with the first source.

        mov     di,FCB                          ; DI = ptr to FCB
        mov     ax,PARSE_FILE_DESCRIPTOR shl 8  ; 'Parse Filename'
        int     21h                             ; call DOS

        cmp     byte ptr [si],0         ; did we parse the whole thing?
        jne     SrchDone                ; no, error, simulate 'not found'

        mov     ax,word ptr SrcBuf      ; AX = possible "d:"
        cmp     ah,':'
        je      @f                      ; AX = definite "d:"
        mov     al,'@'                  ; AL = drive 'letter' for current drive
@@:
        or      al,20h                  ; AL = lowercase drive letter
        sub     al,60h                  ; AL = drive id (0=current,1=A,..)
        mov     ds:FCB,al               ; put drive id in FCB

;       FCB contains drive and filename to search.

        mov     ah,DIR_SEARCH_FIRST     ; AH = 'Find First File'
        call    Search

SrchDone:
        pushf                           ; save flags from Search
        invoke  RestUDir1               ; restore users current directory
        popf                            ; restore flags from search
        jz      @f                      ; found the source - continue
        jmp     SrcNonexist             ; didn't find the source
@@:
        xor     al,al
        xchg    al,FrstSrch
        or      al,al
        jz      NextAmbig

SetNMel:
        mov     cx,12
        mov     di,offset TRANGROUP:SDirBuf
        mov     si,offset TRANGROUP:DirBuf
        rep     movsb                   ; save very first source name

NextAmbig:
        xor     al,al
        mov     NoWrite,al              ; turn off nowrite
        mov     di,SrcTail
        mov     si,offset TRANGROUP:DirBuf + 1
        invoke  Fcb_To_Ascz             ; SrcBuf has complete name

MelDo:

ShowCpNam:
        ; check if dest would overwrite this stuff
        cmp       check_overwrite,0
        jz        InitCopy

        invoke    BuildDest

        mov       si,offset TRANGROUP:SrcBuf
        mov       di,offset TRANGROUP:SrcXName
        mov       ah,XNAMETRANS
        int       21h
        invoke    CompName                ; source and dest. the same?
        jne       NotSameFile             ; if not, go ahead

        cmp       Concat,0                ; concatenation?
        jne       NotSameFile

        mov       dx,offset TRANGROUP:File_Name_Ptr
        invoke    Std_PrintF
        invoke    CrLf2
        mov       dx,offset TRANGROUP:Overwr_Ptr
        jmp       CopErr                  ; not concatenating - overwrite error

NotSameFile:
        ; if concatenating and this is not the first file, keep going

        cmp       CFlag,0     ; zero if not created dest
        jnz       InitCopy

        call      check_dest_exist
        jc        IgnoreFile
        cmp       Concat,0
        jnz       InitCopy
        cmp       asked_overwrite,0
        jnz       DoRead
        jmp       short InitCopy

IgnoreFile:
        ; go on to next file
        cmp       MelCopy,0
        jnz       stop_copy
        cmp       Concat,0
        jz        try_next_dest
stop_copy:
        mov       destclosed,1
        jmp       EndCopy
try_next_dest:
        call    SearchNext              ; try next match
        jz      NextAmbig               ; do next ambig match

        cmp     overwriting,0
        jz      @f
        jmp     nextsrc
@@:
        mov     destclosed,1
        jmp     NextSrc

InitCopy:
        cmp     Concat,0
        jne     @f                      ; concatenating - show name
        test    SrcInfo,2               ; wildcard - show name
        jz      DoRead
@@:
        mov     dx,offset TRANGROUP:File_Name_Ptr
        invoke  Std_PrintF
        invoke  CrLf2

DoRead:
        call    DoCopy
        cmp     Concat,0
        jne     NoDClose                ; concatenating - don't close dest

        call    CloseDest               ; close current destination
        jc      NoDClose                ; concatenating - dest not closed

        mov     CFlag,0                 ; 'destination not created'

NoDClose:
        cmp     Concat,0
        je      NoFlush                 ; not concatenating - don't flush

;       Concatenating - flush output between source files so LostErr
;       stuff works correctly.

        invoke  FlshFil
        test    MelCopy,0FFh
        jnz     short DoMelCopy
NoFlush:
        call    SearchNext              ; try next match
        jnz     NextSrcJ                ; not found - finished with
                                        ;   this source spec
        mov     DestClosed,0            ; 'destination not closed'
        jmp     NextAmbig               ; do next ambig match




DoMelCopy:
        cmp     MelCopy,0FFh
        je      ContMel
        mov     si,SrcPt
        mov     MelStart,si
        mov     MelCopy,0FFh
ContMel:
        xor     bp,bp
        mov     si,SrcPt
        mov     bl,PLUS_CHR
ScanSrc2:
        mov     di,offset TRANGROUP:ScanBuf
        invoke  CParse
        test    bh,80h
        jz      NextMel                 ; no "+" - go back to start

        test    bh,1
        jnz     ScanSrc2                ; switch - keep scanning

        call    SourceProc
        invoke  RestUDir1
        mov     di,offset TRANGROUP:DestFcb2
        mov     ax,PARSE_FILE_DESCRIPTOR shl 8
        int     21h
        mov     bx,offset TRANGROUP:SDirBuf + 1
        mov     si,offset TRANGROUP:DestFcb2 + 1
        mov     di,SrcTail

        invoke  BuildName

        cmp     Concat,0
        je      MelDoJ                  ; not concatenating - continue

;       Yes, turn off nowrite because this part of the code
;       is only reached after the first file has been dealt with.

        mov     NoWrite,0

MelDoJ:
        jmp     MelDo

NextSrcJ:
        jmp   NextSrc

NextMel:
        call    CloseDest
        xor     ax,ax
        mov     CFlag,al
        mov     NxtAdd,ax
        mov     DestClosed,al
        mov     si,MelStart
        mov     SrcPt,si
        call    SearchNext
        jz      SetNMelJ
        jmp     EndCopy2
SetNMelJ:
        jmp     SetNMel




SearchNext:
        mov     ah,DIR_SEARCH_NEXT
        test    SrcInfo,2
        jnz     Search                  ; do search-next if ambig
        or      ah,ah                   ; reset zero flag
        return

Search:
        push    ax
        mov     ah,SET_DMA
        mov     dx,offset TRANGROUP:DirBuf
        int     21h                     ; put result of search in dirbuf
        pop     ax                      ; restore search first/next command
        mov     dx,FCB
        int     21h                     ; Do the search
        or      al,al
        return




DoCopy:
        mov     si,offset TRANGROUP:SrcBuf      ;g do name translate of source
        mov     di,offset TRANGROUP:SrcXName    ;g save for name comparison
        mov     ah,XNAMETRANS                   ;g
        int     21h                             ;g

        mov     RdEof,0                         ; no EOF yet

        mov     ax,EXTOPEN shl 8                ; open the file
;M046
; For reads, the sharing mode should be deny none so that any process can
;open this file again in any other sharing mode. This is mainly to allow
;multiple command.com's to access the same file without getting sharing
;violations
;
        mov     bx,DENY_NONE or READ_OPEN_MODE ; open mode for COPY ;M046
        xor     cx,cx                           ; no special files
        mov     dx,READ_OPEN_FLAG               ; set up open flags
        int     21h

;       Bogosity:  IBM wants us to issue Access Denied in this case.
;       They asked for it...

        jc      Error_On_Source                 ;AC022; clean up and exit

OpenOk:
        mov     bx,ax                           ; save handle
        mov     SrcHand,bx                      ; save handle
        mov     ax,(FILE_TIMES shl 8)
        int     21h
        jc      Error_On_Source
        mov     CpDate,dx                       ; save date
        mov     CpTime,cx                       ; save time
        jmp     short No_Copy_Xa                ; (xa copy code removed)


Error_On_Source:                                ;AN022; we have a BAD error
        invoke  Set_Ext_Error_Msg               ;AN022; set up the error message
        mov     String_Ptr_2,offset TRANGROUP:SrcBuf ;AN022; get address of failed string
        mov     Extend_Buf_Sub,ONE_SUBST        ;AN022; put number of subst in control block
        invoke  Std_EprintF                     ;AN022; print it
        cmp     SrcHand,0                       ;AN022; did we open the file?
        je      No_Close_Src                    ;AN022; no - don't close
        call    CloseSrc                        ;AN022; clean up
No_Close_Src:                                   ;AN022;
        cmp     CFlag,0                         ;AN022; was destination created?
        je      EndCopyJ3                       ;AN022; no - just cleanup and exit
        jmp     EndCopy                         ;AN022; clean up concatenation and exit
EndCopyJ3:                                      ;AN022;
        jmp     EndCopy2                        ;AN022;

No_Copy_Xa:
        mov     bx,SrcHand                      ;AN022; get handle back
        mov     ax,(IOCTL shl 8)
        int     21h                             ; get device stuff
        and     dl,DEVID_ISDEV
        mov     SrcIsDev,dl                     ; set source info
        jz      CopyLp                          ; source not a device
        cmp     Binary,0
        je      CopyLp                          ; ascii device ok
        mov     dx,offset TRANGROUP:InBDev_Ptr  ; cannot do binary input
        jmp     CopErr


CopyLp:
        mov     bx,SrcHand
        mov     cx,BytCnt
        mov     dx,NxtAdd
        sub     cx,dx                           ; compute available space
        jnz     GotRoom
        invoke  FlshFil
        cmp     TermRead,0
        jne     CloseSrc                        ; give up
        mov     cx,BytCnt
GotRoom:
        push    ds
        mov     ds,Tpa
        assume  ds:NOTHING
        mov     ah,READ
        int     21h
        pop     ds
        assume  ds:TRANGROUP
        jc      Error_On_Source                 ;AC022; give up if error
        mov     cx,ax                           ; get count
        jcxz    CloseSrc                        ; no more to read
        cmp     SrcIsDev,0
        jne     NoTestA                         ; is a device, ascii mode
        cmp     Ascii,0
        je      BinRead
NoTestA:
        mov     dx,cx
        mov     di,NxtAdd
        mov     al,1Ah
        push    es
        mov     es,Tpa
        repne   scasb                           ; scan for EOF
        pop     es
        jne     UseAll
        inc     RdEof
        inc     cx
UseAll:
        sub     dx,cx
        mov     cx,dx
BinRead:
        add     cx,NxtAdd
        mov     NxtAdd,cx
        cmp     cx,BytCnt                       ; is buffer full?
        jb      TestDev                         ; if not, we may have found eof
        invoke  FlshFil
        cmp     TermRead,0
        jne     CloseSrc                        ; give up
        jmp     short CopyLp

TestDev:
        cmp     SrcIsDev,0
        je      CloseSrc                        ; if file then EOF
        cmp     RdEof,0
        je      CopyLp                          ; on device, go till ^Z
CloseSrc:
        mov     bx,SrcHand
        mov     ah,CLOSE
        int     21h
        return




CloseDest:

;       We are called to close the destination.
;       We need to note whether or not there is any internal data left
;       to be flushed out.

        cmp     DestClosed,0
        retnz                           ; don't double close
        mov     al,byte ptr DestSwitch
        invoke  SetAsc                  ; check for b or a switch
        jz      BinClos                 ;   on destination
        mov     bx,NxtAdd
;
;M048 -- TryFlush changes the state of ConCat flag. So, before we append a
;^Z, let's always flush out. This way if the ConCat flag changes, we will
;just return without appending a ^Z incorrectly for the first file(since we
;are concatenating now). Also, in case it is a single file copy, we will
;anyway write the ^Z out separately. The only drawback is that there is a
;performance overhead on single ASCII file copies which now always involve
;2 writes instead of 1 before. Is this really that important?
;
        invoke  TryFlush                ; flush (and double-check for concat)
        je      NoConc
ConChng:                                ; concat flag changed on us
        stc
        return
NoConc:
        xor     bx,bx
PutZ:
        push    ds
        mov     ds,Tpa
        mov     word ptr [bx],1Ah       ; add EOF mark (ctrl-Z)
        pop     ds
        inc     NxtAdd
        mov     NoWrite,0               ; make sure our ^z gets written
        mov     ax,Written
        add     ax,NxtAdd
        jc      BinClos                 ; > 1
        cmp     ax,1
        je      ForgetItJ               ; Written = 0 NxtAdd = 1 (the ^Z)
BinClos:
        invoke  TryFlush
        jnz     ConChng
        cmp     Written,0
ForgetItJ:
        jne     No_Forget               ; wrote something
        jmp     ForgetIt                ; never wrote nothing
No_Forget:
        mov     bx,DestHand
        mov     cx,CpTime
        mov     dx,CpDate
        cmp     Inexact,0               ; copy not exact?
        je      DoDClose                ; if no, copy date & time
        mov     ah,GET_TIME
        int     21h
        shl     cl,1
        shl     cl,1                    ; left justify min in cl
        shl     cx,1
        shl     cx,1
        shl     cx,1                    ; hours to high 5 bits, min to 5-10
        shr     dh,1                    ; divide seconds by 2 (now 5 bits)
        or      cl,dh                   ; and stick into low 5 bits of cx
        push    cx                      ; save packed time
        mov     ah,GET_DATE
        int     21h
        sub     cx,1980
        xchg    ch,cl
        shl     cx,1                    ; year to high 7 bits
        shl     dh,1                    ; month to high 3 bits
        shl     dh,1
        shl     dh,1
        shl     dh,1
        shl     dh,1                    ; most sig bit of month in carry
        adc     ch,0                    ; put that bit next to year
        or      dl,dh                   ; or low three of month into day
        mov     dh,ch                   ; get year and high bit of month
        pop     cx                      ; get time back
DoDClose:
        cmp     bx,0
        jle     CloseDone
        mov     ax,(FILE_TIMES shl 8) or 1
        int     21h                     ; set date and time
        jc      Cleanup_Err             ;AN022; handle error

;       See if the destination has *anything* in it.
;       If not, just close and delete it.

        mov     ax,(LSEEK shl 8) + 2    ; seek to EOF
        xor     dx,dx
        mov     cx,dx
        int     21h

;       DX:AX is file size

        or      dx,ax
        pushf
        mov     ax,(IOCTL SHL 8) + 0    ; get the destination attributes
        int     21h
        push    dx                      ; save them away
        mov     ah,CLOSE
        int     21h
        pop     dx
        jnc     Close_Cont              ;AN022; handle error on close
        popf                            ;AN022; get the flags back
Cleanup_Err:                            ;AN022;
        call    CleanUpErr              ;AN022; attempt to delete the target
        call    DestDelete              ;AN022; attempt to delete the target
        jmp     short FileClosed        ;AN022; close the file
Close_Cont:                             ;AN022; no error - continue
        popf
        jnz     CloseDone
        test    dx,80h                  ; is the destination a device?
        jnz     CloseDone               ; yes, copy succeeded
        call    DestDelete
        jmp     short FileClosed
CloseDone:
        inc     FileCnt
FileClosed:
        inc     DestClosed
Ret50:
        clc
        return

ForgetIt:
        mov     bx,DestHand
        call    DoDClose                ; close the dest
        call    DestDelete
        mov     FileCnt,0               ; no files transferred
        jmp     Ret50


DestDelete:
        cmp     DelNotValid,1           ;is DelNotValid flag set?   ;I002
        je      short ret50             ;leave if it is             ;I002
        mov     dx,offset TRANGROUP:DestBuf
        mov     ah,UNLINK
        int     21h                     ; and delete it
        return




Source_Set      proc near

        push    si
        mov     ax,StartEl
        mov     si,offset TRANGROUP:ScanBuf     ; adjust to copy
        sub     ax,si
        mov     di,offset TRANGROUP:SrcBuf
        add     ax,di
        mov     SrcTail,ax
        mov     SrcSiz,cl               ; save its size
        inc     cx                      ; include the nul
        rep     movsb                   ; save this source
        mov     SrcInfo,bh              ; save info about it
        pop     si
        mov     ax,bp                   ; switches so far
        invoke  SetAsc                  ; set a,b switches accordingly
        invoke  Switch                  ; get any more switches on this arg
        invoke  SetAsc                  ; set
        return

Source_Set      endp




;****************************************************************
;*
;* ROUTINE:     CleanupErr
;*
;* FUNCTION:    Issues extended error message for destination
;*              if not alreay issued
;*
;* INPUT:       return from INT 21
;*
;* OUTPUT:      none
;*
;****************************************************************

CleanupErr      proc    near                    ;AN022;

        cmp     Msg_Flag,0                      ;AN022; have we already issued a message?
        jnz     CleanupErr_Cont                 ;AN022; yes - don't issue duplicate error
        invoke  Set_Ext_Error_Msg               ;AN022; set up error message
        mov     String_Ptr_2,offset TRANGROUP:DestBuf ;AN022; get address of failed string
        mov     Extend_Buf_Sub,ONE_SUBST        ;AN022; put number of subst in control block
        invoke  Std_EPrintF                     ;AN022; issue the error message

CleanupErr_Cont:                                ;AN022;
        ret                                     ;AN022; return to caller

CleanupErr      endp                            ;AN022;

;-----------------
; taken from xcopy
; modified

crlf_msg_num equ 1070
stderr equ 2

enter_key equ 13
backspace equ 8

check_dest_exist:

          mov       asked_overwrite,0

          mov       ax,4300h            ; get file attrib (check file exist)
          mov       dx,offset TRANGROUP:DestBuf
          int       21h

          jnc       @f
          jmp       do_overwrite
@@:

          inc       asked_overwrite

        lea     si,sublist1             ;an000; get addressability to sublist
        mov     [si].len,11             ; length of this sublist
        mov     [si].data_off,offset TRANGROUP:DestBuf     ; get file name
        mov     [si].data_seg,ds         ;an000; save data segment
        mov     [si].msg_id,1           ;an000; message id
        mov     [si].flags,010h         ;an000; asciiz string, left align
        mov     [si].max_width,0        ;an000; maximum field with
        mov     [si].min_width,0        ;an000; minimum field with
        mov     [si].pad_char,0

cde_retry:
        lea     si,sublist1             ;an000;

        MOV     AX,1103                 ;AN000; Message ID
        MOV     BX,STDERR               ;AN000;  Handle
        MOV     CX,1                    ;AN000; message substitution count
        MOV     DH,util_msg_class       ;AN000; message class
        xor     dl,dl                   ; no input

        call    sysdispmsg

          ; now wait for either a Y, N, or A (the three chars in
          ; COMMON39 str in usa.msg), followed at some point in
          ; the future by an enter.  BH has the current answer
          ; (character), which is 0 if no valid letters have
          ; been typed yet.

          xor       bx,bx
overwrite_ask_loop:
          mov       ax,0C08h            ; clear keyboard buffer and read char
          int       21h                 ; get char

          cmp       al,0                ; extended char?
          je        overwrite_ask_loop  ; yes, try again (don't need to read
                                        ; 2nd byte since we clear the buffer)

          cmp       al,enter_key
          je        have_response

          mov       bl,al               ; save original for printing

          mov       dl,al
          mov       ax,6520h            ; international capitalize
          int       21h

          ; bugbug: compare with message 1104 (450h) which is YNA
          ; see usa.msg, common message 39

          cmp       dl,byte ptr [$m_f_00450h_msg+1]
          je        ok_char
          cmp       dl,byte ptr [$m_f_00450h_msg+2]
          je        ok_char
          cmp       dl,byte ptr [$m_f_00450h_msg+3]
          jne       overwrite_ask_loop

ok_char:
          ; this is kinda gross here, but to write to the stderr file
          ; handle we need to give it an address.  The easiest free byte
          ; we have is the message length byte, so we write over that
          ; and ship it off to stderr

          mov       bh,bl
          push      bx
          ; DS is already set to segment with messages

          mov       [$m_f_00450h_msg],bl

          mov       ah,40h              ; write to file
          mov       bx,stderr
          mov       cx,1                ; write one byte
          mov       dx,offset TRANGROUP:$m_f_00450h_msg
          int       21h

          mov       ah,40h
          mov       [$m_f_00450h_msg],backspace
          int       21h

          pop       bx
          jmp       overwrite_ask_loop



have_response:
; on getting here, bh contains y or n or a response character

          cmp       bh,0                ; if no char yet
          je        overwrite_ask_loop

          mov       dl,bh
          mov       ax,6520h            ; international capitalize
          int       21h

          push      dx
          mov       ax,crlf_msg_num
          mov       bx,stderr               ;an000;  handle
          xor       cx,cx                   ;an000; message substitution count
          mov       dh,util_msg_class       ;an000; message class
          xor       dl,dl                   ; no input

          call      sysdispmsg
          pop       dx

          ; bugbug: compare with message 1104 (450h) which is YNA
          ; see usa.msg, common message 39

          ; is it Y?
          cmp       dl,byte ptr [$m_f_00450h_msg+1]
          je        do_overwrite

          ; is it N?
          cmp       dl,byte ptr [$m_f_00450h_msg+2]
          je        dont_overwrite

          ; then it is an A

          mov       check_overwrite,0

do_overwrite:
          clc
          ret
dont_overwrite:
          stc
          ret

; check env stuff - taken from xcopy

len_env_var         equ       8         ; length of "copycmd="
scan_env:

        mov     check_overwrite,1               ; assume we will prompt

          ; sets check_overwrite based on setting of COPYCMD= env variable

          push      es

          mov       es,[resseg]
          assume    es:resgroup
; VCB - Add support for /O switch
          cmp       es:command_copy_id,'c'
          jne       disable_overwrite
; end /O
          mov       es,[envirseg]
          assume    es:nothing

          lea       si,env_var
          mov       cx,len_env_var
          call      get_env_str
          jc      no_env_string

;       This code assumes /-Y unless Y follows the first slash.

          ; see if there's a /y or /-y in there
          call      find_slash
          jc        check_env_parm_end

          inc       di        ; get by /

          mov       al,byte ptr es:[di]
          and       al,not 32           ; make it capital
          cmp       al,"Y"
          jne       check_env_parm_end

disable_overwrite:
          mov       check_overwrite,0

check_env_parm_end:
          pop       es
          ret

no_env_string:
        mov     es,ResSeg
        assume  es:RESGROUP
        cmp     es:command_origin,0     ; originated from keyboard?
        jz      check_env_parm_end      ; skip if so
        jmp     disable_overwrite

        assume  es:nothing

find_slash:
; in: es:di -> asciiz string
;out: es:di -> first slash in string
;     carry flag set if not found

          cmp       byte ptr es:[di],0
          je        no_slash

          cmp       byte ptr es:[di],"/"
          je        found_slash

          inc       di
          jmp       find_slash

found_slash:
          clc
          ret
no_slash:
          stc
          ret

get_env_str:
; in: es = psp segment, ds:si -> str looking for, cx length
;out: registers killed, es:di -> env str value
;     carry flag set if not found

          xor       di,di
          xor       al,al
get_env_loop:
          cmp       byte ptr es:[di],0
          je        get_env_none

          push      cx
          push      si
          repe      cmpsb
          pop       si
          pop       cx
          je        get_env_got

          push      cx
          mov       cx,8000h
          repne     scasb
          pop       cx

          jmp       get_env_loop

get_env_got:
          clc
          ret

get_env_none:
          stc
          ret

TRANCODE        ends
                end
