Skip to content

Commit 542ae0e

Browse files
committed
daemon: support programs with arguments
Add support for specifying arguments in both launchd and systemd applications. In launchd, these args are specified in a "ProgramArguments" array; in systemd, they are simply appended to the program name (as someone would run a program on the command line). Note that "ProgramArguments" in the 'launchd' plist now always has at least one element: the path to the executable program. This aligns 'launchd' to standard argument conventions [1]; although it does not yet affect 'git-bundle-web-server' (the only usage of the 'launchd' code at this point), it will in future patches when arguments & arg parsing are added. Finally, add & update unit tests covering the use of arguments in both launchd & systemd. [1] https://man7.org/linux/man-pages/man3/execvp.3.html Signed-off-by: Victoria Dye <[email protected]>
1 parent 5246137 commit 542ae0e

File tree

5 files changed

+89
-1
lines changed

5 files changed

+89
-1
lines changed

internal/daemon/daemon.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type DaemonConfig struct {
1111
Label string
1212
Description string
1313
Program string
14+
Arguments []string
1415
}
1516

1617
type DaemonProvider interface {

internal/daemon/launchd.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"path/filepath"
88

99
"github.com/github/git-bundle-server/internal/common"
10+
"github.com/github/git-bundle-server/internal/utils"
1011
)
1112

1213
type xmlItem struct {
@@ -36,6 +37,15 @@ func (p *plist) addKeyValue(key string, value any) {
3637
switch value := value.(type) {
3738
case string:
3839
p.Config.Elements = append(p.Config.Elements, xmlItem{XMLName: xmlName("string"), Value: value})
40+
case []string:
41+
p.Config.Elements = append(p.Config.Elements,
42+
xmlArray{
43+
XMLName: xmlName("array"),
44+
Elements: utils.Map(value, func(e string) interface{} {
45+
return xmlItem{XMLName: xmlName("string"), Value: e}
46+
}),
47+
},
48+
)
3949
default:
4050
panic("Invalid value type in 'addKeyValue'")
4151
}
@@ -61,6 +71,18 @@ func (c *launchdConfig) toPlist() *plist {
6171
p.addKeyValue("StandardOutPath", c.StdOut)
6272
p.addKeyValue("StandardErrorPath", c.StdErr)
6373

74+
// IMPORTANT!!!
75+
// You must explicitly set the first argument to the executable path
76+
// because 'ProgramArguments' maps directly 'argv' in 'execvp'. The
77+
// programs calling this library likely will, by convention, assume the
78+
// first element of 'argv' is the executing program.
79+
// See https://www.unix.com/man-page/osx/5/launchd.plist/ and
80+
// https://man7.org/linux/man-pages/man3/execvp.3.html for more details.
81+
args := make([]string, len(c.Arguments)+1)
82+
args[0] = c.Program
83+
copy(args[1:], c.Arguments[:])
84+
p.addKeyValue("ProgramArguments", args)
85+
6486
return p
6587
}
6688

internal/daemon/launchd_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ var launchdCreatePlistTests = []struct {
129129
"<key>StandardErrorPath</key>",
130130
"<string>/dev/null</string>",
131131

132+
"<key>ProgramArguments</key>",
133+
"<array>",
134+
fmt.Sprintf("<string>%s</string>", basicDaemonConfig.Program),
135+
"</array>",
136+
132137
"</dict>",
133138
"</plist>",
134139
},
@@ -161,6 +166,47 @@ var launchdCreatePlistTests = []struct {
161166
"<key>StandardErrorPath</key>",
162167
"<string>/dev/null</string>",
163168

169+
"<key>ProgramArguments</key>",
170+
"<array>",
171+
"<string>/path/to/the/program with a space</string>",
172+
"</array>",
173+
174+
"</dict>",
175+
"</plist>",
176+
},
177+
},
178+
{
179+
title: "Created plist captures args",
180+
config: &daemon.DaemonConfig{
181+
Label: "test-with-args",
182+
Program: "/path/to/the/program",
183+
Arguments: []string{"--test", "another-arg"},
184+
},
185+
expectedPlistLines: []string{
186+
`<?xml version="1.0" encoding="UTF-8"?>`,
187+
`<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">`,
188+
`<plist version="1.0">`,
189+
"<dict>",
190+
191+
"<key>Label</key>",
192+
"<string>test-with-args</string>",
193+
194+
"<key>Program</key>",
195+
"<string>/path/to/the/program</string>",
196+
197+
"<key>StandardOutPath</key>",
198+
"<string>/dev/null</string>",
199+
200+
"<key>StandardErrorPath</key>",
201+
"<string>/dev/null</string>",
202+
203+
"<key>ProgramArguments</key>",
204+
"<array>",
205+
"<string>/path/to/the/program</string>",
206+
"<string>--test</string>",
207+
"<string>another-arg</string>",
208+
"</array>",
209+
164210
"</dict>",
165211
"</plist>",
166212
},

internal/daemon/systemd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Description={{.Description}}
1515
1616
[Service]
1717
Type=simple
18-
ExecStart={{sq_escape .Program}}
18+
ExecStart={{sq_escape .Program}}{{range .Arguments}} {{sq_escape .}}{{end}}
1919
`
2020

2121
type systemd struct {

internal/daemon/systemd_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,25 @@ var systemdCreateServiceUnitTests = []struct {
101101
"ExecStart='/path/to/the/program with a space'",
102102
},
103103
},
104+
{
105+
title: "Service unit ExecStart captures args, quoted and escaped",
106+
config: &daemon.DaemonConfig{
107+
Label: "test-escape",
108+
Description: "Another program description",
109+
Program: "/path/to/the/program with a space",
110+
Arguments: []string{
111+
"--my-option",
112+
"an arg with double quotes \", single quotes ', and spaces!",
113+
},
114+
},
115+
expectedServiceUnitLines: []string{
116+
"[Unit]",
117+
"Description=Another program description",
118+
"[Service]",
119+
"Type=simple",
120+
"ExecStart='/path/to/the/program with a space' '--my-option' 'an arg with double quotes \", single quotes \\', and spaces!'",
121+
},
122+
},
104123
}
105124

106125
func TestSystemd_Create(t *testing.T) {

0 commit comments

Comments
 (0)