[23080] | 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 | |
---|
[23260] | 12 | import sys, os, stat, errno, time |
---|
[23245] | 13 | from syslog import * |
---|
[23080] | 14 | import fuse |
---|
| 15 | from fuse import Fuse |
---|
| 16 | |
---|
| 17 | import hesiod |
---|
| 18 | |
---|
[23260] | 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 | print '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): |
---|
| 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 | |
---|
[23151] | 68 | new_fuse = hasattr(fuse, '__version__') |
---|
[23080] | 69 | |
---|
| 70 | fuse.fuse_python_api = (0, 2) |
---|
| 71 | |
---|
| 72 | hello_path = '/README.txt' |
---|
| 73 | hello_str = """This is the pyhesiodfs FUSE autmounter. To access a Hesiod filsys, just access |
---|
| 74 | %(mountpoint)s/name. |
---|
| 75 | |
---|
| 76 | If you're using the Finder, try pressing Cmd+Shift+G and then entering |
---|
| 77 | %(mountpoint)s/name""" |
---|
| 78 | |
---|
[23151] | 79 | if not hasattr(fuse, 'Stat'): |
---|
| 80 | fuse.Stat = object |
---|
| 81 | |
---|
[23080] | 82 | class 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 | |
---|
[23151] | 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 | |
---|
[23080] | 100 | class PyHesiodFS(Fuse): |
---|
| 101 | |
---|
| 102 | def __init__(self, *args, **kwargs): |
---|
| 103 | Fuse.__init__(self, *args, **kwargs) |
---|
[23245] | 104 | |
---|
| 105 | openlog('pyhesiodfs', 0, LOG_DAEMON) |
---|
| 106 | |
---|
[23151] | 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") |
---|
[23260] | 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) |
---|
[23080] | 122 | |
---|
[23260] | 123 | def _user(self): |
---|
| 124 | return fuse.FuseGetContext()['uid'] |
---|
| 125 | |
---|
[23080] | 126 | def getattr(self, path): |
---|
| 127 | st = MyStat() |
---|
| 128 | if path == '/': |
---|
[23260] | 129 | st.st_mode = stat.S_IFDIR | 0777 |
---|
[23080] | 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:]: |
---|
[23260] | 136 | if path[1:] not in self.negcache and self.findLocker(path[1:]): |
---|
[23080] | 137 | st.st_mode = stat.S_IFLNK | 0777 |
---|
[23260] | 138 | st.st_uid = self._user() |
---|
[23080] | 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 |
---|
[23151] | 145 | if new_fuse: |
---|
| 146 | return st |
---|
| 147 | else: |
---|
| 148 | return st.toTuple() |
---|
[23080] | 149 | |
---|
| 150 | def getCachedLockers(self): |
---|
[23260] | 151 | return self.mounts[self._user()].keys() |
---|
[23080] | 152 | |
---|
| 153 | def findLocker(self, name): |
---|
| 154 | """Lookup a locker in hesiod and return its path""" |
---|
[23260] | 155 | if name in self.mounts[self._user()]: |
---|
| 156 | return self.mounts[self._user()][name] |
---|
[23080] | 157 | else: |
---|
[23151] | 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)) |
---|
[23080] | 165 | # FIXME check if the first locker is valid |
---|
[23151] | 166 | if len(filsys.filsys) >= 1: |
---|
| 167 | pointers = filsys.filsys |
---|
[23080] | 168 | pointer = pointers[0] |
---|
| 169 | if pointer['type'] != 'AFS' and pointer['type'] != 'LOC': |
---|
[23245] | 170 | syslog(LOG_NOTICE, "Unknown locker type "+pointer['type']+" for locker "+name+" ("+repr(pointer)+" )") |
---|
[23080] | 171 | return None |
---|
| 172 | else: |
---|
[23260] | 173 | self.mounts[self._user()][name] = pointer['location'] |
---|
[23245] | 174 | syslog(LOG_INFO, "Mounting "+name+" on "+pointer['location']) |
---|
[23080] | 175 | return pointer['location'] |
---|
| 176 | else: |
---|
[23245] | 177 | syslog(LOG_WARNING, "Couldn't find filsys for "+name) |
---|
[23080] | 178 | return None |
---|
| 179 | |
---|
[23151] | 180 | def getdir(self, path): |
---|
| 181 | return [(i, 0) for i in (['.', '..', hello_path[1:]] + self.getCachedLockers())] |
---|
| 182 | |
---|
[23080] | 183 | def readdir(self, path, offset): |
---|
[23151] | 184 | for (r, zero) in self.getdir(path): |
---|
[23080] | 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 |
---|
[23260] | 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 |
---|
[23080] | 227 | |
---|
| 228 | def main(): |
---|
| 229 | global hello_str |
---|
[23151] | 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=""" |
---|
| 238 | pyHesiodFS [mountpath] [options] |
---|
[23080] | 239 | |
---|
[23151] | 240 | """ |
---|
| 241 | if sys.argv[1] == '-f': |
---|
| 242 | sys.argv.pop(1) |
---|
| 243 | server = PyHesiodFS() |
---|
[23080] | 244 | |
---|
| 245 | hello_str = hello_str % {'mountpoint': server.parse(errex=1).mountpoint} |
---|
| 246 | server.main() |
---|
| 247 | |
---|
| 248 | if __name__ == '__main__': |
---|
| 249 | main() |
---|