Skip to content

Commit

Permalink
Moved the lock related code to commoniface and adapted to the ref imp…
Browse files Browse the repository at this point in the history
  • Loading branch information
glpatcern committed Mar 2, 2022
1 parent a12136a commit 4e5617b
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 17 deletions.
19 changes: 16 additions & 3 deletions src/core/commoniface.py
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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())
9 changes: 4 additions & 5 deletions src/core/localiface.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import os
import warnings
from stat import S_ISDIR
import json
import core.commoniface as common

LOCKKEY = 'user.iop.lock' # this is to be compatible with the (future) Lock API in Reva
Expand Down Expand Up @@ -81,7 +80,7 @@ def setxattr(_endpoint, filepath, _userid, key, value):
'''Set the extended attribute <key> to <value> 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)

Expand All @@ -91,7 +90,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

Expand All @@ -100,7 +99,7 @@ def rmxattr(_endpoint, filepath, _userid, key):
'''Remove the extended attribute <key> 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)

Expand All @@ -118,7 +117,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):
Expand Down
6 changes: 4 additions & 2 deletions src/core/wopiutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
10 changes: 3 additions & 7 deletions src/core/xrootiface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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


Expand All @@ -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):
Expand Down

0 comments on commit 4e5617b

Please sign in to comment.