aboutsummaryrefslogtreecommitdiffhomepage
path: root/helper/args.go
blob: 8ef07f4ac6c8e858646af52f8dc258503afaf6f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package helper

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"strings"
	"sync"
)

var (
	ErrContainsNull = errors.New("argument contains null character")
)

// Args is sealed with a slice of arguments for writing to the helper args FD.
// 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.
// argsFD must not be copied after first use.
type argsFD struct {
	seal []byte
	sync.RWMutex
}

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
		}

		// 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
}

func (a *argsFD) WriteTo(w io.Writer) (int64, error) {
	a.RLock()
	defer a.RUnlock()

	if a.seal == nil {
		panic("attempted to activate unsealed args")
	}

	n, err := w.Write(a.seal)
	return int64(n), err
}

func (a *argsFD) String() string {
	if a == nil {
		return "(invalid helper args)"
	}

	if a.seal == nil {
		return "(unsealed helper args)"
	}

	return strings.ReplaceAll(string(a.seal), "\x00", " ")
}

func hasNull(s string) bool {
	for _, b := range s {
		if b == '\x00' {
			return true
		}
	}
	return false
}

// NewArgs returns a new instance of Args
func NewArgs() Args {
	return new(argsFD)
}