blob: f9069c2caa911ecce141f2be21a6204e384af56e [file] [log] [blame]
Mike Frysingerfcdd9422022-12-28 10:59:06 -05001# Copyright 2022 The ChromiumOS Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Internal helper for attaching & detaching disk images.
6
7We have a long history of the kernel flaking when working with loopback devices,
8so this helper takes care of setting up & tearing it down reliably.
9"""
10
Ram Chandrasekar65621a22023-04-11 17:18:12 -060011import logging
Mike Frysingerfcdd9422022-12-28 10:59:06 -050012from pathlib import Path
13import sys
14from typing import List, Optional
15
16from chromite.lib import commandline
17from chromite.lib import constants
18from chromite.lib import cros_build_lib
19from chromite.lib import image_lib
20from chromite.lib import osutils
21from chromite.utils import pformat
22
23
Ram Chandrasekar65621a22023-04-11 17:18:12 -060024_UDEV_RULE_TEMPLATE = """# Don't edit this file.
25# This file will be updated by chromite.
26# This rule looks for images mounted from cros checkout and ignores
27# udev processing (b/273697462 for more context).
28SUBSYSTEM!="block", GOTO="block_udev_end"
29KERNELS!="loop[0-9]*", GOTO="block_udev_end"
30ENV{DEVTYPE}=="disk", TEST!="loop/backing_file", GOTO="block_udev_end"
31ENV{DEVTYPE}=="partition", ENV{ID_PART_ENTRY_SCHEME}!="gpt", \
32 GOTO="block_udev_end"
33
34ATTRS{loop/backing_file}=="%s/*", ENV{CROS_IGNORE_LOOP_DEV}="1"
35
36ENV{CROS_IGNORE_LOOP_DEV}=="1", ENV{SYSTEMD_READY}="0", \
Ram Chandrasekarf4cc49b2023-06-09 09:26:36 -070037 ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}="1", \
38 ENV{UDISKS_IGNORE}="1", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1"
Ram Chandrasekar65621a22023-04-11 17:18:12 -060039
40LABEL="block_udev_end"
41"""
42_UDEV_RULE_FILE = Path("/etc/udev/rules.d/99-chromite-loop-dev.rules")
43
44
45def _create_udev_loopdev_ignore_rule():
46 """Create udev rules to ignore processing cros image loop device events."""
47 if cros_build_lib.IsInsideChroot():
48 return
49
50 new_rule = _UDEV_RULE_TEMPLATE % constants.SOURCE_ROOT
51 try:
52 existing_rule = _UDEV_RULE_FILE.read_text(encoding="utf-8")
53 except FileNotFoundError:
54 existing_rule = None
55 if existing_rule == new_rule:
56 return
57
58 logging.debug(
59 "Creating udev rule to ignore loop device in %s.", _UDEV_RULE_FILE
60 )
61 _UDEV_RULE_FILE.write_text(new_rule, encoding="utf-8")
62 cros_build_lib.run(["udevadm", "control", "--reload-rules"], check=False)
63
64
Mike Frysingerfcdd9422022-12-28 10:59:06 -050065def get_parser() -> commandline.ArgumentParser:
66 """Return a command line parser."""
67 parser = commandline.ArgumentParser(description=__doc__)
68
69 subparsers = parser.add_subparsers(dest="subcommand")
70 subparsers.required = True
71
72 subparser = subparsers.add_parser(
73 "attach", help="Attach a disk image to a loopback device."
74 )
75 subparser.add_argument(
76 "path",
77 type=Path,
78 help="Disk image to attach.",
79 )
80
81 subparser = subparsers.add_parser(
82 "detach", help="Detach a loopback device."
83 )
84 subparser.add_argument(
85 "path",
86 type=Path,
87 help="Loopback device to free.",
88 )
89
90 return parser
91
92
93def main(argv: Optional[List[str]] = None) -> Optional[int]:
94 parser = get_parser()
95 opts = parser.parse_args(argv)
96
97 if not osutils.IsRootUser():
98 result = cros_build_lib.sudo_run(
Mike Frysinger5429f302023-03-27 15:48:52 -040099 [constants.CHROMITE_SCRIPTS_DIR / "cros_losetup"] + argv,
Mike Frysingerfcdd9422022-12-28 10:59:06 -0500100 check=False,
101 )
102 return result.returncode
103
Ram Chandrasekar65621a22023-04-11 17:18:12 -0600104 _create_udev_loopdev_ignore_rule()
105
Mike Frysingerfcdd9422022-12-28 10:59:06 -0500106 if opts.subcommand == "attach":
107 loop_path = image_lib.LoopbackPartitions.attach_image(opts.path)
108 print(
109 pformat.json(
110 {
111 "path": loop_path,
112 },
113 compact=not sys.stdin.isatty(),
114 ),
115 end="",
116 )
117 elif opts.subcommand == "detach":
118 if not image_lib.LoopbackPartitions.detach_loopback(opts.path):
119 return 1
120 else:
121 raise RuntimeError(f"Unknown command: {opts.subcommand}")
122
123 return 0