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
}