/********************************************************************/
 /*          S A S   S A M P L E   L I B R A R Y                     */
 /*                                                                  */
 /*    NAME: TLKTLOAD                                                */
 /*   TITLE: TLKTDUMP executable module loading program              */
 /* PRODUCT: BASE                                                    */
 /*  SYSTEM: ALL                                                     */
 /*    KEYS:                                                         */
 /*   PROCS: PDSCOPY (MVS only)                                      */
 /*    DATA: NONE                                                    */
 /*                                                                  */
 /* SUPPORT: Richard D. Langston         UPDATE: JUN94               */
 /*     REF: TLKTLOAD/TLKTDUMP User Documentation                    */
 /*    MISC: Companion to the TLKTDUMP program                       */
 /*                                                                  */
 /********************************************************************/
 /* TLKTLOAD is a SAS program that reads in a text file generated by */
 /* the TLKTDUMP program. It validates the file and ensures that the */
 /* file has been tranferred correctly. If all is valid, it creates  */
 /* the executable(s) dumped by the TLKTDUMP program.                */
 /*                                                                  */
 /* First %INCLUDE this TLKTLOAD program, then use the %TLKTLOAD     */
 /* macro to invoke the program:                                     */
 /*                                                                  */
 /*      %TLKTLOAD(INFILE=dumpfile,OUTFILE=executable);              */
 /*                                                                  */
 /* The INFILE= option refers to a fileref or a quoted string giving */
 /* the name of the input dump file. This file is one that has been  */
 /* created by the TLKTDUMP program. The INFILE= option must be      */
 /* given.                                                           */
 /*                                                                  */
 /* The OUTFILE= option refers to a fileref or a quoted string giving*/
 /* the name of the executable module or library. On MVS, this will  */
 /* be a load module library. All restored modules will be added to  */
 /* (or replaced in) the library. On CMS, this will be a LOADLIB.    */
 /* All restored modules will be added to (or replaced in) the       */
 /* library.  On VMS platforms, this will be a specific  .EXE file.  */
 /* It will be replaced. On UNIX platforms, this will be a specific  */
 /* executable file, with no extension. It will be replaced. On PC   */
 /* platforms, this will be a specific  .DLL file.                   */
 /*                                                                  */
 /* The dump file can consist of several pieces that have been       */
 /* downloaded separately and "glued" back together, even if they're */
 /* not in the right order. As long as each section is separated by  */
 /* the part delimiter record, any data                              */
 /* between the delimiter records (such as EMAIL address information)*/
 /* will be ignored. If any text has been altered, or any data       */
 /* modified in any way, or if a section appears more than once or   */
 /* not at all,  the TLKTLOAD program will detect it and alert you   */
 /* in the PRINT file.                                               */
 /*                                                                  */
 /* Note that on CMS, it is required that a writeable A disk         */
 /* be present, since our temporary files are hard-coded to use      */
 /* a filemode of A.                                                 */
 /*------------------------------------------------------------------*/
 
%MACRO TLKTLOAD(INFILE=,OUTFILE=);
%LET VERSION = 1.02;
 
 /*==================================================================*/
 /* In this section, we define the temporary files for the REORDER   */
 /* and SASCODE filedefs. These files will be used in creating the   */
 /* intermediate dump file with all parts in the right order and     */
 /* without any fence information. Also at this point we will try    */
 /* to determine what the correct DCB info will be for the executable*/
 /* if possible.                                                     */
 /*==================================================================*/
 
 /*------------------------------------------------------------------*/
 /* We may need to perform an explicit OS command to delete the      */
 /* REORDER and SASCODE temporary files used during our execution.   */
 /* To ensure we can simply reference the macro variable to invoke   */
 /* the command, we create dummy values for the macro variables that */
 /* are simply comments.                                             */
 /*------------------------------------------------------------------*/
 
%LET DELREORD= * No deletion of REORDER necessary;
%LET DELSASCD= * No deletion of SASCODE necessary;
 
 /*------------------------------------------------------------------*/
 /* MVS-type operating systems simply use &&-prefixed data set names */
 /* which will be deleted when the SAS job terminates. We defer      */
 /* setting our file DCB until we determine if the dump represents   */
 /* an IEBCOPY dump file or a PROC PDSCOPY dump file.                */
 /*------------------------------------------------------------------*/
 
