<root is_dir="1">
<a is_dir="1">
<file1.txt>File content for this file</file1.txt>
<hello>Hello World</hello>
</a>
<empty_dir is_dir="1" />
</root>
The example models the following directory tree:
/ |-- a | |-- file1.txt | `-- hello `-- empty_dirThere are some limitations in having an XML file representing a file system. You can't for example have filenames starting with a dot because tags are not allowed to start with a dot in XML.
On to the code, the following snippet implements the read-only file system which is backed by an XML file:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import errno
import fuse
import stat
import os
import time
import xml.etree.ElementTree as etree
fuse.fuse_python_api = (0, 2)
# Use same timestamp for all files
_file_timestamp = int(time.time())
class MyStat(fuse.Stat):
"""
Convenient class for Stat objects.
Set up the stat object with appropriate
values depending on constructor args.
"""
def __init__(self, is_dir, size):
fuse.Stat.__init__(self)
if is_dir:
self.st_mode = stat.S_IFDIR | 0555
self.st_nlink = 2
else:
self.st_mode = stat.S_IFREG | 0444
self.st_nlink = 1
self.st_size = size
self.st_atime = _file_timestamp
self.st_mtime = _file_timestamp
self.st_ctime = _file_timestamp
class MyFS(fuse.Fuse):
def __init__(self, xml_tree, *args, **kw):
fuse.Fuse.__init__(self, *args, **kw)
self.tree = xml_tree
def getattr(self, path):
# We do not support 'dot' files
# since xml tags cannot start with a dot.
if path.find('/.') != -1:
return -errno.ENOENT
entry = self.tree.find(path)
if entry is None:
return -errno.ENOENT
else:
is_dir = entry.get('is_dir', False)
size = entry.text and len(entry.text.strip()) or 0
return MyStat(is_dir, size)
def readdir(self, path, offset):
yield fuse.Direntry('.')
yield fuse.Direntry('..')
for e in self.tree.find(path).getchildren():
yield fuse.Direntry(e.tag)
def open(self, path, flags):
# Only support for 'READ ONLY' flag
access_flags = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
if flags & access_flags != os.O_RDONLY:
return -errno.EACCES
else:
return 0
def read(self, path, size, offset):
entry = self.tree.find(path)
content = entry.text and entry.text.strip() or ''
file_size = len(content)
if offset < file_size:
if offset + size > file_size:
size = file_size - offset
return content[offset:offset+size]
else:
return ''
if __name__ == '__main__':
tree = etree.parse('tree.xml')
fs = MyFS(tree)
fs.parse(errex=1)
fs.main()
I'm using the ElementTree XML API to parse the XML file. The ElementTree API is very easy to use and supports finding elements by specifying a path which fits very well in this context. For example, the following snippet shows how to get the file1.txt element from the example XML file above and extract the content between the tags:
import xml.etree.ElementTree as etree
tree = etree.parse('tree.xml')
el = tree.find('/a/file1.txt')
print el.text.strip() # Remove any white-spaces
The code hasn't change that much since the part 1 post. I've added two new methods which adds support for opening and reading files. The MyStat class is only used as an convenient class to help creating appropriate stat objects. You might notice that I don't do a lot of checking in the code, this is because FUSE do a lot of them for me, this page list some of the assumptions you can make when implementing file system using FUSE.Did you believe it would be this easy to create a mountable file system that uses an XML file for the layout? I didn't.
In the next (and final) post about FUSE I think I'll create some kind of 'use a service on the Internet' file system which can be useful and not just another toy fs.
No comments:
Post a Comment