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  }