1 /* 2 ** Copyright 2003 Double Precision, Inc. 3 ** See COPYING for distribution information. 4 */ 5 6 #ifndef maildirkeywords_h 7 #define maildirkeywords_h 8 9 #include "config.h" 10 #include <stdio.h> 11 #include <string.h> 12 13 #ifdef __cplusplus 14 extern "C" { 15 #endif 16 17 18 /* 19 ** IMAP keywords. This data structure is designed so that it is possible to: 20 ** A) Find all messages that have the given keyword, 21 ** B) Find all keywords that are set for the given message, 22 ** C) Optimize memory usage, even with many keywords set for many msgs. 23 */ 24 25 /* A doubly-linked list of keywords */ 26 27 struct libmail_keywordEntry { 28 struct libmail_keywordEntry *prev, *next; /* Doubly-linked list */ 29 30 31 #define keywordName(ke) ((char *)((ke)+1)) 32 33 union { 34 void *userPtr; /* Misc ptr */ 35 unsigned long userNum; /* Or misc num */ 36 } u; 37 38 struct libmail_kwMessageEntry *firstMsg, *lastMsg; 39 /* Doubly-linked list of messages that use this keyword */ 40 }; 41 42 43 /* The main keyword hash table */ 44 45 struct libmail_kwHashtable { 46 47 struct libmail_keywordEntry heads[43], tails[43]; 48 /* Dummy head/tail nodes for each hash bucket */ 49 50 int keywordAddedRemoved; 51 }; 52 53 struct libmail_kwMessageEntry { 54 struct libmail_kwMessageEntry *next, *prev; 55 /* Doubly-linked list of all keywords set for this message */ 56 57 struct libmail_kwMessageEntry *keywordNext, *keywordPrev; 58 /* Doubly-linked list of all entries for the same keyword */ 59 60 struct libmail_keywordEntry *libmail_keywordEntryPtr; /* The keyword */ 61 struct libmail_kwMessage *libmail_kwMessagePtr; /* The message */ 62 }; 63 64 struct libmail_kwMessage { 65 66 struct libmail_kwMessageEntry *firstEntry, *lastEntry; 67 /* Doubly-linked list of all keywords set for this message */ 68 69 union { 70 void *userPtr; /* Misc ptr */ 71 unsigned long userNum; /* Or misc num */ 72 } u; 73 }; 74 75 /* 76 ** Initialize a libmail_kwHashtable 77 */ 78 void libmail_kwhInit(struct libmail_kwHashtable *); 79 80 81 /* 82 ** Returns 0 if libmail_kwHashtable is empty. Return -1, errno=EIO if it's 83 ** not (sanity check). 84 */ 85 int libmail_kwhCheck(struct libmail_kwHashtable *); 86 87 /* 88 ** Enumerate all defined keywords. 89 */ 90 int libmail_kwEnumerate(struct libmail_kwHashtable *h, 91 int (*callback_func)(struct libmail_keywordEntry *, 92 void *), 93 void *callback_arg); 94 95 /* 96 ** Find a keyword in the hashtable, optionally create it. If createIfNew, 97 ** then we MUST add the returned result to some keywordMessage, else there'll 98 ** be a cleanup problem. 99 */ 100 struct libmail_keywordEntry * 101 libmail_kweFind(struct libmail_kwHashtable *ht, 102 const char *name, 103 int createIfNew); 104 105 106 extern const char *libmail_kwVerbotten; 107 /* 108 ** Optional list of chars prohibited in keyword names. They get automagically 109 ** replaced with underscores. 110 */ 111 extern int libmail_kwCaseSensitive; 112 /* 113 ** Non zero if keyword names are case sensitive. 114 */ 115 116 /* 117 ** Clear a reference to a particular keyword, from a particular message. 118 */ 119 void libmail_kweClear(struct libmail_keywordEntry *); 120 121 /* 122 ** Create an abstract message object, with no keywords currently defined. 123 */ 124 struct libmail_kwMessage *libmail_kwmCreate(); 125 126 /* 127 ** Destroy the message object, automatically removing any keywords that were 128 ** used by the object. 129 */ 130 void libmail_kwmDestroy(struct libmail_kwMessage *); 131 132 /* 133 ** Link a keyword to a message. 134 */ 135 int libmail_kwmSet(struct libmail_kwMessage *, struct libmail_keywordEntry *); 136 137 /* 138 ** Link a keyword to a message, by keyword's name. 139 */ 140 int libmail_kwmSetName(struct libmail_kwHashtable *, 141 struct libmail_kwMessage *, const char *); 142 143 /* 144 ** Compare two messages, return 0 if they have the same keywords. 145 */ 146 int libmail_kwmCmp(struct libmail_kwMessage *, 147 struct libmail_kwMessage *); 148 149 /* 150 ** Clear a keyword from a message. 151 */ 152 int libmail_kwmClearName(struct libmail_kwMessage *, const char *); 153 /* 154 ** libmail_kwmClearName is for INTERNAL USE ONLY -- the name doesn't get vetted 155 ** by libmail_kweFind. 156 */ 157 158 /* 159 ** Clear a keyword from a message, the public version. 160 */ 161 int libmail_kwmClear(struct libmail_kwMessage *, struct libmail_keywordEntry *); 162 /* 163 ** 164 */ 165 int libmail_kwmClearEntry(struct libmail_kwMessageEntry *); 166 167 168 /***************************************************************************** 169 170 The above is low-level stuff. And now, a high level maildir storage API: 171 172 *****************************************************************************/ 173 174 /* 175 ** Read keywords from a maildir. The application presumably has read and 176 ** compiled a list of messages it found in the maildir's cur (and new) 177 ** directories. 178 ** 179 ** The function maildir_kwRead() will now read the keywords associated with 180 ** each message. How the application maintains the list of messages in the 181 ** maildir is irrelevant. The application simply needs to allocate a pointer 182 ** to a libmail_kwMessage structure, one pointer for each message in the 183 ** maildir. Each pointer must be initialized to a NULL, and the application 184 ** provides a set of callback functions, as defined below, that return 185 ** a pointer to this pointer (pay attention now), given the filename. 186 ** maildir_kwRead() invokes the callback functions, as appropriate, while 187 ** it's doing its business. 188 ** 189 ** There's other callback functions too, so let's get to business. 190 ** The application must initialize the following structure before calling 191 ** maildir_kwRead(). This is where the pointers to all callback functions 192 ** are provided: 193 */ 194 195 struct maildir_kwReadInfo { 196 197 struct libmail_kwMessage **(*findMessageByFilename)(const char 198 *filename, 199 int autocreate, 200 size_t *indexNum, 201 void *voidarg); 202 /* 203 ** Return a pointer to a libmail_kwMessage * that's associated with 204 ** the message whose name is 'filename'. 'filename' will not have 205 ** :2, and the message's flags, so the application needs to be 206 ** careful. 207 ** 208 ** All libmail_kwMessage *s are initially NULL. If autocreate is not 209 ** zero, the application must use libmail_kwmCreate() to initialize 210 ** the pointer, before returning. Otherwise, the application should 211 ** return a pointer to a NULL libmail_kwMessage *. 212 ** 213 ** The application may use libmail_kwMessage.u for its own purposes. 214 ** 215 ** The application should return NULL if it can't find 'filename' 216 ** in its list of messages in the maildir. That is a defined 217 ** possibility, and occur in certain race conditions (which are 218 ** properly handled, of course). 219 ** 220 ** If indexNum is not NULL, the application should set *indexNum to 221 ** the found message's index (if the application does not return NULL). 222 ** All messages the application has must be consecutively numbered, 223 ** beginning with 0 and up to, but not including, whatever the 224 ** application returns in getMessageCount(). 225 */ 226 227 size_t (*getMessageCount)(void *voidarg); 228 /* 229 ** The application should return the number of messages it thinks 230 ** there are in the maildir. 231 */ 232 233 struct libmail_kwMessage **(*findMessageByIndex)(size_t indexNum, 234 int autocreate, 235 void *voidarg); 236 /* 237 ** This one is like the findMessageByFilename() callback, except that 238 ** instead of filename the applicationg gets indexNum which ranges 239 ** between 0 and getMessageCount()-1. 240 ** The return code from this callback is identical to the return code 241 ** from findMessageByFilename(), and autocreate's semantics are also 242 ** the same. 243 */ 244 245 const char *(*getMessageFilename)(size_t n, void *voidarg); 246 /* 247 ** The application should return the filename for message #n. The 248 ** application may or may not include :2, in the returned ptr. 249 */ 250 251 struct libmail_kwHashtable * (*getKeywordHashtable)(void *voidarg); 252 /* 253 ** The application should return the libmail_kwHashtable that it 254 ** allocated to store all the keyword stuff. Read keywords are 255 ** allocated from this hashtable. 256 */ 257 258 void (*updateKeywords)(size_t n, struct libmail_kwMessage *kw, 259 void *voidarg); 260 /* 261 ** The updateKeywords callback gets invoked by maildir_kwRead() 262 ** if it needs to throw out the list of keywords it already read for 263 ** a given message, and replace it, instead, with another set of 264 ** keywords. This can happen in certain circumstances. 265 ** 266 ** The application should retrieve the libmail_kwMessage pointer for 267 ** message #n. It may or may not be null. If it's not null, the 268 ** application must use libmail_kwmDestroy(). Then, afterwards, 269 ** the application should save 'kw' as the new pointer. 270 ** 271 ** This callback is provided so that the application may save whatever 272 ** it wants to save in kw->u.userPtr or kw->u.userNum, because 'kw' 273 ** was created by libmail_kwRead(), and not one of the two findMessage 274 ** callbacks. 275 */ 276 277 void *voidarg; 278 /* 279 ** All of the above callbacks receive this voidarg as their last 280 ** argument. 281 */ 282 283 int tryagain; 284 /* 285 ** libmail_kwRead() returns 0 for success, or -1 for a system failure 286 ** (check errno). 287 ** 288 ** If libmail_kwRead() returned 0, the application MUST check 289 ** tryagain. 290 ** 291 ** If tryagain is not 0, the application MUST: 292 ** A) Take any non-NULL libmail_kwMessage pointers that are 293 ** associated with each message in the maildir, use 294 ** libmail_kwmDestroy() to blow them away, and reset each 295 ** pointer to NULL. 296 ** 297 ** B) Invoke libmail_kwRead() again. 298 ** 299 ** A non-0 tryagain indicates a recoverable race condition. 300 */ 301 302 303 /* Everything below is internal */ 304 305 int updateNeeded; 306 int errorOccured; 307 }; 308 309 310 int maildir_kwRead(const char *maildir, 311 struct maildir_kwReadInfo *rki); 312 313 /* 314 ** maildir_kwSave saves new keywords for a particular message: 315 */ 316 317 int maildir_kwSave(const char *maildir, /* The maildir */ 318 const char *filename, 319 /* The message. :2, is allowed, and ignored */ 320 321 struct libmail_kwMessage *newKeyword, 322 /* New keywords. The ptr may be NULL */ 323 324 char **tmpname, 325 char **newname, 326 327 int tryAtomic); 328 329 int maildir_kwSaveArray(const char *maildir, 330 const char *filename, 331 const char **flags, 332 char **tmpname, 333 char **newname, 334 int tryAtomic); 335 336 /* 337 ** maildir_kwSave returns -1 for an error. If it return 0, it will initialize 338 ** *tmpname and *newname, both will be full path filenames. The application 339 ** needs to simply call rename() with both filenames, and free() them, to 340 ** effect the change. Example: 341 ** 342 ** char *tmpname, *newname; 343 ** 344 ** if (maildir_kwSave( ..., &tmpname, &newname) == 0) 345 ** { 346 ** rename(tmpname, newname); 347 ** 348 ** free(tmpname); 349 ** free(newname); 350 ** } 351 ** 352 ** Of course, rename()s return code should also be checked. 353 ** 354 ** If 'tryAtomic' is non-zero, the semantics are going to be slightly 355 ** different. tryAtomic is non-zero when we want to update keywords 356 ** atomically. To do that, first, use maildir_kwRead() (or, most likely 357 ** maildir_kwgReadMaildir) to read the existing keywords, update the keywords 358 ** for the particular message, use maildir_kwSave(), but instead of rename() 359 ** use link(). Whether link succeeds or not, use unlink(tmpname) in any 360 ** case. If link() failed with EEXIST, we had a race condition, so try 361 ** again. 362 ** Note - in NFS environments, must check not only that links succeeds, but 363 ** if stat-ing the tmpfile the number of links also must be 2. 364 */ 365 366 /* 367 ** Set libmail_kwEnabled to ZERO in order to silently disable all maildir 368 ** keyword functionality. It's optional in Courier-IMAP. Setting this flag 369 ** to zero disables all actual keyword read/write functions, however all the 370 ** necessary data still gets created (namely the courierimapkeywords 371 ** subdirectory. 372 */ 373 374 extern int libmail_kwEnabled; 375 376 377 /* 378 ** The following functions are "semi-internal". 379 ** 380 ** maildir_kwExport() uses the same struct maildir_kwReadInfo, to "export" 381 ** the list of keywords assigned to all messages into a file. 382 ** 383 ** maildir_kwImport() imports the saved keyword list. 384 ** 385 ** These functions are meant to save a "snapshot" of the keywords into a 386 ** flag file, nothing else. 387 */ 388 389 int maildir_kwExport(FILE *, struct maildir_kwReadInfo *); 390 int maildir_kwImport(FILE *, struct maildir_kwReadInfo *); 391 392 393 /**************************************************************************** 394 395 Generic maildir_kwRead implementation. 396 397 ****************************************************************************/ 398 399 struct libmail_kwGeneric { 400 401 struct libmail_kwHashtable kwHashTable; 402 403 size_t nMessages; 404 405 struct libmail_kwGenericEntry **messages; 406 int messagesValid; 407 408 struct libmail_kwGenericEntry *messageHashTable[99]; 409 }; 410 411 struct libmail_kwGenericEntry { 412 413 struct libmail_kwGenericEntry *next; /* On the hash table */ 414 415 char *filename; 416 size_t messageNum; 417 struct libmail_kwMessage *keywords; 418 }; 419 420 void libmail_kwgInit(struct libmail_kwGeneric *g); 421 int libmail_kwgDestroy(struct libmail_kwGeneric *g); 422 int libmail_kwgReadMaildir(struct libmail_kwGeneric *g, 423 const char *maildir); 424 425 struct libmail_kwGenericEntry * 426 libmail_kwgFindByName(struct libmail_kwGeneric *g, const char *filename); 427 428 struct libmail_kwGenericEntry * 429 libmail_kwgFindByIndex(struct libmail_kwGeneric *g, size_t n); 430 431 #ifdef __cplusplus 432 } 433 434 #include <set> 435 #include <string> 436 437 /* Some C++ wrappers */ 438 439 namespace mail { 440 namespace keywords { 441 442 class Hashtable { 443 444 public: 445 struct libmail_kwHashtable kwh; 446 447 Hashtable(); 448 ~Hashtable(); 449 450 Hashtable(const Hashtable &); /* UNDEFINED */ 451 Hashtable &operator=(const Hashtable &); 452 /* UNDEFINED */ 453 }; 454 455 456 class MessageBase { 457 public: 458 struct libmail_kwMessage *km; 459 size_t refCnt; 460 461 MessageBase(); 462 ~MessageBase(); 463 464 MessageBase(const MessageBase &); /* UNDEFINED */ 465 MessageBase &operator=(const MessageBase *); 466 /* UNDEFINED */ 467 }; 468 469 class Message { 470 471 MessageBase *b; 472 473 bool copyOnWrite(); 474 475 public: 476 Message(); 477 ~Message(); 478 479 Message(const Message &); 480 Message &operator=(const Message &); 481 482 void getFlags(std::set<std::string> &) const; 483 /* Extract list of flags */ 484 485 bool setFlags(Hashtable &, 486 const std::set<std::string> &); 487 /* Set the flags. */ 488 489 bool addFlag(Hashtable &, std::string); 490 bool remFlag(std::string); 491 empty()492 bool empty() const { 493 return b->km == NULL 494 || b->km->firstEntry == NULL; 495 } 496 497 bool operator==(struct libmail_kwMessage *m) const { 498 return b->km == NULL ? 499 m == NULL || m->firstEntry == NULL: 500 m && libmail_kwmCmp(b->km, m) == 0; 501 } 502 503 bool operator !=(struct libmail_kwMessage *m) const { 504 return ! operator==(m); 505 } 506 replace(struct libmail_kwMessage * p)507 void replace(struct libmail_kwMessage *p) 508 { 509 if (b->km) 510 libmail_kwmDestroy(b->km); 511 b->km=p; 512 } 513 514 }; 515 } 516 } 517 518 519 /* BONUS: */ 520 521 int maildir_kwSave(const char *maildir, 522 const char *filename, 523 std::set<std::string> keywords, 524 525 char **tmpname, 526 char **newname, 527 528 int tryAtomic); 529 530 #endif 531 532 #endif 533