blob: 8db081361863e2c0fa9eac598ea94aa214acf112 [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", \
37 ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}="1", ENV{UDISKS_IGNORE}="1"
38
39LABEL="block_udev_end"
40"""
41_UDEV_RULE_FILE = Path("/etc/udev/rules.d/99-chromite-loop-dev.rules")
42
43
44def _create_udev_loopdev_ignore_rule():
45 """Create udev rules to ignore processing cros image loop device events."""
46 if cros_build_lib.IsInsideChroot():
47 return
48
49 new_rule = _UDEV_RULE_TEMPLATE % constants.SOURCE_ROOT
50 try:
51 existing_rule = _UDEV_RULE_FILE.read_text(encoding="utf-8")
52 except FileNotFoundError:
53 existing_rule = None
54 if existing_rule == new_rule:
55 return
56
57 logging.debug(
58 "Creating udev rule to ignore loop device in %s.", _UDEV_RULE_FILE
59 )
60 _UDEV_RULE_FILE.write_text(new_rule, encoding="utf-8")
61 cros_build_lib.run(["udevadm", "control", "--reload-rules"], check=False)
62
63
Mike Frysingerfcdd9422022-12-28 10:59:06 -050064def get_parser() -> commandline.ArgumentParser:
65 """Return a command line parser."""
66 parser = commandline.ArgumentParser(description=__doc__)
67
68 subparsers = parser.add_subparsers(dest="subcommand")
69 subparsers.required = True
70
71 subparser = subparsers.add_parser(
72 "attach", help="Attach a disk image to a loopback device."
73 )
74 subparser.add_argument(
75 "path",
76 type=Path,
77 help="Disk image to attach.",
78 )
79
80 subparser = subparsers.add_parser(
81 "detach", help="Detach a loopback device."
82 )
83 subparser.add_argument(
84 "path",
85 type=Path,
86 help="Loopback device to free.",
87 )
88
89 return parser
90
91
92def main(argv: Optional[List[str]] = None) -> Optional[int]:
93 parser = get_parser()
94 opts = parser.parse_args(argv)
95
96 if not osutils.IsRootUser():
97 result = cros_build_lib.sudo_run(
Mike Frysinger5429f302023-03-27 15:48:52 -040098 [constants.CHROMITE_SCRIPTS_DIR / "cros_losetup"] + argv,
Mike Frysingerfcdd9422022-12-28 10:59:06 -050099 check=False,
100 )
101 return result.returncode
102
Ram Chandrasekar65621a22023-04-11 17:18:12 -0600103 _create_udev_loopdev_ignore_rule()
104
Mike Frysingerfcdd9422022-12-28 10:59:06 -0500105 if opts.subcommand == "attach":
106 loop_path = image_lib.LoopbackPartitions.attach_image(opts.path)
107 print(
108 pformat.json(
109 {
110 "path": loop_path,
111 },
112 compact=not sys.stdin.isatty(),
113 ),
114 end="",
115 )
116 elif opts.subcommand == "detach":
117 if not image_lib.LoopbackPartitions.detach_loopback(opts.path):
118 return 1
119 else:
120 raise RuntimeError(f"Unknown command: {opts.subcommand}")
121
122 return 0