Cyrus IMAP Server: Mailbox File Formats
***************************************


Intro
=====

This documentation refers to the “version 12” cyrus index format and
associated mailbox files.

No external tools should make use of this information. The only
supported method of access to the mail store is through the standard
interfaces: IMAP, POP, NNTP, LMTP, etc.

A cyrus mailbox is a directory in the filesystem. It contains the
following files:

* zero or more message files

* the "cyrus.header" metadata file

* the "cyrus.index" metadata file

* the "cyrus.cache" metadata file

* zero or one "cyrus.squat" search indexes

* zero or more subdirectories

With “split metadata” configuration, the mailbox may actually be split
between multiple disks, with the files being in the same relative
directory on the meta disk. See the "imapd.conf" option
"metapartition_files" for more information


Message Files
=============

The message files are named by their UID, followed by a “.”, so UID
423 would be named “"423."”. They are stored in wire-format: lines are
terminated by CRLF and binary data is not allowed.


"cyrus.header"
==============

This file contains mailbox-wide information that does not change
often. Its format:

   <Mailbox Header Magic String>
   <Quota Root>\t<Mailbox Unique ID String>\n
   <Space-separated list of user flags>\n
   <Mailbox ACL>\n

The Mailbox Unique ID String is used for non-owner per-user \Seen
flags so they remain with the mailbox during renames, and also by the
replication subsystem to detect mailbox renames.

The ACL is a copy of the value stored in mailboxes.db, and isn’t
actually used.


Locking Considerations
----------------------

The "cyrus.index" file must be locked in exclusive mode while making
changes to the "cyrus.header" file to ensure consistency. All changes
are made by rewriting the entire file and renaming the new version
into place.


"cyrus.cache"
=============

The "cyrus.cache" file is a pure cache of information that’s also
present in the message files. It exists to make ENVELOPE and specific
header fetches more efficient, as well as to assist with searches and
sorts.

If a "cyrus.cache" file is missing or corrupted, it can be re-
generated by running a "reconstruct" on the mailbox.

The format is 10 individual records each prefixed with a 32 bit length
value in network byte order. The offset of each message’s cache record
is stored in the "cyrus.index" file (documented below). The records in
a cyrus.cache file are of variable length, depending on the contents
of the associated message.

The first 4 bytes of the cyrus.cache file are a “generation number”
which must match the first 4 bytes of the associated cyrus.index file.
In the past this was used to track consistency between the files, but
the name locking scheme and per-record CRC check in cyrus 2.4 and
above means this is just a backup consistency check rather than an
essential format feature.

   +------------------------------------------------------------------------+
   |Gen # (32bits)|Size 1 (32bits)|Data 1                                   |
   +------------------------------------------------------------------------+
   |           |Size 2 (32bits)|Data 2            |Size 3 (32bits)| Data 3  |
   +------------------------------------------------------------------------+
   | .....                                                                  |
   +------------------------------------------------------------------------+

While there are occasional changes to the cache format, this
information is NOT stored in the cyrus.cache file. Instead, there is a
“cache_version” field in the cyrus.index record, so multiple different
versions of cache data may exist in the same cache file.

The order of fields per record in the cache file is as follows: (keep
in mind that they are all preceded by a 4 byte network byte order
size).

Envelope Response
   Raw IMAP response for a request for the envelope.

Bodystructure Response
   Raw IMAP response for a request for the bodystructure.

Body Response
   Raw IMAP response for an (old style) request for the body.

