// Copyright 2019 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 proc

import (
	"bytes"
	"fmt"
	"math"

	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/context"
	"gvisor.dev/gvisor/pkg/errors/linuxerr"
	"gvisor.dev/gvisor/pkg/hostarch"
	"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
	"gvisor.dev/gvisor/pkg/sentry/inet"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
	"gvisor.dev/gvisor/pkg/sentry/vfs"
	"gvisor.dev/gvisor/pkg/sync"
	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
	"gvisor.dev/gvisor/pkg/usermem"
)

// +stateify savable
type tcpMemDir int

const (
	tcpRMem tcpMemDir = iota
	tcpWMem
)

// newSysDir returns the dentry corresponding to /proc/sys directory.
func (fs *filesystem) newSysDir(ctx context.Context, root *auth.Credentials, k *kernel.Kernel) kernfs.Inode {
	return fs.newStaticDir(ctx, root, map[string]kernfs.Inode{
		"kernel": fs.newStaticDir(ctx, root, map[string]kernfs.Inode{
			"cap_last_cap": fs.newInode(ctx, root, 0444, newStaticFile(fmt.Sprintf("%d\n", linux.CAP_LAST_CAP))),
			"hostname":     fs.newInode(ctx, root, 0444, &hostnameData{}),
			"sem":          fs.newInode(ctx, root, 0444, newStaticFile(fmt.Sprintf("%d\t%d\t%d\t%d\n", linux.SEMMSL, linux.SEMMNS, linux.SEMOPM, linux.SEMMNI))),
			"shmall":       fs.newInode(ctx, root, 0444, ipcData(linux.SHMALL)),
			"shmmax":       fs.newInode(ctx, root, 0444, ipcData(linux.SHMMAX)),
			"shmmni":       fs.newInode(ctx, root, 0444, ipcData(linux.SHMMNI)),
			"msgmni":       fs.newInode(ctx, root, 0444, ipcData(linux.MSGMNI)),
			"msgmax":       fs.newInode(ctx, root, 0444, ipcData(linux.MSGMAX)),
			"msgmnb":       fs.newInode(ctx, root, 0444, ipcData(linux.MSGMNB)),
			"yama": fs.newStaticDir(ctx, root, map[string]kernfs.Inode{
				"ptrace_scope": fs.newYAMAPtraceScopeFile(ctx, k, root),
			}),
		}),
		"vm": fs.newStaticDir(ctx, root, map[string]kernfs.Inode{
			"max_map_count":     fs.newInode(ctx, root, 0444, newStaticFile("2147483647\n")),
			"mmap_min_addr":     fs.newInode(ctx, root, 0444, &mmapMinAddrData{k: k}),
			"overcommit_memory": fs.newInode(ctx, root, 0444, newStaticFile("0\n")),
		}),
		"net": fs.newSysNetDir(ctx, root, k),
	})
}

