Writing To A DASD Device

Now that we can successfully read data it is time to look at writing.  There are two types of write commands, Write Format and Write Update.  The Write Update commands are used to update an existing block and include the Write Data and the Write Key and Data CCW commands.  The Write Count, Key and Data command is a formatting command.  It is used to add blocks to a track.  Use of this command may also result in the removal of blocks from a track.

When a block is added to a track any previously existing blocks following the point where the new block is added will be deleted.  For example if a track contained five blocks (block-1, block-2, block-3, block-4, block-5) and a Write CKD for block-3 is issued to the track the original blocks 3, 4, and 5 will be deleted and replaced with the new block-3.  The updated track will then contain only three blocks (block-1, block-2, and block-3).

As the name implies the Write CKD command will write the Count, Key and Data components of a record.  We must supple all three to the write command.  The Count Area is eight bytes and consists of the record address in the format CCHHR (5 bytes), a key length (1 byte), and the data length (2 bytes).  Immediately following the Count Area is the Key data (if the key length is non-zero).  The length of the key data is specified in the Count Area.  The record data follows the key data.  As with the key the length of the data is specified in the Count Area.  The total of the length of the Count, Key and Data is the length we specify as the CCW length.

Code To Write Data

It is important to remember that we are writing data and if not careful serious damage could be done by writing to the wrong dataset.  To minimize the chance of accidently destroying something I changed the DDNAME in the DCB to EXCPWRIT.  This serves as a reminder to me to not write over SYS1.LINKLIB or my source PDS.

Our initial program to write DASD blocks is EXCP06.  Much of the code is the same as in our previous examples.  Here we will just examine the code specific to this exercise.

         OPEN  (EXCPDCB,OUTPUT)

We have to specify OUTPUT on our OPEN macro. If we open the DCB in INPUT mode then write commands will be disabled in the Device Mask and all Write CCW Commands will fail.

         DS    0D                                      
CCWSRCH  DC    X'31',AL3(IOBSRCH),X'40',X'00',AL2(5)   
CCWTIC   DC    X'08',AL3(CCWSRCH),X'40',X'00',AL2(0)   
CCWWRITE DC    X'1D',AL3(CARDBUF1),X'40',X'00',AL2(88) 
         DC    X'1D',AL3(CARDBUF2),X'40',X'00',AL2(88) 
         DC    X'1D',AL3(CARDBUF3),X'40',X'00',AL2(88) 
         DC    X'1D',AL3(CARDBUF4),X'40',X'00',AL2(88) 
         DC    X'1D',AL3(CARDBUF5),X'00',X'00',AL2(88)

Here is our Channel Program we will use to write blocks. It is similar to our read Channel Programs. It begins with a Search ID Equal (for Record Zero) followed by a TIC. When then have a series of Write CKD CCWs to write 5 records. We are writing 80 byte records so our data length is 88 (80 bytes of Data + 8 bytes for the Count). These are non-keyed records (key length = 0). Note the Command Chain bit is set for all but the last write command.

*                C C H H     R       KL     DL             
CARDBUF1 DC    X'00000000',X'01',AL1(0),AL2(80),CL80'CARD1'
CARDBUF2 DC    X'00000000',X'02',AL1(0),AL2(80),CL80'CARD2'
CARDBUF3 DC    X'00000000',X'03',AL1(0),AL2(80),CL80'CARD3'
CARDBUF4 DC    X'00000000',X'04',AL1(0),AL2(80),CL80'CARD4'
CARDBUF5 DC    X'00000000',X'05',AL1(0),AL2(80),CL80'CARD5'

Here are the five data buffers for the records we will be writing. Each consists of 8 bytes for the Count and 80 bytes of data. For now the CCHH is set to zeros. When we calculate the actual Cylinder-Head address using the DEB extent data we will fill in these fields. Next is the record number followed by the key length and data length values. Finally we have the 80 bytes of data.

