6c2295
From 3d99c51e1b38a440804a55c9f314f62cc50b8902 Mon Sep 17 00:00:00 2001
9fdf04
From: Giuseppe Scrivano <gscrivan@redhat.com>
9fdf04
Date: Fri, 25 May 2018 18:04:06 +0200
9fdf04
Subject: [PATCH] sd-notify: do not hang when NOTIFY_SOCKET is used with create
9fdf04
9fdf04
if NOTIFY_SOCKET is used, do not block the main runc process waiting
f5ad69
for events on the notify socket.  Bind mount the parent directory of
f5ad69
the notify socket, so that "start" can create the socket and it is
f5ad69
still accessible from the container.
9fdf04
9fdf04
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
9fdf04
---
6c2295
 notify_socket.go | 112 ++++++++++++++++++++++++++++++++++-------------
f5ad69
 signals.go       |   4 +-
f5ad69
 start.go         |  13 +++++-
f5ad69
 utils_linux.go   |  12 ++++-
6c2295
 4 files changed, 105 insertions(+), 36 deletions(-)
9fdf04
9fdf04
diff --git a/notify_socket.go b/notify_socket.go
6c2295
index e7453c62..d961453a 100644
9fdf04
--- a/notify_socket.go
9fdf04
+++ b/notify_socket.go
6c2295
@@ -7,11 +7,13 @@ import (
9fdf04
 	"fmt"
9fdf04
 	"net"
6c2295
 	"os"
f5ad69
+	"path"
9fdf04
 	"path/filepath"
9fdf04
+	"strconv"
9fdf04
+	"time"
9fdf04
 
f5ad69
+	"github.com/opencontainers/runc/libcontainer"
9fdf04
 	"github.com/opencontainers/runtime-spec/specs-go"
9fdf04
-
f5ad69
-	"github.com/sirupsen/logrus"
9fdf04
 	"github.com/urfave/cli"
9fdf04
 )
f5ad69
 
6c2295
@@ -27,12 +29,12 @@ func newNotifySocket(context *cli.Context, notifySocketHost string, id string) *
f5ad69
 	}
f5ad69
 
f5ad69
 	root := filepath.Join(context.GlobalString("root"), id)
f5ad69
-	path := filepath.Join(root, "notify.sock")
f5ad69
+	socketPath := filepath.Join(root, "notify", "notify.sock")
f5ad69
 
f5ad69
 	notifySocket := &notifySocket{
f5ad69
 		socket:     nil,
f5ad69
 		host:       notifySocketHost,
f5ad69
-		socketPath: path,
f5ad69
+		socketPath: socketPath,
f5ad69
 	}
f5ad69
 
f5ad69
 	return notifySocket
6c2295
@@ -44,13 +46,19 @@ func (s *notifySocket) Close() error {
f5ad69
 
f5ad69
 // If systemd is supporting sd_notify protocol, this function will add support
f5ad69
 // for sd_notify protocol from within the container.
f5ad69
-func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) {
f5ad69
-	mount := specs.Mount{Destination: s.host, Source: s.socketPath, Options: []string{"bind"}}
f5ad69
+func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) error {
f5ad69
+	pathInContainer := filepath.Join("/run/notify", path.Base(s.socketPath))
f5ad69
+	mount := specs.Mount{
f5ad69
+		Destination: path.Dir(pathInContainer),
f5ad69
+		Source:      path.Dir(s.socketPath),
f5ad69
+		Options:     []string{"bind", "nosuid", "noexec", "nodev", "ro"},
f5ad69
+	}
f5ad69
 	spec.Mounts = append(spec.Mounts, mount)
f5ad69
-	spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", s.host))
f5ad69
+	spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", pathInContainer))
f5ad69
+	return nil
f5ad69
 }
f5ad69
 
