[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): |
---|
[23270] | 40 | return 'defaultdict(%s, %s)' % (self.default_factory, |
---|
| 41 | super(defaultdict, self).__str__()) |
---|
[23260] | 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 | |
---|
[23270] | 51 | def __init__(self, cache_time=0.5): |
---|
[23260] | 52 | self.cache_time = cache_time |
---|
| 53 | |
---|
| 54 | def add(self, obj): |
---|
| 55 | self[obj] = time.time() |
---|
| 56 | |
---|
| 57 | def remove(self, obj): |
---|
[23270] | 58 | try: |
---|
| 59 | del self[obj] |
---|
| 60 | except KeyError: |
---|
| 61 | pass |
---|
[23260] | 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 | |
---|
[23151] | 71 | new_fuse = hasattr(fuse, '__version__') |
---|
[23080] | 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 | |
---|
[23151] | 82 | if not hasattr(fuse, 'Stat'): |
---|
| 83 | fuse.Stat = object |
---|
| 84 | |
---|
[23080] | 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 | |
---|
[23151] | 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 | |
---|
[23080] | 103 | class PyHesiodFS(Fuse): |
---|
| 104 | |
---|
| 105 | def __init__(self, *args, **kwargs): |
---|
| 106 | Fuse.__init__(self, *args, **kwargs) |
---|
[23245] | 107 | |
---|
| 108 | openlog('pyhesiodfs', 0, LOG_DAEMON) |
---|
| 109 | |
---|
[23151] | 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") |
---|
[23260] | 120 | self.mounts = defaultdict(dict) |
---|
| 121 | |
---|
[23270] | 122 | # Cache deletions for half a second - should give `ln -nsf` |
---|
| 123 | # enough time to make a new symlink |
---|
| 124 | self.negcache = defaultdict(negcache) |
---|
[23080] | 125 | |
---|
[23270] | 126 | def _uid(self): |
---|
[23260] | 127 | return fuse.FuseGetContext()['uid'] |
---|
| 128 | |
---|
[23270] | 129 | def _gid(self): |
---|
| 130 | return fuse.FuseGetContext()['gid'] |
---|
| 131 | |
---|
| 132 | def _pid(self): |
---|
| 133 | return fuse.FuseGetContext()['pid'] |
---|
| 134 | |
---|
[23080] | 135 | def getattr(self, path): |
---|
| 136 | st = MyStat() |
---|
| 137 | if path == '/': |
---|
[23270] | 138 | st.st_mode = stat.S_IFDIR | 0755 |
---|
| 139 | st.st_gid = self._gid() |
---|
[23080] | 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:]: |
---|
[23270] | 146 | if path[1:] not in self.negcache[self._uid()] and self.findLocker(path[1:]): |
---|
[23080] | 147 | st.st_mode = stat.S_IFLNK | 0777 |
---|
[23270] | 148 | st.st_uid = self._uid() |
---|
[23080] | 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 |
---|
[23151] | 155 | if new_fuse: |
---|
| 156 | return st |
---|
| 157 | else: |
---|
| 158 | return st.toTuple() |
---|
[23080] | 159 | |
---|
| 160 | def getCachedLockers(self): |
---|
[23270] | 161 | return self.mounts[self._uid()].keys() |
---|
[23080] | 162 | |
---|
| 163 | def findLocker(self, name): |
---|
| 164 | """Lookup a locker in hesiod and return its path""" |
---|
[23270] | 165 | if name in self.mounts[self._uid()]: |
---|
| 166 | return self.mounts[self._uid()][name] |
---|
[23080] | 167 | else: |
---|
[23151] | 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)) |
---|
[23080] | 175 | # FIXME check if the first locker is valid |
---|
[23151] | 176 | if len(filsys.filsys) >= 1: |
---|
| 177 | pointers = filsys.filsys |
---|
[23080] | 178 | pointer = pointers[0] |
---|
| 179 | if pointer['type'] != 'AFS' and pointer['type'] != 'LOC': |
---|
[23245] | 180 | syslog(LOG_NOTICE, "Unknown locker type "+pointer['type']+" for locker "+name+" ("+repr(pointer)+" )") |
---|
[23080] | 181 | return None |
---|
| 182 | else: |
---|
[23270] | 183 | self.mounts[self._uid()][name] = pointer['location'] |
---|
[23245] | 184 | syslog(LOG_INFO, "Mounting "+name+" on "+pointer['location']) |
---|
[23080] | 185 | return pointer['location'] |
---|
| 186 | else: |
---|
[23245] | 187 | syslog(LOG_WARNING, "Couldn't find filsys for "+name) |
---|
[23080] | 188 | return None |
---|
| 189 | |
---|
[23151] | 190 | def getdir(self, path): |
---|
| 191 | return [(i, 0) for i in (['.', '..', hello_path[1:]] + self.getCachedLockers())] |
---|
| 192 | |
---|
[23080] | 193 | def readdir(self, path, offset): |
---|
[23151] | 194 | for (r, zero) in self.getdir(path): |
---|
[23080] | 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 |
---|
[23260] | 218 | |
---|
| 219 | def symlink(self, src, path): |
---|
| 220 | if path == '/' or path == hello_path: |
---|
| 221 | return -errno.EPERM |
---|
| 222 | elif '/' not in path[1:]: |
---|
[23270] | 223 | self.mounts[self._uid()][path[1:]] = src |
---|
| 224 | self.negcache[self._uid()].remove(path[1:]) |
---|
[23260] | 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:]: |
---|
[23270] | 232 | del self.mounts[self._uid()][path[1:]] |
---|
| 233 | self.negcache[self._uid()].add(path[1:]) |
---|
[23260] | 234 | else: |
---|
| 235 | return -errno.EPERM |
---|
[23080] | 236 | |
---|
| 237 | def main(): |
---|
| 238 | global hello_str |
---|
[23151] | 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] |
---|
[23080] | 248 | |
---|
[23151] | 249 | """ |
---|
| 250 | if sys.argv[1] == '-f': |
---|
| 251 | sys.argv.pop(1) |
---|
| 252 | server = PyHesiodFS() |
---|
[23080] | 253 | |
---|
| 254 | hello_str = hello_str % {'mountpoint': server.parse(errex=1).mountpoint} |
---|
| 255 | server.main() |
---|
| 256 | |
---|
| 257 | if __name__ == '__main__': |
---|
| 258 | main() |
---|