Monday, July 19, 2010

(Single instance) attachment storage

(Mailing list thread for this post.)

Now that v2.0.0 is only waiting for people to report bugs (and me to figure out how to fix them), I've finally had time to start doing what I actually came here (Portugal Telecom/SAPO) to do. :)

The idea is to have dbox and mdbox support saving attachments (or MIME parts in general) to separate files, which with some magic gives a possibility to do single instance attachment storage. Comments welcome.

Reading attachments

dbox metadata would contain entries like (this is a wrapped single line entry):

X1442 2742784 94/b2/01f34a9def84372a440d7a103a159ac6c9fd752b
2744378 27423 27/c8/a1dccc34d0aaa40e413b449a18810f600b4ae77b

So the format is:

"X" 1*(<offset> <byte count> <link path>)

So when reading a dbox message body, it's read as:

offset=0: <first 1442 bytes from dbox body>
offset=1442: <next 2742784 bytes from external file>
offset=2744226: <next 152 bytes from dbox body>
offset=2744378: <next 27423 bytes from external file>
offset=2744378 27423: <the rest from dbox body>

This is all done internally by creating a single istream that lazily opens the external files only when data is actually tried to be read from that part of the message.

The link paths don't have to be in any specific format. In future perhaps it can recognize different formats (even http:// urls and such).

Saving attachments separately

Message MIME structure is being parsed while message is saved. After each MIME part's headers are parsed, it's determined if this part should be stored into attachment storage. By default it only checks that the MIME part isn't multipart/* (because then its child parts would contain attachments). Plugins can also override this. For example they could try to determine if the commonly used clients/webmail always downloads and shows the MIME part when opening the mail (text/*, inline images, etc).

dbox_attachment_min_size specifies the minimum MIME part size that can be saved as an attachment. Anything smaller than that will be stored normally. While reading a potential attachment MIME part body, it's first buffered into memory until the min. size is reached. After that the attachment file is actually created and buffer flushed to it.

Each attachment filename contains a global UID part, so that no two (even identical) attachments will ever contain the same filename. But there can be multiple attachment storages in different mount points, and each one could be configured to do deduplication internally. So identical attachments should somehow be stored to same storage. This is done by taking a hash of the body and using a part of it as the path to the file. For example:

mail_location = dbox:~/dbox:ATTACHMENTS=/attachments/$/$

Each $ would be expanded to 8 bits of the hash in hex (00..ff). So the full path to an attachment could look like:


Sysadmin can then create /attachment/00..ff as symlinks to different storages.

Hashing problems

Some problematic design decisions:
  1. Hash is taken from hardcoded first n kB vs. first dbox_attachment_min_size bytes?
    • + With first n kB, dbox_attachment_min_size can be changed without causing duplication of attachments, otherwise after the change the same attachment could get a hash to a different storage than before the change.

    • - If n kB is larger than dbox_attachment_min_size, it uses more memory.

    • - If n kB is determined to be too small to get uniform attachment distribution to different storages, it can't be changed without recompiling.

  2. Hash is taken from first n bytes vs. everything?

    • + First n bytes are already read to memory anyway and can be hashed efficiently. The attachment file can be created without wasting extra memory or disk I/O. If everything is hashed, the whole attachment has to be first stored to memory or to a temporary file and from there written to final storage.

    • - With first n bytes it's possible for an attacker to generate lots of different large attachments that begin with the same bytes and then overflow a single storage. If everything is hashed with a secure hash function and a system-specific secret random value is added to the hash, this attack isn't possible.

I'm thinking that even though taking a hash of everything is the least efficient option, it's the safest option. It's pretty much guaranteed to give a uniform distribution across all storages, even against intentional attacks. Also the worse performance isn't probably that noticeable, especially assuming a system where local disk isn't used for storing mails, and the temporary files would be created there.

Single instance storage

All of the above assumes that if you want a single instance storage, you'll need to enable it in your storage. Now, what if you can't do that?

I've been planning on making all index/dbox code to use an abstracted out simple filesystem API rather than using POSIX directly. This work can be started by making the attachment reading/writing code use the FS API and then create a single instance storage FS plugin. The plugin would work like:
  • open(ha/sh/hash-guid): The destination storage is in ha/sh/ directory, so a new temp file can be created under it. The hash is part of the filename to make unlink() easier to handle.

    Since the hash is already known at open() time, look up if hashes/<hash> file exists. If it does, open it.

  • write(): Write to the temp file. If hashes/ file is open, do a byte-by-byte comparison of the inputs. If there's a mismatch, close the hashes/ file and mark it as unusable.

  • finish():

    1. If hashes/ file is still open and it's at EOF, link() it to our final destination filename and delete the temp file. If link() fails with ENOENT (it was just expunged), goto b. If link() fails with EMLINK (too many links), goto c.

    2. If hashes/ file didn't exist, link() the temp file to the hash and rename() it to the destination file.

    3. If the hashed file existed but wasn't the same, or if link() failed with EMLINK, link() our temp file to a second temp file and rename() it over the hashes/ file and goto a.

  • unlink(): If hashes/<hash> has the same inode as our file and the link count is 2, unlink() the hash file. After that unlink() our file.

One alternative to avoid using <hash> as part of the filename would be for unlink() to read the file and recalculate its hash, but that would waste disk I/O.

Another possibility would to be to not unlink() the hashes/ files immediately, but rather let some nightly cronjob to stat() through all of the files and unlink() the ones that have link count=1. This could be wastefully inefficient though.

Yet another possibility would be for the plugin to internally calculate the hash and write it somewhere. If it's at the beginning of the file, it could be read from there with some extra disk I/O. But is it worth it?..

Extra features

