684750ab4f
Use a shorter name. We are going to move in iotests from qmp() to command() where possible. But command() is longer than qmp() and don't look better. Let's rename. You can simply grep for '\.command(' and for 'def command(' to check that everything is updated (command() in tests/docker/docker.py is unrelated). Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Reviewed-by: Cédric Le Goater <clg@kaod.org> Reviewed-by: Juan Quintela <quintela@redhat.com> Message-id: 20231006154125.1068348-6-vsementsov@yandex-team.ru [vsementsov: also update three occurrences in tests/avocado/machine_aspeed.py and keep r-b] 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.cmd('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.cmd('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
|