summaryrefslogtreecommitdiff
path: root/docker/utils/socket.py
blob: 164b845afc72722704722d16785f119966cd729f (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
import errno
import os
import select
import struct

import six

try:
    from ..transport import NpipeSocket
except ImportError:
    NpipeSocket = type(None)


class SocketError(Exception):
    pass


def read(socket, n=4096):
    """
    Reads at most n bytes from socket
    """

    recoverable_errors = (errno.EINTR, errno.EDEADLK, errno.EWOULDBLOCK)

    # wait for data to become available
    if not isinstance(socket, NpipeSocket):
        select.select([socket], [], [])

    try:
        if hasattr(socket, 'recv'):
            return socket.recv(n)
        return os.read(socket.fileno(), n)
    except EnvironmentError as e:
        if e.errno not in recoverable_errors:
            raise


def read_exactly(socket, n):
    """
    Reads exactly n bytes from socket
    Raises SocketError if there isn't enough data
    """
    data = six.binary_type()
    while len(data) < n:
        next_data = read(socket, n - len(data))
        if not next_data:
            raise SocketError("Unexpected EOF")
        data += next_data
    return data


def next_frame_size(socket):
    """
    Returns the size of the next frame of data waiting to be read from socket,
    according to the protocol defined here:

    https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/attach-to-a-container
    """
    try:
        data = read_exactly(socket, 8)
    except SocketError:
        return 0

    _, actual = struct.unpack('>BxxxL', data)
    return actual


def frames_iter(socket):
    """
    Returns a generator of frames read from socket
    """
    n = next_frame_size(socket)
    while n > 0:
        yield read(socket, n)
        n = next_frame_size(socket)