xref: /aosp_15_r20/external/toolchain-utils/make_root_writable.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#
4# Copyright 2021 The ChromiumOS Authors
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8"""Script to make / directory on chromebook writable.
9
10This script updates a remote chromebook to make the / directory writable."
11"""
12
13
14__author__ = "[email protected] (Caroline Tice)"
15
16import argparse
17import os
18import sys
19import time
20
21from cros_utils import command_executer
22from cros_utils import locks
23from cros_utils import logger
24from cros_utils import machines
25from cros_utils import misc
26
27
28lock_file = "/tmp/image_chromeos_lock/image_chromeos_lock"
29
30
31def Usage(parser, message):
32    print("ERROR: %s" % message)
33    parser.print_help()
34    sys.exit(0)
35
36
37def RebootChromebook(chromeos_root, remote, cmd_executer):
38    cmd = "sudo reboot"
39    cmd_executer.CrosRunCommand(
40        cmd, chromeos_root=chromeos_root, machine=remote
41    )
42    time.sleep(10)
43    success = False
44    for _ in range(1, 10):
45        if machines.MachineIsPingable(remote):
46            success = True
47            break
48        time.sleep(1)
49    return success
50
51
52def ParseOutput(output):
53    # See comment in FindPartitionNum.
54    lines = output.split("\n")
55    num_str = "-1"
56    for line in lines:
57        l = line.strip()
58        words = l.split()
59        if (
60            len(words) > 2
61            and words[0] == "sudo"
62            and words[1] == "/usr/share/vboot/bin/make_dev_ssd.sh"
63            and words[-2] == "--partitions"
64        ):
65            num_str = words[-1]
66            break
67    num = int(num_str)
68
69    return num
70
71
72def FindPartitionNum(chromeos_root, remote, logs, cmd_executer):
73    partition_cmd = (
74        "/usr/share/vboot/bin/make_dev_ssd.sh " "--remove_rootfs_verification"
75    )
76    _, output, _ = cmd_executer.CrosRunCommandWOutput(
77        partition_cmd,
78        chromeos_root=chromeos_root,
79        machine=remote,
80        terminated_timeout=10,
81    )
82
83    # The command above, with no --partitions flag, should return output
84    # in the following form:
85
86    # make_dev_ssd.sh: INFO: checking system firmware...
87    #
88    #  ERROR: YOU ARE TRYING TO MODIFY THE LIVE SYSTEM IMAGE /dev/mmcblk0.
89    #
90    #  The system may become unusable after that change, especially when you have
91    #  some auto updates in progress. To make it safer, we suggest you to only
92    #  change the partition you have booted with. To do that, re-execute this
93    #  command as:
94    #
95    #  sudo /usr/share/vboot/bin/make_dev_ssd.sh  --partitions 4
96    #
97    #  If you are sure to modify other partition, please invoke the command again
98    #  and explicitly assign only one target partition for each time
99    # (--partitions N )
100    #
101    # make_dev_ssd.sh: ERROR: IMAGE /dev/mmcblk0 IS NOT MODIFIED.
102
103    # We pass this output to the ParseOutput function where it finds the 'sudo'
104    # line with the partition number and returns the partition number.
105
106    num = ParseOutput(output)
107
108    if num == -1:
109        logs.LogOutput('Failed to find partition number in "%s"' % output)
110    return num
111
112
113def TryRemoveRootfsFromPartition(
114    chromeos_root, remote, cmd_executer, partition_num
115):
116    partition_cmd = (
117        "/usr/share/vboot/bin/make_dev_ssd.sh "
118        "--remove_rootfs_verification --partition %d" % partition_num
119    )
120    ret = cmd_executer.CrosRunCommand(
121        partition_cmd,
122        chromeos_root=chromeos_root,
123        machine=remote,
124        terminated_timeout=10,
125    )
126    return ret
127
128
129def TryRemountPartitionAsRW(chromeos_root, remote, cmd_executer):
130    command = "sudo mount -o remount,rw /"
131    ret = cmd_executer.CrosRunCommand(
132        command,
133        chromeos_root=chromeos_root,
134        machine=remote,
135        terminated_timeout=10,
136    )
137    return ret
138
139
140def Main(argv):
141    parser = argparse.ArgumentParser()
142    parser.add_argument(
143        "-c",
144        "--chromeos_root",
145        dest="chromeos_root",
146        help="Target directory for ChromeOS installation.",
147    )
148    parser.add_argument("-r", "--remote", dest="remote", help="Target device.")
149    parser.add_argument(
150        "-n",
151        "--no_lock",
152        dest="no_lock",
153        default=False,
154        action="store_true",
155        help="Do not attempt to lock remote before imaging.  "
156        "This option should only be used in cases where the "
157        "exclusive lock has already been acquired (e.g. in "
158        "a script that calls this one).",
159    )
160
161    options = parser.parse_args(argv[1:])
162
163    # Common initializations
164    log_level = "average"
165    cmd_executer = command_executer.GetCommandExecuter(log_level=log_level)
166    l = logger.GetLogger()
167
168    if options.chromeos_root is None:
169        Usage(parser, "--chromeos_root must be set")
170
171    if options.remote is None:
172        Usage(parser, "--remote must be set")
173
174    options.chromeos_root = os.path.expanduser(options.chromeos_root)
175
176    try:
177        should_unlock = False
178        if not options.no_lock:
179            try:
180                _ = locks.AcquireLock(
181                    list(options.remote.split()), options.chromeos_root
182                )
183                should_unlock = True
184            except Exception as e:
185                raise RuntimeError("Error acquiring machine: %s" % str(e))
186
187        # Workaround for crosbug.com/35684.
188        os.chmod(misc.GetChromeOSKeyFile(options.chromeos_root), 0o600)
189
190        if log_level == "average":
191            cmd_executer.SetLogLevel("verbose")
192
193        if not machines.MachineIsPingable(options.remote):
194            raise RuntimeError(
195                "Machine %s does not appear to be up." % options.remote
196            )
197
198        ret = TryRemountPartitionAsRW(
199            options.chromeos_root, options.remote, cmd_executer
200        )
201
202        if ret != 0:
203            l.LogOutput(
204                "Initial mount command failed. Looking for root partition"
205                " number."
206            )
207            part_num = FindPartitionNum(
208                options.chromeos_root, options.remote, l, cmd_executer
209            )
210            if part_num != -1:
211                l.LogOutput(
212                    "Attempting to remove rootfs verification on partition %d"
213                    % part_num
214                )
215                ret = TryRemoveRootfsFromPartition(
216                    options.chromeos_root,
217                    options.remote,
218                    cmd_executer,
219                    part_num,
220                )
221                if ret == 0:
222                    l.LogOutput(
223                        "Succeeded in removing roofs verification from"
224                        " partition %d. Rebooting..." % part_num
225                    )
226                    if not RebootChromebook(
227                        options.chromeos_root, options.remote, cmd_executer
228                    ):
229                        raise RuntimeError("Chromebook failed to reboot.")
230                    l.LogOutput(
231                        "Reboot succeeded. Attempting to remount partition."
232                    )
233                    ret = TryRemountPartitionAsRW(
234                        options.chromeos_root, options.remote, cmd_executer
235                    )
236                    if ret == 0:
237                        l.LogOutput("Re-mounted / as writable.")
238                    else:
239                        l.LogOutput("Re-mount failed. / is not writable.")
240                else:
241                    l.LogOutput(
242                        "Failed to remove rootfs verification from partition"
243                        " %d." % part_num
244                    )
245        else:
246            l.LogOutput("Re-mounted / as writable.")
247
248        l.LogOutput("Exiting.")
249
250    finally:
251        if should_unlock:
252            locks.ReleaseLock(
253                list(options.remote.split()), options.chromeos_root
254            )
255
256    return ret
257
258
259if __name__ == "__main__":
260    retval = Main(sys.argv)
261    sys.exit(retval)
262