%IF "&SYSSCP."="OS" %THEN %DO;
    %LET REORDER= '&&REORDER';
    %LET SASCODE= '&&SASCODE';
    %END;
 
 /*------------------------------------------------------------------*/
 /* CMS uses real file names, and therefore the DELREORD and DELSASCD*/
 /* macro variables are set to the proper ERASE commands. Note that  */
 /* we also set the DCB of the LOADLIB file.                         */
 /*------------------------------------------------------------------*/
 
%ELSE %IF "&SYSSCP."="CMS" %THEN %DO;
    %LET DCB=RECFM=V LRECL=32760;
    %LET REORDER= 'TEMP REORDER A';
    %LET SASCODE= 'TEMP SASCODE A';
    %LET DELREORD= X "ERASE TEMP REORDER A";
    %LET DELSASCD= X "ERASE TEMP SASCODE A";
    %END;
 
 /*------------------------------------------------------------------*/
 /* For VMS, we can place the files in SAS$WORKLIB, which gets       */
 /* cleared when the SAS job terminates. We can also set the DCB     */
 /* at this time.                                                    */
 /*------------------------------------------------------------------*/
 
%ELSE %IF "&SYSSCP."="VMS" OR "&SYSSCP."="VMS_AXP" %THEN %DO;
    *-----obtain VMS DCB for EXE file-----*;
    %LET DCB=RECFM=F LRECL=512;
    %LET REORDER= 'SAS$WORKLIB:REORDER.DAT';
    %LET SASCODE= 'SAS$WORKLIB:SASCODE.SAS';
    %END;
 
 /*------------------------------------------------------------------*/
 /* For PC platforms, we use explicit file names and set the         */
 /* DEL commands to delete the files. Note that we use OPTIONS       */
 /* NOXWAIT to ensure that the user doesn't have to terminate the    */
 /* shell session.                                                   */
 /*------------------------------------------------------------------*/
 
%ELSE %IF "&SYSSCP."="OS2" %OR "&SYSSCP."="WIN" %THEN %DO;
    %LET DCB=RECFM=N;
    OPTIONS NOXWAIT;
    %LET REORDER= 'REORDER.TMP';
    %LET SASCODE= 'SASCODE.TMP';
    %LET DELREORD= X "DEL REORDER.TMP";
    %LET DELSASCD= X "DEL SASCODE.TMP";
    %END;
 
 /*------------------------------------------------------------------*/
 /* For UNIX platforms, we use explicit file names in /tmp, but      */
 /* we also set up rm commands to ensure that the files are cleaned  */
 /* up.                                                              */
 /*------------------------------------------------------------------*/
 
%ELSE %DO;
    %LET DCB=RECFM=N;
    %LET REORDER= "/tmp/reorder.tmp.&sysjobid.";
    %LET SASCODE= "/tmp/sascode.tmp.&sysjobid.";
    %LET DELREORD= x "rm /tmp/reorder.tmp.&sysjobid.";
    %LET DELSASCD= x "rm /tmp/sascode.tmp.&sysjobid.";
    %END;
 
*------print initial information in print file-----------------------*;
DATA _NULL_; FILE PRINT;
     PUT 'TLKTLOAD: A SAS program to create SAS/TOOLKIT(TM)'
         'executables from downloaded TLKTDUMP files';
     PUT "Version &VERSION.";
     PUT "Requested INPUT file: &INFILE";
     PUT "Requested OUTPUT file: &OUTFILE";
     RUN;
 
 /*------------------------------------------------------------------*/
 /* Since we allow either a fileref or an explicit file name for     */
 /* both INFILE= and OUTFILE=, we must ensure that we have a fileref */
 /* for each, since some code later on may require a fileref and not */
 /* permit an explicit file name. We do this by verifying if the     */
 /* first character of the INFILE and OUTFILE macro variables is a   */
 /* quote (single or double). If either, then we consider the string */
 /* to be an explicit name, in which case a FILENAME statement is    */
 /* generated in a macro variable (FILEST1 or FILEST2). If the name  */
 /* is already a fileref, then FILESTx is a comment statement.       */
 /* We also create the macros INFILEX and OUTFILEX which will always */
 /* contain filerefs, either provided by the user via INFILE= and    */
 /* OUTFILE= or produced via our generated FILENAME statements.      */
 /*------------------------------------------------------------------*/
 
