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