*                                                                      
***********************************************************************
*               WRITE 5 BLOCKS TO TRACK 0 WITH ONE I/O                 
***********************************************************************
*                                                                      
         LA    R1,0          RELATIVE TRACK NUMBER ZERO                
         BAL   R14,GETCCHH   CONVERT TO MBBCCHHR                       
         LTR   R15,R15            CHECK RETURN CODE                    
         BZ    WRITE010           - BRANCH IF GOOD                     
*                                                                      
WRITE000 DS    0H                                                      
         LOG   'TTR CONVERSION FAILED'                                 
         WTO   'TTR CONVERSION FAILED',ROUTCDE=(1,11)                  
         B     EXIT

We begin by calculating the actual Cylinder-Head address for relative track zero.

WRITE010 DS    0H                    
         MVC   IOBSEEK(8),MBBCCHHR   
         MVC   CARDBUF1(4),MBBCCHHR+3
         MVC   CARDBUF2(4),MBBCCHHR+3
         MVC   CARDBUF3(4),MBBCCHHR+3
         MVC   CARDBUF4(4),MBBCCHHR+3
         MVC   CARDBUF5(4),MBBCCHHR+3

We can then set the CCHH address into the IOB Seek field as well as in the Count Area of our I/O buffers.

         MVI   IOBSEEK+7,0        SEARCH FOR R0           
*                                                         
         XC    ECB,ECB            CLEAR ECB               
         EXCP  IOB                ISSUE I/O REQUEST       
*                                                         
         WAIT  1,ECB=ECB          WAIT FOR I/O TO COMPLETE
*                                                         
         LA    R14,EXIT                                   
         CLI   IOBECBAD,X'7F'     WAS IT SUCCESSFUL       
         BNE   PRINTIOB           GO FORMAT IOB AND EXIT

Since we want to write record number one we have to search for Record Zero. If the track is properly formatted it will always have a Record Zero which is not considered to be a data record. We can then issue the EXCP and wait for it to complete. If the EXCP request was not successful we print out the IOB status fields and exit.

***********************************************************************
*                WRITE 5 BLOCKS TO TRACK 1 ONE I/O                     
*                    REWRITE BLOCK 1 (WRITE CKD)                       
***********************************************************************
*                                                                      
         LA    R1,1          RELATIVE TRACK NUMBER ZERO                
         BAL   R14,GETCCHH   CONVERT TO MBBCCHHR                       
         LTR   R15,R15            CHECK RETURN CODE                    
         BNZ   WRITE000           - BRANCH IF FAIL                     
*                                                                      
         MVC   IOBSEEK(8),MBBCCHHR                                     
         MVC   CARDBUF1(4),MBBCCHHR+3                                  
         MVC   CARDBUF2(4),MBBCCHHR+3                                  
         MVC   CARDBUF3(4),MBBCCHHR+3                                  
         MVC   CARDBUF4(4),MBBCCHHR+3                                  
         MVC   CARDBUF5(4),MBBCCHHR+3                                  
*                                                                      
         MVI   IOBSEEK+7,0        SEARCH FOR R0                        
*                                                                      
         XC    ECB,ECB            CLEAR ECB                            
         EXCP  IOB                ISSUE I/O REQUEST                    
*                                                         
         WAIT  1,ECB=ECB          WAIT FOR I/O TO COMPLETE
*                                                         
         LA    R14,EXIT                                   
         CLI   IOBECBAD,X'7F'     WAS IT SUCCESSFUL       
         BNE   PRINTIOB           GO FORMAT IOB AND EXIT

We will now do the same thing for relative track 1 and write the same five records.

*                                                           
         MVI   CCWWRITE+4,0       CLEAR COMMAND CHAIN BIT   
         XC    ECB,ECB            CLEAR ECB                 
         EXCP  IOB                ISSUE I/O REQUEST         
*                                                           
         WAIT  1,ECB=ECB          WAIT FOR I/O TO COMPLETE  
*                                                           
         LA    R14,EXIT                                     
         CLI   IOBECBAD,X'7F'     WAS IT SUCCESSFUL         
         BNE   PRINTIOB           GO FORMAT IOB AND EXIT

