1 #include "first.h" 2 3 #include "array.h" 4 #include "buffer.h" 5 #include "ck.h" 6 7 #include <string.h> 8 #include <stdlib.h> 9 #include <limits.h> 10 11 12 __attribute_cold__ 13 static data_unset *array_data_string_copy(const data_unset *s) { 14 data_string *src = (data_string *)s; 15 data_string *ds = array_data_string_init(); 16 if (!buffer_is_unset(&src->key)) buffer_copy_buffer(&ds->key, &src->key); 17 buffer_copy_buffer(&ds->value, &src->value); 18 return (data_unset *)ds; 19 } 20 21 __attribute_cold__ 22 static void array_data_string_insert_dup(data_unset *dst, data_unset *src) { 23 data_string *ds_dst = (data_string *)dst; 24 data_string *ds_src = (data_string *)src; 25 if (!buffer_is_blank(&ds_dst->value)) 26 buffer_append_str2(&ds_dst->value, CONST_STR_LEN(", "), 27 BUF_PTR_LEN(&ds_src->value)); 28 else 29 buffer_copy_buffer(&ds_dst->value, &ds_src->value); 30 } 31 32 static void array_data_string_free(data_unset *du) { 33 data_string *ds = (data_string *)du; 34 free(ds->key.ptr); 35 free(ds->value.ptr); 36 free(ds); 37 } 38 39 __attribute_noinline__ 40 data_string *array_data_string_init(void) { 41 static const struct data_methods string_fn = { 42 array_data_string_copy, 43 array_data_string_free, 44 array_data_string_insert_dup, 45 }; 46 data_string *ds = ck_calloc(1, sizeof(*ds)); 47 ds->type = TYPE_STRING; 48 ds->fn = &string_fn; 49 return ds; 50 } 51 52 53 __attribute_cold__ 54 static data_unset *array_data_integer_copy(const data_unset *s) { 55 data_integer *src = (data_integer *)s; 56 data_integer *di = array_data_integer_init(); 57 if (!buffer_is_unset(&src->key)) buffer_copy_buffer(&di->key, &src->key); 58 di->value = src->value; 59 return (data_unset *)di; 60 } 61 62 static void array_data_integer_free(data_unset *du) { 63 data_integer *di = (data_integer *)du; 64 free(di->key.ptr); 65 free(di); 66 } 67 68 __attribute_noinline__ 69 data_integer *array_data_integer_init(void) { 70 static const struct data_methods integer_fn = { 71 array_data_integer_copy, 72 array_data_integer_free, 73 NULL 74 }; 75 data_integer *di = ck_calloc(1, sizeof(*di)); 76 di->type = TYPE_INTEGER; 77 di->fn = &integer_fn; 78 return di; 79 } 80 81 82 __attribute_cold__ 83 static data_unset *array_data_array_copy(const data_unset *s) { 84 data_array *src = (data_array *)s; 85 data_array *da = array_data_array_init(); 86 if (!buffer_is_unset(&src->key)) buffer_copy_buffer(&da->key, &src->key); 87 array_copy_array(&da->value, &src->value); 88 return (data_unset *)da; 89 } 90 91 static void array_data_array_free(data_unset *du) { 92 data_array *da = (data_array *)du; 93 free(da->key.ptr); 94 array_free_data(&da->value); 95 free(da); 96 } 97 98 __attribute_noinline__ 99 data_array *array_data_array_init(void) { 100 static const struct data_methods array_fn = { 101 array_data_array_copy, 102 array_data_array_free, 103 NULL 104 }; 105 data_array *da = ck_calloc(1, sizeof(*da)); 106 da->type = TYPE_ARRAY; 107 da->fn = &array_fn; 108 return da; 109 } 110 111 112 __attribute_cold__ 113 static void array_extend(array * const a, uint32_t n) { 114 /* This data structure should not be used for nearly so many entries */ 115 force_assert(a->size <= INT32_MAX-n); 116 a->size += n; 117 a->data = ck_realloc_u32((void**)&a->data, a->size,0,sizeof(*a->data)); 118 a->sorted = ck_realloc_u32((void**)&a->sorted,a->size,0,sizeof(*a->sorted)); 119 memset(a->data+a->used, 0, (a->size-a->used)*sizeof(*a->data)); 120 } 121 122 array *array_init(uint32_t n) { 123 array *a = ck_calloc(1, sizeof(*a)); 124 if (n) array_extend(a, n); 125 return a; 126 } 127 128 void array_free_data(array * const a) { 129 if (a->sorted) free(a->sorted); 130 data_unset ** const data = a->data; 131 const uint32_t sz = a->size; 132 for (uint32_t i = 0; i < sz; ++i) { 133 if (data[i]) data[i]->fn->free(data[i]); 134 } 135 free(data); 136 a->data = NULL; 137 a->sorted = NULL; 138 a->used = 0; 139 a->size = 0; 140 } 141 142 void array_copy_array(array * const dst, const array * const src) { 143 array_free_data(dst); 144 if (0 == src->size) return; 145 146 array_extend(dst, src->size); 147 for (uint32_t i = 0; i < src->used; ++i) { 148 array_insert_unique(dst, src->data[i]->fn->copy(src->data[i])); 149 } 150 } 151 152 void array_free(array * const a) { 153 if (!a) return; 154 array_free_data(a); 155 free(a); 156 } 157 158 void array_reset_data_strings(array * const a) { 159 if (!a) return; 160 161 data_string ** const data = (data_string **)a->data; 162 const uint32_t used = a->used; 163 a->used = 0; 164 for (uint32_t i = 0; i < used; ++i) { 165 data_string * const ds = data[i]; 166 /*force_assert(ds->type == TYPE_STRING);*/ 167 buffer_reset(&ds->key); 168 buffer_reset(&ds->value); 169 } 170 } 171 172 #if 0 /*(unused; see array_extract_element_klen())*/ 173 data_unset *array_pop(array * const a) { 174 data_unset *du; 175 176 force_assert(a->used != 0); 177 178 a->used --; 179 du = a->data[a->used]; 180 force_assert(a->sorted[a->used] == du); /* only works on "simple" lists */ 181 a->data[a->used] = NULL; 182 183 return du; 184 } 185 #endif 186 187 __attribute_pure__ 188 static int array_caseless_compare(const char * const a, const char * const b, const uint32_t len) { 189 for (uint32_t i = 0; i < len; ++i) { 190 unsigned int ca = ((unsigned char *)a)[i]; 191 unsigned int cb = ((unsigned char *)b)[i]; 192 if (ca == cb) continue; 193 194 /* always lowercase for transitive results */ 195 if (light_isupper(ca)) ca |= 0x20; 196 if (light_isupper(cb)) cb |= 0x20; 197 198 if (ca == cb) continue; 199 return (int)(ca - cb); 200 } 201 return 0; 202 } 203 204 __attribute_pure__ 205 static int array_keycmp(const char * const a, const uint32_t alen, const char * const b, const uint32_t blen) { 206 return alen < blen ? -1 : alen > blen ? 1 : array_caseless_compare(a, b, blen); 207 } 208 209 __attribute_cold__ 210 __attribute_pure__ 211 static int array_keycmpb(const char * const k, const uint32_t klen, const buffer * const b) { 212 /* key is non-empty (0==b->used), though possibly blank (1==b->used) 213 * if inserted into key-value array */ 214 /*force_assert(b && b->used);*/ 215 return array_keycmp(k, klen, b->ptr, b->used-1); 216 /*return array_keycmp(k, klen, BUF_PTR_LEN(b));*/ 217 } 218 219 /* returns pos into a->sorted[] which contains copy of data (ptr) in a->data[] 220 * if pos >= 0, or returns -pos-1 if that is the position-1 in a->sorted[] 221 * where the key needs to be inserted (-1 to avoid -0) 222 */ 223 __attribute_hot__ 224 __attribute_pure__ 225 static int32_t array_get_index_ext(const array * const a, const int ext, const char * const k, const uint32_t klen) { 226 /* invariant: [lower-1] < probe < [upper] 227 * invariant: 0 <= lower <= upper <= a->used 228 */ 229 uint_fast32_t lower = 0, upper = a->used; 230 while (lower != upper) { 231 const uint_fast32_t probe = (lower + upper) / 2; 232 const int x = ((data_string *)a->sorted[probe])->ext; 233 /* (compare strings only if ext is 0 for both)*/ 234 const int e = (ext|x) 235 ? ext 236 : array_keycmpb(k, klen, &a->sorted[probe]->key); 237 if (e < x) /* e < [probe] */ 238 upper = probe; /* still: lower <= upper */ 239 else if (e > x) /* e > [probe] */ 240 lower = probe + 1; /* still: lower <= upper */ 241 else /*(e == x)*/ /* found */ 242 return (int32_t)probe; 243 } 244 /* not found: [lower-1] < key < [upper] = [lower] ==> insert at [lower] */ 245 return -(int)lower - 1; 246 } 247 248 data_unset *array_get_element_klen_ext(const array * const a, const int ext, const char *key, const uint32_t klen) { 249 const int32_t ipos = array_get_index_ext(a, ext, key, klen); 250 return ipos >= 0 ? a->sorted[ipos] : NULL; 251 } 252 253 /* returns pos into a->sorted[] which contains copy of data (ptr) in a->data[] 254 * if pos >= 0, or returns -pos-1 if that is the position-1 in a->sorted[] 255 * where the key needs to be inserted (-1 to avoid -0) 256 */ 257 __attribute_hot__ 258 __attribute_pure__ 259 static int32_t array_get_index(const array * const a, const char * const k, const uint32_t klen) { 260 /* invariant: [lower-1] < probe < [upper] 261 * invariant: 0 <= lower <= upper <= a->used 262 */ 263 uint_fast32_t lower = 0, upper = a->used; 264 while (lower != upper) { 265 uint_fast32_t probe = (lower + upper) / 2; 266 const buffer * const b = &a->sorted[probe]->key; 267 /* key is non-empty (0==b->used), though possibly blank (1==b->used), 268 * if inserted into key-value array */ 269 /*force_assert(b && b->used);*/ 270 int cmp = array_keycmp(k, klen, b->ptr, b->used-1); 271 /*int cmp = array_keycmp(k, klen, BUF_PTR_LEN(b));*/ 272 if (cmp < 0) /* key < [probe] */ 273 upper = probe; /* still: lower <= upper */ 274 else if (cmp > 0) /* key > [probe] */ 275 lower = probe + 1; /* still: lower <= upper */ 276 else /*(cmp == 0)*/ /* found */ 277 return (int32_t)probe; 278 } 279 /* not found: [lower-1] < key < [upper] = [lower] ==> insert at [lower] */ 280 return -(int)lower - 1; 281 } 282 283 __attribute_hot__ 284 const data_unset *array_get_element_klen(const array * const a, const char *key, const uint32_t klen) { 285 const int32_t ipos = array_get_index(a, key, klen); 286 return ipos >= 0 ? a->sorted[ipos] : NULL; 287 } 288 289 /* non-const (data_config *) for configparser.y (not array_get_element_klen())*/ 290 data_unset *array_get_data_unset(const array * const a, const char *key, const uint32_t klen) { 291 const int32_t ipos = array_get_index(a, key, klen); 292 return ipos >= 0 ? a->sorted[ipos] : NULL; 293 } 294 295 data_unset *array_extract_element_klen(array * const a, const char *key, const uint32_t klen) { 296 const int32_t ipos = array_get_index(a, key, klen); 297 if (ipos < 0) return NULL; 298 299 /* remove entry from a->sorted: move everything after pos one step left */ 300 data_unset * const entry = a->sorted[ipos]; 301 const uint32_t last_ndx = --a->used; 302 if (last_ndx != (uint32_t)ipos) { 303 data_unset ** const d = a->sorted + ipos; 304 memmove(d, d+1, (last_ndx - (uint32_t)ipos) * sizeof(*d)); 305 } 306 307 if (entry != a->data[last_ndx]) { 308 /* walk a->data[] to find data ptr */ 309 /* (not checking (ndx <= last_ndx) since entry must be in a->data[]) */ 310 uint32_t ndx = 0; 311 while (entry != a->data[ndx]) ++ndx; 312 a->data[ndx] = a->data[last_ndx]; /* swap with last element */ 313 } 314 a->data[last_ndx] = NULL; 315 return entry; 316 } 317 318 static data_unset *array_get_unused_element(array * const a, const data_type_t t) { 319 /* After initial startup and config, most array usage is of homogeneous types 320 * and arrays are cleared once per request, so check only the first unused 321 * element to see if it can be reused */ 322 #if 1 323 data_unset * const du = (a->used < a->size) ? a->data[a->used] : NULL; 324 if (NULL != du && du->type == t) { 325 a->data[a->used] = NULL;/* make empty slot at a->used for next insert */ 326 return du; 327 } 328 return NULL; 329 #else 330 data_unset ** const data = a->data; 331 for (uint32_t i = a->used, sz = a->size; i < sz; ++i) { 332 if (data[i] && data[i]->type == t) { 333 data_unset * const ds = data[i]; 334 335 /* make empty slot at a->used for next insert */ 336 data[i] = data[a->used]; 337 data[a->used] = NULL; 338 339 return ds; 340 } 341 } 342 343 return NULL; 344 #endif 345 } 346 347 __attribute_hot__ 348 static data_unset * array_insert_data_at_pos(array * const a, data_unset * const entry, const uint_fast32_t pos) { 349 if (a->used < a->size) { 350 data_unset * const prev = a->data[a->used]; 351 if (__builtin_expect( (prev != NULL), 0)) 352 prev->fn->free(prev); /* free prior data, if any, from slot */ 353 } 354 else { 355 array_extend(a, 16); 356 } 357 358 uint_fast32_t ndx = a->used++; 359 a->data[ndx] = entry; 360 361 /* move everything one step to the right */ 362 ndx -= pos; 363 data_unset ** const d = a->sorted + pos; 364 if (__builtin_expect( (ndx), 1)) 365 memmove(d+1, d, ndx * sizeof(*a->sorted)); 366 *d = entry; 367 return entry; 368 } 369 370 static data_integer * array_insert_integer_at_pos(array * const a, const uint_fast32_t pos) { 371 #if 0 /*(not currently used by lighttpd in way that reuse would occur)*/ 372 data_integer *di = (data_integer *)array_get_unused_element(a,TYPE_INTEGER); 373 if (NULL == di) di = array_data_integer_init(); 374 #else 375 data_integer * const di = array_data_integer_init(); 376 #endif 377 return (data_integer *)array_insert_data_at_pos(a, (data_unset *)di, pos); 378 } 379 380 __attribute_hot__ 381 static data_string * array_insert_string_at_pos(array * const a, const uint_fast32_t pos) { 382 data_string *ds = (data_string *)array_get_unused_element(a, TYPE_STRING); 383 if (NULL == ds) ds = array_data_string_init(); 384 return (data_string *)array_insert_data_at_pos(a, (data_unset *)ds, pos); 385 } 386 387 __attribute_hot__ 388 buffer * array_get_buf_ptr_ext(array * const a, const int ext, const char * const k, const uint32_t klen) { 389 int32_t ipos = array_get_index_ext(a, ext, k, klen); 390 if (ipos >= 0) return &((data_string *)a->sorted[ipos])->value; 391 392 data_string * const ds = array_insert_string_at_pos(a, (uint32_t)(-ipos-1)); 393 ds->ext = ext; 394 buffer_copy_string_len(&ds->key, k, klen); 395 buffer_clear(&ds->value); 396 return &ds->value; 397 } 398 399 int * array_get_int_ptr(array * const a, const char * const k, const uint32_t klen) { 400 int32_t ipos = array_get_index(a, k, klen); 401 if (ipos >= 0) return &((data_integer *)a->sorted[ipos])->value; 402 403 data_integer * const di =array_insert_integer_at_pos(a,(uint32_t)(-ipos-1)); 404 buffer_copy_string_len(&di->key, k, klen); 405 di->value = 0; 406 return &di->value; 407 } 408 409 buffer * array_get_buf_ptr(array * const a, const char * const k, const uint32_t klen) { 410 int32_t ipos = array_get_index(a, k, klen); 411 if (ipos >= 0) return &((data_string *)a->sorted[ipos])->value; 412 413 data_string * const ds = array_insert_string_at_pos(a, (uint32_t)(-ipos-1)); 414 buffer_copy_string_len(&ds->key, k, klen); 415 buffer_clear(&ds->value); 416 return &ds->value; 417 } 418 419 void array_insert_value(array * const a, const char * const v, const uint32_t vlen) { 420 data_string * const ds = array_insert_string_at_pos(a, a->used); 421 buffer_clear(&ds->key); 422 buffer_copy_string_len(&ds->value, v, vlen); 423 } 424 425 /* if entry already exists return pointer to existing entry, otherwise insert entry and return NULL */ 426 __attribute_cold__ 427 static data_unset **array_find_or_insert(array * const a, data_unset * const entry) { 428 force_assert(NULL != entry); 429 430 /* push value onto end of array if there is no key */ 431 if (buffer_is_unset(&entry->key)) { 432 array_insert_data_at_pos(a, entry, a->used); 433 return NULL; 434 } 435 436 /* try to find the entry */ 437 const int32_t ipos = array_get_index(a, BUF_PTR_LEN(&entry->key)); 438 if (ipos >= 0) return &a->sorted[ipos]; 439 440 array_insert_data_at_pos(a, entry, (uint32_t)(-ipos - 1)); 441 return NULL; 442 } 443 444 /* replace or insert data (free existing entry) */ 445 void array_replace(array * const a, data_unset * const entry) { 446 if (NULL == array_find_or_insert(a, entry)) return; 447 448 /* find the entry (array_find_or_insert() returned non-NULL) */ 449 const int32_t ipos = array_get_index(a, BUF_PTR_LEN(&entry->key)); 450 force_assert(ipos >= 0); 451 data_unset *old = a->sorted[ipos]; 452 force_assert(old != entry); 453 a->sorted[ipos] = entry; 454 455 uint32_t i = 0; 456 while (i < a->used && a->data[i] != old) ++i; 457 force_assert(i != a->used); 458 a->data[i] = entry; 459 460 old->fn->free(old); 461 } 462 463 void array_insert_unique(array * const a, data_unset * const entry) { 464 data_unset **old; 465 466 if (NULL != (old = array_find_or_insert(a, entry))) { 467 if (entry->fn->insert_dup) { 468 force_assert((*old)->type == entry->type); 469 entry->fn->insert_dup(*old, entry); 470 } 471 entry->fn->free(entry); 472 } 473 } 474 475 int array_is_vlist(const array * const a) { 476 for (uint32_t i = 0; i < a->used; ++i) { 477 data_unset *du = a->data[i]; 478 if (!buffer_is_unset(&du->key) || du->type != TYPE_STRING) return 0; 479 } 480 return 1; 481 } 482 483 int array_is_kvany(const array * const a) { 484 for (uint32_t i = 0; i < a->used; ++i) { 485 data_unset *du = a->data[i]; 486 if (buffer_is_unset(&du->key)) return 0; 487 } 488 return 1; 489 } 490 491 int array_is_kvarray(const array * const a) { 492 for (uint32_t i = 0; i < a->used; ++i) { 493 data_unset *du = a->data[i]; 494 if (buffer_is_unset(&du->key) || du->type != TYPE_ARRAY) return 0; 495 } 496 return 1; 497 } 498 499 int array_is_kvstring(const array * const a) { 500 for (uint32_t i = 0; i < a->used; ++i) { 501 data_unset *du = a->data[i]; 502 if (buffer_is_unset(&du->key) || du->type != TYPE_STRING) return 0; 503 } 504 return 1; 505 } 506 507 /* array_match_*() routines follow very similar pattern, but operate on slightly 508 * different data: array key/value, prefix/suffix match, case-insensitive or not 509 * While these could be combined into fewer routines with flags to modify the 510 * behavior, the interface distinctions are useful to add clarity to the code, 511 * and the specialized routines run slightly faster */ 512 513 data_unset * 514 array_match_key_prefix_klen (const array * const a, const char * const s, const uint32_t slen) 515 { 516 for (uint32_t i = 0; i < a->used; ++i) { 517 const buffer * const key = &a->data[i]->key; 518 const uint32_t klen = buffer_clen(key); 519 if (klen <= slen && 0 == memcmp(s, key->ptr, klen)) 520 return a->data[i]; 521 } 522 return NULL; 523 } 524 525 data_unset * 526 array_match_key_prefix_nc_klen (const array * const a, const char * const s, const uint32_t slen) 527 { 528 for (uint32_t i = 0; i < a->used; ++i) { 529 const buffer * const key = &a->data[i]->key; 530 const uint32_t klen = buffer_clen(key); 531 if (klen <= slen && buffer_eq_icase_ssn(s, key->ptr, klen)) 532 return a->data[i]; 533 } 534 return NULL; 535 } 536 537 data_unset * 538 array_match_key_prefix (const array * const a, const buffer * const b) 539 { 540 #ifdef __clang_analyzer__ 541 force_assert(b); 542 #endif 543 return array_match_key_prefix_klen(a, BUF_PTR_LEN(b)); 544 } 545 546 data_unset * 547 array_match_key_prefix_nc (const array * const a, const buffer * const b) 548 { 549 return array_match_key_prefix_nc_klen(a, BUF_PTR_LEN(b)); 550 } 551 552 const buffer * 553 array_match_value_prefix (const array * const a, const buffer * const b) 554 { 555 const uint32_t blen = buffer_clen(b); 556 557 for (uint32_t i = 0; i < a->used; ++i) { 558 const buffer * const value = &((data_string *)a->data[i])->value; 559 const uint32_t vlen = buffer_clen(value); 560 if (vlen <= blen && 0 == memcmp(b->ptr, value->ptr, vlen)) 561 return value; 562 } 563 return NULL; 564 } 565 566 const buffer * 567 array_match_value_prefix_nc (const array * const a, const buffer * const b) 568 { 569 const uint32_t blen = buffer_clen(b); 570 571 for (uint32_t i = 0; i < a->used; ++i) { 572 const buffer * const value = &((data_string *)a->data[i])->value; 573 const uint32_t vlen = buffer_clen(value); 574 if (vlen <= blen && buffer_eq_icase_ssn(b->ptr, value->ptr, vlen)) 575 return value; 576 } 577 return NULL; 578 } 579 580 data_unset * 581 array_match_key_suffix (const array * const a, const buffer * const b) 582 { 583 const uint32_t blen = buffer_clen(b); 584 const char * const end = b->ptr + blen; 585 586 for (uint32_t i = 0; i < a->used; ++i) { 587 const buffer * const key = &a->data[i]->key; 588 const uint32_t klen = buffer_clen(key); 589 if (klen <= blen && 0 == memcmp(end - klen, key->ptr, klen)) 590 return a->data[i]; 591 } 592 return NULL; 593 } 594 595 data_unset * 596 array_match_key_suffix_nc (const array * const a, const buffer * const b) 597 { 598 const uint32_t blen = buffer_clen(b); 599 const char * const end = b->ptr + blen; 600 601 for (uint32_t i = 0; i < a->used; ++i) { 602 const buffer * const key = &a->data[i]->key; 603 const uint32_t klen = buffer_clen(key); 604 if (klen <= blen && buffer_eq_icase_ssn(end - klen, key->ptr, klen)) 605 return a->data[i]; 606 } 607 return NULL; 608 } 609 610 const buffer * 611 array_match_value_suffix (const array * const a, const buffer * const b) 612 { 613 const uint32_t blen = buffer_clen(b); 614 const char * const end = b->ptr + blen; 615 616 for (uint32_t i = 0; i < a->used; ++i) { 617 const buffer * const value = &((data_string *)a->data[i])->value; 618 const uint32_t vlen = buffer_clen(value); 619 if (vlen <= blen && 0 == memcmp(end - vlen, value->ptr, vlen)) 620 return value; 621 } 622 return NULL; 623 } 624 625 const buffer * 626 array_match_value_suffix_nc (const array * const a, const buffer * const b) 627 { 628 const uint32_t blen = buffer_clen(b); 629 const char * const end = b->ptr + blen; 630 631 for (uint32_t i = 0; i < a->used; ++i) { 632 const buffer * const value = &((data_string *)a->data[i])->value; 633 const uint32_t vlen = buffer_clen(value); 634 if (vlen <= blen && buffer_eq_icase_ssn(end - vlen, value->ptr, vlen)) 635 return value; 636 } 637 return NULL; 638 } 639 640 data_unset * 641 array_match_path_or_ext (const array * const a, const buffer * const b) 642 { 643 const uint32_t blen = buffer_clen(b); 644 645 for (uint32_t i = 0; i < a->used; ++i) { 646 /* check extension in the form "^/path" or ".ext$" */ 647 const buffer * const key = &a->data[i]->key; 648 const uint32_t klen = buffer_clen(key); 649 if (klen <= blen 650 && 0 == memcmp((*(key->ptr) == '/' ? b->ptr : b->ptr + blen - klen), 651 key->ptr, klen)) 652 return a->data[i]; 653 } 654 return NULL; 655 } 656