// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package main

import (
	"encoding/json"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"

	"github.com/gitpod-io/gitpod/common-go/log"
	"github.com/opencontainers/runtime-spec/specs-go"
	"github.com/sirupsen/logrus"
	"golang.org/x/xerrors"
)

const RETRY = 3

func main() {
	log := logrus.New()
	log.SetLevel(logrus.DebugLevel)

	var (
		candidates = []string{"bob-runc", "runc"}
		runcPath   string
		bundle     string
		err        error
	)
	for _, c := range candidates {
		runcPath, err = exec.LookPath(c)
		if runcPath != "" {
			break
		}
	}
	if err != nil {
		log.WithError(err).Fatal("runc not found")
	}

	var useFacade bool
	for i, arg := range os.Args {
		if arg == "run" {
			useFacade = true
		}
		if arg == "--bundle" && i+1 < len(os.Args) {
			bundle = os.Args[i+1]
		}
	}

	if useFacade && bundle != "" {
		err = createAndRunc(runcPath, bundle)
	} else {
		err = syscall.Exec(runcPath, os.Args, os.Environ())
	}
	if err != nil {
		log.WithError(err).Fatal("failed")
	}
}

func createAndRunc(runcPath, bundle string) error {
	fn := filepath.Join(bundle, "config.json")
	fc, err := os.ReadFile(fn)
	if err != nil {
		return xerrors.Errorf("cannot read config.json: %w", err)
	}

	var cfg specs.Spec
	err = json.Unmarshal(fc, &cfg)
	if err != nil {
		return xerrors.Errorf("cannot decode config.json: %w", err)
	}

	var hasSysMount bool
	for _, m := range cfg.Mounts {
		if m.Destination == "/sys" {
			hasSysMount = true
			break
		}
	}
	if !hasSysMount {
		cfg.Mounts = append(cfg.Mounts, specs.Mount{
			Destination: "/sys",
			Type:        "sysfs",
			Source:      "sysfs",
		})
	}

	fc, err = json.Marshal(cfg)
	if err != nil {
		return xerrors.Errorf("cannot encode config.json: %w", err)
	}
	for _, fn := range []string{fn, "/tmp/debug.json"} {
		err = os.WriteFile(fn, fc, 0644)
		if err != nil {
			return xerrors.Errorf("cannot encode config.json: %w", err)
		}
	}

	// See here for more details on why retries are necessary.
	// https://github.com/gitpod-io/gitpod/issues/12365
	for i := 0; i <= RETRY; i++ {
		err = syscall.Exec(runcPath, os.Args, os.Environ())
		if err == nil {
			return err
		} else {
			log.WithError(err).Warn("runc failed")
		}
	}

	return xerrors.Errorf("exec %s: %w", runcPath, err)
}
