// Copyright 2020 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 vfs2

import (
	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/sentry/arch"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	"gvisor.dev/gvisor/pkg/sentry/limits"
	"gvisor.dev/gvisor/pkg/sentry/vfs"
	"gvisor.dev/gvisor/pkg/syserror"
	"gvisor.dev/gvisor/pkg/usermem"
)

// Link implements Linux syscall link(2).
func Link(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	oldpathAddr := args[0].Pointer()
	newpathAddr := args[1].Pointer()
	return 0, nil, linkat(t, linux.AT_FDCWD, oldpathAddr, linux.AT_FDCWD, newpathAddr, 0 /* flags */)
}

// Linkat implements Linux syscall linkat(2).
func Linkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	olddirfd := args[0].Int()
	oldpathAddr := args[1].Pointer()
	newdirfd := args[2].Int()
	newpathAddr := args[3].Pointer()
	flags := args[4].Int()
	return 0, nil, linkat(t, olddirfd, oldpathAddr, newdirfd, newpathAddr, flags)
}

func linkat(t *kernel.Task, olddirfd int32, oldpathAddr usermem.Addr, newdirfd int32, newpathAddr usermem.Addr, flags int32) error {
	if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_FOLLOW) != 0 {
		return syserror.EINVAL
	}
	if flags&linux.AT_EMPTY_PATH != 0 && !t.HasCapability(linux.CAP_DAC_READ_SEARCH) {
		return syserror.ENOENT
	}

	oldpath, err := copyInPath(t, oldpathAddr)
	if err != nil {
		return err
	}
	oldtpop, err := getTaskPathOperation(t, olddirfd, oldpath, shouldAllowEmptyPath(flags&linux.AT_EMPTY_PATH != 0), shouldFollowFinalSymlink(flags&linux.AT_SYMLINK_FOLLOW != 0))
	if err != nil {
		return err
	}
	defer oldtpop.Release()

	newpath, err := copyInPath(t, newpathAddr)
	if err != nil {
		return err
	}
	newtpop, err := getTaskPathOperation(t, newdirfd, newpath, disallowEmptyPath, nofollowFinalSymlink)
	if err != nil {
		return err
	}
	defer newtpop.Release()

	return t.Kernel().VFS().LinkAt(t, t.Credentials(), &oldtpop.pop, &newtpop.pop)
}

// Mkdir implements Linux syscall mkdir(2).
func Mkdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	addr := args[0].Pointer()
	mode := args[1].ModeT()
	return 0, nil, mkdirat(t, linux.AT_FDCWD, addr, mode)
}

// Mkdirat implements Linux syscall mkdirat(2).
func Mkdirat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	dirfd := args[0].Int()
	addr := args[1].Pointer()
	mode := args[2].ModeT()
	return 0, nil, mkdirat(t, dirfd, addr, mode)
}

func mkdirat(t *kernel.Task, dirfd int32, addr usermem.Addr, mode uint) error {
	path, err := copyInPath(t, addr)
	if err != nil {
		return err
	}
	tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink)
	if err != nil {
		return err
	}
	defer tpop.Release()
	return t.Kernel().VFS().MkdirAt(t, t.Credentials(), &tpop.pop, &vfs.MkdirOptions{
		Mode: linux.FileMode(mode & (0777 | linux.S_ISVTX) &^ t.FSContext().Umask()),
	})
}

// Mknod implements Linux syscall mknod(2).
func Mknod(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	addr := args[0].Pointer()
	mode := args[1].ModeT()
	dev := args[2].Uint()
	return 0, nil, mknodat(t, linux.AT_FDCWD, addr, linux.FileMode(mode), dev)
}

// Mknodat implements Linux syscall mknodat(2).
func Mknodat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	dirfd := args[0].Int()
	addr := args[1].Pointer()
	mode := args[2].ModeT()
	dev := args[3].Uint()
	return 0, nil, mknodat(t, dirfd, addr, linux.FileMode(mode), dev)
}

func mknodat(t *kernel.Task, dirfd int32, addr usermem.Addr, mode linux.FileMode, dev uint32) error {
	path, err := copyInPath(t, addr)
	if err != nil {
		return err
	}
	tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink)
	if err != nil {
		return err
	}
	defer tpop.Release()

	// "Zero file type is equivalent to type S_IFREG." - mknod(2)
	if mode.FileType() == 0 {
		mode |= linux.ModeRegular
	}
	major, minor := linux.DecodeDeviceID(dev)
	return t.Kernel().VFS().MknodAt(t, t.Credentials(), &tpop.pop, &vfs.MknodOptions{
		Mode:     mode &^ linux.FileMode(t.FSContext().Umask()),
		DevMajor: uint32(major),
		DevMinor: minor,
	})
}

