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