]>
Commit | Line | Data |
---|---|---|
3d004a37 | 1 | #!/usr/bin/env python3 |
187be27c JS |
2 | """ |
3 | QEMU Object Model FUSE filesystem tool | |
4 | ||
5 | This script offers a simple FUSE filesystem within which the QOM tree | |
6 | may be browsed, queried and edited using traditional shell tooling. | |
7 | ||
8 | This script requires the 'fusepy' python package. | |
9 | ||
10 | ENV: | |
11 | QMP_SOCKET: Path to the QMP server socket | |
12 | ||
13 | Usage: | |
14 | qom-fuse /mount/to/here | |
15 | """ | |
5ade7674 | 16 | ## |
5ade7674 | 17 | # Copyright IBM, Corp. 2012 |
f713ed4f | 18 | # Copyright (C) 2020 Red Hat, Inc. |
5ade7674 AL |
19 | # |
20 | # Authors: | |
21 | # Anthony Liguori <aliguori@us.ibm.com> | |
f713ed4f | 22 | # Markus Armbruster <armbru@redhat.com> |
5ade7674 | 23 | # |
26c1ccad JS |
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. | |
5ade7674 AL |
26 | ## |
27 | ||
26c1ccad | 28 | from errno import ENOENT, EPERM |
c6b7eae9 | 29 | import os |
c6b7eae9 JS |
30 | import stat |
31 | import sys | |
32 | ||
33 | import fuse | |
34 | from fuse import FUSE, FuseOSError, Operations | |
35 | ||
c7b942d7 JS |
36 | |
37 | sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) | |
7552823a | 38 | from qemu.qmp import QEMUMonitorProtocol, QMPResponseError |
5ade7674 | 39 | |
c6b7eae9 | 40 | |
5ade7674 AL |
41 | fuse.fuse_python_api = (0, 2) |
42 | ||
26c1ccad | 43 | |
f713ed4f | 44 | class QOMFS(Operations): |
187be27c | 45 | """QOMFS implements fuse.Operations to provide a QOM filesystem.""" |
f713ed4f | 46 | def __init__(self, qmp): |
5ade7674 AL |
47 | self.qmp = qmp |
48 | self.qmp.connect() | |
49 | self.ino_map = {} | |
50 | self.ino_count = 1 | |
51 | ||
52 | def get_ino(self, path): | |
187be27c | 53 | """Get an inode number for a given QOM path.""" |
d7a4228e | 54 | if path in self.ino_map: |
5ade7674 AL |
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): | |
187be27c | 61 | """Is the given QOM path an object?""" |
5ade7674 | 62 | try: |
26c1ccad | 63 | self.qmp.command('qom-list', path=path) |
5ade7674 | 64 | return True |
7552823a | 65 | except QMPResponseError: |
5ade7674 AL |
66 | return False |
67 | ||
68 | def is_property(self, path): | |
187be27c | 69 | """Is the given QOM path a property?""" |
3a14019e MA |
70 | path, prop = path.rsplit('/', 1) |
71 | if path == '': | |
72 | path = '/' | |
5ade7674 | 73 | try: |
5ade7674 AL |
74 | for item in self.qmp.command('qom-list', path=path): |
75 | if item['name'] == prop: | |
76 | return True | |
77 | return False | |
7552823a | 78 | except QMPResponseError: |
5ade7674 AL |
79 | return False |
80 | ||
81 | def is_link(self, path): | |
187be27c | 82 | """Is the given QOM path a link?""" |
3a14019e MA |
83 | path, prop = path.rsplit('/', 1) |
84 | if path == '': | |
85 | path = '/' | |
5ade7674 | 86 | try: |
5ade7674 AL |
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 | |
7552823a | 93 | except QMPResponseError: |
5ade7674 AL |
94 | return False |
95 | ||
7552823a | 96 | def read(self, path, size, offset, fh): |
5ade7674 AL |
97 | if not self.is_property(path): |
98 | return -ENOENT | |
99 | ||
100 | path, prop = path.rsplit('/', 1) | |
3a14019e MA |
101 | if path == '': |
102 | path = '/' | |
5ade7674 | 103 | try: |
f713ed4f | 104 | data = self.qmp.command('qom-get', path=path, property=prop) |
26c1ccad | 105 | data += '\n' # make values shell friendly |
7552823a JS |
106 | except QMPResponseError as err: |
107 | raise FuseOSError(EPERM) from err | |
5ade7674 AL |
108 | |
109 | if offset > len(data): | |
110 | return '' | |
111 | ||
7552823a | 112 | return bytes(data[offset:][:size], encoding='utf-8') |
5ade7674 AL |
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 | ||
f713ed4f | 122 | def getattr(self, path, fh=None): |
5ade7674 | 123 | if self.is_link(path): |
26c1ccad JS |
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 | } | |
5ade7674 | 136 | elif self.is_object(path): |
26c1ccad JS |
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 | } | |
5ade7674 | 149 | elif self.is_property(path): |
26c1ccad JS |
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 | } | |
5ade7674 | 162 | else: |
f713ed4f | 163 | raise FuseOSError(ENOENT) |
5ade7674 AL |
164 | return value |
165 | ||
f713ed4f MA |
166 | def readdir(self, path, fh): |
167 | yield '.' | |
168 | yield '..' | |
5ade7674 | 169 | for item in self.qmp.command('qom-list', path=path): |
f713ed4f | 170 | yield str(item['name']) |
5ade7674 | 171 | |
5ade7674 | 172 | |
26c1ccad | 173 | if __name__ == '__main__': |
f713ed4f MA |
174 | fuse = FUSE(QOMFS(QEMUMonitorProtocol(os.environ['QMP_SOCKET'])), |
175 | sys.argv[1], foreground=True) |