/* REXX */
/*%STUB CALLCMD*/
/*********************************************************************/
/* Copyright:    Licensed Materials - Property of IBM and/or HCL     */
/*                                                                   */
/*        Copyright IBM Corporation. All rights reserved             */
/*        Copyright HCL Technologies limited. All rights reserved    */
/*                                                                   */
/*        US Government Users Restricted Rights -                    */
/*        Use, duplication or disclosure restricted by               */
/*        GSA ADP Schedule Contract with IBM Corp.                   */
/*                                                                   */
/*********************************************************************/

   Call syscalls('ON')

   parse arg '"'manifestFile'"' '"'mappingFile'"' '"'datasetPropertiesFile'"',
                '"'traceOption'"' '"'TempDSN'"' '"'artifactsDirectory'"',
                '"'tempUnit'"' '"'tempVolSerValue'"' '"'useDeployTime'"',
                '"'deployUserId'"'.

   if traceOption == 'true' then interpret 'trace a'

   /* Set temp volser */
   tempVolser  = ''
   If tempVolSerValue /= "" Then tempVolser  = 'VOLUME('tempVolSerValue')'

   Say 'Artifacts directory     :' artifactsDirectory
   Say 'Manifest file           :' manifestFile
   Say 'Mapping file            :' mappingFile
   Say 'Dataset properties file :' datasetPropertiesFile

   call loadContainerMapperIntoMap(mappingFile)
   call loadDatasetPropertiesIntoMap(datasetPropertiesFile)
   Call processManifestContainers(manifestFile)

Exit

loadContainerMapperIntoMap:procedure expose containerMap.

  parse arg mappingFile
  Address syscall "readfile (mappingFile) mapping."
  If retval < 0 Then do
    Say "Problem reading mapping file : "mappingFile". " ||,
        "Errno : "errno" Reason : "right(errnojr,8,0)"."
    Call ExitProgram(8)
  End

  if mapping.0 = 0 then do
    Say 'Mapping file' mappingFile 'is empty.'
    Call ExitProgram(8)
  end

  drop containerMap.
  Do i = 1 to mapping.0
    If Pos('<sourceContainer',mapping.i) > 0 Then Do
      Parse var mapping.i . ' name="' sourceDsn '"' .
      i = i + 1
      If Pos('<targetContainers>', mapping.i) = 0 Then Do
        Say 'Target dataset not found for 'sourceDsn
        Call ExitProgram(8)
      End

      i = i + 1
      targets = ''
      do while pos('<targetContainer ', mapping.i) > 0
        Parse var mapping.i . ' name="' targetDsn '"' .
        targets = targets targetDsn
        i = i + 1
      end
      sourceDsn = Strip(sourceDsn)
      targets = Strip(targets)
      containerMap.sourceDsn = targets
    End
  End

Return

loadDatasetPropertiesIntoMap:procedure expose dsnPropertiesMap.

  parse arg datasetPropertiesFile
  Address syscall "readfile (datasetPropertiesFile) dsnProperties."
  /* File not exist. manifest may have only deleted tags */
  if retval < 0 Then return

  if dsnProperties.0 = 0 then do
    Say 'Dataset properties file' datasetPropertiesFile 'is empty.'
    Call ExitProgram(8)
  end

  drop dsnPropertiesMap.
  Do i = 1 to dsnProperties.0
    If Pos('<dataset ',dsnProperties.i) > 0 Then Do
      Parse var dsnProperties.i . ' name="' dataset '"' .
      i = i + 1
      do while pos('<property ', dsnProperties.i) > 0
        parse var dsnProperties.i . ' name="' name '"' . ' value="' value '"' .
        dsnPropertiesMap.dataset.name = value
        i = i + 1
      end
    end
  End

Return

