37094b6dd5
Now that we are fully switched over to the new QMP library, move it back over the old namespace. This is being done primarily so that we may upload this package simply as "qemu.qmp" without introducing confusion over whether or not "aqmp" is a new protocol or not. The trade-off is increased confusion inside the QEMU developer tree. Sorry! Note: the 'private' member "_aqmp" in legacy.py also changes to "_qmp"; not out of necessity, but just to remove any traces of the "aqmp" name. Signed-off-by: John Snow <jsnow@redhat.com> Reviewed-by: Beraldo Leal <bleal@redhat.com> Acked-by: Hanna Reitz <hreitz@redhat.com> Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@openvz.org> Message-id: 20220330172812.3427355-8-jsnow@redhat.com Signed-off-by: John Snow <jsnow@redhat.com>
208 lines
5.8 KiB
Python
208 lines
5.8 KiB
Python
"""
|
|
QEMU Object Model FUSE filesystem tool
|
|
|
|
This script offers a simple FUSE filesystem within which the QOM tree
|
|
may be browsed, queried and edited using traditional shell tooling.
|
|
|
|
This script requires the 'fusepy' python package.
|
|
|
|
|
|
usage: qom-fuse [-h] [--socket SOCKET] <mount>
|
|
|
|
Mount a QOM tree as a FUSE filesystem
|
|
|
|
positional arguments:
|
|
<mount> Mount point
|
|
|
|
optional arguments:
|
|
-h, --help show this help message and exit
|
|
--socket SOCKET, -s SOCKET
|
|
QMP socket path or address (addr:port). May also be
|
|
set via QMP_SOCKET environment variable.
|
|
"""
|
|
##
|
|
# Copyright IBM, Corp. 2012
|
|
# Copyright (C) 2020 Red Hat, Inc.
|
|
#
|
|
# Authors:
|
|
# Anthony Liguori <aliguori@us.ibm.com>
|
|
# Markus Armbruster <armbru@redhat.com>
|
|
#
|
|
# This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
# See the COPYING file in the top-level directory.
|
|
##
|
|
|
|
import argparse
|
|
from errno import ENOENT, EPERM
|
|
import stat
|
|
import sys
|
|
from typing import (
|
|
IO,
|
|
Dict,
|
|
Iterator,
|
|
Mapping,
|
|
Optional,
|
|
Union,
|
|
)
|
|
|
|
import fuse
|
|
from fuse import FUSE, FuseOSError, Operations
|
|
|
|
from qemu.qmp import ExecuteError
|
|
|
|
from .qom_common import QOMCommand
|
|
|
|
|
|
fuse.fuse_python_api = (0, 2)
|
|
|
|
|
|
class QOMFuse(QOMCommand, Operations):
|
|
"""
|
|
QOMFuse implements both fuse.Operations and QOMCommand.
|
|
|
|
Operations implements the FS, and QOMCommand implements the CLI command.
|
|
"""
|
|
name = 'fuse'
|
|
help = 'Mount a QOM tree as a FUSE filesystem'
|
|
fuse: FUSE
|
|
|
|
@classmethod
|
|
def configure_parser(cls, parser: argparse.ArgumentParser) -> None:
|
|
super().configure_parser(parser)
|
|
parser.add_argument(
|
|
'mount',
|
|
metavar='<mount>',
|
|
action='store',
|
|
help="Mount point",
|
|
)
|
|
|
|
def __init__(self, args: argparse.Namespace):
|
|
super().__init__(args)
|
|
self.mount = args.mount
|
|
self.ino_map: Dict[str, int] = {}
|
|
self.ino_count = 1
|
|
|
|
def run(self) -> int:
|
|
print(f"Mounting QOMFS to '{self.mount}'", file=sys.stderr)
|
|
self.fuse = FUSE(self, self.mount, foreground=True)
|
|
return 0
|
|
|
|
def get_ino(self, path: str) -> int:
|
|
"""Get an inode number for a given QOM path."""
|
|
if path in self.ino_map:
|
|
return self.ino_map[path]
|
|
self.ino_map[path] = self.ino_count
|
|
self.ino_count += 1
|
|
return self.ino_map[path]
|
|
|
|
def is_object(self, path: str) -> bool:
|
|
"""Is the given QOM path an object?"""
|
|
try:
|
|
self.qom_list(path)
|
|
return True
|
|
except ExecuteError:
|
|
return False
|
|
|
|
def is_property(self, path: str) -> bool:
|
|
"""Is the given QOM path a property?"""
|
|
path, prop = path.rsplit('/', 1)
|
|
if path == '':
|
|
path = '/'
|
|
try:
|
|
for item in self.qom_list(path):
|
|
if item.name == prop:
|
|
return True
|
|
return False
|
|
except ExecuteError:
|
|
return False
|
|
|
|
def is_link(self, path: str) -> bool:
|
|
"""Is the given QOM path a link?"""
|
|
path, prop = path.rsplit('/', 1)
|
|
if path == '':
|
|
path = '/'
|
|
try:
|
|
for item in self.qom_list(path):
|
|
if item.name == prop and item.link:
|
|
return True
|
|
return False
|
|
except ExecuteError:
|
|
return False
|
|
|
|
def read(self, path: str, size: int, offset: int, fh: IO[bytes]) -> bytes:
|
|
if not self.is_property(path):
|
|
raise FuseOSError(ENOENT)
|
|
|
|
path, prop = path.rsplit('/', 1)
|
|
if path == '':
|
|
path = '/'
|
|
try:
|
|
data = str(self.qmp.command('qom-get', path=path, property=prop))
|
|
data += '\n' # make values shell friendly
|
|
except ExecuteError as err:
|
|
raise FuseOSError(EPERM) from err
|
|
|
|
if offset > len(data):
|
|
return b''
|
|
|
|
return bytes(data[offset:][:size], encoding='utf-8')
|
|
|
|
def readlink(self, path: str) -> Union[bool, str]:
|
|
if not self.is_link(path):
|
|
return False
|
|
path, prop = path.rsplit('/', 1)
|
|
prefix = '/'.join(['..'] * (len(path.split('/')) - 1))
|
|
return prefix + str(self.qmp.command('qom-get', path=path,
|
|
property=prop))
|
|
|
|
def getattr(self, path: str,
|
|
fh: Optional[IO[bytes]] = None) -> Mapping[str, object]:
|
|
if self.is_link(path):
|
|
value = {
|
|
'st_mode': 0o755 | stat.S_IFLNK,
|
|
'st_ino': self.get_ino(path),
|
|
'st_dev': 0,
|
|
'st_nlink': 2,
|
|
'st_uid': 1000,
|
|
'st_gid': 1000,
|
|
'st_size': 4096,
|
|
'st_atime': 0,
|
|
'st_mtime': 0,
|
|
'st_ctime': 0
|
|
}
|
|
elif self.is_object(path):
|
|
value = {
|
|
'st_mode': 0o755 | stat.S_IFDIR,
|
|
'st_ino': self.get_ino(path),
|
|
'st_dev': 0,
|
|
'st_nlink': 2,
|
|
'st_uid': 1000,
|
|
'st_gid': 1000,
|
|
'st_size': 4096,
|
|
'st_atime': 0,
|
|
'st_mtime': 0,
|
|
'st_ctime': 0
|
|
}
|
|
elif self.is_property(path):
|
|
value = {
|
|
'st_mode': 0o644 | stat.S_IFREG,
|
|
'st_ino': self.get_ino(path),
|
|
'st_dev': 0,
|
|
'st_nlink': 1,
|
|
'st_uid': 1000,
|
|
'st_gid': 1000,
|
|
'st_size': 4096,
|
|
'st_atime': 0,
|
|
'st_mtime': 0,
|
|
'st_ctime': 0
|
|
}
|
|
else:
|
|
raise FuseOSError(ENOENT)
|
|
return value
|
|
|
|
def readdir(self, path: str, fh: IO[bytes]) -> Iterator[str]:
|
|
yield '.'
|
|
yield '..'
|
|
for item in self.qom_list(path):
|
|
yield item.name
|