// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package strace implements the logic to print out the input and the return value
// of each traced syscall.
package strace

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"gvisor.dev/gvisor/pkg/abi"
	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/bits"
	"gvisor.dev/gvisor/pkg/eventchannel"
	"gvisor.dev/gvisor/pkg/marshal/primitive"
	"gvisor.dev/gvisor/pkg/seccomp"
	"gvisor.dev/gvisor/pkg/sentry/arch"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	pb "gvisor.dev/gvisor/pkg/sentry/strace/strace_go_proto"
	slinux "gvisor.dev/gvisor/pkg/sentry/syscalls/linux"

	"gvisor.dev/gvisor/pkg/hostarch"
)

// DefaultLogMaximumSize is the default LogMaximumSize.
const DefaultLogMaximumSize = 1024

// LogMaximumSize determines the maximum display size for data blobs (read,
// write, etc.).
var LogMaximumSize uint = DefaultLogMaximumSize

// EventMaximumSize determines the maximum size for data blobs (read, write,
// etc.) sent over the event channel. Default is 0 because most clients cannot
// do anything useful with binary text dump of byte array arguments.
var EventMaximumSize uint

// LogAppDataAllowed is set to true when printing application data in strace
// logs is allowed.
var LogAppDataAllowed = true

// ItimerTypes are the possible itimer types.
var ItimerTypes = abi.ValueSet{
	linux.ITIMER_REAL:    "ITIMER_REAL",
	linux.ITIMER_VIRTUAL: "ITIMER_VIRTUAL",
	linux.ITIMER_PROF:    "ITIMER_PROF",
}

func hexNum(num uint64) string {
	return "0x" + strconv.FormatUint(num, 16)
}

func hexArg(arg arch.SyscallArgument) string {
	return hexNum(arg.Uint64())
}

func iovecs(t *kernel.Task, addr hostarch.Addr, iovcnt int, printContent bool, maxBytes uint64) string {
	if iovcnt < 0 || iovcnt > linux.UIO_MAXIOV {
		return fmt.Sprintf("%#x (error decoding iovecs: invalid iovcnt)", addr)
	}
	ars, err := t.CopyInIovecs(addr, iovcnt)
	if err != nil {
		return fmt.Sprintf("%#x (error decoding iovecs: %v)", addr, err)
	}

	var totalBytes uint64
	var truncated bool
	iovs := make([]string, iovcnt)
	for i := 0; !ars.IsEmpty(); i, ars = i+1, ars.Tail() {
		ar := ars.Head()
		if ar.Length() == 0 || !printContent {
			iovs[i] = fmt.Sprintf("{base=%#x, len=%d}", ar.Start, ar.Length())
			continue
		}

		size := uint64(ar.Length())
		if truncated || totalBytes+size > maxBytes {
			truncated = true
			size = maxBytes - totalBytes
		} else {
			totalBytes += uint64(ar.Length())
		}

		b := make([]byte, size)
		amt, err := t.CopyInBytes(ar.Start, b)
		if err != nil {
			iovs[i] = fmt.Sprintf("{base=%#x, len=%d, %q..., error decoding string: %v}", ar.Start, ar.Length(), b[:amt], err)
			continue
		}

		dot := ""
		if truncated {
			// Indicate truncation.
			dot = "..."
		}
		iovs[i] = fmt.Sprintf("{base=%#x, len=%d, %q%s}", ar.Start, ar.Length(), b[:amt], dot)
	}

	return fmt.Sprintf("%#x %s", addr, strings.Join(iovs, ", "))
}

func dump(t *kernel.Task, addr hostarch.Addr, size uint, maximumBlobSize uint, printContent bool) string {
	if !printContent {
		return fmt.Sprintf("{base=%#x, len=%d}", addr, size)
	}
	origSize := size
	if size > maximumBlobSize {
		size = maximumBlobSize
	}
	if size == 0 {
		return ""
	}

	b := make([]byte, size)
	amt, err := t.CopyInBytes(addr, b)
	if err != nil {
		return fmt.Sprintf("%#x (error decoding string: %s)", addr, err)
	}

	dot := ""
	if uint(amt) < origSize {
		// ... if we truncated the dump.
		dot = "..."
	}

	return fmt.Sprintf("%#x %q%s", addr, b[:amt], dot)
}

