/* subscribers.c - Subscriber pool implementation */ #include #include #include #include #include #include #include #include /* Case-insensitive string comparison for email addresses */ static long subscriber_cmp(const void *a, const void *b) { const char *s1 = (const char *)a; const char *s2 = (const char *)b; return strcasecmp(s1, s2); } /* Helper to trim leading/trailing whitespace and newlines */ static char *trim_whitespace(char *str) { char *end; while (isspace((unsigned char)*str)) str++; if (*str == 0) return str; end = str + strlen(str) - 1; while (end > str && isspace((unsigned char)*end)) end--; end[1] = '\0'; return str; } int subscriber_pool_init(subscriber_pool_t *pool, const char *filepath) { if (!pool || !filepath) return -EINVAL; memset(pool, 0, sizeof(subscriber_pool_t)); pool->filepath = strdup(filepath); if (!pool->filepath) return -ENOMEM; return splay_init(&pool->tree, subscriber_cmp); } int subscriber_add(subscriber_pool_t *pool, const char *email) { if (!pool || !email || strlen(email) == 0) return -EINVAL; /* Allocate container entry */ subscriber_entry_t *entry = calloc(1, sizeof(subscriber_entry_t)); if (!entry) return -ENOMEM; entry->email = strdup(email); if (!entry->email) { free(entry); return -ENOMEM; } /* Insert into splay tree using normalized email as key */ int rc = splay_insert(&pool->tree, &entry->node, entry->email); if (rc != 0) { /* Entry already exists or insertion error */ free(entry->email); free(entry); return rc; } return 0; } int subscriber_remove(subscriber_pool_t *pool, const char *email) { if (!pool || !email) return -EINVAL; splay_node_t *node = splay_lookup(&pool->tree, email); if (!node) return -ENOENT; splay_delete(&pool->tree, node); /* Retrieve wrapping entry struct and release memory */ subscriber_entry_t *entry = (subscriber_entry_t *)node; free(entry->email); free(entry); return 0; } int subscriber_exists(subscriber_pool_t *pool, const char *email) { if (!pool || !email) return 0; return (splay_lookup(&pool->tree, email) != NULL); } int subscriber_load_file(subscriber_pool_t *pool) { if (!pool || !pool->filepath) return -EINVAL; FILE *fp = fopen(pool->filepath, "r"); if (!fp) { /* If file doesn't exist yet, it will be created on first save */ if (errno == ENOENT) return 0; return -errno; } char line[512]; while (fgets(line, sizeof(line), fp)) { char *email = trim_whitespace(line); /* Ignore empty lines and comment lines starting with '#' */ if (*email == '\0' || *email == '#') continue; subscriber_add(pool, email); } fclose(fp); return 0; } /* Atomic save using temporary file + rename to prevent corruption */ int subscriber_save_file(subscriber_pool_t *pool) { if (!pool || !pool->filepath) return -EINVAL; char tmp_path[1024]; snprintf(tmp_path, sizeof(tmp_path), "%s.tmp.%d", pool->filepath, getpid()); FILE *fp = fopen(tmp_path, "w"); if (!fp) return -errno; /* Write plain-text header */ fprintf(fp, "# Subrelay Subscribers List\n"); fprintf(fp, "# Do not edit manually while subrelayd is running\n\n"); /* In-order traversal across splay tree nodes */ splay_node_t *curr = splay_first(&pool->tree); while (curr != NULL) { subscriber_entry_t *entry = (subscriber_entry_t *)curr; fprintf(fp, "%s\n", entry->email); curr = splay_next(&pool->tree, curr); } fflush(fp); fclose(fp); /* Atomic swap */ if (rename(tmp_path, pool->filepath) != 0) { unlink(tmp_path); return -errno; } return 0; } void subscriber_pool_destroy(subscriber_pool_t *pool) { if (!pool) return; /* Traverse tree and free all allocated nodes */ splay_node_t *curr = splay_first(&pool->tree); while (curr != NULL) { splay_node_t *next = splay_next(&pool->tree, curr); splay_delete(&pool->tree, curr); subscriber_entry_t *entry = (subscriber_entry_t *)curr; free(entry->email); free(entry); curr = next; } if (pool->filepath) free(pool->filepath); memset(pool, 0, sizeof(subscriber_pool_t)); return; }