//
// Copyright 2019-2020 Nestybox, 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
//
//    https://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 nsenter

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"syscall"
	"time"

	_ "github.com/nestybox/sysbox-runc/libcontainer/nsenter"
	"github.com/nestybox/sysbox-runc/libcontainer/utils"
	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink/nl"
	"golang.org/x/sys/unix"

	"github.com/nestybox/sysbox-fs/domain"
	"github.com/nestybox/sysbox-fs/fuse"
	"github.com/nestybox/sysbox-fs/mount"
	"github.com/nestybox/sysbox-fs/process"
	"github.com/nestybox/sysbox-runc/libcontainer"
)

const oobMaxFds = 4 // Max fds that can be sent via SCM_RIGHTS from nsenter agent to sysbox-fs.

func init() {
	if len(os.Args) > 1 && os.Args[1] == "nsenter" {
		runtime.GOMAXPROCS(1)
		runtime.LockOSThread()
	}
}

// Pid struct. Utilized by sysbox-runc's nsexec code.
type pid struct {
	Pid           int `json:"pid"`
	PidFirstChild int `json:"pid_first"`
}

// NSenterEvent struct serves as a transport abstraction (envelope) to carry
// all the potential messages that can be exchanged between sysbox-fs master
// instance and secondary (forked) ones (aka child nsenter processes). These
// nsenter processes are dispatched to perform actions inside the container
// namespaces (e.g,. open files, mounts, etc.) which cannot be executed by
// sysbox-fs' main instance.
//
// Every bidirectional transaction is represented by an event structure
// (nsenterEvent), which holds both 'request' and 'response' messages, as well
// as the context necessary to complete any action demanding inter-namespace
// message exchanges.
type NSenterEvent struct {

	// Credentials for the process on whose behalf sysbox-fs is creating the nsenter event.
	Pid uint32 `json:"pid"`
	Uid uint32 `json:"uid"`
	Gid uint32 `json:"gid"`

	// namespace-types to attach to.
	Namespace *[]domain.NStype `json:"namespace"`

	// namepsaces to create (i.e., unshare)
	CloneFlags uint32

	// Request message to be sent.
	ReqMsg *domain.NSenterMessage `json:"request"`

	// Response message to be received.
	ResMsg *domain.NSenterMessage `json:"response"`

	// Nsenter process carrying out the nsexec instruction.
	Process *os.Process `json:"process"`

	// Asynchronous flag to tag events for which no response is expected.
	Async bool

	// IPC pipes among sysbox-fs parent / child processes.
	parentPipe *os.File

	// Zombie Reaper (for left-over nsenter child processes)
	reaper *zombieReaper

	// Backpointer to Nsenter service
	service *nsenterService

	// File descriptors exchanged via SCM_RIGHTS
	fileDescr []int
}

//
// Generic getter / setter methods.
//

func (e *NSenterEvent) SetRequestMsg(m *domain.NSenterMessage) {
	e.ReqMsg = m
}

func (e *NSenterEvent) GetRequestMsg() *domain.NSenterMessage {
	return e.ReqMsg
}

func (e *NSenterEvent) SetResponseMsg(m *domain.NSenterMessage) {
	e.ResMsg = m
}

func (e *NSenterEvent) GetResponseMsg() *domain.NSenterMessage {
	return e.ResMsg
}

func (e *NSenterEvent) GetProcessID() uint32 {
	return uint32(e.Process.Pid)
}

///////////////////////////////////////////////////////////////////////////////
//
// nsenterEvent methods below execute within the context of sysbox-fs' main
// instance, upon invocation of sysbox-fs' handler or seccomp-bpf logic.
//
///////////////////////////////////////////////////////////////////////////////

// getRespFileDescriptors receives the file descriptor sent via SCM_RIGHTS from the child process.
// Returns the fds (or an empty slice if no valid fds were sent), or an error if the receive operation fails.
func (e *NSenterEvent) getRespFileDescriptors(pipe *os.File) ([]int, error) {

	// out-of-band buffer for receiving file descriptors via SCM_RIGHTS
	oob := make([]byte, getOobBufferSize())

	// Recvmsg will unblock when the nsenter agent sends the file descriptor(s),
	// if it dies, or if the timeout expires. If the nsenter agent has no file
	// descriptors to send, it will send a zero-length SCM_RIGHTS message.
	_, oobn, _, _, err := unix.Recvmsg(int(pipe.Fd()), nil, oob, 0)
	if err != nil {
		logrus.Warnf("failed to receive fd(s) via SCM_RIGHTS: %v", err)
		return nil, fmt.Errorf("error receiving fd via SCM_RIGHTS: %v", err)
	}

	// Parse the control message to extract the file descriptors
	oob = oob[:oobn]
	msgs, err := unix.ParseSocketControlMessage(oob)
	if err != nil {
		logrus.Warnf("failed to parse socket control message (oobn=%d): %v", oobn, err)
		return nil, fmt.Errorf("error parsing socket control message: %v", err)
	}

	// Find the SCM_RIGHTS message and extract the file descriptors
	fds := []int{}
	for _, msg := range msgs {
		if msg.Header.Level == unix.SOL_SOCKET && msg.Header.Type == unix.SCM_RIGHTS {
			fds, err = unix.ParseUnixRights(&msg)
			if err != nil {
				logrus.Warnf("failed to parse Unix rights: %v", err)
				return nil, fmt.Errorf("error parsing Unix rights: %v", err)
			}
			break
		}
	}

	return fds, nil
}

