Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * lockcmds.c
4 : * LOCK command support code
5 : *
6 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/commands/lockcmds.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include "access/table.h"
18 : #include "access/xact.h"
19 : #include "catalog/namespace.h"
20 : #include "catalog/pg_inherits.h"
21 : #include "commands/lockcmds.h"
22 : #include "commands/tablecmds.h"
23 : #include "miscadmin.h"
24 : #include "nodes/nodeFuncs.h"
25 : #include "parser/parse_clause.h"
26 : #include "rewrite/rewriteHandler.h"
27 : #include "storage/lmgr.h"
28 : #include "utils/acl.h"
29 : #include "utils/lsyscache.h"
30 : #include "utils/syscache.h"
31 :
32 : static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
33 : static AclResult LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid);
34 : static void RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid,
35 : Oid oldrelid, void *arg);
36 : static void LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait,
37 : List *ancestor_views);
38 :
39 : /*
40 : * LOCK TABLE
41 : */
42 : void
7664 tgl 43 GIC 522 : LockTableCommand(LockStmt *lockstmt)
7664 tgl 44 ECB : {
45 : ListCell *p;
46 :
47 : /*
48 : * Iterate over the list and process the named relations one at a time
49 : */
7664 tgl 50 GIC 5144 : foreach(p, lockstmt->relations)
7664 tgl 51 ECB : {
4293 rhaas 52 GIC 4660 : RangeVar *rv = (RangeVar *) lfirst(p);
2298 tgl 53 CBC 4660 : bool recurse = rv->inh;
5080 tgl 54 ECB : Oid reloid;
55 :
1836 andres 56 GIC 4660 : reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
1836 andres 57 CBC 4660 : lockstmt->nowait ? RVR_NOWAIT : 0,
4148 rhaas 58 ECB : RangeVarCallbackForLockTable,
4148 rhaas 59 GIC 4660 : (void *) &lockstmt->mode);
4859 simon 60 ECB :
1836 ishii 61 GIC 4625 : if (get_rel_relkind(reloid) == RELKIND_VIEW)
1818 ishii 62 CBC 33 : LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
1836 63 4592 : else if (recurse)
1146 fujii 64 4589 : LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
5080 tgl 65 ECB : }
5080 tgl 66 GIC 484 : }
7664 tgl 67 ECB :
68 : /*
69 : * Before acquiring a table lock on the named table, check whether we have
70 : * permission to do so.
71 : */
72 : static void
4148 rhaas 73 GIC 4696 : RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
4148 rhaas 74 ECB : void *arg)
75 : {
3955 bruce 76 GIC 4696 : LOCKMODE lockmode = *(LOCKMODE *) arg;
4148 rhaas 77 ECB : char relkind;
78 : char relpersistence;
79 : AclResult aclresult;
80 :
4148 rhaas 81 GIC 4696 : if (!OidIsValid(relid))
3955 bruce 82 LBC 0 : return; /* doesn't exist, so no permissions check */
4148 rhaas 83 GBC 4696 : relkind = get_rel_relkind(relid);
4148 rhaas 84 CBC 4696 : if (!relkind)
3955 bruce 85 LBC 0 : return; /* woops, concurrently dropped; no permissions
3955 bruce 86 EUB : * check */
87 :
88 : /* Currently, we only allow plain tables or views to be locked */
884 tgl 89 GIC 4696 : if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
884 tgl 90 ECB : relkind != RELKIND_VIEW)
884 tgl 91 UIC 0 : ereport(ERROR,
884 tgl 92 EUB : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
93 : errmsg("cannot lock relation \"%s\"",
94 : rv->relname),
95 : errdetail_relkind_not_supported(relkind)));
96 :
97 : /*
98 : * Make note if a temporary relation has been accessed in this
99 : * transaction.
100 : */
1542 michael 101 GIC 4696 : relpersistence = get_rel_persistence(relid);
1542 michael 102 CBC 4696 : if (relpersistence == RELPERSISTENCE_TEMP)
1534 103 5 : MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
1542 michael 104 ECB :
105 : /* Check permissions. */
1836 ishii 106 GIC 4696 : aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
4148 rhaas 107 CBC 4696 : if (aclresult != ACLCHECK_OK)
1954 peter_e 108 24 : aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
4148 rhaas 109 ECB : }
110 :
111 : /*
112 : * Apply LOCK TABLE recursively over an inheritance tree
113 : *
114 : * This doesn't check permission to perform LOCK TABLE on the child tables,
115 : * because getting here means that the user has permission to lock the
116 : * parent which is enough.
117 : */
118 : static void
1146 fujii 119 GIC 4625 : LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
4148 rhaas 120 ECB : {
121 : List *children;
122 : ListCell *lc;
123 :
1146 fujii 124 GIC 4625 : children = find_all_inheritors(reloid, NoLock, NULL);
4148 rhaas 125 ECB :
4148 rhaas 126 GIC 11102 : foreach(lc, children)
5080 tgl 127 ECB : {
4148 rhaas 128 GIC 6477 : Oid childreloid = lfirst_oid(lc);
4148 rhaas 129 ECB :
130 : /* Parent already locked. */
1146 fujii 131 GIC 6477 : if (childreloid == reloid)
1146 fujii 132 CBC 4625 : continue;
5080 tgl 133 ECB :
4148 rhaas 134 GIC 1852 : if (!nowait)
4148 rhaas 135 CBC 1840 : LockRelationOid(childreloid, lockmode);
136 12 : else if (!ConditionalLockRelationOid(childreloid, lockmode))
5080 tgl 137 ECB : {
138 : /* try to throw error by name; relation could be deleted... */
4148 rhaas 139 UIC 0 : char *relname = get_rel_name(childreloid);
3955 bruce 140 EUB :
4148 rhaas 141 UIC 0 : if (!relname)
3955 bruce 142 UBC 0 : continue; /* child concurrently dropped, just skip it */
4148 rhaas 143 0 : ereport(ERROR,
4148 rhaas 144 EUB : (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
145 : errmsg("could not obtain lock on relation \"%s\"",
146 : relname)));
147 : }
148 :
149 : /*
150 : * Even if we got the lock, child might have been concurrently
151 : * dropped. If so, we can skip it.
152 : */
4148 rhaas 153 GIC 1852 : if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(childreloid)))
4148 rhaas 154 ECB : {
155 : /* Release useless lock */
4148 rhaas 156 UIC 0 : UnlockRelationOid(childreloid, lockmode);
4148 rhaas 157 UBC 0 : continue;
4148 rhaas 158 EUB : }
159 : }
4148 rhaas 160 GIC 4625 : }
5080 tgl 161 ECB :
162 : /*
163 : * Apply LOCK TABLE recursively over a view
164 : *
165 : * All tables and views appearing in the view definition query are locked
166 : * recursively with the same lock mode.
167 : */
168 :
169 : typedef struct
170 : {
171 : LOCKMODE lockmode; /* lock mode to use */
172 : bool nowait; /* no wait mode */
173 : Oid check_as_user; /* user for checking the privilege */
174 : Oid viewoid; /* OID of the view to be locked */
175 : List *ancestor_views; /* OIDs of ancestor views */
176 : } LockViewRecurse_context;
177 :
178 : static bool
1836 ishii 179 GIC 999 : LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
1836 ishii 180 ECB : {
1836 ishii 181 GIC 999 : if (node == NULL)
1836 ishii 182 CBC 618 : return false;
1836 ishii 183 ECB :
1836 ishii 184 GIC 381 : if (IsA(node, Query))
1836 ishii 185 ECB : {
1818 ishii 186 GIC 54 : Query *query = (Query *) node;
1818 ishii 187 ECB : ListCell *rtable;
188 :
1836 ishii 189 GIC 111 : foreach(rtable, query->rtable)
1836 ishii 190 ECB : {
1818 ishii 191 GIC 60 : RangeTblEntry *rte = lfirst(rtable);
1818 ishii 192 ECB : AclResult aclresult;
193 :
884 tgl 194 GIC 60 : Oid relid = rte->relid;
884 tgl 195 CBC 60 : char relkind = rte->relkind;
196 60 : char *relname = get_rel_name(relid);
1836 ishii 197 ECB :
198 : /* Currently, we only allow plain tables or views to be locked. */
884 tgl 199 CBC 60 : if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
884 tgl 200 ECB : relkind != RELKIND_VIEW)
884 tgl 201 GIC 3 : continue;
202 :
203 : /*
204 : * We might be dealing with a self-referential view. If so, we
205 : * can just stop recursing, since we already locked it.
885 tgl 206 ECB : */
1818 ishii 207 GIC 57 : if (list_member_oid(context->ancestor_views, relid))
885 tgl 208 CBC 6 : continue;
1836 ishii 209 ECB :
210 : /*
211 : * Check permissions as the specified user. This will either be
383 dean.a.rasheed 212 : * the view owner or the current user.
213 : */
383 dean.a.rasheed 214 GBC 51 : aclresult = LockTableAclCheck(relid, context->lockmode,
383 dean.a.rasheed 215 EUB : context->check_as_user);
1836 ishii 216 GIC 51 : if (aclresult != ACLCHECK_OK)
884 tgl 217 3 : aclcheck_error(aclresult, get_relkind_objtype(relkind), relname);
218 :
219 : /* We have enough rights to lock the relation; do so. */
1836 ishii 220 CBC 48 : if (!context->nowait)
221 48 : LockRelationOid(relid, context->lockmode);
1836 ishii 222 UIC 0 : else if (!ConditionalLockRelationOid(relid, context->lockmode))
1836 ishii 223 LBC 0 : ereport(ERROR,
1836 ishii 224 ECB : (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
225 : errmsg("could not obtain lock on relation \"%s\"",
226 : relname)));
227 :
884 tgl 228 GIC 48 : if (relkind == RELKIND_VIEW)
885 229 12 : LockViewRecurse(relid, context->lockmode, context->nowait,
230 : context->ancestor_views);
1836 ishii 231 36 : else if (rte->inh)
1146 fujii 232 36 : LockTableRecurse(relid, context->lockmode, context->nowait);
1836 ishii 233 ECB : }
234 :
1836 ishii 235 GIC 51 : return query_tree_walker(query,
236 : LockViewRecurse_walker,
237 : context,
238 : QTW_IGNORE_JOINALIASES);
1836 ishii 239 ECB : }
240 :
1836 ishii 241 GIC 327 : return expression_tree_walker(node,
242 : LockViewRecurse_walker,
243 : context);
244 : }
245 :
246 : static void
885 tgl 247 CBC 45 : LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait,
885 tgl 248 ECB : List *ancestor_views)
249 : {
250 : LockViewRecurse_context context;
251 : Relation view;
252 : Query *viewquery;
253 :
254 : /* caller has already locked the view */
1539 andres 255 CBC 45 : view = table_open(reloid, NoLock);
1836 ishii 256 45 : viewquery = get_view_query(view);
1836 ishii 257 ECB :
258 : /*
383 dean.a.rasheed 259 : * If the view has the security_invoker property set, check permissions as
260 : * the current user. Otherwise, check permissions as the view owner.
261 : */
1836 ishii 262 GIC 45 : context.lockmode = lockmode;
1836 ishii 263 CBC 45 : context.nowait = nowait;
383 dean.a.rasheed 264 GIC 45 : if (RelationHasSecurityInvoker(view))
383 dean.a.rasheed 265 CBC 6 : context.check_as_user = GetUserId();
266 : else
267 39 : context.check_as_user = view->rd_rel->relowner;
1836 ishii 268 45 : context.viewoid = reloid;
1362 tgl 269 GIC 45 : context.ancestor_views = lappend_oid(ancestor_views, reloid);
270 :
1836 ishii 271 45 : LockViewRecurse_walker((Node *) viewquery, &context);
272 :
904 peter 273 42 : context.ancestor_views = list_delete_last(context.ancestor_views);
1818 ishii 274 ECB :
1539 andres 275 GIC 42 : table_close(view, NoLock);
1836 ishii 276 42 : }
277 :
278 : /*
279 : * Check whether the current user is permitted to lock this relation.
4148 rhaas 280 ECB : */
281 : static AclResult
1836 ishii 282 GIC 4747 : LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
4148 rhaas 283 ECB : {
284 : AclResult aclresult;
285 : AclMode aclmask;
286 :
287 : /* any of these privileges permit any lock mode */
86 jdavis 288 GNC 4747 : aclmask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
289 :
290 : /* SELECT privileges also permit ACCESS SHARE and below */
291 4747 : if (lockmode <= AccessShareLock)
292 4372 : aclmask |= ACL_SELECT;
293 :
294 : /* INSERT privileges also permit ROW EXCLUSIVE and below */
295 4747 : if (lockmode <= RowExclusiveLock)
296 4426 : aclmask |= ACL_INSERT;
297 :
1836 ishii 298 CBC 4747 : aclresult = pg_class_aclcheck(reloid, userid, aclmask);
2890 sfrost 299 ECB :
300 : /*
301 : * If this is a partition, check permissions of its ancestors if needed.
302 : */
86 jdavis 303 GNC 4774 : if (aclresult != ACLCHECK_OK &&
304 27 : has_partition_ancestor_privs(reloid, userid, ACL_MAINTAIN))
86 jdavis 305 UNC 0 : aclresult = ACLCHECK_OK;
306 :
4148 rhaas 307 GBC 4747 : return aclresult;
308 : }
|