// newSysNetDir returns the dentry corresponding to /proc/sys/net directory.
func (fs *filesystem) newSysNetDir(ctx context.Context, root *auth.Credentials, k *kernel.Kernel) kernfs.Inode {
	var contents map[string]kernfs.Inode

	// TODO(gvisor.dev/issue/1833): Support for using the network stack in the
	// network namespace of the calling process.
	if stack := k.RootNetworkNamespace().Stack(); stack != nil {
		contents = map[string]kernfs.Inode{
			"ipv4": fs.newStaticDir(ctx, root, map[string]kernfs.Inode{
				"ip_forward":          fs.newInode(ctx, root, 0444, &ipForwarding{stack: stack}),
				"ip_local_port_range": fs.newInode(ctx, root, 0644, &portRange{stack: stack}),
				"tcp_recovery":        fs.newInode(ctx, root, 0644, &tcpRecoveryData{stack: stack}),
				"tcp_rmem":            fs.newInode(ctx, root, 0644, &tcpMemData{stack: stack, dir: tcpRMem}),
				"tcp_sack":            fs.newInode(ctx, root, 0644, &tcpSackData{stack: stack}),
				"tcp_wmem":            fs.newInode(ctx, root, 0644, &tcpMemData{stack: stack, dir: tcpWMem}),

				// The following files are simple stubs until they are implemented in
				// netstack, most of these files are configuration related. We use the
				// value closest to the actual netstack behavior or any empty file, all
				// of these files will have mode 0444 (read-only for all users).
				"ip_local_reserved_ports": fs.newInode(ctx, root, 0444, newStaticFile("")),
				"ipfrag_time":             fs.newInode(ctx, root, 0444, newStaticFile("30")),
				"ip_nonlocal_bind":        fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"ip_no_pmtu_disc":         fs.newInode(ctx, root, 0444, newStaticFile("1")),

				// tcp_allowed_congestion_control tell the user what they are able to
				// do as an unprivledged process so we leave it empty.
				"tcp_allowed_congestion_control":   fs.newInode(ctx, root, 0444, newStaticFile("")),
				"tcp_available_congestion_control": fs.newInode(ctx, root, 0444, newStaticFile("reno")),
				"tcp_congestion_control":           fs.newInode(ctx, root, 0444, newStaticFile("reno")),

				// Many of the following stub files are features netstack doesn't
				// support. The unsupported features return "0" to indicate they are
				// disabled.
				"tcp_base_mss":              fs.newInode(ctx, root, 0444, newStaticFile("1280")),
				"tcp_dsack":                 fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"tcp_early_retrans":         fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"tcp_fack":                  fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"tcp_fastopen":              fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"tcp_fastopen_key":          fs.newInode(ctx, root, 0444, newStaticFile("")),
				"tcp_invalid_ratelimit":     fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"tcp_keepalive_intvl":       fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"tcp_keepalive_probes":      fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"tcp_keepalive_time":        fs.newInode(ctx, root, 0444, newStaticFile("7200")),
				"tcp_mtu_probing":           fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"tcp_no_metrics_save":       fs.newInode(ctx, root, 0444, newStaticFile("1")),
				"tcp_probe_interval":        fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"tcp_probe_threshold":       fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"tcp_retries1":              fs.newInode(ctx, root, 0444, newStaticFile("3")),
				"tcp_retries2":              fs.newInode(ctx, root, 0444, newStaticFile("15")),
				"tcp_rfc1337":               fs.newInode(ctx, root, 0444, newStaticFile("1")),
				"tcp_slow_start_after_idle": fs.newInode(ctx, root, 0444, newStaticFile("1")),
				"tcp_synack_retries":        fs.newInode(ctx, root, 0444, newStaticFile("5")),
				"tcp_syn_retries":           fs.newInode(ctx, root, 0444, newStaticFile("3")),
				"tcp_timestamps":            fs.newInode(ctx, root, 0444, newStaticFile("1")),
			}),
			"core": fs.newStaticDir(ctx, root, map[string]kernfs.Inode{
				"default_qdisc": fs.newInode(ctx, root, 0444, newStaticFile("pfifo_fast")),
				"message_burst": fs.newInode(ctx, root, 0444, newStaticFile("10")),
				"message_cost":  fs.newInode(ctx, root, 0444, newStaticFile("5")),
				"optmem_max":    fs.newInode(ctx, root, 0444, newStaticFile("0")),
				"rmem_default":  fs.newInode(ctx, root, 0444, newStaticFile("212992")),
				"rmem_max":      fs.newInode(ctx, root, 0444, newStaticFile("212992")),
				"somaxconn":     fs.newInode(ctx, root, 0444, newStaticFile("128")),
				"wmem_default":  fs.newInode(ctx, root, 0444, newStaticFile("212992")),
				"wmem_max":      fs.newInode(ctx, root, 0444, newStaticFile("212992")),
			}),
		}
	}

	return fs.newStaticDir(ctx, root, contents)
}

// mmapMinAddrData implements vfs.DynamicBytesSource for
// /proc/sys/vm/mmap_min_addr.
//
// +stateify savable
type mmapMinAddrData struct {
	kernfs.DynamicBytesFile

	k *kernel.Kernel
}

var _ dynamicInode = (*mmapMinAddrData)(nil)

// Generate implements vfs.DynamicBytesSource.Generate.
func (d *mmapMinAddrData) Generate(ctx context.Context, buf *bytes.Buffer) error {
	fmt.Fprintf(buf, "%d\n", d.k.Platform.MinUserAddress())
	return nil
}

// hostnameData implements vfs.DynamicBytesSource for /proc/sys/kernel/hostname.
//
// +stateify savable
type hostnameData struct {
	kernfs.DynamicBytesFile
}

var _ dynamicInode = (*hostnameData)(nil)

// Generate implements vfs.DynamicBytesSource.Generate.
func (*hostnameData) Generate(ctx context.Context, buf *bytes.Buffer) error {
	utsns := kernel.UTSNamespaceFromContext(ctx)
	defer utsns.DecRef(ctx)
	buf.WriteString(utsns.HostName())
	buf.WriteString("\n")
	return nil
}

// tcpSackData implements vfs.WritableDynamicBytesSource for
// /proc/sys/net/tcp_sack.
//
// +stateify savable
type tcpSackData struct {
	kernfs.DynamicBytesFile

	stack   inet.Stack `state:"wait"`
	enabled *bool
}

