From a6deb8053825b4c7024c2b04f5d4f5a12ace1272 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 24 Nov 2025 08:04:20 +0100 Subject: pkg/updater: factor out of syz-ci Factor syzkaller updating functionality out of syz-ci so that it can be reused in other binaries. No functional changes intended. --- pkg/updater/updater.go | 324 +++++++++++++++++++++++++++++++++++++++++++++ syz-ci/manager.go | 9 +- syz-ci/syz-ci.go | 76 +++++++++-- syz-ci/updater.go | 348 ------------------------------------------------- 4 files changed, 397 insertions(+), 360 deletions(-) create mode 100644 pkg/updater/updater.go delete mode 100644 syz-ci/updater.go diff --git a/pkg/updater/updater.go b/pkg/updater/updater.go new file mode 100644 index 000000000..dd62a061a --- /dev/null +++ b/pkg/updater/updater.go @@ -0,0 +1,324 @@ +// 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 updater + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/google/syzkaller/pkg/instance" + "github.com/google/syzkaller/pkg/log" + "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/pkg/vcs" + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/targets" +) + +const ( + RebuildPeriod = 12 * time.Hour + BuildRetryPeriod = 10 * time.Minute // used for both syzkaller and kernel +) + +// 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 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 +} + +type Config struct { + // If set, exit on updates instead of restarting the current binary. + ExitOnUpdate bool + BuildSem *instance.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 { + return nil, fmt.Errorf("updater: failed to get wd: %w", err) + } + bin := os.Args[0] + if !filepath.IsAbs(bin) { + bin = filepath.Join(wd, bin) + } + bin = filepath.Clean(bin) + exe := filepath.Base(bin) + if wd != filepath.Dir(bin) { + return nil, fmt.Errorf("updater: %v executable must be in cwd (it will be overwritten on update)", exe) + } + + gopath := filepath.Join(wd, "gopath") + syzkallerDir := filepath.Join(gopath, "src", "github.com", "google", "syzkaller") + osutil.MkdirAll(syzkallerDir) + + // List of required files in syzkaller build (contents of latest/current dirs). + syzFiles := map[string]bool{ + "tag": true, // contains syzkaller repo git hash + "bin/syz-ci": true, // these are just copied from syzkaller dir + "bin/syz-manager": true, + "sys/*/test/*": 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 { + 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 *Updater) UpdateOnStart(autoupdate bool, updatePending, shutdown chan struct{}) { + os.RemoveAll(upd.currentDir) + latestTag := upd.checkLatest() + if latestTag != "" { + var exeMod time.Time + if st, err := os.Stat(upd.exe); err == nil { + exeMod = st.ModTime() + } + uptodate := prog.GitRevisionBase == latestTag && time.Since(exeMod) < time.Minute + if uptodate || !autoupdate { + if uptodate { + // Have a fresh up-to-date build, probably just restarted. + log.Logf(0, "current executable is up-to-date (%v)", latestTag) + } else { + log.Logf(0, "autoupdate is turned off, using latest build %v", latestTag) + } + if err := osutil.LinkFiles(upd.latestDir, upd.currentDir, upd.syzFiles); err != nil { + log.Fatal(err) + } + return + } + } + log.Logf(0, "current executable is on %v", prog.GitRevision) + log.Logf(0, "latest syzkaller build is on %v", latestTag) + + // No syzkaller build or executable is stale. + lastCommit := prog.GitRevisionBase + if lastCommit != latestTag { + // Latest build and syz-ci are inconsistent. Rebuild everything. + lastCommit = "" + latestTag = "" + } + for { + lastCommit = upd.pollAndBuild(lastCommit) + latestTag := upd.checkLatest() + if latestTag != "" { + // The build was successful or we had the latest build from previous runs. + // Either way, use the latest build. + log.Logf(0, "using syzkaller built on %v", latestTag) + if err := osutil.LinkFiles(upd.latestDir, upd.currentDir, upd.syzFiles); err != nil { + log.Fatal(err) + } + if autoupdate && prog.GitRevisionBase != latestTag { + upd.UpdateAndRestart() + } + break + } + + // No good build at all, try again later. + log.Logf(0, "retrying in %v", BuildRetryPeriod) + select { + case <-time.After(BuildRetryPeriod): + case <-shutdown: + os.Exit(0) + } + } + if autoupdate { + go func() { + upd.waitForUpdate() + close(updatePending) + }() + } +} + +// waitForUpdate polls and rebuilds syzkaller. +// Returns when we have a new good build in latest. +func (upd *Updater) waitForUpdate() { + time.Sleep(RebuildPeriod) + latestTag := upd.checkLatest() + lastCommit := latestTag + for { + lastCommit = upd.pollAndBuild(lastCommit) + if latestTag != upd.checkLatest() { + break + } + 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 *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 upd.cfg.ExitOnUpdate { + log.Logf(0, "exiting, please restart syz-ci to run the new version") + os.Exit(0) + } + if err := syscall.Exec(upd.exe, os.Args, os.Environ()); err != nil { + log.Fatal(err) + } + log.Fatalf("not reachable") +} + +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) + return lastCommit + } + log.Logf(0, "syzkaller: poll: %v (%v)", commit.Hash, commit.Title) + if lastCommit == commit.Hash { + return lastCommit + } + log.Logf(0, "syzkaller: building ...") + if err := upd.build(commit); err != nil { + 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 *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 + upd.cfg.BuildSem.Wait() + defer upd.cfg.BuildSem.Signal() + + if upd.descriptions != "" { + files, err := os.ReadDir(upd.descriptions) + if err != nil { + return fmt.Errorf("failed to read descriptions dir: %w", err) + } + for _, f := range files { + src := filepath.Join(upd.descriptions, f.Name()) + dst := "" + switch filepath.Ext(src) { + case ".txt", ".const": + dst = filepath.Join(upd.syzkallerDir, "sys", targets.Linux, f.Name()) + case ".test": + dst = filepath.Join(upd.syzkallerDir, "sys", targets.Linux, "test", f.Name()) + case ".h": + dst = filepath.Join(upd.syzkallerDir, "executor", f.Name()) + default: + continue + } + if err := osutil.CopyFile(src, dst); err != nil { + return err + } + } + } + // This will also generate descriptions and should go before the 'go test' below. + cmd := osutil.Command(instance.MakeBin, "host", "ci") + cmd.Dir = upd.syzkallerDir + cmd.Env = append([]string{"GOPATH=" + upd.gopathDir}, os.Environ()...) + if _, err := osutil.Run(time.Hour, cmd); err != nil { + return fmt.Errorf("make host failed: %w", err) + } + 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="+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) + } + } + cmd = osutil.Command("go", "test", "-short", "./...") + cmd.Dir = upd.syzkallerDir + cmd.Env = append([]string{ + "GOPATH=" + upd.gopathDir, + "SYZ_DISABLE_SANDBOXING=yes", + }, os.Environ()...) + if _, err := osutil.Run(time.Hour, cmd); err != nil { + return fmt.Errorf("testing failed: %w", err) + } + tagFile := filepath.Join(upd.syzkallerDir, "tag") + if err := osutil.WriteFile(tagFile, []byte(commit.Hash)); err != nil { + return fmt.Errorf("failed to write tag file: %w", err) + } + if err := osutil.CopyFiles(upd.syzkallerDir, upd.latestDir, upd.syzFiles); err != nil { + return fmt.Errorf("failed to copy syzkaller: %w", err) + } + return nil +} + +// checkLatest returns tag of the latest build, +// or an empty string if latest build is missing/broken. +func (upd *Updater) checkLatest() string { + if !osutil.FilesExist(upd.latestDir, upd.syzFiles) { + return "" + } + tag, _ := os.ReadFile(filepath.Join(upd.latestDir, "tag")) + return string(tag) +} diff --git a/syz-ci/manager.go b/syz-ci/manager.go index 4f68f2ca4..204bba875 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{ @@ -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 7df27b683..ff1f90518 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", diff --git a/syz-ci/updater.go b/syz-ci/updater.go deleted file mode 100644 index 80e5b4db5..000000000 --- a/syz-ci/updater.go +++ /dev/null @@ -1,348 +0,0 @@ -// 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 ( - "errors" - "fmt" - "os" - "path/filepath" - "strings" - "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" - "github.com/google/syzkaller/pkg/vcs" - "github.com/google/syzkaller/prog" - "github.com/google/syzkaller/sys/targets" -) - -const ( - syzkallerRebuildPeriod = 12 * time.Hour - buildRetryPeriod = 10 * time.Minute // used for both syzkaller and kernel -) - -// SyzUpdater 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 -} - -func NewSyzUpdater(cfg *Config) *SyzUpdater { - wd, err := os.Getwd() - if err != nil { - log.Fatalf("failed to get wd: %v", err) - } - bin := os.Args[0] - if !filepath.IsAbs(bin) { - bin = filepath.Join(wd, bin) - } - 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) - } - - gopath := filepath.Join(wd, "gopath") - syzkallerDir := filepath.Join(gopath, "src", "github.com", "google", "syzkaller") - osutil.MkdirAll(syzkallerDir) - - // List of required files in syzkaller build (contents of latest/current dirs). - syzFiles := map[string]bool{ - "tag": true, // contains syzkaller repo git hash - "bin/syz-ci": true, // these are just copied from syzkaller dir - "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 - } - } - 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, - } -} - -// 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{}) { - os.RemoveAll(upd.currentDir) - latestTag := upd.checkLatest() - if latestTag != "" { - var exeMod time.Time - if st, err := os.Stat(upd.exe); err == nil { - exeMod = st.ModTime() - } - uptodate := prog.GitRevisionBase == latestTag && time.Since(exeMod) < time.Minute - if uptodate || !autoupdate { - if uptodate { - // Have a fresh up-to-date build, probably just restarted. - log.Logf(0, "current executable is up-to-date (%v)", latestTag) - } else { - log.Logf(0, "autoupdate is turned off, using latest build %v", latestTag) - } - if err := osutil.LinkFiles(upd.latestDir, upd.currentDir, upd.syzFiles); err != nil { - log.Fatal(err) - } - return - } - } - log.Logf(0, "current executable is on %v", prog.GitRevision) - log.Logf(0, "latest syzkaller build is on %v", latestTag) - - // No syzkaller build or executable is stale. - lastCommit := prog.GitRevisionBase - if lastCommit != latestTag { - // Latest build and syz-ci are inconsistent. Rebuild everything. - lastCommit = "" - latestTag = "" - } - for { - lastCommit = upd.pollAndBuild(lastCommit) - latestTag := upd.checkLatest() - if latestTag != "" { - // The build was successful or we had the latest build from previous runs. - // Either way, use the latest build. - log.Logf(0, "using syzkaller built on %v", latestTag) - if err := osutil.LinkFiles(upd.latestDir, upd.currentDir, upd.syzFiles); err != nil { - log.Fatal(err) - } - if autoupdate && prog.GitRevisionBase != latestTag { - upd.UpdateAndRestart() - } - return - } - - // No good build at all, try again later. - log.Logf(0, "retrying in %v", buildRetryPeriod) - select { - case <-time.After(buildRetryPeriod): - case <-shutdown: - os.Exit(0) - } - } -} - -// WaitForUpdate polls and rebuilds syzkaller. -// Returns when we have a new good build in latest. -func (upd *SyzUpdater) WaitForUpdate() { - time.Sleep(syzkallerRebuildPeriod) - latestTag := upd.checkLatest() - lastCommit := latestTag - for { - lastCommit = upd.pollAndBuild(lastCommit) - if latestTag != upd.checkLatest() { - break - } - time.Sleep(buildRetryPeriod) - } - log.Logf(0, "syzkaller: update available, restarting") -} - -// UpdateAndRestart updates and restarts the current executable. -// Does not return. -func (upd *SyzUpdater) 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 { - log.Logf(0, "exiting, please restart syz-ci to run the new version") - os.Exit(0) - } - if err := syscall.Exec(upd.exe, os.Args, os.Environ()); err != nil { - log.Fatal(err) - } - log.Fatalf("not reachable") -} - -func (upd *SyzUpdater) 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) - return lastCommit - } - log.Logf(0, "syzkaller: poll: %v (%v)", commit.Hash, commit.Title) - if lastCommit == commit.Hash { - return lastCommit - } - log.Logf(0, "syzkaller: building ...") - if err := upd.build(commit); err != nil { - log.Logf(0, "syzkaller: %v", err) - upd.uploadBuildError(commit, err) - } - return commit.Hash -} - -// nolint: goconst // "GOPATH=" looks good here, ignore -func (upd *SyzUpdater) 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() - - if upd.descriptions != "" { - files, err := os.ReadDir(upd.descriptions) - if err != nil { - return fmt.Errorf("failed to read descriptions dir: %w", err) - } - for _, f := range files { - src := filepath.Join(upd.descriptions, f.Name()) - dst := "" - switch filepath.Ext(src) { - case ".txt", ".const": - dst = filepath.Join(upd.syzkallerDir, "sys", targets.Linux, f.Name()) - case ".test": - dst = filepath.Join(upd.syzkallerDir, "sys", targets.Linux, "test", f.Name()) - case ".h": - dst = filepath.Join(upd.syzkallerDir, "executor", f.Name()) - default: - continue - } - if err := osutil.CopyFile(src, dst); err != nil { - return err - } - } - } - // This will also generate descriptions and should go before the 'go test' below. - cmd := osutil.Command(instance.MakeBin, "host", "ci") - cmd.Dir = upd.syzkallerDir - cmd.Env = append([]string{"GOPATH=" + upd.gopathDir}, os.Environ()...) - 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, "/") - 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], - ) - if _, err := osutil.Run(time.Hour, cmd); err != nil { - return fmt.Errorf("make target failed: %w", err) - } - } - cmd = osutil.Command("go", "test", "-short", "./...") - cmd.Dir = upd.syzkallerDir - cmd.Env = append([]string{ - "GOPATH=" + upd.gopathDir, - "SYZ_DISABLE_SANDBOXING=yes", - }, os.Environ()...) - if _, err := osutil.Run(time.Hour, cmd); err != nil { - return fmt.Errorf("testing failed: %w", err) - } - tagFile := filepath.Join(upd.syzkallerDir, "tag") - if err := osutil.WriteFile(tagFile, []byte(commit.Hash)); err != nil { - return fmt.Errorf("failed to write tag file: %w", err) - } - if err := osutil.CopyFiles(upd.syzkallerDir, upd.latestDir, upd.syzFiles); err != nil { - return fmt.Errorf("failed to copy syzkaller: %w", err) - } - 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 { - if !osutil.FilesExist(upd.latestDir, upd.syzFiles) { - return "" - } - tag, _ := os.ReadFile(filepath.Join(upd.latestDir, "tag")) - return string(tag) -} -- cgit mrf-deployment