// Called by sysbox-fs handler routines to parse the response generated
// by sysbox-fs' grand-child processes.
func (e *NSenterEvent) processResponse(pipe *os.File) error {

	// First, receive the file descriptor(s) via SCM_RIGHTS (if any)
	fds, err := e.getRespFileDescriptors(pipe)
	if err != nil {
		return err
	}

	// Now decode the JSON response
	// Raw message payload to aid in decoding generic messages (see below
	// explanation).
	var payload json.RawMessage
	nsenterMsg := domain.NSenterMessage{
		Payload: &payload,
	}

	// Decode received msg header to help us determine the payload type.
	// Received message will be decoded in two phases. The decode instruction
	// below help us determine the message-type being received. Based on the
	// obtained type, we are able to decode the payload generated by the
	// remote-end. This second step is executed as part of a subsequent
	// unmarshal instruction (see further below).
	if err := json.NewDecoder(pipe).Decode(&nsenterMsg); err != nil {
		logrus.Warnf("Error decoding received nsenterMsg response: %s", err)
		return fmt.Errorf("decoding received nsenterMsg response: %s", err)
	}

	switch nsenterMsg.Type {

	case domain.LookupResponse:
		logrus.Debug("Received nsenterEvent lookupResponse message.")

		var p domain.FileInfo

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.OpenFileResponse:
		logrus.Debug("Received nsenterEvent OpenResponse message.")

		var p int

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.ReadFileResponse:
		logrus.Debug("Received nsenterEvent readResponse message.")

		var p []byte

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.WriteFileResponse:
		logrus.Debug("Received nsenterEvent writeResponse message.")

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: "",
		}
		break

	case domain.ReadDirResponse:
		logrus.Debug("Received nsenterEvent readDirAllResponse message.")

		var p []domain.FileInfo

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.ReadLinkResponse:
		logrus.Debug("Received nsenterEvent readLinkResponse message.")

		var p string

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.MountSyscallResponse:
		logrus.Debug("Received nsenterEvent mountSyscallResponse message.")

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: "",
		}
		break

	case domain.UmountSyscallResponse:
		logrus.Debug("Received nsenterEvent umountSyscallResponse message.")

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: "",
		}
		break

	case domain.MountInfoResponse:
		logrus.Debug("Received nsenterEvent mountInfoResponse message.")

		var p domain.MountInfoRespPayload

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.MountInodeResponse:
		logrus.Debug("Received nsenterEvent mountInodeResponse message.")

		var p domain.MountInodeRespPayload

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.ChownSyscallResponse:
		logrus.Debug("Received nsenterEvent chownSyscallResponse message.")

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: "",
		}
		break

	case domain.SetxattrSyscallResponse:
		logrus.Debug("Received nsenterEvent setxattrSyscallResponse message.")

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: "",
		}
		break

	case domain.GetxattrSyscallResponse:
		logrus.Debug("Received nsenterEvent getxattrSyscallResponse message.")

		var p domain.GetxattrRespPayload

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.RemovexattrSyscallResponse:
		logrus.Debug("Received nsenterEvent removexattrSyscallResponse message.")

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: "",
		}
		break

	case domain.ListxattrSyscallResponse:
		logrus.Debug("Received nsenterEvent listxattrSyscallResponse message.")

		var p domain.ListxattrRespPayload

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.SleepResponse:
		logrus.Debug("Received nsenterEvent sleepResponse message.")

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: "",
		}
		break

	case domain.UidInfoResponse:
		logrus.Debug("Received nsenterEvent uidInfoResponse message.")

		var p domain.UidInfoRespPayload

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.GidInfoResponse:
		logrus.Debug("Received nsenterEvent gidInfoResponse message.")

		var p domain.GidInfoRespPayload

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.Openat2SyscallResponse:
		logrus.Debug("Received nsenterEvent openat2SyscallResponse message.")

		var p domain.Openat2RespPayload

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		// Openat2() returns a file descriptor
		if len(fds) == 0 {
			return fmt.Errorf("expected valid file descriptor for openat2 response, got %v", fds)
		}

		p.Fd = fds[0]

		// Insert the received fd into the response payload
		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	case domain.ErrorResponse:
		logrus.Debug("Received nsenterEvent errorResponse message.")

		var p fuse.IOerror

		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ResMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		break

	default:
		return errors.New("Received unsupported nsenterEvent message.")
	}

	return nil
}

// Auxiliary function to obtain the FS path associated to any given namespace.
// These FS paths are utilized by sysbox-runc's nsexec logic to enter the
// desired namespaces.
//
// Expected format example: "mnt:/proc/<pid>/ns/mnt"
func (e *NSenterEvent) namespacePaths() []string {

	var paths []string

	// Note: e.Namespace is assumed to be ordered such that if userns is present, it's
	// always first.

	for _, nstype := range *(e.Namespace) {
		path := nstype + ":" + filepath.Join("/proc", strconv.Itoa(int(e.Pid)), "/ns", nstype)
		paths = append(paths, path)
	}

	return paths
}