func path(t *kernel.Task, addr hostarch.Addr) string {
	if addr == 0 {
		return "<null>"
	}
	path, err := t.CopyInString(addr, linux.PATH_MAX)
	if err != nil {
		return fmt.Sprintf("%#x (error decoding path: %s)", addr, err)
	}
	return fmt.Sprintf("%#x %s", addr, path)
}

func fd(t *kernel.Task, fd int32) string {
	root := t.FSContext().RootDirectory()
	defer root.DecRef(t)

	vfsObj := root.Mount().Filesystem().VirtualFilesystem()
	if fd == linux.AT_FDCWD {
		wd := t.FSContext().WorkingDirectory()
		defer wd.DecRef(t)

		name, _ := vfsObj.PathnameWithDeleted(t, root, wd)
		return fmt.Sprintf("AT_FDCWD %s", name)
	}

	file := t.GetFile(fd)
	if file == nil {
		// Cast FD to uint64 to avoid printing negative hex.
		return fmt.Sprintf("%#x (bad FD)", uint64(fd))
	}
	defer file.DecRef(t)

	name, _ := vfsObj.PathnameWithDeleted(t, root, file.VirtualDentry())
	return fmt.Sprintf("%#x %s", fd, name)
}

func fdpair(t *kernel.Task, addr hostarch.Addr) string {
	var fds [2]int32
	_, err := primitive.CopyInt32SliceIn(t, addr, fds[:])
	if err != nil {
		return fmt.Sprintf("%#x (error decoding fds: %s)", addr, err)
	}

	return fmt.Sprintf("%#x [%d %d]", addr, fds[0], fds[1])
}

func uname(t *kernel.Task, addr hostarch.Addr) string {
	var u linux.UtsName
	if _, err := u.CopyIn(t, addr); err != nil {
		return fmt.Sprintf("%#x (error decoding utsname: %s)", addr, err)
	}

	return fmt.Sprintf("%#x %s", addr, u)
}

func utimensTimespec(t *kernel.Task, addr hostarch.Addr) string {
	if addr == 0 {
		return "null"
	}

	var tim linux.Timespec
	if _, err := tim.CopyIn(t, addr); err != nil {
		return fmt.Sprintf("%#x (error decoding timespec: %s)", addr, err)
	}

	var ns string
	switch tim.Nsec {
	case linux.UTIME_NOW:
		ns = "UTIME_NOW"
	case linux.UTIME_OMIT:
		ns = "UTIME_OMIT"
	default:
		ns = fmt.Sprintf("%v", tim.Nsec)
	}
	return fmt.Sprintf("%#x {sec=%v nsec=%s}", addr, tim.Sec, ns)
}

func timespec(t *kernel.Task, addr hostarch.Addr) string {
	if addr == 0 {
		return "null"
	}

	var tim linux.Timespec
	if _, err := tim.CopyIn(t, addr); err != nil {
		return fmt.Sprintf("%#x (error decoding timespec: %s)", addr, err)
	}
	return fmt.Sprintf("%#x {sec=%v nsec=%v}", addr, tim.Sec, tim.Nsec)
}

func timeval(t *kernel.Task, addr hostarch.Addr) string {
	if addr == 0 {
		return "null"
	}

	var tim linux.Timeval
	if _, err := tim.CopyIn(t, addr); err != nil {
		return fmt.Sprintf("%#x (error decoding timeval: %s)", addr, err)
	}

	return fmt.Sprintf("%#x {sec=%v usec=%v}", addr, tim.Sec, tim.Usec)
}

func utimbuf(t *kernel.Task, addr hostarch.Addr) string {
	if addr == 0 {
		return "null"
	}

	var utim linux.Utime
	if _, err := utim.CopyIn(t, addr); err != nil {
		return fmt.Sprintf("%#x (error decoding utimbuf: %s)", addr, err)
	}

	return fmt.Sprintf("%#x {actime=%v, modtime=%v}", addr, utim.Actime, utim.Modtime)
}

