Go on z/OS

Go on z/OS

Go on z/OS

This group is a space for open discussions and sharing technical information, and also provides a connection to the wider Go open source community for anyone who is interested in Go on z/OS

 View Only

Launching Go Programs Using Batch

By Joon Lee posted 03/13/26 12:00 PM

  

Running Go Programs in z/OS Batch

Go is well-known for it's simplicity and performance, leading to many developers using Go to create powerful cloud-native applications. With the go-recordio package, you can seamlessly integrate your Go application with z/OS batch processing, opening up the possibilities for mainframe automation and integration!

This blog will go through how you can run your Go programs as batch jobs on z/OS, with a focus on redirecting standard output to traditional mainframe datasets like SYSPRINT.

Why Run Go in Batch Mode?

Traditional z/OS batch processing uses JCL (Job Control Language) to orchestrate jobs that read from and write to datasets. By running Go programs in batch mode, you can:

  • Modernize legacy workflows while maintaining compatibility with existing JCL infrastructure
  • Leverage Go's concurrency for efficient data processing
  • Integrate with existing datasets (VSAM, PDS, sequential files)
  • Use familiar Go idioms like channels and goroutines in mainframe environments
  • Simplify maintenance with Go's clear syntax compared to traditional mainframe languages

Writing Go Output to Datasets

Before we dive into an example, there's something that we need to address first. When running Go programs in z/OS batch, standard Go output functions like println() and fmt.Println() don't automatically write to mainframe datasets like SYSPRINT. The go-recordio package solves this by providing:

  • Record-oriented I/O compatible with z/OS datasets
  • Character encoding conversion (ASCII to EBCDIC)
  • File descriptor redirection to route output to proper destinations

A Quick Example

Let's examine a complete example that redirects Go's standard output and error streams to SYSPRINT using channels for synchronization. Below is the Go program (batch.go):

package main

import (
	"bufio"
	"fmt"
	"os"
	"syscall"

	"github.com/ibmruntimes/go-recordio/v2"
	"github.com/ibmruntimes/go-recordio/v2/utils"
)

func readFdWriteIO(done chan<- struct{}, fd uintptr, stdio int32) {
	defer func() {
		done <- struct{}{}
	}()
	
	file := os.NewFile(fd, "tmp")
	scanner := bufio.NewScanner(file)
	stream := zosrecordio.Stdout()
	
	if stdio == 2 {
		stream = zosrecordio.Stderr()
	}
	
	for scanner.Scan() {
		line := scanner.Text() + "\n"
		data := []byte(line)
		utils.AtoE(data)
		stream.Fwrite(data)
	}
	stream.Fclose()
}


func redirect(doneChan chan struct{}) {
	r, w, err := os.Pipe()
	if err != nil {
		os.Exit(1)
	}
	syscall.Close(2)
	nfd := utils.Dup2(w.Fd(), 2)
	if nfd != 2 {
		os.Exit(1)
	}
	syscall.Close(int(w.Fd()))
	go readFdWriteIO(doneChan, r.Fd(), 2)
	
	
	r, w, err = os.Pipe()
	if err != nil {
		os.Exit(1)
	}
	syscall.Close(1)
	nfd := utils.Dup2(w.Fd(), 1)
	if nfd != 1 {
		os.Exit(1)

	}
	syscall.Close(int(w.Fd()))
	go readFdWriteIO(doneChan, r.Fd(), 1)
	
}

func unredirect(doneChan chan struct{}) {
	syscall.Close(2)
	syscall.Close(1)
	// Wait for both goroutines to complete
	<-doneChan
	<-doneChan
}

func main() {
	doneChan := make(chan struct{}, 2)
	redirect(doneChan)
	fmt.Println("Hello World")
	println("Redirecting fd 2 to SYSPRINT")
	unredirect(doneChan)
}

Let's breakdown how the program works.

1. Channel-Based Synchronization

