-
Notifications
You must be signed in to change notification settings - Fork 1
/
mount.py
executable file
·321 lines (275 loc) · 10 KB
/
mount.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#!/usr/bin/python
import fuse
from fuse import Fuse
import os
import sys
import traceback
from threading import RLock
import operations as op
import storage
from log import log
import log as _log
from util.attr import issubclass
from util.struct import DictAttr
import errno
from aspect import Aspect
fuse.fuse_python_api = (0, 2)
class TagFS( Fuse):
archive = None
repository = None #config.default_repo
logfile = None #config.default_logfile
storage_type = None #config.default_storage_type
query_grammar = DictAttr()
aspects = []
@property
def mountpoint( me):
return me.fuse_args.mountpoint
def __getattr__( me, name):
klas = getattr( op.Operations, name, None)
if not klas:
return Fuse.__getattr__( me, name)
#log( '__getattr__', name)
return me.get_op( klas)
def prepare_storage( me):
mem = me.storage_type == 'memory'
if mem:
me.storage_type = 'sqlite'
main_storage = getattr( storage, me.storage_type)
for a in me.aspects: #TODO topologically sorted
aspect_storage = getattr(a, me.storage_type, None)
if not aspect_storage: continue
main_storage.include( aspect_storage)
main_storage.setup_listeners()
me.db = main_storage( me, memory=mem)
def get_func_name( me, obj):
return obj.__class__.__name__.lower()
class Func1:
def __init__( me, op_klas, context):
me.ctx = context
me.op_klas = op_klas
def __call__( me, path, *args):
if not isinstance( path, unicode): path = unicode( path, 'utf-8')
ctx = me.ctx
op_klas = me.op_klas
log('------', op_klas.__name__, path, *args)
d = me._from_path( path)
if d.malformed:
return -op_klas.malformed
op_klas, func = me._get_op_func( d.asp, op_klas, d.typ)
if not func:
log('func:', d.typ + ' not implemented')
return -op_klas.not_implemented
return me._exec( func, op_klas( ctx, d.q, *args), d.obj)
def _exec( me, func, op, *operands):
log('func:', func.__name__, op.__class__)
res = None
db = me.ctx.db
try:
res = func( op, *[ o for o in operands if o])
except Exception, e:
log('<<<<<<<<<<<<<<< Surprise:', e, '>>>>>>>>>>>', traceback.format_exc() )
db.rollback()
res = errno.ENOSYS
if not isinstance(res, bool) and res in errno.errorcode:
res = -res
db.rollback()
else:
db.commit()
if isinstance(res,unicode):
res = res.encode('utf-8')
log('------ result:', repr(res))
return res
def _from_path( _me, path):
asp, path_in_aspect = _me._get_aspect( path)
q = asp.Parser( _me.ctx, _me.op_klas, path, path_in_aspect)
obj = None
typ = q.parse()
if not isinstance( typ, str):
obj = typ
typ = _me.ctx.get_func_name( typ)
#log('typ', typ)
malformed = typ == 'malformed'
del _me
return DictAttr( **locals())
def _get_aspect( me, path):
for a in me.ctx.aspects:
z = a.rexp().match( path)
if z:
path_in_asp = path[ z.end():]
if path_in_asp.startswith( os.path.sep):
path_in_asp = path_in_asp[ len(os.path.sep):]
log( 'aspect', a.__name__, 'path_in_aspect:', path_in_asp)
return a, path_in_asp
return Aspect, None
def _get_op_func( me, asp, op_klas, name):
op_klas = getattr( asp, op_klas.__name__, op_klas)
func = getattr( op_klas, name, None)
return op_klas, func
class Func2( Func1):
use_coercion = False
def __call__( me, path1, path2):
if not isinstance( path1, unicode): path1 = unicode( path1, 'utf-8')
if not isinstance( path2, unicode): path2 = unicode( path2, 'utf-8')
ctx = me.ctx
op_klas = me.op_klas
log( '======== ', op_klas.__name__, path1, path2)
d1 = me._from_path( path1)
d2 = me._from_path( path2)
if d1.malformed or d2.malformed:
return -op_klas.malformed
fname = d1.typ +'_'+ d2.typ
op_klas1, func1 = me._get_op_func( d1.asp, op_klas, fname)
op_klas2, func2 = me._get_op_func( d2.asp, op_klas, fname)
if me.use_coercion:
func = func1 or func2
else:
i1 = len(ctx.aspects) if d1.asp is Aspect else ctx.aspects.index( d1.asp)
if i1 > ctx.aspects.index( d2.asp):
op_klas, func = op_klas2, func2
else:
op_klas, func = op_klas1, func1
if not func:
log('func:', fname + ' not implemented')
return -op_klas.not_implemented
if me.use_coercion:
op_klas = op_klas1 if func1 else op_klas2
if issubclass( op_klas2, op_klas1): # mimic python rules for coercion
func = func2
op_klas = op_klas2
return me._exec( func, op_klas( ctx, d1.q, d2.q), d1.obj, d2.obj)
def get_op( me, op_klas):
f = None
if issubclass( op_klas, op.Op1):
f = me.Func1( op_klas, me)
elif issubclass( op_klas, op.Op2):
f = me.Func2( op_klas, me)
else:
log( 'get_op unknown op_klas', op_klas)
assert 0
#log( 'get_op', op_klas.__name__, f.__class__.__name__)
return f
def statfs( me):
log( 'statfs')
return os.statvfs( me.archive)
def fsinit( me, *args):
log( 'fsinit', args)
if 0:
me.asgid = int( me.asgid)
me.asuid = int( me.asuid)
if me.asgid:
os.setgid( me.asgid)
if me.asuid:
os.setuid( me.asuid)
for a in me.aspects:
a.init( me)
return 0
def fsdestroy ( me, *args):
log( 'fsdestroy', args)
return -errno.ENOSYS
##############
class Lock( object):
def __init__( me):
me.lock = RLock()
def __enter__( me):
me.lock.acquire()
return me
def __exit__( me, type, value, traceback):
me.lock.release()
class FsFile( object):
def __init__( me, path, flags, *mode):
me.fd = os.open( path, flags)
me.direct_io = 0
me.keep_cache = 0
me.lock = Lock()
def read( me, length, offset):
with me.lock:
os.lseek( me.fd, offset, os.SEEK_SET)
buf = os.read( me.fd, length)
return buf
def write( me, buf, offset):
with me.lock:
os.lseek( me.fd, offset, os.SEEK_SET)
bytes = os.write( me.fd, buf)
return bytes
def release( me, flags):
with me.lock:
os.close( me.fd)
def fsync( me, isfsyncfile):
with me.lock:
if isfsyncfile and hasattr( os, 'fdatasync'):
os.fdatasync( me.fd)
else:
os.fsync( me.fd)
def flush( me):
with me.lock:
os.close( os.dup( me.fd))
def fgetattr( me):
with me.lock:
return os.fstat( me.fd)
def ftruncate( me, len):
with me.lock:
os.ftruncate( me.fd, len)
from os.path import basename
def usage():
return '''
USAGE:
{prog} [-d] [-o option1=value1] <mountpoint>
PARAMETERS:
-h - show full help with options
-d - show verbose debug info
'''.format( prog = basename( sys.argv[0]))
# USAGE ( fstab):
# {prog}# <mount_point> fuse allow_other[,<options>] 0 0
def create_if_not_exists( name):
name = os.path.normpath( os.path.expanduser( name))
if not os.path.isdir( name):
log('creating dir: ', name)
try:
os.makedirs( name, 755)
except OSError, e:
log('Cannot create: ', name, e)
return None
else:
log('found dir: ', name)
os.chmod( name, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
#os.chmod( name, 755)
return name
def create_repo( path, config):
repo = create_if_not_exists( path or config.default_repo)
if not repo:
return
arch = create_if_not_exists( os.path.join( repo, 'archive'))
if not arch:
return
return repo, arch
from stat import *
def main( args, config):
if len( args) == 1:
print usage()
return -1
srv = TagFS( version='%prog ' + fuse.__version__, usage=usage(), dash_s_do='setsingle')
srv.flags = 0
srv.multithreaded = False
srv.parser.add_option( mountopt='repository', metavar='PATH', default=config.default_repo, help='repository path [default: %s]' % config.default_repo)
srv.parser.add_option( mountopt='logfile', metavar='FILE', default=config.default_logfile, help='log debug info to a file; leave empty to disable [default: %s]' % config.default_logfile)
srv.parser.add_option( mountopt='storage_type', metavar='[sqlite|memory]', default=config.default_storage_type, help='storage backend: %s]' % config.default_storage_type)
srv.parse( values=srv, errex=1)
_log.DEBUG = '-d' in args
_log.LOGFILE = srv.logfile
d = create_repo( srv.repository, config)
if not d: return
srv.aspects = config.default_enabled_aspects
srv.query_grammar.update( config.default_query_grammar)
srv.repository, srv.archive = d
if not srv.storage_type:
srv.storage_type = config.default_storage_type
srv.prepare_storage()
srv.file_class = FsFile
srv.main()
return srv
if __name__ == '__main__':
import config
rval = main( sys.argv, config)
if not rval:
sys.exit( -1)
# vim:ts=4:sw=4:expandtab