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  }