DATA _NULL_;
     LENGTH WHICH $8;
     WHICH = 'INFILE'; NUMBER=1; LINK DOIT;
     WHICH = 'OUTFILE'; NUMBER=2; LINK DOIT;
     RETURN;
DOIT:;
     XFILE=LEFT(SYMGET(WHICH));
     IF SUBSTR(XFILE,1,1)='"' OR SUBSTR(XFILE,1,1)="'" THEN DO;
        FILENAME='RDL'!!PUT(NUMBER,Z5.);
        STMT='FILENAME '!!TRIM(FILENAME)!!' '!!XFILE;
        END;
     ELSE DO;
        FILENAME=XFILE;
        STMT='* No FILENAME statement necessary for '!!WHICH;
        END;
     CALL SYMPUT(TRIM(WHICH)!!'X',TRIM(FILENAME));
     CALL SYMPUT('FILEST'!!PUT(NUMBER,1.),STMT);
     RETURN;
     RUN;
 
*-----invoke the possible FILENAME statements------------------------*;
&FILEST1;
&FILEST2;
 
 /*==================================================================*/
 /* In this section, we read through the dump file and determine     */
 /* the start and end locations for each part of the dump. This is   */
 /* necessary because the parts may not be in the right order, and   */
 /* also because we permit extraneous records (such as mail headers) */
 /* to appear between the parts. With this first pass of the dump    */
 /* file, the only error checking done is to ensure that each IDENT  */
 /* indicator is the same and that we encounter and EOF record       */
 /* somewhere in the dump.                                           */
 /* Note that each part is delineated by a header and trailer record */
 /* that starts with ****PART in column 1. The part number follows,  */
 /* followed then by some identifier string. That string can be      */
 /* supplied by the dump creator, but the string always has the      */
 /* creation datetime appended to it.                                */
 /*==================================================================*/
 
DATA PARTS; INFILE &INFILEX LENGTH=L END=EOF;
     RETAIN MAXPARTN FOUNDEOF 0 SEQUENCE START IDENT_S;
     LENGTH IDENT $80;
 
     *-----read a record of the dump---------------------------------*;
     INPUT @; INPUT @1 RECORD $VARYING80. L;
 
     *-----if the record is a part header, obtain more info----------*;
     IF RECORD=:'****PART ' THEN DO;
 
        *-----get the IDENT info from the record---------------------*;
        I=INDEX(RECORD,'IDENT = ');
        IF I THEN DO;
           I+8;
           I+(SUBSTR(RECORD,I,1)=':');
           RRECORD=REVERSE(RECORD);
           J=81-VERIFY(RRECORD,'* ');
           IDENT=SUBSTR(RECORD,I,J-I+1);
           END;
 
        *-----get the part number from the record--------------------*;
        WHICH=SCAN(SUBSTR(RECORD,13),1,': ');
        NUM=SCAN(RECORD,2,' ');
        IF VERIFY(NUM,'0123456789 ')=0 THEN DO;
           SEQUENCE=INPUT(NUM,BEST12.);
           MAXPARTN=MAX(MAXPARTN,SEQUENCE);
           END;
 
        *-----part number XXX means we found the last section--------*;
        ELSE IF NUM='XXX'
           THEN FOUNDEOF=1;
 
        *-----record the record number of start of data--------------*;
        IF WHICH='START' THEN DO;
           START=_N_+1;
           IDENT_S=IDENT;
           END;
 
        *-----record the record number of end of data----------------*;
        ELSE DO;
           END=_N_-1;
 
           *-----ensure IDENT values match between start and end-----*;
           IF IDENT_S NE IDENT THEN DO;
              FILE PRINT;
              PUT 'ERROR: IDENT labels for start and end do not '
                  'match for part ' sequence;
              ABORT;
              END;
           OUTPUT;
           END;
        END;
 
     *-----if at end of file, ensure we found an EOF record----------*;
     IF EOF THEN DO;
        IF NOT FOUNDEOF THEN DO;
           PUT 'ERROR: An EOF was not found in the dump file, '
               'indicating at least the last section was omitted.';
           ABORT;
           END;
 
        *-----save part count for later use--------------------------*;
        CALL SYMPUT('MAXPARTN',PUT(MAXPARTN,10.));
        END;
     KEEP SEQUENCE START END IDENT;
     RUN;
 
 /*------------------------------------------------------------------*/
 /* Verify that all the IDENT values are the same for all parts. If  */
 /* not, we have an error condition.                                 */
 /*------------------------------------------------------------------*/
 
