diff --git a/pkg/bisect/bisect.go b/pkg/bisect/bisect.go index 55a5d44f76c5..a37436495250 100644 --- a/pkg/bisect/bisect.go +++ b/pkg/bisect/bisect.go @@ -36,8 +36,8 @@ type Config struct { Syzkaller SyzkallerConfig Repro ReproConfig Manager *mgrconfig.Config - BuildSemaphore *instance.Semaphore - TestSemaphore *instance.Semaphore + BuildSemaphore *osutil.Semaphore + TestSemaphore *osutil.Semaphore BuildCPUs int // CrossTree specifies whether a cross tree bisection is to take place, i.e. // Kernel.Commit is not reachable from Kernel.Branch. diff --git a/pkg/hash/hash.go b/pkg/hash/hash.go index 350d3a3e8be1..6b3005fc35a9 100644 --- a/pkg/hash/hash.go +++ b/pkg/hash/hash.go @@ -16,7 +16,12 @@ type Sig [sha1.Size]byte func Hash(pieces ...any) Sig { h := sha1.New() for _, data := range pieces { - binary.Write(h, binary.LittleEndian, data) + if str, ok := data.(string); ok { + data = []byte(str) + } + if err := binary.Write(h, binary.LittleEndian, data); err != nil { + panic(err) + } } var sig Sig copy(sig[:], h.Sum(nil)) diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go index b4460e3ab15c..df0d74b98ff8 100644 --- a/pkg/instance/instance.go +++ b/pkg/instance/instance.go @@ -40,8 +40,8 @@ type Env interface { type env struct { cfg *mgrconfig.Config optionalFlags bool - buildSem *Semaphore - testSem *Semaphore + buildSem *osutil.Semaphore + testSem *osutil.Semaphore } type BuildKernelConfig struct { @@ -56,7 +56,7 @@ type BuildKernelConfig struct { BuildCPUs int } -func NewEnv(cfg *mgrconfig.Config, buildSem, testSem *Semaphore) (Env, error) { +func NewEnv(cfg *mgrconfig.Config, buildSem, testSem *osutil.Semaphore) (Env, error) { if !vm.AllowsOvercommit(cfg.Type) { return nil, fmt.Errorf("test instances are not supported for %v VMs", cfg.Type) } @@ -508,40 +508,6 @@ func RunnerCmd(prog, fwdAddr, os, arch string, poolIdx, vmIdx int, threaded, new "-threaded=%t -new-env=%t", prog, fwdAddr, os, arch, poolIdx, vmIdx, threaded, newEnv) } -type Semaphore struct { - ch chan struct{} -} - -func NewSemaphore(count int) *Semaphore { - s := &Semaphore{ - ch: make(chan struct{}, count), - } - for i := 0; i < count; i++ { - s.Signal() - } - return s -} - -func (s *Semaphore) Wait() { - <-s.ch -} - -func (s *Semaphore) WaitC() <-chan struct{} { - return s.ch -} - -func (s *Semaphore) Available() int { - return len(s.ch) -} - -func (s *Semaphore) Signal() { - if av := s.Available(); av == cap(s.ch) { - // Not super reliable, but let it be here just in case. - panic(fmt.Sprintf("semaphore capacity (%d) is exceeded (%d)", cap(s.ch), av)) - } - s.ch <- struct{}{} -} - // RunSmokeTest executes syz-manager in the smoke test mode and returns two values: // The crash report, if the testing failed. // An error if there was a problem not related to testing the kernel. diff --git a/pkg/mgrconfig/load.go b/pkg/mgrconfig/load.go index 4e01cbd5fe62..0681915b3f7c 100644 --- a/pkg/mgrconfig/load.go +++ b/pkg/mgrconfig/load.go @@ -130,19 +130,9 @@ var ( func SetTargets(cfg *Config) error { var err error - cfg.TargetOS, cfg.TargetVMArch, cfg.TargetArch, err = splitTarget(cfg.RawTarget) - if err != nil { - return err - } - cfg.Target, err = prog.GetTarget(cfg.TargetOS, cfg.TargetArch) - if err != nil { - return err - } - cfg.SysTarget = targets.Get(cfg.TargetOS, cfg.TargetVMArch) - if cfg.SysTarget == nil { - return fmt.Errorf("unsupported OS/arch: %v/%v", cfg.TargetOS, cfg.TargetVMArch) - } - return nil + cfg.TargetOS, cfg.TargetVMArch, cfg.TargetArch, cfg.Target, cfg.SysTarget, + err = SplitTarget(cfg.RawTarget) + return err } func Complete(cfg *Config) error { @@ -402,21 +392,29 @@ func (cfg *Config) completeFocusAreas() error { return nil } -func splitTarget(target string) (string, string, string, error) { - if target == "" { - return "", "", "", fmt.Errorf("target is empty") +func SplitTarget(str string) (os, vmarch, arch string, target *prog.Target, sysTarget *targets.Target, err error) { + if str == "" { + err = fmt.Errorf("target is empty") + return } - targetParts := strings.Split(target, "/") + targetParts := strings.Split(str, "/") if len(targetParts) != 2 && len(targetParts) != 3 { - return "", "", "", fmt.Errorf("bad config param target") + err = fmt.Errorf("bad config param target") + return } - os := targetParts[0] - vmarch := targetParts[1] - arch := targetParts[1] + os = targetParts[0] + vmarch = targetParts[1] + arch = targetParts[1] if len(targetParts) == 3 { arch = targetParts[2] } - return os, vmarch, arch, nil + sysTarget = targets.Get(os, vmarch) + if sysTarget == nil { + err = fmt.Errorf("unsupported OS/arch: %v/%v", os, vmarch) + return + } + target, err = prog.GetTarget(os, arch) + return } func ParseEnabledSyscalls(target *prog.Target, enabled, disabled []string, diff --git a/pkg/osutil/semaphore.go b/pkg/osutil/semaphore.go new file mode 100644 index 000000000000..83d251ce0408 --- /dev/null +++ b/pkg/osutil/semaphore.go @@ -0,0 +1,42 @@ +// Copyright 2025 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 osutil + +import ( + "fmt" +) + +type Semaphore struct { + ch chan struct{} +} + +func NewSemaphore(count int) *Semaphore { + s := &Semaphore{ + ch: make(chan struct{}, count), + } + for i := 0; i < count; i++ { + s.Signal() + } + return s +} + +func (s *Semaphore) Wait() { + <-s.ch +} + +func (s *Semaphore) WaitC() <-chan struct{} { + return s.ch +} + +func (s *Semaphore) Available() int { + return len(s.ch) +} + +func (s *Semaphore) Signal() { + if av := s.Available(); av == cap(s.ch) { + // Not super reliable, but let it be here just in case. + panic(fmt.Sprintf("semaphore capacity (%v) is exceeded (%v)", cap(s.ch), av)) + } + s.ch <- struct{}{} +} diff --git a/syz-ci/updater.go b/pkg/updater/updater.go similarity index 63% rename from syz-ci/updater.go rename to pkg/updater/updater.go index 80e5b4db56d1..00181f7329dd 100644 --- a/syz-ci/updater.go +++ b/pkg/updater/updater.go @@ -1,10 +1,9 @@ // 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 +package updater import ( - "errors" "fmt" "os" "path/filepath" @@ -12,7 +11,6 @@ import ( "syscall" "time" - "github.com/google/syzkaller/dashboard/dashapi" "github.com/google/syzkaller/pkg/instance" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" @@ -22,38 +20,54 @@ import ( ) const ( - syzkallerRebuildPeriod = 12 * time.Hour - buildRetryPeriod = 10 * time.Minute // used for both syzkaller and kernel + RebuildPeriod = 12 * time.Hour + BuildRetryPeriod = 10 * time.Minute // used for both syzkaller and kernel ) -// SyzUpdater handles everything related to syzkaller updates. +// Updater handles everything related to syzkaller updates. // As kernel builder, it maintains 2 builds: // - latest: latest known good syzkaller build // - current: currently used syzkaller build // // Additionally it updates and restarts the current executable as necessary. // Current executable is always built on the same revision as the rest of syzkaller binaries. -type SyzUpdater struct { - repo vcs.Repo - exe string - repoAddress string - branch string - descriptions string - gopathDir string - syzkallerDir string - latestDir string - currentDir string - syzFiles map[string]bool - targets map[string]bool - dashboardAddr string - compilerID string - cfg *Config +type Updater struct { + repo vcs.Repo + exe string + repoAddress string + branch string + descriptions string + gopathDir string + syzkallerDir string + latestDir string + currentDir string + syzFiles map[string]bool + compilerID string + cfg *Config } -func NewSyzUpdater(cfg *Config) *SyzUpdater { +type Config struct { + // If set, exit on updates instead of restarting the current binary. + ExitOnUpdate bool + BuildSem *osutil.Semaphore + ReportBuildError func(commit *vcs.Commit, compilerID string, buildErr error) + SyzkallerRepo string + SyzkallerBranch string + SyzkallerDescriptions string + Targets map[Target]bool +} + +type Target struct { + OS string + VMArch string + Arch string +} + +func New(cfg *Config) (*Updater, error) { + os.Unsetenv("GOPATH") wd, err := os.Getwd() if err != nil { - log.Fatalf("failed to get wd: %v", err) + return nil, fmt.Errorf("updater: failed to get wd: %w", err) } bin := os.Args[0] if !filepath.IsAbs(bin) { @@ -62,7 +76,7 @@ func NewSyzUpdater(cfg *Config) *SyzUpdater { bin = filepath.Clean(bin) exe := filepath.Base(bin) if wd != filepath.Dir(bin) { - log.Fatalf("%v executable must be in cwd (it will be overwritten on update)", exe) + return nil, fmt.Errorf("updater: %v executable must be in cwd (it will be overwritten on update)", exe) } gopath := filepath.Join(wd, "gopath") @@ -76,42 +90,40 @@ func NewSyzUpdater(cfg *Config) *SyzUpdater { "bin/syz-manager": true, "sys/*/test/*": true, } - targets := make(map[string]bool) - for _, mgr := range cfg.Managers { - mgrcfg := mgr.managercfg - os, vmarch, arch := mgrcfg.TargetOS, mgrcfg.TargetVMArch, mgrcfg.TargetArch - targets[os+"/"+vmarch+"/"+arch] = true - syzFiles[fmt.Sprintf("bin/%v_%v/syz-execprog", os, vmarch)] = true - if mgrcfg.SysTarget.ExecutorBin == "" { - syzFiles[fmt.Sprintf("bin/%v_%v/syz-executor", os, arch)] = true + for target := range cfg.Targets { + sysTarget := targets.Get(target.OS, target.VMArch) + if sysTarget == nil { + return nil, fmt.Errorf("unsupported OS/arch: %v/%v", target.OS, target.VMArch) + } + syzFiles[fmt.Sprintf("bin/%v_%v/syz-execprog", target.OS, target.VMArch)] = true + if sysTarget.ExecutorBin == "" { + syzFiles[fmt.Sprintf("bin/%v_%v/syz-executor", target.OS, target.Arch)] = true } } compilerID, err := osutil.RunCmd(time.Minute, "", "go", "version") if err != nil { - log.Fatalf("%v", err) - } - return &SyzUpdater{ - repo: vcs.NewSyzkallerRepo(syzkallerDir), - exe: exe, - repoAddress: cfg.SyzkallerRepo, - branch: cfg.SyzkallerBranch, - descriptions: cfg.SyzkallerDescriptions, - gopathDir: gopath, - syzkallerDir: syzkallerDir, - latestDir: filepath.Join("syzkaller", "latest"), - currentDir: filepath.Join("syzkaller", "current"), - syzFiles: syzFiles, - targets: targets, - dashboardAddr: cfg.DashboardAddr, - compilerID: strings.TrimSpace(string(compilerID)), - cfg: cfg, + return nil, err } + return &Updater{ + repo: vcs.NewSyzkallerRepo(syzkallerDir), + exe: exe, + repoAddress: cfg.SyzkallerRepo, + branch: cfg.SyzkallerBranch, + descriptions: cfg.SyzkallerDescriptions, + gopathDir: gopath, + syzkallerDir: syzkallerDir, + latestDir: filepath.Join("syzkaller", "latest"), + currentDir: filepath.Join("syzkaller", "current"), + syzFiles: syzFiles, + compilerID: strings.TrimSpace(string(compilerID)), + cfg: cfg, + }, nil } // UpdateOnStart does 3 things: // - ensures that the current executable is fresh // - ensures that we have a working syzkaller build in current -func (upd *SyzUpdater) UpdateOnStart(autoupdate bool, shutdown chan struct{}) { +func (upd *Updater) UpdateOnStart(autoupdate bool, updatePending, shutdown chan struct{}) { os.RemoveAll(upd.currentDir) latestTag := upd.checkLatest() if latestTag != "" { @@ -156,23 +168,29 @@ func (upd *SyzUpdater) UpdateOnStart(autoupdate bool, shutdown chan struct{}) { if autoupdate && prog.GitRevisionBase != latestTag { upd.UpdateAndRestart() } - return + break } // No good build at all, try again later. - log.Logf(0, "retrying in %v", buildRetryPeriod) + log.Logf(0, "retrying in %v", BuildRetryPeriod) select { - case <-time.After(buildRetryPeriod): + case <-time.After(BuildRetryPeriod): case <-shutdown: os.Exit(0) } } + if autoupdate { + go func() { + upd.waitForUpdate() + close(updatePending) + }() + } } -// WaitForUpdate polls and rebuilds syzkaller. +// waitForUpdate polls and rebuilds syzkaller. // Returns when we have a new good build in latest. -func (upd *SyzUpdater) WaitForUpdate() { - time.Sleep(syzkallerRebuildPeriod) +func (upd *Updater) waitForUpdate() { + time.Sleep(RebuildPeriod) latestTag := upd.checkLatest() lastCommit := latestTag for { @@ -180,20 +198,21 @@ func (upd *SyzUpdater) WaitForUpdate() { if latestTag != upd.checkLatest() { break } - time.Sleep(buildRetryPeriod) + time.Sleep(BuildRetryPeriod) } log.Logf(0, "syzkaller: update available, restarting") } // UpdateAndRestart updates and restarts the current executable. +// If ExitOnUpdate is set, exits without restarting instead. // Does not return. -func (upd *SyzUpdater) UpdateAndRestart() { +func (upd *Updater) UpdateAndRestart() { log.Logf(0, "restarting executable for update") latestBin := filepath.Join(upd.latestDir, "bin", upd.exe) if err := osutil.CopyFile(latestBin, upd.exe); err != nil { log.Fatal(err) } - if *flagExitOnUpgrade { + if upd.cfg.ExitOnUpdate { log.Logf(0, "exiting, please restart syz-ci to run the new version") os.Exit(0) } @@ -203,7 +222,7 @@ func (upd *SyzUpdater) UpdateAndRestart() { log.Fatalf("not reachable") } -func (upd *SyzUpdater) pollAndBuild(lastCommit string) string { +func (upd *Updater) pollAndBuild(lastCommit string) string { commit, err := upd.repo.Poll(upd.repoAddress, upd.branch) if err != nil { log.Logf(0, "syzkaller: failed to poll: %v", err) @@ -215,19 +234,21 @@ func (upd *SyzUpdater) pollAndBuild(lastCommit string) string { } log.Logf(0, "syzkaller: building ...") if err := upd.build(commit); err != nil { - log.Logf(0, "syzkaller: %v", err) - upd.uploadBuildError(commit, err) + log.Errorf("syzkaller: failed to build: %v", err) + if upd.cfg.ReportBuildError != nil { + upd.cfg.ReportBuildError(commit, upd.compilerID, err) + } } return commit.Hash } // nolint: goconst // "GOPATH=" looks good here, ignore -func (upd *SyzUpdater) build(commit *vcs.Commit) error { +func (upd *Updater) build(commit *vcs.Commit) error { // syzkaller testing may be slowed down by concurrent kernel builds too much // and cause timeout failures, so we serialize it with other builds: // https://groups.google.com/forum/#!msg/syzkaller-openbsd-bugs/o-G3vEsyQp4/f_nFpoNKBQAJ - buildSem.Wait() - defer buildSem.Signal() + upd.cfg.BuildSem.Wait() + defer upd.cfg.BuildSem.Signal() if upd.descriptions != "" { files, err := os.ReadDir(upd.descriptions) @@ -259,16 +280,15 @@ func (upd *SyzUpdater) build(commit *vcs.Commit) error { if _, err := osutil.Run(time.Hour, cmd); err != nil { return fmt.Errorf("make host failed: %w", err) } - for target := range upd.targets { - parts := strings.Split(target, "/") + for target := range upd.cfg.Targets { cmd = osutil.Command(instance.MakeBin, "target") cmd.Dir = upd.syzkallerDir cmd.Env = append([]string{}, os.Environ()...) cmd.Env = append(cmd.Env, "GOPATH="+upd.gopathDir, - "TARGETOS="+parts[0], - "TARGETVMARCH="+parts[1], - "TARGETARCH="+parts[2], + "TARGETOS="+target.OS, + "TARGETVMARCH="+target.VMArch, + "TARGETARCH="+target.Arch, ) if _, err := osutil.Run(time.Hour, cmd); err != nil { return fmt.Errorf("make target failed: %w", err) @@ -293,53 +313,9 @@ func (upd *SyzUpdater) build(commit *vcs.Commit) error { return nil } -func (upd *SyzUpdater) uploadBuildError(commit *vcs.Commit, buildErr error) { - var output []byte - var verbose *osutil.VerboseError - title := buildErr.Error() - if errors.As(buildErr, &verbose) { - output = verbose.Output - } - title = "syzkaller: " + title - for _, mgrcfg := range upd.cfg.Managers { - if upd.dashboardAddr == "" || mgrcfg.DashboardClient == "" { - log.Logf(0, "not uploading build error for %v: no dashboard", mgrcfg.Name) - continue - } - dash, err := dashapi.New(mgrcfg.DashboardClient, upd.dashboardAddr, mgrcfg.DashboardKey) - if err != nil { - log.Logf(0, "failed to report build error for %v: %v", mgrcfg.Name, err) - return - } - managercfg := mgrcfg.managercfg - req := &dashapi.BuildErrorReq{ - Build: dashapi.Build{ - Manager: managercfg.Name, - ID: commit.Hash, - OS: managercfg.TargetOS, - Arch: managercfg.TargetArch, - VMArch: managercfg.TargetVMArch, - SyzkallerCommit: commit.Hash, - SyzkallerCommitDate: commit.CommitDate, - CompilerID: upd.compilerID, - KernelRepo: upd.repoAddress, - KernelBranch: upd.branch, - }, - Crash: dashapi.Crash{ - Title: title, - Log: output, - }, - } - if err := dash.ReportBuildError(req); err != nil { - // TODO: log ReportBuildError error to dashboard. - log.Logf(0, "failed to report build error for %v: %v", mgrcfg.Name, err) - } - } -} - // checkLatest returns tag of the latest build, // or an empty string if latest build is missing/broken. -func (upd *SyzUpdater) checkLatest() string { +func (upd *Updater) checkLatest() string { if !osutil.FilesExist(upd.latestDir, upd.syzFiles) { return "" } diff --git a/syz-ci/manager.go b/syz-ci/manager.go index 4f68f2ca450e..116b605f0cd6 100644 --- a/syz-ci/manager.go +++ b/syz-ci/manager.go @@ -33,6 +33,7 @@ import ( "github.com/google/syzkaller/pkg/mgrconfig" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/pkg/report" + "github.com/google/syzkaller/pkg/updater" "github.com/google/syzkaller/pkg/vcs" "github.com/google/syzkaller/prog" _ "github.com/google/syzkaller/sys" @@ -41,11 +42,11 @@ import ( ) // This is especially slightly longer than syzkaller rebuild period. -// If we set kernelRebuildPeriod = syzkallerRebuildPeriod and both are changed +// If we set kernelRebuildPeriod = updater.RebuildPeriod 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 +const kernelRebuildPeriod = updater.RebuildPeriod + time.Hour // List of required files in kernel build (contents of latest/current dirs). var imageFiles = map[string]bool{ @@ -169,12 +170,12 @@ func createManager(cfg *Config, mgrcfg *ManagerConfig, debug bool) (*Manager, er // Gates kernel builds, syzkaller builds and coverage report generation. // 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 buildSem = instance.NewSemaphore(1) +var buildSem = osutil.NewSemaphore(1) // Gates tests that require extra VMs. // Currently we overcommit instances in such cases, so we'd like to minimize the number of // simultaneous env.Test calls. -var testSem = instance.NewSemaphore(1) +var testSem = osutil.NewSemaphore(1) const fuzzingMinutesBeforeCover = 360 const benchUploadPeriod = 30 * time.Minute @@ -200,7 +201,7 @@ func (mgr *Manager) loop(ctx context.Context) { benchUploadTime = time.Now().Add(benchUploadPeriod) - ticker := time.NewTicker(buildRetryPeriod) + ticker := time.NewTicker(updater.BuildRetryPeriod) defer ticker.Stop() loop: @@ -274,7 +275,7 @@ func (mgr *Manager) archiveCommit(commit string) { func (mgr *Manager) pollAndBuild(ctx context.Context, lastCommit string, latestInfo *BuildInfo) ( string, *BuildInfo, time.Duration) { - rebuildAfter := buildRetryPeriod + rebuildAfter := updater.BuildRetryPeriod commit, err := mgr.repo.Poll(mgr.mgrcfg.Repo, mgr.mgrcfg.Branch) if err != nil { mgr.buildFailed = true diff --git a/syz-ci/syz-ci.go b/syz-ci/syz-ci.go index 7df27b683563..ff1f90518515 100644 --- a/syz-ci/syz-ci.go +++ b/syz-ci/syz-ci.go @@ -54,6 +54,7 @@ package main import ( "context" "encoding/json" + "errors" "flag" "fmt" "net" @@ -71,6 +72,7 @@ import ( "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/mgrconfig" "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/pkg/updater" "github.com/google/syzkaller/pkg/vcs" ) @@ -265,7 +267,6 @@ func main() { serveHTTP(cfg) - os.Unsetenv("GOPATH") if cfg.Goroot != "" { os.Setenv("GOROOT", cfg.Goroot) os.Setenv("PATH", filepath.Join(cfg.Goroot, "bin")+ @@ -273,14 +274,29 @@ func main() { } updatePending := make(chan struct{}) - updater := NewSyzUpdater(cfg) - updater.UpdateOnStart(*flagAutoUpdate, shutdownPending) - if *flagAutoUpdate { - go func() { - updater.WaitForUpdate() - close(updatePending) - }() + updateTargets := make(map[updater.Target]bool) + for _, mgr := range cfg.Managers { + updateTargets[updater.Target{ + OS: mgr.managercfg.TargetOS, + VMArch: mgr.managercfg.TargetVMArch, + Arch: mgr.managercfg.TargetArch, + }] = true + } + updater, err := updater.New(&updater.Config{ + ExitOnUpdate: *flagExitOnUpgrade, + BuildSem: buildSem, + ReportBuildError: func(commit *vcs.Commit, compilerID string, buildErr error) { + uploadSyzkallerBuildError(cfg, commit, compilerID, buildErr) + }, + SyzkallerRepo: cfg.SyzkallerRepo, + SyzkallerBranch: cfg.SyzkallerBranch, + SyzkallerDescriptions: cfg.SyzkallerDescriptions, + Targets: updateTargets, + }) + if err != nil { + log.Fatal(err) } + updater.UpdateOnStart(*flagAutoUpdate, updatePending, shutdownPending) ctx, stop := context.WithCancel(context.Background()) var managers []*Manager @@ -389,6 +405,50 @@ func serveHTTP(cfg *Config) { }() } +func uploadSyzkallerBuildError(cfg *Config, commit *vcs.Commit, compilerID string, buildErr error) { + var output []byte + var verbose *osutil.VerboseError + title := buildErr.Error() + if errors.As(buildErr, &verbose) { + output = verbose.Output + } + title = "syzkaller: " + title + for _, mgrcfg := range cfg.Managers { + if cfg.DashboardAddr == "" || mgrcfg.DashboardClient == "" { + log.Logf(0, "not uploading build error for %v: no dashboard", mgrcfg.Name) + continue + } + dash, err := dashapi.New(mgrcfg.DashboardClient, cfg.DashboardAddr, mgrcfg.DashboardKey) + if err != nil { + log.Logf(0, "failed to report build error for %v: %v", mgrcfg.Name, err) + return + } + managercfg := mgrcfg.managercfg + req := &dashapi.BuildErrorReq{ + Build: dashapi.Build{ + Manager: managercfg.Name, + ID: commit.Hash, + OS: managercfg.TargetOS, + Arch: managercfg.TargetArch, + VMArch: managercfg.TargetVMArch, + SyzkallerCommit: commit.Hash, + SyzkallerCommitDate: commit.CommitDate, + CompilerID: compilerID, + KernelRepo: cfg.SyzkallerRepo, + KernelBranch: cfg.SyzkallerBranch, + }, + Crash: dashapi.Crash{ + Title: title, + Log: output, + }, + } + if err := dash.ReportBuildError(req); err != nil { + // TODO: log ReportBuildError error to dashboard. + log.Logf(0, "failed to report build error for %v: %v", mgrcfg.Name, err) + } + } +} + func loadConfig(filename string) (*Config, error) { cfg := &Config{ SyzkallerRepo: "https://github.com/google/syzkaller.git",