From 8f20e0fe5dd352536e755834ecfea05cd4550393 Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Mon, 6 Dec 2021 09:29:36 +0100 Subject: [PATCH] Moved the lock related code to commoniface and adapted to the ref implementation See https://github.com/cs3org/cs3apis/blob/main/cs3/storage/provider/v1beta1/resources.proto And https://github.com/cs3org/cs3apis/pull/160 --- src/core/commoniface.py | 19 ++++++++++++++++--- src/core/localiface.py | 9 ++++----- src/core/wopiutils.py | 6 ++++-- src/core/xrootiface.py | 10 +++------- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/core/commoniface.py b/src/core/commoniface.py index d203b330..b13b773d 100644 --- a/src/core/commoniface.py +++ b/src/core/commoniface.py @@ -1,13 +1,16 @@ ''' commoniface.py -common entities used by all storage interfaces for the IOP WOPI server +Common entities used by all storage interfaces for the IOP WOPI server. +Includes functions to store and retrieve Reva-compatible locks. Author: Giuseppe.LoPresti@cern.ch, CERN/IT-ST ''' import time import json +from base64 import urlsafe_b64encode, urlsafe_b64decode + # standard file missing message ENOENT_MSG = 'No such file or directory' @@ -22,5 +25,15 @@ LOCKKEY = 'user.iop.lock' def genrevalock(appname, value): - '''Return a JSON-formatted lock compatible with the Reva implementation of the CS3 Lock API''' - return json.dumps({'h': appname if appname else 'wopi', 't': int(time.time()), 'md': value}) + '''Return a base64-encoded lock compatible with the Reva implementation of the CS3 Lock API + cf. https://github.com/cs3org/cs3apis/blob/main/cs3/storage/provider/v1beta1/resources.proto''' + return urlsafe_b64encode(json.dumps( + {'type': 'LOCK_TYPE_SHARED', + 'h': appname if appname else 'wopi', + 'md': value, + 'mtime': int(time.time()), + }).encode()).decode() + +def retrieverevalock(rawlock): + '''Restores the JSON payload from a base64-encoded Reva lock''' + return json.loads(urlsafe_b64decode(rawlock).decode()) diff --git a/src/core/localiface.py b/src/core/localiface.py index 1399ba90..7d920c14 100644 --- a/src/core/localiface.py +++ b/src/core/localiface.py @@ -10,7 +10,6 @@ import os import warnings from stat import S_ISDIR -import json import core.commoniface as common # module-wide state @@ -79,7 +78,7 @@ def setxattr(_endpoint, filepath, _userid, key, value): '''Set the extended attribute to on behalf of the given userid''' try: os.setxattr(_getfilepath(filepath), 'user.' + key, str(value).encode()) - except (FileNotFoundError, PermissionError, OSError) as e: + except OSError as e: log.error('msg="Failed to setxattr" filepath="%s" key="%s" exception="%s"' % (filepath, key, e)) raise IOError(e) @@ -89,7 +88,7 @@ def getxattr(_endpoint, filepath, _userid, key): try: filepath = _getfilepath(filepath) return os.getxattr(filepath, 'user.' + key).decode('UTF-8') - except (FileNotFoundError, PermissionError, OSError) as e: + except OSError as e: log.error('msg="Failed to getxattr" filepath="%s" key="%s" exception="%s"' % (filepath, key, e)) return None @@ -98,7 +97,7 @@ def rmxattr(_endpoint, filepath, _userid, key): '''Remove the extended attribute on behalf of the given userid''' try: os.removexattr(_getfilepath(filepath), 'user.' + key) - except (FileNotFoundError, PermissionError, OSError) as e: + except OSError as e: log.error('msg="Failed to rmxattr" filepath="%s" key="%s" exception="%s"' % (filepath, key, e)) raise IOError(e) @@ -116,7 +115,7 @@ def getlock(endpoint, filepath, _userid): '''Get the lock metadata as an xattr on behalf of the given userid''' l = getxattr(endpoint, filepath, '0:0', common.LOCKKEY) if l: - return json.loads(l) + return common.retrieverevalock(l) return None def refreshlock(endpoint, filepath, _userid, appname, value): diff --git a/src/core/wopiutils.py b/src/core/wopiutils.py index f45e1033..7f325ace 100644 --- a/src/core/wopiutils.py +++ b/src/core/wopiutils.py @@ -159,7 +159,8 @@ def generateAccessToken(userid, fileid, viewmode, user, folderurl, endpoint, app def retrieveWopiLock(fileid, operation, lock, acctok, overridefilename=None): - '''Retrieves and logs a lock for a given file: returns the lock and its holder, or (None, None) if missing''' + '''Retrieves and logs a lock for a given file: returns the lock and its holder, or (None, None) if missing + TODO use the native lock metadata (breaking change) such as the `mtime` and return the whole lock''' encacctok = flask.request.args['access_token'][-20:] if 'access_token' in flask.request.args else 'N/A' lockcontent = st.getlock(acctok['endpoint'], overridefilename if overridefilename else acctok['filename'], acctok['userid']) if not lockcontent: @@ -203,7 +204,8 @@ def retrieveWopiLock(fileid, operation, lock, acctok, overridefilename=None): def _makeLock(lock): - '''Generates the lock payload given the raw data''' + '''Generates the lock payload given the raw data + TODO drop the expiration time in favour of the lock native metadata''' lockcontent = {} lockcontent['wopilock'] = lock # append or overwrite the expiration time diff --git a/src/core/xrootiface.py b/src/core/xrootiface.py index 60ee6f8e..0a834124 100644 --- a/src/core/xrootiface.py +++ b/src/core/xrootiface.py @@ -12,8 +12,6 @@ from stat import S_ISDIR from base64 import b64encode from pwd import getpwnam -from base64 import urlsafe_b64encode, urlsafe_b64decode -import json from XRootD import client as XrdClient from XRootD.client.flags import OpenFlags, QueryCode, MkDirFlags, StatInfoFlags @@ -275,8 +273,7 @@ def setlock(endpoint, filepath, userid, appname, value): The special option "c" (create-if-not-exists) is used to be atomic''' try: log.debug('msg="Invoked setlock" filepath="%s"' % filepath) - setxattr(endpoint, filepath, userid, common.LOCKKEY, \ - urlsafe_b64encode(common.genrevalock(appname, value).encode()).decode() + '&mgm.option=c') + setxattr(endpoint, filepath, userid, common.LOCKKEY, common.genrevalock(appname, value) + '&mgm.option=c') except IOError as e: if EXCL_XATTR_MSG in str(e): raise IOError(common.EXCL_ERROR) @@ -286,7 +283,7 @@ def getlock(endpoint, filepath, userid): '''Get the lock metadata as an xattr''' l = getxattr(endpoint, filepath, userid, common.LOCKKEY) if l: - return json.loads(urlsafe_b64decode(l).decode()) + return common.retrieverevalock(l) return None # no pre-existing lock found, or error attempting to read it: assume it does not exist @@ -299,8 +296,7 @@ def refreshlock(endpoint, filepath, userid, appname, value): if l['h'] != appname and l['h'] != 'wopi': raise IOError('File is locked by %s' % l['h']) # this is non-atomic, but the lock was already held - setxattr(endpoint, filepath, userid, common.LOCKKEY, \ - urlsafe_b64encode(common.genrevalock(appname, value).encode()).decode()) + setxattr(endpoint, filepath, userid, common.LOCKKEY, common.genrevalock(appname, value)) def unlock(endpoint, filepath, userid, _appname):