// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build ignore

/*
This program must be on the most current zos release.

This program generates with data from

	//\'CEE.SCEELIB\(CELQS003\)\'
	syscall_zos_s390x.go

to output files:

	zsyscall_zos_s390x.go (generated syscall)
	zsymaddr_zos_s390x.s  (access to the function variable for functions that may not exist)
	zsysnum_zos_s390x.go  (offset from libvec)

synopsis:

	  go run ./mksyscall_zos_s390x.go

	or (with default flags)
	  go run mksyscall_zos_s390x.go -o_sysnum zsysnum_zos_s390x.go -o_syscall zsyscall_zos_s390x.go -i_syscall syscall_zos_s390x.go -o_asm zsymaddr_zos_s390x.s

	or if processed on a different platform
	  go run ./mksyscall_zos_s390x.go -i_testfile CELQS003.txt
		where CELQS003.txt is a text file copy of //\'CEE.SCEELIB\(CELQS003\)\'
*/
package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"
)

var (
	sysnumfile = flag.String("o_sysnum", "zsysnum_zos_s390x.go", "zos LE offsets output file in Go")
	outputgo   = flag.String("o_syscall", "zsyscall_zos_s390x.go", "zos generated syscall output file in Go")
	inputgo    = flag.String("i_syscall", "syscall_zos_s390x.go", "zos input file that contain //sys statements")
	outasm     = flag.String("o_asm", "zsymaddr_zos_s390x.s", "zos output file for function variable addresses")
	testfile   = flag.String("i_testfile", "", "file for local validation")
)
var copyr = `// %s
// Code generated by the command above; see README.md. DO NOT EDIT.

//go:build zos && s390x
`
var AsmTemplate = `
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT

TEXT ·get_%sAddr(SB), NOSPLIT|NOFRAME, $0-8
	MOVD $·%s(SB), R8
	MOVD R8, ret+0(FP)
	RET
`

// cmdLine returns this programs's commandline arguments
func cmdLine() string {
	_, fileName, _, _ := runtime.Caller(1)
	return "go run " + path.Base(fileName) + " -o_sysnum " + *sysnumfile + " -o_syscall " + *outputgo + " -i_syscall " + *inputgo + " -o_asm " + *outasm
}

func out(ch chan string, file io.ReadCloser) {
	defer file.Close()
	defer close(ch)
	rd := bufio.NewReader(file)
loop:
	for {
		str, err := rd.ReadString('\n')
		if err != nil {
			if err != io.EOF {
				log.Fatal("Read Error:", err)
			}
			break loop
		} else {
			ch <- str
		}
	}
}

type SO struct {
	Symbol string
	Offset int64
}

type SOList []SO

func (s SOList) Len() int      { return len(s) }
func (s SOList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s SOList) Less(i, j int) bool {
	if s[i].Offset == s[j].Offset {
		return s[i].Symbol < s[j].Symbol
	}
	return s[i].Offset < s[j].Offset
}

// Param is function parameter
type Param struct {
	Name string
	Type string
}

// parseParamList parses parameter list and returns a slice of parameters
func parseParamList(list string) []string {
	list = strings.TrimSpace(list)
	if list == "" {
		return []string{}
	}
	return regexp.MustCompile(`\s*,\s*`).Split(list, -1)
}

// parseParam splits a parameter into name and type
func parseParam(p string) Param {
	ps := regexp.MustCompile(`^(\S*) (\S*)$`).FindStringSubmatch(p)
	if ps == nil {
		fmt.Fprintf(os.Stderr, "malformed parameter: %s\n", p)
		os.Exit(1)
	}
	return Param{ps[1], ps[2]}
}

