Skip to content

Commit

Permalink
Merge branch 'cernbox'
Browse files Browse the repository at this point in the history
  • Loading branch information
glpatcern committed Feb 18, 2022
2 parents f5010ef + 7dd1652 commit 91dec37
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 216 deletions.
26 changes: 26 additions & 0 deletions src/core/commoniface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'''
commoniface.py
common entities used by all storage interfaces for the IOP WOPI server
Author: Giuseppe.LoPresti@cern.ch, CERN/IT-ST
'''

import time
import json

# standard file missing message
ENOENT_MSG = 'No such file or directory'

# standard error thrown when attempting to overwrite a file/xattr in O_EXCL mode
EXCL_ERROR = 'File exists and islock flag requested'

# standard error thrown when attempting an operation without the required access rights
ACCESS_ERROR = 'Operation not permitted'

# name of the xattr storing the Reva lock
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})
30 changes: 25 additions & 5 deletions src/core/cs3iface.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import cs3.rpc.v1beta1.code_pb2 as cs3code
import cs3.types.v1beta1.types_pb2 as types

ENOENT_MSG = 'No such file or directory'
import core.commoniface as common

# module-wide state
ctx = {} # "map" to store some module context: cf. init()
Expand Down Expand Up @@ -94,7 +94,7 @@ def stat(endpoint, fileid, userid, versioninv=1):
'mtime': statInfo.info.mtime.seconds
}
ctx['log'].info('msg="Failed stat" inode="%s" reason="%s"' % (fileid, statInfo.status.message.replace('"', "'")))
raise IOError(ENOENT_MSG if statInfo.status.code == cs3code.CODE_NOT_FOUND else statInfo.status.message)
raise IOError(common.ENOENT_MSG if statInfo.status.code == cs3code.CODE_NOT_FOUND else statInfo.status.message)


def statx(endpoint, fileid, userid, versioninv=0):
Expand Down Expand Up @@ -148,6 +148,26 @@ def rmxattr(_endpoint, filepath, userid, key):
ctx['log'].debug('msg="Invoked rmxattr" result="%s"' % res)


def setlock(endpoint, filepath, userid, appname, value):
'''Set a lock to filepath with the given value metadata and appname as holder'''
raise NotImplementedError


def getlock(endpoint, filepath, userid, appname):
'''Get the lock metadata for the given filepath'''
raise NotImplementedError


def refreshlock(endpoint, filepath, userid, appname, value):
'''Refresh the lock metadata for the given filepath'''
raise NotImplementedError


def unlock(endpoint, filepath, userid, appname):
'''Remove the lock for the given filepath'''
raise NotImplementedError


def readfile(_endpoint, filepath, userid):
'''Read a file using the given userid as access token. Note that the function is a generator, managed by Flask.'''
tstart = time.time()
Expand All @@ -156,7 +176,7 @@ def readfile(_endpoint, filepath, userid):
initfiledownloadres = ctx['cs3stub'].InitiateFileDownload(request=req, metadata=[('x-access-token', userid)])
if initfiledownloadres.status.code == cs3code.CODE_NOT_FOUND:
ctx['log'].info('msg="File not found on read" filepath="%s"' % filepath)
yield IOError(ENOENT_MSG)
yield IOError(common.ENOENT_MSG)
elif initfiledownloadres.status.code != cs3code.CODE_OK:
ctx['log'].error('msg="Failed to initiateFileDownload on read" filepath="%s" reason="%s"' %
(filepath, initfiledownloadres.status.message.replace('"', "'")))
Expand Down Expand Up @@ -239,14 +259,14 @@ def renamefile(_endpoint, filepath, newfilepath, userid):
ctx['log'].debug('msg="Invoked renamefile" result="%s"' % res)


def removefile(_endpoint, filepath, userid, _force=0):
def removefile(_endpoint, filepath, userid, force=False):
'''Remove a file using the given userid as access token.
The force argument is ignored for now for CS3 storage.'''
reference = cs3spr.Reference(path=filepath)
req = cs3sp.DeleteRequest(ref=reference)
res = ctx['cs3stub'].Delete(request=req, metadata=[('x-access-token', userid)])
if res.status.code != cs3code.CODE_OK:
if str(res) == ENOENT_MSG:
if str(res) == common.ENOENT_MSG:
ctx['log'].info('msg="Invoked removefile on non-existing file" filepath="%s"' % filepath)
else:
ctx['log'].error('msg="Failed to remove file" filepath="%s" reason="%s"' % (filepath, res.status.message.replace('"', "'")))
Expand Down
3 changes: 2 additions & 1 deletion src/core/ioplocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import time
import http
import core.wopiutils as utils
import core.commoniface as common

# convenience references to global entities
st = None
Expand Down Expand Up @@ -133,7 +134,7 @@ def createLock(filestat, filename, userid, endpoint):
(filename, filestat['inode'], lockid))
return str(lockid), http.client.OK
except IOError as e:
if 'File exists and islock flag requested' not in str(e):
if common.EXCL_ERROR not in str(e):
# writing failed
log.error('msg="cboxLock: unable to store LibreOffice-compatible lock file" filename="%s" reason="%s"' %
(filename, e))
Expand Down
38 changes: 36 additions & 2 deletions src/core/localiface.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import os
import warnings
from stat import S_ISDIR
import json
import core.commoniface as common

# module-wide state
config = None
Expand Down Expand Up @@ -101,6 +103,38 @@ def rmxattr(_endpoint, filepath, _userid, key):
raise IOError(e)


def setlock(endpoint, filepath, _userid, appname, value):
'''Set the lock as an xattr on behalf of the given userid'''
if not getxattr(endpoint, filepath, '0:0', common.LOCKKEY):
# we do not protect from race conditions here
setxattr(endpoint, filepath, '0:0', common.LOCKKEY, common.genrevalock(appname, value))
else:
raise IOError(common.EXCL_ERROR)


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 None

def refreshlock(endpoint, filepath, _userid, appname, value):
'''Refresh the lock value as an xattr on behalf of the given userid'''
l = getlock(endpoint, filepath, _userid)
if not l:
raise IOError('File was not locked')
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, '0:0', common.LOCKKEY, common.genrevalock(appname, value))


def unlock(endpoint, filepath, _userid, _appname):
'''Remove the lock as an xattr on behalf of the given userid'''
rmxattr(endpoint, filepath, '0:0', common.LOCKKEY)


def readfile(_endpoint, filepath, _userid):
'''Read a file on behalf of the given userid. Note that the function is a generator, managed by Flask.'''
log.debug('msg="Invoking readFile" filepath="%s"' % filepath)
Expand Down Expand Up @@ -148,7 +182,7 @@ def writefile(_endpoint, filepath, _userid, content, islock=False):
# as f goes out of scope here, we'd get a false ResourceWarning, which is ignored by the above filter
except FileExistsError:
log.info('msg="File exists on write but islock flag requested" filepath="%s"' % filepath)
raise IOError('File exists and islock flag requested')
raise IOError(common.EXCL_ERROR)
except OSError as e:
log.warning('msg="Error writing file in O_EXCL mode" filepath="%s" error="%s"' % (filepath, e))
raise IOError(e)
Expand All @@ -174,7 +208,7 @@ def renamefile(_endpoint, origfilepath, newfilepath, _userid):
raise IOError(e)


def removefile(_endpoint, filepath, _userid, _force=0):
def removefile(_endpoint, filepath, _userid, force=False):
'''Remove a file on behalf of the given userid.
The force argument is irrelevant and ignored for local storage.'''
try:
Expand Down
Loading

0 comments on commit 91dec37

Please sign in to comment.