xref: /openbmc/qemu/scripts/qmp/qom-fuse (revision 187be27c)
1#!/usr/bin/env python3
2"""
3QEMU Object Model FUSE filesystem tool
4
5This script offers a simple FUSE filesystem within which the QOM tree
6may be browsed, queried and edited using traditional shell tooling.
7
8This script requires the 'fusepy' python package.
9
10ENV:
11    QMP_SOCKET: Path to the QMP server socket
12
13Usage:
14    qom-fuse /mount/to/here
15"""
16##
17# Copyright IBM, Corp. 2012
18# Copyright (C) 2020 Red Hat, Inc.
19#
20# Authors:
21#  Anthony Liguori   <aliguori@us.ibm.com>
22#  Markus Armbruster <armbru@redhat.com>
23#
24# This work is licensed under the terms of the GNU GPL, version 2 or later.
25# See the COPYING file in the top-level directory.
26##
27
28from errno import ENOENT, EPERM
29import os
30import stat
31import sys
32
33import fuse
34from fuse import FUSE, FuseOSError, Operations
35
36
37sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
38from qemu.qmp import QEMUMonitorProtocol, QMPResponseError
39
40
41fuse.fuse_python_api = (0, 2)
42
43
44class QOMFS(Operations):
45    """QOMFS implements fuse.Operations to provide a QOM filesystem."""
46    def __init__(self, qmp):
47        self.qmp = qmp
48        self.qmp.connect()
49        self.ino_map = {}
50        self.ino_count = 1
51
52    def get_ino(self, path):
53        """Get an inode number for a given QOM path."""
54        if path in self.ino_map:
55            return self.ino_map[path]
56        self.ino_map[path] = self.ino_count
57        self.ino_count += 1
58        return self.ino_map[path]
59
60    def is_object(self, path):
61        """Is the given QOM path an object?"""
62        try:
63            self.qmp.command('qom-list', path=path)
64            return True
65        except QMPResponseError:
66            return False
67
68    def is_property(self, path):
69        """Is the given QOM path a property?"""
70        path, prop = path.rsplit('/', 1)
71        if path == '':
72            path = '/'
73        try:
74            for item in self.qmp.command('qom-list', path=path):
75                if item['name'] == prop:
76                    return True
77            return False
78        except QMPResponseError:
79            return False
80
81    def is_link(self, path):
82        """Is the given QOM path a link?"""
83        path, prop = path.rsplit('/', 1)
84        if path == '':
85            path = '/'
86        try:
87            for item in self.qmp.command('qom-list', path=path):
88                if item['name'] == prop:
89                    if item['type'].startswith('link<'):
90                        return True
91                    return False
92            return False
93        except QMPResponseError:
94            return False
95
96    def read(self, path, size, offset, fh):
97        if not self.is_property(path):
98            return -ENOENT
99
100        path, prop = path.rsplit('/', 1)
101        if path == '':
102            path = '/'
103        try:
104            data = self.qmp.command('qom-get', path=path, property=prop)
105            data += '\n'  # make values shell friendly
106        except QMPResponseError as err:
107            raise FuseOSError(EPERM) from err
108
109        if offset > len(data):
110            return ''
111
112        return bytes(data[offset:][:size], encoding='utf-8')
113
114    def readlink(self, path):
115        if not self.is_link(path):
116            return False
117        path, prop = path.rsplit('/', 1)
118        prefix = '/'.join(['..'] * (len(path.split('/')) - 1))
119        return prefix + str(self.qmp.command('qom-get', path=path,
120                                             property=prop))
121
122    def getattr(self, path, fh=None):
123        if self.is_link(path):
124            value = {
125                'st_mode': 0o755 | stat.S_IFLNK,
126                'st_ino': self.get_ino(path),
127                'st_dev': 0,
128                'st_nlink': 2,
129                'st_uid': 1000,
130                'st_gid': 1000,
131                'st_size': 4096,
132                'st_atime': 0,
133                'st_mtime': 0,
134                'st_ctime': 0
135            }
136        elif self.is_object(path):
137            value = {
138                'st_mode': 0o755 | stat.S_IFDIR,
139                'st_ino': self.get_ino(path),
140                'st_dev': 0,
141                'st_nlink': 2,
142                'st_uid': 1000,
143                'st_gid': 1000,
144                'st_size': 4096,
145                'st_atime': 0,
146                'st_mtime': 0,
147                'st_ctime': 0
148            }
149        elif self.is_property(path):
150            value = {
151                'st_mode': 0o644 | stat.S_IFREG,
152                'st_ino': self.get_ino(path),
153                'st_dev': 0,
154                'st_nlink': 1,
155                'st_uid': 1000,
156                'st_gid': 1000,
157                'st_size': 4096,
158                'st_atime': 0,
159                'st_mtime': 0,
160                'st_ctime': 0
161            }
162        else:
163            raise FuseOSError(ENOENT)
164        return value
165
166    def readdir(self, path, fh):
167        yield '.'
168        yield '..'
169        for item in self.qmp.command('qom-list', path=path):
170            yield str(item['name'])
171
172
173if __name__ == '__main__':
174    fuse = FUSE(QOMFS(QEMUMonitorProtocol(os.environ['QMP_SOCKET'])),
175                sys.argv[1], foreground=True)
176