The program uses a buffered channel with a capacity of 2 to coordinate the completion of two goroutines:

doneChan := make(chan struct{}, 2)

2. File Descriptor Redirection

The redirect() function creates pipes and redirects stdout (fd 1) and stderr (fd 2):

r, w, err := os.Pipe()
syscall.Close(1)
nfd := utils.Dup2(w.Fd(), 1)

This ensures that any output written to stdout or stderr is captured by the pipe's read end.

3. Concurrent Output Processing

Two goroutines run concurrently to process output:

go readFdWriteIO(doneChan, r.Fd(), 1)  // stdout
go readFdWriteIO(doneChan, r.Fd(), 2)  // stderr

Each goroutine:

  • Reads lines from the pipe
  • Converts ASCII to EBCDIC using utils.AtoE()
  • Writes to the appropriate z/OS stream (SYSPRINT or SYSERR)
  • Signals completion via the channel

4. Graceful Shutdown

The unredirect() function ensures clean termination:

syscall.Close(2)
syscall.Close(1)
<-doneChan  // Wait for stderr goroutine
<-doneChan  // Wait for stdout goroutine

This prevents the program from exiting before all output is written.

The JCL

Below is our JCL file which we have named gobatch.jcl:

//GOUSER1 JOB (&SYSUID),MSGLEVEL=(1,1),
// MSGCLASS=H,TIME=(0,29),REGION=900M
//HOLD OUTPUT JESDS=ALL,DEFAULT=Y,OUTDISP=(HOLD,HOLD)
//*---------------------------------------------------
//DOEXE EXEC PGM=BATCH2,
// PARM=('POSIX(ON) FILETAG(AUTOCVT,AUTOTAG)/')
//STEPLIB DD DSN=&SYSUID..GO.PDSE,DISP=SHARE
//SYSPRINT DD SYSOUT=*,DCB=(RECFM=V,LRECL=133)
//SYSOUT DD SYSOUT=*,DCB=(RECFM=V,LRECL=133)
//SYSTERM DD SYSOUT=*,DCB=(RECFM=V,LRECL=133)
//SYSUDUMP DD DSN=&SYSUID..DUMPS,DISP=SHARE
//SYSMDUMP DD DSN=&SYSUID..DUMPS,DISP=SHARE
//CEEDUMP  DD DSN=&SYSUID..DUMPS,DISP=SHARE
//STDENV DD *
PATH=/bin:/usr/bin
_BPXK_AUTOCVT=ON
_TAG_REDIR_ERR=txt
_TAG_REDIR_IN=txt
_TAG_REDIR_OUT=txt
/*

The key JCL elements:

  • PGM=BATCH2: Specifies the Go executable to run
  • PARM: Enables POSIX mode and automatic file tagging
  • STEPLIB: Points to the PDS containing the Go executable
  • SYSPRINT: Defines the output dataset (SYSOUT=*)
  • STDENV: Sets environment variables for the POSIX environment

Building and Deploying

To build the Go program, simply run:

go build batch.go

This will create a binary called batch. Next we need to copy our program to a z/OS PDS.

cp -X batch "//'YOUR.GO.PDSE(BATCH)'"

The -X flag preserves the executable attribute. Now we can customize and submit our JCL:

  1. Edit gobatch.jcl to replace &SYSUID with your user ID
  2. Update the STEPLIB DD to point to your PDS
  3. Submit the job using TSO or ISPF

Finally, we can check the SYSPRINT output in SDSF or your job output viewer:

Hello World
Redirecting fd 2 to SYSPRINT

Conclusion

Running Go programs in z/OS batch environments combines the best of a modern programming language with mainframe reliability. The go-recordio package makes this integration easy! Whether you're modernizing legacy applications or building new batch processes, Go provides a great alternative to traditional mainframe languages while maintaining full compatibility with z/OS infrastructure.

0 comments
21 views

Permalink