/* * Copyright (c) 2001-2009 Hypertriton, Inc. * 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. */ /* * Implementation of the generic Object class. */ #include #include #include #include #include #ifdef AG_NETWORK #include #endif #include #include #include #include #include #include #include AG_ObjectClass agObjectClass = { "Agar(Object)", sizeof(AG_Object), { 7, 2 }, NULL, /* init */ NULL, /* reinit */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ NULL /* edit */ }; const AG_Version agPropTblVer = { 2, 1 }; int agObjectIgnoreDataErrors = 0; /* Don't fail on a data load failure. */ int agObjectIgnoreUnknownObjs = 0; /* Don't fail on unknown object types. */ int agObjectBackups = 1; /* Backup object save files. */ void AG_ObjectInit(void *p, void *cl) { AG_Object *ob = p; AG_ObjectClass **hier; int i, nHier; ob->name[0] = '\0'; ob->save_pfx = "/world"; ob->archivePath = NULL; ob->cls = (cl != NULL) ? cl : &agObjectClass; ob->parent = NULL; ob->root = ob; ob->flags = 0; ob->nevents = 0; ob->vars = NULL; ob->nVars = 0; #ifdef AG_LOCKDEBUG ob->lockinfo = Malloc(sizeof(char *)); ob->nlockinfo = 0; #endif #ifdef AG_DEBUG ob->debugFn = NULL; ob->debugPtr = NULL; #endif AG_MutexInitRecursive(&ob->lock); TAILQ_INIT(&ob->deps); TAILQ_INIT(&ob->children); TAILQ_INIT(&ob->events); TAILQ_INIT(&ob->timeouts); if (AG_ObjectGetInheritHier(ob, &hier, &nHier) == 0) { for (i = 0; i < nHier; i++) { if (hier[i]->init != NULL) hier[i]->init(ob); } Free(hier); } else { AG_FatalError("AG_ObjectInit: %s", AG_GetError()); } ob->flags &= ~(AG_OBJECT_RESIDENT); } void AG_ObjectInitStatic(void *p, void *cl) { AG_ObjectInit(p, cl); OBJECT(p)->flags |= AG_OBJECT_STATIC; } /* Create a new object instance and mark it resident. */ void * AG_ObjectNew(void *parent, const char *name, AG_ObjectClass *cl) { char nameGen[AG_OBJECT_NAME_MAX]; AG_Object *obj; if (name == NULL) { AG_ObjectGenName(parent, cl, nameGen, sizeof(nameGen)); } else { if (parent != NULL && AG_ObjectFindChild(parent, name) != NULL) { AG_SetError(_("%s: Existing child object %s"), OBJECT(parent)->name, name); return (NULL); } } if ((obj = malloc(cl->size)) == NULL) { AG_SetError("Out of memory"); return (NULL); } AG_ObjectInit(obj, cl); AG_ObjectSetName(obj, "%s", (name != NULL) ? name : nameGen); obj->flags |= AG_OBJECT_RESIDENT; if (parent != NULL) { AG_ObjectAttach(parent, obj); } return (obj); } void AG_ObjectRemain(void *p, int flags) { AG_Object *ob = p; AG_ObjectLock(ob); if (flags & AG_OBJECT_REMAIN_DATA) { ob->flags |= (AG_OBJECT_REMAIN_DATA|AG_OBJECT_RESIDENT); } else { ob->flags &= ~AG_OBJECT_REMAIN_DATA; } AG_ObjectUnlock(ob); } /* * Free an object's dataset. Dependencies are preserved, but they are all * assumed to have reference counts of 0. */ void AG_ObjectFreeDataset(void *p) { AG_Object *ob = p; int preserveDeps; AG_ObjectClass **hier; int i, nHier; AG_ObjectLock(ob); if (!OBJECT_RESIDENT(ob)) { goto out; } preserveDeps = (ob->flags & AG_OBJECT_PRESERVE_DEPS); ob->flags |= AG_OBJECT_PRESERVE_DEPS; if (AG_ObjectGetInheritHier(ob, &hier, &nHier) == 0) { for (i = nHier-1; i >= 0; i--) { if (hier[i]->reinit != NULL) hier[i]->reinit(ob); } Free(hier); } else { AG_FatalError("AG_ObjectFreeDataset: %s: %s", ob->name, AG_GetError()); } ob->flags &= ~(AG_OBJECT_RESIDENT); if (!preserveDeps) ob->flags &= ~(AG_OBJECT_PRESERVE_DEPS); out: AG_ObjectUnlock(ob); } /* * Recursive function to construct absolute object names. * The object's VFS must be locked. */ static int GenerateObjectPath(void *obj, char *path, size_t path_len) { AG_Object *ob = obj; size_t name_len, cur_len; int rv = 0; AG_ObjectLock(ob); cur_len = strlen(path)+1; name_len = strlen(ob->name)+1; if (sizeof("/")+name_len+sizeof("/")+cur_len >= path_len) { AG_SetError(_("The path exceeds >= %lu bytes."), (unsigned long)path_len); AG_ObjectUnlock(ob); return (-1); } /* Prepend / and the object name. */ memmove(&path[name_len], path, cur_len); /* Move the NUL as well */ path[0] = '/'; memcpy(&path[1], ob->name, name_len-1); /* Omit the NUL */ if (ob->parent != ob->root && ob->parent != NULL) { rv = GenerateObjectPath(ob->parent, path, path_len); } AG_ObjectUnlock(ob); return (rv); } /* * Copy the absolute pathname of an object to a fixed-size buffer. * The buffer size must be >2 bytes. */ int AG_ObjectCopyName(void *obj, char *path, size_t path_len) { AG_Object *ob = obj; int rv = 0; path[0] = '/'; path[1] = '\0'; AG_LockVFS(ob); AG_ObjectLock(ob); if (ob != ob->root) { Strlcat(path, ob->name, path_len); } if (ob != ob->root && ob->parent != ob->root && ob->parent != NULL) { rv = GenerateObjectPath(ob->parent, path, path_len); } AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (rv); } /* * Search an object and its children for a dependency upon robj. * The object's VFS must be locked. */ static int FindObjectInUse(void *p, void *robj) { AG_Object *ob = p, *cob; AG_ObjectDep *dep; AG_ObjectLock(ob); TAILQ_FOREACH(dep, &ob->deps, deps) { if (dep->obj == robj && robj != ob) { AG_SetError(_("The `%s' object is used by `%s'."), OBJECT(robj)->name, ob->name); goto used; } } TAILQ_FOREACH(cob, &ob->children, cobjs) { if (FindObjectInUse(cob, robj)) goto used; } AG_ObjectUnlock(ob); return (0); used: AG_ObjectUnlock(ob); return (1); } /* * Return 1 if the given object or one of its children is being referenced. * Return value is only valid as long as the VFS is locked. */ int AG_ObjectInUse(void *p) { AG_Object *ob = p, *cob; AG_Object *root; AG_LockVFS(ob); root = AG_ObjectRoot(ob); if (FindObjectInUse(root, ob)) { goto used; } TAILQ_FOREACH(cob, &ob->children, cobjs) { if (AG_ObjectInUse(cob)) goto used; } AG_UnlockVFS(ob); return (0); used: AG_UnlockVFS(ob); return (1); } #ifdef AG_LEGACY /* Move an object from one parent object to another. */ void AG_ObjectMove(void *childp, void *newparentp) { AG_Object *child = childp; AG_Object *oparent = child->parent; AG_Object *nparent = newparentp; #ifdef AG_DEBUG if (oparent->root != nparent->root) AG_FatalError("Cannot move objects across VFSes"); #endif AG_LockVFS(oparent); AG_ObjectLock(oparent); AG_ObjectLock(nparent); AG_ObjectLock(child); TAILQ_REMOVE(&oparent->children, child, cobjs); child->parent = NULL; AG_PostEvent(oparent, child, "detached", NULL); TAILQ_INSERT_TAIL(&nparent->children, child, cobjs); child->parent = nparent; AG_PostEvent(nparent, child, "attached", NULL); AG_PostEvent(oparent, child, "moved", "%p", nparent); #ifdef AG_OBJDEBUG Debug(child, "Moving from %s to new parent %s\n", oparent->name, nparent->name); Debug(oparent, "Detached object: %s (moving to %s)\n", child->name, nparent->name); Debug(nparent, "Attached object: %s (originally in %s)\n", child->name, oparent->name); #endif AG_ObjectLock(child); AG_ObjectLock(nparent); AG_ObjectLock(oparent); AG_UnlockVFS(oparent); } #endif /* AG_LEGACY */ /* 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; AG_LockVFS(parent); AG_ObjectLock(parent); AG_ObjectLock(chld); if (chld->flags & AG_OBJECT_NAME_ONATTACH) { AG_ObjectGenName(parent, chld->cls, chld->name, sizeof(chld->name)); } TAILQ_INSERT_TAIL(&parent->children, chld, cobjs); chld->parent = parent; chld->root = parent->root; AG_PostEvent(parent, chld, "attached", NULL); AG_PostEvent(chld, parent, "child-attached", NULL); #ifdef AG_OBJDEBUG Debug(parent, "Attached child object: %s\n", chld->name); Debug(chld, "Attached to new parent: %s\n", parent->name); #endif AG_ObjectUnlock(chld); AG_ObjectUnlock(parent); AG_UnlockVFS(parent); } /* * Attach an object to some other object specified by a pathname specific * to the given VFS. */ int AG_ObjectAttachToNamed(void *vfsRoot, const char *path, void *child) { char ppath[AG_PATHNAME_MAX]; void *parent; char *p; if (Strlcpy(ppath, path, sizeof(ppath)) >= sizeof(ppath)) { AG_SetError(_("Path name overflow")); return (-1); } if ((p = strrchr(ppath, '/')) != NULL) { *p = '\0'; } else { AG_SetError(_("Not an absolute path: %s"), path); return (-1); } AG_LockVFS(vfsRoot); if (ppath[0] == '\0') { AG_ObjectAttach(vfsRoot, child); } else { if ((parent = AG_ObjectFind(vfsRoot, ppath)) == NULL) { AG_SetError(_("%s: Cannot attach to %s (%s)"), OBJECT(child)->name, ppath, AG_GetError()); AG_UnlockVFS(vfsRoot); return (-1); } AG_ObjectAttach(parent, child); } AG_UnlockVFS(vfsRoot); return (0); } /* Detach a child object from its parent. */ void AG_ObjectDetach(void *childp) { AG_Object *child = childp; #ifdef AG_THREADS AG_Object *root = child->root; #endif AG_Object *parent = child->parent; #ifdef AG_THREADS AG_LockVFS(root); #endif AG_ObjectLock(parent); AG_ObjectLock(child); AG_ObjectCancelTimeouts(child, AG_CANCEL_ONDETACH); TAILQ_REMOVE(&parent->children, child, cobjs); child->parent = NULL; child->root = child; AG_PostEvent(parent, child, "detached", NULL); AG_PostEvent(child, parent, "child-detached", NULL); #ifdef AG_OBJDEBUG Debug(parent, "Detached child object %s\n", child->name); Debug(child, "Detached from parent %s\n", parent->name); #endif AG_ObjectUnlock(child); AG_ObjectUnlock(parent); #ifdef AG_THREADS AG_UnlockVFS(root); #endif } void AG_ObjectDelete(void *p) { AG_Object *obj = p; AG_Object *root = obj->root; if (root != NULL) { AG_ObjectLock(root); } if (obj->parent != NULL) { AG_ObjectDetach(obj); } AG_ObjectDestroy(obj); if (root != NULL) { AG_ObjectUnlock(root); } } /* Traverse the object tree using a pathname. */ static void * FindObjectByName(const AG_Object *parent, const char *name) { char node_name[AG_OBJECT_PATH_MAX]; void *rv; char *s; AG_Object *child; Strlcpy(node_name, name, sizeof(node_name)); if ((s = strchr(node_name, '/')) != NULL) { *s = '\0'; } TAILQ_FOREACH(child, &parent->children, cobjs) { if (strcmp(child->name, node_name) != 0) continue; if ((s = strchr(name, '/')) != NULL) { rv = FindObjectByName(child, &s[1]); if (rv != NULL) { return (rv); } else { return (NULL); } } return (child); } return (NULL); } /* * Search for the named object (absolute path). Return value is only valid * as long as VFS is locked. */ void * AG_ObjectFind(void *vfsRoot, const char *name) { void *rv; #ifdef AG_DEBUG if (name[0] != '/') AG_FatalError("AG_ObjectFind: Not an absolute path: %s", name); #endif if (name[0] == '/' && name[1] == '\0') return (vfsRoot); AG_LockVFS(vfsRoot); rv = FindObjectByName(vfsRoot, &name[1]); AG_UnlockVFS(vfsRoot); if (rv == NULL) { AG_SetError(_("The object `%s' does not exist."), name); } return (rv); } /* * Search for the named object (absolute path), using format string parameter. * Return value is only valid as long as VFS is locked. */ void * AG_ObjectFindF(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_FatalError("AG_ObjectFindF: Not an absolute path: %s", path); #endif AG_LockVFS(vfsRoot); rv = FindObjectByName(vfsRoot, &path[1]); AG_UnlockVFS(vfsRoot); if (rv == NULL) { AG_SetError(_("The object `%s' does not exist."), path); } return (rv); } /* * Traverse an object's ancestry looking for parent object of the given class. * THREADS: Result valid as long as Object's VFS remains 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); } /* Clear the dependency table. */ void AG_ObjectFreeDeps(AG_Object *ob) { AG_ObjectDep *dep, *ndep; AG_ObjectLock(ob); for (dep = TAILQ_FIRST(&ob->deps); dep != TAILQ_END(&ob->deps); dep = ndep) { ndep = TAILQ_NEXT(dep, deps); Free(dep); } TAILQ_INIT(&ob->deps); AG_ObjectUnlock(ob); } /* * Remove any entry in the object's dependency table with a reference count * of zero. Also applies to the object's children. */ void AG_ObjectFreeDummyDeps(AG_Object *ob) { AG_Object *cob; AG_ObjectDep *dep, *ndep; AG_ObjectLock(ob); for (dep = TAILQ_FIRST(&ob->deps); dep != TAILQ_END(&ob->deps); dep = ndep) { ndep = TAILQ_NEXT(dep, deps); if (dep->count == 0) { TAILQ_REMOVE(&ob->deps, dep, deps); Free(dep); } } TAILQ_FOREACH(cob, &ob->children, cobjs) { AG_ObjectFreeDummyDeps(cob); } AG_ObjectUnlock(ob); } static void FreeChildObject(AG_Object *obj) { AG_Object *cob, *ncob; #ifdef AG_OBJDEBUG Debug(obj, "Freeing children\n"); #endif AG_ObjectLock(obj); for (cob = TAILQ_FIRST(&obj->children); cob != TAILQ_END(&obj->children); cob = ncob) { ncob = TAILQ_NEXT(cob, cobjs); FreeChildObject(cob); } AG_ObjectUnlock(obj); AG_ObjectDetach(obj); AG_ObjectDestroy(obj); } /* * Detach and free all child objects under the specified object. None of * the child objects must currently be in use. */ void AG_ObjectFreeChildren(void *p) { AG_Object *pob = p; AG_Object *cob, *ncob; AG_ObjectLock(pob); for (cob = TAILQ_FIRST(&pob->children); cob != TAILQ_END(&pob->children); cob = ncob) { ncob = TAILQ_NEXT(cob, cobjs); FreeChildObject(cob); } TAILQ_INIT(&pob->children); AG_ObjectUnlock(pob); } /* Clear an object's variable list. */ void AG_ObjectFreeVariables(void *pObj) { AG_Object *ob = pObj; Uint i; for (i = 0; i < ob->nVars; i++) { AG_FreeVariable(&ob->vars[i]); } Free(ob->vars); ob->vars = NULL; ob->nVars = 0; } /* * Destroy the event handlers registered by an object, cancelling * any scheduled execution. */ void AG_ObjectFreeEvents(AG_Object *ob) { AG_Event *eev, *neev; AG_ObjectLock(ob); for (eev = TAILQ_FIRST(&ob->events); eev != TAILQ_END(&ob->events); eev = neev) { neev = TAILQ_NEXT(eev, events); if (eev->flags & AG_EVENT_SCHEDULED) { AG_CancelEvent(ob, eev->name); } Free(eev); } TAILQ_INIT(&ob->events); AG_ObjectUnlock(ob); } /* Cancel any scheduled timeout event associated with the object. */ void AG_ObjectCancelTimeouts(void *p, Uint flags) { AG_Object *ob = p, *tob; extern struct ag_objectq agTimeoutObjQ; AG_Event *ev; AG_LockTiming(); AG_ObjectLock(ob); TAILQ_FOREACH(ev, &ob->events, events) { if ((ev->flags & AG_EVENT_SCHEDULED) && (ev->timeout.flags & flags)) { #ifdef AG_OBJDEBUG Debug(ob, "Cancelling scheduled event <%s>\n", ev->name); #endif AG_DelTimeout(ob, &ev->timeout); ev->flags &= ~(AG_EVENT_SCHEDULED); } } TAILQ_FOREACH(tob, &agTimeoutObjQ, tobjs) { if (tob == ob) TAILQ_REMOVE(&agTimeoutObjQ, ob, tobjs); } TAILQ_INIT(&ob->timeouts); AG_ObjectUnlock(ob); AG_UnlockTiming(); } /* * Release the resources allocated by an object and its children, assuming * that none of them are currently in use. */ void AG_ObjectDestroy(void *p) { AG_Object *ob = p; AG_ObjectClass **hier; int i, nHier; #ifdef AG_OBJDEBUG if (ob->parent != NULL) { AG_FatalError("AG_ObjectDestroy: %s still attached to %p", ob->name, ob->parent); } Debug(ob, "Destroying\n"); #endif AG_ObjectCancelTimeouts(ob, 0); AG_ObjectFreeChildren(ob); AG_ObjectFreeDataset(ob); AG_ObjectFreeDeps(ob); if (AG_ObjectGetInheritHier(ob, &hier, &nHier) == 0) { for (i = nHier-1; i >= 0; i--) { if (hier[i]->destroy != NULL) hier[i]->destroy(ob); } Free(hier); } else { AG_FatalError("%s: %s", ob->name, AG_GetError()); } AG_ObjectFreeVariables(ob); AG_ObjectFreeEvents(ob); AG_MutexDestroy(&ob->lock); Free(ob->archivePath); if ((ob->flags & AG_OBJECT_STATIC) == 0) Free(ob); } /* * Copy the full pathname to an object's data file to a fixed-size buffer. * The path is only valid as long as the VFS is locked. */ /* NOTE: Will return data into path even upon failure. */ int AG_ObjectCopyFilename(void *p, char *path, size_t path_len) { char load_path[AG_PATHNAME_MAX], *loadpathp = &load_path[0]; char obj_name[AG_OBJECT_PATH_MAX]; AG_Object *ob = p; char *dir; AG_ObjectLock(ob); if (ob->archivePath != NULL) { Strlcpy(path, ob->archivePath, path_len); goto out; } AG_GetString(agConfig, "load-path", load_path, sizeof(load_path)); AG_ObjectCopyName(ob, obj_name, sizeof(obj_name)); for (dir = Strsep(&loadpathp, ":"); dir != NULL; dir = Strsep(&loadpathp, ":")) { Strlcpy(path, dir, path_len); if (ob->save_pfx != NULL) { Strlcat(path, ob->save_pfx, path_len); } Strlcat(path, obj_name, path_len); Strlcat(path, AG_PATHSEP, path_len); Strlcat(path, ob->name, path_len); Strlcat(path, ".", path_len); Strlcat(path, ob->cls->name, path_len); if (AG_FileExists(path)) goto out; } AG_SetError(_("The %s%s%c%s.%s file is not in load-path."), ob->save_pfx != NULL ? ob->save_pfx : "", obj_name, AG_PATHSEPCHAR, ob->name, ob->cls->name); 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, size_t path_len) { char load_path[AG_PATHNAME_MAX], *loadpathp = &load_path[0]; char obj_name[AG_OBJECT_PATH_MAX]; AG_Object *ob = p; char *dir; AG_ObjectLock(ob); AG_GetString(agConfig, "load-path", load_path, sizeof(load_path)); AG_ObjectCopyName(ob, obj_name, sizeof(obj_name)); for (dir = Strsep(&loadpathp, ":"); dir != NULL; dir = Strsep(&loadpathp, ":")) { char tmp_path[AG_PATHNAME_MAX]; Strlcpy(tmp_path, dir, sizeof(tmp_path)); if (ob->save_pfx != NULL) { Strlcat(tmp_path, ob->save_pfx, sizeof(tmp_path)); } Strlcat(tmp_path, obj_name, sizeof(tmp_path)); if (AG_FileExists(tmp_path)) { Strlcpy(path, tmp_path, path_len); goto out; } } AG_SetError(_("The %s directory is not in load-path."), obj_name); AG_ObjectUnlock(ob); return (-1); out: AG_ObjectUnlock(ob); return (0); } /* * Load an object's data into memory and mark it resident. If the object * is either marked non-persistent or no data can be found in storage, * just mark the object resident. */ int AG_ObjectPageIn(void *p) { AG_Object *ob = p; int dataFound; AG_ObjectLock(ob); if (!OBJECT_PERSISTENT(ob)) { ob->flags |= AG_OBJECT_RESIDENT; goto out; } if (!OBJECT_RESIDENT(ob)) { if (AG_ObjectLoadData(ob, &dataFound) == -1) { if (dataFound == 0) { /* * Data not found in storage, just assume * the object has never been saved before. */ ob->flags |= AG_OBJECT_RESIDENT; goto out; } else { goto fail; } } } out: AG_ObjectUnlock(ob); return (0); fail: AG_ObjectUnlock(ob); return (-1); } /* * If the given object is persistent and no longer referenced, save * its data and free it from memory. The object must be resident. */ int AG_ObjectPageOut(void *p) { AG_Object *ob = p; AG_ObjectLock(ob); if (!OBJECT_PERSISTENT(ob)) { goto out; } if (!OBJECT_RESIDENT(ob)) { AG_SetError(_("Object is non-resident")); goto fail; } if (!AG_ObjectInUse(ob)) { if (AG_FindEventHandler(ob->root, "object-page-out") != NULL) { AG_PostEvent(ob, ob->root, "object-page-out", NULL); } else { if (AG_ObjectSave(ob) == -1) goto fail; } if ((ob->flags & AG_OBJECT_REMAIN_DATA) == 0) AG_ObjectFreeDataset(ob); } out: AG_ObjectUnlock(ob); return (0); fail: AG_ObjectUnlock(ob); return (-1); } /* 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_ObjectResolveDeps(ob) == -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); } /* * Resolve the encoded dependencies of an object and its children. * The object's VFS must be locked. */ int AG_ObjectResolveDeps(void *p) { AG_Object *ob = p, *cob; AG_ObjectDep *dep; AG_ObjectLock(ob); TAILQ_FOREACH(dep, &ob->deps, deps) { #ifdef AG_OBJDEBUG Debug(ob, "Resolving dependency: %s\n", dep->path); #endif if (dep->obj != NULL) { continue; } if ((dep->obj = AG_ObjectFind(ob->root, dep->path)) == NULL) { AG_SetError(_("%s: Cannot resolve dependency `%s'"), ob->name, dep->path); goto fail; } #ifdef AG_OBJDEBUG Debug(ob, "Dependency resolves to %p (%s)\n", dep->obj, dep->obj->name); #endif Free(dep->path); dep->path = NULL; } TAILQ_FOREACH(cob, &ob->children, cobjs) { AG_ObjectLock(cob); if (!OBJECT_PERSISTENT(cob)) { AG_ObjectUnlock(cob); continue; } if (AG_ObjectResolveDeps(cob) == -1) { AG_ObjectUnlock(cob); goto fail; } AG_ObjectUnlock(cob); } AG_ObjectUnlock(ob); return (0); fail: AG_ObjectUnlock(ob); return (-1); } /* 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)); AG_CopyString(cs->libs, ds, sizeof(cs->libs)); Strlcpy(cs->spec, cs->hier, sizeof(cs->spec)); if (cs->libs[0] != '\0') { Strlcat(cs->spec, "@", sizeof(cs->spec)); Strlcat(cs->spec, cs->libs, sizeof(cs->spec)); } 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.libs[0] = '\0'; oh->cs.spec[0] = '\0'; oh->cs.name[0] = '\0'; } /* Dataset start offset */ oh->dataOffs = AG_ReadUint32(ds); /* Object flags */ oh->flags = (Uint)AG_ReadUint32(ds); return (0); } /* Load an object dependency table. */ static int ReadDependencyTable(AG_DataSource *ds, AG_Object *ob) { Uint32 i, count; AG_ObjectDep *dep; count = AG_ReadUint32(ds); for (i = 0; i < count; i++) { if ((dep = malloc(sizeof(AG_ObjectDep))) == NULL) { AG_SetError("Out of memory for dependency table"); goto fail; } if ((dep->path = AG_ReadString(ds)) == NULL) { free(dep); goto fail; } dep->obj = NULL; dep->count = 0; dep->persistent = 1; TAILQ_INSERT_TAIL(&ob->deps, dep, deps); #ifdef AG_OBJDEBUG Debug(ob, "Dependency: %s\n", dep->path); #endif } return (0); fail: AG_ObjectFreeDeps(ob); return (-1); } /* Return the default datafile path for the given object. */ static int GetDatafile(char *path, AG_Object *ob) { if (ob->archivePath != NULL) { Strlcpy(path, ob->archivePath, AG_PATHNAME_MAX); } else { if (AG_ObjectCopyFilename(ob, path, AG_PATHNAME_MAX) == -1) return (-1); } return (0); } /* Load Object variables. */ int AG_ObjectLoadVariables(void *p, AG_DataSource *ds) { AG_Object *ob = p; Uint32 i, count; Uint j; if (AG_ReadVersion(ds, "AG_PropTbl", &agPropTblVer, NULL) == -1) return (-1); AG_ObjectLock(ob); if (ob->flags & AG_OBJECT_FLOATING_VARS) AG_ObjectFreeVariables(ob); count = AG_ReadUint32(ds); for (i = 0; i < count; i++) { char key[64]; Uint32 code; if (AG_CopyString(key, ds, sizeof(key)) >= sizeof(key)) { AG_SetError("Variable name too long: %s", key); goto fail; } code = AG_ReadUint32(ds); for (j = 0; j < AG_VARIABLE_TYPE_LAST; j++) { if (agVariableTypes[j].code == code) break; } if (j == AG_VARIABLE_TYPE_LAST) { AG_SetError("Unknown variable code: %u", (Uint)code); goto fail; } switch (agVariableTypes[j].typeTgt) { 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_UINT8: AG_SetBool(ob, key, AG_ReadUint8(ds)); break; case AG_VARIABLE_SINT8: AG_SetBool(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; case AG_VARIABLE_UINT32: AG_SetUint32(ob, key, AG_ReadUint32(ds)); break; case AG_VARIABLE_SINT32: AG_SetSint32(ob, key, AG_ReadSint32(ds)); break; case AG_VARIABLE_FLOAT: AG_SetFloat(ob, key, AG_ReadFloat(ds)); break; case AG_VARIABLE_DOUBLE: AG_SetDouble(ob, key, AG_ReadDouble(ds)); break; case AG_VARIABLE_STRING: AG_SetStringNODUP(ob, key, AG_ReadString(ds)); break; default: AG_SetError("Attempt to load variable of type %s", agVariableTypes[j].name); goto fail; } } AG_ObjectUnlock(ob); return (0); fail: AG_ObjectUnlock(ob); return (-1); } /* Save Object variables. */ void AG_ObjectSaveVariables(void *p, AG_DataSource *ds) { AG_Object *ob = p; off_t countOffs; Uint32 count = 0; Uint i; AG_WriteVersion(ds, "AG_PropTbl", &agPropTblVer); countOffs = AG_Tell(ds); AG_WriteUint32(ds, 0); AG_ObjectLock(ob); for (i = 0; i < ob->nVars; i++) { AG_Variable *V = &ob->vars[i]; const AG_VariableTypeInfo *Vt = &agVariableTypes[V->type]; if (Vt->code == -1) continue; /* Nonpersistent */ AG_LockVariable(V); if (V->fn.fnVoid != NULL && AG_EvalVariable(ob, V) == -1) { Debug(ob, "Failed to eval %s (%s); excluding from archive", V->name, AG_GetError()); AG_UnlockVariable(V); continue; } AG_WriteString(ds, (char *)V->name); AG_WriteUint32(ds, Vt->code); switch (AG_VARIABLE_TYPE(V)) { case AG_VARIABLE_UINT: AG_WriteUint32(ds, (Uint32)V->data.u); break; case AG_VARIABLE_INT: AG_WriteSint32(ds, (Sint32)V->data.i); break; case AG_VARIABLE_UINT8: AG_WriteUint8(ds, V->data.u8); break; case AG_VARIABLE_SINT8: AG_WriteSint8(ds, V->data.s8); break; case AG_VARIABLE_UINT16: AG_WriteUint16(ds, V->data.u16); break; case AG_VARIABLE_SINT16: AG_WriteSint16(ds, V->data.s16); break; case AG_VARIABLE_UINT32: AG_WriteUint32(ds, V->data.u32); break; case AG_VARIABLE_SINT32: AG_WriteSint32(ds, V->data.s32); break; case AG_VARIABLE_FLOAT: AG_WriteFloat(ds, V->data.flt); break; case AG_VARIABLE_DOUBLE: AG_WriteDouble(ds, V->data.dbl); break; case AG_VARIABLE_STRING: AG_WriteString(ds, V->data.s); break; default: break; } AG_UnlockVariable(V); count++; } 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 Object information is read, datasets are skipped and * dependencies are left unresolved. */ int AG_ObjectLoadGenericFromFile(void *p, const char *pPath) { AG_Object *ob = p; AG_ObjectHeader oh; char path[AG_PATHNAME_MAX]; AG_DataSource *ds; Uint32 count, i; if (!OBJECT_PERSISTENT(ob)) { AG_SetError(_("Object is non-persistent")); return (-1); } AG_LockVFS(ob); AG_ObjectLock(ob); AG_ObjectCancelTimeouts(ob, AG_CANCEL_ONLOAD); if (pPath != NULL) { Strlcpy(path, pPath, sizeof(path)); } else { if (GetDatafile(path, ob) == -1) goto fail_unlock; } #ifdef AG_OBJDEBUG Debug(ob, "Loading generic data from %s\n", path); #endif if ((ds = AG_OpenFile(path, "rb")) == NULL) { AG_SetError("%s: %s", path, AG_GetError()); goto fail_unlock; } /* Free any resident dataset in order to clear the dependencies. */ if (OBJECT_RESIDENT(ob)) { ob->flags |= AG_OBJECT_WAS_RESIDENT; AG_ObjectFreeDataset(ob); } AG_ObjectFreeDeps(ob); /* Object header */ if (AG_ObjectReadHeader(ds, &oh) == -1) { goto fail; } ob->flags &= ~(AG_OBJECT_SAVED_FLAGS); ob->flags |= oh.flags; /* Dependencies, properties */ if (ReadDependencyTable(ds, ob) == -1) goto fail; 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 *cl; /* 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) { AG_SetError("Archived object `%s' clashes with " "existing object of different type", cname); goto fail; } if (!OBJECT_PERSISTENT(chld)) { AG_SetError("Archived object `%s' clashes with " "existing non-persistent object", cname); goto fail; } if (AG_ObjectLoadGeneric(chld) == -1) { goto fail; } continue; } /* Create a new child object. */ if ((cl = AG_LoadClass(hier)) == NULL) { AG_SetError("%s: %s", ob->name, AG_GetError()); if (agObjectIgnoreUnknownObjs) { #ifdef AG_OBJDEBUG Debug(ob, "%s; ignoring\n", AG_GetError()); #endif continue; } else { goto fail; } goto fail; } chld = Malloc(cl->size); AG_ObjectInit(chld, cl); AG_ObjectSetName(chld, "%s", 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_ObjectFreeDataset(ob); AG_ObjectFreeDeps(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); if (!OBJECT_PERSISTENT(ob)) { goto out; } AG_ObjectCancelTimeouts(ob, AG_CANCEL_ONLOAD); *dataFound = 1; /* Open the file. */ if (pPath != NULL) { Strlcpy(path, pPath, sizeof(path)); } else { if (GetDatafile(path, ob) == -1) { *dataFound = 0; goto fail_unlock; } } #ifdef AG_OBJDEBUG Debug(ob, "Loading dataset from %s\n", path); #endif if ((ds = AG_OpenFile(path, "rb")) == NULL) { AG_SetError("%s: %s", path, AG_GetError()); *dataFound = 0; goto fail_unlock; } /* Seek to the start of the dataset. */ 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) { AG_SetSourceDebug(ds, 1); } if (AG_ObjectGetInheritHier(ob, &hier, &nHier) == -1) { goto fail; } if (OBJECT_RESIDENT(ob)) { AG_ObjectFreeDataset(ob); } for (i = 0; i < nHier; i++) { #ifdef AG_CLASSDEBUG Debug(ob, "Loading as %s\n", hier[i]->name); #endif if (hier[i]->load == NULL) continue; if (hier[i]->load(ob, ds, &ver) == -1) { Free(hier); goto fail; } } Free(hier); ob->flags |= AG_OBJECT_RESIDENT; AG_CloseFile(ds); AG_PostEvent(ob, ob->root, "object-post-load-data", "%s", path); out: AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (0); fail: AG_CloseFile(ds); fail_unlock: AG_UnlockVFS(ob); return (-1); } static void BackupObjectFile(void *p, const char *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 (!OBJECT_PERSISTENT(cobj)) { AG_ObjectUnlock(cobj); continue; } 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; off_t countOffs, dataOffs; Uint32 count; AG_Object *chld; AG_ObjectDep *dep; AG_ObjectClass **hier; int i, nHier; AG_ObjectLock(ob); /* Header */ AG_WriteVersion(ds, agObjectClass.name, &agObjectClass.ver); AG_WriteString(ds, ob->cls->hier); AG_WriteString(ds, ob->cls->libs); dataOffs = AG_Tell(ds); AG_WriteUint32(ds, 0); /* Data offs */ AG_WriteUint32(ds, (Uint32)(ob->flags & AG_OBJECT_SAVED_FLAGS)); /* Dependency and property tables */ countOffs = AG_Tell(ds); AG_WriteUint32(ds, 0); for (dep = TAILQ_FIRST(&ob->deps), count = 0; dep != TAILQ_END(&ob->deps); dep = TAILQ_NEXT(dep, deps)) { char depName[AG_OBJECT_PATH_MAX]; if (!dep->persistent) { continue; } AG_ObjectCopyName(dep->obj, depName, sizeof(depName)); AG_WriteString(ds, depName); count++; } AG_WriteUint32At(ds, count, countOffs); AG_ObjectSaveVariables(ob, ds); /* Table of child objects */ if (ob->flags & AG_OBJECT_CHLD_AUTOSAVE) { countOffs = AG_Tell(ds); AG_WriteUint32(ds, 0); count = 0; TAILQ_FOREACH(chld, &ob->children, cobjs) { if (!OBJECT_PERSISTENT(chld)) { continue; } AG_WriteString(ds, chld->name); AG_WriteString(ds, chld->cls->hier); count++; } AG_WriteUint32At(ds, count, countOffs); } else { AG_WriteUint32(ds, 0); } /* Dataset */ AG_WriteUint32At(ds, AG_Tell(ds), dataOffs); AG_WriteVersion(ds, ob->cls->name, &ob->cls->ver); if (ob->flags & AG_OBJECT_DEBUG_DATA) { AG_SetSourceDebug(ds, 1); } if (AG_ObjectGetInheritHier(ob, &hier, &nHier) == -1) { goto fail; } for (i = 0; i < nHier; i++) { #ifdef AG_CLASSDEBUG 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); if (ob->flags & AG_OBJECT_DEBUG_DATA) { AG_SetSourceDebug(ds, 0); } AG_ObjectUnlock(ob); return (0); fail: if (ob->flags & AG_OBJECT_DEBUG_DATA) { AG_SetSourceDebug(ds, 0); } 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; int i, nHier; AG_ObjectLock(ob); AG_ObjectCancelTimeouts(ob, AG_CANCEL_ONLOAD); /* Object header */ if (AG_ObjectReadHeader(ds, &oh) == -1) { goto fail; } ob->flags &= ~(AG_OBJECT_SAVED_FLAGS); ob->flags |= oh.flags; /* Dependencies, properties */ if (ReadDependencyTable(ds, ob) == -1) goto fail; if (AG_ObjectLoadVariables(ob, ds) == -1) goto fail; /* Table of child objects, expected empty. */ if (AG_ReadUint32(ds) != 0) { AG_SetError("Archived object has children"); goto fail; } /* Dataset */ if (AG_ReadVersion(ds, ob->cls->name, &ob->cls->ver, &ver) == -1) goto fail; if (ob->flags & AG_OBJECT_DEBUG_DATA) { AG_SetSourceDebug(ds, 1); } if (AG_ObjectGetInheritHier(ob, &hier, &nHier) == -1) { goto fail; } for (i = 0; i < nHier; i++) { #ifdef AG_CLASSDEBUG Debug(ob, "Loading as %s\n", hier[i]->name); #endif if (hier[i]->load == NULL) continue; if (hier[i]->load(ob, ds, &ver) == -1) { Free(hier); goto fail; } } Free(hier); ob->flags |= AG_OBJECT_RESIDENT; if (ob->flags & AG_OBJECT_DEBUG_DATA) { AG_SetSourceDebug(ds, 0); } AG_ObjectUnlock(ob); return (0); fail: if (ob->flags & AG_OBJECT_DEBUG_DATA) { AG_SetSourceDebug(ds, 0); } AG_ObjectFreeDataset(ob); AG_ObjectFreeDeps(ob); AG_ObjectUnlock(ob); return (-1); } /* Archive an object to a file. */ int AG_ObjectSaveToFile(void *p, const char *pPath) { char pathDir[AG_PATHNAME_MAX]; char path[AG_PATHNAME_MAX]; char name[AG_OBJECT_PATH_MAX]; AG_Object *ob = p; AG_DataSource *ds; int pagedTemporarily; int dataFound; AG_LockVFS(ob); AG_ObjectLock(ob); if (!OBJECT_PERSISTENT(ob)) { AG_SetError(_("The `%s' object is non-persistent."), ob->name); goto fail_unlock; } AG_ObjectCopyName(ob, name, sizeof(name)); if (pPath != NULL) { Strlcpy(path, pPath, sizeof(path)); } else if (ob->archivePath == NULL) { /* Create the save directory if needed. */ AG_GetString(agConfig, "save-path", pathDir, sizeof(pathDir)); if (ob->save_pfx != NULL) { Strlcat(pathDir, ob->save_pfx, sizeof(pathDir)); } Strlcat(pathDir, name, sizeof(pathDir)); if (AG_FileExists(pathDir) == 0 && AG_MkPath(pathDir) == -1) goto fail_unlock; } /* * If we are trying to save a non-resident object, page it in * temporarily for the duration of the operation. * * XXX TODO Allow partial generic and data modifications instead. */ if (!OBJECT_RESIDENT(ob)) { if (AG_ObjectLoadData(ob, &dataFound) == -1) { if (!dataFound) { /* * Data has not been found, just assume * that this object has never been saved * before and mark it resident. */ ob->flags |= AG_OBJECT_RESIDENT; } else { AG_FatalError("LoadData failed: %s", AG_GetError()); AG_SetError("Failed to load non-resident " "object for save: %s", AG_GetError()); goto fail_unlock; } } pagedTemporarily = 1; } else { pagedTemporarily = 0; } if (pPath == NULL) { if (ob->archivePath != NULL) { Strlcpy(path, ob->archivePath, sizeof(path)); } else { Strlcpy(path, pathDir, 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 AG_OBJDEBUG Debug(ob, "Saving object to %s\n", path); #endif if (agObjectBackups) { BackupObjectFile(ob, path); } else { AG_FileDelete(path); } if ((ds = AG_OpenFile(path, "wb")) == NULL) { goto fail_free; } if (AG_ObjectSerialize(ob, ds) == -1) { goto fail; } AG_CloseFile(ds); if (pagedTemporarily) { AG_ObjectFreeDataset(ob); } AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (0); fail: AG_CloseFile(ds); fail_free: if (pagedTemporarily) { AG_ObjectFreeDataset(ob); } fail_unlock: AG_ObjectUnlock(ob); AG_UnlockVFS(ob); return (-1); } /* * Change the name of an object. * 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); } /* Specify a function to invoke in order to process Debug() messages. */ void AG_ObjectSetDebugFn(void *pObj, void (*fn)(void *, void *, const char *), void *pUser) { #ifdef DEBUG AG_Object *obj = pObj; AG_ObjectLock(obj); obj->debugFn = fn; obj->debugPtr = pUser; AG_ObjectUnlock(obj); #endif } /* * Return the archive path of an object into a fixed buffer. Returned path * is valid as long as the object is locked. */ void AG_ObjectGetArchivePath(void *p, char *buf, size_t buf_len) { AG_Object *ob = p; AG_ObjectLock(ob); Strlcpy(buf, ob->archivePath, buf_len); AG_ObjectUnlock(ob); } /* Set the default path to use with ObjectLoad() and ObjectSave(). */ void AG_ObjectSetArchivePath(void *p, const char *path) { AG_Object *ob = p; AG_ObjectLock(ob); Free(ob->archivePath); ob->archivePath = Strdup(path); AG_ObjectUnlock(ob); } /* Change an object's class. This is thread unsafe and should not be used. */ void AG_ObjectSetClass(void *p, void *cl) { OBJECT(p)->cls = cl; } /* Add a new dependency or increment the reference count on one. */ AG_ObjectDep * AG_ObjectAddDep(void *p, void *depobj, int persistent) { AG_Object *ob = p; AG_ObjectDep *dep; AG_ObjectLock(ob); AG_ObjectLock(depobj); TAILQ_FOREACH(dep, &ob->deps, deps) { if (dep->obj == depobj) break; } if (dep != NULL) { if (++dep->count > AG_OBJECT_DEP_MAX) dep->count = AG_OBJECT_DEP_MAX; } else { dep = Malloc(sizeof(AG_ObjectDep)); dep->obj = depobj; dep->count = 1; dep->persistent = persistent; TAILQ_INSERT_TAIL(&ob->deps, dep, deps); } AG_ObjectUnlock(depobj); AG_ObjectUnlock(ob); return (dep); } /* Resolve a given dependency. */ int AG_ObjectFindDep(void *p, Uint32 ind, void **objp) { AG_Object *ob = p; AG_ObjectDep *dep; Uint32 i; if (ind == 0) { *objp = NULL; return (0); } else if (ind == 1) { *objp = (void *)ob; return (0); } AG_ObjectLock(ob); for (dep = TAILQ_FIRST(&ob->deps), i = 2; dep != TAILQ_END(&ob->deps); dep = TAILQ_NEXT(dep, deps), i++) { if (i == ind) break; } if (dep == NULL) { AG_SetError(_("Unable to resolve dependency %s:%u."), ob->name, (Uint)ind); AG_ObjectUnlock(ob); return (-1); } *objp = dep->obj; AG_ObjectUnlock(ob); return (0); } /* * Encode an object dependency. The values 0 and 1 are reserved, 0 is a * NULL value and 1 is the parent object itself. */ Uint32 AG_ObjectEncodeName(void *p, const void *depobjp) { AG_Object *ob = p; const AG_Object *depobj = depobjp; AG_ObjectDep *dep; Uint32 i; if (depobjp == NULL) { return (0); } else if (p == depobjp) { return (1); } AG_ObjectLock(ob); for (dep = TAILQ_FIRST(&ob->deps), i = 2; dep != TAILQ_END(&ob->deps); dep = TAILQ_NEXT(dep, deps), i++) { if (dep->obj == depobj) { AG_ObjectUnlock(ob); return (i); } } AG_FatalError("AG_ObjectEncodeName: %s: No such dep", depobj->name); AG_ObjectUnlock(ob); return (0); } /* * Decrement the reference count on a dependency and remove it if the * reference count reaches 0. */ void AG_ObjectDelDep(void *p, const void *depobj) { AG_Object *ob = p; AG_ObjectDep *dep; AG_ObjectLock(ob); TAILQ_FOREACH(dep, &ob->deps, deps) { if (dep->obj == depobj) break; } if (dep == NULL) { goto out; } if (dep->count == AG_OBJECT_DEP_MAX) { /* Wired */ goto out; } if ((dep->count-1) == 0) { if ((ob->flags & AG_OBJECT_PRESERVE_DEPS) == 0) { TAILQ_REMOVE(&ob->deps, dep, deps); Free(dep); } else { dep->count = 0; } } else if (dep->count == 0) { AG_FatalError("AG_ObjectDelDep: Negative refcount"); } else { dep->count--; } out: AG_ObjectUnlock(ob); } /* 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); } /* Change the save prefix of an object and its children. */ void AG_ObjectSetSavePfx(void *p, char *path) { AG_Object *ob = p, *cob; AG_LockVFS(ob); AG_ObjectLock(ob); ob->save_pfx = path; TAILQ_FOREACH(cob, &ob->children, cobjs) { AG_ObjectSetSavePfx(cob, path); } AG_ObjectUnlock(ob); AG_UnlockVFS(ob); } /* * 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); } /* Duplicate an object and its children. */ /* XXX EXPERIMENTAL */ void * AG_ObjectDuplicate(void *p, const char *newName) { char nameSave[AG_OBJECT_NAME_MAX]; AG_Object *ob = p; AG_ObjectClass *cl = ob->cls; AG_Object *dob; dob = Malloc(cl->size); AG_ObjectLock(ob); AG_ObjectInit(dob, cl); AG_ObjectSetName(dob, "%s", newName); if (AG_ObjectPageIn(ob) == -1) { goto fail; } /* Change the name and attach to the same parent as the original. */ AG_ObjectAttach(ob->parent, dob); dob->flags = (ob->flags & AG_OBJECT_DUPED_FLAGS); /* Save the state of the original object using the new name. */ /* XXX Save to temp location!! */ Strlcpy(nameSave, ob->name, sizeof(nameSave)); Strlcpy(ob->name, dob->name, sizeof(ob->name)); if (AG_ObjectSave(ob) == -1) { AG_ObjectPageOut(ob); goto fail; } if (AG_ObjectPageOut(ob) == -1) { goto fail; } if (AG_ObjectLoad(dob) == -1) { goto fail; } Strlcpy(ob->name, nameSave, sizeof(ob->name)); AG_ObjectUnlock(ob); return (dob); fail: Strlcpy(ob->name, nameSave, sizeof(ob->name)); AG_ObjectUnlock(ob); AG_ObjectDestroy(dob); return (NULL); } /* * Return a cryptographic digest of an object's most recent archive. The * digest is accurate as long as the object is locked. */ size_t AG_ObjectCopyChecksum(void *p, enum ag_object_checksum_alg alg, char *digest) { AG_Object *ob = p; char path[AG_PATHNAME_MAX]; Uchar buf[AG_BUFFER_MAX]; FILE *f; size_t totlen = 0; size_t rv; AG_ObjectLock(ob); if (AG_ObjectCopyFilename(ob, path, sizeof(path)) == -1) { goto fail; } /* TODO locking */ if ((f = fopen(path, "r")) == NULL) { AG_SetError("Unable to open %s", path); goto fail; } switch (alg) { case AG_OBJECT_MD5: { AG_MD5_CTX ctx; AG_MD5Init(&ctx); while ((rv = fread(buf, 1, sizeof(buf), f)) > 0) { AG_MD5Update(&ctx, buf, (Uint)rv); totlen += rv; } AG_MD5End(&ctx, digest); } break; case AG_OBJECT_SHA1: { AG_SHA1_CTX ctx; AG_SHA1Init(&ctx); while ((rv = fread(buf, 1, sizeof(buf), f)) > 0) { AG_SHA1Update(&ctx, buf, (Uint)rv); totlen += rv; } AG_SHA1End(&ctx, digest); } break; case AG_OBJECT_RMD160: { AG_RMD160_CTX ctx; AG_RMD160Init(&ctx); while ((rv = fread(buf, 1, sizeof(buf), f)) > 0) { AG_RMD160Update(&ctx, buf, (Uint)rv); totlen += rv; } AG_RMD160End(&ctx, digest); } break; } fclose(f); AG_ObjectUnlock(ob); return (totlen); fail: AG_ObjectUnlock(ob); return (0); } /* * Return a set of cryptographic digests for an object's most recent archive. * The digests are accurate as long as the object is locked. */ int AG_ObjectCopyDigest(void *ob, size_t *len, char *digest) { char md5[AG_MD5_DIGEST_STRING_LENGTH]; char sha1[AG_SHA1_DIGEST_STRING_LENGTH]; char rmd160[AG_RMD160_DIGEST_STRING_LENGTH]; AG_ObjectLock(ob); if ((*len = AG_ObjectCopyChecksum(ob, AG_OBJECT_MD5, md5)) == 0 || AG_ObjectCopyChecksum(ob, AG_OBJECT_SHA1, sha1) == 0 || AG_ObjectCopyChecksum(ob, AG_OBJECT_RMD160, rmd160) == 0) { goto fail; } if (Snprintf(digest, AG_OBJECT_DIGEST_MAX, "(md5|%s sha1|%s rmd160|%s)", md5, sha1, rmd160) >= AG_OBJECT_DIGEST_MAX) { AG_SetError(_("String overflow")); goto fail; } AG_ObjectUnlock(ob); return (0); fail: AG_ObjectUnlock(ob); return (-1); } /* * 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) { char bufCur[AG_BUFFER_MAX], bufLast[AG_BUFFER_MAX]; char pathCur[AG_PATHNAME_MAX]; char pathLast[AG_PATHNAME_MAX]; AG_Object *ob = p; FILE *fLast, *fCur; size_t rvLast, rvCur; AG_ObjectLock(ob); if (!OBJECT_PERSISTENT(ob) || !OBJECT_RESIDENT(ob)) { AG_ObjectUnlock(ob); return (0); } AG_ObjectCopyFilename(ob, pathLast, sizeof(pathLast)); if ((fLast = fopen(pathLast, "r")) == NULL) { AG_ObjectUnlock(ob); return (1); } AG_GetString(agConfig, "tmp-path", pathCur, sizeof(pathCur)); Strlcat(pathCur, "/_chg.", sizeof(pathCur)); Strlcat(pathCur, ob->name, sizeof(pathCur)); if (AG_ObjectSaveToFile(ob, pathCur) == -1) { fclose(fLast); AG_ObjectUnlock(ob); return (1); } if ((fCur = fopen(pathCur, "r")) == NULL) { fclose(fLast); AG_ObjectUnlock(ob); return (1); } 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)) { goto changed; } if (feof(fLast)) { if (!feof(fCur)) { goto changed; } break; } if (feof(fCur)) { if (!feof(fLast)) { goto changed; } break; } } AG_FileDelete(pathCur); fclose(fCur); fclose(fLast); AG_ObjectUnlock(ob); return (0); changed: AG_FileDelete(pathCur); fclose(fCur); fclose(fLast); AG_ObjectUnlock(ob); return (1); } /* * 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. The class name is used as prefix. */ void AG_ObjectGenName(void *p, AG_ObjectClass *cl, char *name, size_t len) { AG_Object *pobj = p; Uint i = 0; AG_Object *ch; tryname: Snprintf(name, len, "%s #%u", cl->name, i); 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; } } } /* Generate a unique object name using the specified prefix. */ void AG_ObjectGenNamePfx(void *p, const char *pfx, char *name, size_t len) { AG_Object *pobj = p; Uint i = 1; AG_Object *ch; tryname: Snprintf(name, len, "%s%u", pfx, i); 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; } } } #ifdef AG_LOCKDEBUG void AG_ObjectLockDebug(AG_Object *ob, const char *info) { if (agDebugLvl >= 10) { Debug(ob, "Locking (%s)...", info); } AG_MutexLock(&ob->lock); if (agDebugLvl >= 10) { Debug(ob, "OK\n"); } ob->lockinfo = (const char **)AG_Realloc(ob->lockinfo, (ob->nlockinfo+1)*sizeof(char *)); ob->lockinfo[ob->nlockinfo++] = info; } void AG_ObjectUnlockDebug(AG_Object *ob, const char *info) { ob->nlockinfo--; AG_MutexUnlock(&ob->lock); if (agDebugLvl >= 10) { Debug(ob, "Unlocked (%s)\n", info); } } #endif /* AG_LOCKDEBUG */