/*
 *   Copyright (C) 1985, 1991 by the University of Waterloo,
 *   Computer Systems Group. All rights reserved. No part
 *   of this software may be reproduced in any form or by
 *   any means - graphic, electronic or mechanical,
 *   including photocopying, recording, taping or
 *   information storage and retrieval systems - except
 *   with the written permission of the copyright owner.
 */

/*
 * listtxt -- create a readable version of a System/370 object file
 */

#include <cmslib.h>
#include <ctype.h>
#include <depsets.h>
#include <errno.h>
#include <file.h>
#include <obj370.h>
#include <setup.h>
#include <stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysiod.h>

/* option_code -- order must match optiontable and optionexpl below */

typedef enum
  {
    OPT__ERROR,
    OPT_LOWER,
    OPT_TXTDUMP
  } option_code;

static const char optiontable[] =
  {
    "Lower\0"
    "Txtdump\0"
  };

static const char * const optionexpl[] =
  {
    "access CMS files with lower case file names",
    "format TXT record data"
  };

extern sys_entry *_findfd( int fd );

static char *proc_cmdline( char *cmdline, int *tdptr );
static void usage( void );
static void processdeck( char *ipbuff, int norecs, int txtdump );
static void esdproc( esd_record *esdptr );
static void txtproc( txt_record *txtptr, int txtdump );
static void rldproc( rld_record *rldptr );
static void endproc( end_record *endptr );
static void *symproc( sym_record *symptr );
static void symfmt( sym_entry *septr, int symsize );
static void *lmalloc( size_t size );

const int _parms = UNTOKENIZED;


int main( int  argc,
          char **argv )
  {
    char        *fnbuff, *ipbuff;
    int         objfd, txtdump, buflen, retval;
    struct stat stbuff;

    fnbuff = proc_cmdline( argv[ 0 ], &txtdump );
    objfd = open( fnbuff, O_RDONLY | O_BINARY );
    *strchr( fnbuff, '(' ) = '\0';
    strrtrm( fnbuff );
    if( objfd < 0 )
      {
        fprintf( stderr,
            "Unable to open file \"%s\" for input\n %s\n", fnbuff,
            strerror( errno ) );
        return( 28 );
      }
    if( fstat( objfd, &stbuff ) )
      {
        fprintf( stderr, "Unable to access file attributes for \"%s\"\n",
            fnbuff );
        close( objfd );
        return( 34 );
      }
    if( ( stbuff.st_lrecl != 80 ) ||
        ( stbuff.st_recfm != 'F' ) )
      {
        fprintf( stderr, "File \"%s\" is not RECFM F LRECL 80\n",
            fnbuff );
        close( objfd );
        return( 34 );
      }

    /* MM/DD/YY HH:MM:SS... */
    fnbuff = stbuff.st_mtime;
    printf( "File: %8.8s %8.8s %2.2s (%02X/%02X/%02X %02X:%02X:%02X)\n",
        stbuff.st_fname, stbuff.st_ftype, stbuff.st_fmode,
        fnbuff[ 1 ], fnbuff[ 2 ], fnbuff[ 0 ],
        fnbuff[ 3 ], fnbuff[ 4 ], fnbuff[ 5 ] );

    buflen = stbuff.st_size * 80;
    ipbuff = lmalloc( buflen );
    retval = _fsread( &( _findfd( objfd )->se_fdbarea ), ipbuff, buflen,
        stbuff.st_size, &buflen );
    close( objfd );
    if( retval )
        fprintf( stderr, "Error reading file \"%s\"\n %s\n", fnbuff,
            strerror( _fserrno( 'r', retval ) ) );
    else
        processdeck( ipbuff, stbuff.st_size, txtdump );
    return( 0 );
  }

static char *proc_cmdline( char *cmdline, int *tdptr )
  {
    char *optptr, *optend, *fnbuff;

    cmdline = strchr( cmdline, ' ' );
    if( cmdline == NULL )
        usage();
    cmdline = strskip( cmdline, ' ' );
    if( ( *cmdline == '?' ) ||
        ( *cmdline == '\0' ) )
        usage();
    *tdptr = 0;
    optptr = strchr( cmdline, '(' );
    if( optptr != NULL )
      {
        *optptr = '\0';
        ++optptr;
        strrtrm( optptr );
        while( *optptr != '\0' )
          {
            optptr = strskip( optptr, ' ' );
            optend = strchr( optptr, ' ' );
            if( optend == NULL )
                optend = optptr + strlen( optptr );
            switch( tblookup( optiontable, optptr, optend - optptr ) )
              {
                case OPT__ERROR:
                    fprintf( stderr, "Unknown option \"%.*s\"\n",
                        optend - optptr, optptr );
                    exit( 24 );
                case OPT_TXTDUMP:
                    *tdptr = 1;
                    break;
                case OPT_LOWER:
                    setmcase( 1 );
                    break;
              }
            optptr = optend;
          }
      }
    strrtrm( cmdline );
    fnbuff = lmalloc( strlen( cmdline ) + 12 );
    strcpy( fnbuff, cmdline );
    if( ( strchr( fnbuff, ' ' ) == NULL ) &&
        ( strchr( fnbuff, '.' ) == NULL ) )
        strcat( fnbuff, " TEXT" );
    strcat( fnbuff, " ( RAW" );
    return( fnbuff );
  }

