#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Copyright 2021 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Script to make / directory on chromebook writable. This script updates a remote chromebook to make the / directory writable." """ from __future__ import print_function __author__ = 'cmtice@google.com (Caroline Tice)' import argparse import os import sys import time from cros_utils import command_executer from cros_utils import locks from cros_utils import logger from cros_utils import machines from cros_utils import misc lock_file = '/tmp/image_chromeos_lock/image_chromeos_lock' def Usage(parser, message): print('ERROR: %s' % message) parser.print_help() sys.exit(0) def RebootChromebook(chromeos_root, remote, cmd_executer): cmd = 'sudo reboot' cmd_executer.CrosRunCommand(cmd, chromeos_root=chromeos_root, machine=remote) time.sleep(10) success = False for _ in range(1, 10): if machines.MachineIsPingable(remote): success = True break time.sleep(1) return success def ParseOutput(output): # See comment in FindPartitionNum. lines = output.split('\n') num_str = '-1' for line in lines: l = line.strip() words = l.split() if (len(words) > 2 and words[0] == 'sudo' and words[1] == '/usr/share/vboot/bin/make_dev_ssd.sh' and words[-2] == '--partitions'): num_str = words[-1] break num = int(num_str) return num def FindPartitionNum(chromeos_root, remote, logs, cmd_executer): partition_cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh ' '--remove_rootfs_verification') _, output, _ = cmd_executer.CrosRunCommandWOutput( partition_cmd, chromeos_root=chromeos_root, machine=remote, terminated_timeout=10) # The command above, with no --partitions flag, should return output # in the following form: # make_dev_ssd.sh: INFO: checking system firmware... # # ERROR: YOU ARE TRYING TO MODIFY THE LIVE SYSTEM IMAGE /dev/mmcblk0. # # The system may become unusable after that change, especially when you have # some auto updates in progress. To make it safer, we suggest you to only # change the partition you have booted with. To do that, re-execute this # command as: # # sudo /usr/share/vboot/bin/make_dev_ssd.sh --partitions 4 # # If you are sure to modify other partition, please invoke the command again # and explicitly assign only one target partition for each time # (--partitions N ) # # make_dev_ssd.sh: ERROR: IMAGE /dev/mmcblk0 IS NOT MODIFIED. # We pass this output to the ParseOutput function where it finds the 'sudo' # line with the partition number and returns the partition number. num = ParseOutput(output) if num == -1: logs.LogOutput('Failed to find partition number in "%s"' % output) return num def TryRemoveRootfsFromPartition(chromeos_root, remote, cmd_executer, partition_num): partition_cmd = ('/usr/share/vboot/bin/make_dev_ssd.sh ' '--remove_rootfs_verification --partition %d' % partition_num) ret = cmd_executer.CrosRunCommand( partition_cmd, chromeos_root=chromeos_root, machine=remote, terminated_timeout=10) return ret def TryRemountPartitionAsRW(chromeos_root, remote, cmd_executer): command = 'sudo mount -o remount,rw /' ret = cmd_executer.CrosRunCommand( command, chromeos_root=chromeos_root, machine=remote, terminated_timeout=10) return ret def Main(argv): parser = argparse.ArgumentParser() parser.add_argument( '-c', '--chromeos_root', dest='chromeos_root', help='Target directory for ChromeOS installation.') parser.add_argument('-r', '--remote', dest='remote', help='Target device.') parser.add_argument( '-n', '--no_lock', dest='no_lock', default=False, action='store_true', help='Do not attempt to lock remote before imaging. ' 'This option should only be used in cases where the ' 'exclusive lock has already been acquired (e.g. in ' 'a script that calls this one).') options = parser.parse_args(argv[1:]) # Common initializations log_level = 'average' cmd_executer = command_executer.GetCommandExecuter(log_level=log_level) l = logger.GetLogger() if options.chromeos_root is None: Usage(parser, '--chromeos_root must be set') if options.remote is None: Usage(parser, '--remote must be set') options.chromeos_root = os.path.expanduser(options.chromeos_root) try: should_unlock = False if not options.no_lock: try: _ = locks.AcquireLock( list(options.remote.split()), options.chromeos_root) should_unlock = True except Exception as e: raise RuntimeError('Error acquiring machine: %s' % str(e)) # Workaround for crosbug.com/35684. os.chmod(misc.GetChromeOSKeyFile(options.chromeos_root), 0o600) if log_level == 'average': cmd_executer.SetLogLevel('verbose') if not machines.MachineIsPingable(options.remote): raise RuntimeError('Machine %s does not appear to be up.' % options.remote) ret = TryRemountPartitionAsRW(options.chromeos_root, options.remote, cmd_executer) if ret != 0: l.LogOutput('Initial mount command failed. Looking for root partition' ' number.') part_num = FindPartitionNum(options.chromeos_root, options.remote, l, cmd_executer) if part_num != -1: l.LogOutput('Attempting to remove rootfs verification on partition %d' % part_num) ret = TryRemoveRootfsFromPartition(options.chromeos_root, options.remote, cmd_executer, part_num) if ret == 0: l.LogOutput('Succeeded in removing roofs verification from' ' partition %d. Rebooting...' % part_num) if not RebootChromebook(options.chromeos_root, options.remote, cmd_executer): raise RuntimeError('Chromebook failed to reboot.') l.LogOutput('Reboot succeeded. Attempting to remount partition.') ret = TryRemountPartitionAsRW(options.chromeos_root, options.remote, cmd_executer) if ret == 0: l.LogOutput('Re-mounted / as writable.') else: l.LogOutput('Re-mount failed. / is not writable.') else: l.LogOutput('Failed to remove rootfs verification from partition' ' %d.' % part_num) else: l.LogOutput('Re-mounted / as writable.') l.LogOutput('Exiting.') finally: if should_unlock: locks.ReleaseLock(list(options.remote.split()), options.chromeos_root) return ret if __name__ == '__main__': retval = Main(sys.argv) sys.exit(retval)