/* REXX */
/*--------------------------------------------------------------------*/
/* XCFDIAG - z/OS XCF / sysplex first-response diagnostic v1.2       */
/*                                                                    */
/* Author  : SARATH S                                                 */
/* Purpose : Correlate sysplex membership, XCF signalling paths,      */
/*           groups, couple data sets, IBMXCF Health Checker results, */
/*           policies and recent IXC/IXL SYSLOG evidence.             */
/*                                                                    */
/* Examples:                                                          */
/*   TSO XCFDIAG                                                      */
/*   TSO XCFDIAG HIST 120                                             */
/*   TSO XCFDIAG LOCAL                                                */
/*   TSO XCFDIAG FULL                                                 */
/*   TSO XCFDIAG FULL HIST 120                                        */
/*   TSO XCFDIAG SAMPLE                                               */
/*   TSO XCFDIAG HELP                                                 */
/*                                                                    */
/* Output:                                                            */
/*   <userid>.XCFDIAG.RPT  - persistent latest diagnostic report.     */
/*   <userid>.XCFDIAG.HELP - persistent README / HELP text.           */
/*   <userid>.XCFDIAG.SAMPLE - persistent fabricated sample report.  */
/*                                                                    */
/* Safety:                                                            */
/*   Read-only. XCFDIAG issues DISPLAY commands only. Remote path     */
/*   collection uses ROUTE only to issue DISPLAY XCF commands.        */
/*   It never issues SETXCF, VARY XCF, REPLY, FORCE, CANCEL or DUMP.  */
/*--------------------------------------------------------------------*/

numeric digits 20
signal on syntax name FatalSyntax

/*--------------------------- Defaults ------------------------------*/
mode = 'NORMAL'
historyMin = 60
localOnly = 0
reportMode = 'REPORT'
maxGroupDrill = 10
maxLogEvents = 60
logMatchCap = 250
isfDelayValue = '5'

rpt. = ''
rpt.0 = 0
rptCount = 0
ispfActive = 0
sdsfOn = 0

uid = sysvar('SYSUID')
localSys = translate(MVSVAR('SYSNAME'))
reportDsn = uid'.XCFDIAG.RPT'
helpDsn = uid'.XCFDIAG.HELP'
sampleDsn = uid'.XCFDIAG.SAMPLE'

if sysvar('SYSISPF') = 'ACTIVE' then ispfActive = 1

/*------------------------ Analysis state ---------------------------*/
sysplexName = 'N/A'
systemCount = 0
systemName. = ''
systemStatus. = ''
systemSeen. = 0
activeSystemCount = 0
nonActiveSystemCount = 0

pathRows = 0
pathWorking = 0
pathProblem = 0
pathSevere = 0
pathTransitional = 0
pathUnknown = 0
pathIssue. = ''
pathIssueCount = 0

pairSeen. = 0
pairKey. = ''
pairSrc. = ''
pairDst. = ''
pairWorking. = 0
pairProblem. = 0
pairSevere. = 0
pairCount = 0

groupCount = 0
groupProblemCount = 0
groupSevereCount = 0
groupName. = ''
groupSize. = ''
groupFlag. = ''
groupDetail. = ''
groupDetailCount = 0

cdsTypeCount = 0
cdsType. = ''
cdsSeen. = 0
cdsPrimary. = 0
cdsAlternate. = 0
cdsPrimarySeen. = 0
cdsAlternateSeen. = 0
cdsAlternateNone. = 0
cdsPrimaryText. = ''
cdsAlternateText. = ''
cdsPrimaryVol. = ''
cdsAlternateVol. = ''
cdsWarn. = 0
cdsIssue. = ''
cdsIssueCount = 0
cdsUnknownCount = 0

policyIssue. = ''
policyIssueCount = 0

hcAvailable = 0
hcFound = 0
hcProblemCount = 0
hcName. = ''
hcOwner. = ''
hcStatus. = ''
hcResult. = ''
hcSeverity. = ''

logAvailable = 1
logCapHit = 0
logEventCount = 0
logDate. = ''
logTime. = ''
logSystem. = ''
logMsgid. = ''
logSeverity. = ''
logText. = ''
logNote = ''
logStartDate = ''
logStartTime = ''
logStopDate = ''
logStopTime = ''

fullIssue. = ''
fullIssueCount = 0

cmdCount = 0
cmdLabel. = ''
cmdText. = ''
cmdRc. = ''
cmdLines. = ''
cmdState. = ''
coreFailureCount = 0
remoteFailureCount = 0

findingCount = 0
findingSeverity. = ''
findingText. = ''
severityRank = 0
overallStatus = 'NORMAL'
evidenceStatus = 'COMPLETE'
currentCondition = 'NO XCF PROBLEM IDENTIFIED'

parse arg rawArgs
call ParseArguments rawArgs
paRc = result

if paRc <> 0 then do
  call PresentReport
  exit 12
end

if reportMode = 'HELP' then do
  call HelpReport
  call PresentReport
  exit 0
end

if reportMode = 'SAMPLE' then do
  call SampleReport
  call PresentReport
  exit 0
end

/*------------------------- SDSF startup -----------------------------*/
rc = isfcalls('ON')
if rc <> 0 then do
  call Emit 'XCFDIAG: Unable to establish SDSF REXX environment.'
  call Emit 'RC='rc
  call Emit ''
  call Emit 'No XCF command was issued.'
  call PresentReport
  exit 20
end
sdsfOn = 1

isfprefix = '**'
isfowner = '*'
isfsysname = localSys
isfdelay = isfDelayValue

/*---------------------- Core XCF collection ------------------------*/
call CaptureCommand 'BASE', 'D XCF'
baseRc = result
if baseRc = 0 then call ParseBaseXCF
else coreFailureCount = coreFailureCount + 1

call CaptureCommand 'SYSPLEX', 'D XCF,SYSPLEX,ALL'
sysRc = result
if sysRc = 0 then call ParseSysplex
else coreFailureCount = coreFailureCount + 1

/* Make sure the executing system is represented even if parsing of */
/* an older/reformatted IXC337I response was incomplete.             */
if systemCount = 0 then call AddSystem localSys, 'UNKNOWN'

call CaptureCommand 'GROUPS', 'D XCF,GROUP'
grpRc = result
if grpRc = 0 then do
  call ParseGroups
  call DrillProblemGroups
end
else coreFailureCount = coreFailureCount + 1

call CaptureCommand 'COUPLE', 'D XCF,COUPLE,TYPE=ALL'
cdsRc = result
if cdsRc = 0 then call ParseCouple
else coreFailureCount = coreFailureCount + 1

call CaptureCommand 'POLICY', 'D XCF,POLICY'
polRc = result
if polRc = 0 then call ParsePolicy

/* Signalling path data is local-system information. Collect it from */
/* every discovered active member unless LOCAL was requested.        */
call CollectAllPaths

/* IBMXCF Health Checker information is optional and best effort. */
call FetchHealthChecker

/* Current SYSLOG search is deliberately bounded to the requested */
/* history window and the local system.                              */
call PrepareLogRange
call CollectLogPrefix 'IXC'
if logAvailable then call CollectLogPrefix 'IXL'

/* FULL mode adds larger, lower-frequency displays. */
if mode = 'FULL' then call FullCollection

call Evaluate
call Header
call TriageSummary
call PriorityFindings
call SysplexReport
call PathReport
call GroupReport
call CoupleReport
call HealthReport
call PolicyReport
call RecentEvents
if mode = 'FULL' then call FullReport
call CommandStatusReport
call CommandReference
call Recommendations
call Notes
call Footer
call Cleanup
call PresentReport
exit 0


/*====================================================================*/
/* ARGUMENT PARSING                                                   */
/*====================================================================*/

ParseArguments:
parse upper arg paRaw
paRaw = strip(paRaw)

if paRaw = '' then return 0

paWords = words(paRaw)
paI = 1

do while paI <= paWords
  paKey = word(paRaw,paI)

  select
    when paKey = 'HELP' | paKey = 'H' | paKey = '?' then do
      reportMode = 'HELP'
      return 0
    end

    when paKey = 'SAMPLE' then do
      reportMode = 'SAMPLE'
      mode = 'SAMPLE'
      return 0
    end

    when paKey = 'FULL' then do
      mode = 'FULL'
      paI = paI + 1
    end

    when paKey = 'LOCAL' | paKey = 'NOREMOTE' then do
      localOnly = 1
      paI = paI + 1
    end

    when paKey = 'HIST' | paKey = 'HISTORY' then do
      if paI = paWords then do
        call Emit 'XCFDIAG: HIST requires a number of minutes.'
        return 12
      end
      paVal = word(paRaw,paI+1)
      if datatype(paVal,'W') = 0 then do
        call Emit 'XCFDIAG: HIST must be a whole number.'
        return 12
      end
      historyMin = paVal
      if historyMin < 15 then historyMin = 15
      if historyMin > 240 then historyMin = 240
      paI = paI + 2
    end

    otherwise do
      if datatype(paKey,'W') then do
        historyMin = paKey
        if historyMin < 15 then historyMin = 15
        if historyMin > 240 then historyMin = 240
        paI = paI + 1
      end
      else do
        call Emit 'XCFDIAG: Unknown parameter: 'paKey
        call Emit 'Use TSO XCFDIAG HELP for syntax.'
        return 12
      end
    end
  end
end

return 0


/*====================================================================*/
/* SYSTEM COMMAND COLLECTION                                          */
/*====================================================================*/

CaptureCommand:
parse arg ccLabel, ccCommand

drop ISFULOG. ISFMSG ISFMSG2.
cmdRaw. = ''
cmdRaw.0 = 0
cmdRawCount = 0

isfdelay = isfDelayValue
Address SDSF "ISFSLASH '"ccCommand"'"
ccRc = rc

if ccRc = 0 then do
  if symbol('ISFULOG.0') = 'VAR' then do
    do ccI = 1 to isfulog.0
      ccLine = strip(isfulog.ccI)
      if ccLine = '' then iterate
      cmdRawCount = cmdRawCount + 1
      cmdRaw.cmdRawCount = ccLine
    end
  end
