Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * lockcmds.c
4 : * LOCK command support code
5 : *
6 : * Portions Copyright (c) 1996-2017, 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 "catalog/namespace.h"
18 : #include "catalog/pg_inherits_fn.h"
19 : #include "commands/lockcmds.h"
20 : #include "miscadmin.h"
21 : #include "parser/parse_clause.h"
22 : #include "storage/lmgr.h"
23 : #include "utils/acl.h"
24 : #include "utils/lsyscache.h"
25 : #include "utils/syscache.h"
26 :
27 : static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
28 : static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
29 : static void RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid,
30 : Oid oldrelid, void *arg);
31 :
32 : /*
33 : * LOCK TABLE
34 : */
35 : void
36 40 : LockTableCommand(LockStmt *lockstmt)
37 : {
38 : ListCell *p;
39 :
40 : /*---------
41 : * During recovery we only accept these variations:
42 : * LOCK TABLE foo IN ACCESS SHARE MODE
43 : * LOCK TABLE foo IN ROW SHARE MODE
44 : * LOCK TABLE foo IN ROW EXCLUSIVE MODE
45 : * This test must match the restrictions defined in LockAcquireExtended()
46 : *---------
47 : */
48 40 : if (lockstmt->mode > RowExclusiveLock)
49 22 : PreventCommandDuringRecovery("LOCK TABLE");
50 :
51 : /*
52 : * Iterate over the list and process the named relations one at a time
53 : */
54 68 : foreach(p, lockstmt->relations)
55 : {
56 40 : RangeVar *rv = (RangeVar *) lfirst(p);
57 40 : bool recurse = rv->inh;
58 : Oid reloid;
59 :
60 40 : reloid = RangeVarGetRelidExtended(rv, lockstmt->mode, false,
61 40 : lockstmt->nowait,
62 : RangeVarCallbackForLockTable,
63 40 : (void *) &lockstmt->mode);
64 :
65 29 : if (recurse)
66 28 : LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
67 : }
68 28 : }
69 :
70 : /*
71 : * Before acquiring a table lock on the named table, check whether we have
72 : * permission to do so.
73 : */
74 : static void
75 43 : RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
76 : void *arg)
77 : {
78 43 : LOCKMODE lockmode = *(LOCKMODE *) arg;
79 : char relkind;
80 : AclResult aclresult;
81 :
82 43 : if (!OidIsValid(relid))
83 0 : return; /* doesn't exist, so no permissions check */
84 43 : relkind = get_rel_relkind(relid);
85 43 : if (!relkind)
86 0 : return; /* woops, concurrently dropped; no permissions
87 : * check */
88 :
89 : /* Currently, we only allow plain tables to be locked */
90 43 : if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
91 1 : ereport(ERROR,
92 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
93 : errmsg("\"%s\" is not a table",
94 : rv->relname)));
95 :
96 : /* Check permissions. */
97 42 : aclresult = LockTableAclCheck(relid, lockmode);
98 42 : if (aclresult != ACLCHECK_OK)
99 8 : aclcheck_error(aclresult, ACL_KIND_CLASS, rv->relname);
100 : }
101 :
102 : /*
103 : * Apply LOCK TABLE recursively over an inheritance tree
104 : *
105 : * We use find_inheritance_children not find_all_inheritors to avoid taking
106 : * locks far in advance of checking privileges. This means we'll visit
107 : * multiply-inheriting children more than once, but that's no problem.
108 : */
109 : static void
110 30 : LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
111 : {
112 : List *children;
113 : ListCell *lc;
114 :
115 30 : children = find_inheritance_children(reloid, NoLock);
116 :
117 32 : foreach(lc, children)
118 : {
119 3 : Oid childreloid = lfirst_oid(lc);
120 : AclResult aclresult;
121 :
122 : /* Check permissions before acquiring the lock. */
123 3 : aclresult = LockTableAclCheck(childreloid, lockmode);
124 3 : if (aclresult != ACLCHECK_OK)
125 : {
126 1 : char *relname = get_rel_name(childreloid);
127 :
128 1 : if (!relname)
129 0 : continue; /* child concurrently dropped, just skip it */
130 1 : aclcheck_error(aclresult, ACL_KIND_CLASS, relname);
131 : }
132 :
133 : /* We have enough rights to lock the relation; do so. */
134 2 : if (!nowait)
135 2 : LockRelationOid(childreloid, lockmode);
136 0 : else if (!ConditionalLockRelationOid(childreloid, lockmode))
137 : {
138 : /* try to throw error by name; relation could be deleted... */
139 0 : char *relname = get_rel_name(childreloid);
140 :
141 0 : if (!relname)
142 0 : continue; /* child concurrently dropped, just skip it */
143 0 : ereport(ERROR,
144 : (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 : */
153 2 : if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(childreloid)))
154 : {
155 : /* Release useless lock */
156 0 : UnlockRelationOid(childreloid, lockmode);
157 0 : continue;
158 : }
159 :
160 2 : LockTableRecurse(childreloid, lockmode, nowait);
161 : }
162 29 : }
163 :
164 : /*
165 : * Check whether the current user is permitted to lock this relation.
166 : */
167 : static AclResult
168 45 : LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
169 : {
170 : AclResult aclresult;
171 : AclMode aclmask;
172 :
173 : /* Verify adequate privilege */
174 45 : if (lockmode == AccessShareLock)
175 9 : aclmask = ACL_SELECT;
176 36 : else if (lockmode == RowExclusiveLock)
177 7 : aclmask = ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
178 : else
179 29 : aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
180 :
181 45 : aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
182 :
183 45 : return aclresult;
184 : }
|