processManifestContainers :
  parse arg manifestFile

  call loadManifestIntoStem(manifestFile)

  isDeleted = 0
  manifestVersion = ''
  Do i = 1 to manifest.0
    Select
      When (Pos('<manifest',manifest.i) > 0) Then
      do
         parse var manifest.i . '<manifest version="' manifestVersion '"' .
      end
      When (Pos('<deleted>',manifest.i) > 0) Then
        isDeleted = 1

      When (Pos('</deleted>',manifest.i) > 0) Then
        isDeleted = 0

      When (Pos('<container',manifest.i) > 0) Then
      Do
        Parse var manifest.i . ' name="' DataSet '" type="' type '"' .

        if type == 'PDS' then
          call createMemberList

        Call performDeployment dataset type isDeleted

      End
      Otherwise
        Nop
    End
  End

Return

loadManifestIntoStem:procedure expose manifest.
  parse arg manifestFile
  Address syscall "readfile (manifestFile) manifest."
  If retval < 0 Then
  Do
    Say "Problem reading manifest file : "manifestFile". " ||,
        "Errno : "errno" Reason : "right(errnojr,8,0)"."
    Call ExitProgram(8)
  End

  if manifest.0 = 0 then
  do
    Say 'Manifest file' manifestFile 'is empty.'
    Call ExitProgram(8)
  end
Return

createMemberList:procedure expose i manifest. memberlist.

  drop memberlist.
  memberlist.0 = 0
  m = 0
  i = i + 1
  Do while Pos('<resource',manifest.i) > 0
    m = m + 1
    memberlist.m.name = getResourceName(manifest.i)
    memberlist.m.modifiedUserid = getLastModifiedUserId(manifest.i)
    memberlist.m.modifiedTimestamp = getLastModifiedTimestamp(manifest.i)
    i = i + 1
  End
  i = i - 1
  memberlist.0 = m

return

getResourceName:procedure

  Parse arg resourceTag . '<resource' . ' name="' value '"' .

return value

getLastModifiedUserId:procedure

  Parse arg resourceTag . '<resource' . ' lastModifiedUserid="' value '"' .

return value

getLastModifiedTimestamp:procedure

  Parse arg resourceTag . '<resource' . ' lastModifiedTimestamp="' value '"' .

return value

performDeployment:
  parse arg sourceDsn type isDeleted

  if isDeleted Then
  Do
    targets = getTargetDatasetsFromContainerMap(sourceDsn)
    do g = 1 to words(targets)
      DeplDSN = word(targets, g)
      If type = 'PDS' then
        call deletePdsMembers(DeplDSN)
      else
        call deleteDataset(DeplDSN)
    End
  end
  else do
    if type = 'PDS' then
      call copyPdsMembers sourceDsn
    else
      call copySequentialDataset sourceDsn

    call deleteTempDataset
  end

Return

copySequentialDataset:
  Parse arg sourceDsn
  if manifestVersion = 'LV1_0' then
    filePath = artifactsDirectory"/MVS/"sourceDsn
  else
    filePath = artifactsDirectory"/"sourceDsn

  space = getFileSizeInTracks(filePath)
  call createTempSequentialDataset sourceDsn TempDSN space

  cpOption = dsnPropertiesMap.sourceDsn.CP_OPTION
  slashSlashQuotedDsn = getSlashSlashQuotedDsn(TempDSN)
  cpCommand = "cp" cpOption "'"filePath"'" slashSlashQuotedDsn
  call executeCpCommand(cpCommand)
  targets = getTargetDatasetsFromContainerMap(sourceDsn)
  do p = 1 to words(targets)
    DeplDSN = word(targets, p)
    call createTargetDatasetIfNotExist sourceDsn DeplDSN TempDSN
    Say 'Deploying sequential dataset to 'DeplDSN'.'
    call runIebgenerProgram TempDSN DeplDSN
  end

return