func stat(t *kernel.Task, addr hostarch.Addr) string {
	if addr == 0 {
		return "null"
	}

	var stat linux.Stat
	if _, err := stat.CopyIn(t, addr); err != nil {
		return fmt.Sprintf("%#x (error decoding stat: %s)", addr, err)
	}
	return fmt.Sprintf("%#x {dev=%d, ino=%d, mode=%s, nlink=%d, uid=%d, gid=%d, rdev=%d, size=%d, blksize=%d, blocks=%d, atime=%s, mtime=%s, ctime=%s}", addr, stat.Dev, stat.Ino, linux.FileMode(stat.Mode), stat.Nlink, stat.UID, stat.GID, stat.Rdev, stat.Size, stat.Blksize, stat.Blocks, time.Unix(stat.ATime.Sec, stat.ATime.Nsec), time.Unix(stat.MTime.Sec, stat.MTime.Nsec), time.Unix(stat.CTime.Sec, stat.CTime.Nsec))
}

func itimerval(t *kernel.Task, addr hostarch.Addr) string {
	if addr == 0 {
		return "null"
	}

	interval := timeval(t, addr)
	value := timeval(t, addr+hostarch.Addr((*linux.Timeval)(nil).SizeBytes()))
	return fmt.Sprintf("%#x {interval=%s, value=%s}", addr, interval, value)
}

func itimerspec(t *kernel.Task, addr hostarch.Addr) string {
	if addr == 0 {
		return "null"
	}

	interval := timespec(t, addr)
	value := timespec(t, addr+hostarch.Addr((*linux.Timespec)(nil).SizeBytes()))
	return fmt.Sprintf("%#x {interval=%s, value=%s}", addr, interval, value)
}

func stringVector(t *kernel.Task, addr hostarch.Addr) string {
	vec, err := t.CopyInVector(addr, slinux.ExecMaxElemSize, slinux.ExecMaxTotalSize)
	if err != nil {
		return fmt.Sprintf("%#x {error copying vector: %v}", addr, err)
	}
	s := fmt.Sprintf("%#x [", addr)
	for i, v := range vec {
		if i != 0 {
			s += ", "
		}
		s += fmt.Sprintf("%q", v)
	}
	s += "]"
	return s
}

func rusage(t *kernel.Task, addr hostarch.Addr) string {
	if addr == 0 {
		return "null"
	}

	var ru linux.Rusage
	if _, err := ru.CopyIn(t, addr); err != nil {
		return fmt.Sprintf("%#x (error decoding rusage: %s)", addr, err)
	}
	return fmt.Sprintf("%#x %+v", addr, ru)
}

func capHeader(t *kernel.Task, addr hostarch.Addr) string {
	if addr == 0 {
		return "null"
	}

	var hdr linux.CapUserHeader
	if _, err := hdr.CopyIn(t, addr); err != nil {
		return fmt.Sprintf("%#x (error decoding header: %s)", addr, err)
	}

	var version string
	switch hdr.Version {
	case linux.LINUX_CAPABILITY_VERSION_1:
		version = "1"
	case linux.LINUX_CAPABILITY_VERSION_2:
		version = "2"
	case linux.LINUX_CAPABILITY_VERSION_3:
		version = "3"
	default:
		version = strconv.FormatUint(uint64(hdr.Version), 16)
	}

	return fmt.Sprintf("%#x {Version: %s, Pid: %d}", addr, version, hdr.Pid)
}

