]> git.proxmox.com Git - mirror_qemu.git/blame - python/qemu/utils/qom_fuse.py
python/qemu: rename command() to cmd()
[mirror_qemu.git] / python / qemu / utils / qom_fuse.py
CommitLineData
187be27c
JS
1"""
2QEMU Object Model FUSE filesystem tool
3
4This script offers a simple FUSE filesystem within which the QOM tree
5may be browsed, queried and edited using traditional shell tooling.
6
7This script requires the 'fusepy' python package.
8
187be27c 9
2aa10179
JS
10usage: qom-fuse [-h] [--socket SOCKET] <mount>
11
12Mount a QOM tree as a FUSE filesystem
13
14positional arguments:
15 <mount> Mount point
16
17optional arguments:
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.
187be27c 22"""
5ade7674 23##
5ade7674 24# Copyright IBM, Corp. 2012
f713ed4f 25# Copyright (C) 2020 Red Hat, Inc.
5ade7674
AL
26#
27# Authors:
28# Anthony Liguori <aliguori@us.ibm.com>
f713ed4f 29# Markus Armbruster <armbru@redhat.com>
5ade7674 30#
26c1ccad
JS
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.
5ade7674
AL
33##
34
2aa10179 35import argparse
26c1ccad 36from errno import ENOENT, EPERM
c6b7eae9
JS
37import stat
38import sys
30ec845c
JS
39from typing import (
40 IO,
41 Dict,
42 Iterator,
43 Mapping,
44 Optional,
45 Union,
46)
c6b7eae9
JS
47
48import fuse
49from fuse import FUSE, FuseOSError, Operations
50
37094b6d 51from qemu.qmp import ExecuteError
8d6cdc51 52
173d185d 53from .qom_common import QOMCommand
5ade7674 54
c6b7eae9 55
5ade7674
AL
56fuse.fuse_python_api = (0, 2)
57
26c1ccad 58
2aa10179
JS
59class QOMFuse(QOMCommand, Operations):
60 """
61 QOMFuse implements both fuse.Operations and QOMCommand.
62
63 Operations implements the FS, and QOMCommand implements the CLI command.
64 """
65 name = 'fuse'
66 help = 'Mount a QOM tree as a FUSE filesystem'
67 fuse: FUSE
68
69 @classmethod
70 def configure_parser(cls, parser: argparse.ArgumentParser) -> None:
71 super().configure_parser(parser)
72 parser.add_argument(
73 'mount',
74 metavar='<mount>',
75 action='store',
76 help="Mount point",
77 )
78
79 def __init__(self, args: argparse.Namespace):
80 super().__init__(args)
81 self.mount = args.mount
82 self.ino_map: Dict[str, int] = {}
5ade7674
AL
83 self.ino_count = 1
84
2aa10179
JS
85 def run(self) -> int:
86 print(f"Mounting QOMFS to '{self.mount}'", file=sys.stderr)
87 self.fuse = FUSE(self, self.mount, foreground=True)
88 return 0
89
30ec845c 90 def get_ino(self, path: str) -> int:
187be27c 91 """Get an inode number for a given QOM path."""
d7a4228e 92 if path in self.ino_map:
5ade7674
AL
93 return self.ino_map[path]
94 self.ino_map[path] = self.ino_count
95 self.ino_count += 1
96 return self.ino_map[path]
97
30ec845c 98 def is_object(self, path: str) -> bool:
187be27c 99 """Is the given QOM path an object?"""
5ade7674 100 try:
9ec8a386 101 self.qom_list(path)
5ade7674 102 return True
8d6cdc51 103 except ExecuteError:
5ade7674
AL
104 return False
105
30ec845c 106 def is_property(self, path: str) -> bool:
187be27c 107 """Is the given QOM path a property?"""
3a14019e
MA
108 path, prop = path.rsplit('/', 1)
109 if path == '':
110 path = '/'
5ade7674 111 try:
9ec8a386
JS
112 for item in self.qom_list(path):
113 if item.name == prop:
5ade7674
AL
114 return True
115 return False
8d6cdc51 116 except ExecuteError:
5ade7674
AL
117 return False
118
30ec845c 119 def is_link(self, path: str) -> bool:
187be27c 120 """Is the given QOM path a link?"""
3a14019e
MA
121 path, prop = path.rsplit('/', 1)
122 if path == '':
123 path = '/'
5ade7674 124 try:
9ec8a386
JS
125 for item in self.qom_list(path):
126 if item.name == prop and item.link:
127 return True
5ade7674 128 return False
8d6cdc51 129 except ExecuteError:
5ade7674
AL
130 return False
131
30ec845c 132 def read(self, path: str, size: int, offset: int, fh: IO[bytes]) -> bytes:
5ade7674 133 if not self.is_property(path):
2cea7134 134 raise FuseOSError(ENOENT)
5ade7674
AL
135
136 path, prop = path.rsplit('/', 1)
3a14019e
MA
137 if path == '':
138 path = '/'
5ade7674 139 try:
684750ab 140 data = str(self.qmp.cmd('qom-get', path=path, property=prop))
26c1ccad 141 data += '\n' # make values shell friendly
8d6cdc51 142 except ExecuteError as err:
7552823a 143 raise FuseOSError(EPERM) from err
5ade7674
AL
144
145 if offset > len(data):
2cea7134 146 return b''
5ade7674 147
7552823a 148 return bytes(data[offset:][:size], encoding='utf-8')
5ade7674 149
30ec845c 150 def readlink(self, path: str) -> Union[bool, str]:
5ade7674
AL
151 if not self.is_link(path):
152 return False
153 path, prop = path.rsplit('/', 1)
154 prefix = '/'.join(['..'] * (len(path.split('/')) - 1))
684750ab
VSO
155 return prefix + str(self.qmp.cmd('qom-get', path=path,
156 property=prop))
5ade7674 157
30ec845c
JS
158 def getattr(self, path: str,
159 fh: Optional[IO[bytes]] = None) -> Mapping[str, object]:
5ade7674 160 if self.is_link(path):
26c1ccad
JS
161 value = {
162 'st_mode': 0o755 | stat.S_IFLNK,
163 'st_ino': self.get_ino(path),
164 'st_dev': 0,
165 'st_nlink': 2,
166 'st_uid': 1000,
167 'st_gid': 1000,
168 'st_size': 4096,
169 'st_atime': 0,
170 'st_mtime': 0,
171 'st_ctime': 0
172 }
5ade7674 173 elif self.is_object(path):
26c1ccad
JS
174 value = {
175 'st_mode': 0o755 | stat.S_IFDIR,
176 'st_ino': self.get_ino(path),
177 'st_dev': 0,
178 'st_nlink': 2,
179 'st_uid': 1000,
180 'st_gid': 1000,
181 'st_size': 4096,
182 'st_atime': 0,
183 'st_mtime': 0,
184 'st_ctime': 0
185 }
5ade7674 186 elif self.is_property(path):
26c1ccad
JS
187 value = {
188 'st_mode': 0o644 | stat.S_IFREG,
189 'st_ino': self.get_ino(path),
190 'st_dev': 0,
191 'st_nlink': 1,
192 'st_uid': 1000,
193 'st_gid': 1000,
194 'st_size': 4096,
195 'st_atime': 0,
196 'st_mtime': 0,
197 'st_ctime': 0
198 }
5ade7674 199 else:
f713ed4f 200 raise FuseOSError(ENOENT)
5ade7674
AL
201 return value
202
30ec845c 203 def readdir(self, path: str, fh: IO[bytes]) -> Iterator[str]:
f713ed4f
MA
204 yield '.'
205 yield '..'
9ec8a386
JS
206 for item in self.qom_list(path):
207 yield item.name