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.
This commit is contained in:
Dmitry Vyukov 2017-06-30 15:32:30 +02:00
parent 51a013e7b9
commit cc9db10249
2 changed files with 109 additions and 94 deletions

View File

@ -30,10 +30,8 @@ const kernelRebuildPeriod = syzkallerRebuildPeriod + time.Hour
// List of required files in kernel build (contents of latest/current dirs). // List of required files in kernel build (contents of latest/current dirs).
var imageFiles = []string{ var imageFiles = []string{
"kernel.tag", // git hash of kernel checkout "tag", // serialized BuildInfo
"compiler.tag", // compiler identity string (e.g. "gcc 7.1.1") "kernel.config", // kernel config used for build
"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)
"image", // kernel image "image", // kernel image
"key", // root ssh key for the image "key", // root ssh key for the image
"obj/vmlinux", // vmlinux with debug info "obj/vmlinux", // vmlinux with debug info
@ -45,18 +43,18 @@ var imageFiles = []string{
// - latest: latest known good kernel build // - latest: latest known good kernel build
// - current: currently used kernel build // - current: currently used kernel build
type Manager struct { type Manager struct {
name string name string
workDir string workDir string
kernelDir string kernelDir string
currentDir string currentDir string
latestDir string latestDir string
compilerTag string compilerID string
configTag string configTag string
cfg *Config cfg *Config
mgrcfg *ManagerConfig mgrcfg *ManagerConfig
cmd *ManagerCmd cmd *ManagerCmd
dash *dashboard.Dashboard dash *dashboard.Dashboard
stop chan struct{} stop chan struct{}
} }
func createManager(dash *dashboard.Dashboard, cfg *Config, mgrcfg *ManagerConfig, stop chan struct{}) *Manager { 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. // Assume compiler and config don't change underneath us.
compilerTag, err := kernel.CompilerIdentity(mgrcfg.Compiler) compilerID, err := kernel.CompilerIdentity(mgrcfg.Compiler)
if err != nil { if err != nil {
Fatal(err) Fatal(err)
} }
@ -76,17 +74,17 @@ func createManager(dash *dashboard.Dashboard, cfg *Config, mgrcfg *ManagerConfig
} }
mgr := &Manager{ mgr := &Manager{
name: mgrcfg.Name, name: mgrcfg.Name,
workDir: filepath.Join(dir, "workdir"), workDir: filepath.Join(dir, "workdir"),
kernelDir: filepath.Join(dir, "kernel"), kernelDir: filepath.Join(dir, "kernel"),
currentDir: filepath.Join(dir, "current"), currentDir: filepath.Join(dir, "current"),
latestDir: filepath.Join(dir, "latest"), latestDir: filepath.Join(dir, "latest"),
compilerTag: compilerTag, compilerID: compilerID,
configTag: hash.String(configData), configTag: hash.String(configData),
cfg: cfg, cfg: cfg,
mgrcfg: mgrcfg, mgrcfg: mgrcfg,
dash: dash, dash: dash,
stop: stop, stop: stop,
} }
os.RemoveAll(mgr.currentDir) os.RemoveAll(mgr.currentDir)
return mgr return mgr
@ -101,19 +99,19 @@ func (mgr *Manager) loop() {
lastCommit := "" lastCommit := ""
nextBuildTime := time.Now() nextBuildTime := time.Now()
var managerRestartTime time.Time var managerRestartTime time.Time
latestTime, latestKernelTag, latestCompilerTag, latestConfigTag := mgr.checkLatest() latestInfo := mgr.checkLatest()
if time.Since(latestTime) < kernelRebuildPeriod/2 { if latestInfo != nil && time.Since(latestInfo.Time) < kernelRebuildPeriod/2 {
// If we have a reasonably fresh build, // If we have a reasonably fresh build,
// start manager straight away and don't rebuild kernel for a while. // start manager straight away and don't rebuild kernel for a while.
Logf(0, "%v: using latest image built on %v", mgr.name, latestKernelTag) Logf(0, "%v: using latest image built on %v", mgr.name, latestInfo.KernelCommit)
managerRestartTime = latestTime managerRestartTime = latestInfo.Time
nextBuildTime = time.Now().Add(kernelRebuildPeriod) nextBuildTime = time.Now().Add(kernelRebuildPeriod)
mgr.restartManager() mgr.restartManager()
} else { } else if latestInfo != nil {
Logf(0, "%v: latest image is on %v", mgr.name, formatTag(latestKernelTag)) Logf(0, "%v: latest image is on %v", mgr.name, latestInfo.KernelCommit)
} }
ticker := time.NewTicker(kernelRebuildPeriod) ticker := time.NewTicker(buildRetryPeriod)
defer ticker.Stop() defer ticker.Stop()
loop: loop:
@ -126,9 +124,10 @@ loop:
} else { } else {
Logf(0, "%v: poll: %v", mgr.name, commit) Logf(0, "%v: poll: %v", mgr.name, commit)
if commit != lastCommit && if commit != lastCommit &&
(commit != latestKernelTag || (latestInfo == nil ||
mgr.compilerTag != latestCompilerTag || commit != latestInfo.KernelCommit ||
mgr.configTag != latestConfigTag) { mgr.compilerID != latestInfo.CompilerID ||
mgr.configTag != latestInfo.KernelConfigTag) {
lastCommit = commit lastCommit = commit
select { select {
case kernelBuildSem <- struct{}{}: case kernelBuildSem <- struct{}{}:
@ -138,7 +137,10 @@ loop:
} else { } else {
Logf(0, "%v: build successful, [re]starting manager", mgr.name) Logf(0, "%v: build successful, [re]starting manager", mgr.name)
rebuildAfter = kernelRebuildPeriod 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 <-kernelBuildSem
case <-mgr.stop: case <-mgr.stop:
@ -155,8 +157,8 @@ loop:
default: default:
} }
if managerRestartTime != latestTime { if latestInfo != nil && (latestInfo.Time != managerRestartTime || mgr.cmd == nil) {
managerRestartTime = latestTime managerRestartTime = latestInfo.Time
mgr.restartManager() mgr.restartManager()
} }
@ -174,24 +176,33 @@ loop:
Logf(0, "%v: stopped", mgr.name) Logf(0, "%v: stopped", mgr.name)
} }
// checkLatest checks if we have a good working latest build // BuildInfo characterizes a kernel build.
// and returns its kernel/compiler/config tags. type BuildInfo struct {
// If the build is missing/broken, zero mod time is returned. Time time.Time // when the build was done
func (mgr *Manager) checkLatest() (mod time.Time, kernelTag, compilerTag, configTag string) { 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) { if !osutil.FilesExist(mgr.latestDir, imageFiles) {
return return nil
} }
configData, err := ioutil.ReadFile(filepath.Join(mgr.latestDir, "kernel.config")) info, _ := loadBuildInfo(mgr.latestDir)
if err != nil { return info
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
} }
func (mgr *Manager) build() error { func (mgr *Manager) build() error {
@ -230,26 +241,21 @@ func (mgr *Manager) build() error {
return err return err
} }
writeTagFile := func(filename, data string) error { var tagData []byte
f := filepath.Join(tmpDir, filename) tagData = append(tagData, kernelCommit...)
if err := ioutil.WriteFile(f, []byte(data), osutil.DefaultFilePerm); err != nil { tagData = append(tagData, mgr.compilerID...)
return fmt.Errorf("failed to write tag file: %v", err) tagData = append(tagData, mgr.configTag...)
} info := &BuildInfo{
return nil 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 { if err := config.SaveFile(filepath.Join(tmpDir, "tag"), info); err != nil {
return err return fmt.Errorf("failed to write tag file: %v", 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
} }
// Now try to replace latest with our tmp dir as atomically as we can get on Linux. // 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) Logf(0, "%v: failed to create current image dir: %v", mgr.name, err)
return 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 { if err != nil {
Logf(0, "%v: failed to create manager config: %v", mgr.name, err) Logf(0, "%v: failed to create manager config: %v", mgr.name, err)
return return
@ -282,12 +293,12 @@ func (mgr *Manager) restartManager() {
mgr.cmd = NewManagerCmd(mgr.name, logFile, bin, "-config", cfgFile) 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{ mgrcfg := &mgrconfig.Config{
Cover: true, Cover: true,
Reproduce: true, Reproduce: true,
Sandbox: "setuid", Sandbox: "setuid",
Rpc: "localhost:0", Rpc: ":0",
Procs: 1, Procs: 1,
} }
err := config.LoadData(mgr.mgrcfg.Manager_Config, mgrcfg) err := config.LoadData(mgr.mgrcfg.Manager_Config, mgrcfg)
@ -295,11 +306,14 @@ func (mgr *Manager) writeConfig() (string, error) {
return "", err return "", err
} }
current := mgr.currentDir current := mgr.currentDir
// TODO(dvyukov): we use kernel.tag because dashboard does not support build info yet. tag := info.Tag
// Later we should use tag file because it identifies kernel+compiler+config. if mgr.dash == nil {
tag, err := ioutil.ReadFile(filepath.Join(current, "kernel.tag")) // Dashboard identifies builds by unique tags that are combined
if err != nil { // from kernel tag, compiler tag and config tag.
return "", fmt.Errorf("failed to read tag file: %v", err) // 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.Name = mgr.cfg.Name + "-" + mgr.name
mgrcfg.Hub_Addr = mgr.cfg.Hub_Addr 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.Dashboard_Key = mgr.cfg.Dashboard_Key
mgrcfg.Workdir = mgr.workDir mgrcfg.Workdir = mgr.workDir
mgrcfg.Vmlinux = filepath.Join(current, "obj", "vmlinux") 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.Syzkaller = filepath.FromSlash("syzkaller/current")
mgrcfg.Image = filepath.Join(current, "image") mgrcfg.Image = filepath.Join(current, "image")
mgrcfg.Sshkey = filepath.Join(current, "key") mgrcfg.Sshkey = filepath.Join(current, "key")

View File

@ -95,8 +95,12 @@ func (upd *SyzUpdater) UpdateOnStart(shutdown chan struct{}) {
} }
return return
} }
Logf(0, "current executable is on %v", formatTag(exeTag)) if exeTag == "" {
Logf(0, "latest syzkaller build is on %v", formatTag(latestTag)) 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. // No syzkaller build or executable is stale.
lastCommit := "" lastCommit := ""
@ -220,10 +224,3 @@ func readTag(file string) (tag string, mod time.Time) {
} }
return return
} }
func formatTag(tag string) string {
if tag == "" {
return "unknown"
}
return tag
}