[Box Backup] Workaround for struct packing problem on ARM processors (was: Another wish for 2011)

Leif Linderstam ell2 at live.se
Sat Feb 12 21:18:19 GMT 2011


Hi Chris,

Now I have made a suggested workaround for the struct packing problem. As
far as I know the problem only manifests itself in the source file
BackupStoreDirectory.cpp. The packing directive is ignored by the compiler
and the struct en_StreamFormat is therefore automatically padded to 36
bytes, while on other platforms its size is only 34.

My solution is to add a class to be used instead of the struct for packing
integers into network blocks of the correct size. The two functions that
used the problematic struct has been changed to use this new class. This
works quite well. The server on the ARM box uses this changed code and
communicates without trouble with the unchanged client on a normal x86
Linux box. I have added the new and changed code at the end of this
message.

There is however another problem which I am working on at the moment. The
debug version of bbstored works quite well, but the release version has
problems with the directory structure of the store. It creates the .rfw
files directly in the root dir of the store. Only when it has created
about 255 files it creates a subdir in which it continues to create .rfw
files. In other words it seems to ignore the directory structure and
creates a flat structure. (It is a bit annoying that this only happens in
the release version, it becomes a pain to debug it then.)



In the meantime, back to the proposed changes. The new integer packing/
unpacking class is a template class so that it always is of the correct
size and is possible to place entirely on the stack (no heap allocation
needed). This way it has almost the same allocation properties as the old
plain struct. The class has only fairly optimized inline functions so that
its speed matches that of the old solution. The packing and unpacking is
done with bit-shift operators. This way it is independent of the byte
order of the target CPU. The only requirement is that the target uses
2-complements integers and that the type uint8_t is 8 bits and nothing
else, but a platform that does not meet those requirements nowadays would
be a very strange one indeed. (By the way the old solution also fails if
these requirements are not met.)

First comes the changes in the file BackupStoreDirectory.cpp. The struct
en_StreamFormat is removed and replaced with an integer constant that
calculates the size needed for the new packing class. The calculation
quite intentionally uses explicit size values instead of using sizeof()
because the size of the message must always be the same for all
platforms regardless of what the sizeof() operator might return. Another
variant would be to define explicit size constants for each integer type
and use those in the calculation (that would actually be a nicer
solution, but I did not think of it until just now).

------------------------------------------------------------------------

static const int en_StreamFormat_Size =
    8  // uint64_t mModificationTime
  + 8  // int64_t mObjectID
  + 8  // int64_t mSizeInBlocks
  + 8  // uint64_t mAttributesHash
  + 2; // int16_t mFlags
  // Then a BackupStoreFilename (but that is not included in this size)
  // Then a StreamableMemBlock for attributes (also not included in this size)
------------------------------------------------------------------------



Then the two functions where the old en_StreamFormat was used are
changed.

------------------------------------------------------------------------


void BackupStoreDirectory::Entry::ReadFromStream(IOStream &rStream, int Timeout)
{
    // Grab the raw bytes from the stream which compose the header
    StreamMessage<en_StreamFormat_Size> entry;
    if(!rStream.ReadFullBuffer(entry.GetReceiveBuffer(), entry.GetSize(), 0 /* not interested in bytes read if this fails */, Timeout))
    {
        THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
    }

    // Do reading first before modifying the variables, to be more exception safe
    
    // Get the filename
    BackupStoreFilename name;
    name.ReadFromStream(rStream, Timeout);
    
    // Get the attributes
    mAttributes.ReadFromStream(rStream, Timeout);

    // Store the rest of the bits
    mModificationTime =    entry.FetchUInt64();
    mObjectID =         entry.FetchInt64();
    mSizeInBlocks =     entry.FetchInt64();
    mAttributesHash =    entry.FetchUInt64();
    mFlags =         entry.FetchInt16();
    mName =            name;
}