DATA _NULL_; SET PARTS END=EOF; BY IDENT NOTSORTED;
     NDIFF+FIRST.IDENT;
     IF EOF AND NDIFF NE 1;
     FILE PRINT;
     PUT 'ERROR: Identifying string is not the same for all parts '
         'of the dump';
     ABORT;
     RUN;
 
 /*------------------------------------------------------------------*/
 /* Sort the section indicators into the correct sequence. We also   */
 /* create a set of observations to merge together to ensure that    */
 /* there is one and only one section per sequence number.           */
 /*------------------------------------------------------------------*/
 
PROC SORT DATA=PARTS(DROP=IDENT); BY SEQUENCE;
DATA MATCHUP;
     DO SEQUENCE=0 TO &MAXPARTN;
        OUTPUT;
        END;
     RUN;
 
 /*------------------------------------------------------------------*/
 /* Now merge the two together and ensure that 1) all sections are   */
 /* present and 2) no section is repeated.                           */
 /*------------------------------------------------------------------*/
 
DATA _NULL_; MERGE PARTS(IN=HAVE) MATCHUP END=EOF; BY SEQUENCE;
     FILE PRINT;
     RETAIN ERROR 0;
     IF NOT HAVE THEN DO;
        PUT 'ERROR: Part ' sequence ' of the dump file is not '
            'present. Processing cannot continue.';
        ERROR = 1;
        END;
     ELSE IF NOT (FIRST.SEQUENCE AND LAST.SEQUENCE) THEN DO;
        PUT 'ERROR: Part ' sequence ' of the dump file appears '
            'multiple times. Processing cannot continue.';
        ERROR = 1;
        END;
     IF EOF AND ERROR
        THEN ABORT;
     RUN;
 
 /*------------------------------------------------------------------*/
 /* At this point, we know that the dump contains all the sections   */
 /* we need, and we know the locations of each section. We generate  */
 /* a SAS program into the SASCODE file that will be of the form     */
 /*                                                                  */
 /*        DATA _NULL_; INFILE &INFILEX                              */
 /*                            LENGTH=L FIRSTOBS=x OBS=y;            */
 /*                     FILE REORDER ;                          */
 /*             INPUT @; INPUT @1 LINE $VARYING200. L;               */
 /*             IF LINE NE ' ' THEN PUT _INFILE_;                    */
 /*             RUN;                                                 */
 /*                                                                  */
 /* This code will write into the REORDER file only the dump records */
 /* we want, eliminating headers/trailers and any other superfluous  */
 /* text, including blank lines. Note that the MOD option is not     */
 /* used for the first DATA step. Also, blank lines are preserved    */
 /* for the first part of the dump, which is textual in nature.      */
 /*------------------------------------------------------------------*/
 
*-----ensure we have FILEREFs for REORDER and SASCODe----------------*;
FILENAME REORDER &REORDER;
FILENAME SASCODE &SASCODE;
 
*-----Generate the SAS code described above--------------------------*;
DATA _NULL_; SET PARTS(RENAME=(START=FIRSTOBS END=OBS)); FILE SASCODE;
     LENGTH MOD $3;
     RETAIN OBS MOD;
     IF SEQUENCE=0
        THEN CALL SYMPUT('STARTREC',PUT(OBS-FIRSTOBS+2,BEST12.));
     PUT "DATA _NULL_; INFILE &INFILEX LENGTH=L " FIRSTOBS= OBS=
         '; FILE REORDER ' MOD ';';
     PUT 'INPUT @; INPUT @1 LINE $VARYING200. L; ';
     IF SEQUENCE NE 0 THEN PUT "IF LINE NE ' ' THEN ";
     PUT "PUT _INFILE_;";
     PUT 'RUN;';
     MOD='MOD';
     RUN;
 
*-----Invoke the SAS code that has just been generated---------------*;
%INC SASCODE; RUN;
 
*-----Clear out the SASCODE file since it is no longer needed--------*;
FILENAME SASCODE CLEAR;
&DELSASCD;
 