end

cmdRaw.0 = cmdRawCount

ccState = 'OK'
if ccRc <> 0 then ccState = 'FAILED'
else if cmdRawCount = 0 then ccState = 'NO RESPONSE'
else if CommandTextFailed() then ccState = 'REJECTED'

call RecordCommand ccLabel, ccCommand, ccRc, cmdRawCount, ccState

if ccRc <> 0 then return ccRc
if ccState = 'REJECTED' then return 8
if cmdRawCount = 0 then return 8
return 0


CommandTextFailed:
ctfBad = 0
do ctfI = 1 to cmdRawCount
  ctfU = translate(cmdRaw.ctfI)
  if pos('NOT AUTHORIZED',ctfU) > 0 then ctfBad = 1
  if pos('COMMAND REJECTED',ctfU) > 0 then ctfBad = 1
  if pos('COMMAND INVALID',ctfU) > 0 then ctfBad = 1
  if pos('IXC328I',ctfU) > 0 then ctfBad = 1
  if pos('IEE341I',ctfU) > 0 then ctfBad = 1
end
return ctfBad


RecordCommand:
parse arg rcLabel, rcText, rcCode, rcLines, rcState
cmdCount = cmdCount + 1
cmdLabel.cmdCount = rcLabel
cmdText.cmdCount = rcText
cmdRc.cmdCount = rcCode
cmdLines.cmdCount = rcLines
cmdState.cmdCount = rcState
return


/*====================================================================*/
/* BASE / SYSPLEX PARSING                                             */
/*====================================================================*/

ParseBaseXCF:
do pbI = 1 to cmdRawCount
  pbLine = strip(cmdRaw.pbI)
  pbU = translate(pbLine)

  if word(pbU,1) = 'SYSPLEX' & pos(':',pbLine) > 0 then do
    pbName = word(pbLine,2)
    if right(pbName,1) = ':' then
      pbName = left(pbName,length(pbName)-1)
    if pbName <> '' then sysplexName = translate(pbName)
  end
end
return


ParseSysplex:
psCurrent = ''

do psI = 1 to cmdRawCount
  psLine = strip(cmdRaw.psI)
  psU = translate(psLine)

  if pos('SYSPLEX ',psU) > 0 & pos('SYSPLEX MODE:',psU) = 0 then do
    psP = pos('SYSPLEX ',psU)
    psRest = strip(substr(psLine,psP+8))
    if pos(':',psRest) > 0 then do
      psName = word(psRest,1)
      if right(psName,1) = ':' then
        psName = left(psName,length(psName)-1)
      if psName <> '' & psName <> 'MODE' then
        sysplexName = translate(psName)
    end
  end

  /* IXC337I detailed blocks use a line of the form SYSTEM sysname. */
  if word(psU,1) = 'SYSTEM' then do
    psCandidate = StripPunct(word(psLine,2))
    if psCandidate <> '' & length(psCandidate) <= 8 then do
      if psCandidate <> 'TYPE' & psCandidate <> 'STATUS' then do
        if psCandidate <> 'STATUS-DETECTION' then do
          psCurrent = translate(psCandidate)
          call AddSystem psCurrent, 'UNKNOWN'
        end
      end
    end
  end

  if psCurrent <> '' & pos('STATUS:',psU) > 0 then do
    psP = pos('STATUS:',psU)
    psStat = StripPunct(word(substr(psLine,psP+7),1))
    psStat = translate(psStat)
    if psStat <> '' then do
      systemStatus.psCurrent = psStat
    end
  end
end

call RecountSystems
return


AddSystem:
parse arg asName, asStatus
asName = translate(strip(asName))
if asName = '' then return
asKey = 'K'c2x(asName)

if systemSeen.asKey = 0 then do
  systemSeen.asKey = 1
  systemCount = systemCount + 1
  systemName.systemCount = asName
  systemStatus.asName = asStatus
end
else do
  if asStatus <> '' & asStatus <> 'UNKNOWN' then
    systemStatus.asName = asStatus
end
return


RecountSystems:
activeSystemCount = 0
nonActiveSystemCount = 0

do rsI = 1 to systemCount
  rsName = systemName.rsI
  rsStat = translate(ValueOr(systemStatus.rsName,'UNKNOWN'))

  if rsStat = 'ACTIVE' then activeSystemCount = activeSystemCount + 1
  else if rsStat <> 'UNKNOWN' then
    nonActiveSystemCount = nonActiveSystemCount + 1
end
return


/*====================================================================*/
/* XCF GROUP ANALYSIS                                                 */
/*====================================================================*/

ParseGroups:
pgInGroups = 0

do pgI = 1 to cmdRawCount
  pgLine = strip(cmdRaw.pgI)
  pgU = translate(pgLine)

  if pos('GROUPS(SIZE):',pgU) > 0 then do
    pgInGroups = 1
    pgP = pos('GROUPS(SIZE):',pgU)
    pgLine = strip(substr(pgLine,pgP+13))
  end
  else if pgInGroups = 0 then iterate

  if pgInGroups then do
    if left(translate(pgLine),3) = 'IXC' then leave
    if pos('INDICATES',translate(pgLine)) > 0 then iterate

    do pgW = 1 to words(pgLine)
      pgTok = StripPunct(word(pgLine,pgW))
      if pos('(',pgTok) = 0 | pos(')',pgTok) = 0 then iterate

      pgFlag = ''
      if left(pgTok,1) = '*' | left(pgTok,1) = '!' then do
        pgFlag = left(pgTok,1)
        pgTok = substr(pgTok,2)
      end

      pgP = pos('(',pgTok)
      pgName = left(pgTok,pgP-1)
      pgSize = substr(pgTok,pgP+1)
      pgP2 = pos(')',pgSize)
      if pgP2 > 0 then pgSize = left(pgSize,pgP2-1)

      if pgName = '' then iterate
      if length(pgName) > 8 then iterate

      groupCount = groupCount + 1
      groupName.groupCount = translate(pgName)
      groupSize.groupCount = pgSize
      groupFlag.groupCount = pgFlag

      if pgFlag = '*' then groupProblemCount = groupProblemCount + 1
      if pgFlag = '!' then do
        groupProblemCount = groupProblemCount + 1
        groupSevereCount = groupSevereCount + 1
      end
    end
  end
end
return


DrillProblemGroups:
dpgDone = 0

do dpgI = 1 to groupCount
  dpgFlag = groupFlag.dpgI
  if dpgFlag = '' then iterate
  if dpgDone >= maxGroupDrill then leave

  dpgName = groupName.dpgI
  dpgCmd = 'D XCF,GROUP,'dpgName

  call CaptureCommand 'GROUP-'dpgName, dpgCmd
  dpgRc = result
  dpgDone = dpgDone + 1

  if dpgRc <> 0 then iterate

  do dpgL = 1 to cmdRawCount
    dpgLine = strip(cmdRaw.dpgL)
    dpgU = translate(dpgLine)

    if pos('INDICATES',dpgU) > 0 then iterate

    if pos('MEMBER',dpgU) > 0 | pos('STATUS',dpgU) > 0 |,
       left(dpgLine,1) = '*' | left(dpgLine,1) = '!' then do
      groupDetailCount = groupDetailCount + 1
      groupDetail.groupDetailCount = dpgName' : 'dpgLine
    end
  end
end
return


/*====================================================================*/
/* COUPLE DATA SET ANALYSIS                                           */
/*====================================================================*/

ParseCouple:
pcType = ''
pcRole = ''

do pcI = 1 to cmdRawCount
  pcLine = strip(cmdRaw.pcI)
  pcU = translate(pcLine)

  /* IXC358I headers are of the form:
     SYSPLEX COUPLE DATA SETS
     ARM     COUPLE DATA SETS
     CFRM    COUPLE DATA SETS
     LOGR    COUPLE DATA SETS
     SFM     COUPLE DATA SETS
     WLM     COUPLE DATA SETS
     BPXMCDS COUPLE DATA SETS

     Do not treat arbitrary text containing "COUPLE DATA SETS" as a
     type header. The previous parser did that and could create a
     bogus type such as "OF". */
  pcHead = StripPunct(word(pcU,1))
  if IsCdsType(pcHead) then do
    if word(pcU,2) = 'COUPLE' & word(pcU,3) = 'DATA' &,
       StripPunct(word(pcU,4)) = 'SETS' then do
      pcType = pcHead
      pcRole = ''
      call AddCdsType pcType
      iterate
    end
  end

  if pcType = '' then iterate

  pcKey = 'K'c2x(pcType)
  pcW1 = StripPunct(word(pcU,1))
  pcW2 = StripPunct(word(pcU,2))

  /* IXC358I uses variable spacing:
       PRIMARY    DSN: dsn
       ALTERNATE  DSN: dsn
     Token parsing deliberately ignores spacing between PRIMARY and DSN. */
  if pcW1 = 'PRIMARY' & pcW2 = 'DSN' then do
    cdsPrimarySeen.pcKey = 1
    pcRole = 'PRIMARY'
    pcDsn = ''

    if words(pcLine) >= 3 then
      pcDsn = StripPunct(word(pcLine,3))

    cdsPrimaryText.pcKey = pcDsn

    if pcDsn <> '' then
      cdsPrimary.pcKey = 1
    iterate
  end

  if pcW1 = 'ALTERNATE' & pcW2 = 'DSN' then do
    cdsAlternateSeen.pcKey = 1
    pcRole = 'ALTERNATE'
    pcDsn = ''

    if words(pcLine) >= 3 then
      pcDsn = StripPunct(word(pcLine,3))

    pcDsnU = translate(pcDsn)
    cdsAlternateText.pcKey = pcDsn

    if pcDsnU = 'NONE' | pos('NONE SPECIFIED',pcU) > 0 |,
       pos('NONE USED',pcU) > 0 then
      cdsAlternateNone.pcKey = 1
    else if pcDsn <> '' then
      cdsAlternate.pcKey = 1
    iterate
  end

  if pos('NO ALTERNATE',pcU) > 0 |,
     pos('ALTERNATE NONE SPECIFIED',pcU) > 0 |,
     pos('ALTERNATE: NONE SPECIFIED',pcU) > 0 then do
    cdsAlternateSeen.pcKey = 1
    cdsAlternateNone.pcKey = 1
    pcRole = ''
  end

  if pcW1 = 'VOLSER' then do
    pcVol = ''

    if words(pcLine) >= 2 then do
      pcVolTry = StripPunct(word(pcLine,2))
      if translate(pcVolTry) <> 'DEVN' then
        pcVol = pcVolTry
    end

    if pcRole = 'PRIMARY' then
      cdsPrimaryVol.pcKey = pcVol
    else if pcRole = 'ALTERNATE' then
      cdsAlternateVol.pcKey = pcVol

    pcRole = ''
    iterate
  end

  if pos('NOT IN USE BY ALL SYSTEMS',pcU) > 0 then do
    cdsWarn.pcKey = 1
    call AddCdsIssue pcType,
      'Couple data set function is not in use by all systems.'
  end

  if pos('REMOVED',pcU) > 0 | pos('UNAVAILABLE',pcU) > 0 then do
    cdsWarn.pcKey = 1
    call AddCdsIssue pcType, pcLine
  end
