// Copyright 2018 Google Inc.
//
// 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 (
	"fmt"
	"sort"
	"strconv"

	"gvisor.googlesource.com/gvisor/pkg/sentry/context"
	"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
	"gvisor.googlesource.com/gvisor/pkg/sentry/fs/proc/device"
	"gvisor.googlesource.com/gvisor/pkg/sentry/fs/ramfs"
	"gvisor.googlesource.com/gvisor/pkg/sentry/kernel"
	"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/kdefs"
	"gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
	"gvisor.googlesource.com/gvisor/pkg/syserror"
)

// walkDescriptors finds the descriptor (file-flag pair) for the fd identified
// by p, and calls the toInodeOperations callback with that descriptor.  This is a helper
// method for implementing fs.InodeOperations.Lookup.
func walkDescriptors(t *kernel.Task, p string, toInode func(*fs.File, kernel.FDFlags) *fs.Inode) (*fs.Inode, error) {
	n, err := strconv.ParseUint(p, 10, 64)
	if err != nil {
		// Not found.
		return nil, syserror.ENOENT
	}

	var file *fs.File
	var fdFlags kernel.FDFlags
	t.WithMuLocked(func(t *kernel.Task) {
		if fdm := t.FDMap(); fdm != nil {
			file, fdFlags = fdm.GetDescriptor(kdefs.FD(n))
		}
	})
	if file == nil {
		return nil, syserror.ENOENT
	}
	return toInode(file, fdFlags), nil
}

// readDescriptors reads fds in the task starting at offset, and calls the
// toDentAttr callback for each to get a DentAttr, which it then emits. This is
// a helper for implementing fs.InodeOperations.Readdir.
func readDescriptors(t *kernel.Task, c *fs.DirCtx, offset int, toDentAttr func(int) fs.DentAttr) (int, error) {
	var fds kernel.FDs
	t.WithMuLocked(func(t *kernel.Task) {
		if fdm := t.FDMap(); fdm != nil {
			fds = fdm.GetFDs()
		}
	})

	fdInts := make([]int, 0, len(fds))
	for _, fd := range fds {
		fdInts = append(fdInts, int(fd))
	}

	// Find the fd to start at.
	idx := sort.SearchInts(fdInts, offset)
	if idx == len(fdInts) {
		return offset, nil
	}
	fdInts = fdInts[idx:]

	var fd int
	for _, fd = range fdInts {
		name := strconv.FormatUint(uint64(fd), 10)
		if err := c.DirEmit(name, toDentAttr(fd)); err != nil {
			// Returned offset is the next fd to serialize.
			return fd, err
		}
	}
	// We serialized them all.  Next offset should be higher than last
	// serialized fd.
	return fd + 1, nil
}

// fd is a single file in /proc/TID/fd/.
type fd struct {
	ramfs.Symlink
	*fs.File
}

// newFD returns a new fd based on an existing file.
//
// This inherits one reference to the file.
func newFd(t *kernel.Task, f *fs.File, msrc *fs.MountSource) *fs.Inode {
	fd := &fd{File: f}
	// RootOwner by default, is overridden in UnstableAttr()
	fd.InitSymlink(t, fs.RootOwner, "")
	return newFile(fd, msrc, fs.Symlink, t)
}

// GetFile returns the fs.File backing this fd.  The dirent and flags
// arguments are ignored.
func (f *fd) GetFile(context.Context, *fs.Dirent, fs.FileFlags) (*fs.File, error) {
	// Take a reference on the fs.File.
	f.File.IncRef()
	return f.File, nil
}

// Readlink returns the current target.
func (f *fd) Readlink(ctx context.Context, _ *fs.Inode) (string, error) {
	root := fs.RootFromContext(ctx)
	defer root.DecRef()
	n, _ := f.Dirent.FullName(root)
	return n, nil
}

// Getlink implements fs.InodeOperations.Getlink.
func (f *fd) Getlink(context.Context, *fs.Inode) (*fs.Dirent, error) {
	f.Dirent.IncRef()
	return f.Dirent, nil
}

// Truncate is ignored.
func (f *fd) Truncate(context.Context, *fs.Inode, int64) error {
	return nil
}

// Close releases the reference on the file.
func (f *fd) Close() error {
	f.DecRef()
	return nil
}

// fdDir implements /proc/TID/fd.
//
// +stateify savable
type fdDir struct {
	ramfs.Dir

	// We hold a reference on the task's fdmap but only keep an indirect
	// task pointer to avoid Dirent loading circularity caused by fdmap's
	// potential back pointers into the dirent tree.
	t *kernel.Task
}

// newFdDir creates a new fdDir.
func newFdDir(t *kernel.Task, msrc *fs.MountSource) *fs.Inode {
	f := &fdDir{t: t}
	f.InitDir(t, nil, fs.RootOwner, fs.FilePermissions{User: fs.PermMask{Read: true, Execute: true}})
	return newFile(f, msrc, fs.SpecialDirectory, t)
}

