unix: extend z/OS support

extend support for z/OS target by adding the following syscalls:
- Sendfile (library implementation)
- Fcntl
- MmapPtr
- MunmapPtr

Change-Id: I098748802c2b275c15758e9d0132fca0490e9b70
GitHub-Last-Rev: 5aafd0b757c20e7c29d29e6286fd952ea494408f
GitHub-Pull-Request: golang/sys#223
Reviewed-on: https://go-review.googlesource.com/c/sys/+/620375
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Reviewed-by: Bill O'Farrell <billotosyr@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/unix/syscall_zos_s390x.go b/unix/syscall_zos_s390x.go
index 44db49e..7bf5c04 100644
--- a/unix/syscall_zos_s390x.go
+++ b/unix/syscall_zos_s390x.go
@@ -3155,3 +3155,59 @@
 	}
 	return
 }
+
+func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) {
+	if raceenabled {
+		raceReleaseMerge(unsafe.Pointer(&ioSync))
+	}
+	return sendfile(outfd, infd, offset, count)
+}
+
+func sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) {
+	// TODO: use LE call instead if the call is implemented
+	originalOffset, err := Seek(infd, 0, SEEK_CUR)
+	if err != nil {
+		return -1, err
+	}
+	//start reading data from in_fd
+	if offset != nil {
+		_, err := Seek(infd, *offset, SEEK_SET)
+		if err != nil {
+			return -1, err
+		}
+	}
+
+	buf := make([]byte, count)
+	readBuf := make([]byte, 0)
+	var n int = 0
+	for i := 0; i < count; i += n {
+		n, err := Read(infd, buf)
+		if n == 0 {
+			if err != nil {
+				return -1, err
+			} else { // EOF
+				break
+			}
+		}
+		readBuf = append(readBuf, buf...)
+		buf = buf[0:0]
+	}
+
+	n2, err := Write(outfd, readBuf)
+	if err != nil {
+		return -1, err
+	}
+
+	//When sendfile() returns, this variable will be set to the
+	// offset of the byte following the last byte that was read.
+	if offset != nil {
+		*offset = *offset + int64(n)
+		// If offset is not NULL, then sendfile() does not modify the file
+		// offset of in_fd
+		_, err := Seek(infd, originalOffset, SEEK_SET)
+		if err != nil {
+			return -1, err
+		}
+	}
+	return n2, nil
+}
diff --git a/unix/syscall_zos_test.go b/unix/syscall_zos_test.go
index a77d60e..fad3744 100644
--- a/unix/syscall_zos_test.go
+++ b/unix/syscall_zos_test.go
@@ -295,7 +295,11 @@
 	defer writeFile.Close()
 	defer readFile.Close()
 
-	cmd := exec.Command(os.Args[0], "-test.run=^TestPassFD$", "--", t.TempDir())
+	exe, err := os.Executable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	cmd := exec.Command(exe, "-test.run=^TestPassFD$", "--", t.TempDir())
 	cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
 	if lp := os.Getenv("LD_LIBRARY_PATH"); lp != "" {
 		cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+lp)