end

do pcJ = 1 to cdsTypeCount
  pcType = cdsType.pcJ
  pcKey = 'K'c2x(pcType)

  if cdsPrimarySeen.pcKey = 0 then
    cdsUnknownCount = cdsUnknownCount + 1
  else if cdsPrimary.pcKey = 0 then do
    cdsWarn.pcKey = 1
    call AddCdsIssue pcType,
      'PRIMARY DSN field was present but the data set name was blank.'
  end

  if cdsAlternateNone.pcKey = 1 then do
    cdsWarn.pcKey = 1
    call AddCdsIssue pcType,
      'No alternate couple data set is defined/identified.'
  end
  else if cdsAlternateSeen.pcKey = 0 then
    cdsUnknownCount = cdsUnknownCount + 1
  else if cdsAlternate.pcKey = 0 then do
    cdsWarn.pcKey = 1
    call AddCdsIssue pcType,
      'ALTERNATE DSN field was present but the data set name was blank.'
  end
end
return


AddCdsType:
parse arg actType
actType = translate(strip(actType))

if \IsCdsType(actType) then return

actKey = 'K'c2x(actType)

if cdsSeen.actKey = 0 then do
  cdsSeen.actKey = 1
  cdsTypeCount = cdsTypeCount + 1
  cdsType.cdsTypeCount = actType
end
return


IsCdsType:
procedure
parse arg ictType
ictType = translate(strip(ictType))

ictValid = 'SYSPLEX ARM CFRM SFM LOGR WLM BPXMCDS'
if wordpos(ictType,ictValid) > 0 then return 1
return 0


AddCdsIssue:
parse arg aciType, aciText
cdsIssueCount = cdsIssueCount + 1
cdsIssue.cdsIssueCount = aciType' : 'aciText
return


/*====================================================================*/
/* POLICY ANALYSIS                                                    */
/*====================================================================*/

ParsePolicy:
do ppI = 1 to cmdRawCount
  ppLine = strip(cmdRaw.ppI)
  ppU = translate(ppLine)

  if pos('NOT ACTIVE ON ALL SYSTEMS',ppU) > 0 |,
     pos('POLICY CHANGE PENDING',ppU) > 0 |,
     pos('PENDING POLICY',ppU) > 0 |,
     pos('UNAVAILABLE',ppU) > 0 then do
    policyIssueCount = policyIssueCount + 1
    policyIssue.policyIssueCount = ppLine
  end
end
return


/*====================================================================*/
/* SIGNALLING PATH ANALYSIS                                           */
/*====================================================================*/

CollectAllPaths:
capCollected = 0

do capI = 1 to systemCount
  capSys = systemName.capI
  capStat = translate(ValueOr(systemStatus.capSys,'UNKNOWN'))

  if localOnly & capSys <> localSys then iterate

  if capStat <> 'ACTIVE' & capStat <> 'UNKNOWN' then iterate

  if capSys = localSys then do
    capIn = 'D XCF,PATHIN'
    capOut = 'D XCF,PATHOUT'
  end
  else do
    capIn = 'RO 'capSys',D XCF,PATHIN'
    capOut = 'RO 'capSys',D XCF,PATHOUT'
  end

  call CaptureCommand 'PATHIN-'capSys, capIn
  capRc = result
  if capRc = 0 then do
    call ParsePaths capSys, 'PATHIN'
    capCollected = capCollected + 1
  end
  else do
    if capSys <> localSys then
      remoteFailureCount = remoteFailureCount + 1
    else coreFailureCount = coreFailureCount + 1
  end

  call CaptureCommand 'PATHOUT-'capSys, capOut
  capRc = result
  if capRc = 0 then do
    call ParsePaths capSys, 'PATHOUT'
    capCollected = capCollected + 1
  end
  else do
    if capSys <> localSys then
      remoteFailureCount = remoteFailureCount + 1
    else coreFailureCount = coreFailureCount + 1
  end
end

if capCollected = 0 then coreFailureCount = coreFailureCount + 1
return


ParsePaths:
parse arg ppaSource, ppaDirection

do ppaI = 1 to cmdRawCount
  ppaLine = strip(cmdRaw.ppaI)
  ppaU = translate(ppaLine)

  ppaStatus = PathStatus(ppaU)
  if ppaStatus = '' then iterate

  pathRows = pathRows + 1

  ppaPos = WordPosExact(ppaStatus,ppaU)
  ppaRemote = 'UNKNOWN'
  ppaId = word(ppaLine,1)

  if ppaPos > 1 then ppaRemote = StripPunct(word(ppaLine,ppaPos-1))
  ppaRemote = translate(ppaRemote)

  select
    when ppaStatus = 'WORKING' then pathWorking = pathWorking + 1

    when ppaStatus = 'STARTING' | ppaStatus = 'RESTARTING' |,
         ppaStatus = 'STOPPING' | ppaStatus = 'LINKING' |,
         ppaStatus = 'REBUILDING' | ppaStatus = 'QUIESCING' |,
         ppaStatus = 'QUIESCED' then do
      pathProblem = pathProblem + 1
      pathTransitional = pathTransitional + 1
      call AddPathIssue ppaSource, ppaRemote, ppaDirection,
                        ppaId, ppaStatus, ppaLine
    end

    when ppaStatus = 'INOPERATIVE' | ppaStatus = 'STOPFAILED' |,
         ppaStatus = 'STALL-IOPND' | ppaStatus = 'STALL-INOP' |,
         ppaStatus = 'STALL-SS?' | ppaStatus = 'STALL-SS' then do
      pathProblem = pathProblem + 1
      pathSevere = pathSevere + 1
      call AddPathIssue ppaSource, ppaRemote, ppaDirection,
                        ppaId, ppaStatus, ppaLine
    end

    otherwise pathUnknown = pathUnknown + 1
  end

  if ppaRemote <> 'UNKNOWN' & ppaRemote <> '????????' then
    call AddPair ppaSource, ppaRemote, ppaStatus
end
return


PathStatus:
parse arg ppsLine
ppsStates = 'STALL-IOPND STALL-INOP STALL-SS? STALL-SS STOPFAILED' ,
            'INOPERATIVE RESTARTING REBUILDING QUIESCING QUIESCED' ,
            'STARTING STOPPING LINKING WORKING'

do ppsI = 1 to words(ppsStates)
  ppsState = word(ppsStates,ppsI)
  if WordPosExact(ppsState,ppsLine) > 0 then return ppsState
end
return ''


AddPathIssue:
parse arg apiSrc, apiDst, apiDir, apiId, apiStatus, apiLine
pathIssueCount = pathIssueCount + 1
pathIssue.pathIssueCount = apiSrc' -> 'apiDst' 'apiDir' 'apiId,
                               apiStatus' | 'apiLine
return


AddPair:
parse arg apSrc, apDst, apStatus
apKey = 'K'c2x(translate(apSrc)'|'translate(apDst))

if pairSeen.apKey = 0 then do
  pairSeen.apKey = 1
  pairCount = pairCount + 1
  pairKey.pairCount = apKey
  pairSrc.apKey = translate(apSrc)
  pairDst.apKey = translate(apDst)
end

if apStatus = 'WORKING' then
  pairWorking.apKey = pairWorking.apKey + 1
else do
  pairProblem.apKey = pairProblem.apKey + 1
  if apStatus = 'INOPERATIVE' | apStatus = 'STOPFAILED' |,
     apStatus = 'STALL-IOPND' | apStatus = 'STALL-INOP' |,
     apStatus = 'STALL-SS?' | apStatus = 'STALL-SS' then
    pairSevere.apKey = pairSevere.apKey + 1
end
return


/*====================================================================*/
/* IBM HEALTH CHECKER                                                 */
/*====================================================================*/

FetchHealthChecker:
drop NAME. OWNER. STATE. STATUS. RESULT. EXCOUNT. SEVERITY.
drop ISFMSG ISFMSG2.

isfcklim = 9999
isfcols = 'NAME OWNER STATE STATUS RESULT EXCOUNT SEVERITY'
Address SDSF "ISFEXEC CK"
fhRc = rc

if fhRc <> 0 then do
  hcAvailable = 0
  drop isfcklim
  return
end

hcAvailable = 1

do fhI = 1 to isfrows
  fhName = translate(ValueOr(NAME.fhI,''))
  fhOwner = translate(ValueOr(OWNER.fhI,''))

  if left(fhOwner,6) <> 'IBMXCF' & left(fhName,4) <> 'XCF_' then
    iterate

  hcFound = hcFound + 1
  fhU = ValueOr(STATUS.fhI,'') ValueOr(RESULT.fhI,'')
  fhU = fhU ValueOr(SEVERITY.fhI,'')
  fhU = translate(fhU)

  if pos('EXCEPTION',fhU) > 0 | pos('FAILED',fhU) > 0 |,
     pos('WARNING',fhU) > 0 | pos('SEVERE',fhU) > 0 then do
    hcProblemCount = hcProblemCount + 1
    hcName.hcProblemCount = fhName
    hcOwner.hcProblemCount = fhOwner
    hcStatus.hcProblemCount = ValueOr(STATUS.fhI,'N/A')
    hcResult.hcProblemCount = ValueOr(RESULT.fhI,'N/A')
    hcSeverity.hcProblemCount = ValueOr(SEVERITY.fhI,'N/A')
  end