Now we will write just one record to the same track. The Command Chain bit in the first Write CCW is cleared which will make it the last CCW in the Channel Program. This should result in only one record on the track since all previously existing records following the record being written are erased.

*                                                          
         LA    R1,2          RELATIVE TRACK NUMBER ONE     
         BAL   R14,GETCCHH   CONVERT TO MBBCCHHR           
         LTR   R15,R15            CHECK RETURN CODE        
         BNZ   WRITE000           - BRANCH IF FAIL         
*                                                          
         MVI   CCWWRITE+4,0            CLEAR COMMAND CHAIN 
*                                                          
         MVC   IOBSEEK(8),MBBCCHHR                         
         MVC   CARDBUF1(4),MBBCCHHR+3                      
         MVC   CARDBUF2(4),MBBCCHHR+3                      
         MVC   CARDBUF3(4),MBBCCHHR+3                      
         MVC   CARDBUF4(4),MBBCCHHR+3                      
         MVC   CARDBUF5(4),MBBCCHHR+3

Now we will write five records to relative track 2 but we will write them one at a time. We begin as before by calculating the CCHH address and updating the IOB Seek and Count fields in the I/O buffers.

*                                                          
         MVI   IOBSEEK+7,0        SERCH FOR R0             
         LA    R1,CARDBUF1        RECORD 1                 
         STCM  R1,B'0111',CCWWRITE+1  UPDATE RECORD        
*                                                          
         XC    ECB,ECB            CLEAR ECB                
         EXCP  IOB                ISSUE I/O REQUEST        
*                                                          
         WAIT  1,ECB=ECB          WAIT FOR I/O TO COMPLETE 
*                                                          
         LA    R14,EXIT                                    
         CLI   IOBECBAD,X'7F'     WAS IT SUCCESSFUL        
         BNE   PRINTIOB           GO FORMAT IOB AND EXIT

Now we search for record zero. We place the I/O buffer for record one in the Write CKD CCW.

*                                                          
         MVI   IOBSEEK+7,1        SERCH FOR R1             
         LA    R1,CARDBUF2        RECORD 2                 
         STCM  R1,B'0111',CCWWRITE+1  UPDATE RECORD        
*                                                          
         XC    ECB,ECB            CLEAR ECB                
         EXCP  IOB                ISSUE I/O REQUEST        
*                                                          
         WAIT  1,ECB=ECB          WAIT FOR I/O TO COMPLETE 
*                                                          
         LA    R14,EXIT                                    
         CLI   IOBECBAD,X'7F'     WAS IT SUCCESSFUL        
         BNE   PRINTIOB           GO FORMAT IOB AND EXIT

To write the second record we update the search address to record one (the record we just wrote). We also place the I/O buffer for record two into the Write CKD CCW.

*                                                         
         MVI   IOBSEEK+7,4        SERCH FOR R4            
         LA    R1,CARDBUF5        RECORD 5                
         STCM  R1,B'0111',CCWWRITE+1  UPDATE RECORD       
*                                                         
         XC    ECB,ECB            CLEAR ECB               
         EXCP  IOB                ISSUE I/O REQUEST       
*                                                         
         WAIT  1,ECB=ECB          WAIT FOR I/O TO COMPLETE
*                                                         
         LA    R14,EXIT                                   
         CLI   IOBECBAD,X'7F'     WAS IT SUCCESSFUL       
         BNE   PRINTIOB           GO FORMAT IOB AND EXIT

The code to write records 3, 4, and 5 is same – each time updating the serarch field and the I/O buffer address.

At this time we should have written three tracks to our dataset. The first track contains 5 blocks, the second contains 1 blocks, and the third contains 5 blocks. Now we need an EOF record. Instead of writing the EOF ourselves we will allow EXCP to do it for us during CLOSE processing. This will also update the last track used and track balance fields is the DSCB. We need to supply the address of the last block we have written as well as the space remaining (track balance) for the last track used. We do this by placing these values into the DCB prior to issuing the CLOSE macro.

         MVC   EXCPDCB+5(8),IOBSEEK        LAST MBBCCHHR USED
         MVC   EXCPDCB+8(5),CARDBUF5                         
