github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/tool/flags.go (about) 1 // Copyright 2020 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 tool 5 6 import ( 7 "bytes" 8 "encoding/hex" 9 "errors" 10 "flag" 11 "fmt" 12 "strings" 13 14 "github.com/google/syzkaller/pkg/log" 15 ) 16 17 type Flag struct { 18 Name string 19 Value string 20 } 21 22 // OptionalFlags produces command line flag value that encapsulates the given flags as optional. 23 // This is intended for programmatic use only when we invoke older versions of binaries with new unsupported flags. 24 // Use tool.Init to support optional flags in the binary. 25 // The format keeps flags reasonably readable ("-optional=foo=bar:baz=123"), not subject to accidental splitting 26 // into multiple arguments due to spaces and supports bool/non-bool flags. 27 func OptionalFlags(flags []Flag) string { 28 return fmt.Sprintf("-%v=%v", optionalFlag, serializeFlags(flags)) 29 } 30 31 func ParseFlags(set *flag.FlagSet, args []string) error { 32 flagOptional := set.String(optionalFlag, "", "optional flags for programmatic use only") 33 if err := set.Parse(args); err != nil { 34 return err 35 } 36 flags, err := deserializeFlags(*flagOptional) 37 if err != nil { 38 return err 39 } 40 for _, f := range flags { 41 ff := set.Lookup(f.Name) 42 if ff == nil { 43 log.Logf(0, "ignoring optional flag %q=%q", f.Name, f.Value) 44 continue 45 } 46 if err := ff.Value.Set(f.Value); err != nil { 47 return err 48 } 49 } 50 return nil 51 } 52 53 const optionalFlag = "optional" 54 55 func serializeFlags(flags []Flag) string { 56 if len(flags) == 0 { 57 return "" 58 } 59 buf := new(bytes.Buffer) 60 for _, f := range flags { 61 fmt.Fprintf(buf, ":%v=%v", flagEscape(f.Name), flagEscape(f.Value)) 62 } 63 return buf.String()[1:] 64 } 65 66 func deserializeFlags(value string) ([]Flag, error) { 67 if value == "" { 68 return nil, nil 69 } 70 var flags []Flag 71 for _, arg := range strings.Split(value, ":") { 72 eq := strings.IndexByte(arg, '=') 73 if eq == -1 { 74 return nil, fmt.Errorf("failed to parse flags %q: no eq", value) 75 } 76 name, err := flagUnescape(arg[:eq]) 77 if err != nil { 78 return nil, fmt.Errorf("failed to parse flags %q: %w", value, err) 79 } 80 value, err := flagUnescape(arg[eq+1:]) 81 if err != nil { 82 return nil, fmt.Errorf("failed to parse flags %q: %w", value, err) 83 } 84 flags = append(flags, Flag{name, value}) 85 } 86 return flags, nil 87 } 88 89 func flagEscape(s string) string { 90 buf := new(bytes.Buffer) 91 for i := 0; i < len(s); i++ { 92 ch := s[i] 93 if ch <= 0x20 || ch >= 0x7f || ch == ':' || ch == '=' || ch == '\\' { 94 buf.Write([]byte{'\\', 'x'}) 95 buf.WriteString(hex.EncodeToString([]byte{ch})) 96 continue 97 } 98 buf.WriteByte(ch) 99 } 100 return buf.String() 101 } 102 103 func flagUnescape(s string) (string, error) { 104 buf := new(bytes.Buffer) 105 for i := 0; i < len(s); i++ { 106 ch := s[i] 107 if ch <= 0x20 || ch >= 0x7f || ch == ':' || ch == '=' { 108 return "", fmt.Errorf("unescaped char %v", ch) 109 } 110 if ch == '\\' { 111 if i+4 > len(s) || s[i+1] != 'x' { 112 return "", fmt.Errorf("truncated escape sequence") 113 } 114 res, err := hex.DecodeString(s[i+2 : i+4]) 115 if err != nil { 116 return "", err 117 } 118 buf.WriteByte(res[0]) 119 i += 3 120 continue 121 } 122 buf.WriteByte(ch) 123 } 124 return buf.String(), nil 125 } 126 127 // CfgsFlag allows passing a list of configuration files to the same flag and 128 // provides parsing utilities. 129 type CfgsFlag []string 130 131 // String correctly converts the flag values into a string which is required to 132 // parse them afterwards. 133 func (cfgs *CfgsFlag) String() string { 134 return fmt.Sprint(*cfgs) 135 } 136 137 // Set is used by flag.Parse to correctly parse the command line arguments. 138 func (cfgs *CfgsFlag) Set(value string) error { 139 if len(*cfgs) > 0 { 140 return errors.New("configs flag were already set") 141 } 142 for _, cfg := range strings.Split(value, ",") { 143 cfg = strings.TrimSpace(cfg) 144 *cfgs = append(*cfgs, cfg) 145 } 146 return nil 147 }