...

Source file src/github.com/opencontainers/runc/notify_socket.go

Documentation: github.com/opencontainers/runc

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"net"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/opencontainers/runc/libcontainer"
    13  	"github.com/opencontainers/runtime-spec/specs-go"
    14  	"github.com/urfave/cli"
    15  )
    16  
    17  type notifySocket struct {
    18  	socket     *net.UnixConn
    19  	host       string
    20  	socketPath string
    21  }
    22  
    23  func newNotifySocket(context *cli.Context, notifySocketHost string, id string) *notifySocket {
    24  	if notifySocketHost == "" {
    25  		return nil
    26  	}
    27  
    28  	root := filepath.Join(context.GlobalString("root"), id)
    29  	socketPath := filepath.Join(root, "notify", "notify.sock")
    30  
    31  	notifySocket := &notifySocket{
    32  		socket:     nil,
    33  		host:       notifySocketHost,
    34  		socketPath: socketPath,
    35  	}
    36  
    37  	return notifySocket
    38  }
    39  
    40  func (s *notifySocket) Close() error {
    41  	return s.socket.Close()
    42  }
    43  
    44  // If systemd is supporting sd_notify protocol, this function will add support
    45  // for sd_notify protocol from within the container.
    46  func (s *notifySocket) setupSpec(spec *specs.Spec) {
    47  	pathInContainer := filepath.Join("/run/notify", path.Base(s.socketPath))
    48  	mount := specs.Mount{
    49  		Destination: path.Dir(pathInContainer),
    50  		Source:      path.Dir(s.socketPath),
    51  		Options:     []string{"bind", "nosuid", "noexec", "nodev", "ro"},
    52  	}
    53  	spec.Mounts = append(spec.Mounts, mount)
    54  	spec.Process.Env = append(spec.Process.Env, "NOTIFY_SOCKET="+pathInContainer)
    55  }
    56  
    57  func (s *notifySocket) bindSocket() error {
    58  	addr := net.UnixAddr{
    59  		Name: s.socketPath,
    60  		Net:  "unixgram",
    61  	}
    62  
    63  	socket, err := net.ListenUnixgram("unixgram", &addr)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	err = os.Chmod(s.socketPath, 0o777)
    69  	if err != nil {
    70  		socket.Close()
    71  		return err
    72  	}
    73  
    74  	s.socket = socket
    75  	return nil
    76  }
    77  
    78  func (s *notifySocket) setupSocketDirectory() error {
    79  	return os.Mkdir(path.Dir(s.socketPath), 0o755)
    80  }
    81  
    82  func notifySocketStart(context *cli.Context, notifySocketHost, id string) (*notifySocket, error) {
    83  	notifySocket := newNotifySocket(context, notifySocketHost, id)
    84  	if notifySocket == nil {
    85  		return nil, nil
    86  	}
    87  
    88  	if err := notifySocket.bindSocket(); err != nil {
    89  		return nil, err
    90  	}
    91  	return notifySocket, nil
    92  }
    93  
    94  func (n *notifySocket) waitForContainer(container libcontainer.Container) error {
    95  	s, err := container.State()
    96  	if err != nil {
    97  		return err
    98  	}
    99  	return n.run(s.InitProcessPid)
   100  }
   101  
   102  func (n *notifySocket) run(pid1 int) error {
   103  	if n.socket == nil {
   104  		return nil
   105  	}
   106  	notifySocketHostAddr := net.UnixAddr{Name: n.host, Net: "unixgram"}
   107  	client, err := net.DialUnix("unixgram", nil, &notifySocketHostAddr)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	ticker := time.NewTicker(time.Millisecond * 100)
   113  	defer ticker.Stop()
   114  
   115  	fileChan := make(chan []byte)
   116  	go func() {
   117  		for {
   118  			buf := make([]byte, 4096)
   119  			r, err := n.socket.Read(buf)
   120  			if err != nil {
   121  				return
   122  			}
   123  			got := buf[0:r]
   124  			// systemd-ready sends a single datagram with the state string as payload,
   125  			// so we don't need to worry about partial messages.
   126  			for _, line := range bytes.Split(got, []byte{'\n'}) {
   127  				if bytes.HasPrefix(got, []byte("READY=")) {
   128  					fileChan <- line
   129  					return
   130  				}
   131  			}
   132  
   133  		}
   134  	}()
   135  
   136  	for {
   137  		select {
   138  		case <-ticker.C:
   139  			_, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid1)))
   140  			if err != nil {
   141  				return nil
   142  			}
   143  		case b := <-fileChan:
   144  			var out bytes.Buffer
   145  			_, err = out.Write(b)
   146  			if err != nil {
   147  				return err
   148  			}
   149  
   150  			_, err = out.Write([]byte{'\n'})
   151  			if err != nil {
   152  				return err
   153  			}
   154  
   155  			_, err = client.Write(out.Bytes())
   156  			if err != nil {
   157  				return err
   158  			}
   159  
   160  			// now we can inform systemd to use pid1 as the pid to monitor
   161  			newPid := "MAINPID=" + strconv.Itoa(pid1)
   162  			_, err := client.Write([]byte(newPid + "\n"))
   163  			if err != nil {
   164  				return err
   165  			}
   166  			return nil
   167  		}
   168  	}
   169  }
   170  

View as plain text