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
# 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. """
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')
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
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')
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
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:
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):
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()