11 /* SCACHE *scache_multi_create()
13 /* This module implements an in-memory, multi-session cache.
15 /* scache_multi_create() instantiates a session cache that
16 /* stores multiple sessions.
18 /* Fatal error: memory allocation problem;
19 /* panic: internal consistency failure.
21 /* scache(3), generic session cache API
25 /* The Secure Mailer license must be distributed with this software.
28 /* IBM T.J. Watson Research
30 /* Yorktown Heights, NY 10598, USA
37 #include <stddef.h> /* offsetof() */
40 /* Utility library. */
49 /*#define msg_verbose 1*/
55 /* Application-specific. */
58 * SCACHE_MULTI is a derived type from the SCACHE super-class.
60 * Each destination has an entry in the destination hash table, and each
61 * destination->endpoint binding is kept in a circular list under its
62 * destination hash table entry.
64 * Likewise, each endpoint has an entry in the endpoint hash table, and each
65 * endpoint->session binding is kept in a circular list under its endpoint
68 * We do not attempt to limit the number of destination or endpoint entries,
69 * nor do we attempt to limit the number of sessions. Doing so would require
70 * a write-through cache. Currently, the CTABLE cache module supports only
71 * read-through caching.
73 * This is no problem because the number of cached destinations is limited by
74 * design. Sites that specify a wild-card domain pattern, and thus cache
75 * every session in recent history, may be in for a surprise.
78 SCACHE scache
[1]; /* super-class */
79 HTABLE
*dest_cache
; /* destination->endpoint bindings */
80 HTABLE
*endp_cache
; /* endpoint->session bindings */
81 int sess_count
; /* number of cached sessions */
85 * Storage for a destination or endpoint list head. Each list head knows its
86 * own hash table entry name, so that we can remove the list when it becomes
87 * empty. List items are stored in a circular list under the list head.
90 RING ring
[1]; /* circular list linkage */
91 char *parent_key
; /* parent linkage: hash table */
92 SCACHE_MULTI
*cache
; /* parent linkage: cache */
95 #define RING_TO_MULTI_HEAD(p) RING_TO_APPL((p), SCACHE_MULTI_HEAD, ring)
98 * Storage for a destination->endpoint binding. This is an element in a
99 * circular list, whose list head specifies the destination.
102 RING ring
[1]; /* circular list linkage */
103 SCACHE_MULTI_HEAD
*head
; /* parent linkage: list head */
104 char *endp_label
; /* endpoint name */
105 char *dest_prop
; /* binding properties */
108 #define RING_TO_MULTI_DEST(p) RING_TO_APPL((p), SCACHE_MULTI_DEST, ring)
110 static void scache_multi_expire_dest(int, char *);
113 * Storage for an endpoint->session binding. This is an element in a
114 * circular list, whose list head specifies the endpoint.
117 RING ring
[1]; /* circular list linkage */
118 SCACHE_MULTI_HEAD
*head
; /* parent linkage: list head */
119 int fd
; /* cached session */
120 char *endp_prop
; /* binding properties */
123 #define RING_TO_MULTI_ENDP(p) RING_TO_APPL((p), SCACHE_MULTI_ENDP, ring)
125 static void scache_multi_expire_endp(int, char *);
128 * When deleting a circular list element, are we deleting the entire
129 * circular list, or are we removing a single list element. We need this
130 * distinction to avoid a re-entrancy problem between htable_delete() and
133 #define BOTTOM_UP 1 /* one item */
134 #define TOP_DOWN 2 /* whole list */
136 /* scache_multi_drop_endp - destroy endpoint->session binding */
138 static void scache_multi_drop_endp(SCACHE_MULTI_ENDP
*endp
, int direction
)
140 const char *myname
= "scache_multi_drop_endp";
141 SCACHE_MULTI_HEAD
*head
;
144 msg_info("%s: endp_prop=%s fd=%d", myname
,
145 endp
->endp_prop
, endp
->fd
);
150 event_cancel_timer(scache_multi_expire_endp
, (char *) endp
);
153 * In bottom-up mode, remove the list head from the endpoint hash when
154 * the list becomes empty. Otherwise, remove the endpoint->session
155 * binding from the list.
157 ring_detach(endp
->ring
);
159 head
->cache
->sess_count
--;
160 if (direction
== BOTTOM_UP
&& ring_pred(head
->ring
) == head
->ring
)
161 htable_delete(head
->cache
->endp_cache
, head
->parent_key
, myfree
);
164 * Destroy the endpoint->session binding.
166 if (endp
->fd
>= 0 && close(endp
->fd
) != 0)
167 msg_warn("%s: close(%d): %m", myname
, endp
->fd
);
168 myfree(endp
->endp_prop
);
170 myfree((char *) endp
);
173 /* scache_multi_expire_endp - event timer call-back */
175 static void scache_multi_expire_endp(int unused_event
, char *context
)
177 SCACHE_MULTI_ENDP
*endp
= (SCACHE_MULTI_ENDP
*) context
;
179 scache_multi_drop_endp(endp
, BOTTOM_UP
);
182 /* scache_multi_free_endp - hash table destructor call-back */
184 static void scache_multi_free_endp(char *ptr
)
186 SCACHE_MULTI_HEAD
*head
= (SCACHE_MULTI_HEAD
*) ptr
;
187 SCACHE_MULTI_ENDP
*endp
;
191 * Delete each endpoint->session binding in the list, then delete the
192 * list head. Note: this changes the list, so we must iterate carefully.
194 while ((ring
= ring_succ(head
->ring
)) != head
->ring
) {
195 endp
= RING_TO_MULTI_ENDP(ring
);
196 scache_multi_drop_endp(endp
, TOP_DOWN
);
198 myfree((char *) head
);
201 /* scache_multi_save_endp - save endpoint->session binding */
203 static void scache_multi_save_endp(SCACHE
*scache
, int ttl
,
204 const char *endp_label
,
205 const char *endp_prop
, int fd
)
207 const char *myname
= "scache_multi_save_endp";
208 SCACHE_MULTI
*sp
= (SCACHE_MULTI
*) scache
;
209 SCACHE_MULTI_HEAD
*head
;
210 SCACHE_MULTI_ENDP
*endp
;
213 msg_panic("%s: bad ttl: %d", myname
, ttl
);
216 * Look up or instantiate the list head with the endpoint name.
218 if ((head
= (SCACHE_MULTI_HEAD
*)
219 htable_find(sp
->endp_cache
, endp_label
)) == 0) {
220 head
= (SCACHE_MULTI_HEAD
*) mymalloc(sizeof(*head
));
221 ring_init(head
->ring
);
223 htable_enter(sp
->endp_cache
, endp_label
, (char *) head
)->key
;
228 * Add the endpoint->session binding to the list. There can never be a
229 * duplicate, because each session must have a different file descriptor.
231 endp
= (SCACHE_MULTI_ENDP
*) mymalloc(sizeof(*endp
));
234 endp
->endp_prop
= mystrdup(endp_prop
);
235 ring_prepend(head
->ring
, endp
->ring
);
239 * Make sure this binding will go away eventually.
241 event_request_timer(scache_multi_expire_endp
, (char *) endp
, ttl
);
244 msg_info("%s: endp_label=%s -> endp_prop=%s fd=%d",
245 myname
, endp_label
, endp_prop
, fd
);
248 /* scache_multi_find_endp - look up session for named endpoint */
250 static int scache_multi_find_endp(SCACHE
*scache
, const char *endp_label
,
253 const char *myname
= "scache_multi_find_endp";
254 SCACHE_MULTI
*sp
= (SCACHE_MULTI
*) scache
;
255 SCACHE_MULTI_HEAD
*head
;
256 SCACHE_MULTI_ENDP
*endp
;
261 * Look up the list head with the endpoint name.
263 if ((head
= (SCACHE_MULTI_HEAD
*)
264 htable_find(sp
->endp_cache
, endp_label
)) == 0) {
266 msg_info("%s: no endpoint cache: endp_label=%s",
272 * Use the first available session. Remove the session from the cache
273 * because we're giving it to someone else.
275 if ((ring
= ring_succ(head
->ring
)) != head
->ring
) {
276 endp
= RING_TO_MULTI_ENDP(ring
);
279 vstring_strcpy(endp_prop
, endp
->endp_prop
);
281 msg_info("%s: found: endp_label=%s -> endp_prop=%s fd=%d",
282 myname
, endp_label
, endp
->endp_prop
, fd
);
283 scache_multi_drop_endp(endp
, BOTTOM_UP
);
287 msg_info("%s: not found: endp_label=%s", myname
, endp_label
);
291 /* scache_multi_drop_dest - delete destination->endpoint binding */
293 static void scache_multi_drop_dest(SCACHE_MULTI_DEST
*dest
, int direction
)
295 const char *myname
= "scache_multi_drop_dest";
296 SCACHE_MULTI_HEAD
*head
;
299 msg_info("%s: dest_prop=%s endp_label=%s",
300 myname
, dest
->dest_prop
, dest
->endp_label
);
305 event_cancel_timer(scache_multi_expire_dest
, (char *) dest
);
308 * In bottom-up mode, remove the list head from the destination hash when
309 * the list becomes empty. Otherwise, remove the destination->endpoint
310 * binding from the list.
312 ring_detach(dest
->ring
);
314 if (direction
== BOTTOM_UP
&& ring_pred(head
->ring
) == head
->ring
)
315 htable_delete(head
->cache
->dest_cache
, head
->parent_key
, myfree
);
318 * Destroy the destination->endpoint binding.
320 myfree(dest
->dest_prop
);
321 myfree(dest
->endp_label
);
323 myfree((char *) dest
);
326 /* scache_multi_expire_dest - event timer call-back */
328 static void scache_multi_expire_dest(int unused_event
, char *context
)
330 SCACHE_MULTI_DEST
*dest
= (SCACHE_MULTI_DEST
*) context
;
332 scache_multi_drop_dest(dest
, BOTTOM_UP
);
335 /* scache_multi_free_dest - hash table destructor call-back */
337 static void scache_multi_free_dest(char *ptr
)
339 SCACHE_MULTI_HEAD
*head
= (SCACHE_MULTI_HEAD
*) ptr
;
340 SCACHE_MULTI_DEST
*dest
;
344 * Delete each destination->endpoint binding in the list, then delete the
345 * list head. Note: this changes the list, so we must iterate carefully.
347 while ((ring
= ring_succ(head
->ring
)) != head
->ring
) {
348 dest
= RING_TO_MULTI_DEST(ring
);
349 scache_multi_drop_dest(dest
, TOP_DOWN
);
351 myfree((char *) head
);
354 /* scache_multi_save_dest - save destination->endpoint binding */
356 static void scache_multi_save_dest(SCACHE
*scache
, int ttl
,
357 const char *dest_label
,
358 const char *dest_prop
,
359 const char *endp_label
)
361 const char *myname
= "scache_multi_save_dest";
362 SCACHE_MULTI
*sp
= (SCACHE_MULTI
*) scache
;
363 SCACHE_MULTI_HEAD
*head
;
364 SCACHE_MULTI_DEST
*dest
;
369 msg_panic("%s: bad ttl: %d", myname
, ttl
);
372 * Look up or instantiate the list head with the destination name.
374 if ((head
= (SCACHE_MULTI_HEAD
*)
375 htable_find(sp
->dest_cache
, dest_label
)) == 0) {
376 head
= (SCACHE_MULTI_HEAD
*) mymalloc(sizeof(*head
));
377 ring_init(head
->ring
);
379 htable_enter(sp
->dest_cache
, dest_label
, (char *) head
)->key
;
384 * Look up or instantiate the destination->endpoint binding. Update the
385 * expiration time if this destination->endpoint binding already exists.
387 RING_FOREACH(ring
, head
->ring
) {
388 dest
= RING_TO_MULTI_DEST(ring
);
389 if (strcmp(dest
->endp_label
, endp_label
) == 0
390 && strcmp(dest
->dest_prop
, dest_prop
) == 0) {
396 dest
= (SCACHE_MULTI_DEST
*) mymalloc(sizeof(*dest
));
398 dest
->endp_label
= mystrdup(endp_label
);
399 dest
->dest_prop
= mystrdup(dest_prop
);
400 ring_prepend(head
->ring
, dest
->ring
);
404 * Make sure this binding will go away eventually.
406 event_request_timer(scache_multi_expire_dest
, (char *) dest
, ttl
);
409 msg_info("%s: dest_label=%s -> dest_prop=%s endp_label=%s%s",
410 myname
, dest_label
, dest_prop
, endp_label
,
411 refresh
? " (refreshed)" : "");
414 /* scache_multi_find_dest - look up session for named destination */
416 static int scache_multi_find_dest(SCACHE
*scache
, const char *dest_label
,
420 const char *myname
= "scache_multi_find_dest";
421 SCACHE_MULTI
*sp
= (SCACHE_MULTI
*) scache
;
422 SCACHE_MULTI_HEAD
*head
;
423 SCACHE_MULTI_DEST
*dest
;
428 * Look up the list head with the destination name.
430 if ((head
= (SCACHE_MULTI_HEAD
*)
431 htable_find(sp
->dest_cache
, dest_label
)) == 0) {
433 msg_info("%s: no destination cache: dest_label=%s",
439 * Search endpoints for the first available session.
441 RING_FOREACH(ring
, head
->ring
) {
442 dest
= RING_TO_MULTI_DEST(ring
);
443 fd
= scache_multi_find_endp(scache
, dest
->endp_label
, endp_prop
);
445 vstring_strcpy(dest_prop
, dest
->dest_prop
);
450 msg_info("%s: not found: dest_label=%s", myname
, dest_label
);
454 /* scache_multi_size - size of multi-element cache object */
456 static void scache_multi_size(SCACHE
*scache
, SCACHE_SIZE
*size
)
458 SCACHE_MULTI
*sp
= (SCACHE_MULTI
*) scache
;
460 size
->dest_count
= sp
->dest_cache
->used
;
461 size
->endp_count
= sp
->endp_cache
->used
;
462 size
->sess_count
= sp
->sess_count
;
465 /* scache_multi_free - destroy multi-element cache object */
467 static void scache_multi_free(SCACHE
*scache
)
469 SCACHE_MULTI
*sp
= (SCACHE_MULTI
*) scache
;
471 htable_free(sp
->dest_cache
, scache_multi_free_dest
);
472 htable_free(sp
->endp_cache
, scache_multi_free_endp
);
477 /* scache_multi_create - initialize */
479 SCACHE
*scache_multi_create(void)
481 SCACHE_MULTI
*sp
= (SCACHE_MULTI
*) mymalloc(sizeof(*sp
));
483 sp
->scache
->save_endp
= scache_multi_save_endp
;
484 sp
->scache
->find_endp
= scache_multi_find_endp
;
485 sp
->scache
->save_dest
= scache_multi_save_dest
;
486 sp
->scache
->find_dest
= scache_multi_find_dest
;
487 sp
->scache
->size
= scache_multi_size
;
488 sp
->scache
->free
= scache_multi_free
;
490 sp
->dest_cache
= htable_create(1);
491 sp
->endp_cache
= htable_create(1);