| 613 | static atomic_int g_quarantine_state = CBM_QSET_UNINIT; |
| 614 | |
| 615 | static void cbm_quarantine_load(void) { |
| 616 | const char *qf = getenv("CBM_INDEX_QUARANTINE_FILE"); |
| 617 | if (!qf || !qf[0]) { |
| 618 | return; /* normal path: empty set */ |
| 619 | } |
| 620 | FILE *f = cbm_fopen(qf, "rb"); |
| 621 | if (!f) { |
| 622 | return; |
| 623 | } |
| 624 | CBMHashTable *set = cbm_ht_create(16); |
| 625 | if (!set) { |
| 626 | (void)fclose(f); |
| 627 | return; |
| 628 | } |
| 629 | char line[2048]; |
| 630 | while (fgets(line, sizeof(line), f)) { |
| 631 | size_t len = strlen(line); |
| 632 | while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) { |
| 633 | line[--len] = '\0'; |
| 634 | } |
| 635 | if (len == 0) { |
| 636 | continue; |
| 637 | } |
| 638 | /* Line format: "path\tphase" where phase is "crash" or "hang". A bare |
| 639 | * "path" line (no tab) is tolerated and defaults to phase "crash" for |
| 640 | * backward compatibility with older quarantine files. */ |
| 641 | char *tab = strchr(line, '\t'); |
| 642 | const char *phase = "crash"; |
| 643 | if (tab) { |
| 644 | *tab = '\0'; |
| 645 | if (tab[1]) { |
| 646 | phase = tab + 1; |
| 647 | } |
| 648 | } |
| 649 | if (line[0] == '\0') { |
| 650 | continue; /* empty path (line began with a tab) — skip */ |
| 651 | } |
| 652 | /* The table borrows the key + value pointers, so dup both. Intentionally |
| 653 | * never freed: the set lives for the whole (short-lived worker) process. |
| 654 | * The value stores the phase so cbm_index_quarantine_phase() can report |
| 655 | * "crash" vs "hang"; membership (cbm_index_is_quarantined) is value != NULL. */ |
| 656 | char *pval = cbm_strdup(phase); |
| 657 | if (!pval) { |
| 658 | continue; |
| 659 | } |
| 660 | if (cbm_ht_has(set, line)) { |
| 661 | /* Duplicate path line: reuse the stored key (the table borrows key |
| 662 | * pointers, so a fresh copy would leak on replace) and free the |
| 663 | * value it displaces. */ |
| 664 | free(cbm_ht_set(set, line, (void *)pval)); |
| 665 | } else { |
| 666 | char *key = cbm_strdup(line); |
| 667 | if (key) { |
| 668 | cbm_ht_set(set, key, (void *)pval); |
| 669 | } else { |
| 670 | /* Partial failure: don't leak the value copy. */ |
| 671 | free(pval); |
| 672 | } |
no test coverage detected