/*! \file vattr.cpp * \brief Manages the user-defined attributes. * */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "externs.h" #include "sqlite_backend.h" using namespace std; ATTR *vattr_find_LEN(const UTF8 *pAttrName, size_t nAttrName) { string key(reinterpret_cast(pAttrName), nAttrName); auto it = mudstate.vattr_name_map.find(key); if (it != mudstate.vattr_name_map.end()) { return static_cast(anum_table[it->second]); } return nullptr; } ATTR *vattr_alloc_LEN(const UTF8 *pName, size_t nName, int flags) { int number = mudstate.attr_next++; anum_extend(number); ATTR *vp = vattr_define_LEN(pName, nName, number, flags); if (!mudstate.bSQLiteLoading) { if (!g_pSQLiteBackend->GetDB().PutMeta("attr_next", mudstate.attr_next)) { Log.tinyprintf(T("vattr_alloc_LEN: failed to persist attr_next=%d" ENDLINE), mudstate.attr_next); } } return vp; } ATTR *vattr_define_LEN(const UTF8 *pName, size_t nName, int number, int flags) { // Validate the attribute number before it reaches anum_extend()/anum_set() // (#808). User attribute numbers are always in [A_USER_START, A_USER_MAX]; // the read path (atr_num) already validates this range, but this write path // did not, so a negative number would OOB-write anum_table and a huge one // would allocate a giant table. // if ( number < A_USER_START || number > A_USER_MAX) { return nullptr; } ATTR *vp = vattr_find_LEN(pName, nName); if (vp) { return vp; } vp = static_cast(MEMALLOC(sizeof(ATTR))); if (vp) { string key(reinterpret_cast(pName), nName); auto [it, inserted] = mudstate.vattr_name_map.emplace(std::move(key), number); if (!inserted) { MEMFREE(vp); return static_cast(anum_table[it->second]); } // ATTR::name points directly into the map key. unordered_map // guarantees pointer stability for existing elements across // insertions, so this pointer remains valid until the element // is erased. // vp->name = reinterpret_cast(it->first.c_str()); vp->flags = flags; vp->number = number; mudstate.vattr_numbers.insert(number); anum_extend(vp->number); anum_set(vp->number, static_cast(vp)); if ( !mudstate.bSQLiteLoading && number >= A_USER_START) { if (!g_pSQLiteBackend->GetDB().PutAttrName(number, reinterpret_cast(pName), flags)) { Log.tinyprintf(T("vattr_define_LEN: failed to persist attrname #%d" ENDLINE), number); } } } else { STARTLOG(LOG_PROBLEMS, "ATR", "MEM"); log_printf(T("vattr_define_LEN: out of memory allocating vattr.")); ENDLOG; } return vp; } void do_dbclean(dbref executor, dbref caller, dbref enactor, int eval, int key) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); // Phase 1: Find orphaned vattr names — user-defined attribute // names (attrnum >= 256) not referenced by any object. // std::vector orphans = g_pSQLiteBackend->GetDB().FindOrphanedAttrNames(); // Phase 2: Purge from SQLite first. If this fails, bail out // without touching in-memory state so the process and database // stay consistent. // int purged = g_pSQLiteBackend->GetDB().PurgeOrphanedAttrNames(); if (purged < 0) { notify(executor, M_("@dbclean: SQLite error during orphan purge.")); return; } // Phase 3: SQLite succeeded — now remove orphans from in-memory maps. // for (int anum : orphans) { ATTR *vp = static_cast(anum_table[anum]); if (vp) { if (vp->name) { std::string name(reinterpret_cast(vp->name)); mudstate.vattr_name_map.erase(name); } mudstate.vattr_numbers.erase(anum); anum_set(anum, nullptr); MEMFREE(vp); } } g_pSQLiteBackend->GetDB().Analyze(); // #1661 / #1622: count goes to the catalogue, not an English "s". // notify(executor, tprintf(MN_("@dbclean: %d orphaned attribute name purged.", "@dbclean: %d orphaned attribute names purged.", purged), purged)); } void vattr_delete_LEN(UTF8 *pName, size_t nName) { string key(reinterpret_cast(pName), nName); auto it = mudstate.vattr_name_map.find(key); if (it != mudstate.vattr_name_map.end()) { int anum = it->second; ATTR *vp = static_cast(anum_table[anum]); anum_set(anum, nullptr); mudstate.vattr_name_map.erase(it); mudstate.vattr_numbers.erase(anum); if ( !mudstate.bSQLiteLoading && anum >= A_USER_START) { if (!g_pSQLiteBackend->GetDB().DelAttrName(anum)) { Log.tinyprintf(T("vattr_delete_LEN: failed to delete attrname #%d" ENDLINE), anum); } } MEMFREE(vp); } } ATTR *vattr_rename_LEN(UTF8 *pOldName, size_t nOldName, UTF8 *pNewName, size_t nNewName) { string oldkey(reinterpret_cast(pOldName), nOldName); auto it = mudstate.vattr_name_map.find(oldkey); if (it == mudstate.vattr_name_map.end()) { return nullptr; } int anum = it->second; ATTR *vp = static_cast(anum_table[anum]); string newkey(reinterpret_cast(pNewName), nNewName); auto existing = mudstate.vattr_name_map.find(newkey); if ( existing != mudstate.vattr_name_map.end() && existing->second != anum) { return nullptr; } if (newkey == oldkey) { return vp; } mudstate.vattr_name_map.erase(it); auto [newit, inserted] = mudstate.vattr_name_map.emplace(std::move(newkey), anum); if (!inserted) { mudstate.vattr_name_map.emplace(oldkey, anum); return nullptr; } vp->name = reinterpret_cast(newit->first.c_str()); if ( !mudstate.bSQLiteLoading && anum >= A_USER_START) { if (!g_pSQLiteBackend->GetDB().PutAttrName(anum, reinterpret_cast(pNewName), vp->flags)) { Log.tinyprintf(T("vattr_rename_LEN: failed to rename attrname #%d" ENDLINE), anum); } } return vp; } ATTR *vattr_first(void) { if (mudstate.vattr_numbers.empty()) { return nullptr; } return static_cast(anum_table[*mudstate.vattr_numbers.begin()]); } ATTR *vattr_next(ATTR *vp) { if (vp == nullptr) { return vattr_first(); } auto it = mudstate.vattr_numbers.upper_bound(vp->number); if (it == mudstate.vattr_numbers.end()) { return nullptr; } return static_cast(anum_table[*it]); }