func capData(t *kernel.Task, hdrAddr, dataAddr hostarch.Addr) string {
	if dataAddr == 0 {
		return "null"
	}

	var hdr linux.CapUserHeader
	if _, err := hdr.CopyIn(t, hdrAddr); err != nil {
		return fmt.Sprintf("%#x (error decoding header: %v)", dataAddr, err)
	}

	var p, i, e uint64

	switch hdr.Version {
	case linux.LINUX_CAPABILITY_VERSION_1:
		var data linux.CapUserData
		if _, err := data.CopyIn(t, dataAddr); err != nil {
			return fmt.Sprintf("%#x (error decoding data: %v)", dataAddr, err)
		}
		p = uint64(data.Permitted)
		i = uint64(data.Inheritable)
		e = uint64(data.Effective)
	case linux.LINUX_CAPABILITY_VERSION_2, linux.LINUX_CAPABILITY_VERSION_3:
		var data [2]linux.CapUserData
		if _, err := linux.CopyCapUserDataSliceIn(t, dataAddr, data[:]); err != nil {
			return fmt.Sprintf("%#x (error decoding data: %v)", dataAddr, err)
		}
		p = uint64(data[0].Permitted) | (uint64(data[1].Permitted) << 32)
		i = uint64(data[0].Inheritable) | (uint64(data[1].Inheritable) << 32)
		e = uint64(data[0].Effective) | (uint64(data[1].Effective) << 32)
	default:
		return fmt.Sprintf("%#x (unknown version %d)", dataAddr, hdr.Version)
	}

	return fmt.Sprintf("%#x {Permitted: %s, Inheritable: %s, Effective: %s}", dataAddr, CapabilityBitset.Parse(p), CapabilityBitset.Parse(i), CapabilityBitset.Parse(e))
}

