blob: 0815611fe1a1bdad294e04c2b858ee18bd02996b [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 Huangf5311a02016-02-04 15:23:46 +080063# A string that will always be included in the response of
64# GET http://OVERLORD_SERVER:_OVERLORD_HTTP_PORT
65_OVERLORD_RESPONSE_KEYWORD = 'HTTP'
66
Wei-Ning Huang7ec55342015-09-17 08:46:06 +080067SUCCESS = 'success'
68FAILED = 'failed'
69DISCONNECTED = 'disconnected'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080070
Joel Kitching22b89042015-08-06 18:23:29 +080071
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080072class PingTimeoutError(Exception):
73 pass
74
75
76class RequestError(Exception):
77 pass
78
79
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080080class BufferedSocket(object):
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080081 """A buffered socket that supports unrecv.
82
83 Allow putting back data back to the socket for the next recv() call.
84 """
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080085 def __init__(self, sock):
86 self.sock = sock
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080087 self._buf = ''
88
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080089 def fileno(self):
90 return self.sock.fileno()
91
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080092 def Recv(self, bufsize, flags=0):
93 if self._buf:
94 if len(self._buf) >= bufsize:
95 ret = self._buf[:bufsize]
96 self._buf = self._buf[bufsize:]
97 return ret
98 else:
99 ret = self._buf
100 self._buf = ''
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800101 return ret + self.sock.recv(bufsize - len(ret), flags)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800102 else:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800103 return self.sock.recv(bufsize, flags)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800104
105 def UnRecv(self, buf):
106 self._buf = buf + self._buf
107
108 def Send(self, *args, **kwargs):
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800109 return self.sock.send(*args, **kwargs)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800110
111 def RecvBuf(self):
112 """Only recive from buffer."""
113 ret = self._buf
114 self._buf = ''
115 return ret
116
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800117 def Close(self):
118 self.sock.close()
119
120
121class TLSSettings(object):
122 def __init__(self, tls_cert_file, enable_tls_without_verify):
123 """Constructor.
124
125 Args:
126 tls_cert_file: TLS certificate in PEM format.
127 enable_tls_without_verify: enable TLS but don't verify certificate.
128 """
129 self._tls_cert_file = tls_cert_file
130 self._tls_context = None
131
132 if self._tls_cert_file is not None or enable_tls_without_verify:
133 self._tls_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
134 self._tls_context.verify_mode = ssl.CERT_NONE
135 if self._tls_cert_file:
136 self._tls_context.verify_mode = ssl.CERT_REQUIRED
137 self._tls_context.check_hostname = True
138 try:
139 self._tls_context.load_verify_locations(self._tls_cert_file)
140 except IOError as e:
141 logging.error('TLSSettings: %s: %s', self._tls_cert_file, e)
142 sys.exit(1)
143
144 def Enabled(self):
145 return self._tls_context is not None
146
147 def Context(self):
148 return self._tls_context
149
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800150
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800151class Ghost(object):
152 """Ghost implements the client protocol of Overlord.
153
154 Ghost provide terminal/shell/logcat functionality and manages the client
155 side connectivity.
156 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800157 NONE, AGENT, TERMINAL, SHELL, LOGCAT, FILE, FORWARD = range(7)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800158
159 MODE_NAME = {
160 NONE: 'NONE',
161 AGENT: 'Agent',
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800162 TERMINAL: 'Terminal',
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800163 SHELL: 'Shell',
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800164 LOGCAT: 'Logcat',
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800165 FILE: 'File',
166 FORWARD: 'Forward'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800167 }
168
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800169 RANDOM_MID = '##random_mid##'
170
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800171 def __init__(self, overlord_addrs, tls_settings=None, mode=AGENT, mid=None,
172 sid=None, prop_file=None, terminal_sid=None, tty_device=None,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800173 command=None, file_op=None, port=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800174 """Constructor.
175
176 Args:
177 overlord_addrs: a list of possible address of overlord.
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800178 tls_settings: a TLSSetting object.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800179 mode: client mode, either AGENT, SHELL or LOGCAT
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800180 mid: a str to set for machine ID. If mid equals Ghost.RANDOM_MID, machine
181 id is randomly generated.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800182 sid: session ID. If the connection is requested by overlord, sid should
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800183 be set to the corresponding session id assigned by overlord.
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800184 prop_file: properties file filename.
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800185 terminal_sid: the terminal session ID associate with this client. This is
186 use for file download.
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800187 tty_device: the terminal device to open, if tty_device is None, as pseudo
188 terminal will be opened instead.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800189 command: the command to execute when we are in SHELL mode.
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800190 file_op: a tuple (action, filepath, perm). action is either 'download' or
191 'upload'. perm is the permission to set for the file.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800192 port: port number to forward.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800193 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800194 assert mode in [Ghost.AGENT, Ghost.TERMINAL, Ghost.SHELL, Ghost.FILE,
195 Ghost.FORWARD]
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800196 if mode == Ghost.SHELL:
197 assert command is not None
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800198 if mode == Ghost.FILE:
199 assert file_op is not None
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800200
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800201 self._platform = platform.system()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800202 self._overlord_addrs = overlord_addrs
Wei-Ning Huangad330c52015-03-12 20:34:18 +0800203 self._connected_addr = None
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800204 self._tls_settings = tls_settings
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800205 self._mid = mid
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800206 self._sock = None
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800207 self._mode = mode
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800208 self._machine_id = self.GetMachineID()
Wei-Ning Huangfed95862015-08-07 03:17:11 +0800209 self._session_id = sid if sid is not None else str(uuid.uuid4())
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800210 self._terminal_session_id = terminal_sid
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800211 self._ttyname_to_sid = {}
212 self._terminal_sid_to_pid = {}
213 self._prop_file = prop_file
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800214 self._properties = {}
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800215 self._register_status = DISCONNECTED
216 self._reset = threading.Event()
217
218 # RPC
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800219 self._requests = {}
220 self._queue = Queue.Queue()
221
222 # Protocol specific
223 self._last_ping = 0
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800224 self._tty_device = tty_device
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800225 self._shell_command = command
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800226 self._file_op = file_op
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800227 self._download_queue = Queue.Queue()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800228 self._port = port
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800229
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800230 def SetIgnoreChild(self, status):
231 # Only ignore child for Agent since only it could spawn child Ghost.
232 if self._mode == Ghost.AGENT:
233 signal.signal(signal.SIGCHLD,
234 signal.SIG_IGN if status else signal.SIG_DFL)
235
236 def GetFileSha1(self, filename):
237 with open(filename, 'r') as f:
238 return hashlib.sha1(f.read()).hexdigest()
239
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800240 def OverlordHTTPSEnabled(self):
241 """Determine if SSL is enabled on the Overlord HTTP server."""
Wei-Ning Huang58833882015-09-16 16:52:37 +0800242 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
243 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800244 sock.settimeout(_CONNECT_TIMEOUT)
Wei-Ning Huang58833882015-09-16 16:52:37 +0800245 sock.connect((self._connected_addr[0], _OVERLORD_HTTP_PORT))
246 sock.send('GET\r\n')
247
248 data = sock.recv(16)
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800249 return _OVERLORD_RESPONSE_KEYWORD not in data
Wei-Ning Huang58833882015-09-16 16:52:37 +0800250 except Exception:
251 return False # For whatever reason above failed, assume HTTP
252
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800253 def Upgrade(self):
254 logging.info('Upgrade: initiating upgrade sequence...')
255
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800256 server_tls_enabled = self.OverlordHTTPSEnabled()
257 if self._tls_settings.Enabled() and not server_tls_enabled:
258 logging.error('Upgrade: TLS enforced but found Overlord HTTP server '
259 'without TLS enabled! Possible mis-configuration or '
260 'DNS/IP spoofing detected, abort')
261 return
262
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800263 scriptpath = os.path.abspath(sys.argv[0])
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800264 url = 'http%s://%s:%d/upgrade/ghost.py' % (
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800265 's' if server_tls_enabled else '', self._connected_addr[0],
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800266 _OVERLORD_HTTP_PORT)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800267
268 # Download sha1sum for ghost.py for verification
269 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800270 with contextlib.closing(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800271 urllib2.urlopen(url + '.sha1', timeout=_CONNECT_TIMEOUT,
272 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800273 if f.getcode() != 200:
274 raise RuntimeError('HTTP status %d' % f.getcode())
275 sha1sum = f.read().strip()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800276 except (ssl.SSLError, ssl.CertificateError) as e:
277 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
278 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800279 except Exception:
280 logging.error('Upgrade: failed to download sha1sum file, abort')
281 return
282
283 if self.GetFileSha1(scriptpath) == sha1sum:
284 logging.info('Upgrade: ghost is already up-to-date, skipping upgrade')
285 return
286
287 # Download upgrade version of ghost.py
288 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800289 with contextlib.closing(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800290 urllib2.urlopen(url, timeout=_CONNECT_TIMEOUT,
291 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800292 if f.getcode() != 200:
293 raise RuntimeError('HTTP status %d' % f.getcode())
294 data = f.read()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800295 except (ssl.SSLError, ssl.CertificateError) as e:
296 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
297 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800298 except Exception:
299 logging.error('Upgrade: failed to download upgrade, abort')
300 return
301
302 # Compare SHA1 sum
303 if hashlib.sha1(data).hexdigest() != sha1sum:
304 logging.error('Upgrade: sha1sum mismatch, abort')
305 return
306
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800307 try:
308 with open(scriptpath, 'w') as f:
309 f.write(data)
310 except Exception:
311 logging.error('Upgrade: failed to write upgrade onto disk, abort')
312 return
313
314 logging.info('Upgrade: restarting ghost...')
315 self.CloseSockets()
316 self.SetIgnoreChild(False)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800317 os.execve(scriptpath, [scriptpath] + sys.argv[1:], os.environ)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800318
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800319 def LoadProperties(self):
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800320 try:
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800321 if self._prop_file:
322 with open(self._prop_file, 'r') as f:
323 self._properties = json.loads(f.read())
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800324 except Exception as e:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800325 logging.error('LoadProperties: ' + str(e))
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800326
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800327 def CloseSockets(self):
328 # Close sockets opened by parent process, since we don't use it anymore.
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800329 if self._platform == 'Linux':
330 for fd in os.listdir('/proc/self/fd/'):
331 try:
332 real_fd = os.readlink('/proc/self/fd/%s' % fd)
333 if real_fd.startswith('socket'):
334 os.close(int(fd))
335 except Exception:
336 pass
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800337
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800338 def SpawnGhost(self, mode, sid=None, terminal_sid=None, tty_device=None,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800339 command=None, file_op=None, port=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800340 """Spawn a child ghost with specific mode.
341
342 Returns:
343 The spawned child process pid.
344 """
Joel Kitching22b89042015-08-06 18:23:29 +0800345 # Restore the default signal handler, so our child won't have problems.
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800346 self.SetIgnoreChild(False)
347
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800348 pid = os.fork()
349 if pid == 0:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800350 self.CloseSockets()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800351 g = Ghost([self._connected_addr], tls_settings=self._tls_settings,
352 mode=mode, mid=Ghost.RANDOM_MID, sid=sid,
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800353 terminal_sid=terminal_sid, tty_device=tty_device,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800354 command=command, file_op=file_op, port=port)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800355 g.Start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800356 sys.exit(0)
357 else:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800358 self.SetIgnoreChild(True)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800359 return pid
360
361 def Timestamp(self):
362 return int(time.time())
363
364 def GetGateWayIP(self):
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800365 if self._platform == 'Darwin':
366 output = subprocess.check_output(['route', '-n', 'get', 'default'])
367 ret = re.search('gateway: (.*)', output)
368 if ret:
369 return [ret.group(1)]
370 elif self._platform == 'Linux':
371 with open('/proc/net/route', 'r') as f:
372 lines = f.readlines()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800373
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800374 ips = []
375 for line in lines:
376 parts = line.split('\t')
377 if parts[2] == '00000000':
378 continue
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800379
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800380 try:
381 h = parts[2].decode('hex')
382 ips.append('%d.%d.%d.%d' % tuple(ord(x) for x in reversed(h)))
383 except TypeError:
384 pass
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800385
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800386 return ips
387 else:
388 logging.warning('GetGateWayIP: unsupported platform')
389 return []
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800390
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800391 def GetShopfloorIP(self):
392 try:
393 import factory_common # pylint: disable=W0612
394 from cros.factory.test import shopfloor
395
396 url = shopfloor.get_server_url()
397 match = re.match(r'^https?://(.*):.*$', url)
398 if match:
399 return [match.group(1)]
400 except Exception:
401 pass
402 return []
403
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800404 def GetMachineID(self):
405 """Generates machine-dependent ID string for a machine.
406 There are many ways to generate a machine ID:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800407 Linux:
408 1. factory device_id
409 2. factory device-data
410 3. /sys/class/dmi/id/product_uuid (only available on intel machines)
411 4. MAC address
412 We follow the listed order to generate machine ID, and fallback to the
413 next alternative if the previous doesn't work.
414
415 Darwin:
416 All Darwin system should have the IOPlatformSerialNumber attribute.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800417 """
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800418 if self._mid == Ghost.RANDOM_MID:
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800419 return str(uuid.uuid4())
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800420 elif self._mid:
421 return self._mid
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800422
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800423 # Darwin
424 if self._platform == 'Darwin':
425 output = subprocess.check_output(['ioreg', '-rd1', '-c',
426 'IOPlatformExpertDevice'])
427 ret = re.search('"IOPlatformSerialNumber" = "(.*)"', output)
428 if ret:
429 return ret.group(1)
430
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800431 # Try factory device id
432 try:
433 import factory_common # pylint: disable=W0612
434 from cros.factory.test import event_log
435 with open(event_log.DEVICE_ID_PATH) as f:
436 return f.read().strip()
437 except Exception:
438 pass
439
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800440 # Try factory device data
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800441 try:
442 p = subprocess.Popen('factory device-data | grep mlb_serial_number | '
443 'cut -d " " -f 2', stdout=subprocess.PIPE,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800444 stderr=subprocess.PIPE, shell=True)
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800445 stdout, unused_stderr = p.communicate()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800446 if stdout == '':
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800447 raise RuntimeError('empty mlb number')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800448 return stdout.strip()
449 except Exception:
450 pass
451
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800452 # Try DMI product UUID
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800453 try:
454 with open('/sys/class/dmi/id/product_uuid', 'r') as f:
455 return f.read().strip()
456 except Exception:
457 pass
458
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800459 # Use MAC address if non is available
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800460 try:
461 macs = []
462 ifaces = sorted(os.listdir('/sys/class/net'))
463 for iface in ifaces:
464 if iface == 'lo':
465 continue
466
467 with open('/sys/class/net/%s/address' % iface, 'r') as f:
468 macs.append(f.read().strip())
469
470 return ';'.join(macs)
471 except Exception:
472 pass
473
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800474 raise RuntimeError('can\'t generate machine ID')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800475
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800476 def GetProcessWorkingDirectory(self, pid):
477 if self._platform == 'Linux':
478 return os.readlink('/proc/%d/cwd' % pid)
479 elif self._platform == 'Darwin':
480 PROC_PIDVNODEPATHINFO = 9
481 proc_vnodepathinfo_size = 2352
482 vid_path_offset = 152
483
484 proc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('libproc'))
485 buf = ctypes.create_string_buffer('\0' * proc_vnodepathinfo_size)
486 proc.proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0,
487 ctypes.byref(buf), proc_vnodepathinfo_size)
488 buf = buf.raw[vid_path_offset:]
489 n = buf.index('\0')
490 return buf[:n]
491 else:
492 raise RuntimeError('GetProcessWorkingDirectory: unsupported platform')
493
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800494 def Reset(self):
495 """Reset state and clear request handlers."""
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800496 if self._sock is not None:
497 self._sock.Close()
498 self._sock = None
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800499 self._reset.clear()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800500 self._last_ping = 0
501 self._requests = {}
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800502 self.LoadProperties()
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800503 self._register_status = DISCONNECTED
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800504
505 def SendMessage(self, msg):
506 """Serialize the message and send it through the socket."""
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800507 self._sock.Send(json.dumps(msg) + _SEPARATOR)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800508
509 def SendRequest(self, name, args, handler=None,
510 timeout=_REQUEST_TIMEOUT_SECS):
511 if handler and not callable(handler):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800512 raise RequestError('Invalid request handler for msg "%s"' % name)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800513
514 rid = str(uuid.uuid4())
515 msg = {'rid': rid, 'timeout': timeout, 'name': name, 'params': args}
Wei-Ning Huange2981862015-08-03 15:03:08 +0800516 if timeout >= 0:
517 self._requests[rid] = [self.Timestamp(), timeout, handler]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800518 self.SendMessage(msg)
519
520 def SendResponse(self, omsg, status, params=None):
521 msg = {'rid': omsg['rid'], 'response': status, 'params': params}
522 self.SendMessage(msg)
523
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800524 def HandleTTYControl(self, fd, control_str):
525 msg = json.loads(control_str)
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800526 command = msg['command']
527 params = msg['params']
528 if command == 'resize':
529 # some error happened on websocket
530 if len(params) != 2:
531 return
532 winsize = struct.pack('HHHH', params[0], params[1], 0, 0)
533 fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
534 else:
535 logging.warn('Invalid request command "%s"', command)
536
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800537 def SpawnTTYServer(self, unused_var):
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800538 """Spawn a TTY server and forward I/O to the TCP socket."""
539 logging.info('SpawnTTYServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800540
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800541 try:
542 if self._tty_device is None:
543 pid, fd = os.forkpty()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800544
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800545 if pid == 0:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800546 ttyname = os.ttyname(sys.stdout.fileno())
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800547 try:
548 server = GhostRPCServer()
549 server.RegisterTTY(self._session_id, ttyname)
550 server.RegisterSession(self._session_id, os.getpid())
551 except Exception:
552 # If ghost is launched without RPC server, the call will fail but we
553 # can ignore it.
554 pass
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800555
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800556 # The directory that contains the current running ghost script
557 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800558
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800559 env = os.environ.copy()
560 env['USER'] = os.getenv('USER', 'root')
561 env['HOME'] = os.getenv('HOME', '/root')
562 env['PATH'] = os.getenv('PATH') + ':%s' % script_dir
563 os.chdir(env['HOME'])
564 os.execve(_SHELL, [_SHELL], env)
565 else:
566 fd = os.open(self._tty_device, os.O_RDWR)
Wei-Ning Huang39169902015-09-19 06:00:23 +0800567 tty.setraw(fd)
568 attr = termios.tcgetattr(fd)
569 attr[0] &= ~(termios.IXON | termios.IXOFF)
570 attr[2] |= termios.CLOCAL
571 attr[2] &= ~termios.CRTSCTS
572 attr[4] = termios.B115200
573 attr[5] = termios.B115200
574 termios.tcsetattr(fd, termios.TCSANOW, attr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800575
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800576 nonlocals = {'control_state': None, 'control_str': ''}
577
578 def _ProcessBuffer(buf):
579 write_buffer = ''
580 while buf:
581 if nonlocals['control_state']:
582 if chr(_CONTROL_END) in buf:
583 index = buf.index(chr(_CONTROL_END))
584 nonlocals['control_str'] += buf[:index]
585 self.HandleTTYControl(fd, nonlocals['control_str'])
586 nonlocals['control_state'] = None
587 nonlocals['control_str'] = ''
588 buf = buf[index+1:]
589 else:
590 nonlocals['control_str'] += buf
591 buf = ''
592 else:
593 if chr(_CONTROL_START) in buf:
594 nonlocals['control_state'] = _CONTROL_START
595 index = buf.index(chr(_CONTROL_START))
596 write_buffer += buf[:index]
597 buf = buf[index+1:]
598 else:
599 write_buffer += buf
600 buf = ''
601
602 if write_buffer:
603 os.write(fd, write_buffer)
604
605 _ProcessBuffer(self._sock.RecvBuf())
606
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800607 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800608 rd, unused_wd, unused_xd = select.select([self._sock, fd], [], [])
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800609
610 if fd in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800611 self._sock.Send(os.read(fd, _BUFSIZE))
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800612
613 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800614 buf = self._sock.Recv(_BUFSIZE)
615 if len(buf) == 0:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800616 raise RuntimeError('connection terminated')
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800617 _ProcessBuffer(buf)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800618 except Exception as e:
619 logging.error('SpawnTTYServer: %s', e)
620 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800621 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800622
623 logging.info('SpawnTTYServer: terminated')
624 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800625
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800626 def SpawnShellServer(self, unused_var):
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800627 """Spawn a shell server and forward input/output from/to the TCP socket."""
628 logging.info('SpawnShellServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800629
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800630 # Add ghost executable to PATH
631 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
632 env = os.environ.copy()
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800633 env['PATH'] = '%s:%s' % (script_dir, os.getenv('PATH'))
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800634
635 # Execute shell command from HOME directory
636 os.chdir(os.getenv('HOME', '/tmp'))
637
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800638 p = subprocess.Popen(self._shell_command, stdin=subprocess.PIPE,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800639 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800640 shell=True, env=env)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800641
642 def make_non_block(fd):
643 fl = fcntl.fcntl(fd, fcntl.F_GETFL)
644 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
645
646 make_non_block(p.stdout)
647 make_non_block(p.stderr)
648
649 try:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800650 p.stdin.write(self._sock.RecvBuf())
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800651
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800652 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800653 rd, unused_wd, unused_xd = select.select(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800654 [p.stdout, p.stderr, self._sock], [], [])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800655 if p.stdout in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800656 self._sock.Send(p.stdout.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800657
658 if p.stderr in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800659 self._sock.Send(p.stderr.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800660
661 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800662 ret = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800663 if len(ret) == 0:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800664 raise RuntimeError('connection terminated')
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800665
666 try:
667 idx = ret.index(_STDIN_CLOSED * 2)
668 p.stdin.write(ret[:idx])
669 p.stdin.close()
670 except ValueError:
671 p.stdin.write(ret)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800672 p.poll()
673 if p.returncode != None:
674 break
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800675 except Exception as e:
676 logging.error('SpawnShellServer: %s', e)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800677 finally:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800678 # Check if the process is terminated. If not, Send SIGTERM to process,
679 # then wait for 1 second. Send another SIGKILL to make sure the process is
680 # terminated.
681 p.poll()
682 if p.returncode is None:
683 try:
684 p.terminate()
685 time.sleep(1)
686 p.kill()
687 except Exception:
688 pass
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800689
690 p.wait()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800691 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800692
693 logging.info('SpawnShellServer: terminated')
694 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800695
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800696 def InitiateFileOperation(self, unused_var):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800697 if self._file_op[0] == 'download':
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800698 try:
699 size = os.stat(self._file_op[1]).st_size
700 except OSError as e:
701 logging.error('InitiateFileOperation: download: %s', e)
702 sys.exit(1)
703
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800704 self.SendRequest('request_to_download',
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800705 {'terminal_sid': self._terminal_session_id,
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800706 'filename': os.path.basename(self._file_op[1]),
707 'size': size})
Wei-Ning Huange2981862015-08-03 15:03:08 +0800708 elif self._file_op[0] == 'upload':
709 self.SendRequest('clear_to_upload', {}, timeout=-1)
710 self.StartUploadServer()
711 else:
712 logging.error('InitiateFileOperation: unknown file operation, ignored')
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800713
714 def StartDownloadServer(self):
715 logging.info('StartDownloadServer: started')
716
717 try:
718 with open(self._file_op[1], 'rb') as f:
719 while True:
720 data = f.read(_BLOCK_SIZE)
721 if len(data) == 0:
722 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800723 self._sock.Send(data)
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800724 except Exception as e:
725 logging.error('StartDownloadServer: %s', e)
726 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800727 self._sock.Close()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800728
729 logging.info('StartDownloadServer: terminated')
730 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800731
Wei-Ning Huange2981862015-08-03 15:03:08 +0800732 def StartUploadServer(self):
733 logging.info('StartUploadServer: started')
Wei-Ning Huange2981862015-08-03 15:03:08 +0800734 try:
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800735 filepath = self._file_op[1]
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800736 dirname = os.path.dirname(filepath)
737 if not os.path.exists(dirname):
738 try:
739 os.makedirs(dirname)
740 except Exception:
741 pass
Wei-Ning Huange2981862015-08-03 15:03:08 +0800742
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800743 with open(filepath, 'wb') as f:
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800744 if self._file_op[2]:
745 os.fchmod(f.fileno(), self._file_op[2])
746
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800747 f.write(self._sock.RecvBuf())
748
Wei-Ning Huange2981862015-08-03 15:03:08 +0800749 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800750 rd, unused_wd, unused_xd = select.select([self._sock], [], [])
Wei-Ning Huange2981862015-08-03 15:03:08 +0800751 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800752 buf = self._sock.Recv(_BLOCK_SIZE)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800753 if len(buf) == 0:
754 break
755 f.write(buf)
756 except socket.error as e:
757 logging.error('StartUploadServer: socket error: %s', e)
758 except Exception as e:
759 logging.error('StartUploadServer: %s', e)
760 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800761 self._sock.Close()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800762
763 logging.info('StartUploadServer: terminated')
764 sys.exit(0)
765
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800766 def SpawnPortForwardServer(self, unused_var):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800767 """Spawn a port forwarding server and forward I/O to the TCP socket."""
768 logging.info('SpawnPortForwardServer: started')
769
770 src_sock = None
771 try:
772 src_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800773 src_sock.settimeout(_CONNECT_TIMEOUT)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800774 src_sock.connect(('localhost', self._port))
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800775
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800776 src_sock.send(self._sock.RecvBuf())
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800777
778 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800779 rd, unused_wd, unused_xd = select.select([self._sock, src_sock], [], [])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800780
781 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800782 data = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800783 if len(data) == 0:
784 raise RuntimeError('connection terminated')
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800785 src_sock.send(data)
786
787 if src_sock in rd:
788 data = src_sock.recv(_BUFSIZE)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800789 if len(data) == 0:
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800790 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800791 self._sock.Send(data)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800792 except Exception as e:
793 logging.error('SpawnPortForwardServer: %s', e)
794 finally:
795 if src_sock:
796 src_sock.close()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800797 self._sock.Close()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800798
799 logging.info('SpawnPortForwardServer: terminated')
800 sys.exit(0)
801
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800802 def Ping(self):
803 def timeout_handler(x):
804 if x is None:
805 raise PingTimeoutError
806
807 self._last_ping = self.Timestamp()
808 self.SendRequest('ping', {}, timeout_handler, 5)
809
Wei-Ning Huangae923642015-09-24 14:08:09 +0800810 def HandleFileDownloadRequest(self, msg):
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800811 params = msg['params']
Wei-Ning Huangae923642015-09-24 14:08:09 +0800812 filepath = params['filename']
813 if not os.path.isabs(filepath):
814 filepath = os.path.join(os.getenv('HOME', '/tmp'), filepath)
815
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800816 try:
Wei-Ning Huang11c35022015-10-21 16:52:32 +0800817 with open(filepath, 'r') as _:
Wei-Ning Huang46a3fc92015-10-06 02:35:27 +0800818 pass
819 except Exception as e:
Wei-Ning Huangae923642015-09-24 14:08:09 +0800820 return self.SendResponse(msg, str(e))
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800821
822 self.SpawnGhost(self.FILE, params['sid'],
Wei-Ning Huangae923642015-09-24 14:08:09 +0800823 file_op=('download', filepath))
824 self.SendResponse(msg, SUCCESS)
825
826 def HandleFileUploadRequest(self, msg):
827 params = msg['params']
828
829 # Resolve upload filepath
830 filename = params['filename']
831 dest_path = filename
832
833 # If dest is specified, use it first
834 dest_path = params.get('dest', '')
835 if dest_path:
836 if not os.path.isabs(dest_path):
837 dest_path = os.path.join(os.getenv('HOME', '/tmp'), dest_path)
838
839 if os.path.isdir(dest_path):
840 dest_path = os.path.join(dest_path, filename)
841 else:
842 target_dir = os.getenv('HOME', '/tmp')
843
844 # Terminal session ID found, upload to it's current working directory
845 if params.has_key('terminal_sid'):
846 pid = self._terminal_sid_to_pid.get(params['terminal_sid'], None)
847 if pid:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800848 try:
849 target_dir = self.GetProcessWorkingDirectory(pid)
850 except Exception as e:
851 logging.error(e)
Wei-Ning Huangae923642015-09-24 14:08:09 +0800852
853 dest_path = os.path.join(target_dir, filename)
854
855 try:
856 os.makedirs(os.path.dirname(dest_path))
857 except Exception:
858 pass
859
860 try:
861 with open(dest_path, 'w') as _:
862 pass
863 except Exception as e:
864 return self.SendResponse(msg, str(e))
865
Wei-Ning Huangd6f69762015-10-01 21:02:07 +0800866 # If not check_only, spawn FILE mode ghost agent to handle upload
867 if not params.get('check_only', False):
868 self.SpawnGhost(self.FILE, params['sid'],
869 file_op=('upload', dest_path, params.get('perm', None)))
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800870 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800871
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800872 def HandleRequest(self, msg):
Wei-Ning Huange2981862015-08-03 15:03:08 +0800873 command = msg['name']
874 params = msg['params']
875
876 if command == 'upgrade':
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800877 self.Upgrade()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800878 elif command == 'terminal':
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800879 self.SpawnGhost(self.TERMINAL, params['sid'],
880 tty_device=params['tty_device'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800881 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800882 elif command == 'shell':
883 self.SpawnGhost(self.SHELL, params['sid'], command=params['command'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800884 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800885 elif command == 'file_download':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800886 self.HandleFileDownloadRequest(msg)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800887 elif command == 'clear_to_download':
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800888 self.StartDownloadServer()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800889 elif command == 'file_upload':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800890 self.HandleFileUploadRequest(msg)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800891 elif command == 'forward':
892 self.SpawnGhost(self.FORWARD, params['sid'], port=params['port'])
893 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800894
895 def HandleResponse(self, response):
896 rid = str(response['rid'])
897 if rid in self._requests:
898 handler = self._requests[rid][2]
899 del self._requests[rid]
900 if callable(handler):
901 handler(response)
902 else:
Joel Kitching22b89042015-08-06 18:23:29 +0800903 logging.warning('Received unsolicited response, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800904
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800905 def ParseMessage(self, buf, single=True):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800906 if single:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800907 try:
908 index = buf.index(_SEPARATOR)
909 except ValueError:
910 self._sock.UnRecv(buf)
911 return
912
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800913 msgs_json = [buf[:index]]
914 self._sock.UnRecv(buf[index + 2:])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800915 else:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800916 msgs_json = buf.split(_SEPARATOR)
917 self._sock.UnRecv(msgs_json.pop())
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800918
919 for msg_json in msgs_json:
920 try:
921 msg = json.loads(msg_json)
922 except ValueError:
923 # Ignore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800924 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800925 continue
926
927 if 'name' in msg:
928 self.HandleRequest(msg)
929 elif 'response' in msg:
930 self.HandleResponse(msg)
931 else: # Ingnore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800932 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800933
934 def ScanForTimeoutRequests(self):
Joel Kitching22b89042015-08-06 18:23:29 +0800935 """Scans for pending requests which have timed out.
936
937 If any timed-out requests are discovered, their handler is called with the
938 special response value of None.
939 """
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800940 for rid in self._requests.keys()[:]:
941 request_time, timeout, handler = self._requests[rid]
942 if self.Timestamp() - request_time > timeout:
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800943 if callable(handler):
944 handler(None)
945 else:
946 logging.error('Request %s timeout', rid)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800947 del self._requests[rid]
948
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800949 def InitiateDownload(self):
950 ttyname, filename = self._download_queue.get()
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800951 sid = self._ttyname_to_sid[ttyname]
952 self.SpawnGhost(self.FILE, terminal_sid=sid,
Wei-Ning Huangae923642015-09-24 14:08:09 +0800953 file_op=('download', filename))
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800954
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800955 def Listen(self):
956 try:
957 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800958 rds, unused_wd, unused_xd = select.select([self._sock], [], [],
959 _PING_INTERVAL / 2)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800960
961 if self._sock in rds:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800962 data = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800963
964 # Socket is closed
965 if len(data) == 0:
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800966 break
967
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800968 self.ParseMessage(data, self._register_status != SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800969
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800970 if (self._mode == self.AGENT and
971 self.Timestamp() - self._last_ping > _PING_INTERVAL):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800972 self.Ping()
973 self.ScanForTimeoutRequests()
974
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800975 if not self._download_queue.empty():
976 self.InitiateDownload()
977
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800978 if self._reset.is_set():
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800979 break
980 except socket.error:
981 raise RuntimeError('Connection dropped')
982 except PingTimeoutError:
983 raise RuntimeError('Connection timeout')
984 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800985 self.Reset()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800986
987 self._queue.put('resume')
988
989 if self._mode != Ghost.AGENT:
990 sys.exit(1)
991
992 def Register(self):
993 non_local = {}
994 for addr in self._overlord_addrs:
995 non_local['addr'] = addr
996 def registered(response):
997 if response is None:
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800998 self._reset.set()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800999 raise RuntimeError('Register request timeout')
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001000
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001001 self._register_status = response['response']
1002 if response['response'] != SUCCESS:
1003 self._reset.set()
1004 raise RuntimeError('Reigster: ' + response['response'])
1005 else:
1006 logging.info('Registered with Overlord at %s:%d', *non_local['addr'])
1007 self._connected_addr = non_local['addr']
1008 self.Upgrade() # Check for upgrade
1009 self._queue.put('pause', True)
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001010
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001011 try:
1012 logging.info('Trying %s:%d ...', *addr)
1013 self.Reset()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001014
1015 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1016 sock.settimeout(_CONNECT_TIMEOUT)
1017
1018 try:
1019 if self._tls_settings.Enabled():
1020 tls_context = self._tls_settings.Context()
1021 sock = tls_context.wrap_socket(sock, server_hostname=addr[0])
1022
1023 sock.connect(addr)
1024 except (ssl.SSLError, ssl.CertificateError) as e:
1025 logging.error('%s: %s', e.__class__.__name__, e)
1026 continue
1027 except IOError as e:
1028 if e.errno == 2: # No such file or directory
1029 logging.error('%s: %s', e.__class__.__name__, e)
1030 continue
1031 raise
1032
1033 self._sock = BufferedSocket(sock)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001034
1035 logging.info('Connection established, registering...')
1036 handler = {
1037 Ghost.AGENT: registered,
Wei-Ning Huangb8461202015-09-01 20:07:41 +08001038 Ghost.TERMINAL: self.SpawnTTYServer,
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001039 Ghost.SHELL: self.SpawnShellServer,
1040 Ghost.FILE: self.InitiateFileOperation,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +08001041 Ghost.FORWARD: self.SpawnPortForwardServer,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001042 }[self._mode]
1043
1044 # Machine ID may change if MAC address is used (USB-ethernet dongle
1045 # plugged/unplugged)
1046 self._machine_id = self.GetMachineID()
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001047 self.SendRequest('register',
1048 {'mode': self._mode, 'mid': self._machine_id,
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001049 'sid': self._session_id,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001050 'properties': self._properties}, handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001051 except socket.error:
1052 pass
1053 else:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001054 sock.settimeout(None)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001055 self.Listen()
1056
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001057 raise RuntimeError('Cannot connect to any server')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001058
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001059 def Reconnect(self):
1060 logging.info('Received reconnect request from RPC server, reconnecting...')
1061 self._reset.set()
1062
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001063 def GetStatus(self):
1064 return self._register_status
1065
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001066 def AddToDownloadQueue(self, ttyname, filename):
1067 self._download_queue.put((ttyname, filename))
1068
Wei-Ning Huangd521f282015-08-07 05:28:04 +08001069 def RegisterTTY(self, session_id, ttyname):
1070 self._ttyname_to_sid[ttyname] = session_id
Wei-Ning Huange2981862015-08-03 15:03:08 +08001071
1072 def RegisterSession(self, session_id, process_id):
1073 self._terminal_sid_to_pid[session_id] = process_id
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001074
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001075 def StartLanDiscovery(self):
1076 """Start to listen to LAN discovery packet at
1077 _OVERLORD_LAN_DISCOVERY_PORT."""
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001078
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001079 def thread_func():
1080 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1081 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1082 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001083 try:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001084 s.bind(('0.0.0.0', _OVERLORD_LAN_DISCOVERY_PORT))
1085 except socket.error as e:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001086 logging.error('LAN discovery: %s, abort', e)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001087 return
1088
1089 logging.info('LAN Discovery: started')
1090 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001091 rd, unused_wd, unused_xd = select.select([s], [], [], 1)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001092
1093 if s in rd:
1094 data, source_addr = s.recvfrom(_BUFSIZE)
1095 parts = data.split()
1096 if parts[0] == 'OVERLORD':
1097 ip, port = parts[1].split(':')
1098 if not ip:
1099 ip = source_addr[0]
1100 self._queue.put((ip, int(port)), True)
1101
1102 try:
1103 obj = self._queue.get(False)
1104 except Queue.Empty:
1105 pass
1106 else:
1107 if type(obj) is not str:
1108 self._queue.put(obj)
1109 elif obj == 'pause':
1110 logging.info('LAN Discovery: paused')
1111 while obj != 'resume':
1112 obj = self._queue.get(True)
1113 logging.info('LAN Discovery: resumed')
1114
1115 t = threading.Thread(target=thread_func)
1116 t.daemon = True
1117 t.start()
1118
1119 def StartRPCServer(self):
Joel Kitching22b89042015-08-06 18:23:29 +08001120 logging.info('RPC Server: started')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001121 rpc_server = SimpleJSONRPCServer((_DEFAULT_BIND_ADDRESS, _GHOST_RPC_PORT),
1122 logRequests=False)
1123 rpc_server.register_function(self.Reconnect, 'Reconnect')
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001124 rpc_server.register_function(self.GetStatus, 'GetStatus')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001125 rpc_server.register_function(self.RegisterTTY, 'RegisterTTY')
Wei-Ning Huange2981862015-08-03 15:03:08 +08001126 rpc_server.register_function(self.RegisterSession, 'RegisterSession')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001127 rpc_server.register_function(self.AddToDownloadQueue, 'AddToDownloadQueue')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001128 t = threading.Thread(target=rpc_server.serve_forever)
1129 t.daemon = True
1130 t.start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001131
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001132 def ScanServer(self):
1133 for meth in [self.GetGateWayIP, self.GetShopfloorIP]:
1134 for addr in [(x, _OVERLORD_PORT) for x in meth()]:
1135 if addr not in self._overlord_addrs:
1136 self._overlord_addrs.append(addr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001137
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001138 def Start(self, lan_disc=False, rpc_server=False):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001139 logging.info('%s started', self.MODE_NAME[self._mode])
1140 logging.info('MID: %s', self._machine_id)
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001141 logging.info('SID: %s', self._session_id)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001142
Wei-Ning Huangb05cde32015-08-01 09:48:41 +08001143 # We don't care about child process's return code, not wait is needed. This
1144 # is used to prevent zombie process from lingering in the system.
1145 self.SetIgnoreChild(True)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001146
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001147 if lan_disc:
1148 self.StartLanDiscovery()
1149
1150 if rpc_server:
1151 self.StartRPCServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001152
1153 try:
1154 while True:
1155 try:
1156 addr = self._queue.get(False)
1157 except Queue.Empty:
1158 pass
1159 else:
1160 if type(addr) == tuple and addr not in self._overlord_addrs:
1161 logging.info('LAN Discovery: got overlord address %s:%d', *addr)
1162 self._overlord_addrs.append(addr)
1163
1164 try:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001165 self.ScanServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001166 self.Register()
Joel Kitching22b89042015-08-06 18:23:29 +08001167 # Don't show stack trace for RuntimeError, which we use in this file for
1168 # plausible and expected errors (such as can't connect to server).
1169 except RuntimeError as e:
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001170 logging.info('%s, retrying in %ds', e.message, _RETRY_INTERVAL)
Joel Kitching22b89042015-08-06 18:23:29 +08001171 time.sleep(_RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001172 except Exception as e:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001173 unused_x, unused_y, exc_traceback = sys.exc_info()
Joel Kitching22b89042015-08-06 18:23:29 +08001174 traceback.print_tb(exc_traceback)
1175 logging.info('%s: %s, retrying in %ds',
1176 e.__class__.__name__, e.message, _RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001177 time.sleep(_RETRY_INTERVAL)
1178
1179 self.Reset()
1180 except KeyboardInterrupt:
1181 logging.error('Received keyboard interrupt, quit')
1182 sys.exit(0)
1183
1184
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001185def GhostRPCServer():
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001186 """Returns handler to Ghost's JSON RPC server."""
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001187 return jsonrpclib.Server('http://localhost:%d' % _GHOST_RPC_PORT)
1188
1189
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001190def ForkToBackground():
1191 """Fork process to run in background."""
1192 pid = os.fork()
1193 if pid != 0:
1194 logging.info('Ghost(%d) running in background.', pid)
1195 sys.exit(0)
1196
1197
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001198def DownloadFile(filename):
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001199 """Initiate a client-initiated file download."""
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001200 filepath = os.path.abspath(filename)
1201 if not os.path.exists(filepath):
Joel Kitching22b89042015-08-06 18:23:29 +08001202 logging.error('file `%s\' does not exist', filename)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001203 sys.exit(1)
1204
1205 # Check if we actually have permission to read the file
1206 if not os.access(filepath, os.R_OK):
Joel Kitching22b89042015-08-06 18:23:29 +08001207 logging.error('can not open %s for reading', filepath)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001208 sys.exit(1)
1209
1210 server = GhostRPCServer()
1211 server.AddToDownloadQueue(os.ttyname(0), filepath)
1212 sys.exit(0)
1213
1214
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001215def main():
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001216 # Setup logging format
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001217 logger = logging.getLogger()
1218 logger.setLevel(logging.INFO)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001219 handler = logging.StreamHandler()
1220 formatter = logging.Formatter('%(asctime)s %(message)s', '%Y/%m/%d %H:%M:%S')
1221 handler.setFormatter(formatter)
1222 logger.addHandler(handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001223
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001224 parser = argparse.ArgumentParser()
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001225 parser.add_argument('--fork', dest='fork', action='store_true', default=False,
1226 help='fork procecess to run in background')
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +08001227 parser.add_argument('--mid', metavar='MID', dest='mid', action='store',
1228 default=None, help='use MID as machine ID')
1229 parser.add_argument('--rand-mid', dest='mid', action='store_const',
1230 const=Ghost.RANDOM_MID, help='use random machine ID')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001231 parser.add_argument('--no-lan-disc', dest='lan_disc', action='store_false',
1232 default=True, help='disable LAN discovery')
1233 parser.add_argument('--no-rpc-server', dest='rpc_server',
1234 action='store_false', default=True,
1235 help='disable RPC server')
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001236 parser.add_argument('--tls-cert-file', metavar='TLS_CERT_FILE',
1237 dest='tls_cert_file', type=str, default=None,
1238 help='file containing the server TLS certificate in PEM '
1239 'format')
1240 parser.add_argument('--enable-tls-without-verify',
1241 dest='enable_tls_without_verify', action='store_true',
1242 default=False,
1243 help='Enable TLS but don\'t verify certificate')
Joel Kitching22b89042015-08-06 18:23:29 +08001244 parser.add_argument('--prop-file', metavar='PROP_FILE', dest='prop_file',
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001245 type=str, default=None,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001246 help='file containing the JSON representation of client '
1247 'properties')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001248 parser.add_argument('--download', metavar='FILE', dest='download', type=str,
1249 default=None, help='file to download')
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001250 parser.add_argument('--reset', dest='reset', default=False,
1251 action='store_true',
1252 help='reset ghost and reload all configs')
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001253 parser.add_argument('overlord_ip', metavar='OVERLORD_IP', type=str,
1254 nargs='*', help='overlord server address')
1255 args = parser.parse_args()
1256
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001257 if args.fork:
1258 ForkToBackground()
1259
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001260 if args.reset:
1261 GhostRPCServer().Reconnect()
1262 sys.exit()
1263
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001264 if args.download:
1265 DownloadFile(args.download)
1266
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001267 addrs = [('localhost', _OVERLORD_PORT)]
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001268 addrs += [(x, _OVERLORD_PORT) for x in args.overlord_ip]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001269
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001270 prop_file = os.path.abspath(args.prop_file) if args.prop_file else None
1271
1272 tls_settings = TLSSettings(args.tls_cert_file, args.enable_tls_without_verify)
1273 g = Ghost(addrs, tls_settings, Ghost.AGENT, args.mid,
1274 prop_file=prop_file)
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001275 g.Start(args.lan_disc, args.rpc_server)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001276
1277
1278if __name__ == '__main__':
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001279 try:
1280 main()
1281 except Exception as e:
1282 logging.error(e)