void BackupStoreDirectory::Entry::WriteToStream(IOStream &rStream) const
{
    // Build a structure
    StreamMessage<en_StreamFormat_Size> entry;
    entry.AddUInt64(mModificationTime);
    entry.AddInt64(mObjectID);
    entry.AddInt64(mSizeInBlocks);
    entry.AddUInt64(mAttributesHash);
    entry.AddInt16(mFlags);
    
    // Write it
    rStream.Write(entry.GetSendBuffer(), entry.GetSize());
    
    // Write the filename
    mName.WriteToStream(rStream);
    
    // Write any attributes
    mAttributes.WriteToStream(rStream);
}
------------------------------------------------------------------------




Finally the new template class (which I put directly in the .cpp file,
but should of course be in its own header file):

------------------------------------------------------------------------


template<int Size>
class StreamMessage
{
  static const int mBufferSize = Size;
  uint8_t mBuffer[mBufferSize];
  int mPos;
  bool mOverflow;

 public:
  inline StreamMessage();

  inline void AddInt8(int8_t x);
  inline void AddUInt8(uint8_t x);
  inline void AddInt16(int16_t x);
  inline void AddUInt16(uint16_t x);
  inline void AddInt32(int32_t x);
  inline void AddUInt32(uint32_t x);
  inline void AddInt64(int64_t x);
  inline void AddUInt64(uint64_t x);

  inline int8_t   FetchInt8();
  inline uint8_t  FetchUInt8();
  inline int16_t  FetchInt16();
  inline uint16_t FetchUInt16();
  inline int32_t  FetchInt32();
  inline uint32_t FetchUInt32();
  inline int64_t  FetchInt64();
  inline uint64_t FetchUInt64();

  inline void Reset();

  inline const uint8_t * GetSendBuffer() const;
  inline uint8_t * GetReceiveBuffer();

  static inline int GetSize();
};


template<int Size>
inline
StreamMessage<Size>::StreamMessage() : mPos(0), mOverflow(false)
{}
 

template<int Size>
inline void
StreamMessage<Size>::AddInt8(int8_t x)
{
  AddUInt8(static_cast<uint8_t>(x));
}


template<int Size>
inline void
StreamMessage<Size>::AddUInt8(uint8_t x)
{
  if (mPos < mBufferSize)
  {
    mBuffer[mPos++] = x;
  }
  else
  {
    mOverflow = true;
  }
}

 
template<int Size>
inline void
StreamMessage<Size>::AddInt16(int16_t x)
{
  AddUInt16(static_cast<uint16_t>(x));
}


template<int Size>
inline void
StreamMessage<Size>::AddUInt16(uint16_t x)
{
  if (mPos+1 < mBufferSize)
  {
    mBuffer[mPos++] = (x >> 8) & 0xFF;
    mBuffer[mPos++] = x & 0xFF;
  }
  else
  {
    mPos = mBufferSize;
    mOverflow = true;
  }
}

 
template<int Size>
inline void
StreamMessage<Size>::AddInt32(int32_t x)
{
  AddUInt32(static_cast<uint32_t>(x));
}


template<int Size>
inline void  
StreamMessage<Size>::AddUInt32(uint32_t x)
{
  if (mPos+3 < mBufferSize)
  {
    mBuffer[mPos++] = (x >> 24) & 0xFF;
    mBuffer[mPos++] = (x >> 16) & 0xFF;
    mBuffer[mPos++] = (x >> 8) & 0xFF;
    mBuffer[mPos++] = x & 0xFF;
  }
  else
  {
    mPos = mBufferSize;
    mOverflow = true;
  }
}


template<int Size>
inline void
StreamMessage<Size>::AddInt64(int64_t x)
{
  AddUInt64(static_cast<uint64_t>(x));
}


template<int Size>
inline void
StreamMessage<Size>::AddUInt64(uint64_t x)
{
  if (mPos+7 < mBufferSize)
  {
    mBuffer[mPos++] = (x >> 56) & 0xFF;
    mBuffer[mPos++] = (x >> 48) & 0xFF;
    mBuffer[mPos++] = (x >> 40) & 0xFF;
    mBuffer[mPos++] = (x >> 32) & 0xFF;
    mBuffer[mPos++] = (x >> 24) & 0xFF;
    mBuffer[mPos++] = (x >> 16) & 0xFF;
    mBuffer[mPos++] = (x >> 8) & 0xFF;
    mBuffer[mPos++] = x & 0xFF;
  }
  else
  {
    mPos = mBufferSize;
    mOverflow = true;
  }
}


