Changeset 1064


Ignore:
Timestamp:
Oct 5, 2008, 5:27:30 AM (16 years ago)
Author:
broder
Message:

Make ConsoleFS RouteFS-based

Location:
trunk/packages/sipb-xen-console
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/packages/sipb-xen-console/debian/changelog

    r1062 r1064  
     1sipb-xen-console (8.1) unstable; urgency=low
     2
     3  * ConsoleFS is now RouteFS-based
     4
     5 -- Evan Broder <broder@mit.edu>  Sun, 05 Oct 2008 05:26:52 -0400
     6
    17sipb-xen-console (8.0) unstable; urgency=low
    28
  • trunk/packages/sipb-xen-console/debian/control

    r1062 r1064  
    1010Provides: ${diverted-files}
    1111Conflicts: ${diverted-files}
    12 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
     12Depends: 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
    1313Description: SIPB Xen serial console server
    1414 This package  should be installed on sipb-xen-console
  • trunk/packages/sipb-xen-console/files/usr/bin/sipb-xen-consolefs

    r841 r1064  
    11#!/usr/bin/python
    22
    3 import fuse
    4 from fuse import Fuse
     3import routefs
     4from routes import Mapper
    55
     6from syslog import *
    67from time import time
    78
    8 import stat     # for file properties
    9 import os         # for filesystem modes (O_RDONLY, etc)
    10 import errno   # for error number codes (ENOENT, etc)
    11                            # - note: these must be returned as negatives
    12 
    13 from syslog import *
     9import os
     10import errno
    1411
    1512from invirt.config import structs as config
    1613from invirt import database
    1714
    18 fuse.fuse_python_api = (0, 2)
    19 
    2015realpath = "/home/machines/"
    2116
    22 def getDepth(path):
    23         """
    24         Return the depth of a given path, zero-based from root ('/')
    25         """
    26         if path == '/':
    27                 return 0
    28         else:
    29                 return path.count('/')
    30 
    31 def getParts(path):
    32         """
    33         Return the slash-separated parts of a given path as a list
    34         """
    35         if path == '/':
    36                 return ['/']
    37         else:
    38                 # [1:] because otherwise you get an empty list element from the
    39                 # initial '/'
    40                 return path[1:].split('/')
    41 
    42 class MyStat:
    43         def __init__(self):
    44                 self.st_mode = 0
    45                 self.st_ino = 0
    46                 self.st_dev = 0
    47                 self.st_nlink = 0
    48                 self.st_uid = 0
    49                 self.st_gid = 0
    50                 self.st_size = 0
    51                 self.st_atime = 0
    52                 self.st_mtime = 0
    53                 self.st_ctime = 0
    54        
    55         def toTuple(self):
    56                 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)
    57 
    58 class ConsoleFS(Fuse):
     17class ConsoleFS(routefs.RouteFS):
    5918        """
    6019        ConsoleFS creates a series of subdirectories each mirroring the same real
    6120        directory, except for a single file - the .k5login - which is dynamically
    6221        generated for each subdirectory
    63        
    64         This filesystem only implements the getattr, getdir, read, and readlink
    65         calls, beacuse this is a read-only filesystem
    6622        """
    6723       
     
    7026                the user who mounts the filesystem (i.e. root)
    7127                """
    72                 Fuse.__init__(self, *args, **kw)
     28                super(ConsoleFS, self).__init__(*args, **kw)
    7329                self.lasttime = time()
    74                 self.allow_other = 1
     30                self.fuse_args.add("allow_other", True)
    7531               
    7632                openlog('sipb-xen-consolefs ', LOG_PID, LOG_DAEMON)
    7733               
    7834                syslog(LOG_DEBUG, 'Init complete.')
     35
     36        def make_map(self):
     37                m = Mapper()
     38                m.connect('', controller='getMachines')
     39                m.connect(':machine', controller='getMirror')
     40                m.connect(':machine/.k5login', controller='getK5login')
     41                m.connect(':machine/*(path)', controller='getMirror')
     42                return m
    7943       
    80         def mirrorPath(self, path):
    81                 """Translate a virtual path to its real path counterpart"""
    82                 return realpath + "/".join(getParts(path)[1:])
    83        
    84         def getMachines(self):
     44        def getMachines(self, **kw):
    8545                """Get the list of VMs in the database, clearing the cache if it's
    8646                older than 15 seconds"""
     
    8848                        self.lasttime = time()
    8949                        database.clear_cache()
    90                 return [machine.name for machine in database.Machine.select()]
     50                return [machine.name for machine in database.Machine.query()]
    9151       
    92         def getUid(self, machine_name):
    93                 """Calculate the UID of a machine-account, which is just machine_id+1000
     52        def getMirror(self, machine, path='', **kw):
     53                """Translate the path into its realpath equivalent, and return that
    9454                """
    95                 return database.Machine.get_by(name=machine_name).machine_id + 1000
     55                real = realpath + path
     56                if os.path.isdir(real):
     57                        # The list is converted to a set so that we can handle the case
     58                        # where there is already a .k5login in the realpath gracefully   
     59                        return routefs.Directory(set(os.listdir(real) + ['.k5login']))
     60                elif os.path.islink(real):
     61                        return routefs.Symlink(os.readlink(real))
     62                elif os.path.isfile(real):
     63                        return open(real).read()
     64                else:
     65                        return -errno.EINVAL
    9666       
    97         def getK5login(self, machine_name):
     67        def getK5login(self, machine, **kw):
    9868                """Build the ACL for a machine and turn it into a .k5login file
    9969                """
    100                 machine = database.Machine.get_by(name=machine_name)
     70                machine = database.Machine.query().filter_by(name=machine).one()
    10171                users = [acl.user for acl in machine.acl]
    10272                return "\n".join(map(self.userToPrinc, users) + [''])
     73       
     74        def mirrorPath(self, path):
     75                """Translate a virtual path to its real path counterpart"""
     76                return realpath + "/".join(getParts(path)[1:])
    10377       
    10478        def userToPrinc(self, user):
     
    11387               
    11488                return princ.replace('.', '/') + '@' + realm
    115        
    116         def getattr(self, path):
    117                 """
    118                 - st_mode (protection bits)
    119                 - st_ino (inode number)
    120                 - st_dev (device)
    121                 - st_nlink (number of hard links)
    122                 - st_uid (user ID of owner)
    123                 - st_gid (group ID of owner)
    124                 - st_size (size of file, in bytes)
    125                 - st_atime (time of most recent access)
    126                 - st_mtime (time of most recent content modification)
    127                 - st_ctime (platform dependent; time of most recent metadata change on Unix,
    128                                         or the time of creation on Windows).
    129                 """
    130                
    131                 syslog(LOG_DEBUG, "*** getattr: " + path)
    132                
    133                 depth = getDepth(path)
    134                 parts = getParts(path)
    135                
    136                 st = MyStat()
    137                 # / is a directory
    138                 if path == '/':
    139                         st.st_mode = stat.S_IFDIR | 0755
    140                         st.st_nlink = 2
    141                 # /foo is a directory if foo is a machine - otherwise it doesn't exist
    142                 elif depth == 1:
    143                         if parts[-1] in self.getMachines():
    144                                 st.st_mode = stat.S_IFDIR | 0755
    145                                 st.st_nlink = 2
    146                                 # Homedirs should be owned by the user whose homedir it is
    147                                 st.st_uid = st.st_gid = self.getUid(parts[0])
    148                         else:
    149                                 return -errno.ENOENT
    150                 # Catch the .k5login file, because it's a special case
    151                 elif depth == 2 and parts[-1] == '.k5login':
    152                         st.st_mode = stat.S_IFREG | 0444
    153                         st.st_nlink = 1
    154                         st.st_size = len(self.getK5login(parts[0]))
    155                         # The .k5login file should be owned by the user whose homedir it is
    156                         st.st_uid = st.st_gid = self.getUid(parts[0])
    157                 # For anything else, we get the mirror path and call out to the OS
    158                 else:
    159                         stats = list(os.lstat(self.mirrorPath(path)))
    160                         # Shadow the UID and GID from the original homedir
    161                         stats[4:6] = [self.getUid(parts[0])] * 2
    162                         return tuple(stats)
    163                 return st.toTuple()
    164        
    165         # This call isn't actually used in the version of Fuse on console, but we
    166         # wanted to leave it implemented to ease the transition in the future
    167         def readdir(self, path, offset):
    168                 """Return a generator with the listing for a directory
    169                 """
    170                 syslog(LOG_DEBUG, '*** readdir %s %s' % (path, offset))
    171                 for (value, zero) in self.getdir(path):
    172                         yield fuse.Direntry(value)
    173        
    174         def getdir(self, path):
    175                 """Return a list of tuples of the form (item, 0) with the contents of
    176                 the directory path
    177                
    178                 Fuse doesn't add '.' or '..' on its own, so we have to
    179                 """
    180                 syslog(LOG_DEBUG, '*** getdir %s' % path)
    181                
    182                 # '/' contains a directory for each machine
    183                 if path == '/':
    184                         contents = self.getMachines()
    185                 # The directory for each machine contains the same files as the realpath
    186                 # but also the .k5login
    187                 #
    188                 # The list is converted to a set so that we can handle the case where
    189                 # there is already a .k5login in the realpath gracefully
    190                 elif getDepth(path) == 1:
    191                         contents = set(os.listdir(self.mirrorPath(path)) + ['.k5login'])
    192                 # If it's not the root of the homedir, just pass the call onto the OS
    193                 # for realpath
    194                 else:
    195                         contents = os.listdir(self.mirrorPath(path))
    196                 # Format the list the way that Fuse wants it - and don't forget to add
    197                 # '.' and '..'
    198                 return [(i, 0) for i in (list(contents) + ['.', '..'])]
    199        
    200         def read(self, path, length, offset):
    201                 """Read length bytes starting at offset of path. In most cases, this
    202                 just gets passed on to the OS
    203                 """
    204                 syslog(LOG_DEBUG, '*** read %s %s %s' % (path, length, offset))
    205                
    206                 parts = getParts(path)
    207                
    208                 # If the depth is less than 2, then either it's a directory or the file
    209                 # doesn't exist
    210                 # (realistically this doesn't appear to ever happen)
    211                 if getDepth(path) < 2:
    212                         return -errno.ENOENT
    213                 # If we're asking for a real .k5login file, then create it and return
    214                 # the snippet requested
    215                 elif parts[1:] == ['.k5login']:
    216                         if parts[0] not in self.getMachines():
    217                                 return -errno.ENOENT
    218                         else:
    219                                 return self.getK5login(parts[0])[offset:length + offset]
    220                 # Otherwise, pass the call onto the OS
    221                 # (note that the file will get closed when this call returns and the
    222                 # file descriptor goes out of scope)
    223                 else:
    224                         fname = self.mirrorPath(path)
    225                         if not os.path.isfile(fname):
    226                                 return -errno.ENOENT
    227                         else:
    228                                 f = open(fname)
    229                                 f.seek(offset)
    230                                 return f.read(length)
    231        
    232         def readlink(self, path):
    233                 syslog(LOG_DEBUG, '*** readlink %s' % path)
    234                
    235                 # There aren't any symlinks here
    236                 if getDepth(path) < 2:
    237                         return -errno.ENOENT
    238                 # But there might be here
    239                 else:
    240                         return os.readlink(self.mirrorPath(path))
    24189
    24290if __name__ == '__main__':
    24391        database.connect()
    244         usage="""
    245 ConsoleFS [mount_path]
    246 """
    247         server = ConsoleFS()
    248         server.flags = 0
    249         server.main()
     92        routefs.main(ConsoleFS)
Note: See TracChangeset for help on using the changeset viewer.