blob: f443afd795b9f2d830d6b4555e93bdebc221e701 [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
7"""Trainsition to release state directly without reboot."""
8
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
12import resource
13import shutil
14import signal
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080015import socket
Wei-Han Chene97d3532016-03-31 19:22:01 +080016import tempfile
17import textwrap
18import time
19
20import factory_common # pylint: disable=unused-import
21from cros.factory.gooftool import chroot
Wei-Han Chen0a3320e2016-04-23 01:32:07 +080022from cros.factory.gooftool.common import ExecFactoryPar
Wei-Han Chen9adf9de2016-04-01 19:35:41 +080023from cros.factory.gooftool.common import Util
Wei-Han Chen0a3320e2016-04-23 01:32:07 +080024from cros.factory.test.env import paths
Wei-Han Chene97d3532016-03-31 19:22:01 +080025from cros.factory.utils import process_utils
26from cros.factory.utils import sync_utils
27from cros.factory.utils import sys_utils
28
29
Wei-Han Chen9adf9de2016-04-01 19:35:41 +080030"""Directory of scripts for battery cut-off"""
31SCRIPT_DIR = '/usr/local/factory/sh'
32
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080033WIPE_IN_TMPFS_LOG = 'wipe_in_tmpfs.log'
Wei-Han Chene97d3532016-03-31 19:22:01 +080034
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080035def _CopyLogFileToStateDev(state_dev, logfile):
Wei-Han Chene97d3532016-03-31 19:22:01 +080036 with sys_utils.MountPartition(state_dev,
37 rw=True,
38 fstype='ext4') as mount_point:
39 shutil.copyfile(logfile,
40 os.path.join(mount_point, os.path.basename(logfile)))
41
42
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080043def _OnError(ip, port, token, state_dev, wipe_in_tmpfs_log=None,
44 wipe_init_log=None):
45 if wipe_in_tmpfs_log:
46 _CopyLogFileToStateDev(state_dev, wipe_in_tmpfs_log)
47 if wipe_init_log:
48 _CopyLogFileToStateDev(state_dev, wipe_init_log)
49 _InformStation(ip, port, token,
50 wipe_in_tmpfs_log=wipe_in_tmpfs_log,
51 wipe_init_log=wipe_init_log,
52 success=False)
53
54
Wei-Han Chene97d3532016-03-31 19:22:01 +080055def Daemonize(logfile=None):
56 """Starts a daemon process and terminates current process.
57
58 A daemon process will be started, and continue excuting the following codes.
59 The original process that calls this function will be terminated.
60
61 Example::
62
63 def DaemonFunc():
64 Daemonize()
65 # the process calling DaemonFunc is terminated.
66 # the following codes will be executed in a daemon process
67 ...
68
69 If you would like to keep the original process alive, you could fork a child
70 process and let child process start the daemon.
71 """
72 # fork from parent process
73 if os.fork():
74 # stop parent process
75 os._exit(0) # pylint: disable=protected-access
76
77 # decouple from parent process
78 os.chdir('/')
79 os.umask(0)
80 os.setsid()
81
82 # fork again
83 if os.fork():
84 os._exit(0) # pylint: disable=protected-access
85
86 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
87 if maxfd == resource.RLIM_INFINITY:
88 maxfd = 1024
89
90 for fd in xrange(maxfd):
91 try:
92 os.close(fd)
93 except OSError:
94 pass
95
96 # Reopen fd 0 (stdin), 1 (stdout), 2 (stderr) to prevent errors from reading
97 # or writing to these files.
98 # Since we have closed all file descriptors, os.open should open a file with
99 # file descriptor equals to 0
100 os.open('/dev/null', os.O_RDWR)
101 if logfile is None:
102 os.dup2(0, 1) # stdout
103 os.dup2(0, 2) # stderr
104 else:
105 os.open(logfile, os.O_RDWR | os.O_CREAT)
106 os.dup2(1, 2) # stderr
107
108
109def ResetLog(logfile=None):
110 if len(logging.getLogger().handlers) > 0:
111 for handler in logging.getLogger().handlers:
112 logging.getLogger().removeHandler(handler)
113 logging.basicConfig(filename=logfile, level=logging.NOTSET)
114
115
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800116def WipeInTmpFs(is_fast=None, cutoff_args=None, shopfloor_url=None,
117 station_ip=None, station_port=None, wipe_finish_token=None):
Wei-Han Chene97d3532016-03-31 19:22:01 +0800118 """prepare to wipe by pivot root to tmpfs and unmount statefull partition.
119
120 Args:
121 is_fast: whether or not to apply fast wipe.
122 cutoff_args: arguments to be passed to battery_cutoff.sh after wiping.
123 shopfloor_url: for inform_shopfloor.sh
124 """
125
Wei-Han Chene97d3532016-03-31 19:22:01 +0800126 Daemonize()
127
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800128 logfile = os.path.join('/tmp', WIPE_IN_TMPFS_LOG)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800129 ResetLog(logfile)
130
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800131 factory_par = paths.GetFactoryPythonArchivePath()
Wei-Han Chene97d3532016-03-31 19:22:01 +0800132
133 new_root = tempfile.mkdtemp(prefix='tmpfs.')
134 binary_deps = [
135 'activate_date', 'backlight_tool', 'busybox', 'cgpt', 'cgpt.bin',
136 'clobber-log', 'clobber-state', 'coreutils', 'crossystem', 'dd',
137 'display_boot_message', 'dumpe2fs', 'ectool', 'flashrom', 'halt',
138 'initctl', 'mkfs.ext4', 'mktemp', 'mosys', 'mount', 'mount-encrypted',
139 'od', 'pango-view', 'pkill', 'pv', 'python', 'reboot', 'setterm', 'sh',
140 'shutdown', 'stop', 'umount', 'vpd', 'wget', 'lsof', ]
141 if os.path.exists('/sbin/frecon'):
142 binary_deps.append('/sbin/frecon')
143 else:
144 binary_deps.append('/usr/bin/ply-image')
145
146 etc_issue = textwrap.dedent("""
147 You are now in tmp file system created for in-place wiping.
148
149 For debugging wiping fails, see log files under
150 /tmp
151 /mnt/stateful_partition/unencrypted
152
153 The log file name should be
154 - wipe_in_tmpfs.log
155 - wipe_init.log
156
157 You can also run scripts under /usr/local/factory/sh for wiping process.
158 """)
159
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800160 util = Util()
161
162 root_disk = util.GetPrimaryDevicePath()
163 release_rootfs = util.GetReleaseRootPartitionPath()
164 state_dev = util.GetPrimaryDevicePath(1)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800165 wipe_args = 'factory' + (' fast' if is_fast else '')
166
167 logging.debug('state_dev: %s', state_dev)
168 logging.debug('factory_par: %s', factory_par)
169
170 old_root = 'old_root'
171
172 try:
173 with chroot.TmpChroot(
174 new_root,
175 file_dir_list=[
176 '/bin', '/etc', '/lib', '/lib64', '/opt', '/root', '/sbin',
177 '/usr/share/fonts/notocjk',
178 '/usr/share/cache/fontconfig',
179 '/usr/share/chromeos-assets/images',
180 '/usr/share/chromeos-assets/text/boot_messages',
181 '/usr/share/misc/chromeos-common.sh',
182 '/usr/local/factory/sh',
183 factory_par],
184 binary_list=binary_deps, etc_issue=etc_issue).PivotRoot(old_root):
185 logging.debug(
186 'lsof: %s',
187 process_utils.SpawnOutput('lsof -p %d' % os.getpid(), shell=True))
188
Wei-Han Chene97d3532016-03-31 19:22:01 +0800189 process_utils.Spawn(['sync'], call=True)
190 time.sleep(3)
191
192 # Restart gooftool under new root. Since current gooftool might be using
193 # some resource under stateful partition, restarting gooftool ensures that
194 # everything new gooftool is using comes from tmpfs and we can safely
195 # unmount stateful partition.
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800196 args = []
197 if wipe_args:
198 args += ['--wipe_args', wipe_args]
199 if cutoff_args:
200 args += ['--cutoff_args', cutoff_args]
201 if shopfloor_url:
202 args += ['--shopfloor_url', shopfloor_url]
203 if station_ip:
204 args += ['--station_ip', station_ip]
205 if station_port:
206 args += ['--station_port', station_port]
207 if wipe_finish_token:
208 args += ['--wipe_finish_token', wipe_finish_token]
209 args += ['--state_dev', state_dev]
210 args += ['--release_rootfs', release_rootfs]
211 args += ['--root_disk', root_disk]
212 args += ['--old_root', old_root]
213
214 ExecFactoryPar('gooftool', 'wipe_init', *args)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800215 raise RuntimeError('Should not reach here')
216 except: # pylint: disable=bare-except
217 logging.exception('wipe_in_place failed')
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800218 _OnError(station_ip, station_port, wipe_finish_token, state_dev,
219 wipe_in_tmpfs_log=logfile, wipe_init_log=None)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800220 raise
221
222
223def _StopAllUpstartJobs(exclude_list=None):
224 logging.debug('stopping upstart jobs')
225
226 # Try three times to stop running services because some service will respawn
227 # one time after being stopped, e.g. shill_respawn. Two times should be enough
228 # to stop shill. Adding one more try for safety.
229
230 if exclude_list is None:
231 exclude_list = []
232
233 for unused_tries in xrange(3):
234 service_list = process_utils.SpawnOutput(['initctl', 'list']).splitlines()
235 service_list = [
236 line.split()[0] for line in service_list if 'start/running' in line]
237 for service in service_list:
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800238 if service in exclude_list or service.startswith('console-'):
Wei-Han Chene97d3532016-03-31 19:22:01 +0800239 continue
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800240 process_utils.Spawn(['stop', service], call=True, log=True)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800241
242
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800243def _UnmountStatefulPartition(root, state_dev):
Wei-Han Chene97d3532016-03-31 19:22:01 +0800244 logging.debug('unmount stateful partition')
Wei-Han Chene97d3532016-03-31 19:22:01 +0800245 # mount points that need chromeos_shutdown to umount
246
247 # 1. find mount points on stateful partition
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800248 mount_output = process_utils.SpawnOutput(['mount'], log=True)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800249
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800250 mount_point_list = []
251 for line in mount_output.splitlines():
252 fields = line.split()
253 if fields[0] == state_dev:
254 mount_point_list.append(fields[2])
255
256 logging.debug('stateful partitions mounted on: %s', mount_point_list)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800257 # 2. find processes that are using stateful partitions
258
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800259 def _ListProcOpening(path_list):
260 lsof_cmd = ['lsof', '-t'] + path_list
Wei-Han Chene97d3532016-03-31 19:22:01 +0800261 return [int(line)
262 for line in process_utils.SpawnOutput(lsof_cmd).splitlines()]
263
264 proc_list = _ListProcOpening(mount_point_list)
265
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800266 if os.getpid() in proc_list:
Wei-Han Chene97d3532016-03-31 19:22:01 +0800267 logging.error('wipe_init itself is using stateful partition')
268 logging.error(
269 'lsof: %s',
270 process_utils.SpawnOutput('lsof -p %d' % os.getpid(), shell=True))
271 raise RuntimeError('using stateful partition')
272
273 def _KillOpeningBySignal(sig):
274 proc_list = _ListProcOpening(mount_point_list)
275 if not proc_list:
276 return True # we are done
277 for pid in proc_list:
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800278 try:
279 os.kill(pid, sig)
280 except: # pylint: disable=bare-except
281 logging.exception('killing process %d failed', pid)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800282 return False # need to check again
283
284 sync_utils.Retry(10, 0.1, None, _KillOpeningBySignal, signal.SIGTERM)
285 sync_utils.Retry(10, 0.1, None, _KillOpeningBySignal, signal.SIGKILL)
286
287 proc_list = _ListProcOpening(mount_point_list)
288 assert not proc_list, "processes using stateful partition: %s" % proc_list
289
290 os.unlink(os.path.join(root, 'var', 'run'))
291 os.unlink(os.path.join(root, 'var', 'lock'))
292
293 if os.path.exists(os.path.join(root, 'dev', 'mapper', 'encstateful')):
294 def _UmountEncrypted():
295 try:
296 process_utils.Spawn(['mount-encrypted', 'umount'], check_call=True)
297 return True
298 except: # pylint: disable=bare-except
299 return False
300 sync_utils.Retry(10, 0.1, None, _UmountEncrypted)
301
302 for mount_point in mount_point_list:
303 process_utils.Spawn(['umount', '-n', '-R', mount_point], call=True)
304 process_utils.Spawn(['sync'], call=True)
305
306
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800307def _InformStation(ip, port, token, wipe_init_log=None,
308 wipe_in_tmpfs_log=None, success=True):
309 if not ip:
310 return
311 port = int(port)
312
313 logging.debug('inform station %s:%d', ip, port)
314
315 try:
316 sync_utils.WaitFor(
317 lambda: 0 == process_utils.Spawn(['ping', '-w1', '-c1', ip],
318 call=True).returncode,
319 timeout_secs=180, poll_interval=1)
320 except: # pylint: disable=bare-except
321 logging.exception('cannot get network connection...')
322 else:
323 sock = socket.socket()
324 sock.connect((ip, port))
325
326 response = dict(token=token, success=success)
327
328 if wipe_init_log:
329 with open(wipe_init_log) as f:
330 response['wipe_init_log'] = f.read()
331
332 if wipe_in_tmpfs_log:
333 with open(wipe_in_tmpfs_log) as f:
334 response['wipe_in_tmpfs_log'] = f.read()
335
336 sock.sendall(json.dumps(response) + '\n')
337 sock.close()
338
339
340def _WipeStateDev(release_rootfs, root_disk, wipe_args):
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800341 stateful_partition_path = '/mnt/stateful_partition'
342
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800343 clobber_state_env = os.environ.copy()
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800344 clobber_state_env.update(ROOT_DEV=release_rootfs,
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800345 ROOT_DISK=root_disk,
346 FACTORY_RETURN_AFTER_WIPING='YES')
347 logging.debug('clobber-state: root_dev=%s, root_disk=%s',
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800348 release_rootfs, root_disk)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800349 process_utils.Spawn(['clobber-state', wipe_args], env=clobber_state_env,
350 call=True)
351
352 shutil.move('/tmp/clobber-state.log', os.path.join(stateful_partition_path,
353 'unencrypted',
354 'clobber-state.log'))
355 # remove developer flag, which is created by clobber-state after wiping.
356 try:
357 os.unlink(os.path.join(stateful_partition_path, '.developer'))
358 except OSError:
359 pass
360
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800361def _EnableReleasePartition(release_rootfs):
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800362 logging.debug('enable release partition: %s', release_rootfs)
363 Util().EnableReleasePartition(release_rootfs)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800364
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800365
366def _InformShopfloor(shopfloor_url):
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800367 if shopfloor_url:
368 logging.debug('inform shopfloor %s', shopfloor_url)
369 proc = process_utils.Spawn([os.path.join(SCRIPT_DIR,
370 'inform_shopfloor.sh'),
371 shopfloor_url, 'factory_wipe'], check_call=True)
372 logging.debug('stdout: %s', proc.stdout_data)
373 logging.debug('stderr: %s', proc.stderr_data)
374
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800375
376def _BatteryCutoff(cutoff_args):
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800377 if cutoff_args is None:
378 cutoff_args = ''
379 logging.debug('battery_cutoff: args=%s', cutoff_args)
380 battery_cutoff_script = os.path.join(SCRIPT_DIR, 'battery_cutoff.sh')
381 process_utils.Spawn('%s %s' % (battery_cutoff_script, cutoff_args),
382 shell=True, check_call=True)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800383
384
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800385def WipeInit(wipe_args, cutoff_args, shopfloor_url, state_dev, release_rootfs,
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800386 root_disk, old_root, station_ip, station_port, finish_token):
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800387 Daemonize()
Wei-Han Chene97d3532016-03-31 19:22:01 +0800388 logfile = '/tmp/wipe_init.log'
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800389 wipe_in_tmpfs_log = os.path.join(old_root, 'tmp', WIPE_IN_TMPFS_LOG)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800390 ResetLog(logfile)
391
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800392 logging.debug('wipe_args: %s', wipe_args)
393 logging.debug('cutoff_args: %s', cutoff_args)
394 logging.debug('shopfloor_url: %s', shopfloor_url)
395 logging.debug('state_dev: %s', state_dev)
396 logging.debug('release_rootfs: %s', release_rootfs)
397 logging.debug('root_disk: %s', root_disk)
398 logging.debug('old_root: %s', old_root)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800399
Wei-Han Chene97d3532016-03-31 19:22:01 +0800400 try:
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800401 _StopAllUpstartJobs(exclude_list=['boot-services', 'dbus', 'factory-wipe',
402 'shill', 'openssh-server'])
403 _UnmountStatefulPartition(old_root, state_dev)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800404
405 process_utils.Spawn([os.path.join(SCRIPT_DIR, 'display_wipe_message.sh'),
406 'wipe'], call=True)
407
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800408 _WipeStateDev(release_rootfs, root_disk, wipe_args)
409
410 _EnableReleasePartition(release_rootfs)
411
412 _InformShopfloor(shopfloor_url)
413
414 _InformStation(station_ip, station_port, finish_token,
415 wipe_init_log=logfile,
416 wipe_in_tmpfs_log=wipe_in_tmpfs_log,
417 success=True)
418
419 _BatteryCutoff(cutoff_args)
420
421 # should not reach here
422 time.sleep(1e8)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800423 except: # pylint: disable=bare-except
424 logging.exception('wipe_init failed')
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800425 _OnError(station_ip, station_port, finish_token, state_dev,
426 wipe_in_tmpfs_log=wipe_in_tmpfs_log, wipe_init_log=logfile)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800427 raise