1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
6
7package syscall_test
8
9import (
10	"flag"
11	"fmt"
12	"internal/testenv"
13	"io"
14	"io/ioutil"
15	"net"
16	"os"
17	"os/exec"
18	"path/filepath"
19	"runtime"
20	"strconv"
21	"syscall"
22	"testing"
23	"time"
24)
25
26// Tests that below functions, structures and constants are consistent
27// on all Unix-like systems.
28func _() {
29	// program scheduling priority functions and constants
30	var (
31		_ func(int, int, int) error   = syscall.Setpriority
32		_ func(int, int) (int, error) = syscall.Getpriority
33	)
34	const (
35		_ int = syscall.PRIO_USER
36		_ int = syscall.PRIO_PROCESS
37		_ int = syscall.PRIO_PGRP
38	)
39
40	// termios constants
41	const (
42		_ int = syscall.TCIFLUSH
43		_ int = syscall.TCIOFLUSH
44		_ int = syscall.TCOFLUSH
45	)
46
47	// fcntl file locking structure and constants
48	var (
49		_ = syscall.Flock_t{
50			// Comment out the Type and Whence tests because
51			// on the Hurd they are int32, not int16.
52			// Type:   int16(0),
53			// Whence: int16(0),
54			Start: int64(0),
55			Len:   int64(0),
56			Pid:   int32(0),
57		}
58	)
59	const (
60		_ = syscall.F_GETLK
61		_ = syscall.F_SETLK
62		_ = syscall.F_SETLKW
63	)
64}
65
66// TestFcntlFlock tests whether the file locking structure matches
67// the calling convention of each kernel.
68// On some Linux systems, glibc uses another set of values for the
69// commands and translates them to the correct value that the kernel
70// expects just before the actual fcntl syscall. As Go uses raw
71// syscalls directly, it must use the real value, not the glibc value.
72// Thus this test also verifies that the Flock_t structure can be
73// roundtripped with F_SETLK and F_GETLK.
74func TestFcntlFlock(t *testing.T) {
75	if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
76		t.Skip("skipping; no child processes allowed on iOS")
77	}
78	flock := syscall.Flock_t{
79		Type:  syscall.F_WRLCK,
80		Start: 31415, Len: 271828, Whence: 1,
81	}
82	if os.Getenv("GO_WANT_HELPER_PROCESS") == "" {
83		// parent
84		tempDir, err := ioutil.TempDir("", "TestFcntlFlock")
85		if err != nil {
86			t.Fatalf("Failed to create temp dir: %v", err)
87		}
88		name := filepath.Join(tempDir, "TestFcntlFlock")
89		fd, err := syscall.Open(name, syscall.O_CREAT|syscall.O_RDWR|syscall.O_CLOEXEC, 0)
90		if err != nil {
91			t.Fatalf("Open failed: %v", err)
92		}
93		defer os.RemoveAll(tempDir)
94		defer syscall.Close(fd)
95		if err := syscall.Ftruncate(fd, 1<<20); err != nil {
96			t.Fatalf("Ftruncate(1<<20) failed: %v", err)
97		}
98		if err := syscall.FcntlFlock(uintptr(fd), syscall.F_SETLK, &flock); err != nil {
99			t.Fatalf("FcntlFlock(F_SETLK) failed: %v", err)
100		}
101		cmd := exec.Command(os.Args[0], "-test.run=^TestFcntlFlock$")
102		cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
103		cmd.ExtraFiles = []*os.File{os.NewFile(uintptr(fd), name)}
104		out, err := cmd.CombinedOutput()
105		if len(out) > 0 || err != nil {
106			t.Fatalf("child process: %q, %v", out, err)
107		}
108	} else {
109		// child
110		got := flock
111		// make sure the child lock is conflicting with the parent lock
112		got.Start--
113		got.Len++
114		if err := syscall.FcntlFlock(3, syscall.F_GETLK, &got); err != nil {
115			t.Fatalf("FcntlFlock(F_GETLK) failed: %v", err)
116		}
117		flock.Pid = int32(syscall.Getppid())
118		// Linux kernel always set Whence to 0
119		flock.Whence = 0
120		if got.Type == flock.Type && got.Start == flock.Start && got.Len == flock.Len && got.Pid == flock.Pid && got.Whence == flock.Whence {
121			os.Exit(0)
122		}
123		t.Fatalf("FcntlFlock got %v, want %v", got, flock)
124	}
125}
126
127// TestPassFD tests passing a file descriptor over a Unix socket.
128//
129// This test involved both a parent and child process. The parent
130// process is invoked as a normal test, with "go test", which then
131// runs the child process by running the current test binary with args
132// "-test.run=^TestPassFD$" and an environment variable used to signal
133// that the test should become the child process instead.
134func TestPassFD(t *testing.T) {
135	testenv.MustHaveExec(t)
136
137	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
138		passFDChild()
139		return
140	}
141
142	if runtime.GOOS == "aix" {
143		// Unix network isn't properly working on AIX 7.2 with Technical Level < 2
144		out, err := exec.Command("oslevel", "-s").Output()
145		if err != nil {
146			t.Skipf("skipping on AIX because oslevel -s failed: %v", err)
147		}
148		if len(out) < len("7200-XX-ZZ-YYMM") { // AIX 7.2, Tech Level XX, Service Pack ZZ, date YYMM
149			t.Skip("skipping on AIX because oslevel -s hasn't the right length")
150		}
151		aixVer := string(out[:4])
152		tl, err := strconv.Atoi(string(out[5:7]))
153		if err != nil {
154			t.Skipf("skipping on AIX because oslevel -s output cannot be parsed: %v", err)
155		}
156		if aixVer < "7200" || (aixVer == "7200" && tl < 2) {
157			t.Skip("skipped on AIX versions previous to 7.2 TL 2")
158		}
159
160	}
161
162	tempDir, err := ioutil.TempDir("", "TestPassFD")
163	if err != nil {
164		t.Fatal(err)
165	}
166	defer os.RemoveAll(tempDir)
167
168	fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)
169	if err != nil {
170		t.Fatalf("Socketpair: %v", err)
171	}
172	defer syscall.Close(fds[0])
173	defer syscall.Close(fds[1])
174	writeFile := os.NewFile(uintptr(fds[0]), "child-writes")
175	readFile := os.NewFile(uintptr(fds[1]), "parent-reads")
176	defer writeFile.Close()
177	defer readFile.Close()
178
179	cmd := exec.Command(os.Args[0], "-test.run=^TestPassFD$", "--", tempDir)
180	cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
181	cmd.ExtraFiles = []*os.File{writeFile}
182
183	out, err := cmd.CombinedOutput()
184	if len(out) > 0 || err != nil {
185		t.Fatalf("child process: %q, %v", out, err)
186	}
187
188	c, err := net.FileConn(readFile)
189	if err != nil {
190		t.Fatalf("FileConn: %v", err)
191	}
192	defer c.Close()
193
194	uc, ok := c.(*net.UnixConn)
195	if !ok {
196		t.Fatalf("unexpected FileConn type; expected UnixConn, got %T", c)
197	}
198
199	buf := make([]byte, 32) // expect 1 byte
200	oob := make([]byte, 32) // expect 24 bytes
201	closeUnix := time.AfterFunc(5*time.Second, func() {
202		t.Logf("timeout reading from unix socket")
203		uc.Close()
204	})
205	_, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
206	if err != nil {
207		t.Fatalf("ReadMsgUnix: %v", err)
208	}
209	closeUnix.Stop()
210
211	scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
212	if err != nil {
213		t.Fatalf("ParseSocketControlMessage: %v", err)
214	}
215	if len(scms) != 1 {
216		t.Fatalf("expected 1 SocketControlMessage; got scms = %#v", scms)
217	}
218	scm := scms[0]
219	gotFds, err := syscall.ParseUnixRights(&scm)
220	if err != nil {
221		t.Fatalf("syscall.ParseUnixRights: %v", err)
222	}
223	if len(gotFds) != 1 {
224		t.Fatalf("wanted 1 fd; got %#v", gotFds)
225	}
226
227	f := os.NewFile(uintptr(gotFds[0]), "fd-from-child")
228	defer f.Close()
229
230	got, err := ioutil.ReadAll(f)
231	want := "Hello from child process!\n"
232	if string(got) != want {
233		t.Errorf("child process ReadAll: %q, %v; want %q", got, err, want)
234	}
235}
236
237// passFDChild is the child process used by TestPassFD.
238func passFDChild() {
239	defer os.Exit(0)
240
241	// Look for our fd. It should be fd 3, but we work around an fd leak
242	// bug here (https://golang.org/issue/2603) to let it be elsewhere.
243	var uc *net.UnixConn
244	for fd := uintptr(3); fd <= 10; fd++ {
245		f := os.NewFile(fd, "unix-conn")
246		var ok bool
247		netc, _ := net.FileConn(f)
248		uc, ok = netc.(*net.UnixConn)
249		if ok {
250			break
251		}
252	}
253	if uc == nil {
254		fmt.Println("failed to find unix fd")
255		return
256	}
257
258	// Make a file f to send to our parent process on uc.
259	// We make it in tempDir, which our parent will clean up.
260	flag.Parse()
261	tempDir := flag.Arg(0)
262	f, err := ioutil.TempFile(tempDir, "")
263	if err != nil {
264		fmt.Printf("TempFile: %v", err)
265		return
266	}
267
268	f.Write([]byte("Hello from child process!\n"))
269	f.Seek(0, io.SeekStart)
270
271	rights := syscall.UnixRights(int(f.Fd()))
272	dummyByte := []byte("x")
273	n, oobn, err := uc.WriteMsgUnix(dummyByte, rights, nil)
274	if err != nil {
275		fmt.Printf("WriteMsgUnix: %v", err)
276		return
277	}
278	if n != 1 || oobn != len(rights) {
279		fmt.Printf("WriteMsgUnix = %d, %d; want 1, %d", n, oobn, len(rights))
280		return
281	}
282}
283
284// TestUnixRightsRoundtrip tests that UnixRights, ParseSocketControlMessage,
285// and ParseUnixRights are able to successfully round-trip lists of file descriptors.
286func TestUnixRightsRoundtrip(t *testing.T) {
287	testCases := [...][][]int{
288		{{42}},
289		{{1, 2}},
290		{{3, 4, 5}},
291		{{}},
292		{{1, 2}, {3, 4, 5}, {}, {7}},
293	}
294	for _, testCase := range testCases {
295		b := []byte{}
296		var n int
297		for _, fds := range testCase {
298			// Last assignment to n wins
299			n = len(b) + syscall.CmsgLen(4*len(fds))
300			b = append(b, syscall.UnixRights(fds...)...)
301		}
302		// Truncate b
303		b = b[:n]
304
305		scms, err := syscall.ParseSocketControlMessage(b)
306		if err != nil {
307			t.Fatalf("ParseSocketControlMessage: %v", err)
308		}
309		if len(scms) != len(testCase) {
310			t.Fatalf("expected %v SocketControlMessage; got scms = %#v", len(testCase), scms)
311		}
312		for i, scm := range scms {
313			gotFds, err := syscall.ParseUnixRights(&scm)
314			if err != nil {
315				t.Fatalf("ParseUnixRights: %v", err)
316			}
317			wantFds := testCase[i]
318			if len(gotFds) != len(wantFds) {
319				t.Fatalf("expected %v fds, got %#v", len(wantFds), gotFds)
320			}
321			for j, fd := range gotFds {
322				if fd != wantFds[j] {
323					t.Fatalf("expected fd %v, got %v", wantFds[j], fd)
324				}
325			}
326		}
327	}
328}
329
330func TestRlimit(t *testing.T) {
331	var rlimit, zero syscall.Rlimit
332	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit)
333	if err != nil {
334		t.Fatalf("Getrlimit: save failed: %v", err)
335	}
336	if zero == rlimit {
337		t.Fatalf("Getrlimit: save failed: got zero value %#v", rlimit)
338	}
339	set := rlimit
340	set.Cur = set.Max - 1
341	if runtime.GOOS == "darwin" && set.Cur > 10240 {
342		// The max file limit is 10240, even though
343		// the max returned by Getrlimit is 1<<63-1.
344		// This is OPEN_MAX in sys/syslimits.h.
345		set.Cur = 10240
346	}
347	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &set)
348	if err != nil {
349		t.Fatalf("Setrlimit: set failed: %#v %v", set, err)
350	}
351	var get syscall.Rlimit
352	err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &get)
353	if err != nil {
354		t.Fatalf("Getrlimit: get failed: %v", err)
355	}
356	set = rlimit
357	set.Cur = set.Max - 1
358	if runtime.GOOS == "darwin" && set.Cur > 10240 {
359		set.Cur = 10240
360	}
361	if set != get {
362		t.Fatalf("Rlimit: change failed: wanted %#v got %#v", set, get)
363	}
364	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit)
365	if err != nil {
366		t.Fatalf("Setrlimit: restore failed: %#v %v", rlimit, err)
367	}
368}
369
370func TestSeekFailure(t *testing.T) {
371	_, err := syscall.Seek(-1, 0, io.SeekStart)
372	if err == nil {
373		t.Fatalf("Seek(-1, 0, 0) did not fail")
374	}
375	str := err.Error() // used to crash on Linux
376	t.Logf("Seek: %v", str)
377	if str == "" {
378		t.Fatalf("Seek(-1, 0, 0) return error with empty message")
379	}
380}
381
382func TestSetsockoptString(t *testing.T) {
383	// should not panic on empty string, see issue #31277
384	err := syscall.SetsockoptString(-1, 0, 0, "")
385	if err == nil {
386		t.Fatalf("SetsockoptString: did not fail")
387	}
388}
389
390func TestENFILETemporary(t *testing.T) {
391	if !syscall.ENFILE.Temporary() {
392		t.Error("ENFILE is not treated as a temporary error")
393	}
394}
395