newt/syscfg/restrict.go (257 lines of code) (raw):

/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ // Currently, two forms of restrictions are supported: // 1. "$notnull" // 2. expression // // The "$notnull" string indicates that the setting must be set to something // other than the empty string. // // An expression string can take two forms: // 1. Full expression // 2. Shorthand // // A full expression has the same syntax as syscfg expressions. // // A shorthand expression has one of the following forms: // [!]<req-setting> // (DEPRECATED) [!]<req-setting> if <base-val> // (DEPRECATED) [!]<req-setting> if <expression> // // Examples: // # Can't enable this setting unless LOG_FCB is enabled. // # (shorthand) // pkg.restrictions: // - LOG_FCB // // # Can't enable this setting unless LOG_FCB is disabled. // # (shorthand) // pkg.restrictions: // - !LOG_FCB // // # Can't enable this setting (`MYSETTING`) unless LOG_FCB is enabled and // # CONSOLE_UART is set to "uart0". // # (full expression) // pkg.restrictions: // - '(LOG_FCB && CONSOLE_UART == "uart0") || !MYSETTING package syscfg import ( "encoding/json" "fmt" "mynewt.apache.org/newt/newt/cfgv" "strings" log "github.com/sirupsen/logrus" "mynewt.apache.org/newt/newt/parse" "mynewt.apache.org/newt/util" ) type CfgRestrictionCode int const ( CFG_RESTRICTION_CODE_NOTNULL = iota CFG_RESTRICTION_CODE_EXPR CFG_RESTRICTION_CODE_CHOICE CFG_RESTRICTION_CODE_RANGE ) var cfgRestrictionNameCodeMap = map[string]CfgRestrictionCode{ "$notnull": CFG_RESTRICTION_CODE_NOTNULL, "expr": CFG_RESTRICTION_CODE_EXPR, "choice": CFG_RESTRICTION_CODE_CHOICE, "range": CFG_RESTRICTION_CODE_RANGE, } type CfgRestrictionRange struct { LExpr string RExpr string } type CfgRestriction struct { BaseSetting string Code CfgRestrictionCode // Only used if Code is either CFG_RESTRICTION_CODE_EXPR or CFG_RESTRICTION_CODE_RANGE Expr string // Only used if Code is CFG_RESTRICTION_CODE_RANGE Ranges []CfgRestrictionRange } func (c CfgRestrictionCode) String() string { for s, code := range cfgRestrictionNameCodeMap { if code == c { return s } } return "???" } func parseCfgRestrictionCode(s string) (CfgRestrictionCode, error) { if c, ok := cfgRestrictionNameCodeMap[s]; ok { return c, nil } return 0, util.FmtNewtError("cannot parse cfg restriction code \"%s\"", s) } func (c CfgRestrictionCode) MarshalJSON() ([]byte, error) { return util.MarshalJSONStringer(c) } func (c *CfgRestrictionCode) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return util.ChildNewtError(err) } x, err := parseCfgRestrictionCode(s) if err != nil { return err } *c = x return nil } func readRestriction(baseSetting string, text string) (CfgRestriction, error) { r := CfgRestriction{ BaseSetting: baseSetting, } var ok bool if r.Code, ok = cfgRestrictionNameCodeMap[text]; !ok { // If the restriction text isn't a defined string, parse it as an // expression. r.Code = CFG_RESTRICTION_CODE_EXPR r.Expr = text } return r, nil } func translateShorthandExpr(expr string, baseSetting string) string { tokens, err := parse.Lex(expr) if err != nil { return "" } ifi := -1 var ift *parse.Token for i, t := range tokens { if t.Code == parse.TOKEN_IDENT && t.Text == "if" { ifi = i ift = &t break } } if ifi == 0 || ifi == len(tokens)-1 { return "" } if ifi == -1 { if parse.FindBinaryToken(tokens) == -1 { // [!]<req-setting> return fmt.Sprintf("(%s) || !%s", expr, baseSetting) } else { // Full expression return "" } } if parse.FindBinaryToken(tokens[ifi+1:]) == -1 { // [!]<req-setting> if <base-val> return fmt.Sprintf("(%s) || %s != (%s)", expr[:ift.Offset], baseSetting, expr[tokens[ifi+1].Offset:]) } else { // [!]<req-setting> if <expression> return fmt.Sprintf("(%s) || !(%s)", expr[:ift.Offset], expr[tokens[ifi+1].Offset:]) } } func normalizeExpr(expr string, baseSetting string) string { shexpr := translateShorthandExpr(expr, baseSetting) if shexpr != "" { log.Debugf("Translating shorthand restriction: `%s` ==> `%s`", expr, shexpr) expr = shexpr } return expr } func (r *CfgRestriction) validateRangesBounds(settings *cfgv.Settings) bool { for _, rtoken := range r.Ranges { if len(rtoken.RExpr) > 0 { expr := fmt.Sprintf("(%s) <= (%s)", rtoken.LExpr, rtoken.RExpr) val, err := parse.ParseAndEval(expr, settings) if !val || err != nil { return false } } } return true } func (r *CfgRestriction) createRangeExpr() string { exprOutTokens := []string{} for _, rtoken := range r.Ranges { if len(rtoken.RExpr) > 0 { exprOutTokens = append(exprOutTokens, fmt.Sprintf("(((%s) >= (%s)) && ((%s) <= (%s)))", r.BaseSetting, rtoken.LExpr, r.BaseSetting, rtoken.RExpr)) } else { exprOutTokens = append(exprOutTokens, fmt.Sprintf("((%s) == (%s))", r.BaseSetting, rtoken.LExpr)) } } return strings.Join(exprOutTokens, " || ") } func (cfg *Cfg) settingViolationText(entry CfgEntry, r CfgRestriction) string { prefix := fmt.Sprintf("Setting %s(%s) ", entry.Name, entry.Value) if r.Code == CFG_RESTRICTION_CODE_NOTNULL { return prefix + "must not be null" } else if r.Code == CFG_RESTRICTION_CODE_CHOICE { return prefix + "must be one of defined choices (see definition)" } else if r.Code == CFG_RESTRICTION_CODE_RANGE { return prefix + "must be in range: " + r.Expr } else { return prefix + "requires: " + r.Expr } } func (cfg *Cfg) packageViolationText(pkgName string, r CfgRestriction) string { return fmt.Sprintf("Package %s requires: %s", pkgName, r.Expr) } func (r *CfgRestriction) relevantSettingNames() []string { var names []string if r.BaseSetting != "" { names = append(names, r.BaseSetting) } if r.Code == CFG_RESTRICTION_CODE_EXPR { tokens, _ := parse.Lex(normalizeExpr(r.Expr, r.BaseSetting)) for _, token := range tokens { if token.Code == parse.TOKEN_IDENT { names = append(names, token.Text) } } } else if r.Code == CFG_RESTRICTION_CODE_RANGE { tokens, _ := parse.Lex(r.createRangeExpr()) for _, token := range tokens { if token.Code == parse.TOKEN_IDENT { names = append(names, token.Text) } } } return names } func (cfg *Cfg) restrictionMet( r CfgRestriction, settings *cfgv.Settings) bool { baseEntry := cfg.Settings[r.BaseSetting] switch r.Code { case CFG_RESTRICTION_CODE_NOTNULL: return baseEntry.Value != "" case CFG_RESTRICTION_CODE_CHOICE: if baseEntry.Value == "" { // Assume empty value a valid choice (use $notnull if need otherwise) return true } value := strings.ToLower(baseEntry.Value) for _, choice := range baseEntry.ValidChoices { if strings.ToLower(choice) == value { return true } } return false case CFG_RESTRICTION_CODE_RANGE: expr := r.createRangeExpr() if expr == "" { util.OneTimeWarning( "Ignoring illegal range expression for setting \"%s\": "+ "`%s`\n", r.BaseSetting, r.Expr) return true } val, err := parse.ParseAndEval(expr, settings) if err != nil { util.OneTimeWarning( "Ignoring illegal range expression for setting \"%s\": "+ "`%s`\n", r.BaseSetting, r.Expr) return true } // invalid bounds may or may not result in an error so just emit a warning if !r.validateRangesBounds(settings) { util.OneTimeWarning( "Invalid bounds (lval > rval) for range expression for setting \"%s\": "+ "`%s`\n", r.BaseSetting, r.Expr) } return val case CFG_RESTRICTION_CODE_EXPR: var expr string if r.BaseSetting != "" { expr = normalizeExpr(r.Expr, r.BaseSetting) } else { expr = r.Expr } val, err := parse.ParseAndEval(expr, settings) if err != nil { util.OneTimeWarning( "Ignoring illegal expression for setting \"%s\": "+ "`%s` %s\n", r.BaseSetting, r.Expr, err.Error()) return true } return val default: panic("Invalid restriction code: " + string(r.Code)) } } func createRangeRestriction(baseSetting string, expr string) (CfgRestriction, error) { r := CfgRestriction{ BaseSetting: baseSetting, Code: CFG_RESTRICTION_CODE_RANGE, Expr: expr, Ranges: []CfgRestrictionRange{}, } exprTokens := strings.Split(expr, ",") for _, token := range exprTokens { rtoken := CfgRestrictionRange{} limits := strings.Split(token, "..") if len(limits) == 1 { rtoken.LExpr = limits[0] } else if len(limits) == 2 && len(strings.TrimSpace(limits[1])) > 0 { rtoken.LExpr = limits[0] rtoken.RExpr = limits[1] } else { return r, util.FmtNewtError("invalid token in range expression \"%s\"", token) } r.Ranges = append(r.Ranges, rtoken) } return r, nil }