copyPDSMembers :

  Parse arg sourceDsn

  if manifestVersion = 'LV1_0' then
    filePath = artifactsDirectory"/MVS/"sourceDsn
  else
    filePath = artifactsDirectory"/"sourceDsn

  spaceAndDirBlocks = getSpaceInTracksAndDirBlocks(filePath)
  call createTempPdsDataset(sourceDsn TempDSN spaceAndDirBlocks)

  slashSlashQuotedDsn = getSlashSlashQuotedDsn(TempDSN)
  cpOption = dsnPropertiesMap.sourceDsn.CP_OPTION
  cpCommand = "cp" cpOption "'"filePath"/'*" slashSlashQuotedDsn
  call executeCpCommand(cpCommand)

  call updateStatsForMembers(TempDSN)

  call freeIebcopyDDs

  Address TSO "ALLOC F(SYSPRINT) NEW REUSE"

  Address TSO "ALLOC F(INDD)  DA('"TempDSN"') SHR REUSE"
  If (rc > 0) then
  Do
    say 'Problem allocating dataset 'TempDSN'. Return code : 'rc
    call ExitProgram(8)
  End

  Address TSO "ALLOC F(SYSUT4) NEW" ||,
          " CYLINDERS UNIT("tempUnit") SPACE(5 10) "tempVolser

  Address TSO "ALLOC F(SYSIN) NEW REUSE"

  call writeSysinRecordsForIebcopy

  targets = getTargetDatasetsFromContainerMap(sourceDsn)
  do r = 1 to words(targets)
    DeplDSN = word(targets, r)
    call createTargetDatasetIfNotExist sourceDsn DeplDSN TempDSN
    call runIebcopyProgram DeplDSN
  end

  call freeIebcopyDDs

Return

runIebgenerProgram:procedure
  parse arg SrceDSN TargDSN
  Address TSO "ALLOC F(SYSPRINT) NEW REUSE"
  Address TSO "ALLOC F(SYSUT1)  DA('"SrceDSN"') SHR REUSE"
  Address TSO "ALLOC F(SYSUT2) DA('"TargDSN"') SHR REUSE"
  Address TSO "ALLOC F(SYSIN) DUMMY REUSE "
  Address ISPEXEC "ISPEXEC SELECT PGM(IEBGENER)"
  Copy_rc = rc
  Address TSO  "EXECIO * DISKR SYSPRINT (FINIS STEM sysmsgs."

  x = Msg('off')
    Address TSO "FREE F(SYSIN,SYSPRINT,SYSUT1,SYSUT2)"
  x = Msg('on')

  If Copy_rc <> 0 Then
  do
    Do xmit = 1 to sysmsgs.0
      Say Strip(sysmsgs.xmit)
    End
    call ExitProgram(Copy_rc)
  End

return

executeCpCommand:procedure

  parse arg cpCommand

  sh_rc = bpxwunix(cpCommand,,stdout.,stderr.)
  If sh_rc /= 0 Then do
    say "Command :" cpCommand "failed with rc :" sh_rc
    If stderr.0 > 0 Then
    Do e = 1 to stderr.0
        Say stderr.e
    End
    If stdout.0 > 0 Then
    Do o = 1 to stdout.0
        Say stdout.o
    End
    Call ExitProgram(sh_rc)
  end

return

updateStatsForMembers:procedure expose memberlist. useDeployTime deployUserId

  parse arg pdsName

  Address ISPEXEC "DSINFO DATASET('"pdsName"')"
  If rc <> 0 Then do
    say 'Unable to update STATS; DSINFO failed. Return code : 'rc
    Call ExitProgram(8)
  end
  If Strip(zdsrf) = 'U' Then do
    return
  end

  Address ISPEXEC "LMINIT DATAID(UDID) DATASET('"pdsName"') ENQ(SHRW)"
  if rc <> 0 then do
    say "LMINIT failed for dataset" pdsName "with return code :" rc
    Call ExitProgram(rc)
  end

  Address ISPEXEC "CONTROL ERRORS RETURN"

  if useDeployTime == 'true' then do
    if (deployUserId = '') then do
        modifiedUser = USERID()
    end
    else do
        modifiedUser = deployUserId
    end

    if Length(modifiedUser) > 7 then do
      Say 'Skipping timestamp update for 'pdsName'. userId length='Length(modifiedUser)'.'
      Address ISPEXEC "LMFREE DATAID(&UDID)"
      return
    end

    currentDate = Date('S')
    currentTime = Time('N')
    modifiedDate = Left(currentDate,4)'/'Substr(currentDate,5,2)'/'Right(currentDate,2)
    modifiedTime = Left(currentTime,5)

    do m = 1 to memberlist.0
      call executeLmstats UDID, memberlist.m.name, modifiedUser, modifiedDate, modifiedTime
    end
  end
  else do
    do m = 1 to memberlist.0
      if memberlist.m.modifiedUserid /= '' & memberlist.m.modifiedTimestamp /= ''
      then do
        modifiedUser = memberlist.m.modifiedUserid
        if Length(modifiedUser) < 8 then do
           parse var memberlist.m.modifiedTimestamp modifiedDate modifiedTime
           call executeLmstats UDID, memberlist.m.name, modifiedUser, modifiedDate, modifiedTime
        end
        else do
          say "User id" modifiedUser " for member " ||
             memberlist.m.name ||
             " is too long to set as modifier. Skipping STATS update for this member."
        end
      end
    end
  end

  Address ISPEXEC "CONTROL ERRORS CANCEL"

  Address ISPEXEC "LMFREE DATAID(&UDID)"
  if rc <> 0 then do
    say "LMFREE failed with return code :" rc
    call ExitProgram(rc)
  end