*                                                            
         L     R1,=A(19254)       EMPTY TRACK BALANCE        
         S     R1,=A(185+80)      R1                         
         S     R1,=A(185+80)      R2                         
         S     R1,=A(185+80)      R3                         
         S     R1,=A(185+80)      R4                         
         S     R1,=A(185+80)      R5                         
         STCM  R1,B'0011',EXCPDCB+18            TRACK BALANCE

We need the MBBCCHHR of the last record. The MBB portion is contained in the IOBSEEK field. We then copy the CCHHR from the Count Area of our last I/O buffer (the last block written).

Now we have to calculate the track balance. We begin with the empty track valance value (19,254 for a 3350) and subtract out the values for the blocks written. A non-keyed record has an overhead of 185 bytes (which includes the Count Area length). We add this to the data length to get the number of bytes consumed by a record and subtract it from the track balance. This value is stored into the DCB. I am using fixed offset values into the DCB. These fields are mapped by the DCBD macro so you could choose to use symbolic names.

As part of this exercise I was looking for how to get these values from the DSCB when opening a dataset. I had expected to find them in the JFCB but they were now where to be found. It is possible they are available if various options are specifed (such as DISP=MOD) or Open for Update. Instead of trying various combinations to cause Open to provide the values I used RDJFCB to read get the JFCB for the open dataset. From the JFCB I can get the Dataset Name and the Volume Serial. With this information I can read the Format 1 DSCB using the OBTAIN macro. This then gives me access to the last track used and track balance values stored in the DSCB. Although these values are not used in this example I do include the code to obtain them.

Upon running the program we should have a dataset containing 11 records. I used TSO EDIT to view the contents of the dataset.

edit 'tcs3.excp06.data' cntl                                
 DATA SET 'TCS3.EXCP06.DATA' NOT LINE NUMBERED, USING NONUM 
 EDIT                                                       
list                                                        
 CARD1                                                      
 CARD2                                                      
 CARD3                                                      
 CARD4                                                      
 CARD5                                                      
 CARD1                                                      
 CARD1                                                      
 CARD2                                                      
 CARD3                                                      
 CARD4                                                      
 CARD5                                                      
 END OF DATA                                                
end nos                                                     
 READY

The results are exactly as expected. I was interested in peeking a little deeper into the contents of the tracks in the dataset. I created a utility program that will report on the blocks making up the tracks in a dataset. The source for this program (DSUINFO) is available in the downloads. I use the Read Multiple CKD CCW Command to read the tracks and then report some basic information for each track.

Here is the DSUINFO output for the dataset created by EXCP06.

                                                   ---------- DSUINFO ----------

   TCS3.EXCP06.DATA                                VOL=SER=WORK01    DEV TYPE=3350
     EXTENTS =  1      TRACKS =      3     BLKSIZE =     80     LRECL =     80     DSORG = PS      RECFM = F 
TT=    0 CC=0148 HH=000F BLKS=  5 EOF=  0 KL=  0/  0/  0 DL=    80/    80/    80
TT=    1 CC=0148 HH=0010 BLKS=  1 EOF=  0 KL=  0/  0/  0 DL=    80/    80/    80
TT=    2 CC=0148 HH=0011 BLKS=  5 EOF=  0 KL=  0/  0/  0 DL=    80/    80/    80
TT=    3 CC=0148 HH=0012 BLKS=  0 EOF=  1 KL=  0/  0/  0 DL=     0/     0/     0
TT=    4 CC=0148 HH=0013 BLKS=  0 EOF=  1 KL=  0/  0/  0 DL=     0/     0/     0

We can see there are 5 tracks in the dataset. It was allocated with SPACE=(TRK,(5,0)). The first track contains 5 data blocks and no EOF blocks. The Min/Max/Avg Key Lenght is 0 and the Min/Max/Avg Data length is 80. The second track contains one block and the third track contains 5 blocks. You may notice the EOF occurs on the fourth track. The last track simply contains whatever was left over from previous use. In this case it happens to be a single EOF block.