static void usage( void )
  {
    char **optexplptr, *opttblptr;

    fprintf( stderr, "Usage: file-specifier"
        " { ( options } { redirection }\noptions:\n" );
    optexplptr = optionexpl;
    for( opttblptr = optiontable;
         *opttblptr != '\0';
         opttblptr += ( 1 + strlen( opttblptr ) ) )
        fprintf( stderr, "  %-8s %s\n", opttblptr, *optexplptr++ );
    exit( 1 );
  }

static void processdeck( char *ipbuff,
                         int norecs,
                         int txtdump )
  {
    for( ; norecs > 0; --norecs )
      {
        if( ipbuff[ 0 ] == PUNCH_12_2_9 )
          {
            if( memcmp( &ipbuff[ 1 ], "ESD", 3 ) == 0 )
                esdproc( (esd_record *) ipbuff );
            else if( memcmp( &ipbuff[ 1 ], "TXT", 3 ) == 0 )
                txtproc( (txt_record *) ipbuff, txtdump );
            else if( memcmp( &ipbuff[ 1 ], "RLD", 3 ) == 0 )
                rldproc( (rld_record *) ipbuff );
            else if( memcmp( &ipbuff[ 1 ], "END", 3 ) == 0 )
                endproc( (end_record *) ipbuff );
            else if( memcmp( &ipbuff[ 1 ], "SYM", 3 ) == 0 )
              {
                /* "SYM" records are packed together;  symproc handles */
                /*  all consecutive SYM records before returning... */
                ipbuff = symproc( (sym_record *) ipbuff );
                continue;
              }
            else
                fprintf( stderr, "Unknown label \"%.3s\"\n", ipbuff + 1);
          }
        ipbuff += 80;
      }
  }

static void esdproc( esd_record *esdptr )
  {
    esd_item    *esditptr;
    int         esd_item_cnt, esdtype, esd_flag;
    char        *esdstr;

    esd_item_cnt = ( esdptr->esd_itemcnt ) / sizeof( esd_item );
    printf( "ESD %04X  %d", esdptr->esd_esdid, esd_item_cnt );
    if( esd_item_cnt == 0 )
      {
        printf( " entries\n" );
        return;
      }
    if( esd_item_cnt == 1 )
        printf( " entry:  " );
    else
        printf( " entries:" );
    esditptr = esdptr->esd_items;
    for(;;)
      {
        esdtype = esditptr->esd__ta.esd_type;
        if( esdtype == ESDT_SD )
            esdstr = "SD";
        else if( esdtype == ESDT_LD )
            esdstr = "LD";
        else if( esdtype == ESDT_ER )
            esdstr = "ER";
        else if( esdtype == ESDT_PC )
            esdstr = "PC";
        else if( esdtype == ESDT_CM )
            esdstr = "CM";
        else if( esdtype == ESDT_XD )
            esdstr = "XD";
        else if( esdtype == ESDT_WX )
            esdstr = "WX";
        else
            esdstr = "??";
        esd_flag = esditptr->esd__ae.esd_align;
        printf( " %8.8s %s %06X %02X %06X ", esditptr->esd_name, esdstr,
            esditptr->esd__ta.esd_addr, esd_flag,
            esditptr->esd__ae.esd_extra );
        if( ( esdtype == ESDT_CM ) ||
            ( esdtype == ESDT_PC ) ||
            ( esdtype == ESDT_SD ) )
          {
            if( esd_flag == 0x40 )
                printf( "(No AMODE/RMODE)" );
            else
              {
                if( esd_flag & ESDA_RMODE_ANY )
                    printf( "(RMODE ANY/" );
                else
                    printf( "(RMODE 24/" );
                esd_flag &= 0x03;
                if( esd_flag == ESDA_AMODE_24D )
                    printf( "AMODE 24 default)" );
                else if( esd_flag == ESDA_AMODE_24 )
                    printf( "AMODE 24)" );
                else if( esd_flag == ESDA_AMODE_31 )
                    printf( "AMODE 31)" );
                else
                    printf( "AMODE ANY)" );
              }
          }
        printf( "\n" );
        if( --esd_item_cnt == 0 )
            return;
        printf( "                    " );
        ++esditptr;
      }
  }