// pre fills in the pre-execution arguments for a system call. If an argument
// cannot be interpreted before the system call is executed, then a hex value
// will be used. Note that a full output slice will always be provided, that is
// len(return) == len(args).
func (i *SyscallInfo) pre(t *kernel.Task, args arch.SyscallArguments, maximumBlobSize uint) []string {
	var output []string

	for arg := range args {
		if arg >= len(i.format) {
			break
		}
		switch i.format[arg] {
		case FD:
			output = append(output, fd(t, args[arg].Int()))
		case WriteBuffer:
			output = append(output, dump(t, args[arg].Pointer(), args[arg+1].SizeT(), maximumBlobSize, LogAppDataAllowed /* content */))
		case WriteIOVec:
			output = append(output, iovecs(t, args[arg].Pointer(), int(args[arg+1].Int()), LogAppDataAllowed /* content */, uint64(maximumBlobSize)))
		case ReadIOVec, IOVec:
			output = append(output, iovecs(t, args[arg].Pointer(), int(args[arg+1].Int()), false /* content */, uint64(maximumBlobSize)))
		case SendMsgHdr:
			output = append(output, msghdr(t, args[arg].Pointer(), LogAppDataAllowed /* content */, uint64(maximumBlobSize)))
		case RecvMsgHdr:
			output = append(output, msghdr(t, args[arg].Pointer(), false /* content */, uint64(maximumBlobSize)))
		case Path:
			output = append(output, path(t, args[arg].Pointer()))
		case ExecveStringVector:
			output = append(output, stringVector(t, args[arg].Pointer()))
		case SetSockOptVal:
			output = append(output, sockOptVal(t, args[arg-2].Uint64() /* level */, args[arg-1].Uint64() /* optName */, args[arg].Pointer() /* optVal */, args[arg+1].Uint64() /* optLen */, maximumBlobSize))
		case SockOptLevel:
			output = append(output, sockOptLevels.Parse(args[arg].Uint64()))
		case SockOptName:
			output = append(output, sockOptNames[args[arg-1].Uint64() /* level */].Parse(args[arg].Uint64()))
		case SockAddr:
			output = append(output, sockAddr(t, args[arg].Pointer(), uint32(args[arg+1].Uint64())))
		case SockLen:
			output = append(output, sockLenPointer(t, args[arg].Pointer()))
		case SockFamily:
			output = append(output, SocketFamily.Parse(uint64(args[arg].Int())))
		case SockType:
			output = append(output, sockType(args[arg].Int()))
		case SockProtocol:
			output = append(output, sockProtocol(args[arg-2].Int(), args[arg].Int()))
		case SockFlags:
			output = append(output, sockFlags(args[arg].Int()))
		case Timespec:
			output = append(output, timespec(t, args[arg].Pointer()))
		case UTimeTimespec:
			output = append(output, utimensTimespec(t, args[arg].Pointer()))
		case ItimerVal:
			output = append(output, itimerval(t, args[arg].Pointer()))
		case ItimerSpec:
			output = append(output, itimerspec(t, args[arg].Pointer()))
		case Timeval:
			output = append(output, timeval(t, args[arg].Pointer()))
		case Utimbuf:
			output = append(output, utimbuf(t, args[arg].Pointer()))
		case CloneFlags:
			output = append(output, CloneFlagSet.Parse(uint64(args[arg].Uint())))
		case OpenFlags:
			output = append(output, open(uint64(args[arg].Uint())))
		case Mode:
			output = append(output, linux.FileMode(args[arg].ModeT()).String())
		case FutexOp:
			output = append(output, futex(uint64(args[arg].Uint())))
		case PtraceRequest:
			output = append(output, PtraceRequestSet.Parse(args[arg].Uint64()))
		case ItimerType:
			output = append(output, ItimerTypes.Parse(uint64(args[arg].Int())))
		case Signal:
			output = append(output, signalNames.ParseDecimal(args[arg].Uint64()))
		case SignalMaskAction:
			output = append(output, signalMaskActions.Parse(uint64(args[arg].Int())))
		case SigSet:
			output = append(output, sigSet(t, args[arg].Pointer()))
		case SigAction:
			output = append(output, sigAction(t, args[arg].Pointer()))
		case CapHeader:
			output = append(output, capHeader(t, args[arg].Pointer()))
		case CapData:
			output = append(output, capData(t, args[arg-1].Pointer(), args[arg].Pointer()))
		case PollFDs:
			output = append(output, pollFDs(t, args[arg].Pointer(), uint(args[arg+1].Uint()), false))
		case EpollCtlOp:
			output = append(output, epollCtlOps.Parse(uint64(args[arg].Int())))
		case EpollEvent:
			output = append(output, epollEvent(t, args[arg].Pointer()))
		case EpollEvents:
			output = append(output, epollEvents(t, args[arg].Pointer(), 0 /* numEvents */, uint64(maximumBlobSize)))
		case SelectFDSet:
			output = append(output, fdSet(t, int(args[0].Int()), args[arg].Pointer()))
		case MmapProt:
			output = append(output, ProtectionFlagSet.Parse(uint64(args[arg].Uint())))
		case MmapFlags:
			output = append(output, MmapFlagSet.Parse(uint64(args[arg].Uint())))
		case CloseRangeFlags:
			output = append(output, CloseRangeFlagSet.Parse(uint64(args[arg].Uint())))
		case Oct:
			output = append(output, "0o"+strconv.FormatUint(args[arg].Uint64(), 8))
		case Hex:
			fallthrough
		default:
			output = append(output, hexArg(args[arg]))
		}
	}

	return output
}

