X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/invirt-console.git/blobdiff_plain/b78f64c598c8db7c0106f25a3bd2ad83e7d4ce45..fbdc5f70da412104a209617ca38b091f0797f411:/files/usr/bin/sipb-xen-consolefs diff --git a/files/usr/bin/sipb-xen-consolefs b/files/usr/bin/sipb-xen-consolefs index 7cd3766..d5187c5 100755 --- a/files/usr/bin/sipb-xen-consolefs +++ b/files/usr/bin/sipb-xen-consolefs @@ -10,18 +10,13 @@ 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 + fuse.fuse_python_api = (0, 2) -machines = ['moo17', 'remus'] realpath = "/home/machines/" -uid = 1000 - -def dirFromList(list): - """ - Return a properly formatted list of items suitable to a directory listing. - ['a', 'b', 'c'] => [('a', 0), ('b', 0), ('c', 0)] - """ - return [(x, 0) for x in list] def getDepth(path): """ @@ -39,6 +34,8 @@ def getParts(path): if path == '/': return ['/'] else: + # [1:] because otherwise you get an empty list element from the + # initial '/' return path[1:].split('/') class MyStat: @@ -47,7 +44,7 @@ class MyStat: self.st_ino = 0 self.st_dev = 0 self.st_nlink = 0 - self.st_uid = uid + self.st_uid = 0 self.st_gid = 0 self.st_size = 0 self.st_atime = 0 @@ -59,15 +56,62 @@ class MyStat: class ConsoleFS(Fuse): """ + 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) - print 'Init complete.' + self.lasttime = time() + self.allow_other = 1 + + openlog('sipb-xen-consolefs ', LOG_PID, LOG_DAEMON) + + syslog(LOG_DEBUG, 'Init complete.') def mirrorPath(self, path): + """Translate a virtual path to its real path counterpart""" return realpath + "/".join(getParts(path)[1:]) + 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 getUid(self, machine_name): + """Calculate the UID of a machine-account, which is just machine_id+1000 + """ + return sipb_xen_database.Machine.get_by(name=machine_name).machine_id + 1000 + + def getK5login(self, machine_name): + """Build the ACL for a machine and turn it into a .k5login file + """ + 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) + ['']) + + 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 = "ATHENA.MIT.EDU" + + return princ.replace('.', '/') + '@' + realm + def getattr(self, path): """ - st_mode (protection bits) @@ -83,48 +127,98 @@ class ConsoleFS(Fuse): or the time of creation on Windows). """ - print "*** getattr: " + path + 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 machines: + 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 = 17 + 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: - st = os.lstat(self.mirrorPath(path)) + 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): - print '*** readdir', 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 == '/': - for r in ['.', '..']+machines: - yield fuse.Direntry(r) + 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: - for r in set(os.listdir(self.mirrorPath(path)) + ['.k5login']): - yield fuse.Direntry(r) + 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: - for r in os.listdir(self.mirrorPath(path)): - yield fuse.Direntry(r) + 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 ): - print '*** read', path, length, offset + 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 - elif getParts(path)[1:] == ['.k5login']: - pass + # 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): @@ -133,8 +227,19 @@ class ConsoleFS(Fuse): 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__': + sipb_xen_database.connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen') usage=""" ConsoleFS [mount_path] """