├── .gitignore ├── routefs ├── __init__.py ├── dictfs.py └── examples │ ├── __init__.py │ ├── dictexfs.py │ ├── homefs.py │ └── pyhesiodfs.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | RouteFS.egg-info 2 | build 3 | dist 4 | *.pyc 5 | .DS_Store -------------------------------------------------------------------------------- /routefs/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | RouteFS is a base class for developing read-only FUSE filesystems that 3 | lets you focus on the directory tree instead of the system calls. 4 | 5 | RouteFS uses the Routes library developed for Pylons. URLs were 6 | inspired by filesystems, and now you can have filesystems inspired by 7 | URLs. 8 | """ 9 | 10 | 11 | import errno 12 | import stat 13 | 14 | import fuse 15 | import routes 16 | import signal 17 | 18 | fuse.fuse_python_api = (0, 2) 19 | 20 | 21 | class RouteStat(fuse.Stat): 22 | """ 23 | RouteStat is a descendent of fuse.Stat, defined to make sure that 24 | all of the necessary attributes are always defined 25 | """ 26 | def __init__(self): 27 | self.st_mode = 0 28 | self.st_ino = 0 29 | self.st_dev = 0 30 | self.st_nlink = 0 31 | self.st_uid = 0 32 | self.st_gid = 0 33 | self.st_size = 0 34 | self.st_atime = 0 35 | self.st_mtime = 0 36 | self.st_ctime = 0 37 | 38 | 39 | class RouteFS(fuse.Fuse): 40 | """ 41 | RouteFS: Web 2.0 for filesystems 42 | """ 43 | def __init__(self, *args, **kwargs): 44 | super(RouteFS, self).__init__(*args, **kwargs) 45 | 46 | self.map = self.make_map() 47 | 48 | def make_map(self): 49 | """ 50 | This method should be overridden by descendents of RouteFS to 51 | define the routing for the filesystem 52 | """ 53 | m = routes.Mapper() 54 | 55 | m.connect('{controller}') 56 | 57 | return m 58 | 59 | def _get_file(self, path): 60 | """ 61 | Find the filesystem entry object for a given path 62 | """ 63 | match = self.map.match(path) 64 | if match is None: 65 | return NoEntry() 66 | controller = match.pop('controller') 67 | result = getattr(self, controller)(**match) 68 | if result is None: 69 | return NoEntry() 70 | if type(result) is str: 71 | result = File(result) 72 | if type(result) is list: 73 | result = Directory(result) 74 | return result 75 | 76 | 77 | def __getattr__(self, attr): 78 | """ 79 | If the requested attribute is one of the standard FUSE op, 80 | return a function which finds the file matching the requested 81 | path, and passes the op to the object corresponding to that 82 | file. 83 | """ 84 | def op(path, *args): 85 | file = self._get_file(path) 86 | if hasattr(file, attr) and callable(getattr(file, attr)): 87 | return getattr(file, attr)(*args) 88 | 89 | if attr in ['getattr', 'readlink', 'readdir', 'mknod', 'mkdir', 90 | 'unlink', 'rmdir', 'symlink', 'rename', 'link', 'chmod', 91 | 'chown', 'truncate', 'utime', 'open', 'read', 92 | 'write', 'release', 'fsync', 'flush', 'getxattr', 93 | 'listxattr', 'setxattr', 'removexattr', 'ftruncate', 'create']: 94 | return op 95 | else: 96 | raise AttributeError, attr 97 | 98 | 99 | class TreeKey(object): 100 | pass 101 | 102 | 103 | class NoEntry(TreeKey): 104 | def getattr(self): 105 | return -errno.ENOENT 106 | 107 | def create(self, flags, mode): 108 | return -errno.EACCES 109 | 110 | 111 | 112 | class TreeEntry(TreeKey): 113 | default_mode = 0444 114 | 115 | def __new__(cls, contents, mode=None): 116 | return super(TreeEntry, cls).__new__(cls, contents) 117 | 118 | def __init__(self, contents, mode=None): 119 | if mode is None: 120 | self.mode = self.default_mode 121 | else: 122 | self.mode = mode 123 | 124 | super(TreeEntry, self).__init__(contents) 125 | 126 | 127 | class Directory(TreeEntry, list): 128 | """ 129 | A dummy class representing a filesystem entry that should be a 130 | directory 131 | """ 132 | default_mode = 0555 133 | 134 | def getattr(self): 135 | st = RouteStat() 136 | st.st_mode = stat.S_IFDIR | self.mode 137 | st.st_nlink = 2 138 | return st 139 | 140 | def readdir(self, offset): 141 | for member in ['.', '..'] + self: 142 | yield fuse.Direntry(str(member)) 143 | 144 | 145 | class Symlink(TreeEntry, str): 146 | """ 147 | A dummy class representing something that should be a symlink 148 | """ 149 | default_mode = 0777 150 | 151 | def getattr(self): 152 | st = RouteStat() 153 | st.st_mode = stat.S_IFLNK | self.mode 154 | st.st_nlink = 1 155 | st.st_size = len(self) 156 | return st 157 | 158 | def readlink(self): 159 | return self 160 | 161 | 162 | class File(TreeEntry, str): 163 | """ 164 | A dummy class representing something that should be a file 165 | """ 166 | default_mode = 0444 167 | 168 | def getattr(self): 169 | st = RouteStat() 170 | st.st_mode = stat.S_IFREG | self.mode 171 | st.st_nlink = 1 172 | st.st_size = len(self) 173 | return st 174 | 175 | def read(self, length, offset): 176 | return self[offset:offset + length] 177 | 178 | 179 | def main(cls): 180 | """ 181 | A convenience function for initializing a RouteFS filesystem 182 | """ 183 | server = cls(version="%prog " + fuse.__version__, 184 | usage=fuse.Fuse.fusage, 185 | dash_s_do='setsingle') 186 | server.parse(values=server, errex=1) 187 | signal.signal(signal.SIGINT, signal.SIG_DFL) 188 | server.main() 189 | 190 | 191 | from dictfs import DictFS 192 | 193 | __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main'] 194 | -------------------------------------------------------------------------------- /routefs/dictfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | DictFS allows you to easily create read-only filesystems when the 3 | file tree is known in advance. 4 | 5 | To create your own DictFS descendent, simply override the files 6 | property, which can be created either using the property 7 | decorator, or just a simple assignment. 8 | 9 | A dictionary represents a directory, with keys corresponding to 10 | file names and the values corresponding to the file contents. 11 | """ 12 | 13 | 14 | import os 15 | 16 | from routes import Mapper 17 | 18 | import routefs 19 | 20 | 21 | class DictFS(routefs.RouteFS): 22 | @property 23 | def files(self): 24 | """ 25 | This property should be overridden in your DictFS descendant 26 | """ 27 | return dict() 28 | 29 | def make_map(self): 30 | m = Mapper() 31 | 32 | m.connect('/{path:.*}', controller='handler') 33 | 34 | return m 35 | 36 | def handler(self, path, **kwargs): 37 | if path != '': 38 | elements = path.split(os.path.sep) 39 | else: 40 | elements = [] 41 | 42 | try: 43 | tree = self.files 44 | for elt in elements: 45 | tree = tree[elt] 46 | except KeyError: 47 | return 48 | 49 | if type(tree) is dict: 50 | return tree.keys() 51 | else: 52 | return tree 53 | -------------------------------------------------------------------------------- /routefs/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebroder/python-routefs/89393effc26ae47230ca185e692984ee2bda52c0/routefs/examples/__init__.py -------------------------------------------------------------------------------- /routefs/examples/dictexfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | 4 | import routefs 5 | 6 | 7 | class DictExFS(routefs.DictFS): 8 | files = dict(Hello='World', 9 | Directory=dict(a='a', b='b', c=routefs.Symlink('a'))) 10 | 11 | 12 | if __name__ == '__main__': 13 | routefs.main(DictExFS) 14 | -------------------------------------------------------------------------------- /routefs/examples/homefs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | RouteFS Example: HomeFS 4 | 5 | If you work on a system where home directories are on network storage 6 | (i.e. not in /home), mount HomeFS on /home. It's an automounter that 7 | will automatically create symlinks from user -> their homedir whenever 8 | /home/user is accessed in any way. 9 | """ 10 | 11 | 12 | import pwd 13 | 14 | from routes import Mapper 15 | 16 | import routefs 17 | 18 | 19 | class HomeFS(routefs.RouteFS): 20 | def __init__(self, *args, **kwargs): 21 | super(HomeFS, self).__init__(*args, **kwargs) 22 | self.cache = {} 23 | 24 | def make_map(self): 25 | m = Mapper() 26 | m.connect('/', controller='getList') 27 | m.connect('/{action}', controller='getUser') 28 | return m 29 | 30 | def getUser(self, action, **kwargs): 31 | try: 32 | if action not in self.cache: 33 | self.cache[action] = pwd.getpwnam(action).pw_dir 34 | return routefs.Symlink(self.cache[action]) 35 | except KeyError: 36 | return 37 | 38 | def getList(self, **kwargs): 39 | return self.cache.keys() 40 | 41 | 42 | if __name__ == '__main__': 43 | routefs.main(HomeFS) 44 | -------------------------------------------------------------------------------- /routefs/examples/pyhesiodfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | 4 | import hesiod 5 | from routes import Mapper 6 | 7 | import routefs 8 | 9 | 10 | class PyHesiodFS(routefs.RouteFS): 11 | def __init__(self, *args, **kwargs): 12 | super(PyHesiodFS, self).__init__(*args, **kwargs) 13 | self.fuse_args.add("allow_other", True) 14 | 15 | self.cache = {} 16 | 17 | def make_map(self): 18 | m = Mapper() 19 | m.connect('/', controller='getList') 20 | m.connect('/README.txt', controller='getReadme') 21 | m.connect('/{action}', controller='getLocker') 22 | return m 23 | 24 | def getLocker(self, action, **kwargs): 25 | if action in self.cache: 26 | return routefs.Symlink(self.cache[action]) 27 | 28 | try: 29 | filsys = hesiod.FilsysLookup(action).filsys[0] 30 | if filsys['type'] == 'AFS': 31 | self.cache[action] = filsys['location'] 32 | return routefs.Symlink(self.cache[action]) 33 | except (TypeError, KeyError, IndexError): 34 | return 35 | 36 | def getList(self, **kwargs): 37 | return self.cache.keys() + ['README.txt'] 38 | 39 | def getReadme(self, **kwargs): 40 | return """ 41 | This is the pyHesiodFS FUSE automounter. To access a Hesiod filsys, 42 | just access /mit/name. 43 | 44 | If you're using the Finder, try pressing Cmd+Shift+G and then entering 45 | /mit/name 46 | """ 47 | 48 | 49 | if __name__ == '__main__': 50 | routefs.main(PyHesiodFS) 51 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name="RouteFS", 7 | version="1.2.0", 8 | description="RouteFS: A FUSE API wrapper based on URL routing", 9 | author="Evan Broder", 10 | author_email="broder@mit.edu", 11 | url="http://github.com/ebroder/python-routefs/wikis", 12 | license="MPL, GPL", 13 | packages=find_packages(), 14 | install_requires=['fuse_python>=0.2a', 'Routes>=1.9'], 15 | classifiers=[ 16 | 'Development Status :: 5 - Production/Stable', 17 | 'Environment :: Plugins', 18 | 'Environment :: No Input/Output (Daemon)', 19 | 'Intended Audience :: Developers', 20 | 'Intended Audience :: System Administrators', 21 | 'License :: OSI Approved :: GNU General Public License (GPL)', 22 | 'License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)', 23 | 'License :: DFSG approved', 24 | 'Operating System :: MacOS :: MacOS X', 25 | 'Operating System :: POSIX', 26 | 'Programming Language :: Python' 27 | ] 28 | ) 29 | --------------------------------------------------------------------------------