1/* @(#) implementation of data-stream cache for udpxy 2 * 3 * Copyright 2008-2011 Pavel V. Cherenkov (pcherenkov@gmail.com) 4 * 5 * This file is part of udpxy. 6 * 7 * udpxy is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * 12 * udpxy is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with udpxy. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21#include <assert.h> 22#include <stdlib.h> 23#include <sys/types.h> 24 25#include "mtrace.h" 26#include "osdef.h" 27#include "util.h" 28#include "dpkt.h" 29#include "cache.h" 30 31static int ltrace_on = 1; 32 33#define LTRACE( expr ) if( ltrace_on ) TRACE( (expr) ) 34 35struct dscache { 36 struct chunk *head, *tail; /* cached data chunks */ 37 struct chunk *read_p, *write_p; /* read/write-from chunk */ 38 39 size_t chunklen; /* length of an individual chunk */ 40 size_t max_totlen; /* max combined size of chunks */ 41 size_t totlen; /* combined size of chunks */ 42 43 struct chunk *free_head, 44 *free_tail; /* free (released) chunks */ 45 struct dstream_ctx 46 tpl_spc; /* template of a stream context */ 47}; 48 49 50/* clone dstream context 51 */ 52static int 53clone_spc( const struct dstream_ctx* src, struct dstream_ctx* dst ) 54{ 55 assert( src && dst ); 56 57 dst->stype = src->stype; 58 dst->flags = src->flags; 59 dst->mtu = src->mtu; 60 61 62 dst->pkt = calloc( src->max_pkt, sizeof(dst->pkt[0]) ); 63 if( NULL == dst->pkt ) { 64 (void) tmfprintf( g_flog, "%s: calloc(%ld, %ld)", 65 (long)src->max_pkt, (long)sizeof(dst->pkt[0]) ); 66 return -1; 67 } 68 69 dst->max_pkt = src->max_pkt; 70 dst->pkt_count = 0; 71 72 return 0; 73} 74 75 76/* allocate from free-chunk list 77 */ 78static struct chunk* 79freelist_alloc( struct dscache* cache, size_t chunklen ) 80{ 81 struct chunk *chnk, *p; 82 83 assert( cache && (chunklen > 0) ); 84 85 for( chnk = cache->free_head, p = NULL; chnk; ) { 86 if( chnk->length >= chunklen ) { 87 break; 88 } 89 else { 90 p = chnk; 91 chnk = chnk->next; 92 } 93 } 94 95 if( !chnk ) return NULL; 96 97 /* remove from the list */ 98 chnk->used_length = (size_t)0; 99 100 if( cache->free_tail == chnk ) 101 cache->free_tail = p; 102 103 if( cache->free_head == chnk ) 104 cache->free_head = chnk->next; 105 106 if( p ) 107 p->next = chnk->next; 108 109 /* reset the context */ 110 clone_spc( &cache->tpl_spc, &chnk->spc ); 111 112 chnk->next = NULL; 113 114 LTRACE( (void)tmfprintf( g_flog, 115 "Freelist-allocated chunk(%p) of len=[%ld]\n", 116 (const void*)chnk, (long)chnk->length ) ); 117 return chnk; 118} 119 120 121/* allocate chunk on the heap 122 */ 123static struct chunk* 124heap_alloc( struct dscache* cache, size_t chunklen ) 125{ 126 struct chunk *chnk = NULL; 127 int rc = 0; 128 129 assert( cache && (chunklen > 0) ); 130 131 /* allocate on the heap */ 132 if( (cache->totlen + chunklen) > cache->max_totlen ) { 133 (void) tmfprintf( g_flog, "%s: cannot add [%ld] bytes to cache, " 134 "current size=[%ld], max size=[%ld]\n", 135 __func__, (long)chunklen, (long)cache->totlen, 136 (long)cache->max_totlen ); 137 return NULL; 138 } 139 140 rc = -1; 141 do { 142 chnk = malloc( sizeof(struct chunk) ); 143 if( NULL == chnk ) { 144 (void) tmfprintf( g_flog, 145 "%s: not enough memory for chunk structure size=[%ld]\n", 146 __func__, (long)sizeof(struct chunk) ); 147 break; 148 } 149 150 if( NULL == (chnk->data = malloc( chunklen )) ) { 151 (void) tmfprintf( g_flog, "%s: not enough memory for chunk " 152 "data of size=[%ld]\n", __func__, chunklen ); 153 break; 154 } 155 156 chnk->length = chunklen; 157 chnk->used_length = (size_t)0; 158 159 if( 0 != clone_spc( &cache->tpl_spc, &chnk->spc ) ) 160 break; 161 162 rc = 0; 163 } while(0); 164 165 /* error: cleanup */ 166 if( 0 != rc ) { 167 if( !chnk ) return NULL; 168 169 if( chnk->data ) free( chnk->data ); 170 free( chnk ); 171 172 return NULL; 173 } 174 175 cache->totlen += chnk->length; 176 LTRACE( (void)tmfprintf( g_flog, 177 "Heap-allocated chunk=[%p] of len=[%ld]\n", 178 (const void*)chnk, (long)chnk->length ) ); 179 180 return chnk; 181} 182 183 184/* add buffer (chunk) of specific size to cache 185 */ 186static struct chunk* 187dscache_alloc( struct dscache* cache, size_t chunklen ) 188{ 189 struct chunk *chnk = freelist_alloc( cache, chunklen ); 190 if( !chnk ) 191 chnk = heap_alloc( cache, chunklen ); 192 193 if( !chnk ) { 194 LTRACE( (void)tmfprintf( g_flog, 195 "%s: cannot allocate chunk", __func__ ) ); 196 return NULL; 197 } 198 199 chnk->next = NULL; 200 201 if( NULL == cache->head ) 202 cache->head = chnk; 203 204 if( NULL != cache->tail ) 205 cache->tail->next = chnk; 206 207 cache->tail = chnk; 208 209 return chnk; 210} 211 212 213/* initialize the cache 214 */ 215struct dscache* 216dscache_init( size_t maxlen, struct dstream_ctx* spc, 217 size_t chunklen, unsigned nchunks ) 218{ 219 struct dscache* cache = NULL; 220 unsigned i; 221 222 assert( (maxlen > 0) && spc && chunklen ); 223 assert( spc->mtu && spc->max_pkt ); 224 225 cache = malloc( sizeof(struct dscache) ); 226 if( NULL == cache ) 227 return NULL; 228 229 cache->head = cache->tail = NULL; 230 cache->read_p = cache->write_p = NULL; 231 cache->max_totlen = maxlen; 232 cache->totlen = 0; 233 cache->chunklen = chunklen; 234 235 cache->free_head = cache->free_tail = NULL; 236 cache->tpl_spc = *spc; 237 238 for( i = 0; i < nchunks; ++i ) { 239 if( NULL == dscache_alloc( cache, cache->chunklen ) ) { 240 dscache_free( cache ); 241 return NULL; 242 } 243 } 244 245 cache->read_p = cache->write_p = NULL; 246 247 return cache; 248} 249 250 251/* get chunk at R-head, allocate new one if needed 252 */ 253struct chunk* 254dscache_rget( struct dscache* cache, int* error ) 255{ 256 struct chunk* chnk = NULL; 257 258 assert( cache && error ); 259 260 if( NULL == cache->head ) { 261 LTRACE( (void)tmfprintf( g_flog, "%s: cache is empty\n", 262 __func__ ) ); 263 *error = ERR_CACHE_END; 264 return NULL; 265 } 266 267 chnk = cache->read_p ? cache->read_p->next : cache->head; 268 if( NULL == chnk ) { 269 chnk = dscache_alloc( cache, cache->chunklen ); 270 if( !chnk ) { 271 *error = ERR_CACHE_ALLOC; 272 return NULL; 273 } 274 } 275 276 /* check that next chunk is unused */ 277 assert( 0 == chnk->used_length ); 278 279 cache->read_p = chnk; 280 LTRACE( (void)tmfprintf( g_flog, "Cache R-head moved to [%p]\n", 281 (const void*)cache->read_p ) ); 282 283 return cache->read_p; 284} 285 286 287/* specify length of the used chunk's portion 288 */ 289int 290dscache_rset( struct dscache* cache, size_t usedlen ) 291{ 292 assert( cache ); 293 294 if( !cache->read_p ) { 295 LTRACE( (void) tmfprintf( g_flog, 296 "%s: R-head is not positioned\n", __func__ ) ); 297 return ERR_CACHE_END; 298 } 299 300 cache->read_p->used_length = usedlen; 301 302 return 0; 303} 304 305 306 307/* free up data chunk: move it from data- to free-chunk list */ 308static int 309dscache_freechunk( struct dscache* cache, struct chunk* chnk ) 310{ 311 struct chunk *p = NULL; 312 313 assert( chnk && cache->head ); 314 315 /* remove chunk from data-chunk list */ 316 if( cache->head == chnk ) { 317 p = NULL; /* no 'previous' */ 318 cache->head = chnk->next; 319 } 320 else { 321 for( p = cache->head; p; ) { 322 if( p->next == chnk ) 323 break; 324 } 325 if( !p ) /* not found */ 326 return -1; 327 328 p->next = chnk->next; 329 } 330 331 /* adjust head positions */ 332 if( cache->read_p == chnk ) 333 cache->read_p = NULL; 334 if( cache->write_p == chnk ) 335 cache->write_p = NULL; 336 337 /* adjust the tail if we shortened it */ 338 if( cache->tail == chnk ) 339 cache->tail = p; 340 341 chnk->next = NULL; 342 free_dstream_ctx( &chnk->spc ); 343 344 /* add chunk to the free-chunk list */ 345 if( !cache->free_head ) 346 cache->free_head = chnk; 347 348 if( cache->free_tail ) 349 cache->free_tail->next = chnk; 350 351 cache->free_tail = chnk; 352 353 LTRACE( (void)tmfprintf( g_flog, "Free-listed chunk(%p)\n", 354 (const void*)chnk ) ); 355 return 0; 356} 357 358 359/* advance get a chunk at W-head 360 */ 361struct chunk* 362dscache_wget( struct dscache* cache, int* error ) 363{ 364 struct chunk *chnk = NULL, *old_chnk = NULL; 365 366 assert( cache && error ); 367 368 chnk = cache->write_p; 369 if( chnk ) { 370 if( NULL == chnk->next ) { 371 LTRACE( (void)tmfprintf( g_flog, 372 "%s: W-head cannot advance beyond end of cache\n", 373 __func__ ) ); 374 375 *error = ERR_CACHE_END; 376 return NULL; 377 } 378 379 /* free up the chunk behind */ 380 old_chnk = chnk; 381 chnk = chnk->next; 382 383 if( 0 != dscache_freechunk( cache, old_chnk ) ) { 384 LTRACE( (void)tmfprintf( g_flog, "%s: Cannot free chunk(%p)\n", 385 __func__, (const void*)old_chnk ) ); 386 *error = ERR_CACHE_INTRNL; 387 return NULL; 388 } 389 } 390 else { 391 chnk = cache->head; 392 LTRACE( (void)tmfprintf( g_flog, "%s: W-head moved to cache-head [%p]\n", 393 __func__, (const void*)chnk ) ); 394 if( NULL == chnk ) 395 *error = ERR_CACHE_END; 396 } 397 398 if( (size_t)0 == chnk->used_length ) { 399 LTRACE( (void)tmfprintf( g_flog, 400 "%s: W-head cannot move to unpopulated data\n", __func__ ) ); 401 *error = ERR_CACHE_BADDATA; 402 return NULL; 403 } 404 405 cache->write_p = chnk; 406 return chnk; 407} 408 409 410/* free all chunks in a list 411 */ 412static void 413free_chunklist( struct chunk* head ) 414{ 415 struct chunk *chnk, *p; 416 417 for( chnk = head; chnk; ) { 418 p = chnk->next; 419 420 free( chnk->data ); 421 free_dstream_ctx( &chnk->spc ); 422 423 free( chnk ); 424 LTRACE( (void)tmfprintf( g_flog, "Heap-freed chunk(%p)\n", 425 (const void*)chnk ) ); 426 427 chnk = p; 428 } 429 430 return; 431} 432 433 434/* release all resources from cache 435 */ 436void 437dscache_free( struct dscache* cache ) 438{ 439 assert( cache ); 440 441 LTRACE( (void)tmfprintf( g_flog, "Purging chunk list\n" ) ); 442 free_chunklist( cache->head ); 443 444 LTRACE( (void)tmfprintf( g_flog, "Purging free list\n" ) ); 445 free_chunklist( cache->free_head ); 446 447 free( cache ); 448 LTRACE( (void)tmfprintf( g_flog, "Cache freed up\n" ) ); 449 450 451 return; 452} 453 454/* __EOF__ */ 455 456