func main() {
	flag.Parse()
	sidedeck := "//'CEE.SCEELIB(CELQS003)'"
	if *testfile != "" {
		sidedeck = *testfile
	}
	args := []string{"-u", sidedeck}
	cmd := exec.Command("/bin/cat", args...)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		println("err stdout ")
		log.Fatal(err)
	}
	c1 := make(chan string)
	go out(c1, stdout)
	err2 := cmd.Start()
	if err2 != nil {
		log.Fatal(err2)
	}
	longest := 0
	outstanding := 1
	// IMPORT DATA64,CELQV003,'environ',001
	r1 := regexp.MustCompile("^ +IMPORT +CODE64,CELQV003,'([A-Za-z_][A-Za-z0-9_]*)',([0-9A-F][0-9A-F][0-9A-F]) *\n$")
	m := make(map[string]int64)
	for outstanding > 0 {
		select {
		case msg1, ok := <-c1:
			if ok {
				result := r1.FindStringSubmatch(msg1)
				if result != nil {
					if len(result) > 2 {
						symbol := "SYS_" + strings.ToUpper(result[1])
						offset, e1 := strconv.ParseInt(result[2], 16, 64)
						if e1 == nil {
							if len(symbol) > longest {
								longest = len(symbol)
							}
							m[symbol] = offset
						} else {
							fmt.Printf("ERROR %s\n", msg1)
						}
					}
				}
			} else {
				c1 = nil
				outstanding--
			}

		}
	}

	list := make(SOList, len(m))

	i := 0
	for k, v := range m {
		list[i] = SO{k, v}
		i++
	}
	sort.Sort(list)
	fmt.Printf("Writing %s\n", *sysnumfile)
	err = writesysnum(*sysnumfile, &list)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error writesysnum %s %v\n", *sysnumfile, err)
		os.Exit(1)
	}
	err = gofmt(*sysnumfile)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error gofmt %s %v\n", *sysnumfile, err)
		os.Exit(1)
	}

	fmt.Printf("Reading %s\n", *inputgo)
	f, err := os.Open(*inputgo)
	if err != nil {
		fmt.Fprintf(os.Stderr, err.Error())
		os.Exit(1)
	}

	// open and setup the asm output file
	fmt.Printf("Writing %s\n", *outasm)
	fasm, asmerr := os.Create(*outasm)
	if asmerr != nil {
		fmt.Fprintf(os.Stderr, "Error open %s %s\n", *outasm, asmerr.Error())
		os.Exit(1)
	}
	asm := bufio.NewWriter(fasm)
	fmt.Fprintf(asm, copyr, cmdLine())
	fmt.Fprintf(asm, `#include "textflag.h"

//  provide the address of function variable to be fixed up.
`)

	// open and setup the Go output file
	fmt.Printf("Writing %s\n", *outputgo)
	fgo, goerr := os.Create(*outputgo)
	if goerr != nil {
		fmt.Fprintf(os.Stderr, "Error open %s %s\n", *outputgo, goerr.Error())
		os.Exit(1)
	}
	go1 := bufio.NewWriter(fgo)
	fmt.Fprintf(go1, copyr, cmdLine())
	fmt.Fprintf(go1, `

package unix

import (
	"syscall"
	"unsafe"
	"runtime"
)

var _ syscall.Errno

`)

	s := bufio.NewScanner(f)
	scanErr := processStream(s, asm, go1, &m)

	asm.Flush()
	fasm.Close()

	go1.Flush()
	fgo.Close()
	err = gofmt(*outputgo)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error gofmt %s %v\n", *outputgo, err)
		os.Exit(1)
	}
	if scanErr != nil {
		fmt.Fprintf(os.Stderr, "%s", scanErr.Error())
		os.Exit(1)
	}

}

func writesysnum(file string, l *SOList) error {
	f, err := os.Create(file)
	if err != nil {
		return err
	}
	w := bufio.NewWriter(f)
	defer f.Close()
	defer w.Flush()
	fmt.Fprintf(w, copyr, cmdLine())
	fmt.Fprintf(w, `package unix
const (
`)
	for _, item := range *l {
		fmt.Fprintf(w, "    %-40s = 0x%X   // %d\n", item.Symbol, item.Offset, item.Offset)
	}
	fmt.Fprintf(w, `
)`)
	return nil
}
func gofmt(file string) error {
	cmd := exec.Command("gofmt", "-w", file)
	_, err := cmd.Output()

	if err != nil {
		return err
	}

	return nil
}