The attachment files begin with an extensible header. This allows a couple of extra features to reduce disk space:

  1. The attachment could be compressed (header contains compressed-flag)

  2. If base64 attachment is in a standardized form that can be 100% reliably converted back to its original form, it could be stored decoded and then encoded back to original on the fly.

It would be nice if it was also possible to compress (and decompress) attachments after they were already stored. This would be possible, but it would require finding all the links to the message and recreating them to point to the new message. (Simply overwriting the file in place would require there are no readers at the same time, and that's not easy to guarantee, except if Dovecot was entirely stopped. I also considered some symlinking schemes but they seemed too complex and they'd also waste inodes and performance.)

Code status

Initial version of the attachment reading/writing code is already done and works (lacks some error handling and probably performance optimizations). The SIS plugin code is also started and should be working soon.

This code is very isolated and can't cause any destabilization unless it's enabled, so I'm thinking about just adding it to v2.0 as soon as it works, although the config file comments should indicate that it's still considered unstable.


  1. New and used slot machines - Pragmatic Play - AprCasino
    NEW AND 출장샵 NEW SLOT MACHINES WITH A aprcasino HIGH RTP! For the ultimate high-quality gaming 바카라 사이트 experience, Pragmatic Play offers ford escape titanium all of the

    1. I recently had the pleasure of experiencing the exceptional services provided by Kaya VIP Travel in Istanbul, and I must say, it was truly remarkable. From the moment I made my reservation until the end of my journey, their professionalism and dedication to customer satisfaction were evident.

      The comfort and luxury offered by their VIP class Mercedes Vito vehicles surpassed my expectations. The spacious interior, comfortable seats, and meticulous interior design truly made my travel experience a memorable one. I felt pampered and at ease throughout the journey.

      One aspect that truly stood out for me was the level of safety and security provided by Kaya VIP Travel. Their experienced drivers, coupled with their TURSAB registered agency, ensured a journey that was not only enjoyable but also reliable and secure. It was comforting to know that my well-being was their top priority.

      Furthermore, the flexibility and personalized service offered by Kaya VIP Travel were commendable. They tailored the journey according to my specific needs and requests, going above and beyond to ensure my satisfaction. Their attention to detail and commitment to providing a seamless experience were truly impressive.

      Overall, my experience with Kaya VIP Travel was exceptional. They set a high standard for VIP transfer services in Istanbul, and I would highly recommend them to anyone seeking a comfortable, luxurious, and reliable travel experience. Whether it's for business or leisure, Kaya VIP Travel will exceed your expectations and make your journey truly unforgettable.

  2. "This is very educational content and written well for a change. It's nice to see that some people still understand how to write a quality post.!

  3. Thank you for nice information. Please visit our web: click here

  4. Admiring the hard work you put into your blog and detailed information you provide. Great read! I’ve bookmarked your site and I’m including your RSS feeds to my Google account.


  5. Hello to every one, as I am actually keen of reading this web site’s post to be updated regularly. It contains good stuff. Just wanted to tell you, I enjoyed this post. It was funny. Keep on posting!

    배구토토 사다리타기토토 스포츠레몬티비 스포츠베팅

  6. I have to admit that this is a fantastic post, and I value the information. You talk about (Single instance) attachment storage. I would like to read more of your arguments because they are so compelling. But I'm now seeking for the assignment writing service new zealand service for a homework project. This benefits me, and I do my task more quickly. All pupils can benefit from the website's extensive content.

  7. I’ve got you saved as a favorite to look at new things you post, Im waiting

  8. I will make sure to read this blog more. You made a good point

  9. This was super interesting article to read. Thanks for sharing it here!

  10. I bookmarked to check out new information on this great blog.

  11. the information you provide on this website has helped me greatly. 안전놀이터

  12. Thanks for sharing a information about the storage. It is very informative and valuable post. Keep update more blogs like this. Traffic Lawyer Prince William VA


  13. I'm sorry, but it seems like "dovecot" is not a recognized term or topic in my knowledge base as of my last update in September 2021. Therefore, I'm unable to provide comments or information on it. If you could provide more context or details about what "dovecot" refers to, I would be happy to try and assist you planning lawyer Fairfax VA

  14. Dovecot is a popular open-source IMAP and POP3 email server for Unix-like operating systems. It's known for its performance, security, and flexibility.
    Accidente de Motocicleta

  15. Abogado de Accidentes de Motocicleta Virginia
    The attachment storage solution was highly recommended for its performance, ease of uploading and accessing attachments, reliable storage capacity, and seamless search and retrieval of attachments. The system's security measures provided peace of mind for sensitive data. The user interface was intuitive, and attachment categorization and organization options were practical and efficient. The version control feature was appreciated for easy revision management. The service integrated smoothly with existing tools and applications, and customer support was responsive. Backup and recovery options offered additional data protection. The pricing structure was competitive and cost-effective. Overall, the solution streamlined workflow and enhanced productivity, and the customer would highly recommend it to other businesses seeking efficient attachment storage.

  16. SMM Panel Provider offers a comprehensive solution for your social media needs. They're my trusted partner for growth.

  17. The blog offers a unique perspective on Dovecot IMAP Server Development, highlighting the author's experiences and challenges. It could benefit from more technical details and specific examples. The blog's personal anecdotes and reflections add a human touch, making it relatable. The writing style is engaging and easy to follow, making it accessible to both technical and non-technical audiences. Overall, the blog effectively combines technical content with personal experiences.Leyes de Divorcio de Nueva York Propiedad

  18. This article by Timo Sirainen discusses the development of single instance attachment storage in email systems. It details the format for reading attachments, saving attachments separately, and discusses hashing options. The article proposes changes to existing code to create a single instance storage plugin, with additional features like compressing and converting base64 attachments. contract dispute