[lxc-devel] [lxd/master] Make lxd-agent static

stgraber on Github lxc-bot at linuxcontainers.org
Tue May 19 21:15:59 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200519/1615586c/attachment-0001.bin>
-------------- next part --------------
From 7f4ad6aef20938900567faf1e504a9348d1852b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 19 May 2020 17:12:28 -0400
Subject: [PATCH 1/2] shared: Reimplement GetPollRevents without cgo
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/netutils/network_linux.go     | 202 -----------------------
 shared/netutils/network_linux_cgo.go | 210 ++++++++++++++++++++++++
 shared/util_linux.go                 | 208 ++++++++++++++++++++++++
 shared/util_linux_cgo.go             | 231 ---------------------------
 4 files changed, 418 insertions(+), 433 deletions(-)
 create mode 100644 shared/netutils/network_linux_cgo.go

diff --git a/shared/netutils/network_linux.go b/shared/netutils/network_linux.go
index 2f31ef70e6..c006e7f388 100644
--- a/shared/netutils/network_linux.go
+++ b/shared/netutils/network_linux.go
@@ -1,177 +1,16 @@
 // +build linux
-// +build cgo
 
 package netutils
 
 import (
-	"fmt"
 	"io"
-	"net"
-	"os"
-	"strings"
-	"unsafe"
 
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
-	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
 )
 
-/*
-#include "unixfd.h"
-#include "netns_getifaddrs.c"
-*/
-import "C"
-
-// NetnsGetifaddrs returns a map of InstanceStateNetwork for a particular process.
-func NetnsGetifaddrs(initPID int32) (map[string]api.InstanceStateNetwork, error) {
-	var netnsidAware C.bool
-	var ifaddrs *C.struct_netns_ifaddrs
-	var netnsID C.__s32
-
-	if initPID > 0 {
-		f, err := os.Open(fmt.Sprintf("/proc/%d/ns/net", initPID))
-		if err != nil {
-			return nil, err
-		}
-		defer f.Close()
-
-		netnsID = C.netns_get_nsid(C.__s32(f.Fd()))
-		if netnsID < 0 {
-			return nil, fmt.Errorf("Failed to retrieve network namespace id")
-		}
-	} else {
-		netnsID = -1
-	}
-
-	ret := C.netns_getifaddrs(&ifaddrs, netnsID, &netnsidAware)
-	if ret < 0 {
-		return nil, fmt.Errorf("Failed to retrieve network interfaces and addresses")
-	}
-	defer C.netns_freeifaddrs(ifaddrs)
-
-	if netnsID >= 0 && !netnsidAware {
-		return nil, fmt.Errorf("Netlink requests are not fully network namespace id aware")
-	}
-
-	// We're using the interface name as key here but we should really
-	// switch to the ifindex at some point to handle ip aliasing correctly.
-	networks := map[string]api.InstanceStateNetwork{}
-
-	for addr := ifaddrs; addr != nil; addr = addr.ifa_next {
-		var address [C.INET6_ADDRSTRLEN]C.char
-		addNetwork, networkExists := networks[C.GoString(addr.ifa_name)]
-		if !networkExists {
-			addNetwork = api.InstanceStateNetwork{
-				Addresses: []api.InstanceStateNetworkAddress{},
-				Counters:  api.InstanceStateNetworkCounters{},
-			}
-		}
-
-		// Interface flags
-		netState := "down"
-		netType := "unknown"
-
-		if (addr.ifa_flags & C.IFF_BROADCAST) > 0 {
-			netType = "broadcast"
-		}
-
-		if (addr.ifa_flags & C.IFF_LOOPBACK) > 0 {
-			netType = "loopback"
-		}
-
-		if (addr.ifa_flags & C.IFF_POINTOPOINT) > 0 {
-			netType = "point-to-point"
-		}
-
-		if (addr.ifa_flags & C.IFF_UP) > 0 {
-			netState = "up"
-		}
-		addNetwork.State = netState
-		addNetwork.Type = netType
-		addNetwork.Mtu = int(addr.ifa_mtu)
-
-		if initPID != 0 && int(addr.ifa_ifindex_peer) > 0 {
-			hostInterface, err := net.InterfaceByIndex(int(addr.ifa_ifindex_peer))
-			if err == nil {
-				addNetwork.HostName = hostInterface.Name
-			}
-		}
-
-		// Addresses
-		if addr.ifa_addr != nil && (addr.ifa_addr.sa_family == C.AF_INET || addr.ifa_addr.sa_family == C.AF_INET6) {
-			family := "inet"
-			if addr.ifa_addr.sa_family == C.AF_INET6 {
-				family = "inet6"
-			}
-
-			addrPtr := C.get_addr_ptr(addr.ifa_addr)
-			if addrPtr == nil {
-				return nil, fmt.Errorf("Failed to retrieve valid address pointer")
-			}
-
-			addressStr := C.inet_ntop(C.int(addr.ifa_addr.sa_family), addrPtr, &address[0], C.INET6_ADDRSTRLEN)
-			if addressStr == nil {
-				return nil, fmt.Errorf("Failed to retrieve address string")
-			}
-
-			if addNetwork.Addresses == nil {
-				addNetwork.Addresses = []api.InstanceStateNetworkAddress{}
-			}
-
-			goAddrString := C.GoString(addressStr)
-			scope := "global"
-			if strings.HasPrefix(goAddrString, "127") {
-				scope = "local"
-			}
-
-			if goAddrString == "::1" {
-				scope = "local"
-			}
-
-			if strings.HasPrefix(goAddrString, "169.254") {
-				scope = "link"
-			}
-
-			if strings.HasPrefix(goAddrString, "fe80:") {
-				scope = "link"
-			}
-
-			address := api.InstanceStateNetworkAddress{}
-			address.Family = family
-			address.Address = goAddrString
-			address.Netmask = fmt.Sprintf("%d", int(addr.ifa_prefixlen))
-			address.Scope = scope
-
-			addNetwork.Addresses = append(addNetwork.Addresses, address)
-		} else if addr.ifa_addr != nil && addr.ifa_addr.sa_family == C.AF_PACKET {
-			if (addr.ifa_flags & C.IFF_LOOPBACK) == 0 {
-				var buf [1024]C.char
-
-				hwaddr := C.get_packet_address(addr.ifa_addr, &buf[0], 1024)
-				if hwaddr == nil {
-					return nil, fmt.Errorf("Failed to retrieve hardware address")
-				}
-
-				addNetwork.Hwaddr = C.GoString(hwaddr)
-			}
-		}
-
-		if addr.ifa_stats_type == C.IFLA_STATS64 {
-			addNetwork.Counters.BytesReceived = int64(addr.ifa_stats64.rx_bytes)
-			addNetwork.Counters.BytesSent = int64(addr.ifa_stats64.tx_bytes)
-			addNetwork.Counters.PacketsReceived = int64(addr.ifa_stats64.rx_packets)
-			addNetwork.Counters.PacketsSent = int64(addr.ifa_stats64.tx_packets)
-		}
-		ifName := C.GoString(addr.ifa_name)
-
-		networks[ifName] = addNetwork
-	}
-
-	return networks, nil
-}
-
 // WebsocketExecMirror mirrors a websocket connection with a set of Writer/Reader.
 func WebsocketExecMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser, exited chan struct{}, fd int) (chan bool, chan bool) {
 	readDone := make(chan bool, 1)
@@ -209,44 +48,3 @@ func WebsocketExecMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser
 
 	return readDone, writeDone
 }
