1f5593
From ecf53c23545092019602578583031c28fde4d2a1 Mon Sep 17 00:00:00 2001
1f5593
From: Giuseppe Scrivano <gscrivan@redhat.com>
1f5593
Date: Fri, 25 May 2018 18:04:06 +0200
1f5593
Subject: [PATCH] sd-notify: do not hang when NOTIFY_SOCKET is used with create
1f5593
1f5593
if NOTIFY_SOCKET is used, do not block the main runc process waiting
1f5593
for events on the notify socket.  Change the logic to create a new
1f5593
process that monitors exclusively the notify socket until an event is
1f5593
received.
1f5593
1f5593
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
1f5593
---
1f5593
 init.go          |  12 +++++++
1f5593
 notify_socket.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++---------
1f5593
 signals.go       |   5 +--
1f5593
 3 files changed, 99 insertions(+), 19 deletions(-)
1f5593
1f5593
diff --git a/init.go b/init.go
1f5593
index c8f453192..6a3d9e91c 100644
1f5593
--- a/init.go
1f5593
+++ b/init.go
1f5593
@@ -20,6 +20,18 @@ var initCommand = cli.Command{
1f5593
 	Name:  "init",
1f5593
 	Usage: `initialize the namespaces and launch the process (do not call it outside of runc)`,
1f5593
 	Action: func(context *cli.Context) error {
1f5593
+		// If NOTIFY_SOCKET is used create a new process that stays around
1f5593
+		// so to not block "runc start".  It will automatically exits when the
1f5593
+		// container notifies that it is ready, or when the container is deleted
1f5593
+		if os.Getenv("_NOTIFY_SOCKET_FD") != "" {
1f5593
+			fd := os.Getenv("_NOTIFY_SOCKET_FD")
1f5593
+			pid := os.Getenv("_NOTIFY_SOCKET_PID")
1f5593
+			hostNotifySocket := os.Getenv("_NOTIFY_SOCKET_HOST")
1f5593
+			notifySocketPath := os.Getenv("_NOTIFY_SOCKET_PATH")
1f5593
+			notifySocketInit(fd, pid, hostNotifySocket, notifySocketPath)
1f5593
+			os.Exit(0)
1f5593
+		}
1f5593
+
1f5593
 		factory, _ := libcontainer.New("")
1f5593
 		if err := factory.StartInitialization(); err != nil {
1f5593
 			// as the error is sent back to the parent there is no need to log
1f5593
diff --git a/notify_socket.go b/notify_socket.go
1f5593
index cd6c0a989..e04e9d660 100644
1f5593
--- a/notify_socket.go
1f5593
+++ b/notify_socket.go
1f5593
@@ -6,10 +6,13 @@ import (
1f5593
 	"bytes"
1f5593
 	"fmt"
1f5593
 	"net"
1f5593
+	"os"
1f5593
+	"os/exec"
1f5593
 	"path/filepath"
1f5593
+	"strconv"
1f5593
+	"time"
1f5593
 
1f5593
 	"github.com/opencontainers/runtime-spec/specs-go"
1f5593
-
1f5593
 	"github.com/sirupsen/logrus"
1f5593
 	"github.com/urfave/cli"
1f5593
 )
1f5593
@@ -64,24 +67,94 @@ func (s *notifySocket) setupSocket() error {
1f5593
 	return nil
1f5593
 }
1f5593
 
1f5593
+func (notifySocket *notifySocket) notifyNewPid(pid int) {
1f5593
+	notifySocketHostAddr := net.UnixAddr{Name: notifySocket.host, Net: "unixgram"}
1f5593
+	client, err := net.DialUnix("unixgram", nil, &notifySocketHostAddr)
1f5593
+	if err != nil {
1f5593
+		return
1f5593
+	}
1f5593
+	newPid := fmt.Sprintf("MAINPID=%d\n", pid)
1f5593
+	client.Write([]byte(newPid))
1f5593
+}
1f5593
+
1f5593
 // pid1 must be set only with -d, as it is used to set the new process as the main process
1f5593
 // for the service in systemd
1f5593
 func (notifySocket *notifySocket) run(pid1 int) {
1f5593
-	buf := make([]byte, 512)
1f5593
-	notifySocketHostAddr := net.UnixAddr{Name: notifySocket.host, Net: "unixgram"}
1f5593
-	client, err := net.DialUnix("unixgram", nil, &notifySocketHostAddr)
1f5593
+	file, err := notifySocket.socket.File()
1f5593
 	if err != nil {
1f5593
 		logrus.Error(err)
1f5593
 		return
1f5593
 	}
1f5593
-	for {
1f5593
-		r, err := notifySocket.socket.Read(buf)
1f5593
-		if err != nil {
1f5593
-			break
1f5593
+	defer file.Close()
1f5593
+	defer notifySocket.socket.Close()
1f5593
+
1f5593
+	cmd := exec.Command("/proc/self/exe", "init")
1f5593
+	cmd.ExtraFiles = []*os.File{file}
1f5593
+	cmd.Env = append(cmd.Env, "_NOTIFY_SOCKET_FD=3",
1f5593
+		fmt.Sprintf("_NOTIFY_SOCKET_PID=%d", pid1),
1f5593
+		fmt.Sprintf("_NOTIFY_SOCKET_HOST=%s", notifySocket.host),
1f5593
+		fmt.Sprintf("_NOTIFY_SOCKET_PATH=%s", notifySocket.socketPath))
1f5593
+
1f5593
+	if err := cmd.Start(); err != nil {
1f5593
+		logrus.Fatal(err)
1f5593
+	}
1f5593
+	notifySocket.notifyNewPid(cmd.Process.Pid)
1f5593
+	cmd.Process.Release()
1f5593
+}
1f5593
+
1f5593
+func notifySocketInit(envFd string, envPid string, notifySocketHost string, notifySocketPath string) {
1f5593
+	intFd, err := strconv.Atoi(envFd)
1f5593
+	if err != nil {
1f5593
+		return
1f5593
+	}
1f5593
+	pid1, err := strconv.Atoi(envPid)
1f5593
+	if err != nil {
1f5593
+		return
1f5593
+	}
1f5593
+
1f5593
+	file := os.NewFile(uintptr(intFd), "unixgram")
1f5593
+	defer file.Close()
1f5593
+
1f5593
+	fileChan := make(chan []byte)
1f5593
+	exitChan := make(chan bool)
1f5593
+
1f5593
+	go func() {
1f5593
+		for {
1f5593
+			buf := make([]byte, 512)
1f5593
+			r, err := file.Read(buf)
1f5593
+			if err != nil {
1f5593
+				return
1f5593
+			}
1f5593
+			fileChan <- buf[0:r]
1f5593
 		}
1f5593
-		var out bytes.Buffer
1f5593
-		for _, line := range bytes.Split(buf[0:r], []byte{'\n'}) {
1f5593
-			if bytes.HasPrefix(line, []byte("READY=")) {
1f5593
+	}()
1f5593
+	go func() {
1f5593
+		for {
1f5593
+			if _, err := os.Stat(notifySocketPath); os.IsNotExist(err) {
1f5593
+				exitChan <- true
1f5593
+				return
1f5593
+			}
1f5593
+			time.Sleep(time.Second)
1f5593
+		}
1f5593
+	}()
1f5593
+
1f5593
+	notifySocketHostAddr := net.UnixAddr{Name: notifySocketHost, Net: "unixgram"}
1f5593
+	client, err := net.DialUnix("unixgram", nil, &notifySocketHostAddr)
1f5593
+	if err != nil {
1f5593
+		return
1f5593
+	}
1f5593
+
1f5593
+	for {
1f5593
+		select {
1f5593
+		case <-exitChan:
1f5593
+			return
1f5593
+		case b := <-fileChan:
1f5593
+			for _, line := range bytes.Split(b, []byte{'\n'}) {
1f5593
+				if !bytes.HasPrefix(line, []byte("READY=")) {
1f5593
+					continue
1f5593
+				}
1f5593
+
1f5593
+				var out bytes.Buffer
1f5593
 				_, err = out.Write(line)
1f5593
 				if err != nil {
1f5593
 					return
1f5593
@@ -98,10 +171,8 @@ func (notifySocket *notifySocket) run(pid1 int) {
1f5593
 				}
1f5593
 
1f5593
 				// now we can inform systemd to use pid1 as the pid to monitor
1f5593
-				if pid1 > 0 {
1f5593
-					newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
1f5593
-					client.Write([]byte(newPid))
1f5593
-				}
1f5593
+				newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
1f5593
+				client.Write([]byte(newPid))
1f5593
 				return
1f5593
 			}
1f5593
 		}
1f5593
diff --git a/signals.go b/signals.go
1f5593
index 1811de837..d0988cb39 100644
1f5593
--- a/signals.go
1f5593
+++ b/signals.go
1f5593
@@ -70,7 +70,7 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach
1f5593
 			h.notifySocket.run(pid1)
1f5593
 			return 0, nil
1f5593
 		} else {
1f5593
-			go h.notifySocket.run(0)
1f5593
+			h.notifySocket.run(os.Getpid())
1f5593
 		}
1f5593
 	}
1f5593
 
1f5593
@@ -98,9 +98,6 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach
1f5593
 					// status because we must ensure that any of the go specific process
1f5593
 					// fun such as flushing pipes are complete before we return.
1f5593
 					process.Wait()
1f5593
-					if h.notifySocket != nil {
1f5593
-						h.notifySocket.Close()
1f5593
-					}
1f5593
 					return e.status, nil
1f5593
 				}
1f5593
 			}