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
15 import sipb_xen_database
17 fuse.fuse_python_api = (0, 2)
21 Return the depth of a given path, zero-based from root ('/')
26 return path.count('/')
30 Return the slash-separated parts of a given path as a list
32 # [1:] to exclude leading empty element
33 split = path.split('/')
40 parts = getParts(path)
41 return parts, len(parts)
57 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)
59 class RemConfFS(Fuse):
61 RemConfFS creates a filesytem for configuring remctl, like this:
72 The machine list and the acls are drawn from a database.
74 This filesystem only implements the getattr, getdir, read, and readlink
75 calls, because this is a read-only filesystem.
78 def __init__(self, *args, **kw):
79 """Initialize the filesystem and set it to allow_other access besides
80 the user who mounts the filesystem (i.e. root)
82 Fuse.__init__(self, *args, **kw)
83 self.lasttime = time()
86 openlog('sipb-xen-remconffs ', LOG_PID, LOG_DAEMON)
88 syslog(LOG_DEBUG, 'Init complete.')
90 def getMachines(self):
91 """Get the list of VMs in the database, clearing the cache if it's
92 older than 15 seconds"""
93 if time() - self.lasttime > 15:
94 self.lasttime = time()
95 sipb_xen_database.clear_cache()
96 return [machine.name for machine in sipb_xen_database.Machine.select()]
98 def getacl(self, machine_name):
99 """Build the ACL file for a machine
101 machine = sipb_xen_database.Machine.get_by(name=machine_name)
102 users = [acl.user for acl in machine.acl]
103 return "\n".join(map(self.userToPrinc, users)
104 + ['include /etc/remctl/acl/web',
108 """Build the master conf file, with all machines
110 return '\n'.join("control %s /usr/sbin/sipb-xen-remote-proxy-control"
111 " /etc/remctl/remconffs/acl/%s"
112 % (machine_name, machine_name)
113 for machine_name in self.getMachines())+'\n'
115 def userToPrinc(self, user):
116 """Convert Kerberos v4-style names to v5-style and append a default
117 realm if none is specified
120 (princ, realm) = user.split('@')
123 realm = "ATHENA.MIT.EDU"
125 return princ.replace('.', '/') + '@' + realm
127 def getattr(self, path):
129 - st_mode (protection bits)
130 - st_ino (inode number)
132 - st_nlink (number of hard links)
133 - st_uid (user ID of owner)
134 - st_gid (group ID of owner)
135 - st_size (size of file, in bytes)
136 - st_atime (time of most recent access)
137 - st_mtime (time of most recent content modification)
138 - st_ctime (platform dependent; time of most recent metadata change on Unix,
139 or the time of creation on Windows).
142 syslog(LOG_DEBUG, "*** getattr: " + path)
144 depth = getDepth(path)
145 parts = getParts(path)
149 st.st_mode = stat.S_IFDIR | 0755
152 if parts[0] == 'acl':
153 st.st_mode = stat.S_IFDIR | 0755
155 elif parts[0] == 'conf':
156 st.st_mode = stat.S_IFREG | 0444
158 st.st_size = len(self.getconf())
162 if parts[0] != 'acl':
164 if parts[1] not in self.getMachines():
166 st.st_mode = stat.S_IFREG | 0444
168 st.st_size = len(self.getacl(parts[1]))
172 # This call isn't actually used in the version of Fuse on console, but we
173 # wanted to leave it implemented to ease the transition in the future
174 def readdir(self, path, offset):
175 """Return a generator with the listing for a directory
177 syslog(LOG_DEBUG, '*** readdir %s %s' % (path, offset))
178 for (value, zero) in self.getdir(path):
179 yield fuse.Direntry(value)
181 def getdir(self, path):
182 """Return a list of tuples of the form (item, 0) with the contents of
185 Fuse doesn't add '.' or '..' on its own, so we have to
187 syslog(LOG_DEBUG, '*** getdir %s' % path)
189 parts, depth = parse(path)
192 contents = ('acl', 'conf')
194 if parts[0] == 'acl':
195 contents = self.getMachines()
199 return -errno.ENOTDIR
201 # Format the list the way that Fuse wants it - and don't forget to add
203 return [(i, 0) for i in (list(contents) + ['.', '..'])]
205 def read(self, path, length, offset):
206 """Read length bytes starting at offset of path. In most cases, this
207 just gets passed on to the OS
209 syslog(LOG_DEBUG, '*** read %s %s %s' % (path, length, offset))
211 parts, depth = parse(path)
215 elif parts[0] == 'conf':
216 return self.getconf()[offset:offset+length]
217 elif parts[0] == 'acl':
220 if parts[1] in self.getMachines():
221 return self.getacl(parts[1])[offset:offset+length]
224 def readlink(self, path):
225 syslog(LOG_DEBUG, '*** readlink %s' % path)
229 if __name__ == '__main__':
230 sipb_xen_database.connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')