/*------------------------------------------------------------------------- * * common.c * common routines between pg_dump and pg4_dump * * Since pg4_dump is long-dead code, there is no longer any useful distinction * between this file and pg_dump.c. * * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/bin/pg_dump/common.c * *------------------------------------------------------------------------- */ #include "postgres_fe.h" #include #include "catalog/pg_class.h" #include "pg_backup_archiver.h" /* * Variables for mapping DumpId to DumpableObject */ static DumpableObject **dumpIdMap = NULL; static int allocedDumpIds = 0; static DumpId lastDumpId = 0; /* * Variables for mapping CatalogId to DumpableObject */ static bool catalogIdMapValid = false; static DumpableObject **catalogIdMap = NULL; static int numCatalogIds = 0; /* * These variables are static to avoid the notational cruft of having to pass * them into findTableByOid() and friends. For each of these arrays, we * build a sorted-by-OID index array immediately after it's built, and then * we use binary search in findTableByOid() and friends. (qsort'ing the base * arrays themselves would be simpler, but it doesn't work because pg_dump.c * may have already established pointers between items.) */ static TableInfo *tblinfo; static TypeInfo *typinfo; static FuncInfo *funinfo; static OprInfo *oprinfo; static int numTables; static int numTypes; static int numFuncs; static int numOperators; static int numCollations; static DumpableObject **tblinfoindex; static DumpableObject **typinfoindex; static DumpableObject **funinfoindex; static DumpableObject **oprinfoindex; static DumpableObject **collinfoindex; static void flagInhTables(TableInfo *tbinfo, int numTables, InhInfo *inhinfo, int numInherits); static void flagInhAttrs(TableInfo *tblinfo, int numTables); static DumpableObject **buildIndexArray(void *objArray, int numObjs, Size objSize); static int DOCatalogIdCompare(const void *p1, const void *p2); static void findParentsByOid(TableInfo *self, InhInfo *inhinfo, int numInherits); static int strInArray(const char *pattern, char **arr, int arr_size); /* * getSchemaData * Collect information about all potentially dumpable objects */ TableInfo * getSchemaData(int *numTablesPtr) { NamespaceInfo *nsinfo; ExtensionInfo *extinfo; AggInfo *agginfo; InhInfo *inhinfo; RuleInfo *ruleinfo; ProcLangInfo *proclanginfo; CastInfo *castinfo; OpclassInfo *opcinfo; OpfamilyInfo *opfinfo; CollInfo *collinfo; ConvInfo *convinfo; TSParserInfo *prsinfo; TSTemplateInfo *tmplinfo; TSDictInfo *dictinfo; TSConfigInfo *cfginfo; FdwInfo *fdwinfo; ForeignServerInfo *srvinfo; DefaultACLInfo *daclinfo; int numNamespaces; int numExtensions; int numAggregates; int numInherits; int numRules; int numProcLangs; int numCasts; int numOpclasses; int numOpfamilies; int numConversions; int numTSParsers; int numTSTemplates; int numTSDicts; int numTSConfigs; int numForeignDataWrappers; int numForeignServers; int numDefaultACLs; if (g_verbose) write_msg(NULL, "reading schemas\n"); nsinfo = getNamespaces(&numNamespaces); if (g_verbose) write_msg(NULL, "reading extensions\n"); extinfo = getExtensions(&numExtensions); if (g_verbose) write_msg(NULL, "reading user-defined functions\n"); funinfo = getFuncs(&numFuncs); funinfoindex = buildIndexArray(funinfo, numFuncs, sizeof(FuncInfo)); /* this must be after getFuncs */ if (g_verbose) write_msg(NULL, "reading user-defined types\n"); typinfo = getTypes(&numTypes); typinfoindex = buildIndexArray(typinfo, numTypes, sizeof(TypeInfo)); /* this must be after getFuncs, too */ if (g_verbose) write_msg(NULL, "reading procedural languages\n"); proclanginfo = getProcLangs(&numProcLangs); if (g_verbose) write_msg(NULL, "reading user-defined aggregate functions\n"); agginfo = getAggregates(&numAggregates); if (g_verbose) write_msg(NULL, "reading user-defined operators\n"); oprinfo = getOperators(&numOperators); oprinfoindex = buildIndexArray(oprinfo, numOperators, sizeof(OprInfo)); if (g_verbose) write_msg(NULL, "reading user-defined operator classes\n"); opcinfo = getOpclasses(&numOpclasses); if (g_verbose) write_msg(NULL, "reading user-defined operator families\n"); opfinfo = getOpfamilies(&numOpfamilies); if (g_verbose) write_msg(NULL, "reading user-defined text search parsers\n"); prsinfo = getTSParsers(&numTSParsers); if (g_verbose) write_msg(NULL, "reading user-defined text search templates\n"); tmplinfo = getTSTemplates(&numTSTemplates); if (g_verbose) write_msg(NULL, "reading user-defined text search dictionaries\n"); dictinfo = getTSDictionaries(&numTSDicts); if (g_verbose) write_msg(NULL, "reading user-defined text search configurations\n"); cfginfo = getTSConfigurations(&numTSConfigs); if (g_verbose) write_msg(NULL, "reading user-defined foreign-data wrappers\n"); fdwinfo = getForeignDataWrappers(&numForeignDataWrappers); if (g_verbose) write_msg(NULL, "reading user-defined foreign servers\n"); srvinfo = getForeignServers(&numForeignServers); if (g_verbose) write_msg(NULL, "reading default privileges\n"); daclinfo = getDefaultACLs(&numDefaultACLs); if (g_verbose) write_msg(NULL, "reading user-defined collations\n"); collinfo = getCollations(&numCollations); collinfoindex = buildIndexArray(collinfo, numCollations, sizeof(CollInfo)); if (g_verbose) write_msg(NULL, "reading user-defined conversions\n"); convinfo = getConversions(&numConversions); if (g_verbose) write_msg(NULL, "reading type casts\n"); castinfo = getCasts(&numCasts); if (g_verbose) write_msg(NULL, "reading user-defined tables\n"); tblinfo = getTables(&numTables); tblinfoindex = buildIndexArray(tblinfo, numTables, sizeof(TableInfo)); if (g_verbose) write_msg(NULL, "reading table inheritance information\n"); inhinfo = getInherits(&numInherits); if (g_verbose) write_msg(NULL, "reading rewrite rules\n"); ruleinfo = getRules(&numRules); /* * Identify extension member objects and mark them as not to be dumped. * This must happen after reading all objects that can be direct members * of extensions, but before we begin to process table subsidiary objects. */ if (g_verbose) write_msg(NULL, "finding extension members\n"); getExtensionMembership(extinfo, numExtensions); /* Link tables to parents, mark parents of target tables interesting */ if (g_verbose) write_msg(NULL, "finding inheritance relationships\n"); flagInhTables(tblinfo, numTables, inhinfo, numInherits); if (g_verbose) write_msg(NULL, "reading column info for interesting tables\n"); getTableAttrs(tblinfo, numTables); if (g_verbose) write_msg(NULL, "flagging inherited columns in subtables\n"); flagInhAttrs(tblinfo, numTables); if (g_verbose) write_msg(NULL, "reading indexes\n"); getIndexes(tblinfo, numTables); if (g_verbose) write_msg(NULL, "reading constraints\n"); getConstraints(tblinfo, numTables); if (g_verbose) write_msg(NULL, "reading triggers\n"); getTriggers(tblinfo, numTables); *numTablesPtr = numTables; return tblinfo; } /* flagInhTables - * Fill in parent link fields of every target table, and mark * parents of target tables as interesting * * Note that only direct ancestors of targets are marked interesting. * This is sufficient; we don't much care whether they inherited their * attributes or not. * * modifies tblinfo */ static void flagInhTables(TableInfo *tblinfo, int numTables, InhInfo *inhinfo, int numInherits) { int i, j; int numParents; TableInfo **parents; for (i = 0; i < numTables; i++) { /* Sequences and views never have parents */ if (tblinfo[i].relkind == RELKIND_SEQUENCE || tblinfo[i].relkind == RELKIND_VIEW) continue; /* Don't bother computing anything for non-target tables, either */ if (!tblinfo[i].dobj.dump) continue; /* Find all the immediate parent tables */ findParentsByOid(&tblinfo[i], inhinfo, numInherits); /* Mark the parents as interesting for getTableAttrs */ numParents = tblinfo[i].numParents; parents = tblinfo[i].parents; for (j = 0; j < numParents; j++) parents[j]->interesting = true; } } /* flagInhAttrs - * for each dumpable table in tblinfo, flag its inherited attributes * so when we dump the table out, we don't dump out the inherited attributes * * modifies tblinfo */ static void flagInhAttrs(TableInfo *tblinfo, int numTables) { int i, j, k; for (i = 0; i < numTables; i++) { TableInfo *tbinfo = &(tblinfo[i]); int numParents; TableInfo **parents; TableInfo *parent; /* Sequences and views never have parents */ if (tbinfo->relkind == RELKIND_SEQUENCE || tbinfo->relkind == RELKIND_VIEW) continue; /* Don't bother computing anything for non-target tables, either */ if (!tbinfo->dobj.dump) continue; numParents = tbinfo->numParents; parents = tbinfo->parents; if (numParents == 0) continue; /* nothing to see here, move along */ /*---------------------------------------------------------------- * For each attr, check the parent info: if no parent has an attr * with the same name, then it's not inherited. If there *is* an * attr with the same name, then only dump it if: * * - it is NOT NULL and zero parents are NOT NULL * OR * - it has a default value AND the default value does not match * all parent default values, or no parents specify a default. * * See discussion on -hackers around 2-Apr-2001. *---------------------------------------------------------------- */ for (j = 0; j < tbinfo->numatts; j++) { bool foundAttr; /* Attr was found in a parent */ bool foundNotNull; /* Attr was NOT NULL in a parent */ bool defaultsMatch; /* All non-empty defaults match */ bool defaultsFound; /* Found a default in a parent */ AttrDefInfo *attrDef; foundAttr = false; foundNotNull = false; defaultsMatch = true; defaultsFound = false; attrDef = tbinfo->attrdefs[j]; for (k = 0; k < numParents; k++) { int inhAttrInd; parent = parents[k]; inhAttrInd = strInArray(tbinfo->attnames[j], parent->attnames, parent->numatts); if (inhAttrInd != -1) { AttrDefInfo *inhDef = parent->attrdefs[inhAttrInd]; foundAttr = true; foundNotNull |= parent->notnull[inhAttrInd]; if (inhDef != NULL) { defaultsFound = true; /* * If any parent has a default and the child doesn't, * we have to emit an explicit DEFAULT NULL clause for * the child, else the parent's default will win. */ if (attrDef == NULL) { attrDef = (AttrDefInfo *) malloc(sizeof(AttrDefInfo)); attrDef->dobj.objType = DO_ATTRDEF; attrDef->dobj.catId.tableoid = 0; attrDef->dobj.catId.oid = 0; AssignDumpId(&attrDef->dobj); attrDef->adtable = tbinfo; attrDef->adnum = j + 1; attrDef->adef_expr = strdup("NULL"); attrDef->dobj.name = strdup(tbinfo->dobj.name); attrDef->dobj.namespace = tbinfo->dobj.namespace; attrDef->dobj.dump = tbinfo->dobj.dump; attrDef->separate = false; addObjectDependency(&tbinfo->dobj, attrDef->dobj.dumpId); tbinfo->attrdefs[j] = attrDef; } if (strcmp(attrDef->adef_expr, inhDef->adef_expr) != 0) { defaultsMatch = false; /* * Whenever there is a non-matching parent * default, add a dependency to force the parent * default to be dumped first, in case the * defaults end up being dumped as separate * commands. Otherwise the parent default will * override the child's when it is applied. */ addObjectDependency(&attrDef->dobj, inhDef->dobj.dumpId); } } } } /* * Based on the scan of the parents, decide if we can rely on the * inherited attr */ if (foundAttr) /* Attr was inherited */ { /* Set inherited flag by default */ tbinfo->inhAttrs[j] = true; tbinfo->inhAttrDef[j] = true; tbinfo->inhNotNull[j] = true; /* * Clear it if attr had a default, but parents did not, or * mismatch */ if ((attrDef != NULL) && (!defaultsFound || !defaultsMatch)) { tbinfo->inhAttrs[j] = false; tbinfo->inhAttrDef[j] = false; } /* * Clear it if NOT NULL and none of the parents were NOT NULL */ if (tbinfo->notnull[j] && !foundNotNull) { tbinfo->inhAttrs[j] = false; tbinfo->inhNotNull[j] = false; } /* Clear it if attr has local definition */ if (tbinfo->attislocal[j]) tbinfo->inhAttrs[j] = false; } } } } /* * AssignDumpId * Given a newly-created dumpable object, assign a dump ID, * and enter the object into the lookup table. * * The caller is expected to have filled in objType and catId, * but not any of the other standard fields of a DumpableObject. */ void AssignDumpId(DumpableObject *dobj) { dobj->dumpId = ++lastDumpId; dobj->name = NULL; /* must be set later */ dobj->namespace = NULL; /* may be set later */ dobj->dump = true; /* default assumption */ dobj->ext_member = false; /* default assumption */ dobj->dependencies = NULL; dobj->nDeps = 0; dobj->allocDeps = 0; while (dobj->dumpId >= allocedDumpIds) { int newAlloc; if (allocedDumpIds <= 0) { newAlloc = 256; dumpIdMap = (DumpableObject **) pg_malloc(newAlloc * sizeof(DumpableObject *)); } else { newAlloc = allocedDumpIds * 2; dumpIdMap = (DumpableObject **) pg_realloc(dumpIdMap, newAlloc * sizeof(DumpableObject *)); } memset(dumpIdMap + allocedDumpIds, 0, (newAlloc - allocedDumpIds) * sizeof(DumpableObject *)); allocedDumpIds = newAlloc; } dumpIdMap[dobj->dumpId] = dobj; /* mark catalogIdMap invalid, but don't rebuild it yet */ catalogIdMapValid = false; } /* * Assign a DumpId that's not tied to a DumpableObject. * * This is used when creating a "fixed" ArchiveEntry that doesn't need to * participate in the sorting logic. */ DumpId createDumpId(void) { return ++lastDumpId; } /* * Return the largest DumpId so far assigned */ DumpId getMaxDumpId(void) { return lastDumpId; } /* * Find a DumpableObject by dump ID * * Returns NULL for invalid ID */ DumpableObject * findObjectByDumpId(DumpId dumpId) { if (dumpId <= 0 || dumpId >= allocedDumpIds) return NULL; /* out of range? */ return dumpIdMap[dumpId]; } /* * Find a DumpableObject by catalog ID * * Returns NULL for unknown ID * * We use binary search in a sorted list that is built on first call. * If AssignDumpId() and findObjectByCatalogId() calls were freely intermixed, * the code would work, but possibly be very slow. In the current usage * pattern that does not happen, indeed we build the list at most twice. */ DumpableObject * findObjectByCatalogId(CatalogId catalogId) { DumpableObject **low; DumpableObject **high; if (!catalogIdMapValid) { if (catalogIdMap) free(catalogIdMap); getDumpableObjects(&catalogIdMap, &numCatalogIds); if (numCatalogIds > 1) qsort((void *) catalogIdMap, numCatalogIds, sizeof(DumpableObject *), DOCatalogIdCompare); catalogIdMapValid = true; } /* * We could use bsearch() here, but the notational cruft of calling * bsearch is nearly as bad as doing it ourselves; and the generalized * bsearch function is noticeably slower as well. */ if (numCatalogIds <= 0) return NULL; low = catalogIdMap; high = catalogIdMap + (numCatalogIds - 1); while (low <= high) { DumpableObject **middle; int difference; middle = low + (high - low) / 2; /* comparison must match DOCatalogIdCompare, below */ difference = oidcmp((*middle)->catId.oid, catalogId.oid); if (difference == 0) difference = oidcmp((*middle)->catId.tableoid, catalogId.tableoid); if (difference == 0) return *middle; else if (difference < 0) low = middle + 1; else high = middle - 1; } return NULL; } /* * Find a DumpableObject by OID, in a pre-sorted array of one type of object * * Returns NULL for unknown OID */ static DumpableObject * findObjectByOid(Oid oid, DumpableObject **indexArray, int numObjs) { DumpableObject **low; DumpableObject **high; /* * This is the same as findObjectByCatalogId except we assume we need not * look at table OID because the objects are all the same type. * * We could use bsearch() here, but the notational cruft of calling * bsearch is nearly as bad as doing it ourselves; and the generalized * bsearch function is noticeably slower as well. */ if (numObjs <= 0) return NULL; low = indexArray; high = indexArray + (numObjs - 1); while (low <= high) { DumpableObject **middle; int difference; middle = low + (high - low) / 2; difference = oidcmp((*middle)->catId.oid, oid); if (difference == 0) return *middle; else if (difference < 0) low = middle + 1; else high = middle - 1; } return NULL; } /* * Build an index array of DumpableObject pointers, sorted by OID */ static DumpableObject ** buildIndexArray(void *objArray, int numObjs, Size objSize) { DumpableObject **ptrs; int i; ptrs = (DumpableObject **) malloc(numObjs * sizeof(DumpableObject *)); for (i = 0; i < numObjs; i++) ptrs[i] = (DumpableObject *) ((char *) objArray + i * objSize); /* We can use DOCatalogIdCompare to sort since its first key is OID */ if (numObjs > 1) qsort((void *) ptrs, numObjs, sizeof(DumpableObject *), DOCatalogIdCompare); return ptrs; } /* * qsort comparator for pointers to DumpableObjects */ static int DOCatalogIdCompare(const void *p1, const void *p2) { DumpableObject *obj1 = *(DumpableObject **) p1; DumpableObject *obj2 = *(DumpableObject **) p2; int cmpval; /* * Compare OID first since it's usually unique, whereas there will only be * a few distinct values of tableoid. */ cmpval = oidcmp(obj1->catId.oid, obj2->catId.oid); if (cmpval == 0) cmpval = oidcmp(obj1->catId.tableoid, obj2->catId.tableoid); return cmpval; } /* * Build an array of pointers to all known dumpable objects * * This simply creates a modifiable copy of the internal map. */ void getDumpableObjects(DumpableObject ***objs, int *numObjs) { int i, j; *objs = (DumpableObject **) pg_malloc(allocedDumpIds * sizeof(DumpableObject *)); j = 0; for (i = 1; i < allocedDumpIds; i++) { if (dumpIdMap[i]) (*objs)[j++] = dumpIdMap[i]; } *numObjs = j; } /* * Add a dependency link to a DumpableObject * * Note: duplicate dependencies are currently not eliminated */ void addObjectDependency(DumpableObject *dobj, DumpId refId) { if (dobj->nDeps >= dobj->allocDeps) { if (dobj->allocDeps <= 0) { dobj->allocDeps = 16; dobj->dependencies = (DumpId *) pg_malloc(dobj->allocDeps * sizeof(DumpId)); } else { dobj->allocDeps *= 2; dobj->dependencies = (DumpId *) pg_realloc(dobj->dependencies, dobj->allocDeps * sizeof(DumpId)); } } dobj->dependencies[dobj->nDeps++] = refId; } /* * Remove a dependency link from a DumpableObject * * If there are multiple links, all are removed */ void removeObjectDependency(DumpableObject *dobj, DumpId refId) { int i; int j = 0; for (i = 0; i < dobj->nDeps; i++) { if (dobj->dependencies[i] != refId) dobj->dependencies[j++] = dobj->dependencies[i]; } dobj->nDeps = j; } /* * findTableByOid * finds the entry (in tblinfo) of the table with the given oid * returns NULL if not found */ TableInfo * findTableByOid(Oid oid) { return (TableInfo *) findObjectByOid(oid, tblinfoindex, numTables); } /* * findTypeByOid * finds the entry (in typinfo) of the type with the given oid * returns NULL if not found */ TypeInfo * findTypeByOid(Oid oid) { return (TypeInfo *) findObjectByOid(oid, typinfoindex, numTypes); } /* * findFuncByOid * finds the entry (in funinfo) of the function with the given oid * returns NULL if not found */ FuncInfo * findFuncByOid(Oid oid) { return (FuncInfo *) findObjectByOid(oid, funinfoindex, numFuncs); } /* * findOprByOid * finds the entry (in oprinfo) of the operator with the given oid * returns NULL if not found */ OprInfo * findOprByOid(Oid oid) { return (OprInfo *) findObjectByOid(oid, oprinfoindex, numOperators); } /* * findCollationByOid * finds the entry (in collinfo) of the collation with the given oid * returns NULL if not found */ CollInfo * findCollationByOid(Oid oid) { return (CollInfo *) findObjectByOid(oid, collinfoindex, numCollations); } /* * findParentsByOid * find a table's parents in tblinfo[] */ static void findParentsByOid(TableInfo *self, InhInfo *inhinfo, int numInherits) { Oid oid = self->dobj.catId.oid; int i, j; int numParents; numParents = 0; for (i = 0; i < numInherits; i++) { if (inhinfo[i].inhrelid == oid) numParents++; } self->numParents = numParents; if (numParents > 0) { self->parents = (TableInfo **) pg_malloc(sizeof(TableInfo *) * numParents); j = 0; for (i = 0; i < numInherits; i++) { if (inhinfo[i].inhrelid == oid) { TableInfo *parent; parent = findTableByOid(inhinfo[i].inhparent); if (parent == NULL) { write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n", inhinfo[i].inhparent, self->dobj.name, oid); exit_nicely(); } self->parents[j++] = parent; } } } else self->parents = NULL; } /* * parseOidArray * parse a string of numbers delimited by spaces into a character array * * Note: actually this is used for both Oids and potentially-signed * attribute numbers. This should cause no trouble, but we could split * the function into two functions with different argument types if it does. */ void parseOidArray(const char *str, Oid *array, int arraysize) { int j, argNum; char temp[100]; char s; argNum = 0; j = 0; for (;;) { s = *str++; if (s == ' ' || s == '\0') { if (j > 0) { if (argNum >= arraysize) { write_msg(NULL, "could not parse numeric array \"%s\": too many numbers\n", str); exit_nicely(); } temp[j] = '\0'; array[argNum++] = atooid(temp); j = 0; } if (s == '\0') break; } else { if (!(isdigit((unsigned char) s) || s == '-') || j >= sizeof(temp) - 1) { write_msg(NULL, "could not parse numeric array \"%s\": invalid character in number\n", str); exit_nicely(); } temp[j++] = s; } } while (argNum < arraysize) array[argNum++] = InvalidOid; } /* * strInArray: * takes in a string and a string array and the number of elements in the * string array. * returns the index if the string is somewhere in the array, -1 otherwise */ static int strInArray(const char *pattern, char **arr, int arr_size) { int i; for (i = 0; i < arr_size; i++) { if (strcmp(pattern, arr[i]) == 0) return i; } return -1; } /* * Support for simple list operations */ void simple_oid_list_append(SimpleOidList *list, Oid val) { SimpleOidListCell *cell; cell = (SimpleOidListCell *) pg_malloc(sizeof(SimpleOidListCell)); cell->next = NULL; cell->val = val; if (list->tail) list->tail->next = cell; else list->head = cell; list->tail = cell; } void simple_string_list_append(SimpleStringList *list, const char *val) { SimpleStringListCell *cell; /* this calculation correctly accounts for the null trailing byte */ cell = (SimpleStringListCell *) pg_malloc(sizeof(SimpleStringListCell) + strlen(val)); cell->next = NULL; strcpy(cell->val, val); if (list->tail) list->tail->next = cell; else list->head = cell; list->tail = cell; } bool simple_oid_list_member(SimpleOidList *list, Oid val) { SimpleOidListCell *cell; for (cell = list->head; cell; cell = cell->next) { if (cell->val == val) return true; } return false; } bool simple_string_list_member(SimpleStringList *list, const char *val) { SimpleStringListCell *cell; for (cell = list->head; cell; cell = cell->next) { if (strcmp(cell->val, val) == 0) return true; } return false; } /* * Safer versions of some standard C library functions. If an * out-of-memory condition occurs, these functions will bail out * safely; therefore, their return value is guaranteed to be non-NULL. * * XXX need to refactor things so that these can be in a file that can be * shared by pg_dumpall and pg_restore as well as pg_dump. */ char * pg_strdup(const char *string) { char *tmp; if (!string) exit_horribly(NULL, NULL, "cannot duplicate null pointer\n"); tmp = strdup(string); if (!tmp) exit_horribly(NULL, NULL, "out of memory\n"); return tmp; } void * pg_malloc(size_t size) { void *tmp; tmp = malloc(size); if (!tmp) exit_horribly(NULL, NULL, "out of memory\n"); return tmp; } void * pg_calloc(size_t nmemb, size_t size) { void *tmp; tmp = calloc(nmemb, size); if (!tmp) exit_horribly(NULL, NULL, "out of memory\n"); return tmp; } void * pg_realloc(void *ptr, size_t size) { void *tmp; tmp = realloc(ptr, size); if (!tmp) exit_horribly(NULL, NULL, "out of memory\n"); return tmp; }