[storage] BFSS now create read only file (closes #2151672)
authorPierre-Yves David <pierre-yves.david@logilab.fr>
Wed, 18 Jan 2012 15:27:08 +0100
changeset 8180 1f6ba9afb925
parent 8179 e52a084e955c
child 8181 166ad5458ed8
[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.
__init__.py
server/sources/storages.py
server/test/unittest_storage.py
--- 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))