// Copyright 2010 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.

package runtime

import (
	"runtime/internal/atomic"
	"unsafe"
)

type mOS struct {
	waitsemacount uint32
	notesig       *int8
	errstr        *byte
	ignoreHangup  bool
}

func closefd(fd int32) int32

//go:noescape
func open(name *byte, mode, perm int32) int32

//go:noescape
func pread(fd int32, buf unsafe.Pointer, nbytes int32, offset int64) int32

//go:noescape
func pwrite(fd int32, buf unsafe.Pointer, nbytes int32, offset int64) int32

func seek(fd int32, offset int64, whence int32) int64

//go:noescape
func exits(msg *byte)

//go:noescape
func brk_(addr unsafe.Pointer) int32

func sleep(ms int32) int32

func rfork(flags int32) int32

//go:noescape
func plan9_semacquire(addr *uint32, block int32) int32

//go:noescape
func plan9_tsemacquire(addr *uint32, ms int32) int32

//go:noescape
func plan9_semrelease(addr *uint32, count int32) int32

//go:noescape
func notify(fn unsafe.Pointer) int32

func noted(mode int32) int32

//go:noescape
func nsec(*int64) int64

//go:noescape
func sigtramp(ureg, note unsafe.Pointer)

func setfpmasks()

//go:noescape
func tstart_plan9(newm *m)

func errstr() string

type _Plink uintptr

//go:linkname os_sigpipe os.sigpipe
func os_sigpipe() {
	throw("too many writes on closed pipe")
}

func sigpanic() {
	g := getg()
	if !canpanic(g) {
		throw("unexpected signal during runtime execution")
	}

	note := gostringnocopy((*byte)(unsafe.Pointer(g.m.notesig)))
	switch g.sig {
	case _SIGRFAULT, _SIGWFAULT:
		i := index(note, "addr=")
		if i >= 0 {
			i += 5
		} else if i = index(note, "va="); i >= 0 {
			i += 3
		} else {
			panicmem()
		}
		addr := note[i:]
		g.sigcode1 = uintptr(atolwhex(addr))
		if g.sigcode1 < 0x1000 || g.paniconfault {
			panicmem()
		}
		print("unexpected fault address ", hex(g.sigcode1), "\n")
		throw("fault")
	case _SIGTRAP:
		if g.paniconfault {
			panicmem()
		}
		throw(note)
	case _SIGINTDIV:
		panicdivide()
	case _SIGFLOAT:
		panicfloat()
	default:
		panic(errorString(note))
	}
}

func atolwhex(p string) int64 {
	for hasPrefix(p, " ") || hasPrefix(p, "\t") {
		p = p[1:]
	}
	neg := false
	if hasPrefix(p, "-") || hasPrefix(p, "+") {
		neg = p[0] == '-'
		p = p[1:]
		for hasPrefix(p, " ") || hasPrefix(p, "\t") {
			p = p[1:]
		}
	}
	var n int64
	switch {
	case hasPrefix(p, "0x"), hasPrefix(p, "0X"):
		p = p[2:]
		for ; len(p) > 0; p = p[1:] {
			if '0' <= p[0] && p[0] <= '9' {
				n = n*16 + int64(p[0]-'0')
			} else if 'a' <= p[0] && p[0] <= 'f' {
				n = n*16 + int64(p[0]-'a'+10)
			} else if 'A' <= p[0] && p[0] <= 'F' {
				n = n*16 + int64(p[0]-'A'+10)
			} else {
				break
			}
		}
	case hasPrefix(p, "0"):
		for ; len(p) > 0 && '0' <= p[0] && p[0] <= '7'; p = p[1:] {
			n = n*8 + int64(p[0]-'0')
		}
	default:
		for ; len(p) > 0 && '0' <= p[0] && p[0] <= '9'; p = p[1:] {
			n = n*10 + int64(p[0]-'0')
		}
	}
	if neg {
		n = -n
	}
	return n
}

type sigset struct{}