// post fills in the post-execution arguments for a system call. This modifies
// the given output slice in place with arguments that may only be interpreted
// after the system call has been executed.
func (i *SyscallInfo) post(t *kernel.Task, args arch.SyscallArguments, rval uintptr, output []string, maximumBlobSize uint) {
	for arg := range output {
		if arg >= len(i.format) {
			break
		}
		switch i.format[arg] {
		case ReadBuffer:
			output[arg] = dump(t, args[arg].Pointer(), uint(rval), maximumBlobSize, LogAppDataAllowed /* content */)
		case ReadIOVec:
			printLength := uint64(rval)
			if printLength > uint64(maximumBlobSize) {
				printLength = uint64(maximumBlobSize)
			}
			output[arg] = iovecs(t, args[arg].Pointer(), int(args[arg+1].Int()), LogAppDataAllowed /* content */, printLength)
		case WriteIOVec, IOVec, WriteBuffer:
			// We already have a big blast from write.
			output[arg] = "..."
		case SendMsgHdr:
			output[arg] = msghdr(t, args[arg].Pointer(), false /* content */, uint64(maximumBlobSize))
		case RecvMsgHdr:
			output[arg] = msghdr(t, args[arg].Pointer(), LogAppDataAllowed /* content */, uint64(maximumBlobSize))
		case PostPath:
			output[arg] = path(t, args[arg].Pointer())
		case PipeFDs:
			output[arg] = fdpair(t, args[arg].Pointer())
		case Uname:
			output[arg] = uname(t, args[arg].Pointer())
		case Stat:
			output[arg] = stat(t, args[arg].Pointer())
		case PostSockAddr:
			output[arg] = postSockAddr(t, args[arg].Pointer(), args[arg+1].Pointer())
		case SockLen:
			output[arg] = sockLenPointer(t, args[arg].Pointer())
		case PostTimespec:
			output[arg] = timespec(t, args[arg].Pointer())
		case PostItimerVal:
			output[arg] = itimerval(t, args[arg].Pointer())
		case PostItimerSpec:
			output[arg] = itimerspec(t, args[arg].Pointer())
		case Timeval:
			output[arg] = timeval(t, args[arg].Pointer())
		case Rusage:
			output[arg] = rusage(t, args[arg].Pointer())
		case PostSigSet:
			output[arg] = sigSet(t, args[arg].Pointer())
		case PostSigAction:
			output[arg] = sigAction(t, args[arg].Pointer())
		case PostCapData:
			output[arg] = capData(t, args[arg-1].Pointer(), args[arg].Pointer())
		case PollFDs:
			output[arg] = pollFDs(t, args[arg].Pointer(), uint(args[arg+1].Uint()), true)
		case EpollEvents:
			output[arg] = epollEvents(t, args[arg].Pointer(), uint64(rval), uint64(maximumBlobSize))
		case GetSockOptVal:
			output[arg] = getSockOptVal(t, args[arg-2].Uint64() /* level */, args[arg-1].Uint64() /* optName */, args[arg].Pointer() /* optVal */, args[arg+1].Pointer() /* optLen */, maximumBlobSize, rval)
		case SetSockOptVal:
			// No need to print the value again. While it usually
			// isn't, the string version of this arg can be long.
			output[arg] = hexArg(args[arg])
		}
	}
}

// printEntry prints the given system call entry.
func (i *SyscallInfo) printEnter(t *kernel.Task, args arch.SyscallArguments) []string {
	output := i.pre(t, args, LogMaximumSize)

	switch len(output) {
	case 0:
		t.Infof("%s E %s()", t.Name(), i.name)
	case 1:
		t.Infof("%s E %s(%s)", t.Name(), i.name,
			output[0])
	case 2:
		t.Infof("%s E %s(%s, %s)", t.Name(), i.name,
			output[0], output[1])
	case 3:
		t.Infof("%s E %s(%s, %s, %s)", t.Name(), i.name,
			output[0], output[1], output[2])
	case 4:
		t.Infof("%s E %s(%s, %s, %s, %s)", t.Name(), i.name,
			output[0], output[1], output[2], output[3])
	case 5:
		t.Infof("%s E %s(%s, %s, %s, %s, %s)", t.Name(), i.name,
			output[0], output[1], output[2], output[3], output[4])
	case 6:
		t.Infof("%s E %s(%s, %s, %s, %s, %s, %s)", t.Name(), i.name,
			output[0], output[1], output[2], output[3], output[4], output[5])
	}

	return output
}