end

drop isfcklim
return


/*====================================================================*/
/* BOUNDED SYSLOG ANALYSIS                                            */
/*====================================================================*/

PrepareLogRange:
nowSec = time('S')
startSec = nowSec - (historyMin * 60)
dayBack = 0

if startSec < 0 then do
  startSec = startSec + 86400
  dayBack = 1
end

isfdate = 'mmddyyyy /'
baseDay = date('B') - dayBack
logStartDate = date('U',baseDay,'B')
logStopDate = date('U')
logStartTime = SecClock(startSec)
logStopTime = time('N')
return


CollectLogPrefix:
parse arg clpPattern
if \logAvailable then return

call InitLogSearch clpPattern
clpMatches = 0

do forever
  Address SDSF "ISFLOG READ TYPE(SYSLOG)"
  clpRc = rc

  if clpRc > 4 then do
    logAvailable = 0
    logNote = 'Current SYSLOG unavailable/not authorized; SDSF RC='clpRc
    call ClearLogSearch
    return
  end

  clpLines = 0
  if symbol('ISFLINE.0') <> 'LIT' then clpLines = isfline.0

  if clpLines > 0 then do
    clpDate = ''
    clpTime = ''
    if symbol('ISFFIRSTLINEDATE') <> 'LIT' then
      clpDate = isffirstlinedate
    if symbol('ISFFIRSTLINETIME') <> 'LIT' then
      clpTime = isffirstlinetime

    do clpI = 1 to clpLines
      clpLine = strip(isfline.clpI)
      clpMsg = ExtractMsgId(clpLine)

      if IsInterestingXcfMsg(clpMsg,clpLine) then
        call AddLogEvent clpDate, clpTime, localSys, clpMsg, clpLine

      clpMatches = clpMatches + 1
    end
  end

  if clpMatches >= logMatchCap then do
    logCapHit = 1
    leave
  end

  if symbol('ISFNEXTLINETOKEN') = 'LIT' then leave
  if isfnextlinetoken = '' then leave
  isfstartlinetoken = isfnextlinetoken
end

call ClearLogSearch
return


InitLogSearch:
parse arg ilsPattern
drop isfstartlinetoken isfnextlinetoken isfline.
drop isffirstlinedate isffirstlinetime

isfsysid = localSys
isflogstartdate = logStartDate
isflogstarttime = logStartTime
isflogstopdate = logStopDate
isflogstoptime = logStopTime
isffind = ilsPattern
isffindlim = 9999999
isfscrolltype = 'FINDNEXT'
isflinelim = 1
return


ClearLogSearch:
drop isffind isffindlim isfscrolltype isflinelim
drop isfstartlinetoken isfnextlinetoken isfline.
drop isffirstlinedate isffirstlinetime
return


ExtractMsgId:
parse arg emiLine
emiU = translate(emiLine)

do emiI = 1 to words(emiU)
  emiW = StripPunct(word(emiU,emiI))
  if left(emiW,3) = 'IXC' | left(emiW,3) = 'IXL' then do
    if length(emiW) >= 6 & length(emiW) <= 9 then return emiW
  end
end
return ''


IsInterestingXcfMsg:
parse arg iimMsg, iimLine
if iimMsg = '' then return 0

iimSpecial = 'IXC101I IXC105I IXC418I IXC431I IXC633I IXC636I' ,
             'IXC466I IXC615I IXC402D IXC430E IXC440E IXC640E' ,
             'IXC635E IXC455D IXC246E IXC454I IXC467I'

if wordpos(iimMsg,iimSpecial) > 0 then return 1

iimLast = right(iimMsg,1)
if iimLast = 'A' | iimLast = 'D' | iimLast = 'E' | iimLast = 'W' then
  return 1

iimU = translate(iimLine)
if pos('STALL',iimU) > 0 | pos('INOPERATIVE',iimU) > 0 then return 1
if pos('PARTITION',iimU) > 0 | pos('SIGNALL',iimU) > 0 then return 1
if pos('COUPLE DATA SET',iimU) > 0 & pos('FAIL',iimU) > 0 then return 1
return 0


AddLogEvent:
parse arg aleDate, aleTime, aleSys, aleMsg, aleLine
if logEventCount >= maxLogEvents then return

logEventCount = logEventCount + 1
logDate.logEventCount = aleDate
logTime.logEventCount = aleTime
logSystem.logEventCount = aleSys
logMsgid.logEventCount = aleMsg
logSeverity.logEventCount = MsgSeverity(aleMsg,aleLine)
logText.logEventCount = aleLine
return


MsgSeverity:
parse arg msMsg, msLine
msU = translate(msLine)

if msMsg = 'IXC402D' | msMsg = 'IXC455D' then return 'HIGH'
if msMsg = 'IXC430E' | msMsg = 'IXC440E' | msMsg = 'IXC640E' then
  return 'HIGH'
if pos('STALL-SS',msU) > 0 then return 'HIGH'

msLast = right(msMsg,1)
if msLast = 'A' | msLast = 'D' | msLast = 'E' then return 'WARNING'
return 'INFO'


/*====================================================================*/
/* FULL MODE                                                          */
/*====================================================================*/

FullCollection:
call CaptureCommand 'CF', 'D XCF,CF,CFNAME=ALL'
fcRc = result
if fcRc = 0 then call CollectFullIssues 'CF'

call CaptureCommand 'STRUCTURES', 'D XCF,STR'
fcRc = result
if fcRc = 0 then call CollectFullIssues 'STRUCTURE'

call CaptureCommand 'CLASSDEF', 'D XCF,CLASSDEF'
fcRc = result
if fcRc = 0 then call CollectFullIssues 'CLASSDEF'

call CaptureCommand 'ARMSTATUS', 'D XCF,ARMSTATUS'
fcRc = result
if fcRc = 0 then call CollectFullIssues 'ARM'
return


CollectFullIssues:
parse arg cfiArea

do cfiI = 1 to cmdRawCount
  cfiLine = strip(cmdRaw.cfiI)
  cfiU = translate(cfiLine)

  cfiHit = 0
  if pos('INOPERATIVE',cfiU) > 0 then cfiHit = 1
  if pos('UNAVAILABLE',cfiU) > 0 then cfiHit = 1
  if pos('NO CONNECTIVITY',cfiU) > 0 then cfiHit = 1
  if pos('FAILED',cfiU) > 0 then cfiHit = 1
  if pos('REBUILDING',cfiU) > 0 then cfiHit = 1
  if pos('REBUILD PENDING',cfiU) > 0 then cfiHit = 1
  if pos('PENDING POLICY',cfiU) > 0 then cfiHit = 1
  if pos('FPCONN',cfiU) > 0 then cfiHit = 1
  if pos('NOT ACTIVE ON ALL SYSTEMS',cfiU) > 0 then cfiHit = 1

  if cfiHit then do
    fullIssueCount = fullIssueCount + 1
    fullIssue.fullIssueCount = cfiArea' : 'cfiLine
  end
end
return


/*====================================================================*/
/* EVALUATION                                                         */
/*====================================================================*/

Evaluate:
/* Live sysplex membership. */
if nonActiveSystemCount > 0 then do
  call SetSeverity 3
  call AddFinding 'HIGH',
    nonActiveSystemCount' sysplex system(s) are not reported ACTIVE.'
end

/* Signalling path condition. */
if pathSevere > 0 then do
  call SetSeverity 3
  call AddFinding 'HIGH',
    pathSevere' severe/non-viable XCF signalling path state(s) found.'
end
else if pathTransitional > 0 then do
  call SetSeverity 2
  call AddFinding 'WARNING',
    pathTransitional' XCF signalling path(s) are transitional/degraded.'
end

/* Group stall markers from IXC331I. */
if groupSevereCount > 0 then do
  call SetSeverity 3
  call AddFinding 'HIGH',
    groupSevereCount' XCF group(s) have severe problem markers (!).'
end
else if groupProblemCount > 0 then do
  call SetSeverity 2
  call AddFinding 'WARNING',
    groupProblemCount' XCF group(s) have problem markers (* or !).'
end

/* Couple data set resilience/access issues. */
if cdsIssueCount > 0 then do
  call SetSeverity 2
  call AddFinding 'WARNING',
    cdsIssueCount' couple-data-set resilience/access issue(s) found.'
end

if cdsUnknownCount > 0 then do
  evidenceStatus = 'INCOMPLETE'
  if severityRank = 0 then call SetSeverity 1
  call AddFinding 'INFO',
    cdsUnknownCount' couple-data-set field(s) could not be confirmed.'
end

if hcProblemCount > 0 then do
  call SetSeverity 2
  call AddFinding 'WARNING',
    hcProblemCount' IBMXCF Health Checker exception(s) are visible.'
end

if policyIssueCount > 0 then do
  call SetSeverity 2
  call AddFinding 'WARNING',
    policyIssueCount' XCF policy exception/pending condition(s) found.'
end

if fullIssueCount > 0 then do
  call SetSeverity 2
  call AddFinding 'WARNING',
    fullIssueCount' FULL-mode CF/structure/class/ARM clue(s) found.'
end

/* Historical events do not by themselves prove a current outage. */
if logEventCount > 0 & severityRank = 0 then do
  call SetSeverity 1
  call AddFinding 'INFO',
    logEventCount' recent IXC/IXL event(s) merit review.'
end

if coreFailureCount > 0 | remoteFailureCount > 0 then do
  evidenceStatus = 'INCOMPLETE'
  if severityRank = 0 then call SetSeverity 1
  call AddFinding 'INFO',
    'Some diagnostic evidence could not be collected; see command status.'
