diff options
author | Joerg Steffens <joerg.steffens@bareos.com> | 2015-09-24 20:35:17 +0300 |
---|---|---|
committer | Joerg Steffens <joerg.steffens@bareos.com> | 2015-09-27 18:32:28 +0300 |
commit | 54c03c0cb90d43f6e26d483114eaaf4dd1ca6439 (patch) | |
tree | 47056850c00474dba78eb331867819cea991b8af | |
parent | 2240426e91472380abd8f611980cc2bc3595beab (diff) |
implement restore for the bareos.fuse
-rw-r--r-- | bareos/fuse/bareosfuse.py | 83 | ||||
-rw-r--r-- | bareos/fuse/node/base.py | 142 | ||||
-rw-r--r-- | bareos/fuse/node/bvfscommon.py | 67 | ||||
-rw-r--r-- | bareos/fuse/node/bvfsdir.py | 21 | ||||
-rw-r--r-- | bareos/fuse/node/bvfsfile.py | 80 | ||||
-rw-r--r-- | bareos/fuse/node/directory.py | 2 | ||||
-rw-r--r-- | bareos/fuse/root.py | 4 |
7 files changed, 350 insertions, 49 deletions
diff --git a/bareos/fuse/bareosfuse.py b/bareos/fuse/bareosfuse.py index aa23733..cda79a1 100644 --- a/bareos/fuse/bareosfuse.py +++ b/bareos/fuse/bareosfuse.py @@ -46,36 +46,97 @@ class BareosFuse(fuse.Fuse): self.logger.debug( "%s: %s" %(i, getattr(self,i))) parameter[i] = getattr(self,i) else: - self.logger.debug( "%s: missing" %(i)) + self.logger.debug( "%s: missing, default: %s" %(i, str(getattr(self,i,None)))) self.logger.debug('options: %s' % (parameter)) - password = bareos.bsock.Password(self.password) - parameter['password']=password + if not hasattr(self, 'password'): + raise RuntimeError("missing parameter password") + else: + password = bareos.bsock.Password(self.password) + parameter['password']=password self.bsock = bareos.bsock.BSockJson(**parameter) - self.bareos = Root(self.bsock) + if not hasattr(self, 'restoreclient'): + self.restoreclient = None + #raise RuntimeError("missing parameter restoreclient") + # TODO: check if restoreclient is valid + if not hasattr(self, 'restorepath'): + self.restorepath = '/var/cache/bareosfs/' + self.bareos = Root(self.bsock, self.restoreclient, self.restorepath) super(BareosFuse, self).main(*args, **kw) self.logger.debug('done') - def getattr(self, path): if self.bareos: stat = self.bareos.getattr(Path(path)) if isinstance(stat, fuse.Stat): - self.logger.debug("fuse %s: nlink=%i" % (path, stat.st_nlink)) + self.logger.debug("%s: nlink=%i" % (path, stat.st_nlink)) else: - self.logger.debug("fuse %s: (int) %i" % (path, stat)) + self.logger.debug("%s: (int) %i" % (path, stat)) return stat + def read(self, path, size, offset): + result = self.bareos.read(Path(path), size, offset) + #self.logger.debug("%s: %s" % (path, result)) + self.logger.debug("%s" % (path)) + return result def readdir(self, path, offset): entries = self.bareos.readdir(Path(path), offset) - self.logger.debug("fuse %s: %s" % (path, entries)) + self.logger.debug("%s: %s" % (path, entries)) if not entries: return -errno.ENOENT else: return [fuse.Direntry(f) for f in entries] + def readlink(self, path): + result = self.bareos.readlink(Path(path)) + self.logger.debug("%s: %s" % (path, result)) + return result - def read(self, path, size, offset): - result = self.bareos.read(Path(path), size, offset) - self.logger.debug("fuse %s: %s" % (path, result)) + def listxattr(self, path, size): + ''' + list extended attributes + ''' + keys = self.bareos.listxattr(Path(path)) + if size == 0: + # We are asked for size of the attr list, ie. joint size of attrs + # plus null separators. + result = len("".join(keys)) + len(keys) + self.logger.debug("%s: len=%s" % (path, result)) + else: + result = keys + self.logger.debug("%s: keys=%s" % (path, ", ".join(keys))) + return result + + def getxattr(self, path, key, size): + ''' + get value of extended attribute + burpfs exposes some filesystem attributes for the root directory + (e.g. backup number, cache prefix - see FileSystem.xattr_fields_root) + and may also expose several other attributes for each file/directory + in the future (see FileSystem.xattr_fields) + ''' + value = self.bareos.getxattr(Path(path), key) + self.logger.debug("%s: %s=%s" % (path, key, str(value))) + if value == None: + result = -errno.ENODATA + elif size == 0: + # we are asked for size of the value + result = len(value) + self.logger.debug("%s: len=%s" % (path, result)) + else: + result = value + self.logger.debug("%s: %s=%s" % (path, key, result)) + return result + + def setxattr(self, path, key, value, flags): + ''' + set value of extended attribute + ''' + result = self.bareos.setxattr(Path(path), key, value, flags) + self.logger.debug("%s: %s=%s: %s" % (path, key, value, str(result))) + return result + + def rename(self, oldpath, newpath): + result = self.bareos.rename(Path(oldpath), Path(newpath)) + self.logger.debug("%s => %s: %s" % (oldpath, newpath, result)) return result diff --git a/bareos/fuse/node/base.py b/bareos/fuse/node/base.py index 7b61fe0..f859b0b 100644 --- a/bareos/fuse/node/base.py +++ b/bareos/fuse/node/base.py @@ -18,8 +18,10 @@ class Base(object): self.logger = logging.getLogger() self.root = root self.bsock = root.bsock + self.id = None self.set_name(name) self.stat = fuse.Stat() + self.xattr = {} self.content = None self.subnodes = {} self.subnodes_old = self.subnodes.copy() @@ -40,6 +42,9 @@ class Base(object): """ return None + # Interface + # ========= + def get_name(self): return self.name @@ -49,18 +54,6 @@ class Base(object): def set_static(self, value=True): self.static = value - def getattr(self, path): - self.logger.debug("%s(\"%s\")" % (str(self), str(path))) - result = -errno.ENOENT - if path.len() == 0: - self.update() - result = self.get_stat() - else: - if path.get(0) in self.subnodes: - topdir = path.shift() - result = self.subnodes[topdir].getattr(path) - return result - def get_stat(self): return self.stat @@ -101,20 +94,6 @@ class Base(object): #"mtime": 1441134679 #}, - def read(self, path, size, offset): - self.logger.debug("%s(\"%s\", %d, %d)" % (str(self), str(path), size, offset)) - result = -errno.ENOENT - if path.len() == 0: - self.logger.debug( "content: " + str(self.content) ) - self.update() - if self.content != None: - result = self.content[offset:offset+size] - else: - if path.get(0) in self.subnodes: - topdir = path.shift() - result = self.subnodes[topdir].read(path, size, offset) - return result - def add_subnode(self, classtype, *args, **kwargs): instance = self.root.factory.get_instance(classtype, *args, **kwargs) name = instance.get_name() @@ -153,7 +132,114 @@ class Base(object): # remove marker for nodes to be deleted after update self.subnodes_old = {} + # Filesystem methods + # ================== + + def getattr(self, path): + #self.logger.debug("%s(\"%s\")" % (str(self), str(path))) + result = -errno.ENOENT + if path.len() == 0: + self.update() + result = self.get_stat() + else: + if path.get(0) in self.subnodes: + topdir = path.shift() + result = self.subnodes[topdir].getattr(path) + return result + + + def read(self, path, size, offset): + #self.logger.debug("%s(\"%s\", %d, %d)" % (str(self), str(path), size, offset)) + result = -errno.ENOENT + if path.len() == 0: + self.logger.debug( "content: " + str(self.content) ) + self.update() + if self.content != None: + result = self.content[offset:offset+size] + else: + if path.get(0) in self.subnodes: + topdir = path.shift() + result = self.subnodes[topdir].read(path, size, offset) + return result + + def readlink(self, path): + #self.logger.debug("%s(\"%s\", %d, %d)" % (str(self), str(path), size, offset)) + result = -errno.ENOENT + if path.len() == 0: + pass + else: + if path.get(0) in self.subnodes: + topdir = path.shift() + result = self.subnodes[topdir].readlink(path) + return result + + def listxattr(self, path): + ''' + list extended attributes + ''' + #self.logger.debug("%s(\"%s\", %s)" % (str(self.get_name()), str(path), str(size))) + result = [] + if path.len() == 0: + result = self.xattr.keys() + else: + if path.get(0) in self.subnodes: + topdir = path.shift() + result = self.subnodes[topdir].listxattr(path) + return result + + def getxattr(self, path, key): + ''' + get value of extended attribute + ''' + #self.logger.debug("%s(\"%s\")" % (str(self), str(path))) + result = None + if path.len() == 0: + try: + result = self.xattr[key] + except KeyError: + pass + else: + if path.get(0) in self.subnodes: + topdir = path.shift() + result = self.subnodes[topdir].getxattr(path, key) + return result + + def setxattr(self, path, key, value, flags): + ''' + set value of extended attribute + ''' + #self.logger.debug("%s(\"%s\")" % (str(self), str(path))) + #result = -errno.EOPNOTSUPP + result = 0 + if path.len() == 0: + self.xattr[key] = value + else: + if path.get(0) in self.subnodes: + topdir = path.shift() + result = self.subnodes[topdir].setxattr(path, key, value, flags) + return result + + def rename(self, oldpath, newpath): + self.logger.debug("%s(\"%s %s\")" % (str(self), str(oldpath), str(newpath))) + result = -errno.ENOENT + if path.len() == 0: + #self.update() + #result = self.get_stat() + pass + else: + if path.get(0) in self.subnodes: + topdir = path.shift() + result = self.subnodes[topdir].rename(oldpath, newpath) + return result + + # Helpers + # ======= + def _convert_date_bareos_unix(self, bareosdate): - unixtimestamp = int(DateParser.parse(bareosdate).strftime("%s")) - self.logger.debug( "unix timestamp: %d" % (unixtimestamp)) + unixtimestamp = 0 + try: + unixtimestamp = int(DateParser.parse(bareosdate).strftime("%s")) + self.logger.debug( "unix timestamp: %d" % (unixtimestamp)) + except ValueError: + pass return unixtimestamp diff --git a/bareos/fuse/node/bvfscommon.py b/bareos/fuse/node/bvfscommon.py new file mode 100644 index 0000000..ad84e86 --- /dev/null +++ b/bareos/fuse/node/bvfscommon.py @@ -0,0 +1,67 @@ +""" +Bareos specific Fuse node. +""" + +from bareos.fuse.node.base import Base +import logging +import os +import stat + +class BvfsCommon(Base): + XattrKeyRestoreTrigger = "user.bareos.do" + XattrKeyRestoreJobId = "user.bareos.restore_job_id" + + def init(self, root, jobid, path, filename, stat): + self.jobid = jobid + self.static = True + self.restorepath = os.path.normpath("/%s/jobid=%s/" % (self.root.restorepath, self.jobid)) + if filename: + self.restorepathfull = os.path.normpath("%s%s%s" % (self.restorepath, path, filename)) + else: + self.restorepathfull = os.path.normpath("%s%s" % (self.restorepath, path)) + self.xattr = { + 'user.bareos.restorepath': str(self.restorepathfull), + 'user.bareos.restored': 'no', + 'user.bareos.do_options': "mark | restore", + 'user.bareos.do': '', + } + if stat: + self.set_stat(stat) + + def listxattr(self, path): + # update xattrs + if self.xattr['user.bareos.restored'] != 'yes': + if os.access(self.restorepathfull, os.F_OK): + self.xattr['user.bareos.restored'] = 'yes' + return super(BvfsCommon, self).listxattr(path) + + # Helpers + # ======= + + def restore(self, path, pathIds, fileIds): + self.logger.debug( "%s: start", self.get_name() ) + bvfs_restore_id = "b20042" + dirId='' + if pathIds: + dirId='dirid=' + ",".join(map(str, pathIds)) + fileId='' + if fileIds: + fileId='fileid=' + ",".join(map(str, fileIds)) + select = self.bsock.call( + '.bvfs_restore jobid={jobid} {dirid} {fileid} path={bvfs_restore_id}'.format( + jobid = self.jobid, + dirid = dirId, + fileid = fileId, + bvfs_restore_id = bvfs_restore_id)) + restore = self.bsock.call( + 'restore file=?{bvfs_restore_id} restoreclient={restoreclient} where="{where}" yes'.format( + restoreclient = self.root.restoreclient, + where = self.restorepath, + bvfs_restore_id = bvfs_restore_id)) + try: + restorejobid=restore['run']['jobid'] + self.setxattr(path, self.XattrKeyRestoreJobId, str(restorejobid), 0) + except KeyError: + self.logger.debug("failed to get resulting jobid of run command (maybe old version of Bareos Director)") + cleanup = self.bsock.call('.bvfs_cleanup path={bvfs_restore_id}'.format(bvfs_restore_id = bvfs_restore_id)) + self.logger.debug( "%s: end", self.get_name() ) diff --git a/bareos/fuse/node/bvfsdir.py b/bareos/fuse/node/bvfsdir.py index b4537bd..40b7cc6 100644 --- a/bareos/fuse/node/bvfsdir.py +++ b/bareos/fuse/node/bvfsdir.py @@ -3,20 +3,23 @@ Bareos specific Fuse node. """ from bareos.exceptions import * +from bareos.fuse.node.bvfscommon import BvfsCommon from bareos.fuse.node.bvfsfile import BvfsFile from bareos.fuse.node.directory import Directory from dateutil import parser as DateParser import errno import logging -class BvfsDir(Directory): +class BvfsDir(Directory, BvfsCommon): def __init__(self, root, name, jobid, pathid, directory = None): super(BvfsDir, self).__init__(root, name) self.jobid = jobid self.pathid = pathid self.static = True + self.directory = directory if directory: self.set_stat(directory['stat']) + BvfsCommon.init(self, self.root, self.jobid, self.directory['fullpath'], None, self.directory['stat']) @classmethod def get_id(cls, name, jobid, pathid, directory = None): @@ -27,6 +30,19 @@ class BvfsDir(Directory): id = "pathid=%s" % (str(pathid)) return id + def setxattr(self, path, key, value, flags): + if path.len() == 0: + if key == self.XattrKeyRestoreTrigger and value == "restore": + if not self.root.restoreclient: + self.logger.warning("no restoreclient given, files can not be restored") + return -errno.ENOENT + if not self.root.restorepath: + self.logger.warning("no restorepath given, files can not be restored") + return -errno.ENOENT + else: + self.restore(path, [ self.pathid ], None) + return super(BvfsDir, self).setxattr(path, key, value, flags) + def do_update(self): directories = self.get_directories(self.pathid) files = self.get_files(self.pathid) @@ -36,7 +52,7 @@ class BvfsDir(Directory): pathid = i['pathid'] self.add_subnode(BvfsDir, name, self.jobid, pathid, i) for i in files: - self.add_subnode(BvfsFile, i) + self.add_subnode(BvfsFile, i, self.directory['fullpath']) def get_directories(self, pathid): if pathid == None: @@ -52,6 +68,7 @@ class BvfsDir(Directory): if i['name'] != "." and i['name'] != "..": if i['name'] == "/": self.pathid = i['pathid'] + BvfsCommon.init(self, self.root, self.jobid, i['fullpath'], None, i['stat']) else: found = True if not found: diff --git a/bareos/fuse/node/bvfsfile.py b/bareos/fuse/node/bvfsfile.py index 13e1191..06e7fc4 100644 --- a/bareos/fuse/node/bvfsfile.py +++ b/bareos/fuse/node/bvfsfile.py @@ -2,16 +2,84 @@ Bareos specific Fuse node. """ +from bareos.fuse.node.bvfscommon import BvfsCommon from bareos.fuse.node.file import File -import stat +import errno +import os -class BvfsFile(File): - def __init__(self, root, file): +class BvfsFile(File, BvfsCommon): + def __init__(self, root, file, bvfspath): super(BvfsFile, self).__init__(root, file['name'], content = None) self.file = file - self.static = True - self.set_stat(self.file['stat']) + self.bvfspath = bvfspath + self.id = self.get_id(file, bvfspath) + BvfsCommon.init(self, self.root, self.file['jobid'], self.bvfspath, self.file['name'], self.file['stat']) @classmethod - def get_id(cls, file): + def get_id(cls, file, bvfspath): return str(file['fileid']) + + # Filesystem methods + # ================== + + def readlink(self, path): + self.logger.debug( "readlink %s" % (path) ) + try: + pathname = os.readlink(self.restorepathfull) + if pathname.startswith("/"): + # Path name is absolute, sanitize it. + return str(os.path.relpath(pathname, self.restorepath)) + else: + return str(pathname) + except OSError: + return -errno.ENOENT + + def setxattr(self, path, key, value, flags): + if path.len() == 0: + if key == self.XattrKeyRestoreTrigger and value == "restore": + if not self.root.restoreclient: + self.logger.error("no restoreclient given, files can not be restored") + return -errno.EPERM + if not self.root.restorepath: + self.logger.error("no restorepath given, files can not be restored") + return -errno.EPERM + else: + self.restore(path, None, [ self.file['fileid'] ]) + return super(BvfsFile, self).setxattr(path, key, value, flags) + + + # File methods + # ============ + + def open(self, path, flags): + return os.open(self.restorepathfull, flags) + + #def create(self, path, mode, fi=None): + #full_path = self._full_path(path) + #return os.open(full_path, os.O_WRONLY | os.O_CREAT, mode) + + def read(self, path, length, offset): + try: + fh=self.open(path, os.O_RDONLY) + os.lseek(fh, offset, os.SEEK_SET) + return os.read(fh, length) + except OSError: + return -errno.ENOENT + + #def write(self, path, buf, offset, fh): + #os.lseek(fh, offset, os.SEEK_SET) + #return os.write(fh, buf) + + #def truncate(self, path, length, fh=None): + #full_path = self._full_path(path) + #with open(full_path, 'r+') as f: + #f.truncate(length) + + #def flush(self, path, fh): + #return os.fsync(fh) + + #def release(self, path, fh): + #return os.close(fh) + + #def fsync(self, path, fdatasync, fh): + #return self.flush(path, fh) diff --git a/bareos/fuse/node/directory.py b/bareos/fuse/node/directory.py index 8438a3b..3559d0c 100644 --- a/bareos/fuse/node/directory.py +++ b/bareos/fuse/node/directory.py @@ -20,7 +20,7 @@ class Directory(Base): self.stat.st_size = 4096 def readdir(self, path, offset): - self.logger.debug("%s(\"%s\")" % (str(self), str(path))) + self.logger.debug("%s(\"%s\")" % (str(self.get_name()), str(path))) # copy default dirs if path.len() == 0: self.update() diff --git a/bareos/fuse/root.py b/bareos/fuse/root.py index a6f8735..6cd7ec3 100644 --- a/bareos/fuse/root.py +++ b/bareos/fuse/root.py @@ -10,8 +10,10 @@ class Root(Directory): """ Define filesystem structure of root (/) directory. """ - def __init__(self, bsock): + def __init__(self, bsock, restoreclient, restorepath): self.bsock = bsock + self.restoreclient = restoreclient + self.restorepath = restorepath super(Root, self).__init__(self, None) self.factory = NodeFactory(self) self.add_subnode(Jobs, "jobs") |