/* * Copyright (c) 2001-2023 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * The base class of the Agar object system. */ #include #include #include #include #include /* Expensive debugging output related to AG_Object VFS operations. */ /* #define DEBUG_OBJECT */ /* Extra debugging output related to serialization. */ /* #define DEBUG_SERIALIZATION */ /* Debug class registration and module linking */ /* #define AG_DEBUG_CLASSES */ AG_ObjectClass agObjectClass = { "AG_Object", sizeof(AG_Object), { 7,3, AGC_OBJECT, 0xE027 }, NULL, /* init */ NULL, /* reset */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ NULL /* edit */ }; Uint32 agObjectSignature = 0; /* Object validity signature */ Uint32 agNonObjectSignature = 0; /* Non-Object validity signature */ #ifdef AG_SERIALIZATION int agObjectIgnoreUnknownObjs = 0; /* Don't fail on unknown object types. */ int agObjectBackups = 1; /* Backup object save files. */ #endif #ifdef AG_THREADS AG_Mutex agClassLock; /* Lock on class tables */ #endif AG_ObjectClass **agClasses; /* Class table (flat array) */ Uint agClassCount = 0; AG_Tbl *agClassTbl = NULL; /* Class table (hash) */ #ifdef AG_NAMESPACES AG_Namespace *agNamespaceTbl = NULL; /* Namespace table */ int agNamespaceCount = 0; #endif #ifdef AG_ENABLE_DSO char **agModuleDirs = NULL; /* Module search directories */ int agModuleDirCount = 0; #endif /* Import inlinables */ #undef AG_INLINE_HEADER #include /* Initialize an AG_Object instance. */ void AG_ObjectInit(void *pObj, void *pClass) { AG_Object *ob = pObj; AG_ObjectClass *C = (pClass != NULL) ? AGOBJECTCLASS(pClass) : &agObjectClass; AG_ObjectClass **hier; int i, nHier; ob->tag = agObjectSignature; ob->cid = C->ver.cid; #ifdef AG_DEBUG memset(ob->name, '\0', sizeof(ob->name)); #else ob->name[0] = '\0'; #endif ob->cls = C; ob->parent = NULL; ob->root = ob; ob->flags = 0; AG_MutexInitRecursive(&ob->lock); TAILQ_INIT(&ob->events); #ifdef AG_TIMERS TAILQ_INIT(&ob->timers); #endif TAILQ_INIT(&ob->vars); TAILQ_INIT(&ob->children); if (AG_ObjectGetInheritHier(ob, &hier, &nHier) != 0) { AG_FatalError(NULL); } for (i = 0; i < nHier; i++) { if (hier[i]->init != NULL) hier[i]->init(ob); } free(hier); } /* Initialize an AG_Object instance (and set the STATIC flag on it). */ void AG_ObjectInitStatic(void *obj, void *C) { AG_ObjectInit(obj, C); OBJECT(obj)->flags |= AG_OBJECT_STATIC; } /* * Allocate, initialize and attach a new object instance of the specified * class. */ void * AG_ObjectNew(void *parent, const char *name, AG_ObjectClass *C) { char nameGen[AG_OBJECT_NAME_MAX]; AG_Object *obj; if (name == NULL) { AG_ObjectGenName(parent, C, nameGen, sizeof(nameGen)); } else { if (parent != NULL && AG_ObjectFindChild(parent, name) != NULL) { AG_SetErrorV("E7", _("Existing child object")); return (NULL); } } if ((obj = TryMalloc(C->size)) == NULL) { return (NULL); } AG_ObjectInit(obj, C); AG_ObjectSetNameS(obj, (name != NULL) ? name : (const char *)nameGen); if (parent != NULL) { AG_ObjectAttach(parent, obj); } return (obj); } /* * Restore an object to an initial state prior to deserialization or release. */ void AG_ObjectReset(void *p) { AG_Object *ob = p; AG_ObjectClass **hier; int i, nHier; AG_ObjectLock(ob); if (AG_ObjectGetInheritHier(ob, &hier, &nHier) != 0) { AG_FatalError(NULL); } for (i = nHier-1; i >= 0; i--) { if (hier[i]->reset != NULL) hier[i]->reset(ob); } AG_ObjectUnlock(ob); free(hier); } #if AG_MODEL != AG_SMALL /* * Recursive function to construct absolute object names. * The Object and its parent VFS must be locked. */ static int GenerateObjectPath(void *_Nonnull obj, char *_Nonnull path, AG_Size path_len) { AG_Object *ob = obj; AG_Size name_len, cur_len; int rv = 0; cur_len = strlen(path); name_len = strlen(ob->name); if (name_len+cur_len+1 > path_len) { AG_SetErrorV("E4", _("Path buffer overflow")); return (-1); } /* Prepend separator, object name. */ memmove(&path[name_len+1], path, cur_len+1); /* Move the NUL as well */ path[0] = AG_PATHSEPCHAR; memcpy(&path[1], ob->name, name_len); /* Omit the NUL */ if (ob->parent != ob->root && ob->parent != NULL) { rv = GenerateObjectPath(ob->parent, path, path_len); } return (rv); } /* * Copy the absolute pathname of an object to a fixed-size buffer. * Buffer size must be at least 2 bytes in size. */ int AG_ObjectCopyName(void *obj, char *path, AG_Size path_len) { AG_Object *ob = obj; int rv = 0; if (path_len < 2) { AG_SetErrorV("E5", _("Path buffer overflow")); return (-1); } path[0] = AG_PATHSEPCHAR; path[1] = '\0'; AG_LockVFS(ob); AG_ObjectLock(ob); if (ob == ob->root) { Strlcat(path, ob->name, path_len); } else { rv = GenerateObjectPath(ob, path, path_len); } AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (rv); } /* * Return the name of the class which obj belongs to. * * If full=0, return only the last subclass (eg. "AG_Button"). * If full=1, return the full inheritance hierarchy (eg. "AG_Widget:AG_Button"). * The caller should free(3) the returned string after use. */ char * AG_ObjectGetClassName(const void *obj, int full) { const AG_Object *ob = obj; return Strdup(full ? AGOBJECT_CLASS(ob)->hier : AGOBJECT_CLASS(ob)->name); } /* * Return the fullpath of an object (relative to the root of its parent VFS). * * The returned path is true for as long as the VFS remains locked (the * caller may wish to use AG_LockVFS() to guarantee atomicity of operations * which are dependent on the returned path being true). * * The caller should free(3) the returned string after use. * * This routine uses recursion. It will fail and return NULL if insufficient * memory is available to construct the complete path. */ char * AG_ObjectGetName(void *obj) { AG_Object *ob = obj; AG_Object *pob; char *path; AG_Size pathLen = 1; AG_LockVFS(ob); AG_ObjectLock(ob); for (pob = ob; pob->parent != NULL; pob = pob->parent) { pathLen += strlen(pob->name) + 1; } if ((path = TryMalloc(pathLen+1)) == NULL) { goto fail; } path[0] = AG_PATHSEPCHAR; path[1] = '\0'; if (ob == ob->root) { Strlcat(path, ob->name, pathLen); } else { GenerateObjectPath(ob, path, pathLen); } AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (path); fail: AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (NULL); } /* Initialize an AG_Object instance (name argument variant). */ void AG_ObjectInitNamed(void *obj, void *cl, const char *name) { AG_ObjectInit(obj, cl); if (name != NULL) { AG_ObjectSetNameS(obj, name); } else { OBJECT(obj)->flags |= AG_OBJECT_NAME_ONATTACH; } } #endif /* !AG_SMALL */ /* * Set a variable which is a pointer to a function (with optional arguments). * The object must be locked. */ AG_Variable * AG_SetFn(void *p, const char *name, AG_EventFn fn, const char *fmt, ...) { AG_Object *obj = p; AG_Event *ev; AG_Variable *V; if (fn == NULL) { AG_Unset(obj, name); return (NULL); } ev = AG_EventNew(fn, obj, NULL); if (fmt) { va_list ap; va_start(ap, fmt); AG_EventGetArgs(ev, fmt, ap); va_end(ap); } #ifdef AG_DEBUG Debug(obj, "Set \"" AGSI_YEL "%s" AGSI_RST "\" -> (" AGSI_CYAN "Function" AGSI_RST " *)%p\n", name, fn); #endif V = AG_FetchVariable(obj, name, AG_VARIABLE_FUNCTION); V->data.p = ev; V->info.pFlags = 0; return (V); } /* Attach an object to another object. */ void AG_ObjectAttach(void *parentp, void *pChld) { AG_Object *parent = parentp; AG_Object *chld = pChld; if (parent == NULL) return; #ifdef AG_DEBUG if (parent == chld) AG_FatalErrorV("E31", "parent == chld"); #endif #ifdef AG_TYPE_SAFETY if (!AG_OBJECT_VALID(parent)) { AG_FatalErrorV("E38a", "Parent object is invalid"); } if (!AG_OBJECT_VALID(chld)) { AG_FatalErrorV("E38b", "Child object is invalid"); } #endif AG_LockVFS(parent); AG_ObjectLock(parent); AG_ObjectLock(chld); /* * Update the child's "parent" and "root" pointers. If we are using * a custom attach function, we assume that it will follow through. */ chld->parent = parent; chld->root = parent->root; /* Call the attach function if one is defined. */ if (AG_Defined(chld, "attach-fn")) { AG_Event *ev; if ((ev = AG_GetPointer(chld,"attach-fn")) != NULL && ev->fn != NULL) { ev->fn(ev); } goto out; } /* Name the object if it has the name-on-attach flag set. */ if (chld->name[0] == '\0' && (chld->flags & AG_OBJECT_NAME_ONATTACH)) { AG_ObjectGenName(parent, chld->cls, chld->name, sizeof(chld->name)); } /* Attach the object. */ TAILQ_INSERT_TAIL(&parent->children, chld, cobjs); /* Notify the child object. */ AG_PostEvent(chld, "attached", "%p", parent); #ifdef DEBUG_OBJECT if (chld->name[0] != '\0') { Debug(parent, "Attached child: %s\n", chld->name); } else { Debug(parent, "Attached child: <%p>\n", chld); } if (parent->name[0] != '\0') { Debug(chld, "New parent: %s\n", parent->name); } else { Debug(chld, "New parent: <%p>\n", parent); } #endif /* DEBUG_OBJECT */ out: #ifdef AG_THREADS AG_ObjectUnlock(chld); AG_ObjectUnlock(parent); AG_UnlockVFS(parent); #endif return; } /* Detach a child object from its parent. */ void AG_ObjectDetach(void *pChld) { AG_Object *chld = pChld; #ifdef AG_THREADS AG_Object *root = chld->root; #endif AG_Object *parent = chld->parent; #ifdef AG_TIMERS AG_Timer *to, *toNext; #endif #ifdef AG_TYPE_SAFETY if (!AG_OBJECT_VALID(chld)) { AG_FatalErrorV("E36a", "Child object is invalid"); } if (!AG_OBJECT_VALID(parent)) { AG_FatalErrorV("E36b", "Parent object is invalid"); } #endif #ifdef AG_THREADS AG_LockVFS(root); AG_ObjectLock(parent); AG_ObjectLock(chld); #endif /* Call the detach function if one is defined. */ if (AG_Defined(chld, "detach-fn")) { AG_Event *ev; if ((ev = AG_GetPointer(chld,"detach-fn")) != NULL && ev->fn != NULL) { ev->fn(ev); } goto out; } #ifdef AG_TIMERS /* Cancel any running timer associated with the object. */ AG_LockTiming(); for (to = TAILQ_FIRST(&chld->timers); to != TAILQ_END(&chld->timers); to = toNext) { toNext = TAILQ_NEXT(to, pvt.timers); AG_DelTimer(chld, to); } AG_UnlockTiming(); #endif AG_PostEvent(chld, "detached", "%p", parent); /* Remove the object from the parent's children list. */ TAILQ_REMOVE(&parent->children, chld, cobjs); chld->parent = NULL; chld->root = chld; #ifdef DEBUG_OBJECT if (chld->name[0] != '\0') { Debug(parent, "Detached child: %s\n", chld->name); } else { Debug(parent, "Detached child: <%p>\n", chld); } #endif /* DEBUG_OBJECT */ out: #ifdef AG_THREADS AG_ObjectUnlock(chld); AG_ObjectUnlock(parent); AG_UnlockVFS(root); #endif return; } /* * Detach a child object from its parent (Lockless variant). * Both parent and child objects must be locked. */ void AG_ObjectDetachLockless(void *pChld) { AG_Object *chld = pChld; AG_Object *parent = chld->parent; #ifdef AG_TIMERS AG_Timer *to, *toNext; #endif #ifdef AG_TYPE_SAFETY if (!AG_OBJECT_VALID(chld)) { AG_FatalErrorV("E36a", "Child object is invalid"); } if (!AG_OBJECT_VALID(parent)) { AG_FatalErrorV("E36b", "Parent object is invalid"); } #endif if (AG_Defined(chld, "detach-fn")) { AG_Event *ev; if ((ev = AG_GetPointer(chld,"detach-fn")) != NULL && ev->fn != NULL) { ev->fn(ev); } return; } #ifdef AG_TIMERS /* Cancel any running timer associated with the object. */ AG_LockTiming(); for (to = TAILQ_FIRST(&chld->timers); to != TAILQ_END(&chld->timers); to = toNext) { toNext = TAILQ_NEXT(to, pvt.timers); AG_DelTimer(chld, to); } AG_UnlockTiming(); #endif AG_PostEvent(chld, "detached", "%p", parent); /* Remove the object from the parent's children list. */ TAILQ_REMOVE(&parent->children, chld, cobjs); chld->parent = NULL; chld->root = chld; #ifdef DEBUG_OBJECT if (chld->name[0] != '\0') { Debug(parent, "Detached child: %s\n", chld->name); } else { Debug(parent, "Detached child: <%p>\n", chld); } #endif /* DEBUG_OBJECT */ } /* Traverse the object tree using a pathname. */ static void *_Nullable _Pure_Attribute FindObjectByName(const AG_Object *_Nonnull parent, const char *_Nonnull name) { char chldName[AG_OBJECT_PATH_MAX]; void *rv; char *s; AG_Object *child; if (Strlcpy(chldName, name, sizeof(chldName)) >= sizeof(chldName)) { AG_SetErrorS(_("Path overflow")); return (NULL); } if ((s = strchr(chldName, AG_PATHSEPCHAR)) != NULL) { *s = '\0'; } TAILQ_FOREACH(child, &parent->children, cobjs) { if (strcmp(child->name, chldName) != 0) continue; if ((s = strchr(name, AG_PATHSEPCHAR)) != NULL && s[1] != '\0') { rv = FindObjectByName(child, &s[1]); if (rv != NULL) { return (rv); } else { return (NULL); } } return (child); } return (NULL); } /* * Search for the named object (absolute path). * * Returned pointer is guaranteed to be valid as long as the VFS is locked. * If no such object exists, return NULL (without SetError). */ void * AG_ObjectFindS(void *vfsRoot, const char *name) { void *rv; #ifdef AG_DEBUG if (name[0] != AG_PATHSEPCHAR) AG_FatalErrorV("E32", "Not an absolute path"); #endif if (name[0] == AG_PATHSEPCHAR && name[1] == '\0') { return (vfsRoot); } AG_LockVFS(vfsRoot); rv = FindObjectByName(vfsRoot, &name[1]); AG_UnlockVFS(vfsRoot); return (rv); } /* * Search for the named object (absolute path as format string). * * Returned pointer is guaranteed to be valid as long as the VFS is locked. * If no such object exists, return NULL (without SetError). */ void * AG_ObjectFind(void *vfsRoot, const char *fmt, ...) { char path[AG_OBJECT_PATH_MAX]; void *rv; va_list ap; va_start(ap, fmt); Vsnprintf(path, sizeof(path), fmt, ap); va_end(ap); #ifdef AG_DEBUG if (path[0] != AG_PATHSEPCHAR) AG_FatalErrorV("E32", "Not an absolute path"); #endif AG_LockVFS(vfsRoot); rv = FindObjectByName(vfsRoot, &path[1]); AG_UnlockVFS(vfsRoot); return (rv); } /* * Traverse an object's ancestry looking for parent object of the given class. * Return value is only valid as long as VFS is locked. */ void * AG_ObjectFindParent(void *p, const char *name, const char *t) { AG_Object *ob = AGOBJECT(p); AG_LockVFS(p); while (ob != NULL) { AG_Object *po = AGOBJECT(ob->parent); if (po == NULL) { goto fail; } if ((t == NULL || AG_ClassIsNamed(po->cls, t)) && (name == NULL || strcmp(po->name, name) == 0)) { AG_UnlockVFS(p); return ((void *)po); } ob = AGOBJECT(ob->parent); } fail: AG_UnlockVFS(p); return (NULL); } void AG_ObjectFreeChildrenLockless(AG_Object *obj) { AG_Object *child, *childNext; struct ag_objectq detached; #ifdef AG_TYPE_SAFETY if (!AG_OBJECT_VALID(obj)) AG_FatalErrorV("E37", "Parent object is invalid"); #endif TAILQ_INIT(&detached); for (child = TAILQ_FIRST(&obj->children); child != TAILQ_END(&obj->children); child = childNext) { childNext = TAILQ_NEXT(child, cobjs); #ifdef DEBUG_OBJECT if (child->name[0] != '\0') { Debug(obj, "Detaching child: %s\n", child->name); } else { Debug(obj, "Detaching child: <%p>\n", child); } #endif AG_ObjectDetachLockless(child); TAILQ_INSERT_TAIL(&detached, child, cobjs); } TAILQ_INIT(&obj->children); for (child = TAILQ_FIRST(&detached); child != TAILQ_END(&detached); child = childNext) { childNext = TAILQ_NEXT(child, cobjs); AG_ObjectDestroy(child); } } /* Detach and destroy all child objects under a given parent. */ void AG_ObjectFreeChildren(void *pObj) { AG_Object *obj = pObj; if (!AG_OBJECT_VALID(obj)) return; AG_ObjectLock(obj); AG_ObjectFreeChildrenLockless(obj); AG_ObjectUnlock(obj); } #if AG_MODEL != AG_SMALL void AG_ObjectFreeChildrenOfTypeLockless(AG_Object *obj, const char *pattern) { AG_Object *child, *childNext; struct ag_objectq detached; #ifdef AG_TYPE_SAFETY if (!AG_OBJECT_VALID(obj)) AG_FatalErrorV("E37", "Parent object is invalid"); #endif TAILQ_INIT(&detached); for (child = TAILQ_FIRST(&obj->children); child != TAILQ_END(&obj->children); child = childNext) { childNext = TAILQ_NEXT(child, cobjs); if (!AG_OfClass(child, pattern)) continue; #ifdef DEBUG_OBJECT if (child->name[0] != '\0') { Debug(obj, "Detaching child: %s\n", child->name); } else { Debug(obj, "Detaching child: <%p>\n", child); } #endif AG_ObjectDetachLockless(child); TAILQ_REMOVE(&obj->children, child, cobjs); TAILQ_INSERT_TAIL(&detached, child, cobjs); } for (child = TAILQ_FIRST(&detached); child != TAILQ_END(&detached); child = childNext) { childNext = TAILQ_NEXT(child, cobjs); AG_ObjectDestroy(child); } } /* Detach and destroy matching child objects under a given parent. */ void AG_ObjectFreeChildrenOfType(void *pObj, const char *pattern) { AG_Object *obj = pObj; if (!AG_OBJECT_VALID(obj)) return; AG_ObjectLock(obj); AG_ObjectFreeChildrenOfTypeLockless(obj, pattern); AG_ObjectUnlock(obj); } #endif /* !AG_SMALL */ /* Destroy the object variables. */ void AG_ObjectFreeVariables(void *pObj) { AG_Object *ob = pObj; AG_Variable *V, *Vnext; AG_ObjectLock(ob); for (V = TAILQ_FIRST(&ob->vars); V != TAILQ_END(&ob->vars); V = Vnext) { Vnext = TAILQ_NEXT(V, vars); AG_FreeVariable(V); free(V); } TAILQ_INIT(&ob->vars); AG_ObjectUnlock(ob); } /* Destroy the event handler structures. */ void AG_ObjectFreeEvents(AG_Object *ob) { AG_Event *ev, *evNext; AG_ObjectLock(ob); for (ev = TAILQ_FIRST(&ob->events); ev != TAILQ_END(&ob->events); ev = evNext) { evNext = TAILQ_NEXT(ev, events); free(ev); } TAILQ_INIT(&ob->events); AG_ObjectUnlock(ob); } /* * Release all resources allocated by an object and its children. * Invokes the object reset() and destroy() operations. * * None of the objects must be in use. */ void AG_ObjectDestroy(void *p) { AG_Object *ob = p; AG_ObjectClass **hier; AG_Object *child, *childNext; AG_Variable *V, *Vnext; AG_Event *ev, *evNext; int i, nHier; #ifdef AG_TYPE_SAFETY if (!AG_OBJECT_VALID(ob)) AG_FatalErrorV("E36", "Object is invalid"); #endif #ifdef AG_DEBUG if (ob->parent != NULL) { AG_Debug(ob, "I'm still attached to %s\n", OBJECT(ob->parent)->name); AG_FatalErrorV("E33", "Object is still attached"); } #endif /* * Release the child objects. */ for (child = TAILQ_FIRST(&ob->children); child != TAILQ_END(&ob->children); child = childNext) { childNext = TAILQ_NEXT(child, cobjs); #ifdef DEBUG_OBJECT if (child->name[0] != '\0') { Debug(ob, "Freeing child: %s\n", child->name); } else { Debug(ob, "Freeing child: <%p>\n", child); } #endif AG_ObjectDetachLockless(child); AG_ObjectDestroy(child); } /* * Invoke reset() and destroy() for every class in the object's * inheritance hierarchy. */ if (AG_ObjectGetInheritHier(ob, &hier, &nHier) != 0) { AG_FatalError(NULL); } for (i = nHier-1; i >= 0; i--) { if (hier[i]->reset != NULL) hier[i]->reset(ob); if (hier[i]->destroy != NULL) hier[i]->destroy(ob); } free(hier); /* * Release defined variables and event handler structures. */ for (V = TAILQ_FIRST(&ob->vars); V != TAILQ_END(&ob->vars); V = Vnext) { Vnext = TAILQ_NEXT(V, vars); AG_FreeVariable(V); free(V); } for (ev = TAILQ_FIRST(&ob->events); ev != TAILQ_END(&ob->events); ev = evNext) { evNext = TAILQ_NEXT(ev, events); free(ev); } /* Invalidate the validity tag and class ID. */ ob->tag = 0; ob->cid = 0; /* Release the object's locking device. */ AG_MutexDestroy(&ob->lock); /* Release the object structure (if dynamically allocated). */ if ((ob->flags & AG_OBJECT_STATIC) == 0) free(ob); } #ifdef AG_SERIALIZATION /* * Search the AG_CONFIG_PATH_DATA path group for an object file matching * "." and return its fullpath into a fixed-size buffer. * * If set, the special AG_Object variable "archive-path" will override the * default path and arrange for the object to be loaded from a specific file. * * Under threads the returned path is only valid as long as the VFS is locked. * This function will clobber path even when it fails and returns -1. */ int AG_ObjectCopyFilename(void *p, char *path, AG_Size pathSize) { char name[AG_OBJECT_PATH_MAX]; AG_ConfigPathQ *pathGroup = &agConfig->paths[AG_CONFIG_PATH_DATA]; AG_ConfigPath *loadPath; const char *archivePath; AG_Object *ob = p; AG_ObjectLock(ob); if (AG_Defined(ob, "archive-path") && (archivePath = AG_GetStringP(ob,"archive-path")) != NULL && archivePath[0] != '\0') { Strlcpy(path, archivePath, pathSize); goto out; } AG_ObjectCopyName(ob, name, sizeof(name)); TAILQ_FOREACH(loadPath, pathGroup, paths) { Strlcpy(path, loadPath->s, pathSize); Strlcat(path, name, pathSize); Strlcat(path, AG_PATHSEP, pathSize); Strlcat(path, ob->name, pathSize); Strlcat(path, ".", pathSize); Strlcat(path, ob->cls->name, pathSize); /* TODO: check signature */ if (AG_FileExists(path)) goto out; } AG_SetErrorV("E5", _("File not found")); AG_ObjectUnlock(ob); return (-1); out: AG_ObjectUnlock(ob); return (0); } /* * Copy the full pathname of an object's data dir to a fixed-size buffer. * The path is only valid as long as the VFS is locked. */ int AG_ObjectCopyDirname(void *p, char *path, AG_Size pathSize) { char tp[AG_PATHNAME_MAX]; char name[AG_OBJECT_PATH_MAX]; AG_ConfigPathQ *pathGroup = &agConfig->paths[AG_CONFIG_PATH_DATA]; AG_ConfigPath *loadPath; AG_Object *ob = p; AG_ObjectLock(ob); AG_ObjectCopyName(ob, name, sizeof(name)); TAILQ_FOREACH(loadPath, pathGroup, paths) { Strlcpy(tp, loadPath->s, sizeof(tp)); Strlcat(tp, name, sizeof(tp)); if (AG_FileExists(tp)) { Strlcpy(path, tp, pathSize); goto out; } } AG_SetErrorV("E6", _("Directory is not in load-path.")); AG_ObjectUnlock(ob); return (-1); out: AG_ObjectUnlock(ob); return (0); } /* Load both the generic part and the dataset of an object from file. */ int AG_ObjectLoadFromFile(void *p, const char *path) { AG_Object *ob = p; int dataFound; AG_LockVFS(ob); AG_ObjectLock(ob); if (AG_ObjectLoadGenericFromFile(ob, path) == -1 || AG_ObjectLoadDataFromFile(ob, &dataFound, path) == -1) { goto fail; } AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (0); fail: AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (-1); } int AG_ObjectLoad(void *p) { return AG_ObjectLoadFromFile(p, NULL); } int AG_ObjectLoadData(void *p, int *dataFound) { return AG_ObjectLoadDataFromFile(p, dataFound, NULL); } int AG_ObjectLoadGeneric(void *p) { return AG_ObjectLoadGenericFromFile(p, NULL); } /* Read an Agar archive header. */ int AG_ObjectReadHeader(AG_DataSource *ds, AG_ObjectHeader *oh) { /* Signature and version data */ if (AG_ReadVersion(ds, agObjectClass.name, &agObjectClass.ver, &oh->ver) == -1) return (-1); /* Class hierarchy and module references */ if (oh->ver.minor >= 2) { AG_ObjectClassSpec *cs = &oh->cs; char *c; AG_CopyString(cs->hier, ds, sizeof(cs->hier)); #ifdef AG_ENABLE_DSO AG_CopyString(cs->libs, ds, sizeof(cs->libs)); #else AG_SkipString(ds); #endif Strlcpy(cs->spec, cs->hier, sizeof(cs->spec)); #ifdef AG_ENABLE_DSO if (cs->libs[0] != '\0') { Strlcat(cs->spec, "@", sizeof(cs->spec)); Strlcat(cs->spec, cs->libs, sizeof(cs->spec)); } #endif if ((c = strrchr(cs->hier, ':')) != NULL && c[1] != '\0') { Strlcpy(cs->name, &c[1], sizeof(cs->name)); } else { Strlcpy(cs->name, cs->hier, sizeof(cs->name)); } } else { oh->cs.hier[0] = '\0'; oh->cs.spec[0] = '\0'; oh->cs.name[0] = '\0'; #ifdef AG_ENABLE_DSO oh->cs.libs[0] = '\0'; #endif } /* Dataset start offset */ oh->dataOffs = AG_ReadUint32(ds); /* Object flags */ oh->flags = (Uint)AG_ReadUint32(ds); return (0); } /* Load Object variables. */ int AG_ObjectLoadVariables(void *p, AG_DataSource *ds) { const AG_Version propTblVer = { 2, 1 }; AG_Object *ob = p; Uint count, i, j; /* TODO 2.0 remove this redundant signature */ if (AG_ReadVersion(ds, "AG_PropTbl", &propTblVer, NULL) == -1) return (-1); AG_ObjectLock(ob); count = (Uint)AG_ReadUint32(ds); #if AG_MODEL != AG_SMALL if (count > AG_OBJECT_MAX_VARIABLES) { AG_SetErrorS(_("Too many variables")); return (-1); } #endif for (i = 0; i < count; i++) { char key[64]; Sint8 code; char *s; if (AG_CopyString(key, ds, sizeof(key)) >= sizeof(key)) { AG_SetErrorV("E8", _("Variable name is too long")); goto fail; } code = (Sint8)AG_ReadSint32(ds); /* XXX */ #ifdef DEBUG_SERIALIZATION Debug(ob, "key \"%s\" = code %d\n", key, code) #endif for (j = 0; j < AG_VARIABLE_TYPE_LAST; j++) { if (agVariableTypes[j].code == code) break; } if (j == AG_VARIABLE_TYPE_LAST) { AG_SetErrorV("E9", _("Unknown variable type")); goto fail; } switch (agVariableTypes[j].typeTgt) { #if AG_MODEL == AG_SMALL case AG_VARIABLE_UINT: AG_SetUint(ob, key, AG_ReadUint16(ds)); break; case AG_VARIABLE_INT: AG_SetInt(ob, key, AG_ReadSint16(ds)); break; #else case AG_VARIABLE_UINT: AG_SetUint(ob, key, (Uint)AG_ReadUint32(ds)); break; case AG_VARIABLE_INT: AG_SetInt(ob, key, (int)AG_ReadSint32(ds)); break; case AG_VARIABLE_ULONG: AG_SetUlong(ob, key, (Ulong)AG_ReadUint64(ds)); break; case AG_VARIABLE_LONG: AG_SetLong(ob, key, (long)AG_ReadSint64(ds)); break; case AG_VARIABLE_UINT32: AG_SetUint32(ob, key, AG_ReadUint32(ds)); break; case AG_VARIABLE_SINT32: AG_SetSint32(ob, key, AG_ReadSint32(ds)); break; #endif /* MD or LG */ case AG_VARIABLE_UINT8: AG_SetUint8(ob, key, AG_ReadUint8(ds)); break; case AG_VARIABLE_SINT8: AG_SetSint8(ob, key, AG_ReadSint8(ds)); break; case AG_VARIABLE_UINT16: AG_SetUint16(ob, key, AG_ReadUint16(ds)); break; case AG_VARIABLE_SINT16: AG_SetSint16(ob, key, AG_ReadSint16(ds)); break; #ifdef AG_HAVE_64BIT case AG_VARIABLE_UINT64: AG_SetUint64(ob, key, AG_ReadUint64(ds)); break; case AG_VARIABLE_SINT64: AG_SetSint64(ob, key, AG_ReadSint64(ds)); break; #endif #ifdef AG_HAVE_FLOAT case AG_VARIABLE_FLOAT: AG_SetFloat(ob, key, AG_ReadFloat(ds)); break; case AG_VARIABLE_DOUBLE: AG_SetDouble(ob, key, AG_ReadDouble(ds)); break; #endif case AG_VARIABLE_STRING: if ((s = AG_ReadString(ds)) != NULL) { AG_SetStringNODUP(ob, key, s); } else { AG_SetString(ob, key, ""); } break; default: AG_SetErrorV("E10", _("Incompatible variable type")); goto fail; } } AG_ObjectUnlock(ob); return (0); fail: AG_ObjectUnlock(ob); return (-1); } /* Save persistent object variables. */ void AG_ObjectSaveVariables(void *pObj, AG_DataSource *ds) { const AG_Version propTblVer = { 2, 1 }; AG_Object *ob = pObj; AG_Offset countOffs; Uint32 count = 0; AG_Variable *V; /* TODO 2.0 remove this redundant signature */ AG_WriteVersion(ds, "AG_PropTbl", &propTblVer); countOffs = AG_Tell(ds); AG_WriteUint32(ds, 0); AG_ObjectLock(ob); TAILQ_FOREACH(V, &ob->vars, vars) { const AG_VariableTypeInfo *Vt = &agVariableTypes[V->type]; void *p; if (Vt->code == -1) { Verbose("Save: skipping %s (non-persistent)\n", V->name); continue; } AG_LockVariable(V); AG_WriteString(ds, (char *)V->name); AG_WriteSint32(ds, (Sint32)Vt->code); /* XXX */ p = (agVariableTypes[V->type].indirLvl > 0) ? V->data.p : (void *)&V->data; switch (AG_VARIABLE_TYPE(V)) { #if AG_MODEL == AG_SMALL case AG_VARIABLE_UINT: AG_WriteUint16(ds, *(Uint *)p); break; case AG_VARIABLE_INT: AG_WriteSint16(ds, *(int *)p); break; #else /* MEDIUM or LARGE */ case AG_VARIABLE_UINT: AG_WriteUint32(ds, (Uint32)*(Uint *)p); break; case AG_VARIABLE_INT: AG_WriteSint32(ds, (Sint32)*(int *)p); break; case AG_VARIABLE_ULONG: AG_WriteUint64(ds, (Uint64)*(Ulong *)p); break; case AG_VARIABLE_LONG: AG_WriteSint64(ds, (Sint64)*(long *)p); break; #endif case AG_VARIABLE_UINT8: AG_WriteUint8(ds, *(Uint8 *)p); break; case AG_VARIABLE_SINT8: AG_WriteSint8(ds, *(Sint8 *)p); break; case AG_VARIABLE_UINT16: AG_WriteUint16(ds, *(Uint16 *)p); break; case AG_VARIABLE_SINT16: AG_WriteSint16(ds, *(Sint16 *)p); break; #if AG_MODEL != AG_SMALL case AG_VARIABLE_UINT32: AG_WriteUint32(ds, *(Uint32 *)p); break; case AG_VARIABLE_SINT32: AG_WriteSint32(ds, *(Sint32 *)p); break; #endif #ifdef AG_HAVE_64BIT case AG_VARIABLE_UINT64: AG_WriteUint64(ds, *(Uint64 *)p); break; case AG_VARIABLE_SINT64: AG_WriteSint64(ds, *(Sint64 *)p); break; #endif #ifdef AG_HAVE_FLOAT case AG_VARIABLE_FLOAT: AG_WriteFloat(ds, *(float *)p); break; case AG_VARIABLE_DOUBLE: AG_WriteDouble(ds, *(double *)p); break; #endif case AG_VARIABLE_STRING: AG_WriteString(ds, V->data.s); break; default: break; } AG_UnlockVariable(V); if (++count > AG_OBJECT_MAX_VARIABLES) { AG_FatalErrorV("E34", "Too many variables to save"); /* break; */ } } AG_ObjectUnlock(ob); AG_WriteUint32At(ds, count, countOffs); } /* * Load an Agar object (or a virtual filesystem of Agar objects) from an * archive file. * * Only the generic part is read, datasets are skipped and dependencies * are left unresolved. */ int AG_ObjectLoadGenericFromFile(void *p, const char *pPath) { AG_Object *ob = p; #if AG_MODEL == AG_SMALL AG_ObjectHeader *oh; #else AG_ObjectHeader oh; #endif char path[AG_PATHNAME_MAX]; AG_DataSource *ds; Uint32 count, i; AG_LockVFS(ob); AG_ObjectLock(ob); if (pPath != NULL) { Strlcpy(path, pPath, sizeof(path)); } else { if (AG_ObjectCopyFilename(ob, path, sizeof(path)) == -1) goto fail_unlock; } #ifdef DEBUG_SERIALIZATION Debug(ob, "Loading generic data from %s\n", path); #endif if ((ds = AG_OpenFile(path, "rb")) == NULL) goto fail_unlock; /* Free any resident dataset in order to clear the dependencies. */ AG_ObjectReset(ob); #if AG_MODEL == AG_SMALL if ((oh = TryMalloc(sizeof(AG_ObjectHeader))) == NULL) { goto fail; } if (AG_ObjectReadHeader(ds, oh) == -1) { goto fail; } ob->flags &= ~(AG_OBJECT_SAVED_FLAGS); ob->flags |= oh->flags; free(oh); #else if (AG_ObjectReadHeader(ds, &oh) == -1) { goto fail; } ob->flags &= ~(AG_OBJECT_SAVED_FLAGS); ob->flags |= oh.flags; #endif /* Skip over legacy (pre-1.6) dependency table */ count = AG_ReadUint32(ds); for (i = 0; i < count; i++) AG_SkipString(ds); /* Load the set of Variables */ if (AG_ObjectLoadVariables(ob, ds) == -1) goto fail; /* Load the generic part of the archived child objects. */ count = AG_ReadUint32(ds); for (i = 0; i < count; i++) { char cname[AG_OBJECT_NAME_MAX]; char hier[AG_OBJECT_HIER_MAX]; AG_Object *chld; AG_ObjectClass *C; /* TODO check that there are no duplicate names. */ AG_CopyString(cname, ds, sizeof(cname)); AG_CopyString(hier, ds, sizeof(hier)); /* Look for an existing object of the given name. */ if ((chld = AG_ObjectFindChild(ob, cname)) != NULL) { if (strcmp(chld->cls->hier, hier) != 0) { #ifdef AG_VERBOSITY AG_SetError(_("Archived object `%s' clashes with " "existing object of incompatible type"), cname); #else AG_SetErrorS("E12"); #endif goto fail; } if (AG_ObjectLoadGeneric(chld) == -1) { goto fail; } continue; } /* Create a new child object. */ #ifdef AG_ENABLE_DSO C = AG_LoadClass(hier); #else C = AG_LookupClass(hier); #endif if (C == NULL) { #ifdef AG_VERBOSITY AG_SetError("%s: %s", ob->name, AG_GetError()); #else AG_SetErrorS("E14"); #endif if (agObjectIgnoreUnknownObjs) { #ifdef DEBUG_SERIALIZATION Debug(ob, "%s; ignoring\n", AG_GetError()); #endif continue; } else { goto fail; } goto fail; } if ((chld = TryMalloc(C->size)) == NULL) { goto fail; } AG_ObjectInit(chld, C); AG_ObjectSetNameS(chld, cname); AG_ObjectAttach(ob, chld); if (AG_ObjectLoadGeneric(chld) == -1) goto fail; } AG_CloseFile(ds); AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (0); fail: AG_ObjectReset(ob); AG_CloseFile(ds); fail_unlock: AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (-1); } /* Load an Agar object dataset from an object archive file. */ int AG_ObjectLoadDataFromFile(void *p, int *dataFound, const char *pPath) { AG_ObjectHeader oh; char path[AG_PATHNAME_MAX]; AG_Object *ob = p; AG_DataSource *ds; AG_Version ver; AG_ObjectClass **hier; int i, nHier; AG_LockVFS(ob); AG_ObjectLock(ob); *dataFound = 1; /* Open the file. */ if (pPath != NULL) { Strlcpy(path, pPath, sizeof(path)); } else { if (AG_ObjectCopyFilename(ob, path, sizeof(path)) == -1) { *dataFound = 0; goto fail_unlock; } } #ifdef DEBUG_SERIALIZATION Debug(ob, "Loading dataset from %s\n", path); #endif if ((ds = AG_OpenFile(path, "rb")) == NULL) { *dataFound = 0; goto fail_unlock; } if (AG_ObjectReadHeader(ds, &oh) == -1 || AG_Seek(ds, oh.dataOffs, AG_SEEK_SET) == -1 || AG_ReadVersion(ds, ob->cls->name, &ob->cls->ver, &ver) == -1) { goto fail; } if (ob->flags & AG_OBJECT_DEBUG_DATA) { #ifdef AG_DEBUG AG_SetSourceDebug(ds, 1); #else AG_SetErrorV("E15", _("Can't read without DEBUG")); goto fail; #endif } if (AG_ObjectGetInheritHier(ob, &hier, &nHier) == -1) goto fail; AG_ObjectReset(ob); for (i = 0; i < nHier; i++) { #ifdef DEBUG_SERIALIZATION Debug(ob, "Loading as %s\n", hier[i]->name); #endif if (hier[i]->load == NULL) continue; if (hier[i]->load(ob, ds, &ver) == -1) { #ifdef AG_VERBOSITY AG_SetError("<0x%x>: %s", (Uint)AG_Tell(ds), AG_GetError()); #else AG_SetErrorS("E16"); #endif free(hier); goto fail; } } free(hier); AG_CloseFile(ds); AG_PostEvent(ob->root, "object-post-load", "%p,%s", ob, path); AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (0); fail: AG_CloseFile(ds); fail_unlock: AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (-1); } static void BackupObjectFile(const char *_Nonnull orig) { char path[AG_PATHNAME_MAX]; if (AG_FileExists(orig)) { Strlcpy(path, orig, sizeof(path)); Strlcat(path, ".bak", sizeof(path)); rename(orig, path); } } /* Save the state of an object and its children. */ int AG_ObjectSaveAll(void *p) { AG_Object *obj = p, *cobj; AG_LockVFS(obj); AG_ObjectLock(obj); if (AG_ObjectSave(obj) == -1) { goto fail; } TAILQ_FOREACH(cobj, &obj->children, cobjs) { AG_ObjectLock(cobj); if (AG_ObjectSaveAll(cobj) == -1) { AG_ObjectUnlock(cobj); goto fail; } AG_ObjectUnlock(cobj); } AG_ObjectUnlock(obj); AG_UnlockVFS(obj); return (0); fail: AG_ObjectUnlock(obj); AG_UnlockVFS(obj); return (-1); } /* Serialize an object to an arbitrary AG_DataSource(3). */ int AG_ObjectSerialize(void *p, AG_DataSource *ds) { AG_Object *ob = p; AG_Offset dataOffs; AG_ObjectClass **hier; int i, nHier; #ifdef AG_DEBUG int debugSave; #endif AG_ObjectLock(ob); /* Header */ AG_WriteVersion(ds, agObjectClass.name, &agObjectClass.ver); AG_WriteString(ds, ob->cls->hier); #ifdef AG_ENABLE_DSO AG_WriteString(ds, ob->cls->libs); #else AG_WriteString(ds, ""); #endif dataOffs = AG_Tell(ds); AG_WriteUint32(ds, 0); /* Data offs */ AG_WriteUint32(ds, (Uint32)(ob->flags & AG_OBJECT_SAVED_FLAGS)); /* Legacy (pre-1.6) dependency table. */ AG_WriteUint32(ds, 0); /* Persistent object variables */ AG_ObjectSaveVariables(ob, ds); /* Legacy (pre-1.7) child object metadata */ AG_WriteUint32(ds, 0); /* Dataset */ AG_WriteUint32At(ds, AG_Tell(ds), dataOffs); AG_WriteVersion(ds, ob->cls->name, &ob->cls->ver); #ifdef AG_DEBUG if (ob->flags & AG_OBJECT_DEBUG_DATA) { debugSave = AG_SetSourceDebug(ds, 1); } else { debugSave = 0; } #endif if (AG_ObjectGetInheritHier(ob, &hier, &nHier) == -1) { goto fail; } for (i = 0; i < nHier; i++) { #ifdef DEBUG_SERIALIZATION Debug(ob, "Saving as %s\n", hier[i]->name); #endif if (hier[i]->save == NULL) continue; if (hier[i]->save(ob, ds) == -1) { free(hier); goto fail; } } free(hier); #ifdef AG_DEBUG if (ob->flags & AG_OBJECT_DEBUG_DATA) AG_SetSourceDebug(ds, debugSave); #endif AG_ObjectUnlock(ob); return (0); fail: #ifdef AG_DEBUG if (ob->flags & AG_OBJECT_DEBUG_DATA) AG_SetSourceDebug(ds, debugSave); #endif AG_ObjectUnlock(ob); return (-1); } /* * Unserialize single object (not a VFS) from an arbitrary AG_DataSource(3). * To unserialize complete virtual filesystems, see AG_ObjectLoadFromFile(). */ int AG_ObjectUnserialize(void *p, AG_DataSource *ds) { AG_Object *ob = p; AG_ObjectHeader oh; AG_Version ver; AG_ObjectClass **hier = NULL; Uint32 count; int i, nHier; #ifdef AG_DEBUG int debugSave; #endif AG_ObjectLock(ob); /* Object header */ if (AG_ObjectReadHeader(ds, &oh) == -1) { goto fail; } ob->flags &= ~(AG_OBJECT_SAVED_FLAGS); ob->flags |= oh.flags; /* Skip over legacy (pre-1.6) dependency table */ count = AG_ReadUint32(ds); for (i = 0; i < count; i++) AG_SkipString(ds); /* Load the set of Variables */ if (AG_ObjectLoadVariables(ob, ds) == -1) goto fail; /* Table of child objects, expected empty. */ if (AG_ReadUint32(ds) != 0) { AG_SetErrorV("E17", "nChildren != 0"); goto fail; } /* Dataset */ if (AG_ReadVersion(ds, ob->cls->name, &ob->cls->ver, &ver) == -1) goto fail; if (ob->flags & AG_OBJECT_DEBUG_DATA) { #ifdef AG_DEBUG debugSave = AG_SetSourceDebug(ds, 1); #else AG_SetErrorV("E15", _("Can't read without DEBUG")); goto fail; #endif } else { #ifdef AG_DEBUG debugSave = 0; #endif } if (AG_ObjectGetInheritHier(ob, &hier, &nHier) == -1) { goto fail_dbg; } for (i = 0; i < nHier; i++) { #ifdef DEBUG_SERIALIZATION Debug(ob, "Loading as %s\n", hier[i]->name); #endif if (hier[i]->load == NULL) continue; if (hier[i]->load(ob, ds, &ver) == -1) { #ifdef AG_VERBOSITY AG_SetError("<0x%x>: %s", (Uint)AG_Tell(ds), AG_GetError()); #else AG_SetErrorS("E18"); #endif free(hier); goto fail_dbg; } } free(hier); #ifdef AG_DEBUG if (ob->flags & AG_OBJECT_DEBUG_DATA) AG_SetSourceDebug(ds, debugSave); #endif AG_ObjectUnlock(ob); return (0); fail_dbg: #ifdef AG_DEBUG if (ob->flags & AG_OBJECT_DEBUG_DATA) AG_SetSourceDebug(ds, debugSave); #endif fail: AG_ObjectReset(ob); AG_ObjectUnlock(ob); return (-1); } /* Archive an object to a file. */ int AG_ObjectSaveToFile(void *p, const char *pPath) { char dirPath[AG_PATHNAME_MAX]; char path[AG_PATHNAME_MAX]; char name[AG_OBJECT_PATH_MAX]; AG_Object *ob = p; AG_DataSource *ds; int hasArchivePath; AG_LockVFS(ob); AG_ObjectLock(ob); AG_ObjectCopyName(ob, name, sizeof(name)); hasArchivePath = AG_Defined(ob, "archive-path"); if (pPath != NULL) { Strlcpy(path, pPath, sizeof(path)); } else if (!hasArchivePath) { /* * Create the save directory if needed (but never do this * if an archive-path is set). */ if (AG_ConfigGetPath(AG_CONFIG_PATH_DATA, 0, dirPath, sizeof(dirPath)) >= sizeof(dirPath) || Strlcat(dirPath, name, sizeof(dirPath)) >= sizeof(dirPath)) { AG_SetErrorV("E4", _("Path overflow")); goto fail_unlock; } if (AG_FileExists(dirPath) == 0 && AG_MkPath(dirPath) == -1) goto fail_unlock; } if (pPath == NULL) { if (hasArchivePath) { AG_GetString(ob, "archive-path", path, sizeof(path)); } else { Strlcpy(path, dirPath, sizeof(path)); Strlcat(path, AG_PATHSEP, sizeof(path)); Strlcat(path, ob->name, sizeof(path)); Strlcat(path, ".", sizeof(path)); Strlcat(path, ob->cls->name, sizeof(path)); } } #ifdef DEBUG_SERIALIZATION Debug(ob, "Saving object to %s\n", path); #endif if (agObjectBackups) { BackupObjectFile(path); } else { AG_FileDelete(path); } if ((ds = AG_OpenFile(path, "wb")) == NULL) { goto fail_unlock; } if (AG_ObjectSerialize(ob, ds) == -1) { goto fail; } AG_CloseFile(ds); AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (0); fail: AG_CloseFile(ds); fail_unlock: AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (-1); } /* Shorthand for AG_ObjectSaveToFile() */ int AG_ObjectSave(void *p) { return AG_ObjectSaveToFile(p, NULL); } /* Load an object from an AG_Db database entry. */ int AG_ObjectLoadFromDB(void *obj, AG_Db *db, const AG_Dbt *key) { AG_DataSource *ds; AG_Dbt val; if (AG_DbGet(db, key, &val) == -1) { return (-1); } if ((ds = AG_OpenCore(val.data, val.size)) == NULL) { return (-1); } if (AG_ObjectUnserialize(obj, ds) == -1) { AG_CloseCore(ds); return (-1); } return (0); } /* Archive an object to an AG_Db database entry. */ int AG_ObjectSaveToDB(void *pObj, AG_Db *db, const AG_Dbt *key) { AG_Object *obj = pObj; AG_DataSource *ds; AG_Dbt dbKey, dbVal; int rv; if ((ds = AG_OpenAutoCore()) == NULL) return (-1); AG_LockVFS(obj); AG_ObjectLock(obj); rv = AG_ObjectSerialize(obj, ds); AG_ObjectUnlock(obj); AG_UnlockVFS(obj); if (rv == -1) goto fail; dbKey.data = obj->name; dbKey.size = strlen(obj->name)+1; dbVal.data = AG_CORE_SOURCE(ds)->data; dbVal.size = AG_CORE_SOURCE(ds)->size; rv = AG_DbPut(db, &dbKey, &dbVal); AG_CloseAutoCore(ds); return (rv); fail: AG_CloseAutoCore(ds); return (-1); } #endif /* AG_SERIALIZATION */ /* * Change the name of an object (C string). * The parent VFS, if any, must be locked. */ void AG_ObjectSetNameS(void *p, const char *name) { AG_Object *ob = p; char *c; AG_ObjectLock(ob); if (name == NULL) { ob->name[0] = '\0'; } else { #ifdef AG_DEBUG if (Strlcpy(ob->name, name, sizeof(ob->name)) >= sizeof(ob->name)) Verbose("Truncated object name: \"%s\"", ob->name); #else Strlcpy(ob->name, name, sizeof(ob->name)); #endif for (c = &ob->name[0]; *c != '\0'; c++) { if (*c == '/' || *c == '\\') /* Pathname separator */ *c = '_'; } } AG_ObjectUnlock(ob); } /* * Change the name of an object (format string). * The parent VFS, if any, must be locked. */ void AG_ObjectSetName(void *p, const char *fmt, ...) { AG_Object *ob = p; va_list ap; char *c; AG_ObjectLock(ob); if (fmt != NULL) { va_start(ap, fmt); Vsnprintf(ob->name, sizeof(ob->name), fmt, ap); va_end(ap); } else { ob->name[0] = '\0'; } for (c = &ob->name[0]; *c != '\0'; c++) { if (*c == '/' || *c == '\\') /* Pathname separator */ *c = '_'; } AG_ObjectUnlock(ob); } #if AG_MODEL != AG_SMALL /* Move an object towards the head of its parent's children list. */ void AG_ObjectMoveUp(void *p) { AG_Object *ob = p, *prev; AG_Object *parent = ob->parent; AG_LockVFS(parent); if (parent != NULL && ob != TAILQ_FIRST(&parent->children)) { prev = TAILQ_PREV(ob, ag_objectq, cobjs); TAILQ_REMOVE(&parent->children, ob, cobjs); TAILQ_INSERT_BEFORE(prev, ob, cobjs); } AG_UnlockVFS(parent); } /* Move an object towards the tail of its parent's children list. */ void AG_ObjectMoveDown(void *p) { AG_Object *ob = p; AG_Object *parent = ob->parent; AG_Object *next = TAILQ_NEXT(ob, cobjs); AG_LockVFS(parent); if (parent != NULL && next != NULL) { TAILQ_REMOVE(&parent->children, ob, cobjs); TAILQ_INSERT_AFTER(&parent->children, next, ob, cobjs); } AG_UnlockVFS(parent); } /* Move an object to the head of its parent's children list. */ void AG_ObjectMoveToHead(void *p) { AG_Object *ob = p; AG_Object *parent = ob->parent; AG_LockVFS(parent); if (parent != NULL) { TAILQ_REMOVE(&parent->children, ob, cobjs); TAILQ_INSERT_HEAD(&parent->children, ob, cobjs); } AG_UnlockVFS(parent); } /* Move an object to the tail of its parent's children list. */ void AG_ObjectMoveToTail(void *p) { AG_Object *ob = p; AG_Object *parent = ob->parent; AG_LockVFS(parent); if (parent != NULL) { TAILQ_REMOVE(&parent->children, ob, cobjs); TAILQ_INSERT_TAIL(&parent->children, ob, cobjs); } AG_UnlockVFS(parent); } #endif /* !AG_SMALL */ #ifdef AG_SERIALIZATION /* * Remove the data files of an object and its children. * The object's VFS must be locked. */ void AG_ObjectUnlinkDatafiles(void *p) { char path[AG_PATHNAME_MAX]; AG_Object *ob = p, *cob; AG_ObjectLock(ob); if (AG_ObjectCopyFilename(ob, path, sizeof(path)) == 0) { AG_FileDelete(path); } TAILQ_FOREACH(cob, &ob->children, cobjs) { AG_ObjectUnlinkDatafiles(cob); } if (AG_ObjectCopyDirname(ob, path, sizeof(path)) == 0) { AG_RmDir(path); } AG_ObjectUnlock(ob); } /* * Check whether the dataset of the given object or any of its children are * different with respect to the last archive. The result is only valid as * long as the object and VFS are locked, and this assumes that no other * application is concurrently accessing the datafiles. */ int AG_ObjectChangedAll(void *p) { AG_Object *ob = p, *cob; AG_LockVFS(ob); AG_ObjectLock(ob); if (AG_ObjectChanged(ob) == 1) { goto changed; } TAILQ_FOREACH(cob, &ob->children, cobjs) { if (AG_ObjectChangedAll(cob) == 1) goto changed; } AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (0); changed: AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (1); } /* * Check whether the dataset of the given object is different with respect * to its last archive. The result is only valid as long as the object is * locked, and this assumes no other application is concurrently accessing * the datafiles. */ int AG_ObjectChanged(void *p) { #if AG_MODEL == AG_SMALL char *bufCur = Malloc(AG_BUFFER_MAX); char *bufLast = Malloc(AG_BUFFER_MAX); #else char bufCur[AG_BUFFER_MAX]; char bufLast[AG_BUFFER_MAX]; #endif char pathCur[AG_PATHNAME_MAX]; char pathLast[AG_PATHNAME_MAX]; AG_Object *ob = p; FILE *fLast, *fCur; AG_Size rvLast, rvCur; int rv = 0; AG_ObjectLock(ob); AG_ObjectCopyFilename(ob, pathLast, sizeof(pathLast)); if ((fLast = fopen(pathLast, "r")) == NULL) { rv = 1; goto out; } AG_ConfigGetPath(AG_CONFIG_PATH_TEMP, 0, pathCur, sizeof(pathCur)); Strlcat(pathCur, AG_PATHSEP, sizeof(pathCur)); Strlcat(pathCur, "_chg.", sizeof(pathCur)); Strlcat(pathCur, ob->name, sizeof(pathCur)); if (AG_ObjectSaveToFile(ob, pathCur) == -1) { fclose(fLast); rv = 1; goto out; } if ((fCur = fopen(pathCur, "r")) == NULL) { fclose(fLast); rv = 1; goto out; } for (;;) { rvLast = fread(bufLast, 1, sizeof(bufLast), fLast); rvCur = fread(bufCur, 1, sizeof(bufCur), fCur); if (rvLast != rvCur || (rvLast > 0 && memcmp(bufLast, bufCur, rvLast) != 0)) { rv = 1; break; } if (feof(fLast)) { if (!feof(fCur)) { rv = 1; } break; } if (feof(fCur)) { if (!feof(fLast)) { rv = 1; } break; } } AG_FileDelete(pathCur); fclose(fCur); fclose(fLast); out: AG_ObjectUnlock(ob); #if AG_MODEL == AG_SMALL free(bufCur); free(bufLast); #endif return (rv); } #endif /* AG_SERIALIZATION */ /* * Generate an object name that is unique in the given parent object. The * name is only guaranteed to remain unique as long as the VFS and parent * object are locked. */ void AG_ObjectGenName(void *p, AG_ObjectClass *C, char *name, AG_Size len) { AG_Object *pobj = p, *chld; char *ccBase, *cc, *dBase; Uint i; if ((ccBase = strchr(C->name, '_')) != NULL) { /* Skip any prefix */ ccBase++; } else { ccBase = C->name; } if (len < 4) { return; } name[0] = tolower(ccBase[0]); len--; for (cc = &ccBase[1], dBase = &name[1]; *cc != '\0' && len > 0; cc++, dBase++) { *dBase = tolower(*cc); len--; } *dBase = '\0'; i = 0; tryname: StrlcpyUint(dBase, i, len); if (pobj != NULL) { AG_LockVFS(pobj); TAILQ_FOREACH(chld, &pobj->children, cobjs) { if (strcmp(chld->name, name) == 0) break; } AG_UnlockVFS(pobj); if (chld != NULL) { i++; goto tryname; } } } #if AG_MODEL != AG_SMALL /* Generate a unique object name using the specified prefix. */ void AG_ObjectGenNamePfx(void *p, const char *pfx, char *name, AG_Size len) { AG_Object *pobj = p; Uint i = 1; AG_Object *ch; tryname: Strlcpy(name, pfx, len); StrlcatUint(name, i, len); if (pobj != NULL) { AG_LockVFS(pobj); TAILQ_FOREACH(ch, &pobj->children, cobjs) { if (strcmp(ch->name, name) == 0) break; } AG_UnlockVFS(pobj); if (ch != NULL) { i++; goto tryname; } } } #endif /* !AG_SMALL */ #ifdef AG_LEGACY void AG_ObjectSetArchivePath(void *obj, const char *path) { AG_SetString(obj, "archive-path", path); } #endif /* !AG_LEGACY */ static void InitClass(AG_ObjectClass *_Nonnull C, const char *_Nonnull hier) { const char *c; AG_Size rv; if (Strlcpy(C->hier, hier, sizeof(C->hier)) >= sizeof(C->hier)) goto too_big; if ((c = strrchr(hier, ':')) != NULL && c[1] != '\0') { rv = Strlcpy(C->name, &c[1], sizeof(C->name)); } else { rv = Strlcpy(C->name, hier, sizeof(C->name)); } if (rv >= sizeof(C->name)) { goto too_big; } TAILQ_INIT(&C->sub); return; too_big: AG_FatalError("Class name overflow"); } /* * Initialize the object class description table. * Invoked internally by AG_InitCore(). */ void AG_InitClassTbl(void) { AG_Variable V; #ifdef AG_NAMESPACES agNamespaceTbl = Malloc(sizeof(AG_Namespace)); agNamespaceCount = 0; AG_RegisterNamespace("Agar", "AG_", "https://libagar.org/"); #endif #ifdef AG_ENABLE_DSO agModuleDirs = Malloc(sizeof(char *)); agModuleDirCount = 0; #endif /* Initialize the class tree */ InitClass(&agObjectClass, "AG_Object"); #ifdef AG_ENABLE_DSO agObjectClass.libs[0] = '\0'; #endif /* Initialize the class table. */ agClassTbl = AG_TblNew(AG_OBJECT_CLASSTBLSIZE, 0); /* AG_Object -> agObjectClass */ AG_InitPointer(&V, &agObjectClass); if (AG_TblInsert(agClassTbl, "AG_Object", &V) == -1) AG_FatalError(NULL); AG_MutexInitRecursive(&agClassLock); } /* * Release the object class description table. * Invoked internally by AG_Destroy(). */ void AG_DestroyClassTbl(void) { #ifdef AG_NAMESPACES free(agNamespaceTbl); agNamespaceTbl = NULL; agNamespaceCount = 0; #endif #ifdef AG_ENABLE_DSO { int i; for (i = 0; i < agModuleDirCount; i++) { free(agModuleDirs[i]); } free(agModuleDirs); agModuleDirs = NULL; agModuleDirCount = 0; } #endif AG_TblDestroy(agClassTbl); free(agClassTbl); agClassTbl = NULL; AG_MutexDestroy(&agClassLock); } #ifdef AG_NAMESPACES /* * Parse a class specification string either in the conventional form * "AG_Class1:AG_Class2:...[@lib]", or in "Agar(Class1:Class2:...)[@lib]" * format if NAMESPACE is supported. */ int AG_ParseClassSpec(AG_ObjectClassSpec *cs, const char *spec) { char buf[AG_OBJECT_HIER_MAX], *pBuf, *pTok; char nsName[AG_OBJECT_HIER_MAX], *pNsName = nsName; const char *s, *p, *pOpen = NULL; char *c; AG_Namespace *ns; AG_Size rv; int iTok, i=0, len=0; cs->hier[0] = '\0'; cs->name[0] = '\0'; # ifdef AG_ENABLE_DSO cs->libs[0] = '\0'; # endif *pNsName = '\0'; for (s = &spec[0]; *s != '\0'; s++) { if (++len >= AG_OBJECT_HIER_MAX) { AG_SetErrorV("E22", _("Class is too long")); return (-1); } if (s[0] == '(' && s[1] != '\0') { if (pOpen || nsName[0] == '\0') { AG_SetErrorV("E23", _("Class syntax error")); return (-1); } pOpen = &s[1]; continue; } if (pOpen == NULL) { if (*s != ':') { *pNsName = *s; pNsName++; } # ifdef AG_ENABLE_DSO if (s[0] == '@' && s[1] != '\0') if (Strlcpy(cs->libs, &s[1], sizeof(cs->libs)) >= sizeof(cs->libs)) AG_FatalError("DSO name overflow"); # endif } if (*s == ')') { if ((s - pOpen) == 0) { pOpen = NULL; continue; } *pNsName = '\0'; pNsName = &nsName[0]; if ((ns = AG_GetNamespace(nsName)) == NULL) { AG_SetErrorV("E24", _("No such namespace")); return (-1); } for (p = pOpen, iTok = 0; (p < s) && (iTok < sizeof(buf)-1); p++, iTok++) { buf[iTok] = *p; } buf[iTok] = '\0'; for (pBuf = buf; (pTok = Strsep(&pBuf, ":")) != NULL; ) { i += Strlcpy(&cs->hier[i], ns->pfx, sizeof(cs->hier)-i); i += Strlcpy(&cs->hier[i], pTok, sizeof(cs->hier)-i); i += Strlcpy(&cs->hier[i], ":", sizeof(cs->hier)-i); } pOpen = NULL; continue; } } /* Fill in the "hier" (full hierarchy) field. */ if (i > 0 && cs->hier[i-1] == ':') { cs->hier[i-1] = '\0'; /* Strip last ':' */ } if (i == 0) { /* Flat format */ #ifdef AG_DEBUG if (Strlcpy(cs->hier, spec, sizeof(cs->hier)) >= sizeof(cs->hier)) AG_FatalError("Class hierarchy overflow"); #else Strlcpy(cs->hier, spec, sizeof(cs->hier)); #endif } else { cs->hier[i] = '\0'; } if ((c = strrchr(cs->hier, '@')) != NULL) *c = '\0'; /* Fill in the "name" (short name) field. */ if ((c = strrchr(cs->hier, ':')) != NULL && c[1] != '\0') { rv = Strlcpy(cs->name, &c[1], sizeof(cs->name)); } else { rv = Strlcpy(cs->name, cs->hier, sizeof(cs->name)); } if (rv >= sizeof(cs->name)) { AG_FatalError("Class name overflow"); } if ((c = strrchr(cs->name, '@')) != NULL) { *c = '\0'; } /* Fill in the "spec" (full hierarchy + @libs) field. */ Strlcpy(cs->spec, cs->hier, sizeof(cs->spec)); /* Fill in the "libs" (DSO modules) field. */ # ifdef AG_ENABLE_DSO if (cs->libs[0] != '\0') if (Strlcat(cs->spec, cs->libs, sizeof(cs->spec)) >= sizeof(cs->spec)) AG_FatalError("DSO name overflow"); # endif return (0); } #else /* !AG_NAMESPACES */ /* * Parse a class specification string only in the conventional format * "AG_Class1:AG_Class2:...[@lib]" (no namespace support). */ int AG_ParseClassSpec(AG_ObjectClassSpec *cs, const char *spec) { char *c; Strlcpy(cs->hier, spec, sizeof(cs->hier)); Strlcpy(cs->spec, spec, sizeof(cs->spec)); if ((c = strchr(cs->hier, '@')) != NULL) { # ifdef AG_ENABLE_DSO Strlcpy(cs->libs, &c[1], sizeof(cs->libs)); # endif *c = '\0'; } # ifdef AG_ENABLE_DSO else { cs->libs[0] = '\0'; } # endif if ((c = strrchr(spec, ':')) != NULL && c[1] != '\0') { Strlcpy(cs->name, &c[1], sizeof(cs->name)); } else { Strlcpy(cs->name, spec, sizeof(cs->name)); } return (0); } #endif /* !AG_NAMESPACES */ /* Register object class as described by the given AG_ObjectClass structure. */ void AG_RegisterClass(void *p) { AG_ObjectClass *C = p; AG_ObjectClassSpec cs; AG_Variable V; char *s; if (AG_ParseClassSpec(&cs, C->hier) == -1) { AG_FatalError(NULL); } InitClass(C, cs.hier); #ifdef AG_ENABLE_DSO Strlcpy(C->libs, cs.libs, sizeof(C->libs)); #endif #ifdef AG_DEBUG_CLASSES Debug(NULL, "[ Register %s (%s) ]\n", cs.name, cs.hier); #endif AG_MutexLock(&agClassLock); /* Insert into the class tree. */ if ((s = strrchr(cs.hier, ':')) != NULL) { *s = '\0'; if ((C->super = AG_LookupClass(cs.hier)) == NULL) AG_FatalError(NULL); } else { C->super = &agObjectClass; /* Base AG_Object class */ } TAILQ_INSERT_TAIL(&C->super->sub, C, subclasses); /* Insert into the class table. */ AG_InitPointer(&V, C); if (AG_TblInsert(agClassTbl, C->hier, &V) == -1) AG_FatalError(NULL); AG_MutexUnlock(&agClassLock); } /* Unregister an object class. */ void AG_UnregisterClass(void *p) { AG_ObjectClass *C = p; AG_ObjectClass *Csuper = C->super; Uint h; AG_MutexLock(&agClassLock); h = AG_TblHash(agClassTbl, C->hier); if (AG_TblExistsHash(agClassTbl, h, C->hier)) { #ifdef AG_DEBUG_CLASSES Debug(NULL, "[ Unregister %s ]\n", C->name); #endif /* Remove from the class tree. */ TAILQ_REMOVE(&Csuper->sub, C, subclasses); C->super = NULL; /* Remove from the class table. */ AG_TblDeleteHash(agClassTbl, h, C->hier); } AG_MutexUnlock(&agClassLock); } #if AG_MODEL != AG_SMALL /* * Allocate, initialize and zero an AG_ObjectClass (or derivative thereof). * * This gives an alternative to passing a statically-initialized AG_ObjectClass * to AG_RegisterClass(). Here we auto-allocate it instead, * and the methods can be set using AG_ClassSet{Init,Reset,Destroy,...}(). */ void * AG_CreateClass(const char *hier, AG_Size objectSize, AG_Size classSize, Uint major, Uint minor) { AG_ObjectClass *C; if ((C = TryMalloc(classSize)) == NULL) { return (NULL); } memset(C, 0, classSize); if (Strlcpy(C->hier, hier, sizeof(C->hier)) >= sizeof(C->hier)) { AG_FatalError("Class hierarchy overflow"); } C->size = objectSize; C->ver.major = major; C->ver.minor = minor; AG_RegisterClass(C); return (C); } /* Set Object class operations procedurally. */ #define AG_CLASS_SET_FN_BODY(fnName, fnType, op) \ fnType fnName (void *Cp, fnType fn) { \ AG_ObjectClass *C = (AG_ObjectClass *)Cp; \ fnType fnOrig = C->op; \ C->op = fn; \ return (fnOrig); \ } AG_CLASS_SET_FN_BODY(AG_ClassSetInit, AG_ObjectInitFn, init); AG_CLASS_SET_FN_BODY(AG_ClassSetReset, AG_ObjectResetFn, reset); AG_CLASS_SET_FN_BODY(AG_ClassSetDestroy, AG_ObjectDestroyFn, destroy); AG_CLASS_SET_FN_BODY(AG_ClassSetLoad, AG_ObjectLoadFn, load); AG_CLASS_SET_FN_BODY(AG_ClassSetSave, AG_ObjectSaveFn, save); AG_CLASS_SET_FN_BODY(AG_ClassSetEdit, AG_ObjectEditFn, edit); #undef AG_CLASS_SET_FN_BODY /* Unregister and free an auto-allocated AG_ObjectClass (or derivative thereof) */ void AG_DestroyClass(void *C) { AG_UnregisterClass(C); free(C); } #endif /* !AG_SMALL */ /* * Lookup information about a registered object class. * Return a normalized class description (or NULL if no such class exists). */ AG_ObjectClass * AG_LookupClass(const char *inSpec) { AG_ObjectClassSpec cs; AG_Variable *V; if (inSpec[0] == '\0' || #ifdef AG_NAMESPACES strcmp(inSpec, "Agar(Object)") == 0 || #endif strcmp(inSpec, "AG_Object") == 0) return (&agObjectClass); if (AG_ParseClassSpec(&cs, inSpec) == -1) return (NULL); /* Look up the class table. */ AG_MutexLock(&agClassLock); if ((V = AG_TblLookup(agClassTbl, cs.hier)) != NULL) { AG_MutexUnlock(&agClassLock); return ((AG_ObjectClass *)V->data.p); } AG_MutexUnlock(&agClassLock); AG_SetErrorV("E25", _("No such class")); return (NULL); } #ifdef AG_ENABLE_DSO /* * Transform "PFX_Foo" string to "pfxFooClass". */ static int GetClassSymbol(char *_Nonnull sym, AG_Size len, const AG_ObjectClassSpec *_Nonnull cs) { char *d; const char *c; int inPfx = 1; AG_Size l = 0; for (c = &cs->name[0], d = &sym[0]; *c != '\0'; c++) { if (*c == '_') { inPfx = 0; continue; } if ((l+2) >= len) { goto toolong; } *d = inPfx ? (char) tolower((int) *c) : *c; d++; l++; } *d = '\0'; if (Strlcat(sym, "Class", len) >= len) { goto toolong; } return (0); toolong: AG_SetErrorS(_("Symbol is too long")); return (-1); } /* * Look for a "@libs" string in the class specification and scan module * directories for the required libraries. If they are found, bring them * into the current process's address space. If successful, look up the * "pfxFooClass" symbol and register the class. * * Multiple libraries can be specified with commas. The "pfxFooClass" * symbol is assumed to be defined in the first library in the list. */ AG_ObjectClass * AG_LoadClass(const char *classSpec) { AG_ObjectClassSpec cs; AG_ObjectClass *C; char *s, *lib; char sym[AG_OBJECT_HIER_MAX]; AG_DSO *dso; void *pClass = NULL; int i; if (AG_ParseClassSpec(&cs, classSpec) == -1) return (NULL); AG_MutexLock(&agClassLock); if ((C = AG_LookupClass(cs.hier)) != NULL) { AG_MutexUnlock(&agClassLock); return (C); /* Found a registered class */ } if (cs.libs[0] == '\0') { AG_SetError(_("Class " AGSI_BR_CYAN "%s" AGSI_RST " not found."), cs.hier); goto fail; } for (i = 0, s = cs.libs; /* Attempt dynamic linking */ (lib = Strsep(&s, ", ")) != NULL; i++) { # ifdef AG_DEBUG_CLASSES Debug(NULL, "<%s>: Linking %s...", classSpec, lib); # endif if ((dso = AG_LoadDSO(lib, 0)) == NULL) { AG_SetError("DSO(%s): %s", classSpec, AG_GetError()); goto fail; } /* Look up "pfxFooClass" in the first library. */ if (i == 0) { if (GetClassSymbol(sym, sizeof(sym), &cs) == -1) { goto fail; } if (AG_SymDSO(dso, sym, &pClass) == -1) { AG_UnloadDSO(dso); /* XXX TODO undo other DSOs we just loaded */ goto fail; } } # ifdef AG_DEBUG_CLASSES Debug(NULL, "OK\n"); # endif } if (pClass == NULL) { AG_SetError(_("<%s>: No library specified"), classSpec); goto fail; } AG_RegisterClass(pClass); AG_MutexUnlock(&agClassLock); return (pClass); fail: # ifdef AG_DEBUG_CLASSES Debug(NULL, "%s\n", AG_GetError()); # endif AG_MutexUnlock(&agClassLock); return (pClass); } /* * Unregister the given class and decrement the reference count / unload * related dynamically-linked libraries. */ void AG_UnloadClass(AG_ObjectClass *C) { char *s, *lib; AG_DSO *dso; AG_UnregisterClass(C); for (s = C->libs; (lib = Strsep(&s, ", ")) != NULL; ) { if ((dso = AG_LookupDSO(lib)) != NULL) AG_UnloadDSO(dso); } } #endif /* AG_ENABLE_DSO */ #ifdef AG_NAMESPACES /* Register a new namespace. */ AG_Namespace * AG_RegisterNamespace(const char *name, const char *pfx, const char *url) { AG_Namespace *ns; agNamespaceTbl = Realloc(agNamespaceTbl, (agNamespaceCount+1)*sizeof(AG_Namespace)); ns = &agNamespaceTbl[agNamespaceCount++]; ns->name = name; ns->pfx = pfx; ns->url = url; return (ns); } /* Unregister a namespace. */ void AG_UnregisterNamespace(const char *name) { int i; for (i = 0; i < agNamespaceCount; i++) { if (strcmp(agNamespaceTbl[i].name, name) == 0) break; } if (i < agNamespaceCount) { if (i < agNamespaceCount-1) { memmove(&agNamespaceTbl[i], &agNamespaceTbl[i+1], (agNamespaceCount-i-1)*sizeof(AG_Namespace)); } agNamespaceCount--; } } #endif /* AG_NAMESPACES */ #ifdef AG_ENABLE_DSO /* Register a new module directory path. */ void AG_RegisterModuleDirectory(const char *path) { char *s, *p; agModuleDirs = Realloc(agModuleDirs, (agModuleDirCount+1)*sizeof(char *)); agModuleDirs[agModuleDirCount++] = s = Strdup(path); if (*(p = &s[strlen(s)-1]) == AG_PATHSEPCHAR) *p = '\0'; } /* Unregister a module directory path. */ void AG_UnregisterModuleDirectory(const char *path) { int i; for (i = 0; i < agModuleDirCount; i++) { if (strcmp(agModuleDirs[i], path) == 0) break; } if (i < agModuleDirCount) { free(agModuleDirs[i]); if (i < agModuleDirCount-1) { memmove(&agModuleDirs[i], &agModuleDirs[i+1], (agModuleDirCount-i-1)*sizeof(char *)); } agModuleDirCount--; } } #endif /* AG_ENABLE_DSO */ /* General case fallback for AG_ClassIsNamed() */ int AG_ClassIsNamedGeneral(const AG_ObjectClass *C, const char *cn) { char cname[AG_OBJECT_HIER_MAX], *cp, *c; char nname[AG_OBJECT_HIER_MAX], *np, *s; Strlcpy(cname, cn, sizeof(cname)); Strlcpy(nname, C->hier, sizeof(nname)); cp = cname; np = nname; while ((c = Strsep(&cp, ":")) != NULL && (s = Strsep(&np, ":")) != NULL) { if (c[0] == '*' && c[1] == '\0') continue; if (strcmp(c, s) != 0) return (0); } return (1); } /* * Return an array of class description pointers ("AG_ObjectClass *") for * each class in the inheritance hierarchy of obj. For example: * * "AG_Widget:AG_Box:AG_Titlebar" -> { &agWidgetClass, * &agBoxClass, * &agTitlebarClass } * * The caller should release the returned array using free() after use. */ int AG_ObjectGetInheritHier(void *obj, AG_ObjectClass ***hier, int *nHier) { char cname[AG_OBJECT_HIER_MAX], *c; AG_ObjectClass *C, **pHier; int i, stop = 0; if (AGOBJECT(obj)->cls->hier[0] == '\0') { (*nHier) = 0; return (0); } (*nHier) = 1; Strlcpy(cname, AGOBJECT(obj)->cls->hier, sizeof(cname)); for (c = &cname[0]; *c != '\0'; c++) { if (*c == ':') (*nHier)++; } pHier = (*hier) = Malloc((*nHier)*sizeof(AG_ObjectClass *)); for (c = &cname[0], i = 0; ; c++) { if (*c != ':' && *c != '\0') { continue; } if (*c == '\0') { stop++; } else { *c = '\0'; } if ((C = AG_LookupClass(cname)) == NULL) { AG_SetError( _("No such class " AGSI_BR_CYAN "%s" AGSI_RST ". " "Missing AG_RegisterClass(3) call?"), AGOBJECT(obj)->cls->hier); free(pHier); return (-1); } *c = ':'; pHier[i++] = C; if (stop) break; } return (0); }