return

executeLmstats:procedure

  parse arg dataid, memberName, modifiedUser, modifiedDate, modifiedTime

  lmmstatsCmd = "LMMSTATS DATAID(&dataid) MEMBER("memberName")" ||,
    " MODDATE4("modifiedDate") MODTIME("modifiedTime") USER("modifiedUser")"

  Address ISPEXEC lmmstatsCmd
  lmmstatsRc = rc
  If lmmstatsRc <> 0 then do
    Address ISPEXEC "LMFREE DATAID(&dataid)"
    if rc <> 0 then do
      say "LMFREE failed with return code :" rc
      call ExitProgram(rc)
    end
    say "LMMSTATS failed for member" memberName "with return code :" lmmstatsRc
    call ExitProgram(lmmstatsRc)
  end

return

createTempSequentialDataset:procedure expose dsnPropertiesMap.

  parse arg sourceDsn TempDSN space

  recFormat = dsnPropertiesMap.sourceDsn.REC_FORMAT
  recLen = dsnPropertiesMap.sourceDsn.REC_LEN
  blockSize = dsnPropertiesMap.sourceDsn.BLOCK_SIZE

  allocateCommand = "ALLOC DA('"TempDSN"') NEW DSORG(PS) " ||,
    " TRACKS SPACE("space","space") " ||,
    " BLKSIZE("blockSize") LRECL("recLen") RECFM("recFormat") "

  ADDRESS TSO allocateCommand

  if rc <> 0 then do
    say "Allocate command:" allocateCommand
    say "Failed with rc" rc
    call ExitProgram(8)
  end

  ADDRESS TSO " FREE DA('"TempDSN"') "

return

createTempPdsDataset:procedure expose dsnPropertiesMap.

  parse arg sourceDsn TempDSN space dirBlocks

  recFormat = dsnPropertiesMap.sourceDsn.REC_FORMAT
  recLen = dsnPropertiesMap.sourceDsn.REC_LEN
  blockSize = dsnPropertiesMap.sourceDsn.BLOCK_SIZE
  dsOrg = dsnPropertiesMap.sourceDsn.REC_ORG
  dsnType = dsnPropertiesMap.sourceDsn.LIB_TYPE

  if dsnType = 'PDSE' then dsnType = "LIBRARY"

  allocateCommand = "ALLOC DA('"TempDSN"') NEW DSORG("dsOrg") " ||,
    " TRACKS SPACE("space","space") DIR("dirBlocks") DSNTYPE("dsnType") " ||,
    " BLKSIZE("blockSize") LRECL("recLen") RECFM("recFormat") "

  ADDRESS TSO allocateCommand

  if rc <> 0 then do
    say "Allocate command:" allocateCommand
    say "Failed with rc" rc
    call ExitProgram(8)
  end

  ADDRESS TSO " FREE DA('"TempDSN"') "

return