static void txtproc( txt_record *txtptr, int txtdump )
  {
    int *intptr;
    char *bufptr, *bufend;

    if( txtdump == 0 )
      {
        printf( "TXT Addr %06X  Size %02X  ESDID %04X\n",
            txtptr->txt__f.txt_faddr, txtptr->txt_size,
            txtptr->txt_esdid );
        return;
      }
    intptr = (int *) ( txtptr->txt_info );
    printf( "TXT %06X %04X %08X %08X %08X %08X %08X %08X %08X\n",
        txtptr->txt__f.txt_faddr, txtptr->txt_esdid, intptr[ 0 ],
        intptr[ 1 ], intptr[ 2 ], intptr[ 3 ], intptr[ 4 ], intptr[ 5 ],
        intptr[ 6 ] );
    printf( "    %02X          %08X %08X %08X %08X %08X %08X %08X\n",
        txtptr->txt_size, intptr[ 7 ], intptr[ 8 ], intptr[ 9 ],
        intptr[ 10 ], intptr[ 11 ], intptr[ 12 ], intptr[ 13 ] );
    bufptr = txtptr->txt_info;
    bufend = &bufptr[ 56 ];
    for( ; bufptr < bufend; ++bufptr )
        if( isprint( *bufptr ) == 0 )
            *bufptr = '.';
    *bufptr = '\0';
    printf( "                  >%s<\n", txtptr->txt_info );
  }

static void rldproc( rld_record *rldptr )
  {
    int         same_esdids, rldoffset, relesdid, posesdid, rldflag;
    rld_entry   *rldentptr;
    rld_faddr   *rldfaptr;
    char        fdstr[ 8 ];

    printf( "RLD" );
    same_esdids = 0;
    rldentptr = rldptr->rld_data;
    for( rldoffset = 0; rldoffset < rldptr->rld_size; rldoffset += 4 )
      {
        if( rldoffset )
            printf( "\n   " );
        if( same_esdids == 0 )
          {
            relesdid = rldentptr->rld_relesdid;
            posesdid = rldentptr->rld_posesdid;
            rldfaptr = &rldentptr->rld__fa;
            rldoffset += 4;
          }
        rldflag = rldfaptr->rld_flag;
        same_esdids = ( ( rldflag & 0x01 ) != 0 );
        strcpy( fdstr, "      +" );
        switch( rldflag >> 4 )
          {
            case 0:
                memcpy( fdstr, "A/Y", 3 );
                break;
            case 1:
                fdstr[ 0 ] = 'V';
                break;
            case 2:
                fdstr[ 0 ] = 'Q';
                break;
            case 3:
                memcpy( fdstr, "CXD", 3 );
          }
        fdstr[ 4 ] = "1234"[ ( rldflag & 0x0c ) >> 2 ];
        if( ( rldflag & 0x02 ) == 0x02 )
            fdstr[ 6 ] = '-';
        printf( " %04X %04X %02X(%s) %06X", relesdid, posesdid, rldflag,
            fdstr, rldfaptr->rld_addr );
        ++rldfaptr;
        rldentptr = (rld_entry *) rldfaptr;
      }
    printf( "\n" );
  }

static void endproc( end_record *endptr )
  {
    printf( "END Entry" );
    if( endptr->end__e.end_entpt == 0x404040 )
        printf( " (none)" );
    else
        printf( " %06X", endptr->end__e.end_entpt );
    printf( " ESDID" );
    if( endptr->end_esdid == 0x4040 )
        printf( " (none)" );
    else
        printf( " %04X  ", endptr->end_esdid );
    printf( " %c pgmid=\"%19.19s\" deckid=\"%8.8s\"\n", endptr->end_12,
        endptr->end_pgmid, endptr->end_deckid );
  }

static void *symproc( sym_record *symptr )
  {
    sym_entry   *septr;
    char        *bufptr;
    int         bufsize, symsize;

    septr = (sym_entry *) ( symptr->sym_info );
    bufptr = symptr->sym_deckid;
    bufsize = symptr->sym_size;
    for(;;)
      {
        ++symptr;
        if( memcmp( symptr->sym_label, "SYM", 3 ) )
            break;
        symsize = symptr->sym_size;
        memcpy( bufptr, symptr->sym_info, symsize );
        bufptr += symsize;
        bufsize += symsize;
      }
    symfmt( septr, bufsize );
    return( symptr );
  }

