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 from invirt import database
16 from invirt.config import structs as config
18 fuse.fuse_python_api = (0, 2)
22 Return the depth of a given path, zero-based from root ('/')
27 return path.count('/')
31 Return the slash-separated parts of a given path as a list
33 # [1:] to exclude leading empty element
34 split = path.split('/')
41 parts = getParts(path)
42 return parts, len(parts)
58 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)
60 class RemConfFS(Fuse):
62 RemConfFS creates a filesytem for configuring remctl, like this:
73 The machine list and the acls are drawn from a database.
75 This filesystem only implements the getattr, getdir, read, and readlink
76 calls, because this is a read-only filesystem.
79 def __init__(self, *args, **kw):
80 """Initialize the filesystem and set it to allow_other access besides
81 the user who mounts the filesystem (i.e. root)
83 Fuse.__init__(self, *args, **kw)
84 self.lasttime = time()
87 openlog('sipb-xen-remconffs ', LOG_PID, LOG_DAEMON)
89 syslog(LOG_DEBUG, 'Init complete.')
91 def getMachines(self):
92 """Get the list of VMs in the database, clearing the cache if it's
93 older than 15 seconds"""
94 if time() - self.lasttime > 15:
95 self.lasttime = time()
96 database.clear_cache()
97 return [machine.name for machine in database.Machine.select()]
99 def getacl(self, machine_name):
100 """Build the ACL file for a machine
102 machine = database.Machine.get_by(name=machine_name)
103 users = [acl.user for acl in machine.acl]
104 return "\n".join(map(self.userToPrinc, users)
105 + ['include /etc/remctl/acl/web',
109 """Build the master conf file, with all machines
111 return '\n'.join("control %s /usr/sbin/sipb-xen-remote-proxy-control"
112 " /etc/remctl/remconffs/acl/%s"
113 % (machine_name, machine_name)
114 for machine_name in self.getMachines())+'\n'
116 def userToPrinc(self, user):
117 """Convert Kerberos v4-style names to v5-style and append a default
118 realm if none is specified
121 (princ, realm) = user.split('@')
124 realm = config.authn[0].realm
126 return princ.replace('.', '/') + '@' + realm
128 def getattr(self, path):
130 - st_mode (protection bits)
131 - st_ino (inode number)
133 - st_nlink (number of hard links)
134 - st_uid (user ID of owner)
135 - st_gid (group ID of owner)
136 - st_size (size of file, in bytes)
137 - st_atime (time of most recent access)
138 - st_mtime (time of most recent content modification)
139 - st_ctime (platform dependent; time of most recent metadata change on Unix,
140 or the time of creation on Windows).
143 syslog(LOG_DEBUG, "*** getattr: " + path)
145 depth = getDepth(path)
146 parts = getParts(path)
150 st.st_mode = stat.S_IFDIR | 0755
153 if parts[0] == 'acl':
154 st.st_mode = stat.S_IFDIR | 0755
156 elif parts[0] == 'conf':
157 st.st_mode = stat.S_IFREG | 0444
159 st.st_size = len(self.getconf())
163 if parts[0] != 'acl':
165 if parts[1] not in self.getMachines():
167 st.st_mode = stat.S_IFREG | 0444
169 st.st_size = len(self.getacl(parts[1]))
173 # This call isn't actually used in the version of Fuse on console, but we
174 # wanted to leave it implemented to ease the transition in the future
175 def readdir(self, path, offset):
176 """Return a generator with the listing for a directory
178 syslog(LOG_DEBUG, '*** readdir %s %s' % (path, offset))
179 for (value, zero) in self.getdir(path):
180 yield fuse.Direntry(value)
182 def getdir(self, path):
183 """Return a list of tuples of the form (item, 0) with the contents of
186 Fuse doesn't add '.' or '..' on its own, so we have to
188 syslog(LOG_DEBUG, '*** getdir %s' % path)
190 parts, depth = parse(path)
193 contents = ('acl', 'conf')
195 if parts[0] == 'acl':
196 contents = self.getMachines()
200 return -errno.ENOTDIR
202 # Format the list the way that Fuse wants it - and don't forget to add
204 return [(i, 0) for i in (list(contents) + ['.', '..'])]
206 def read(self, path, length, offset):
207 """Read length bytes starting at offset of path. In most cases, this
208 just gets passed on to the OS
210 syslog(LOG_DEBUG, '*** read %s %s %s' % (path, length, offset))
212 parts, depth = parse(path)
216 elif parts[0] == 'conf':
217 return self.getconf()[offset:offset+length]
218 elif parts[0] == 'acl':
221 if parts[1] in self.getMachines():
222 return self.getacl(parts[1])[offset:offset+length]
225 def readlink(self, path):
226 syslog(LOG_DEBUG, '*** readlink %s' % path)
230 if __name__ == '__main__':