source: trunk/debathena/debathena/pyhesiodfs/pyHesiodFS.py @ 23270

Revision 23270, 7.8 KB checked in by broder, 14 years ago (diff)
Updates to pyHesiodFS from MacAthena (various bugfixes, and making the negative cache per-user and shorter lasting)
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
12import sys, os, stat, errno, time
13from syslog import *
14import fuse
15from fuse import Fuse
16
17import hesiod
18
19try:
20    from collections import defaultdict
21except 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
43class 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
71new_fuse = hasattr(fuse, '__version__')
72
73fuse.fuse_python_api = (0, 2)
74
75hello_path = '/README.txt'
76hello_str = """This is the pyhesiodfs FUSE autmounter. To access a Hesiod filsys, just access
77%(mountpoint)s/name.
78
79If you're using the Finder, try pressing Cmd+Shift+G and then entering
80%(mountpoint)s/name"""
81
82if not hasattr(fuse, 'Stat'):
83    fuse.Stat = object
84
85class 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
103class 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._uid()] 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._uid()].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._uid()].add(path[1:])
234        else:
235            return -errno.EPERM
236
237def 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="""
247pyHesiodFS [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
257if __name__ == '__main__':
258    main()
Note: See TracBrowser for help on using the repository browser.