Now an interesting question might be what happens if the dataset is alloced with only three tracks – where will the EOF be located?

                                                   ---------- DSUINFO ----------

   TCS3.EXCP06.DATA                                VOL=SER=WORK01    DEV TYPE=3350
     EXTENTS =  1      TRACKS =      3     BLKSIZE =     80     LRECL =     80     DSORG = PS      RECFM = F  
TT=    0 CC=006A HH=001B BLKS=  5 EOF=  0 KL=  0/  0/  0 DL=    80/    80/    80
TT=    1 CC=006A HH=001C BLKS=  1 EOF=  0 KL=  0/  0/  0 DL=    80/    80/    80
TT=    2 CC=006A HH=001D BLKS=  5 EOF=  0 KL=  0/  0/  0 DL=    80/    80/    80

And here is the answer to that question. There is no EOF record written. If all tracks in all extents are read without ever encountering an EOF record then the EOF is implied to exist after the last record in the last extent.

Finally we can check the DSCB to verify the last track used and the track balance are valid.

Last Block Used TTR ... 000205          
Bytes Unused Last Trk . 4609     (17929)

4 Comments

  1. Hi Tommy,

    Great info !

    I ran the EXCP06 program on the Turnkey MVS.

    Just wondering, what kind of DCB / … other macros etc, would ICKDSF be using ?

    I.e. it has “unrestricted” access to the whole disk, right ?

    Mike

    • Mike,
      Without looking at the code for ICKDSF which I have not done I can’t say exactly how this is done in ICKDSF. I can tell you how it could be done, in fact there are several ways it can be done – some easier than others. To do EXCP to a DASD device you must have a DCB (a control block in your application protection key), a DEB (a control block in the system protect key – you can’t modify it unless you have access to system protected storage), and a valid UCB that defines the device. For EXCP the DCB basically is the connection between your program and one or more extents on the DASD device (usually the extents that make up a data set). The DEB contains information about these extents (how many, where they begin and end, and what type of access your application has – read/write and track/cylinder access). The DEB also points to the UCB which defines the actual DASD device. The OS normally constructs the DEB as part of the open process and destroys it in close. It is possible to bypass open/close and build your own DCB/DEB. This is not really all that complicated but it does require Storage Key ZERO access. When an EXCP is issued against a DCB the CCHH for the request is checked against the extents in the DEB to determine if the request is valid. If you were to create a DEB that contained one extent, and this one extent defined the whole volume, then you could access any track on the volume (like ICKDSF does). This is how I would go about doing this myself.

      You could also look into modifying a DEB created by open. To do this you would open a data set on the volume that existed as a single extent and then while running in Storage Key ZERO you could modify the extent information to reflect the whole volume. I have not done this myself but I see no reason it would not work.

      If you do decided to try this please be aware that you could damage or destroy data anywhere on the volume including other data sets and even the VTOC or Volume Label area that could make the volume unusable. I would recommend creating a new volume just for that purpose that did not contain any data I did not want to keep – AND as always backups are good before running this type of code.

      The great thing about Hercules is that we can easily create new volumes and easily back up volumes outside of Hercules/MVS-or-any-other-operating-system.

      I hope this answers your question.
      -Tommy

  2. Hi Tommy,

    I think you’ve just written a nice outline for your next blog post :)

    Modifying an Open’d DEB is probably the first thing I could try.

    I am very interested in seeing the details on creating a DEB / UCB, and bypassing the OPEN process.

    I have a thread going in the H390-MVS [at] yahoogroups [dot] com “EXCP to dasd – Writing a disk utility like ICKDSF”, where all the gurus are throwing around terms that you’ve mentioned.

    I downloaded CBT 860,861, 862, which is G. Postpischil’ code collection, which he mentioned in my thread, and apparently has the functionality I’m looking for.

    I haven’t looked at that yet, I’ve been laboring over the “simple” process of installing the ZAP program (well assembling it and linking it WAS simple), but then “hooking” up the APF stuff / RACF (for the related TSO PARMLIB command), has kept me busy for the last few hours.

    Mike

Leave a Reply

Your email address will not be published. Required fields are marked *