blob: 1e75a01cba99ae0f9ce82d5bae5d26fcc5374800 [file] [log] [blame]
Wei-Han Chene97d3532016-03-31 19:22:01 +08001#!/usr/bin/env python
2# -*- coding: UTF-8 -*-
3# Copyright 2016 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Joel Kitching679a00b2016-08-03 11:42:58 +08007"""Transition to release state directly without reboot."""
Wei-Han Chene97d3532016-03-31 19:22:01 +08008
Wei-Han Chenbe1355a2016-04-24 19:31:03 +08009import json
Wei-Han Chene97d3532016-03-31 19:22:01 +080010import logging
Wei-Han Chene97d3532016-03-31 19:22:01 +080011import os
Shun-Hsing Oub5724832016-07-21 11:45:58 +080012import re
Wei-Han Chene97d3532016-03-31 19:22:01 +080013import resource
14import shutil
15import signal
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080016import socket
Wei-Han Chene97d3532016-03-31 19:22:01 +080017import tempfile
18import textwrap
19import time
20
21import factory_common # pylint: disable=unused-import
22from cros.factory.gooftool import chroot
Wei-Han Chen0a3320e2016-04-23 01:32:07 +080023from cros.factory.gooftool.common import ExecFactoryPar
Wei-Han Chen9adf9de2016-04-01 19:35:41 +080024from cros.factory.gooftool.common import Util
Wei-Han Chen0a3320e2016-04-23 01:32:07 +080025from cros.factory.test.env import paths
Wei-Han Chene97d3532016-03-31 19:22:01 +080026from cros.factory.utils import process_utils
27from cros.factory.utils import sync_utils
28from cros.factory.utils import sys_utils
29
30
Wei-Han Chen9adf9de2016-04-01 19:35:41 +080031"""Directory of scripts for battery cut-off"""
32SCRIPT_DIR = '/usr/local/factory/sh'
33
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080034WIPE_IN_TMPFS_LOG = 'wipe_in_tmpfs.log'
Wei-Han Chene97d3532016-03-31 19:22:01 +080035
Joel Kitching679a00b2016-08-03 11:42:58 +080036
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080037def _CopyLogFileToStateDev(state_dev, logfile):
Wei-Han Chene97d3532016-03-31 19:22:01 +080038 with sys_utils.MountPartition(state_dev,
39 rw=True,
40 fstype='ext4') as mount_point:
41 shutil.copyfile(logfile,
42 os.path.join(mount_point, os.path.basename(logfile)))
43
44
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080045def _OnError(ip, port, token, state_dev, wipe_in_tmpfs_log=None,
46 wipe_init_log=None):
47 if wipe_in_tmpfs_log:
48 _CopyLogFileToStateDev(state_dev, wipe_in_tmpfs_log)
49 if wipe_init_log:
50 _CopyLogFileToStateDev(state_dev, wipe_init_log)
51 _InformStation(ip, port, token,
52 wipe_in_tmpfs_log=wipe_in_tmpfs_log,
53 wipe_init_log=wipe_init_log,
54 success=False)
55
56
Wei-Han Chene97d3532016-03-31 19:22:01 +080057def Daemonize(logfile=None):
58 """Starts a daemon process and terminates current process.
59
60 A daemon process will be started, and continue excuting the following codes.
61 The original process that calls this function will be terminated.
62
63 Example::
64
65 def DaemonFunc():
66 Daemonize()
67 # the process calling DaemonFunc is terminated.
68 # the following codes will be executed in a daemon process
69 ...
70
71 If you would like to keep the original process alive, you could fork a child
72 process and let child process start the daemon.
73 """
74 # fork from parent process
75 if os.fork():
76 # stop parent process
77 os._exit(0) # pylint: disable=protected-access
78
79 # decouple from parent process
80 os.chdir('/')
81 os.umask(0)
82 os.setsid()
83
84 # fork again
85 if os.fork():
86 os._exit(0) # pylint: disable=protected-access
87
88 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
89 if maxfd == resource.RLIM_INFINITY:
90 maxfd = 1024
91
92 for fd in xrange(maxfd):
93 try:
94 os.close(fd)
95 except OSError:
96 pass
97
98 # Reopen fd 0 (stdin), 1 (stdout), 2 (stderr) to prevent errors from reading
99 # or writing to these files.
100 # Since we have closed all file descriptors, os.open should open a file with
101 # file descriptor equals to 0
102 os.open('/dev/null', os.O_RDWR)
103 if logfile is None:
104 os.dup2(0, 1) # stdout
105 os.dup2(0, 2) # stderr
106 else:
107 os.open(logfile, os.O_RDWR | os.O_CREAT)
108 os.dup2(1, 2) # stderr
109
110
111def ResetLog(logfile=None):
112 if len(logging.getLogger().handlers) > 0:
113 for handler in logging.getLogger().handlers:
114 logging.getLogger().removeHandler(handler)
115 logging.basicConfig(filename=logfile, level=logging.NOTSET)
116
117
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800118def WipeInTmpFs(is_fast=None, cutoff_args=None, shopfloor_url=None,
119 station_ip=None, station_port=None, wipe_finish_token=None):
Wei-Han Chene97d3532016-03-31 19:22:01 +0800120 """prepare to wipe by pivot root to tmpfs and unmount statefull partition.
121
122 Args:
123 is_fast: whether or not to apply fast wipe.
124 cutoff_args: arguments to be passed to battery_cutoff.sh after wiping.
125 shopfloor_url: for inform_shopfloor.sh
126 """
127
Wei-Han Chene97d3532016-03-31 19:22:01 +0800128 Daemonize()
129
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800130 # Set the defual umask.
131 os.umask(0022)
132
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800133 logfile = os.path.join('/tmp', WIPE_IN_TMPFS_LOG)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800134 ResetLog(logfile)
135
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800136 factory_par = paths.GetFactoryPythonArchivePath()
Wei-Han Chene97d3532016-03-31 19:22:01 +0800137
138 new_root = tempfile.mkdtemp(prefix='tmpfs.')
139 binary_deps = [
140 'activate_date', 'backlight_tool', 'busybox', 'cgpt', 'cgpt.bin',
141 'clobber-log', 'clobber-state', 'coreutils', 'crossystem', 'dd',
142 'display_boot_message', 'dumpe2fs', 'ectool', 'flashrom', 'halt',
143 'initctl', 'mkfs.ext4', 'mktemp', 'mosys', 'mount', 'mount-encrypted',
144 'od', 'pango-view', 'pkill', 'pv', 'python', 'reboot', 'setterm', 'sh',
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800145 'shutdown', 'stop', 'umount', 'vpd', 'wget', 'lsof']
Wei-Han Chene97d3532016-03-31 19:22:01 +0800146 if os.path.exists('/sbin/frecon'):
147 binary_deps.append('/sbin/frecon')
148 else:
149 binary_deps.append('/usr/bin/ply-image')
150
151 etc_issue = textwrap.dedent("""
152 You are now in tmp file system created for in-place wiping.
153
154 For debugging wiping fails, see log files under
155 /tmp
156 /mnt/stateful_partition/unencrypted
157
158 The log file name should be
159 - wipe_in_tmpfs.log
160 - wipe_init.log
161
162 You can also run scripts under /usr/local/factory/sh for wiping process.
163 """)
164
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800165 util = Util()
166
167 root_disk = util.GetPrimaryDevicePath()
168 release_rootfs = util.GetReleaseRootPartitionPath()
169 state_dev = util.GetPrimaryDevicePath(1)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800170 wipe_args = 'factory' + (' fast' if is_fast else '')
171
172 logging.debug('state_dev: %s', state_dev)
173 logging.debug('factory_par: %s', factory_par)
174
175 old_root = 'old_root'
176
177 try:
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800178 # pango load library module dynamically. Therefore we need to query it
179 # first.
180 pango_query_output = process_utils.SpawnOutput(
181 ['pango-querymodules', '--system'])
182 m = re.search(r'^# ModulesPath = (.+)$', pango_query_output, re.M)
183 assert m != None, 'Failed to find pango module path.'
184 pango_module = m.group(1)
185
Wei-Han Chene97d3532016-03-31 19:22:01 +0800186 with chroot.TmpChroot(
187 new_root,
188 file_dir_list=[
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800189 # Basic rootfs.
Wei-Ning Huang71f94e12016-07-17 23:21:41 +0800190 '/bin', '/etc', '/lib', '/lib64', '/root', '/sbin',
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800191 # Factory related scripts.
192 factory_par,
193 '/usr/local/factory/sh',
194 # Fonts and assets required for showing message.
195 pango_module,
Wei-Han Chene97d3532016-03-31 19:22:01 +0800196 '/usr/share/fonts/notocjk',
197 '/usr/share/cache/fontconfig',
198 '/usr/share/chromeos-assets/images',
199 '/usr/share/chromeos-assets/text/boot_messages',
200 '/usr/share/misc/chromeos-common.sh',
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800201 # File required for enable ssh connection.
202 '/mnt/stateful_partition/etc/ssh',
203 '/root/.ssh',
204 '/usr/share/chromeos-ssh-config',
205 # /var/empty is required by openssh server.
206 '/var/empty'],
Wei-Han Chene97d3532016-03-31 19:22:01 +0800207 binary_list=binary_deps, etc_issue=etc_issue).PivotRoot(old_root):
208 logging.debug(
209 'lsof: %s',
210 process_utils.SpawnOutput('lsof -p %d' % os.getpid(), shell=True))
211
Wei-Han Chene97d3532016-03-31 19:22:01 +0800212 process_utils.Spawn(['sync'], call=True)
213 time.sleep(3)
214
215 # Restart gooftool under new root. Since current gooftool might be using
216 # some resource under stateful partition, restarting gooftool ensures that
217 # everything new gooftool is using comes from tmpfs and we can safely
218 # unmount stateful partition.
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800219 args = []
220 if wipe_args:
221 args += ['--wipe_args', wipe_args]
222 if cutoff_args:
223 args += ['--cutoff_args', cutoff_args]
224 if shopfloor_url:
225 args += ['--shopfloor_url', shopfloor_url]
226 if station_ip:
227 args += ['--station_ip', station_ip]
228 if station_port:
229 args += ['--station_port', station_port]
230 if wipe_finish_token:
231 args += ['--wipe_finish_token', wipe_finish_token]
232 args += ['--state_dev', state_dev]
233 args += ['--release_rootfs', release_rootfs]
234 args += ['--root_disk', root_disk]
235 args += ['--old_root', old_root]
236
237 ExecFactoryPar('gooftool', 'wipe_init', *args)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800238 raise RuntimeError('Should not reach here')
239 except: # pylint: disable=bare-except
240 logging.exception('wipe_in_place failed')
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800241 _OnError(station_ip, station_port, wipe_finish_token, state_dev,
242 wipe_in_tmpfs_log=logfile, wipe_init_log=None)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800243 raise
244
245
246def _StopAllUpstartJobs(exclude_list=None):
247 logging.debug('stopping upstart jobs')
248
249 # Try three times to stop running services because some service will respawn
250 # one time after being stopped, e.g. shill_respawn. Two times should be enough
251 # to stop shill. Adding one more try for safety.
252
253 if exclude_list is None:
254 exclude_list = []
255
256 for unused_tries in xrange(3):
257 service_list = process_utils.SpawnOutput(['initctl', 'list']).splitlines()
258 service_list = [
259 line.split()[0] for line in service_list if 'start/running' in line]
260 for service in service_list:
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800261 if service in exclude_list or service.startswith('console-'):
Wei-Han Chene97d3532016-03-31 19:22:01 +0800262 continue
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800263 process_utils.Spawn(['stop', service], call=True, log=True)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800264
265
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800266def _UnmountStatefulPartition(root, state_dev):
Wei-Han Chene97d3532016-03-31 19:22:01 +0800267 logging.debug('unmount stateful partition')
Wei-Han Chene97d3532016-03-31 19:22:01 +0800268 # mount points that need chromeos_shutdown to umount
269
270 # 1. find mount points on stateful partition
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800271 mount_output = process_utils.SpawnOutput(['mount'], log=True)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800272
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800273 mount_point_list = []
274 for line in mount_output.splitlines():
275 fields = line.split()
276 if fields[0] == state_dev:
277 mount_point_list.append(fields[2])
278
279 logging.debug('stateful partitions mounted on: %s', mount_point_list)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800280 # 2. find processes that are using stateful partitions
281
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800282 def _ListProcOpening(path_list):
283 lsof_cmd = ['lsof', '-t'] + path_list
Wei-Han Chene97d3532016-03-31 19:22:01 +0800284 return [int(line)
285 for line in process_utils.SpawnOutput(lsof_cmd).splitlines()]
286
287 proc_list = _ListProcOpening(mount_point_list)
288
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800289 if os.getpid() in proc_list:
Wei-Han Chene97d3532016-03-31 19:22:01 +0800290 logging.error('wipe_init itself is using stateful partition')
291 logging.error(
292 'lsof: %s',
293 process_utils.SpawnOutput('lsof -p %d' % os.getpid(), shell=True))
294 raise RuntimeError('using stateful partition')
295
296 def _KillOpeningBySignal(sig):
297 proc_list = _ListProcOpening(mount_point_list)
298 if not proc_list:
299 return True # we are done
300 for pid in proc_list:
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800301 try:
302 os.kill(pid, sig)
303 except: # pylint: disable=bare-except
304 logging.exception('killing process %d failed', pid)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800305 return False # need to check again
306
307 sync_utils.Retry(10, 0.1, None, _KillOpeningBySignal, signal.SIGTERM)
308 sync_utils.Retry(10, 0.1, None, _KillOpeningBySignal, signal.SIGKILL)
309
310 proc_list = _ListProcOpening(mount_point_list)
311 assert not proc_list, "processes using stateful partition: %s" % proc_list
312
313 os.unlink(os.path.join(root, 'var', 'run'))
314 os.unlink(os.path.join(root, 'var', 'lock'))
315
316 if os.path.exists(os.path.join(root, 'dev', 'mapper', 'encstateful')):
You-Cheng Syuf0990462016-09-07 14:56:19 +0800317 # Doing what 'mount-encrypted umount' should do.
318 for mount_point in mount_point_list:
319 process_utils.Spawn(['umount', '-n', '-R', mount_point], call=True)
320 process_utils.Spawn(['umount', '-nR', os.path.join(root, 'var')],
321 check_call=True)
322 process_utils.Spawn(['dmsetup', 'remove', 'encstateful'], check_call=True)
323 process_utils.Spawn(['losetup', '-D'], check_call=True)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800324
325 for mount_point in mount_point_list:
326 process_utils.Spawn(['umount', '-n', '-R', mount_point], call=True)
327 process_utils.Spawn(['sync'], call=True)
328
You-Cheng Syuf0990462016-09-07 14:56:19 +0800329 # Check if the stateful partition is unmounted successfully.
330 process_utils.Spawn(r'mount | grep -c "^\S*stateful" | grep -q ^0$',
331 shell=True, check_call=True)
332
Wei-Han Chene97d3532016-03-31 19:22:01 +0800333
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800334def _InformStation(ip, port, token, wipe_init_log=None,
335 wipe_in_tmpfs_log=None, success=True):
336 if not ip:
337 return
338 port = int(port)
339
340 logging.debug('inform station %s:%d', ip, port)
341
342 try:
343 sync_utils.WaitFor(
344 lambda: 0 == process_utils.Spawn(['ping', '-w1', '-c1', ip],
345 call=True).returncode,
346 timeout_secs=180, poll_interval=1)
347 except: # pylint: disable=bare-except
348 logging.exception('cannot get network connection...')
349 else:
350 sock = socket.socket()
351 sock.connect((ip, port))
352
353 response = dict(token=token, success=success)
354
355 if wipe_init_log:
356 with open(wipe_init_log) as f:
357 response['wipe_init_log'] = f.read()
358
359 if wipe_in_tmpfs_log:
360 with open(wipe_in_tmpfs_log) as f:
361 response['wipe_in_tmpfs_log'] = f.read()
362
363 sock.sendall(json.dumps(response) + '\n')
364 sock.close()
365
366
367def _WipeStateDev(release_rootfs, root_disk, wipe_args):
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800368 stateful_partition_path = '/mnt/stateful_partition'
369
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800370 clobber_state_env = os.environ.copy()
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800371 clobber_state_env.update(ROOT_DEV=release_rootfs,
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800372 ROOT_DISK=root_disk,
373 FACTORY_RETURN_AFTER_WIPING='YES')
374 logging.debug('clobber-state: root_dev=%s, root_disk=%s',
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800375 release_rootfs, root_disk)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800376 process_utils.Spawn(['clobber-state', wipe_args], env=clobber_state_env,
377 call=True)
378
379 shutil.move('/tmp/clobber-state.log', os.path.join(stateful_partition_path,
380 'unencrypted',
381 'clobber-state.log'))
382 # remove developer flag, which is created by clobber-state after wiping.
383 try:
384 os.unlink(os.path.join(stateful_partition_path, '.developer'))
385 except OSError:
386 pass
387
Joel Kitching679a00b2016-08-03 11:42:58 +0800388
Earl Ou564a7872016-10-05 10:22:00 +0800389def EnableReleasePartition(release_rootfs):
390 """Enables a release image partition on disk."""
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800391 logging.debug('enable release partition: %s', release_rootfs)
392 Util().EnableReleasePartition(release_rootfs)
Earl Ou564a7872016-10-05 10:22:00 +0800393 logging.debug('Device will boot from %s after reboot.', release_rootfs)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800394
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800395
396def _InformShopfloor(shopfloor_url):
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800397 if shopfloor_url:
398 logging.debug('inform shopfloor %s', shopfloor_url)
399 proc = process_utils.Spawn([os.path.join(SCRIPT_DIR,
400 'inform_shopfloor.sh'),
401 shopfloor_url, 'factory_wipe'], check_call=True)
402 logging.debug('stdout: %s', proc.stdout_data)
403 logging.debug('stderr: %s', proc.stderr_data)
404
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800405
406def _BatteryCutoff(cutoff_args):
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800407 if cutoff_args is None:
408 cutoff_args = ''
409 logging.debug('battery_cutoff: args=%s', cutoff_args)
410 battery_cutoff_script = os.path.join(SCRIPT_DIR, 'battery_cutoff.sh')
411 process_utils.Spawn('%s %s' % (battery_cutoff_script, cutoff_args),
412 shell=True, check_call=True)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800413
414
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800415def WipeInit(wipe_args, cutoff_args, shopfloor_url, state_dev, release_rootfs,
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800416 root_disk, old_root, station_ip, station_port, finish_token):
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800417 Daemonize()
Wei-Han Chene97d3532016-03-31 19:22:01 +0800418 logfile = '/tmp/wipe_init.log'
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800419 wipe_in_tmpfs_log = os.path.join(old_root, 'tmp', WIPE_IN_TMPFS_LOG)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800420 ResetLog(logfile)
421
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800422 logging.debug('wipe_args: %s', wipe_args)
423 logging.debug('cutoff_args: %s', cutoff_args)
424 logging.debug('shopfloor_url: %s', shopfloor_url)
425 logging.debug('state_dev: %s', state_dev)
426 logging.debug('release_rootfs: %s', release_rootfs)
427 logging.debug('root_disk: %s', root_disk)
428 logging.debug('old_root: %s', old_root)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800429
Wei-Han Chene97d3532016-03-31 19:22:01 +0800430 try:
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800431 _StopAllUpstartJobs(exclude_list=[
432 # Milestone marker that use to determine the running of other services.
433 'boot-services',
434 'system-services',
435 'failsafe',
436 # Keep dbus to make sure we can shutdown the device.
437 'dbus',
438 # Keep shill for connecting to shopfloor or stations.
439 'shill',
440 # Keep openssh-server for debugging purpose.
441 'openssh-server',
442 # sslh is a service in ARC++ for muxing between ssh and adb.
443 'sslh'
444 ])
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800445 _UnmountStatefulPartition(old_root, state_dev)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800446
447 process_utils.Spawn([os.path.join(SCRIPT_DIR, 'display_wipe_message.sh'),
448 'wipe'], call=True)
449
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800450 _WipeStateDev(release_rootfs, root_disk, wipe_args)
451
Earl Ou564a7872016-10-05 10:22:00 +0800452 EnableReleasePartition(release_rootfs)
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800453
454 _InformShopfloor(shopfloor_url)
455
456 _InformStation(station_ip, station_port, finish_token,
457 wipe_init_log=logfile,
458 wipe_in_tmpfs_log=wipe_in_tmpfs_log,
459 success=True)
460
461 _BatteryCutoff(cutoff_args)
462
463 # should not reach here
464 time.sleep(1e8)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800465 except: # pylint: disable=bare-except
466 logging.exception('wipe_init failed')
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800467 _OnError(station_ip, station_port, finish_token, state_dev,
468 wipe_in_tmpfs_log=wipe_in_tmpfs_log, wipe_init_log=logfile)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800469 raise