blob: 793f2ed752eea555aab0b3502ace98fbf13cccba [file] [log] [blame]
Wei-Han Chene97d3532016-03-31 19:22:01 +08001# Copyright 2016 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Joel Kitching679a00b2016-08-03 11:42:58 +08005"""Transition to release state directly without reboot."""
Wei-Han Chene97d3532016-03-31 19:22:01 +08006
Wei-Han Chenbe1355a2016-04-24 19:31:03 +08007import json
Wei-Han Chene97d3532016-03-31 19:22:01 +08008import logging
Wei-Han Chene97d3532016-03-31 19:22:01 +08009import os
10import resource
11import shutil
12import signal
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080013import socket
Wei-Han Chene97d3532016-03-31 19:22:01 +080014import tempfile
15import textwrap
16import time
17
Wei-Han Chene97d3532016-03-31 19:22:01 +080018from cros.factory.gooftool import chroot
Wei-Han Chen0a3320e2016-04-23 01:32:07 +080019from cros.factory.gooftool.common import ExecFactoryPar
Shen-En Shih502b3102018-04-24 11:12:01 +080020from cros.factory.gooftool.common import Shell
Wei-Han Chen9adf9de2016-04-01 19:35:41 +080021from cros.factory.gooftool.common import Util
Wei-Han Chen0a3320e2016-04-23 01:32:07 +080022from cros.factory.test.env import paths
Wei-Han Chenb05699a2017-07-12 16:37:47 +080023from cros.factory.utils import file_utils
Wei-Han Chene97d3532016-03-31 19:22:01 +080024from cros.factory.utils import process_utils
25from cros.factory.utils import sync_utils
26from cros.factory.utils import sys_utils
27
28
Hung-Te Lina3195462016-10-14 15:48:29 +080029CUTOFF_SCRIPT_DIR = '/usr/local/factory/sh/cutoff'
Peter Shih18898302018-03-05 15:32:58 +080030"""Directory of scripts for device cut-off"""
Wei-Han Chen9adf9de2016-04-01 19:35:41 +080031
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080032WIPE_IN_TMPFS_LOG = 'wipe_in_tmpfs.log'
Wei-Han Chene97d3532016-03-31 19:22:01 +080033
Wei-Han Chenb05699a2017-07-12 16:37:47 +080034STATEFUL_PARTITION_PATH = '/mnt/stateful_partition/'
35
36WIPE_MARK_FILE = 'wipe_mark_file'
37
Cheng Yuehe84775f2020-02-12 14:03:35 +080038CRX_CACHE_PAYLOAD_NAME = 'dev_image/opt/cros_payloads/release_image.crx_cache'
Hung-Te Lindd3425d2017-07-12 20:10:52 +080039CRX_CACHE_TAR_PATH = '/tmp/crx_cache.tar'
Wei-Han Chenb05699a2017-07-12 16:37:47 +080040
Peter Shihe6afab32018-09-11 17:16:48 +080041class WipeError(Exception):
Wei-Han Chenb05699a2017-07-12 16:37:47 +080042 """Failed to complete wiping."""
43
Joel Kitching679a00b2016-08-03 11:42:58 +080044
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080045def _CopyLogFileToStateDev(state_dev, logfile):
Wei-Han Chene97d3532016-03-31 19:22:01 +080046 with sys_utils.MountPartition(state_dev,
47 rw=True,
48 fstype='ext4') as mount_point:
49 shutil.copyfile(logfile,
50 os.path.join(mount_point, os.path.basename(logfile)))
51
52
Wei-Han Chenbe1355a2016-04-24 19:31:03 +080053def _OnError(ip, port, token, state_dev, wipe_in_tmpfs_log=None,
54 wipe_init_log=None):
55 if wipe_in_tmpfs_log:
56 _CopyLogFileToStateDev(state_dev, wipe_in_tmpfs_log)
57 if wipe_init_log:
58 _CopyLogFileToStateDev(state_dev, wipe_init_log)
59 _InformStation(ip, port, token,
60 wipe_in_tmpfs_log=wipe_in_tmpfs_log,
61 wipe_init_log=wipe_init_log,
62 success=False)
63
64
Wei-Han Chene97d3532016-03-31 19:22:01 +080065def Daemonize(logfile=None):
66 """Starts a daemon process and terminates current process.
67
You-Cheng Syu461ec032017-03-06 15:56:58 +080068 A daemon process will be started, and continue executing the following codes.
Wei-Han Chene97d3532016-03-31 19:22:01 +080069 The original process that calls this function will be terminated.
70
71 Example::
72
73 def DaemonFunc():
74 Daemonize()
75 # the process calling DaemonFunc is terminated.
76 # the following codes will be executed in a daemon process
77 ...
78
79 If you would like to keep the original process alive, you could fork a child
80 process and let child process start the daemon.
81 """
82 # fork from parent process
83 if os.fork():
84 # stop parent process
85 os._exit(0) # pylint: disable=protected-access
86
87 # decouple from parent process
88 os.chdir('/')
89 os.umask(0)
90 os.setsid()
91
92 # fork again
93 if os.fork():
94 os._exit(0) # pylint: disable=protected-access
95
96 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
97 if maxfd == resource.RLIM_INFINITY:
98 maxfd = 1024
99
Yilin Yangbf84d2e2020-05-13 10:34:46 +0800100 for fd in range(maxfd):
Wei-Han Chene97d3532016-03-31 19:22:01 +0800101 try:
102 os.close(fd)
103 except OSError:
104 pass
105
106 # Reopen fd 0 (stdin), 1 (stdout), 2 (stderr) to prevent errors from reading
107 # or writing to these files.
108 # Since we have closed all file descriptors, os.open should open a file with
109 # file descriptor equals to 0
110 os.open('/dev/null', os.O_RDWR)
111 if logfile is None:
112 os.dup2(0, 1) # stdout
113 os.dup2(0, 2) # stderr
114 else:
115 os.open(logfile, os.O_RDWR | os.O_CREAT)
116 os.dup2(1, 2) # stderr
117
Meng-Huan Yu818a7412020-12-16 16:44:54 +0800118 # Set the default umask.
119 os.umask(0o022)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800120
121def ResetLog(logfile=None):
Peter Shih19a938f2018-02-26 14:26:16 +0800122 if logging.getLogger().handlers:
Wei-Han Chene97d3532016-03-31 19:22:01 +0800123 for handler in logging.getLogger().handlers:
124 logging.getLogger().removeHandler(handler)
Meng-Huan Yu345698d2020-04-30 15:18:44 +0800125 log_format = '[%(asctime)-15s] %(levelname)s:%(name)s:%(message)s'
126 # logging.NOTSET is the lowerest level.
127 logging.basicConfig(filename=logfile, level=logging.NOTSET, format=log_format)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800128
129
Hung-Te Lin7b27f0c2016-10-18 18:41:29 +0800130def WipeInTmpFs(is_fast=None, shopfloor_url=None, station_ip=None,
Wei-Han Chenf3924112019-02-25 14:52:58 +0800131 station_port=None, wipe_finish_token=None,
Meng-Huan Yu7a4f0f52020-01-07 20:11:01 +0800132 keep_developer_mode_flag=False, test_umount=False):
You-Cheng Syu461ec032017-03-06 15:56:58 +0800133 """prepare to wipe by pivot root to tmpfs and unmount stateful partition.
Wei-Han Chene97d3532016-03-31 19:22:01 +0800134
135 Args:
136 is_fast: whether or not to apply fast wipe.
Wei-Han Chene97d3532016-03-31 19:22:01 +0800137 shopfloor_url: for inform_shopfloor.sh
138 """
139
Shen-En Shih502b3102018-04-24 11:12:01 +0800140 def _CheckBug78323428():
141 # b/78323428: Check if dhcpcd is locking /var/run. If dhcpcd is locking
142 # /var/run, unmount will fail. Need CL:1021611 to use /run instead.
143 for pid in Shell('pgrep dhcpcd').stdout.splitlines():
144 lock_result = Shell('ls -al /proc/%s/fd | grep /var/run' % pid)
145 if lock_result.stdout:
146 raise WipeError('dhcpcd is still locking on /var/run. Please use a '
147 'newer ChromeOS image with CL:1021611 included. '
148 'Lock info: "%s"' % lock_result.stdout)
149 _CheckBug78323428()
150
Wei-Han Chene97d3532016-03-31 19:22:01 +0800151 Daemonize()
152
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800153 logfile = os.path.join('/tmp', WIPE_IN_TMPFS_LOG)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800154 ResetLog(logfile)
155
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800156 factory_par = paths.GetFactoryPythonArchivePath()
Wei-Han Chene97d3532016-03-31 19:22:01 +0800157
158 new_root = tempfile.mkdtemp(prefix='tmpfs.')
159 binary_deps = [
Allen Webb363823f2020-10-20 07:54:13 -0700160 'activate_date', 'backlight_tool', 'bash', 'busybox', 'cgpt', 'cgpt.bin',
Wei-Han Chene97d3532016-03-31 19:22:01 +0800161 'clobber-log', 'clobber-state', 'coreutils', 'crossystem', 'dd',
162 'display_boot_message', 'dumpe2fs', 'ectool', 'flashrom', 'halt',
163 'initctl', 'mkfs.ext4', 'mktemp', 'mosys', 'mount', 'mount-encrypted',
164 'od', 'pango-view', 'pkill', 'pv', 'python', 'reboot', 'setterm', 'sh',
Cheng-Han Yang6f12dc42017-11-30 15:28:38 +0800165 'shutdown', 'stop', 'umount', 'vpd', 'curl', 'lsof', 'jq', '/sbin/frecon',
Allen Webb363823f2020-10-20 07:54:13 -0700166 'stressapptest', 'fuser', 'login'
167 ]
Wei-Han Chene97d3532016-03-31 19:22:01 +0800168
169 etc_issue = textwrap.dedent("""
170 You are now in tmp file system created for in-place wiping.
171
172 For debugging wiping fails, see log files under
173 /tmp
174 /mnt/stateful_partition/unencrypted
175
176 The log file name should be
177 - wipe_in_tmpfs.log
178 - wipe_init.log
179
180 You can also run scripts under /usr/local/factory/sh for wiping process.
181 """)
182
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800183 util = Util()
184
185 root_disk = util.GetPrimaryDevicePath()
186 release_rootfs = util.GetReleaseRootPartitionPath()
187 state_dev = util.GetPrimaryDevicePath(1)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800188 wipe_args = 'factory' + (' fast' if is_fast else '')
189
190 logging.debug('state_dev: %s', state_dev)
191 logging.debug('factory_par: %s', factory_par)
192
193 old_root = 'old_root'
194
195 try:
196 with chroot.TmpChroot(
197 new_root,
198 file_dir_list=[
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800199 # Basic rootfs.
Stephen Boyd14d6ca42021-04-19 09:17:06 -0700200 '/bin',
201 '/etc',
202 '/lib',
203 '/lib64',
204 '/root',
205 '/sbin',
206 '/usr/sbin',
207 '/usr/bin',
208 '/usr/lib',
209 '/usr/lib64',
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800210 # Factory related scripts.
211 factory_par,
212 '/usr/local/factory/sh',
Wei-Han Chen85ace052017-06-24 15:39:50 +0800213 # Factory config files
214 '/usr/local/factory/py/config',
Wei-Han Chene97d3532016-03-31 19:22:01 +0800215 '/usr/share/fonts/notocjk',
216 '/usr/share/cache/fontconfig',
217 '/usr/share/chromeos-assets/images',
218 '/usr/share/chromeos-assets/text/boot_messages',
219 '/usr/share/misc/chromeos-common.sh',
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800220 # File required for enable ssh connection.
221 '/mnt/stateful_partition/etc/ssh',
222 '/root/.ssh',
Meng-Huan Yu35af9ae2020-06-01 16:54:47 +0800223 '/usr/share/chromeos-ssh-config',
224 # /mnt/empty is required by openssh server.
Stephen Boyd14d6ca42021-04-19 09:17:06 -0700225 '/mnt/empty',
226 ],
227 binary_list=binary_deps,
228 etc_issue=etc_issue).PivotRoot(old_root):
Meng-Huan Yu9eca9082020-06-23 16:42:32 +0800229 logging.debug('ps -aux: %s', process_utils.SpawnOutput(['ps', '-aux']))
Wei-Han Chene97d3532016-03-31 19:22:01 +0800230 logging.debug(
231 'lsof: %s',
232 process_utils.SpawnOutput('lsof -p %d' % os.getpid(), shell=True))
233
Hung-Te Lin6ce54bd2017-06-27 16:20:36 +0800234 # Modify display_wipe_message so we have shells in VT2.
235 # --dev-mode provides shell with etc-issue.
236 # --enable-vt1 allows drawing escapes (OSC) on VT1 but it'll also display
237 # etc-issue and login prompt.
238 # For now we only want login prompts on VT2+.
239 process_utils.Spawn(['sed', '-i',
240 's/--no-login/--dev-mode/g;s/--enable-vt1//g',
241 '/usr/sbin/display_boot_message'],
242 call=True)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800243
244 # Restart gooftool under new root. Since current gooftool might be using
245 # some resource under stateful partition, restarting gooftool ensures that
246 # everything new gooftool is using comes from tmpfs and we can safely
247 # unmount stateful partition.
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800248 args = []
249 if wipe_args:
250 args += ['--wipe_args', wipe_args]
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800251 if shopfloor_url:
252 args += ['--shopfloor_url', shopfloor_url]
253 if station_ip:
254 args += ['--station_ip', station_ip]
255 if station_port:
256 args += ['--station_port', station_port]
257 if wipe_finish_token:
258 args += ['--wipe_finish_token', wipe_finish_token]
Meng-Huan Yu7a4f0f52020-01-07 20:11:01 +0800259 if test_umount:
260 args += ['--test_umount']
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800261 args += ['--state_dev', state_dev]
262 args += ['--release_rootfs', release_rootfs]
263 args += ['--root_disk', root_disk]
264 args += ['--old_root', old_root]
Wei-Han Chenf3924112019-02-25 14:52:58 +0800265 if keep_developer_mode_flag:
266 args += ['--keep_developer_mode_flag_after_clobber_state']
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800267
268 ExecFactoryPar('gooftool', 'wipe_init', *args)
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800269 raise WipeError('Should not reach here')
Hung-Te Linc8174b52017-06-02 11:11:45 +0800270 except Exception:
Wei-Han Chene97d3532016-03-31 19:22:01 +0800271 logging.exception('wipe_in_place failed')
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800272 _OnError(station_ip, station_port, wipe_finish_token, state_dev,
273 wipe_in_tmpfs_log=logfile, wipe_init_log=None)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800274 raise
275
276
277def _StopAllUpstartJobs(exclude_list=None):
278 logging.debug('stopping upstart jobs')
279
Wei-Han Chene97d3532016-03-31 19:22:01 +0800280 if exclude_list is None:
281 exclude_list = []
282
Meng-Huan Yu54835f22020-03-04 20:01:47 +0800283 # Try three times to stop running services because some service will respawn
284 # one time after being stopped, e.g. shill_respawn. Two times should be enough
285 # to stop shill. Adding one more try for safety.
Yilin Yangbf84d2e2020-05-13 10:34:46 +0800286 for unused_tries in range(3):
Meng-Huan Yu54835f22020-03-04 20:01:47 +0800287
288 # There may be LOG_PATH optional parameter for upstart job, the initctl
289 # output may different. The possible output:
290 # "service_name start/running"
291 # "service_name ($LOG_PATH) start/running"
292 initctl_output = process_utils.SpawnOutput(['initctl', 'list']).splitlines()
293
294 running_service_list = []
295 for line in initctl_output:
296 if 'start/running' not in line:
Wei-Han Chene97d3532016-03-31 19:22:01 +0800297 continue
Meng-Huan Yu54835f22020-03-04 20:01:47 +0800298
299 service_name = line.split()[0]
300 log_path = line.split()[1][1:-1] if '(' in line.split()[1] else ''
301 running_service_list.append((service_name, log_path))
302
303 logging.info('Running services (service_name, LOG_PATH): %r',
304 running_service_list)
305
306 to_stop_service_list = [
307 service for service in running_service_list
308 if not (service[0] in exclude_list or service[0].startswith('console-'))
309 ]
310 logging.info('Going to stop services (service_name, LOG_PATH): %r',
311 to_stop_service_list)
312
313 for service, log_path in to_stop_service_list:
314 stop_cmd = ['stop', service]
315 stop_cmd += ["LOG_PATH=" + log_path] if log_path else []
Meng-Huan Yuf2172b72020-03-04 20:12:09 +0800316 process_utils.Spawn(stop_cmd, log=True, log_stderr_on_error=True)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800317
318
Stimim Chen34e41312021-04-07 20:27:01 +0800319def _CollectMountPointsToUmount(state_dev):
Hung-Te Lindd3425d2017-07-12 20:10:52 +0800320 # Find mount points on stateful partition.
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800321 mount_output = process_utils.SpawnOutput(['mount'], log=True)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800322
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800323 mount_point_list = []
Meng-Huan Yu2f86dda2020-12-16 19:28:54 +0800324 namespace_list = []
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800325 for line in mount_output.splitlines():
326 fields = line.split()
327 if fields[0] == state_dev:
328 mount_point_list.append(fields[2])
Meng-Huan Yu2f86dda2020-12-16 19:28:54 +0800329 if fields[0] == 'nsfs':
330 namespace_list.append(fields[2])
Betul Soysalfc79d3f2021-02-27 02:57:45 +0000331 # Mount type of mount namespace is 'proc' for some kernel versions. Make
332 # sure to unmount mount namespaces to successfully unmount stateful
333 # partition.
334 if fields[0] == 'proc' and fields[2].find('/run/namespaces/mnt_') != -1:
335 namespace_list.append(fields[2])
Meng-Huan Yu2f86dda2020-12-16 19:28:54 +0800336
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800337 logging.debug('stateful partitions mounted on: %s', mount_point_list)
Meng-Huan Yu2f86dda2020-12-16 19:28:54 +0800338 logging.debug('namespace mounted on: %s', namespace_list)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800339
Stimim Chen34e41312021-04-07 20:27:01 +0800340 return mount_point_list, namespace_list
341
342
343def _UnmountStatefulPartition(root, state_dev, test_umount):
344 logging.debug('Unmount stateful partition.')
345
346 # Expected stateful partition mount point.
347 state_dir = os.path.join(root, STATEFUL_PARTITION_PATH.strip(os.path.sep))
348
349 # If not in testing mode, touch a mark file so we can check if the stateful
350 # partition is wiped successfully.
351 if not test_umount:
352 file_utils.WriteFile(os.path.join(state_dir, WIPE_MARK_FILE), '')
353
354 # Backup extension cache (crx_cache) if available (will be restored after
355 # wiping by clobber-state).
356 crx_cache_path = os.path.join(state_dir, CRX_CACHE_PAYLOAD_NAME)
357 if os.path.exists(crx_cache_path):
358 shutil.copyfile(crx_cache_path, CRX_CACHE_TAR_PATH)
359
360 mount_point_list, namespace_list = _CollectMountPointsToUmount(state_dev)
361
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800362 def _ListProcOpening(path_list):
363 lsof_cmd = ['lsof', '-t'] + path_list
Wei-Han Chene97d3532016-03-31 19:22:01 +0800364 return [int(line)
365 for line in process_utils.SpawnOutput(lsof_cmd).splitlines()]
366
Hung-Te Lin09226ef2017-01-11 18:00:19 +0800367 def _ListMinijail():
368 # Not sure why, but if we use 'minijail0', then we can't find processes that
369 # starts with /sbin/minijail0.
370 list_cmd = ['pgrep', 'minijail']
371 return [int(line)
372 for line in process_utils.SpawnOutput(list_cmd).splitlines()]
373
Hung-Te Lindd3425d2017-07-12 20:10:52 +0800374 # Find processes that are using stateful partitions.
Wei-Han Chene97d3532016-03-31 19:22:01 +0800375 proc_list = _ListProcOpening(mount_point_list)
376
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800377 if os.getpid() in proc_list:
Wei-Han Chene97d3532016-03-31 19:22:01 +0800378 logging.error('wipe_init itself is using stateful partition')
379 logging.error(
380 'lsof: %s',
381 process_utils.SpawnOutput('lsof -p %d' % os.getpid(), shell=True))
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800382 raise WipeError('wipe_init itself is using stateful partition')
Wei-Han Chene97d3532016-03-31 19:22:01 +0800383
384 def _KillOpeningBySignal(sig):
chuntsen421b6e22019-02-19 19:51:24 +0800385 for mount_point in mount_point_list:
386 cmd = ['fuser', '-k', '-%d' % sig, '-m', mount_point]
Meng-Huan Yuf2172b72020-03-04 20:12:09 +0800387 process_utils.Spawn(cmd, call=True, log=True)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800388 proc_list = _ListProcOpening(mount_point_list)
389 if not proc_list:
390 return True # we are done
391 for pid in proc_list:
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800392 try:
393 os.kill(pid, sig)
Hung-Te Linc8174b52017-06-02 11:11:45 +0800394 except Exception:
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800395 logging.exception('killing process %d failed', pid)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800396 return False # need to check again
397
Hung-Te Lindd3425d2017-07-12 20:10:52 +0800398 # Try to kill processes using stateful partition gracefully.
Wei-Han Chene97d3532016-03-31 19:22:01 +0800399 sync_utils.Retry(10, 0.1, None, _KillOpeningBySignal, signal.SIGTERM)
400 sync_utils.Retry(10, 0.1, None, _KillOpeningBySignal, signal.SIGKILL)
401
402 proc_list = _ListProcOpening(mount_point_list)
403 assert not proc_list, "processes using stateful partition: %s" % proc_list
404
You-Cheng Syu2ea26dd2016-12-06 20:50:05 +0800405 def _Unmount(mount_point, critical):
Hung-Te Lin09226ef2017-01-11 18:00:19 +0800406 logging.info('try to unmount %s', mount_point)
Yilin Yangbf84d2e2020-05-13 10:34:46 +0800407 for unused_i in range(10):
You-Cheng Syu2ea26dd2016-12-06 20:50:05 +0800408 output = process_utils.Spawn(['umount', '-n', '-R', mount_point],
Meng-Huan Yuf2172b72020-03-04 20:12:09 +0800409 log=True,
Meng-Huan Yu98f78232020-02-19 17:40:54 +0800410 log_stderr_on_error=True).stderr_data
You-Cheng Syu2ea26dd2016-12-06 20:50:05 +0800411 # some mount points need to be unmounted multiple times.
412 if (output.endswith(': not mounted\n') or
413 output.endswith(': not found\n')):
414 return
415 time.sleep(0.5)
Hung-Te Lin09226ef2017-01-11 18:00:19 +0800416 logging.error('failed to unmount %s', mount_point)
You-Cheng Syu2ea26dd2016-12-06 20:50:05 +0800417 if critical:
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800418 raise WipeError('Unmounting %s is critical. Stop.' % mount_point)
You-Cheng Syu2ea26dd2016-12-06 20:50:05 +0800419
Stimim Chen34e41312021-04-07 20:27:01 +0800420 def _UnmountAll(critical):
421 # Remove all mounted namespace to release stateful partition.
422 for ns_mount_point in namespace_list:
423 _Unmount(ns_mount_point, critical)
Hung-Te Lin09226ef2017-01-11 18:00:19 +0800424
Stimim Chen34e41312021-04-07 20:27:01 +0800425 # Doing what 'mount-encrypted umount' should do.
426 for mount_point in mount_point_list:
427 _Unmount(mount_point, critical)
428 _Unmount(os.path.join(root, 'var'), critical)
429
430 if os.path.exists(os.path.join(root, 'dev', 'mapper', 'encstateful')):
431 _UnmountAll(critical=False)
Hung-Te Lin09226ef2017-01-11 18:00:19 +0800432 # minijail will make encstateful busy, but usually we can't just kill them.
433 # Need to list the processes and solve each-by-each.
434 proc_list = _ListMinijail()
Meng-Huan Yu917e09d2020-06-22 14:32:32 +0800435 assert not proc_list, (
436 "processes still using minijail: %s" %
437 process_utils.SpawnOutput(['pgrep', '-al', 'minijail']))
Hung-Te Lin09226ef2017-01-11 18:00:19 +0800438
Jeffy Chend5b08e12017-03-06 10:22:59 +0800439 process_utils.Spawn(['dmsetup', 'remove', 'encstateful',
440 '--noudevrules', '--noudevsync'], check_call=True)
You-Cheng Syuf0990462016-09-07 14:56:19 +0800441 process_utils.Spawn(['losetup', '-D'], check_call=True)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800442
Stimim Chen34e41312021-04-07 20:27:01 +0800443 _UnmountAll(critical=True)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800444 process_utils.Spawn(['sync'], call=True)
445
Stimim Chen34e41312021-04-07 20:27:01 +0800446 mount_point_list, namespace_list = _CollectMountPointsToUmount(state_dev)
447
448 if mount_point_list or namespace_list:
449 error_message = ('Mount points are not cleared. '
450 f'mount_point_list: {mount_point_list} '
451 f'namespace_list: {namespace_list}')
452 raise WipeError(error_message)
453
You-Cheng Syuf0990462016-09-07 14:56:19 +0800454 # Check if the stateful partition is unmounted successfully.
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800455 if _IsStateDevMounted(state_dev):
456 raise WipeError('Failed to unmount stateful_partition')
457
458
459def _IsStateDevMounted(state_dev):
460 try:
461 output = process_utils.CheckOutput(['df', state_dev])
462 return output.splitlines()[-1].split()[0] == state_dev
463 except Exception:
464 return False
You-Cheng Syuf0990462016-09-07 14:56:19 +0800465
Wei-Han Chene97d3532016-03-31 19:22:01 +0800466
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800467def _InformStation(ip, port, token, wipe_init_log=None,
468 wipe_in_tmpfs_log=None, success=True):
469 if not ip:
470 return
471 port = int(port)
472
473 logging.debug('inform station %s:%d', ip, port)
474
475 try:
476 sync_utils.WaitFor(
Peter Shih14458732018-02-26 14:40:15 +0800477 lambda: process_utils.Spawn(['ping', '-w1', '-c1', ip],
478 call=True).returncode == 0,
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800479 timeout_secs=180, poll_interval=1)
Hung-Te Linc8174b52017-06-02 11:11:45 +0800480 except Exception:
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800481 logging.exception('cannot get network connection...')
482 else:
483 sock = socket.socket()
484 sock.connect((ip, port))
485
486 response = dict(token=token, success=success)
487
488 if wipe_init_log:
489 with open(wipe_init_log) as f:
490 response['wipe_init_log'] = f.read()
491
492 if wipe_in_tmpfs_log:
493 with open(wipe_in_tmpfs_log) as f:
494 response['wipe_in_tmpfs_log'] = f.read()
495
496 sock.sendall(json.dumps(response) + '\n')
497 sock.close()
498
499
Wei-Han Chenf3924112019-02-25 14:52:58 +0800500def _WipeStateDev(release_rootfs, root_disk, wipe_args, state_dev,
501 keep_developer_mode_flag):
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800502 clobber_state_env = os.environ.copy()
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800503 clobber_state_env.update(ROOT_DEV=release_rootfs,
Earl Oueeb289d2016-11-04 14:36:40 +0800504 ROOT_DISK=root_disk)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800505 logging.debug('clobber-state: root_dev=%s, root_disk=%s',
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800506 release_rootfs, root_disk)
Meng-Huan Yu2e014842020-09-22 13:50:16 +0800507 process_utils.Spawn(['clobber-state', wipe_args], env=clobber_state_env,
508 check_call=True, log=True)
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800509
Hung-Te Lindd3425d2017-07-12 20:10:52 +0800510 logging.info('Checking if stateful partition is mounted...')
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800511 # Check if the stateful partition is wiped.
512 if not _IsStateDevMounted(state_dev):
513 process_utils.Spawn(['mount', state_dev, STATEFUL_PARTITION_PATH],
Hung-Te Lindd3425d2017-07-12 20:10:52 +0800514 check_call=True, log=True)
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800515
Hung-Te Lindd3425d2017-07-12 20:10:52 +0800516 logging.info('Checking wipe mark file %s...', WIPE_MARK_FILE)
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800517 if os.path.exists(
518 os.path.join(STATEFUL_PARTITION_PATH, WIPE_MARK_FILE)):
519 raise WipeError(WIPE_MARK_FILE + ' still exists')
Hung-Te Lindd3425d2017-07-12 20:10:52 +0800520
521 # Restore CRX cache.
522 logging.info('Checking CRX cache %s...', CRX_CACHE_TAR_PATH)
523 if os.path.exists(CRX_CACHE_TAR_PATH):
524 process_utils.Spawn(['tar', '-xpvf', CRX_CACHE_TAR_PATH, '-C',
525 STATEFUL_PARTITION_PATH], check_call=True, log=True)
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800526
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800527 try:
Wei-Han Chenf3924112019-02-25 14:52:58 +0800528 if not keep_developer_mode_flag:
529 # Remove developer flag, which is created by clobber-state after wiping.
530 os.unlink(os.path.join(STATEFUL_PARTITION_PATH, '.developer_mode'))
531 # Otherwise we don't care.
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800532 except OSError:
533 pass
Hung-Te Lindd3425d2017-07-12 20:10:52 +0800534
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800535 process_utils.Spawn(['umount', STATEFUL_PARTITION_PATH], call=True)
536 # Make sure that everything is synced.
537 process_utils.Spawn(['sync'], call=True)
538 time.sleep(3)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800539
Joel Kitching679a00b2016-08-03 11:42:58 +0800540
Earl Ou564a7872016-10-05 10:22:00 +0800541def EnableReleasePartition(release_rootfs):
542 """Enables a release image partition on disk."""
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800543 logging.debug('enable release partition: %s', release_rootfs)
544 Util().EnableReleasePartition(release_rootfs)
Earl Ou564a7872016-10-05 10:22:00 +0800545 logging.debug('Device will boot from %s after reboot.', release_rootfs)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800546
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800547
548def _InformShopfloor(shopfloor_url):
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800549 if shopfloor_url:
550 logging.debug('inform shopfloor %s', shopfloor_url)
Hung-Te Lina3195462016-10-14 15:48:29 +0800551 proc = process_utils.Spawn(
Yilun Lindbb8af72018-01-31 16:01:17 +0800552 [
553 os.path.join(CUTOFF_SCRIPT_DIR, 'inform_shopfloor.sh'),
554 shopfloor_url, 'factory_wipe'
555 ],
556 read_stdout=True,
557 read_stderr=True)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800558 logging.debug('stdout: %s', proc.stdout_data)
559 logging.debug('stderr: %s', proc.stderr_data)
Yilun Lindbb8af72018-01-31 16:01:17 +0800560 if proc.returncode != 0:
Peter Shihbf6f22b2018-02-26 14:05:28 +0800561 raise RuntimeError('InformShopfloor failed.')
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800562
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800563
Hung-Te Lin7b27f0c2016-10-18 18:41:29 +0800564def _Cutoff():
565 logging.debug('cutoff')
Hung-Te Lina3195462016-10-14 15:48:29 +0800566 cutoff_script = os.path.join(CUTOFF_SCRIPT_DIR, 'cutoff.sh')
You-Cheng Syue6844172017-11-28 16:39:32 +0800567 process_utils.Spawn([cutoff_script], check_call=True)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800568
569
Hung-Te Lin7b27f0c2016-10-18 18:41:29 +0800570def WipeInit(wipe_args, shopfloor_url, state_dev, release_rootfs,
Wei-Han Chenf3924112019-02-25 14:52:58 +0800571 root_disk, old_root, station_ip, station_port, finish_token,
Meng-Huan Yu7a4f0f52020-01-07 20:11:01 +0800572 keep_developer_mode_flag, test_umount):
Wei-Han Chenc8f24562016-04-23 19:42:42 +0800573 Daemonize()
Wei-Han Chene97d3532016-03-31 19:22:01 +0800574 logfile = '/tmp/wipe_init.log'
575 ResetLog(logfile)
Meng-Huan Yu7e530ce2019-12-23 17:35:57 +0800576 wipe_in_tmpfs_log = os.path.join(old_root, 'tmp', WIPE_IN_TMPFS_LOG)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800577
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800578 logging.debug('wipe_args: %s', wipe_args)
Wei-Han Chen0a3320e2016-04-23 01:32:07 +0800579 logging.debug('shopfloor_url: %s', shopfloor_url)
580 logging.debug('state_dev: %s', state_dev)
581 logging.debug('release_rootfs: %s', release_rootfs)
582 logging.debug('root_disk: %s', root_disk)
583 logging.debug('old_root: %s', old_root)
Meng-Huan Yu7a4f0f52020-01-07 20:11:01 +0800584 logging.debug('test_umount: %s', test_umount)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800585
Wei-Han Chene97d3532016-03-31 19:22:01 +0800586 try:
Meng-Huan Yud4a2c802020-05-13 15:46:31 +0800587 # Enable upstart log under /var/log/upstart.log for Tast.
588 process_utils.Spawn(['initctl', 'log-priority', 'info'],
589 log=True,
590 log_stderr_on_error=True)
591
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800592 _StopAllUpstartJobs(exclude_list=[
593 # Milestone marker that use to determine the running of other services.
594 'boot-services',
595 'system-services',
596 'failsafe',
597 # Keep dbus to make sure we can shutdown the device.
598 'dbus',
599 # Keep shill for connecting to shopfloor or stations.
600 'shill',
cyuehefe3cf92020-01-07 12:01:22 +0800601 # Keep wpasupplicant since shopfloor may connect over WiFi.
602 'wpasupplicant',
Shun-Hsing Oub5724832016-07-21 11:45:58 +0800603 # Keep openssh-server for debugging purpose.
604 'openssh-server',
605 # sslh is a service in ARC++ for muxing between ssh and adb.
606 'sslh'
Peter Shihe6afab32018-09-11 17:16:48 +0800607 ])
Meng-Huan Yuedd78e32020-12-16 16:45:36 +0800608 _UnmountStatefulPartition(old_root, state_dev, test_umount)
Wei-Han Chen9adf9de2016-04-01 19:35:41 +0800609
Meng-Huan Yu7a4f0f52020-01-07 20:11:01 +0800610 # When testing, stop the wiping process with no error. In normal
611 # process, this function will run forever until reboot.
612 if test_umount:
613 logging.info('Finished unmount, stop wiping process because test_umount '
614 'is set.')
615 return
616
Meng-Huan Yua97e44b2020-02-14 17:07:36 +0800617 # The following code could not be executed when factory is not installed
618 # due to lacking of CUTOFF_SCRIPT_DIR.
619 process_utils.Spawn(
620 [os.path.join(CUTOFF_SCRIPT_DIR, 'display_wipe_message.sh'), 'wipe'],
621 call=True)
622
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800623 try:
Wei-Han Chenf3924112019-02-25 14:52:58 +0800624 _WipeStateDev(release_rootfs, root_disk, wipe_args, state_dev,
625 keep_developer_mode_flag)
Wei-Han Chenb05699a2017-07-12 16:37:47 +0800626 except Exception:
627 process_utils.Spawn(
628 [os.path.join(CUTOFF_SCRIPT_DIR, 'display_wipe_message.sh'),
629 'wipe_failed'], call=True)
630 raise
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800631
Earl Ou564a7872016-10-05 10:22:00 +0800632 EnableReleasePartition(release_rootfs)
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800633
634 _InformShopfloor(shopfloor_url)
635
636 _InformStation(station_ip, station_port, finish_token,
637 wipe_init_log=logfile,
638 wipe_in_tmpfs_log=wipe_in_tmpfs_log,
639 success=True)
640
Hung-Te Lin7b27f0c2016-10-18 18:41:29 +0800641 _Cutoff()
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800642
643 # should not reach here
Hung-Te Lindd3425d2017-07-12 20:10:52 +0800644 logging.info('Going to sleep forever!')
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800645 time.sleep(1e8)
Hung-Te Linc8174b52017-06-02 11:11:45 +0800646 except Exception:
Meng-Huan Yud4a2c802020-05-13 15:46:31 +0800647 # This error message is used to detect error in Factory.Finalize Tast test.
648 # Keep sync if changed this.
Wei-Han Chene97d3532016-03-31 19:22:01 +0800649 logging.exception('wipe_init failed')
Wei-Han Chenbe1355a2016-04-24 19:31:03 +0800650 _OnError(station_ip, station_port, finish_token, state_dev,
651 wipe_in_tmpfs_log=wipe_in_tmpfs_log, wipe_init_log=logfile)
Wei-Han Chene97d3532016-03-31 19:22:01 +0800652 raise