#!/usr/bin/python import fuse from fuse import Fuse from time import time import stat # for file properties import os # for filesystem modes (O_RDONLY, etc) import errno # for error number codes (ENOENT, etc) # - note: these must be returned as negatives from syslog import * import sipb_xen_database from invirt.config import structs as config fuse.fuse_python_api = (0, 2) def getDepth(path): """ Return the depth of a given path, zero-based from root ('/') """ if path == '/': return 0 else: return path.count('/') def getParts(path): """ Return the slash-separated parts of a given path as a list """ # [1:] to exclude leading empty element split = path.split('/') if split[-1]: return split[1:] else: return split[1:-1] def parse(path): parts = getParts(path) return parts, len(parts) class MyStat: def __init__(self): self.st_mode = 0 self.st_ino = 0 self.st_dev = 0 self.st_nlink = 0 self.st_uid = 0 self.st_gid = 0 self.st_size = 0 self.st_atime = 0 self.st_mtime = 0 self.st_ctime = 0 def toTuple(self): return (self.st_mode, self.st_ino, self.st_dev, self.st_nlink, self.st_uid, self.st_gid, self.st_size, self.st_atime, self.st_mtime, self.st_ctime) class RemConfFS(Fuse): """ RemConfFS creates a filesytem for configuring remctl, like this: / |-- acl | |-- machine1 | ... | `-- machinen `-- conf.d |-- machine1 ... `-- machinen The machine list and the acls are drawn from a database. This filesystem only implements the getattr, getdir, read, and readlink calls, because this is a read-only filesystem. """ def __init__(self, *args, **kw): """Initialize the filesystem and set it to allow_other access besides the user who mounts the filesystem (i.e. root) """ Fuse.__init__(self, *args, **kw) self.lasttime = time() self.allow_other = 1 openlog('sipb-xen-remconffs ', LOG_PID, LOG_DAEMON) syslog(LOG_DEBUG, 'Init complete.') def getMachines(self): """Get the list of VMs in the database, clearing the cache if it's older than 15 seconds""" if time() - self.lasttime > 15: self.lasttime = time() sipb_xen_database.clear_cache() return [machine.name for machine in sipb_xen_database.Machine.select()] def getacl(self, machine_name): """Build the ACL file for a machine """ machine = sipb_xen_database.Machine.get_by(name=machine_name) users = [acl.user for acl in machine.acl] return "\n".join(map(self.userToPrinc, users) + ['include /etc/remctl/acl/web', '']) def getconf(self): """Build the master conf file, with all machines """ return '\n'.join("control %s /usr/sbin/sipb-xen-remote-proxy-control" " /etc/remctl/remconffs/acl/%s" % (machine_name, machine_name) for machine_name in self.getMachines())+'\n' def userToPrinc(self, user): """Convert Kerberos v4-style names to v5-style and append a default realm if none is specified """ if '@' in user: (princ, realm) = user.split('@') else: princ = user realm = config.authn[0].realm return princ.replace('.', '/') + '@' + realm def getattr(self, path): """ - st_mode (protection bits) - st_ino (inode number) - st_dev (device) - st_nlink (number of hard links) - st_uid (user ID of owner) - st_gid (group ID of owner) - st_size (size of file, in bytes) - st_atime (time of most recent access) - st_mtime (time of most recent content modification) - st_ctime (platform dependent; time of most recent metadata change on Unix, or the time of creation on Windows). """ syslog(LOG_DEBUG, "*** getattr: " + path) depth = getDepth(path) parts = getParts(path) st = MyStat() if path == '/': st.st_mode = stat.S_IFDIR | 0755 st.st_nlink = 2 elif depth == 1: if parts[0] == 'acl': st.st_mode = stat.S_IFDIR | 0755 st.st_nlink = 2 elif parts[0] == 'conf': st.st_mode = stat.S_IFREG | 0444 st.st_nlink = 1 st.st_size = len(self.getconf()) else: return -errno.ENOENT elif depth == 2: if parts[0] != 'acl': return -errno.ENOENT if parts[1] not in self.getMachines(): return -errno.ENOENT st.st_mode = stat.S_IFREG | 0444 st.st_nlink = 1 st.st_size = len(self.getacl(parts[1])) return st.toTuple() # This call isn't actually used in the version of Fuse on console, but we # wanted to leave it implemented to ease the transition in the future def readdir(self, path, offset): """Return a generator with the listing for a directory """ syslog(LOG_DEBUG, '*** readdir %s %s' % (path, offset)) for (value, zero) in self.getdir(path): yield fuse.Direntry(value) def getdir(self, path): """Return a list of tuples of the form (item, 0) with the contents of the directory path Fuse doesn't add '.' or '..' on its own, so we have to """ syslog(LOG_DEBUG, '*** getdir %s' % path) parts, depth = parse(path) if depth == 0: contents = ('acl', 'conf') elif depth == 1: if parts[0] == 'acl': contents = self.getMachines() else: return -errno.ENOENT else: return -errno.ENOTDIR # Format the list the way that Fuse wants it - and don't forget to add # '.' and '..' return [(i, 0) for i in (list(contents) + ['.', '..'])] def read(self, path, length, offset): """Read length bytes starting at offset of path. In most cases, this just gets passed on to the OS """ syslog(LOG_DEBUG, '*** read %s %s %s' % (path, length, offset)) parts, depth = parse(path) if depth == 0: return -errno.EISDIR elif parts[0] == 'conf': return self.getconf()[offset:offset+length] elif parts[0] == 'acl': if depth == 1: return -errno.EISDIR if parts[1] in self.getMachines(): return self.getacl(parts[1])[offset:offset+length] return -errno.ENOENT def readlink(self, path): syslog(LOG_DEBUG, '*** readlink %s' % path) return -errno.ENOENT if __name__ == '__main__': sipb_xen_database.connect(db.uri) usage=""" $0 [mount_path] """ server = RemConfFS() server.flags = 0 server.main()