// Open implements Linux syscall open(2).
func Open(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	addr := args[0].Pointer()
	flags := args[1].Uint()
	mode := args[2].ModeT()
	return openat(t, linux.AT_FDCWD, addr, flags, mode)
}

// Openat implements Linux syscall openat(2).
func Openat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	dirfd := args[0].Int()
	addr := args[1].Pointer()
	flags := args[2].Uint()
	mode := args[3].ModeT()
	return openat(t, dirfd, addr, flags, mode)
}

// Creat implements Linux syscall creat(2).
func Creat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	addr := args[0].Pointer()
	mode := args[1].ModeT()
	return openat(t, linux.AT_FDCWD, addr, linux.O_WRONLY|linux.O_CREAT|linux.O_TRUNC, mode)
}

func openat(t *kernel.Task, dirfd int32, pathAddr usermem.Addr, flags uint32, mode uint) (uintptr, *kernel.SyscallControl, error) {
	path, err := copyInPath(t, pathAddr)
	if err != nil {
		return 0, nil, err
	}
	tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, shouldFollowFinalSymlink(flags&linux.O_NOFOLLOW == 0))
	if err != nil {
		return 0, nil, err
	}
	defer tpop.Release()

	file, err := t.Kernel().VFS().OpenAt(t, t.Credentials(), &tpop.pop, &vfs.OpenOptions{
		Flags: flags | linux.O_LARGEFILE,
		Mode:  linux.FileMode(mode & (0777 | linux.S_ISUID | linux.S_ISGID | linux.S_ISVTX) &^ t.FSContext().Umask()),
	})
	if err != nil {
		return 0, nil, err
	}
	defer file.DecRef()

	fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{
		CloseOnExec: flags&linux.O_CLOEXEC != 0,
	})
	return uintptr(fd), nil, err
}

// Rename implements Linux syscall rename(2).
func Rename(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	oldpathAddr := args[0].Pointer()
	newpathAddr := args[1].Pointer()
	return 0, nil, renameat(t, linux.AT_FDCWD, oldpathAddr, linux.AT_FDCWD, newpathAddr, 0 /* flags */)
}

// Renameat implements Linux syscall renameat(2).
func Renameat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	olddirfd := args[0].Int()
	oldpathAddr := args[1].Pointer()
	newdirfd := args[2].Int()
	newpathAddr := args[3].Pointer()
	return 0, nil, renameat(t, olddirfd, oldpathAddr, newdirfd, newpathAddr, 0 /* flags */)
}

// Renameat2 implements Linux syscall renameat2(2).
func Renameat2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	olddirfd := args[0].Int()
	oldpathAddr := args[1].Pointer()
	newdirfd := args[2].Int()
	newpathAddr := args[3].Pointer()
	flags := args[4].Uint()
	return 0, nil, renameat(t, olddirfd, oldpathAddr, newdirfd, newpathAddr, flags)
}

func renameat(t *kernel.Task, olddirfd int32, oldpathAddr usermem.Addr, newdirfd int32, newpathAddr usermem.Addr, flags uint32) error {
	oldpath, err := copyInPath(t, oldpathAddr)
	if err != nil {
		return err
	}
	// "If oldpath refers to a symbolic link, the link is renamed" - rename(2)
	oldtpop, err := getTaskPathOperation(t, olddirfd, oldpath, disallowEmptyPath, nofollowFinalSymlink)
	if err != nil {
		return err
	}
	defer oldtpop.Release()

	newpath, err := copyInPath(t, newpathAddr)
	if err != nil {
		return err
	}
	newtpop, err := getTaskPathOperation(t, newdirfd, newpath, disallowEmptyPath, nofollowFinalSymlink)
	if err != nil {
		return err
	}
	defer newtpop.Release()

	return t.Kernel().VFS().RenameAt(t, t.Credentials(), &oldtpop.pop, &newtpop.pop, &vfs.RenameOptions{
		Flags: flags,
	})
}

