~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/Documentation/filesystems/directory-locking.rst

Version: ~ [ linux-6.11.5 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.58 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.114 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.169 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.228 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.284 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.322 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  1 =================
  2 Directory Locking
  3 =================
  4 
  5 
  6 Locking scheme used for directory operations is based on two
  7 kinds of locks - per-inode (->i_rwsem) and per-filesystem
  8 (->s_vfs_rename_mutex).
  9 
 10 When taking the i_rwsem on multiple non-directory objects, we
 11 always acquire the locks in order by increasing address.  We'll call
 12 that "inode pointer" order in the following.
 13 
 14 
 15 Primitives
 16 ==========
 17 
 18 For our purposes all operations fall in 6 classes:
 19 
 20 1. read access.  Locking rules:
 21 
 22         * lock the directory we are accessing (shared)
 23 
 24 2. object creation.  Locking rules:
 25 
 26         * lock the directory we are accessing (exclusive)
 27 
 28 3. object removal.  Locking rules:
 29 
 30         * lock the parent (exclusive)
 31         * find the victim
 32         * lock the victim (exclusive)
 33 
 34 4. link creation.  Locking rules:
 35 
 36         * lock the parent (exclusive)
 37         * check that the source is not a directory
 38         * lock the source (exclusive; probably could be weakened to shared)
 39 
 40 5. rename that is _not_ cross-directory.  Locking rules:
 41 
 42         * lock the parent (exclusive)
 43         * find the source and target
 44         * decide which of the source and target need to be locked.
 45           The source needs to be locked if it's a non-directory, target - if it's
 46           a non-directory or about to be removed.
 47         * take the locks that need to be taken (exclusive), in inode pointer order
 48           if need to take both (that can happen only when both source and target
 49           are non-directories - the source because it wouldn't need to be locked
 50           otherwise and the target because mixing directory and non-directory is
 51           allowed only with RENAME_EXCHANGE, and that won't be removing the target).
 52 
 53 6. cross-directory rename.  The trickiest in the whole bunch.  Locking rules:
 54 
 55         * lock the filesystem
 56         * if the parents don't have a common ancestor, fail the operation.
 57         * lock the parents in "ancestors first" order (exclusive). If neither is an
 58           ancestor of the other, lock the parent of source first.
 59         * find the source and target.
 60         * verify that the source is not a descendent of the target and
 61           target is not a descendent of source; fail the operation otherwise.
 62         * lock the subdirectories involved (exclusive), source before target.
 63         * lock the non-directories involved (exclusive), in inode pointer order.
 64 
 65 The rules above obviously guarantee that all directories that are going
 66 to be read, modified or removed by method will be locked by the caller.
 67 
 68 
 69 Splicing
 70 ========
 71 
 72 There is one more thing to consider - splicing.  It's not an operation
 73 in its own right; it may happen as part of lookup.  We speak of the
 74 operations on directory trees, but we obviously do not have the full
 75 picture of those - especially for network filesystems.  What we have
 76 is a bunch of subtrees visible in dcache and locking happens on those.
 77 Trees grow as we do operations; memory pressure prunes them.  Normally
 78 that's not a problem, but there is a nasty twist - what should we do
 79 when one growing tree reaches the root of another?  That can happen in
 80 several scenarios, starting from "somebody mounted two nested subtrees
 81 from the same NFS4 server and doing lookups in one of them has reached
 82 the root of another"; there's also open-by-fhandle stuff, and there's a
 83 possibility that directory we see in one place gets moved by the server
 84 to another and we run into it when we do a lookup.
 85 
 86 For a lot of reasons we want to have the same directory present in dcache
 87 only once.  Multiple aliases are not allowed.  So when lookup runs into
 88 a subdirectory that already has an alias, something needs to be done with
 89 dcache trees.  Lookup is already holding the parent locked.  If alias is
 90 a root of separate tree, it gets attached to the directory we are doing a
 91 lookup in, under the name we'd been looking for.  If the alias is already
 92 a child of the directory we are looking in, it changes name to the one
 93 we'd been looking for.  No extra locking is involved in these two cases.
 94 However, if it's a child of some other directory, the things get trickier.
 95 First of all, we verify that it is *not* an ancestor of our directory
 96 and fail the lookup if it is.  Then we try to lock the filesystem and the
 97 current parent of the alias.  If either trylock fails, we fail the lookup.
 98 If trylocks succeed, we detach the alias from its current parent and
 99 attach to our directory, under the name we are looking for.
100 
101 Note that splicing does *not* involve any modification of the filesystem;
102 all we change is the view in dcache.  Moreover, holding a directory locked
103 exclusive prevents such changes involving its children and holding the
104 filesystem lock prevents any changes of tree topology, other than having a
105 root of one tree becoming a child of directory in another.  In particular,
106 if two dentries have been found to have a common ancestor after taking
107 the filesystem lock, their relationship will remain unchanged until
108 the lock is dropped.  So from the directory operations' point of view
109 splicing is almost irrelevant - the only place where it matters is one
110 step in cross-directory renames; we need to be careful when checking if
111 parents have a common ancestor.
112 
113 
114 Multiple-filesystem stuff
115 =========================
116 
117 For some filesystems a method can involve a directory operation on
118 another filesystem; it may be ecryptfs doing operation in the underlying
119 filesystem, overlayfs doing something to the layers, network filesystem
120 using a local one as a cache, etc.  In all such cases the operations
121 on other filesystems must follow the same locking rules.  Moreover, "a
122 directory operation on this filesystem might involve directory operations
123 on that filesystem" should be an asymmetric relation (or, if you will,
124 it should be possible to rank the filesystems so that directory operation
125 on a filesystem could trigger directory operations only on higher-ranked
126 ones - in these terms overlayfs ranks lower than its layers, network
127 filesystem ranks lower than whatever it caches on, etc.)
128 
129 
130 Deadlock avoidance
131 ==================
132 
133 If no directory is its own ancestor, the scheme above is deadlock-free.
134 
135 Proof:
136 
137 There is a ranking on the locks, such that all primitives take
138 them in order of non-decreasing rank.  Namely,
139 
140   * rank ->i_rwsem of non-directories on given filesystem in inode pointer
141     order.
142   * put ->i_rwsem of all directories on a filesystem at the same rank,
143     lower than ->i_rwsem of any non-directory on the same filesystem.
144   * put ->s_vfs_rename_mutex at rank lower than that of any ->i_rwsem
145     on the same filesystem.
146   * among the locks on different filesystems use the relative
147     rank of those filesystems.
148 
149 For example, if we have NFS filesystem caching on a local one, we have
150 
151   1. ->s_vfs_rename_mutex of NFS filesystem
152   2. ->i_rwsem of directories on that NFS filesystem, same rank for all
153   3. ->i_rwsem of non-directories on that filesystem, in order of
154      increasing address of inode
155   4. ->s_vfs_rename_mutex of local filesystem
156   5. ->i_rwsem of directories on the local filesystem, same rank for all
157   6. ->i_rwsem of non-directories on local filesystem, in order of
158      increasing address of inode.
159 
160 It's easy to verify that operations never take a lock with rank
161 lower than that of an already held lock.
162 
163 Suppose deadlocks are possible.  Consider the minimal deadlocked
164 set of threads.  It is a cycle of several threads, each blocked on a lock
165 held by the next thread in the cycle.
166 
167 Since the locking order is consistent with the ranking, all
168 contended locks in the minimal deadlock will be of the same rank,
169 i.e. they all will be ->i_rwsem of directories on the same filesystem.
170 Moreover, without loss of generality we can assume that all operations
171 are done directly to that filesystem and none of them has actually
172 reached the method call.
173 
174 In other words, we have a cycle of threads, T1,..., Tn,
175 and the same number of directories (D1,...,Dn) such that
176 
177         T1 is blocked on D1 which is held by T2
178 
179         T2 is blocked on D2 which is held by T3
180 
181         ...
182 
183         Tn is blocked on Dn which is held by T1.
184 
185 Each operation in the minimal cycle must have locked at least
186 one directory and blocked on attempt to lock another.  That leaves
187 only 3 possible operations: directory removal (locks parent, then
188 child), same-directory rename killing a subdirectory (ditto) and
189 cross-directory rename of some sort.
190 
191 There must be a cross-directory rename in the set; indeed,
192 if all operations had been of the "lock parent, then child" sort
193 we would have Dn a parent of D1, which is a parent of D2, which is
194 a parent of D3, ..., which is a parent of Dn.  Relationships couldn't
195 have changed since the moment directory locks had been acquired,
196 so they would all hold simultaneously at the deadlock time and
197 we would have a loop.
198 
199 Since all operations are on the same filesystem, there can't be
200 more than one cross-directory rename among them.  Without loss of
201 generality we can assume that T1 is the one doing a cross-directory
202 rename and everything else is of the "lock parent, then child" sort.
203 
204 In other words, we have a cross-directory rename that locked
205 Dn and blocked on attempt to lock D1, which is a parent of D2, which is
206 a parent of D3, ..., which is a parent of Dn.  Relationships between
207 D1,...,Dn all hold simultaneously at the deadlock time.  Moreover,
208 cross-directory rename does not get to locking any directories until it
209 has acquired filesystem lock and verified that directories involved have
210 a common ancestor, which guarantees that ancestry relationships between
211 all of them had been stable.
212 
213 Consider the order in which directories are locked by the
214 cross-directory rename; parents first, then possibly their children.
215 Dn and D1 would have to be among those, with Dn locked before D1.
216 Which pair could it be?
217 
218 It can't be the parents - indeed, since D1 is an ancestor of Dn,
219 it would be the first parent to be locked.  Therefore at least one of the
220 children must be involved and thus neither of them could be a descendent
221 of another - otherwise the operation would not have progressed past
222 locking the parents.
223 
224 It can't be a parent and its child; otherwise we would've had
225 a loop, since the parents are locked before the children, so the parent
226 would have to be a descendent of its child.
227 
228 It can't be a parent and a child of another parent either.
229 Otherwise the child of the parent in question would've been a descendent
230 of another child.
231 
232 That leaves only one possibility - namely, both Dn and D1 are
233 among the children, in some order.  But that is also impossible, since
234 neither of the children is a descendent of another.
235 
236 That concludes the proof, since the set of operations with the
237 properties required for a minimal deadlock can not exist.
238 
239 Note that the check for having a common ancestor in cross-directory
240 rename is crucial - without it a deadlock would be possible.  Indeed,
241 suppose the parents are initially in different trees; we would lock the
242 parent of source, then try to lock the parent of target, only to have
243 an unrelated lookup splice a distant ancestor of source to some distant
244 descendent of the parent of target.   At that point we have cross-directory
245 rename holding the lock on parent of source and trying to lock its
246 distant ancestor.  Add a bunch of rmdir() attempts on all directories
247 in between (all of those would fail with -ENOTEMPTY, had they ever gotten
248 the locks) and voila - we have a deadlock.
249 
250 Loop avoidance
251 ==============
252 
253 These operations are guaranteed to avoid loop creation.  Indeed,
254 the only operation that could introduce loops is cross-directory rename.
255 Suppose after the operation there is a loop; since there hadn't been such
256 loops before the operation, at least on of the nodes in that loop must've
257 had its parent changed.  In other words, the loop must be passing through
258 the source or, in case of exchange, possibly the target.
259 
260 Since the operation has succeeded, neither source nor target could have
261 been ancestors of each other.  Therefore the chain of ancestors starting
262 in the parent of source could not have passed through the target and
263 vice versa.  On the other hand, the chain of ancestors of any node could
264 not have passed through the node itself, or we would've had a loop before
265 the operation.  But everything other than source and target has kept
266 the parent after the operation, so the operation does not change the
267 chains of ancestors of (ex-)parents of source and target.  In particular,
268 those chains must end after a finite number of steps.
269 
270 Now consider the loop created by the operation.  It passes through either
271 source or target; the next node in the loop would be the ex-parent of
272 target or source resp.  After that the loop would follow the chain of
273 ancestors of that parent.  But as we have just shown, that chain must
274 end after a finite number of steps, which means that it can't be a part
275 of any loop.  Q.E.D.
276 
277 While this locking scheme works for arbitrary DAGs, it relies on
278 ability to check that directory is a descendent of another object.  Current
279 implementation assumes that directory graph is a tree.  This assumption is
280 also preserved by all operations (cross-directory rename on a tree that would
281 not introduce a cycle will leave it a tree and link() fails for directories).
282 
283 Notice that "directory" in the above == "anything that might have
284 children", so if we are going to introduce hybrid objects we will need
285 either to make sure that link(2) doesn't work for them or to make changes
286 in is_subdir() that would make it work even in presence of such beasts.

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php