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