Age Owner Branch data TLA 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
8035 tgl@sss.pgh.pa.us 41 :CBC 573 : 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 [ + - + + : 5783 : foreach(p, lockstmt->relations)
+ + ]
49 : : {
4664 rhaas@postgresql.org 50 : 5248 : RangeVar *rv = (RangeVar *) lfirst(p);
2669 tgl@sss.pgh.pa.us 51 : 5248 : bool recurse = rv->inh;
52 : : Oid reloid;
53 : :
2207 andres@anarazel.de 54 : 5248 : reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
55 : 5248 : lockstmt->nowait ? RVR_NOWAIT : 0,
56 : : RangeVarCallbackForLockTable,
4519 rhaas@postgresql.org 57 [ + + ]: 5248 : (void *) &lockstmt->mode);
58 : :
2207 ishii@postgresql.org 59 [ + + ]: 5213 : if (get_rel_relkind(reloid) == RELKIND_VIEW)
2189 60 : 33 : LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
2207 61 [ + + ]: 5180 : else if (recurse)
1517 fujii@postgresql.org 62 : 5177 : LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
63 : : }
5451 tgl@sss.pgh.pa.us 64 : 535 : }
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
4519 rhaas@postgresql.org 71 : 5292 : RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
72 : : void *arg)
73 : : {
4326 bruce@momjian.us 74 : 5292 : LOCKMODE lockmode = *(LOCKMODE *) arg;
75 : : char relkind;
76 : : char relpersistence;
77 : : AclResult aclresult;
78 : :
4519 rhaas@postgresql.org 79 [ - + ]: 5292 : if (!OidIsValid(relid))
4326 bruce@momjian.us 80 :UBC 0 : return; /* doesn't exist, so no permissions check */
4519 rhaas@postgresql.org 81 :CBC 5292 : relkind = get_rel_relkind(relid);
82 [ - + ]: 5292 : if (!relkind)
4326 bruce@momjian.us 83 :UBC 0 : return; /* woops, concurrently dropped; no permissions
84 : : * check */
85 : :
86 : : /* Currently, we only allow plain tables or views to be locked */
1255 tgl@sss.pgh.pa.us 87 [ + + + + :CBC 5292 : if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
- + ]
88 : : relkind != RELKIND_VIEW)
1255 tgl@sss.pgh.pa.us 89 [ # # ]:UBC 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 : : */
1913 michael@paquier.xyz 99 :CBC 5292 : relpersistence = get_rel_persistence(relid);
100 [ + + ]: 5292 : if (relpersistence == RELPERSISTENCE_TEMP)
1905 101 : 5 : MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
102 : :
103 : : /* Check permissions. */
2207 ishii@postgresql.org 104 : 5292 : aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
4519 rhaas@postgresql.org 105 [ + + ]: 5292 : if (aclresult != ACLCHECK_OK)
2325 peter_e@gmx.net 106 : 24 : 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
1517 fujii@postgresql.org 117 : 5213 : LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
118 : : {
119 : : List *children;
120 : : ListCell *lc;
121 : :
122 : 5213 : children = find_all_inheritors(reloid, NoLock, NULL);
123 : :
4519 rhaas@postgresql.org 124 [ + - + + : 12448 : foreach(lc, children)
+ + ]
125 : : {
126 : 7235 : Oid childreloid = lfirst_oid(lc);
127 : :
128 : : /* Parent already locked. */
1517 fujii@postgresql.org 129 [ + + ]: 7235 : if (childreloid == reloid)
130 : 5213 : continue;
131 : :
4519 rhaas@postgresql.org 132 [ + + ]: 2022 : if (!nowait)
133 : 2010 : LockRelationOid(childreloid, lockmode);
134 [ - + ]: 12 : else if (!ConditionalLockRelationOid(childreloid, lockmode))
135 : : {
136 : : /* try to throw error by name; relation could be deleted... */
4519 rhaas@postgresql.org 137 :UBC 0 : char *relname = get_rel_name(childreloid);
138 : :
139 [ # # ]: 0 : if (!relname)
4326 bruce@momjian.us 140 : 0 : continue; /* child concurrently dropped, just skip it */
4519 rhaas@postgresql.org 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 : : */
4519 rhaas@postgresql.org 151 [ - + ]:CBC 2022 : if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(childreloid)))
152 : : {
153 : : /* Release useless lock */
4519 rhaas@postgresql.org 154 :UBC 0 : UnlockRelationOid(childreloid, lockmode);
155 : 0 : continue;
156 : : }
157 : : }
4519 rhaas@postgresql.org 158 :CBC 5213 : }
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
2207 ishii@postgresql.org 177 : 1050 : LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
178 : : {
179 [ + + ]: 1050 : if (node == NULL)
180 : 669 : return false;
181 : :
182 [ + + ]: 381 : if (IsA(node, Query))
183 : : {
2189 184 : 54 : Query *query = (Query *) node;
185 : : ListCell *rtable;
186 : :
2207 187 [ + - + + : 111 : foreach(rtable, query->rtable)
+ + ]
188 : : {
2189 189 : 60 : RangeTblEntry *rte = lfirst(rtable);
190 : : AclResult aclresult;
191 : :
1255 tgl@sss.pgh.pa.us 192 : 60 : Oid relid = rte->relid;
193 : 60 : char relkind = rte->relkind;
194 : 60 : char *relname = get_rel_name(relid);
195 : :
196 : : /* Currently, we only allow plain tables or views to be locked. */
197 [ + + + - : 60 : if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+ + ]
198 : : relkind != RELKIND_VIEW)
199 : 3 : 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 : : */
2189 ishii@postgresql.org 205 [ + + ]: 57 : if (list_member_oid(context->ancestor_views, relid))
1256 tgl@sss.pgh.pa.us 206 : 6 : continue;
207 : :
208 : : /*
209 : : * Check permissions as the specified user. This will either be
210 : : * the view owner or the current user.
211 : : */
754 dean.a.rasheed@gmail 212 : 51 : aclresult = LockTableAclCheck(relid, context->lockmode,
213 : : context->check_as_user);
2207 ishii@postgresql.org 214 [ + + ]: 51 : if (aclresult != ACLCHECK_OK)
1255 tgl@sss.pgh.pa.us 215 : 3 : aclcheck_error(aclresult, get_relkind_objtype(relkind), relname);
216 : :
217 : : /* We have enough rights to lock the relation; do so. */
2207 ishii@postgresql.org 218 [ + - ]: 48 : if (!context->nowait)
219 : 48 : LockRelationOid(relid, context->lockmode);
2207 ishii@postgresql.org 220 [ # # ]:UBC 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 : :
1255 tgl@sss.pgh.pa.us 226 [ + + ]:CBC 48 : if (relkind == RELKIND_VIEW)
1256 227 : 12 : LockViewRecurse(relid, context->lockmode, context->nowait,
228 : : context->ancestor_views);
2207 ishii@postgresql.org 229 [ + - ]: 36 : else if (rte->inh)
1517 fujii@postgresql.org 230 : 36 : LockTableRecurse(relid, context->lockmode, context->nowait);
231 : : }
232 : :
2207 ishii@postgresql.org 233 : 51 : return query_tree_walker(query,
234 : : LockViewRecurse_walker,
235 : : context,
236 : : QTW_IGNORE_JOINALIASES);
237 : : }
238 : :
239 : 327 : return expression_tree_walker(node,
240 : : LockViewRecurse_walker,
241 : : context);
242 : : }
243 : :
244 : : static void
1256 tgl@sss.pgh.pa.us 245 : 45 : 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 */
1910 andres@anarazel.de 253 : 45 : view = table_open(reloid, NoLock);
2207 ishii@postgresql.org 254 : 45 : 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 : 45 : context.lockmode = lockmode;
261 : 45 : context.nowait = nowait;
754 dean.a.rasheed@gmail 262 [ - + + + : 45 : if (RelationHasSecurityInvoker(view))
+ - ]
263 : 6 : context.check_as_user = GetUserId();
264 : : else
265 : 39 : context.check_as_user = view->rd_rel->relowner;
2207 ishii@postgresql.org 266 : 45 : context.viewoid = reloid;
1733 tgl@sss.pgh.pa.us 267 : 45 : context.ancestor_views = lappend_oid(ancestor_views, reloid);
268 : :
2207 ishii@postgresql.org 269 : 45 : LockViewRecurse_walker((Node *) viewquery, &context);
270 : :
1275 peter@eisentraut.org 271 : 42 : context.ancestor_views = list_delete_last(context.ancestor_views);
272 : :
1910 andres@anarazel.de 273 : 42 : table_close(view, NoLock);
2207 ishii@postgresql.org 274 : 42 : }
275 : :
276 : : /*
277 : : * Check whether the current user is permitted to lock this relation.
278 : : */
279 : : static AclResult
280 : 5343 : LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
281 : : {
282 : : AclResult aclresult;
283 : : AclMode aclmask;
284 : :
285 : : /* any of these privileges permit any lock mode */
32 nathan@postgresql.or 286 :GNC 5343 : aclmask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
287 : :
288 : : /* SELECT privileges also permit ACCESS SHARE and below */
457 jdavis@postgresql.or 289 [ + + ]:CBC 5343 : if (lockmode <= AccessShareLock)
290 : 4959 : aclmask |= ACL_SELECT;
291 : :
292 : : /* INSERT privileges also permit ROW EXCLUSIVE and below */
293 [ + + ]: 5343 : if (lockmode <= RowExclusiveLock)
294 : 5015 : aclmask |= ACL_INSERT;
295 : :
2207 ishii@postgresql.org 296 : 5343 : aclresult = pg_class_aclcheck(reloid, userid, aclmask);
297 : :
4519 rhaas@postgresql.org 298 : 5343 : return aclresult;
299 : : }
|