1#
2# BitBake XMLRPC Client Interface
3#
4# Copyright (C) 2006 - 2007  Michael 'Mickey' Lauer
5# Copyright (C) 2006 - 2008  Richard Purdie
6#
7# SPDX-License-Identifier: GPL-2.0-only
8#
9
10import socket
11import http.client
12import xmlrpc.client
13
14import bb
15from bb.ui import uievent
16
17class BBTransport(xmlrpc.client.Transport):
18    def __init__(self, timeout):
19        self.timeout = timeout
20        self.connection_token = None
21        xmlrpc.client.Transport.__init__(self)
22
23    # Modified from default to pass timeout to HTTPConnection
24    def make_connection(self, host):
25        #return an existing connection if possible.  This allows
26        #HTTP/1.1 keep-alive.
27        if self._connection and host == self._connection[0]:
28            return self._connection[1]
29
30        # create a HTTP connection object from a host descriptor
31        chost, self._extra_headers, x509 = self.get_host_info(host)
32        #store the host argument along with the connection object
33        self._connection = host, http.client.HTTPConnection(chost, timeout=self.timeout)
34        return self._connection[1]
35
36    def set_connection_token(self, token):
37        self.connection_token = token
38
39    def send_content(self, h, body):
40        if self.connection_token:
41            h.putheader("Bitbake-token", self.connection_token)
42        xmlrpc.client.Transport.send_content(self, h, body)
43
44def _create_server(host, port, timeout = 60):
45    t = BBTransport(timeout)
46    s = xmlrpc.client.ServerProxy("http://%s:%d/" % (host, port), transport=t, allow_none=True, use_builtin_types=True)
47    return s, t
48
49def check_connection(remote, timeout):
50    try:
51        host, port = remote.split(":")
52        port = int(port)
53    except Exception as e:
54        bb.warn("Failed to read remote definition (%s)" % str(e))
55        raise e
56
57    server, _transport = _create_server(host, port, timeout)
58    try:
59        ret, err =  server.runCommand(['getVariable', 'TOPDIR'])
60        if err or not ret:
61            return False
62    except ConnectionError:
63        return False
64    return True
65
66class BitBakeXMLRPCServerConnection(object):
67    def __init__(self, host, port, clientinfo=("localhost", 0), observer_only = False, featureset = None):
68        self.connection, self.transport = _create_server(host, port)
69        self.clientinfo = clientinfo
70        self.observer_only = observer_only
71        if featureset:
72            self.featureset = featureset
73        else:
74            self.featureset = []
75
76        self.events = uievent.BBUIEventQueue(self.connection, self.clientinfo)
77
78        _, error = self.connection.runCommand(["setFeatures", self.featureset])
79        if error:
80            # disconnect the client, we can't make the setFeature work
81            self.connection.removeClient()
82            # no need to log it here, the error shall be sent to the client
83            raise BaseException(error)
84
85    def connect(self, token = None):
86        if token is None:
87            if self.observer_only:
88                token = "observer"
89            else:
90                token = self.connection.addClient()
91
92        if token is None:
93            return None
94
95        self.transport.set_connection_token(token)
96        return self
97
98    def removeClient(self):
99        if not self.observer_only:
100            self.connection.removeClient()
101
102    def terminate(self):
103        # Don't wait for server indefinitely
104        socket.setdefaulttimeout(2)
105        try:
106            self.events.system_quit()
107        except:
108            pass
109        try:
110            self.connection.removeClient()
111        except:
112            pass
113
114def connectXMLRPC(remote, featureset, observer_only = False, token = None):
115    # The format of "remote" must be "server:port"
116    try:
117        [host, port] = remote.split(":")
118        port = int(port)
119    except Exception as e:
120        bb.warn("Failed to parse remote definition %s (%s)" % (remote, str(e)))
121        raise e
122
123    # We need our IP for the server connection. We get the IP
124    # by trying to connect with the server
125    try:
126        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
127        s.connect((host, port))
128        ip = s.getsockname()[0]
129        s.close()
130    except Exception as e:
131        bb.warn("Could not create socket for %s:%s (%s)" % (host, port, str(e)))
132        raise e
133    try:
134        connection = BitBakeXMLRPCServerConnection(host, port, (ip, 0), observer_only, featureset)
135        return connection.connect(token)
136    except Exception as e:
137        bb.warn("Could not connect to server at %s:%s (%s)" % (host, port, str(e)))
138        raise e
139
140
141
142