1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright(c) 2018 Intel Corporation 3 */ 4 5 #include <ctype.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 10 #include <rte_string_fns.h> 11 #include <rte_cryptodev.h> 12 #include <rte_malloc.h> 13 14 #include "fips_validation.h" 15 16 #define skip_white_spaces(pos) \ 17 ({ \ 18 __typeof__(pos) _p = (pos); \ 19 for ( ; isspace(*_p); _p++) \ 20 ; \ 21 _p; \ 22 }) 23 24 static int 25 get_file_line(void) 26 { 27 FILE *fp = info.fp_rd; 28 char *line = info.one_line_text; 29 int ret; 30 uint32_t loc = 0; 31 32 memset(line, 0, MAX_LINE_CHAR); 33 while ((ret = fgetc(fp)) != EOF) { 34 char c = (char)ret; 35 36 if (loc >= MAX_LINE_CHAR - 1) 37 return -ENOMEM; 38 if (c == '\n') 39 break; 40 line[loc++] = c; 41 } 42 43 if (ret == EOF) 44 return -EOF; 45 46 return 0; 47 } 48 49 int 50 fips_test_fetch_one_block(void) 51 { 52 size_t size; 53 int ret = 0; 54 uint32_t i; 55 56 for (i = 0; i < info.nb_vec_lines; i++) { 57 free(info.vec[i]); 58 info.vec[i] = NULL; 59 } 60 61 i = 0; 62 do { 63 if (i >= MAX_LINE_PER_VECTOR) { 64 ret = -ENOMEM; 65 goto error_exit; 66 } 67 68 ret = get_file_line(); 69 size = strlen(info.one_line_text); 70 if (size == 0) 71 break; 72 73 info.vec[i] = calloc(1, size + 5); 74 if (info.vec[i] == NULL) 75 goto error_exit; 76 77 strlcpy(info.vec[i], info.one_line_text, size + 1); 78 i++; 79 } while (ret == 0); 80 81 info.nb_vec_lines = i; 82 83 return ret; 84 85 error_exit: 86 for (i = 0; i < MAX_LINE_PER_VECTOR; i++) 87 if (info.vec[i] != NULL) { 88 free(info.vec[i]); 89 info.vec[i] = NULL; 90 } 91 92 info.nb_vec_lines = 0; 93 94 return -ENOMEM; 95 } 96 97 static void 98 fips_test_parse_version(void) 99 { 100 int len = strlen(info.vec[0]); 101 char *ptr = info.vec[0]; 102 103 info.version = strtof(ptr + len - 4, NULL); 104 } 105 106 static int 107 fips_test_parse_header(void) 108 { 109 uint32_t i; 110 char *tmp; 111 int ret; 112 int algo_parsed = 0; 113 time_t t = time(NULL); 114 struct tm *tm_now = localtime(&t); 115 116 ret = fips_test_fetch_one_block(); 117 if (ret < 0) 118 return ret; 119 120 if (info.nb_vec_lines) 121 fips_test_parse_version(); 122 123 for (i = 0; i < info.nb_vec_lines; i++) { 124 if (!algo_parsed) { 125 if (strstr(info.vec[i], "AES")) { 126 algo_parsed = 1; 127 info.algo = FIPS_TEST_ALGO_AES; 128 ret = parse_test_aes_init(); 129 if (ret < 0) 130 return ret; 131 } else if (strstr(info.vec[i], "GCM")) { 132 algo_parsed = 1; 133 info.algo = FIPS_TEST_ALGO_AES_GCM; 134 ret = parse_test_gcm_init(); 135 if (ret < 0) 136 return ret; 137 } else if (strstr(info.vec[i], "CMAC")) { 138 algo_parsed = 1; 139 info.algo = FIPS_TEST_ALGO_AES_CMAC; 140 ret = parse_test_cmac_init(); 141 if (ret < 0) 142 return 0; 143 } else if (strstr(info.vec[i], "CCM")) { 144 algo_parsed = 1; 145 info.algo = FIPS_TEST_ALGO_AES_CCM; 146 ret = parse_test_ccm_init(); 147 if (ret < 0) 148 return 0; 149 } else if (strstr(info.vec[i], "HMAC")) { 150 algo_parsed = 1; 151 info.algo = FIPS_TEST_ALGO_HMAC; 152 ret = parse_test_hmac_init(); 153 if (ret < 0) 154 return ret; 155 } else if (strstr(info.vec[i], "TDES")) { 156 algo_parsed = 1; 157 info.algo = FIPS_TEST_ALGO_TDES; 158 ret = parse_test_tdes_init(); 159 if (ret < 0) 160 return 0; 161 } else if (strstr(info.vec[i], "PERMUTATION")) { 162 algo_parsed = 1; 163 info.algo = FIPS_TEST_ALGO_TDES; 164 ret = parse_test_tdes_init(); 165 if (ret < 0) 166 return 0; 167 } else if (strstr(info.vec[i], "VARIABLE")) { 168 algo_parsed = 1; 169 info.algo = FIPS_TEST_ALGO_TDES; 170 ret = parse_test_tdes_init(); 171 if (ret < 0) 172 return 0; 173 } else if (strstr(info.vec[i], "SUBSTITUTION")) { 174 algo_parsed = 1; 175 info.algo = FIPS_TEST_ALGO_TDES; 176 ret = parse_test_tdes_init(); 177 if (ret < 0) 178 return 0; 179 } else if (strstr(info.vec[i], "SHA-")) { 180 algo_parsed = 1; 181 info.algo = FIPS_TEST_ALGO_SHA; 182 ret = parse_test_sha_init(); 183 if (ret < 0) 184 return ret; 185 } else if (strstr(info.vec[i], "XTS")) { 186 algo_parsed = 1; 187 info.algo = FIPS_TEST_ALGO_AES_XTS; 188 ret = parse_test_xts_init(); 189 if (ret < 0) 190 return ret; 191 } 192 } 193 194 tmp = strstr(info.vec[i], "# Config info for "); 195 if (tmp != NULL) { 196 fprintf(info.fp_wr, "%s%s\n", "# Config info for DPDK Cryptodev ", 197 info.device_name); 198 continue; 199 } 200 201 tmp = strstr(info.vec[i], "# HMAC information for "); 202 if (tmp != NULL) { 203 fprintf(info.fp_wr, "%s%s\n", "# HMAC information for " 204 "DPDK Cryptodev ", 205 info.device_name); 206 continue; 207 } 208 209 tmp = strstr(info.vec[i], "# Config Info for : "); 210 if (tmp != NULL) { 211 212 fprintf(info.fp_wr, "%s%s\n", "# Config Info for DPDK Cryptodev : ", 213 info.device_name); 214 continue; 215 } 216 217 tmp = strstr(info.vec[i], "# information for "); 218 if (tmp != NULL) { 219 220 char tmp_output[128] = {0}; 221 222 strlcpy(tmp_output, info.vec[i], tmp - info.vec[i] + 1); 223 224 fprintf(info.fp_wr, "%s%s%s\n", tmp_output, 225 "information for DPDK Cryptodev ", 226 info.device_name); 227 continue; 228 } 229 230 tmp = strstr(info.vec[i], " test information for "); 231 if (tmp != NULL) { 232 char tmp_output[128] = {0}; 233 234 strlcpy(tmp_output, info.vec[i], tmp - info.vec[i] + 1); 235 236 fprintf(info.fp_wr, "%s%s%s\n", tmp_output, 237 "test information for DPDK Cryptodev ", 238 info.device_name); 239 continue; 240 } 241 242 tmp = strstr(info.vec[i], "\" information for \""); 243 if (tmp != NULL) { 244 char tmp_output[128] = {0}; 245 246 strlcpy(tmp_output, info.vec[i], tmp - info.vec[i] + 1); 247 248 fprintf(info.fp_wr, "%s%s%s\n", tmp_output, 249 "\" information for DPDK Cryptodev ", 250 info.device_name); 251 continue; 252 } 253 254 if (i == info.nb_vec_lines - 1) { 255 /** update the time as current time, write to file */ 256 fprintf(info.fp_wr, "%s%s\n", "# Generated on ", 257 asctime(tm_now)); 258 continue; 259 } 260 261 /* to this point, no field need to update, 262 * only copy to rsp file 263 */ 264 fprintf(info.fp_wr, "%s\n", info.vec[i]); 265 } 266 267 return 0; 268 } 269 270 static int 271 parse_file_type(const char *path) 272 { 273 const char *tmp = path + strlen(path) - 3; 274 275 if (strstr(tmp, REQ_FILE_PREFIX)) 276 info.file_type = FIPS_TYPE_REQ; 277 else if (strstr(tmp, RSP_FILE_PREFIX)) 278 info.file_type = FIPS_TYPE_RSP; 279 else if (strstr(path, FAX_FILE_PREFIX)) 280 info.file_type = FIPS_TYPE_FAX; 281 else if (strstr(path, JSON_FILE_PREFIX)) 282 info.file_type = FIPS_TYPE_JSON; 283 else 284 return -EINVAL; 285 286 return 0; 287 } 288 289 int 290 fips_test_init(const char *req_file_path, const char *rsp_file_path, 291 const char *device_name) 292 { 293 if (strcmp(req_file_path, rsp_file_path) == 0) { 294 RTE_LOG(ERR, USER1, "File paths cannot be the same\n"); 295 return -EINVAL; 296 } 297 298 fips_test_clear(); 299 300 if (rte_strscpy(info.file_name, req_file_path, 301 sizeof(info.file_name)) < 0) { 302 RTE_LOG(ERR, USER1, "Path %s too long\n", req_file_path); 303 return -EINVAL; 304 } 305 info.algo = FIPS_TEST_ALGO_MAX; 306 if (parse_file_type(req_file_path) < 0) { 307 RTE_LOG(ERR, USER1, "File %s type not supported\n", 308 req_file_path); 309 return -EINVAL; 310 } 311 312 info.fp_rd = fopen(req_file_path, "r"); 313 if (!info.fp_rd) { 314 RTE_LOG(ERR, USER1, "Cannot open file %s\n", req_file_path); 315 return -EINVAL; 316 } 317 318 if (info.file_type == FIPS_TYPE_JSON) { 319 #ifdef USE_JANSSON 320 json_error_t error; 321 json_info.json_root = json_loadf(info.fp_rd, 0, &error); 322 if (!json_info.json_root) { 323 RTE_LOG(ERR, USER1, "Cannot parse json file %s (line %d, column %d)\n", 324 req_file_path, error.line, error.column); 325 return -EINVAL; 326 } 327 #else /* USE_JANSSON */ 328 RTE_LOG(ERR, USER1, "No json library configured.\n"); 329 return -EINVAL; 330 #endif /* USE_JANSSON */ 331 } 332 333 info.fp_wr = fopen(rsp_file_path, "w"); 334 if (!info.fp_wr) { 335 RTE_LOG(ERR, USER1, "Cannot open file %s\n", rsp_file_path); 336 return -EINVAL; 337 } 338 339 info.one_line_text = calloc(1, MAX_LINE_CHAR); 340 if (!info.one_line_text) { 341 RTE_LOG(ERR, USER1, "Insufficient memory\n"); 342 return -ENOMEM; 343 } 344 345 if (rte_strscpy(info.device_name, device_name, 346 sizeof(info.device_name)) < 0) { 347 RTE_LOG(ERR, USER1, "Device name %s too long\n", device_name); 348 return -EINVAL; 349 } 350 351 if (info.file_type == FIPS_TYPE_JSON) 352 return 0; 353 354 if (fips_test_parse_header() < 0) { 355 RTE_LOG(ERR, USER1, "Failed parsing header\n"); 356 return -1; 357 } 358 359 return 0; 360 } 361 362 void 363 fips_test_clear(void) 364 { 365 if (info.fp_rd) 366 fclose(info.fp_rd); 367 if (info.fp_wr) 368 fclose(info.fp_wr); 369 free(info.one_line_text); 370 if (info.nb_vec_lines) { 371 uint32_t i; 372 373 for (i = 0; i < info.nb_vec_lines; i++) 374 free(info.vec[i]); 375 } 376 377 memset(&info, 0, sizeof(info)); 378 } 379 380 int 381 fips_test_parse_one_case(void) 382 { 383 uint32_t i, j = 0; 384 uint32_t is_interim; 385 uint32_t interim_cnt = 0; 386 int ret; 387 388 info.vec_start_off = 0; 389 390 if (info.interim_callbacks) { 391 for (i = 0; i < info.nb_vec_lines; i++) { 392 is_interim = 0; 393 for (j = 0; info.interim_callbacks[j].key != NULL; j++) 394 if (strstr(info.vec[i], 395 info.interim_callbacks[j].key)) { 396 is_interim = 1; 397 398 ret = info.interim_callbacks[j].cb( 399 info.interim_callbacks[j].key, 400 info.vec[i], 401 info.interim_callbacks[j].val); 402 if (ret < 0) 403 return ret; 404 } 405 406 if (is_interim) 407 interim_cnt += 1; 408 } 409 } 410 411 if (interim_cnt) { 412 if (info.version == 21.4f) { 413 for (i = 0; i < interim_cnt; i++) 414 fprintf(info.fp_wr, "%s\n", info.vec[i]); 415 fprintf(info.fp_wr, "\n"); 416 417 if (info.nb_vec_lines == interim_cnt) 418 return 1; 419 } else { 420 for (i = 0; i < info.nb_vec_lines; i++) 421 fprintf(info.fp_wr, "%s\n", info.vec[i]); 422 fprintf(info.fp_wr, "\n"); 423 return 1; 424 } 425 } 426 427 info.vec_start_off = interim_cnt; 428 429 for (i = info.vec_start_off; i < info.nb_vec_lines; i++) { 430 for (j = 0; info.callbacks[j].key != NULL; j++) 431 if (strstr(info.vec[i], info.callbacks[j].key)) { 432 ret = info.callbacks[j].cb( 433 info.callbacks[j].key, 434 info.vec[i], info.callbacks[j].val); 435 if (ret < 0) 436 return ret; 437 break; 438 } 439 } 440 441 return 0; 442 } 443 444 void 445 fips_test_write_one_case(void) 446 { 447 uint32_t i; 448 449 for (i = info.vec_start_off; i < info.nb_vec_lines; i++) 450 fprintf(info.fp_wr, "%s\n", info.vec[i]); 451 } 452 453 #ifdef USE_JANSSON 454 int 455 fips_test_parse_one_json_vector_set(void) 456 { 457 json_t *algo_obj = json_object_get(json_info.json_vector_set, "algorithm"); 458 const char *algo_str = json_string_value(algo_obj); 459 460 /* Vector sets contain the algorithm type, and nothing else we need. */ 461 if (strstr(algo_str, "AES-GCM")) 462 info.algo = FIPS_TEST_ALGO_AES_GCM; 463 else if (strstr(algo_str, "AES-CCM")) 464 info.algo = FIPS_TEST_ALGO_AES_CCM; 465 else if (strstr(algo_str, "AES-GMAC")) 466 info.algo = FIPS_TEST_ALGO_AES_GMAC; 467 else if (strstr(algo_str, "HMAC")) 468 info.algo = FIPS_TEST_ALGO_HMAC; 469 else if (strstr(algo_str, "CMAC")) 470 info.algo = FIPS_TEST_ALGO_AES_CMAC; 471 else if (strstr(algo_str, "AES-CBC")) 472 info.algo = FIPS_TEST_ALGO_AES_CBC; 473 else if (strstr(algo_str, "AES-XTS")) 474 info.algo = FIPS_TEST_ALGO_AES_XTS; 475 else if (strstr(algo_str, "AES-CTR")) 476 info.algo = FIPS_TEST_ALGO_AES_CTR; 477 else if (strstr(algo_str, "SHA")) 478 info.algo = FIPS_TEST_ALGO_SHA; 479 else if (strstr(algo_str, "TDES-CBC") || 480 strstr(algo_str, "TDES-ECB")) 481 info.algo = FIPS_TEST_ALGO_TDES; 482 else if (strstr(algo_str, "RSA")) 483 info.algo = FIPS_TEST_ALGO_RSA; 484 else if (strstr(algo_str, "ECDSA")) 485 info.algo = FIPS_TEST_ALGO_ECDSA; 486 else 487 return -EINVAL; 488 489 return 0; 490 } 491 492 int 493 fips_test_parse_one_json_group(void) 494 { 495 int ret, i; 496 json_t *param; 497 498 if (info.interim_callbacks) { 499 char json_value[FIPS_TEST_JSON_BUF_LEN]; 500 for (i = 0; info.interim_callbacks[i].key != NULL; i++) { 501 param = json_object_get(json_info.json_test_group, 502 info.interim_callbacks[i].key); 503 if (!param) 504 continue; 505 506 switch (json_typeof(param)) { 507 case JSON_STRING: 508 snprintf(json_value, sizeof(json_value), "%s", 509 json_string_value(param)); 510 break; 511 512 case JSON_INTEGER: 513 snprintf(json_value, sizeof(json_value), "%"JSON_INTEGER_FORMAT, 514 json_integer_value(param)); 515 break; 516 517 default: 518 return -EINVAL; 519 } 520 521 ret = info.interim_callbacks[i].cb( 522 info.interim_callbacks[i].key, json_value, 523 info.interim_callbacks[i].val 524 ); 525 if (ret < 0) 526 return ret; 527 } 528 529 if (info.parse_interim_writeback) { 530 ret = info.parse_interim_writeback(NULL); 531 if (ret < 0) 532 return ret; 533 } 534 } 535 536 return 0; 537 } 538 539 int 540 fips_test_parse_one_json_case(void) 541 { 542 uint32_t i; 543 int ret = 0; 544 json_t *param; 545 546 for (i = 0; info.callbacks[i].key != NULL; i++) { 547 param = json_object_get(json_info.json_test_case, info.callbacks[i].key); 548 if (!param) 549 continue; 550 551 switch (json_typeof(param)) { 552 case JSON_STRING: 553 snprintf(info.one_line_text, MAX_LINE_CHAR, "%s", 554 json_string_value(param)); 555 break; 556 557 case JSON_INTEGER: 558 snprintf(info.one_line_text, MAX_LINE_CHAR, "%"JSON_INTEGER_FORMAT, 559 json_integer_value(param)); 560 break; 561 562 default: 563 return -EINVAL; 564 } 565 566 ret = info.callbacks[i].cb(info.callbacks[i].key, info.one_line_text, 567 info.callbacks[i].val); 568 if (ret < 0) 569 return ret; 570 } 571 572 return 0; 573 } 574 #endif /* USE_JANSSON */ 575 576 static int 577 parser_read_uint64_hex(uint64_t *value, const char *p) 578 { 579 char *next; 580 uint64_t val; 581 582 p = skip_white_spaces(p); 583 584 val = strtoul(p, &next, 16); 585 if (p == next) 586 return -EINVAL; 587 588 p = skip_white_spaces(next); 589 if (*p != '\0') 590 return -EINVAL; 591 592 *value = val; 593 return 0; 594 } 595 596 int 597 parser_read_uint8_hex(uint8_t *value, const char *p) 598 { 599 uint64_t val = 0; 600 int ret = parser_read_uint64_hex(&val, p); 601 602 if (ret < 0) 603 return ret; 604 605 if (val > UINT8_MAX) 606 return -ERANGE; 607 608 *value = val; 609 return 0; 610 } 611 612 int 613 parse_uint8_known_len_hex_str(const char *key, char *src, struct fips_val *val) 614 { 615 struct fips_val tmp_val = {0}; 616 uint32_t len = val->len; 617 int ret; 618 619 if (len == 0) { 620 if (val->val != NULL) { 621 rte_free(val->val); 622 val->val = NULL; 623 } 624 625 return 0; 626 } 627 628 ret = parse_uint8_hex_str(key, src, &tmp_val); 629 if (ret < 0) 630 return ret; 631 632 if (tmp_val.len == val->len) { 633 val->val = tmp_val.val; 634 return 0; 635 } 636 637 if (tmp_val.len < val->len) { 638 rte_free(tmp_val.val); 639 return -EINVAL; 640 } 641 642 val->val = rte_zmalloc(NULL, val->len, 0); 643 if (!val->val) { 644 rte_free(tmp_val.val); 645 memset(val, 0, sizeof(*val)); 646 return -ENOMEM; 647 } 648 649 memcpy(val->val, tmp_val.val, val->len); 650 rte_free(tmp_val.val); 651 652 return 0; 653 } 654 655 int 656 parse_uint8_hex_str(const char *key, char *src, struct fips_val *val) 657 { 658 uint32_t len, j; 659 660 #ifdef USE_JANSSON 661 /* 662 * Offset not applicable in case of JSON test vectors. 663 */ 664 if (info.file_type == FIPS_TYPE_JSON) { 665 RTE_SET_USED(key); 666 } else 667 #endif 668 src += strlen(key); 669 670 len = strlen(src) / 2; 671 672 if (val->val) { 673 rte_free(val->val); 674 val->val = NULL; 675 } 676 677 val->val = rte_zmalloc(NULL, len + 1, 0); 678 if (!val->val) 679 return -ENOMEM; 680 681 for (j = 0; j < len; j++) { 682 char byte[3] = {src[j * 2], src[j * 2 + 1], '\0'}; 683 684 if (parser_read_uint8_hex(&val->val[j], byte) < 0) { 685 rte_free(val->val); 686 memset(val, 0, sizeof(*val)); 687 return -EINVAL; 688 } 689 } 690 691 val->len = len; 692 693 return 0; 694 } 695 696 int 697 parser_read_uint32_val(const char *key, char *src, struct fips_val *val) 698 { 699 #ifdef USE_JANSSON 700 if (info.file_type == FIPS_TYPE_JSON) { 701 RTE_SET_USED(key); 702 703 return parser_read_uint32(&val->len, src); 704 } 705 # endif 706 char *data = src + strlen(key); 707 size_t data_len = strlen(data); 708 int ret; 709 710 if (data[data_len - 1] == ']') { 711 char *tmp_data = calloc(1, data_len + 1); 712 713 if (tmp_data == NULL) 714 return -ENOMEM; 715 716 strlcpy(tmp_data, data, data_len); 717 718 ret = parser_read_uint32(&val->len, tmp_data); 719 720 free(tmp_data); 721 } else 722 ret = parser_read_uint32(&val->len, data); 723 724 return ret; 725 } 726 727 int 728 parser_read_uint32_bit_val(const char *key, char *src, struct fips_val *val) 729 { 730 int ret; 731 732 ret = parser_read_uint32_val(key, src, val); 733 734 if (ret < 0) 735 return ret; 736 737 val->len /= 8; 738 739 return 0; 740 } 741 742 int 743 writeback_hex_str(const char *key, char *dst, struct fips_val *val) 744 { 745 char *str = dst; 746 uint32_t len; 747 748 str += strlen(key); 749 750 for (len = 0; len < val->len; len++) 751 snprintf(str + len * 2, 255, "%02x", val->val[len]); 752 753 return 0; 754 } 755 756 static int 757 parser_read_uint64(uint64_t *value, const char *p) 758 { 759 char *next; 760 uint64_t val; 761 762 p = skip_white_spaces(p); 763 if (!isdigit(*p)) 764 return -EINVAL; 765 766 val = strtoul(p, &next, 10); 767 if (p == next) 768 return -EINVAL; 769 770 p = next; 771 switch (*p) { 772 case 'T': 773 val *= 1024ULL; 774 /* fall through */ 775 case 'G': 776 val *= 1024ULL; 777 /* fall through */ 778 case 'M': 779 val *= 1024ULL; 780 /* fall through */ 781 case 'k': 782 case 'K': 783 val *= 1024ULL; 784 p++; 785 break; 786 } 787 788 p = skip_white_spaces(p); 789 if (*p != '\0') 790 return -EINVAL; 791 792 *value = val; 793 return 0; 794 } 795 796 int 797 parser_read_uint32(uint32_t *value, char *p) 798 { 799 uint64_t val = 0; 800 int ret = parser_read_uint64(&val, p); 801 802 if (ret < 0) 803 return ret; 804 805 if (val > UINT32_MAX) 806 return -EINVAL; 807 808 *value = val; 809 return 0; 810 } 811 812 int 813 parser_read_uint16(uint16_t *value, const char *p) 814 { 815 uint64_t val = 0; 816 int ret = parser_read_uint64(&val, p); 817 818 if (ret < 0) 819 return ret; 820 821 if (val > UINT16_MAX) 822 return -ERANGE; 823 824 *value = val; 825 return 0; 826 } 827 828 void 829 parse_write_hex_str(struct fips_val *src) 830 { 831 writeback_hex_str("", info.one_line_text, src); 832 833 fprintf(info.fp_wr, "%s\n", info.one_line_text); 834 } 835 836 int 837 update_info_vec(uint32_t count) 838 { 839 const struct fips_test_callback *cb; 840 uint32_t i, j; 841 842 if (!info.writeback_callbacks) 843 return -1; 844 845 cb = &info.writeback_callbacks[0]; 846 847 if ((info.version == 21.4f) && (!(strstr(info.vec[0], cb->key)))) { 848 fprintf(info.fp_wr, "%s%u\n", cb->key, count); 849 i = 0; 850 } else { 851 snprintf(info.vec[0], strlen(info.vec[0]) + 4, "%s%u", cb->key, 852 count); 853 i = 1; 854 } 855 856 for (; i < info.nb_vec_lines; i++) { 857 for (j = 1; info.writeback_callbacks[j].key != NULL; j++) { 858 cb = &info.writeback_callbacks[j]; 859 if (strstr(info.vec[i], cb->key)) { 860 cb->cb(cb->key, info.vec[i], cb->val); 861 break; 862 } 863 } 864 } 865 866 return 0; 867 } 868