"""
pyvfs.vfs -- abstract VFS layer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Internal VFS protocol. You can use this module to build your own
filesystems.
"""
import os
import stat
import time
import pwd
import grp
import threading
import logging
import inspect
import traceback
from io import BytesIO
DEFAULT_DIR_MODE = 0o755
DEFAULT_FILE_MODE = 0o644
[docs]class Eperm(Exception):
pass
[docs]class Eexist(Exception):
def __init__(self, target=None):
Exception.__init__(self, str(target))
self.target = target
[docs]class Edebug(Exception):
pass
def _restrict_debug(c):
def wrapped(*argv, **kwarg):
stack = inspect.stack()
try:
caller = stack[1][0].f_locals['self']
line = stack[1][0].f_code.co_firstlineno
assert isinstance(caller, Storage) or\
isinstance(caller, Inode)
except AssertionError:
logging.warning("Inode method %s called from: %s:%s" %\
(c, caller, line))
except:
logging.error("Got error while analyzing stack: %s" %\
(traceback.format_exc()))
return c(*argv, **kwarg)
return wrapped
def _restrict_bypass(c):
return c
if os.environ.get("PYVFS_LOG", "False").lower() in (
"yes", "true", "on", "t", "1"):
restrict = _restrict_debug
else:
restrict = _restrict_bypass
[docs]class Inode(BytesIO, object):
"""
VFS inode
"""
mode = 0
cleanup = None
# static member for special names
special_names = [
".",
"..",
]
def __init__(self, name, parent=None, mode=0, storage=None, **kwarg):
BytesIO.__init__(self)
self.parent = parent or self
self.storage = storage or parent.storage
self.children = {}
# if there is no transaction yet, create a blank one
if not isinstance(self.cleanup, dict):
self.cleanup = {}
self.name = name
self.type = 0
self.dev = 0
self.ctime = self.atime = self.mtime = int(time.time())
self.uidnum = self.muidnum = os.getuid()
self.gidnum = os.getgid()
self.uid = self.muid = pwd.getpwuid(self.uidnum).pw_name
self.gid = grp.getgrgid(self.gidnum).gr_name
self.writelock = False
if not self.mode:
if mode & stat.S_IFDIR:
self.mode = stat.S_IFDIR | DEFAULT_DIR_MODE
self.children["."] = self
self.children[".."] = self.parent
elif mode == stat.S_IFLNK:
self.mode = mode
else:
self.mode = stat.S_IFREG | DEFAULT_FILE_MODE
# all is ok for this moment, so we can clean up
# the transaction and create one exit hook
def __hash__(self):
return self.path
def _update_register(self):
if self.orphaned:
return
try:
self._check_special(self.name)
self.storage.unregister(self)
except:
pass
self.path = int(abs(hash(self.absolute_path())))
self.storage.register(self)
self.cleanup["storage"] = (self.storage.destroy, (self,))
for (i, k) in [x for x in list(self.children.items())
if x[0] not in (".", "..")]:
k._update_register()
def _get_name(self):
return self.__name
def _set_name(self, name):
self._check_special(name)
try:
if name in self.parent.children:
raise Eexist(self.parent.children[name])
del self.parent.children[self.name]
except Eexist as e:
raise e
except:
pass
self.__name = name
if (self.parent != self) and (self.parent != None):
self.parent.children[name] = self
try:
self._update_register()
except Exception as e:
self.destroy()
raise e
name = property(_get_name, _set_name)
def _check_special(self, *args):
for i in args:
if i in self.special_names:
raise Eperm()
@property
def orphaned(self):
if self.parent == self:
return False
if self.parent is None:
return True
return self.parent.orphaned
@restrict
def absolute_path(self, stop=None):
if (self.parent is not None) and\
(self.parent != self) and\
(self != stop):
return "%s/%s" % (self.parent.absolute_path(stop), self.name)
else:
return ""
@restrict
def commit(self):
pass
@restrict
def sync(self):
pass
@restrict
def open(self):
pass
@restrict
def destroy(self):
ret = {}
for (i, k) in list(self.cleanup.items()):
try:
if len(k) < 3:
kwarg = {}
else:
kwarg = k[2]
if len(k) < 2:
argv = []
else:
argv = k[1]
ret[i] = k[0](*argv, **kwarg)
except Exception as e:
ret[i] = e
logging.debug("destroy returned: %s" % (ret))
return ret
@restrict
def add(self, inode):
if inode.name in self.children:
raise Eexist()
self.children[inode.name] = inode
inode.parent = self
inode.storage = self.storage
inode._update_register()
[docs] @restrict
def remove(self, inode):
"""
Remove a child from a directory
"""
self._check_special(inode.name)
inode.parent = None
del self.children[inode.name]
[docs] @restrict
def create(self, name, mode=0, klass=None, **kwarg):
"""
Create a child in a directory
"""
self._check_special(name)
if klass is None:
klass = type(self)
self.children[name] = klass(name, self, mode=mode,
storage=self.storage, **kwarg)
return self.children[name]
[docs] @restrict
def rename(self, old_name, new_name):
"""
Rename a child
"""
# just a legacy interface
logging.debug("deprecated call Inode.rename() used")
self.children[old_name].name = new_name
@property
def length(self):
if self.mode & stat.S_IFDIR:
return len(list(self.children.keys()))
else:
return self.seek(0, 2)
[docs]class Storage(object):
"""
High-level storage insterface. Implements a simple protocol
for the file management and file lookup dictionary.
Should be provided with root 'inode' class on init. The 'inode'
class MUST support the interface... that should be defined :)
"""
def __init__(self, inode=Inode, **kwarg):
self.files = {}
self.lock = threading.RLock()
self.root = inode(name="/", mode=stat.S_IFDIR, storage=self,
**kwarg)
[docs] def register(self, inode):
"""
Register a new inode in the dictionary
"""
self.files[inode.path] = inode
[docs] def unregister(self, inode):
"""
Remove an inode from the dictionary
"""
del self.files[inode.path]
[docs] def create(self, name, parent, mode=0):
"""
Create an inode
"""
with self.lock:
new = parent.create(name, mode)
self.register(new)
return new
def checkout(self, target):
return self.files[target]
def reparent(self, new_parent, inode, new_name=None):
with self.lock:
lookup = new_name or inode.name
if lookup in new_parent.children:
raise Eexist()
inode.parent.remove(inode)
if new_name:
inode.name = new_name
new_parent.add(inode)
def truncate(self, inode, size=0):
with self.lock:
inode.seek(size)
inode.truncate()
inode.commit()
def open(self, inode):
with self.lock:
inode.sync()
inode.open()
def sync(self, inode):
with self.lock:
inode.sync()
def commit(self, inode):
with self.lock:
if inode.writelock:
inode.writelock = False
inode.commit()
def write(self, inode, data, offset=0):
with self.lock:
inode.writelock = True
inode.seek(offset, os.SEEK_SET)
inode.write(data)
return len(data)
def read(self, inode, size, offset=0):
with self.lock:
if offset == 0:
inode.sync()
inode.seek(offset, os.SEEK_SET)
data = inode.read(size)
return data
def destroy(self, inode):
with self.lock:
for i, k in list(inode.children.items()):
if i not in inode.special_names:
self.remove(k)
inode.parent.remove(inode)
self.unregister(inode)
def remove(self, inode):
with self.lock:
inode.destroy()
def chmod(self, inode, mode):
inode.mode = ((inode.mode & 0o7777) ^ inode.mode) |\
(mode & 0o7777)
def chown(self, inode, uid, gid):
if uid > -1:
try:
inode.uid = pwd.getpwuid(uid).pw_name
except:
inode.uid = ""
inode.uidnum = uid
if gid > -1:
try:
inode.gid = grp.getgrgid(gid).gr_name
except:
inode.gid = ""
inode.gidnum = gid