7d49a95e24f8992ebd6b61ddef8ea580c24a3b85
[invirt/packages/invirt-remote.git] / files / usr / sbin / sipb-xen-remconffs
1 #!/usr/bin/python
2
3 import fuse
4 from fuse import Fuse
5
6 from time import time
7
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 *
14
15 import sipb_xen_database
16 from invirt.config import structs as config
17
18 fuse.fuse_python_api = (0, 2)
19
20 def getDepth(path):
21         """
22         Return the depth of a given path, zero-based from root ('/')
23         """
24         if path == '/':
25                 return 0
26         else:
27                 return path.count('/')
28
29 def getParts(path):
30         """
31         Return the slash-separated parts of a given path as a list
32         """
33         # [1:] to exclude leading empty element
34         split = path.split('/')
35         if split[-1]:
36                 return split[1:]
37         else:
38                 return split[1:-1]
39
40 def parse(path):
41         parts = getParts(path)
42         return parts, len(parts)
43
44 class MyStat:
45         def __init__(self):
46                 self.st_mode = 0
47                 self.st_ino = 0
48                 self.st_dev = 0
49                 self.st_nlink = 0
50                 self.st_uid = 0
51                 self.st_gid = 0
52                 self.st_size = 0
53                 self.st_atime = 0
54                 self.st_mtime = 0
55                 self.st_ctime = 0
56         
57         def toTuple(self):
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)
59
60 class RemConfFS(Fuse):
61         """
62         RemConfFS creates a filesytem for configuring remctl, like this:
63         /
64         |-- acl
65         |   |-- machine1
66         |   ...
67         |   `-- machinen
68         `-- conf.d
69             |-- machine1
70             ...
71             `-- machinen
72
73         The machine list and the acls are drawn from a database.
74         
75         This filesystem only implements the getattr, getdir, read, and readlink
76         calls, because this is a read-only filesystem.
77         """
78         
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)
82                 """
83                 Fuse.__init__(self, *args, **kw)
84                 self.lasttime = time()
85                 self.allow_other = 1
86                 
87                 openlog('sipb-xen-remconffs ', LOG_PID, LOG_DAEMON)
88                 
89                 syslog(LOG_DEBUG, 'Init complete.')
90
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                         sipb_xen_database.clear_cache()
97                 return [machine.name for machine in sipb_xen_database.Machine.select()]
98                 
99         def getacl(self, machine_name):
100                 """Build the ACL file for a machine
101                 """
102                 machine = sipb_xen_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',
106                                     ''])
107                 
108         def getconf(self):
109                 """Build the master conf file, with all machines
110                 """
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'
115         
116         def userToPrinc(self, user):
117                 """Convert Kerberos v4-style names to v5-style and append a default
118                 realm if none is specified
119                 """
120                 if '@' in user:
121                         (princ, realm) = user.split('@')
122                 else:
123                         princ = user
124                         realm = config.authn[0].realm
125                 
126                 return princ.replace('.', '/') + '@' + realm
127         
128         def getattr(self, path):
129                 """
130                 - st_mode (protection bits)
131                 - st_ino (inode number)
132                 - st_dev (device)
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).
141                 """
142                 
143                 syslog(LOG_DEBUG, "*** getattr: " + path)
144                 
145                 depth = getDepth(path)
146                 parts = getParts(path)
147                 
148                 st = MyStat()
149                 if path == '/':
150                         st.st_mode = stat.S_IFDIR | 0755
151                         st.st_nlink = 2
152                 elif depth == 1:
153                         if parts[0] == 'acl':
154                                 st.st_mode = stat.S_IFDIR | 0755
155                                 st.st_nlink = 2
156                         elif parts[0] == 'conf':
157                                 st.st_mode = stat.S_IFREG | 0444
158                                 st.st_nlink = 1
159                                 st.st_size = len(self.getconf())
160                         else:
161                                 return -errno.ENOENT
162                 elif depth == 2:
163                         if parts[0] != 'acl':
164                                 return -errno.ENOENT
165                         if parts[1] not in self.getMachines():
166                                 return -errno.ENOENT
167                         st.st_mode = stat.S_IFREG | 0444
168                         st.st_nlink = 1
169                         st.st_size = len(self.getacl(parts[1]))
170
171                 return st.toTuple()
172         
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
177                 """
178                 syslog(LOG_DEBUG, '*** readdir %s %s' % (path, offset))
179                 for (value, zero) in self.getdir(path):
180                         yield fuse.Direntry(value)
181         
182         def getdir(self, path):
183                 """Return a list of tuples of the form (item, 0) with the contents of
184                 the directory path
185                 
186                 Fuse doesn't add '.' or '..' on its own, so we have to
187                 """
188                 syslog(LOG_DEBUG, '*** getdir %s' % path)
189                 
190                 parts, depth = parse(path)
191
192                 if depth == 0:
193                         contents = ('acl', 'conf')
194                 elif depth == 1:
195                         if parts[0] == 'acl':
196                                 contents = self.getMachines()
197                         else:
198                                 return -errno.ENOENT
199                 else:
200                         return -errno.ENOTDIR
201
202                 # Format the list the way that Fuse wants it - and don't forget to add
203                 # '.' and '..'
204                 return [(i, 0) for i in (list(contents) + ['.', '..'])]
205
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
209                 """
210                 syslog(LOG_DEBUG, '*** read %s %s %s' % (path, length, offset))
211                 
212                 parts, depth = parse(path)
213                 
214                 if depth == 0:
215                         return -errno.EISDIR
216                 elif parts[0] == 'conf':
217                         return self.getconf()[offset:offset+length]
218                 elif parts[0] == 'acl':
219                         if depth == 1:
220                                 return -errno.EISDIR
221                         if parts[1] in self.getMachines():
222                                 return self.getacl(parts[1])[offset:offset+length]
223                 return -errno.ENOENT
224         
225         def readlink(self, path):
226                 syslog(LOG_DEBUG, '*** readlink %s' % path)
227                 return -errno.ENOENT
228
229
230 if __name__ == '__main__':
231         sipb_xen_database.connect(config.db.uri)
232         usage="""
233 $0 [mount_path]
234 """
235         server = RemConfFS()
236         server.flags = 0
237         server.main()