From cc9db1024912344c72138b46478f0c1d2b58e8b0 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Fri, 30 Jun 2017 15:32:30 +0200 Subject: [PATCH] syz-ci: extend build info We currently store 3 tags (compiler id, kernel commit and config hash). But we also kernel git report/branch. To not store 2 more tag files, combine everything into a single json file that holds all info about the build. Will allow simpler extenstion in future as well. --- syz-ci/manager.go | 188 ++++++++++++++++++++++++------------------- syz-ci/syzupdater.go | 15 ++-- 2 files changed, 109 insertions(+), 94 deletions(-) diff --git a/syz-ci/manager.go b/syz-ci/manager.go index c986542c..afb0d8d1 100644 --- a/syz-ci/manager.go +++ b/syz-ci/manager.go @@ -30,10 +30,8 @@ const kernelRebuildPeriod = syzkallerRebuildPeriod + time.Hour // List of required files in kernel build (contents of latest/current dirs). var imageFiles = []string{ - "kernel.tag", // git hash of kernel checkout - "compiler.tag", // compiler identity string (e.g. "gcc 7.1.1") - "kernel.config", // kernel config used for the build (identified with SHA1 hash of contents) - "tag", // SHA1 hash of the previous 3 tags (this is what uniquely identifies the build) + "tag", // serialized BuildInfo + "kernel.config", // kernel config used for build "image", // kernel image "key", // root ssh key for the image "obj/vmlinux", // vmlinux with debug info @@ -45,18 +43,18 @@ var imageFiles = []string{ // - latest: latest known good kernel build // - current: currently used kernel build type Manager struct { - name string - workDir string - kernelDir string - currentDir string - latestDir string - compilerTag string - configTag string - cfg *Config - mgrcfg *ManagerConfig - cmd *ManagerCmd - dash *dashboard.Dashboard - stop chan struct{} + name string + workDir string + kernelDir string + currentDir string + latestDir string + compilerID string + configTag string + cfg *Config + mgrcfg *ManagerConfig + cmd *ManagerCmd + dash *dashboard.Dashboard + stop chan struct{} } func createManager(dash *dashboard.Dashboard, cfg *Config, mgrcfg *ManagerConfig, stop chan struct{}) *Manager { @@ -66,7 +64,7 @@ func createManager(dash *dashboard.Dashboard, cfg *Config, mgrcfg *ManagerConfig } // Assume compiler and config don't change underneath us. - compilerTag, err := kernel.CompilerIdentity(mgrcfg.Compiler) + compilerID, err := kernel.CompilerIdentity(mgrcfg.Compiler) if err != nil { Fatal(err) } @@ -76,17 +74,17 @@ func createManager(dash *dashboard.Dashboard, cfg *Config, mgrcfg *ManagerConfig } mgr := &Manager{ - name: mgrcfg.Name, - workDir: filepath.Join(dir, "workdir"), - kernelDir: filepath.Join(dir, "kernel"), - currentDir: filepath.Join(dir, "current"), - latestDir: filepath.Join(dir, "latest"), - compilerTag: compilerTag, - configTag: hash.String(configData), - cfg: cfg, - mgrcfg: mgrcfg, - dash: dash, - stop: stop, + name: mgrcfg.Name, + workDir: filepath.Join(dir, "workdir"), + kernelDir: filepath.Join(dir, "kernel"), + currentDir: filepath.Join(dir, "current"), + latestDir: filepath.Join(dir, "latest"), + compilerID: compilerID, + configTag: hash.String(configData), + cfg: cfg, + mgrcfg: mgrcfg, + dash: dash, + stop: stop, } os.RemoveAll(mgr.currentDir) return mgr @@ -101,19 +99,19 @@ func (mgr *Manager) loop() { lastCommit := "" nextBuildTime := time.Now() var managerRestartTime time.Time - latestTime, latestKernelTag, latestCompilerTag, latestConfigTag := mgr.checkLatest() - if time.Since(latestTime) < kernelRebuildPeriod/2 { + 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. - Logf(0, "%v: using latest image built on %v", mgr.name, latestKernelTag) - managerRestartTime = latestTime + Logf(0, "%v: using latest image built on %v", mgr.name, latestInfo.KernelCommit) + managerRestartTime = latestInfo.Time nextBuildTime = time.Now().Add(kernelRebuildPeriod) mgr.restartManager() - } else { - Logf(0, "%v: latest image is on %v", mgr.name, formatTag(latestKernelTag)) + } else if latestInfo != nil { + Logf(0, "%v: latest image is on %v", mgr.name, latestInfo.KernelCommit) } - ticker := time.NewTicker(kernelRebuildPeriod) + ticker := time.NewTicker(buildRetryPeriod) defer ticker.Stop() loop: @@ -126,9 +124,10 @@ loop: } else { Logf(0, "%v: poll: %v", mgr.name, commit) if commit != lastCommit && - (commit != latestKernelTag || - mgr.compilerTag != latestCompilerTag || - mgr.configTag != latestConfigTag) { + (latestInfo == nil || + commit != latestInfo.KernelCommit || + mgr.compilerID != latestInfo.CompilerID || + mgr.configTag != latestInfo.KernelConfigTag) { lastCommit = commit select { case kernelBuildSem <- struct{}{}: @@ -138,7 +137,10 @@ loop: } else { Logf(0, "%v: build successful, [re]starting manager", mgr.name) rebuildAfter = kernelRebuildPeriod - latestTime, latestKernelTag, latestCompilerTag, latestConfigTag = mgr.checkLatest() + latestInfo = mgr.checkLatest() + if latestInfo == nil { + Logf(0, "%v: failed to read build info after build", mgr.name) + } } <-kernelBuildSem case <-mgr.stop: @@ -155,8 +157,8 @@ loop: default: } - if managerRestartTime != latestTime { - managerRestartTime = latestTime + if latestInfo != nil && (latestInfo.Time != managerRestartTime || mgr.cmd == nil) { + managerRestartTime = latestInfo.Time mgr.restartManager() } @@ -174,24 +176,33 @@ loop: Logf(0, "%v: stopped", mgr.name) } -// checkLatest checks if we have a good working latest build -// and returns its kernel/compiler/config tags. -// If the build is missing/broken, zero mod time is returned. -func (mgr *Manager) checkLatest() (mod time.Time, kernelTag, compilerTag, configTag string) { +// 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 + 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 + return nil } - configData, err := ioutil.ReadFile(filepath.Join(mgr.latestDir, "kernel.config")) - if err != nil { - return - } - configTag = hash.String(configData) - compilerTag, _ = readTag(filepath.Join(mgr.latestDir, "compiler.tag")) - if compilerTag == "" { - return - } - kernelTag, mod = readTag(filepath.Join(mgr.latestDir, "kernel.tag")) - return + info, _ := loadBuildInfo(mgr.latestDir) + return info } func (mgr *Manager) build() error { @@ -230,26 +241,21 @@ func (mgr *Manager) build() error { return err } - writeTagFile := func(filename, data string) error { - f := filepath.Join(tmpDir, filename) - if err := ioutil.WriteFile(f, []byte(data), osutil.DefaultFilePerm); err != nil { - return fmt.Errorf("failed to write tag file: %v", err) - } - return nil + var tagData []byte + tagData = append(tagData, kernelCommit...) + 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, + KernelConfigTag: mgr.configTag, } - if err := writeTagFile("kernel.tag", kernelCommit); err != nil { - return err - } - if err := writeTagFile("compiler.tag", mgr.compilerTag); err != nil { - return err - } - - var tag []byte - tag = append(tag, kernelCommit...) - tag = append(tag, mgr.configTag...) - tag = append(tag, mgr.compilerTag...) - if err := writeTagFile("tag", hash.String(tag)); err != nil { - return err + if err := config.SaveFile(filepath.Join(tmpDir, "tag"), info); err != nil { + return fmt.Errorf("failed to write tag file: %v", err) } // Now try to replace latest with our tmp dir as atomically as we can get on Linux. @@ -272,7 +278,12 @@ func (mgr *Manager) restartManager() { Logf(0, "%v: failed to create current image dir: %v", mgr.name, err) return } - cfgFile, err := mgr.writeConfig() + info, err := loadBuildInfo(mgr.currentDir) + if err != nil { + Logf(0, "%v: failed to load build info: %v", mgr.name, err) + return + } + cfgFile, err := mgr.writeConfig(info) if err != nil { Logf(0, "%v: failed to create manager config: %v", mgr.name, err) return @@ -282,12 +293,12 @@ func (mgr *Manager) restartManager() { mgr.cmd = NewManagerCmd(mgr.name, logFile, bin, "-config", cfgFile) } -func (mgr *Manager) writeConfig() (string, error) { +func (mgr *Manager) writeConfig(info *BuildInfo) (string, error) { mgrcfg := &mgrconfig.Config{ Cover: true, Reproduce: true, Sandbox: "setuid", - Rpc: "localhost:0", + Rpc: ":0", Procs: 1, } err := config.LoadData(mgr.mgrcfg.Manager_Config, mgrcfg) @@ -295,11 +306,14 @@ func (mgr *Manager) writeConfig() (string, error) { return "", err } current := mgr.currentDir - // TODO(dvyukov): we use kernel.tag because dashboard does not support build info yet. - // Later we should use tag file because it identifies kernel+compiler+config. - tag, err := ioutil.ReadFile(filepath.Join(current, "kernel.tag")) - if err != nil { - return "", fmt.Errorf("failed to read tag file: %v", err) + tag := info.Tag + 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. + tag = info.KernelCommit } mgrcfg.Name = mgr.cfg.Name + "-" + mgr.name mgrcfg.Hub_Addr = mgr.cfg.Hub_Addr @@ -308,7 +322,11 @@ func (mgr *Manager) writeConfig() (string, error) { mgrcfg.Dashboard_Key = mgr.cfg.Dashboard_Key mgrcfg.Workdir = mgr.workDir mgrcfg.Vmlinux = filepath.Join(current, "obj", "vmlinux") - mgrcfg.Tag = string(tag) + // 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.Kernel_Src = mgr.kernelDir + mgrcfg.Tag = tag mgrcfg.Syzkaller = filepath.FromSlash("syzkaller/current") mgrcfg.Image = filepath.Join(current, "image") mgrcfg.Sshkey = filepath.Join(current, "key") diff --git a/syz-ci/syzupdater.go b/syz-ci/syzupdater.go index ec8c09e4..8d72052a 100644 --- a/syz-ci/syzupdater.go +++ b/syz-ci/syzupdater.go @@ -95,8 +95,12 @@ func (upd *SyzUpdater) UpdateOnStart(shutdown chan struct{}) { } return } - Logf(0, "current executable is on %v", formatTag(exeTag)) - Logf(0, "latest syzkaller build is on %v", formatTag(latestTag)) + if exeTag == "" { + Logf(0, "current executable is bootstrap") + } else { + Logf(0, "current executable is on %v", exeTag) + Logf(0, "latest syzkaller build is on %v", latestTag) + } // No syzkaller build or executable is stale. lastCommit := "" @@ -220,10 +224,3 @@ func readTag(file string) (tag string, mod time.Time) { } return } - -func formatTag(tag string) string { - if tag == "" { - return "unknown" - } - return tag -}