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 darwin dragonfly freebsd linux netbsd openbsd solaris 6 7package unix_test 8 9import ( 10 "flag" 11 "fmt" 12 "io/ioutil" 13 "net" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "runtime" 18 "syscall" 19 "testing" 20 "time" 21 22 "golang.org/x/sys/unix" 23) 24 25// Tests that below functions, structures and constants are consistent 26// on all Unix-like systems. 27func _() { 28 // program scheduling priority functions and constants 29 var ( 30 _ func(int, int, int) error = unix.Setpriority 31 _ func(int, int) (int, error) = unix.Getpriority 32 ) 33 const ( 34 _ int = unix.PRIO_USER 35 _ int = unix.PRIO_PROCESS 36 _ int = unix.PRIO_PGRP 37 ) 38 39 // termios constants 40 const ( 41 _ int = unix.TCIFLUSH 42 _ int = unix.TCIOFLUSH 43 _ int = unix.TCOFLUSH 44 ) 45 46 // fcntl file locking structure and constants 47 var ( 48 _ = unix.Flock_t{ 49 Type: int16(0), 50 Whence: int16(0), 51 Start: int64(0), 52 Len: int64(0), 53 Pid: int32(0), 54 } 55 ) 56 const ( 57 _ = unix.F_GETLK 58 _ = unix.F_SETLK 59 _ = unix.F_SETLKW 60 ) 61} 62 63func TestErrnoSignalName(t *testing.T) { 64 testErrors := []struct { 65 num syscall.Errno 66 name string 67 }{ 68 {syscall.EPERM, "EPERM"}, 69 {syscall.EINVAL, "EINVAL"}, 70 {syscall.ENOENT, "ENOENT"}, 71 } 72 73 for _, te := range testErrors { 74 t.Run(fmt.Sprintf("%d/%s", te.num, te.name), func(t *testing.T) { 75 e := unix.ErrnoName(te.num) 76 if e != te.name { 77 t.Errorf("ErrnoName(%d) returned %s, want %s", te.num, e, te.name) 78 } 79 }) 80 } 81 82 testSignals := []struct { 83 num syscall.Signal 84 name string 85 }{ 86 {syscall.SIGHUP, "SIGHUP"}, 87 {syscall.SIGPIPE, "SIGPIPE"}, 88 {syscall.SIGSEGV, "SIGSEGV"}, 89 } 90 91 for _, ts := range testSignals { 92 t.Run(fmt.Sprintf("%d/%s", ts.num, ts.name), func(t *testing.T) { 93 s := unix.SignalName(ts.num) 94 if s != ts.name { 95 t.Errorf("SignalName(%d) returned %s, want %s", ts.num, s, ts.name) 96 } 97 }) 98 } 99} 100 101func TestFcntlInt(t *testing.T) { 102 t.Parallel() 103 file, err := ioutil.TempFile("", "TestFnctlInt") 104 if err != nil { 105 t.Fatal(err) 106 } 107 defer os.Remove(file.Name()) 108 defer file.Close() 109 f := file.Fd() 110 flags, err := unix.FcntlInt(f, unix.F_GETFD, 0) 111 if err != nil { 112 t.Fatal(err) 113 } 114 if flags&unix.FD_CLOEXEC == 0 { 115 t.Errorf("flags %#x do not include FD_CLOEXEC", flags) 116 } 117} 118 119// TestFcntlFlock tests whether the file locking structure matches 120// the calling convention of each kernel. 121func TestFcntlFlock(t *testing.T) { 122 name := filepath.Join(os.TempDir(), "TestFcntlFlock") 123 fd, err := unix.Open(name, unix.O_CREAT|unix.O_RDWR|unix.O_CLOEXEC, 0) 124 if err != nil { 125 t.Fatalf("Open failed: %v", err) 126 } 127 defer unix.Unlink(name) 128 defer unix.Close(fd) 129 flock := unix.Flock_t{ 130 Type: unix.F_RDLCK, 131 Start: 0, Len: 0, Whence: 1, 132 } 133 if err := unix.FcntlFlock(uintptr(fd), unix.F_GETLK, &flock); err != nil { 134 t.Fatalf("FcntlFlock failed: %v", err) 135 } 136} 137 138// TestPassFD tests passing a file descriptor over a Unix socket. 139// 140// This test involved both a parent and child process. The parent 141// process is invoked as a normal test, with "go test", which then 142// runs the child process by running the current test binary with args 143// "-test.run=^TestPassFD$" and an environment variable used to signal 144// that the test should become the child process instead. 145func TestPassFD(t *testing.T) { 146 if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") { 147 t.Skip("cannot exec subprocess on iOS, skipping test") 148 } 149 150 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { 151 passFDChild() 152 return 153 } 154 155 tempDir, err := ioutil.TempDir("", "TestPassFD") 156 if err != nil { 157 t.Fatal(err) 158 } 159 defer os.RemoveAll(tempDir) 160 161 fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM, 0) 162 if err != nil { 163 t.Fatalf("Socketpair: %v", err) 164 } 165 defer unix.Close(fds[0]) 166 defer unix.Close(fds[1]) 167 writeFile := os.NewFile(uintptr(fds[0]), "child-writes") 168 readFile := os.NewFile(uintptr(fds[1]), "parent-reads") 169 defer writeFile.Close() 170 defer readFile.Close() 171 172 cmd := exec.Command(os.Args[0], "-test.run=^TestPassFD$", "--", tempDir) 173 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 174 if lp := os.Getenv("LD_LIBRARY_PATH"); lp != "" { 175 cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+lp) 176 } 177 cmd.ExtraFiles = []*os.File{writeFile} 178 179 out, err := cmd.CombinedOutput() 180 if len(out) > 0 || err != nil { 181 t.Fatalf("child process: %q, %v", out, err) 182 } 183 184 c, err := net.FileConn(readFile) 185 if err != nil { 186 t.Fatalf("FileConn: %v", err) 187 } 188 defer c.Close() 189 190 uc, ok := c.(*net.UnixConn) 191 if !ok { 192 t.Fatalf("unexpected FileConn type; expected UnixConn, got %T", c) 193 } 194 195 buf := make([]byte, 32) // expect 1 byte 196 oob := make([]byte, 32) // expect 24 bytes 197 closeUnix := time.AfterFunc(5*time.Second, func() { 198 t.Logf("timeout reading from unix socket") 199 uc.Close() 200 }) 201 _, oobn, _, _, err := uc.ReadMsgUnix(buf, oob) 202 if err != nil { 203 t.Fatalf("ReadMsgUnix: %v", err) 204 } 205 closeUnix.Stop() 206 207 scms, err := unix.ParseSocketControlMessage(oob[:oobn]) 208 if err != nil { 209 t.Fatalf("ParseSocketControlMessage: %v", err) 210 } 211 if len(scms) != 1 { 212 t.Fatalf("expected 1 SocketControlMessage; got scms = %#v", scms) 213 } 214 scm := scms[0] 215 gotFds, err := unix.ParseUnixRights(&scm) 216 if err != nil { 217 t.Fatalf("unix.ParseUnixRights: %v", err) 218 } 219 if len(gotFds) != 1 { 220 t.Fatalf("wanted 1 fd; got %#v", gotFds) 221 } 222 223 f := os.NewFile(uintptr(gotFds[0]), "fd-from-child") 224 defer f.Close() 225 226 got, err := ioutil.ReadAll(f) 227 want := "Hello from child process!\n" 228 if string(got) != want { 229 t.Errorf("child process ReadAll: %q, %v; want %q", got, err, want) 230 } 231} 232 233// passFDChild is the child process used by TestPassFD. 234func passFDChild() { 235 defer os.Exit(0) 236 237 // Look for our fd. It should be fd 3, but we work around an fd leak 238 // bug here (http://golang.org/issue/2603) to let it be elsewhere. 239 var uc *net.UnixConn 240 for fd := uintptr(3); fd <= 10; fd++ { 241 f := os.NewFile(fd, "unix-conn") 242 var ok bool 243 netc, _ := net.FileConn(f) 244 uc, ok = netc.(*net.UnixConn) 245 if ok { 246 break 247 } 248 } 249 if uc == nil { 250 fmt.Println("failed to find unix fd") 251 return 252 } 253 254 // Make a file f to send to our parent process on uc. 255 // We make it in tempDir, which our parent will clean up. 256 flag.Parse() 257 tempDir := flag.Arg(0) 258 f, err := ioutil.TempFile(tempDir, "") 259 if err != nil { 260 fmt.Printf("TempFile: %v", err) 261 return 262 } 263 264 f.Write([]byte("Hello from child process!\n")) 265 f.Seek(0, 0) 266 267 rights := unix.UnixRights(int(f.Fd())) 268 dummyByte := []byte("x") 269 n, oobn, err := uc.WriteMsgUnix(dummyByte, rights, nil) 270 if err != nil { 271 fmt.Printf("WriteMsgUnix: %v", err) 272 return 273 } 274 if n != 1 || oobn != len(rights) { 275 fmt.Printf("WriteMsgUnix = %d, %d; want 1, %d", n, oobn, len(rights)) 276 return 277 } 278} 279 280// TestUnixRightsRoundtrip tests that UnixRights, ParseSocketControlMessage, 281// and ParseUnixRights are able to successfully round-trip lists of file descriptors. 282func TestUnixRightsRoundtrip(t *testing.T) { 283 testCases := [...][][]int{ 284 {{42}}, 285 {{1, 2}}, 286 {{3, 4, 5}}, 287 {{}}, 288 {{1, 2}, {3, 4, 5}, {}, {7}}, 289 } 290 for _, testCase := range testCases { 291 b := []byte{} 292 var n int 293 for _, fds := range testCase { 294 // Last assignment to n wins 295 n = len(b) + unix.CmsgLen(4*len(fds)) 296 b = append(b, unix.UnixRights(fds...)...) 297 } 298 // Truncate b 299 b = b[:n] 300 301 scms, err := unix.ParseSocketControlMessage(b) 302 if err != nil { 303 t.Fatalf("ParseSocketControlMessage: %v", err) 304 } 305 if len(scms) != len(testCase) { 306 t.Fatalf("expected %v SocketControlMessage; got scms = %#v", len(testCase), scms) 307 } 308 for i, scm := range scms { 309 gotFds, err := unix.ParseUnixRights(&scm) 310 if err != nil { 311 t.Fatalf("ParseUnixRights: %v", err) 312 } 313 wantFds := testCase[i] 314 if len(gotFds) != len(wantFds) { 315 t.Fatalf("expected %v fds, got %#v", len(wantFds), gotFds) 316 } 317 for j, fd := range gotFds { 318 if fd != wantFds[j] { 319 t.Fatalf("expected fd %v, got %v", wantFds[j], fd) 320 } 321 } 322 } 323 } 324} 325 326func TestRlimit(t *testing.T) { 327 var rlimit, zero unix.Rlimit 328 err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit) 329 if err != nil { 330 t.Fatalf("Getrlimit: save failed: %v", err) 331 } 332 if zero == rlimit { 333 t.Fatalf("Getrlimit: save failed: got zero value %#v", rlimit) 334 } 335 set := rlimit 336 set.Cur = set.Max - 1 337 err = unix.Setrlimit(unix.RLIMIT_NOFILE, &set) 338 if err != nil { 339 t.Fatalf("Setrlimit: set failed: %#v %v", set, err) 340 } 341 var get unix.Rlimit 342 err = unix.Getrlimit(unix.RLIMIT_NOFILE, &get) 343 if err != nil { 344 t.Fatalf("Getrlimit: get failed: %v", err) 345 } 346 set = rlimit 347 set.Cur = set.Max - 1 348 if set != get { 349 // Seems like Darwin requires some privilege to 350 // increase the soft limit of rlimit sandbox, though 351 // Setrlimit never reports an error. 352 switch runtime.GOOS { 353 case "darwin": 354 default: 355 t.Fatalf("Rlimit: change failed: wanted %#v got %#v", set, get) 356 } 357 } 358 err = unix.Setrlimit(unix.RLIMIT_NOFILE, &rlimit) 359 if err != nil { 360 t.Fatalf("Setrlimit: restore failed: %#v %v", rlimit, err) 361 } 362} 363 364func TestSeekFailure(t *testing.T) { 365 _, err := unix.Seek(-1, 0, 0) 366 if err == nil { 367 t.Fatalf("Seek(-1, 0, 0) did not fail") 368 } 369 str := err.Error() // used to crash on Linux 370 t.Logf("Seek: %v", str) 371 if str == "" { 372 t.Fatalf("Seek(-1, 0, 0) return error with empty message") 373 } 374} 375 376func TestDup(t *testing.T) { 377 file, err := ioutil.TempFile("", "TestDup") 378 if err != nil { 379 t.Fatalf("Tempfile failed: %v", err) 380 } 381 defer os.Remove(file.Name()) 382 defer file.Close() 383 f := int(file.Fd()) 384 385 newFd, err := unix.Dup(f) 386 if err != nil { 387 t.Fatalf("Dup: %v", err) 388 } 389 390 err = unix.Dup2(newFd, newFd+1) 391 if err != nil { 392 t.Fatalf("Dup2: %v", err) 393 } 394 395 b1 := []byte("Test123") 396 b2 := make([]byte, 7) 397 _, err = unix.Write(newFd+1, b1) 398 if err != nil { 399 t.Fatalf("Write to dup2 fd failed: %v", err) 400 } 401 _, err = unix.Seek(f, 0, 0) 402 if err != nil { 403 t.Fatalf("Seek failed: %v", err) 404 } 405 _, err = unix.Read(f, b2) 406 if err != nil { 407 t.Fatalf("Read back failed: %v", err) 408 } 409 if string(b1) != string(b2) { 410 t.Errorf("Dup: stdout write not in file, expected %v, got %v", string(b1), string(b2)) 411 } 412} 413 414func TestPoll(t *testing.T) { 415 if runtime.GOOS == "android" || 416 (runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64")) { 417 t.Skip("mkfifo syscall is not available on android and iOS, skipping test") 418 } 419 420 f, cleanup := mktmpfifo(t) 421 defer cleanup() 422 423 const timeout = 100 424 425 ok := make(chan bool, 1) 426 go func() { 427 select { 428 case <-time.After(10 * timeout * time.Millisecond): 429 t.Errorf("Poll: failed to timeout after %d milliseconds", 10*timeout) 430 case <-ok: 431 } 432 }() 433 434 fds := []unix.PollFd{{Fd: int32(f.Fd()), Events: unix.POLLIN}} 435 n, err := unix.Poll(fds, timeout) 436 ok <- true 437 if err != nil { 438 t.Errorf("Poll: unexpected error: %v", err) 439 return 440 } 441 if n != 0 { 442 t.Errorf("Poll: wrong number of events: got %v, expected %v", n, 0) 443 return 444 } 445} 446 447func TestGetwd(t *testing.T) { 448 fd, err := os.Open(".") 449 if err != nil { 450 t.Fatalf("Open .: %s", err) 451 } 452 defer fd.Close() 453 // These are chosen carefully not to be symlinks on a Mac 454 // (unlike, say, /var, /etc) 455 dirs := []string{"/", "/usr/bin"} 456 switch runtime.GOOS { 457 case "android": 458 dirs = []string{"/", "/system/bin"} 459 case "darwin": 460 switch runtime.GOARCH { 461 case "arm", "arm64": 462 d1, err := ioutil.TempDir("", "d1") 463 if err != nil { 464 t.Fatalf("TempDir: %v", err) 465 } 466 d2, err := ioutil.TempDir("", "d2") 467 if err != nil { 468 t.Fatalf("TempDir: %v", err) 469 } 470 dirs = []string{d1, d2} 471 } 472 } 473 oldwd := os.Getenv("PWD") 474 for _, d := range dirs { 475 err = os.Chdir(d) 476 if err != nil { 477 t.Fatalf("Chdir: %v", err) 478 } 479 pwd, err := unix.Getwd() 480 if err != nil { 481 t.Fatalf("Getwd in %s: %s", d, err) 482 } 483 os.Setenv("PWD", oldwd) 484 err = fd.Chdir() 485 if err != nil { 486 // We changed the current directory and cannot go back. 487 // Don't let the tests continue; they'll scribble 488 // all over some other directory. 489 fmt.Fprintf(os.Stderr, "fchdir back to dot failed: %s\n", err) 490 os.Exit(1) 491 } 492 if pwd != d { 493 t.Fatalf("Getwd returned %q want %q", pwd, d) 494 } 495 } 496} 497 498func TestFstatat(t *testing.T) { 499 defer chtmpdir(t)() 500 501 touch(t, "file1") 502 503 var st1 unix.Stat_t 504 err := unix.Stat("file1", &st1) 505 if err != nil { 506 t.Fatalf("Stat: %v", err) 507 } 508 509 var st2 unix.Stat_t 510 err = unix.Fstatat(unix.AT_FDCWD, "file1", &st2, 0) 511 if err != nil { 512 t.Fatalf("Fstatat: %v", err) 513 } 514 515 if st1 != st2 { 516 t.Errorf("Fstatat: returned stat does not match Stat") 517 } 518 519 err = os.Symlink("file1", "symlink1") 520 if err != nil { 521 t.Fatal(err) 522 } 523 524 err = unix.Lstat("symlink1", &st1) 525 if err != nil { 526 t.Fatalf("Lstat: %v", err) 527 } 528 529 err = unix.Fstatat(unix.AT_FDCWD, "symlink1", &st2, unix.AT_SYMLINK_NOFOLLOW) 530 if err != nil { 531 t.Fatalf("Fstatat: %v", err) 532 } 533 534 if st1 != st2 { 535 t.Errorf("Fstatat: returned stat does not match Lstat") 536 } 537} 538 539func TestFchmodat(t *testing.T) { 540 defer chtmpdir(t)() 541 542 touch(t, "file1") 543 err := os.Symlink("file1", "symlink1") 544 if err != nil { 545 t.Fatal(err) 546 } 547 548 mode := os.FileMode(0444) 549 err = unix.Fchmodat(unix.AT_FDCWD, "symlink1", uint32(mode), 0) 550 if err != nil { 551 t.Fatalf("Fchmodat: unexpected error: %v", err) 552 } 553 554 fi, err := os.Stat("file1") 555 if err != nil { 556 t.Fatal(err) 557 } 558 559 if fi.Mode() != mode { 560 t.Errorf("Fchmodat: failed to change file mode: expected %v, got %v", mode, fi.Mode()) 561 } 562 563 mode = os.FileMode(0644) 564 didChmodSymlink := true 565 err = unix.Fchmodat(unix.AT_FDCWD, "symlink1", uint32(mode), unix.AT_SYMLINK_NOFOLLOW) 566 if err != nil { 567 if (runtime.GOOS == "android" || runtime.GOOS == "linux" || runtime.GOOS == "solaris") && err == unix.EOPNOTSUPP { 568 // Linux and Illumos don't support flags != 0 569 didChmodSymlink = false 570 } else { 571 t.Fatalf("Fchmodat: unexpected error: %v", err) 572 } 573 } 574 575 if !didChmodSymlink { 576 // Didn't change mode of the symlink. On Linux, the permissions 577 // of a symbolic link are always 0777 according to symlink(7) 578 mode = os.FileMode(0777) 579 } 580 581 var st unix.Stat_t 582 err = unix.Lstat("symlink1", &st) 583 if err != nil { 584 t.Fatal(err) 585 } 586 587 got := os.FileMode(st.Mode & 0777) 588 if got != mode { 589 t.Errorf("Fchmodat: failed to change symlink mode: expected %v, got %v", mode, got) 590 } 591} 592 593func TestMkdev(t *testing.T) { 594 major := uint32(42) 595 minor := uint32(7) 596 dev := unix.Mkdev(major, minor) 597 598 if unix.Major(dev) != major { 599 t.Errorf("Major(%#x) == %d, want %d", dev, unix.Major(dev), major) 600 } 601 if unix.Minor(dev) != minor { 602 t.Errorf("Minor(%#x) == %d, want %d", dev, unix.Minor(dev), minor) 603 } 604} 605 606// mktmpfifo creates a temporary FIFO and provides a cleanup function. 607func mktmpfifo(t *testing.T) (*os.File, func()) { 608 err := unix.Mkfifo("fifo", 0666) 609 if err != nil { 610 t.Fatalf("mktmpfifo: failed to create FIFO: %v", err) 611 } 612 613 f, err := os.OpenFile("fifo", os.O_RDWR, 0666) 614 if err != nil { 615 os.Remove("fifo") 616 t.Fatalf("mktmpfifo: failed to open FIFO: %v", err) 617 } 618 619 return f, func() { 620 f.Close() 621 os.Remove("fifo") 622 } 623} 624 625// utilities taken from os/os_test.go 626 627func touch(t *testing.T, name string) { 628 f, err := os.Create(name) 629 if err != nil { 630 t.Fatal(err) 631 } 632 if err := f.Close(); err != nil { 633 t.Fatal(err) 634 } 635} 636 637// chtmpdir changes the working directory to a new temporary directory and 638// provides a cleanup function. Used when PWD is read-only. 639func chtmpdir(t *testing.T) func() { 640 oldwd, err := os.Getwd() 641 if err != nil { 642 t.Fatalf("chtmpdir: %v", err) 643 } 644 d, err := ioutil.TempDir("", "test") 645 if err != nil { 646 t.Fatalf("chtmpdir: %v", err) 647 } 648 if err := os.Chdir(d); err != nil { 649 t.Fatalf("chtmpdir: %v", err) 650 } 651 return func() { 652 if err := os.Chdir(oldwd); err != nil { 653 t.Fatalf("chtmpdir: %v", err) 654 } 655 os.RemoveAll(d) 656 } 657} 658