From: Evan Broder Date: Sun, 5 Oct 2008 09:27:30 +0000 (-0400) Subject: Make ConsoleFS RouteFS-based X-Git-Tag: sipb-xen-console/8.1^0 X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/invirt-console.git/commitdiff_plain/66429809208491c7cc08a847471a3b86d71e9ff1?ds=sidebyside Make ConsoleFS RouteFS-based svn path=/trunk/packages/sipb-xen-console/; revision=1064 --- diff --git a/debian/changelog b/debian/changelog index ed08e24..f6e31e5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +sipb-xen-console (8.1) unstable; urgency=low + + * ConsoleFS is now RouteFS-based + + -- Evan Broder Sun, 05 Oct 2008 05:26:52 -0400 + sipb-xen-console (8.0) unstable; urgency=low * Update config files to work with Hardy diff --git a/debian/control b/debian/control index 77b05e0..8233836 100644 --- a/debian/control +++ b/debian/control @@ -9,7 +9,7 @@ Package: sipb-xen-console Architecture: all Provides: ${diverted-files} Conflicts: ${diverted-files} -Depends: sipb-xen-base, ${shlibs:Depends}, ${misc:Depends}, conserver-client, daemon, debathena-kerberos-config, fuse-utils, libnss-pgsql1, nscd, openssh-server, python, python-fuse, sipb-xen-chrony-config, sipb-xen-database-common, remctl-server +Depends: sipb-xen-base, ${shlibs:Depends}, ${misc:Depends}, conserver-client, daemon, debathena-kerberos-config, fuse-utils, libnss-pgsql1, nscd, openssh-server, python, python-routefs, sipb-xen-chrony-config, sipb-xen-database-common, remctl-server Description: SIPB Xen serial console server This package should be installed on sipb-xen-console It makes sure that necessary tools are available. diff --git a/files/usr/bin/sipb-xen-consolefs b/files/usr/bin/sipb-xen-consolefs index 3255b15..aa7d1ca 100755 --- a/files/usr/bin/sipb-xen-consolefs +++ b/files/usr/bin/sipb-xen-consolefs @@ -1,106 +1,80 @@ #!/usr/bin/python -import fuse -from fuse import Fuse +import routefs +from routes import Mapper +from syslog import * 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 os +import errno from invirt.config import structs as config from invirt import database -fuse.fuse_python_api = (0, 2) - realpath = "/home/machines/" -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 - """ - if path == '/': - return ['/'] - else: - # [1:] because otherwise you get an empty list element from the - # initial '/' - return path[1:].split('/') - -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 ConsoleFS(Fuse): +class ConsoleFS(routefs.RouteFS): """ ConsoleFS creates a series of subdirectories each mirroring the same real directory, except for a single file - the .k5login - which is dynamically generated for each subdirectory - - This filesystem only implements the getattr, getdir, read, and readlink - calls, beacuse 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) + super(ConsoleFS, self).__init__(*args, **kw) self.lasttime = time() - self.allow_other = 1 + self.fuse_args.add("allow_other", True) openlog('sipb-xen-consolefs ', LOG_PID, LOG_DAEMON) syslog(LOG_DEBUG, 'Init complete.') + + def make_map(self): + m = Mapper() + m.connect('', controller='getMachines') + m.connect(':machine', controller='getMirror') + m.connect(':machine/.k5login', controller='getK5login') + m.connect(':machine/*(path)', controller='getMirror') + return m - def mirrorPath(self, path): - """Translate a virtual path to its real path counterpart""" - return realpath + "/".join(getParts(path)[1:]) - - def getMachines(self): + def getMachines(self, **kw): """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() database.clear_cache() - return [machine.name for machine in database.Machine.select()] + return [machine.name for machine in database.Machine.query()] - def getUid(self, machine_name): - """Calculate the UID of a machine-account, which is just machine_id+1000 + def getMirror(self, machine, path='', **kw): + """Translate the path into its realpath equivalent, and return that """ - return database.Machine.get_by(name=machine_name).machine_id + 1000 + real = realpath + path + if os.path.isdir(real): + # The list is converted to a set so that we can handle the case + # where there is already a .k5login in the realpath gracefully + return routefs.Directory(set(os.listdir(real) + ['.k5login'])) + elif os.path.islink(real): + return routefs.Symlink(os.readlink(real)) + elif os.path.isfile(real): + return open(real).read() + else: + return -errno.EINVAL - def getK5login(self, machine_name): + def getK5login(self, machine, **kw): """Build the ACL for a machine and turn it into a .k5login file """ - machine = database.Machine.get_by(name=machine_name) + machine = database.Machine.query().filter_by(name=machine).one() users = [acl.user for acl in machine.acl] return "\n".join(map(self.userToPrinc, users) + ['']) + def mirrorPath(self, path): + """Translate a virtual path to its real path counterpart""" + return realpath + "/".join(getParts(path)[1:]) + def userToPrinc(self, user): """Convert Kerberos v4-style names to v5-style and append a default realm if none is specified @@ -112,138 +86,7 @@ class ConsoleFS(Fuse): 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() - # / is a directory - if path == '/': - st.st_mode = stat.S_IFDIR | 0755 - st.st_nlink = 2 - # /foo is a directory if foo is a machine - otherwise it doesn't exist - elif depth == 1: - if parts[-1] in self.getMachines(): - st.st_mode = stat.S_IFDIR | 0755 - st.st_nlink = 2 - # Homedirs should be owned by the user whose homedir it is - st.st_uid = st.st_gid = self.getUid(parts[0]) - else: - return -errno.ENOENT - # Catch the .k5login file, because it's a special case - elif depth == 2 and parts[-1] == '.k5login': - st.st_mode = stat.S_IFREG | 0444 - st.st_nlink = 1 - st.st_size = len(self.getK5login(parts[0])) - # The .k5login file should be owned by the user whose homedir it is - st.st_uid = st.st_gid = self.getUid(parts[0]) - # For anything else, we get the mirror path and call out to the OS - else: - stats = list(os.lstat(self.mirrorPath(path))) - # Shadow the UID and GID from the original homedir - stats[4:6] = [self.getUid(parts[0])] * 2 - return tuple(stats) - 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) - - # '/' contains a directory for each machine - if path == '/': - contents = self.getMachines() - # The directory for each machine contains the same files as the realpath - # but also the .k5login - # - # The list is converted to a set so that we can handle the case where - # there is already a .k5login in the realpath gracefully - elif getDepth(path) == 1: - contents = set(os.listdir(self.mirrorPath(path)) + ['.k5login']) - # If it's not the root of the homedir, just pass the call onto the OS - # for realpath - else: - contents = os.listdir(self.mirrorPath(path)) - # 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 = getParts(path) - - # If the depth is less than 2, then either it's a directory or the file - # doesn't exist - # (realistically this doesn't appear to ever happen) - if getDepth(path) < 2: - return -errno.ENOENT - # If we're asking for a real .k5login file, then create it and return - # the snippet requested - elif parts[1:] == ['.k5login']: - if parts[0] not in self.getMachines(): - return -errno.ENOENT - else: - return self.getK5login(parts[0])[offset:length + offset] - # Otherwise, pass the call onto the OS - # (note that the file will get closed when this call returns and the - # file descriptor goes out of scope) - else: - fname = self.mirrorPath(path) - if not os.path.isfile(fname): - return -errno.ENOENT - else: - f = open(fname) - f.seek(offset) - return f.read(length) - - def readlink(self, path): - syslog(LOG_DEBUG, '*** readlink %s' % path) - - # There aren't any symlinks here - if getDepth(path) < 2: - return -errno.ENOENT - # But there might be here - else: - return os.readlink(self.mirrorPath(path)) if __name__ == '__main__': database.connect() - usage=""" -ConsoleFS [mount_path] -""" - server = ConsoleFS() - server.flags = 0 - server.main() + routefs.main(ConsoleFS)