-
-// AbstractUnixSendFd sends a Unix file descriptor over a Unix socket.
-func AbstractUnixSendFd(sockFD int, sendFD int) error {
-	fd := C.int(sendFD)
-	skFd := C.int(sockFD)
-	ret := C.lxc_abstract_unix_send_fds(skFd, &fd, C.int(1), nil, C.size_t(0))
-	if ret < 0 {
-		return fmt.Errorf("Failed to send file descriptor via abstract unix socket")
-	}
-
-	return nil
-}
-
-// AbstractUnixReceiveFd receives a Unix file descriptor from a Unix socket.
-func AbstractUnixReceiveFd(sockFD int) (*os.File, error) {
-	fd := C.int(-1)
-	skFd := C.int(sockFD)
-	ret := C.lxc_abstract_unix_recv_fds(skFd, &fd, C.int(1), nil, C.size_t(0))
-	if ret < 0 {
-		return nil, fmt.Errorf("Failed to receive file descriptor via abstract unix socket")
-	}
-
-	file := os.NewFile(uintptr(fd), "")
-	return file, nil
-}
-
-// AbstractUnixReceiveFdData is a low level function to receive a file descriptor over a unix socket.
-func AbstractUnixReceiveFdData(sockFD int, numFds int, iov unsafe.Pointer, iovLen int32) (uint64, []C.int, error) {
-	cfd := make([]C.int, numFds)
-	skFd := C.int(sockFD)
-	ret, errno := C.lxc_abstract_unix_recv_fds_iov(skFd, (*C.int)(&cfd[0]), C.int(numFds), (*C.struct_iovec)(iov), C.size_t(iovLen))
-	if ret < 0 {
-		return 0, []C.int{-C.EBADF}, fmt.Errorf("Failed to receive file descriptor via abstract unix socket: errno=%d", errno)
-	}
-
-	if ret == 0 {
-		return 0, []C.int{-C.EBADF}, io.EOF
-	}
-
-	return uint64(ret), cfd, nil
-}
diff --git a/shared/netutils/network_linux_cgo.go b/shared/netutils/network_linux_cgo.go
new file mode 100644
index 0000000000..e161f37de3
--- /dev/null
+++ b/shared/netutils/network_linux_cgo.go
@@ -0,0 +1,210 @@
+// +build linux
+// +build cgo
+
+package netutils
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"os"
+	"strings"
+	"unsafe"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+/*
+#include "unixfd.h"
+#include "netns_getifaddrs.c"
+*/
+import "C"
+
+// NetnsGetifaddrs returns a map of InstanceStateNetwork for a particular process.
+func NetnsGetifaddrs(initPID int32) (map[string]api.InstanceStateNetwork, error) {
+	var netnsidAware C.bool
+	var ifaddrs *C.struct_netns_ifaddrs
+	var netnsID C.__s32
+
+	if initPID > 0 {
+		f, err := os.Open(fmt.Sprintf("/proc/%d/ns/net", initPID))
+		if err != nil {
+			return nil, err
+		}
+		defer f.Close()
+
+		netnsID = C.netns_get_nsid(C.__s32(f.Fd()))
+		if netnsID < 0 {
+			return nil, fmt.Errorf("Failed to retrieve network namespace id")
+		}
+	} else {
+		netnsID = -1
+	}
+
+	ret := C.netns_getifaddrs(&ifaddrs, netnsID, &netnsidAware)
+	if ret < 0 {
+		return nil, fmt.Errorf("Failed to retrieve network interfaces and addresses")
+	}
+	defer C.netns_freeifaddrs(ifaddrs)
+
+	if netnsID >= 0 && !netnsidAware {
+		return nil, fmt.Errorf("Netlink requests are not fully network namespace id aware")
+	}
+
+	// We're using the interface name as key here but we should really
+	// switch to the ifindex at some point to handle ip aliasing correctly.
+	networks := map[string]api.InstanceStateNetwork{}
+
+	for addr := ifaddrs; addr != nil; addr = addr.ifa_next {
+		var address [C.INET6_ADDRSTRLEN]C.char
+		addNetwork, networkExists := networks[C.GoString(addr.ifa_name)]
+		if !networkExists {
+			addNetwork = api.InstanceStateNetwork{
+				Addresses: []api.InstanceStateNetworkAddress{},
+				Counters:  api.InstanceStateNetworkCounters{},
+			}
+		}
+
+		// Interface flags
+		netState := "down"
+		netType := "unknown"
+
+		if (addr.ifa_flags & C.IFF_BROADCAST) > 0 {
+			netType = "broadcast"
+		}
+
+		if (addr.ifa_flags & C.IFF_LOOPBACK) > 0 {
+			netType = "loopback"
+		}
+
+		if (addr.ifa_flags & C.IFF_POINTOPOINT) > 0 {
+			netType = "point-to-point"
+		}
+
+		if (addr.ifa_flags & C.IFF_UP) > 0 {
+			netState = "up"
+		}
+		addNetwork.State = netState
+		addNetwork.Type = netType
+		addNetwork.Mtu = int(addr.ifa_mtu)
+
+		if initPID != 0 && int(addr.ifa_ifindex_peer) > 0 {
+			hostInterface, err := net.InterfaceByIndex(int(addr.ifa_ifindex_peer))
+			if err == nil {
+				addNetwork.HostName = hostInterface.Name
+			}
+		}
+
+		// Addresses
+		if addr.ifa_addr != nil && (addr.ifa_addr.sa_family == C.AF_INET || addr.ifa_addr.sa_family == C.AF_INET6) {
+			family := "inet"
+			if addr.ifa_addr.sa_family == C.AF_INET6 {
+				family = "inet6"
+			}
+
+			addrPtr := C.get_addr_ptr(addr.ifa_addr)
+			if addrPtr == nil {
+				return nil, fmt.Errorf("Failed to retrieve valid address pointer")
+			}
+
+			addressStr := C.inet_ntop(C.int(addr.ifa_addr.sa_family), addrPtr, &address[0], C.INET6_ADDRSTRLEN)
+			if addressStr == nil {
+				return nil, fmt.Errorf("Failed to retrieve address string")
+			}
+
+			if addNetwork.Addresses == nil {
+				addNetwork.Addresses = []api.InstanceStateNetworkAddress{}
+			}
+
+			goAddrString := C.GoString(addressStr)
+			scope := "global"
+			if strings.HasPrefix(goAddrString, "127") {
+				scope = "local"
+			}
+
+			if goAddrString == "::1" {
+				scope = "local"
+			}
+
+			if strings.HasPrefix(goAddrString, "169.254") {
+				scope = "link"
+			}
+
+			if strings.HasPrefix(goAddrString, "fe80:") {
+				scope = "link"
+			}
+
+			address := api.InstanceStateNetworkAddress{}
+			address.Family = family
+			address.Address = goAddrString
+			address.Netmask = fmt.Sprintf("%d", int(addr.ifa_prefixlen))
+			address.Scope = scope
+
+			addNetwork.Addresses = append(addNetwork.Addresses, address)
+		} else if addr.ifa_addr != nil && addr.ifa_addr.sa_family == C.AF_PACKET {
+			if (addr.ifa_flags & C.IFF_LOOPBACK) == 0 {
+				var buf [1024]C.char
+
+				hwaddr := C.get_packet_address(addr.ifa_addr, &buf[0], 1024)
+				if hwaddr == nil {
+					return nil, fmt.Errorf("Failed to retrieve hardware address")
+				}
+
+				addNetwork.Hwaddr = C.GoString(hwaddr)
+			}
+		}
+
+		if addr.ifa_stats_type == C.IFLA_STATS64 {
+			addNetwork.Counters.BytesReceived = int64(addr.ifa_stats64.rx_bytes)
+			addNetwork.Counters.BytesSent = int64(addr.ifa_stats64.tx_bytes)
+			addNetwork.Counters.PacketsReceived = int64(addr.ifa_stats64.rx_packets)
+			addNetwork.Counters.PacketsSent = int64(addr.ifa_stats64.tx_packets)
+		}
+		ifName := C.GoString(addr.ifa_name)
+
+		networks[ifName] = addNetwork
+	}
+
+	return networks, nil
+}
+
+// AbstractUnixSendFd sends a Unix file descriptor over a Unix socket.
+func AbstractUnixSendFd(sockFD int, sendFD int) error {
+	fd := C.int(sendFD)
+	skFd := C.int(sockFD)
+	ret := C.lxc_abstract_unix_send_fds(skFd, &fd, C.int(1), nil, C.size_t(0))
+	if ret < 0 {
+		return fmt.Errorf("Failed to send file descriptor via abstract unix socket")
+	}
+
+	return nil
+}
+
+// AbstractUnixReceiveFd receives a Unix file descriptor from a Unix socket.
+func AbstractUnixReceiveFd(sockFD int) (*os.File, error) {
+	fd := C.int(-1)
+	skFd := C.int(sockFD)
+	ret := C.lxc_abstract_unix_recv_fds(skFd, &fd, C.int(1), nil, C.size_t(0))
+	if ret < 0 {
+		return nil, fmt.Errorf("Failed to receive file descriptor via abstract unix socket")
+	}
+
+	file := os.NewFile(uintptr(fd), "")
+	return file, nil
+}
+
+// AbstractUnixReceiveFdData is a low level function to receive a file descriptor over a unix socket.
+func AbstractUnixReceiveFdData(sockFD int, numFds int, iov unsafe.Pointer, iovLen int32) (uint64, []C.int, error) {
+	cfd := make([]C.int, numFds)
+	skFd := C.int(sockFD)
+	ret, errno := C.lxc_abstract_unix_recv_fds_iov(skFd, (*C.int)(&cfd[0]), C.int(numFds), (*C.struct_iovec)(iov), C.size_t(iovLen))
+	if ret < 0 {
+		return 0, []C.int{-C.EBADF}, fmt.Errorf("Failed to receive file descriptor via abstract unix socket: errno=%d", errno)
+	}
+
+	if ret == 0 {
+		return 0, []C.int{-C.EBADF}, io.EOF
+	}
+
+	return uint64(ret), cfd, nil
+}
diff --git a/shared/util_linux.go b/shared/util_linux.go
index 47d35a0e30..df7a527c3e 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -5,15 +5,19 @@ package shared
 import (
 	"bufio"
 	"fmt"
+	"io"
 	"os"
 	"path/filepath"
 	"reflect"
 	"strings"
+	"sync"
+	"sync/atomic"
 	"syscall"
 	"unsafe"
 
 	"golang.org/x/sys/unix"
 
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/units"
 )
 
