// Copyright 2016 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.

// syz-crush replays crash log on multiple VMs. Usage:
//   syz-crush -config=config.file execution.log
// Intended for reproduction of particularly elusive crashes.
package main

import (
	"flag"
	"io/ioutil"
	"sync"
	"sync/atomic"
	"time"

	"github.com/google/syzkaller/pkg/instance"
	"github.com/google/syzkaller/pkg/log"
	"github.com/google/syzkaller/pkg/mgrconfig"
	"github.com/google/syzkaller/pkg/osutil"
	"github.com/google/syzkaller/pkg/report"
	"github.com/google/syzkaller/prog"
	"github.com/google/syzkaller/vm"
)

var (
	flagConfig = flag.String("config", "", "configuration file")
)

func main() {
	flag.Parse()
	cfg, err := mgrconfig.LoadFile(*flagConfig)
	if err != nil {
		log.Fatalf("%v", err)
	}
	if len(flag.Args()) != 1 {
		log.Fatalf("usage: syz-crush -config=config.file execution.log")
	}
	if _, err := prog.GetTarget(cfg.TargetOS, cfg.TargetArch); err != nil {
		log.Fatalf("%v", err)
	}
	vmPool, err := vm.Create(cfg, false)
	if err != nil {
		log.Fatalf("%v", err)
	}
	reporter, err := report.NewReporter(cfg)
	if err != nil {
		log.Fatalf("%v", err)
	}

	log.Logf(0, "booting test machines...")
	var shutdown uint32
	var wg sync.WaitGroup
	wg.Add(vmPool.Count() + 1)
	for i := 0; i < vmPool.Count(); i++ {
		i := i
		go func() {
			defer wg.Done()
			for {
				runInstance(cfg, reporter, vmPool, i)
				if atomic.LoadUint32(&shutdown) != 0 {
					break
				}
			}
		}()
	}

	shutdownC := make(chan struct{})
	osutil.HandleInterrupts(shutdownC)
	go func() {
		<-shutdownC
		wg.Done()
		atomic.StoreUint32(&shutdown, 1)
	}()
	wg.Wait()
}

func runInstance(cfg *mgrconfig.Config, reporter report.Reporter, vmPool *vm.Pool, index int) {
	inst, err := vmPool.Create(index)
	if err != nil {
		log.Logf(0, "failed to create instance: %v", err)
		return
	}
	defer inst.Close()

	execprogBin, err := inst.Copy(cfg.SyzExecprogBin)
	if err != nil {
		log.Logf(0, "failed to copy execprog: %v", err)
		return
	}
	executorBin, err := inst.Copy(cfg.SyzExecutorBin)
	if err != nil {
		log.Logf(0, "failed to copy executor: %v", err)
		return
	}
	logFile, err := inst.Copy(flag.Args()[0])
	if err != nil {
		log.Logf(0, "failed to copy log: %v", err)
		return
	}

	cmd := instance.ExecprogCmd(execprogBin, executorBin, cfg.TargetOS, cfg.TargetArch, cfg.Sandbox,
		true, true, true, cfg.Procs, -1, -1, logFile)
	outc, errc, err := inst.Run(time.Hour, nil, cmd)
	if err != nil {
		log.Logf(0, "failed to run execprog: %v", err)
		return
	}

	log.Logf(0, "vm-%v: crushing...", index)
	rep := inst.MonitorExecution(outc, errc, reporter, false)
	if rep == nil {
		// This is the only "OK" outcome.
		log.Logf(0, "vm-%v: running long enough, restarting", index)
	} else {
		f, err := ioutil.TempFile(".", "syz-crush")
		if err != nil {
			log.Logf(0, "failed to create temp file: %v", err)
			return
		}
		defer f.Close()
		log.Logf(0, "vm-%v: crashed: %v, saving to %v", index, rep.Title, f.Name())
		f.Write(rep.Output)
	}
}