Namespaces and naming rewrite - stage 1 (mbname_t and friends)

Bron Gondwana brong at fastmail.fm
Wed Sep 2 09:04:27 EDT 2015


I've just pushed a massive commit to master: 65c9129253cfb22542c906330aecfb3068484a0b

This commit removes mboxname_parts and replaces it with an opaque type mbname_t.

My eventual goal is to replace all use of internal mailbox names throughout the code with one of these.  It will need to be made slightly more lightweight (not parsing the passed name immediately unless requested) so you can pass an internal name around with one at no extra cost, but in general.

a) mbentry_t will have one of these rather than a ->name parameter.
b) mbentry_t will be opaque too, so you have to use functions to update it.  This will mean you can still call mbentry_intname(mbentry) and get the name.
c) mboxlist_findall and friends will hence get a const one of these, and can interrogate it for values, but need to call mbname_dup(mbname) if they want to edit one, making their own copy.

The function spec (so far)

Accessing formatted versions of the mailbox (or the owner user):

const char *mbname_userid(const mbname_t *mbname);
const char *mbname_intname(const mbname_t *mbname);
const char *mbname_extname(const mbname_t *mbname, const struct namespace *ns, const char *userid);

Despite taking a "cont mbname_t *" these functions do cast it to non-const internally to update cache fields, but only in ways that are recalculable and don't change the meaning.

Accessing individual parts of the structure:

const char *mbname_domain(const mbname_t *mbname);
const char *mbname_localpart(const mbname_t *mbname);
const strarray_t *mbname_boxes(const mbname_t *mbname);
time_t mbname_isdeleted(const mbname_t *mbname);

Note that mailboxes (all of them for shared, subfolders of the user for user) are in a strarray_t, meaning that the separate NEVER leaks.  The mailbox gondwana.name!user.bron^gondwana.sub.folder^with^dots would look like this with unixhs on:

mbname_userid => bron.gondwana at gondwana.name
mbname_intname =>  gondwana.name!user.bron^gondwana.sub.folder^with^dots
mbname_extname
  => INBOX/sub/folder.with.dots (my namespace)
  => user/bron.gondwana/sub/folder.with.dots (other namespace)
mbname_domain => gondwana.name
mbname_localpart => bron.gondwana
mbname_boxes => ["sub", "folder.with.dots"]
mbname_isdeleted => 0

(if the folder is deleted, it gets both a DELETED. on the front, and a .%X of the timestamp on the end in internal and admin external namespaces)

Note that the '^' character isn't stored in mbname_t at all, or the '/' character.  They're purely an artifact of the namespace or intname representation.

Now this is just stage 1.  Stage 2 is removing mboxname_hiersep_to{internal,external}.  This has been a real rabbit hole, because we're going to need another format:

mbname_recipient => bron.gondwana+sub/folder.with.dots at gondwana.name

Which will be used for all the LMTP and Sieve rule fudging.  Yay.

=====

OK, let's look at how you create one of these things:

mbname_t *mbname_from_userid(const char *userid);
mbname_t *mbname_from_localdom(const char *localpart, const char *domain);
mbname_t *mbname_from_intname(const char *intname);
mbname_t *mbname_from_extname(const char *extname, const struct namespace *ns, const char *userid);

(you can pass NULL to from_intname or from_userid to get a blank mbname)

We'll need an mbname_from_recipient soon as well.

Each of these resolves into the internal format for later conversion or extraction of parts.

And how you edit one of these things:

void mbname_set_localpart(mbname_t *mbname, const char *localpart);
void mbname_set_domain(mbname_t *mbname, const char *domain);
void mbname_set_isdeleted(mbname_t *mbname, time_t del);
void mbname_push_boxes(mbname_t *mbname, const char *item);
char *mbname_pop_box(mbname_t *mbname); /* free it yourself punk */
void mbname_truncate_boxes(mbname_t *mbname, size_t len);

They should all be pretty clear.  Note again, no separator - you're editing the boxes array or changing the user/domain.  All of these also nuke any internal cached intname/extname/userid representations, so they recalculate on next request.

Finally, you can 

void mbname_free(mbname_t **mbnamep);

Note that it frees the pointer, so it's always safe to call like this:

mbname_t *mbname = NULL;

if (condition) {
    mbname = mbname_from_userid(userid);
    do_stuff(mbname, ...);
}

mbname_free(&mbname);

And it will clean up and zero the mbname pointer itself.  You can even have a mbname_free(&mbname) earlier in the code, and it won't double-free.

=============

A Glossary.

Variable naming in Cyrus is crap in a lot of places, and it comes from inconsistent use of names.  So here's a thing.  Please use either exactly this variable name, or if there are more than one then descriptive derivations of them, for example:

mbname_t *oldmbname = NULL;
mbname_t *newmbname = NULL;

[...]

mbname:  This is ALWAYS an mbname_t * - it SHOULD be const (I break this in a few places though, at the moment)

intname: this is a "char *" (and again, SHOULD almost always be const).  mailboxes.db key format version of name.  SYNONYMS: mboxname, mailboxname (deprecated), name (die in a fire), mailbox (die in a fire)

extname: this is the name in some user's namespace, usually imapd_namespace or friends.  SYNOYNMS: name (deprecated, don't do this), mailbox (die in a fire).

userid: this is the full user at domain.  Right now we still have rubbish like 'proxy_userid', because 'userid' has been polluted with namespace translations.  That's going away.  userid is ALWAYS fully formed, bron.gondwana at gondwana.name.  (I haven't got a firm opinion yet on what happens if !strcasecmp(domain, config_defdomain) - but it will be consistent, whatever the final choice is).  SYNONYMS: user (deprecated)

localpart: the left hand side of userid.  bron.gondwana.  Always in external/real form.  SYNOYNMS: userid (die in a fire, see above)

domain: the left hand side of userid. gondwana.name, etc.

isdeleted: timestamp - if non zero, timestamp at which the folder was deleted.

boxes: an array of sub mailboxes.  If you need to process it separately to the main mailboxname, it should be a variable called boxes.

recipient: (coming soon in another commit) a userid with additional plus addressing.  SYNONYMS: rcpt.


It is really important to use the exact variable name where possible.  It makes code easier to understand, to copy/paste (nobody does that, right?)

There are a COUPLE of weird places that need to do mailbox name fiddling directly (I'm thinking of the partial match code for LIST \HasChildren calculation, and the fuzzy matcher in lmtpd), but otherwise - always use the mbname_set_* APIs.

This should make working with names a ton easier.  All mbname_tointernal and mbname_toexternal code is gone now.

Bron.




-- 
  Bron Gondwana
  brong at fastmail.fm


More information about the Cyrus-devel mailing list