// printExit prints the given system call exit.
func (i *SyscallInfo) printExit(t *kernel.Task, elapsed time.Duration, output []string, args arch.SyscallArguments, retval uintptr, err error, errno int) {
	var rval string
	if err == nil {
		// Fill in the output after successful execution.
		i.post(t, args, retval, output, LogMaximumSize)
		rval = fmt.Sprintf("%d (%#x) (%v)", retval, retval, elapsed)
	} else {
		rval = fmt.Sprintf("%d errno=%d (%s) (%v)", -1, errno, err, elapsed)
	}

	switch len(output) {
	case 0:
		t.Infof("%s X %s() = %s", t.Name(), i.name,
			rval)
	case 1:
		t.Infof("%s X %s(%s) = %s", t.Name(), i.name,
			output[0], rval)
	case 2:
		t.Infof("%s X %s(%s, %s) = %s", t.Name(), i.name,
			output[0], output[1], rval)
	case 3:
		t.Infof("%s X %s(%s, %s, %s) = %s", t.Name(), i.name,
			output[0], output[1], output[2], rval)
	case 4:
		t.Infof("%s X %s(%s, %s, %s, %s) = %s", t.Name(), i.name,
			output[0], output[1], output[2], output[3], rval)
	case 5:
		t.Infof("%s X %s(%s, %s, %s, %s, %s) = %s", t.Name(), i.name,
			output[0], output[1], output[2], output[3], output[4], rval)
	case 6:
		t.Infof("%s X %s(%s, %s, %s, %s, %s, %s) = %s", t.Name(), i.name,
			output[0], output[1], output[2], output[3], output[4], output[5], rval)
	}
}

// sendEnter sends the syscall enter to event log.
func (i *SyscallInfo) sendEnter(t *kernel.Task, args arch.SyscallArguments) []string {
	output := i.pre(t, args, EventMaximumSize)

	event := pb.Strace{
		Process:  t.Name(),
		Function: i.name,
		Info: &pb.Strace_Enter{
			Enter: &pb.StraceEnter{},
		},
	}
	event.Args = append(event.Args, output...)
	eventchannel.Emit(&event)

	return output
}

// sendExit sends the syscall exit to event log.
func (i *SyscallInfo) sendExit(t *kernel.Task, elapsed time.Duration, output []string, args arch.SyscallArguments, rval uintptr, err error, errno int) {
	if err == nil {
		// Fill in the output after successful execution.
		i.post(t, args, rval, output, EventMaximumSize)
	}

	exit := &pb.StraceExit{
		Return:    fmt.Sprintf("%#x", rval),
		ElapsedNs: elapsed.Nanoseconds(),
	}
	if err != nil {
		exit.Error = err.Error()
		exit.ErrNo = int64(errno)
	}
	event := pb.Strace{
		Process:  t.Name(),
		Function: i.name,
		Info:     &pb.Strace_Exit{Exit: exit},
	}
	event.Args = append(event.Args, output...)
	eventchannel.Emit(&event)
}

type syscallContext struct {
	info        SyscallInfo
	args        arch.SyscallArguments
	start       time.Time
	logOutput   []string
	eventOutput []string
	flags       uint32
}

// SyscallEnter implements kernel.Stracer.SyscallEnter. It logs the syscall
// entry trace.
func (s SyscallMap) SyscallEnter(t *kernel.Task, sysno uintptr, args arch.SyscallArguments, flags uint32) any {
	info, ok := s[sysno]
	if !ok {
		info = SyscallInfo{
			name:   fmt.Sprintf("sys_%d", sysno),
			format: defaultFormat,
		}
	}

	var output, eventOutput []string
	if bits.IsOn(flags, kernel.StraceEnableLog) {
		output = info.printEnter(t, args)
	}
	if bits.IsOn(flags, kernel.StraceEnableEvent) {
		eventOutput = info.sendEnter(t, args)
	}

	return &syscallContext{
		info:        info,
		args:        args,
		start:       time.Now(),
		logOutput:   output,
		eventOutput: eventOutput,
		flags:       flags,
	}
}

// SyscallExit implements kernel.Stracer.SyscallExit. It logs the syscall
// exit trace.
func (s SyscallMap) SyscallExit(context any, t *kernel.Task, sysno, rval uintptr, err error) {
	errno := kernel.ExtractErrno(err, int(sysno))
	c := context.(*syscallContext)

	elapsed := time.Since(c.start)
	if bits.IsOn(c.flags, kernel.StraceEnableLog) {
		c.info.printExit(t, elapsed, c.logOutput, c.args, rval, err, errno)
	}
	if bits.IsOn(c.flags, kernel.StraceEnableEvent) {
		c.info.sendExit(t, elapsed, c.eventOutput, c.args, rval, err, errno)
	}
}