Binary Bodystructure
   Offsets into the message file to pull out various body parts.
   Because of the nature of MIME parts, this is somewhat recursive.

   This looks like the following (starting the octet following the
   cache field size). All of the fields are bit32s.

      [
       [Number of message parts+1 for the rfc822 header if present]
       [
        [Offset in the message file of the header of this part]
        [Size (octets) of the header of this part]
        [Offset in the message file of the content of this part]
        [Size (octets) of the content of this part]
        [Encoding Type of this part]
       ]
          (repeat for each part as well as once for the headers)
       [zero *or* number of sub-parts in the case of a multipart.
        if nonzero, this is a recursion into the top structure]
          (repeat for each part)
      ]

   Note if this is not a message/rfc822, than the values for the sizes
   of the part 0 are -1 (to indicate that it doesn’t exist). Sub-parts
   are not possible for a part 0, so they aren’t included when finding
   recursive entries.

   The offset and size info for both the mime header and content part
   are useful in order to do fast indexing on the appropriate parts of
   the message file when a client does a FETCH request for
   BODY[HEADER], or BODY[2.MIME].

   Note that the top level RFC822 headers are a treated as a separate
   part from their body text (“0” or “HEADER”).

   In the case of a multipart/alternative, the content size & offset
   refers to the size of the entire mime part.

   A very simple message (with a single text/plain part) would
   therefore look like:

      [[2][rfc822 header][text/plain body part info][0]]

   A simple multipart/alternative message might look like:

      [[3][rfc822 header][text/plain message part info]
          [second message part info][0][0]]

   A message with an attachment that has two subparts:

      [[3][rfc822 header info][rfc822 first body part info][attachment info][0][
            [3][NIL header info][sub part 1 info][sub part 2 info][0][0]]]

   A message with an attached message/rfc822 message with the
   following total structure:

      message/rfc822
        0 headers; content-type: multipart/mixed
        1 text/plain
        2 message/rfc822
          0 headers; content-type: multipart/alternative
          1 text/plain
          2 text/html

      [[3][rfc822 header part 0][text/plain part 1][overall attachment info][0][
           [3][rfc822 header part 2.0][text/plain part 2.1][text/html part 2.2]
              [0][0]]]

Cache Header
   Any cached header fields. The exact set of fields here depends on
   the cache record version - there is a function in "imap/mailbox.c"
   to determine if a named header would be cached based on the
   version. These are in the same format they would appear in the
   message file:

      HeaderName: headerdata\r\n

   Examples include: References, In-Reply-To, etc.

From
   The from header.

To
   The to header.

Cc
   The CC header.

Bcc
   The BCC header.

Subject
   The Subject header.


Locking Considerations
----------------------

The "cyrus.index" file must be locked in exclusive mode while making
changes to the "cyrus.cache" file to ensure consistency. All new cache
records are created by reading the current end-of-file offset,
appending the new cache record, and storing that start offset into the
associated cyrus.index record.


"cyrus.index"
=============

The cyrus.index file is NOT just a cache - it stores information not
present in the message file!

The cyrus.index file consists of a fixed width header, followed by
fixed width records. In the past, it would be rewritten on every
expunge, but since Cyrus 2.4 the expunged records remain in the
cyrus.index file for a configurable time to support QRESYNC and more
efficient delayed expunge.

The cyrus.index file is the “heart” of the mailbox format - containing
checksums (CRC32) of everything else, and the most frequently updated
fields. All fields are stored in network byte order and aligned on 4
byte boundaries. Due to some 64 bit values being stored, the header
and individual records are aligned on 8 byte boundaries.

The overall format looks sort of like this:

   cyrus.index:
   +----------------+
   | Mailbox Header |
   +----------------+
   | Msg: Num 1     |
   +----------------+
   | Msg: Num 2     |
   +----------------+
   |     ...        |
   +----------------+

The basic idea being that there is one header, and then all the
message records are evenly spaced throughout the file. All of the
message records are at well-known offsets, making any part of the file
accessable at roughly equal speed.


Locking Considerations
----------------------

"cyrus.index" files can not be repacked (i.e. records can not change
UID for a particular offset, and the file can’t be rewritten or
deleted) unless there’s an exclusive namelock held for the mailbox
name. This is to avoid race conditions and simplify the use of
mailboxes. Whenever a mailbox is opened, the caller holds a shared
namelock on the mailbox name for the duration of the “mailbox
object“‘s existence.

All reads of a "cyrus.index" file must be done with a lock held, and
all writes must be done with an exclusive lock held. This ensures
CRC32 checksums of individual headers and records are always
consistent. There are no direct “offset” reads done any more, instead
the mailbox API provides a way to read an entire cyrus.index header or
cyrus.index record into a struct, performing consistency checks.
Writes are also done with a complete record struct.


Detail of "cyrus.index" header
------------------------------

The index header contains the following information, in order:

Generation Number (4 bytes)
   A number that is basically the “revision number” of the mailbox. It
   must match between the cache and index files. This is to ensure
   that if we fail to sync both the cache and index files and a crash
   happens (so that only one is synced), we do not provide bad data to
   the user. This is also backed by having individual cache checksums
   on each record.

