2 QEMU Object Model FUSE filesystem tool
4 This script offers a simple FUSE filesystem within which the QOM tree
5 may be browsed, queried and edited using traditional shell tooling.
7 This script requires the 'fusepy' python package.
10 usage: qom-fuse [-h] [--socket SOCKET] <mount>
12 Mount a QOM tree as a FUSE filesystem
18 -h, --help show this help message and exit
19 --socket SOCKET, -s SOCKET
20 QMP socket path or address (addr:port). May also be
21 set via QMP_SOCKET environment variable.
24 # Copyright IBM, Corp. 2012
25 # Copyright (C) 2020 Red Hat, Inc.
31 # This work is licensed under the terms of the GNU GPL, version 2 or later.
32 # See the COPYING file in the top-level directory.
36 from errno import ENOENT, EPERM
49 from fuse import FUSE, FuseOSError, Operations
51 from qemu.aqmp import ExecuteError
53 from .qom_common import QOMCommand
56 fuse.fuse_python_api = (0, 2)
59 class QOMFuse(QOMCommand, Operations):
61 QOMFuse implements both fuse.Operations and QOMCommand.
63 Operations implements the FS, and QOMCommand implements the CLI command.
66 help = 'Mount a QOM tree as a FUSE filesystem'
70 def configure_parser(cls, parser: argparse.ArgumentParser) -> None:
71 super().configure_parser(parser)
79 def __init__(self, args: argparse.Namespace):
80 super().__init__(args)
81 self.mount = args.mount
82 self.ino_map: Dict[str, int] = {}
86 print(f"Mounting QOMFS to '{self.mount}'", file=sys.stderr)
87 self.fuse = FUSE(self, self.mount, foreground=True)
90 def get_ino(self, path: str) -> int:
91 """Get an inode number for a given QOM path."""
92 if path in self.ino_map:
93 return self.ino_map[path]
94 self.ino_map[path] = self.ino_count
96 return self.ino_map[path]
98 def is_object(self, path: str) -> bool:
99 """Is the given QOM path an object?"""
106 def is_property(self, path: str) -> bool:
107 """Is the given QOM path a property?"""
108 path, prop = path.rsplit('/', 1)
112 for item in self.qom_list(path):
113 if item.name == prop:
119 def is_link(self, path: str) -> bool:
120 """Is the given QOM path a link?"""
121 path, prop = path.rsplit('/', 1)
125 for item in self.qom_list(path):
126 if item.name == prop and item.link:
132 def read(self, path: str, size: int, offset: int, fh: IO[bytes]) -> bytes:
133 if not self.is_property(path):
134 raise FuseOSError(ENOENT)
136 path, prop = path.rsplit('/', 1)
140 data = str(self.qmp.command('qom-get', path=path, property=prop))
141 data += '\n' # make values shell friendly
142 except ExecuteError as err:
143 raise FuseOSError(EPERM) from err
145 if offset > len(data):
148 return bytes(data[offset:][:size], encoding='utf-8')
150 def readlink(self, path: str) -> Union[bool, str]:
151 if not self.is_link(path):
153 path, prop = path.rsplit('/', 1)
154 prefix = '/'.join(['..'] * (len(path.split('/')) - 1))
155 return prefix + str(self.qmp.command('qom-get', path=path,
158 def getattr(self, path: str,
159 fh: Optional[IO[bytes]] = None) -> Mapping[str, object]:
160 if self.is_link(path):
162 'st_mode': 0o755 | stat.S_IFLNK,
163 'st_ino': self.get_ino(path),
173 elif self.is_object(path):
175 'st_mode': 0o755 | stat.S_IFDIR,
176 'st_ino': self.get_ino(path),
186 elif self.is_property(path):
188 'st_mode': 0o644 | stat.S_IFREG,
189 'st_ino': self.get_ino(path),
200 raise FuseOSError(ENOENT)
203 def readdir(self, path: str, fh: IO[bytes]) -> Iterator[str]:
206 for item in self.qom_list(path):