Copyright 1984 by ABComputing July 15, 1984 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º Random File I/O º º º º by º º º º Bill Salkin º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Let me tell you a story before we discuss random I/O. A friend of mine owns a Sanyo 550 computer, which on the surface seems to be similar to the IBM-PC. The Sanyo 550 has an 8088 chip, and supports Microsoft DOS 1.25. His system has only one single-sided drive, so drive A and B are the same physical drive. On his Sanyo, the copy command A>copy SOURCE B:TARGET does NOT pause to allow disk-swapping, which makes copying a file from drive A to a new disk in drive B impossible. (The Sanyo CAN copy single files from physical drive A to physical drive B, but two physical drives are required.) I knew that my next few nights would be occupied remedying this problem. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Program COPYKAT ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ I wrote a copy program that copies a single file into memory (the Sanyo does have memory, thank goodness), pauses for the user to swap disks, and finally writes the file to a diskette. This program, called COPYKAT, has been cleaned up and is used to discuss random file I/O in this column. (Incidentally, my program worked so well that my friend decided to buy a second disk drive. Win some, lose some.) It may seem natural to sequentially read the SOURCE file (the first filespec on the command line), but I actually used random-access methods. (The reason is given later.) In sequential file-reading, records are read in a consecutive manner: if the first record was read, then the second record is read next. In random I/O, records are accessed in a random manner; reading the first record does not imply any intention to read the second record. It is assumed from this point forward that you have either read last month's "DOS 1.1, 2.0 Sequential File-reading" article or have acquired the same information at the knee of a master. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Get the Filespec Out! ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The program COPYKAT is an .EXE module. When an .EXE module is invoked the DS and ES registers point to the Program Segment Prefix (PSP). When a DOS command is invoked, DOS creates two FCBs (File Control Blocks), assuming that two filespecs will follow the command name. This assumption is correct for COPYKAT: A>COPYKAT SOURCE B:TARGET In COPYKAT's data segment an FCB called IN_FCB is reserved for the SOURCE file. The FCB is declared by: IN_FCB DB 37 DUP(0) Similarly, OUT_FCB is reserved for the TARGET file. When COPYKAT is invoked, DOS creates an FCB for SOURCE at offset 05CH into the PSP, and an FCB for TARGET at offset 06CH into the PSP. The SOURCE filespec at PSP:5CH is copied into IN_FCB by: ;--MOVE FIRST FILESPEC (SOURCE) IN COMMAND LINE TO IN_FCB. MOV AX,DATA ;ES -> DATA MOV ES,AX MOV DI,OFFSET IN_FCB ;ES:DI ->IN_FCB ;--DS -> PSP MOV CX,12 ;12 BYTES FOR FILESPEC MOV SI,05CH ;DS:SI = PSP + 5C = FIRST FILE NAME REP MOVSB ;MOV CX BYTES FROM DS:SI -> ES:DI The MOVSB instruction moves one byte from the address pointed to by DS:SI (SOURCE filespec) to the address pointed to by ES:DI (IN_FCB). The REP (repeat) instruction causes the MOVSB instruction to be executed CX times, that is, twelve times as CX contains 12. Thus, the REP instruction copies the 12 byte filespec from PSP:5C to IN_FCB. Similarly, the FCB for the TARGET file, called OUT_FCB, is filled with the filespec of the TARGET file. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Standard Linkage to DOS ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Next, COPYKAT performs the standard linkage to DOS. (A sacred ritual enjoyed by native tribes and programmers.) ;--STANDARD LINKAGE TO DOS ASSUME DS:DATA PUSH DS ; DO SUB AX,AX ; PUSH AX ; NOT MOV AX,DATA ; MOV DS,AX ; ALTER! This section of code does not appear at the beginning of COPYKAT, for the filespecs needed are available at offsets into the PSP, and the DS and ES registers initially point to this area. After copying the filespecs into our data area, the PSP is no longer needed, and standard linkage conventions are then used to establish the addressability of our program's data segment. These conventions point the DS register to our data segment and prepare the stack for a far return to DOS. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Start of Free Memory ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Before reading the SOURCE file into memory we must answer several questions. First, we must determine a "safe" area of RAM to read SOURCE into. If we are not careful, COPYKAT can overlay itself with the SOURCE file, almost certainly requiring a HARD system reset (power off). A simple approach is to find the start of "free memory," and place the first byte of SOURCE at this location. By free memory I mean an area in memory above the COPYKAT program. (I refer to it as free memory only because we are using it freely; any data stored there is destroyed.) Sound the trumpets! IBM to the rescue. The MEMORY combine-type in a segment definition supposedly places that segment above all other segments in memory when the program is executed. This MEMORY segment points to the start of free memory and is just what we need. Unfortunately, it never worked that way for me. (Sounds like potential for another article.) The MEMORY segment did not "rise" to the occasion, and so another trick was required. To simulate a MEMORY segment, we must find the highest addressed segment in our module. The LINKER, upon special request, creates a file called a LINK.MAP containing, among other items, the ordering of segments in a module. With the source code as shown below, the link map reveals that the stack segment is the highest segment in our module. As this segment occupies 256 bytes, to find the start of free memory we simply add 256 to the starting address in the SS register. The SOURCE file will be read into this area, called IO_AREA in COPYKAT. ;--DETERMINE FIRST BYTE OF FREE MEMORY. THIS WILL BE USED AS THE DTA AREA ; AND IS STORED IN IO_AREA (IN SEGMENT NUMBERS). MOV AX,SS ADD AX,10H ;STACK LENGTH = 10H * 16. MOV IO_AREA,AX ;IO_AREA POINTS TO FIRST BYTE OF FREE MEMORY. Notice that we add only 10H = 16 decimal to the AX register, for later the value in IO_AREA will be placed into the DS register. This multiplies the value in IO_AREA by 10H, and 10H multiplied by 10H = 100H = 256 decimal, which is the length of the stack segment. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Set Up the DTA ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ All transfers to and from a disk pass through an intermediate buffer area called the disk transfer area (DTA). We make IO_AREA the DTA by using DOS function-call 1AH, to insure that the file is read into free memory. MOV AX,IO_AREA ;POINT DS:DX -> AREA FOR DTA MOV DS,AX MOV DX,0 MOV AH,1AH ;REQUEST "SET DTA" SERVICE INT 21H ;INVOKE "SET DTA" SERVICE ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Check for TARGET File ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ If the TARGET file exists, then it is deleted and recreated as a zero-length file. This is necessary because DOS gladly writes the number of records we specify to the old TARGET file. If this number is less than the number of records in the old TARGET file, the tail end of the old TARGET file remains. Notice that deleting and then recreating a file changes the time and date stamps on the file, and COPYKAT does not bother to save this information! Perhaps this is what drove my friend to purchase another disk drive. (I knew the program wasn't foolproof, anyway.) You will certainly want to save the time and date stamps prior to deleting the file and readjust the appropriate fields in the new zero-length TARGET file. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Record-length Field ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ COPYKAT reads one byte from SOURCE into IO_AREA (the start of free memory) with each read operation. This is indicated by setting the record-length field (offset 14 bytes into the FCB) to 1. We must also adjust the pointer for the TARGET file. COPY2: MOV WORD PTR IN_FCB+14,1 ;RECORD LENGTH FIELD OF IN_FCB MOV WORD PTR OUT_FCB+14,1 ;RECORD LENGTH FIELD OF OUT_FCB As random record 0 corresponds to the beginning of a file, to read from the beginning of SOURCE we place a 0 in the random record field of IN_FCB. MOV WORD PTR IN_FCB+33,0 ;SET UPPER RANDOM RECORD NUMBER MOV WORD PTR IN_FCB+35,0 ;SET LOWER RANDOM RECORD NUMBER MOV WORD PTR OUT_FCB+33,0 ;SET UPPER RANDOM RECORD NUMBER MOV WORD PTR OUT_FCB+35,0 ;SET LOWER RANDOM RECORD NUMBER Notice that the random record number field occupies TWO words. Thus, an extremely large number of records can be read, and this is the reason I chose random I/O over sequential I/O. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Reading the Entire File ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Here is another hardware side-effect. As IO_AREA is a pointer to a segment, we are restricted to reading files that are <= 64K in size, unless we perform some trickery. Obviously such an arrangement is not a happy one for files larger than 64K. One solution is to determine the number of 64K blocks occupied by the file and use this as a loop counter for the number of 64K byte reads to perform. The file size field in an FCB occupies two words, with the high-order word at an offset of 18 bytes from the FCB start. high-order word low-order word ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ 000000000000000a ³ xxxxxxxxxxxxxxxxx ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Notice that the numeric value of the high-order part ("a") is 16 a * 2 = a * 65536 = a * 64K In other words, the number in the high-order word of the file size actually represents the number of 64K blocks in the file. What luck! Here are the particulars: ;--DETERMINE IF SIZE OF SOURCE FILE >64K MOV AX,WORD PTR IN_FCB+18 ;HIGH-ORDER PART OF FILE SIZE CMP AX,0 ;AX = 0 MEANS FILE SIZE < 64K JNE COPY3 MOV CX,1 JMP COPY4 COPY3: MOV CX,AX ;USE HIGH-ORDER PART AS LOOP COUNTER INC CX ;ACCOUNT FOR INITIAL 64K To perform the read/write operation, we use the Random-block read DOS function-call 27H. This call reads the number of records specified in the CX register (which is later adjusted to be 64K bytes) into IO_AREA, which is the start of free memory. In this manner, a segment of data is read into free memory. This function call returns the number of bytes read, and this information is directly fed to the write function-call so the proper amount of data is written. No special handling is required for the EOF marker. Finally, the Random-block read updates the random-record field to point to the next record in SOURCE, and we are prepared for the next read/write cycle. It's as simple as that. ;--PERFORM THE READ/WRITE OPERATION. COPY4: PUSH CX ;SAVE NUMBER OF 64K BYTE SEGMENTS IN FILE ;--RANDOM READ OF IN_FCB MOV CX,0FFFFH ;READ 64K (MAX) BYTES FROM FILE MOV AH,27H ;REQUEST "RANDOM BLOCK READ" SERVICE MOV DX,OFFSET IN_FCB INT 21H ;INVOKE "RANDOM BLOCK READ" SERVICE ;--RANDOM WRITE TO OUT_FCB MOV AH,28H ;REQUEST "RANDOM BLOCK WRITE" SERVICE MOV DX,OFFSET OUT_FCB INT 21H ;INVOKE "RANDOM BLOCK WRITE" SERVICE CMP AL,1 ;AL = 1 IF INSUFFICIENT DISK SPACE. JE COPY5 POP CX ;RESTORE NUMBER OF 64K BYTES IN FILE SIZE LOOP COPY4 ; FOR LOOP OPERATION ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Conclusion ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ That's COPYKAT. It has many limitations, such as being unable to perform wildcard copies, or copies such as copy B:SOURCE D: But these are relatively minor points that you can correct if you are interested. And this is the spirit in which I present source code. The heart of the code is presented, and if you are intrigued I hope that you will provide the left and right ventricles. COPYKAT would be much easier to implement under DOS 2.x than DOS 1.x, but my friend's most current release was DOS 1.25. I may feel guilty one night and redo this article using only DOS 2.x function calls. But don't hold your breath. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Program COPYKAT ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ CR EQU 10 ;CARRIAGE RETURN = ASCII 10 LF EQU 13 ;LINEFEED = ASCII 13 ;------------------------------------------------------------------------ DATA SEGMENT PARA IO_AREA DW 0 ;POINTER TO START OF FREE MEMORY IN_FCB DB 37 DUP(0) ;FCB FOR SOURCE FILE OUT_FCB DB 37 DUP(0) ;FCB FOR TARGET FILE MSG1 DB CR,LF,'Source file not found. Copy aborted.',CR,LF,'$' MSG2 DB CR,LF,'Directory full. Copy aborted.',CR,LF,'$' MSG3 DB CR,LF,' 1 File Copied',CR,LF,'$' MSG4 DB CR,LF,'Insufficient Disk Space. Copy aborted.',CR,LF,'$' MESSAGE_OPTION DB 0 ;INDICATES WHICH MSGI WILL BE DISPLAYED DATA ENDS ;------------------------------------------------------------------------ ;------------------------------------------------------------------------ STACK SEGMENT PARA STACK 'STACK' DB 40H DUP('STCK') STACK ENDS ;------------------------------------------------------------------------ ;------------------------------------------------------------------------ CODE SEGMENT PARA 'CODE' ASSUME CS:CODE, SS:STACK, ES:DATA COPYKAT PROC FAR ;--MOVE FIRST FILESPEC IN COMMAND LINE (SOURCE) TO IN_FCB. MOV AX,DATA ;ES -> DATA MOV ES,AX MOV DI,OFFSET IN_FCB ;ES:DI ->IN_FCB MOV CX,12 ;12 BYTES FOR FILESPEC MOV SI,05CH ;DS:SI = PSP + 5C = FIRST FILE NAME REP MOVSB ;MOV CX BYTES FROM DS:SI -> ES:DI ;--MOVE SECOND FILESPEC IN COMMAND LINE (TARGET) TO OUT_FCB. MOV DI,OFFSET OUT_FCB ;ES:DI ->OUT_FCB MOV CX,12 ;12 BYTES FOR FILESPEC MOV SI,06CH ;DS:SI = PSP + 6C = SECOND FILE NAME REP MOVSB ;MOV CX BYTES FROM DS:SI -> ES:DI ;--STANDARD LINKAGE TO DOS ASSUME DS:DATA PUSH DS ; DO SUB AX,AX ; PUSH AX ; NOT MOV AX,DATA ; MOV DS,AX ; ALTER! ;--DETERMINE FIRST BYTE OF FREE MEMORY. THIS WILL BE USED AS THE DTA AREA ; AND IS STORED IN IO_AREA (IN SEGMENT NUMBERS). MOV AX,SS ADD AX,10H ;STACK LENGTH = 256 BYTES MOV IO_AREA,AX ;IO_AREA POINTS TO FIRST BYTE OF FREE MEMORY. ;--MAKE THE DTA POINT TO IO_AREA PUSH DS MOV AX,IO_AREA ;POINT DS:DX -> AREA FOR DTA MOV DS,AX MOV DX,0 MOV AH,1AH ;REQUEST "SET DTA" SERVICE INT 21H ;INVOKE "SET DTA" SERVICE POP DS ;--OPEN IN_FCB. MOV AH,0FH ;REQUEST "OPEN FCB" SERVICE MOV DX,OFFSET IN_FCB INT 21H ;INVOKE "OPEN FCB" SERVICE CMP AL,0FFH ;AL = FFH IF FILE NOT FOUND JNE COPY1 ;--FILE NOT FOUND ABORT! MOV MESSAGE_OPTION,1 CALL MESSAGE CALL CLOSE_FCB RET ;--SOURCE FILE FOUND. DELETE DESTINATION FILE, IF IT ALREADY EXISTS. COPY1: MOV AH,13H ;REQUEST "DELETE FCB" SERVICE MOV DX,OFFSET OUT_FCB INT 21H ;INVOKE "DELETE FCB" SERVICE ;--RECREATE 0 LENGTH DESTINATION FILE MOV AH,16H ;REQUEST "CREATE FCB" SERVICE INT 21H ;INVOKE "CREATE FCB" SERVICE CMP AL,0FFH ;AH = FFH IF NO ROOM IN DIRECTORY JNE COPY2 ;--DIRECTORY FULL. ABORT! MOV MESSAGE_OPTION,2 CALL MESSAGE CALL CLOSE_FCB RET ;--PERFORM A RANDOM READ OF 1 BYTE, AT THE BEGINNING OF THE FILE. COPY2: MOV WORD PTR IN_FCB+14,1 ;RECORD LENGTH FIELD OF IN_FCB MOV WORD PTR OUT_FCB+14,1 ;RECORD LENGTH FIELD OF OUT_FCB MOV WORD PTR IN_FCB+33,0 ;SET UPPER RANDOM RECORD NUMBER MOV WORD PTR IN_FCB+35,0 ;SET LOWER RANDOM RECORD NUMBER MOV WORD PTR OUT_FCB+33,0 ;SET UPPER RANDOM RECORD NUMBER MOV WORD PTR OUT_FCB+35,0 ;SET LOWER RANDOM RECORD NUMBER ;--DETERMINE IF SIZE OF SOURCE FILE >64K MOV AX,WORD PTR IN_FCB+18 ;HIGH ORDER PART OF FILE SIZE CMP AX,0 ;AX = 0 MEANS FILE SIZE < 64K JNE COPY3 MOV CX,1 JMP COPY4 COPY3: MOV CX,AX ;USE HIGH ORDER PART AS LOOP COUNTER INC CX ;ACCOUNT FOR INITIAL 64K ;--PERFORM THE READ/WRITE OPERATION. COPY4: PUSH CX ;SAVE NUMBER OF 64K BYTE SEGMENTS IN FILE ;--RANDOM READ OF IN_FCB MOV CX,0FFFFH ;READ 64K (MAX) BYTES FROM FILE MOV AH,27H ;REQUEST "RANDOM BLOCK READ" SERVICE MOV DX,OFFSET IN_FCB INT 21H ;INVOKE "RANDOM BLOCK READ" SERVICE ;--RANDOM WRITE TO OUT_FCB MOV AH,28H ;REQUEST "RANDOM BLOCK WRITE" SERVICE MOV DX,OFFSET OUT_FCB INT 21H ;INVOKE "RANDOM BLOCK WRITE" SERVICE CMP AL,1 ;AL = 1 IF INSUFFICIENT DISK SPACE. JE COPY5 POP CX ;RESTORE NUMBER OF 64K BYTES IN FILE SIZE LOOP COPY4 ; FOR LOOP OPERATION ;--PRINT "1 FILE COPIED" MESSAGE MOV MESSAGE_OPTION,3 CALL MESSAGE CALL CLOSE_FCB RET ;--PRINT "INSUFFICIENT DISK SPACE" COPY5: POP CX ;REQUIRED AS CX WAS PUSHED IN READ/WRITE LOOP MOV MESSAGE_OPTION,4 CALL MESSAGE CALL CLOSE_FCB RET ;------------------------------------------------------------------------ ;------------------------------------------------------------------------ ;ROUTINE CLOSE_FCB ; ; BRIEF: ; CLOSES THE FCBs "IN_FCB" AND "OUT_FCB" ; ;------------------------------------------------------------------------ CLOSE_FCB PROC NEAR MOV AH,10H ;REQUEST "CLOSE FILE" SERVICE MOV DX,OFFSET IN_FCB INT 21H ;INVOKE "CLOSE FILE" SERVICE MOV AH,10H ;REQUEST "CLOSE FILE" SERVICE MOV DX,OFFSET OUT_FCB INT 21H ;INVOKE "CLOSE FILE" SERVICE RET CLOSE_FCB ENDP ;------------------------------------------------------------------------ ;------------------------------------------------------------------------ ;ROUTINE MESSAGE ; ; BRIEF: ; DISPLAYS VARIOUS MESSAGES. ; INPUT: ; MESSAGE_OPTION. ; OUTPUT: ; ONE OF MSG1, MSG2, MSG3, OR MSG4. ; ;------------------------------------------------------------------------ MESSAGE PROC NEAR CMP MESSAGE_OPTION,1 JE MES1 CMP MESSAGE_OPTION,2 JE MES2 CMP MESSAGE_OPTION,3 JE MES3 CMP MESSAGE_OPTION,4 JE MES4 RET ;INVALID OPTION, RETURN TO CALLER ;--PRINT "Source file not found. Copy aborted." MES1: MOV DX,OFFSET MSG1 JMP MES5 ;--PRINT "Directory full. Copy aborted." MES2: MOV DX,OFFSET MSG2 JMP MES5 ;--PRINT " 1 File Copied." MES3: MOV DX,OFFSET MSG3 JMP MES5 ;--PRINT "Insufficient Disk Space. Copy aborted." MES4: MOV DX,OFFSET MSG4 MES5: MOV AH,9 ;REQUEST "PRINT STRING" SERVICE INT 21H ;INVOKE "PRINT STRING" SERVICE RET MESSAGE ENDP ;------------------------------------------------------------------------ COPYKAT ENDP ;END OF PROCedure CODE ENDS ;END OF Segment END COPYKAT ;EXECUTE PROGRAM STARTING AT "COPYKAT" ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ File Name: ÛÛ dos2.txt ÛÛ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