@@ -940,7 +944,11 @@
 			p2status := BLOCKED
 			done := make(chan bool)
 			execP2 := func(isBlock bool) {
-				cmd := exec.Command(os.Args[0], "-test.run=^TestFlock$", strconv.Itoa(c.p2mode), f.Name())
+				exe, err := os.Executable()
+				if err != nil {
+					t.Fatal(err)
+				}
+				cmd := exec.Command(exe, "-test.run=^TestFlock$", strconv.Itoa(c.p2mode), f.Name())
 				cmd.Env = append(os.Environ(), "TEST_FLOCK_HELPER=1")
 				out, _ := cmd.CombinedOutput()
 				if p2status, err = strconv.Atoi(string(out)); err != nil {
@@ -1007,7 +1015,11 @@
 		if err != nil {
 			t.Fatalf("Flock: %s", err.Error())
 		}
-		cmd := exec.Command(os.Args[0], "-test.run=TestLegacyFlock", f.Name())
+		exe, err := os.Executable()
+		if err != nil {
+			t.Fatal(err)
+		}
+		cmd := exec.Command(exe, "-test.run=TestLegacyFlock", f.Name())
 		cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
 		out, err := cmd.CombinedOutput()
 		if len(out) > 0 || err != nil {
@@ -2375,7 +2387,11 @@
 
 	for _, c := range testCases {
 		t.Run(c.name, func(t *testing.T) {
-			cmd := exec.Command(os.Args[0], "-test.run=^TestWait4$", fmt.Sprint(c.exitCode))
+			exe, err := os.Executable()
+			if err != nil {
+				t.Fatal(err)
+			}
+			cmd := exec.Command(exe, "-test.run=^TestWait4$", fmt.Sprint(c.exitCode))
 			cmd.Env = []string{"TEST_WAIT4_HELPER=1"}
 			if err := cmd.Start(); err != nil {
 				t.Fatal(err)
@@ -2676,7 +2692,11 @@
 	defer os.Remove(f.Name())
 	f.Close()
 
-	cmd := exec.Command(os.Args[0], "-test.v", "-test.run=^TestMountNamespace$")
+	exe, err := os.Executable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	cmd := exec.Command(exe, "-test.v", "-test.run=^TestMountNamespace$")
 	cmd.Env = append(os.Environ(), "SETNS_HELPER_PROCESS=1")
 	cmd.Env = append(cmd.Env, "MNT_NS_FILE="+f.Name())
 
@@ -3670,7 +3690,11 @@
 		}
 	}
 
-	cmd := exec.Command(os.Args[0], "-test.run=^TestSetns$")
+	exe, err := os.Executable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	cmd := exec.Command(exe, "-test.run=^TestSetns$")
 	cmd.Env = append(os.Environ(), "SETNS_HELPER_PROCESS=1")
 	stdin, err := cmd.StdinPipe()
 	if err != nil {
@@ -3869,5 +3893,126 @@
 
 	if !bytes.Equal(text, buffer[:n]) {
 		t.Fatalf("Expected %+v, read %+v\n", text, buffer[:n])
+
 	}
+
+}
+
+func TestSendfile(t *testing.T) {
+	srcContent := "hello, world"
+	srcFile, err := os.Create(filepath.Join(t.TempDir(), "source"))
+	if err != nil {
+		t.Fatal("error: ", err)
+	}
+	defer srcFile.Close()
+
+	dstFile, err := os.Create(filepath.Join(t.TempDir(), "dst"))
+	if err != nil {
+		t.Fatal("error: ", err)
+	}
+	defer dstFile.Close()
+
+	err = os.WriteFile(srcFile.Name(), []byte(srcContent), 0644)
+	if err != nil {
+		t.Fatal("error: ", err)
+	}
+
+	n, err := unix.Sendfile(int(dstFile.Fd()), int(srcFile.Fd()), nil, len(srcContent))
+	if n != len(srcContent) {
+		t.Fatal("error: mismatch content length want ", len(srcContent), " got ", n)
+	}
+	if err != nil {
+		t.Fatal("error: ", err)
+	}
+
+	b, err := os.ReadFile(dstFile.Name())
+	if err != nil {
+		t.Fatal("error: ", err)
+	}
+
+	content := string(b)
+	if content != srcContent {
+		t.Fatal("content mismatch: ", content, " vs ", srcContent)
+	}
+}
+
+func TestSendfileSocket(t *testing.T) {
+	// Set up source data file.
+	name := filepath.Join(t.TempDir(), "source")
+	const contents = "contents"
+	err := os.WriteFile(name, []byte(contents), 0666)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	done := make(chan bool)
+
+	// Start server listening on a socket.
+	ln, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Skipf("listen failed: %s\n", err)
+	}
+	defer ln.Close()
+	go func() {
+		conn, err := ln.Accept()
+		if err != nil {
+			t.Errorf("failed to accept: %v", err)
+			return
+		}
+		defer conn.Close()
+		b, err := io.ReadAll(conn)
+		if err != nil {
+			t.Errorf("failed to read: %v", err)
+			return
+		}
+		if string(b) != contents {
+			t.Errorf("contents not transmitted: got %s (len=%d), want %s", string(b), len(b), contents)
+		}
+		done <- true
+	}()
+
+	// Open source file.
+	src, err := os.Open(name)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Send source file to server.
+	conn, err := net.Dial("tcp", ln.Addr().String())
+	if err != nil {
+		t.Fatal(err)
+	}
+	file, err := conn.(*net.TCPConn).File()
+	if err != nil {
+		t.Fatal(err)
+	}
+	var off int64
+	n, err := unix.Sendfile(int(file.Fd()), int(src.Fd()), &off, len(contents))
+	if err != nil {
+		t.Errorf("Sendfile failed %s\n", err)
+	}
+	if n != len(contents) {
+		t.Errorf("written count wrong: want %d, got %d", len(contents), n)
+	}
+	// Note: off is updated on some systems and not others. Oh well.
+	// Linux: increments off by the amount sent.
+	// Darwin: leaves off unchanged.
+	// It would be nice to fix Darwin if we can.
+	if off != 0 && off != int64(len(contents)) {
+		t.Errorf("offset wrong: god %d, want %d or %d", off, 0, len(contents))
+	}
+	// The cursor position should be unchanged.
+	pos, err := src.Seek(0, 1)
+	if err != nil {
+		t.Errorf("can't get cursor position %s\n", err)
+	}
+	if pos != 0 {
+		t.Errorf("cursor position wrong: got %d, want 0", pos)
+	}
+
+	file.Close() // Note: required to have the close below really send EOF to the server.
+	conn.Close()
+
+	// Wait for server to close.
+	<-done
 }