end

if nonActiveSystemCount > 0 then
  currentCondition = 'SYSPLEX MEMBER NOT ACTIVE'
else if pathSevere > 0 then
  currentCondition = 'XCF SIGNALLING PATH FAILURE / STALL'
else if pathTransitional > 0 then
  currentCondition = 'XCF SIGNALLING PATH DEGRADED / TRANSITIONAL'
else if groupSevereCount > 0 then
  currentCondition = 'SEVERE XCF GROUP CONDITION'
else if groupProblemCount > 0 then
  currentCondition = 'XCF GROUP CONDITION REQUIRES REVIEW'
else if cdsIssueCount > 0 then
  currentCondition = 'COUPLE DATA SET RESILIENCE / ACCESS WARNING'
else if hcProblemCount > 0 then
  currentCondition = 'IBMXCF HEALTH CHECKER EXCEPTION'
else if policyIssueCount > 0 then
  currentCondition = 'XCF POLICY CONDITION REQUIRES REVIEW'
else if logEventCount > 0 then
  currentCondition = 'RECENT XCF/XES EVENT - CURRENT STATE STABLE'
else
  currentCondition = 'NO XCF PROBLEM IDENTIFIED'

select
  when severityRank >= 4 then overallStatus = 'CRITICAL'
  when severityRank = 3 then overallStatus = 'HIGH'
  when severityRank = 2 then overallStatus = 'WARNING'
  when severityRank = 1 then overallStatus = 'ATTENTION'
  otherwise overallStatus = 'NORMAL'
end

if findingCount = 0 then
  call AddFinding 'INFO',
    'No current XCF problem was identified by the collected evidence.'
return


SetSeverity:
parse arg ssRank
if ssRank > severityRank then severityRank = ssRank
return


AddFinding:
parse arg afSeverity, afText
findingCount = findingCount + 1
findingSeverity.findingCount = afSeverity
findingText.findingCount = afText
return


/*====================================================================*/
/* REPORT                                                             */
/*====================================================================*/

Header:
call Emit ''
call Emit copies('=',78)
call Emit ' XCFDIAG - z/OS XCF / SYSPLEX FIRST-RESPONSE DIAGNOSTIC'
call KV 'Author', 'SARATH S'
call KV 'Date', DisplayDate()
call KV 'Time', time('N')
call KV 'System', localSys
call KV 'Sysplex', sysplexName
call KV 'Mode', mode
call KV 'SYSLOG history', historyMin' minutes'
if localOnly then call KV 'Remote path collection', 'DISABLED (LOCAL)'
else call KV 'Remote path collection', 'ENABLED / BEST EFFORT'
call KV 'Saved report', reportDsn
call Emit copies('=',78)
return


TriageSummary:
call Section 'TRIAGE SUMMARY'
call KV 'Current severity', overallStatus
call KV 'Current condition', currentCondition
call KV 'Evidence completeness', evidenceStatus
call KV 'Systems discovered', systemCount
call KV 'Systems ACTIVE', activeSystemCount
call KV 'Systems non-ACTIVE', nonActiveSystemCount
call KV 'Signalling path rows', pathRows
call KV 'Working path rows', pathWorking
call KV 'Problem path rows', pathProblem
call KV 'Problem XCF groups', groupProblemCount
call KV 'Configured CDS types', cdsTypeCount
call KV 'CDS issues', cdsIssueCount
call KV 'CDS fields unconfirmed', cdsUnknownCount

if hcAvailable then do
  call KV 'IBMXCF checks visible', hcFound
  call KV 'IBMXCF exceptions', hcProblemCount
end
else call KV 'IBMXCF checks', 'NOT AVAILABLE / NOT AUTHORIZED'

if logAvailable then call KV 'Recent IXC/IXL events', logEventCount
else call KV 'Recent SYSLOG', 'UNAVAILABLE'
return


PriorityFindings:
call Section 'PRIORITY FINDINGS'
do pfI = 1 to findingCount
  call Emit right(pfI,2)'. ['findingSeverity.pfI'] 'findingText.pfI
end
return


SysplexReport:
call Section 'SYSPLEX MEMBERSHIP'

if systemCount = 0 then do
  call Emit 'No system membership could be parsed from XCF responses.'
  return
end

call Emit left('System',12) left('Status',20) 'Assessment'
call Emit copies('-',62)

do srI = 1 to systemCount
  srName = systemName.srI
  srStat = ValueOr(systemStatus.srName,'UNKNOWN')

  if translate(srStat) = 'ACTIVE' then srAssess = 'OK'
  else if translate(srStat) = 'UNKNOWN' then srAssess = 'NOT PARSED'
  else srAssess = 'INVESTIGATE'

  call Emit left(srName,12) left(srStat,20) srAssess
end
return


PathReport:
call Section 'XCF SIGNALLING PATH HEALTH'

if pathRows = 0 then do
  call Emit 'No signalling path rows were parsed.'
  call Emit 'Review COMMAND COLLECTION STATUS for authority/format issues.'
  return
end

call KV 'Path rows parsed', pathRows
call KV 'WORKING', pathWorking
call KV 'Transitional/degraded', pathTransitional
call KV 'Severe/non-viable', pathSevere
call Emit ''

call Emit 'SYSTEM-TO-SYSTEM CONNECTIVITY'
call Emit left('Source',10) left('Remote',10) left('Working',10),
          left('Other',10) 'Assessment'
call Emit copies('-',60)

do prI = 1 to pairCount
  prKey = pairKey.prI
  prW = pairWorking.prKey
  prP = pairProblem.prKey
  prS = pairSevere.prKey

  if prW > 0 & prP = 0 then prAssess = 'OK'
  else if prW > 0 & prP > 0 then prAssess = 'DEGRADED'
  else if prW = 0 & prS > 0 then prAssess = 'FAILED/RISK'
  else prAssess = 'CHECK'

  call Emit left(pairSrc.prKey,10) left(pairDst.prKey,10),
            left(prW,10) left(prP,10) prAssess
end

if pathIssueCount > 0 then do
  call Emit ''
  call Emit 'PATH EXCEPTIONS / TRANSITIONAL STATES'
  do prJ = 1 to pathIssueCount
    call Emit '  'pathIssue.prJ
  end
end
return


GroupReport:
call Section 'XCF GROUP HEALTH'
call KV 'Groups parsed', groupCount
call KV 'Groups with problem marker', groupProblemCount
call KV 'Groups with severe marker', groupSevereCount

if groupProblemCount = 0 then do
  call Emit ''
  call Emit 'No * or ! problem marker was detected in D XCF,GROUP.'
end
else do
  call Emit ''
  call Emit left('Flag',6) left('Group',12) left('Size',8) 'Assessment'
  call Emit copies('-',48)

  do grI = 1 to groupCount
    if groupFlag.grI = '' then iterate
    if groupFlag.grI = '!' then grAssess = 'SEVERE'
    else grAssess = 'PROBLEM'

    call Emit left(groupFlag.grI,6) left(groupName.grI,12),
              left(groupSize.grI,8) grAssess
  end

  if groupDetailCount > 0 then do
    call Emit ''
    call Emit 'DRILL-DOWN HIGHLIGHTS'
    do grJ = 1 to groupDetailCount
      call Emit '  'groupDetail.grJ
    end
  end
end
return


CoupleReport:
call Section 'COUPLE DATA SET HEALTH'

if cdsTypeCount = 0 then do
  call Emit 'No recognized couple data set type could be parsed.'
  call Emit 'Assessment is INCOMPLETE, not OK.'
  return
end

call Emit left('Type',10) left('Primary',14) left('Alternate',14),
          'Assessment'
call Emit copies('-',62)

do crI = 1 to cdsTypeCount
  crType = cdsType.crI
  crKey = 'K'c2x(crType)

  if cdsPrimarySeen.crKey = 0 then
    crPri = 'UNKNOWN'
  else if cdsPrimary.crKey = 1 then
    crPri = 'AVAILABLE'
  else
    crPri = 'BLANK'

  if cdsAlternateNone.crKey = 1 then
    crAlt = 'NOT DEFINED'
  else if cdsAlternateSeen.crKey = 0 then
    crAlt = 'UNKNOWN'
  else if cdsAlternate.crKey = 1 then
    crAlt = 'AVAILABLE'
  else
    crAlt = 'BLANK'

  if cdsWarn.crKey then
    crAssess = 'CHECK'
  else if crPri = 'UNKNOWN' | crAlt = 'UNKNOWN' then
    crAssess = 'INCOMPLETE'
  else if crPri = 'AVAILABLE' & crAlt = 'AVAILABLE' then
    crAssess = 'OK'
  else
    crAssess = 'CHECK'

  call Emit left(crType,10) left(crPri,14) left(crAlt,14) crAssess
end

call Emit ''
call Emit 'DATA SET DETAILS'
call Emit copies('-',62)

do crK = 1 to cdsTypeCount
  crType = cdsType.crK
  crKey = 'K'c2x(crType)

  call Emit crType
  call KV '  Primary DSN', ValueOr(cdsPrimaryText.crKey,'UNKNOWN')
  call KV '  Primary VOLSER', ValueOr(cdsPrimaryVol.crKey,'UNKNOWN')

  if cdsAlternateNone.crKey = 1 then do
    call KV '  Alternate DSN', 'NOT DEFINED'
    call KV '  Alternate VOLSER', 'N/A'
  end
  else do
    call KV '  Alternate DSN',
      ValueOr(cdsAlternateText.crKey,'UNKNOWN')
    call KV '  Alternate VOLSER',
      ValueOr(cdsAlternateVol.crKey,'UNKNOWN')
  end

  call Emit ''
end

if cdsIssueCount > 0 then do
  call Emit 'CDS FINDINGS'
  do crJ = 1 to cdsIssueCount
    call Emit '  'cdsIssue.crJ
  end
end

if cdsUnknownCount > 0 then do
  call Emit ''
  call Emit 'NOTE: UNKNOWN means XCFDIAG could not prove that field from'
  call Emit 'the returned IXC358I text. It does NOT mean the CDS is absent.'
  call Emit 'The assessment is therefore INCOMPLETE rather than OK.'
