syzkaller/pkg/config/config.go
Dmitry Vyukov af643baa32 vm: overhaul
VM infrastructure currently has several problems:
 - Config struct is complete mess with a superset of params for all VM types
 - verification of Config is mess spread across several places
 - there is no place where VM code could do global initialization
   like creating GCE connection, uploading GCE image to GCS,
   matching adb devices with consoles, etc
 - it hard to add private VM implementations
   such impl would need to add code to config package
   which would lead to constant merge conflicts
 - interface for VM implementation is mixed with interface for VM users
   this does not allow to provide best interface for both of them
 - there is no way to add common code for all VM implementations

This change solves these problems by:
 - splitting VM interface for users (vm package) and VM interface
   for VM implementations (vmimpl pacakge), this in turn allows
   to add common code
 - adding Pool concept that allows to do global initialization
   and config checking at the right time
 - decoupling manager config from VM-specific config
   each VM type now defines own config

Note: manager configs need to be changed after this change:
VM-specific parts are moved to own "vm" subobject.

Note: this change also drops "local" VM type.
Its story was long unclear and there is now syz-stress which solves the same problem.
2017-06-03 11:31:42 +02:00

100 lines
2.7 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 config
import (
"encoding/json"
"fmt"
"io/ioutil"
"reflect"
"strings"
)
func LoadFile(filename string, cfg interface{}) error {
if filename == "" {
return fmt.Errorf("no config file specified")
}
data, err := ioutil.ReadFile(filename)
if err != nil {
return fmt.Errorf("failed to read config file: %v", err)
}
return LoadData(data, cfg)
}
func LoadData(data []byte, cfg interface{}) error {
if err := checkUnknownFields(data, reflect.ValueOf(cfg).Type()); err != nil {
return err
}
if err := json.Unmarshal(data, cfg); err != nil {
return fmt.Errorf("failed to parse config file: %v", err)
}
return nil
}
func checkUnknownFields(data []byte, typ reflect.Type) error {
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct {
return fmt.Errorf("config type is not pointer to struct")
}
return checkUnknownFieldsRec(data, "", typ)
}
func checkUnknownFieldsRec(data []byte, prefix string, typ reflect.Type) error {
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
if typ.Kind() != reflect.Struct {
return fmt.Errorf("config type is not pointer to struct")
}
fields := make(map[string]reflect.Type)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if field.Tag.Get("json") == "-" {
continue
}
fields[strings.ToLower(field.Name)] = field.Type
}
f := make(map[string]interface{})
if err := json.Unmarshal(data, &f); err != nil {
return fmt.Errorf("failed to parse config file: %v", err)
}
for k, v := range f {
field, ok := fields[strings.ToLower(k)]
if !ok {
return fmt.Errorf("unknown field '%v%v' in config", prefix, k)
}
if field.Kind() == reflect.Slice &&
(field.PkgPath() != "encoding/json" || field.Name() != "RawMessage") {
vv := reflect.ValueOf(v)
if vv.Type().Kind() != reflect.Slice {
return fmt.Errorf("bad json array type '%v%v'", prefix, k)
}
for i := 0; i < vv.Len(); i++ {
e := vv.Index(i).Interface()
prefix1 := fmt.Sprintf("%v%v[%v].", prefix, k, i)
if err := checkUnknownFieldsStruct(e, prefix1, field.Elem()); err != nil {
return err
}
}
}
if err := checkUnknownFieldsStruct(v, prefix+k+".", field); err != nil {
return err
}
}
return nil
}
func checkUnknownFieldsStruct(val interface{}, prefix string, typ reflect.Type) error {
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
if typ.Kind() != reflect.Struct {
return nil
}
inner, err := json.Marshal(val)
if err != nil {
return fmt.Errorf("failed to marshal inner struct '%v%v':", prefix, err)
}
return checkUnknownFieldsRec(inner, prefix, typ)
}