Skip to content

Commit 3033c83

Browse files
committed
Rename to executable name
1 parent ab57fcb commit 3033c83

File tree

1 file changed

+390
-0
lines changed

1 file changed

+390
-0
lines changed

pycloudfuse

+390
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
#!/usr/bin/python
2+
"""
3+
Fuse interface to Rackspace Cloudfiles & Openstack Object Storage
4+
5+
Copyright (C) 2011 by Memset Ltd. http://www.memset.com/
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in
15+
all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
THE SOFTWARE.
24+
25+
FIXME sometimes gives this error
26+
27+
LOOKUP /test
28+
getattr /test
29+
DEBUG:root:getattr('/test',) {}: enter
30+
DEBUG:root:stat '/test'
31+
DEBUG:root:stat path '/test'
32+
DEBUG:root:listdir ''
33+
DEBUG:root:listdir root
34+
WARNING:root:stat: Response error: 400: Bad request syntax ('0')
35+
DEBUG:root:getattr: caught error: [Errno 1] Operation not permitted: 400: Bad request syntax ('0')
36+
DEBUG:root:getattr: returns -1
37+
unique: 111, error: -1 (Operation not permitted), outsize: 16
38+
39+
FIXME retry?
40+
"""
41+
42+
# FIXME how do logging?
43+
44+
# FIXME make it single threaded
45+
46+
import fuse
47+
from time import time
48+
import stat
49+
import os
50+
import errno
51+
import logging
52+
from ftpcloudfs.fs import CloudFilesFS
53+
from functools import wraps
54+
55+
fuse.fuse_python_api = (0, 2)
56+
fuse.feature_assert('stateful_files', 'has_init')
57+
58+
def return_errnos(func):
59+
"""
60+
Decorator to catch EnvironmentError~s and return negative errnos from them
61+
62+
Other exceptions are not caught.
63+
"""
64+
@wraps(func)
65+
def wrapper(*args,**kwargs):
66+
name = getattr(func, "func_name", "unknown")
67+
try:
68+
logging.debug("%s%r %r: enter" % (name, args[1:], kwargs))
69+
rc = func(*args,**kwargs)
70+
except EnvironmentError, e:
71+
logging.debug("%s: caught error: %s" % (name, e))
72+
rc = -e.errno
73+
logging.debug("%s: returns %r" % (name, rc))
74+
return rc
75+
return wrapper
76+
77+
def flag2mode(flags):
78+
md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
79+
m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
80+
81+
if flags | os.O_APPEND:
82+
m = m.replace('w', 'a', 1)
83+
84+
return m
85+
86+
class CloudFuseFile(object):
87+
"""
88+
An open file
89+
"""
90+
91+
def __init__(self, path, flags, *mode):
92+
#self.parent = parent
93+
#self.fs = fs
94+
self.path = path
95+
self.reading = self.writing = False
96+
if flags & (os.O_WRONLY|os.O_CREAT):
97+
mode = "w"
98+
self.writing = True
99+
elif flags & os.O_RDWR:
100+
# Not supported!
101+
mode = "rw"
102+
self.reading = True
103+
self.writing = True
104+
else:
105+
mode = "r"
106+
self.reading = True
107+
if flags & os.O_APPEND:
108+
mode += "+"
109+
# FIXME ignores os.O_TRUNC, os.O_EXCL
110+
self.file = self.fs.open(path, mode)
111+
if self.writing:
112+
self.parent.file_opened(self.path)
113+
114+
@return_errnos
115+
def read(self, length, offset):
116+
# FIXME self.file.seek(offset)
117+
# check we aren't really seeking
118+
return self.file.read(length)
119+
120+
@return_errnos
121+
def write(self, buf, offset):
122+
# FIXME self.file.seek(offset)
123+
# check we aren't really seeking
124+
self.file.write(buf)
125+
return len(buf)
126+
127+
@return_errnos
128+
def release(self, flags):
129+
self.file.close()
130+
if self.writing:
131+
self.parent.file_closed(self.path)
132+
133+
@return_errnos
134+
def fsync(self, isfsyncfile):
135+
pass
136+
137+
@return_errnos
138+
def flush(self):
139+
pass
140+
141+
@return_errnos
142+
def fgetattr(self):
143+
if self.writing:
144+
logging.debug("Returning synthetic stat for open file %r" % self.path)
145+
mode = 0644|stat.S_IFREG
146+
mtime = time()
147+
bytes = 0 # FIXME could read bytes so far out of the open file
148+
count = 1
149+
return os.stat_result((mode, 0L, 0L, count, 0, 0, bytes, mtime, mtime, mtime))
150+
return self.fs.stat(self.path)
151+
152+
@return_errnos
153+
def ftruncate(self, len):
154+
# FIXME could implement this
155+
# maybe should ignore if write pos = 0
156+
return -errno.ENOSYS
157+
158+
@return_errnos
159+
def lock(self, cmd, owner, **kw):
160+
return -errno.ENOSYS
161+
162+
class CloudFuse(fuse.Fuse):
163+
"""
164+
Fuse interface to Rackspace Cloudfiles & Openstack Object Storage
165+
"""
166+
CONFIG_KEYS = ('username','api_key','cache_timeout','authurl','use_snet')
167+
INT_CONFIG_KEYS = ('cache_timeout',)
168+
BOOL_CONFIG_KEYS = ('use_snet',)
169+
170+
def __init__(self, username=None, api_key=None, cache_timeout=600, authurl=None, use_snet=False, *args, **kw):
171+
fuse.Fuse.__init__(self, *args, **kw)
172+
self.username = username
173+
self.api_key = api_key
174+
self.cache_timeout = cache_timeout
175+
self.authurl = authurl
176+
self.use_snet = use_snet
177+
self.read_config()
178+
self.fs = CloudFilesFS(self.username, self.api_key, servicenet=self.use_snet, authurl=self.authurl)
179+
self.open_files = {} # count of open files
180+
self.file_class.fs = self.fs # FIXME untidy and not re-entrant!
181+
self.file_class.parent = self # FIXME
182+
logging.debug("Finished init")
183+
184+
def __repr__(self):
185+
return "%s(%r)" % (self.__class__.__name__, self.username)
186+
187+
def file_opened(self, path):
188+
"""Keep track of a file being opened"""
189+
logging.debug("File opened: %r" % path)
190+
self.open_files[path] = self.open_files.get(path, 0) + 1
191+
logging.debug("open files: %r" % self.open_files)
192+
193+
def file_closed(self, path):
194+
"""Keep track of a file being closed"""
195+
logging.debug("File closed: %r" % path)
196+
count = self.open_files.get(path)
197+
if count is None:
198+
return
199+
count -= 1
200+
if count:
201+
self.open_files[path] = count
202+
else:
203+
del self.open_files[path]
204+
logging.debug("open files: %r" % self.open_files)
205+
206+
def read_config(self, config="~/.cloudfuse"):
207+
"""
208+
Reads the config file in ~/.cloudfuse
209+
"""
210+
config = os.path.expanduser(config)
211+
try:
212+
fd = open(config, "r")
213+
except IOError:
214+
logging.warning("Failed to read config file %r" % config)
215+
return
216+
try:
217+
for line in fd:
218+
try:
219+
line = line.strip()
220+
if not line or line.startswith("#"):
221+
continue
222+
key, value = line.split("=", 1)
223+
key = key.strip()
224+
value = value.strip()
225+
if key not in self.CONFIG_KEYS:
226+
logging.warning("Ignoring unknown config key %r" % key)
227+
continue
228+
if key in self.INT_CONFIG_KEYS:
229+
key = int(key)
230+
if key in self.BOOL_CONFIG_KEYS:
231+
key = value == "true"
232+
logging.debug("setting %r = %r from %r" % (key, value, config))
233+
setattr(self, key, value)
234+
except ValueError:
235+
logging.warning("Ignoring bad line in %r: %r" % (config, line))
236+
continue
237+
finally:
238+
fd.close()
239+
240+
@return_errnos
241+
def getattr(self, path):
242+
"""Stat the path"""
243+
# FIXME should be write files..
244+
# If have a file open for write it may not actually exist yet
245+
if path in self.open_files:
246+
logging.debug("Returning synthetic stat for open file %r" % path)
247+
mode = 0644|stat.S_IFREG
248+
mtime = time()
249+
bytes = 0 # FIXME could read bytes so far out of the open file
250+
count = 1
251+
return os.stat_result((mode, 0L, 0L, count, 0, 0, bytes, mtime, mtime, mtime))
252+
return self.fs.stat(path)
253+
254+
@return_errnos
255+
def readdir(self, path, offset):
256+
# FIXME use yield?
257+
# What about other attributes?
258+
# What about . and .. ?
259+
return [ fuse.Direntry(leaf) for leaf in self.fs.listdir(path) ]
260+
261+
@return_errnos
262+
def mythread(self):
263+
return -errno.ENOSYS
264+
265+
@return_errnos
266+
def chmod(self, path, mode):
267+
return 0 # FIXME not really!
268+
return -errno.ENOSYS
269+
270+
@return_errnos
271+
def chown(self, path, uid, gid):
272+
return 0 # FIXME not really!
273+
return -errno.ENOSYS
274+
275+
@return_errnos
276+
def link(self, dst, src):
277+
return -errno.ENOSYS
278+
279+
@return_errnos
280+
def mkdir(self, path, mode):
281+
self.fs.mkdir(path)
282+
return 0
283+
284+
@return_errnos
285+
def mknod(self, path, mode, dev):
286+
# FIXME could do with a better touch method...
287+
if not stat.S_ISREG(mode):
288+
return -errno.ENOSYS
289+
fd = self.fs.open(path, "w")
290+
fd.close()
291+
292+
@return_errnos
293+
def readlink(self, path):
294+
return -errno.ENOSYS
295+
296+
@return_errnos
297+
def rename(self, src, dst):
298+
self.fs.rename(src, dst)
299+
return 0
300+
301+
@return_errnos
302+
def rmdir(self, path):
303+
self.fs.rmdir(path)
304+
return 0
305+
306+
@return_errnos
307+
def statfs(self):
308+
"""
309+
Information about the whole filesystem which we collect from the container stats
310+
"""
311+
bytes = 0
312+
files = 0
313+
for leaf, stat in self.fs.listdir_with_stat("/"):
314+
bytes += stat.st_size
315+
files += stat.st_nlink
316+
block_size = 4096
317+
used = bytes // block_size
318+
total = 1024*1024*1024*1024 // block_size
319+
while used >= total:
320+
total *= 2
321+
free = total - used
322+
total_files = 1024*1024
323+
while files >= total_files:
324+
total_files *= 2
325+
free_files = total_files - files
326+
return fuse.StatVfs(
327+
f_bsize = block_size, # preferred size of file blocks, in bytes
328+
f_frsize = block_size,# fundamental size of file blcoks, in bytes
329+
f_blocks = total, # total number of blocks in the filesystem
330+
f_bfree = free, # number of free blocks
331+
f_bavail = free, # Free blocks available to non-super user.
332+
f_files = total_files, # total number of file inodes
333+
f_ffree = free_files, # nunber of free file inodes
334+
)
335+
336+
@return_errnos
337+
def symlink(self, dst, src):
338+
return -errno.ENOSYS
339+
340+
@return_errnos
341+
def truncate(self, path, size):
342+
# FIXME if is open for write, do nothing
343+
fd = self.fs.open(path, "w")
344+
# FIXME nasty!
345+
fd.write("\000" * size)
346+
fd.close()
347+
return 0
348+
#return -errno.ENOSYS
349+
350+
@return_errnos
351+
def unlink(self, path):
352+
self.fs.remove(path)
353+
return 0
354+
355+
@return_errnos
356+
def utime(self, path, times):
357+
return 0 # FIXME not really!
358+
return -errno.ENOSYS
359+
360+
#@return_errnos
361+
#def file_class(self, path, flags, *mode):
362+
# """
363+
# Returns a class which acts like a python file object
364+
# """
365+
# return CloudFuseFile(self, self.fs, path, flags, *mode)
366+
file_class = CloudFuseFile
367+
368+
369+
def main():
370+
fs = CloudFuse(version="%prog " + fuse.__version__,
371+
usage=CloudFuse.__doc__.strip(),
372+
dash_s_do='setsingle')
373+
374+
fs.parser.add_option(mountopt="root", metavar="PATH", default='/',
375+
help="mirror filesystem from under PATH [default: %default]")
376+
fs.parse(values=fs, errex=1)
377+
378+
# FIXME Is there a better way than this?
379+
debug = "debug" in fs.fuse_args.optlist
380+
if debug:
381+
logging.getLogger().setLevel(logging.DEBUG)
382+
383+
#fs.flags = 0
384+
#fs.multithreaded = 0
385+
#fs.main()
386+
387+
fs.main()
388+
389+
if __name__ == '__main__':
390+
main()

0 commit comments

Comments
 (0)