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 | When developing a descendent of RouteFS, any methods defined in that |
---|
10 | class are considered "controllers", and receive any other parameters |
---|
11 | specified by the URL as keyword arguments. |
---|
12 | """ |
---|
13 | |
---|
14 | import fuse |
---|
15 | import routes |
---|
16 | import errno |
---|
17 | import stat |
---|
18 | |
---|
19 | fuse.fuse_python_api = (0, 2) |
---|
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 | class RouteMeta(type): |
---|
39 | """ |
---|
40 | Metaclass to calculate controller methods |
---|
41 | |
---|
42 | Routes needs to be pre-seeded with a list of "controllers". For |
---|
43 | all descendents of RouteFS, the list of controllers is defined to |
---|
44 | be any non-private methods of the class that were not in the |
---|
45 | RouteFS class. |
---|
46 | """ |
---|
47 | def __init__(cls, classname, bases, dict_): |
---|
48 | super(RouteMeta, cls).__init__(classname, bases, dict_) |
---|
49 | if bases != (fuse.Fuse,): |
---|
50 | new_funcs = set(dict_.keys()).difference(dir(RouteFS)) |
---|
51 | cls.controllers([func for func in new_funcs \ |
---|
52 | if not func.startswith('_')]) |
---|
53 | |
---|
54 | class RouteFS(fuse.Fuse): |
---|
55 | """ |
---|
56 | RouteFS: Web 2.0 for filesystems |
---|
57 | """ |
---|
58 | __metaclass__ = RouteMeta |
---|
59 | def __init__(self, *args, **kwargs): |
---|
60 | super(RouteFS, self).__init__(*args, **kwargs) |
---|
61 | |
---|
62 | self.map = self.make_map() |
---|
63 | self.map.create_regs(self.controller_list) |
---|
64 | |
---|
65 | def make_map(self): |
---|
66 | """ |
---|
67 | This method should be overridden by descendents of RouteFS to |
---|
68 | define the routing for the filesystem |
---|
69 | """ |
---|
70 | m = routes.Mapper() |
---|
71 | |
---|
72 | m.connect(':controller') |
---|
73 | |
---|
74 | return m |
---|
75 | |
---|
76 | @classmethod |
---|
77 | def controllers(cls, lst): |
---|
78 | cls.controller_list = lst |
---|
79 | |
---|
80 | def _get_file(self, path): |
---|
81 | """ |
---|
82 | Find the filesystem entry object for a given path |
---|
83 | """ |
---|
84 | match = self.map.match(path) |
---|
85 | if match is None: |
---|
86 | return NoEntry() |
---|
87 | controller = match.pop('controller') |
---|
88 | result = getattr(self, controller)(**match) |
---|
89 | if result is None: |
---|
90 | return NoEntry() |
---|
91 | if type(result) is str: |
---|
92 | result = File(result) |
---|
93 | if type(result) is list: |
---|
94 | result = Directory(result) |
---|
95 | return result |
---|
96 | |
---|
97 | def readdir(self, path, offset): |
---|
98 | """ |
---|
99 | If the path referred to is a directory, return the elements of |
---|
100 | that diectory |
---|
101 | """ |
---|
102 | return self._get_file(path).readdir(offset) |
---|
103 | |
---|
104 | def getattr(self, path): |
---|
105 | """ |
---|
106 | Return the stat information for a path |
---|
107 | |
---|
108 | The stat information for a directory, symlink, or file is |
---|
109 | predetermined based on which it is. |
---|
110 | """ |
---|
111 | return self._get_file(path).getattr() |
---|
112 | |
---|
113 | def read(self, path, length, offset): |
---|
114 | """ |
---|
115 | If the path specified is a file, return the requested portion |
---|
116 | of the file |
---|
117 | """ |
---|
118 | return self._get_file(path).read(length, offset) |
---|
119 | |
---|
120 | def readlink(self, path): |
---|
121 | """ |
---|
122 | If the path specified is a symlink, return the target |
---|
123 | """ |
---|
124 | return self._get_file(path).readlink() |
---|
125 | |
---|
126 | class TreeKey(object): |
---|
127 | def getattr(self): |
---|
128 | return -errno.EINVAL |
---|
129 | def readdir(self, offset): |
---|
130 | return -errno.EINVAL |
---|
131 | def read(self, length, offset): |
---|
132 | return -errno.EINVAL |
---|
133 | def readlink(self): |
---|
134 | return -errno.EINVAL |
---|
135 | |
---|
136 | class NoEntry(TreeKey): |
---|
137 | def getattr(self): |
---|
138 | return -errno.ENOENT |
---|
139 | def readdir(self, offset): |
---|
140 | return -errno.ENOENT |
---|
141 | def read(self, length, offset): |
---|
142 | return -errno.ENOENT |
---|
143 | def readlink(self): |
---|
144 | return -errno.ENOENT |
---|
145 | |
---|
146 | class TreeEntry(TreeKey): |
---|
147 | default_mode = 0444 |
---|
148 | |
---|
149 | def __new__(cls, contents, mode=None): |
---|
150 | return super(TreeEntry, cls).__new__(cls, contents) |
---|
151 | |
---|
152 | def __init__(self, contents, mode=None): |
---|
153 | if mode is None: |
---|
154 | self.mode = self.default_mode |
---|
155 | else: |
---|
156 | self.mode = mode |
---|
157 | |
---|
158 | super(TreeEntry, self).__init__(contents) |
---|
159 | |
---|
160 | class Directory(TreeEntry, list): |
---|
161 | """ |
---|
162 | A dummy class representing a filesystem entry that should be a |
---|
163 | directory |
---|
164 | """ |
---|
165 | default_mode = 0555 |
---|
166 | |
---|
167 | def getattr(self): |
---|
168 | st = RouteStat() |
---|
169 | st.st_mode = stat.S_IFDIR | self.mode |
---|
170 | st.st_nlink = 2 |
---|
171 | return st |
---|
172 | |
---|
173 | def readdir(self, offset): |
---|
174 | for member in ['.', '..'] + self: |
---|
175 | yield fuse.Direntry(str(member)) |
---|
176 | |
---|
177 | class Symlink(TreeEntry, str): |
---|
178 | """ |
---|
179 | A dummy class representing something that should be a symlink |
---|
180 | """ |
---|
181 | default_mode = 0777 |
---|
182 | |
---|
183 | def getattr(self): |
---|
184 | st = RouteStat() |
---|
185 | st.st_mode = stat.S_IFLNK | self.mode |
---|
186 | st.st_nlink = 1 |
---|
187 | st.st_size = len(self) |
---|
188 | return st |
---|
189 | |
---|
190 | def readlink(self): |
---|
191 | return self |
---|
192 | |
---|
193 | class File(TreeEntry, str): |
---|
194 | """ |
---|
195 | A dummy class representing something that should be a file |
---|
196 | """ |
---|
197 | default_mode = 0444 |
---|
198 | |
---|
199 | def getattr(self): |
---|
200 | st = RouteStat() |
---|
201 | st.st_mode = stat.S_IFREG | self.mode |
---|
202 | st.st_nlink = 1 |
---|
203 | st.st_size = len(self) |
---|
204 | return st |
---|
205 | |
---|
206 | def read(self, length, offset): |
---|
207 | return self[offset:offset + length] |
---|
208 | |
---|
209 | def main(cls): |
---|
210 | """ |
---|
211 | A convenience function for initializing a RouteFS filesystem |
---|
212 | """ |
---|
213 | server = cls(version="%prog " + fuse.__version__, |
---|
214 | usage=fuse.Fuse.fusage, |
---|
215 | dash_s_do='setsingle') |
---|
216 | server.parse(values=server, errex=1) |
---|
217 | server.main() |
---|
218 | |
---|
219 | from dictfs import DictFS |
---|
220 | |
---|
221 | __all__ = ['RouteFS', 'DictFS', 'Symlink', 'Directory', 'File', 'main'] |
---|