// Called to initialize a new m (including the bootstrap m).
// Called on the parent thread (main thread in case of bootstrap), can allocate memory.
func mpreinit(mp *m) {
	// Initialize stack and goroutine for note handling.
	mp.gsignal = malg(32 * 1024)
	mp.gsignal.m = mp
	mp.notesig = (*int8)(mallocgc(_ERRMAX, nil, true))
	// Initialize stack for handling strings from the
	// errstr system call, as used in package syscall.
	mp.errstr = (*byte)(mallocgc(_ERRMAX, nil, true))
}

func msigsave(mp *m) {
}

func msigrestore(sigmask sigset) {
}

//go:nosplit
//go:nowritebarrierrec
func clearSignalHandlers() {
}

func sigblock() {
}

// Called to initialize a new m (including the bootstrap m).
// Called on the new thread, cannot allocate memory.
func minit() {
	if atomic.Load(&exiting) != 0 {
		exits(&emptystatus[0])
	}
	// Mask all SSE floating-point exceptions
	// when running on the 64-bit kernel.
	setfpmasks()
}

// Called from dropm to undo the effect of an minit.
func unminit() {
}

var sysstat = []byte("/dev/sysstat\x00")

func getproccount() int32 {
	var buf [2048]byte
	fd := open(&sysstat[0], _OREAD, 0)
	if fd < 0 {
		return 1
	}
	ncpu := int32(0)
	for {
		n := read(fd, unsafe.Pointer(&buf), int32(len(buf)))
		if n <= 0 {
			break
		}
		for i := int32(0); i < n; i++ {
			if buf[i] == '\n' {
				ncpu++
			}
		}
	}
	closefd(fd)
	if ncpu == 0 {
		ncpu = 1
	}
	return ncpu
}

var devswap = []byte("/dev/swap\x00")
var pagesize = []byte(" pagesize\n")

func getPageSize() uintptr {
	var buf [2048]byte
	var pos int
	fd := open(&devswap[0], _OREAD, 0)
	if fd < 0 {
		// There's not much we can do if /dev/swap doesn't
		// exist. However, nothing in the memory manager uses
		// this on Plan 9, so it also doesn't really matter.
		return minPhysPageSize
	}
	for pos < len(buf) {
		n := read(fd, unsafe.Pointer(&buf[pos]), int32(len(buf)-pos))
		if n <= 0 {
			break
		}
		pos += int(n)
	}
	closefd(fd)
	text := buf[:pos]
	// Find "<n> pagesize" line.
	bol := 0
	for i, c := range text {
		if c == '\n' {
			bol = i + 1
		}
		if bytesHasPrefix(text[i:], pagesize) {
			// Parse number at the beginning of this line.
			return uintptr(_atoi(text[bol:]))
		}
	}
	// Again, the page size doesn't really matter, so use a fallback.
	return minPhysPageSize
}

func bytesHasPrefix(s, prefix []byte) bool {
	if len(s) < len(prefix) {
		return false
	}
	for i, p := range prefix {
		if s[i] != p {
			return false
		}
	}
	return true
}

var pid = []byte("#c/pid\x00")

func getpid() uint64 {
	var b [20]byte
	fd := open(&pid[0], 0, 0)
	if fd >= 0 {
		read(fd, unsafe.Pointer(&b), int32(len(b)))
		closefd(fd)
	}
	c := b[:]
	for c[0] == ' ' || c[0] == '\t' {
		c = c[1:]
	}
	return uint64(_atoi(c))
}

func osinit() {
	initBloc()
	ncpu = getproccount()
	physPageSize = getPageSize()
	getg().m.procid = getpid()
}

//go:nosplit
func crash() {
	notify(nil)
	*(*int)(nil) = 0
}

//go:nosplit
func getRandomData(r []byte) {
	extendRandom(r, 0)
}

func initsig(preinit bool) {
	if !preinit {
		notify(unsafe.Pointer(funcPC(sigtramp)))
	}
}

//go:nosplit
func osyield() {
	sleep(0)
}

//go:nosplit
func usleep(µs uint32) {
	ms := int32(µs / 1000)
	if ms == 0 {
		ms = 1
	}
	sleep(ms)
}

//go:nosplit
func nanotime1() int64 {
	var scratch int64
	ns := nsec(&scratch)
	// TODO(aram): remove hack after I fix _nsec in the pc64 kernel.
	if ns == 0 {
		return scratch
	}
	return ns
}