// Sysbox-fs nsenter requests are generated through this method. Handlers seeking to
// access namespaced resources will call this method to dispatch an nsenter agent,
// which will enter the container namespaces to perform the requested operations.
func (e *NSenterEvent) SendRequest() error {

	logrus.Debug("Executing nsenterEvent's SendRequest() method")

	// Alert the zombie reaper that nsenter is about to start. Notice that we
	// skip reaper's services for async requests as, in those cases, the caller
	// is expected to sigkill its generated nsenter processes.
	if !e.Async {
		e.reaper.nsenterStarted()
	}
	defer func() {
		if !e.Async {
			e.reaper.nsenterEnded()
		}
	}()

	// Create a socket pair
	parentPipe, childPipe, err := utils.NewSockPair("nsenterPipe")
	if err != nil {
		return errors.New("Error creating sysbox-fs nsenter pipe")
	}
	e.parentPipe = parentPipe
	defer func() {
		if !e.Async {
			e.parentPipe.Close()
		}
	}()

	// Set the SO_PASSCRED on the socket (so we can pass process credentials across it)
	socket := int(parentPipe.Fd())
	err = syscall.SetsockoptInt(socket, syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)
	if err != nil {
		return fmt.Errorf("Error setting socket options on nsenter pipe: %v", err)
	}

	// Create the nsenter instruction packet
	r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)

	// existing namespaces to join (if any)
	namespaces := e.namespacePaths()
	r.AddData(&libcontainer.Bytemsg{
		Type:  libcontainer.NsPathsAttr,
		Value: []byte(strings.Join(namespaces, ",")),
	})

	// new namespaces to create (after joining existing namespaces)
	r.AddData(&libcontainer.Int32msg{
		Type:  libcontainer.CloneFlagsAttr,
		Value: e.CloneFlags,
	})

	// Prepare exec.cmd in charge of running: "sysbox-fs nsenter".
	cmd := &exec.Cmd{
		Path:        "/proc/self/exe",
		Args:        []string{os.Args[0], "nsenter"},
		ExtraFiles:  []*os.File{childPipe},
		Env:         []string{"_LIBCONTAINER_INITPIPE=3", fmt.Sprintf("GOMAXPROCS=%s", os.Getenv("GOMAXPROCS"))},
		SysProcAttr: &syscall.SysProcAttr{Pdeathsig: syscall.SIGTERM},
		Stdin:       nil,
		Stdout:      nil,
		Stderr:      nil,
	}

	// Launch sysbox-fs' first child process.
	err = cmd.Start()
	childPipe.Close()
	if err != nil {
		logrus.Errorf("Error launching sysbox-fs first child process: %s", err)
		return errors.New("Error launching sysbox-fs first child process")
	}

	// Send the config to child process.
	if _, err := io.Copy(e.parentPipe, bytes.NewReader(r.Serialize())); err != nil {
		logrus.Warnf("Error copying payload to pipe: %s", err)
		if !e.Async {
			e.reaper.nsenterReapReq()
		}
		return errors.New("Error copying payload to pipe")
	}

	// Wait for sysbox-fs' first child process to finish.
	status, err := cmd.Process.Wait()
	if err != nil {
		logrus.Warnf("Error waiting for sysbox-fs first child process: %d, status: %s, error: %s",
			cmd.Process.Pid, status.String(), err)
		if !e.Async {
			e.reaper.nsenterReapReq()
		}
		return err
	}
	if !status.Success() {
		logrus.Warnf("Sysbox-fs first child process error status: %s, pid: %d",
			status.String(), cmd.Process.Pid)
		if !e.Async {
			e.reaper.nsenterReapReq()
		}
		return errors.New("Error waiting for sysbox-fs first child process")
	}

	// Receive sysbox-fs' first-child pid.
	var pid pid
	decoder := json.NewDecoder(e.parentPipe)
	if err := decoder.Decode(&pid); err != nil {
		logrus.Warnf("Error receiving first-child pid: %s", err)
		return errors.New("Error receiving first-child pid")
	}

	firstChildProcess, err := os.FindProcess(pid.PidFirstChild)
	if err != nil {
		logrus.Warnf("Error finding first-child pid: %s", err)
		return err
	}

	// Wait for sysbox-fs' second child process to finish. Ignore the error in
	// case the child has already been reaped for any reason.
	_, _ = firstChildProcess.Wait()

	// Sysbox-fs' third child (grand-child) process remains and will enter the
	// go runtime. This is the nsenter agent process.
	process, err := os.FindProcess(pid.Pid)
	if err != nil {
		logrus.Warnf("Error finding grand-child pid %d: %s", pid.Pid, err)
		return err
	}
	e.Process = process

	//
	// Transfer the nsenterEvent details to grand-child for processing.
	//

	// Send the pid, uid, and gid using SCM creds, so the nsenter process
	// receives them properly and can use them as needed. The kernel translates
	// the creds across namespaces (e.g., from sysbox-fs namespaces to nsenter
	// agent namespaces).
	reqCred := &syscall.Ucred{
		Pid: int32(e.Pid),
		Uid: e.Uid,
		Gid: e.Gid,
	}

	credMsg := syscall.UnixCredentials(reqCred)
	if err := syscall.Sendmsg(socket, nil, credMsg, nil, 0); err != nil {
		logrus.Warnf("Error while sending process credentials to nsenter (%v).", err)
		if !e.Async {
			e.reaper.nsenterReapReq()
		}
		return err
	}

	// Transfer the rest of the payload
	data, err := json.Marshal(*(e.ReqMsg))
	if err != nil {
		logrus.Warnf("Error while encoding nsenter payload (%v).", err)
		if !e.Async {
			e.reaper.nsenterReapReq()
		}
		return err
	}
	_, err = e.parentPipe.Write(data)
	if err != nil {
		logrus.Warnf("Error while writing nsenter payload into pipeline (%v)", err)
		if !e.Async {
			e.reaper.nsenterReapReq()
		}
		return err
	}

	// Return if dealing with an asynchronous request.
	if e.Async {
		return nil
	}

	// Wait for sysbox-fs' grand-child response and process it accordingly.
	ierr := e.processResponse(e.parentPipe)

	// Destroy the socket pair.
	if err := unix.Shutdown(int(parentPipe.Fd()), unix.SHUT_WR); err != nil {
		logrus.Warnf("Error shutting down sysbox-fs nsenter pipe: %s", err)
	}

	if ierr != nil {
		e.reaper.nsenterReapReq()
		return ierr
	}

	e.Process.Wait()

	return nil
}

