/src/postgres/src/backend/commands/lockcmds.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * lockcmds.c |
4 | | * LOCK command support code |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, 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 | | LockTableCommand(LockStmt *lockstmt) |
42 | 0 | { |
43 | 0 | ListCell *p; |
44 | | |
45 | | /* |
46 | | * Iterate over the list and process the named relations one at a time |
47 | | */ |
48 | 0 | foreach(p, lockstmt->relations) |
49 | 0 | { |
50 | 0 | RangeVar *rv = (RangeVar *) lfirst(p); |
51 | 0 | bool recurse = rv->inh; |
52 | 0 | Oid reloid; |
53 | |
|
54 | 0 | reloid = RangeVarGetRelidExtended(rv, lockstmt->mode, |
55 | 0 | lockstmt->nowait ? RVR_NOWAIT : 0, |
56 | 0 | RangeVarCallbackForLockTable, |
57 | 0 | &lockstmt->mode); |
58 | |
|
59 | 0 | if (get_rel_relkind(reloid) == RELKIND_VIEW) |
60 | 0 | LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL); |
61 | 0 | else if (recurse) |
62 | 0 | LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait); |
63 | 0 | } |
64 | 0 | } |
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 | | RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid, |
72 | | void *arg) |
73 | 0 | { |
74 | 0 | LOCKMODE lockmode = *(LOCKMODE *) arg; |
75 | 0 | char relkind; |
76 | 0 | char relpersistence; |
77 | 0 | AclResult aclresult; |
78 | |
|
79 | 0 | if (!OidIsValid(relid)) |
80 | 0 | return; /* doesn't exist, so no permissions check */ |
81 | 0 | relkind = get_rel_relkind(relid); |
82 | 0 | 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 | 0 | if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE && |
88 | 0 | relkind != RELKIND_VIEW) |
89 | 0 | ereport(ERROR, |
90 | 0 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
91 | 0 | errmsg("cannot lock relation \"%s\"", |
92 | 0 | rv->relname), |
93 | 0 | errdetail_relkind_not_supported(relkind))); |
94 | | |
95 | | /* |
96 | | * Make note if a temporary relation has been accessed in this |
97 | | * transaction. |
98 | | */ |
99 | 0 | relpersistence = get_rel_persistence(relid); |
100 | 0 | if (relpersistence == RELPERSISTENCE_TEMP) |
101 | 0 | MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE; |
102 | | |
103 | | /* Check permissions. */ |
104 | 0 | aclresult = LockTableAclCheck(relid, lockmode, GetUserId()); |
105 | 0 | if (aclresult != ACLCHECK_OK) |
106 | 0 | aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname); |
107 | 0 | } |
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 | | LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait) |
118 | 0 | { |
119 | 0 | List *children; |
120 | 0 | ListCell *lc; |
121 | |
|
122 | 0 | children = find_all_inheritors(reloid, NoLock, NULL); |
123 | |
|
124 | 0 | foreach(lc, children) |
125 | 0 | { |
126 | 0 | Oid childreloid = lfirst_oid(lc); |
127 | | |
128 | | /* Parent already locked. */ |
129 | 0 | if (childreloid == reloid) |
130 | 0 | continue; |
131 | | |
132 | 0 | if (!nowait) |
133 | 0 | LockRelationOid(childreloid, lockmode); |
134 | 0 | else if (!ConditionalLockRelationOid(childreloid, lockmode)) |
135 | 0 | { |
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 | 0 | (errcode(ERRCODE_LOCK_NOT_AVAILABLE), |
143 | 0 | errmsg("could not obtain lock on relation \"%s\"", |
144 | 0 | relname))); |
145 | 0 | } |
146 | | |
147 | | /* |
148 | | * Even if we got the lock, child might have been concurrently |
149 | | * dropped. If so, we can skip it. |
150 | | */ |
151 | 0 | if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(childreloid))) |
152 | 0 | { |
153 | | /* Release useless lock */ |
154 | 0 | UnlockRelationOid(childreloid, lockmode); |
155 | 0 | continue; |
156 | 0 | } |
157 | 0 | } |
158 | 0 | } |
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 | | LockViewRecurse_walker(Node *node, LockViewRecurse_context *context) |
178 | 0 | { |
179 | 0 | if (node == NULL) |
180 | 0 | return false; |
181 | | |
182 | 0 | if (IsA(node, Query)) |
183 | 0 | { |
184 | 0 | Query *query = (Query *) node; |
185 | 0 | ListCell *rtable; |
186 | |
|
187 | 0 | foreach(rtable, query->rtable) |
188 | 0 | { |
189 | 0 | RangeTblEntry *rte = lfirst(rtable); |
190 | 0 | AclResult aclresult; |
191 | |
|
192 | 0 | Oid relid = rte->relid; |
193 | 0 | char relkind = rte->relkind; |
194 | 0 | char *relname = get_rel_name(relid); |
195 | | |
196 | | /* Currently, we only allow plain tables or views to be locked. */ |
197 | 0 | if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE && |
198 | 0 | relkind != RELKIND_VIEW) |
199 | 0 | 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 | 0 | if (list_member_oid(context->ancestor_views, relid)) |
206 | 0 | continue; |
207 | | |
208 | | /* |
209 | | * Check permissions as the specified user. This will either be |
210 | | * the view owner or the current user. |
211 | | */ |
212 | 0 | aclresult = LockTableAclCheck(relid, context->lockmode, |
213 | 0 | context->check_as_user); |
214 | 0 | if (aclresult != ACLCHECK_OK) |
215 | 0 | aclcheck_error(aclresult, get_relkind_objtype(relkind), relname); |
216 | | |
217 | | /* We have enough rights to lock the relation; do so. */ |
218 | 0 | if (!context->nowait) |
219 | 0 | LockRelationOid(relid, context->lockmode); |
220 | 0 | else if (!ConditionalLockRelationOid(relid, context->lockmode)) |
221 | 0 | ereport(ERROR, |
222 | 0 | (errcode(ERRCODE_LOCK_NOT_AVAILABLE), |
223 | 0 | errmsg("could not obtain lock on relation \"%s\"", |
224 | 0 | relname))); |
225 | | |
226 | 0 | if (relkind == RELKIND_VIEW) |
227 | 0 | LockViewRecurse(relid, context->lockmode, context->nowait, |
228 | 0 | context->ancestor_views); |
229 | 0 | else if (rte->inh) |
230 | 0 | LockTableRecurse(relid, context->lockmode, context->nowait); |
231 | 0 | } |
232 | | |
233 | 0 | return query_tree_walker(query, |
234 | 0 | LockViewRecurse_walker, |
235 | 0 | context, |
236 | 0 | QTW_IGNORE_JOINALIASES); |
237 | 0 | } |
238 | | |
239 | 0 | return expression_tree_walker(node, |
240 | 0 | LockViewRecurse_walker, |
241 | 0 | context); |
242 | 0 | } |
243 | | |
244 | | static void |
245 | | LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, |
246 | | List *ancestor_views) |
247 | 0 | { |
248 | 0 | LockViewRecurse_context context; |
249 | 0 | Relation view; |
250 | 0 | Query *viewquery; |
251 | | |
252 | | /* caller has already locked the view */ |
253 | 0 | view = table_open(reloid, NoLock); |
254 | 0 | 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 | 0 | context.lockmode = lockmode; |
261 | 0 | context.nowait = nowait; |
262 | 0 | if (RelationHasSecurityInvoker(view)) |
263 | 0 | context.check_as_user = GetUserId(); |
264 | 0 | else |
265 | 0 | context.check_as_user = view->rd_rel->relowner; |
266 | 0 | context.viewoid = reloid; |
267 | 0 | context.ancestor_views = lappend_oid(ancestor_views, reloid); |
268 | |
|
269 | 0 | LockViewRecurse_walker((Node *) viewquery, &context); |
270 | |
|
271 | 0 | context.ancestor_views = list_delete_last(context.ancestor_views); |
272 | |
|
273 | 0 | table_close(view, NoLock); |
274 | 0 | } |
275 | | |
276 | | /* |
277 | | * Check whether the current user is permitted to lock this relation. |
278 | | */ |
279 | | static AclResult |
280 | | LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid) |
281 | 0 | { |
282 | 0 | AclResult aclresult; |
283 | 0 | AclMode aclmask; |
284 | | |
285 | | /* any of these privileges permit any lock mode */ |
286 | 0 | aclmask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE; |
287 | | |
288 | | /* SELECT privileges also permit ACCESS SHARE and below */ |
289 | 0 | if (lockmode <= AccessShareLock) |
290 | 0 | aclmask |= ACL_SELECT; |
291 | | |
292 | | /* INSERT privileges also permit ROW EXCLUSIVE and below */ |
293 | 0 | if (lockmode <= RowExclusiveLock) |
294 | 0 | aclmask |= ACL_INSERT; |
295 | |
|
296 | 0 | aclresult = pg_class_aclcheck(reloid, userid, aclmask); |
297 | |
|
298 | 0 | return aclresult; |
299 | 0 | } |