Skip to content

Commit 899b074

Browse files
author
Mrunal Patel
authored
Merge pull request #1308 from giuseppe/fix-systemd-notify
fix systemd-notify when using a different PID namespace
2 parents 707dd48 + d5026f0 commit 899b074

File tree

5 files changed

+160
-25
lines changed

5 files changed

+160
-25
lines changed

notify_socket.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// +build linux
2+
3+
package main
4+
5+
import (
6+
"bytes"
7+
"fmt"
8+
"net"
9+
"path/filepath"
10+
11+
"github.com/Sirupsen/logrus"
12+
"github.com/opencontainers/runtime-spec/specs-go"
13+
"github.com/urfave/cli"
14+
)
15+
16+
type notifySocket struct {
17+
socket *net.UnixConn
18+
host string
19+
socketPath string
20+
}
21+
22+
func newNotifySocket(context *cli.Context, notifySocketHost string, id string) *notifySocket {
23+
if notifySocketHost == "" {
24+
return nil
25+
}
26+
27+
root := filepath.Join(context.GlobalString("root"), id)
28+
path := filepath.Join(root, "notify.sock")
29+
30+
notifySocket := &notifySocket{
31+
socket: nil,
32+
host: notifySocketHost,
33+
socketPath: path,
34+
}
35+
36+
return notifySocket
37+
}
38+
39+
func (ns *notifySocket) Close() error {
40+
return ns.socket.Close()
41+
}
42+
43+
// If systemd is supporting sd_notify protocol, this function will add support
44+
// for sd_notify protocol from within the container.
45+
func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) {
46+
mount := specs.Mount{Destination: s.host, Type: "bind", Source: s.socketPath, Options: []string{"bind"}}
47+
spec.Mounts = append(spec.Mounts, mount)
48+
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", s.host))
49+
}
50+
51+
func (s *notifySocket) setupSocket() error {
52+
addr := net.UnixAddr{
53+
Name: s.socketPath,
54+
Net: "unixgram",
55+
}
56+
57+
socket, err := net.ListenUnixgram("unixgram", &addr)
58+
if err != nil {
59+
return err
60+
}
61+
62+
s.socket = socket
63+
return nil
64+
}
65+
66+
// pid1 must be set only with -d, as it is used to set the new process as the main process
67+
// for the service in systemd
68+
func (notifySocket *notifySocket) run(pid1 int) {
69+
buf := make([]byte, 512)
70+
notifySocketHostAddr := net.UnixAddr{Name: notifySocket.host, Net: "unixgram"}
71+
client, err := net.DialUnix("unixgram", nil, &notifySocketHostAddr)
72+
if err != nil {
73+
logrus.Error(err)
74+
return
75+
}
76+
for {
77+
r, err := notifySocket.socket.Read(buf)
78+
if err != nil {
79+
break
80+
}
81+
var out bytes.Buffer
82+
for _, line := range bytes.Split(buf[0:r], []byte{'\n'}) {
83+
if bytes.HasPrefix(line, []byte("READY=")) {
84+
_, err = out.Write(line)
85+
if err != nil {
86+
return
87+
}
88+
89+
_, err = out.Write([]byte{'\n'})
90+
if err != nil {
91+
return
92+
}
93+
94+
_, err = client.Write(out.Bytes())
95+
if err != nil {
96+
return
97+
}
98+
99+
// now we can inform systemd to use pid1 as the pid to monitor
100+
if pid1 > 0 {
101+
newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
102+
client.Write([]byte(newPid))
103+
}
104+
return
105+
}
106+
}
107+
}
108+
}

restore.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,14 @@ func restoreContainer(context *cli.Context, spec *specs.Spec, config *configs.Co
165165
if err != nil {
166166
return -1, err
167167
}
168-
handler := newSignalHandler(!context.Bool("no-subreaper"))
168+
169+
notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
170+
if notifySocket != nil {
171+
notifySocket.setupSpec(context, spec)
172+
notifySocket.setupSocket()
173+
}
174+
175+
handler := newSignalHandler(!context.Bool("no-subreaper"), notifySocket)
169176
if err := container.Restore(process, options); err != nil {
170177
return -1, err
171178
}
@@ -181,10 +188,7 @@ func restoreContainer(context *cli.Context, spec *specs.Spec, config *configs.Co
181188
return -1, err
182189
}
183190
}
184-
if detach {
185-
return 0, nil
186-
}
187-
return handler.forward(process, tty)
191+
return handler.forward(process, tty, detach)
188192
}
189193