var _ vfs.WritableDynamicBytesSource = (*tcpSackData)(nil)

// Generate implements vfs.DynamicBytesSource.Generate.
func (d *tcpSackData) Generate(ctx context.Context, buf *bytes.Buffer) error {
	if d.enabled == nil {
		sack, err := d.stack.TCPSACKEnabled()
		if err != nil {
			return err
		}
		d.enabled = &sack
	}

	val := "0\n"
	if *d.enabled {
		// Technically, this is not quite compatible with Linux. Linux stores these
		// as an integer, so if you write "2" into tcp_sack, you should get 2 back.
		// Tough luck.
		val = "1\n"
	}
	_, err := buf.WriteString(val)
	return err
}

// Write implements vfs.WritableDynamicBytesSource.Write.
func (d *tcpSackData) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
	if offset != 0 {
		// No need to handle partial writes thus far.
		return 0, linuxerr.EINVAL
	}
	if src.NumBytes() == 0 {
		return 0, nil
	}

	// Limit the amount of memory allocated.
	src = src.TakeFirst(hostarch.PageSize - 1)

	var v int32
	n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts)
	if err != nil {
		return 0, err
	}
	if d.enabled == nil {
		d.enabled = new(bool)
	}
	*d.enabled = v != 0
	return n, d.stack.SetTCPSACKEnabled(*d.enabled)
}

// tcpRecoveryData implements vfs.WritableDynamicBytesSource for
// /proc/sys/net/ipv4/tcp_recovery.
//
// +stateify savable
type tcpRecoveryData struct {
	kernfs.DynamicBytesFile

	stack inet.Stack `state:"wait"`
}

var _ vfs.WritableDynamicBytesSource = (*tcpRecoveryData)(nil)

// Generate implements vfs.DynamicBytesSource.Generate.
func (d *tcpRecoveryData) Generate(ctx context.Context, buf *bytes.Buffer) error {
	recovery, err := d.stack.TCPRecovery()
	if err != nil {
		return err
	}

	_, err = buf.WriteString(fmt.Sprintf("%d\n", recovery))
	return err
}

// Write implements vfs.WritableDynamicBytesSource.Write.
func (d *tcpRecoveryData) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
	if offset != 0 {
		// No need to handle partial writes thus far.
		return 0, linuxerr.EINVAL
	}
	if src.NumBytes() == 0 {
		return 0, nil
	}

	// Limit the amount of memory allocated.
	src = src.TakeFirst(hostarch.PageSize - 1)

	var v int32
	n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts)
	if err != nil {
		return 0, err
	}
	if err := d.stack.SetTCPRecovery(inet.TCPLossRecovery(v)); err != nil {
		return 0, err
	}
	return n, nil
}

// tcpMemData implements vfs.WritableDynamicBytesSource for
// /proc/sys/net/ipv4/tcp_rmem and /proc/sys/net/ipv4/tcp_wmem.
//
// +stateify savable
type tcpMemData struct {
	kernfs.DynamicBytesFile

	dir   tcpMemDir
	stack inet.Stack `state:"wait"`

	// mu protects against concurrent reads/writes to FDs based on the dentry
	// backing this byte source.
	mu sync.Mutex `state:"nosave"`
}

var _ vfs.WritableDynamicBytesSource = (*tcpMemData)(nil)

// Generate implements vfs.DynamicBytesSource.Generate.
func (d *tcpMemData) Generate(ctx context.Context, buf *bytes.Buffer) error {
	d.mu.Lock()
	defer d.mu.Unlock()

	size, err := d.readSizeLocked()
	if err != nil {
		return err
	}
	_, err = buf.WriteString(fmt.Sprintf("%d\t%d\t%d\n", size.Min, size.Default, size.Max))
	return err
}

// Write implements vfs.WritableDynamicBytesSource.Write.
func (d *tcpMemData) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
	if offset != 0 {
		// No need to handle partial writes thus far.
		return 0, linuxerr.EINVAL
	}
	if src.NumBytes() == 0 {
		return 0, nil
	}
	d.mu.Lock()
	defer d.mu.Unlock()

	// Limit the amount of memory allocated.
	src = src.TakeFirst(hostarch.PageSize - 1)
	size, err := d.readSizeLocked()
	if err != nil {
		return 0, err
	}
	buf := []int32{int32(size.Min), int32(size.Default), int32(size.Max)}
	n, err := usermem.CopyInt32StringsInVec(ctx, src.IO, src.Addrs, buf, src.Opts)
	if err != nil {
		return 0, err
	}
	newSize := inet.TCPBufferSize{
		Min:     int(buf[0]),
		Default: int(buf[1]),
		Max:     int(buf[2]),
	}
	if err := d.writeSizeLocked(newSize); err != nil {
		return 0, err
	}
	return n, nil
}