end
return

HealthReport:
call Section 'IBM XCF HEALTH CHECKER'

if \hcAvailable then do
  call Emit 'SDSF CK / IBMXCF data was unavailable or not authorized.'
  call Emit 'XCFDIAG continued with direct XCF displays.'
  return
end

call KV 'IBMXCF checks visible', hcFound
call KV 'Exceptions detected', hcProblemCount

if hcProblemCount = 0 then do
  call Emit ''
  call Emit 'No IBMXCF exception was identified from the available CK rows.'
  return
end

call Emit ''
call Emit left('Check',34) left('Status',15) left('Result',15) 'Severity'
call Emit copies('-',78)

do hrI = 1 to hcProblemCount
  call Emit left(hcName.hrI,34) left(hcStatus.hrI,15),
            left(hcResult.hrI,15) hcSeverity.hrI
end
return


PolicyReport:
call Section 'XCF POLICY STATUS'

if policyIssueCount = 0 then do
  call Emit 'No obvious pending/not-active policy condition was detected.'
  call Emit 'See command status to confirm D XCF,POLICY was available.'
end
else do
  do porI = 1 to policyIssueCount
    call Emit '  'policyIssue.porI
  end
end
return


RecentEvents:
call Section 'RECENT XCF / XES SYSLOG EVENTS'

if \logAvailable then do
  call Emit logNote
  call Emit ''
  call Emit 'Live XCF displays remain valid; historical correlation is'
  call Emit 'incomplete for this invocation.'
  return
end

call KV 'Window start', logStartDate' 'logStartTime
call KV 'Window end', logStopDate' 'logStopTime
call KV 'System searched', localSys
call KV 'Interesting events retained', logEventCount

if logCapHit then do
  call Emit ''
  call Emit 'NOTE: SYSLOG match cap was reached. The search was bounded'
  call Emit 'to protect production resource use.'
end

if logEventCount = 0 then do
  call Emit ''
  call Emit 'No selected IXC/IXL event was found in the requested window.'
  return
end

call Emit ''
do reI = 1 to logEventCount
  reStamp = strip(logDate.reI' 'logTime.reI)
  call Emit left(reStamp,22) left(logMsgid.reI,10),
            left(logSeverity.reI,9) logText.reI
end
return


FullReport:
call Section 'FULL-MODE CF / STRUCTURE / CLASS / ARM CLUES'

if fullIssueCount = 0 then do
  call Emit 'No generic exception keyword was detected in FULL displays.'
  call Emit 'This does not replace product-specific CF/XES analysis.'
end
else do
  do frI = 1 to fullIssueCount
    call Emit '  'fullIssue.frI
  end
end
return


CommandStatusReport:
call Section 'COMMAND COLLECTION STATUS'
call Emit left('Area',18) left('RC',7) left('Lines',8),
          left('State',14) 'Command'
call Emit copies('-',78)

do csrI = 1 to cmdCount
  call Emit left(cmdLabel.csrI,18) left(cmdRc.csrI,7),
            left(cmdLines.csrI,8) left(cmdState.csrI,14),
            cmdText.csrI
end
return


CommandReference:
call Section 'DISPLAY / RECOVERY COMMAND REFERENCE'
call Emit 'READ-ONLY DEBUGGING COMMANDS'
call Emit '  D XCF'
call Emit '  D XCF,SYSPLEX,ALL'
call Emit '  D XCF,GROUP'
call Emit '  D XCF,COUPLE,TYPE=ALL'
call Emit '  D XCF,PATHIN'
call Emit '  D XCF,PATHOUT'
call Emit '  D XCF,POLICY'
call Emit '  D XCF,CF,CFNAME=ALL'
call Emit '  D XCF,STR'
call Emit ''
call Emit 'TARGETED EXAMPLES'
call Emit '  D XCF,GROUP,<groupname>'
call Emit '  D XCF,PI,STRNAME=<structure>'
call Emit '  D XCF,PO,STRNAME=<structure>'
call Emit '  D XCF,STR,STRNAME=<structure>'
call Emit '  RO <system>,D XCF,PATHIN'
call Emit '  RO <system>,D XCF,PATHOUT'
call Emit ''
call Emit 'MANUAL RECOVERY EXAMPLES - XCFDIAG NEVER ISSUES THESE'
call Emit '  SETXCF START,PATHIN,STRNAME=(<structure>)'
call Emit '  SETXCF START,PATHOUT,STRNAME=(<structure>)'
call Emit '  SETXCF START,REBUILD,STRNAME=<structure>'
call Emit ''
call Emit 'Use SETXCF only after the exact failed resource and required'
call Emit 'recovery are confirmed. Do not copy a sample recovery command'
call Emit 'blindly into a production console.'
return


Recommendations:
call Section 'RECOMMENDED DEBUGGING / POSSIBLE RESOLUTION'

if nonActiveSystemCount > 0 then do
  call Emit '1. Investigate the non-ACTIVE sysplex member(s) first.'
  call Emit '   Review current IXC messages and hardware/system status.'
end
else if pathSevere > 0 then do
  call Emit '1. Start with the signalling path exceptions above.'
  call Emit '   Compare PATHIN/PATHOUT from both sides of each affected'
  call Emit '   system pair and identify the common device/structure.'
end
else if groupProblemCount > 0 then do
  call Emit '1. Start with the XCF group(s) marked * or !.'
  call Emit '   Use the drill-down member/job evidence above to identify'
  call Emit '   the stalled or impacted group member.'
end
else if cdsIssueCount > 0 then do
  call Emit '1. Start with the couple data set issue(s) above.'
  call Emit '   Verify primary/alternate accessibility and which systems'
  call Emit '   are using the affected function couple data set.'
end
else do
  call Emit '1. No immediate disruptive XCF recovery action is indicated.'
  call Emit '   If an application symptom remains, use its group/structure'
  call Emit '   name to narrow the next investigation.'
end

call Emit ''
call Emit '2. Review IBMXCF Health Checker exceptions where present.'
call Emit '3. Correlate recent IXC/IXL events with the incident timestamp.'
call Emit '4. For a signalling structure issue, inspect CF/STRUCTURE data.'
call Emit '5. For a CTC signalling issue, inspect the associated CTC path.'
call Emit '6. Do not issue SETXCF/VARY XCF/replies solely from this report.'
call Emit '   Recovery commands can partition systems or alter signalling'
call Emit '   and require normal operational judgement/change controls.'
return


Notes:
call Section 'IMPORTANT NOTES'
call Emit '  * XCFDIAG is read-only.'
call Emit '  * It issues DISPLAY XCF commands and optional ROUTE commands'
call Emit '    whose payload is still DISPLAY XCF.'
call Emit '  * Default mode does not issue SETXCF, VARY XCF, REPLY, DUMP,'
call Emit '    FORCE, CANCEL or any configuration-changing command.'
call Emit '  * PATHIN/PATHOUT are system-local, so remote routing is used'
call Emit '    for active members when authority permits.'
call Emit '  * Remote ROUTE requires the site''s normal OPERCMDS authority.'
call Emit '  * Recent SYSLOG analysis is limited to the executing system.'
call Emit '    A separate-spool remote system can therefore have additional'
call Emit '    historical evidence not visible in this run.'
call Emit '  * FULL mode requests more CF/structure/class/ARM output and'
call Emit '    should be used when the normal report points to those areas.'
call Emit '  * Severity labels are XCFDIAG triage classifications, not IBM'
call Emit '    product-defined severity thresholds.'
call Emit '  * Current healthy state does not prove an earlier XCF incident'
call Emit '    did not occur; use archived logs for older incidents.'
return


Footer:
call Emit ''
call Emit copies('=',78)
call Emit ' XCFDIAG completed. No XCF/system configuration was changed.'
call Emit copies('=',78)
return


/*====================================================================*/
/* SAMPLE REPORT                                                      */
/*====================================================================*/

SampleReport:
call Emit ''
call Emit copies('=',78)
call Emit ' XCFDIAG - z/OS XCF / SYSPLEX FIRST-RESPONSE DIAGNOSTIC'
call KV 'Author', 'SARATH S'
call KV 'Date', DisplayDate()
call KV 'Time', time('N')
call KV 'System', localSys
call KV 'Sysplex', 'SAMPLEX  [SAMPLE DATA]'
call KV 'Mode', 'SAMPLE'
call KV 'Saved report', sampleDsn
call Emit copies('=',78)
call Emit ''
call Emit '*** SAMPLE REPORT - FABRICATED DATA - NO COMMANDS EXECUTED ***'
call Emit 'Only System/Date/Time identify the executing environment.'
call Emit 'All systems, groups, paths, CDS values, messages and findings'
call Emit 'below are examples intended to demonstrate XCFDIAG output.'

call Section 'TRIAGE SUMMARY'
call KV 'Current severity', 'WARNING'
call KV 'Current condition', 'XCF SIGNALLING REDUNDANCY DEGRADED'
call KV 'Evidence completeness', 'SAMPLE / FABRICATED'
call KV 'Systems discovered', 4
call KV 'Systems ACTIVE', 4
call KV 'Systems non-ACTIVE', 0
call KV 'Signalling path rows', 18
call KV 'Working path rows', 17
call KV 'Problem path rows', 1
call KV 'Problem XCF groups', 0
call KV 'Configured CDS types', 6
call KV 'CDS issues', 1
call KV 'IBMXCF exceptions', 1
call KV 'Recent IXC/IXL events', 3

call Section 'PRIORITY FINDINGS'
call Emit ' 1. [WARNING] One outbound signalling path to SAMPLP2 is'
call Emit '    INOPERATIVE. An alternate signalling path is WORKING.'
call Emit ' 2. [WARNING] SFM has a primary CDS but no alternate CDS.'
call Emit ' 3. [INFO] No system partitioning or XCF group impairment is'
call Emit '    represented in this sample incident.'

