diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 902af68969..51ef7c3f8d 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -13,7 +13,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.14 2002/04/27 03:45:00 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.15 2002/04/29 22:15:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,9 +37,10 @@ #include "storage/backendid.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/catcache.h" #include "utils/fmgroids.h" #include "utils/guc.h" -#include "utils/catcache.h" +#include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/syscache.h" @@ -66,10 +67,25 @@ * path is determined by GUC. The factory default path contains the PUBLIC * namespace (if it exists), preceded by the user's personal namespace * (if one exists). + * + * If namespaceSearchPathValid is false, then namespaceSearchPath (and the + * derived variables) need to be recomputed from namespace_search_path. + * We mark it invalid upon an assignment to namespace_search_path or receipt + * of a syscache invalidation event for pg_namespace. The recomputation + * is done during the next lookup attempt. + * + * Any namespaces mentioned in namespace_search_path that are not readable + * by the current user ID are simply left out of namespaceSearchPath; so + * we have to be willing to recompute the path when current userid changes. + * namespaceUser is the userid the path has been computed for. */ static List *namespaceSearchPath = NIL; +static bool namespaceSearchPathValid = true; + +static Oid namespaceUser = InvalidOid; + /* this flag must be updated correctly when namespaceSearchPath is changed */ static bool pathContainsSystemNamespace = false; @@ -103,12 +119,14 @@ typedef struct DelConstraint /* Local functions */ +static void recomputeNamespacePath(void); static Oid GetTempTableNamespace(void); static void RemoveTempRelations(Oid tempNamespaceId); static List *FindTempRelations(Oid tempNamespaceId); static List *FindDeletionConstraints(List *relOids); static List *TopoSortRels(List *relOids, List *constraintList); static void RemoveTempRelationsCallback(void); +static void NamespaceCallback(Datum arg, Oid relid); /* @@ -137,12 +155,18 @@ RangeVarGetRelid(const RangeVar *relation, bool failOK) if (relation->schemaname) { /* use exact schema given */ + AclResult aclresult; + namespaceId = GetSysCacheOid(NAMESPACENAME, CStringGetDatum(relation->schemaname), 0, 0, 0); if (!OidIsValid(namespaceId)) elog(ERROR, "Namespace \"%s\" does not exist", relation->schemaname); + aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, relation->schemaname); + relId = get_relname_relid(relation->relname, namespaceId); } else @@ -210,11 +234,14 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation) else { /* use the default creation namespace */ + recomputeNamespacePath(); namespaceId = defaultCreationNamespace; if (!OidIsValid(namespaceId)) elog(ERROR, "No namespace has been selected to create in"); } + /* Note: callers will check for CREATE rights when appropriate */ + return namespaceId; } @@ -229,9 +256,11 @@ RelnameGetRelid(const char *relname) Oid relid; List *lptr; + recomputeNamespacePath(); + /* * If a TEMP-table namespace has been set up, it is implicitly first - * in the search path. + * in the search path. We do not need to check USAGE permission. */ if (OidIsValid(myTempNamespace)) { @@ -241,7 +270,8 @@ RelnameGetRelid(const char *relname) } /* - * If system namespace is not in path, implicitly search it before path + * If system namespace is not in path, implicitly search it before path. + * We do not check USAGE permission. */ if (!pathContainsSystemNamespace) { @@ -281,6 +311,8 @@ TypenameGetTypid(const char *typname) Oid typid; List *lptr; + recomputeNamespacePath(); + /* * If system namespace is not in path, implicitly search it before path */ @@ -327,6 +359,8 @@ OpclassnameGetOpcid(Oid amid, const char *opcname) Oid opcid; List *lptr; + recomputeNamespacePath(); + /* * If system namespace is not in path, implicitly search it before path */ @@ -414,17 +448,23 @@ FuncnameGetCandidates(List *names, int nargs) if (schemaname) { /* use exact schema given */ + AclResult aclresult; + namespaceId = GetSysCacheOid(NAMESPACENAME, CStringGetDatum(schemaname), 0, 0, 0); if (!OidIsValid(namespaceId)) elog(ERROR, "Namespace \"%s\" does not exist", schemaname); + aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, schemaname); } else { /* flag to indicate we need namespace search */ namespaceId = InvalidOid; + recomputeNamespacePath(); } /* Search syscache by name and (optionally) nargs only */ @@ -598,17 +638,23 @@ OpernameGetCandidates(List *names, char oprkind) if (schemaname) { /* use exact schema given */ + AclResult aclresult; + namespaceId = GetSysCacheOid(NAMESPACENAME, CStringGetDatum(schemaname), 0, 0, 0); if (!OidIsValid(namespaceId)) elog(ERROR, "Namespace \"%s\" does not exist", schemaname); + aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, schemaname); } else { /* flag to indicate we need namespace search */ namespaceId = InvalidOid; + recomputeNamespacePath(); } /* Search syscache by name only */ @@ -736,6 +782,8 @@ OpclassGetCandidates(Oid amid) CatCList *catlist; int i; + recomputeNamespacePath(); + /* Search syscache by AM OID only */ catlist = SearchSysCacheList(CLAAMNAMENSP, 1, ObjectIdGetDatum(amid), @@ -891,11 +939,14 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p) else { /* use the default creation namespace */ + recomputeNamespacePath(); namespaceId = defaultCreationNamespace; if (!OidIsValid(namespaceId)) elog(ERROR, "No namespace has been selected to create in"); } + /* Note: callers will check for CREATE rights when appropriate */ + *objname_p = objname; return namespaceId; } @@ -965,6 +1016,118 @@ isTempNamespace(Oid namespaceId) return false; } +/* + * recomputeNamespacePath - recompute path derived variables if needed. + */ +static void +recomputeNamespacePath(void) +{ + Oid userId = GetUserId(); + char *rawname; + List *namelist; + List *oidlist; + List *newpath; + List *l; + MemoryContext oldcxt; + + /* + * Do nothing if path is already valid. + */ + if (namespaceSearchPathValid && namespaceUser == userId) + return; + + /* Need a modifiable copy of namespace_search_path string */ + rawname = pstrdup(namespace_search_path); + + /* Parse string into list of identifiers */ + if (!SplitIdentifierString(rawname, ',', &namelist)) + { + /* syntax error in name list */ + /* this should not happen if GUC checked check_search_path */ + elog(ERROR, "recomputeNamespacePath: invalid list syntax"); + } + + /* + * Convert the list of names to a list of OIDs. If any names are not + * recognizable or we don't have read access, just leave them out of + * the list. (We can't raise an error, since the search_path setting + * has already been accepted.) + */ + oidlist = NIL; + foreach(l, namelist) + { + char *curname = (char *) lfirst(l); + Oid namespaceId; + + if (strcmp(curname, "$user") == 0) + { + /* $user --- substitute namespace matching user name, if any */ + HeapTuple tuple; + + tuple = SearchSysCache(SHADOWSYSID, + ObjectIdGetDatum(userId), + 0, 0, 0); + if (HeapTupleIsValid(tuple)) + { + char *uname; + + uname = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename); + namespaceId = GetSysCacheOid(NAMESPACENAME, + CStringGetDatum(uname), + 0, 0, 0); + ReleaseSysCache(tuple); + if (OidIsValid(namespaceId) && + pg_namespace_aclcheck(namespaceId, userId, + ACL_USAGE) == ACLCHECK_OK) + oidlist = lappendi(oidlist, namespaceId); + } + } + else + { + /* normal namespace reference */ + namespaceId = GetSysCacheOid(NAMESPACENAME, + CStringGetDatum(curname), + 0, 0, 0); + if (OidIsValid(namespaceId) && + pg_namespace_aclcheck(namespaceId, userId, + ACL_USAGE) == ACLCHECK_OK) + oidlist = lappendi(oidlist, namespaceId); + } + } + + /* + * Now that we've successfully built the new list of namespace OIDs, + * save it in permanent storage. + */ + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + newpath = listCopy(oidlist); + MemoryContextSwitchTo(oldcxt); + + /* Now safe to assign to state variable. */ + freeList(namespaceSearchPath); + namespaceSearchPath = newpath; + + /* + * Update info derived from search path. + */ + pathContainsSystemNamespace = intMember(PG_CATALOG_NAMESPACE, + namespaceSearchPath); + + if (namespaceSearchPath == NIL) + defaultCreationNamespace = InvalidOid; + else + defaultCreationNamespace = (Oid) lfirsti(namespaceSearchPath); + + /* Mark the path valid. */ + namespaceSearchPathValid = true; + namespaceUser = userId; + + /* Clean up. */ + pfree(rawname); + freeList(namelist); + freeList(oidlist); +} + /* * GetTempTableNamespace * Initialize temp table namespace on first use in a particular backend @@ -979,8 +1142,12 @@ GetTempTableNamespace(void) * First, do permission check to see if we are authorized to make * temp tables. We use a nonstandard error message here since * "databasename: permission denied" might be a tad cryptic. + * + * Note we apply the check to the session user, not the currently + * active userid, since we are not going to change our minds about + * temp table availability during the session. */ - if (pg_database_aclcheck(MyDatabaseId, GetUserId(), + if (pg_database_aclcheck(MyDatabaseId, GetSessionUserId(), ACL_CREATE_TEMP) != ACLCHECK_OK) elog(ERROR, "%s: not authorized to create temp tables", DatabaseName); @@ -1320,7 +1487,8 @@ check_search_path(const char *proposed) /* * Verify that all the names are either valid namespace names or "$user". - * (We do not require $user to correspond to a valid namespace; should we?) + * We do not require $user to correspond to a valid namespace. + * We do not check for USAGE rights, either; should we? */ foreach(l, namelist) { @@ -1348,104 +1516,12 @@ check_search_path(const char *proposed) void assign_search_path(const char *newval) { - char *rawname; - List *namelist; - List *oidlist; - List *newpath; - List *l; - MemoryContext oldcxt; - /* - * If we aren't inside a transaction, we cannot do database access so - * cannot look up the names. In this case, do nothing; the internal - * search path will be fixed later by InitializeSearchPath. (We assume - * this situation can only happen in the postmaster or early in backend - * startup.) + * We mark the path as needing recomputation, but don't do anything until + * it's needed. This avoids trying to do database access during GUC + * initialization. */ - if (!IsTransactionState()) - return; - - /* Need a modifiable copy of string */ - rawname = pstrdup(newval); - - /* Parse string into list of identifiers */ - if (!SplitIdentifierString(rawname, ',', &namelist)) - { - /* syntax error in name list */ - /* this should not happen if GUC checked check_search_path */ - elog(ERROR, "assign_search_path: invalid list syntax"); - } - - /* - * Convert the list of names to a list of OIDs. If any names are not - * recognizable, just leave them out of the list. (This is our only - * reasonable recourse when the already-accepted default is bogus.) - */ - oidlist = NIL; - foreach(l, namelist) - { - char *curname = (char *) lfirst(l); - Oid namespaceId; - - if (strcmp(curname, "$user") == 0) - { - /* $user --- substitute namespace matching user name, if any */ - HeapTuple tuple; - - tuple = SearchSysCache(SHADOWSYSID, - ObjectIdGetDatum(GetSessionUserId()), - 0, 0, 0); - if (HeapTupleIsValid(tuple)) - { - char *uname; - - uname = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename); - namespaceId = GetSysCacheOid(NAMESPACENAME, - CStringGetDatum(uname), - 0, 0, 0); - if (OidIsValid(namespaceId)) - oidlist = lappendi(oidlist, namespaceId); - ReleaseSysCache(tuple); - } - } - else - { - /* normal namespace reference */ - namespaceId = GetSysCacheOid(NAMESPACENAME, - CStringGetDatum(curname), - 0, 0, 0); - if (OidIsValid(namespaceId)) - oidlist = lappendi(oidlist, namespaceId); - } - } - - /* - * Now that we've successfully built the new list of namespace OIDs, - * save it in permanent storage. - */ - oldcxt = MemoryContextSwitchTo(TopMemoryContext); - newpath = listCopy(oidlist); - MemoryContextSwitchTo(oldcxt); - - /* Now safe to assign to state variable. */ - freeList(namespaceSearchPath); - namespaceSearchPath = newpath; - - /* - * Update info derived from search path. - */ - pathContainsSystemNamespace = intMember(PG_CATALOG_NAMESPACE, - namespaceSearchPath); - - if (namespaceSearchPath == NIL) - defaultCreationNamespace = InvalidOid; - else - defaultCreationNamespace = (Oid) lfirsti(namespaceSearchPath); - - /* Clean up. */ - pfree(rawname); - freeList(namelist); - freeList(oidlist); + namespaceSearchPathValid = false; } /* @@ -1469,19 +1545,32 @@ InitializeSearchPath(void) MemoryContextSwitchTo(oldcxt); pathContainsSystemNamespace = true; defaultCreationNamespace = PG_CATALOG_NAMESPACE; + namespaceSearchPathValid = true; + namespaceUser = GetUserId(); } else { /* - * If a search path setting was provided before we were able to - * execute lookups, establish the internal search path now. + * In normal mode, arrange for a callback on any syscache invalidation + * of pg_namespace rows. */ - if (namespace_search_path && *namespace_search_path && - namespaceSearchPath == NIL) - assign_search_path(namespace_search_path); + CacheRegisterSyscacheCallback(NAMESPACEOID, + NamespaceCallback, + (Datum) 0); } } +/* + * NamespaceCallback + * Syscache inval callback function + */ +static void +NamespaceCallback(Datum arg, Oid relid) +{ + /* Force search path to be recomputed on next use */ + namespaceSearchPathValid = false; +} + /* * Fetch the active search path, expressed as a List of OIDs. * @@ -1490,5 +1579,6 @@ InitializeSearchPath(void) List * fetch_search_path(void) { + recomputeNamespacePath(); return namespaceSearchPath; }