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