Format (4 bytes)
   Basically obsolete (indicates netnews or regular).

Minor Version (4 bytes)
   Indicates the version number of the index file. This can be used
   for on-the-fly upgrades of the index and cache files.

Start Offset (4 bytes)
   Size of index header.

Record Size (4 bytes)
   Size of an index record.

Num Records (4 bytes)
   How many records are in this index (including records for expunged
   records. See below for “Exists” which has moved from pre-version 12
   files.

Last Appenddate (4 bytes)
   (time_t) of the last time a message was appended

Last UID (4 bytes)
   Highest UID of all messages in the mailbox (UIDNEXT - 1).

Quota Mailbox Used (8 bytes)
   Total amount of storage used by all of the messages in the mailbox.
   Platforms that don’t support 64-bit integers only use the last 4
   bytes.

POP3 Last Login (4 bytes)
   (time_t) of the last pop3 login to this INBOX, used to enforce the
   “poptimeout” "imapd.conf" option.

UIDvalidity (4 bytes)
   The UID validitiy of this mailbox. Cyrus currently uses the
   "time()" when this mailbox was created.

Deleted, Answered, and Flagged (4 bytes each)
   Counts of how many messages have each flag.

Mailbox Options (4 bytes)
   Bitmask of mailbox options, consisting of any combination of the
   following:

   POP3_NEW_UIDL
      Flag signalling that we’re using “*uidvalidity*.*uid*” instead
      of just “*uid*” for the output of the POP3 UIDL command.

   IMAP_SHAREDSEEN
      Flag signalling that we’re supporting a shared \Seen flag on the
      mailbox.

   IMAP_DUPDELIVER
      Flag signalling that we’re allowing duplicate delivery of
      messages to the mailbox, overriding system-wide duplicate
      suppression.

   MAILBOX_NEEDS_REPACK
      Flag signalling that the mailbox is due to be repacked. During
      mailbox_close() every process will attempt to take an exclusive
      namelock on the mailbox and repack.

   MAILBOX_DELETED
      Flag signalling that the mailbox is deleted. This can be set
      with a shared namelock, and indicates to all other users of the
      mailbox that they need to close it and attempt cleanup. The last
      process to close the mailbox will perform the final cleanup
      under an exclusive namelock, giving the other processes a chance
      to finish their current operation first without files
      disappearing from under them!

Leaked Cache (4 bytes)
   Number of leaked records in the cache file.

Highest ModSeq (8 bytes)
   Highest Modification Sequence of all the messages in the mailbox
   (CONDSTORE).

Deleted ModSeq (8 bytes)
   Lowest Modification Sequence before which expunged message data may
   have been purged from the mailbox and forgotten (CONDSTORE/QRESYNC
   support).

Exists (4 bytes)
   See NumRecords above. This is the count of non-expunged records in
   the mailbox and corresponds to the IMAP status item “EXISTS”.

First Expunged (4 bytes)
   lowest modified time of an expunged message in this mailbox (or
   zero if there are no expunged messages) - used to determine if the
   mailbox needs repacking.

Last Repack Time (4 bytes)
   a timestamp for the last repack, to ensure repacks aren’t done too
   close together if expunges were closely spaced

Header File CRC (4 bytes)
   CRC32 value of the bytes in the "cyrus.header" file for this
   mailbox. Must be rewritten whenever the cyrus.header file is
   changed (see locking considerations above - this is why the
   cyrus.index must be exclusively locked!)

Sync CRC (4 bytes)
   An XOR of the CRC32 of a specially generated value for each of the
   non-expunged records in this mailbox. This is a cached value which
   allows the replication subsystem to quickly determine that all non-
   expunged records in a mailbox are in sync and detect possible
   “split brain” scenarios with low bandwidth use.

Recent UID (4 bytes)
   The highest UID last time an IMAP client logged in as the mailbox
   owner (or anybody if SHAREDSEEN is enabled) selected this mailbox.
   Used to generate the \Recent flags in IMAP

Recent Time (4 bytes)
   Used for consistency with the seen_db code, but probably not
   actually necessary. Oh well

Header CRC (4 bytes)
   Must always be the LAST record of the header. This is the CRC32 of
   the actual bytes on disk (network order format) for the rest of the
   cyrus.index. By keeping it last, it can be easily calculated with
   the following snippet of code: "crc = crc32_map(buf,
   OFFSET_HEADER_CRC);" - i.e. crc32 from the start of the buffer to
   just before this field.

