summaryrefslogtreecommitdiff
path: root/contrib/python/podman/libs/_containers_attach.py
blob: bd73542b95ee4cba674fb78e7d8eaeb832c8e16c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
"""Exported method Container.attach()."""

import fcntl
import os
import select
import signal
import socket
import struct
import sys
import termios
import tty

CONMON_BUFSZ = 8192


class Mixin:
    """Publish attach() for inclusion in Container class."""

    def attach(self, eot=4, stdin=None, stdout=None):
        """Attach to container's PID1 stdin and stdout.

        stderr is ignored.
        """
        if not self.containerrunning:
            raise Exception('you can only attach to running containers')

        if stdin is None:
            stdin = sys.stdin.fileno()

        if stdout is None:
            stdout = sys.stdout.fileno()

        with self._client() as podman:
            attach = podman.GetAttachSockets(self._id)

        # This is the UDS where all the IO goes
        io_socket = attach['sockets']['io_socket']
        assert len(io_socket) <= 107,\
            'Path length for sockets too long. {} > 107'.format(
                len(io_socket)
            )

        # This is the control socket where resizing events are sent to conmon
        ctl_socket = attach['sockets']['control_socket']

        def resize_handler(signum, frame):
            """Send the new window size to conmon.

            The method arguments are not used.
            """
            packed = fcntl.ioctl(stdout, termios.TIOCGWINSZ,
                                 struct.pack('HHHH', 0, 0, 0, 0))
            rows, cols, _, _ = struct.unpack('HHHH', packed)
            # TODO: Need some kind of timeout in case pipe is blocked
            with open(ctl_socket, 'w') as skt:
                # send conmon window resize message
                skt.write('1 {} {}\n'.format(rows, cols))

        def log_handler(signum, frame):
            """Send command to reopen log to conmon.

            The method arguments are not used.
            """
            with open(ctl_socket, 'w') as skt:
                # send conmon reopen log message
                skt.write('2\n')

        try:
            # save off the old settings for terminal
            original_attr = termios.tcgetattr(stdout)
            tty.setraw(stdin)

            # initialize containers window size
            resize_handler(None, sys._getframe(0))

            # catch any resizing events and send the resize info
            # to the control fifo "socket"
            signal.signal(signal.SIGWINCH, resize_handler)

        except termios.error:
            original_attr = None

        try:
            # TODO: socket.SOCK_SEQPACKET may not be supported in Windows
            with socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) as skt:
                # Prepare socket for communicating with conmon/container
                skt.connect(io_socket)
                skt.sendall(b'\n')

                sources = [skt, stdin]
                while sources:
                    readable, _, _ = select.select(sources, [], [])
                    if skt in readable:
                        data = skt.recv(CONMON_BUFSZ)
                        if not data:
                            sources.remove(skt)

                        # Remove source marker when writing
                        os.write(stdout, data[1:])

                    if stdin in readable:
                        data = os.read(stdin, CONMON_BUFSZ)
                        if not data:
                            sources.remove(stdin)

                        skt.sendall(data)

                        if eot in data:
                            sources.clear()
        finally:
            if original_attr:
                termios.tcsetattr(stdout, termios.TCSADRAIN, original_attr)
                signal.signal(signal.SIGWINCH, signal.SIG_DFL)