// ConvertToSysnoMap converts the names to a map keyed on the syscall number
// and value set to true.
//
// The map is in a convenient format to pass to SyscallFlagsTable.Enable().
func (s SyscallMap) ConvertToSysnoMap(syscalls []string) (map[uintptr]bool, error) {
	if syscalls == nil {
		// Sentinel: no list.
		return nil, nil
	}

	l := make(map[uintptr]bool)
	for _, sc := range syscalls {
		// Try to match this system call.
		sysno, ok := s.ConvertToSysno(sc)
		if !ok {
			return nil, fmt.Errorf("syscall %q not found", sc)
		}
		l[sysno] = true
	}

	// Success.
	return l, nil
}

// ConvertToSysno converts the name to system call number. Returns false
// if syscall with same name is not found.
func (s SyscallMap) ConvertToSysno(syscall string) (uintptr, bool) {
	for sysno, info := range s {
		if info.name != "" && info.name == syscall {
			return sysno, true
		}
	}
	return 0, false
}

// Name returns the syscall name.
func (s SyscallMap) Name(sysno uintptr) string {
	if info, ok := s[sysno]; ok {
		return info.name
	}
	return fmt.Sprintf("sys_%d", sysno)
}

// Initialize prepares all syscall tables for use by this package.
//
// N.B. This is not in an init function because we can't be sure all syscall
// tables are registered with the kernel when init runs.
func Initialize() {
	for _, table := range kernel.SyscallTables() {
		// Is this known?
		sys, ok := Lookup(table.OS, table.Arch)
		if !ok {
			continue
		}

		table.Stracer = sys
	}
}

// SinkType defines where to send straces to.
type SinkType uint32

const (
	// SinkTypeLog sends straces to text log
	SinkTypeLog SinkType = 1 << iota

	// SinkTypeEvent sends strace to event log
	SinkTypeEvent
)

func convertToSyscallFlag(sinks SinkType) uint32 {
	ret := uint32(0)
	if bits.IsOn(uint32(sinks), uint32(SinkTypeLog)) {
		ret |= kernel.StraceEnableLog
	}
	if bits.IsOn(uint32(sinks), uint32(SinkTypeEvent)) {
		ret |= kernel.StraceEnableEvent
	}
	return ret
}

// Enable enables the syscalls in allowlist in all syscall tables.
//
// Preconditions: Initialize has been called.
func Enable(allowlist []string, sinks SinkType) error {
	flags := convertToSyscallFlag(sinks)
	for _, table := range kernel.SyscallTables() {
		// Is this known?
		sys, ok := Lookup(table.OS, table.Arch)
		if !ok {
			continue
		}

		// Convert to a set of system calls numbers.
		wl, err := sys.ConvertToSysnoMap(allowlist)
		if err != nil {
			return err
		}

		table.FeatureEnable.Enable(flags, wl, true)
	}

	// Done.
	return nil
}

// Disable will disable Strace for all system calls and missing syscalls.
//
// Preconditions: Initialize has been called.
func Disable(sinks SinkType) {
	flags := convertToSyscallFlag(sinks)
	for _, table := range kernel.SyscallTables() {
		// Strace will be disabled for all syscalls including missing.
		table.FeatureEnable.Enable(flags, nil, false)
	}
}

// EnableAll enables all syscalls in all syscall tables.
//
// Preconditions: Initialize has been called.
func EnableAll(sinks SinkType) {
	flags := convertToSyscallFlag(sinks)
	for _, table := range kernel.SyscallTables() {
		// Is this known?
		if _, ok := Lookup(table.OS, table.Arch); !ok {
			continue
		}

		table.FeatureEnable.EnableAll(flags)
	}
}

func init() {
	t, ok := Lookup(abi.Host, arch.Host)
	if ok {
		// Provide the native table as the lookup for seccomp
		// debugging. This is best-effort. This is provided this way to
		// avoid dependencies from seccomp to this package.
		seccomp.SyscallName = t.Name
	}
}