*------get information from the header portion-----------------------*;
DATA _NULL_; INFILE REORDER LENGTH=L END=EOF;
 
     *-----get ident, length, version, host-----*;
     LENGTH LRECL $8 TLKTDMPV $4 HOST IEBCOPY $8 RECFM $8 BLKSIZE $8;
     INPUT LRECL= HOST= & TLKTDMPV= IEBCOPY= ;
     FILELREC=LRECL;
 
     *-----get IEBCOPY information if necessary-----*;
     if IEBCOPY='YES' THEN DO;
        INPUT @19 RECFM= LRECL= BLKSIZE=;
        CALL SYMPUT('RECFM',TRIM(RECFM));
        CALL SYMPUT('IEBLRECL',TRIM(LRECL));
        CALL SYMPUT('BLKSIZE',TRIM(BLKSIZE));
        END;
     ELSE IEBCOPY='NO';
 
     *-----make macros out of ident and lrecl for later use-----*;
     CALL SYMPUT('LRECL',TRIM(FILELREC));
     CALL SYMPUT('IEBCOPY',TRIM(IEBCOPY));
 
     *-----verify we are on the right host-----*;
     IF HOST NE "&SYSSCP." THEN DO;
        FILE PRINT;
        PUT 'ERROR: The TLKTDUMP file is meant for the ' HOST
            ' operating system but this SAS program is being '
            "run on the &SYSSCP. operating system.";
        ABORT;
        END;
 
     *-----warn if the version number does not match-----*;
     IF TLKTDMPV NE "&VERSION" THEN DO;
        FILE PRINT;
        PUT 'WARNING: The TLKTDUMP file was generated with TLKTDUMP'
            ' Version ' TLKTDMPV ' but this TLKTLOAD is '
            " Version &VERSION.. Beware of incompatibilities...";
        END;
     STOP;
     RUN;
 
 /*------------------------------------------------------------------*/
 /* Here we have to do some host-specific things. For MVS, we must   */
 /* now deal with IEBCOPY information. The IEBCOPY utility is very   */
 /* sensitive about correct DCB information for its unload file.     */
 /* Therefore, when TLKTDUMP creates a dump file, it records the DCB */
 /* of the unload file so that we will be able to recreate the       */
 /* file with correct DCBs. If instead we're using the PDSCOPY       */
 /* unload file, we can always use DCB=(RECFM=VB,LRECL=32756,        */
 /* BLKSIZE=32760). And note that if we are not recreating an        */
 /* IEBCOPY unload file, we're recreating a PDSCOPY unload file,     */
 /* which must be a temporary. Therefore, we redefine the output     */
 /* file to be SEQFILE. Note that we used OUTFILEX earlier to hold   */
 /* the output fileref. We can redefine OUTFILEX to SEQFILE for      */
 /* PDSCOPY unload files. For CMS, we have to deal with the          */
 /* peculiarity that the DATA step does not permit you to use a      */
 /* LOADLIB file as an output file. This is because it expects you   */
 /* to treat the LOADLIB file as a library, not as a sequential      */
 /* file. To overcome this, we use a temporary file TEMP DATA A and  */
 /* have the fileref RDL00003 refer to it. We reset OUTFILEX         */
 /* accordingly. Once we populate TEMP DATA A, we'll copy it to the  */
 /* desired LOADLIB file.                                            */
 /*------------------------------------------------------------------*/
 
%IF "&SYSSCP."="OS" %THEN %DO;
    %IF "&IEBCOPY." NE "YES" %THEN %DO;
        %LET DCB=RECFM=VB LRECL=32756 BLKSIZE=32760;
        FILENAME SEQFILE '&&OUT';
        %LET OUTFILEX=SEQFILE;
        %END;
     %ELSE %LET DCB=RECFM=&RECFM LRECL=&IEBLRECL BLKSIZE=&BLKSIZE;
     %END;
 
%IF "&SYSSCP."="CMS" %THEN %DO;
     FILENAME RDL00003 'TEMP DATA A';
     %LET OUTFILEX=RDL00003;
     %END;
 
 /*------------------------------------------------------------------*/
 /* Finally, we are at the point where we can read through the dump  */
 /* file and create our local binary file from the dump.             */
 /*------------------------------------------------------------------*/
 
