[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