func processStream(s *bufio.Scanner, asm, go1 *bufio.Writer, m *map[string]int64) error {
	for s.Scan() {
		t := s.Text()
		t = strings.TrimSpace(t)
		t = regexp.MustCompile(`\s+`).ReplaceAllString(t, ` `)
		nonblock := regexp.MustCompile(`^\/\/sysnb `).FindStringSubmatch(t)
		if regexp.MustCompile(`^\/\/sys `).FindStringSubmatch(t) == nil && nonblock == nil {
			continue
		}

		// Line must be of the form
		//	func Open(path string, mode int, perm int) (fd int, errno error)
		// Split into name, in params, out params.
		f := regexp.MustCompile(`^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*((?i)SYS_[A-Z0-9_]+))?$`).FindStringSubmatch(t)
		if f == nil {
			return fmt.Errorf("%s:%s\nmalformed //sys declaration\n", *inputgo, t)
		}
		funct, inps, outps, sysname := f[2], f[3], f[4], f[5]

		if sysname == "" {
			// if it is empty, it is derived from the function name
			sysname = "SYS_" + funct
			sysname = regexp.MustCompile(`([a-z])([A-Z])`).ReplaceAllString(sysname, `${1}_$2`)
			sysname = strings.ToUpper(sysname)
		}

		// Split argument lists on comma.
		in := parseParamList(inps)
		out := parseParamList(outps)
		val, ok := (*m)[sysname]
		if !ok {
			return fmt.Errorf("%s:%s\nsysname %s not found on this system\n", *inputgo, s.Text(), sysname)
		}
		var newfunc bool
		if val > 3488 {
			fmt.Fprintf(asm, AsmTemplate, funct, funct)
			newfunc = true
		} else {
			newfunc = false
		}
		// Try in vain to keep people from editing this file.
		// The theory is that they jump into the middle of the file
		// without reading the header.
		text1 := "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
		text2 := ""
		text3 := ""

		// Go function header.
		outDecl := ""
		if len(out) > 0 {
			outDecl = fmt.Sprintf(" (%s)", strings.Join(out, ", "))
		}
		if newfunc {
			text1 += fmt.Sprintf("func impl_%s(%s)%s {\n", funct, strings.Join(in, ", "), outDecl)
			text2 += fmt.Sprintf("//go:nosplit\nfunc get_%sAddr() *(func(%s) %s)\nvar %s = enter_%s\n", funct, strings.Join(in, ", "), outDecl, funct, funct)
			text2 += fmt.Sprintf("func enter_%s(%s)%s {\n", funct, strings.Join(in, ", "), outDecl)
			text2 += fmt.Sprintf("funcref := get_%sAddr()\n", funct)
			text2 += fmt.Sprintf("\tif funcptrtest(GetZosLibVec()+%s<<4, \"\") == 0 {\n\t\t*funcref = impl_%s\n", sysname, funct)
			text2 += fmt.Sprintf("\t} else {\n\t\t*funcref = error_%s\n", funct)
			text2 += fmt.Sprintf("\t}\n")
			text3 += fmt.Sprintf("func error_%s(%s)%s {\n", funct, strings.Join(in, ", "), outDecl)
		} else {
			text1 += fmt.Sprintf("func %s(%s)%s {\n", funct, strings.Join(in, ", "), outDecl)
		}

		// Check if err return available
		errvar := ""
		for _, param := range out {
			p := parseParam(param)
			if p.Type == "error" {
				errvar = p.Name
				break
			}
		}
		// Prepare arguments to Syscall.
		var args []string
		var fargs []string // for call fowarding
		n := 0
		for _, param := range in {
			p := parseParam(param)
			fargs = append(fargs, p.Name)
			if regexp.MustCompile(`^\*`).FindStringSubmatch(p.Type) != nil {
				args = append(args, "uintptr(unsafe.Pointer("+p.Name+"))")
			} else if p.Type == "string" && errvar != "" {
				text1 += fmt.Sprintf("\tvar _p%d *byte\n", n)
				text1 += fmt.Sprintf("\t_p%d, %s = BytePtrFromString(%s)\n", n, errvar, p.Name)
				text1 += fmt.Sprintf("\tif %s != nil {\n\t\treturn\n\t}\n", errvar)
				args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
				n++
			} else if p.Type == "string" {
				fmt.Fprintf(os.Stderr, *inputgo+":"+funct+" uses string arguments, but has no error return\n")
				text1 += fmt.Sprintf("\tvar _p%d *byte\n", n)
				text1 += fmt.Sprintf("\t_p%d, _ = BytePtrFromString(%s)\n", n, p.Name)
				args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
				n++
			} else if regexp.MustCompile(`^\[\](.*)`).FindStringSubmatch(p.Type) != nil {
				// Convert slice into pointer, length.
				// Have to be careful not to take address of &a[0] if len == 0:
				// pass dummy pointer in that case.
				// Used to pass nil, but some OSes or simulators reject write(fd, nil, 0).
				text1 += fmt.Sprintf("\tvar _p%d unsafe.Pointer\n", n)
				text1 += fmt.Sprintf("\tif len(%s) > 0 {\n\t\t_p%d = unsafe.Pointer(&%s[0])\n\t}", p.Name, n, p.Name)
				text1 += fmt.Sprintf(" else {\n\t\t_p%d = unsafe.Pointer(&_zero)\n\t}\n", n)
				args = append(args, fmt.Sprintf("uintptr(_p%d)", n), fmt.Sprintf("uintptr(len(%s))", p.Name))
				n++
			} else {
				args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
			}
		}

		// Determine which form to use; pad args with zeros.
		asmcall := "CallLeFuncWithErr"

		// Actual call.
		arglist := strings.Join(args, ", ")
		call := fmt.Sprintf("%s(GetZosLibVec()+%s<<4, %s)", asmcall, sysname, arglist)

		// Assign return values.
		body := ""
		ret := []string{"_", "_", "_"}
		doErrno := false
		for i := 0; i < len(out); i++ {
			p := parseParam(out[i])
			reg := ""
			if p.Name == "err" {
				reg = "e1"
				ret[0] = "r0"
				ret[1] = "e2"
				ret[2] = reg
				doErrno = true
			} else {
				reg = fmt.Sprintf("r%d", i)
				ret[i] = reg

			}
			if p.Type == "bool" {
				reg = fmt.Sprintf("%s != 0", reg)
			}
			if reg != "e1" {
				body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
				if newfunc {
					text3 += fmt.Sprintf("\t%s = -1\n", p.Name)
				}
			}
		}
		if ret[0] == "_" && ret[1] == "_" && ret[2] == "_" {
			if nonblock == nil {
				text1 += fmt.Sprintf("\truntime.EnterSyscall()\n")
			}
			text1 += fmt.Sprintf("\t%s\n", call)
			if nonblock == nil {
				text1 += fmt.Sprintf("\truntime.ExitSyscall()\n")
			}
			text2 += fmt.Sprintf("\t(*funcref)(%s)\n", strings.Join(fargs, ", "))
			text2 += "\treturn\n"
		} else {
			if nonblock == nil {
				text1 += fmt.Sprintf("\truntime.EnterSyscall()\n")
			}
			text1 += fmt.Sprintf("\t%s, %s, %s := %s\n", ret[0], ret[1], ret[2], call)
			if nonblock == nil {
				text1 += fmt.Sprintf("\truntime.ExitSyscall()\n")
			}
			text2 += fmt.Sprintf("\treturn (*funcref)(%s)\n", strings.Join(fargs, ", "))
		}
		text1 += body

		if doErrno {
			if newfunc {
				text3 += fmt.Sprintf("\terr = ENOSYS\n")
			}
			text1 += "\tif int64(r0) == -1 {\n"
			text1 += "\t\terr = errnoErr2(e1,e2)\n"
			text1 += "\t}\n"
		}
		if newfunc {
			text2 += "}\n\n"
			text3 += "\treturn\n"
			text3 += "}\n\n"
		}
		text1 += "\treturn\n"
		text1 += "}\n\n"
		fmt.Fprintf(go1, "%s", text1)
		if newfunc {
			fmt.Fprintf(go1, "%s", text2)
			fmt.Fprintf(go1, "%s", text3)
		}

	}
	if err := s.Err(); err != nil {
		return err
	}
	return nil
}
