Home | History | Annotate | Download | only in config
      1 // Copyright 2017 syzkaller project authors. All rights reserved.
      2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
      3 
      4 package config
      5 
      6 import (
      7 	"encoding/json"
      8 	"fmt"
      9 	"io/ioutil"
     10 	"reflect"
     11 	"strings"
     12 
     13 	"github.com/google/syzkaller/pkg/osutil"
     14 )
     15 
     16 func LoadFile(filename string, cfg interface{}) error {
     17 	if filename == "" {
     18 		return fmt.Errorf("no config file specified")
     19 	}
     20 	data, err := ioutil.ReadFile(filename)
     21 	if err != nil {
     22 		return fmt.Errorf("failed to read config file: %v", err)
     23 	}
     24 	return LoadData(data, cfg)
     25 }
     26 
     27 func LoadData(data []byte, cfg interface{}) error {
     28 	if err := checkUnknownFields(data, reflect.ValueOf(cfg).Type()); err != nil {
     29 		return err
     30 	}
     31 	if err := json.Unmarshal(data, cfg); err != nil {
     32 		return fmt.Errorf("failed to parse config file: %v", err)
     33 	}
     34 	return nil
     35 }
     36 
     37 func SaveFile(filename string, cfg interface{}) error {
     38 	data, err := SaveData(cfg)
     39 	if err != nil {
     40 		return err
     41 	}
     42 	return osutil.WriteFile(filename, data)
     43 }
     44 
     45 func SaveData(cfg interface{}) ([]byte, error) {
     46 	return json.MarshalIndent(cfg, "", "\t")
     47 }
     48 
     49 func checkUnknownFields(data []byte, typ reflect.Type) error {
     50 	if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct {
     51 		return fmt.Errorf("config type is not pointer to struct")
     52 	}
     53 	return checkUnknownFieldsRec(data, "", typ)
     54 }
     55 
     56 func checkUnknownFieldsRec(data []byte, prefix string, typ reflect.Type) error {
     57 	if typ.Kind() == reflect.Ptr {
     58 		typ = typ.Elem()
     59 	}
     60 	if typ.Kind() != reflect.Struct {
     61 		return fmt.Errorf("config type is not pointer to struct")
     62 	}
     63 	fields := make(map[string]reflect.Type)
     64 	for i := 0; i < typ.NumField(); i++ {
     65 		field := typ.Field(i)
     66 		tag := field.Tag.Get("json")
     67 		if tag == "-" {
     68 			continue
     69 		}
     70 		name := strings.ToLower(field.Name)
     71 		if tag != "" {
     72 			if tag != strings.ToLower(tag) {
     73 				return fmt.Errorf("json tag on '%v%v' should be lower-case", prefix, name)
     74 			}
     75 			name = tag
     76 		}
     77 		fields[name] = field.Type
     78 	}
     79 	f := make(map[string]interface{})
     80 	if err := json.Unmarshal(data, &f); err != nil {
     81 		return fmt.Errorf("failed to parse config file: %v", err)
     82 	}
     83 	for k, v := range f {
     84 		field, ok := fields[strings.ToLower(k)]
     85 		if !ok {
     86 			return fmt.Errorf("unknown field '%v%v' in config", prefix, k)
     87 		}
     88 		if v != nil && field.Kind() == reflect.Slice &&
     89 			(field.PkgPath() != "encoding/json" || field.Name() != "RawMessage") {
     90 			vv := reflect.ValueOf(v)
     91 			if vv.Type().Kind() != reflect.Slice {
     92 				return fmt.Errorf("bad json array type '%v%v'", prefix, k)
     93 			}
     94 			for i := 0; i < vv.Len(); i++ {
     95 				e := vv.Index(i).Interface()
     96 				prefix1 := fmt.Sprintf("%v%v[%v].", prefix, k, i)
     97 				if err := checkUnknownFieldsStruct(e, prefix1, field.Elem()); err != nil {
     98 					return err
     99 				}
    100 			}
    101 		}
    102 		if err := checkUnknownFieldsStruct(v, prefix+k+".", field); err != nil {
    103 			return err
    104 		}
    105 	}
    106 	return nil
    107 }
    108 
    109 func checkUnknownFieldsStruct(val interface{}, prefix string, typ reflect.Type) error {
    110 	if typ.Kind() == reflect.Ptr {
    111 		typ = typ.Elem()
    112 	}
    113 	if typ.Kind() != reflect.Struct {
    114 		return nil
    115 	}
    116 	if typ.PkgPath() == "time" && typ.Name() == "Time" {
    117 		return nil
    118 	}
    119 	inner, err := json.Marshal(val)
    120 	if err != nil {
    121 		return fmt.Errorf("failed to marshal inner struct %q: %v", prefix, err)
    122 	}
    123 	return checkUnknownFieldsRec(inner, prefix, typ)
    124 }
    125