        DIM VERSION$/"*** SDOSDISKBACKUP V1.1g ***"/
!
!       SDOS UTILITY TO COPY FILES BY WILDCARD, OR COPY DISKS, AND VERIFY COPY
!
!       Command Format:
!
!       .SDOSDISKBACKUP <source> <qualifiers> {NONSTOP} TO/OVER <destination>
!
!       The following description specifies not only the possibilities, but
!       the command line order required if multiple options are used.
!
!       <source> may be one of the following:
!                a standard SDOS file name, with no embedded *s,
!                         which will cause the specified file to be copied
!                a list of wildcard specifications with embedded *s,
!                         which will cause copying of all matching files
!                the name of a disk device, whose content is to be saved
!
!        The source may be followed by optional qualifiers:
!                 EXCEPT allows a list of (wildcard) file names NOT to be
!                         copied, including "?", meaning, "ask the user..."
!                 CHANGED indicates that only files whose Backup protect bit
!                         is clear are to be copied, and then the Backup
!                         protect bit is to be set.
!                 AFTER "date" allows all files after a certain date
!                         to be copied.
!                 BEFORE "date" allows all files before a certain date
!                         to be copied.
!
!        NONSTOP indicates that errors are to be reported but ignored;
!                a best effort copy is all that is desired.
!
!        TO specifies that the files be copied to the destination; complaints
!                will be issued if the source file already exists.
!        OVER specifies that target files with the same name are to be replaced
!
!        <destination> may one of the following:
!                a disk device name, which will become the target of the copy
!                          an optional phrase, USING MAPALGORITHM :xxxx,
!                          allows the mapalgorithm of the target disk to be
!                          set to a specific value.
!                a * or a disk device name followed by a *, meaning the
!                          target(s) have the same names as the source.
!                a single file name or a disk device name followed by a
!                          single file name which becomes the target of
!                          the copy.
!^L
!
!       Revision History:
!
!       1979-1982       SDOSDISKVALIDATE V1.0.  Primitive version of V1.1
!
!       12/27/82        SDOSDISKVALIDATE V1.1g.
!                       Major enhancements for SDOS 1.1g
!                       Reorganized code; fixed rafts of stupid bugs
!                       Add option to honor "B" (Backup protect) bit;
!                           sets it after copy.
!                       Added OVER option to allow explicit backing up over
!                           files that already exist.
!                       Rewrote copy loop to speed it up; now do slow
!                               copies only in region near a disk error
!                       On disk-to-disk copies, mount both disks according
!                          to mapalgorithm of 1st if not equal to 1; else
!                          use map algorithm of (hueristic) :0003.
!                       Require SDOSDISKBACKUP ... TO Dn:*; like with COPY
!                       Added NONSTOP option: continues copying in spite
!                             of errors
!                       Allow backup of device into a filename, and converse.
!                       If file BACKUP, and no wildcard specified, then
!                             don't search directory; speeds copies
!                       Allows backing up of large files by splitting them?
!
!   2/1/84 RCW          Fixed wildcard bug where would not match correctly
!                       in all cases on text*text*text (two or more *'s)
!   2/8/84 IDB          Fixed SDOSDISKBACKUP * TO * fails with "Channel Open"
!
!^L
! Variables used to record Source File information
    Dim SourceDisk$(80),SourceIsDiskOnly/0/,SourceNames$(10)[18]
    Dim SourceIsSingleFile,SourceFileDate$(6),SourceFileProt$(1)

! Variables used to record exception list information
    Dim ExceptNames$(10)[18],ExceptOption/0/,ExceptQueryUser/0/
    Dim ChangedOption/0/,Beforedateoption/0/,AfterDateOption/0/
    Dim BeforeDate,AfterDate

! Variables used to record Destination File information
    Dim DestinationDisk$(80),DestinationIsDiskOnly/0/
    Dim NewDestinationDisk$(80),NewDestinationIsDiskOnly/0/
    Dim DestinationNames$(1)[18],DestinationIsStarFile
    Dim DestinationIsSingleFile,UsingMapAlgorithm/0/,NewMapAlgorithm

! Variables used to record copy style desired
    Dim NonStopMode,CopyStyle
    Dim Copyinto/0/,CopyOver/1/,CopyTo/2/

! Variables used to manage copy buffer
    Dim halfbuffer
    Dim NBPS,NSPT,NTPC,NCYL
    Dim readsize
    Dim readpointer
    Dim writepointer
    Dim verifyreadpointer
    Dim verifywritepointer

! Variables used to define channel numbers
    Dim SourceFile/1/
    Dim DestinationFile/2/
    Dim SourceAtFile/3/
    Dim ExceptAtFile/4/
    Dim BootFile/5/
    Dim DirectoryFile/5/

! Variables used to hold filenames of files being copied
    Dim SourceNamePtr
    Dim SourceNameEnd
    Dim SourceName$(18)
    Dim SourceAtName$(18)
    Dim ExceptNamePtr
    Dim ExceptName$(18)
    Dim ExceptAtName$(18)
    Dim namecounter

! Variables used to defeat the len("constant string") problem
    Dim Except$/"EXCEPT"/
    Dim Changed$/"CHANGED"/
    Dim Before$/"BEFORE"/
    Dim After$/"AFTER"/
    Dim Nonstop$/"NONSTOP"/
    Dim To$/"TO"/
    Dim Over$/"OVER"/
    Dim Into$/"INTO"/
    Dim Using$/"USING"/
    Dim Mapalgorithm$/"MAPALGORITHM"/

! Boot Sector variables
    Dim BootDiskInfo/:10/
    Dim BootMapAlgorithm/:16/
    Dim BootChecksum/:1F/

! Directory variables
    Dim DirectoryRec$(32)
    Dim HCSIC/19/
    Dim Nclusters/20/
    Dim WriteProtect/:40/
    Dim BackupProtect/:01/

! Variables used for syscalls
    Dim IsConsole$/:1A,2/
    Dim SCCreate$/1,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0/
    Dim CCDumpBuffers$/:E,4,0,1/
    Dim CCSetFileDate$/:E,8,0,:10/
    Dim CCSetFileProt$/:E,8,0,:11/
    Dim CCSetFileSize$/:E,4,0,:12/
    Dim CCUnlockDisk$/:E,4,0,:10/
    Dim CCDismountDisk$/:E,4,0,:11/
    Dim CCSetMapAlgorithm$/:E,8,0,:12/
    Dim SCGetFileSize$/:F,14,0,:3/
    Dim SCGetParams$/:F,14,0,:5/
    Dim SCGetFileDate$/:F,14,0,:10/
    Dim SCGetFileProt$/:F,14,0,:11/
    Dim readb$/:B,14,0,0,0,0,0,0,0,0,0,0,0,0/
    Dim writeb$/:D,8,0,0,0,0,0,0/
    Dim SetError$/:13,4,0,0/
    Dim DisplayError$/:15,2/
    Dim ErrorExit$/:12,4,0,0/

! Error codes (real)
    Dim ErrBadcommandformat/102/
    Dim ErrProgtermabnormal/104/
    Dim ErrEofhit/1001/
    Dim ErrFileisopen/1002/
    Dim ErrFilenotfound/1011/
    Dim ErrBadFnameSize/1013/
    Dim ErrNodiskspace/1015/
    Dim ErrWrongfilesystem/1018/
    Dim ErrBadFileName/1023/
    Dim ErrDevicetimedout/1042/
    Dim ErrDiskread/1045/
    Dim ErrDiskwrite/1046/
    Dim ErrDiskseek/1047/
    Dim ErrDskwrtprot/1048/
    Dim ErrClustersizelimitsfile/1052/
    Dim ErrNosuchdevice/1056/
    Dim ErrNotopentoconsole/1059/
    Dim ErrFilealreadyexists/1065/
    Dim ErrEndofmedium/1071/

! Error codes (artificial)
    Dim AbortSectorCopy/200/
    Dim AbortFileCopy/201/
    Dim AbortBackup/202/
    Dim CantVerify/203/
    Dim ErrEofHitonread/204/

! Miscellaneous Variables
    Dim zeroes$/0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0/
    Dim OneByteString$(1)
    Dim TwoByteString$(2)
    Dim CopyBytesOk
    Dim filesize
    Dim RunTimeErr/0/
    Dim Answer$(1)
    Dim Splitmessageseen/0/
!^L
Def constructdynamicstring$ external

Subroutine Backup(Copybuffer$)

Subroutine DisplayErrorString
    SetError$(3)=int(err/256)
    SetError$(4)=err&:FF
    syscall SetError$
    syscall DisplayError$
    exit subroutine
end

Subroutine DisplayErrorStringCr
    displayerrorstring
    print
    exit subroutine
end

Subroutine col40check
    if col(0)>40
    then
        print
        print '     ';
    fi
    exit subroutine
end

Subroutine ReportErrorSub(RESString$)
    print "**** '";
    displayerrorstring
    print "' ";
    col40check
    print RESString$;
    exit subroutine
end

Subroutine ReportError(REString$)
    ReportErrorSub(REString$)
    print
    exit subroutine
end

Subroutine ReportErrorOccuredWhile(REOWString$)
    reporterrorsub('occured while attempting to ')
    col40check
    print REOWString$
    exit subroutine
end

Subroutine ExitWithError(EWE)
    ErrorExit$(3)=int(EWE/256)
    ErrorExit$(4)=EWE&:FF
    syscall errorexit$
end

Subroutine badcommandexit
    ExitWithError(ErrBadcommandformat)
end

Subroutine abnormaltermexit
    ExitWithError(ErrProgtermabnormal)
end
!^L
Def Ival(ValString$)
    ! Do a destructive val
    ! Return the val of Valstring$ and shortens up ValString$
    ! Input number must integer or hex only
    Dim Digits$/"0123456789"/, HexDigits$/"0123456789ABCDEF"/
    Dim BlankChars$/" ",:9/
    Ivaltemp=val(ValString$) \ ! and error out if not a numeric prefix
    for i=1 to len(ValString$) while find(BlankChars$,ValString$(i,1))...
&       do ! skip blanks
    if ValString$(i,1)=':'
    then for i=i+1 to len(ValString$) ...
&       while find(HexDigits$,ValString$[i,1]) do ! scan past digits
    else for i=i to len(ValString$) ...
&       while find(Digits$,ValString$[i,1]) do ! scan past digits
    ValString$=right$(ValString$,i)
    return Ivaltemp
end

Subroutine Deblank(Deblank$)
    while find(Deblank$," ")=1 do Deblank$=Right$(Deblank$,2)
    exit subroutine
end

Def ExtractPathName$(PathName$)
    ! Returns PathName prefix
    ! A pathname is the "xxx:yyy:zzz:" stuff
    Dim PathTemp$(80)
    Deblank(pathName$) \ ! Eat any leading blanks
    PathTemp$=''
    repeat
        i=find(PathName$,":")
        j=find(PathName$,' ')
        if (i=0) or ((j<>0) and (j<i)) then return PathTemp$
        PathTemp$=PathTemp$ cat PathName$(1,i)
        PathName$=right$(PathName$,i+1)
    end
end

Def ExtractWildCards(WildCards$,WildCardNames$(*))
    ! Break WildCards$ up into seperate strings and store in WildCardNames$
    ! Complain if any are longer than valid file names,
    ! or there are too many of them
    for i=1 to len(WildCardNames$) do WildCardNames$(i)="" \ ! make all null
    Let NumberOfWildCards=0
    if find(WildCards$,' ')=1 then return false \ ! No filenames
    Repeat
        if NumberOfWildCards=len(WildCardNames$)
        then return true \ ! Too many filenames
        let NumberOfWildCards=NumberOfWildCards+1
        i1=find(WildCards$,",") \ ! Find the first delimiter
        i=find(WildCards$," ")
        if i=0 then i=i1
        else if (i1<>0) and (i1<i) then i=i1
        if i=0
        then
            ! No delimiter, use the remaining string
            Let WildCardNames$(NumberOfWildCards)=WildCards$
            WildCards$='' \ ! Eat the file name
            return false
        else
            ! Delimeter found, save the file name
            Let WildCardNames$(NumberOfWildCards)=WildCards$(1,i-1)
            WildCards$=Right$(WildCards$,i+1) \ ! Eat file name and delimeter
            Deblank(WildCards$) \ ! Eat any spaces
            if find(WildCards$,',')=1
            then
                WildCards$=Right$(WildCards$,2) \ ! Eat the comma
                Deblank(WildCards$) \ ! Eat any spaces
            else if i<>i1 then return false \ ! No comma, done
        fi
    end
end
!^L
Subroutine ExtractDate(ExtractDate$,ResultDate)
    ! Processes MM/DD/YY to produce single number YY*65536+MM*256+DD
    ! where YY, MM, and DD are BCD representations
    Dim EDDate(3)

    Deblank(ExtractDate$)
    if error when EDDate(2)=Ival(ExtractDate$) then IllegalDate
    if EDDate(2)>99 then IllegalDate
    Deblank(ExtractDate$)
    if find(ExtractDate$,'/')<>1 then IllegalDate
    ExtractDate$=right$(ExtractDate$,2)

    Deblank(ExtractDate$)
    if error when EDDate(3)=Ival(ExtractDate$) then IllegalDate
    if EDDate(3)>99 then IllegalDate
    Deblank(ExtractDate$)
    if find(ExtractDate$,'/')<>1 then IllegalDate
    ExtractDate$=right$(ExtractDate$,2)

    Deblank(ExtractDate$)
    if error when EDDate(1)=Ival(ExtractDate$) then IllegalDate
    if EDDate(1)>99 then IllegalDate
    ResultDate=0
    for i=1 to 3 do
        j=int(EDDate(i)/10)
        ResultDate=ResultDate*16+j
        ResultDate=ResultDate*16+EDDate(i)-j*10
    end
    exit subroutine

IllegalDate: Print "Illegal date specification" \ badcommandexit
end
!^L
Def MatchWildCardTo(WildCard$,MatchToString$)
    ! Function to match WildCard containing "*" to MatchToString
    ! Returns TRUE or FALSE based on match result

    Let i=find(WildCard$,"*")
    if i=0 then return MatchToString$=WildCard$ \ ! Wildcard has form: <text>

    ! WildCard$ has form: <text>*<moretext>
    if find(MatchToString$,WildCard$(1,i-1))<>1
    then return false \ ! Prefixes don't match
    MatchToPointer=i \ ! skip past matched prefix
    WildCardPointer=1 \ ! Set up for For loop in Repeat, below
    Repeat
        ! i holds offset from WildCardPointer to next "*" in WildCard$
        for WildCardPointer=WildCardPointer+i to len(WildCard$) ...
&           Until WildCard$(WildCardPointer,1)<>"*" ...
&           do ! Skip past matched text and possible multiple "*"s

        ! Now match something to the "*" skipped in the WildCard$
        if WildCardPointer>Len(WildCard$) then return true \ ! Matched !
        if MatchToPointer>Len(MatchToString$) then return false
        ! At this point, WildCard$ has form: <matchedtext>*<text>...
        Let i=find(Right$(WildCard$,WildCardPointer),"*")
        if i=0
        then
            ! Wildcard has form:  *<text>
            i=Len(WildCard$)-WildCardPointer+1 \ ! Amount to match
            if Len(MatchToString$)-MatchToPointer+1 < i
            then return false \ ! Tail of MatchToString$ is too short
            ! Tail is long enough, see if matches wildcard tail
            return MatchToString$[Len(MatchToString$)-i+1,i]=...
&                  Right$(WildCard$,WildCardPointer)
        fi
        ! WildCard$ has form: *<text>*<moretext>
        i1=find(Right$(MatchToString$,MatchToPointer),...
&               WildCard$[WildCardPointer,i-1])
        if i1=0 then return false \ ! Can't find text in MatchToString$

        ! MatchToPointer= MatchToPointer + where found (disp 0) + how long
        Let MatchToPointer= MatchToPointer + (i1-1) + (i-1)
    end
end
!^L
Subroutine CheckFileNameESub(CFNESName$,CFNESIndirectFile$)
    print 'occured while checking filename ';
    col40check
    if CFNESIndirectFile$<>'' then print '@'; CFNESIndirectfile$; ', ';
    print CFNESName$
    badcommandexit
end

Subroutine CheckFileNameError(CFNEError,CFNEName$,CFNEIndirectFile$)
    if error when error CFNEError then ! this sets err variable
    ReportErrorSub('')
    CheckFileNameESub(CFNEName$,CFNEIndirectFile$)
end

Subroutine CheckFileName(CFName$,CFAtName$)
    Dim CFi
    Dim FileSet$/'*$abcdefghijklmnopqrstuvwxyz.0123456789'/
    if len(CFName$)>16
    then CheckFileNameError(ErrBadFnameSize,CFName$,CFAtName$)
    if find(FileSet$(1,28),lowercase$(CFName$(1,1)))=0
    then CheckFileNameError(ErrBadFileName,CFName$,CFAtName$)
    for CFi=1 to len(CFName$) do
        if find(FileSet$,lowercase$(CFName$(CFi,1)))=0
        then
            print "**** 'Bad File name '";
            CheckFileNameEsub(CFName$,CFAtName$)
        fi
    end
    exit subroutine
end

Def GetNextName(NameList$(*),NextName$,NextNamePtr,AtFile,AtFileName$)
    ! Returns the next name in the list in NextName$
    ! Returns true if NextName$ contains valid filename (not eof on NameList$)
    ! Allows indirect file processing by prefixing filename with '@'
    ! Checks the '@' file spec for correctness before opening
    repeat
        if AtFileName$<>''
        then
            input #AtFile, NextName$
            NextName$=uppercase$(NextName$)
            if NextName$='' or eof(AtFile)
            then
                close #AtFile
                AtFileName$=''
            else return true
        fi
        NextNamePtr=NextNamePtr+1
        if NextNamePtr>len(NameList$) then return false
        NextName$=NameList$(NextNamePtr)
        if NextName$='' then return false
        if find(NextName$,'@')=1
        then
            AtFileName$=right$(NextName$,2)
            CheckFileName(AtFileName$,'')
            if error when open #AtFile, SourceDisk$ cat AtFileName$
            then
                if err<>ErrFilenotfound then error
                print "Indirect file '"; AtFileName$; "' not found"
                badcommandexit
            fi
        else return true
    end
end

Subroutine CheckFileNames
    SourceNamePtr=0
    SourceAtName$=''
    while GetNextName(SourceNames$,SourceName$,SourceNamePtr,...
&       SourceAtFile,SourceAtName$) do
        CheckFileName(SourceName$,SourceAtName$)
    end
    if ExceptOption
    then
        ExceptNamePtr=0
        ExceptAtName$=''
        while GetNextName(ExceptNames$,ExceptName$,ExceptNamePtr,...
&           ExceptAtFile,ExceptAtName$) do
            CheckFileName(ExceptName$,ExceptAtName$)
        end
    fi
    if DestinationIsSingleFile
    then
        if find(DestinationNames$(1),'@')=1
        then print 'Indirect file not allowed for destination' \ badcommandexit
        else CheckFileName(DestinationNames$(1),'')
    fi
    exit subroutine
end

Subroutine SetDiskMap(Disk)
    !D!print 'SDM: diskmap=';NewMapAlgorithm
    TwoByteString$= chr$(NewMapAlgorithm**-8) cat chr$(NewMapAlgorithm&:FF)
    syscall #Disk, CCSetMapAlgorithm$,TwoByteString$
    exit subroutine
end
!^L
Subroutine CheckAbortFileCopy
    if nonstopmode then error abortfilecopy
    input 'Abort file copy? (Yes or No, <CR>=No) ' answer$
    if find(lowercase$(answer$),'y')=1
    then error abortfilecopy
    exit subroutine
end

Subroutine DismountDestinationDisk
    !D!print 'DDD: opening disk '; DestinationDisk$
    open #DestinationFile, DestinationDisk$
    repeat
        !D!print 'DDD: dismounting disk'
        if error when syscall #DestinationFile, CCDismountDisk$
        then
            if err=ErrFileisopen
            then
                ReportErrorOccuredWhile('dismount' cat DestinationDisk$)
                CheckAbortFileCopy
            else error
        else
            print DestinationDisk$; ' dismounted'
            close #destinationfile
            exit subroutine
        fi
    end
end

Def newfilename$(nname$)
    Dim nntemp$(5)
    !D!print 'NN: namecounter= '; namecounter
    if DestinationIsDiskOnly then return DestinationDisk$
    if namecounter=0
    then return DestinationDisk$ cat nname$
    else
        nntemp$=num$(namecounter)
        nntemp$(1,1)='.'
        if len(nname$)+len(nntemp$)>16
        then return DestinationDisk$ cat nname$(1,16-len(nntemp$)) cat nntemp$
        else return DestinationDisk$ cat nname$ cat nntemp$
    fi
end

Subroutine getnewfilename(GNFDestinationName$)
    !D!print 'GNF:'
    gnewfilename:...
&   repeat
        gnfloop:...
&       repeat
            print 'New destination file name ';
            if SourceDisk$<>DestinationDisk$
            then
                print '(Default='; newfilename$(GNFDestinationName$); ') ';
                input '' CopyBuffer$
                defaultfile=(CopyBuffer$='')
            else
                input '' CopyBuffer$
                defaultfile=false
            fi
            if not defaultfile
            then
                CopyBuffer$=uppercase$(CopyBuffer$)
                NewDestinationDisk$=ExtractPathName$(CopyBuffer$)
                if NewDestinationDisk$='' then NewDestinationDisk$='DISK:'
                if SourceDisk$=NewDestinationDisk$
                then
                    print 'Source disk must be different from destination disk'
                    exit gnfloop
                fi
                if ExtractWildCards(CopyBuffer$,DestinationNames$)
                then print 'Only one destination file allowed' \ exit gnfloop
                NewDestinationIsDiskOnly=DestinationNames$(1)=''
                if error when open #destinationfile, NewDestinationdisk$
                then
                    if err=ErrNosuchdevice then call displayerrorstringcr
                    else error
                    if error when close #destinationfile then ! do nothing
                    exit gnfloop
                fi
                if error when close #destinationfile then ! do nothing
                DestinationDisk$=NewDestinationDisk$
                GNFDestinationName$=DestinationNames$(1)
                DestinationIsDiskOnly=NewDestinationIsDiskOnly
                namecounter=0 \ ! we're using user supplied name
            fi
            !D!print 'GNF: all parameters collected'
            !D!print 'GNF: DestinationDisk$= ';DestinationDisk$
            !D!print 'GNF: DestinationIsDiskOnly= ';
            !D!if DestinationIsDiskOnly then print 'true' else print 'false'
            !D!print 'GNF: New target name= '; newfilename$(GNFDestinationName$)
            exit gnewfilename
        end
        CheckAbortFileCopy
    end
    exit subroutine
end

Subroutine SetFileSize(SFSSize)
    !D!print 'SFS:';SFSSize
    position #DestinationFile, SFSSize
    syscall #DestinationFile,CCSetFileSize$
    exit subroutine
end

Def getfilesize(gfsfile)
    dim gfstemp$(4), gfsfilesize
    syscall #gfsfile, SCGetFileSize$,'',gfstemp$
    gfsfilesize=0
    for i=1 to 4 do gfsfilesize=gfsfilesize*256+gfstemp$(i)
    return gfsfilesize
end

Subroutine checkfilesize
    !D!print 'CFS: writepointer, filesize= ';
    !D!print writepointer;
    !D!if not DestinationIsDiskOnly then print getfilesize(destinationfile);
    !D!print
    if not DestinationIsDiskOnly and getfilesize(DestinationFile)<>writepointer
    then
        RunTimeErr=true
        print '**** Error, destination file is longer than source file'
        SetFileSize(writepointer)
        print '     ...truncated'
    fi
    exit subroutine
end

Subroutine SetupNewFile(SNFDestinationName$)
    !D!print 'SNF:'
    getnewfilename(SNFDestinationName$)
    DismountDestinationDisk
    input '<CR> when new destination device is ready ' answer$
    if not destinationisdiskonly
    then
        if error when open #destinationfile, newfilename$(SNFDestinationName$)
        then if err<>ErrFilenotfound then error fi
        else
            close #destinationfile
            print 'Copy over ';newfilename$(SNFDestinationName$);
            input '? (<CR>=no) ' answer$
            if not find(lowercase$(answer$),'y')=1
            then error abortfilecopy
            else delete newfilename$(SNFDestinationName$)
        fi
    fi
    exit subroutine
end

Subroutine CreateFile(CrFName$,CFSize)
    ! a cheap trick is to create with pre-allocation then close and reopen
    ! this works well except in a multi user environment where the
    ! allocated (then de-allocated) clusters can be sucked away by another
    ! user
    j=CFSize
    for i=18 to 15 step -1 do
        k=int(j/256)
        SCCreate$(i)=j-k*256
        j=k
    end
    !D!j=0
    !D!for i=15 to 18 do j=j*256+SCCreate$(i)
    !D!if j<>CFSize then print 'CreateFile: CFSize, j='; CFSize, j
    fileisnotsparse=true
    repeat
        if error when
            syscall #destinationfile, SCCreate$,...
&               newfilename$(CrFName$), TwoByteString$
        then
            if err=ErrClustersizelimitsfile
            then
                create #destinationfile, newfilename$(CrFname$)
                ! (no preallocation)
                exit subroutine
            elseif err=ErrNodiskspace
            then
                ReportErrorOccuredWhile('create the destination file')
                CheckAbortFileCopy
                CFloop:...
&               repeat
                    if error when
                        setupnewfile(CrFName$)
                    then
                        if err=abortfilecopy
                        then error
                        else
                            ReportErrorOccuredWhile(...
&                               'setup the new destination file')
                            CheckAbortFileCopy
                        fi
                    else exit CFloop
                end
            else error
        else exit subroutine
    end
end

Subroutine SplitFileContinue(SFCDestinationName$,SFCDeleteCopiedPortion)
    !D!print 'SFC:'
    if not SourceIsDiskOnly and not DestinationIsDiskOnly
    then
        !D!print 'SFC: set file date'
        syscall #DestinationFile, CCSetFileDate$, SourceFileDate$
        !D!print 'SFC: set file protection'
        syscall #DestinationFile, CCSetFileProt$, SourceFileProt$
    fi
    if SFCDeleteCopiedPortion
    then
        close #DestinationFile
        if not DestinationIsDiskOnly
        then
            delete newfilename$(SFCDestinationName$)
            print '     ...'; newfilename$(SFCDestinationName$); ' deleted'
            readpointer=readpointer-writepointer \ ! back out the stuff we copied
        fi
    else
        checkfilesize
        close #DestinationFile
    fi
    if not SFCDeleteCopiedPortion then namecounter=namecounter+1
    writepointer=0
    verifywritepointer=writepointer
    verifyreadpointer=readpointer
    SFCloop:...
&   repeat
        if error when
            setupnewfile(SFCDestinationName$)
            CreateFile(SFCDestinationName$,filesize-readpointer)
            if destinationisdiskonly
            then
                SetDiskMap(DestinationFile)
                syscall #Destinationfile, CCUnlockDisk$
            fi
            position #SourceFile, readpointer
        then
            if err=abortfilecopy
            then error
            else
                ReportErrorOccuredWhile('setup the new destination file')
                CheckAbortFileCopy
            fi
        else exit SFCloop
    end
    ! force us out to top level copy to continue the copy
    error abortsectorcopy
end

Subroutine SplitFile(SFDestinationName$)
    !D!print 'SF:'
    if not nonstopmode
    then
        if not Splitmessageseen
        then
            print 'There are three choices:'
            print '1. Move the portion of the output file already copied'
            print '   from this disk onto another disk and continue copying'
            print '   (option M).'
            print '2. Split the output file by keeping the portion of the'
            print '   output file already copied on this disk, and continue'
            print '   copying (option S). Split is not allowed if source'
            print '   disk and destination disk are the same.'
            print '3. Abort this file copy and delete the portion of the'
            print '   output file already copied (option A).'
            Splitmessageseen=true \ ! see this once per program execution
        fi
        if SourceDisk$=DestinationDisk$
        then
            input 'Move or Abort (M or A, <CR>=Move)? ' Answer$
            if find(lowercase$(answer$),'a')<>1 then answer$='m' fi
        else
            input 'Move, Split, or Abort (M, S, A, <CR>=Split)? ' Answer$
            if find(lowercase$(answer$),'a')<>1 and...
&               find(lowercase$(answer$),'m')<>1 then answer$='s'
        fi
    fi
    if nonstopmode or find(lowercase$(Answer$),'a')=1
    then
        print '     ...file copy aborted'
        deleteabortedfile=true
        error AbortFileCopy
    else
        ! he wants to split the file
        SplitFileContinue(SFDestinationName$,find(lowercase$(Answer$),'m')=1)
    fi
    ! force us out to top level copy to continue the copy
    error AbortSectorCopy
end

Subroutine SkipBytesNoError
    !D!print 'SBNE:'
    verifyreadpointer=verifyreadpointer+ReadSize
    readpointer=verifyreadpointer
    ! warning! readpointer <> writepointer when file is split
    ! warning! verifyreadpointer <> verifywritepointer when file is split
    verifywritepointer=verifywritepointer+ReadSize
    writepointer=verifywritepointer
    exit subroutine
end

Subroutine SkipBytes
    CopyBytesOk=false
    print '     ...skipped';ReadSize;'bytes'
    SkipBytesNoError
    RunTimeErr=true \ ! no way to make a perfect backup now
    error AbortSectorCopy
end

Subroutine CheckAbortSectorCopy
    if NonStopMode
    then call SkipBytes
    else
        print 'Abort file copy, Retry, or Skip bytes ';
        input '(A, R, or S, <CR>=Retry)? ' Answer$
        answer$=lowercase$(answer$)
        if find(answer$,'a')=1
        then
            deleteabortedfile=true
            error AbortFileCopy
        elseif find(Answer$,'s')=1
        then call SkipBytes \ ! SkipBytes sets RunTimeErr
        else error AbortSectorCopy \ ! this makes him try again eventually
    fi
end

!D!Subroutine DisplayBuffer(DB$)
!D!   for DBi=1 to len(DB$)/16 do
!D!       print hex$((DBi-1)*16)[2,4]; '/';
!D!       for DBj=1 to 16 do print ' '; hex$(DB$((DBi-1)*16+DBj))[4,2];
!D!       print
!D!   end
!D!   exit subroutine
!D!end

Subroutine SingleSectorRead
    !D!print 'SSR: position input file to';verifyreadpointer;
    !D!print 'readcount=';readsize
    position #SourceFile, verifyreadpointer
    if error when syscall #SourceFile,readb$,'',CopyBuffer$(1,ReadSize)
    then
        ReportErrorOccuredWhile('read the source file @' cat...
&           num$(verifyreadpointer))
        CheckAbortSectorCopy \ ! this always produces error
    fi
    exit subroutine
end

Subroutine DumpBuffers
    Dim DBerr
    !D!print 'DB:'
    DBerr=0
    DBEloop:...
&   repeat
        if error when syscall #DestinationFile, CCDumpBuffers$
        then
            DBerr=err
            !D!print 'DB: err='; DBerr
            if err <> ErrEndofmedium and err <> ErrClustersizelimitsfile...
&               and err <> ErrDskwrtprot and err <> ErrDiskseek...
&               and err <> ErrDiskwrite and err <> ErrDevicetimedout...
&               and err <> ErrNodiskspace then exit DBEloop fi
        else exit DBEloop
    end
    if DBerr then error DBerr
    else exit subroutine
end

Subroutine undopreallocation(undoname$,undosize)
    !D!print 'Undo:', undoname$, undosize
    close #destinationfile
    open #destinationfile, newfilename$(undoname$)
    position #DestinationFile, undosize
    fileisnotsparse=false
    exit subroutine
end

Subroutine SingleSectorWrite(SSWDestinationName$)
    if DestinationIsDiskOnly
    then
        i=1
        !D!print 'SSW: position output file to'; verifywritepointer+i-1;
        !D!print 'writecount=';readsize+1-i
    else
        for i=1 to readsize while CopyBuffer$(i)=0 do ! eat sparse bytes
        !D!print 'SSW: i=';i;
        !D!print 'position output file to'; verifywritepointer+i-1;
        !D!print 'writecount=';readsize+1-i
    fi
    ! i=1 \ ! sparse write problem shunt
    if i=NBPS and fileisnotsparse
    then call undopreallocation(SSWDestinationName$,verifywritepointer)
    position #DestinationFile, verifywritepointer+i-1
    if error when
        syscall #DestinationFile,writeb$,CopyBuffer$(i,ReadSize+1-i)
        ! force it to leave the buffer pool and go to the disk
        syscall #DestinationFile, CCDumpBuffers$
    then
        ReportErrorOccuredWhile('write the destination file @'...
&           cat num$(verifywritepointer))
        if err=ErrEofhit or err=ErrNodiskspace or err=ErrEndofmedium or...
&           err=ErrClustersizelimitsfile
        then
            ! eof, disk full, end of media,
            ! or file system can't support file that big
            print '     ...Successfully copied'; verifyreadpointer;...
&               'bytes (';numf$('##.#',verifyreadpointer*100/filesize);...
&               '%) of the source file'
            print '     ...The destination file contains';...
&               verifywritepointer;...
&               'bytes (';numf$('##.#',verifywritepointer*100/filesize);...
&               '%) of the source file'
            ! now that we got the error message out, we can ask
            ! if he wants to split the file or what
            SplitFile(SSWDestinationName$) \ ! this errors out
        fi
        if err=ErrDskwrtprot then error AbortBackup
        CheckAbortSectorCopy \ ! this always produces error
    fi
    if not DestinationIsDiskOnly
    then call SetFileSize(verifywritepointer+readsize)
    exit subroutine
end

Subroutine SingleSectorVerify
    !D!print 'SSV: position output file to'; verifywritepointer
    position #DestinationFile, verifywritepointer
    if error when
        syscall #DestinationFile,readb$,'',CopyBuffer$(ReadSize+1,ReadSize)
    then
        ReportErrorOccuredWhile('read the destination file @' cat...
&           num$(verifywritepointer))
        CheckAbortSectorCopy \ ! this always produces error
    else
        ! the stuff read back ok, now verify good copy
        if CopyBuffer$(1,ReadSize)<>CopyBuffer$(ReadSize+1,ReadSize)
        then
            print '**** Verify error occured @';num$(verifyreadpointer)
            !D!print 'SSV: source buffer='
            !D!DisplayBuffer(CopyBuffer$(1,readsize))
            !D!print 'SSV: destination buffer='
            !D!DisplayBuffer(CopyBuffer$(readsize+1,readsize))
            CheckAbortSectorCopy \ ! this always produces error
        else call SkipBytesNoError
    fi
    !D!print 'SSV: exit'
    exit subroutine
end

Subroutine SingleSectorCopy(SSCDestinationName$)
    !D!print 'SSC:'
    if error when
        len(CopyBuffer$)=maxlen(CopyBuffer$)
        ! somewhere we got an error else we wouldn't be here
        ! doing careful copy
        ! so, let's backup the pointers
        readpointer=verifyreadpointer
        if verifywritepointer <> writepointer
        then
            writepointer=verifywritepointer
            if not DestinationIsDiskOnly
            then SetFileSize(writepointer)
        fi
        !D!print 'SSC: rp, wp='; readpointer; writepointer
        if filesize-verifyreadpointer<NBPS
        then ReadSize=filesize-verifyreadpointer
        else ReadSize=NBPS
        !D!print 'SSC: readsize='; readsize
        SingleSectorRead
        SingleSectorWrite(SSCDestinationName$)
        SingleSectorVerify
    then
        !D!print 'SSC: err='; err
        if err=AbortSectorCopy then exit subroutine \ ! ignore this error
        error \ ! pass on all other errors
    fi
    !D!print 'SSC: exit'
    exit subroutine
end

Subroutine Verify
    !D!print 'V: pos input to';verifyreadpointer;
    !D!print 'pos output to';verifywritepointer
    ! NBPS should be <= 1/2 maxlen(CopyBuffer$)...
    ! ... (it's nice to be able to read an entire sector, although not...
    ! ... an absolute requirement)
    ! sdos manual also says 128<=NBPS<=4096, so 8k buffer is appropriate
    ! when error are generated, they are passed on to the upper level
    ! which calls singlesector copy, which reports all errors
    len(CopyBuffer$)=maxlen(CopyBuffer$)
    Dumpbuffers
    position #SourceFile, verifyreadpointer
    position #DestinationFile, verifywritepointer
    repeat
        ReadSize=writePointer-verifyWritePointer
        if ReadSize>halfbuffer
        then ReadSize=halfbuffer
        !D!print 'V: readsize= '; num$(readsize);
        ! go get a chunk of the source file
        syscall #SourceFile,readb$,'',CopyBuffer$(1,ReadSize)
        ! go get a chunk of the destination file
        syscall #DestinationFile,readb$,'',CopyBuffer$(readsize+1,ReadSize)
        ! verify the two chunks
        if CopyBuffer$(1,ReadSize)=CopyBuffer$(readsize+1,ReadSize)
        then
            ! advance the verify pointers
            verifyreadpointer=verifyreadpointer+ReadSize
            verifywritepointer=verifywritepointer+ReadSize
            !D!print ', verifed ok, new vrp, vwp=';
            !D!print verifyreadpointer; verifywritepointer
        else
            !D!print ', error, source buffer='
            !D!DisplayBuffer(CopyBuffer$(1,readsize))
            !D!print 'V: destination buffer='
            !D!DisplayBuffer(CopyBuffer$(readsize+1,readsize))
            error cantverify
        fi
    when verifywritepointer<writepointer
    exit subroutine
end

Subroutine WriteSparse(WSName$)
    if len(CopyBuffer$)=0 then exit subroutine
    i=1
    repeat
        j=i
        findsparseness:...
&       repeat
            if len(CopyBuffer$)<128
            then
                j=len(CopyBuffer$)+1
                k=j
                exit findsparseness
            fi
            for j=j to len(CopyBuffer$) step 128 ...
&               while CopyBuffer$(j)<>0 do ! skip non-sparse
            if j>len(CopyBuffer$)
            then
                ! non-sparse to end of buffer
                j=len(CopyBuffer$)+1
                k=j
                exit findsparseness
            fi
            ! some sector starts with a zero byte, see if sector is sparse
            for k=j to len(CopyBuffer$)-15 step 16 while...
&               Copybuffer$(k,16)=zeroes$ do ! skip sparse area
            ! assert CopyBuffer$(i,j-i) is non-sparse
            ! assert CopyBuffer$(j,k-j) is sparse
            ! assert i and j are on sector boundaries
            if k-j>=128
            then
                ! real sparse sector(s) found
                ! force k to sector boundary<=k
                k=((k-1)&:FF80)+1
                !D!print 'WS: file is sparse, fileisnotsparse=';
                !D!print fileisnotsparse
                if fileisnotsparse
                then call undopreallocation(WSName$,writepointer)
                exit findsparseness
            else
                j=j+128
                if j>len(CopyBuffer$)
                then
                    ! non-sparse to end of buffer
                    j=len(CopyBuffer$)+1
                    k=j
                    exit findsparseness
                fi
            fi
        end
        ! assert CopyBuffer$(i,j-i) is non-sparse
        ! assert CopyBuffer$(j,k-j) is sparse
        ! assert i, j, and k are on sector boundaries...
        ! unless len(copybuffer$)<maxlen(copybuffer$) or...
        ! len(CopyBuffer$)<128 in which case...
        ! j and k are set to len(CopyBuffer$)+1
        syscall #DestinationFile, writeb$, CopyBuffer$(i,j-i)
        ! if the write errored, we won't get here
        writepointer=writepointer+k-i
        if k>j
        then
            ! advance the file positions over the sparse area
            endoffileissparse=true
            position #DestinationFile, writepointer
        else endoffileissparse=false
        !D!print 'WS: i,j,k, rp, wp=';i;j;k;readpointer;writepointer
        i=k \ ! skip the non-sparse and sparse areas we just found
    when i<len(CopyBuffer$)
    exit subroutine
end

Subroutine SetNBPSonerror
    if err=errdiskread then NBPS=SourceNBPS
    elseif err=errdiskwrite then NBPS=DestinationNBPS
    elseif err=errdevicetimedout
    then
        NBPS=DestinationNBPS
        if SourceNBPS<DestinationNBPS
        then NBPS=SourceNBPS
    fi
    if NBPS>maxlen(CopyBuffer$) then NBPS=maxlen(CopyBuffer$)
    readpointer=int(readpointer/NBPS)*NBPS
    writepointer=int(writepointer/NBPS)*NBPS
    verifyreadpointer=int(verifyreadpointer/NBPS)*NBPS
    verifywritepointer=int(verifywritepointer/NBPS)*NBPS
    exit subroutine
end

Subroutine CopyBytes(CBDestinationName$)
    !D!print 'CB: destinationname= '; CBDestinationName$
    ! move the file contents from source to destination
    syscall #DestinationFile, SCGetParams$,'',CopyBuffer$
    DestinationNBPS=0
    for i=1 to 2 do DestinationNBPS=DestinationNBPS*256+CopyBuffer$(i)
    !D!print 'CB: DestinationNBPS='; DestinationNBPS
    readpointer=0 \ ! read next from this point in source file
    writepointer=0 \ ! write next to this point in destination file
    verifyreadpointer=0 \ ! start verify here in source file
    verifywritepointer=0 \ ! start verify here in destination file
    CopyBytesOk=true \ ! Assume no copy errors will occur
    while verifyreadpointer<filesize do
        NBPS=128
        readpointersave=readpointer
        writepointersave=writepointer
        !D!print 'CB: position input file to'; readpointer
        position #SourceFile, readpointer \ ! for benefit of main copy loop
        !D!print 'CB: position output file to'; writepointer
        position #DestinationFile, writepointer
        if error when
            endoffileissparse=false
            read #SourceFile,CopyBuffer$
            !D!print 'CB: rp='; num$(readpointer);
            readpointer=readpointer+len(CopyBuffer$)
            !D!print ',';len(copybuffer$);'bytes were read, new rp=';
            !D!print readpointer
            if DestinationIsDiskOnly
            then
                syscall #DestinationFile, writeb$, CopyBuffer$
                !D!print 'CB: wp= '; num$(writepointer);
                !D!print ',';len(copybuffer$);'bytes were written, new wp=';
                writepointer=writepointer+len(copybuffer$)
                !D!print writepointer
            else call WriteSparse(CBDestinationName$)
            syscall #DestinationFile, CCDumpBuffers$ \ ! after each write
            if eof(SourceFile) then error ErrEofhitonread
        then
            !D!print 'CB: err='; err
            !D!print 'CB: rp, wp, fileszize, endoffileissparse=';
            !D!print readpointer;writepointer;filesize;endoffileissparse
            if err<>ErrEofHitonread
            then
                if error when DumpBuffers then ! do nothing
                SetNBPSonerror
                len(CopyBuffer$)=NBPS
                readpointer=readpointersave
                writepointer=writepointersave
                position #SourceFile, readpointer
                position #DestinationFile, writepointer
                if error when
                    for i=1 to maxlen(CopyBuffer$) step NBPS
                        !D!print 'CB:small buffer copy, rp, wp='; readpointer; writepointer
                        syscall #SourceFile,readb$,'',CopyBuffer$(1,NBPS)
                        syscall #DestinationFile, writeb$, CopyBuffer$(1,NBPS)
                        ! force it to leave the buffer pool and go to the disk
                        syscall #DestinationFile, CCDumpBuffers$
                        readpointer=readpointer + NBPS
                        writepointer=writepointer + NBPS
                    end
                then
                    SetNBPSonerror
                    !D!print 'CB: error after writing single sectors='; err
                fi
            fi
            if err=ErrEofhitonread and not DestinationIsDiskOnly and...
&               (readpointer<>filesize or endoffileissparse)
            then
                ! this maneuver is designed to overcome the 'sparse-to-eof'
                ! gotcha (it's a gotcha because SDOS doesn't do setfilesize
                ! right, that is, zero out to eof when file is extended)
                !D!print 'CB: position to filesize-1'
                position #DestinationFile, filesize-1
                !D!print 'CB: write zero byte at eof'
                if error when write #destinationfile, chr$(0) then ! ignore it
            fi
            ! don't care about the error at this level
            ! verify as far as we can
            if error when verify
            then
                !D!print 'CB: verify error=';err
                SetNBPSonerror
                if NBPS>halfbuffer then NBPS=halfbuffer
                len(CopyBuffer$)=maxlen(CopyBuffer$)
                position #SourceFile, verifyreadpointer
                position #DestinationFile, verifywritepointer
                if error when
                    smallverifyloop:...
&                   repeat
                        readsize=writepointer-verifywritepointer
                        if readsize>NBPS then readsize=NBPS
                        !D!print 'CB:small buffer verify, vrp, vwp=, readsize';
                        !D!print  verifyreadpointer; verifywritepointer; readsize
                        syscall #SourceFile, readb$,'',...
&                           CopyBuffer$(1,readsize)
                        syscall #DestinationFile, readb$, '',...
&                           CopyBuffer$(readsize+1,readsize)
                        if CopyBuffer$(1,readsize)<>...
&                           CopyBuffer$(readsize+1,readsize)
                        then exit smallverifyloop
                        ! advance the verify pointers
                        verifyreadpointer=verifyreadpointer+readsize
                        verifywritepointer=verifywritepointer+readsize
                    when verifywritepointer<writepointer
                then
                    SetNBPSonerror
                    !D!print 'CB: error after verifying single sectors='; err
                fi
            fi
            if error when singlesectorcopy(CBDestinationName$)
            then
                !D!print 'CB:single sector copy err='; err
                error
            fi
        fi
    end
    ! assert all source bytes are copied and verified (as best we could do)
    !D!print 'CB:checking file size'
    checkfilesize
    if not SourceIsDiskOnly and not DestinationIsDiskOnly
    then
        syscall #DestinationFile, CCSetFileDate$,SourceFileDate$ \ ! Set file date
        syscall #DestinationFile, CCSetFileProt$,SourceFileProt$ \ ! Set file protection
    fi
    if not SourceIsDiskOnly and ChangedOption and CopyBytesOk
    then
        SourceFileProt$(1)=SourceFileProt$(1) or BackupProtect
        syscall #SourceFile, CCSetFileProt$,SourceFileProt$ \ ! Set file protection
    fi
    exit subroutine
end
!^L
Subroutine CloseSourceFile(CSFName$)
    if error when close #SourceFile
    then call ReportErrorOccuredWhile('close ' cat SourceDisk$ cat CSFName$)
    exit subroutine
end

Subroutine CloseDestinationFile(CDFName$)
    if error when close #DestinationFile
    then call ReportErrorOccuredWhile('close ' cat DestinationDisk$ cat...
&       CDFName$)
    exit subroutine
end

Subroutine DeleteFile(DFName$)
    RunTimeErr=true \ ! signal abnormal completion
    if error when close #sourcefile then ! do nothing
    if error when close #destinationfile then ! do nothing
    if not DestinationIsDiskOnly and deleteabortedfile
    then
        !D!print 'DF: deleting ';newfilename$(DFName$)
        if error when delete newfilename$(DFName$)
        then
            !D!print 'DF: delete err= '; err
            if err<>ErrFilenotfound
            then
                CopyBuffer$=newfilename$(DFName$)
                ReportErrorOccuredWhile('delete ' cat CopyBuffer$)
            fi
        else print '     ...'; newfilename$(DFName$); ' deleted'
    fi
    exit subroutine
end

Subroutine CopyFile(CopyName$)
    Dim TargetName$(18)
    namecounter=0 \ ! non-zero if file has been split
    !D!print 'CF: copyname= '; Copyname$
    if DestinationIsStarFile
    then TargetName$=CopyName$
    else TargetName$=DestinationNames$(1) \ ! This is null for disk only
    if not SourceIsDiskOnly
    then
        if error when open #SourceFile, SourceDisk$ cat CopyName$
        then
            ReportErrorOccuredWhile('open ' cat SourceDisk$ cat CopyName$)
            exit subroutine
        fi
        ! Get file date (needed at least for setting target file date)
        if error when syscall #SourceFile, SCGetFileDate$,'',SourceFileDate$
        then
            ReportErrorOccuredWhile('read the file date on ' cat...
&               SourceDisk$ cat CopyName$)
            CloseSourceFile(CopyName$)
            exit subroutine
        fi
        if AfterDateOption or BeforeDateOption
        then
            j=0 \ ! Compute date
            for i=6 to 4 step -1 do j=j*256+SourceFileDate$(i)
            if (BeforeDateOption and j>=BeforeDate) or...
&              (AfterDateOption and j<=AfterDate)
            then
                CloseSourceFile(CopyName$)
                exit subroutine
            fi
        fi
        ! Get file protection (needed at least to change backup bit if copied)
        if error when syscall #SourceFile, SCGetFileProt$,'',SourceFileProt$
        then
            ReportErrorOccuredWhile('read the file protection on ' cat...
&               SourceDisk$ cat CopyName$)
            CloseSourceFile(CopyName$)
            exit subroutine
        fi
        if ChangedOption and SourceFileProt$(1)&BackupProtect
        then
            CloseSourceFile(CopyName$)
            exit subroutine
        fi
        if ExceptQueryUser
        then
            print 'Copy '; SourceDisk$;CopyName$;
            if CopyStyle=CopyOver then print ' over ';
            elseif CopyStyle=CopyInto then print ' into ';
            else print ' to ';
            print DestinationDisk$;TargetName$;
            input '? (<CR>=yes) ' CopyBuffer$ \ ! Get his answer
            if CopyBuffer$<>'' and find(lowercase$(CopyBuffer$),'y')<>1
            then
                CloseSourceFile(CopyName$)
                exit subroutine
            fi
        fi
    fi
    syscall #SourceFile, SCGetParams$,'',CopyBuffer$
    SourceNBPS=0
    for i=1 to 2 do SourceNBPS=SourceNBPS*256+CopyBuffer$(i)
    !D!print 'CF: SourceNBPS='; SourceNBPS
    if SourceIsDiskOnly
    then
        NSPT=0 \ NTPC=0 \ NCYL=0
        for i=3 to 4 do NSPT=NSPT*256+CopyBuffer$(i)
        for i=5 to 6 do NTPC=NTPC*256+CopyBuffer$(i)
        for i=7 to 8 do NCYL=NCYL*256+CopyBuffer$(i)
        filesize=SourceNBPS*NSPT*NTPC*NCYL
    else filesize=getfilesize(SourceFile)
    !D!print 'CF: filesize=';filesize
    if not DestinationIsDiskOnly
    then
        CFDelete=(CopyStyle=CopyOver)
        if CopyStyle=CopyTo
        then
            if error when open #DestinationFile, newfilename$(TargetName$)
            then
                if err<>ErrFilenotfound
                then
                    CopyBuffer$=newfilename$(TargetName$)
                    ReportErrorOccuredWhile('open ' cat CopyBuffer$)
                    CloseSourceFile(CopyName$)
                    exit subroutine
                fi
            else
                CloseDestinationFile(TargetName$)
                if NonStopMode
                then
                    ! this seemingly ridiculous statement forces an error
                    ! and then traps it, so that err will contain the error
                    ! we wish to report
                    if error when error ErrFilealreadyexists then ! do nothing
                    Reporterror(DestinationDisk$ cat TargetName$ cat...
&                       ' not copied')
                else
                    print 'Copy ';SourceDisk$;CopyName$;' over ';...
&                       DestinationDisk$;TargetName$;'? (<CR>=no) ';
                    input "" answer$ \ ! Get his answer
                    if SourceDisk$=DestinationDisk$ and CopyName$=TargetName$
                    then
                        CloseSourceFile(CopyName$)
                        exit subroutine
                    fi
                    CFDelete=(find(lowercase$(answer$),'y')=1)
                fi
                if not CFDelete
                then
                    CloseSourceFile(CopyName$)
                    exit subroutine
                fi
            fi
        fi
        CFDeleted=false
        if CFDelete
        then
            if error when delete newfilename$(TargetName$)
            then
                if err<>ErrFilenotfound
                then
                    CopyBuffer$=newfilename$(TargetName$)
                    ReportErrorOccuredWhile('delete ' cat CopyBuffer$)
                    CloseSourceFile(CopyName$)
                    exit subroutine
                fi
            else CFDeleted=true
        fi
        if CopyStyle=CopyInto
        then
            if error when open #DestinationFile, newfilename$(TargetName$)
            then
                if err<>ErrFilenotfound
                then
                    CopyBuffer$=newfilename$(TargetName$)
                    ReportErrorOccuredWhile('open ' cat CopyBuffer$)
                    CloseSourceFile(CopyName$)
                    exit subroutine
                else
                    if error when
                        CreateFile(TargetName$,filesize)
                    then
                        CopyBuffer$=newfilename$(TargetName$)
                        if err<>abortfilecopy
                        then ReportErrorOccuredWhile(...
&                           'create ' cat CopyBuffer$)
                        CloseSourceFile(CopyName$)
                        exit subroutine
                    fi
                fi
            else setfilesize(0) \ ! copy into existing file
        else
            if error when CreateFile(TargetName$,filesize)
            then
                CopyBuffer$=newfilename$(TargetName$)
                if err<>abortfilecopy
                then ReportErrorOccuredWhile('create ' cat CopyBuffer$)
                CloseSourceFile(CopyName$)
                exit subroutine
            fi
        fi
    fi
    if not ExceptQueryUser or CopyName$<>TargetName$
    then
        print 'Copying '; SourceDisk$;CopyName$;
        if CopyStyle=CopyOver and CFDeleted then print ' over ';
        elseif CopyStyle=CopyInto then print ' into ';
        else print ' to ';
        print DestinationDisk$;TargetName$
    fi
    deleteabortedfile=false \ ! assume we don't delete target file
    if error when
        CopyBytes(TargetName$)
        CloseSourceFile(CopyName$)
        CloseDestinationFile(TargetName$)
    then
        !D!print 'CF: err='; err
        if err=AbortBackup
        then
            deleteabortedfile=true
            deletefile(targetname$)
            abnormaltermexit
        elseif err=AbortFileCopy
        then call deletefile(targetname$)
        else error
    fi
    exit subroutine
end
!^L
Subroutine CopyCheck(CCName$)
    dim CCmatched
    if ExceptOption
    then
        ExceptNamePtr=0
        ExceptAtName$=''
        CCmatched=false
        while GetNextName(ExceptNames$,ExceptName$,ExceptNamePtr,...
&           ExceptAtFile,ExceptAtName$) do
            CCmatched=CCmatched or MatchWildCardTo(ExceptName$,CCName$)
        end
        if not CCmatched then call CopyFile(CCName$) fi
    else call CopyFile(CCName$)
    exit subroutine
end

Subroutine CopyNonStarFiles
    ! Copies all non-star file names, compressing the list
    CNSi=0
    SourceNamePtr=0
    while SourceNamePtr<len(SourceNames$) and...
&       SourceNames$(SourceNamePtr+1)<>'' do
        SourceNamePtr=SourceNamePtr+1
        SourceName$=SourceNames$(SourceNamePtr)
        if find(SourceName$,'@')=1
        then
            ! It's an at file
            open #SourceAtFile, SourceDisk$ cat right$(SourceName$,2)
            CNSi=CNSi+1 \ ! Leave this name in the SourceName list
            SourceNames$(CNSi)=SourceName$
            CNSStarFile=false
            input #SourceAtFile, SourceName$
            while not eof(SourceAtFile) and SourceName$<>'' do
                SourceName$=uppercase$(SourceName$)
                if find(SourceName$,'*')
                then CNSStarFile=true
                else call CopyCheck(SourceName$)
                input #SourceAtFile, SourceName$
            end
            close #SourceAtFile
            ! Remove at files from the list if they contain no star files
            if not CNSStarFile then CNSi=CNSi-1 fi
        else
            ! It's not an atfile
            if find(SourceName$,'*')
            then
                CNSi=CNSi+1 \ ! Leave this name in the SourceName list
                SourceNames$(CNSi)=SourceName$
            else call CopyCheck(SourceName$)
        fi
    end
    if CNSi<len(SourceNames$) then SourceNames$(CNSi+1)='' \ ! Mark list end
    exit subroutine
end

Subroutine readdirectoryentry
    directorypointer=directorypointer+maxlen(DirectoryRec$)
    repeat
        if error when
            read #DirectoryFile @directorypointer-maxlen(directoryrec$),...
&               DirectoryRec$
        then
            ReportErrorOccuredWhile('read a directory entry')
            input 'Retry or skip this entry (r or s, <CR>=Retry)? ' answer$
            if find(lowercase$(answer$),'s')
            then
                print 'Directory entry skipped'
                RunTimeErr=true
                exit subroutine
            fi
        else exit subroutine
    end
end

Subroutine CopyStarFiles
    opendirectory:...
&   repeat
        if error when open #DirectoryFile, SourceDisk$ cat 'directory.sys'
        then
            ReportErrorOccuredWhile('open DIRECTORY.SYS')
            input 'Retry? (y or n, <CR>=Retry)? ' answer$
            if find(lowercase$(answer$),'n') then exit subroutine fi
        else exit opendirectory
    end
    directorypointer=0
    readdirectoryentry
    while not eof(DirectoryFile) do
        if (DirectoryRec$(Nclusters)=0 and DirectoryRec$(Nclusters+1)=0)...
&           or DirectoryRec$(HCSIC)=0
        then ! Skip this (deleted) directory entry
        else
            SourceNamePtr=0
            SourceAtName$=''
            while GetNextName(SourceNames$,SourceName$,SourceNamePtr,...
&               SourceAtFile,SourceAtName$) do
                if find(SourceName$,'*')<>0
                then
                    CSFi=find(DirectoryRec$(1,16),' ')
                    if CSFi=0 then CSFi=17
                    if MatchWildCardTo(SourceName$,DirectoryRec$(1,CSFi-1))
                    then call CopyCheck(DirectoryRec$(1,CSFi-1)) fi
                fi
            end
        fi
        readdirectoryentry
    end
    close #DirectoryFile
    exit subroutine
end
!^L
!
!                                       src  dst
!                                       disk disk
!source spec         destination spec   note note legal map
!-----------------------------------------------------------
!diskdevice:         diskdevice:       ! 2  ! 3  ! yes !yes!
!diskdevice:         *                 !    !    ! no  ! x !
!diskdevice:         singlefile        ! 1  !    ! yes ! no!
!-----------------------------------------------------------
!singlefile          diskdevice:       !    ! 4  ! yes !yes!
!singlefile          *                 !    !    ! yes ! no!
!singlefile          singlefile        !    !    ! yes ! no!
!-----------------------------------------------------------
!wildcardspec        diskdevice:       ! 5  !    ! no  ! x !
!wildcardspec        *                 !    !    ! yes ! no!
!wildcardspec        singlefile        ! 5  !    ! no  ! x !
!-----------------------------------------------------------
!
!Notes:
!
!1. If error when open 'boot.sys'
!   then set map to :0001
!
!2. If error when open 'boot.sys'
!   then set map to :0003
!
!3. If map specified
!   then use specified map
!   else use same map as source disk
!
!4. If map specified
!   then use specified map
!   else
!      if boot checksum is ok on source disk
!      then use mapalgorithm from 'boot.sys' on source disk
!      else use mapalgorithm :0001
!   fi
!
!5. Give error 'Concatenation copies not allowed'
!

Subroutine GetDiskMap
    position #BootFile, BootMapAlgorithm
    read #BootFile, TwoByteString$
    ! NewMapAlgorithm= map from boot sector
    NewMapAlgorithm= TwoByteString$(1)**8 + TwoByteString$(2)
    !D!print 'get diskmap=';NewMapAlgorithm
    exit subroutine
end

Subroutine CopyDisk
    sdosdisk=false
    if SourceIsDiskOnly
    then
        open #SourceFile, SourceDisk$
        ! Dismount the source disk, no trapping
        syscall #SourceFile, CCDismountDisk$
        if DestinationIsDiskOnly
        then
            open #DestinationFile, DestinationDisk$
            ! dismount the destination disk, no trapping
            syscall #DestinationFile, CCDismountDisk$
            ! unlock the disk so we can write on it
            syscall #DestinationFile, CCUnlockDisk$
            ! Source and destination are disks only
            if error when open #BootFile, SourceDisk$ cat 'boot.sys'
            then
                if err<>ErrWrongfilesystem then error
                ! It's not an SDOS disk, don't allow map specification
                if UsingMapAlgorithm
                then
                    print 'Mapalgorithm specification not allowed when';...
&                         ' source disk is not an SDOS disk'
                    badcommandexit
                else
                    ! Best guess as to what might work well
                    NewMapAlgorithm=:0003
                    ! set source map to NewMapAlgorithm
                    SetDiskMap(SourceFile)
                fi
            else
                ! It's an SDOS disk
                sdosdisk=true
                ! Use source disk map unless mapalgorithm is specified
                if not UsingMapAlgorithm
                then
                    ! read boot sector, boot checksum already verified by SDOS
                    GetDiskMap
                fi
                close #BootFile
            fi
            ! set destination map to NewMapAlgorithm
            SetDiskMap(DestinationFile)
        else
            ! Source is disk, destination is a single file
            if error when open #BootFile, SourceDisk$ cat 'boot.sys'
            then
                ! It's not an SDOS disk
                NewMapAlgorithm=:0001
                ! set source map to NewMapAlgorithm
                SetDiskMap(SourceFile)
            else
                ! It's an SDOS disk, source map is set by the 'open'
                close #BootFile
            fi
        fi
        CopyFile('') \ ! Disk to disk copy
    else
        ! Source is not a disk only
        if SourceIsSingleFile and DestinationIsDiskOnly
        then
            ! source is single file and destination is a disk
            open #DestinationFile, DestinationDisk$
            ! dismount the destination disk, no trapping
            syscall #DestinationFile, CCDismountDisk$
            ! unlock the disk so we can write on it
            syscall #DestinationFile, CCUnlockDisk$

            ! see if the source is an sdos disk image
            open #BootFile, SourceDisk$ cat SourceNames$(1)
            position #BootFile, BootDiskInfo
            checksum=0
            for i=BootdiskInfo to BootCheckSum do
                read #BootFile, OneByteString$
                checksum=checksum + OneByteString$(1)
            end
            ! if boot checksum ok and size of source file is not too small
            sdosdisk=(Checksum&:FF=:FF) and not eof(BootFile)            
            if not UsingMapAlgorithm
            then
                ! Map not specified, read boot sector to find map
                if sdosdisk then call GetDiskMap
                else NewMapAlgorithm=:0001 \ ! It's not an SDOS disk
            fi
            close #BootFile

            ! set destination map to NewMapAlgorithm
            SetDiskMap(DestinationFile)
            CopyFile(SourceNames$(1)) \ ! file to disk copy
        fi
    fi
    if sdosdisk
    then
        ! destination is an sdos disk, set map in boot sector
        open #bootfile, DestinationDisk$
        syscall #bootfile, CCUnlockDisk$
        position #bootfile, bootmapalgorithm
        ! len of twobytestring is set by getdiskmap
        twobytestring$(1)=newmapalgorithm**-8
        twobytestring$(2)=newmapalgorithm&:ff
        write #bootfile, twobytestring$
        position #bootfile, bootdiskinfo
        checksum=0
        for i=bootdiskinfo to bootchecksum-1 do
            read #bootfile, onebytestring$
            checksum=checksum+onebytestring$(1)
        end
        ! len onebytestring$ is set by checksum loop above
        onebytestring$(1)=com(checksum)&:ff
        write #bootfile, onebytestring$
    fi
    exit subroutine
end
!^L
SDOSDISKBACKUP: ! Command line parsing
    if maxlen(copybuffer$)<128
    then print 'Copy buffer too small' \ abnormaltermexit

    if col(0)>1
    then
        input "" CopyBuffer$ \ ! Get command line
        print Version$
    else
        print Version$
        print "SdosDiskBackup accepts command line input only."
        print "Please refer to user's manual for command options and format."
        badcommandexit
    fi

    !D!print 'maxlen copybuffer= '; maxlen(CopyBuffer$)
    ! constructdynamicstring ensures even number of bytes
    halfbuffer=maxlen(copybuffer$)/2
    !D!print 'halfbuffer= '; halfbuffer

    CopyBuffer$=uppercase$(CopyBuffer$) \ ! Get rid of stupid keyboard shifts
    ! Rip out all the tab characters.
    for i=1 to len(CopyBuffer$) do if CopyBuffer$(i)=:9
        then CopyBuffer$(i)=asc(" ")

    ! Deblank(CopyBuffer$) \ ! ExtractPathName does this
    SourceDisk$=ExtractPathName$(CopyBuffer$)
    if SourceDisk$="" then SourceDisk$="DISK:" \ ! use standard name

    if ExtractWildCards(CopyBuffer$,SourceNames$)
    then Print "Too many source filenames" \ badcommandexit
    SourceIsDiskOnly=SourceNames$(1)='' \ ! True if no filenames
    SourceIsSingleFile=(not SourceIsDiskOnly) and (SourceNames$(1)<>'') and...
&       (not find(SourceNames$(1),'*')) and (SourceNames$(2)='')
    Goto ExtractQualifiers
!^L
QualifierIsIllegal:
    Print "No qualifier allowed when source is disk device" \ badcommandexit

DuplicateQualifier:
    Print "Qualifier specified more than once" \ badcommandexit

ExtractQualifiers: ! Pick up qualifiers EXCEPT, CHANGED, or AFTER
    Deblank(CopyBuffer$)
    if find(CopyBuffer$,Except$)=1
    then
        if SourceIsDiskOnly then QualifierIsIllegal
        if ExceptOption or ExceptQueryUser then DuplicateQualifier
        ExceptOption=true
        CopyBuffer$=Right$(CopyBuffer$,len(Except$)+1)
        Deblank(CopyBuffer$)
        if ExtractWildCards(CopyBuffer$,ExceptNames$)
        then Print "Too many exception filenames" \ badcommandexit
        j=1
        for i=1 to len(ExceptNames$)
            ! Hunt for "?" used as file name
            if ExceptNames$(i)="?" then ExceptQueryUser=true \ ! Ask the user
            else
                if ExceptQueryUser then ExceptNames$(j)=ExceptNames$(i)
                j=j+1
            fi
        end
        if ExceptNames$(1)='' then ExceptOption=false
        if ExceptQueryUser then ExceptNames$(j)="" \ ! Install new endlist mark
        Goto ExtractQualifiers
    fi

    if find(CopyBuffer$,Changed$)=1
    then
        if SourceIsDiskOnly then QualifierIsIllegal
        if ChangedOption then DuplicateQualifier
        ChangedOption=true
        CopyBuffer$=Right$(CopyBuffer$,len(Changed$)+1)
        Goto ExtractQualifiers
    fi
!^L
    if find(CopyBuffer$,Before$)=1
    then
        if SourceIsDiskOnly then QualifierIsIllegal
        if BeforeDateOption then DuplicateQualifier
        BeforeDateOption=true
        CopyBuffer$=Right$(CopyBuffer$,len(Before$)+1)
        ExtractDate(CopyBuffer$,BeforeDate)
        Goto ExtractQualifiers
    fi

    if find(CopyBuffer$,After$)=1
    then
        if SourceIsDiskOnly then QualifierIsIllegal
        if AfterDateOption then DuplicateQualifier
        AfterDateOption=true
        CopyBuffer$=Right$(CopyBuffer$,len(After$)+1)
        ExtractDate(CopyBuffer$,AfterDate)
        Goto ExtractQualifiers
    fi
!^L
    ! Process NONSTOP keyword
    NonStopMode=(find(CopyBuffer$,Nonstop$)=1)
    if NonStopMode then CopyBuffer$=Right$(CopyBuffer$,len(Nonstop$)+1)

    ! Process TO/OVER specification
    Deblank(CopyBuffer$)
    if find(CopyBuffer$,To$)=1
    then
        CopyStyle=copyto \ ! Don't step on any files on target
        CopyBuffer$=Right$(CopyBuffer$,len(To$)+1)
    elseif find(CopyBuffer$,Over$)=1
    then
        CopyStyle=CopyOver \ ! Replace target files if they exist
        CopyBuffer$=Right$(CopyBuffer$,len(Over$)+1)
    elseif find(CopyBuffer$,Into$)=1
    then
        CopyStyle=CopyInto \ ! copy into same file
        CopyBuffer$=Right$(CopyBuffer$,len(Into$)+1)
    else
        Print "Must have either TO, OVER, or INTO option specified"
        badcommandexit
    fi

    ! Pick up destination specification
    ! Deblank(CopyBuffer$) \ ! ExtractPathName does this
    DestinationDisk$=ExtractPathName$(CopyBuffer$)
    if ExtractWildCards(CopyBuffer$,DestinationNames$)
    then Print "Only one destination file allowed" \ badcommandexit
    DestinationIsDiskOnly=DestinationNames$(1)=''
    if DestinationDisk$=''
    then
        if DestinationIsDiskOnly
        then
            print 'Illegal destination specification. Must be'
            print "a single file, a '*' file, or a DISK device."
            badcommandexit
        else DestinationDisk$='DISK:'
    fi
    DestinationIsStarFile=DestinationNames$(1)='*'
    if DestinationIsStarFile then DestinationNames$(1)=''
    DestinationIsSingleFile=(not DestinationIsDiskOnly) and...
&       (DestinationNames$(1)<>'') and...
&       (not find(DestinationNames$(1),'*'))

    ! Do legal checks
    if SourceIsDiskOnly and DestinationIsStarFile
    then print "DISK device to/over/into '*' is not allowed" \ badcommandexit
    if not SourceIsDiskOnly and not SourceIsSingleFile and...
&       (DestinationIsDiskOnly or DestinationIsSingleFile)
    then print 'Concatenation copies not allowed' \ badcommandexit

    ! Get the map algorithm
    Deblank(CopyBuffer$)
    if find(CopyBuffer$,Using$)=1
    then
        i=len(Using$)+1
        while find(right$(CopyBuffer$,i),' ')=1 do i=i+1 \ ! Deblank
        if find(right$(CopyBuffer$,i),MapAlgorithm$)=1
        then
            CopyBuffer$=right$(CopyBuffer$,i+len(MapAlgorithm$))
            UsingMapAlgorithm=true
            if not DestinationIsDiskOnly or...
&               (not SourceIsDiskOnly and not SourceIsSingleFile)
            then
                print "'Using Mapalgorithm' specification allowed only when";
                print ' the destination'
                print 'is a DISK device and the source is a DISK device or';
                print ' a single file.'
                badcommandexit
            fi
            if error when NewMapAlgorithm=Ival(CopyBuffer$)
            then Print "Illegal MapAlgorithm specification" \ badcommandexit
        fi
    else UsingMapAlgorithm=false

    ! Anything left?
    Deblank(CopyBuffer$)
    if CopyBuffer$<> ''
    then print "'"; CopyBuffer$; "' at end of command line" \ badcommandexit

    if not NonStopMode
    then
        if error when syscall IsConsole$
        then
            if err=ErrNotopentoconsole
            then
                ! channel 0 is not opened to the console, do file assumed
                print 'Nonstop mode assumed'
                NonStopMode=true
            else error
        fi
    fi

    if DestinationIsDiskOnly
    then
        print 'Writing on the DISK device can damage the file structure.'
        input 'Are you sure you want to write on the DISK device? ' CopyBuffer$
        if lowercase$(CopyBuffer$)<>'yes'
        then call abnormaltermexit \ ! program terminates abnormally
    fi

!^L
! **** Command line is parsed. Do the Backup ***
!   print '***************************************************'
!
!   for i=1 to 10 do
!       if SourceNames$(i)='' and (not(ExceptOption) or ExceptNames$(i)='')
!       then exit i
!       print i, "'"; SourceNames$(i); "'";
!       if ExceptOption then print tab(40); "'"; ExceptNames$(i); "'";
!        print
!   end
!
!   print "SourceDisk$= '"; SourceDisk$; "'  SourceIsDiskOnly= ";
!   if SourceIsDiskOnly then print 'true'; else print 'false';
!   print '.  SourceIsSingleFile= ';
!   if SourceIsSingleFile then print 'true.' else print 'false.'
!
!   print 'ExceptOption= ';
!   if Exceptoption then print 'true'; else print 'false';
!   print '.  ExceptQueryUser= ';
!   if ExceptQueryUser then print 'true.' else print 'false.'
!
!   print 'ChangedOption= ';
!   if ChangedOption then print 'true.' else print 'false.'
!
!   print 'BeforeDateOption= ';
!   if BeforeDateOption then print 'true.  BeforeDate='; BeforeDate
!   else print 'false.'
!   print 'AfterDateOption= ';
!   if AfterDateOption then print 'true.  AfterDate='; AfterDate
!   else print 'false.'
!
!   print 'NonStopMode= ';
!   if NonStopMode then print 'true.' else print 'false.'
!
!   print 'CopyOver= ';
!   if CopyOver then print 'true.' else print 'false.'
!
!   print "DestinationDisk$= '";DestinationDisk$;"'  DestinationIsDiskOnly= ";
!   if DestinationIsDiskOnly then print 'true.' else print 'false.'
!
!   print "DestinationNames(1)= '"; DestinationNames$(1); "'"
!
!   print 'DestinationIsStarFile= ';
!   if DestinationIsStarFile then print 'true'; else print 'false';
!   print '.  DestinationIsSingleFile= ';
!   if DestinationIsSingleFile then print 'true.' else print 'false.'
!
!   print 'UsingMapAlgorithm= ';
!   if UsingMapAlgorithm
!   then print 'true.  NewMapAlgorithm='; Hex$(NewMapAlgorithm)
!   else print 'false.'
!
!   print "Remaining input line= '"; CopyBuffer$; "'"
!   print '***************************************************'
    CheckFileNames
    if SourceIsDiskOnly or DestinationIsDiskOnly
    then call CopyDisk \ ! disk-disk, file-disk, or disk-file copy
    else
        if SourceNames$(1) <> '' then call CopyNonStarFiles
        if SourceNames$(1) <> '' then call CopyStarFiles
    fi
    if RunTimeErr then call abnormaltermexit
    exit subroutine
end

    call backup(constructdynamicstring$)
end
