blob: 1d3b758ad88223c0065ed7852141d243cc7b622a [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
11import os
12from 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
24def get_parser() -> commandline.ArgumentParser:
25 """Return a command line parser."""
26 parser = commandline.ArgumentParser(description=__doc__)
27
28 subparsers = parser.add_subparsers(dest="subcommand")
29 subparsers.required = True
30
31 subparser = subparsers.add_parser(
32 "attach", help="Attach a disk image to a loopback device."
33 )
34 subparser.add_argument(
35 "path",
36 type=Path,
37 help="Disk image to attach.",
38 )
39
40 subparser = subparsers.add_parser(
41 "detach", help="Detach a loopback device."
42 )
43 subparser.add_argument(
44 "path",
45 type=Path,
46 help="Loopback device to free.",
47 )
48
49 return parser
50
51
52def main(argv: Optional[List[str]] = None) -> Optional[int]:
53 parser = get_parser()
54 opts = parser.parse_args(argv)
55
56 if not osutils.IsRootUser():
57 result = cros_build_lib.sudo_run(
58 [os.path.join(constants.CHROMITE_SCRIPTS_DIR, "cros_losetup")]
59 + argv,
60 check=False,
61 )
62 return result.returncode
63
64 if opts.subcommand == "attach":
65 loop_path = image_lib.LoopbackPartitions.attach_image(opts.path)
66 print(
67 pformat.json(
68 {
69 "path": loop_path,
70 },
71 compact=not sys.stdin.isatty(),
72 ),
73 end="",
74 )
75 elif opts.subcommand == "detach":
76 if not image_lib.LoopbackPartitions.detach_loopback(opts.path):
77 return 1
78 else:
79 raise RuntimeError(f"Unknown command: {opts.subcommand}")
80
81 return 0