main pico / pkg / rsync-receiver / rsyncopts / popt.go
Eric Bower  ·  2026-05-31
  1package rsyncopts
  2
  3import (
  4	"fmt"
  5	"math"
  6	"strconv"
  7	"strings"
  8)
  9
 10type poptOption struct {
 11	longName  string
 12	shortName string
 13	argInfo   int
 14	arg       any // depends on argInfo
 15	val       int // 0 means don't return, just update arg
 16	// descrip    string
 17	// argDescrip string
 18}
 19
 20func (o *poptOption) name() string {
 21	if o.longName == "" {
 22		return "-" + o.shortName
 23	}
 24	return "--" + o.longName
 25}
 26
 27// see popt(3).
 28const (
 29	POPT_ARG_NONE          = iota // int; No argument expected
 30	POPT_ARG_STRING               // char*; No type checking to be performed
 31	POPT_ARG_INT                  // int; An integer argument is expected
 32	POPT_ARG_LONG                 // long; A long integer is expected
 33	POPT_ARG_INCLUDE_TABLE        // nest another option table
 34	POPT_ARG_CALLBACK             // call a function
 35	POPT_ARG_INTL_DOMAIN          // <not documented in popt(3)>
 36	POPT_ARG_VAL                  // int; Integer value taken from val
 37	POPT_ARG_FLOAT                // float; A float argument is expected
 38	POPT_ARG_DOUBLE               // double; A double argument is expected
 39	POPT_ARG_LONGLONG             // long long; A long long integer is expected
 40	POPT_ARG_MAINCALL      = 16 + 11
 41	POPT_ARG_ARGV          = 12
 42	POPT_ARG_SHORT         = 13
 43	POPT_ARG_BITSET        = 16 + 14
 44)
 45
 46const POPT_ARG_MASK = 0x000000FF
 47
 48const (
 49	POPT_ARGFLAG_OR = 0x08000000
 50)
 51
 52const (
 53	POPT_BIT_SET = POPT_ARG_VAL | POPT_ARGFLAG_OR
 54)
 55
 56type PoptError struct {
 57	Errno int32
 58	Err   error
 59}
 60
 61func (pe *PoptError) Unwrap() error { return pe.Err }
 62
 63func (pe *PoptError) Error() string { return pe.Err.Error() }
 64
 65// TODO(later): turn these into sentinel error values.
 66// which stringify like poptStrerror().
 67const (
 68	POPT_ERROR_NOARG        = -10 // missing argument
 69	POPT_ERROR_BADOPT       = -11 // unknown option
 70	POPT_ERROR_UNWANTEDARG  = -12 // option does not take an argument
 71	POPT_ERROR_OPTSTOODEEP  = -13 // aliases nested too deeply
 72	POPT_ERROR_BADQUOTE     = -15 // error in parameter quoting
 73	POPT_ERROR_ERRNO        = -16 // errno set, use strerror(errno)
 74	POPT_ERROR_BADNUMBER    = -17 // invalid numeric value
 75	POPT_ERROR_OVERFLOW     = -18 // number too large or too small
 76	POPT_ERROR_BADOPERATION = -19 // mutually exclusive logical operations requested
 77	POPT_ERROR_NULLARG      = -20 // opt->arg should not be NULL
 78	POPT_ERROR_MALLOC       = -21 // memory allocation failed
 79	POPT_ERROR_BADCONFIG    = -22 // config file failed sanity test
 80)
 81
 82type Context struct {
 83	// state
 84	table       []poptOption
 85	args        []string
 86	nextCharArg string
 87	nextArg     string
 88
 89	// output
 90	Options       *Options
 91	RemainingArgs []string
 92}
 93
 94func (pc *Context) findOption(longName, shortName string) *poptOption {
 95	for idx, opt := range pc.table {
 96		if longName != "" && opt.longName == longName {
 97			return &pc.table[idx]
 98		}
 99		if shortName != "" && opt.shortName == shortName {
100			return &pc.table[idx]
101		}
102	}
103	return nil
104}
105
106func (pc *Context) poptSaveInt(opt *poptOption, val int) bool {
107	intPtr := opt.arg.(*int)
108	if intPtr == nil {
109		return false
110	}
111	if opt.argInfo&POPT_ARGFLAG_OR != 0 {
112		*intPtr |= val
113	} else {
114		*intPtr = val
115	}
116	return true
117}
118
119func (pc *Context) poptSaveArg(opt *poptOption, nextArg string) int32 {
120	argType := opt.argInfo & POPT_ARG_MASK
121	switch argType {
122	case POPT_ARG_INT:
123		i, err := strconv.ParseInt(nextArg, 0, 64)
124		if err != nil {
125			return POPT_ERROR_BADNUMBER
126		}
127		if i < math.MinInt32 || i > math.MaxInt32 {
128			return POPT_ERROR_OVERFLOW
129		}
130		pc.poptSaveInt(opt, int(i))
131		return 0
132
133	case POPT_ARG_STRING:
134		stringPtr := opt.arg.(*string)
135		if stringPtr == nil {
136			return 0
137		}
138		*stringPtr = nextArg
139		return 0
140	}
141
142	return POPT_ERROR_BADOPERATION
143}
144
145func (pc *Context) poptGetNextOpt() (int32, error) {
146	var opt *poptOption
147	for {
148		var longArg string
149		if pc.nextCharArg == "" && len(pc.args) == 0 {
150			return -1, nil // done
151		}
152		if pc.nextCharArg == "" {
153			// process next long option
154			origOptString := pc.args[0]
155			pc.args = pc.args[1:]
156			if origOptString == "" {
157				return -1, &PoptError{
158					Errno: POPT_ERROR_BADOPT,
159					Err:   fmt.Errorf("unknown option: origOptString empty"),
160				}
161			}
162			if origOptString[0] != '-' || origOptString == "-" {
163				pc.RemainingArgs = append(pc.RemainingArgs, origOptString)
164				continue
165			}
166			before, after, found := strings.Cut(origOptString, "=")
167			if found {
168				longArg = after
169			}
170			// remove the one dash we ensured is present
171			before = strings.TrimPrefix(before, "-")
172			// a second dash is permitted
173			before = strings.TrimPrefix(before, "-")
174			opt = pc.findOption(before, "")
175			if opt == nil {
176				// try and parse it as a short option
177				pc.nextCharArg = origOptString[1:]
178				longArg = ""
179			}
180		}
181		if pc.nextCharArg != "" {
182			// process next short option
183			opt = pc.findOption("", pc.nextCharArg[:1])
184			if opt == nil {
185				return -1, &PoptError{
186					Errno: POPT_ERROR_BADOPT,
187					Err:   fmt.Errorf("option %q not found", pc.nextCharArg[:1]),
188				}
189			}
190			pc.nextCharArg = pc.nextCharArg[1:]
191		}
192		if opt == nil {
193			// neither long nor short? how can we end up here?
194			return -1, &PoptError{
195				Errno: POPT_ERROR_BADOPT,
196				Err:   fmt.Errorf("neither long nor short option found"),
197			}
198		}
199		argType := opt.argInfo & POPT_ARG_MASK
200		if argType == POPT_ARG_NONE || argType == POPT_ARG_VAL {
201			if longArg != "" || strings.HasPrefix(pc.nextCharArg, "=") {
202				return -1, &PoptError{
203					Errno: POPT_ERROR_UNWANTEDARG,
204					Err:   fmt.Errorf("option %s does not take an argument", opt.name()),
205				}
206			}
207			if opt.arg != nil {
208				val := 1
209				if argType == POPT_ARG_VAL {
210					val = opt.val
211				}
212				if !pc.poptSaveInt(opt, val) {
213					return -1, &PoptError{
214						Errno: POPT_ERROR_BADOPERATION,
215						Err:   fmt.Errorf("poptSaveInt"),
216					}
217				}
218			}
219		} else {
220			nextArg := longArg
221			if longArg != "" {
222			} else if pc.nextCharArg != "" {
223				nextArg = strings.TrimPrefix(pc.nextCharArg, "=")
224				pc.nextCharArg = ""
225			} else {
226				if len(pc.args) == 0 {
227					return -1, &PoptError{
228						Errno: POPT_ERROR_NOARG,
229						Err:   fmt.Errorf("missing argument for option %s", opt.name()),
230					}
231				}
232				nextArg = pc.args[0]
233				pc.args = pc.args[1:]
234			}
235			pc.nextArg = nextArg
236			if opt.arg != nil {
237				if errno := pc.poptSaveArg(opt, nextArg); errno != 0 {
238					return -1, &PoptError{
239						Errno: errno,
240						Err:   fmt.Errorf("poptSaveArg"),
241					}
242				}
243			}
244		}
245		if opt.val != 0 && argType != POPT_ARG_VAL {
246			return int32(opt.val), nil
247		}
248	}
249}
250
251func (pc *Context) poptGetOptArg() string {
252	ret := pc.nextArg
253	pc.nextArg = ""
254	return ret
255}