mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-24 11:59:58 +00:00
9fa03fa5c9
We append underlying error to the title of boot/test errors. The error can come from anywhere and can contain dynamic data, which can cause duplication of bugs. Put the underlying error into report body instead.
581 lines
18 KiB
Go
581 lines
18 KiB
Go
// Copyright 2017 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 main
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/google/syzkaller/dashboard/dashapi"
|
|
"github.com/google/syzkaller/pkg/build"
|
|
"github.com/google/syzkaller/pkg/config"
|
|
"github.com/google/syzkaller/pkg/hash"
|
|
"github.com/google/syzkaller/pkg/instance"
|
|
"github.com/google/syzkaller/pkg/log"
|
|
"github.com/google/syzkaller/pkg/osutil"
|
|
"github.com/google/syzkaller/pkg/report"
|
|
"github.com/google/syzkaller/pkg/vcs"
|
|
"github.com/google/syzkaller/syz-manager/mgrconfig"
|
|
)
|
|
|
|
// This is especially slightly longer than syzkaller rebuild period.
|
|
// If we set kernelRebuildPeriod = syzkallerRebuildPeriod and both are changed
|
|
// during that period (or around that period), we can rebuild kernel, restart
|
|
// manager and then instantly shutdown everything for syzkaller update.
|
|
// Instead we rebuild syzkaller, restart and then rebuild kernel.
|
|
const kernelRebuildPeriod = syzkallerRebuildPeriod + time.Hour
|
|
|
|
// List of required files in kernel build (contents of latest/current dirs).
|
|
var imageFiles = map[string]bool{
|
|
"tag": true, // serialized BuildInfo
|
|
"kernel.config": false, // kernel config used for build
|
|
"image": true, // kernel image
|
|
"kernel": false,
|
|
"initrd": false,
|
|
"key": false, // root ssh key for the image
|
|
"obj/vmlinux": false, // Linux object file with debug info
|
|
"obj/zircon.elf": false, // Zircon object file with debug info
|
|
"obj/akaros-kernel-64b": false, // Akaros object file with debug info
|
|
}
|
|
|
|
// Manager represents a single syz-manager instance.
|
|
// Handles kernel polling, image rebuild and manager process management.
|
|
// As syzkaller builder, it maintains 2 builds:
|
|
// - latest: latest known good kernel build
|
|
// - current: currently used kernel build
|
|
type Manager struct {
|
|
name string
|
|
workDir string
|
|
kernelDir string
|
|
currentDir string
|
|
latestDir string
|
|
compilerID string
|
|
syzkallerCommit string
|
|
configTag string
|
|
configData []byte
|
|
cfg *Config
|
|
repo vcs.Repo
|
|
mgrcfg *ManagerConfig
|
|
managercfg *mgrconfig.Config
|
|
cmd *ManagerCmd
|
|
dash *dashapi.Dashboard
|
|
stop chan struct{}
|
|
}
|
|
|
|
func createManager(cfg *Config, mgrcfg *ManagerConfig, stop chan struct{}) *Manager {
|
|
dir := osutil.Abs(filepath.Join("managers", mgrcfg.Name))
|
|
if err := osutil.MkdirAll(dir); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if mgrcfg.RepoAlias == "" {
|
|
mgrcfg.RepoAlias = mgrcfg.Repo
|
|
}
|
|
|
|
var dash *dashapi.Dashboard
|
|
if cfg.DashboardAddr != "" && mgrcfg.DashboardClient != "" {
|
|
dash = dashapi.New(mgrcfg.DashboardClient, cfg.DashboardAddr, mgrcfg.DashboardKey)
|
|
}
|
|
|
|
// Assume compiler and config don't change underneath us.
|
|
compilerID, err := build.CompilerIdentity(mgrcfg.Compiler)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
var configData []byte
|
|
if mgrcfg.KernelConfig != "" {
|
|
if configData, err = ioutil.ReadFile(mgrcfg.KernelConfig); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
syzkallerCommit, _ := readTag(filepath.FromSlash("syzkaller/current/tag"))
|
|
if syzkallerCommit == "" {
|
|
log.Fatalf("no tag in syzkaller/current/tag")
|
|
}
|
|
|
|
// Prepare manager config skeleton (other fields are filled in writeConfig).
|
|
managercfg, err := mgrconfig.LoadPartialData(mgrcfg.ManagerConfig)
|
|
if err != nil {
|
|
log.Fatalf("failed to load manager %v config: %v", mgrcfg.Name, err)
|
|
}
|
|
managercfg.Name = cfg.Name + "-" + mgrcfg.Name
|
|
managercfg.Syzkaller = filepath.FromSlash("syzkaller/current")
|
|
|
|
kernelDir := filepath.Join(dir, "kernel")
|
|
repo, err := vcs.NewRepo(managercfg.TargetOS, managercfg.Type, kernelDir)
|
|
if err != nil {
|
|
log.Fatalf("failed to create repo for %v: %v", mgrcfg.Name, err)
|
|
}
|
|
|
|
mgr := &Manager{
|
|
name: managercfg.Name,
|
|
workDir: filepath.Join(dir, "workdir"),
|
|
kernelDir: kernelDir,
|
|
currentDir: filepath.Join(dir, "current"),
|
|
latestDir: filepath.Join(dir, "latest"),
|
|
compilerID: compilerID,
|
|
syzkallerCommit: syzkallerCommit,
|
|
configTag: hash.String(configData),
|
|
configData: configData,
|
|
cfg: cfg,
|
|
repo: repo,
|
|
mgrcfg: mgrcfg,
|
|
managercfg: managercfg,
|
|
dash: dash,
|
|
stop: stop,
|
|
}
|
|
os.RemoveAll(mgr.currentDir)
|
|
return mgr
|
|
}
|
|
|
|
// Gates kernel builds.
|
|
// Kernel builds take whole machine, so we don't run more than one at a time.
|
|
// Also current image build script uses some global resources (/dev/nbd0) and can't run in parallel.
|
|
var kernelBuildSem = make(chan struct{}, 1)
|
|
|
|
func (mgr *Manager) loop() {
|
|
lastCommit := ""
|
|
nextBuildTime := time.Now()
|
|
var managerRestartTime time.Time
|
|
latestInfo := mgr.checkLatest()
|
|
if latestInfo != nil && time.Since(latestInfo.Time) < kernelRebuildPeriod/2 {
|
|
// If we have a reasonably fresh build,
|
|
// start manager straight away and don't rebuild kernel for a while.
|
|
log.Logf(0, "%v: using latest image built on %v", mgr.name, latestInfo.KernelCommit)
|
|
managerRestartTime = latestInfo.Time
|
|
nextBuildTime = time.Now().Add(kernelRebuildPeriod)
|
|
mgr.restartManager()
|
|
} else if latestInfo != nil {
|
|
log.Logf(0, "%v: latest image is on %v", mgr.name, latestInfo.KernelCommit)
|
|
}
|
|
|
|
ticker := time.NewTicker(buildRetryPeriod)
|
|
defer ticker.Stop()
|
|
|
|
loop:
|
|
for {
|
|
if time.Since(nextBuildTime) >= 0 {
|
|
rebuildAfter := buildRetryPeriod
|
|
commit, err := mgr.repo.Poll(mgr.mgrcfg.Repo, mgr.mgrcfg.Branch)
|
|
if err != nil {
|
|
mgr.Errorf("failed to poll: %v", err)
|
|
} else {
|
|
log.Logf(0, "%v: poll: %v", mgr.name, commit.Hash)
|
|
if commit.Hash != lastCommit &&
|
|
(latestInfo == nil ||
|
|
commit.Hash != latestInfo.KernelCommit ||
|
|
mgr.compilerID != latestInfo.CompilerID ||
|
|
mgr.configTag != latestInfo.KernelConfigTag) {
|
|
lastCommit = commit.Hash
|
|
select {
|
|
case kernelBuildSem <- struct{}{}:
|
|
log.Logf(0, "%v: building kernel...", mgr.name)
|
|
if err := mgr.build(commit); err != nil {
|
|
log.Logf(0, "%v: %v", mgr.name, err)
|
|
} else {
|
|
log.Logf(0, "%v: build successful, [re]starting manager", mgr.name)
|
|
rebuildAfter = kernelRebuildPeriod
|
|
latestInfo = mgr.checkLatest()
|
|
if latestInfo == nil {
|
|
mgr.Errorf("failed to read build info after build")
|
|
}
|
|
}
|
|
<-kernelBuildSem
|
|
case <-mgr.stop:
|
|
break loop
|
|
}
|
|
}
|
|
}
|
|
nextBuildTime = time.Now().Add(rebuildAfter)
|
|
}
|
|
|
|
select {
|
|
case <-mgr.stop:
|
|
break loop
|
|
default:
|
|
}
|
|
|
|
if latestInfo != nil && (latestInfo.Time != managerRestartTime || mgr.cmd == nil) {
|
|
managerRestartTime = latestInfo.Time
|
|
mgr.restartManager()
|
|
}
|
|
|
|
select {
|
|
case <-ticker.C:
|
|
case <-mgr.stop:
|
|
break loop
|
|
}
|
|
}
|
|
|
|
if mgr.cmd != nil {
|
|
mgr.cmd.Close()
|
|
mgr.cmd = nil
|
|
}
|
|
log.Logf(0, "%v: stopped", mgr.name)
|
|
}
|
|
|
|
// BuildInfo characterizes a kernel build.
|
|
type BuildInfo struct {
|
|
Time time.Time // when the build was done
|
|
Tag string // unique tag combined from compiler id, kernel commit and config tag
|
|
CompilerID string // compiler identity string (e.g. "gcc 7.1.1")
|
|
KernelRepo string
|
|
KernelBranch string
|
|
KernelCommit string // git hash of kernel checkout
|
|
KernelCommitTitle string
|
|
KernelCommitDate time.Time
|
|
KernelConfigTag string // SHA1 hash of .config contents
|
|
}
|
|
|
|
func loadBuildInfo(dir string) (*BuildInfo, error) {
|
|
info := new(BuildInfo)
|
|
if err := config.LoadFile(filepath.Join(dir, "tag"), info); err != nil {
|
|
return nil, err
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
// checkLatest checks if we have a good working latest build and returns its build info.
|
|
// If the build is missing/broken, nil is returned.
|
|
func (mgr *Manager) checkLatest() *BuildInfo {
|
|
if !osutil.FilesExist(mgr.latestDir, imageFiles) {
|
|
return nil
|
|
}
|
|
info, _ := loadBuildInfo(mgr.latestDir)
|
|
return info
|
|
}
|
|
|
|
func (mgr *Manager) build(kernelCommit *vcs.Commit) error {
|
|
var tagData []byte
|
|
tagData = append(tagData, mgr.name...)
|
|
tagData = append(tagData, kernelCommit.Hash...)
|
|
tagData = append(tagData, mgr.compilerID...)
|
|
tagData = append(tagData, mgr.configTag...)
|
|
info := &BuildInfo{
|
|
Time: time.Now(),
|
|
Tag: hash.String(tagData),
|
|
CompilerID: mgr.compilerID,
|
|
KernelRepo: mgr.mgrcfg.Repo,
|
|
KernelBranch: mgr.mgrcfg.Branch,
|
|
KernelCommit: kernelCommit.Hash,
|
|
KernelCommitTitle: kernelCommit.Title,
|
|
KernelCommitDate: kernelCommit.Date,
|
|
KernelConfigTag: mgr.configTag,
|
|
}
|
|
|
|
// We first form the whole image in tmp dir and then rename it to latest.
|
|
tmpDir := mgr.latestDir + ".tmp"
|
|
if err := os.RemoveAll(tmpDir); err != nil {
|
|
return fmt.Errorf("failed to remove tmp dir: %v", err)
|
|
}
|
|
if err := osutil.MkdirAll(tmpDir); err != nil {
|
|
return fmt.Errorf("failed to create tmp dir: %v", err)
|
|
}
|
|
if err := config.SaveFile(filepath.Join(tmpDir, "tag"), info); err != nil {
|
|
return fmt.Errorf("failed to write tag file: %v", err)
|
|
}
|
|
if err := build.Image(mgr.managercfg.TargetOS, mgr.managercfg.TargetVMArch, mgr.managercfg.Type,
|
|
mgr.kernelDir, tmpDir, mgr.mgrcfg.Compiler, mgr.mgrcfg.Userspace,
|
|
mgr.mgrcfg.KernelCmdline, mgr.mgrcfg.KernelSysctl, mgr.configData); err != nil {
|
|
if _, ok := err.(build.KernelBuildError); ok {
|
|
rep := &report.Report{
|
|
Title: fmt.Sprintf("%v build error", mgr.mgrcfg.RepoAlias),
|
|
Output: []byte(err.Error()),
|
|
}
|
|
if err := mgr.reportBuildError(rep, info, tmpDir); err != nil {
|
|
mgr.Errorf("failed to report image error: %v", err)
|
|
}
|
|
}
|
|
return fmt.Errorf("kernel build failed: %v", err)
|
|
}
|
|
|
|
if err := mgr.testImage(tmpDir, info); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Now try to replace latest with our tmp dir as atomically as we can get on Linux.
|
|
if err := os.RemoveAll(mgr.latestDir); err != nil {
|
|
return fmt.Errorf("failed to remove latest dir: %v", err)
|
|
}
|
|
return os.Rename(tmpDir, mgr.latestDir)
|
|
}
|
|
|
|
func (mgr *Manager) restartManager() {
|
|
if !osutil.FilesExist(mgr.latestDir, imageFiles) {
|
|
mgr.Errorf("can't start manager, image files missing")
|
|
return
|
|
}
|
|
if mgr.cmd != nil {
|
|
mgr.cmd.Close()
|
|
mgr.cmd = nil
|
|
}
|
|
if err := osutil.LinkFiles(mgr.latestDir, mgr.currentDir, imageFiles); err != nil {
|
|
mgr.Errorf("failed to create current image dir: %v", err)
|
|
return
|
|
}
|
|
info, err := loadBuildInfo(mgr.currentDir)
|
|
if err != nil {
|
|
mgr.Errorf("failed to load build info: %v", err)
|
|
return
|
|
}
|
|
buildTag, err := mgr.uploadBuild(info, mgr.currentDir)
|
|
if err != nil {
|
|
mgr.Errorf("failed to upload build: %v", err)
|
|
return
|
|
}
|
|
cfgFile, err := mgr.writeConfig(buildTag)
|
|
if err != nil {
|
|
mgr.Errorf("failed to create manager config: %v", err)
|
|
return
|
|
}
|
|
bin := filepath.FromSlash("syzkaller/current/bin/syz-manager")
|
|
logFile := filepath.Join(mgr.currentDir, "manager.log")
|
|
mgr.cmd = NewManagerCmd(mgr.name, logFile, mgr.Errorf, bin, "-config", cfgFile)
|
|
}
|
|
|
|
func (mgr *Manager) testImage(imageDir string, info *BuildInfo) error {
|
|
log.Logf(0, "%v: testing image...", mgr.name)
|
|
mgrcfg, err := mgr.createTestConfig(imageDir, info)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create manager config: %v", err)
|
|
}
|
|
defer os.RemoveAll(mgrcfg.Workdir)
|
|
switch typ := mgrcfg.Type; typ {
|
|
case "gce", "qemu", "gvisor":
|
|
default:
|
|
// Other types don't support creating machines out of thin air.
|
|
return nil
|
|
}
|
|
env, err := instance.NewEnv(mgrcfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
const (
|
|
testVMs = 3
|
|
maxFailures = 1
|
|
)
|
|
results, err := env.Test(testVMs, nil, nil, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
failures := 0
|
|
var failureErr error
|
|
for _, res := range results {
|
|
if res == nil {
|
|
continue
|
|
}
|
|
failures++
|
|
switch err := res.(type) {
|
|
case *instance.TestError:
|
|
if rep := err.Report; rep != nil {
|
|
rep.Report = append([]byte(rep.Title), rep.Report...)
|
|
if err.Boot {
|
|
rep.Title = fmt.Sprintf("%v boot error", mgr.mgrcfg.RepoAlias)
|
|
} else {
|
|
rep.Title = fmt.Sprintf("%v test error", mgr.mgrcfg.RepoAlias)
|
|
}
|
|
if err := mgr.reportBuildError(rep, info, imageDir); err != nil {
|
|
mgr.Errorf("failed to report image error: %v", err)
|
|
}
|
|
}
|
|
if err.Boot {
|
|
failureErr = fmt.Errorf("VM boot failed with: %v", err)
|
|
} else {
|
|
failureErr = fmt.Errorf("VM testing failed with: %v", err)
|
|
}
|
|
default:
|
|
failureErr = res
|
|
}
|
|
}
|
|
if failures > maxFailures {
|
|
return failureErr
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (mgr *Manager) reportBuildError(rep *report.Report, info *BuildInfo, imageDir string) error {
|
|
if mgr.dash == nil {
|
|
log.Logf(0, "%v: image testing failed: %v\n\n%s\n\n%s\n",
|
|
mgr.name, rep.Title, rep.Report, rep.Output)
|
|
return nil
|
|
}
|
|
build, err := mgr.createDashboardBuild(info, imageDir, "error")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req := &dashapi.BuildErrorReq{
|
|
Build: *build,
|
|
Crash: dashapi.Crash{
|
|
Title: rep.Title,
|
|
Corrupted: false, // Otherwise they get merged with other corrupted reports.
|
|
Maintainers: rep.Maintainers,
|
|
Log: rep.Output,
|
|
Report: rep.Report,
|
|
},
|
|
}
|
|
return mgr.dash.ReportBuildError(req)
|
|
}
|
|
|
|
func (mgr *Manager) createTestConfig(imageDir string, info *BuildInfo) (*mgrconfig.Config, error) {
|
|
mgrcfg := new(mgrconfig.Config)
|
|
*mgrcfg = *mgr.managercfg
|
|
mgrcfg.Name += "-test"
|
|
mgrcfg.Tag = info.KernelCommit
|
|
mgrcfg.Workdir = filepath.Join(imageDir, "workdir")
|
|
if err := instance.SetConfigImage(mgrcfg, imageDir); err != nil {
|
|
return nil, err
|
|
}
|
|
mgrcfg.KernelSrc = mgr.kernelDir
|
|
if err := mgrconfig.Complete(mgrcfg); err != nil {
|
|
return nil, fmt.Errorf("bad manager config: %v", err)
|
|
}
|
|
return mgrcfg, nil
|
|
}
|
|
|
|
func (mgr *Manager) writeConfig(buildTag string) (string, error) {
|
|
mgrcfg := new(mgrconfig.Config)
|
|
*mgrcfg = *mgr.managercfg
|
|
|
|
if mgr.dash != nil {
|
|
mgrcfg.DashboardClient = mgr.dash.Client
|
|
mgrcfg.DashboardAddr = mgr.dash.Addr
|
|
mgrcfg.DashboardKey = mgr.dash.Key
|
|
}
|
|
if mgr.cfg.HubAddr != "" {
|
|
mgrcfg.HubClient = mgr.cfg.Name
|
|
mgrcfg.HubAddr = mgr.cfg.HubAddr
|
|
mgrcfg.HubKey = mgr.cfg.HubKey
|
|
}
|
|
mgrcfg.Tag = buildTag
|
|
mgrcfg.Workdir = mgr.workDir
|
|
if err := instance.SetConfigImage(mgrcfg, mgr.currentDir); err != nil {
|
|
return "", err
|
|
}
|
|
// Strictly saying this is somewhat racy as builder can concurrently
|
|
// update the source, or even delete and re-clone. If this causes
|
|
// problems, we need to make a copy of sources after build.
|
|
mgrcfg.KernelSrc = mgr.kernelDir
|
|
if err := mgrconfig.Complete(mgrcfg); err != nil {
|
|
return "", fmt.Errorf("bad manager config: %v", err)
|
|
}
|
|
configFile := filepath.Join(mgr.currentDir, "manager.cfg")
|
|
if err := config.SaveFile(configFile, mgrcfg); err != nil {
|
|
return "", err
|
|
}
|
|
return configFile, nil
|
|
}
|
|
|
|
func (mgr *Manager) uploadBuild(info *BuildInfo, imageDir string) (string, error) {
|
|
if mgr.dash == nil {
|
|
// Dashboard identifies builds by unique tags that are combined
|
|
// from kernel tag, compiler tag and config tag.
|
|
// This combined tag is meaningless without dashboard,
|
|
// so we use kenrel tag (commit tag) because it communicates
|
|
// at least some useful information.
|
|
return info.KernelCommit, nil
|
|
}
|
|
|
|
build, err := mgr.createDashboardBuild(info, imageDir, "normal")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
commitTitles, fixCommits, err := mgr.pollCommits(info.KernelCommit)
|
|
if err != nil {
|
|
// This is not critical for operation.
|
|
mgr.Errorf("failed to poll commits: %v", err)
|
|
}
|
|
build.Commits = commitTitles
|
|
build.FixCommits = fixCommits
|
|
if err := mgr.dash.UploadBuild(build); err != nil {
|
|
return "", err
|
|
}
|
|
return build.ID, nil
|
|
}
|
|
|
|
func (mgr *Manager) createDashboardBuild(info *BuildInfo, imageDir, typ string) (*dashapi.Build, error) {
|
|
var kernelConfig []byte
|
|
if kernelConfigFile := filepath.Join(imageDir, "kernel.config"); osutil.IsExist(kernelConfigFile) {
|
|
var err error
|
|
if kernelConfig, err = ioutil.ReadFile(kernelConfigFile); err != nil {
|
|
return nil, fmt.Errorf("failed to read kernel.config: %v", err)
|
|
}
|
|
}
|
|
// Resulting build depends on both kernel build tag and syzkaller commmit.
|
|
// Also mix in build type, so that image error builds are not merged into normal builds.
|
|
var tagData []byte
|
|
tagData = append(tagData, info.Tag...)
|
|
tagData = append(tagData, mgr.syzkallerCommit...)
|
|
tagData = append(tagData, typ...)
|
|
build := &dashapi.Build{
|
|
Manager: mgr.name,
|
|
ID: hash.String(tagData),
|
|
OS: mgr.managercfg.TargetOS,
|
|
Arch: mgr.managercfg.TargetArch,
|
|
VMArch: mgr.managercfg.TargetVMArch,
|
|
SyzkallerCommit: mgr.syzkallerCommit,
|
|
CompilerID: info.CompilerID,
|
|
KernelRepo: info.KernelRepo,
|
|
KernelBranch: info.KernelBranch,
|
|
KernelCommit: info.KernelCommit,
|
|
KernelCommitTitle: info.KernelCommitTitle,
|
|
KernelCommitDate: info.KernelCommitDate,
|
|
KernelConfig: kernelConfig,
|
|
}
|
|
return build, nil
|
|
}
|
|
|
|
// pollCommits asks dashboard what commits it is interested in (i.e. fixes for
|
|
// open bugs) and returns subset of these commits that are present in a build
|
|
// on commit buildCommit.
|
|
func (mgr *Manager) pollCommits(buildCommit string) ([]string, []dashapi.FixCommit, error) {
|
|
resp, err := mgr.dash.BuilderPoll(mgr.name)
|
|
if err != nil || len(resp.PendingCommits) == 0 {
|
|
return nil, nil, err
|
|
}
|
|
var present []string
|
|
if len(resp.PendingCommits) != 0 {
|
|
commits, err := mgr.repo.ListRecentCommits(buildCommit)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
m := make(map[string]bool, len(commits))
|
|
for _, com := range commits {
|
|
m[vcs.CanonicalizeCommit(com)] = true
|
|
}
|
|
for _, com := range resp.PendingCommits {
|
|
if m[vcs.CanonicalizeCommit(com)] {
|
|
present = append(present, com)
|
|
}
|
|
}
|
|
}
|
|
var fixCommits []dashapi.FixCommit
|
|
if resp.ReportEmail != "" {
|
|
// TODO(dvyukov): mmots contains weird squashed commits titled "linux-next" or "origin",
|
|
// which contain hundreds of other commits. This makes fix attribution totally broken.
|
|
if mgr.mgrcfg.Repo != "git://git.cmpxchg.org/linux-mmots.git" {
|
|
commits, err := mgr.repo.ExtractFixTagsFromCommits(buildCommit, resp.ReportEmail)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
for _, com := range commits {
|
|
fixCommits = append(fixCommits, dashapi.FixCommit{
|
|
Title: com.Title,
|
|
BugID: com.Tag,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return present, fixCommits, nil
|
|
}
|
|
|
|
// Errorf logs non-fatal error and sends it to dashboard.
|
|
func (mgr *Manager) Errorf(msg string, args ...interface{}) {
|
|
log.Logf(0, mgr.name+": "+msg, args...)
|
|
if mgr.dash != nil {
|
|
mgr.dash.LogError(mgr.name, msg, args...)
|
|
}
|
|
}
|