func (e *NSenterEvent) ReceiveResponse() *domain.NSenterMessage {
	return e.ResMsg
}

// TerminateRequest serves to unwind the nsenter-event FSM after the generation
// of an asynchronous event. This method is not required for regular nsenter
// events, as in those cases the SendRequest() method itself takes care of
// cleaning all the utilized resources.
func (e *NSenterEvent) TerminateRequest() error {

	logrus.Debug("Executing nsenterEvent's TerminateRequest() method")

	if e.Process == nil {
		return nil
	}

	// Destroy the socket pair.
	if err := unix.Shutdown(int(e.parentPipe.Fd()), unix.SHUT_WR); err != nil {
		logrus.Warnf("Error shutting down sysbox-fs nsenter pipe: %s", err)
	}

	// Kill ongoing request.
	if err := e.Process.Kill(); err != nil {
		return err
	}

	e.Process.Wait()
	e.Process = nil

	return nil
}

///////////////////////////////////////////////////////////////////////////////
//
// nsenterEvent methods below execute within the context of container
// namespaces. In other words, they are invoked as part of "sysbox-fs nsenter"
// execution.
//
///////////////////////////////////////////////////////////////////////////////

func (e *NSenterEvent) processLookupRequest() error {

	payload := e.ReqMsg.Payload.(domain.LookupPayload)

	pmi, err := processPayloadMounts(payload.MountSysfs, payload.MountProcfs)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}
	defer pmi.cleanup(pmi.sysfsMountpoint, pmi.procfsMountpoint)

	payload.Entry = replaceProcfsAndSysfsPaths(payload.Entry, pmi)

	// Verify if the resource being looked up is reachable and obtain FileInfo
	// details.
	info, err := os.Lstat(payload.Entry)
	if err != nil {
		// Send an error-message response.
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}

		return nil
	}

	// Allocate new FileInfo struct to return to sysbpx-fs' main instance.
	fileInfo := domain.FileInfo{
		Fname:    info.Name(),
		Fsize:    info.Size(),
		Fmode:    info.Mode(),
		FmodTime: info.ModTime(),
		FisDir:   info.IsDir(),
		Fsys:     info.Sys().(*syscall.Stat_t),
	}

	// Create a response message.
	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.LookupResponse,
		Payload: fileInfo,
	}

	return nil
}

// Once a file has been opened with open(), no permission checking is performed
// by subsequent system calls that work with the returned file descriptor (such
// as read(), write(), fstat(), fcntl(), and mmap()).
func (e *NSenterEvent) processOpenFileRequest() error {

	payload := e.ReqMsg.Payload.(domain.OpenFilePayload)

	pmi, err := processPayloadMounts(payload.MountSysfs, payload.MountProcfs)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}
	defer pmi.cleanup(pmi.sysfsMountpoint, pmi.procfsMountpoint)

	payload.File = replaceProcfsAndSysfsPaths(payload.File, pmi)

	// Extract openflags from the incoming payload.
	openFlags, err := strconv.Atoi(payload.Flags)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}
	// Extract openMode from the incoming payload.
	mode, err := strconv.Atoi(payload.Mode)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}

	// Open the file in question. Notice that we are hardcoding the 'mode'
	// argument (third one) as this one is not relevant in a procfs; that
	// is, user cannot create files -- openflags 'O_CREAT' and 'O_TMPFILE'
	// are not expected (refer to "man open(2)" for details).
	fd, err := os.OpenFile(payload.File, openFlags, os.FileMode(mode))
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}
	fd.Close()

	// Create a response message.
	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.OpenFileResponse,
		Payload: nil,
	}

	return nil
}

func (e *NSenterEvent) processFileReadRequest() error {
	var (
		fd  *os.File
		err error
		sz  int
	)

	payload := e.ReqMsg.Payload.(domain.ReadFilePayload)

	pmi, err := processPayloadMounts(payload.MountSysfs, payload.MountProcfs)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}
	defer pmi.cleanup(pmi.sysfsMountpoint, pmi.procfsMountpoint)

	payload.File = replaceProcfsAndSysfsPaths(payload.File, pmi)

	fd, err = os.Open(payload.File)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}
	defer fd.Close()

	data := make([]byte, payload.Len)

	sz, err = fd.ReadAt(data, payload.Offset)
	if err != nil && err != io.EOF {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}

	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.ReadFileResponse,
		Payload: data[:sz],
	}

	return nil
}