There are also spare fields in the index header, to allow for future
expansion without forcing an upgrade of the file, and to round up to
be divisible by 8 bytes.


Detail of "cyrus.index" records
-------------------------------

These records start immediately following the "cyrus.index" header,
and are all fixed size. They are in-order by uid of the message.

UID (4 bytes)
   UID of the message

INTERNALDATE (4 bytes)
   INTERNALDATE of the message (where possible, this matches the
   creation and modification times of the file on disk to help
   reconstruct in the event of data loss)

SENTDATE (4 bytes)
   Contents of the Date: header chomped to day resolution with
   timezone stripped.

SIZE (4 bytes)
   Size of the whole message (in octets)

HEADER SIZE (4 bytes)
   Size of the message header (in octets)

GMTIME (4 bytes)
   Contents of the Date: header at 1 second resolution and converted
   to GMT (for sort)

CACHE_OFFSET (4 bytes)
   Offset into the "cyrus.cache" file for the beginning of this
   message’s cache entry.

LAST UPDATED (4 bytes)
   (time_t) of the last time this record was changed

SYSTEM FLAGS (4 bytes)
   Bitmask showing which system flags are set/unset

USER FLAGS (MAX_USER_FLAGS / 32 bytes)
   Bitmask showing which user flags are set/unset (bits correspond to
   positions in the cyrus.header flag list, i.e. (1<<0) == the flag
   name

CONTENT_LINES (4 bytes)
   Number of text lines contained in the message content (body).

CACHE_VERSION (4 bytes)
   Indicates the version number of the cache record for the message
   (determines which headers are cached, see list in mailbox.c).

GUID (MESSAGE_GUID_SIZE bytes)
   Globally Unique IDentifier of the message (used by replication
   engine). This is the sha1 value of the bytes as stored on disk.

MODSEQ (8 bytes)
   Modification Sequence of the message (CONDSTORE).

CACHE_CRC (4 bytes)
   This is the CRC32 of all the bytes of the cache record (all 10
   fields) as stored on disk. Again, calculated over the exact bytes
   stored in the "cyrus.cache" file.

RECORD_CRC (4 bytes)
   Like the header CRC - this is the CRC32 of all the bytes in on-disk
   order that exist in this record. Records are always rewritten as
   the entire record, including the updated CRC, so it’s always
   consistent if you have a lock on the "cyrus.index" file, because
   writers will wait until they get an exclusive lock to make
   modifications.


Notes
=====

* Expunge is super quick now - it’s just a flag update!

* Append is relatively fast (it only adds to the end of both the cache
  and index files and modifies the index header)

* Message unlinks always happen during the “close” phase - which may
  be noticed when you select another mailbox, but otherwise are
  delayed from the actual action. With delayed expunge, the unlinks
  are pushed off to cyr_expire which is a background task, and will
  never be noticed by the user.

* Message delivery is something like this:

  1. write/sync message file

  2. write/sync new "cyrus.cache" record

  3. write/sync new "cyrus.index" record

  4. calculate, write, sync new "cyrus.index" header

  5. acknowledge message delivery

  The message isn’t delivered until the new index header is written.
  In case of a crash before the new index header is written, any
  previous writes will be overwritten on the next delivery (and will
  not be noticed by the readers).

  Note that certain power failure situations (power failure in the
  middle of a disk sector write) could cause a mailbox to need
  reconstruction (possibly even losing some flag state). These failure
  modes are not possible in the “Hardware RAID disk model” (which we
  will describe somewhere else when we get around to it).


Future considerations
=====================

* Cache all header fields? (or all up to Xk?) This could greatly
  improve speeds of clients that just ask for everything, but also
  increases the expense of rewriting the cache file (as well as the
  size it takes on disk).

* Reformat cache file to use a
  (size)(size)(size)(size)(data)(data)(data) format. This makes
  accesses anywhere in the cache file equally fast, as opposed to
  having to iterate through all the entires for a given message to get
  to the last one. Note that either way is still O(1) so maybe it
  doesn’t matter much.

* It would be useful to store a uniqueid -> mailbox name index, so
  that we could fix arbitron again.
