]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blobdiff - src/allmydata/storage/immutable.py
immutable: prevent clients from reading past the end of share data, which would allow...
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / storage / immutable.py
index a6da31f07d944eab1a67fb714dc62375d194838d..a50ff422d607c13845584cc3ba6556630e5d86dc 100644 (file)
@@ -1,13 +1,15 @@
 import os, stat, struct, time
 
-from foolscap import Referenceable
+from foolscap.api import Referenceable
 
 from zope.interface import implements
 from allmydata.interfaces import RIBucketWriter, RIBucketReader
 from allmydata.util import base32, fileutil, log
 from allmydata.util.assertutil import precondition
+from allmydata.util.hashutil import constant_time_compare
 from allmydata.storage.lease import LeaseInfo
-from allmydata.storage.common import DataTooLargeError
+from allmydata.storage.common import UnknownImmutableContainerVersionError, \
+     DataTooLargeError
 
 # each share file (in storage/shares/$SI/$SHNUM) contains lease information
 # and share data. The share data is accessed by RIBucketWriter.write and
@@ -26,14 +28,17 @@ from allmydata.storage.common import DataTooLargeError
 #   B+0x44: expiration time, 4 bytes big-endian seconds-since-epoch
 #   B+0x48: next lease, or end of record
 
-# Footnote 1: as of Tahoe v1.3.0 this field is not used by storage servers, but it is still
-# filled in by storage servers in case the storage server software gets downgraded from >= Tahoe
-# v1.3.0 to < Tahoe v1.3.0, or the share file is moved from one storage server to another.  The
-# value stored in this field is truncated, so If the actual share data length is >= 2**32, then
-# the value stored in this field will be the actual share data length modulo 2**32.
+# Footnote 1: as of Tahoe v1.3.0 this field is not used by storage servers,
+# but it is still filled in by storage servers in case the storage server
+# software gets downgraded from >= Tahoe v1.3.0 to < Tahoe v1.3.0, or the
+# share file is moved from one storage server to another. The value stored in
+# this field is truncated, so if the actual share data length is >= 2**32,
+# then the value stored in this field will be the actual share data length
+# modulo 2**32.
 
 class ShareFile:
     LEASE_SIZE = struct.calcsize(">L32s32sL")
+    sharetype = "immutable"
 
     def __init__(self, filename, max_size=None, create=False):
         """ If max_size is not None then I won't allow more than max_size to be written to me. If create=True and max_size must not be None. """
@@ -41,8 +46,8 @@ class ShareFile:
         self.home = filename
         self._max_size = max_size
         if create:
-            # touch the file, so later callers will see that we're working on it.
-            # Also construct the metadata.
+            # touch the file, so later callers will see that we're working on
+            # it. Also construct the metadata.
             assert not os.path.exists(self.home)
             fileutil.make_dirs(os.path.dirname(self.home))
             f = open(self.home, 'wb')
@@ -64,7 +69,10 @@ class ShareFile:
             filesize = os.path.getsize(self.home)
             (version, unused, num_leases) = struct.unpack(">LLL", f.read(0xc))
             f.close()
-            assert version == 1, version
+            if version != 1:
+                msg = "sharefile %s had version %d but we wanted 1" % \
+                      (filename, version)
+                raise UnknownImmutableContainerVersionError(msg)
             self._num_leases = num_leases
             self._lease_offset = filesize - (num_leases * self.LEASE_SIZE)
         self._data_offset = 0xc
@@ -74,12 +82,10 @@ class ShareFile:
 
     def read_share_data(self, offset, length):
         precondition(offset >= 0)
-        # reads beyond the end of the data are truncated. Reads that start beyond the end of the
-        # data return an empty string.
-        # I wonder why Python doesn't do the following computation for me?
+        # reads beyond the end of the data are truncated. Reads that start
+        # beyond the end of the data return an empty string.
         seekpos = self._data_offset+offset
-        fsize = os.path.getsize(self.home)
-        actuallength = max(0, min(length, fsize-seekpos))
+        actuallength = max(0, min(length, self._lease_offset-seekpos))
         if actuallength == 0:
             return ""
         f = open(self.home, 'rb')
@@ -135,7 +141,7 @@ class ShareFile:
 
     def renew_lease(self, renew_secret, new_expire_time):
         for i,lease in enumerate(self.get_leases()):
-            if lease.renew_secret == renew_secret:
+            if constant_time_compare(lease.renew_secret, renew_secret):
                 # yup. See if we need to update the owner time.
                 if new_expire_time > lease.expiration_time:
                     # yes
@@ -165,7 +171,7 @@ class ShareFile:
         leases = list(self.get_leases())
         num_leases_removed = 0
         for i,lease in enumerate(leases):
-            if lease.cancel_secret == cancel_secret:
+            if constant_time_compare(lease.cancel_secret, cancel_secret):
                 leases[i] = None
                 num_leases_removed += 1
         if not num_leases_removed:
@@ -271,13 +277,20 @@ class BucketWriter(Referenceable):
     def _abort(self):
         if self.closed:
             return
+
         os.remove(self.incominghome)
         # if we were the last share to be moved, remove the incoming/
         # directory that was our parent
         parentdir = os.path.split(self.incominghome)[0]
         if not os.listdir(parentdir):
             os.rmdir(parentdir)
+        self._sharefile = None
 
+        # We are now considered closed for further writing. We must tell
+        # the storage server about this so that it stops expecting us to
+        # use the space it allocated for us earlier.
+        self.closed = True
+        self.ss.bucket_writer_closed(self, 0)
 
 
 class BucketReader(Referenceable):
@@ -290,7 +303,9 @@ class BucketReader(Referenceable):
         self.shnum = shnum
 
     def __repr__(self):
-        return "<%s %s %s>" % (self.__class__.__name__, base32.b2a_l(self.storage_index[:8], 60), self.shnum)
+        return "<%s %s %s>" % (self.__class__.__name__,
+                               base32.b2a_l(self.storage_index[:8], 60),
+                               self.shnum)
 
     def remote_read(self, offset, length):
         start = time.time()