func (e *NSenterEvent) processFileWriteRequest() error {
	var (
		fd  *os.File
		err error
	)

	payload := e.ReqMsg.Payload.(domain.WriteFilePayload)

	pmi, err := processPayloadMounts(payload.MountSysfs, payload.MountProcfs)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}
	defer pmi.cleanup(pmi.sysfsMountpoint, pmi.procfsMountpoint)

	payload.File = replaceProcfsAndSysfsPaths(payload.File, pmi)

	fd, err = os.OpenFile(payload.File, os.O_WRONLY, 0)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}
	defer fd.Close()

	_, err = fd.WriteAt(payload.Data, payload.Offset)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}

	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.WriteFileResponse,
		Payload: nil,
	}

	return nil
}

func (e *NSenterEvent) processDirReadRequest() error {

	payload := e.ReqMsg.Payload.(domain.ReadDirPayload)

	pmi, err := processPayloadMounts(payload.MountSysfs, payload.MountProcfs)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}
	defer pmi.cleanup(pmi.sysfsMountpoint, pmi.procfsMountpoint)

	payload.Dir = replaceProcfsAndSysfsPaths(payload.Dir, pmi)

	// Perform readDir operation and return error msg should this one fail.
	dirContent, err := ioutil.ReadDir(payload.Dir)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}

	// Create a FileInfo slice to return to sysbox-fs' main instance.
	var dirContentList []domain.FileInfo

	for _, entry := range dirContent {
		elem := domain.FileInfo{
			Fname:    entry.Name(),
			Fsize:    entry.Size(),
			Fmode:    entry.Mode(),
			FmodTime: entry.ModTime(),
			FisDir:   entry.IsDir(),
			Fsys:     entry.Sys().(*syscall.Stat_t),
		}
		dirContentList = append(dirContentList, elem)
	}

	// Create a response message.
	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.ReadDirResponse,
		Payload: dirContentList,
	}

	return nil
}

func (e *NSenterEvent) processReadLinkRequest() error {

	payload := e.ReqMsg.Payload.(domain.ReadLinkPayload)

	pmi, err := processPayloadMounts(payload.MountSysfs, payload.MountProcfs)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: err,
		}
		return nil
	}
	defer pmi.cleanup(pmi.sysfsMountpoint, pmi.procfsMountpoint)

	payload.Link = replaceProcfsAndSysfsPaths(payload.Link, pmi)

	// Perform readLink operation and return error msg should this one fail.
	link, err := os.Readlink(payload.Link)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: err,
		}
		return nil
	}

	// Create a response message.
	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.ReadLinkResponse,
		Payload: link,
	}

	return nil
}

func (e *NSenterEvent) processMountSyscallRequest() error {

	var (
		i   int
		err error
	)

	payload := e.ReqMsg.Payload.([]domain.MountSyscallPayload)

	// Extract payload-header from the first element
	header := payload[0].Header

	// For overlayfs mounts we adjust 'nsexec' process' personality (i.e.
	// uid/gid and capabilities) to match the one of the original process
	// performing the syscall. Our goal is mainly to avoid permission issues
	// while accessing kernel's created overlayfs components.
	if payload[0].FsType == "overlay" {

		// Create a dummy 'process' struct to represent the 'sysbox-fs nsenter' process
		// executing this logic.
		pid := os.Getpid()
		this := e.service.prs.ProcessCreate(uint32(pid), 0, 0)

		// Adjust 'nsenter' process personality to match the container's original
		// process.
		if err := this.AdjustPersonality(
			e.Uid,
			e.Gid,
			header.Root,
			header.Cwd,
			header.Capabilities); err != nil {

			// Send an error-message response.
			e.ResMsg = &domain.NSenterMessage{
				Type:    domain.ErrorResponse,
				Payload: &fuse.IOerror{RcvError: err},
			}

			return nil
		}
	}

	// Perform mount instructions.
	for i = 0; i < len(payload); i++ {
		err = unix.Mount(
			payload[i].Source,
			payload[i].Target,
			payload[i].FsType,
			uintptr(payload[i].Flags),
			payload[i].Data,
		)
		if err != nil {
			break
		}
	}

	if err != nil {
		// Unmount previously executed mount instructions (unless it's a remount).
		//
		// TODO: ideally we would revert remounts too, but to do this we need information
		// that we don't have at this stage.
		for j := i - 1; j >= 0; j-- {
			if payload[j].Flags&unix.MS_REMOUNT != unix.MS_REMOUNT {
				_ = unix.Unmount(payload[j].Target, 0)
			}
		}

		// Create error response msg.
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}

		return nil
	}

	// Create success response message.
	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.MountSyscallResponse,
		Payload: "",
	}

	return nil
}

func (e *NSenterEvent) processUmountSyscallRequest() error {

	var (
		i   int
		err error
	)

	payload := e.ReqMsg.Payload.([]domain.UmountSyscallPayload)

	// Perform umount instructions.
	for i = 0; i < len(payload); i++ {
		err = unix.Unmount(
			payload[i].Target,
			int(payload[i].Flags),
		)
		if err != nil {
			// Create error response msg.
			e.ResMsg = &domain.NSenterMessage{
				Type:    domain.ErrorResponse,
				Payload: &fuse.IOerror{RcvError: err},
			}

			break
		}
	}

	// TODO: If an error is found, notice that we will not revert the changes we could have
	// made thus far. In order to do that (i.e., mount again), we need information that we
	// don't have at this stage (mount-source, mount-flags, etc).
	if err != nil {
		return nil
	}

	// Create success response message.
	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.UmountSyscallResponse,
		Payload: "",
	}

	return nil
}