call Section 'SYSPLEX MEMBERSHIP'
call Emit left('System',12) left('Status',20) 'Assessment'
call Emit copies('-',62)
call Emit left(localSys,12) left('ACTIVE',20) 'OK  [REAL SYSTEM NAME]'
call Emit left('SAMPLP2',12) left('ACTIVE',20) 'OK  [SAMPLE DATA]'
call Emit left('SAMPLP3',12) left('ACTIVE',20) 'OK  [SAMPLE DATA]'
call Emit left('SAMPLP4',12) left('ACTIVE',20) 'OK  [SAMPLE DATA]'

call Section 'XCF SIGNALLING PATH HEALTH'
call KV 'Path rows parsed', 18
call KV 'WORKING', 17
call KV 'Transitional/degraded', 0
call KV 'Severe/non-viable', 1
call Emit ''
call Emit left('Source',10) left('Remote',10) left('Working',10),
          left('Other',10) 'Assessment'
call Emit copies('-',60)
call Emit left(localSys,10) left('SAMPLP2',10) left('2',10),
          left('1',10) 'DEGRADED'
call Emit left(localSys,10) left('SAMPLP3',10) left('3',10),
          left('0',10) 'OK'
call Emit left(localSys,10) left('SAMPLP4',10) left('3',10),
          left('0',10) 'OK'
call Emit ''
call Emit 'PATH EXCEPTION'
call Emit '  'localSys' -> SAMPLP2 PATHOUT IXC_SIGNAL1 INOPERATIVE'
call Emit '  Alternate path IXC_SIGNAL2 remains WORKING.'
call Emit ''
call Emit 'INTERPRETATION'
call Emit '  XCF communication remains available, but signalling'
call Emit '  redundancy between the two systems has been reduced.'
call Emit '  A second path failure could cause a more serious outage.'

call Section 'XCF GROUP HEALTH'
call KV 'Groups parsed', 27
call KV 'Groups with problem marker', 0
call KV 'Groups with severe marker', 0
call Emit ''
call Emit 'No * or ! group marker is represented in this sample.'

call Section 'COUPLE DATA SET HEALTH'
call Emit left('Type',10) left('Primary',14) left('Alternate',14),
          'Assessment'
call Emit copies('-',62)
call Emit left('SYSPLEX',10) left('AVAILABLE',14),
          left('AVAILABLE',14) 'OK'
call Emit left('CFRM',10) left('AVAILABLE',14),
          left('AVAILABLE',14) 'OK'
call Emit left('SFM',10) left('AVAILABLE',14),
          left('NOT DEFINED',14) 'CHECK'
call Emit left('ARM',10) left('AVAILABLE',14),
          left('AVAILABLE',14) 'OK'
call Emit left('LOGR',10) left('AVAILABLE',14),
          left('AVAILABLE',14) 'OK'
call Emit left('WLM',10) left('AVAILABLE',14),
          left('AVAILABLE',14) 'OK'
call Emit ''
call Emit 'CDS FINDING'
call Emit '  SFM : Primary is available but no alternate CDS is defined.'
call Emit '  This is a resilience exposure, not proof of a current SFM'
call Emit '  processing failure.'

call Section 'IBM XCF HEALTH CHECKER'
call KV 'IBMXCF checks visible', 14
call KV 'Exceptions detected', 1
call Emit ''
call Emit left('Check',34) left('Status',15) left('Result',15) 'Severity'
call Emit copies('-',78)
call Emit left('XCF_SIG_PATH_SEPARATION',34) left('ACTIVE',15),
          left('EXCEPTION',15) 'MEDIUM'
call Emit ''
call Emit 'Interpretation: the sample signalling configuration has lost'
call Emit 'one element of path separation/redundancy.'

call Section 'XCF POLICY STATUS'
call Emit 'No pending/not-active policy condition in this sample.'

call Section 'RECENT XCF / XES SYSLOG EVENTS'
call KV 'Window', 'Sample previous 60 minutes'
call KV 'System', localSys
call Emit ''
call Emit '14:21:07 IXC305I INFO     SAMPLE PATHOUT STOP/RECOVERY EVENT'
call Emit '14:21:08 IXC307I WARNING  SAMPLE SIGNALLING PATH ERROR'
call Emit '14:21:14 IXC308I WARNING  SAMPLE REDUCED CONNECTIVITY NOTICE'
call Emit ''
call Emit 'Interpretation: these sample events correlate with the current'
call Emit 'INOPERATIVE path. They are not messages read from this system.'

call Section 'ANALYSIS / LIKELY CAUSE'
call Emit 'The sample incident points to a single signalling-resource'
call Emit 'failure rather than a complete sysplex communication outage.'
call Emit 'All four sample members remain ACTIVE and no XCF group problem'
call Emit 'marker is present. Service can continue over the surviving path.'
call Emit ''
call Emit 'Primary hypothesis:'
call Emit '  A signalling path or signalling structure used between'
call Emit '  'localSys' and SAMPLP2 became inoperative.'
call Emit ''
call Emit 'Secondary configuration risk:'
call Emit '  The SFM couple data set has no alternate in this sample.'

call Section 'DISPLAY / RECOVERY COMMAND REFERENCE'
call Emit 'DISPLAY COMMANDS - SAFE / READ-ONLY'
call Emit '  D XCF'
call Emit '  D XCF,SYSPLEX,ALL'
call Emit '  D XCF,PATHIN'
call Emit '  D XCF,PATHOUT'
call Emit '  D XCF,PI,STRNAME=IXC_SIGNAL1'
call Emit '  D XCF,PO,STRNAME=IXC_SIGNAL1'
call Emit '  D XCF,STR,STRNAME=IXC_SIGNAL1'
call Emit '  D XCF,GROUP'
call Emit '  D XCF,COUPLE,TYPE=ALL'
call Emit '  D XCF,CF,CFNAME=ALL'
call Emit '  D XCF,POLICY'
call Emit '  RO <remote-system>,D XCF,PATHIN'
call Emit '  RO <remote-system>,D XCF,PATHOUT'
call Emit ''
call Emit 'MANUAL RECOVERY EXAMPLES - NOT EXECUTED BY XCFDIAG'
call Emit '  SETXCF START,PATHIN,STRNAME=(IXC_SIGNAL1)'
call Emit '  SETXCF START,PATHOUT,STRNAME=(IXC_SIGNAL1)'
call Emit '  SETXCF START,REBUILD,STRNAME=IXC_SIGNAL1'
call Emit ''
call Emit 'The correct recovery depends on the actual failed resource.'
call Emit 'Never issue a SETXCF command merely because it appears here.'

call Section 'RECOMMENDED DEBUGGING / POSSIBLE RESOLUTION'
call Emit '1. Confirm the path state from both systems with PATHIN/PATHOUT.'
call Emit '2. If the path uses a signalling structure, check PI/PO and STR'
call Emit '   for that exact structure and review associated IXC/IXL text.'
call Emit '3. Confirm an alternate signalling path is genuinely WORKING.'
call Emit '4. Review IBMXCF Health Checker path-connectivity/separation'
call Emit '   exceptions for the same pair/resource.'
call Emit '5. If the failed path is expected to be active and the cause is'
call Emit '   understood, use the site-approved SETXCF START procedure for'
call Emit '   the exact DEVICE or STRNAME. XCFDIAG does not issue it.'
call Emit '6. For a signalling-structure sizing/rebuild problem, validate'
call Emit '   the CFRM definition before considering SETXCF START,REBUILD.'
call Emit '7. Separately restore SFM CDS redundancy using normal sysplex'
call Emit '   change procedures; it is not the sample path root cause.'

call Section 'IMPORTANT NOTES'
call Emit '  * THIS IS A SAMPLE REPORT. Diagnostic evidence is fabricated.'
call Emit '  * SAMPLE does not start SDSF and issues no operator command.'
call Emit '  * The system name/date/time are real environment context.'
call Emit '  * Display/recovery syntax is shown for training/reference.'
call Emit '  * SETXCF commands are examples only and are never automated.'
call Emit '  * Real XCFDIAG results depend on live XCF/SDSF/SAF evidence.'

call Emit ''
call Emit copies('=',78)
call Emit ' XCFDIAG SAMPLE completed. No diagnostic commands were issued.'
call Emit copies('=',78)
return


/*====================================================================*/
/* HELP / README                                                      */
/*====================================================================*/

