[storage] BFSS now create read only file (closes #2151672)
In the process binary.to_file now takes a file instead of a file-path. As
storage was currently the only user it should not impact anyone.
--- a/__init__.py Mon Jan 23 12:39:21 2012 +0100
+++ b/__init__.py Wed Jan 18 15:27:08 2012 +0100
@@ -77,25 +77,24 @@
"Binary objects must use raw strings, not %s" % data.__class__
StringIO.write(self, data)
- def to_file(self, filename):
+ def to_file(self, fobj):
"""write a binary to disk
the writing is performed in a safe way for files stored on
Windows SMB shares
"""
pos = self.tell()
- with open(filename, 'wb') as fobj:
- self.seek(0)
- if sys.platform == 'win32':
- while True:
- # the 16kB chunksize comes from the shutil module
- # in stdlib
- chunk = self.read(16*1024)
- if not chunk:
- break
- fobj.write(chunk)
- else:
- fobj.write(self.read())
+ self.seek(0)
+ if sys.platform == 'win32':
+ while True:
+ # the 16kB chunksize comes from the shutil module
+ # in stdlib
+ chunk = self.read(16*1024)
+ if not chunk:
+ break
+ fobj.write(chunk)
+ else:
+ fobj.write(self.read())
self.seek(pos)
@staticmethod
--- a/server/sources/storages.py Mon Jan 23 12:39:21 2012 +0100
+++ b/server/sources/storages.py Wed Jan 18 15:27:08 2012 +0100
@@ -18,6 +18,7 @@
"""custom storages for the system source"""
import os
+import sys
from os import unlink, path as osp
from contextlib import contextmanager
@@ -112,11 +113,29 @@
class BytesFileSystemStorage(Storage):
"""store Bytes attribute value on the file system"""
- def __init__(self, defaultdir, fsencoding='utf-8'):
+ def __init__(self, defaultdir, fsencoding='utf-8', wmode=0444):
if type(defaultdir) is unicode:
defaultdir = defaultdir.encode(fsencoding)
self.default_directory = defaultdir
self.fsencoding = fsencoding
+ # extra umask to use when creating file
+ # 0444 as in "only allow read bit in permission"
+ self._wmode = wmode
+
+ def _writecontent(self, path, binary):
+ """write the content of a binary in readonly file
+
+ As the bfss never alter a create file it does not prevent it to work as
+ intended. This is a beter safe than sorry approach.
+ """
+ write_flag = os.O_WRONLY | os.O_CREAT | os.O_EXCL
+ if sys.platform == 'win32':
+ write_flag |= os.O_BINARY
+ fd = os.open(path, write_flag, self._wmode)
+ fileobj = os.fdopen(fd, 'wb')
+ binary.to_file(fileobj)
+ fileobj.close()
+
def callback(self, source, session, value):
"""sql generator callback when some attribute with a custom storage is
@@ -138,7 +157,7 @@
fpath = self.new_fs_path(entity, attr)
# bytes storage used to store file's path
entity.cw_edited.edited_attribute(attr, Binary(fpath))
- binary.to_file(fpath)
+ self._writecontent(fpath, binary)
AddFileOp.get_instance(entity._cw).add_data(fpath)
return binary
@@ -171,7 +190,7 @@
fpath = self.new_fs_path(entity, attr)
assert not osp.exists(fpath)
# write attribute value on disk
- binary.to_file(fpath)
+ self._writecontent(fpath, binary)
# Mark the new file as added during the transaction.
# The file will be removed on rollback
AddFileOp.get_instance(entity._cw).add_data(fpath)
--- a/server/test/unittest_storage.py Mon Jan 23 12:39:21 2012 +0100
+++ b/server/test/unittest_storage.py Wed Jan 18 15:27:08 2012 +0100
@@ -22,6 +22,7 @@
from logilab.common.testlib import unittest_main, tag, Tags
from cubicweb.devtools.testlib import CubicWebTC
+import os
import os.path as osp
import shutil
import tempfile
@@ -90,6 +91,8 @@
expected_filepath = osp.join(self.tempdir, '%s_data_%s' %
(f1.eid, f1.data_name))
self.assertTrue(osp.isfile(expected_filepath))
+ # file should be read only
+ self.assertFalse(os.access(expected_filepath, os.W_OK))
self.assertEqual(file(expected_filepath).read(), 'the-data')
self.rollback()
self.assertFalse(osp.isfile(expected_filepath))