DATA _NULL_; INFILE REORDER FIRSTOBS=&STARTREC END=EOF LENGTH=INRECL;
     LENGTH STG $160 SRECLEN $20;
     RETAIN PRECNUM RECNUM PARTNUM 0;
 
     *-----not getting hex data (getting length value)-----*;
     HEX=0;
 
     *-----get length of next restore record-----*;
     LINK GETLEN;
     IF LENGTH=0 /* EOF */
        THEN STOP;
 
     *-----read the hex data, computing checksum and making binary---*;
     CHECKSUM=0;
     I=1;
     HEX=1;
     DO WHILE(I<=LENGTH);
 
        *-----extract a piece-----*;
        LINK GETCOMP;
 
        *-----accumulate checksum-----*;
        DO K=1 TO L;
           CHECKSUM=MOD(CHECKSUM+INPUT(SUBSTR(STG,K,1),PIB1.)+1,32768);
           END;
 
        *-----write out the piece-----*;
        LINK PUTDATA;
        I+L;
        END;
 
     *-----not in hex mode (getting checksum)-----*;
     HEX=0;
 
     *-----flush the binary record-----*;
     L=0; LINK PUTDATA;
 
     *-----absorb comma-----*;
     L=1; LINK GETDATA;
 
     *-----get the checksum and validate against computed one-----*;
     LINK GETLEN;
     IF LENGTH NE CHECKSUM THEN DO;
        FILE PRINT;
        PUT 'ERROR: For restore file record number ' RECNUM
            '(input file record ' NRECS ')'
            ' the provided checksum and the computed checksum '
            ' did not match. there is a data integrity problem.';
        ABORT;
        END;
 
     *-----on to next restore file record-----*;
     RETURN;
 
*-----routine to get recnum and length from input file-----*;
GETLEN:;
     J=1;
 
     *-----read string of format xxxx:yyyy,-----*;
     DO UNTIL(STG=',');
 
        *-----get a single byte-----*;
        L=1;
        LINK GETDATA;
 
        *-----concatenate in-----*;
        SUBSTR(SRECLEN,J,1)=STG;
        J+1;
        END;
 
     *-----read recnum before colon, length after-----*;
     RECNUM=INPUT(SCAN(SRECLEN,1,':,'),5.);
     LENGTH=INPUT(SCAN(SRECLEN,2,':,'),5.);
     RETURN;
 
*-----routine to generate a stream of uncompressed data-----*;
GETCOMP:;
 
     *-----read the indicator byte first-----*;
     L=1;
     LINK GETDATA;
     COMPBYTE=INPUT(SUBSTR(STG,1,1),PIB1.);
 
     *-----if over 127, this indicates compression-----*;
     IF COMPBYTE>127 THEN DO;
 
        *-----get rid of high-order bit to get length-----*;
        COMPBYTE=COMPBYTE-128;
 
        *-----read the repeating byte-----*;
        L=1;
        LINK GETDATA;
 
        *-----generate the complete string-----*;
        STG=REPEAT(SUBSTR(STG,1,1),COMPBYTE-1);
        L=COMPBYTE;
        END;
 
     *-----otherwise we get the specified number of bytes-----*;
     ELSE DO;
        L=COMPBYTE;
        LINK GETDATA;
        END;
     RETURN;
 
*-----read L bytes of binary data from the hex dump-----*;
GETDATA:;
     RETAIN INCOL 1;
     LENGTH PART $&LRECL;
 
     *-----determine actual bytes to read, depending on hex mode-----*;
     LL=L*(1+HEX);
     II=1; STG=' ';
 
     *-----read that many bytes-----*;
     DO WHILE(II<=LL);
 
        *-----read remaining bytes, or all we need-----*;
        LLL=MIN(LL-II+1,&LRECL-INCOL+1);
        INPUT @INCOL PART $VARYING&LRECL.. LLL @@;
 
        *-----concatenate that into STG-----*;
        SUBSTR(STG,II,LLL)=PART;
        INCOL+LLL;
 
        *-----if at end of record, see if next record is a fence-----*;
        IF INCOL>&LRECL THEN DO;
           INPUT;
           NRECS+1;
           INCOL=1;
           END;
 
        *-----increment data-read count and continue-----*;
        II+LLL;
        END;
 
     *-----if the data are supposed to be in hex...-----*;
     IF HEX THEN DO;
 
        *-----verify only hex characters used-----*;
        IF VERIFY(SUBSTR(STG,1,LL),'0123456789ABCDEF') THEN DO;
           FILE PRINT;
           PUT 'ERROR: invalid hex characters in string at about '
               'input record ' NRECS ':' STG;
           ABORT;
           END;
 
        *-----informat the string using $HEX-----*;
        STG=INPUT(SUBSTR(STG,1,LL),$HEX160.);
        END;
     RETURN;
 
