root/trunk/source/pyhesiodfs/pyHesiodFS.py

Revision 166, 7.8 kB (checked in by broder, 15 years ago)

To make everything happy, /mit needs to be chgrped to the requestor's
GID, but chmodded 755.

  • Property svn:executable set to *
Line 
1 #!/usr/bin/python2.5
2
3 #    pyHesiodFS:
4 #    Copyright (C) 2007  Quentin Smith <quentin@mit.edu>
5 #    "Hello World" pyFUSE example:
6 #    Copyright (C) 2006  Andrew Straw  <strawman@astraw.com>
7 #
8 #    This program can be distributed under the terms of the GNU LGPL.
9 #    See the file COPYING.
10 #
11
12 import sys, os, stat, errno, time
13 from syslog import *
14 import fuse
15 from fuse import Fuse
16
17 import hesiod
18
19 try:
20     from collections import defaultdict
21 except ImportError:
22     class defaultdict(dict):
23         """
24         A dictionary that automatically will fill in keys that don't exist
25         with the result from some default value factory
26        
27         Based on the collections.defaultdict object in Python 2.5
28         """
29        
30         def __init__(self, default_factory):
31             self.default_factory = default_factory
32             super(defaultdict, self).__init__()
33        
34         def __getitem__(self, y):
35             if y not in self:
36                 self[y] = self.default_factory()
37             return super(defaultdict, self).__getitem__(y)
38        
39         def __str__(self):
40             return 'defaultdict(%s, %s)' % (self.default_factory,
41                                             super(defaultdict, self).__str__())
42
43 class negcache(dict):
44     """
45     A set-like object that automatically expunges entries after
46     they're been there for a certain amount of time.
47    
48     This only supports add, remove, and __contains__
49     """
50    
51     def __init__(self, cache_time=0.5):
52         self.cache_time = cache_time
53    
54     def add(self, obj):
55         self[obj] = time.time()
56    
57     def remove(self, obj):
58         try:
59             del self[obj]
60         except KeyError:
61             pass
62    
63     def __contains__(self, k):
64         if super(negcache, self).__contains__(k):
65             if self[k] + self.cache_time > time.time():
66                 return True
67             else:
68                 del self[k]
69         return False
70
71 new_fuse = hasattr(fuse, '__version__')
72
73 fuse.fuse_python_api = (0, 2)
74
75 hello_path = '/README.txt'
76 hello_str = """This is the pyhesiodfs FUSE autmounter. To access a Hesiod filsys, just access
77 %(mountpoint)s/name.
78
79 If you're using the Finder, try pressing Cmd+Shift+G and then entering
80 %(mountpoint)s/name"""
81
82 if not hasattr(fuse, 'Stat'):
83     fuse.Stat = object
84
85 class MyStat(fuse.Stat):
86     def __init__(self):
87         self.st_mode = 0
88         self.st_ino = 0
89         self.st_dev = 0
90         self.st_nlink = 0
91         self.st_uid = 0
92         self.st_gid = 0
93         self.st_size = 0
94         self.st_atime = 0
95         self.st_mtime = 0
96         self.st_ctime = 0
97
98     def toTuple(self):
99         return (self.st_mode, self.st_ino, self.st_dev, self.st_nlink,
100                 self.st_uid, self.st_gid, self.st_size, self.st_atime,
101                 self.st_mtime, self.st_ctime)
102
103 class PyHesiodFS(Fuse):
104
105     def __init__(self, *args, **kwargs):
106         Fuse.__init__(self, *args, **kwargs)
107        
108         openlog('pyhesiodfs', 0, LOG_DAEMON)
109        
110         try:
111             self.fuse_args.add("allow_other", True)
112         except AttributeError:
113             self.allow_other = 1
114
115         if sys.platform == 'darwin':
116             self.fuse_args.add("noappledouble", True)
117             self.fuse_args.add("noapplexattr", True)
118             self.fuse_args.add("volname", "MIT")
119             self.fuse_args.add("fsname", "pyHesiodFS")
120         self.mounts = defaultdict(dict)
121        
122         # Cache deletions for half a second - should give `ln -nsf`
123         # enough time to make a new symlink
124         self.negcache = defaultdict(negcache)
125    
126     def _uid(self):
127         return fuse.FuseGetContext()['uid']
128    
129     def _gid(self):
130         return fuse.FuseGetContext()['gid']
131    
132     def _pid(self):
133         return fuse.FuseGetContext()['pid']
134    
135     def getattr(self, path):
136         st = MyStat()
137         if path == '/':
138             st.st_mode = stat.S_IFDIR | 0755
139             st.st_gid = self._gid()
140             st.st_nlink = 2
141         elif path == hello_path:
142             st.st_mode = stat.S_IFREG | 0444
143             st.st_nlink = 1
144             st.st_size = len(hello_str)
145         elif '/' not in path[1:]:
146             if path[1:] not in self.negcache[self._pid()] and self.findLocker(path[1:]):
147                 st.st_mode = stat.S_IFLNK | 0777
148                 st.st_uid = self._uid()
149                 st.st_nlink = 1
150                 st.st_size = len(self.findLocker(path[1:]))
151             else:
152                 return -errno.ENOENT
153         else:
154             return -errno.ENOENT
155         if new_fuse:
156             return st
157         else:
158             return st.toTuple()
159
160     def getCachedLockers(self):
161         return self.mounts[self._uid()].keys()
162
163     def findLocker(self, name):
164         """Lookup a locker in hesiod and return its path"""
165         if name in self.mounts[self._uid()]:
166             return self.mounts[self._uid()][name]
167         else:
168             try:
169                 filsys = hesiod.FilsysLookup(name)
170             except IOError, e:
171                 if e.errno in (errno.ENOENT, errno.EMSGSIZE):
172                     raise IOError(errno.ENOENT, os.strerror(errno.ENOENT))
173                 else:
174                     raise IOError(errno.EIO, os.strerror(errno.EIO))
175             # FIXME check if the first locker is valid
176             if len(filsys.filsys) >= 1:
177                 pointers = filsys.filsys
178                 pointer = pointers[0]
179                 if pointer['type'] != 'AFS' and pointer['type'] != 'LOC':
180                     syslog(LOG_NOTICE, "Unknown locker type "+pointer['type']+" for locker "+name+" ("+repr(pointer)+" )")
181                     return None
182                 else:
183                     self.mounts[self._uid()][name] = pointer['location']
184                     syslog(LOG_INFO, "Mounting "+name+" on "+pointer['location'])
185                     return pointer['location']
186             else:
187                 syslog(LOG_WARNING, "Couldn't find filsys for "+name)
188                 return None
189
190     def getdir(self, path):
191         return [(i, 0) for i in (['.', '..', hello_path[1:]] + self.getCachedLockers())]
192
193     def readdir(self, path, offset):
194         for (r, zero) in self.getdir(path):
195             yield fuse.Direntry(r)
196            
197     def readlink(self, path):
198         return self.findLocker(path[1:])
199
200     def open(self, path, flags):
201         if path != hello_path:
202             return -errno.ENOENT
203         accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
204         if (flags & accmode) != os.O_RDONLY:
205             return -errno.EACCES
206
207     def read(self, path, size, offset):
208         if path != hello_path:
209             return -errno.ENOENT
210         slen = len(hello_str)
211         if offset < slen:
212             if offset + size > slen:
213                 size = slen - offset
214             buf = hello_str[offset:offset+size]
215         else:
216             buf = ''
217         return buf
218    
219     def symlink(self, src, path):
220         if path == '/' or path == hello_path:
221             return -errno.EPERM
222         elif '/' not in path[1:]:
223             self.mounts[self._uid()][path[1:]] = src
224             self.negcache[self._pid()].remove(path[1:])
225         else:
226             return -errno.EPERM
227    
228     def unlink(self, path):
229         if path == '/' or path == hello_path:
230             return -errno.EPERM
231         elif '/' not in path[1:]:
232             del self.mounts[self._uid()][path[1:]]
233             self.negcache[self._pid()].add(path[1:])
234         else:
235             return -errno.EPERM
236
237 def main():
238     global hello_str
239     try:
240         usage = Fuse.fusage
241         server = PyHesiodFS(version="%prog " + fuse.__version__,
242                             usage=usage,
243                             dash_s_do='setsingle')
244         server.parse(errex=1)
245     except AttributeError:
246         usage="""
247 pyHesiodFS [mountpath] [options]
248
249 """
250         if sys.argv[1] == '-f':
251             sys.argv.pop(1)
252         server = PyHesiodFS()
253
254     hello_str = hello_str % {'mountpoint': server.parse(errex=1).mountpoint}
255     server.main()
256
257 if __name__ == '__main__':
258     main()
Note: See TracBrowser for help on using the browser.