func (e *NSenterEvent) processChownSyscallRequest() error {

	payload := e.ReqMsg.Payload.([]domain.ChownSyscallPayload)

	for _, p := range payload {
		var err error
		if err = unix.Chown(p.Target, p.TargetUid, p.TargetGid); err != nil {
			e.ResMsg = &domain.NSenterMessage{
				Type:    domain.ErrorResponse,
				Payload: &fuse.IOerror{RcvError: err},
			}
			return nil
		}
	}

	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.ChownSyscallResponse,
		Payload: "",
	}

	return nil
}

func (e *NSenterEvent) processSetxattrSyscallRequest() error {
	var err error

	p := e.ReqMsg.Payload.(domain.SetxattrSyscallPayload)

	if p.Syscall == "lsetxattr" {
		err = unix.Lsetxattr(p.Path, p.Name, p.Val, p.Flags)
	} else {
		err = unix.Setxattr(p.Path, p.Name, p.Val, p.Flags)
	}

	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}

	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.SetxattrSyscallResponse,
		Payload: "",
	}

	return nil
}

func (e *NSenterEvent) processGetxattrSyscallRequest() error {
	var (
		err  error
		size int
	)

	p := e.ReqMsg.Payload.(domain.GetxattrSyscallPayload)
	val := make([]byte, p.Size)

	// Create a 'process' struct to represent the 'sysbox-fs nsenter' process
	// executing this logic.
	pid := os.Getpid()
	this := e.service.prs.ProcessCreate(uint32(pid), 0, 0)

	// Adjust 'nsenter' process personality to match the container's original
	// process.
	if err := this.AdjustPersonality(
		e.Uid,
		e.Gid,
		p.Header.Root,
		p.Header.Cwd,
		p.Header.Capabilities); err != nil {

		// Send an error-message response.
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}

		return nil
	}

	if p.Syscall == "lgetxattr" {
		size, err = unix.Lgetxattr(p.Path, p.Name, val)
	} else {
		size, err = unix.Getxattr(p.Path, p.Name, val)
	}

	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}

	e.ResMsg = &domain.NSenterMessage{
		Type: domain.GetxattrSyscallResponse,
		Payload: domain.GetxattrRespPayload{
			Val:  val,
			Size: size,
		},
	}

	return nil
}

func (e *NSenterEvent) processRemovexattrSyscallRequest() error {
	var err error

	p := e.ReqMsg.Payload.(domain.RemovexattrSyscallPayload)

	if p.Syscall == "lremovexattr" {
		err = unix.Lremovexattr(p.Path, p.Name)
	} else {
		err = unix.Removexattr(p.Path, p.Name)
	}

	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}

	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.RemovexattrSyscallResponse,
		Payload: "",
	}

	return nil
}

func (e *NSenterEvent) processListxattrSyscallRequest() error {
	var (
		err  error
		size int
	)

	p := e.ReqMsg.Payload.(domain.ListxattrSyscallPayload)
	val := make([]byte, p.Size)

	// Create a 'process' struct to represent the 'sysbox-fs nsenter' process
	// executing this logic.
	pid := os.Getpid()
	this := e.service.prs.ProcessCreate(uint32(pid), 0, 0)

	// Adjust 'nsenter' process personality to match the container's original
	// process.
	if err := this.AdjustPersonality(
		e.Uid,
		e.Gid,
		p.Header.Root,
		p.Header.Cwd,
		p.Header.Capabilities); err != nil {

		// Send an error-message response.
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}

		return nil
	}

	if p.Syscall == "llistxattr" {
		size, err = unix.Llistxattr(p.Path, val)
	} else {
		size, err = unix.Listxattr(p.Path, val)
	}

	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}

	e.ResMsg = &domain.NSenterMessage{
		Type: domain.ListxattrSyscallResponse,
		Payload: domain.ListxattrRespPayload{
			Val:  val,
			Size: size,
		},
	}

	return nil
}

// getOobBufferSize computes the size of the out-of-band buffer needed to receive
// process credentials via SCM_CREDENTIALS and up to 4 file descriptors via SCM_RIGHTS.
func getOobBufferSize() int {
	var cred syscall.Ucred
	ucred := syscall.UnixCredentials(&cred)
	maxFds := make([]int, oobMaxFds)

	oobSize := len(ucred) + unix.CmsgSpace(len(syscall.UnixRights(maxFds...)))

	return oobSize
}

func (e *NSenterEvent) getProcCreds(pipe *os.File) error {

	socket := int(pipe.Fd())

	err := syscall.SetsockoptInt(socket, syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)
	if err != nil {
		return fmt.Errorf("Error setting socket options for credential passing: %v", err)
	}

	// out-of-band buffer for receiving process credentials via SCM_CREDENTIALS
	oob := make([]byte, getOobBufferSize())

	_, rbytes, _, _, err := syscall.Recvmsg(socket, nil, oob, 0)
	if err != nil {
		return errors.New("Error decoding received process credentials.")
	}
	oob = oob[:rbytes]

	msgs, err := syscall.ParseSocketControlMessage(oob)
	if err != nil || len(msgs) != 1 {
		return errors.New("Error parsing socket control msg.")
	}

	procCred, err := syscall.ParseUnixCredentials(&msgs[0])
	if err != nil {
		return errors.New("Error parsing unix credentials.")
	}

	e.Pid = uint32(procCred.Pid)
	e.Uid = uint32(procCred.Uid)
	e.Gid = uint32(procCred.Gid)

	return nil
}

