1 /*********************************************************************************************************
2 * Software License Agreement (BSD License) *
3 * Author: Sebastien Decugis <sdecugis@freediameter.net> *
4 * *
5 * Copyright (c) 2019, WIDE Project and NICT *
6 * All rights reserved. *
7 * *
8 * Redistribution and use of this software in source and binary forms, with or without modification, are *
9 * permitted provided that the following conditions are met: *
10 * *
11 * * Redistributions of source code must retain the above *
12 * copyright notice, this list of conditions and the *
13 * following disclaimer. *
14 * *
15 * * Redistributions in binary form must reproduce the above *
16 * copyright notice, this list of conditions and the *
17 * following disclaimer in the documentation and/or other *
18 * materials provided with the distribution. *
19 * *
20 * * Neither the name of the WIDE Project or NICT nor the *
21 * names of its contributors may be used to endorse or *
22 * promote products derived from this software without *
23 * specific prior written permission of WIDE Project and *
24 * NICT. *
25 * *
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
27 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
28 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
29 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
30 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
32 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
33 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
34 *********************************************************************************************************/
35
36 /* Sessions module.
37 *
38 * Basic functionalities to help implementing User sessions state machines from RFC3588.
39 */
40
41 #include "fdproto-internal.h"
42
43 /*********************** Parameters **********************/
44
45 /* Size of the hash table containing the session objects (pow of 2. ex: 6 => 2^6 = 64). must be between 0 and 31. */
46 #ifndef SESS_HASH_SIZE
47 #define SESS_HASH_SIZE 6
48 #endif /* SESS_HASH_SIZE */
49
50 /* Default lifetime of a session, in seconds. (31 days = 2678400 seconds) */
51 #ifndef SESS_DEFAULT_LIFETIME
52 #define SESS_DEFAULT_LIFETIME 2678400
53 #endif /* SESS_DEFAULT_LIFETIME */
54
55 /********************** /Parameters **********************/
56
57 /* Eyescatchers definitions */
58 #define SH_EYEC 0x53554AD1
59 #define SD_EYEC 0x5355D474
60 #define SI_EYEC 0x53551D
61
62 /* Macro to check an object is valid */
63 #define VALIDATE_SH( _obj ) ( ((_obj) != NULL) && ( ((struct session_handler *)(_obj))->eyec == SH_EYEC) )
64 #define VALIDATE_SI( _obj ) ( ((_obj) != NULL) && ( ((struct session *)(_obj))->eyec == SI_EYEC) )
65
66
67 /* Handlers registered by users of the session module */
68 struct session_handler {
69 int eyec; /* An eye catcher also used to ensure the object is valid, must be SH_EYEC */
70 int id; /* A unique integer to identify this handler */
71 void (*cleanup)(struct sess_state *, os0_t, void *); /* The cleanup function to be called for cleaning a state */
72 session_state_dump state_dump; /* dumper function */
73 void *opaque; /* a value that is passed as is to the cleanup callback */
74 };
75
76 static int hdl_id = 0; /* A global counter to initialize the id field */
77 static pthread_mutex_t hdl_lock = PTHREAD_MUTEX_INITIALIZER; /* lock to protect hdl_id; we could use atomic operations otherwise (less portable) */
78
79
80 /* Data structures linked from the sessions, containing the applications states */
81 struct state {
82 int eyec; /* Must be SD_EYEC */
83 struct sess_state *state; /* The state registered by the application, never NULL (or the whole object is deleted) */
84 struct fd_list chain; /* Chaining in the list of session's states ordered by hdl->id */
85 union {
86 struct session_handler *hdl; /* The handler for which this state was registered */
87 os0_t sid; /* For deleted state, the sid of the session it belong to */
88 };
89 };
90
91 /* Session object, one for each value of Session-Id AVP */
92 struct session {
93 int eyec; /* Eyecatcher, SI_EYEC */
94
95 os0_t sid; /* The \0-terminated Session-Id */
96 size_t sidlen; /* cached length of sid */
97 uint32_t hash; /* computed hash of sid */
98 struct fd_list chain_h;/* chaining in the hash table of sessions. */
99
100 struct timespec timeout;/* Timeout date for the session */
101 struct fd_list expire; /* List of expiring sessions, ordered by timeouts. */
102
103 pthread_mutex_t stlock; /* A lock to protect the list of states associated with this session */
104 struct fd_list states; /* Sentinel for the list of states of this session. */
105 int msg_cnt;/* Reference counter for the messages pointing to this session */
106 int is_destroyed; /* boolean telling if fd_sess_detroy has been called on this */
107 };
108
109 /* Sessions hash table, to allow fast sid to session retrieval */
110 static struct {
111 struct fd_list sentinel; /* sentinel element for this sublist. The sublist is ordered by hash value, then fd_os_cmp(sid). */
112 pthread_mutex_t lock; /* the mutex for this sublist -- we might probably change it to rwlock for a little optimization */
113 } sess_hash [ 1 << SESS_HASH_SIZE ] ;
114 #define H_MASK( __hash ) ((__hash) & (( 1 << SESS_HASH_SIZE ) - 1))
115 #define H_LIST( _hash ) (&(sess_hash[H_MASK(_hash)].sentinel))
116 #define H_LOCK( _hash ) (&(sess_hash[H_MASK(_hash)].lock ))
117
118 static uint32_t sess_cnt = 0; /* counts all active session (that are in the expiry list) */
119
120 /* The following are used to generate sid values that are eternaly unique */
121 static uint32_t sid_h; /* initialized to the current time in fd_sess_init */
122 static uint32_t sid_l; /* incremented each time a session id is created */
123 static pthread_mutex_t sid_lock = PTHREAD_MUTEX_INITIALIZER;
124
125 /* Expiring sessions management */
126 static struct fd_list exp_sentinel = FD_LIST_INITIALIZER(exp_sentinel); /* list of sessions ordered by their timeout date */
127 static pthread_mutex_t exp_lock = PTHREAD_MUTEX_INITIALIZER; /* lock protecting the list. */
128 static pthread_cond_t exp_cond = PTHREAD_COND_INITIALIZER; /* condvar used by the expiry mechainsm. */
129 static pthread_t exp_thr = (pthread_t)NULL; /* The expiry thread that handles cleanup of expired sessions */
130
131 /* Hierarchy of the locks, to avoid deadlocks:
132 * hash lock > state lock > expiry lock
133 * i.e. state lock can be taken while holding the hash lock, but not while holding the expiry lock.
134 * As well, the hash lock cannot be taken while holding a state lock.
135 */
136
137 /********************************************************************************************************/
138
139 /* Initialize a session object. It is not linked now. sid must be already malloc'ed. The hash has already been computed. */
new_session(os0_t sid,size_t sidlen,uint32_t hash)140 static struct session * new_session(os0_t sid, size_t sidlen, uint32_t hash)
141 {
142 struct session * sess;
143
144 TRACE_ENTRY("%p %zd", sid, sidlen);
145 CHECK_PARAMS_DO( sid && sidlen, return NULL );
146
147 CHECK_MALLOC_DO( sess = malloc(sizeof(struct session)), return NULL );
148 memset(sess, 0, sizeof(struct session));
149
150 sess->eyec = SI_EYEC;
151
152 sess->sid = sid;
153 sess->sidlen = sidlen;
154 sess->hash = hash;
155 fd_list_init(&sess->chain_h, sess);
156
157 CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &sess->timeout), return NULL );
158 sess->timeout.tv_sec += SESS_DEFAULT_LIFETIME;
159 fd_list_init(&sess->expire, sess);
160
161 CHECK_POSIX_DO( pthread_mutex_init(&sess->stlock, NULL), return NULL );
162 fd_list_init(&sess->states, sess);
163
164 return sess;
165 }
166
167 /* destroy the session object. It should really be already unlinked... */
del_session(struct session * s)168 static void del_session(struct session * s)
169 {
170 ASSERT(FD_IS_LIST_EMPTY(&s->states));
171 free(s->sid);
172 fd_list_unlink(&s->chain_h);
173 fd_list_unlink(&s->expire);
174 CHECK_POSIX_DO( pthread_mutex_destroy(&s->stlock), /* continue */ );
175 free(s);
176 }
177
178 /* The expiry thread */
exp_fct(void * arg)179 static void * exp_fct(void * arg)
180 {
181 fd_log_threadname ( "Session/expire" );
182 TRACE_ENTRY( "" );
183
184
185 do {
186 struct timespec now;
187 struct session * first;
188
189 CHECK_POSIX_DO( pthread_mutex_lock(&exp_lock), break );
190 pthread_cleanup_push( fd_cleanup_mutex, &exp_lock );
191 again:
192 /* Check if there are expiring sessions available */
193 if (FD_IS_LIST_EMPTY(&exp_sentinel)) {
194 /* Just wait for a change or cancelation */
195 CHECK_POSIX_DO( pthread_cond_wait( &exp_cond, &exp_lock ), break /* this might not pop the cleanup handler, but since we ASSERT(0), it is not the big issue... */ );
196 /* Restart the loop on wakeup */
197 goto again;
198 }
199
200 /* Get the pointer to the session that expires first */
201 first = (struct session *)(exp_sentinel.next->o);
202 ASSERT( VALIDATE_SI(first) );
203
204 /* Get the current time */
205 CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &now), break );
206
207 /* If first session is not expired, we just wait until it happens */
208 if ( TS_IS_INFERIOR( &now, &first->timeout ) ) {
209 int ret;
210
211 ret = pthread_cond_timedwait(&exp_cond, &exp_lock, &first->timeout);
212 switch (ret) {
213 case 0:
214 case ETIMEDOUT:
215 /* on wakeup or time-out, loop */
216 goto again;
217 case EINVAL:
218 if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
219 break;
220 }
221 if (TS_IS_INFERIOR(&now, &first->timeout)) {
222 TRACE_DEBUG(FULL, "'pthread_cond_timedwait(&exp_cond, &exp_lock, &first->timeout)' : timer expired before loop could start");
223 goto again;
224 }
225 /* FALLTHROUGH */
226 default:
227 TRACE_ERROR("ERROR: in 'pthread_cond_timedwait(&exp_cond, &exp_lock, &first->timeout)' :\t%s", strerror(ret));
228 break;
229 }
230 }
231
232 /* Now, the first session in the list is expired; destroy it */
233 pthread_cleanup_pop( 0 );
234 CHECK_POSIX_DO( pthread_mutex_unlock(&exp_lock), break );
235
236 CHECK_FCT_DO( fd_sess_destroy( &first ), break );
237
238 } while (1);
239
240 TRACE_DEBUG(INFO, "A system error occurred in session module! Expiry thread is terminating...");
241 ASSERT(0);
242 return NULL;
243 }
244
245
246
247 /********************************************************************************************************/
248
249 /* Initialize the session module */
fd_sess_init(void)250 int fd_sess_init(void)
251 {
252 int i;
253
254 TRACE_ENTRY( "" );
255
256 /* Initialize the global counters */
257 sid_h = (uint32_t) time(NULL);
258 sid_l = 0;
259
260 /* Initialize the hash table */
261 for (i = 0; i < sizeof(sess_hash) / sizeof(sess_hash[0]); i++) {
262 fd_list_init( &sess_hash[i].sentinel, NULL );
263 CHECK_POSIX( pthread_mutex_init(&sess_hash[i].lock, NULL) );
264 }
265
266 return 0;
267 }
268
269 /* Run this when initializations are complete. */
fd_sess_start(void)270 int fd_sess_start(void)
271 {
272 /* Start session garbage collector (expiry) */
273 CHECK_POSIX( pthread_create(&exp_thr, NULL, exp_fct, NULL) );
274
275 return 0;
276 }
277
278 /* Terminate */
fd_sess_fini(void)279 void fd_sess_fini(void)
280 {
281 TRACE_ENTRY("");
282 CHECK_FCT_DO( fd_thr_term(&exp_thr), /* continue */ );
283
284 /* Destroy all sessions in the hash table, and the hash table itself? -- How to do it without a race condition ? */
285
286 return;
287 }
288
289 /* Create a new handler */
fd_sess_handler_create(struct session_handler ** handler,void (* cleanup)(struct sess_state *,os0_t,void *),session_state_dump dumper,void * opaque)290 int fd_sess_handler_create ( struct session_handler ** handler, void (*cleanup)(struct sess_state *, os0_t, void *), session_state_dump dumper, void * opaque )
291 {
292 struct session_handler *new;
293
294 TRACE_ENTRY("%p %p", handler, cleanup);
295
296 CHECK_PARAMS( handler && cleanup );
297
298 CHECK_MALLOC( new = malloc(sizeof(struct session_handler)) );
299 memset(new, 0, sizeof(struct session_handler));
300
301 CHECK_POSIX( pthread_mutex_lock(&hdl_lock) );
302 new->id = ++hdl_id;
303 CHECK_POSIX( pthread_mutex_unlock(&hdl_lock) );
304
305 new->eyec = SH_EYEC;
306 new->cleanup = cleanup;
307 new->state_dump = dumper;
308 new->opaque = opaque;
309
310 *handler = new;
311 return 0;
312 }
313
314 /* Destroy a handler, and all states attached to this handler. This operation is very slow but we don't care since it's rarely used.
315 * Note that it's better to call this function after all sessions have been deleted... */
fd_sess_handler_destroy(struct session_handler ** handler,void ** opaque)316 int fd_sess_handler_destroy ( struct session_handler ** handler, void ** opaque )
317 {
318 struct session_handler * del;
319 /* place to save the list of states to be cleaned up. We do it after finding them to avoid deadlocks. the "o" field becomes a copy of the sid. */
320 struct fd_list deleted_states = FD_LIST_INITIALIZER( deleted_states );
321 int i;
322
323 TRACE_ENTRY("%p", handler);
324 CHECK_PARAMS( handler && VALIDATE_SH(*handler) );
325
326 del = *handler;
327 *handler = NULL;
328
329 del->eyec = 0xdead; /* The handler is not valid anymore for any other operation */
330
331 /* Now find all sessions with data registered for this handler, and move this data to the deleted_states list. */
332 for (i = 0; i < sizeof(sess_hash) / sizeof(sess_hash[0]); i++) {
333 struct fd_list * li_si;
334 CHECK_POSIX( pthread_mutex_lock(&sess_hash[i].lock) );
335
336 for (li_si = sess_hash[i].sentinel.next; li_si != &sess_hash[i].sentinel; li_si = li_si->next) { /* for each session in the hash line */
337 struct fd_list * li_st;
338 struct session * sess = (struct session *)(li_si->o);
339 CHECK_POSIX( pthread_mutex_lock(&sess->stlock) );
340 for (li_st = sess->states.next; li_st != &sess->states; li_st = li_st->next) { /* for each state in this session */
341 struct state * st = (struct state *)(li_st->o);
342 /* The list is ordered */
343 if (st->hdl->id < del->id)
344 continue;
345 if (st->hdl->id == del->id) {
346 /* This state belongs to the handler we are deleting, move the item to the deleted_states list */
347 fd_list_unlink(&st->chain);
348 st->sid = sess->sid;
349 fd_list_insert_before(&deleted_states, &st->chain);
350 }
351 break;
352 }
353 CHECK_POSIX( pthread_mutex_unlock(&sess->stlock) );
354 }
355 CHECK_POSIX( pthread_mutex_unlock(&sess_hash[i].lock) );
356 }
357
358 /* Now, delete all states after calling their cleanup handler */
359 while (!FD_IS_LIST_EMPTY(&deleted_states)) {
360 struct state * st = (struct state *)(deleted_states.next->o);
361 TRACE_DEBUG(FULL, "Calling cleanup handler for session '%s' and data %p", st->sid, st->state);
362 (*del->cleanup)(st->state, st->sid, del->opaque);
363 fd_list_unlink(&st->chain);
364 free(st);
365 }
366
367 if (opaque)
368 *opaque = del->opaque;
369
370 /* Free the handler */
371 free(del);
372
373 return 0;
374 }
375
376
377
378 /* Create a new session object with the default timeout value, and link it. The refcount is increased by 1, whether the session existed or not */
fd_sess_new(struct session ** session,DiamId_t diamid,size_t diamidlen,uint8_t * opt,size_t optlen)379 int fd_sess_new ( struct session ** session, DiamId_t diamid, size_t diamidlen, uint8_t * opt, size_t optlen )
380 {
381 os0_t sid = NULL;
382 size_t sidlen;
383 uint32_t hash;
384 struct session * sess;
385 struct fd_list * li;
386 int found = 0;
387 int ret = 0;
388
389 TRACE_ENTRY("%p %p %zd %p %zd", session, diamid, diamidlen, opt, optlen);
390 CHECK_PARAMS( session && (diamid || opt) );
391
392 if (diamid) {
393 if (!diamidlen) {
394 diamidlen = strlen(diamid);
395 }
396 /* We check if the string is a valid DiameterIdentity */
397 CHECK_PARAMS( fd_os_is_valid_DiameterIdentity((uint8_t *)diamid, diamidlen) );
398 } else {
399 diamidlen = 0;
400 }
401 if (opt) {
402 if (!optlen) {
403 optlen = strlen((char *)opt);
404 } else {
405 CHECK_PARAMS( fd_os_is_valid_os0(opt, optlen) );
406 }
407 } else {
408 optlen = 0;
409 }
410
411 /* Ok, first create the identifier for the string */
412 if (diamid == NULL) {
413 /* opt is the full string */
414 CHECK_MALLOC( sid = os0dup(opt, optlen) );
415 sidlen = optlen;
416 } else {
417 uint32_t sid_h_cpy;
418 uint32_t sid_l_cpy;
419 /* "<diamId>;<high32>;<low32>[;opt]" */
420 sidlen = diamidlen;
421 sidlen += 22; /* max size of ';<high32>;<low32>' */
422 if (opt)
423 sidlen += 1 + optlen; /* ';opt' */
424 sidlen++; /* space for the final \0 also */
425 CHECK_MALLOC( sid = malloc(sidlen) );
426
427 CHECK_POSIX( pthread_mutex_lock(&sid_lock) );
428 if ( ++sid_l == 0 ) /* overflow */
429 ++sid_h;
430 sid_h_cpy = sid_h;
431 sid_l_cpy = sid_l;
432 CHECK_POSIX( pthread_mutex_unlock(&sid_lock) );
433
434 if (opt) {
435 sidlen = snprintf((char*)sid, sidlen, "%.*s;%u;%u;%.*s", (int)diamidlen, diamid, sid_h_cpy, sid_l_cpy, (int)optlen, opt);
436 } else {
437 sidlen = snprintf((char*)sid, sidlen, "%.*s;%u;%u", (int)diamidlen, diamid, sid_h_cpy, sid_l_cpy);
438 }
439 }
440
441 hash = fd_os_hash(sid, sidlen);
442
443 /* Now find the place to add this object in the hash table. */
444 CHECK_POSIX( pthread_mutex_lock( H_LOCK(hash) ) );
445 pthread_cleanup_push( fd_cleanup_mutex, H_LOCK(hash) );
446
447 for (li = H_LIST(hash)->next; li != H_LIST(hash); li = li->next) {
448 int cmp;
449 struct session * s = (struct session *)(li->o);
450
451 /* The list is ordered by hash and sid (in case of collisions) */
452 if (s->hash < hash)
453 continue;
454 if (s->hash > hash)
455 break;
456
457 cmp = fd_os_cmp(s->sid, s->sidlen, sid, sidlen);
458 if (cmp < 0)
459 continue;
460 if (cmp > 0)
461 break;
462
463 /* A session with the same sid was already in the hash table */
464 found = 1;
465 *session = s;
466 break;
467 }
468
469 /* If the session did not exist, we can create it & link it in global tables */
470 if (!found) {
471 CHECK_MALLOC_DO(sess = new_session(sid, sidlen, hash),
472 {
473 ret = ENOMEM;
474 free(sid);
475 goto out;
476 } );
477
478 fd_list_insert_before(li, &sess->chain_h); /* hash table */
479 sess->msg_cnt++;
480 } else {
481 free(sid);
482
483 CHECK_POSIX( pthread_mutex_lock(&(*session)->stlock) );
484 (*session)->msg_cnt++;
485 CHECK_POSIX( pthread_mutex_unlock(&(*session)->stlock) );
486
487 /* it was found: was it previously destroyed? */
488 if ((*session)->is_destroyed == 0) {
489 ret = EALREADY;
490 goto out;
491 } else {
492 /* the session was marked destroyed, let's re-activate it. */
493 sess = *session;
494 sess->is_destroyed = 0;
495
496 /* update the expiry time */
497 CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &sess->timeout), { ASSERT(0); } );
498 sess->timeout.tv_sec += SESS_DEFAULT_LIFETIME;
499 }
500 }
501
502 /* We must insert in the expiry list */
503 CHECK_POSIX( pthread_mutex_lock( &exp_lock ) );
504 pthread_cleanup_push( fd_cleanup_mutex, &exp_lock );
505
506 /* Find the position in that list. We take it in reverse order */
507 for (li = exp_sentinel.prev; li != &exp_sentinel; li = li->prev) {
508 struct session * s = (struct session *)(li->o);
509 if (TS_IS_INFERIOR( &s->timeout, &sess->timeout ) )
510 break;
511 }
512 fd_list_insert_after( li, &sess->expire );
513 sess_cnt++;
514
515 /* We added a new expiring element, we must signal */
516 if (li == &exp_sentinel) {
517 CHECK_POSIX_DO( pthread_cond_signal(&exp_cond), { ASSERT(0); } ); /* if it fails, we might not pop the cleanup handlers, but this should not happen -- and we'd have a serious problem otherwise */
518 }
519
520 /* We're done with the locked part */
521 pthread_cleanup_pop(0);
522 CHECK_POSIX_DO( pthread_mutex_unlock( &exp_lock ), { ASSERT(0); } ); /* if it fails, we might not pop the cleanup handler, but this should not happen -- and we'd have a serious problem otherwise */
523
524 out:
525 ;
526 pthread_cleanup_pop(0);
527 CHECK_POSIX( pthread_mutex_unlock( H_LOCK(hash) ) );
528
529 if (ret) /* in case of error */
530 return ret;
531
532 *session = sess;
533 return 0;
534 }
535
536 /* Find or create a session -- the msg refcount is increased */
fd_sess_fromsid_msg(uint8_t * sid,size_t len,struct session ** session,int * new)537 int fd_sess_fromsid_msg ( uint8_t * sid, size_t len, struct session ** session, int * new)
538 {
539 int ret;
540
541 TRACE_ENTRY("%p %zd %p %p", sid, len, session, new);
542 CHECK_PARAMS( sid && session );
543
544 if (!fd_os_is_valid_os0(sid,len)) {
545 TRACE_DEBUG(INFO, "Warning: a Session-Id value contains \\0 chars... (len:%zd, begin:'%.*s') => Debug messages may be truncated.", len, (int)len, sid);
546 }
547
548 /* All the work is done in sess_new */
549 ret = fd_sess_new ( session, NULL, 0, sid, len );
550 switch (ret) {
551 case 0:
552 case EALREADY:
553 break;
554
555 default:
556 CHECK_FCT(ret);
557 }
558
559 if (new)
560 *new = ret ? 0 : 1;
561
562 return 0;
563 }
564
565 /* Get the sid of a session */
fd_sess_getsid(struct session * session,os0_t * sid,size_t * sidlen)566 int fd_sess_getsid ( struct session * session, os0_t * sid, size_t * sidlen )
567 {
568 TRACE_ENTRY("%p %p", session, sid);
569
570 CHECK_PARAMS( VALIDATE_SI(session) && sid );
571
572 *sid = session->sid;
573 if (sidlen)
574 *sidlen = session->sidlen;
575
576 return 0;
577 }
578
579 /* Change the timeout value of a session */
fd_sess_settimeout(struct session * session,const struct timespec * timeout)580 int fd_sess_settimeout( struct session * session, const struct timespec * timeout )
581 {
582 struct fd_list * li;
583
584 TRACE_ENTRY("%p %p", session, timeout);
585 CHECK_PARAMS( VALIDATE_SI(session) && timeout );
586
587 /* Lock -- do we need to lock the hash table as well? I don't think so... */
588 CHECK_POSIX( pthread_mutex_lock( &exp_lock ) );
589 pthread_cleanup_push( fd_cleanup_mutex, &exp_lock );
590
591 /* Update the timeout */
592 fd_list_unlink(&session->expire);
593 memcpy(&session->timeout, timeout, sizeof(struct timespec));
594
595 /* Find the new position in expire list. We take it in normal order */
596 for (li = exp_sentinel.next; li != &exp_sentinel; li = li->next) {
597 struct session * s = (struct session *)(li->o);
598
599 if (TS_IS_INFERIOR( &s->timeout, &session->timeout ) )
600 continue;
601
602 break;
603 }
604 fd_list_insert_before( li, &session->expire );
605
606 /* We added a new expiring element, we must signal if it was in first position */
607 if (session->expire.prev == &exp_sentinel) {
608 CHECK_POSIX_DO( pthread_cond_signal(&exp_cond), { ASSERT(0); /* so that we don't have a pending cancellation handler */ } );
609 }
610
611 /* We're done */
612 pthread_cleanup_pop(0);
613 CHECK_POSIX( pthread_mutex_unlock( &exp_lock ) );
614
615 return 0;
616 }
617
618 /* Destroy the states associated to a session, and mark it destroyed. */
fd_sess_destroy(struct session ** session)619 int fd_sess_destroy ( struct session ** session )
620 {
621 struct session * sess;
622 int destroy_now;
623 os0_t sid;
624 int ret = 0;
625
626 /* place to save the list of states to be cleaned up. We do it after finding them to avoid deadlocks. the "o" field becomes a copy of the sid. */
627 struct fd_list deleted_states = FD_LIST_INITIALIZER( deleted_states );
628
629 TRACE_ENTRY("%p", session);
630 CHECK_PARAMS( session && VALIDATE_SI(*session) );
631
632 sess = *session;
633 *session = NULL;
634
635 /* Lock the hash line */
636 CHECK_POSIX( pthread_mutex_lock( H_LOCK(sess->hash) ) );
637 pthread_cleanup_push( fd_cleanup_mutex, H_LOCK(sess->hash) );
638
639 /* Unlink from the expiry list */
640 CHECK_POSIX_DO( pthread_mutex_lock( &exp_lock ), { ASSERT(0); /* otherwise cleanup handler is not pop'd */ } );
641 pthread_cleanup_push( fd_cleanup_mutex, &exp_lock );
642 if (!FD_IS_LIST_EMPTY(&sess->expire)) {
643 sess_cnt--;
644 fd_list_unlink( &sess->expire ); /* no need to signal the condition here */
645 }
646 pthread_cleanup_pop(0);
647 CHECK_POSIX_DO( pthread_mutex_unlock( &exp_lock ), { ASSERT(0); /* otherwise cleanup handler is not pop'd */ } );
648
649 /* Now move all states associated to this session into deleted_states */
650 CHECK_POSIX_DO( pthread_mutex_lock( &sess->stlock ), { ASSERT(0); /* otherwise cleanup handler is not pop'd */ } );
651 while (!FD_IS_LIST_EMPTY(&sess->states)) {
652 struct state * st = (struct state *)(sess->states.next->o);
653 fd_list_unlink(&st->chain);
654 fd_list_insert_before(&deleted_states, &st->chain);
655 }
656 CHECK_POSIX_DO( pthread_mutex_unlock( &sess->stlock ), { ASSERT(0); /* otherwise cleanup handler is not pop'd */ } );
657
658 /* Mark the session as destroyed */
659 destroy_now = (sess->msg_cnt == 0);
660 if (destroy_now) {
661 fd_list_unlink( &sess->chain_h );
662 sid = sess->sid;
663 } else {
664 sess->is_destroyed = 1;
665 CHECK_MALLOC_DO( sid = os0dup(sess->sid, sess->sidlen), ret = ENOMEM );
666 }
667 pthread_cleanup_pop(0);
668 CHECK_POSIX( pthread_mutex_unlock( H_LOCK(sess->hash) ) );
669
670 if (ret)
671 return ret;
672
673 /* Now, really delete the states */
674 while (!FD_IS_LIST_EMPTY(&deleted_states)) {
675 struct state * st = (struct state *)(deleted_states.next->o);
676 fd_list_unlink(&st->chain);
677 TRACE_DEBUG(FULL, "Calling handler %p cleanup for state %p registered with session '%s'", st->hdl, st, sid);
678 (*st->hdl->cleanup)(st->state, sid, st->hdl->opaque);
679 free(st);
680 }
681
682 /* Finally, destroy the session itself, if it is not referrenced by any message anymore */
683 if (destroy_now) {
684 del_session(sess);
685 } else {
686 free(sid);
687 }
688
689 return 0;
690 }
691
692 /* Destroy a session if it is not used */
fd_sess_reclaim(struct session ** session)693 int fd_sess_reclaim ( struct session ** session )
694 {
695 struct session * sess;
696 uint32_t hash;
697 int destroy_now = 0;
698
699 TRACE_ENTRY("%p", session);
700 CHECK_PARAMS( session && VALIDATE_SI(*session) );
701
702 sess = *session;
703 hash = sess->hash;
704 *session = NULL;
705
706 CHECK_POSIX( pthread_mutex_lock( H_LOCK(hash) ) );
707 pthread_cleanup_push( fd_cleanup_mutex, H_LOCK(hash) );
708 CHECK_POSIX_DO( pthread_mutex_lock( &sess->stlock ), { ASSERT(0); /* otherwise, cleanup not poped on FreeBSD */ } );
709 pthread_cleanup_push( fd_cleanup_mutex, &sess->stlock );
710 CHECK_POSIX_DO( pthread_mutex_lock( &exp_lock ), { ASSERT(0); /* otherwise, cleanup not poped on FreeBSD */ } );
711 pthread_cleanup_push( fd_cleanup_mutex, &exp_lock );
712
713 /* We only do something if the states list is empty */
714 if (FD_IS_LIST_EMPTY(&sess->states)) {
715 /* In this case, we do as in destroy */
716 fd_list_unlink( &sess->expire );
717 destroy_now = (sess->msg_cnt == 0);
718 if (destroy_now) {
719 fd_list_unlink(&sess->chain_h);
720 } else {
721 /* just mark it as destroyed, it will be freed when the last message stops referencing it */
722 sess->is_destroyed = 1;
723 }
724 }
725
726 pthread_cleanup_pop(0);
727 CHECK_POSIX_DO( pthread_mutex_unlock( &exp_lock ), { ASSERT(0); /* otherwise, cleanup not poped on FreeBSD */ } );
728 pthread_cleanup_pop(0);
729 CHECK_POSIX_DO( pthread_mutex_unlock( &sess->stlock ), { ASSERT(0); /* otherwise, cleanup not poped on FreeBSD */ } );
730 pthread_cleanup_pop(0);
731 CHECK_POSIX( pthread_mutex_unlock( H_LOCK(hash) ) );
732
733 if (destroy_now)
734 del_session(sess);
735
736 return 0;
737 }
738
739 /* Save a state information with a session */
fd_sess_state_store(struct session_handler * handler,struct session * session,struct sess_state ** state)740 int fd_sess_state_store ( struct session_handler * handler, struct session * session, struct sess_state ** state )
741 {
742 struct state *new;
743 struct fd_list * li;
744 int already = 0;
745 int ret = 0;
746
747 TRACE_ENTRY("%p %p %p", handler, session, state);
748 CHECK_PARAMS( handler && VALIDATE_SH(handler) && session && VALIDATE_SI(session) && (!session->is_destroyed) && state );
749
750 /* Lock the session state list */
751 CHECK_POSIX( pthread_mutex_lock(&session->stlock) );
752 pthread_cleanup_push( fd_cleanup_mutex, &session->stlock );
753
754 /* Create the new state object */
755 CHECK_MALLOC_DO(new = malloc(sizeof(struct state)), { ret = ENOMEM; goto out; } );
756 memset(new, 0, sizeof(struct state));
757
758 new->eyec = SD_EYEC;
759 new->state= *state;
760 fd_list_init(&new->chain, new);
761 new->hdl = handler;
762
763 /* find place for this state in the list */
764 for (li = session->states.next; li != &session->states; li = li->next) {
765 struct state * st = (struct state *)(li->o);
766 /* The list is ordered by handler's id */
767 if (st->hdl->id < handler->id)
768 continue;
769
770 if (st->hdl->id == handler->id) {
771 TRACE_DEBUG(INFO, "A state was already stored for session '%s' and handler '%p', at location %p", session->sid, st->hdl, st->state);
772 already = EALREADY;
773 }
774
775 break;
776 }
777
778 if (!already) {
779 fd_list_insert_before(li, &new->chain);
780 *state = NULL;
781 } else {
782 free(new);
783 }
784 out:
785 ;
786 pthread_cleanup_pop(0);
787 CHECK_POSIX( pthread_mutex_unlock(&session->stlock) );
788
789 return ret ?: already;
790 }
791
792 /* Get the data back */
fd_sess_state_retrieve(struct session_handler * handler,struct session * session,struct sess_state ** state)793 int fd_sess_state_retrieve ( struct session_handler * handler, struct session * session, struct sess_state ** state )
794 {
795 struct fd_list * li;
796 struct state * st = NULL;
797
798 TRACE_ENTRY("%p %p %p", handler, session, state);
799 CHECK_PARAMS( handler && VALIDATE_SH(handler) && session && VALIDATE_SI(session) && state );
800
801 *state = NULL;
802
803 /* Lock the session state list */
804 CHECK_POSIX( pthread_mutex_lock(&session->stlock) );
805 pthread_cleanup_push( fd_cleanup_mutex, &session->stlock );
806
807 /* find the state in the list */
808 for (li = session->states.next; li != &session->states; li = li->next) {
809 st = (struct state *)(li->o);
810
811 /* The list is ordered by handler's id */
812 if (st->hdl->id > handler->id)
813 break;
814 }
815
816 /* If we found the state */
817 if (st && (st->hdl == handler)) {
818 fd_list_unlink(&st->chain);
819 *state = st->state;
820 free(st);
821 }
822
823 pthread_cleanup_pop(0);
824 CHECK_POSIX( pthread_mutex_unlock(&session->stlock) );
825
826 return 0;
827 }
828
829 /* For the messages module */
fd_sess_fromsid(uint8_t * sid,size_t len,struct session ** session,int * new)830 int fd_sess_fromsid ( uint8_t * sid, size_t len, struct session ** session, int * new)
831 {
832 TRACE_ENTRY("%p %zd %p %p", sid, len, session, new);
833 CHECK_PARAMS( sid && len && session );
834
835 /* Get the session object */
836 CHECK_FCT( fd_sess_fromsid_msg ( sid, len, session, new) );
837
838 /* Decrease the refcount */
839 CHECK_POSIX( pthread_mutex_lock(&(*session)->stlock) );
840 (*session)->msg_cnt--; /* was increased in fd_sess_new */
841 CHECK_POSIX( pthread_mutex_unlock(&(*session)->stlock) );
842
843 /* Done */
844 return 0;
845 }
846
fd_sess_ref_msg(struct session * session)847 int fd_sess_ref_msg ( struct session * session )
848 {
849 TRACE_ENTRY("%p", session);
850 CHECK_PARAMS( VALIDATE_SI(session) );
851
852 /* Update the msg refcount */
853 CHECK_POSIX( pthread_mutex_lock(&session->stlock) );
854 session->msg_cnt++;
855 CHECK_POSIX( pthread_mutex_unlock(&session->stlock) );
856
857 return 0;
858 }
859
fd_sess_reclaim_msg(struct session ** session)860 int fd_sess_reclaim_msg ( struct session ** session )
861 {
862 int reclaim;
863 uint32_t hash;
864
865 TRACE_ENTRY("%p", session);
866 CHECK_PARAMS( session && VALIDATE_SI(*session) );
867
868 /* Lock the hash line to avoid possibility that session is freed while we are reclaiming */
869 hash = (*session)->hash;
870 CHECK_POSIX( pthread_mutex_lock( H_LOCK(hash)) );
871 pthread_cleanup_push( fd_cleanup_mutex, H_LOCK(hash) );
872
873 /* Update the msg refcount */
874 CHECK_POSIX( pthread_mutex_lock(&(*session)->stlock) );
875 reclaim = (*session)->msg_cnt;
876 (*session)->msg_cnt = reclaim - 1;
877 CHECK_POSIX( pthread_mutex_unlock(&(*session)->stlock) );
878
879 /* Ok, now unlock the hash line */
880 pthread_cleanup_pop( 0 );
881 CHECK_POSIX( pthread_mutex_unlock( H_LOCK(hash) ) );
882
883 /* and reclaim if no message references the session anymore */
884 if (reclaim == 1) {
885 CHECK_FCT(fd_sess_reclaim ( session ));
886 } else {
887 *session = NULL;
888 }
889 return 0;
890 }
891
892
893
894 /* Dump functions */
DECLARE_FD_DUMP_PROTOTYPE(fd_sess_dump,struct session * session,int with_states)895 DECLARE_FD_DUMP_PROTOTYPE(fd_sess_dump, struct session * session, int with_states)
896 {
897 FD_DUMP_HANDLE_OFFSET();
898
899 CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "{session}(@%p): ", session), return NULL);
900
901 if (!VALIDATE_SI(session)) {
902 CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "INVALID/NULL"), return NULL);
903 } else {
904 char timebuf[30];
905 struct tm tm;
906
907 strftime(timebuf, sizeof(timebuf), "%D,%T", localtime_r( &session->timeout.tv_sec , &tm ));
908 CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "'%s'(%zd) h:%x m:%d d:%d to:%s.%06ld",
909 session->sid, session->sidlen, session->hash, session->msg_cnt, session->is_destroyed,
910 timebuf, session->timeout.tv_nsec/1000),
911 return NULL);
912
913 if (with_states) {
914 struct fd_list * li;
915 CHECK_POSIX_DO( pthread_mutex_lock(&session->stlock), /* ignore */ );
916 pthread_cleanup_push( fd_cleanup_mutex, &session->stlock );
917
918 for (li = session->states.next; li != &session->states; li = li->next) {
919 struct state * st = (struct state *)(li->o);
920 CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "\n {state i:%d}(@%p): ", st->hdl->id, st), return NULL);
921 if (st->hdl->state_dump) {
922 CHECK_MALLOC_DO( (*st->hdl->state_dump)( FD_DUMP_STD_PARAMS, st->state),
923 fd_dump_extend( FD_DUMP_STD_PARAMS, "[dumper error]"));
924 } else {
925 CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "<%p>", st->state), return NULL);
926 }
927 }
928
929 pthread_cleanup_pop(0);
930 CHECK_POSIX_DO( pthread_mutex_unlock(&session->stlock), /* ignore */ );
931 }
932 }
933
934 return *buf;
935 }
936
DECLARE_FD_DUMP_PROTOTYPE(fd_sess_dump_hdl,struct session_handler * handler)937 DECLARE_FD_DUMP_PROTOTYPE(fd_sess_dump_hdl, struct session_handler * handler)
938 {
939 FD_DUMP_HANDLE_OFFSET();
940
941 CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "{sesshdl}(@%p): ", handler), return NULL);
942
943 if (!VALIDATE_SH(handler)) {
944 CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "INVALID/NULL"), return NULL);
945 } else {
946 CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "i:%d cl:%p d:%p o:%p", handler->id, handler->cleanup, handler->state_dump, handler->opaque), return NULL);
947 }
948 return *buf;
949 }
950
fd_sess_getcount(uint32_t * cnt)951 int fd_sess_getcount(uint32_t *cnt)
952 {
953 CHECK_PARAMS(cnt);
954 CHECK_POSIX( pthread_mutex_lock( &exp_lock ) );
955 *cnt = sess_cnt;
956 CHECK_POSIX( pthread_mutex_unlock( &exp_lock ) );
957 return 0;
958 }
959