f5ad69
-func (s *notifySocket) setupSocket() error {
f5ad69
+func (s *notifySocket) bindSocket() error {
f5ad69
 	addr := net.UnixAddr{
f5ad69
 		Name: s.socketPath,
f5ad69
 		Net:  "unixgram",
6c2295
@@ -71,45 +79,89 @@ func (s *notifySocket) setupSocket() error {
9fdf04
 	return nil
9fdf04
 }
9fdf04
 
f5ad69
-// pid1 must be set only with -d, as it is used to set the new process as the main process
f5ad69
-// for the service in systemd
f5ad69
-func (s *notifySocket) run(pid1 int) {
9fdf04
-	buf := make([]byte, 512)
f5ad69
-	notifySocketHostAddr := net.UnixAddr{Name: s.host, Net: "unixgram"}
f5ad69
+func (s *notifySocket) setupSocketDirectory() error {
f5ad69
+	return os.Mkdir(path.Dir(s.socketPath), 0755)
f5ad69
+}
9fdf04
+
f5ad69
+func notifySocketStart(context *cli.Context, notifySocketHost, id string) (*notifySocket, error) {
f5ad69
+	notifySocket := newNotifySocket(context, notifySocketHost, id)
f5ad69
+	if notifySocket == nil {
f5ad69
+		return nil, nil
f5ad69
+	}
9fdf04
+
f5ad69
+	if err := notifySocket.bindSocket(); err != nil {
f5ad69
+		return nil, err
9fdf04
+	}
f5ad69
+	return notifySocket, nil
9fdf04
+}
9fdf04
+
f5ad69
+func (n *notifySocket) waitForContainer(container libcontainer.Container) error {
f5ad69
+	s, err := container.State()
9fdf04
+	if err != nil {
f5ad69
+		return err
9fdf04
+	}
f5ad69
+	return n.run(s.InitProcessPid)
f5ad69
+}
f5ad69
+
f5ad69
+func (n *notifySocket) run(pid1 int) error {
f5ad69
+	if n.socket == nil {
f5ad69
+		return nil
9fdf04
+	}
f5ad69
+	notifySocketHostAddr := net.UnixAddr{Name: n.host, Net: "unixgram"}
f5ad69
 	client, err := net.DialUnix("unixgram", nil, &notifySocketHostAddr)
f5ad69
 	if err != nil {
f5ad69
-		logrus.Error(err)
f5ad69
-		return
f5ad69
+		return err
f5ad69
 	}
f5ad69
-	for {
f5ad69
-		r, err := s.socket.Read(buf)
f5ad69
-		if err != nil {
f5ad69
-			break
9fdf04
+
f5ad69
+	ticker := time.NewTicker(time.Millisecond * 100)
f5ad69
+	defer ticker.Stop()
9fdf04
+
9fdf04
+	fileChan := make(chan []byte)
9fdf04
+	go func() {
9fdf04
+		for {
9fdf04
+			buf := make([]byte, 512)
f5ad69
+			r, err := n.socket.Read(buf)
9fdf04
+			if err != nil {
9fdf04
+				return
9fdf04
+			}
f5ad69
+			got := buf[0:r]
f5ad69
+			if !bytes.HasPrefix(got, []byte("READY=")) {
f5ad69
+				continue
f5ad69
+			}
f5ad69
+			fileChan <- got
f5ad69
+			return
9fdf04
 		}
9fdf04
-		var out bytes.Buffer
9fdf04
-		for _, line := range bytes.Split(buf[0:r], []byte{'\n'}) {
9fdf04
-			if bytes.HasPrefix(line, []byte("READY=")) {
9fdf04
+	}()
9fdf04
+
9fdf04
+	for {
9fdf04
+		select {
f5ad69
+		case <-ticker.C:
f5ad69
+			_, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid1)))
f5ad69
+			if err != nil {
f5ad69
+				return nil
f5ad69
+			}
9fdf04
+		case b := <-fileChan:
9fdf04
+			for _, line := range bytes.Split(b, []byte{'\n'}) {
9fdf04
+				var out bytes.Buffer
9fdf04
 				_, err = out.Write(line)
9fdf04
 				if err != nil {
f5ad69
-					return
f5ad69
+					return err
f5ad69
 				}
f5ad69
 
f5ad69
 				_, err = out.Write([]byte{'\n'})
f5ad69
 				if err != nil {
f5ad69
-					return
f5ad69
+					return err
f5ad69
 				}
f5ad69
 
f5ad69
 				_, err = client.Write(out.Bytes())
f5ad69
 				if err != nil {
f5ad69
-					return
f5ad69
+					return err
9fdf04
 				}
9fdf04
 
9fdf04
 				// now we can inform systemd to use pid1 as the pid to monitor
9fdf04
-				if pid1 > 0 {
9fdf04
-					newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
9fdf04
-					client.Write([]byte(newPid))
9fdf04
-				}
f5ad69
-				return
9fdf04
+				newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
9fdf04
+				client.Write([]byte(newPid))
f5ad69
+				return nil
9fdf04
 			}
9fdf04
 		}
f5ad69
 	}
9fdf04
diff --git a/signals.go b/signals.go
6c2295
index b67f65a0..dd25e094 100644
9fdf04
--- a/signals.go
9fdf04
+++ b/signals.go
f5ad69
@@ -70,6 +70,7 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach
9fdf04
 			h.notifySocket.run(pid1)
9fdf04
 			return 0, nil
9fdf04
 		}
f5ad69
+		h.notifySocket.run(os.Getpid())
f5ad69
 		go h.notifySocket.run(0)
9fdf04
 	}
9fdf04
 
f5ad69
@@ -97,9 +98,6 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach
9fdf04
 					// status because we must ensure that any of the go specific process
9fdf04
 					// fun such as flushing pipes are complete before we return.
9fdf04
 					process.Wait()
9fdf04
-					if h.notifySocket != nil {
9fdf04
-						h.notifySocket.Close()
9fdf04
-					}
9fdf04
 					return e.status, nil
9fdf04
 				}
9fdf04
 			}
f5ad69
diff --git a/start.go b/start.go
6c2295
index 2bb698b2..3a1769a4 100644
f5ad69
--- a/start.go
f5ad69
+++ b/start.go
f5ad69
@@ -3,6 +3,7 @@ package main
f5ad69
 import (
f5ad69
 	"errors"
f5ad69
 	"fmt"
f5ad69
+	"os"
f5ad69
 
f5ad69
 	"github.com/opencontainers/runc/libcontainer"
f5ad69
 	"github.com/urfave/cli"
f5ad69
@@ -31,7 +32,17 @@ your host.`,
f5ad69
 		}
f5ad69
 		switch status {
f5ad69
 		case libcontainer.Created:
f5ad69
-			return container.Exec()
f5ad69
+			notifySocket, err := notifySocketStart(context, os.Getenv("NOTIFY_SOCKET"), container.ID())
f5ad69
+			if err != nil {
f5ad69
+				return err
f5ad69
+			}
f5ad69
+			if err := container.Exec(); err != nil {
f5ad69
+				return err
f5ad69
+			}
f5ad69
+			if notifySocket != nil {
f5ad69
+				return notifySocket.waitForContainer(container)
f5ad69
+			}
f5ad69
+			return nil
f5ad69
 		case libcontainer.Stopped:
f5ad69
 			return errors.New("cannot start a container that has stopped")
f5ad69
 		case libcontainer.Running:
f5ad69
diff --git a/utils_linux.go b/utils_linux.go
6c2295
index 984e6b0f..46c26246 100644
f5ad69
--- a/utils_linux.go
f5ad69
+++ b/utils_linux.go
6c2295
@@ -408,7 +408,9 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp
f5ad69
 
f5ad69
 	notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
f5ad69
 	if notifySocket != nil {
f5ad69
-		notifySocket.setupSpec(context, spec)
f5ad69
+		if err := notifySocket.setupSpec(context, spec); err != nil {
f5ad69
+			return -1, err
f5ad69
+		}
f5ad69
 	}
f5ad69
 
f5ad69
 	container, err := createContainer(context, id, spec)
6c2295
@@ -417,10 +419,16 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp
f5ad69
 	}
f5ad69
 
f5ad69
 	if notifySocket != nil {
f5ad69
-		err := notifySocket.setupSocket()
f5ad69
+		err := notifySocket.setupSocketDirectory()
f5ad69
 		if err != nil {
f5ad69
 			return -1, err
f5ad69
 		}
f5ad69
+		if action == CT_ACT_RUN {
f5ad69
+			err := notifySocket.bindSocket()
f5ad69
+			if err != nil {
f5ad69
+				return -1, err
f5ad69
+			}
f5ad69
+		}
f5ad69
 	}
f5ad69
 
f5ad69
 	// Support on-demand socket activation by passing file descriptors into the container init process.
6c2295
-- 
6c2295
2.21.0
6c2295