#include "hashmap.h" #include "lstring.h" #include "types.h" #include "helper.h" #include #include #include #include #include #include static const uint16_t MAGIC = 1234; static const int64_t MAX_AGE = 86400 * 2; // 2 days #define MAX_NAME_LEN 1024 HASHMAP_FUNCS_CREATE(name, struct string, struct user); HASHMAP_FUNCS_CREATE(number, uint32_t, struct user); struct user { uint32_t number; const struct string *name; int64_t lastUse; }; static void insertTime(struct uidmap *uidmap, const struct string *name, const uint32_t number, const int64_t lastUse) { uint8_t *buffer = malloc(sizeof(struct user) + sizeof(struct string) + name->l); struct user *u = (struct user*)buffer; struct string *s = (struct string*)(buffer + sizeof(struct user)); char *str = (char*)(buffer + sizeof(struct user) + sizeof(struct string)); memcpy(str, name->s, name->l); s->s = str; s->l = name->l; u->name = s; u->number = number; u->lastUse = lastUse; if (name_hashmap_put(uidmap->nameToNum, u->name, u) != u) { plog(DEBUG_WARNING, "[uidmap] Collision when inserting %.*s by name (%"PRIu32")", (int)s->l, s->s, number); } if (number_hashmap_put(uidmap->numToName, &u->number, u) != u) { plog(DEBUG_WARNING, "[uidmap] Collision when inserting %.*s by number (%"PRIu32")", (int)s->l, s->s, number); } // TODO: Leak } static void insert(struct uidmap *uidmap, const struct string *name, const uint32_t number) { insertTime(uidmap, name, number, time(NULL)); } static int uint32_compare(const void *a, const void *b) { return (*(const uint32_t*)a) != (*(const uint32_t*)b); } static int string_compare(const void *a, const void *b) { return !equals((const struct string*)a, (const struct string*)b); } static size_t uint32_hash(const void *key) { return *(const uint32_t*)key; } static size_t string_hash(const void *key) { const struct string *key_str = (const struct string*)key; size_t hash = 0; for (size_t pos = 0; pos < key_str->l; ++pos) { hash += key_str->s[pos]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } uint32_t uidmap_getNumberForName(struct uidmap *uidmap, const struct string *name) { // Try string matching char buf[name->l]; struct string lower = { .s = buf, .l = name->l }; for (size_t i = 0; i < name->l; ++i) { buf[i] = tolower(name->s[i]); } // Get struct user *u = name_hashmap_get(uidmap->nameToNum, &lower); if (u != NULL) { u->lastUse = time(NULL); return u->number; } // Not known yet - try numeric uint32_t asNumber = parseUInt32(name); if (asNumber >= 2000) { u = number_hashmap_get(uidmap->numToName, &asNumber); if (u == NULL) { // No collision, use name as number insert(uidmap, &lower, asNumber); return asNumber; } plog(DEBUG_WARNING, "%.*s is numeric (%u) but collides", (int)name->l, name->s, asNumber); } // Default case - generate number asNumber = (uint32_t)string_hash(&lower); while (asNumber < 2000) { asNumber = rand() + 2000; } for (;;) { //plog(DEBUG_WARNING, "Using number %u for %.*s", asNumber, (int)name->l, name->s); u = number_hashmap_get(uidmap->numToName, &asNumber); if (u == NULL) break; do { asNumber = rand() + 2000; } while (asNumber < 2000); } insert(uidmap, &lower, asNumber); return asNumber; } const struct string *uidmap_getNameForNumber(struct uidmap *uidmap, const struct string *numberString) { uint32_t number = parseUInt32(numberString); if (number < 2000) return NULL; struct user *u = number_hashmap_get(uidmap->numToName, &number); if (u == NULL) return NULL; u->lastUse = time(NULL); return u->name; } void uidmap_init(struct uidmap *uidmap) { if (uidmap->nameToNum != NULL) return; uidmap->numToName = calloc(2, sizeof(struct hashmap)); uidmap->nameToNum = uidmap->numToName + 1; hashmap_init(uidmap->numToName, uint32_hash, uint32_compare, 0); hashmap_init(uidmap->nameToNum, string_hash, string_compare, 0); if (uidmap->fileName == NULL) return; // We have a filename, try to load DB FILE *fh = fopen(uidmap->fileName, "rb"); if (fh == NULL) return; fseek(fh, 0, SEEK_END); const int fsize = ftell(fh); fseek(fh, 0, SEEK_SET); if (fsize > 0) { // Guess required map size const size_t entries = (size_t)fsize / 25; hashmap_destroy(uidmap->numToName); hashmap_destroy(uidmap->nameToNum); hashmap_init(uidmap->numToName, uint32_hash, uint32_compare, entries); hashmap_init(uidmap->nameToNum, string_hash, string_compare, entries); } // Not endian safe, do not copy file around... uint16_t len = 0; int64_t lastUse; uint32_t uidNumber; char nbuffer[MAX_NAME_LEN]; struct string name; name.s = nbuffer; if (fread(&len, sizeof(len), 1, fh) != 1 || len != MAGIC) { plog(DEBUG_WARNING, "uid map file missing header magic (%"PRIu16")", len); fclose(fh); return; } int loaded = 0; const int64_t now = time(NULL); for (;;) { if (fread(&len, sizeof(len), 1, fh) != 1) break; if (len > MAX_NAME_LEN) { plog(DEBUG_WARNING, "DB has entry of size %"PRIu16", aborting import"); break; } if (fread(&lastUse, sizeof(lastUse), 1, fh) != 1 || fread(&uidNumber, sizeof(uidNumber), 1, fh) != 1) { plog(DEBUG_WARNING, "DB has truncated entry (no lastUse/uidNumber)"); break; } if (fread(nbuffer, len, 1, fh) != 1) { plog(DEBUG_WARNING, "DB has truncated entry (no name)"); break; } // Check age if (now - lastUse > MAX_AGE) continue; if (lastUse > now) lastUse = now; // Add name.l = len; insertTime(uidmap, &name, uidNumber, lastUse); loaded++; } fclose(fh); plog(DEBUG_INFO, "Loaded %d entries from %s", loaded, uidmap->fileName); } void uidmap_cleanupSave(struct uidmap *uidmap) { if (uidmap->nameToNum == NULL) return; FILE *fh = NULL; if (uidmap->fileName != NULL) { fh = fopen(uidmap->fileName, "wb"); if (fh != NULL) { if (fwrite(&MAGIC, sizeof(MAGIC), 1, fh) != 1) { plog(DEBUG_WARNING, "Cannot write magic to %s", uidmap->fileName); fclose(fh); fh = NULL; } } } const int64_t deadline = time(NULL) - MAX_AGE; struct hashmap_iter *it = hashmap_iter(uidmap->nameToNum); struct user *u = NULL; int saved = 0, removed = 0; uint16_t len; while (it != NULL) { u = name_hashmap_iter_get_data(it); if (u->lastUse < deadline) { it = hashmap_iter_remove(uidmap->nameToNum, it); if (number_hashmap_remove(uidmap->numToName, &u->number) != u) { plog(DEBUG_WARNING, "Could not remove user from numToName that was in nameToNum"); } free(u); removed++; continue; } // Save if file if (fh != NULL && u->name->l < MAX_NAME_LEN) { len = (uint16_t)u->name->l; if (fwrite(&len, sizeof(len), 1, fh) != 1 || fwrite(&u->lastUse, sizeof(u->lastUse), 1, fh) != 1 || fwrite(&u->number, sizeof(u->number), 1, fh) != 1 || fwrite(u->name->s, len, 1, fh) != 1 ) { plog(DEBUG_WARNING, "Could not write record to %s", uidmap->fileName); fclose(fh); fh = NULL; } else { saved++; } } it = hashmap_iter_next(uidmap->nameToNum, it); } if (fh != NULL) { fsync(fileno(fh)); fclose(fh); } if (saved > 0 || removed > 0) { plog(DEBUG_VERBOSE, "Purged %d old entries, saved %d entries", removed, saved); } }