*-----write out the resulting string to the output file-----*;
PUTDATA:;
     FILE &OUTFILEX &DCB;
     IF L=0 THEN PUT;
     ELSE PUT STG $VARYING80. L @@;
     FILE LOG;
     RETURN;
 
RUN;
 
*-----we are done with the dump file and can clear it now------------*;
FILENAME REORDER CLEAR;
&DELREORD;
 
 /*------------------------------------------------------------------*/
 /* We have to do some final things on a host-specific basis.        */
 /* For MVS, if we are dealing with a PDSCOPY unload file, we need   */
 /* to run PROC PDSCOPY to convert the unload file into a real       */
 /* load module PDS. For CMS, we must rename the TEMP DATA A file    */
 /* to the desired LOADLIB file name. This is somewhat tricky on     */
 /* CMS because if we don't have the fileid, we have to obtain it    */
 /* by redirecting the SAS log of a DATA step that accesses the      */
 /* fileref, since the DATA step reports the fileid.                 */
 /* To deal with all this mess, we create a fileref of RDL00004 to   */
 /* refer to a temporary file RDL00004 RDLLOG A. We run PROC PRINTTO */
 /* to redirect the SAS log to that file, then run a DATA step that  */
 /* references the output file. We then run PROC PRINTTO to stop the */
 /* redirection, then read RDL00004 RDLLOG A to get the fileid as    */
 /* specified by the DATA step. We then can issue a COPY command to  */
 /* copy TEMP DATA A to the proper fileid, then we ERASE TEMP DATA   */
 /* A. Note that we can't issue a RENAME because the final LOADLIB   */
 /* file may reside on a different disk.                             */
 /*------------------------------------------------------------------*/
 
%IF "&SYSSCP."="OS" & "&IEBCOPY." NE "YES" %THEN %DO;
*-----for MVS, convert to a load module library via PDSCOPY----------*;
    PROC PDSCOPY IN=SEQFILE OUT=&OUTFILE INTAPE; RUN;
    FILENAME SEQFILE CLEAR;
    %END;
 
%IF "&SYSSCP."="CMS" %THEN %DO;
*-----for CMS, get fileid and issue COPY and ERASE-------------------*;
    FILENAME RDL00004 'RDL00004 RDLLOG A';
    PROC PRINTTO LOG=RDL00004 NEW; RUN;
    DATA _NULL_; INFILE &OUTFILE; STOP; RUN;
    PROC PRINTTO; RUN;
    DATA _NULL_; INFILE RDL00004 LENGTH=L;
         INPUT @; INPUT @1 LINE $VARYING133. L;
         LINE=UPCASE(LINE);
         IF INDEX(LINE,'FILENAME=');
         FILENAME=SCAN(LINE,2,'=,');
         RC = CMS('COPY TEMP DATA A '!!FILENAME!!'(REPLACE');
         RC = CMS('ERASE TEMP DATA A');
         STOP;
         RUN;
    FILENAME RDL00003 CLEAR;
    FILENAME RDL00004 CLEAR;
    X 'ERASE RDL00004 RDLLOG A';
    %END;
 
*------put trailer with support information-----*;
DATA _NULL_; FILE PRINT;
     PUT 'End of TLKTLOAD processing';
     PUT //;
     PUT 'For questions/comments about this SAS program,';
     PUT 'contact:';
     PUT // 'Rick Langston';
     PUT    'Product Manager, SAS/TOOLKIT Software';
     PUT    'SAS Institute Inc.';
     PUT    'SAS Campus Drive';
     PUT    'Cary, NC 27513';
     PUT    '919/677-8000 X7613';
     PUT    'EMAIL: sasrdl@unx.sas.com';
%MEND TLKTLOAD;