func (e *NSenterEvent) processMountInfoRequest() error {

	pid := os.Getpid()

	// Create a 'process' struct to represent the 'sysbox-fs nsenter' process
	// executing this logic.
	process := e.service.prs.ProcessCreate(uint32(pid), 0, 0)

	// Create shallow mountInfo DB.
	mip, err := e.service.mts.NewMountInfoParser(
		nil,
		process,
		false,
		false,
		false)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}

	// Create a MountInfo slice to return to sysbox-fs' main instance.
	mountInfoData, err := mip.ExtractMountInfo()
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}

	// Create a response message.
	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.MountInfoResponse,
		Payload: &domain.MountInfoRespPayload{mountInfoData},
	}

	return nil
}

func (e *NSenterEvent) processMountInodeRequest() error {

	payload := e.ReqMsg.Payload.(domain.MountInodeReqPayload)

	var mpInodeList []domain.Inode

	// Iterate through the received mountpoints and extract the corresponding
	// inode.
	for _, mp := range payload.Mountpoints {
		mpInode := domain.FileInode(mp)
		mpInodeList = append(mpInodeList, mpInode)
	}

	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.MountInodeResponse,
		Payload: &domain.MountInodeRespPayload{mpInodeList},
	}

	return nil
}

func (e *NSenterEvent) processSleepRequest() error {

	payload := e.ReqMsg.Payload.(domain.SleepReqPayload)

	ival, err := strconv.ParseInt(payload.Ival, 10, 64)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return nil
	}

	time.Sleep(time.Duration(ival) * time.Second)

	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.SleepResponse,
		Payload: "",
	}

	return nil
}

func (e *NSenterEvent) processUidInfoRequest() error {

	payload := e.ReqMsg.Payload.(domain.UidInfoReqPayload)

	uObj, err := user.Lookup(payload.User)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: fmt.Errorf("Unknown user name")},
		}
		return nil
	}

	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.SleepResponse,
		Payload: &domain.UidInfoRespPayload{Uid: uObj.Uid},
	}

	return nil
}

func (e *NSenterEvent) processGidInfoRequest() error {

	payload := e.ReqMsg.Payload.(domain.GidInfoReqPayload)

	gObj, err := user.LookupGroup(payload.Group)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: fmt.Errorf("Unknown group name")},
		}
		return nil
	}

	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.SleepResponse,
		Payload: &domain.GidInfoRespPayload{Gid: gObj.Gid},
	}

	return nil
}

func (e *NSenterEvent) processOpenat2SyscallRequest(pipe *os.File) (int, error) {
	var err error

	p := e.ReqMsg.Payload.(domain.Openat2SyscallPayload)

	// If requested, verify that the target file resides on sysbox-fs
	if p.CheckForSysboxfs {
		var statfs unix.Statfs_t
		if err := unix.Statfs(p.Path, &statfs); err != nil {
			e.ResMsg = &domain.NSenterMessage{
				Type:    domain.ErrorResponse,
				Payload: &fuse.IOerror{RcvError: fmt.Errorf("failed to stat filesystem for %s: %v", p.Path, err)},
			}
			return -1, nil
		}

		if statfs.Type != unix.FUSE_SUPER_MAGIC { // sysbox-fs is a FUSE filesystem
			e.ResMsg = &domain.NSenterMessage{
				Type:    domain.ErrorResponse,
				Payload: &fuse.IOerror{RcvError: fmt.Errorf("file %s is not on sysbox-fs", p.Path)},
			}
			return -1, nil
		}
	}

	// Adjust nsenter personality (uid/gid and capabilities) to match
	// the original process performing the syscall. This is needed to
	// ensure proper permission checks when opening the file.
	pid := os.Getpid()
	this := e.service.prs.ProcessCreate(uint32(pid), 0, 0)

	if err := this.AdjustPersonality(
		e.Uid,
		e.Gid,
		p.Header.Root,
		p.Header.Cwd,
		p.Header.Capabilities); err != nil {

		// Send an error-message response.
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}

		return -1, nil
	}

	how := &unix.OpenHow{
		Flags:   p.Flags,
		Mode:    p.Mode,
		Resolve: p.Resolve,
	}

	fd, err := unix.Openat2(unix.AT_FDCWD, p.Path, how)
	if err != nil {
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
		return 0, nil
	}

	// note: the fd is not sent in the response; rather it's sent separately via SCM_RIGHTS so it
	// can be transferred properly from the nsenter process to the parent sysbox-fs (i.e., the
	// kernel will allocate an fd for sysbox-fs which will be a copy of the nsenter's fd).
	e.ResMsg = &domain.NSenterMessage{
		Type:    domain.Openat2SyscallResponse,
		Payload: nil,
	}

	return fd, nil
}

