// +build linux solaris

package libcontainerd

import (
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	goruntime "runtime"
	"strings"
	"time"

	containerd "github.com/docker/containerd/api/grpc/types"
	"github.com/tonistiigi/fifo"
	"golang.org/x/net/context"
	"golang.org/x/sys/unix"
)

var fdNames = map[int]string{
	unix.Stdin:  "stdin",
	unix.Stdout: "stdout",
	unix.Stderr: "stderr",
}

// process keeps the state for both main container process and exec process.
type process struct {
	processCommon

	// Platform specific fields are below here.
	dir string
}

func (p *process) openFifos(terminal bool) (pipe *IOPipe, err error) {
	if err := os.MkdirAll(p.dir, 0700); err != nil {
		return nil, err
	}

	ctx, _ := context.WithTimeout(context.Background(), 15*time.Second)

	io := &IOPipe{}

	io.Stdin, err = fifo.OpenFifo(ctx, p.fifo(unix.Stdin), unix.O_WRONLY|unix.O_CREAT|unix.O_NONBLOCK, 0700)
	if err != nil {
		return nil, err
	}

	defer func() {
		if err != nil {
			io.Stdin.Close()
		}
	}()

	io.Stdout, err = fifo.OpenFifo(ctx, p.fifo(unix.Stdout), unix.O_RDONLY|unix.O_CREAT|unix.O_NONBLOCK, 0700)
	if err != nil {
		return nil, err
	}

	defer func() {
		if err != nil {
			io.Stdout.Close()
		}
	}()

	if goruntime.GOOS == "solaris" || !terminal {
		// For Solaris terminal handling is done exclusively by the runtime therefore we make no distinction
		// in the processing for terminal and !terminal cases.
		io.Stderr, err = fifo.OpenFifo(ctx, p.fifo(unix.Stderr), unix.O_RDONLY|unix.O_CREAT|unix.O_NONBLOCK, 0700)
		if err != nil {
			return nil, err
		}
		defer func() {
			if err != nil {
				io.Stderr.Close()
			}
		}()
	} else {
		io.Stderr = ioutil.NopCloser(emptyReader{})
	}

	return io, nil
}

func (p *process) sendCloseStdin() error {
	_, err := p.client.remote.apiClient.UpdateProcess(context.Background(), &containerd.UpdateProcessRequest{
		Id:         p.containerID,
		Pid:        p.friendlyName,
		CloseStdin: true,
	})
	if err != nil && (strings.Contains(err.Error(), "container not found") || strings.Contains(err.Error(), "process not found")) {
		return nil
	}
	return err
}

func (p *process) closeFifos(io *IOPipe) {
	io.Stdin.Close()
	io.Stdout.Close()
	io.Stderr.Close()
}

type emptyReader struct{}

func (r emptyReader) Read(b []byte) (int, error) {
	return 0, io.EOF
}

func (p *process) fifo(index int) string {
	return filepath.Join(p.dir, p.friendlyName+"-"+fdNames[index])
}