HelpReport:
call Emit ''
call Emit copies('=',78)
call Emit ' XCFDIAG - README / HELP'
call Emit copies('=',78)
call Emit ''
call Emit 'NAME'
call Emit '  XCFDIAG - z/OS XCF / sysplex first-response diagnostic.'
call Emit ''
call Emit 'WHAT IS XCFDIAG?'
call Emit '  XCFDIAG is a read-only diagnostic utility for checking the'
call Emit '  health of a z/OS sysplex and its XCF communication services.'
call Emit '  It correlates live XCF displays, IBMXCF Health Checker data'
call Emit '  and recent IXC/IXL SYSLOG messages into one report.'
call Emit ''
call Emit 'WHEN TO USE'
call Emit '  * A sysplex member appears hung, isolated or inaccessible.'
call Emit '  * CICS/Db2/MQ/GRS/other multisystem work shows XCF symptoms.'
call Emit '  * IXC/IXL signalling, group, CDS, CF or structure messages'
call Emit '    are present in SYSLOG.'
call Emit '  * You want a general sysplex health check during an incident.'
call Emit ''
call Emit 'WHAT XCFDIAG CHECKS'
call Emit '  1. Sysplex membership and member status.'
call Emit '  2. PATHIN/PATHOUT signalling state on active systems.'
call Emit '  3. XCF groups and * / ! problem markers.'
call Emit '  4. Couple data sets, primary/alternate and access clues.'
call Emit '     UNKNOWN means the IXC358I field was not confirmed; it'
call Emit '     never means XCFDIAG proved that the data set is absent.'
call Emit '  5. Active/pending XCF policies.'
call Emit '  6. IBMXCF Health Checker exceptions when SDSF CK is available.'
call Emit '  7. Recent IXC/IXL messages in the current local SYSLOG.'
call Emit '  8. FULL mode: CF, structures, transport classes and ARM.'
call Emit ''
call Emit 'SYNTAX'
call Emit '  TSO XCFDIAG'
call Emit '  TSO XCFDIAG HIST n'
call Emit '  TSO XCFDIAG n'
call Emit '  TSO XCFDIAG LOCAL'
call Emit '  TSO XCFDIAG FULL'
call Emit '  TSO XCFDIAG FULL HIST n'
call Emit '  TSO XCFDIAG SAMPLE'
call Emit '  TSO XCFDIAG HELP'
call Emit ''
call Emit 'PARAMETERS / MODES'
call Emit '  HIST n'
call Emit '      Search the current local SYSLOG for the previous n minutes.'
call Emit '      Minimum 15, maximum 240, default 60.'
call Emit ''
call Emit '  LOCAL'
call Emit '      Do not ROUTE PATHIN/PATHOUT commands to other systems.'
call Emit '      Useful when MVS.ROUTE authority is unavailable.'
call Emit ''
call Emit '  FULL'
call Emit '      Adds D XCF,CF, D XCF,STR, D XCF,CLASSDEF and'
call Emit '      D XCF,ARMSTATUS. This can return substantially more text.'
call Emit ''
call Emit '  SAMPLE'
call Emit '      Builds a fabricated example incident report without'
call Emit '      starting SDSF or issuing any operator command.'
call Emit '      The executing system name/date/time remain real.'
call Emit ''
call Emit 'OUTPUT DATA SETS'
call Emit '  Diagnostic report:'
call Emit '      'reportDsn
call Emit ''
call Emit '  HELP / README:'
call Emit '      'helpDsn
call Emit ''
call Emit '  SAMPLE report:'
call Emit '      'sampleDsn
call Emit ''
call Emit '  All three data sets remain cataloged after Browse is closed.'
call Emit '  A later diagnostic run replaces only XCFDIAG.RPT.'
call Emit '  XCFDIAG HELP replaces only XCFDIAG.HELP.'
call Emit '  XCFDIAG SAMPLE replaces only XCFDIAG.SAMPLE.'
call Emit '  HELP/SAMPLE never overwrite the last real diagnostic report.'
call Emit ''
call Emit 'WHAT HAPPENS IN THE BACKGROUND?'
call Emit '  * Starts the SDSF REXX environment.'
call Emit '  * Issues a bounded set of DISPLAY XCF commands.'
call Emit '  * Routes only PATHIN/PATHOUT DISPLAY commands when needed.'
call Emit '  * Reads IBMXCF Health Checker rows when available.'
call Emit '  * Searches only a bounded recent local SYSLOG time window.'
call Emit '  * Parses/correlates the returned text in REXX.'
call Emit '  * Saves the report and opens it in ISPF Browse.'
call Emit '  * SAMPLE bypasses SDSF/console collection completely.'
call Emit ''
call Emit 'SAFETY / RESOURCE IMPACT'
call Emit '  * Read-only: no XCF configuration is changed.'
call Emit '  * No SETXCF, VARY XCF, REPLY, FORCE, CANCEL or DUMP is issued.'
call Emit '  * SAMPLE issues no DISPLAY command and starts no SDSF session.'
call Emit '  * Normal mode uses small DISPLAY responses plus a bounded'
call Emit '    SYSLOG search; expected CPU/storage impact is low.'
call Emit '  * FULL mode can return much more console text and should be'
call Emit '    reserved for deeper investigation.'
call Emit '  * SYSLOG searches are capped so the exec cannot scan forever.'
call Emit ''
call Emit 'AUTHORITY / DEPENDENCIES'
call Emit '  * SDSF REXX support is required.'
call Emit '  * DISPLAY XCF requires the site''s normal OPERCMDS authority.'
call Emit '  * Remote path collection additionally requires MVS.ROUTE'
call Emit '    authority and remote command routing to be available.'
call Emit '  * SDSF CK and SYSLOG access are optional; missing data is'
call Emit '    reported as incomplete rather than fabricated.'
call Emit ''
call Emit 'LIMITATIONS'
call Emit '  * Current displays show current state, not the entire history.'
call Emit '  * The default SYSLOG search covers the executing system only.'
call Emit '  * Separate JES spools can hide remote historical SYSLOG data.'
call Emit '  * An absent application-specific XCF group cannot be called'
call Emit '    missing unless a known/baseline expected group is supplied.'
call Emit '  * The report suggests investigation; disruptive XCF recovery'
call Emit '    remains a system-programmer/operator decision.'
call Emit ''
call Emit copies('=',78)
return


/*====================================================================*/
/* HELPERS                                                            */
/*====================================================================*/

Section:
parse arg secTitle
call Emit ''
call Emit copies('-',78)
call Emit ' 'secTitle
call Emit copies('-',78)
return


KV:
parse arg kvLabel, kvValue
call Emit ' 'left(kvLabel,31)':' kvValue
return


Emit:
procedure expose rpt. rptCount
emOut = ''

do emI = 1 to arg()
  emPiece = arg(emI)
  if emI = 1 then emOut = emPiece
  else emOut = emOut emPiece
end

if length(emOut) > 132 then emOut = left(emOut,132)

rptCount = rptCount + 1
rpt.rptCount = emOut
rpt.0 = rptCount
return


ValueOr:
procedure
parse arg voValue, voDefault
voValue = strip(voValue)
if voValue = '' then return voDefault
return voValue


StripPunct:
procedure
parse arg spValue
spValue = strip(spValue)

do while length(spValue) > 0
  spC = right(spValue,1)
  if pos(spC,',.:;') = 0 then leave
  spValue = left(spValue,length(spValue)-1)
end

do while length(spValue) > 0
  spC = left(spValue,1)
  if pos(spC,',.:;') = 0 then leave
  spValue = substr(spValue,2)
end

return spValue


WordPosExact:
procedure
parse arg wpeWord, wpeText
wpeWord = translate(wpeWord)
wpeText = translate(wpeText)

do wpeI = 1 to words(wpeText)
  wpeTok = word(wpeText,wpeI)
  wpeTok = strip(wpeTok)
  if wpeTok = wpeWord then return wpeI
end
return 0


SecClock:
procedure
parse arg scSec
scH = scSec % 3600
scR = scSec // 3600
scM = scR % 60
scS = scR // 60
return right('0'scH,2)':'right('0'scM,2)':'right('0'scS,2)


DisplayDate:
procedure
ddD = date('S')
ddYYYY = substr(ddD,1,4)
ddMM = substr(ddD,5,2)
ddDD = substr(ddD,7,2)
ddMonths = 'JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC'
ddMon = word(ddMonths,ddMM + 0)
return ddDD'-'ddMon'-'ddYYYY


/*====================================================================*/
/* REPORT PRESENTATION                                                */
/*====================================================================*/

PresentReport:
procedure expose rpt. rptCount ispfActive reportMode reportDsn helpDsn sampleDsn
/* ZDIAG BATCH BYPASS */
if sysvar('SYSISPF') <> 'ACTIVE' then do
  if rptCount = 0 then return
  address TSO 'EXECIO 'rptCount' DISKW ZREPORT (STEM rpt. FINIS)'
  zdbw = rc
  if zdbw <> 0 then do
    say 'ZDIAG BATCH: ZREPORT write failed RC='zdbw
    exit 20
  end
  return
end
if rptCount = 0 then return

if reportMode = 'HELP' then prDsn = helpDsn
else if reportMode = 'SAMPLE' then prDsn = sampleDsn
else prDsn = reportDsn

prDdn = 'XDRPT001'

/* Replace the prior copy of the same report type, but keep the newly */
/* written data set cataloged after Browse ends.                     */
prTrap = outtrap('xdQuiet.')
Address TSO "DELETE '"prDsn"'"
prTrap = outtrap('OFF')

prAlloc = "ALLOC DA('"prDsn"') F("prDdn") NEW CATALOG"
prAlloc = prAlloc ' TRACKS SPACE(10 5) DSORG(PS)'
prAlloc = prAlloc ' RECFM(F B) LRECL(133) BLKSIZE(0)'

Address TSO prAlloc
prArc = rc

if prArc <> 0 then do
  say 'XCFDIAG: Unable to allocate output data set:'
  say "  '"prDsn"'"
  do prI = 1 to rptCount
    say rpt.prI
  end
  return
end

Address TSO 'EXECIO 'rptCount' DISKW 'prDdn' (STEM rpt. FINIS)'
prWrc = rc
Address TSO 'FREE F('prDdn')'

if prWrc <> 0 then do
  say 'XCFDIAG: Unable to write output data set:'
  say "  '"prDsn"'"
  do prI = 1 to rptCount
    say rpt.prI
  end
  return
end

if \ispfActive then do
  say "XCFDIAG output saved in '"prDsn"'"
  do prI = 1 to rptCount
    say rpt.prI
  end
  return
end

Address ISPEXEC 'CONTROL ERRORS RETURN'
Address ISPEXEC "BROWSE DATASET('"prDsn"')"
prBrc = rc
Address ISPEXEC 'CONTROL ERRORS CANCEL'

/* Intentionally keep the data set after the user exits Browse. */
if prBrc > 4 then do
  say 'XCFDIAG: ISPF Browse failed RC='prBrc
  say "XCFDIAG output remains saved in '"prDsn"'"
  do prI = 1 to rptCount
    say rpt.prI
  end
end
return


Cleanup:
drop isfdate isfsysid
drop isflogstartdate isflogstarttime
drop isflogstopdate isflogstoptime
call ClearLogSearch

if sdsfOn = 1 then do
  clRc = isfcalls('OFF')
  sdsfOn = 0
end
return


FatalSyntax:
call Emit ''
call Emit 'XCFDIAG: REXX syntax/runtime error.'
call Emit 'Condition:' condition('C')
call Emit 'Description:' condition('D')
call Emit 'Source line:' sigl

if datatype(sigl,'W') then call Emit 'Statement:' sourceline(sigl)

if symbol('SDSFON') <> 'LIT' then call Cleanup
call PresentReport
exit 20