190194
func criuOptions(context *cli.Context) *libcontainer.CriuOpts {

signals.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ const signalBufferSize = 2048
1717

1818
// newSignalHandler returns a signal handler for processing SIGCHLD and SIGWINCH signals
1919
// while still forwarding all other signals to the process.
20-
func newSignalHandler(enableSubreaper bool) *signalHandler {
20+
// If notifySocket is present, use it to read systemd notifications from the container and
21+
// forward them to notifySocketHost.
22+
func newSignalHandler(enableSubreaper bool, notifySocket *notifySocket) *signalHandler {
2123
if enableSubreaper {
2224
// set us as the subreaper before registering the signal handler for the container
2325
if err := system.SetSubreaper(1); err != nil {
@@ -30,7 +32,8 @@ func newSignalHandler(enableSubreaper bool) *signalHandler {
3032
// handle all signals for the process.
3133
signal.Notify(s)
3234
return &signalHandler{
33-
signals: s,
35+
signals: s,
36+
notifySocket: notifySocket,
3437
}
3538
}
3639

@@ -42,18 +45,33 @@ type exit struct {
4245
}
4346

4447
type signalHandler struct {
45-
signals chan os.Signal
48+
signals chan os.Signal
49+
notifySocket *notifySocket
4650
}
4751

4852
// forward handles the main signal event loop forwarding, resizing, or reaping depending
4953
// on the signal received.
50-
func (h *signalHandler) forward(process *libcontainer.Process, tty *tty) (int, error) {
54+
func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach bool) (int, error) {
5155
// make sure we know the pid of our main process so that we can return
5256
// after it dies.
57+
if detach && h.notifySocket == nil {
58+
return 0, nil
59+
}
60+
5361
pid1, err := process.Pid()
5462
if err != nil {
5563
return -1, err
5664
}
65+
66+
if h.notifySocket != nil {
67+
if detach {
68+
h.notifySocket.run(pid1)
69+
return 0, nil
70+
} else {
71+
go h.notifySocket.run(0)
72+
}
73+
}
74+
5775
// perform the initial tty resize.
5876
tty.resize()
5977
for s := range h.signals {
@@ -75,6 +93,9 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty) (int, e
7593
// status because we must ensure that any of the go specific process
7694
// fun such as flushing pipes are complete before we return.
7795
process.Wait()
96+
if h.notifySocket != nil {
97+
h.notifySocket.Close()
98+
}
7899
return e.status, nil
79100
}
80101
}

utils.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,6 @@ func setupSpec(context *cli.Context) (*specs.Spec, error) {
6363
if err != nil {
6464
return nil, err
6565
}
66-
notifySocket := os.Getenv("NOTIFY_SOCKET")
67-
if notifySocket != "" {
68-
setupSdNotify(spec, notifySocket)
69-
}
7066
if os.Geteuid() != 0 {
7167
return nil, fmt.Errorf("runc should be run as root")
7268
}

utils_linux.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,6 @@ func newProcess(p specs.Process) (*libcontainer.Process, error) {
9595
return lp, nil
9696
}
9797

98-
// If systemd is supporting sd_notify protocol, this function will add support
99-
// for sd_notify protocol from within the container.
100-
func setupSdNotify(spec *specs.Spec, notifySocket string) {
101-
spec.Mounts = append(spec.Mounts, specs.Mount{Destination: notifySocket, Type: "bind", Source: notifySocket, Options: []string{"bind"}})
102-
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", notifySocket))
103-
}
104-
10598
func destroy(container libcontainer.Container) {
10699
if err := container.Destroy(); err != nil {
107100
logrus.Error(err)
@@ -185,6 +178,7 @@ type runner struct {
185178
consoleSocket string
186179
container libcontainer.Container
187180
create bool
181+
notifySocket *notifySocket
188182
}
189183

190184
func (r *runner) terminalinfo() *libcontainer.TerminalInfo {
@@ -240,7 +234,7 @@ func (r *runner) run(config *specs.Process) (int, error) {
240234
// Setting up IO is a two stage process. We need to modify process to deal
241235
// with detaching containers, and then we get a tty after the container has
242236
// started.
243-
handler := newSignalHandler(r.enableSubreaper)
237+
handler := newSignalHandler(r.enableSubreaper, r.notifySocket)
244238
tty, err := setupIO(process, rootuid, rootgid, config.Terminal, detach)
245239
if err != nil {
246240
r.destroy()
@@ -303,13 +297,13 @@ func (r *runner) run(config *specs.Process) (int, error) {
303297
return -1, err
304298
}
305299
}
306-
if detach {
307-
return 0, nil
308-
}
309-
status, err := handler.forward(process, tty)
300+
status, err := handler.forward(process, tty, detach)
310301
if err != nil {
311302
r.terminate(process)
312303
}
304+
if detach {
305+
return 0, nil
306+
}
313307
r.destroy()
314308
return status, err
315309
}
@@ -343,10 +337,21 @@ func startContainer(context *cli.Context, spec *specs.Spec, create bool) (int, e
343337
if id == "" {
344338
return -1, errEmptyID
345339
}
340+
341+
notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
342+
if notifySocket != nil {
343+
notifySocket.setupSpec(context, spec)
344+
}
345+
346346
container, err := createContainer(context, id, spec)
347347
if err != nil {
348348
return -1, err
349349
}
350+
351+
if notifySocket != nil {
352+
notifySocket.setupSocket()
353+
}
354+
350355
// Support on-demand socket activation by passing file descriptors into the container init process.
351356
listenFDs := []*os.File{}
352357
if os.Getenv("LISTEN_FDS") != "" {
@@ -357,6 +362,7 @@ func startContainer(context *cli.Context, spec *specs.Spec, create bool) (int, e
357362
shouldDestroy: true,
358363
container: container,
359364
listenFDs: listenFDs,
365+
notifySocket: notifySocket,
360366
consoleSocket: context.String("console-socket"),
361367
detach: context.Bool("detach"),
362368
pidFile: context.String("pid-file"),

0 commit comments

Comments
 (0)