writeSysinRecordsForIebcopy:

  DROP SYSLINE.

  sysIndex = 1
  SYSLINE.sysIndex = " COPYGROUP OUTDD=OUTDD,INDD=((INDD,R))"

  Do m = 1 to memberlist.0
    sysIndex = sysIndex + 1
    SYSLINE.sysIndex = "         SELECT MEMBER="memberlist.m.name
  end

  SYSLINE.0 = sysIndex

  Address TSO "EXECIO * DISKW SYSIN (STEM SYSLINE. FINIS)"

return

createTargetDatasetIfNotExist:procedure

  parse arg sourceDsn DeplDSN TempDSN

  Address ISPEXEC "DSINFO DATASET('"DeplDSN"')"
  select
    when rc = 0 then
      nop
    when rc = 8 then do /* Dataset does not exist */
      Say 'Dataset' DeplDSN 'does not exist. Allocating dataset like 'sourceDsn
      call allocateTargetDataset DeplDSN TempDSN
    end
    otherwise do
      say 'DSINFO failed on 'DeplDSN'. Return code : 'rc
      call ExitProgram(8)
    end
  end

return

runIebcopyProgram:

  parse arg targetDsn

  Address TSO "ALLOC F(OUTDD) DA('"targetDsn"') SHR REUSE"
  If (rc > 0) then
  Do
    say 'Problem allocating dataset 'targetDsn'. Return code : 'rc
    call ExitProgram(8)
  End

  Say 'Deploying members to 'targetDsn'.'

  say 'IEBCOPY control statement'
  do x = 1 to SYSLINE.0
    say SYSLINE.x
  end

  Address ISPEXEC "ISPEXEC SELECT PGM(IEBCOPY) PARM(WORK=4M)"
  If rc <> 0 Then
  Do
    Address TSO "EXECIO * DISKR SYSPRINT (FINIS STEM sysprint."
    Do sys = 1 to sysprint.0
      Say Strip(sysprint.sys)
    End
    call freeIebcopyDDs
    Call ExitProgram(8)
  End

return

freeIebcopyDDs:

  x = Msg('off')
    Address TSO "FREE F(SYSIN)"
    Address TSO "FREE F(SYSPRINT)"
    Address TSO "FREE F(SYSUT4)"
    Address TSO "FREE F(OUTDD)"
    Address TSO "FREE F(INDD)"
  x = Msg('on')

return

allocateTargetDataset:procedure

  parse arg targetDsn TempDSN

  blksize = getDatasetBlockSize(TempDSN)

  Address TSO "ALLOC DA('"targetDsn"') LIKE('"TempDSN"') BLKSIZE("blksize")"
  If (rc > 0) then
  Do
    say 'Problem allocating dataset 'targetDsn'. Return code : 'rc
    call ExitProgram(8)
  End

return

getDatasetBlockSize:procedure

  parse arg dataset

  Address ISPEXEC "DSINFO DATASET('"dataset"')"
  If (rc > 0) then
  Do
    say 'DSINFO failed on 'dataset'. Return code : 'rc
    call ExitProgram(8)
  End

  blockSize = Strip(zdsblk)

return blockSize

getFileSizeInTracks:procedure

  parse arg filePath 

  shellcmd="ls -Ego '"filePath"'"
  
  sh_rc = bpxwunix(shellcmd,,stdout.,stderr.)
  If sh_rc /= 0 Then do
    say "Command :" shellcmd "failed with rc :" sh_rc
    If stderr.0 > 0 Then
    Do e = 1 to stderr.0
        Say stderr.e
    End
    Call ExitProgram(sh_rc)
  end

  if stdout.0 /= 1 then do
    say "Unable to get size of the file:" filePath
    call ExitProgram(8)
  end

  fileSize = 0
  parse var stdout.1 stuff 21 sizeInBytes .
  if sizeInBytes <= 56664 Then
    fileSize = 1
  else
    fileSize = Format(sizeInBytes/56664,,0)

return fileSize