template<int Size>
inline int8_t
StreamMessage<Size>::FetchInt8()
{
  return static_cast<int8_t>(FetchUInt8());
}


template<int Size>
inline uint8_t
StreamMessage<Size>::FetchUInt8()
{
  ASSERT(mPos < mBufferSize);

  uint8_t x = 0;
  if (mPos < mBufferSize)
  {
    x = mBuffer[mPos++];
  }
  else
  {
    mOverflow = true;
  }
  return x;
}


template<int Size>
inline int16_t
StreamMessage<Size>::FetchInt16()
{
  return static_cast<int16_t>(FetchUInt16());
}


template<int Size>
inline uint16_t
StreamMessage<Size>::FetchUInt16()
{
  ASSERT(mPos+1 < mBufferSize);

  uint16_t x = 0;
  if (mPos < mBufferSize)
  {
    uint16_t x0, x1;
    x1 = mBuffer[mPos++];
    x0 = mBuffer[mPos++];
    x = (x1 << 8) | x0;
  }
  else
  {
    mPos = mBufferSize;
    mOverflow = true;
  }
  return x;
}


template<int Size>
inline int32_t
StreamMessage<Size>::FetchInt32()
{
  return static_cast<int32_t>(FetchUInt32());
}


template<int Size>
inline uint32_t
StreamMessage<Size>::FetchUInt32()
{
  ASSERT(mPos+3 < mBufferSize);

  uint32_t x = 0;
  if (mPos < mBufferSize)
  {
    uint32_t x0, x1, x2, x3;
    x3 = mBuffer[mPos++];
    x2 = mBuffer[mPos++];
    x1 = mBuffer[mPos++];
    x0 = mBuffer[mPos++];
    x = (x3 << 24) | (x2 << 16) | (x1 << 8) | x0;
  }
  else
  {
    mPos = mBufferSize;
    mOverflow = true;
  }
  return x;
}


template<int Size>
inline int64_t
StreamMessage<Size>::FetchInt64()
{
  return static_cast<int64_t>(FetchUInt64());
}


template<int Size>
inline uint64_t
StreamMessage<Size>::FetchUInt64()
{
  ASSERT(mPos+7 < mBufferSize);

  uint64_t x = 0;
  if (mPos < mBufferSize)
  {
    uint64_t x0, x1, x2, x3, x4, x5, x6, x7;
    x7 = mBuffer[mPos++];
    x6 = mBuffer[mPos++];
    x5 = mBuffer[mPos++];
    x4 = mBuffer[mPos++];
    x3 = mBuffer[mPos++];
    x2 = mBuffer[mPos++];
    x1 = mBuffer[mPos++];
    x0 = mBuffer[mPos++];
    x = (x7 << 56) | (x6 << 48) | (x5 << 40) | (x4 << 32) |
        (x3 << 24) | (x2 << 16) | (x1 << 8) | x0;
  }
  else
  {
    mPos = mBufferSize;
    mOverflow = true;
  }
  return x;
}


template<int Size>
inline void
StreamMessage<Size>::Reset()
{
  mPos = 0;
  mOverflow = false;
}


template<int Size>
inline const uint8_t *
StreamMessage<Size>::GetSendBuffer() const
{
  ASSERT(mPos == mBufferSize && !mOverflow);
  return mBuffer;
}


template<int Size>
inline uint8_t *
StreamMessage<Size>::GetReceiveBuffer()
{
  ASSERT(mPos == 0);
  return mBuffer;
}


template<int Size>
inline int
StreamMessage<Size>::GetSize()
{
  return mBufferSize;
}
------------------------------------------------------------------------




Cheers,
Leif

 		 	   		  


More information about the Boxbackup mailing list