static void symfmt( sym_entry *septr, int symsize )
  {
    int         symoffset, dtyplen, dtypmf, dtypsc, sesize,namelen;
    char        *dtypstr, *lmsptr;

    printf( "SYM" );
    for( symoffset = 0; symoffset < symsize; symoffset += sesize )
      {
        if( symoffset )
            printf( "\n   " );
        sesize = 4;
        printf( " %06X ", septr->se__oa.se_addr );
        if( ( (septr->se__oa.se_org) & 0x08 ) == 0 )
          {
            /* name present... */
            namelen = ( septr->se__oa.se_org & 0x07 ) + 1;
            printf( "%.*s", namelen, septr->se_name );
            sesize += namelen;
          }
        if( ( septr->se__oa.se_org) & 0x80 )
          {
            /* data type... */
            dtypstr = ((char *) septr) + sesize;
            sesize += 2;
            lmsptr = dtypstr + 1;
            dtyplen = *lmsptr;
            ++lmsptr;
            if( ( *dtypstr == SYMDT_C ) ||
                ( *dtypstr == SYMDT_X ) ||
                ( *dtypstr == SYMDT_B ) )
              {
                dtyplen <<= 8;
                dtyplen += *lmsptr;
                ++lmsptr;
                ++sesize;
              }
            ++dtyplen;
            dtypmf = 1;
            if( ( septr->se__oa.se_org) & 0x40 )
              {
                /* multiplicity specified... */
                dtypmf = *lmsptr;
                dtypmf <<= 8;
                ++lmsptr;
                dtypmf += *lmsptr;
                dtypmf <<= 8;
                ++lmsptr;
                dtypmf += *lmsptr;
                ++lmsptr;
                sesize += 3;
              }
            dtypsc = 1;
            if( ( septr->se__oa.se_org) & 0x10 )
              {
                /* scale specified... */
                dtypsc = *(short int *) lmsptr;
                sesize += 2;
              }
            switch( *dtypstr )
              {
                case SYMDT_C:
                    dtypstr = "C";
                    break;
                case SYMDT_X:
                    dtypstr = "X";
                    break;
                case SYMDT_B:
                    dtypstr = "B";
                    break;
                case SYMDT_F:
                    dtypstr = "F";
                    break;
                case SYMDT_H:
                    dtypstr = "H";
                    break;
                case SYMDT_E:
                    dtypstr = "E";
                    break;
                case SYMDT_D:
                    dtypstr = "D";
                    break;
                case SYMDT_AQ:
                    dtypstr = "A/Q";
                    break;
                case SYMDT_Y:
                    dtypstr = "Y";
                    break;
                case SYMDT_S:
                    dtypstr = "S";
                    break;
                case SYMDT_V:
                    dtypstr = "V";
                    break;
                case SYMDT_P:
                    dtypstr = "P";
                    break;
                case SYMDT_Z:
                    dtypstr = "Z";
                    break;
                case SYMDT_L:
                    dtypstr = "L";
                    break;
                default:
                    dtypstr = "(unknown data type)";
              }
            printf( " %d%sL%d:%d ", dtypmf, dtypstr, dtyplen, dtypsc );
            if( ( septr->se__oa.se_org) & 0x20 )
                printf( "(cluster)" );
            else
                printf( "(independent)" );
          }
        else
          {
            /* non-data type... */
            switch( ( septr->se__oa.se_org ) >> 4 )
              {
                case 0:
                    dtypstr = "space";
                    dtyplen = ((char *) septr)[ sesize ];
                    ++sesize;
                    break;
                case 1:
                    dtypstr = "CSECT";
                    break;
                case 2:
                    dtypstr = "DSECT";
                    break;
                case 3:
                    dtypstr = "COMMON";
                    break;
                case 4:
                    dtypstr = "instr";
                    break;
                case 5:
                    dtypstr = "CCW";
                    break;
                case 6:
                    dtypstr = "EQU/ORG";
                    break;
                case 7:
                    dtypstr = "(unknown)";
                    break;
              }
            printf( " %s", dtypstr );
            if( ( ( septr->se__oa.se_org ) >> 4 ) == 0 )
                printf( "(%d)", dtyplen );
          }
        septr = (sym_entry *) ( ((char *) septr) + sesize );
      }
    printf( "\n" );
  }

static void *lmalloc( size_t size )
  {
    void *ptr;

    ptr = malloc( size );
    if( ptr == NULL )
      {
        fprintf( stderr, "Insufficient memory\n" );
        exit( 41 );
      }
    return( ptr );
  }