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

Revision 23260, 7.6 KB checked in by broder, 15 years ago (diff)
Pull new pyHesiodFS that includes per-user caches and the ability to manually remove and create symlinks.
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            print '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):
52        self.cache_time = cache_time
53   
54    def add(self, obj):
55        self[obj] = time.time()
56   
57    def remove(self, obj):
58        del self[obj]
59   
60    def __contains__(self, k):
61        if super(negcache, self).__contains__(k):
62            if self[k] + self.cache_time > time.time():
63                return True
64            else:
65                del self[k]
66        return False
67
68new_fuse = hasattr(fuse, '__version__')
69
70fuse.fuse_python_api = (0, 2)
71
72hello_path = '/README.txt'
73hello_str = """This is the pyhesiodfs FUSE autmounter. To access a Hesiod filsys, just access
74%(mountpoint)s/name.
75
76If you're using the Finder, try pressing Cmd+Shift+G and then entering
77%(mountpoint)s/name"""
78
79if not hasattr(fuse, 'Stat'):
80    fuse.Stat = object
81
82class MyStat(fuse.Stat):
83    def __init__(self):
84        self.st_mode = 0
85        self.st_ino = 0
86        self.st_dev = 0
87        self.st_nlink = 0
88        self.st_uid = 0
89        self.st_gid = 0
90        self.st_size = 0
91        self.st_atime = 0
92        self.st_mtime = 0
93        self.st_ctime = 0
94
95    def toTuple(self):
96        return (self.st_mode, self.st_ino, self.st_dev, self.st_nlink,
97                self.st_uid, self.st_gid, self.st_size, self.st_atime,
98                self.st_mtime, self.st_ctime)
99
100class PyHesiodFS(Fuse):
101
102    def __init__(self, *args, **kwargs):
103        Fuse.__init__(self, *args, **kwargs)
104       
105        openlog('pyhesiodfs', 0, LOG_DAEMON)
106       
107        try:
108            self.fuse_args.add("allow_other", True)
109        except AttributeError:
110            self.allow_other = 1
111
112        if sys.platform == 'darwin':
113            self.fuse_args.add("noappledouble", True)
114            self.fuse_args.add("noapplexattr", True)
115            self.fuse_args.add("volname", "MIT")
116            self.fuse_args.add("fsname", "pyHesiodFS")
117        self.mounts = defaultdict(dict)
118       
119        # Cache deletions for 10 seconds - should give people time to
120        # make a new symlink
121        self.negcache = negcache(10)
122   
123    def _user(self):
124        return fuse.FuseGetContext()['uid']
125   
126    def getattr(self, path):
127        st = MyStat()
128        if path == '/':
129            st.st_mode = stat.S_IFDIR | 0777
130            st.st_nlink = 2
131        elif path == hello_path:
132            st.st_mode = stat.S_IFREG | 0444
133            st.st_nlink = 1
134            st.st_size = len(hello_str)
135        elif '/' not in path[1:]:
136            if path[1:] not in self.negcache and self.findLocker(path[1:]):
137                st.st_mode = stat.S_IFLNK | 0777
138                st.st_uid = self._user()
139                st.st_nlink = 1
140                st.st_size = len(self.findLocker(path[1:]))
141            else:
142                return -errno.ENOENT
143        else:
144            return -errno.ENOENT
145        if new_fuse:
146            return st
147        else:
148            return st.toTuple()
149
150    def getCachedLockers(self):
151        return self.mounts[self._user()].keys()
152
153    def findLocker(self, name):
154        """Lookup a locker in hesiod and return its path"""
155        if name in self.mounts[self._user()]:
156            return self.mounts[self._user()][name]
157        else:
158            try:
159                filsys = hesiod.FilsysLookup(name)
160            except IOError, e:
161                if e.errno in (errno.ENOENT, errno.EMSGSIZE):
162                    raise IOError(errno.ENOENT, os.strerror(errno.ENOENT))
163                else:
164                    raise IOError(errno.EIO, os.strerror(errno.EIO))
165            # FIXME check if the first locker is valid
166            if len(filsys.filsys) >= 1:
167                pointers = filsys.filsys
168                pointer = pointers[0]
169                if pointer['type'] != 'AFS' and pointer['type'] != 'LOC':
170                    syslog(LOG_NOTICE, "Unknown locker type "+pointer['type']+" for locker "+name+" ("+repr(pointer)+" )")
171                    return None
172                else:
173                    self.mounts[self._user()][name] = pointer['location']
174                    syslog(LOG_INFO, "Mounting "+name+" on "+pointer['location'])
175                    return pointer['location']
176            else:
177                syslog(LOG_WARNING, "Couldn't find filsys for "+name)
178                return None
179
180    def getdir(self, path):
181        return [(i, 0) for i in (['.', '..', hello_path[1:]] + self.getCachedLockers())]
182
183    def readdir(self, path, offset):
184        for (r, zero) in self.getdir(path):
185            yield fuse.Direntry(r)
186           
187    def readlink(self, path):
188        return self.findLocker(path[1:])
189
190    def open(self, path, flags):
191        if path != hello_path:
192            return -errno.ENOENT
193        accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
194        if (flags & accmode) != os.O_RDONLY:
195            return -errno.EACCES
196
197    def read(self, path, size, offset):
198        if path != hello_path:
199            return -errno.ENOENT
200        slen = len(hello_str)
201        if offset < slen:
202            if offset + size > slen:
203                size = slen - offset
204            buf = hello_str[offset:offset+size]
205        else:
206            buf = ''
207        return buf
208   
209    def symlink(self, src, path):
210        if path == '/' or path == hello_path:
211            return -errno.EPERM
212        elif '/' not in path[1:]:
213            self.mounts[self._user()][path[1:]] = src
214            self.negcache.remove(path[1:])
215            print self.mounts[self._user()]
216        else:
217            return -errno.EPERM
218   
219    def unlink(self, path):
220        if path == '/' or path == hello_path:
221            return -errno.EPERM
222        elif '/' not in path[1:]:
223            del self.mounts[self._user()][path[1:]]
224            self.negcache.add(path[1:])
225        else:
226            return -errno.EPERM
227
228def main():
229    global hello_str
230    try:
231        usage = Fuse.fusage
232        server = PyHesiodFS(version="%prog " + fuse.__version__,
233                            usage=usage,
234                            dash_s_do='setsingle')
235        server.parse(errex=1)
236    except AttributeError:
237        usage="""
238pyHesiodFS [mountpath] [options]
239
240"""
241        if sys.argv[1] == '-f':
242            sys.argv.pop(1)
243        server = PyHesiodFS()
244
245    hello_str = hello_str % {'mountpoint': server.parse(errex=1).mountpoint}
246    server.main()
247
248if __name__ == '__main__':
249    main()
Note: See TracBrowser for help on using the repository browser.