// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !plan9 && !windows
// +build !plan9,!windows

package main

/*
#include <errno.h>
#include <signal.h>
#include <string.h>

static int clearRestart(int sig) {
	struct sigaction sa;

	memset(&sa, 0, sizeof sa);
	if (sigaction(sig, NULL, &sa) < 0) {
		return errno;
	}
	sa.sa_flags &=~ SA_RESTART;
	if (sigaction(sig, &sa, NULL) < 0) {
		return errno;
	}
	return 0;
}
*/
import "C"

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"os/exec"
	"sync"
	"syscall"
	"time"
)

func init() {
	register("EINTR", EINTR)
	register("Block", Block)
}

// Test various operations when a signal handler is installed without
// the SA_RESTART flag. This tests that the os and net APIs handle EINTR.
func EINTR() {
	if errno := C.clearRestart(C.int(syscall.SIGURG)); errno != 0 {
		log.Fatal(syscall.Errno(errno))
	}
	if errno := C.clearRestart(C.int(syscall.SIGWINCH)); errno != 0 {
		log.Fatal(syscall.Errno(errno))
	}
	if errno := C.clearRestart(C.int(syscall.SIGCHLD)); errno != 0 {
		log.Fatal(syscall.Errno(errno))
	}

	var wg sync.WaitGroup
	testPipe(&wg)
	testNet(&wg)
	testExec(&wg)
	wg.Wait()
	fmt.Println("OK")
}

// spin does CPU bound spinning and allocating for a millisecond,
// to get a SIGURG.
//
//go:noinline
func spin() (float64, []byte) {
	stop := time.Now().Add(time.Millisecond)
	r1 := 0.0
	r2 := make([]byte, 200)
	for time.Now().Before(stop) {
		for i := 1; i < 1e6; i++ {
			r1 += r1 / float64(i)
			r2 = append(r2, bytes.Repeat([]byte{byte(i)}, 100)...)
			r2 = r2[100:]
		}
	}
	return r1, r2
}

// winch sends a few SIGWINCH signals to the process.
func winch() {
	ticker := time.NewTicker(100 * time.Microsecond)
	defer ticker.Stop()
	pid := syscall.Getpid()
	for n := 10; n > 0; n-- {
		syscall.Kill(pid, syscall.SIGWINCH)
		<-ticker.C
	}
}

// sendSomeSignals triggers a few SIGURG and SIGWINCH signals.
func sendSomeSignals() {
	done := make(chan struct{})
	go func() {
		spin()
		close(done)
	}()
	winch()
	<-done
}

// testPipe tests pipe operations.
func testPipe(wg *sync.WaitGroup) {
	r, w, err := os.Pipe()
	if err != nil {
		log.Fatal(err)
	}
	if err := syscall.SetNonblock(int(r.Fd()), false); err != nil {
		log.Fatal(err)
	}
	if err := syscall.SetNonblock(int(w.Fd()), false); err != nil {
		log.Fatal(err)
	}
	wg.Add(2)
	go func() {
		defer wg.Done()
		defer w.Close()
		// Spin before calling Write so that the first ReadFull
		// in the other goroutine will likely be interrupted
		// by a signal.
		sendSomeSignals()
		// This Write will likely be interrupted by a signal
		// as the other goroutine spins in the middle of reading.
		// We write enough data that we should always fill the
		// pipe buffer and need multiple write system calls.
		if _, err := w.Write(bytes.Repeat([]byte{0}, 2<<20)); err != nil {
			log.Fatal(err)
		}
	}()
	go func() {
		defer wg.Done()
		defer r.Close()
		b := make([]byte, 1<<20)
		// This ReadFull will likely be interrupted by a signal,
		// as the other goroutine spins before writing anything.
		if _, err := io.ReadFull(r, b); err != nil {
			log.Fatal(err)
		}
		// Spin after reading half the data so that the Write
		// in the other goroutine will likely be interrupted
		// before it completes.
		sendSomeSignals()
		if _, err := io.ReadFull(r, b); err != nil {
			log.Fatal(err)
		}
	}()
}

// testNet tests network operations.
func testNet(wg *sync.WaitGroup) {
	ln, err := net.Listen("tcp4", "127.0.0.1:0")
	if err != nil {
		if errors.Is(err, syscall.EAFNOSUPPORT) || errors.Is(err, syscall.EPROTONOSUPPORT) {
			return
		}
		log.Fatal(err)
	}
	wg.Add(2)
	go func() {
		defer wg.Done()
		defer ln.Close()
		c, err := ln.Accept()
		if err != nil {
			log.Fatal(err)
		}
		defer c.Close()
		cf, err := c.(*net.TCPConn).File()
		if err != nil {
			log.Fatal(err)
		}
		defer cf.Close()
		if err := syscall.SetNonblock(int(cf.Fd()), false); err != nil {
			log.Fatal(err)
		}
		// See comments in testPipe.
		sendSomeSignals()
		if _, err := cf.Write(bytes.Repeat([]byte{0}, 2<<20)); err != nil {
			log.Fatal(err)
		}
	}()
	go func() {
		defer wg.Done()
		sendSomeSignals()
		c, err := net.Dial("tcp", ln.Addr().String())
		if err != nil {
			log.Fatal(err)
		}
		defer c.Close()
		cf, err := c.(*net.TCPConn).File()
		if err != nil {
			log.Fatal(err)
		}
		defer cf.Close()
		if err := syscall.SetNonblock(int(cf.Fd()), false); err != nil {
			log.Fatal(err)
		}
		// See comments in testPipe.
		b := make([]byte, 1<<20)
		if _, err := io.ReadFull(cf, b); err != nil {
			log.Fatal(err)
		}
		sendSomeSignals()
		if _, err := io.ReadFull(cf, b); err != nil {
			log.Fatal(err)
		}
	}()
}

func testExec(wg *sync.WaitGroup) {
	wg.Add(1)
	go func() {
		defer wg.Done()
		cmd := exec.Command(os.Args[0], "Block")
		stdin, err := cmd.StdinPipe()
		if err != nil {
			log.Fatal(err)
		}
		cmd.Stderr = new(bytes.Buffer)
		cmd.Stdout = cmd.Stderr
		if err := cmd.Start(); err != nil {
			log.Fatal(err)
		}

		go func() {
			sendSomeSignals()
			stdin.Close()
		}()

		if err := cmd.Wait(); err != nil {
			log.Fatalf("%v:\n%s", err, cmd.Stdout)
		}
	}()
}

// Block blocks until stdin is closed.
func Block() {
	io.Copy(io.Discard, os.Stdin)
}
