blob: 2cd7c058a21c2c2ec5e093d08cfd3a7c4a053706 [file] [log] [blame]
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +08001#!/usr/bin/env python
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08002# -*- coding: utf-8 -*-
3#
4# Copyright 2015 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08008import argparse
Wei-Ning Huangb05cde32015-08-01 09:48:41 +08009import contextlib
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +080010import ctypes
11import ctypes.util
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080012import fcntl
Wei-Ning Huangb05cde32015-08-01 09:48:41 +080013import hashlib
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080014import json
15import logging
16import os
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +080017import platform
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080018import Queue
Wei-Ning Huang829e0c82015-05-26 14:37:23 +080019import re
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080020import select
Wei-Ning Huanga301f572015-06-03 17:34:21 +080021import signal
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080022import socket
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080023import ssl
Wei-Ning Huangb05cde32015-08-01 09:48:41 +080024import struct
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080025import subprocess
26import sys
Moja Hsuc9ecc8b2015-07-13 11:39:17 +080027import termios
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080028import threading
29import time
Joel Kitching22b89042015-08-06 18:23:29 +080030import traceback
Wei-Ning Huang39169902015-09-19 06:00:23 +080031import tty
Wei-Ning Huange0def6a2015-11-05 15:41:24 +080032import urllib2
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080033import uuid
34
Wei-Ning Huang2132de32015-04-13 17:24:38 +080035import jsonrpclib
36from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
37
38
39_GHOST_RPC_PORT = 4499
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080040
41_OVERLORD_PORT = 4455
42_OVERLORD_LAN_DISCOVERY_PORT = 4456
Wei-Ning Huangb05cde32015-08-01 09:48:41 +080043_OVERLORD_HTTP_PORT = 9000
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080044
45_BUFSIZE = 8192
46_RETRY_INTERVAL = 2
47_SEPARATOR = '\r\n'
48_PING_TIMEOUT = 3
49_PING_INTERVAL = 5
50_REQUEST_TIMEOUT_SECS = 60
51_SHELL = os.getenv('SHELL', '/bin/bash')
Wei-Ning Huang7dbf4a72016-03-02 20:16:20 +080052_DEFAULT_BIND_ADDRESS = 'localhost'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080053
Moja Hsuc9ecc8b2015-07-13 11:39:17 +080054_CONTROL_START = 128
55_CONTROL_END = 129
56
Wei-Ning Huanga301f572015-06-03 17:34:21 +080057_BLOCK_SIZE = 4096
Wei-Ning Huange0def6a2015-11-05 15:41:24 +080058_CONNECT_TIMEOUT = 3
Wei-Ning Huanga301f572015-06-03 17:34:21 +080059
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +080060# Stream control
61_STDIN_CLOSED = '##STDIN_CLOSED##'
62
Wei-Ning Huang7ec55342015-09-17 08:46:06 +080063SUCCESS = 'success'
64FAILED = 'failed'
65DISCONNECTED = 'disconnected'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080066
Joel Kitching22b89042015-08-06 18:23:29 +080067
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080068class PingTimeoutError(Exception):
69 pass
70
71
72class RequestError(Exception):
73 pass
74
75
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080076class BufferedSocket(object):
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080077 """A buffered socket that supports unrecv.
78
79 Allow putting back data back to the socket for the next recv() call.
80 """
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080081 def __init__(self, sock):
82 self.sock = sock
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080083 self._buf = ''
84
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080085 def fileno(self):
86 return self.sock.fileno()
87
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080088 def Recv(self, bufsize, flags=0):
89 if self._buf:
90 if len(self._buf) >= bufsize:
91 ret = self._buf[:bufsize]
92 self._buf = self._buf[bufsize:]
93 return ret
94 else:
95 ret = self._buf
96 self._buf = ''
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080097 return ret + self.sock.recv(bufsize - len(ret), flags)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080098 else:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080099 return self.sock.recv(bufsize, flags)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800100
101 def UnRecv(self, buf):
102 self._buf = buf + self._buf
103
104 def Send(self, *args, **kwargs):
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800105 return self.sock.send(*args, **kwargs)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800106
107 def RecvBuf(self):
108 """Only recive from buffer."""
109 ret = self._buf
110 self._buf = ''
111 return ret
112
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800113 def Close(self):
114 self.sock.close()
115
116
117class TLSSettings(object):
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800118 def __init__(self, tls_cert_file, verify):
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800119 """Constructor.
120
121 Args:
122 tls_cert_file: TLS certificate in PEM format.
123 enable_tls_without_verify: enable TLS but don't verify certificate.
124 """
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800125 self._enabled = False
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800126 self._tls_cert_file = tls_cert_file
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800127 self._verify = verify
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800128 self._tls_context = None
129
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800130 def _UpdateContext(self):
131 if not self._enabled:
132 self._tls_context = None
133 return
134
135 self._tls_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
136 self._tls_context.verify_mode = ssl.CERT_REQUIRED
137
138 if self._verify:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800139 if self._tls_cert_file:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800140 self._tls_context.check_hostname = True
141 try:
142 self._tls_context.load_verify_locations(self._tls_cert_file)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800143 logging.info('TLSSettings: using user-supplied ca-certificate')
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800144 except IOError as e:
145 logging.error('TLSSettings: %s: %s', self._tls_cert_file, e)
146 sys.exit(1)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800147 else:
148 self._tls_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
149 logging.info('TLSSettings: using built-in ca-certificates')
150 else:
151 self._tls_context.verify_mode = ssl.CERT_NONE
152 logging.info('TLSSettings: skipping TLS verification!!!')
153
154 def SetEnabled(self, enabled):
155 logging.info('TLSSettings: enabled: %s', enabled)
156
157 if self._enabled != enabled:
158 self._enabled = enabled
159 self._UpdateContext()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800160
161 def Enabled(self):
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800162 return self._enabled
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800163
164 def Context(self):
165 return self._tls_context
166
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800167
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800168class Ghost(object):
169 """Ghost implements the client protocol of Overlord.
170
171 Ghost provide terminal/shell/logcat functionality and manages the client
172 side connectivity.
173 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800174 NONE, AGENT, TERMINAL, SHELL, LOGCAT, FILE, FORWARD = range(7)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800175
176 MODE_NAME = {
177 NONE: 'NONE',
178 AGENT: 'Agent',
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800179 TERMINAL: 'Terminal',
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800180 SHELL: 'Shell',
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800181 LOGCAT: 'Logcat',
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800182 FILE: 'File',
183 FORWARD: 'Forward'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800184 }
185
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800186 RANDOM_MID = '##random_mid##'
187
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800188 def __init__(self, overlord_addrs, tls_settings=None, mode=AGENT, mid=None,
189 sid=None, prop_file=None, terminal_sid=None, tty_device=None,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800190 command=None, file_op=None, port=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800191 """Constructor.
192
193 Args:
194 overlord_addrs: a list of possible address of overlord.
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800195 tls_settings: a TLSSetting object.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800196 mode: client mode, either AGENT, SHELL or LOGCAT
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800197 mid: a str to set for machine ID. If mid equals Ghost.RANDOM_MID, machine
198 id is randomly generated.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800199 sid: session ID. If the connection is requested by overlord, sid should
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800200 be set to the corresponding session id assigned by overlord.
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800201 prop_file: properties file filename.
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800202 terminal_sid: the terminal session ID associate with this client. This is
203 use for file download.
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800204 tty_device: the terminal device to open, if tty_device is None, as pseudo
205 terminal will be opened instead.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800206 command: the command to execute when we are in SHELL mode.
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800207 file_op: a tuple (action, filepath, perm). action is either 'download' or
208 'upload'. perm is the permission to set for the file.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800209 port: port number to forward.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800210 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800211 assert mode in [Ghost.AGENT, Ghost.TERMINAL, Ghost.SHELL, Ghost.FILE,
212 Ghost.FORWARD]
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800213 if mode == Ghost.SHELL:
214 assert command is not None
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800215 if mode == Ghost.FILE:
216 assert file_op is not None
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800217
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800218 self._platform = platform.system()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800219 self._overlord_addrs = overlord_addrs
Wei-Ning Huangad330c52015-03-12 20:34:18 +0800220 self._connected_addr = None
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800221 self._tls_settings = tls_settings
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800222 self._mid = mid
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800223 self._sock = None
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800224 self._mode = mode
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800225 self._machine_id = self.GetMachineID()
Wei-Ning Huangfed95862015-08-07 03:17:11 +0800226 self._session_id = sid if sid is not None else str(uuid.uuid4())
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800227 self._terminal_session_id = terminal_sid
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800228 self._ttyname_to_sid = {}
229 self._terminal_sid_to_pid = {}
230 self._prop_file = prop_file
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800231 self._properties = {}
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800232 self._register_status = DISCONNECTED
233 self._reset = threading.Event()
234
235 # RPC
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800236 self._requests = {}
237 self._queue = Queue.Queue()
238
239 # Protocol specific
240 self._last_ping = 0
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800241 self._tty_device = tty_device
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800242 self._shell_command = command
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800243 self._file_op = file_op
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800244 self._download_queue = Queue.Queue()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800245 self._port = port
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800246
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800247 def SetIgnoreChild(self, status):
248 # Only ignore child for Agent since only it could spawn child Ghost.
249 if self._mode == Ghost.AGENT:
250 signal.signal(signal.SIGCHLD,
251 signal.SIG_IGN if status else signal.SIG_DFL)
252
253 def GetFileSha1(self, filename):
254 with open(filename, 'r') as f:
255 return hashlib.sha1(f.read()).hexdigest()
256
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800257 def TLSEnabled(self, host, port):
258 """Determine if TLS is enabled on given server address."""
Wei-Ning Huang58833882015-09-16 16:52:37 +0800259 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
260 try:
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800261 # Allow any certificate since we only want to check if server talks TLS.
262 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
263 context.verify_mode = ssl.CERT_NONE
Wei-Ning Huang58833882015-09-16 16:52:37 +0800264
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800265 sock = context.wrap_socket(sock, server_hostname=host)
266 sock.settimeout(_CONNECT_TIMEOUT)
267 sock.connect((host, port))
268 return True
269 except ssl.SSLError as e:
270 return False
271 except socket.error: # Connect refused or timeout
272 raise
Wei-Ning Huang58833882015-09-16 16:52:37 +0800273 except Exception:
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800274 return False # For whatever reason above failed, assume False
Wei-Ning Huang58833882015-09-16 16:52:37 +0800275
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800276 def Upgrade(self):
277 logging.info('Upgrade: initiating upgrade sequence...')
278
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800279 try:
280 https_enabled = self.TLSEnabled(
281 self._connected_addr[0], _OVERLORD_HTTP_PORT)
282 except socket.error:
283 logging.error('Upgrade: failed to connect to Overlord HTTP server, '
284 'abort')
285 return
286
287 if self._tls_settings.Enabled() and not https_enabled:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800288 logging.error('Upgrade: TLS enforced but found Overlord HTTP server '
289 'without TLS enabled! Possible mis-configuration or '
290 'DNS/IP spoofing detected, abort')
291 return
292
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800293 scriptpath = os.path.abspath(sys.argv[0])
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800294 url = 'http%s://%s:%d/upgrade/ghost.py' % (
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800295 's' if https_enabled else '', self._connected_addr[0],
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800296 _OVERLORD_HTTP_PORT)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800297
298 # Download sha1sum for ghost.py for verification
299 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800300 with contextlib.closing(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800301 urllib2.urlopen(url + '.sha1', timeout=_CONNECT_TIMEOUT,
302 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800303 if f.getcode() != 200:
304 raise RuntimeError('HTTP status %d' % f.getcode())
305 sha1sum = f.read().strip()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800306 except (ssl.SSLError, ssl.CertificateError) as e:
307 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
308 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800309 except Exception:
310 logging.error('Upgrade: failed to download sha1sum file, abort')
311 return
312
313 if self.GetFileSha1(scriptpath) == sha1sum:
314 logging.info('Upgrade: ghost is already up-to-date, skipping upgrade')
315 return
316
317 # Download upgrade version of ghost.py
318 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800319 with contextlib.closing(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800320 urllib2.urlopen(url, timeout=_CONNECT_TIMEOUT,
321 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800322 if f.getcode() != 200:
323 raise RuntimeError('HTTP status %d' % f.getcode())
324 data = f.read()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800325 except (ssl.SSLError, ssl.CertificateError) as e:
326 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
327 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800328 except Exception:
329 logging.error('Upgrade: failed to download upgrade, abort')
330 return
331
332 # Compare SHA1 sum
333 if hashlib.sha1(data).hexdigest() != sha1sum:
334 logging.error('Upgrade: sha1sum mismatch, abort')
335 return
336
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800337 try:
338 with open(scriptpath, 'w') as f:
339 f.write(data)
340 except Exception:
341 logging.error('Upgrade: failed to write upgrade onto disk, abort')
342 return
343
344 logging.info('Upgrade: restarting ghost...')
345 self.CloseSockets()
346 self.SetIgnoreChild(False)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800347 os.execve(scriptpath, [scriptpath] + sys.argv[1:], os.environ)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800348
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800349 def LoadProperties(self):
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800350 try:
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800351 if self._prop_file:
352 with open(self._prop_file, 'r') as f:
353 self._properties = json.loads(f.read())
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800354 except Exception as e:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800355 logging.error('LoadProperties: ' + str(e))
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800356
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800357 def CloseSockets(self):
358 # Close sockets opened by parent process, since we don't use it anymore.
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800359 if self._platform == 'Linux':
360 for fd in os.listdir('/proc/self/fd/'):
361 try:
362 real_fd = os.readlink('/proc/self/fd/%s' % fd)
363 if real_fd.startswith('socket'):
364 os.close(int(fd))
365 except Exception:
366 pass
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800367
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800368 def SpawnGhost(self, mode, sid=None, terminal_sid=None, tty_device=None,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800369 command=None, file_op=None, port=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800370 """Spawn a child ghost with specific mode.
371
372 Returns:
373 The spawned child process pid.
374 """
Joel Kitching22b89042015-08-06 18:23:29 +0800375 # Restore the default signal handler, so our child won't have problems.
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800376 self.SetIgnoreChild(False)
377
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800378 pid = os.fork()
379 if pid == 0:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800380 self.CloseSockets()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800381 g = Ghost([self._connected_addr], tls_settings=self._tls_settings,
382 mode=mode, mid=Ghost.RANDOM_MID, sid=sid,
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800383 terminal_sid=terminal_sid, tty_device=tty_device,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800384 command=command, file_op=file_op, port=port)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800385 g.Start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800386 sys.exit(0)
387 else:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800388 self.SetIgnoreChild(True)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800389 return pid
390
391 def Timestamp(self):
392 return int(time.time())
393
394 def GetGateWayIP(self):
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800395 if self._platform == 'Darwin':
396 output = subprocess.check_output(['route', '-n', 'get', 'default'])
397 ret = re.search('gateway: (.*)', output)
398 if ret:
399 return [ret.group(1)]
400 elif self._platform == 'Linux':
401 with open('/proc/net/route', 'r') as f:
402 lines = f.readlines()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800403
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800404 ips = []
405 for line in lines:
406 parts = line.split('\t')
407 if parts[2] == '00000000':
408 continue
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800409
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800410 try:
411 h = parts[2].decode('hex')
412 ips.append('%d.%d.%d.%d' % tuple(ord(x) for x in reversed(h)))
413 except TypeError:
414 pass
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800415
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800416 return ips
417 else:
418 logging.warning('GetGateWayIP: unsupported platform')
419 return []
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800420
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800421 def GetShopfloorIP(self):
422 try:
423 import factory_common # pylint: disable=W0612
424 from cros.factory.test import shopfloor
425
426 url = shopfloor.get_server_url()
427 match = re.match(r'^https?://(.*):.*$', url)
428 if match:
429 return [match.group(1)]
430 except Exception:
431 pass
432 return []
433
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800434 def GetMachineID(self):
435 """Generates machine-dependent ID string for a machine.
436 There are many ways to generate a machine ID:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800437 Linux:
438 1. factory device_id
439 2. factory device-data
440 3. /sys/class/dmi/id/product_uuid (only available on intel machines)
441 4. MAC address
442 We follow the listed order to generate machine ID, and fallback to the
443 next alternative if the previous doesn't work.
444
445 Darwin:
446 All Darwin system should have the IOPlatformSerialNumber attribute.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800447 """
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800448 if self._mid == Ghost.RANDOM_MID:
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800449 return str(uuid.uuid4())
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800450 elif self._mid:
451 return self._mid
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800452
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800453 # Darwin
454 if self._platform == 'Darwin':
455 output = subprocess.check_output(['ioreg', '-rd1', '-c',
456 'IOPlatformExpertDevice'])
457 ret = re.search('"IOPlatformSerialNumber" = "(.*)"', output)
458 if ret:
459 return ret.group(1)
460
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800461 # Try factory device id
462 try:
463 import factory_common # pylint: disable=W0612
464 from cros.factory.test import event_log
465 with open(event_log.DEVICE_ID_PATH) as f:
466 return f.read().strip()
467 except Exception:
468 pass
469
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800470 # Try factory device data
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800471 try:
472 p = subprocess.Popen('factory device-data | grep mlb_serial_number | '
473 'cut -d " " -f 2', stdout=subprocess.PIPE,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800474 stderr=subprocess.PIPE, shell=True)
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800475 stdout, unused_stderr = p.communicate()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800476 if stdout == '':
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800477 raise RuntimeError('empty mlb number')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800478 return stdout.strip()
479 except Exception:
480 pass
481
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800482 # Try DMI product UUID
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800483 try:
484 with open('/sys/class/dmi/id/product_uuid', 'r') as f:
485 return f.read().strip()
486 except Exception:
487 pass
488
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800489 # Use MAC address if non is available
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800490 try:
491 macs = []
492 ifaces = sorted(os.listdir('/sys/class/net'))
493 for iface in ifaces:
494 if iface == 'lo':
495 continue
496
497 with open('/sys/class/net/%s/address' % iface, 'r') as f:
498 macs.append(f.read().strip())
499
500 return ';'.join(macs)
501 except Exception:
502 pass
503
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800504 raise RuntimeError('can\'t generate machine ID')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800505
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800506 def GetProcessWorkingDirectory(self, pid):
507 if self._platform == 'Linux':
508 return os.readlink('/proc/%d/cwd' % pid)
509 elif self._platform == 'Darwin':
510 PROC_PIDVNODEPATHINFO = 9
511 proc_vnodepathinfo_size = 2352
512 vid_path_offset = 152
513
514 proc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('libproc'))
515 buf = ctypes.create_string_buffer('\0' * proc_vnodepathinfo_size)
516 proc.proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0,
517 ctypes.byref(buf), proc_vnodepathinfo_size)
518 buf = buf.raw[vid_path_offset:]
519 n = buf.index('\0')
520 return buf[:n]
521 else:
522 raise RuntimeError('GetProcessWorkingDirectory: unsupported platform')
523
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800524 def Reset(self):
525 """Reset state and clear request handlers."""
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800526 if self._sock is not None:
527 self._sock.Close()
528 self._sock = None
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800529 self._reset.clear()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800530 self._last_ping = 0
531 self._requests = {}
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800532 self.LoadProperties()
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800533 self._register_status = DISCONNECTED
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800534
535 def SendMessage(self, msg):
536 """Serialize the message and send it through the socket."""
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800537 self._sock.Send(json.dumps(msg) + _SEPARATOR)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800538
539 def SendRequest(self, name, args, handler=None,
540 timeout=_REQUEST_TIMEOUT_SECS):
541 if handler and not callable(handler):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800542 raise RequestError('Invalid request handler for msg "%s"' % name)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800543
544 rid = str(uuid.uuid4())
545 msg = {'rid': rid, 'timeout': timeout, 'name': name, 'params': args}
Wei-Ning Huange2981862015-08-03 15:03:08 +0800546 if timeout >= 0:
547 self._requests[rid] = [self.Timestamp(), timeout, handler]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800548 self.SendMessage(msg)
549
550 def SendResponse(self, omsg, status, params=None):
551 msg = {'rid': omsg['rid'], 'response': status, 'params': params}
552 self.SendMessage(msg)
553
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800554 def HandleTTYControl(self, fd, control_str):
555 msg = json.loads(control_str)
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800556 command = msg['command']
557 params = msg['params']
558 if command == 'resize':
559 # some error happened on websocket
560 if len(params) != 2:
561 return
562 winsize = struct.pack('HHHH', params[0], params[1], 0, 0)
563 fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
564 else:
565 logging.warn('Invalid request command "%s"', command)
566
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800567 def SpawnTTYServer(self, unused_var):
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800568 """Spawn a TTY server and forward I/O to the TCP socket."""
569 logging.info('SpawnTTYServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800570
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800571 try:
572 if self._tty_device is None:
573 pid, fd = os.forkpty()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800574
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800575 if pid == 0:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800576 ttyname = os.ttyname(sys.stdout.fileno())
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800577 try:
578 server = GhostRPCServer()
579 server.RegisterTTY(self._session_id, ttyname)
580 server.RegisterSession(self._session_id, os.getpid())
581 except Exception:
582 # If ghost is launched without RPC server, the call will fail but we
583 # can ignore it.
584 pass
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800585
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800586 # The directory that contains the current running ghost script
587 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800588
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800589 env = os.environ.copy()
590 env['USER'] = os.getenv('USER', 'root')
591 env['HOME'] = os.getenv('HOME', '/root')
592 env['PATH'] = os.getenv('PATH') + ':%s' % script_dir
593 os.chdir(env['HOME'])
594 os.execve(_SHELL, [_SHELL], env)
595 else:
596 fd = os.open(self._tty_device, os.O_RDWR)
Wei-Ning Huang39169902015-09-19 06:00:23 +0800597 tty.setraw(fd)
598 attr = termios.tcgetattr(fd)
599 attr[0] &= ~(termios.IXON | termios.IXOFF)
600 attr[2] |= termios.CLOCAL
601 attr[2] &= ~termios.CRTSCTS
602 attr[4] = termios.B115200
603 attr[5] = termios.B115200
604 termios.tcsetattr(fd, termios.TCSANOW, attr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800605
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800606 nonlocals = {'control_state': None, 'control_str': ''}
607
608 def _ProcessBuffer(buf):
609 write_buffer = ''
610 while buf:
611 if nonlocals['control_state']:
612 if chr(_CONTROL_END) in buf:
613 index = buf.index(chr(_CONTROL_END))
614 nonlocals['control_str'] += buf[:index]
615 self.HandleTTYControl(fd, nonlocals['control_str'])
616 nonlocals['control_state'] = None
617 nonlocals['control_str'] = ''
618 buf = buf[index+1:]
619 else:
620 nonlocals['control_str'] += buf
621 buf = ''
622 else:
623 if chr(_CONTROL_START) in buf:
624 nonlocals['control_state'] = _CONTROL_START
625 index = buf.index(chr(_CONTROL_START))
626 write_buffer += buf[:index]
627 buf = buf[index+1:]
628 else:
629 write_buffer += buf
630 buf = ''
631
632 if write_buffer:
633 os.write(fd, write_buffer)
634
635 _ProcessBuffer(self._sock.RecvBuf())
636
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800637 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800638 rd, unused_wd, unused_xd = select.select([self._sock, fd], [], [])
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800639
640 if fd in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800641 self._sock.Send(os.read(fd, _BUFSIZE))
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800642
643 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800644 buf = self._sock.Recv(_BUFSIZE)
645 if len(buf) == 0:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800646 raise RuntimeError('connection terminated')
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800647 _ProcessBuffer(buf)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800648 except Exception as e:
649 logging.error('SpawnTTYServer: %s', e)
650 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800651 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800652
653 logging.info('SpawnTTYServer: terminated')
654 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800655
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800656 def SpawnShellServer(self, unused_var):
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800657 """Spawn a shell server and forward input/output from/to the TCP socket."""
658 logging.info('SpawnShellServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800659
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800660 # Add ghost executable to PATH
661 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
662 env = os.environ.copy()
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800663 env['PATH'] = '%s:%s' % (script_dir, os.getenv('PATH'))
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800664
665 # Execute shell command from HOME directory
666 os.chdir(os.getenv('HOME', '/tmp'))
667
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800668 p = subprocess.Popen(self._shell_command, stdin=subprocess.PIPE,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800669 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800670 shell=True, env=env)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800671
672 def make_non_block(fd):
673 fl = fcntl.fcntl(fd, fcntl.F_GETFL)
674 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
675
676 make_non_block(p.stdout)
677 make_non_block(p.stderr)
678
679 try:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800680 p.stdin.write(self._sock.RecvBuf())
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800681
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800682 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800683 rd, unused_wd, unused_xd = select.select(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800684 [p.stdout, p.stderr, self._sock], [], [])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800685 if p.stdout in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800686 self._sock.Send(p.stdout.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800687
688 if p.stderr in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800689 self._sock.Send(p.stderr.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800690
691 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800692 ret = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800693 if len(ret) == 0:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800694 raise RuntimeError('connection terminated')
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800695
696 try:
697 idx = ret.index(_STDIN_CLOSED * 2)
698 p.stdin.write(ret[:idx])
699 p.stdin.close()
700 except ValueError:
701 p.stdin.write(ret)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800702 p.poll()
703 if p.returncode != None:
704 break
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800705 except Exception as e:
706 logging.error('SpawnShellServer: %s', e)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800707 finally:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800708 # Check if the process is terminated. If not, Send SIGTERM to process,
709 # then wait for 1 second. Send another SIGKILL to make sure the process is
710 # terminated.
711 p.poll()
712 if p.returncode is None:
713 try:
714 p.terminate()
715 time.sleep(1)
716 p.kill()
717 except Exception:
718 pass
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800719
720 p.wait()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800721 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800722
723 logging.info('SpawnShellServer: terminated')
724 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800725
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800726 def InitiateFileOperation(self, unused_var):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800727 if self._file_op[0] == 'download':
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800728 try:
729 size = os.stat(self._file_op[1]).st_size
730 except OSError as e:
731 logging.error('InitiateFileOperation: download: %s', e)
732 sys.exit(1)
733
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800734 self.SendRequest('request_to_download',
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800735 {'terminal_sid': self._terminal_session_id,
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800736 'filename': os.path.basename(self._file_op[1]),
737 'size': size})
Wei-Ning Huange2981862015-08-03 15:03:08 +0800738 elif self._file_op[0] == 'upload':
739 self.SendRequest('clear_to_upload', {}, timeout=-1)
740 self.StartUploadServer()
741 else:
742 logging.error('InitiateFileOperation: unknown file operation, ignored')
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800743
744 def StartDownloadServer(self):
745 logging.info('StartDownloadServer: started')
746
747 try:
748 with open(self._file_op[1], 'rb') as f:
749 while True:
750 data = f.read(_BLOCK_SIZE)
751 if len(data) == 0:
752 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800753 self._sock.Send(data)
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800754 except Exception as e:
755 logging.error('StartDownloadServer: %s', e)
756 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800757 self._sock.Close()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800758
759 logging.info('StartDownloadServer: terminated')
760 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800761
Wei-Ning Huange2981862015-08-03 15:03:08 +0800762 def StartUploadServer(self):
763 logging.info('StartUploadServer: started')
Wei-Ning Huange2981862015-08-03 15:03:08 +0800764 try:
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800765 filepath = self._file_op[1]
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800766 dirname = os.path.dirname(filepath)
767 if not os.path.exists(dirname):
768 try:
769 os.makedirs(dirname)
770 except Exception:
771 pass
Wei-Ning Huange2981862015-08-03 15:03:08 +0800772
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800773 with open(filepath, 'wb') as f:
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800774 if self._file_op[2]:
775 os.fchmod(f.fileno(), self._file_op[2])
776
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800777 f.write(self._sock.RecvBuf())
778
Wei-Ning Huange2981862015-08-03 15:03:08 +0800779 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800780 rd, unused_wd, unused_xd = select.select([self._sock], [], [])
Wei-Ning Huange2981862015-08-03 15:03:08 +0800781 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800782 buf = self._sock.Recv(_BLOCK_SIZE)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800783 if len(buf) == 0:
784 break
785 f.write(buf)
786 except socket.error as e:
787 logging.error('StartUploadServer: socket error: %s', e)
788 except Exception as e:
789 logging.error('StartUploadServer: %s', e)
790 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800791 self._sock.Close()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800792
793 logging.info('StartUploadServer: terminated')
794 sys.exit(0)
795
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800796 def SpawnPortForwardServer(self, unused_var):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800797 """Spawn a port forwarding server and forward I/O to the TCP socket."""
798 logging.info('SpawnPortForwardServer: started')
799
800 src_sock = None
801 try:
802 src_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800803 src_sock.settimeout(_CONNECT_TIMEOUT)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800804 src_sock.connect(('localhost', self._port))
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800805
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800806 src_sock.send(self._sock.RecvBuf())
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800807
808 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800809 rd, unused_wd, unused_xd = select.select([self._sock, src_sock], [], [])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800810
811 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800812 data = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800813 if len(data) == 0:
814 raise RuntimeError('connection terminated')
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800815 src_sock.send(data)
816
817 if src_sock in rd:
818 data = src_sock.recv(_BUFSIZE)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800819 if len(data) == 0:
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800820 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800821 self._sock.Send(data)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800822 except Exception as e:
823 logging.error('SpawnPortForwardServer: %s', e)
824 finally:
825 if src_sock:
826 src_sock.close()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800827 self._sock.Close()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800828
829 logging.info('SpawnPortForwardServer: terminated')
830 sys.exit(0)
831
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800832 def Ping(self):
833 def timeout_handler(x):
834 if x is None:
835 raise PingTimeoutError
836
837 self._last_ping = self.Timestamp()
838 self.SendRequest('ping', {}, timeout_handler, 5)
839
Wei-Ning Huangae923642015-09-24 14:08:09 +0800840 def HandleFileDownloadRequest(self, msg):
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800841 params = msg['params']
Wei-Ning Huangae923642015-09-24 14:08:09 +0800842 filepath = params['filename']
843 if not os.path.isabs(filepath):
844 filepath = os.path.join(os.getenv('HOME', '/tmp'), filepath)
845
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800846 try:
Wei-Ning Huang11c35022015-10-21 16:52:32 +0800847 with open(filepath, 'r') as _:
Wei-Ning Huang46a3fc92015-10-06 02:35:27 +0800848 pass
849 except Exception as e:
Wei-Ning Huangae923642015-09-24 14:08:09 +0800850 return self.SendResponse(msg, str(e))
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800851
852 self.SpawnGhost(self.FILE, params['sid'],
Wei-Ning Huangae923642015-09-24 14:08:09 +0800853 file_op=('download', filepath))
854 self.SendResponse(msg, SUCCESS)
855
856 def HandleFileUploadRequest(self, msg):
857 params = msg['params']
858
859 # Resolve upload filepath
860 filename = params['filename']
861 dest_path = filename
862
863 # If dest is specified, use it first
864 dest_path = params.get('dest', '')
865 if dest_path:
866 if not os.path.isabs(dest_path):
867 dest_path = os.path.join(os.getenv('HOME', '/tmp'), dest_path)
868
869 if os.path.isdir(dest_path):
870 dest_path = os.path.join(dest_path, filename)
871 else:
872 target_dir = os.getenv('HOME', '/tmp')
873
874 # Terminal session ID found, upload to it's current working directory
875 if params.has_key('terminal_sid'):
876 pid = self._terminal_sid_to_pid.get(params['terminal_sid'], None)
877 if pid:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800878 try:
879 target_dir = self.GetProcessWorkingDirectory(pid)
880 except Exception as e:
881 logging.error(e)
Wei-Ning Huangae923642015-09-24 14:08:09 +0800882
883 dest_path = os.path.join(target_dir, filename)
884
885 try:
886 os.makedirs(os.path.dirname(dest_path))
887 except Exception:
888 pass
889
890 try:
891 with open(dest_path, 'w') as _:
892 pass
893 except Exception as e:
894 return self.SendResponse(msg, str(e))
895
Wei-Ning Huangd6f69762015-10-01 21:02:07 +0800896 # If not check_only, spawn FILE mode ghost agent to handle upload
897 if not params.get('check_only', False):
898 self.SpawnGhost(self.FILE, params['sid'],
899 file_op=('upload', dest_path, params.get('perm', None)))
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800900 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800901
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800902 def HandleRequest(self, msg):
Wei-Ning Huange2981862015-08-03 15:03:08 +0800903 command = msg['name']
904 params = msg['params']
905
906 if command == 'upgrade':
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800907 self.Upgrade()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800908 elif command == 'terminal':
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800909 self.SpawnGhost(self.TERMINAL, params['sid'],
910 tty_device=params['tty_device'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800911 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800912 elif command == 'shell':
913 self.SpawnGhost(self.SHELL, params['sid'], command=params['command'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800914 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800915 elif command == 'file_download':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800916 self.HandleFileDownloadRequest(msg)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800917 elif command == 'clear_to_download':
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800918 self.StartDownloadServer()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800919 elif command == 'file_upload':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800920 self.HandleFileUploadRequest(msg)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800921 elif command == 'forward':
922 self.SpawnGhost(self.FORWARD, params['sid'], port=params['port'])
923 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800924
925 def HandleResponse(self, response):
926 rid = str(response['rid'])
927 if rid in self._requests:
928 handler = self._requests[rid][2]
929 del self._requests[rid]
930 if callable(handler):
931 handler(response)
932 else:
Joel Kitching22b89042015-08-06 18:23:29 +0800933 logging.warning('Received unsolicited response, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800934
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800935 def ParseMessage(self, buf, single=True):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800936 if single:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800937 try:
938 index = buf.index(_SEPARATOR)
939 except ValueError:
940 self._sock.UnRecv(buf)
941 return
942
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800943 msgs_json = [buf[:index]]
944 self._sock.UnRecv(buf[index + 2:])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800945 else:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800946 msgs_json = buf.split(_SEPARATOR)
947 self._sock.UnRecv(msgs_json.pop())
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800948
949 for msg_json in msgs_json:
950 try:
951 msg = json.loads(msg_json)
952 except ValueError:
953 # Ignore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800954 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800955 continue
956
957 if 'name' in msg:
958 self.HandleRequest(msg)
959 elif 'response' in msg:
960 self.HandleResponse(msg)
961 else: # Ingnore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800962 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800963
964 def ScanForTimeoutRequests(self):
Joel Kitching22b89042015-08-06 18:23:29 +0800965 """Scans for pending requests which have timed out.
966
967 If any timed-out requests are discovered, their handler is called with the
968 special response value of None.
969 """
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800970 for rid in self._requests.keys()[:]:
971 request_time, timeout, handler = self._requests[rid]
972 if self.Timestamp() - request_time > timeout:
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800973 if callable(handler):
974 handler(None)
975 else:
976 logging.error('Request %s timeout', rid)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800977 del self._requests[rid]
978
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800979 def InitiateDownload(self):
980 ttyname, filename = self._download_queue.get()
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800981 sid = self._ttyname_to_sid[ttyname]
982 self.SpawnGhost(self.FILE, terminal_sid=sid,
Wei-Ning Huangae923642015-09-24 14:08:09 +0800983 file_op=('download', filename))
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800984
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800985 def Listen(self):
986 try:
987 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800988 rds, unused_wd, unused_xd = select.select([self._sock], [], [],
989 _PING_INTERVAL / 2)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800990
991 if self._sock in rds:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800992 data = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800993
994 # Socket is closed
995 if len(data) == 0:
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800996 break
997
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800998 self.ParseMessage(data, self._register_status != SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800999
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001000 if (self._mode == self.AGENT and
1001 self.Timestamp() - self._last_ping > _PING_INTERVAL):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001002 self.Ping()
1003 self.ScanForTimeoutRequests()
1004
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001005 if not self._download_queue.empty():
1006 self.InitiateDownload()
1007
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001008 if self._reset.is_set():
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001009 break
1010 except socket.error:
1011 raise RuntimeError('Connection dropped')
1012 except PingTimeoutError:
1013 raise RuntimeError('Connection timeout')
1014 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001015 self.Reset()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001016
1017 self._queue.put('resume')
1018
1019 if self._mode != Ghost.AGENT:
1020 sys.exit(1)
1021
1022 def Register(self):
1023 non_local = {}
1024 for addr in self._overlord_addrs:
1025 non_local['addr'] = addr
1026 def registered(response):
1027 if response is None:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001028 self._reset.set()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001029 raise RuntimeError('Register request timeout')
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001030
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001031 self._register_status = response['response']
1032 if response['response'] != SUCCESS:
1033 self._reset.set()
1034 raise RuntimeError('Reigster: ' + response['response'])
1035 else:
1036 logging.info('Registered with Overlord at %s:%d', *non_local['addr'])
1037 self._connected_addr = non_local['addr']
1038 self.Upgrade() # Check for upgrade
1039 self._queue.put('pause', True)
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001040
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001041 try:
1042 logging.info('Trying %s:%d ...', *addr)
1043 self.Reset()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001044
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001045 # Check if server has TLS enabled
1046 self._tls_settings.SetEnabled(self.TLSEnabled(*addr))
1047
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001048 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1049 sock.settimeout(_CONNECT_TIMEOUT)
1050
1051 try:
1052 if self._tls_settings.Enabled():
1053 tls_context = self._tls_settings.Context()
1054 sock = tls_context.wrap_socket(sock, server_hostname=addr[0])
1055
1056 sock.connect(addr)
1057 except (ssl.SSLError, ssl.CertificateError) as e:
1058 logging.error('%s: %s', e.__class__.__name__, e)
1059 continue
1060 except IOError as e:
1061 if e.errno == 2: # No such file or directory
1062 logging.error('%s: %s', e.__class__.__name__, e)
1063 continue
1064 raise
1065
1066 self._sock = BufferedSocket(sock)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001067
1068 logging.info('Connection established, registering...')
1069 handler = {
1070 Ghost.AGENT: registered,
Wei-Ning Huangb8461202015-09-01 20:07:41 +08001071 Ghost.TERMINAL: self.SpawnTTYServer,
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001072 Ghost.SHELL: self.SpawnShellServer,
1073 Ghost.FILE: self.InitiateFileOperation,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +08001074 Ghost.FORWARD: self.SpawnPortForwardServer,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001075 }[self._mode]
1076
1077 # Machine ID may change if MAC address is used (USB-ethernet dongle
1078 # plugged/unplugged)
1079 self._machine_id = self.GetMachineID()
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001080 self.SendRequest('register',
1081 {'mode': self._mode, 'mid': self._machine_id,
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001082 'sid': self._session_id,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001083 'properties': self._properties}, handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001084 except socket.error:
1085 pass
1086 else:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001087 sock.settimeout(None)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001088 self.Listen()
1089
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001090 raise RuntimeError('Cannot connect to any server')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001091
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001092 def Reconnect(self):
1093 logging.info('Received reconnect request from RPC server, reconnecting...')
1094 self._reset.set()
1095
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001096 def GetStatus(self):
1097 return self._register_status
1098
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001099 def AddToDownloadQueue(self, ttyname, filename):
1100 self._download_queue.put((ttyname, filename))
1101
Wei-Ning Huangd521f282015-08-07 05:28:04 +08001102 def RegisterTTY(self, session_id, ttyname):
1103 self._ttyname_to_sid[ttyname] = session_id
Wei-Ning Huange2981862015-08-03 15:03:08 +08001104
1105 def RegisterSession(self, session_id, process_id):
1106 self._terminal_sid_to_pid[session_id] = process_id
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001107
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001108 def StartLanDiscovery(self):
1109 """Start to listen to LAN discovery packet at
1110 _OVERLORD_LAN_DISCOVERY_PORT."""
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001111
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001112 def thread_func():
1113 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1114 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1115 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001116 try:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001117 s.bind(('0.0.0.0', _OVERLORD_LAN_DISCOVERY_PORT))
1118 except socket.error as e:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001119 logging.error('LAN discovery: %s, abort', e)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001120 return
1121
1122 logging.info('LAN Discovery: started')
1123 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001124 rd, unused_wd, unused_xd = select.select([s], [], [], 1)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001125
1126 if s in rd:
1127 data, source_addr = s.recvfrom(_BUFSIZE)
1128 parts = data.split()
1129 if parts[0] == 'OVERLORD':
1130 ip, port = parts[1].split(':')
1131 if not ip:
1132 ip = source_addr[0]
1133 self._queue.put((ip, int(port)), True)
1134
1135 try:
1136 obj = self._queue.get(False)
1137 except Queue.Empty:
1138 pass
1139 else:
1140 if type(obj) is not str:
1141 self._queue.put(obj)
1142 elif obj == 'pause':
1143 logging.info('LAN Discovery: paused')
1144 while obj != 'resume':
1145 obj = self._queue.get(True)
1146 logging.info('LAN Discovery: resumed')
1147
1148 t = threading.Thread(target=thread_func)
1149 t.daemon = True
1150 t.start()
1151
1152 def StartRPCServer(self):
Joel Kitching22b89042015-08-06 18:23:29 +08001153 logging.info('RPC Server: started')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001154 rpc_server = SimpleJSONRPCServer((_DEFAULT_BIND_ADDRESS, _GHOST_RPC_PORT),
1155 logRequests=False)
1156 rpc_server.register_function(self.Reconnect, 'Reconnect')
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001157 rpc_server.register_function(self.GetStatus, 'GetStatus')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001158 rpc_server.register_function(self.RegisterTTY, 'RegisterTTY')
Wei-Ning Huange2981862015-08-03 15:03:08 +08001159 rpc_server.register_function(self.RegisterSession, 'RegisterSession')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001160 rpc_server.register_function(self.AddToDownloadQueue, 'AddToDownloadQueue')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001161 t = threading.Thread(target=rpc_server.serve_forever)
1162 t.daemon = True
1163 t.start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001164
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001165 def ScanServer(self):
1166 for meth in [self.GetGateWayIP, self.GetShopfloorIP]:
1167 for addr in [(x, _OVERLORD_PORT) for x in meth()]:
1168 if addr not in self._overlord_addrs:
1169 self._overlord_addrs.append(addr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001170
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001171 def Start(self, lan_disc=False, rpc_server=False):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001172 logging.info('%s started', self.MODE_NAME[self._mode])
1173 logging.info('MID: %s', self._machine_id)
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001174 logging.info('SID: %s', self._session_id)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001175
Wei-Ning Huangb05cde32015-08-01 09:48:41 +08001176 # We don't care about child process's return code, not wait is needed. This
1177 # is used to prevent zombie process from lingering in the system.
1178 self.SetIgnoreChild(True)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001179
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001180 if lan_disc:
1181 self.StartLanDiscovery()
1182
1183 if rpc_server:
1184 self.StartRPCServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001185
1186 try:
1187 while True:
1188 try:
1189 addr = self._queue.get(False)
1190 except Queue.Empty:
1191 pass
1192 else:
1193 if type(addr) == tuple and addr not in self._overlord_addrs:
1194 logging.info('LAN Discovery: got overlord address %s:%d', *addr)
1195 self._overlord_addrs.append(addr)
1196
1197 try:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001198 self.ScanServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001199 self.Register()
Joel Kitching22b89042015-08-06 18:23:29 +08001200 # Don't show stack trace for RuntimeError, which we use in this file for
1201 # plausible and expected errors (such as can't connect to server).
1202 except RuntimeError as e:
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001203 logging.info('%s, retrying in %ds', e.message, _RETRY_INTERVAL)
Joel Kitching22b89042015-08-06 18:23:29 +08001204 time.sleep(_RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001205 except Exception as e:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001206 unused_x, unused_y, exc_traceback = sys.exc_info()
Joel Kitching22b89042015-08-06 18:23:29 +08001207 traceback.print_tb(exc_traceback)
1208 logging.info('%s: %s, retrying in %ds',
1209 e.__class__.__name__, e.message, _RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001210 time.sleep(_RETRY_INTERVAL)
1211
1212 self.Reset()
1213 except KeyboardInterrupt:
1214 logging.error('Received keyboard interrupt, quit')
1215 sys.exit(0)
1216
1217
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001218def GhostRPCServer():
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001219 """Returns handler to Ghost's JSON RPC server."""
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001220 return jsonrpclib.Server('http://localhost:%d' % _GHOST_RPC_PORT)
1221
1222
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001223def ForkToBackground():
1224 """Fork process to run in background."""
1225 pid = os.fork()
1226 if pid != 0:
1227 logging.info('Ghost(%d) running in background.', pid)
1228 sys.exit(0)
1229
1230
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001231def DownloadFile(filename):
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001232 """Initiate a client-initiated file download."""
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001233 filepath = os.path.abspath(filename)
1234 if not os.path.exists(filepath):
Joel Kitching22b89042015-08-06 18:23:29 +08001235 logging.error('file `%s\' does not exist', filename)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001236 sys.exit(1)
1237
1238 # Check if we actually have permission to read the file
1239 if not os.access(filepath, os.R_OK):
Joel Kitching22b89042015-08-06 18:23:29 +08001240 logging.error('can not open %s for reading', filepath)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001241 sys.exit(1)
1242
1243 server = GhostRPCServer()
1244 server.AddToDownloadQueue(os.ttyname(0), filepath)
1245 sys.exit(0)
1246
1247
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001248def main():
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001249 # Setup logging format
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001250 logger = logging.getLogger()
1251 logger.setLevel(logging.INFO)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001252 handler = logging.StreamHandler()
1253 formatter = logging.Formatter('%(asctime)s %(message)s', '%Y/%m/%d %H:%M:%S')
1254 handler.setFormatter(formatter)
1255 logger.addHandler(handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001256
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001257 parser = argparse.ArgumentParser()
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001258 parser.add_argument('--fork', dest='fork', action='store_true', default=False,
1259 help='fork procecess to run in background')
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +08001260 parser.add_argument('--mid', metavar='MID', dest='mid', action='store',
1261 default=None, help='use MID as machine ID')
1262 parser.add_argument('--rand-mid', dest='mid', action='store_const',
1263 const=Ghost.RANDOM_MID, help='use random machine ID')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001264 parser.add_argument('--no-lan-disc', dest='lan_disc', action='store_false',
1265 default=True, help='disable LAN discovery')
1266 parser.add_argument('--no-rpc-server', dest='rpc_server',
1267 action='store_false', default=True,
1268 help='disable RPC server')
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001269 parser.add_argument('--tls-cert-file', metavar='TLS_CERT_FILE',
1270 dest='tls_cert_file', type=str, default=None,
1271 help='file containing the server TLS certificate in PEM '
1272 'format')
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001273 parser.add_argument('--tls-no-verify', dest='tls_no_verify',
1274 action='store_true', default=False,
1275 help='do not verify certificate if TLS is enabled')
Joel Kitching22b89042015-08-06 18:23:29 +08001276 parser.add_argument('--prop-file', metavar='PROP_FILE', dest='prop_file',
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001277 type=str, default=None,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001278 help='file containing the JSON representation of client '
1279 'properties')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001280 parser.add_argument('--download', metavar='FILE', dest='download', type=str,
1281 default=None, help='file to download')
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001282 parser.add_argument('--reset', dest='reset', default=False,
1283 action='store_true',
1284 help='reset ghost and reload all configs')
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001285 parser.add_argument('overlord_ip', metavar='OVERLORD_IP', type=str,
1286 nargs='*', help='overlord server address')
1287 args = parser.parse_args()
1288
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001289 if args.fork:
1290 ForkToBackground()
1291
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001292 if args.reset:
1293 GhostRPCServer().Reconnect()
1294 sys.exit()
1295
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001296 if args.download:
1297 DownloadFile(args.download)
1298
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001299 addrs = [('localhost', _OVERLORD_PORT)]
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001300 addrs = [(x, _OVERLORD_PORT) for x in args.overlord_ip] + addrs
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001301
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001302 prop_file = os.path.abspath(args.prop_file) if args.prop_file else None
1303
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001304 tls_settings = TLSSettings(args.tls_cert_file, not args.tls_no_verify)
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001305 g = Ghost(addrs, tls_settings, Ghost.AGENT, args.mid,
1306 prop_file=prop_file)
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001307 g.Start(args.lan_disc, args.rpc_server)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001308
1309
1310if __name__ == '__main__':
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001311 try:
1312 main()
1313 except Exception as e:
1314 logging.error(e)