// Method in charge of processing all requests generated by sysbox-fs' master
// instance.
func (e *NSenterEvent) processRequest(pipe *os.File) error {

	// Get the credentials (pid, uid, gid) of the process on whose behalf we are operating;
	// store them in the NSenterEvent. These credentials are sent by sysbox-fs' main instance via
	// SCM creds, so they are translated properly by the kernel across namespaces (i.e., from
	// the sysbox-fs namespaces to the nsenter process namespaces).
	if err := e.getProcCreds(pipe); err != nil {
		return err
	}

	// Raw message payload to aid in decoding generic messages (see below
	// explanation).
	var payload json.RawMessage
	nsenterMsg := domain.NSenterMessage{
		Payload: &payload,
	}

	// Decode received msg header to help us determine the payload type.
	// Received message will be decoded in two phases. The decode instruction
	// below help us determine the message-type being received. Based on the
	// obtained type, we are able to decode the payload generated by the
	// remote-end. This second step is executed as part of a subsequent
	// unmarshal instruction (see further below).
	if err := json.NewDecoder(pipe).Decode(&nsenterMsg); err != nil {
		logrus.Warnf("Error decoding received nsenterMsg request (%v).", err)
		return errors.New("Error decoding received event request.")
	}

	switch nsenterMsg.Type {

	case domain.LookupRequest:
		var p domain.LookupPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		return e.processLookupRequest()

	case domain.OpenFileRequest:
		var p domain.OpenFilePayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		return e.processOpenFileRequest()

	case domain.ReadFileRequest:
		var p domain.ReadFilePayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		return e.processFileReadRequest()

	case domain.WriteFileRequest:
		var p domain.WriteFilePayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		return e.processFileWriteRequest()

	case domain.ReadDirRequest:
		var p domain.ReadDirPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		return e.processDirReadRequest()

	case domain.ReadLinkRequest:
		var p domain.ReadLinkPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		return e.processReadLinkRequest()

	case domain.SetxattrSyscallRequest:
		var p domain.SetxattrSyscallPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		return e.processSetxattrSyscallRequest()

	case domain.GetxattrSyscallRequest:
		var p domain.GetxattrSyscallPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		return e.processGetxattrSyscallRequest()

	case domain.RemovexattrSyscallRequest:
		var p domain.RemovexattrSyscallPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		return e.processRemovexattrSyscallRequest()

	case domain.ListxattrSyscallRequest:
		var p domain.ListxattrSyscallPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}
		return e.processListxattrSyscallRequest()

	case domain.MountSyscallRequest:
		var p []domain.MountSyscallPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}

		return e.processMountSyscallRequest()

	case domain.UmountSyscallRequest:
		var p []domain.UmountSyscallPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}

		return e.processUmountSyscallRequest()

	case domain.MountInfoRequest:
		e.ReqMsg = &domain.NSenterMessage{
			Type: nsenterMsg.Type,
		}

		return e.processMountInfoRequest()

	case domain.MountInodeRequest:
		var p domain.MountInodeReqPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}

		return e.processMountInodeRequest()

	case domain.ChownSyscallRequest:
		var p []domain.ChownSyscallPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}

		return e.processChownSyscallRequest()

	case domain.SleepRequest:
		var p domain.SleepReqPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}

		return e.processSleepRequest()

	case domain.UidInfoRequest:
		var p string
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type: nsenterMsg.Type,
		}

		return e.processUidInfoRequest()

	case domain.GidInfoRequest:
		var p string
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type: nsenterMsg.Type,
		}

		return e.processGidInfoRequest()

	case domain.Openat2SyscallRequest:
		var p domain.Openat2SyscallPayload
		if payload != nil {
			err := json.Unmarshal(payload, &p)
			if err != nil {
				logrus.Error(err)
				return err
			}
		}

		e.ReqMsg = &domain.NSenterMessage{
			Type:    nsenterMsg.Type,
			Payload: p,
		}

		fd, err := e.processOpenat2SyscallRequest(pipe)
		if err != nil {
			return err
		}

		// Store the file descriptor (to be sent later via SCM_RIGHTS)
		e.fileDescr = []int{fd}
		return nil

	default:
		e.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: "Unsupported request",
		}
	}

	return nil
}

// nsenter process initialization function. To be executed within the
// context of one (or more) container namespaces.
func Init() (err error) {

	var (
		pipefd      int
		envInitPipe = os.Getenv("_LIBCONTAINER_INITPIPE")
	)

	// Get the INITPIPE.
	pipefd, err = strconv.Atoi(envInitPipe)
	if err != nil {
		return fmt.Errorf("Unable to convert _LIBCONTAINER_INITPIPE=%s to int: %s",
			envInitPipe, err)
	}

	var pipe = os.NewFile(uintptr(pipefd), "pipe")
	defer pipe.Close()

	// Clear the current process's environment to clean any libcontainer
	// specific env vars.
	os.Clearenv()

	// Setup nsenterService and its dependencies.
	var nsenterSvc = NewNSenterService()
	var processSvc = process.NewProcessService()
	var mountSvc = mount.NewMountService()
	nsenterSvc.Setup(processSvc, mountSvc)
	mountSvc.Setup(nil, nil, processSvc, nsenterSvc)

	var event = NSenterEvent{service: nsenterSvc.(*nsenterService)}

	// Process incoming request; response will be populated in event.ResMsg.
	err = event.processRequest(pipe)
	if err != nil {
		event.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
	}

	// Send any response file descriptors out-of-band via SCM_RIGHTS.
	// Note: we must always do this, even if there are no fds to send; otherwise
	// the receiver (sysbox-fs) will block waiting for an fd that never comes.
	rights := syscall.UnixRights(event.fileDescr...)
	err = syscall.Sendmsg(int(pipe.Fd()), nil, rights, nil, 0)
	if err != nil && event.ResMsg == nil {
		event.ResMsg = &domain.NSenterMessage{
			Type:    domain.ErrorResponse,
			Payload: &fuse.IOerror{RcvError: err},
		}
	}

	// Now encode and send the JSON response.
	data, err := json.Marshal(*(event.ResMsg))
	if err != nil {
		return err
	}
	_, err = pipe.Write(data)
	if err != nil {
		return err
	}

	return nil
}