getSpaceInTracksAndDirBlocks:procedure

  parse arg dirPath

  shellcmd="ls -lk '"dirPath"'"

  sh_rc = bpxwunix(shellcmd,,stdout.,stderr.)
  If sh_rc /= 0 Then do
    say "Command :" shellcmd "failed with rc :" sh_rc
    If stderr.0 > 0 Then
    Do e = 1 to stderr.0
      Say stderr.e
    End
    Call ExitProgram(sh_rc)
  end

  If stdout.0 = 0 then do
    say "Unable to get size of the files for directory:" dirPath
    call ExitProgram(8)
  end

  filesCount = stdout.0 - 1
  dirSize = format(filesCount/6,,0) + 1

  parse var stdout.1 total dirSizeInKiloBytes .

  filesSize = dirSizeInKiloBytes * 1024

  sizeInTracks = 0
  if filesSize <= 56664 Then
    sizeInTracks = 1
  else
    sizeInTracks = format(filesSize/56664,,0)

return sizeInTracks dirSize

deleteDataset:procedure
  parse arg datasetName 
  quotedDsn = "'"datasetName"'"
  ListdsiRC = Listdsi(quotedDsn)
  If ListdsiRC <= 4 Then
  Do
    Say 'Deleting dataset 'datasetName
    Address TSO "DELETE '"datasetName"'"
  End
  else        
    say 'The dataset 'datasetName' was not deleted because it did not exist.'
Return

deletePdsMembers:procedure expose memberlist.

  parse arg dataset
  Address ISPEXEC "LMINIT DATAID(DID) DATASET('"dataset"') ENQ(SHRW)"
  select
    when rc = 0 then
      nop
    when rc = 8 then do
      Say 'Target dataset 'dataset' does not exist. Skipping member deletion.'
      return
    end
    otherwise do
      say 'The LMINIT function failed for dataset 'dataset'. Return code : 'rc
      call ExitProgram(8)
    end
  end

  Address ISPEXEC "LMOPEN DATAID(&DID) OPTION(OUTPUT)"
  If (rc > 0) then
  Do
    Call freeDataset
    say 'The LMOPEN function failed for dataset 'dataset'. Return code : 'rc
    call ExitProgram(8)
  End
 
  Do m = 1 to memberlist.0
    call deletePdsMember dataset memberlist.m.name
  End

  Call closeDataset
  Call freeDataset

Return

deletePdsMember:

  parse arg dataset member
  Say 'Deleting the member 'member' from 'dataset'.'
  Address ISPEXEC "LMMDEL DATAID(&DID) MEMBER("member")"
  Select
    When (rc = 0) Then
        Nop
    When (rc = 8) Then
    Do
        Say 'The deletion of the member 'member' failed ' ||,
        'because member did not exist in the dataset' dataset
    End
    Otherwise
    Do
      Call closeDataset
      Call freeDataset
      say 'The deletion of the member 'member' failed. Return code : 'rc
      call ExitProgram(8)
    End
  End

return

closeDataset:
  Address ISPEXEC "LMCLOSE DATAID(&DID)"
  If (rc <> 0) then
  Do
    say 'The LMCLOSE function failed with return code : 'rc
    call ExitProgram(8)
  End
Return 

freeDataset:
  Address ISPEXEC "LMFREE DATAID(&DID)"
  If (rc <> 0) then
  Do
    say 'The LMFREE function failed with return code : 'rc
    call ExitProgram(8)
  End
Return

deleteTempDataset:

  x = Msg('off')
    Address TSO "DELETE '"TempDSN"'"
  x = msg('on')

return

getSlashSlashQuotedDsn:procedure

  parse arg datasetName

return '"'||'//'||"'"||datasetName||"'"||'"'

getTargetDatasetsFromContainerMap:procedure expose containerMap.
  parse arg sourceDataset
  if symbol('containerMap.sourceDataset') = 'VAR' then
    return containerMap.sourceDataset

  Say "No target dataset found for source dataset:- "sourceDataset
  call ExitProgram(8)
Return

ExitProgram :procedure

   Parse arg exit_rc

   ZISPFRC = exit_rc
   Address ISPEXEC "VPUT (ZISPFRC) SHARED"

Exit exit_rc