// Check implements InodeOperations.Check.
//
// This is to match Linux, which uses a special permission handler to guarantee
// that a process can still access /proc/self/fd after it has executed
// setuid. See fs/proc/fd.c:proc_fd_permission.
func (f *fdDir) Check(ctx context.Context, inode *fs.Inode, req fs.PermMask) bool {
	if fs.ContextCanAccessFile(ctx, inode, req) {
		return true
	}
	if t := kernel.TaskFromContext(ctx); t != nil {
		// Allow access if the task trying to access it is in the
		// thread group corresponding to this directory.
		//
		// N.B. Technically, in Linux 3.11, this compares what would be
		// the equivalent of task pointers. However, this was fixed
		// later in 54708d2858e7 ("proc: actually make
		// proc_fd_permission() thread-friendly").
		if f.t.ThreadGroup() == t.ThreadGroup() {
			return true
		}
	}
	return false
}

// Lookup loads an Inode in /proc/TID/fd into a Dirent.
func (f *fdDir) Lookup(ctx context.Context, dir *fs.Inode, p string) (*fs.Dirent, error) {
	n, err := walkDescriptors(f.t, p, func(file *fs.File, _ kernel.FDFlags) *fs.Inode {
		return newFd(f.t, file, dir.MountSource)
	})
	if err != nil {
		return nil, err
	}
	return fs.NewDirent(n, p), nil
}

// DeprecatedReaddir lists fds in /proc/TID/fd.
func (f *fdDir) DeprecatedReaddir(ctx context.Context, dirCtx *fs.DirCtx, offset int) (int, error) {
	return readDescriptors(f.t, dirCtx, offset, func(fd int) fs.DentAttr {
		return fs.GenericDentAttr(fs.Symlink, device.ProcDevice)
	})
}

// fdInfo is a single file in /proc/TID/fdinfo/.
//
// +stateify savable
type fdInfo struct {
	ramfs.File

	flags   fs.FileFlags
	fdFlags kernel.FDFlags
}

// newFdInfo returns a new fdInfo based on an existing file.
func newFdInfo(t *kernel.Task, file *fs.File, fdFlags kernel.FDFlags, msrc *fs.MountSource) *fs.Inode {
	fdi := &fdInfo{flags: file.Flags(), fdFlags: fdFlags}
	fdi.InitFile(t, fs.RootOwner, fs.FilePermissions{User: fs.PermMask{Read: true}})
	// TODO: Get pos, locks, and other data.  For now we only
	// have flags.
	// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt

	flags := file.Flags().ToLinux() | fdFlags.ToLinuxFileFlags()
	fdi.Append([]byte(fmt.Sprintf("flags:\t0%o\n", flags)))
	return newFile(fdi, msrc, fs.SpecialFile, t)
}

// DeprecatedPwritev implements fs.HandleOperations.DeprecatedPwritev.
func (*fdInfo) DeprecatedPwritev(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) {
	return 0, ramfs.ErrInvalidOp
}

// Truncate implements fs.InodeOperations.Truncate.
func (*fdInfo) Truncate(ctx context.Context, inode *fs.Inode, size int64) error {
	return ramfs.ErrInvalidOp
}

// fdInfoDir implements /proc/TID/fdinfo.  It embeds an fdDir, but overrides
// Lookup and Readdir.
//
// +stateify savable
type fdInfoDir struct {
	ramfs.Dir

	t *kernel.Task
}

// newFdInfoDir creates a new fdInfoDir.
func newFdInfoDir(t *kernel.Task, msrc *fs.MountSource) *fs.Inode {
	fdid := &fdInfoDir{t: t}
	fdid.InitDir(t, nil, fs.RootOwner, fs.FilePermsFromMode(0500))
	return newFile(fdid, msrc, fs.SpecialDirectory, t)
}

// Lookup loads an fd in /proc/TID/fdinfo into a Dirent.
func (fdid *fdInfoDir) Lookup(ctx context.Context, dir *fs.Inode, p string) (*fs.Dirent, error) {
	n, err := walkDescriptors(fdid.t, p, func(file *fs.File, fdFlags kernel.FDFlags) *fs.Inode {
		return newFdInfo(fdid.t, file, fdFlags, dir.MountSource)
	})
	if err != nil {
		return nil, err
	}
	return fs.NewDirent(n, p), nil
}

// DeprecatedReaddir lists fds in /proc/TID/fdinfo.
func (fdid *fdInfoDir) DeprecatedReaddir(ctx context.Context, dirCtx *fs.DirCtx, offset int) (int, error) {
	return readDescriptors(fdid.t, dirCtx, offset, func(fd int) fs.DentAttr {
		return fs.GenericDentAttr(fs.RegularFile, device.ProcDevice)
	})
}