cred *Credential
ngroups, groups uintptr
c uintptr
+ rlim *Rlimit
+ lim Rlimit
)
pidfd = -1
- rlim := origRlimitNofile.Load()
+ rlim = origRlimitNofile.Load()
if sys.UidMappings != nil {
puid = []byte("/proc/self/uid_map\000")
// Restore original rlimit.
if rlim != nil {
- RawSyscall6(SYS_PRLIMIT64, 0, RLIMIT_NOFILE, uintptr(unsafe.Pointer(rlim)), 0, 0, 0)
+ // Some other process may have changed our rlimit by
+ // calling prlimit. We can check for that case because
+ // our current rlimit will not be the value we set when
+ // caching the rlimit in the init function in rlimit.go.
+ //
+ // Note that this test is imperfect, since it won't catch
+ // the case in which some other process used prlimit to
+ // set our rlimits to max-1/max. In that case we will fall
+ // back to the original cur/max when starting the child.
+ // We hope that setting to max-1/max is unlikely.
+ _, _, err1 = RawSyscall6(SYS_PRLIMIT64, 0, RLIMIT_NOFILE, 0, uintptr(unsafe.Pointer(&lim)), 0, 0)
+ if err1 != 0 || (lim.Cur == rlim.Max-1 && lim.Max == rlim.Max) {
+ RawSyscall6(SYS_PRLIMIT64, 0, RLIMIT_NOFILE, uintptr(unsafe.Pointer(rlim)), 0, 0, 0)
+ }
}
// Enable tracing if requested.
package syscall_test
import (
+ "context"
"fmt"
"internal/testenv"
"io"
t.Fatalf("origRlimitNofile got=%v, want=%v", rlimLater, rlimOrig)
}
}
+
+const magicRlimitValue = 42
+
+// TestPrlimitFileLimit tests that we can start a Go program, use
+// prlimit to change its NOFILE limit, and have that updated limit be
+// seen by children. See issue #66797.
+func TestPrlimitFileLimit(t *testing.T) {
+ switch os.Getenv("GO_WANT_HELPER_PROCESS") {
+ case "prlimit1":
+ testPrlimitFileLimitHelper1(t)
+ return
+ case "prlimit2":
+ testPrlimitFileLimitHelper2(t)
+ return
+ }
+
+ origRlimitNofile := syscall.GetInternalOrigRlimitNofile()
+ defer origRlimitNofile.Store(origRlimitNofile.Load())
+
+ // Set our rlimit to magic+1/max.
+ // That will also become the rlimit of the child.
+
+ var lim syscall.Rlimit
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
+ t.Fatal(err)
+ }
+ max := lim.Max
+
+ lim = syscall.Rlimit{
+ Cur: magicRlimitValue + 1,
+ Max: max,
+ }
+ if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
+ t.Fatal(err)
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ exe, err := os.Executable()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ r1, w1, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r1.Close()
+ defer w1.Close()
+
+ r2, w2, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r2.Close()
+ defer w2.Close()
+
+ var output strings.Builder
+
+ const arg = "-test.run=^TestPrlimitFileLimit$"
+ cmd := testenv.CommandContext(t, ctx, exe, arg, "-test.v")
+ cmd = testenv.CleanCmdEnv(cmd)
+ cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=prlimit1")
+ cmd.ExtraFiles = []*os.File{r1, w2}
+ cmd.Stdout = &output
+ cmd.Stderr = &output
+
+ t.Logf("running %s %s", exe, arg)
+
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ // Wait for the child to start.
+ b := make([]byte, 1)
+ if n, err := r2.Read(b); err != nil {
+ t.Fatal(err)
+ } else if n != 1 {
+ t.Fatalf("read %d bytes, want 1", n)
+ }
+
+ // Set the child's prlimit.
+ lim = syscall.Rlimit{
+ Cur: magicRlimitValue,
+ Max: max,
+ }
+ if err := syscall.Prlimit(cmd.Process.Pid, syscall.RLIMIT_NOFILE, &lim, nil); err != nil {
+ t.Fatalf("Prlimit failed: %v", err)
+ }
+
+ // Tell the child to continue.
+ if n, err := w1.Write(b); err != nil {
+ t.Fatal(err)
+ } else if n != 1 {
+ t.Fatalf("wrote %d bytes, want 1", n)
+ }
+
+ err = cmd.Wait()
+ if output.Len() > 0 {
+ t.Logf("%s", output.String())
+ }
+
+ if err != nil {
+ t.Errorf("child failed: %v", err)
+ }
+}
+
+// testPrlimitFileLimitHelper1 is run by TestPrlimitFileLimit.
+func testPrlimitFileLimitHelper1(t *testing.T) {
+ var lim syscall.Rlimit
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("helper1 rlimit is %v", lim)
+ t.Logf("helper1 cached rlimit is %v", syscall.OrigRlimitNofile())
+
+ // Tell the parent that we are ready.
+ b := []byte{0}
+ if n, err := syscall.Write(4, b); err != nil {
+ t.Fatal(err)
+ } else if n != 1 {
+ t.Fatalf("wrote %d bytes, want 1", n)
+ }
+
+ // Wait for the parent to tell us that prlimit was used.
+ if n, err := syscall.Read(3, b); err != nil {
+ t.Fatal(err)
+ } else if n != 1 {
+ t.Fatalf("read %d bytes, want 1", n)
+ }
+
+ if err := syscall.Close(3); err != nil {
+ t.Errorf("Close(3): %v", err)
+ }
+ if err := syscall.Close(4); err != nil {
+ t.Errorf("Close(4): %v", err)
+ }
+
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("after prlimit helper1 rlimit is %v", lim)
+ t.Logf("after prlimit helper1 cached rlimit is %v", syscall.OrigRlimitNofile())
+
+ // Start the grandchild, which should see the rlimit
+ // set by the prlimit called by the parent.
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ exe, err := os.Executable()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ const arg = "-test.run=^TestPrlimitFileLimit$"
+ cmd := testenv.CommandContext(t, ctx, exe, arg, "-test.v")
+ cmd = testenv.CleanCmdEnv(cmd)
+ cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=prlimit2")
+ t.Logf("running %s %s", exe, arg)
+ out, err := cmd.CombinedOutput()
+ if len(out) > 0 {
+ t.Logf("%s", out)
+ }
+ if err != nil {
+ t.Errorf("grandchild failed: %v", err)
+ } else {
+ fmt.Println("OK")
+ }
+}
+
+// testPrlimitFileLimitHelper2 is run by testPrlimitFileLimit1.
+func testPrlimitFileLimitHelper2(t *testing.T) {
+ var lim syscall.Rlimit
+ if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
+ t.Fatal(err)
+ }
+
+ t.Logf("helper2 rlimit is %v", lim)
+ cached := syscall.OrigRlimitNofile()
+ t.Logf("helper2 cached rlimit is %v", cached)
+
+ // The value return by Getrlimit will have been adjusted.
+ // We should have cached the value set by prlimit called by the parent.
+
+ if cached == nil {
+ t.Fatal("no cached rlimit")
+ } else if cached.Cur != magicRlimitValue {
+ t.Fatalf("cached rlimit is %d, want %d", cached.Cur, magicRlimitValue)
+ }
+
+ fmt.Println("OK")
+}