Added comments and changed to using the syslog module instead of print
[invirt/packages/invirt-console.git] / files / usr / bin / sipb-xen-consolefs
index 243a4cf..d5187c5 100755 (executable)
@@ -10,19 +10,14 @@ 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)
 
 realpath = "/home/machines/"
 
-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):
        """
        Return the depth of a given path, zero-based from root ('/')
@@ -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:
@@ -59,32 +56,54 @@ 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)
                self.lasttime = time()
                self.allow_other = 1
-               print 'Init complete.'
+               
+               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:
@@ -108,60 +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 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):
-               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):
-               print '*** getdir', 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()
+                       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', '.', '..'])
+                       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)) + ['.', '..']
-               return [(i, 0) for i in contents]
+                       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
+               # 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):
@@ -170,6 +227,16 @@ 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')