var goexits = []byte("go: exit ")
var emptystatus = []byte("\x00")
var exiting uint32

func goexitsall(status *byte) {
	var buf [_ERRMAX]byte
	if !atomic.Cas(&exiting, 0, 1) {
		return
	}
	getg().m.locks++
	n := copy(buf[:], goexits)
	n = copy(buf[n:], gostringnocopy(status))
	pid := getpid()
	for mp := (*m)(atomic.Loadp(unsafe.Pointer(&allm))); mp != nil; mp = mp.alllink {
		if mp.procid != 0 && mp.procid != pid {
			postnote(mp.procid, buf[:])
		}
	}
	getg().m.locks--
}

var procdir = []byte("/proc/")
var notefile = []byte("/note\x00")

func postnote(pid uint64, msg []byte) int {
	var buf [128]byte
	var tmp [32]byte
	n := copy(buf[:], procdir)
	n += copy(buf[n:], itoa(tmp[:], pid))
	copy(buf[n:], notefile)
	fd := open(&buf[0], _OWRITE, 0)
	if fd < 0 {
		return -1
	}
	len := findnull(&msg[0])
	if write1(uintptr(fd), unsafe.Pointer(&msg[0]), int32(len)) != int32(len) {
		closefd(fd)
		return -1
	}
	closefd(fd)
	return 0
}

//go:nosplit
func exit(e int32) {
	var status []byte
	if e == 0 {
		status = emptystatus
	} else {
		// build error string
		var tmp [32]byte
		status = append(itoa(tmp[:len(tmp)-1], uint64(e)), 0)
	}
	goexitsall(&status[0])
	exits(&status[0])
}

// May run with m.p==nil, so write barriers are not allowed.
//go:nowritebarrier
func newosproc(mp *m) {
	if false {
		print("newosproc mp=", mp, " ostk=", &mp, "\n")
	}
	pid := rfork(_RFPROC | _RFMEM | _RFNOWAIT)
	if pid < 0 {
		throw("newosproc: rfork failed")
	}
	if pid == 0 {
		tstart_plan9(mp)
	}
}

func exitThread(wait *uint32) {
	// We should never reach exitThread on Plan 9 because we let
	// the OS clean up threads.
	throw("exitThread")
}

//go:nosplit
func semacreate(mp *m) {
}

//go:nosplit
func semasleep(ns int64) int {
	_g_ := getg()
	if ns >= 0 {
		ms := timediv(ns, 1000000, nil)
		if ms == 0 {
			ms = 1
		}
		ret := plan9_tsemacquire(&_g_.m.waitsemacount, ms)
		if ret == 1 {
			return 0 // success
		}
		return -1 // timeout or interrupted
	}
	for plan9_semacquire(&_g_.m.waitsemacount, 1) < 0 {
		// interrupted; try again (c.f. lock_sema.go)
	}
	return 0 // success
}

//go:nosplit
func semawakeup(mp *m) {
	plan9_semrelease(&mp.waitsemacount, 1)
}

//go:nosplit
func read(fd int32, buf unsafe.Pointer, n int32) int32 {
	return pread(fd, buf, n, -1)
}

//go:nosplit
func write1(fd uintptr, buf unsafe.Pointer, n int32) int32 {
	return pwrite(int32(fd), buf, n, -1)
}

var _badsignal = []byte("runtime: signal received on thread not created by Go.\n")

// This runs on a foreign stack, without an m or a g. No stack split.
//go:nosplit
func badsignal2() {
	pwrite(2, unsafe.Pointer(&_badsignal[0]), int32(len(_badsignal)), -1)
	exits(&_badsignal[0])
}

func raisebadsignal(sig uint32) {
	badsignal2()
}

func _atoi(b []byte) int {
	n := 0
	for len(b) > 0 && '0' <= b[0] && b[0] <= '9' {
		n = n*10 + int(b[0]) - '0'
		b = b[1:]
	}
	return n
}

func signame(sig uint32) string {
	if sig >= uint32(len(sigtable)) {
		return ""
	}
	return sigtable[sig].name
}

const preemptMSupported = false

func preemptM(mp *m) {
	// Not currently supported.
	//
	// TODO: Use a note like we use signals on POSIX OSes
}
