helper/args: simplify argument parsing and eliminate excess memory copies
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
parent
a8b4b3634b
commit
8492239cba
|
@ -54,8 +54,6 @@ func (p *Proxy) Seal(session, system *Config) error {
|
||||||
return errors.New("no configuration to seal")
|
return errors.New("no configuration to seal")
|
||||||
}
|
}
|
||||||
|
|
||||||
seal := helper.NewArgs()
|
|
||||||
|
|
||||||
var args []string
|
var args []string
|
||||||
if session != nil {
|
if session != nil {
|
||||||
args = append(args, session.Args(p.session)...)
|
args = append(args, session.Args(p.session)...)
|
||||||
|
@ -63,11 +61,12 @@ func (p *Proxy) Seal(session, system *Config) error {
|
||||||
if system != nil {
|
if system != nil {
|
||||||
args = append(args, system.Args(p.system)...)
|
args = append(args, system.Args(p.system)...)
|
||||||
}
|
}
|
||||||
if err := seal.Seal(args); err != nil {
|
if seal, err := helper.NewCheckedArgs(args); err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else {
|
||||||
|
p.seal = seal
|
||||||
}
|
}
|
||||||
|
|
||||||
p.seal = seal
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,100 +1,59 @@
|
||||||
package helper
|
package helper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrContainsNull = errors.New("argument contains null character")
|
ErrContainsNull = errors.New("argument contains null character")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Args is sealed with a slice of arguments for writing to the helper args FD.
|
type argsFD []string
|
||||||
// The sealing args is checked to not contain null characters.
|
|
||||||
// Attempting to seal an instance twice will cause a panic.
|
|
||||||
type Args interface {
|
|
||||||
Seal(args []string) error
|
|
||||||
io.WriterTo
|
|
||||||
fmt.Stringer
|
|
||||||
}
|
|
||||||
|
|
||||||
// argsFD implements Args for helpers expecting null terminated arguments to a file descriptor.
|
// checks whether any element contains the null character
|
||||||
// argsFD must not be copied after first use.
|
// must be called before args use and args must not be modified after call
|
||||||
type argsFD struct {
|
func (a argsFD) check() error {
|
||||||
seal []byte
|
for _, arg := range a {
|
||||||
sync.RWMutex
|
for _, b := range arg {
|
||||||
}
|
if b == '\x00' {
|
||||||
|
|
||||||
func (a *argsFD) Seal(args []string) error {
|
|
||||||
a.Lock()
|
|
||||||
defer a.Unlock()
|
|
||||||
|
|
||||||
if a.seal != nil {
|
|
||||||
panic("args sealed twice")
|
|
||||||
}
|
|
||||||
|
|
||||||
seal := bytes.Buffer{}
|
|
||||||
|
|
||||||
n := 0
|
|
||||||
for _, arg := range args {
|
|
||||||
// reject argument strings containing null
|
|
||||||
if hasNull(arg) {
|
|
||||||
return ErrContainsNull
|
return ErrContainsNull
|
||||||
}
|
}
|
||||||
|
|
||||||
// accumulate buffer size
|
|
||||||
n += len(arg) + 1
|
|
||||||
}
|
}
|
||||||
seal.Grow(n)
|
|
||||||
|
|
||||||
// write null terminated arguments
|
|
||||||
for _, arg := range args {
|
|
||||||
seal.WriteString(arg)
|
|
||||||
seal.WriteByte('\x00')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a.seal = seal.Bytes()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *argsFD) WriteTo(w io.Writer) (int64, error) {
|
func (a argsFD) WriteTo(w io.Writer) (int64, error) {
|
||||||
a.RLock()
|
// assuming already checked
|
||||||
defer a.RUnlock()
|
|
||||||
|
|
||||||
if a.seal == nil {
|
nt := 0
|
||||||
panic("attempted to activate unsealed args")
|
// write null terminated arguments
|
||||||
|
for _, arg := range a {
|
||||||
|
n, err := w.Write([]byte(arg + "\x00"))
|
||||||
|
nt += n
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return int64(nt), err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := w.Write(a.seal)
|
return int64(nt), nil
|
||||||
return int64(n), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *argsFD) String() string {
|
func (a argsFD) String() string {
|
||||||
if a == nil {
|
if a == nil {
|
||||||
return "(invalid helper args)"
|
return "(invalid helper args)"
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.seal == nil {
|
return strings.Join(a, " ")
|
||||||
return "(unsealed helper args)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.ReplaceAll(string(a.seal), "\x00", " ")
|
// NewCheckedArgs returns a checked argument writer for args.
|
||||||
}
|
// Callers must not retain any references to args.
|
||||||
|
func NewCheckedArgs(args []string) (io.WriterTo, error) {
|
||||||
func hasNull(s string) bool {
|
a := argsFD(args)
|
||||||
for _, b := range s {
|
return a, a.check()
|
||||||
if b == '\x00' {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArgs returns a new instance of Args
|
|
||||||
func NewArgs() Args {
|
|
||||||
return new(argsFD)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ var (
|
||||||
// Helper wraps *exec.Cmd and manages status and args fd.
|
// Helper wraps *exec.Cmd and manages status and args fd.
|
||||||
// Args is always 3 and status if set is always 4.
|
// Args is always 3 and status if set is always 4.
|
||||||
type Helper struct {
|
type Helper struct {
|
||||||
lock sync.RWMutex
|
|
||||||
args io.WriterTo
|
args io.WriterTo
|
||||||
|
|
||||||
statP [2]*os.File
|
statP [2]*os.File
|
||||||
|
@ -32,6 +31,7 @@ type Helper struct {
|
||||||
// standard error. If non-nil, entry i becomes file descriptor 5+i.
|
// standard error. If non-nil, entry i becomes file descriptor 5+i.
|
||||||
ExtraFiles []*os.File
|
ExtraFiles []*os.File
|
||||||
|
|
||||||
|
lock sync.RWMutex
|
||||||
*exec.Cmd
|
*exec.Cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ func (h *Helper) Start() error {
|
||||||
|
|
||||||
func New(wt io.WriterTo, name string, arg ...string) *Helper {
|
func New(wt io.WriterTo, name string, arg ...string) *Helper {
|
||||||
if wt == nil {
|
if wt == nil {
|
||||||
panic("attempted to create helper with nil argument writer")
|
panic("attempted to create helper with invalid argument writer")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Helper{args: wt, Cmd: exec.Command(name, arg...)}
|
return &Helper{args: wt, Cmd: exec.Command(name, arg...)}
|
||||||
|
|
Loading…
Reference in New Issue