// Copyright 2018 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

// Package build contains helper functions for building kernels/images.
package build

import (
	"fmt"
	"path/filepath"
	"strings"
	"time"

	"github.com/google/syzkaller/pkg/osutil"
)

// Image creates a disk image for the specified OS/ARCH/VM.
// Kernel is taken from kernelDir, userspace system is taken from userspaceDir.
// If cmdlineFile is not empty, contents of the file are appended to the kernel command line.
// If sysctlFile is not empty, contents of the file are appended to the image /etc/sysctl.conf.
// Output is stored in outputDir and includes (everything except for image is optional):
//  - image: the image
//  - key: ssh key for the image
//  - kernel: kernel for injected boot
//  - initrd: initrd for injected boot
//  - kernel.config: actual kernel config used during build
//  - obj/: directory with kernel object files (e.g. vmlinux for linux)
func Image(targetOS, targetArch, vmType, kernelDir, outputDir, compiler, userspaceDir,
	cmdlineFile, sysctlFile string, config []byte) error {
	builder, err := getBuilder(targetOS, targetArch, vmType)
	if err != nil {
		return err
	}
	if err := osutil.MkdirAll(filepath.Join(outputDir, "obj")); err != nil {
		return err
	}
	if len(config) != 0 {
		// Write kernel config early, so that it's captured on build failures.
		if err := osutil.WriteFile(filepath.Join(outputDir, "kernel.config"), config); err != nil {
			return fmt.Errorf("failed to write config file: %v", err)
		}
	}
	return builder.build(targetArch, vmType, kernelDir, outputDir, compiler, userspaceDir, cmdlineFile, sysctlFile, config)
}

func Clean(targetOS, targetArch, vmType, kernelDir string) error {
	builder, err := getBuilder(targetOS, targetArch, vmType)
	if err != nil {
		return err
	}
	return builder.clean(kernelDir)
}

type KernelBuildError struct {
	*osutil.VerboseError
}

type builder interface {
	build(targetArch, vmType, kernelDir, outputDir, compiler, userspaceDir,
		cmdlineFile, sysctlFile string, config []byte) error
	clean(kernelDir string) error
}

func getBuilder(targetOS, targetArch, vmType string) (builder, error) {
	switch {
	case targetOS == "linux" && targetArch == "amd64" && vmType == "gvisor":
		return gvisor{}, nil
	case targetOS == "linux" && targetArch == "amd64" && (vmType == "qemu" || vmType == "gce"):
		return linux{}, nil
	case targetOS == "fuchsia" && (targetArch == "amd64" || targetArch == "arm64") && vmType == "qemu":
		return fuchsia{}, nil
	case targetOS == "akaros" && targetArch == "amd64" && vmType == "qemu":
		return akaros{}, nil
	default:
		return nil, fmt.Errorf("unsupported image type %v/%v/%v", targetOS, targetArch, vmType)
	}
}

func CompilerIdentity(compiler string) (string, error) {
	if compiler == "" {
		return "", nil
	}
	arg := "--version"
	if strings.HasSuffix(compiler, "bazel") {
		arg = ""
	}
	output, err := osutil.RunCmd(time.Minute, "", compiler, arg)
	if err != nil {
		return "", err
	}
	for _, line := range strings.Split(string(output), "\n") {
		if strings.Contains(line, "Extracting Bazel") {
			continue
		}
		return strings.TrimSpace(line), nil
	}
	return "", fmt.Errorf("no output from compiler --version")
}