| // +build linux |
| |
| package main |
| |
| import ( |
| "bytes" |
| "fmt" |
| "net" |
| "os" |
| "path" |
| "path/filepath" |
| "strconv" |
| "time" |
| |
| "github.com/opencontainers/runc/libcontainer" |
| "github.com/opencontainers/runtime-spec/specs-go" |
| "github.com/urfave/cli" |
| ) |
| |
| type notifySocket struct { |
| socket *net.UnixConn |
| host string |
| socketPath string |
| } |
| |
| func newNotifySocket(context *cli.Context, notifySocketHost string, id string) *notifySocket { |
| if notifySocketHost == "" { |
| return nil |
| } |
| |
| root := filepath.Join(context.GlobalString("root"), id) |
| socketPath := filepath.Join(root, "notify", "notify.sock") |
| |
| notifySocket := ¬ifySocket{ |
| socket: nil, |
| host: notifySocketHost, |
| socketPath: socketPath, |
| } |
| |
| return notifySocket |
| } |
| |
| func (s *notifySocket) Close() error { |
| return s.socket.Close() |
| } |
| |
| // If systemd is supporting sd_notify protocol, this function will add support |
| // for sd_notify protocol from within the container. |
| func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) error { |
| pathInContainer := filepath.Join("/run/notify", path.Base(s.socketPath)) |
| mount := specs.Mount{ |
| Destination: path.Dir(pathInContainer), |
| Source: path.Dir(s.socketPath), |
| Options: []string{"bind", "nosuid", "noexec", "nodev", "ro"}, |
| } |
| spec.Mounts = append(spec.Mounts, mount) |
| spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", pathInContainer)) |
| return nil |
| } |
| |
| func (s *notifySocket) bindSocket() error { |
| addr := net.UnixAddr{ |
| Name: s.socketPath, |
| Net: "unixgram", |
| } |
| |
| socket, err := net.ListenUnixgram("unixgram", &addr) |
| if err != nil { |
| return err |
| } |
| |
| err = os.Chmod(s.socketPath, 0777) |
| if err != nil { |
| socket.Close() |
| return err |
| } |
| |
| s.socket = socket |
| return nil |
| } |
| |
| func (s *notifySocket) setupSocketDirectory() error { |
| return os.Mkdir(path.Dir(s.socketPath), 0755) |
| } |
| |
| func notifySocketStart(context *cli.Context, notifySocketHost, id string) (*notifySocket, error) { |
| notifySocket := newNotifySocket(context, notifySocketHost, id) |
| if notifySocket == nil { |
| return nil, nil |
| } |
| |
| if err := notifySocket.bindSocket(); err != nil { |
| return nil, err |
| } |
| return notifySocket, nil |
| } |
| |
| func (n *notifySocket) waitForContainer(container libcontainer.Container) error { |
| s, err := container.State() |
| if err != nil { |
| return err |
| } |
| return n.run(s.InitProcessPid) |
| } |
| |
| func (n *notifySocket) run(pid1 int) error { |
| if n.socket == nil { |
| return nil |
| } |
| notifySocketHostAddr := net.UnixAddr{Name: n.host, Net: "unixgram"} |
| client, err := net.DialUnix("unixgram", nil, ¬ifySocketHostAddr) |
| if err != nil { |
| return err |
| } |
| |
| ticker := time.NewTicker(time.Millisecond * 100) |
| defer ticker.Stop() |
| |
| fileChan := make(chan []byte) |
| go func() { |
| for { |
| buf := make([]byte, 4096) |
| r, err := n.socket.Read(buf) |
| if err != nil { |
| return |
| } |
| got := buf[0:r] |
| // systemd-ready sends a single datagram with the state string as payload, |
| // so we don't need to worry about partial messages. |
| for _, line := range bytes.Split(got, []byte{'\n'}) { |
| if bytes.HasPrefix(got, []byte("READY=")) { |
| fileChan <- line |
| return |
| } |
| } |
| |
| } |
| }() |
| |
| for { |
| select { |
| case <-ticker.C: |
| _, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid1))) |
| if err != nil { |
| return nil |
| } |
| case b := <-fileChan: |
| var out bytes.Buffer |
| _, err = out.Write(b) |
| if err != nil { |
| return err |
| } |
| |
| _, err = out.Write([]byte{'\n'}) |
| if err != nil { |
| return err |
| } |
| |
| _, err = client.Write(out.Bytes()) |
| if err != nil { |
| return err |
| } |
| |
| // now we can inform systemd to use pid1 as the pid to monitor |
| newPid := fmt.Sprintf("MAINPID=%d\n", pid1) |
| client.Write([]byte(newPid)) |
| return nil |
| } |
| } |
| } |