@@ -493,3 +497,207 @@ func OpenPty(uid, gid int64) (*os.File, *os.File, error) {
 	revert = false
 	return master, slave, nil
 }
+
+// Extensively commented directly in the code. Please leave the comments!
+// Looking at this in a couple of months noone will know why and how this works
+// anymore.
+func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan struct{}, fd int) <-chan []byte {
+	if bufferSize <= (128 * 1024) {
+		bufferSize = (128 * 1024)
+	}
+
+	ch := make(chan ([]byte))
+
+	// Takes care that the closeChannel() function is exactly executed once.
+	// This allows us to avoid using a mutex.
+	var once sync.Once
+	closeChannel := func() {
+		close(ch)
+	}
+
+	// [1]: This function has just one job: Dealing with the case where we
+	// are running an interactive shell session where we put a process in
+	// the background that does hold stdin/stdout open, but does not
+	// generate any output at all. This case cannot be dealt with in the
+	// following function call. Here's why: Assume the above case, now the
+	// attached child (the shell in this example) exits. This will not
+	// generate any poll() event: We won't get POLLHUP because the
+	// background process is holding stdin/stdout open and noone is writing
+	// to it. So we effectively block on GetPollRevents() in the function
+	// below. Hence, we use another go routine here who's only job is to
+	// handle that case: When we detect that the child has exited we check
+	// whether a POLLIN or POLLHUP event has been generated. If not, we know
+	// that there's nothing buffered on stdout and exit.
+	var attachedChildIsDead int32 = 0
+	go func() {
+		<-exited
+
+		atomic.StoreInt32(&attachedChildIsDead, 1)
+
+		ret, revents, err := GetPollRevents(fd, 0, (unix.POLLIN | unix.POLLPRI | unix.POLLERR | unix.POLLHUP | unix.POLLRDHUP | unix.POLLNVAL))
+		if ret < 0 {
+			logger.Errorf("Failed to poll(POLLIN | POLLPRI | POLLHUP | POLLRDHUP) on file descriptor: %s.", err)
+			// Something went wrong so let's exited otherwise we
+			// end up in an endless loop.
+			once.Do(closeChannel)
+		} else if ret > 0 {
+			if (revents & unix.POLLERR) > 0 {
+				logger.Warnf("Detected poll(POLLERR) event.")
+				// Read end has likely been closed so again,
+				// avoid an endless loop.
+				once.Do(closeChannel)
+			} else if (revents & unix.POLLNVAL) > 0 {
+				logger.Warnf("Detected poll(POLLNVAL) event.")
+				// Well, someone closed the fd havent they? So
+				// let's go home.
+				once.Do(closeChannel)
+			}
+		} else if ret == 0 {
+			logger.Debugf("No data in stdout: exiting.")
+			once.Do(closeChannel)
+		}
+	}()
+
+	go func() {
+		readSize := (128 * 1024)
+		offset := 0
+		buf := make([]byte, bufferSize)
+		avoidAtomicLoad := false
+
+		defer once.Do(closeChannel)
+		for {
+			nr := 0
+			var err error
+
+			ret, revents, err := GetPollRevents(fd, -1, (unix.POLLIN | unix.POLLPRI | unix.POLLERR | unix.POLLHUP | unix.POLLRDHUP | unix.POLLNVAL))
+			if ret < 0 {
+				// This condition is only reached in cases where we are massively f*cked since we even handle
+				// EINTR in the underlying C wrapper around poll(). So let's exit here.
+				logger.Errorf("Failed to poll(POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP) on file descriptor: %s. Exiting.", err)
+				return
+			}
+
+			// [2]: If the process exits before all its data has been read by us and no other process holds stdin or
+			// stdout open, then we will observe a (POLLHUP | POLLRDHUP | POLLIN) event. This means, we need to
+			// keep on reading from the pty file descriptor until we get a simple POLLHUP back.
+			both := ((revents & (unix.POLLIN | unix.POLLPRI)) > 0) && ((revents & (unix.POLLHUP | unix.POLLRDHUP)) > 0)
+			if both {
+				logger.Debugf("Detected poll(POLLIN | POLLPRI | POLLHUP | POLLRDHUP) event.")
+				read := buf[offset : offset+readSize]
+				nr, err = r.Read(read)
+			}
+
+			if (revents & unix.POLLERR) > 0 {
+				logger.Warnf("Detected poll(POLLERR) event: exiting.")
+				return
+			} else if (revents & unix.POLLNVAL) > 0 {
+				logger.Warnf("Detected poll(POLLNVAL) event: exiting.")
+				return
+			}
+
+			if ((revents & (unix.POLLIN | unix.POLLPRI)) > 0) && !both {
+				// This might appear unintuitive at first but is actually a nice trick: Assume we are running
+				// a shell session in a container and put a process in the background that is writing to
+				// stdout. Now assume the attached process (aka the shell in this example) exits because we
+				// used Ctrl+D to send EOF or something. If no other process would be holding stdout open we
+				// would expect to observe either a (POLLHUP | POLLRDHUP | POLLIN | POLLPRI) event if there
+				// is still data buffered from the previous process or a simple (POLLHUP | POLLRDHUP) if
+				// no data is buffered. The fact that we only observe a (POLLIN | POLLPRI) event means that
+				// another process is holding stdout open and is writing to it.
+				// One counter argument that can be leveraged is (brauner looks at tycho :))
+				// "Hey, you need to write at least one additional tty buffer to make sure that
+				// everything that the attached child has written is actually shown."
+				// The answer to that is:
+				// "This case can only happen if the process has exited and has left data in stdout which
+				// would generate a (POLLIN | POLLPRI | POLLHUP | POLLRDHUP) event and this case is already
+				// handled and triggers another codepath. (See [2].)"
+				if avoidAtomicLoad || atomic.LoadInt32(&attachedChildIsDead) == 1 {
+					avoidAtomicLoad = true
+					// Handle race between atomic.StorInt32() in the go routine
+					// explained in [1] and atomic.LoadInt32() in the go routine
+					// here:
+					// We need to check for (POLLHUP | POLLRDHUP) here again since we might
+					// still be handling a pure POLLIN event from a write prior to the childs
+					// exit. But the child might have exited right before and performed
+					// atomic.StoreInt32() to update attachedChildIsDead before we
+					// performed our atomic.LoadInt32(). This means we accidentally hit this
+					// codepath and are misinformed about the available poll() events. So we
+					// need to perform a non-blocking poll() again to exclude that case:
+					//
+					// - If we detect no (POLLHUP | POLLRDHUP) event we know the child
+					//   has already exited but someone else is holding stdin/stdout open and
+					//   writing to it.
+					//   Note that his case should only ever be triggered in situations like
+					//   running a shell and doing stuff like:
+					//    > ./lxc exec xen1 -- bash
+					//   root at xen1:~# yes &
+					//   .
+					//   .
+					//   .
+					//   now send Ctrl+D or type "exit". By the time the Ctrl+D/exit event is
+					//   triggered, we will have read all of the childs data it has written to
+					//   stdout and so we can assume that anything that comes now belongs to
+					//   the process that is holding stdin/stdout open.
+					//
+					// - If we detect a (POLLHUP | POLLRDHUP) event we know that we've
+					//   hit this codepath on accident caused by the race between
+					//   atomic.StoreInt32() in the go routine explained in [1] and
+					//   atomic.LoadInt32() in this go routine. So the next call to
+					//   GetPollRevents() will either return
+					//   (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP)
+					//   or (POLLHUP | POLLRDHUP). Both will trigger another codepath (See [2].)
+					//   that takes care that all data of the child that is buffered in
+					//   stdout is written out.
+					ret, revents, err := GetPollRevents(fd, 0, (unix.POLLIN | unix.POLLPRI | unix.POLLERR | unix.POLLHUP | unix.POLLRDHUP | unix.POLLNVAL))
+					if ret < 0 {
+						logger.Errorf("Failed to poll(POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP) on file descriptor: %s. Exiting.", err)
+						return
+					} else if (revents & (unix.POLLHUP | unix.POLLRDHUP | unix.POLLERR | unix.POLLNVAL)) == 0 {
+						logger.Debugf("Exiting but background processes are still running.")
+						return
+					}
+				}
+				read := buf[offset : offset+readSize]
+				nr, err = r.Read(read)
+			}
+
+			// The attached process has exited and we have read all data that may have
+			// been buffered.
+			if ((revents & (unix.POLLHUP | unix.POLLRDHUP)) > 0) && !both {
+				logger.Debugf("Detected poll(POLLHUP) event: exiting.")
+				return
+			}
+
+			offset += nr
+			if offset > 0 && (offset+readSize >= bufferSize || err != nil) {
+				ch <- buf[0:offset]
+				offset = 0
+				buf = make([]byte, bufferSize)
+			}
+		}
+	}()
+
+	return ch
+}
+
+// GetPollRevents poll for events on provided fd.
+func GetPollRevents(fd int, timeout int, flags int) (int, int, error) {
+	pollFd := unix.PollFd{
+		Fd:      int32(fd),
+		Events:  int16(flags),
+		Revents: 0,
+	}
+	pollFds := []unix.PollFd{pollFd}
+
+	again:
+	n, err := unix.Poll(pollFds, timeout)
+	if err != nil {
+		if err == syscall.EAGAIN || err == syscall.EINTR {
+			goto again
+		}
+
+		return -1, -1, err
+	}
+
+	return n, int(pollFds[0].Revents), err
+}
diff --git a/shared/util_linux_cgo.go b/shared/util_linux_cgo.go
index e986dce8ee..d5e85c3165 100644
--- a/shared/util_linux_cgo.go
+++ b/shared/util_linux_cgo.go
@@ -5,15 +5,10 @@ package shared
 
 import (
 	"fmt"
-	"io"
 	"os"
-	"sync"
-	"sync/atomic"
 	"unsafe"
 
 	"golang.org/x/sys/unix"
-
-	"github.com/lxc/lxd/shared/logger"
 )
 
 /*
@@ -38,30 +33,6 @@ import (
 
 #define ABSTRACT_UNIX_SOCK_LEN sizeof(((struct sockaddr_un *)0)->sun_path)
 
-// This is an adaption from https://codereview.appspot.com/4589049, to be
-// included in the stdlib with the stdlib's license.
-
-int get_poll_revents(int lfd, int timeout, int flags, int *revents, int *saved_errno)
-{
-	int ret;
-	struct pollfd pfd = {lfd, flags, 0};
-
-again:
-	ret = poll(&pfd, 1, timeout);
-	if (ret < 0) {
-		if (errno == EINTR || errno == EAGAIN)
-			goto again;
-
-		*saved_errno = errno;
-		fprintf(stderr, "Failed to poll() on file descriptor.\n");
-		return -1;
-	}
-
-	*revents = pfd.revents;
-
-	return ret;
-}
-
 static int read_pid(int fd)
 {
 	ssize_t ret;
@@ -82,26 +53,6 @@ import "C"
 
 const ABSTRACT_UNIX_SOCK_LEN int = C.ABSTRACT_UNIX_SOCK_LEN
 
-const POLLIN int = C.POLLIN
-const POLLPRI int = C.POLLPRI
-const POLLNVAL int = C.POLLNVAL
-const POLLERR int = C.POLLERR
-const POLLHUP int = C.POLLHUP
-const POLLRDHUP int = C.POLLRDHUP
-
-func GetPollRevents(fd int, timeout int, flags int) (int, int, error) {
-	var err error
-	revents := C.int(0)
-	saved_errno := C.int(0)
-
-	ret := C.get_poll_revents(C.int(fd), C.int(timeout), C.int(flags), &revents, &saved_errno)
-	if int(ret) < 0 {
-		err = unix.Errno(saved_errno)
-	}
-
-	return int(ret), int(revents), err
-}
-
 // UserId is an adaption from https://codereview.appspot.com/4589049.
 func UserId(name string) (int, error) {
 	var pw C.struct_passwd
@@ -198,188 +149,6 @@ again:
 	return int(C.int(result.gr_gid)), nil
 }
 
-// Extensively commented directly in the code. Please leave the comments!
-// Looking at this in a couple of months noone will know why and how this works
-// anymore.
-func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan struct{}, fd int) <-chan []byte {
-	if bufferSize <= (128 * 1024) {
-		bufferSize = (128 * 1024)
-	}
-
-	ch := make(chan ([]byte))
-
-	// Takes care that the closeChannel() function is exactly executed once.
-	// This allows us to avoid using a mutex.
-	var once sync.Once
-	closeChannel := func() {
-		close(ch)
-	}
-
-	// [1]: This function has just one job: Dealing with the case where we
-	// are running an interactive shell session where we put a process in
-	// the background that does hold stdin/stdout open, but does not
-	// generate any output at all. This case cannot be dealt with in the
-	// following function call. Here's why: Assume the above case, now the
-	// attached child (the shell in this example) exits. This will not
-	// generate any poll() event: We won't get POLLHUP because the
-	// background process is holding stdin/stdout open and noone is writing
-	// to it. So we effectively block on GetPollRevents() in the function
-	// below. Hence, we use another go routine here who's only job is to
-	// handle that case: When we detect that the child has exited we check
-	// whether a POLLIN or POLLHUP event has been generated. If not, we know
-	// that there's nothing buffered on stdout and exit.
-	var attachedChildIsDead int32 = 0
-	go func() {
-		<-exited
-
-		atomic.StoreInt32(&attachedChildIsDead, 1)
-
-		ret, revents, err := GetPollRevents(fd, 0, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP | POLLNVAL))
-		if ret < 0 {
-			logger.Errorf("Failed to poll(POLLIN | POLLPRI | POLLHUP | POLLRDHUP) on file descriptor: %s.", err)
-			// Something went wrong so let's exited otherwise we
-			// end up in an endless loop.
-			once.Do(closeChannel)
-		} else if ret > 0 {
-			if (revents & POLLERR) > 0 {
-				logger.Warnf("Detected poll(POLLERR) event.")
-				// Read end has likely been closed so again,
-				// avoid an endless loop.
-				once.Do(closeChannel)
-			} else if (revents & POLLNVAL) > 0 {
-				logger.Warnf("Detected poll(POLLNVAL) event.")
-				// Well, someone closed the fd havent they? So
-				// let's go home.
-				once.Do(closeChannel)
-			}
-		} else if ret == 0 {
-			logger.Debugf("No data in stdout: exiting.")
-			once.Do(closeChannel)
-		}
-	}()
-
-	go func() {
-		readSize := (128 * 1024)
-		offset := 0
-		buf := make([]byte, bufferSize)
-		avoidAtomicLoad := false
-
-		defer once.Do(closeChannel)
-		for {
-			nr := 0
-			var err error
-
-			ret, revents, err := GetPollRevents(fd, -1, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP | POLLNVAL))
-			if ret < 0 {
-				// This condition is only reached in cases where we are massively f*cked since we even handle
-				// EINTR in the underlying C wrapper around poll(). So let's exit here.
-				logger.Errorf("Failed to poll(POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP) on file descriptor: %s. Exiting.", err)
-				return
-			}
-
-			// [2]: If the process exits before all its data has been read by us and no other process holds stdin or
-			// stdout open, then we will observe a (POLLHUP | POLLRDHUP | POLLIN) event. This means, we need to
-			// keep on reading from the pty file descriptor until we get a simple POLLHUP back.
-			both := ((revents & (POLLIN | POLLPRI)) > 0) && ((revents & (POLLHUP | POLLRDHUP)) > 0)
-			if both {
-				logger.Debugf("Detected poll(POLLIN | POLLPRI | POLLHUP | POLLRDHUP) event.")
-				read := buf[offset : offset+readSize]
-				nr, err = r.Read(read)
-			}
-
-			if (revents & POLLERR) > 0 {
-				logger.Warnf("Detected poll(POLLERR) event: exiting.")
-				return
-			} else if (revents & POLLNVAL) > 0 {
-				logger.Warnf("Detected poll(POLLNVAL) event: exiting.")
-				return
-			}
-
-			if ((revents & (POLLIN | POLLPRI)) > 0) && !both {
-				// This might appear unintuitive at first but is actually a nice trick: Assume we are running
-				// a shell session in a container and put a process in the background that is writing to
-				// stdout. Now assume the attached process (aka the shell in this example) exits because we
-				// used Ctrl+D to send EOF or something. If no other process would be holding stdout open we
-				// would expect to observe either a (POLLHUP | POLLRDHUP | POLLIN | POLLPRI) event if there
-				// is still data buffered from the previous process or a simple (POLLHUP | POLLRDHUP) if
-				// no data is buffered. The fact that we only observe a (POLLIN | POLLPRI) event means that
-				// another process is holding stdout open and is writing to it.
-				// One counter argument that can be leveraged is (brauner looks at tycho :))
-				// "Hey, you need to write at least one additional tty buffer to make sure that
-				// everything that the attached child has written is actually shown."
-				// The answer to that is:
-				// "This case can only happen if the process has exited and has left data in stdout which
-				// would generate a (POLLIN | POLLPRI | POLLHUP | POLLRDHUP) event and this case is already
-				// handled and triggers another codepath. (See [2].)"
-				if avoidAtomicLoad || atomic.LoadInt32(&attachedChildIsDead) == 1 {
-					avoidAtomicLoad = true
-					// Handle race between atomic.StorInt32() in the go routine
-					// explained in [1] and atomic.LoadInt32() in the go routine
-					// here:
-					// We need to check for (POLLHUP | POLLRDHUP) here again since we might
-					// still be handling a pure POLLIN event from a write prior to the childs
-					// exit. But the child might have exited right before and performed
-					// atomic.StoreInt32() to update attachedChildIsDead before we
-					// performed our atomic.LoadInt32(). This means we accidentally hit this
-					// codepath and are misinformed about the available poll() events. So we
-					// need to perform a non-blocking poll() again to exclude that case:
-					//
-					// - If we detect no (POLLHUP | POLLRDHUP) event we know the child
-					//   has already exited but someone else is holding stdin/stdout open and
-					//   writing to it.
-					//   Note that his case should only ever be triggered in situations like
-					//   running a shell and doing stuff like:
-					//    > ./lxc exec xen1 -- bash
-					//   root at xen1:~# yes &
-					//   .
-					//   .
-					//   .
-					//   now send Ctrl+D or type "exit". By the time the Ctrl+D/exit event is
-					//   triggered, we will have read all of the childs data it has written to
-					//   stdout and so we can assume that anything that comes now belongs to
-					//   the process that is holding stdin/stdout open.
-					//
-					// - If we detect a (POLLHUP | POLLRDHUP) event we know that we've
-					//   hit this codepath on accident caused by the race between
-					//   atomic.StoreInt32() in the go routine explained in [1] and
-					//   atomic.LoadInt32() in this go routine. So the next call to
-					//   GetPollRevents() will either return
-					//   (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP)
-					//   or (POLLHUP | POLLRDHUP). Both will trigger another codepath (See [2].)
-					//   that takes care that all data of the child that is buffered in
-					//   stdout is written out.
-					ret, revents, err := GetPollRevents(fd, 0, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP | POLLNVAL))
-					if ret < 0 {
-						logger.Errorf("Failed to poll(POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP) on file descriptor: %s. Exiting.", err)
-						return
-					} else if (revents & (POLLHUP | POLLRDHUP | POLLERR | POLLNVAL)) == 0 {
-						logger.Debugf("Exiting but background processes are still running.")
-						return
-					}
-				}
-				read := buf[offset : offset+readSize]
-				nr, err = r.Read(read)
-			}
-
-			// The attached process has exited and we have read all data that may have
-			// been buffered.
-			if ((revents & (POLLHUP | POLLRDHUP)) > 0) && !both {
-				logger.Debugf("Detected poll(POLLHUP) event: exiting.")
-				return
-			}
-
-			offset += nr
-			if offset > 0 && (offset+readSize >= bufferSize || err != nil) {
-				ch <- buf[0:offset]
-				offset = 0
-				buf = make([]byte, bufferSize)
-			}
-		}
-	}()
-
-	return ch
-}
-
 func ReadPid(r *os.File) int {
 	return int(C.read_pid(C.int(r.Fd())))
 }

From 65c28c42f68f3ec0c03cc41f9defd1dc6e3716ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 19 May 2020 17:13:02 -0400
Subject: [PATCH 2/2] lxd-agent: Build statically
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile
index 0e0948f59d..3fbd611381 100644
--- a/Makefile
+++ b/Makefile
@@ -19,7 +19,7 @@ endif
 	go get -t -v -d ./...
 	CC=$(CC) go install -v -tags "$(TAG_SQLITE3)" $(DEBUG) ./...
 	CGO_ENABLED=0 go install -v -tags netgo ./lxd-p2c
-	go install -v -tags agent ./lxd-agent
+	CGO_ENABLED=0 go install -v -tags agent,netgo ./lxd-agent
 	@echo "LXD built successfully"
 
 .PHONY: client
@@ -30,7 +30,7 @@ client:
 
 .PHONY: lxd-agent
 lxd-agent:
-	go install -v -tags agent ./lxd-agent
+	CGO_ENABLED=0 go install -v -tags agent,netgo ./lxd-agent
 	@echo "LXD agent built successfully"
 
 .PHONY: lxd-p2c


More information about the lxc-devel mailing list