// Fallocate implements linux system call fallocate(2).
func Fallocate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	fd := args[0].Int()
	mode := args[1].Uint64()
	offset := args[2].Int64()
	length := args[3].Int64()

	file := t.GetFileVFS2(fd)

	if file == nil {
		return 0, nil, syserror.EBADF
	}
	defer file.DecRef()

	if !file.IsWritable() {
		return 0, nil, syserror.EBADF
	}

	if mode != 0 {
		return 0, nil, syserror.ENOTSUP
	}

	if offset < 0 || length <= 0 {
		return 0, nil, syserror.EINVAL
	}

	size := offset + length

	if size < 0 {
		return 0, nil, syserror.EFBIG
	}

	limit := limits.FromContext(t).Get(limits.FileSize).Cur

	if uint64(size) >= limit {
		t.SendSignal(&arch.SignalInfo{
			Signo: int32(linux.SIGXFSZ),
			Code:  arch.SignalInfoUser,
		})
		return 0, nil, syserror.EFBIG
	}

	return 0, nil, file.Impl().Allocate(t, mode, uint64(offset), uint64(length))

	// File length modified, generate notification.
	// TODO(gvisor.dev/issue/1479): Reenable when Inotify is ported.
	// file.Dirent.InotifyEvent(linux.IN_MODIFY, 0)
}

// Rmdir implements Linux syscall rmdir(2).
func Rmdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	pathAddr := args[0].Pointer()
	return 0, nil, rmdirat(t, linux.AT_FDCWD, pathAddr)
}

func rmdirat(t *kernel.Task, dirfd int32, pathAddr usermem.Addr) error {
	path, err := copyInPath(t, pathAddr)
	if err != nil {
		return err
	}
	tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink)
	if err != nil {
		return err
	}
	defer tpop.Release()
	return t.Kernel().VFS().RmdirAt(t, t.Credentials(), &tpop.pop)
}

// Unlink implements Linux syscall unlink(2).
func Unlink(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	pathAddr := args[0].Pointer()
	return 0, nil, unlinkat(t, linux.AT_FDCWD, pathAddr)
}

func unlinkat(t *kernel.Task, dirfd int32, pathAddr usermem.Addr) error {
	path, err := copyInPath(t, pathAddr)
	if err != nil {
		return err
	}
	tpop, err := getTaskPathOperation(t, dirfd, path, disallowEmptyPath, nofollowFinalSymlink)
	if err != nil {
		return err
	}
	defer tpop.Release()
	return t.Kernel().VFS().UnlinkAt(t, t.Credentials(), &tpop.pop)
}

// Unlinkat implements Linux syscall unlinkat(2).
func Unlinkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	dirfd := args[0].Int()
	pathAddr := args[1].Pointer()
	flags := args[2].Int()

	if flags&^linux.AT_REMOVEDIR != 0 {
		return 0, nil, syserror.EINVAL
	}

	if flags&linux.AT_REMOVEDIR != 0 {
		return 0, nil, rmdirat(t, dirfd, pathAddr)
	}
	return 0, nil, unlinkat(t, dirfd, pathAddr)
}

// Symlink implements Linux syscall symlink(2).
func Symlink(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	targetAddr := args[0].Pointer()
	linkpathAddr := args[1].Pointer()
	return 0, nil, symlinkat(t, targetAddr, linux.AT_FDCWD, linkpathAddr)
}

// Symlinkat implements Linux syscall symlinkat(2).
func Symlinkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	targetAddr := args[0].Pointer()
	newdirfd := args[1].Int()
	linkpathAddr := args[2].Pointer()
	return 0, nil, symlinkat(t, targetAddr, newdirfd, linkpathAddr)
}

func symlinkat(t *kernel.Task, targetAddr usermem.Addr, newdirfd int32, linkpathAddr usermem.Addr) error {
	target, err := t.CopyInString(targetAddr, linux.PATH_MAX)
	if err != nil {
		return err
	}
	if len(target) == 0 {
		return syserror.ENOENT
	}
	linkpath, err := copyInPath(t, linkpathAddr)
	if err != nil {
		return err
	}
	tpop, err := getTaskPathOperation(t, newdirfd, linkpath, disallowEmptyPath, nofollowFinalSymlink)
	if err != nil {
		return err
	}
	defer tpop.Release()
	return t.Kernel().VFS().SymlinkAt(t, t.Credentials(), &tpop.pop, target)
}