// Precondition: d.mu must be locked.
func (d *tcpMemData) readSizeLocked() (inet.TCPBufferSize, error) {
	switch d.dir {
	case tcpRMem:
		return d.stack.TCPReceiveBufferSize()
	case tcpWMem:
		return d.stack.TCPSendBufferSize()
	default:
		panic(fmt.Sprintf("unknown tcpMemFile type: %v", d.dir))
	}
}

// Precondition: d.mu must be locked.
func (d *tcpMemData) writeSizeLocked(size inet.TCPBufferSize) error {
	switch d.dir {
	case tcpRMem:
		return d.stack.SetTCPReceiveBufferSize(size)
	case tcpWMem:
		return d.stack.SetTCPSendBufferSize(size)
	default:
		panic(fmt.Sprintf("unknown tcpMemFile type: %v", d.dir))
	}
}

// ipForwarding implements vfs.WritableDynamicBytesSource for
// /proc/sys/net/ipv4/ip_forward.
//
// +stateify savable
type ipForwarding struct {
	kernfs.DynamicBytesFile

	stack   inet.Stack `state:"wait"`
	enabled bool
}

var _ vfs.WritableDynamicBytesSource = (*ipForwarding)(nil)

// Generate implements vfs.DynamicBytesSource.Generate.
func (ipf *ipForwarding) Generate(ctx context.Context, buf *bytes.Buffer) error {
	val := "0\n"
	if ipf.enabled {
		// Technically, this is not quite compatible with Linux. Linux stores these
		// as an integer, so if you write "2" into tcp_sack, you should get 2 back.
		// Tough luck.
		val = "1\n"
	}
	buf.WriteString(val)

	return nil
}

// Write implements vfs.WritableDynamicBytesSource.Write.
func (ipf *ipForwarding) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
	if offset != 0 {
		// No need to handle partial writes thus far.
		return 0, linuxerr.EINVAL
	}
	if src.NumBytes() == 0 {
		return 0, nil
	}

	// Limit input size so as not to impact performance if input size is large.
	src = src.TakeFirst(hostarch.PageSize - 1)

	var v int32
	n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts)
	if err != nil {
		return 0, err
	}
	ipf.enabled = v != 0
	if err := ipf.stack.SetForwarding(ipv4.ProtocolNumber, ipf.enabled); err != nil {
		return 0, err
	}
	return n, nil
}

// portRange implements vfs.WritableDynamicBytesSource for
// /proc/sys/net/ipv4/ip_local_port_range.
//
// +stateify savable
type portRange struct {
	kernfs.DynamicBytesFile

	stack inet.Stack `state:"wait"`

	// start and end store the port range. We must save/restore this here,
	// since a netstack instance is created on restore.
	start *uint16
	end   *uint16
}

var _ vfs.WritableDynamicBytesSource = (*portRange)(nil)

// Generate implements vfs.DynamicBytesSource.Generate.
func (pr *portRange) Generate(ctx context.Context, buf *bytes.Buffer) error {
	if pr.start == nil {
		start, end := pr.stack.PortRange()
		pr.start = &start
		pr.end = &end
	}
	_, err := fmt.Fprintf(buf, "%d %d\n", *pr.start, *pr.end)
	return err
}

// Write implements vfs.WritableDynamicBytesSource.Write.
func (pr *portRange) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
	if offset != 0 {
		// No need to handle partial writes thus far.
		return 0, linuxerr.EINVAL
	}
	if src.NumBytes() == 0 {
		return 0, nil
	}

	// Limit input size so as not to impact performance if input size is
	// large.
	src = src.TakeFirst(hostarch.PageSize - 1)

	ports := make([]int32, 2)
	n, err := usermem.CopyInt32StringsInVec(ctx, src.IO, src.Addrs, ports, src.Opts)
	if err != nil {
		return 0, err
	}

	// Port numbers must be uint16s.
	if ports[0] < 0 || ports[1] < 0 || ports[0] > math.MaxUint16 || ports[1] > math.MaxUint16 {
		return 0, linuxerr.EINVAL
	}

	if err := pr.stack.SetPortRange(uint16(ports[0]), uint16(ports[1])); err != nil {
		return 0, err
	}
	if pr.start == nil {
		pr.start = new(uint16)
		pr.end = new(uint16)
	}
	*pr.start = uint16(ports[0])
	*pr.end = uint16(ports[1])
	return n, nil
}
