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