1 | /* $NetBSD: ext2fs_rename.c,v 1.11 2016/08/15 18:38:10 jdolecek Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2012 The NetBSD Foundation, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Taylor R Campbell. |
9 | * |
10 | * Redistribution and use in source and binary forms, with or without |
11 | * modification, are permitted provided that the following conditions |
12 | * are met: |
13 | * 1. Redistributions of source code must retain the above copyright |
14 | * notice, this list of conditions and the following disclaimer. |
15 | * 2. Redistributions in binary form must reproduce the above copyright |
16 | * notice, this list of conditions and the following disclaimer in the |
17 | * documentation and/or other materials provided with the distribution. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
29 | * POSSIBILITY OF SUCH DAMAGE. |
30 | */ |
31 | |
32 | /* |
33 | * Ext2fs Rename |
34 | */ |
35 | |
36 | #include <sys/cdefs.h> |
37 | __KERNEL_RCSID(0, "$NetBSD: ext2fs_rename.c,v 1.11 2016/08/15 18:38:10 jdolecek Exp $" ); |
38 | |
39 | #include <sys/param.h> |
40 | #include <sys/buf.h> |
41 | #include <sys/errno.h> |
42 | #include <sys/kauth.h> |
43 | #include <sys/mount.h> |
44 | #include <sys/namei.h> |
45 | #include <sys/vnode.h> |
46 | #include <sys/vnode_if.h> |
47 | #include <sys/dirent.h> |
48 | |
49 | #include <miscfs/genfs/genfs.h> |
50 | |
51 | #include <ufs/ext2fs/ext2fs.h> |
52 | #include <ufs/ext2fs/ext2fs_dir.h> |
53 | #include <ufs/ext2fs/ext2fs_extern.h> |
54 | #include <ufs/ufs/inode.h> |
55 | #include <ufs/ufs/ufs_extern.h> |
56 | #include <ufs/ufs/ufsmount.h> |
57 | |
58 | /* |
59 | * Forward declarations |
60 | */ |
61 | static int ext2fs_sane_rename(struct vnode *, struct componentname *, |
62 | struct vnode *, struct componentname *, |
63 | kauth_cred_t, bool); |
64 | static bool ext2fs_rename_ulr_overlap_p(const struct ufs_lookup_results *, |
65 | const struct ufs_lookup_results *); |
66 | static int ext2fs_rename_recalculate_fulr(struct vnode *, |
67 | struct ufs_lookup_results *, const struct ufs_lookup_results *, |
68 | const struct componentname *); |
69 | static bool ext2fs_rmdired_p(struct vnode *); |
70 | static int ext2fs_read_dotdot(struct vnode *, kauth_cred_t, ino_t *); |
71 | static int ext2fs_rename_replace_dotdot(struct vnode *, |
72 | struct vnode *, struct vnode *, kauth_cred_t); |
73 | static int ext2fs_gro_lock_directory(struct mount *, struct vnode *); |
74 | |
75 | static const struct genfs_rename_ops ext2fs_genfs_rename_ops; |
76 | |
77 | /* |
78 | * ext2fs_sane_rename: The hairiest vop, with the saner API. |
79 | * |
80 | * Arguments: |
81 | * |
82 | * . fdvp (from directory vnode), |
83 | * . fcnp (from component name), |
84 | * . tdvp (to directory vnode), |
85 | * . tcnp (to component name), |
86 | * . cred (credentials structure), and |
87 | * . posixly_correct (flag for behaviour if target & source link same file). |
88 | * |
89 | * fdvp and tdvp may be the same, and must be referenced and unlocked. |
90 | */ |
91 | static int |
92 | ext2fs_sane_rename( |
93 | struct vnode *fdvp, struct componentname *fcnp, |
94 | struct vnode *tdvp, struct componentname *tcnp, |
95 | kauth_cred_t cred, bool posixly_correct) |
96 | { |
97 | struct ufs_lookup_results fulr, tulr; |
98 | |
99 | return genfs_sane_rename(&ext2fs_genfs_rename_ops, |
100 | fdvp, fcnp, &fulr, tdvp, tcnp, &tulr, |
101 | cred, posixly_correct); |
102 | } |
103 | |
104 | /* |
105 | * ext2fs_rename: The hairiest vop, with the insanest API. Defer to |
106 | * genfs_insane_rename immediately. |
107 | */ |
108 | int |
109 | ext2fs_rename(void *v) |
110 | { |
111 | |
112 | return genfs_insane_rename(v, &ext2fs_sane_rename); |
113 | } |
114 | |
115 | /* |
116 | * ext2fs_gro_directory_empty_p: Return true if the directory vp is |
117 | * empty. dvp is its parent. |
118 | * |
119 | * vp and dvp must be locked and referenced. |
120 | */ |
121 | static bool |
122 | ext2fs_gro_directory_empty_p(struct mount *mp, kauth_cred_t cred, |
123 | struct vnode *vp, struct vnode *dvp) |
124 | { |
125 | |
126 | (void)mp; |
127 | KASSERT(mp != NULL); |
128 | KASSERT(vp != NULL); |
129 | KASSERT(dvp != NULL); |
130 | KASSERT(vp != dvp); |
131 | KASSERT(vp->v_mount == mp); |
132 | KASSERT(dvp->v_mount == mp); |
133 | KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); |
134 | KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); |
135 | |
136 | return ext2fs_dirempty(VTOI(vp), VTOI(dvp)->i_number, cred); |
137 | } |
138 | |
139 | /* |
140 | * ext2fs_gro_rename_check_possible: Check whether a rename is possible |
141 | * independent of credentials. |
142 | */ |
143 | static int |
144 | ext2fs_gro_rename_check_possible(struct mount *mp, |
145 | struct vnode *fdvp, struct vnode *fvp, |
146 | struct vnode *tdvp, struct vnode *tvp) |
147 | { |
148 | |
149 | (void)mp; |
150 | KASSERT(mp != NULL); |
151 | KASSERT(fdvp != NULL); |
152 | KASSERT(fvp != NULL); |
153 | KASSERT(tdvp != NULL); |
154 | KASSERT(fdvp != fvp); |
155 | KASSERT(fdvp != tvp); |
156 | KASSERT(tdvp != fvp); |
157 | KASSERT(tdvp != tvp); |
158 | KASSERT(fvp != tvp); |
159 | KASSERT(fdvp->v_type == VDIR); |
160 | KASSERT(tdvp->v_type == VDIR); |
161 | KASSERT(fdvp->v_mount == mp); |
162 | KASSERT(fvp->v_mount == mp); |
163 | KASSERT(tdvp->v_mount == mp); |
164 | KASSERT((tvp == NULL) || (tvp->v_mount == mp)); |
165 | KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); |
166 | KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); |
167 | KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); |
168 | KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); |
169 | |
170 | return genfs_ufslike_rename_check_possible( |
171 | VTOI(fdvp)->i_e2fs_flags, VTOI(fvp)->i_e2fs_flags, |
172 | VTOI(tdvp)->i_e2fs_flags, (tvp? VTOI(tvp)->i_e2fs_flags : 0), |
173 | (tvp != NULL), |
174 | EXT2_IMMUTABLE, EXT2_APPEND); |
175 | } |
176 | |
177 | /* |
178 | * ext2fs_gro_rename_check_permitted: Check whether a rename is |
179 | * permitted given our credentials. |
180 | */ |
181 | static int |
182 | ext2fs_gro_rename_check_permitted(struct mount *mp, kauth_cred_t cred, |
183 | struct vnode *fdvp, struct vnode *fvp, |
184 | struct vnode *tdvp, struct vnode *tvp) |
185 | { |
186 | |
187 | (void)mp; |
188 | KASSERT(mp != NULL); |
189 | KASSERT(fdvp != NULL); |
190 | KASSERT(fvp != NULL); |
191 | KASSERT(tdvp != NULL); |
192 | KASSERT(fdvp != fvp); |
193 | KASSERT(fdvp != tvp); |
194 | KASSERT(tdvp != fvp); |
195 | KASSERT(tdvp != tvp); |
196 | KASSERT(fvp != tvp); |
197 | KASSERT(fdvp->v_type == VDIR); |
198 | KASSERT(tdvp->v_type == VDIR); |
199 | KASSERT(fdvp->v_mount == mp); |
200 | KASSERT(fvp->v_mount == mp); |
201 | KASSERT(tdvp->v_mount == mp); |
202 | KASSERT((tvp == NULL) || (tvp->v_mount == mp)); |
203 | KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); |
204 | KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); |
205 | KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); |
206 | KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); |
207 | |
208 | return genfs_ufslike_rename_check_permitted(cred, |
209 | fdvp, VTOI(fdvp)->i_e2fs_mode, VTOI(fdvp)->i_uid, |
210 | fvp, VTOI(fvp)->i_uid, |
211 | tdvp, VTOI(tdvp)->i_e2fs_mode, VTOI(tdvp)->i_uid, |
212 | tvp, (tvp? VTOI(tvp)->i_uid : 0)); |
213 | } |
214 | |
215 | /* |
216 | * ext2fs_gro_remove_check_possible: Check whether a remove is possible |
217 | * independent of credentials. |
218 | */ |
219 | static int |
220 | ext2fs_gro_remove_check_possible(struct mount *mp, |
221 | struct vnode *dvp, struct vnode *vp) |
222 | { |
223 | |
224 | (void)mp; |
225 | KASSERT(mp != NULL); |
226 | KASSERT(dvp != NULL); |
227 | KASSERT(vp != NULL); |
228 | KASSERT(dvp != vp); |
229 | KASSERT(dvp->v_type == VDIR); |
230 | KASSERT(vp->v_type != VDIR); |
231 | KASSERT(dvp->v_mount == mp); |
232 | KASSERT(vp->v_mount == mp); |
233 | KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); |
234 | KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); |
235 | |
236 | return genfs_ufslike_remove_check_possible( |
237 | VTOI(dvp)->i_e2fs_flags, VTOI(vp)->i_e2fs_flags, |
238 | EXT2_IMMUTABLE, EXT2_APPEND); |
239 | } |
240 | |
241 | /* |
242 | * ext2fs_gro_remove_check_permitted: Check whether a remove is |
243 | * permitted given our credentials. |
244 | */ |
245 | static int |
246 | ext2fs_gro_remove_check_permitted(struct mount *mp, kauth_cred_t cred, |
247 | struct vnode *dvp, struct vnode *vp) |
248 | { |
249 | |
250 | (void)mp; |
251 | KASSERT(mp != NULL); |
252 | KASSERT(dvp != NULL); |
253 | KASSERT(vp != NULL); |
254 | KASSERT(dvp != vp); |
255 | KASSERT(dvp->v_type == VDIR); |
256 | KASSERT(vp->v_type != VDIR); |
257 | KASSERT(dvp->v_mount == mp); |
258 | KASSERT(vp->v_mount == mp); |
259 | KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); |
260 | KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); |
261 | |
262 | return genfs_ufslike_remove_check_permitted(cred, |
263 | dvp, VTOI(dvp)->i_e2fs_mode, VTOI(dvp)->i_uid, |
264 | vp, VTOI(vp)->i_uid); |
265 | } |
266 | |
267 | /* |
268 | * ext2fs_gro_rename: Actually perform the rename operation. |
269 | */ |
270 | static int |
271 | ext2fs_gro_rename(struct mount *mp, kauth_cred_t cred, |
272 | struct vnode *fdvp, struct componentname *fcnp, |
273 | void *fde, struct vnode *fvp, |
274 | struct vnode *tdvp, struct componentname *tcnp, |
275 | void *tde, struct vnode *tvp) |
276 | { |
277 | struct ufs_lookup_results *fulr = fde; |
278 | struct ufs_lookup_results *tulr = tde; |
279 | bool directory_p, reparent_p; |
280 | int error; |
281 | |
282 | (void)mp; |
283 | KASSERT(mp != NULL); |
284 | KASSERT(fdvp != NULL); |
285 | KASSERT(fcnp != NULL); |
286 | KASSERT(fulr != NULL); |
287 | KASSERT(fvp != NULL); |
288 | KASSERT(tdvp != NULL); |
289 | KASSERT(tcnp != NULL); |
290 | KASSERT(tulr != NULL); |
291 | KASSERT(fulr != tulr); |
292 | KASSERT(fdvp != fvp); |
293 | KASSERT(fdvp != tvp); |
294 | KASSERT(tdvp != fvp); |
295 | KASSERT(tdvp != tvp); |
296 | KASSERT(fvp != tvp); |
297 | KASSERT(fdvp->v_mount == mp); |
298 | KASSERT(fvp->v_mount == mp); |
299 | KASSERT(tdvp->v_mount == mp); |
300 | KASSERT((tvp == NULL) || (tvp->v_mount == mp)); |
301 | KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE); |
302 | KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE); |
303 | KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE); |
304 | KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE)); |
305 | |
306 | /* |
307 | * We shall need to temporarily bump the link count, so make |
308 | * sure there is room to do so. |
309 | */ |
310 | if ((nlink_t)VTOI(fvp)->i_e2fs_nlink >= EXT2FS_LINK_MAX) |
311 | return EMLINK; |
312 | |
313 | directory_p = (fvp->v_type == VDIR); |
314 | KASSERT(directory_p == ((VTOI(fvp)->i_e2fs_mode & IFMT) == IFDIR)); |
315 | KASSERT((tvp == NULL) || (directory_p == (tvp->v_type == VDIR))); |
316 | KASSERT((tvp == NULL) || (directory_p == |
317 | ((VTOI(tvp)->i_e2fs_mode & IFMT) == IFDIR))); |
318 | |
319 | reparent_p = (fdvp != tdvp); |
320 | KASSERT(reparent_p == (VTOI(fdvp)->i_number != VTOI(tdvp)->i_number)); |
321 | |
322 | /* |
323 | * Commence hacking of the data on disk. |
324 | */ |
325 | |
326 | /* |
327 | * 1) Bump link count while we're moving stuff |
328 | * around. If we crash somewhere before |
329 | * completing our work, the link count |
330 | * may be wrong, but correctable. |
331 | */ |
332 | |
333 | KASSERT((nlink_t)VTOI(fvp)->i_e2fs_nlink < EXT2FS_LINK_MAX); |
334 | VTOI(fvp)->i_e2fs_nlink++; |
335 | VTOI(fvp)->i_flag |= IN_CHANGE; |
336 | error = ext2fs_update(fvp, NULL, NULL, UPDATE_WAIT); |
337 | if (error) |
338 | goto whymustithurtsomuch; |
339 | |
340 | /* |
341 | * 2) If target doesn't exist, link the target |
342 | * to the source and unlink the source. |
343 | * Otherwise, rewrite the target directory |
344 | * entry to reference the source inode and |
345 | * expunge the original entry's existence. |
346 | */ |
347 | |
348 | if (tvp == NULL) { |
349 | /* |
350 | * Account for ".." in new directory. |
351 | * When source and destination have the same |
352 | * parent we don't fool with the link count. |
353 | */ |
354 | if (directory_p && reparent_p) { |
355 | if ((nlink_t)VTOI(tdvp)->i_e2fs_nlink >= EXT2FS_LINK_MAX) { |
356 | error = EMLINK; |
357 | goto whymustithurtsomuch; |
358 | } |
359 | KASSERT((nlink_t)VTOI(tdvp)->i_e2fs_nlink < EXT2FS_LINK_MAX); |
360 | VTOI(tdvp)->i_e2fs_nlink++; |
361 | VTOI(tdvp)->i_flag |= IN_CHANGE; |
362 | error = ext2fs_update(tdvp, NULL, NULL, UPDATE_WAIT); |
363 | if (error) { |
364 | /* |
365 | * Link count update didn't take -- |
366 | * back out the in-memory link count. |
367 | */ |
368 | KASSERT(0 < VTOI(tdvp)->i_e2fs_nlink); |
369 | VTOI(tdvp)->i_e2fs_nlink--; |
370 | VTOI(tdvp)->i_flag |= IN_CHANGE; |
371 | goto whymustithurtsomuch; |
372 | } |
373 | } |
374 | |
375 | error = ext2fs_direnter(VTOI(fvp), tdvp, tulr, tcnp); |
376 | if (error) { |
377 | if (directory_p && reparent_p) { |
378 | /* |
379 | * Directory update didn't take, but |
380 | * the link count update did -- back |
381 | * out the in-memory link count and the |
382 | * on-disk link count. |
383 | */ |
384 | KASSERT(0 < VTOI(tdvp)->i_e2fs_nlink); |
385 | VTOI(tdvp)->i_e2fs_nlink--; |
386 | VTOI(tdvp)->i_flag |= IN_CHANGE; |
387 | (void)ext2fs_update(tdvp, NULL, NULL, |
388 | UPDATE_WAIT); |
389 | } |
390 | goto whymustithurtsomuch; |
391 | } |
392 | } else { |
393 | if (directory_p) |
394 | /* XXX WTF? Why purge here? Why not purge others? */ |
395 | cache_purge(tdvp); |
396 | |
397 | /* |
398 | * Make the target directory's entry for tcnp point at |
399 | * the source node. |
400 | */ |
401 | error = ext2fs_dirrewrite(VTOI(tdvp), tulr, VTOI(fvp), tcnp); |
402 | if (error) |
403 | goto whymustithurtsomuch; |
404 | |
405 | /* |
406 | * If the source and target are directories, and the |
407 | * target is in the same directory as the source, |
408 | * decrement the link count of the common parent |
409 | * directory, since we are removing the target from |
410 | * that directory. |
411 | */ |
412 | if (directory_p && !reparent_p) { |
413 | KASSERT(fdvp == tdvp); |
414 | /* XXX check, don't kassert */ |
415 | KASSERT(0 < VTOI(tdvp)->i_e2fs_nlink); |
416 | VTOI(tdvp)->i_e2fs_nlink--; |
417 | VTOI(tdvp)->i_flag |= IN_CHANGE; |
418 | } |
419 | |
420 | /* |
421 | * Adjust the link count of the target to |
422 | * reflect the dirrewrite above. If this is |
423 | * a directory it is empty and there are |
424 | * no links to it, so we can squash the inode and |
425 | * any space associated with it. We disallowed |
426 | * renaming over top of a directory with links to |
427 | * it above, as the remaining link would point to |
428 | * a directory without "." or ".." entries. |
429 | */ |
430 | /* XXX check, don't kassert */ |
431 | KASSERT(0 < VTOI(tvp)->i_e2fs_nlink); |
432 | VTOI(tvp)->i_e2fs_nlink--; |
433 | if (directory_p) { |
434 | /* |
435 | * XXX The ext2fs_dirempty call earlier does |
436 | * not guarantee anything about nlink. |
437 | */ |
438 | if (VTOI(tvp)->i_e2fs_nlink != 1) |
439 | ufs_dirbad(VTOI(tvp), (doff_t)0, |
440 | "hard-linked directory" ); |
441 | VTOI(tvp)->i_e2fs_nlink = 0; |
442 | error = ext2fs_truncate(tvp, (off_t)0, IO_SYNC, cred); |
443 | #if 0 /* XXX This branch was not in ext2fs_rename! */ |
444 | if (error) |
445 | goto whymustithurtsomuch; |
446 | #endif |
447 | } |
448 | /* |
449 | * XXX Why is this here, and not above the preceding |
450 | * conditional? |
451 | */ |
452 | VTOI(tvp)->i_flag |= IN_CHANGE; |
453 | } |
454 | |
455 | /* |
456 | * If the source is a directory with a new parent, the link |
457 | * count of the old parent directory must be decremented and |
458 | * ".." set to point to the new parent. |
459 | */ |
460 | if (directory_p && reparent_p) { |
461 | error = ext2fs_rename_replace_dotdot(fvp, fdvp, tdvp, cred); |
462 | if (error) |
463 | goto whymustithurtsomuch; |
464 | |
465 | /* XXX WTF? Why purge here? Why not purge others? */ |
466 | cache_purge(fdvp); |
467 | } |
468 | |
469 | /* |
470 | * 3) Unlink the source. |
471 | */ |
472 | |
473 | /* |
474 | * ext2fs_direnter may compact the directory in the process of |
475 | * inserting a new entry. That may invalidate fulr, which we |
476 | * need in order to remove the old entry. In that case, we |
477 | * need to recalculate what fulr should be. |
478 | */ |
479 | if (!reparent_p && (tvp == NULL) && |
480 | ext2fs_rename_ulr_overlap_p(fulr, tulr)) { |
481 | error = ext2fs_rename_recalculate_fulr(fdvp, fulr, tulr, fcnp); |
482 | #if 0 /* XXX */ |
483 | if (error) /* XXX Try to back out changes? */ |
484 | goto whymustithurtsomuch; |
485 | #endif |
486 | } |
487 | |
488 | error = ext2fs_dirremove(fdvp, fulr, fcnp); |
489 | if (error) |
490 | goto whymustithurtsomuch; |
491 | |
492 | /* |
493 | * XXX Perhaps this should go at the top, in case the file |
494 | * system is modified but incompletely so because of an |
495 | * intermediate error. |
496 | */ |
497 | genfs_rename_knote(fdvp, fvp, tdvp, tvp, |
498 | ((tvp != NULL) && (VTOI(tvp)->i_e2fs_nlink == 0))); |
499 | #if 0 /* XXX */ |
500 | genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp); |
501 | #endif |
502 | |
503 | whymustithurtsomuch: |
504 | KASSERT(0 < VTOI(fvp)->i_e2fs_nlink); |
505 | VTOI(fvp)->i_e2fs_nlink--; |
506 | VTOI(fvp)->i_flag |= IN_CHANGE; |
507 | return error; |
508 | } |
509 | |
510 | /* |
511 | * ext2fs_rename_ulr_overlap_p: True iff tulr overlaps with fulr so |
512 | * that entering a directory entry at tulr may move fulr. |
513 | */ |
514 | static bool |
515 | ext2fs_rename_ulr_overlap_p(const struct ufs_lookup_results *fulr, |
516 | const struct ufs_lookup_results *tulr) |
517 | { |
518 | doff_t from_prev_start, from_prev_end, to_start, to_end; |
519 | |
520 | KASSERT(fulr != NULL); |
521 | KASSERT(tulr != NULL); |
522 | KASSERT(fulr != tulr); |
523 | |
524 | /* |
525 | * fulr is from a DELETE lookup, so fulr->ulr_count is the size |
526 | * of the preceding entry (d_reclen). |
527 | */ |
528 | from_prev_end = fulr->ulr_offset; |
529 | KASSERT(fulr->ulr_count <= from_prev_end); |
530 | from_prev_start = (from_prev_end - fulr->ulr_count); |
531 | |
532 | /* |
533 | * tulr is from a RENAME lookup, so tulr->ulr_count is the size |
534 | * of the free space for an entry that we are about to fill. |
535 | */ |
536 | to_start = tulr->ulr_offset; |
537 | KASSERT(tulr->ulr_count < (EXT2FS_MAXDIRSIZE - to_start)); |
538 | to_end = (to_start + tulr->ulr_count); |
539 | |
540 | return |
541 | (((to_start <= from_prev_start) && (from_prev_start < to_end)) || |
542 | ((to_start <= from_prev_end) && (from_prev_end < to_end))); |
543 | } |
544 | |
545 | /* |
546 | * ext2fs_rename_recalculate_fulr: If we have just entered a directory |
547 | * into dvp at tulr, and we were about to remove one at fulr for an |
548 | * entry named fcnp, fulr may be invalid. So, if necessary, |
549 | * recalculate it. |
550 | */ |
551 | static int |
552 | ext2fs_rename_recalculate_fulr(struct vnode *dvp, |
553 | struct ufs_lookup_results *fulr, const struct ufs_lookup_results *tulr, |
554 | const struct componentname *fcnp) |
555 | { |
556 | struct mount *mp; |
557 | struct ufsmount *ump; |
558 | /* XXX int is a silly type for this; blame ufsmount::um_dirblksiz. */ |
559 | int dirblksiz; |
560 | doff_t search_start, search_end; |
561 | doff_t offset; /* Offset of entry we're examining. */ |
562 | struct buf *bp; /* I/O block we're examining. */ |
563 | char *dirbuf; /* Pointer into directory at search_start. */ |
564 | struct ext2fs_direct *ep; /* Pointer to the entry we're examining. */ |
565 | /* XXX direct::d_reclen is 16-bit; |
566 | * ufs_lookup_results::ulr_reclen is 32-bit. Blah. */ |
567 | uint32_t reclen; /* Length of the entry we're examining. */ |
568 | uint32_t prev_reclen; /* Length of the preceding entry. */ |
569 | int error; |
570 | |
571 | KASSERT(dvp != NULL); |
572 | KASSERT(dvp->v_mount != NULL); |
573 | KASSERT(VTOI(dvp) != NULL); |
574 | KASSERT(fulr != NULL); |
575 | KASSERT(tulr != NULL); |
576 | KASSERT(fulr != tulr); |
577 | KASSERT(ext2fs_rename_ulr_overlap_p(fulr, tulr)); |
578 | |
579 | mp = dvp->v_mount; |
580 | ump = VFSTOUFS(mp); |
581 | KASSERT(ump != NULL); |
582 | KASSERT(ump == VTOI(dvp)->i_ump); |
583 | |
584 | dirblksiz = ump->um_dirblksiz; |
585 | KASSERT(0 < dirblksiz); |
586 | KASSERT((dirblksiz & (dirblksiz - 1)) == 0); |
587 | |
588 | /* A directory block may not span across multiple I/O blocks. */ |
589 | KASSERT(dirblksiz <= mp->mnt_stat.f_iosize); |
590 | |
591 | /* Find the bounds of the search. */ |
592 | search_start = tulr->ulr_offset; |
593 | KASSERT(fulr->ulr_reclen < (EXT2FS_MAXDIRSIZE - fulr->ulr_offset)); |
594 | search_end = (fulr->ulr_offset + fulr->ulr_reclen); |
595 | |
596 | /* Compaction must happen only within a directory block. (*) */ |
597 | KASSERT(search_start <= search_end); |
598 | KASSERT((search_end - (search_start &~ (dirblksiz - 1))) <= dirblksiz); |
599 | |
600 | dirbuf = NULL; |
601 | bp = NULL; |
602 | error = ext2fs_blkatoff(dvp, (off_t)search_start, &dirbuf, &bp); |
603 | if (error) |
604 | return error; |
605 | KASSERT(dirbuf != NULL); |
606 | KASSERT(bp != NULL); |
607 | |
608 | /* |
609 | * Guarantee we sha'n't go past the end of the buffer we got. |
610 | * dirbuf is bp->b_data + (search_start & (iosize - 1)), and |
611 | * the valid range is [bp->b_data, bp->b_data + bp->b_bcount). |
612 | */ |
613 | KASSERT((search_end - search_start) <= |
614 | (bp->b_bcount - (search_start & (mp->mnt_stat.f_iosize - 1)))); |
615 | |
616 | prev_reclen = fulr->ulr_count; |
617 | offset = search_start; |
618 | |
619 | /* |
620 | * Search from search_start to search_end for the entry matching |
621 | * fcnp, which must be there because we found it before and it |
622 | * should only at most have moved earlier. |
623 | */ |
624 | for (;;) { |
625 | KASSERT(search_start <= offset); |
626 | KASSERT(offset < search_end); |
627 | |
628 | /* |
629 | * Examine the directory entry at offset. |
630 | */ |
631 | ep = (struct ext2fs_direct *) |
632 | (dirbuf + (offset - search_start)); |
633 | reclen = fs2h16(ep->e2d_reclen); |
634 | |
635 | if (ep->e2d_ino == 0) |
636 | goto next; /* Entry is unused. */ |
637 | |
638 | if (fs2h32(ep->e2d_ino) == UFS_WINO) |
639 | goto next; /* Entry is whiteout. */ |
640 | |
641 | if (fcnp->cn_namelen != ep->e2d_namlen) |
642 | goto next; /* Wrong name length. */ |
643 | |
644 | if (memcmp(ep->e2d_name, fcnp->cn_nameptr, fcnp->cn_namelen)) |
645 | goto next; /* Wrong name. */ |
646 | |
647 | /* Got it! */ |
648 | break; |
649 | |
650 | next: |
651 | if (! ((reclen < search_end) && |
652 | (offset < (search_end - reclen)))) { |
653 | brelse(bp, 0); |
654 | return EIO; /* XXX Panic? What? */ |
655 | } |
656 | |
657 | /* We may not move past the search end. */ |
658 | KASSERT(reclen < search_end); |
659 | KASSERT(offset < (search_end - reclen)); |
660 | |
661 | /* |
662 | * We may not move across a directory block boundary; |
663 | * see (*) above. |
664 | */ |
665 | KASSERT((offset &~ (dirblksiz - 1)) == |
666 | ((offset + reclen) &~ (dirblksiz - 1))); |
667 | |
668 | prev_reclen = reclen; |
669 | offset += reclen; |
670 | } |
671 | |
672 | /* |
673 | * Found the entry. Record where. |
674 | */ |
675 | fulr->ulr_offset = offset; |
676 | fulr->ulr_reclen = reclen; |
677 | |
678 | /* |
679 | * Record the preceding record length, but not if we're at the |
680 | * start of a directory block. |
681 | */ |
682 | fulr->ulr_count = ((offset & (dirblksiz - 1))? prev_reclen : 0); |
683 | |
684 | brelse(bp, 0); |
685 | return 0; |
686 | } |
687 | |
688 | /* |
689 | * ext2fs_gro_remove: Rename an object over another link to itself, |
690 | * effectively removing just the original link. |
691 | */ |
692 | static int |
693 | ext2fs_gro_remove(struct mount *mp, kauth_cred_t cred, |
694 | struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp) |
695 | { |
696 | struct ufs_lookup_results *ulr = de; |
697 | int error; |
698 | |
699 | (void)mp; |
700 | KASSERT(mp != NULL); |
701 | KASSERT(dvp != NULL); |
702 | KASSERT(cnp != NULL); |
703 | KASSERT(ulr != NULL); |
704 | KASSERT(vp != NULL); |
705 | KASSERT(dvp != vp); |
706 | KASSERT(dvp->v_mount == mp); |
707 | KASSERT(vp->v_mount == mp); |
708 | KASSERT(dvp->v_type == VDIR); |
709 | KASSERT(vp->v_type != VDIR); |
710 | KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); |
711 | KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); |
712 | |
713 | error = ext2fs_dirremove(dvp, ulr, cnp); |
714 | if (error) |
715 | return error; |
716 | |
717 | KASSERT(0 < VTOI(vp)->i_e2fs_nlink); |
718 | VTOI(vp)->i_e2fs_nlink--; |
719 | VTOI(vp)->i_flag |= IN_CHANGE; |
720 | |
721 | VN_KNOTE(dvp, NOTE_WRITE); |
722 | VN_KNOTE(vp, (VTOI(vp)->i_e2fs_nlink? NOTE_LINK : NOTE_DELETE)); |
723 | |
724 | return 0; |
725 | } |
726 | |
727 | /* |
728 | * ext2fs_gro_lookup: Look up and save the lookup results. |
729 | */ |
730 | static int |
731 | ext2fs_gro_lookup(struct mount *mp, struct vnode *dvp, |
732 | struct componentname *cnp, void *de_ret, struct vnode **vp_ret) |
733 | { |
734 | struct ufs_lookup_results *ulr_ret = de_ret; |
735 | struct vnode *vp; |
736 | int error; |
737 | |
738 | (void)mp; |
739 | KASSERT(mp != NULL); |
740 | KASSERT(dvp != NULL); |
741 | KASSERT(cnp != NULL); |
742 | KASSERT(ulr_ret != NULL); |
743 | KASSERT(vp_ret != NULL); |
744 | KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); |
745 | |
746 | /* Kludge cargo-culted from dholland's ufs_rename. */ |
747 | cnp->cn_flags &=~ MODMASK; |
748 | cnp->cn_flags |= (LOCKPARENT | LOCKLEAF); |
749 | |
750 | error = relookup(dvp, &vp, cnp, 0 /* dummy */); |
751 | if ((error == 0) && (vp == NULL)) { |
752 | error = ENOENT; |
753 | goto out; |
754 | } else if (error) { |
755 | return error; |
756 | } |
757 | |
758 | /* |
759 | * Thanks to VFS insanity, relookup locks vp, which screws us |
760 | * in various ways. |
761 | */ |
762 | KASSERT(vp != NULL); |
763 | VOP_UNLOCK(vp); |
764 | |
765 | out: *ulr_ret = VTOI(dvp)->i_crap; |
766 | *vp_ret = vp; |
767 | return error; |
768 | } |
769 | |
770 | /* |
771 | * ext2fs_rmdired_p: Check whether the directory vp has been rmdired. |
772 | * |
773 | * vp must be locked and referenced. |
774 | */ |
775 | static bool |
776 | ext2fs_rmdired_p(struct vnode *vp) |
777 | { |
778 | |
779 | KASSERT(vp != NULL); |
780 | KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); |
781 | KASSERT(vp->v_type == VDIR); |
782 | |
783 | /* XXX Is this correct? */ |
784 | return ext2fs_size(VTOI(vp)) == 0; |
785 | } |
786 | |
787 | /* |
788 | * ext2fs_gro_genealogy: Analyze the genealogy of the source and target |
789 | * directories. |
790 | */ |
791 | static int |
792 | ext2fs_gro_genealogy(struct mount *mp, kauth_cred_t cred, |
793 | struct vnode *fdvp, struct vnode *tdvp, |
794 | struct vnode **intermediate_node_ret) |
795 | { |
796 | struct vnode *vp, *dvp; |
797 | ino_t dotdot_ino = -1; /* XXX gcc 4.8.3: maybe-uninitialized */ |
798 | int error; |
799 | |
800 | KASSERT(mp != NULL); |
801 | KASSERT(fdvp != NULL); |
802 | KASSERT(tdvp != NULL); |
803 | KASSERT(fdvp != tdvp); |
804 | KASSERT(intermediate_node_ret != NULL); |
805 | KASSERT(fdvp->v_mount == mp); |
806 | KASSERT(tdvp->v_mount == mp); |
807 | KASSERT(fdvp->v_type == VDIR); |
808 | KASSERT(tdvp->v_type == VDIR); |
809 | |
810 | /* |
811 | * We need to provisionally lock tdvp to keep rmdir from |
812 | * deleting it -- or any ancestor -- at an inopportune moment. |
813 | */ |
814 | error = ext2fs_gro_lock_directory(mp, tdvp); |
815 | if (error) |
816 | return error; |
817 | |
818 | vp = tdvp; |
819 | vref(vp); |
820 | |
821 | for (;;) { |
822 | KASSERT(vp != NULL); |
823 | KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE); |
824 | KASSERT(vp->v_mount == mp); |
825 | KASSERT(vp->v_type == VDIR); |
826 | KASSERT(!ext2fs_rmdired_p(vp)); |
827 | |
828 | /* Did we hit the root without finding fdvp? */ |
829 | if (VTOI(vp)->i_number == UFS_ROOTINO) { |
830 | vput(vp); |
831 | *intermediate_node_ret = NULL; |
832 | return 0; |
833 | } |
834 | |
835 | error = ext2fs_read_dotdot(vp, cred, &dotdot_ino); |
836 | if (error) { |
837 | vput(vp); |
838 | return error; |
839 | } |
840 | |
841 | /* Did we find that fdvp is an ancestor of tdvp? */ |
842 | if (VTOI(fdvp)->i_number == dotdot_ino) { |
843 | /* Unlock vp, but keep it referenced. */ |
844 | VOP_UNLOCK(vp); |
845 | *intermediate_node_ret = vp; |
846 | return 0; |
847 | } |
848 | |
849 | /* Neither -- keep ascending the family tree. */ |
850 | error = vcache_get(mp, &dotdot_ino, sizeof(dotdot_ino), &dvp); |
851 | vput(vp); |
852 | if (error) |
853 | return error; |
854 | error = vn_lock(dvp, LK_EXCLUSIVE); |
855 | if (error) { |
856 | vrele(dvp); |
857 | return error; |
858 | } |
859 | |
860 | KASSERT(dvp != NULL); |
861 | KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE); |
862 | vp = dvp; |
863 | |
864 | if (vp->v_type != VDIR) { |
865 | /* |
866 | * XXX Panic? Print a warning? Can this |
867 | * happen if we lose the race I suspect to |
868 | * exist above, and the `..' inode number has |
869 | * been recycled? |
870 | */ |
871 | vput(vp); |
872 | return ENOTDIR; |
873 | } |
874 | |
875 | if (ext2fs_rmdired_p(vp)) { |
876 | vput(vp); |
877 | return ENOENT; |
878 | } |
879 | } |
880 | } |
881 | |
882 | /* |
883 | * ext2fs_read_dotdot: Store in *ino_ret the inode number of the parent |
884 | * of the directory vp. |
885 | */ |
886 | static int |
887 | ext2fs_read_dotdot(struct vnode *vp, kauth_cred_t cred, ino_t *ino_ret) |
888 | { |
889 | struct ext2fs_dirtemplate dirbuf; |
890 | int error; |
891 | |
892 | KASSERT(vp != NULL); |
893 | KASSERT(ino_ret != NULL); |
894 | KASSERT(vp->v_type == VDIR); |
895 | |
896 | error = ufs_bufio(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0, |
897 | IO_NODELOCKED, cred, NULL, NULL); |
898 | if (error) |
899 | return error; |
900 | |
901 | if (dirbuf.dotdot_namlen != 2 || |
902 | dirbuf.dotdot_name[0] != '.' || |
903 | dirbuf.dotdot_name[1] != '.') |
904 | /* XXX Panic? Print warning? */ |
905 | return ENOTDIR; |
906 | |
907 | *ino_ret = fs2h32(dirbuf.dotdot_ino); |
908 | return 0; |
909 | } |
910 | |
911 | /* |
912 | * ext2fs_rename_replace_dotdot: Change the target of the `..' entry of |
913 | * the directory vp from fdvp to tdvp. |
914 | */ |
915 | static int |
916 | ext2fs_rename_replace_dotdot(struct vnode *vp, |
917 | struct vnode *fdvp, struct vnode *tdvp, |
918 | kauth_cred_t cred) |
919 | { |
920 | struct ext2fs_dirtemplate dirbuf; |
921 | int error; |
922 | |
923 | /* XXX Does it make sense to do this before the sanity checks below? */ |
924 | KASSERT(0 < VTOI(fdvp)->i_e2fs_nlink); |
925 | VTOI(fdvp)->i_e2fs_nlink--; |
926 | VTOI(fdvp)->i_flag |= IN_CHANGE; |
927 | |
928 | error = ufs_bufio(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0, |
929 | IO_NODELOCKED, cred, NULL, NULL); |
930 | if (error) |
931 | return error; |
932 | |
933 | if (dirbuf.dotdot_namlen != 2 || |
934 | dirbuf.dotdot_name[0] != '.' || |
935 | dirbuf.dotdot_name[1] != '.') { |
936 | ufs_dirbad(VTOI(vp), (doff_t)12, "bad `..' entry" ); |
937 | return 0; |
938 | } |
939 | |
940 | if (fs2h32(dirbuf.dotdot_ino) != VTOI(fdvp)->i_number) { |
941 | ufs_dirbad(VTOI(vp), (doff_t)12, |
942 | "`..' does not point at parent" ); |
943 | return 0; |
944 | } |
945 | |
946 | dirbuf.dotdot_ino = h2fs32(VTOI(tdvp)->i_number); |
947 | /* XXX WTF? Why not check error? */ |
948 | (void)ufs_bufio(UIO_WRITE, vp, &dirbuf, sizeof dirbuf, (off_t)0, |
949 | (IO_NODELOCKED | IO_SYNC), cred, NULL, NULL); |
950 | |
951 | return 0; |
952 | } |
953 | |
954 | /* |
955 | * ext2fs_gro_lock_directory: Lock the directory vp, but fail if it has |
956 | * been rmdir'd. |
957 | */ |
958 | static int |
959 | ext2fs_gro_lock_directory(struct mount *mp, struct vnode *vp) |
960 | { |
961 | |
962 | (void)mp; |
963 | KASSERT(mp != NULL); |
964 | KASSERT(vp != NULL); |
965 | KASSERT(vp->v_mount == mp); |
966 | |
967 | vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); |
968 | |
969 | if (ext2fs_rmdired_p(vp)) { |
970 | VOP_UNLOCK(vp); |
971 | return ENOENT; |
972 | } |
973 | |
974 | return 0; |
975 | } |
976 | |
977 | static const struct genfs_rename_ops ext2fs_genfs_rename_ops = { |
978 | .gro_directory_empty_p = ext2fs_gro_directory_empty_p, |
979 | .gro_rename_check_possible = ext2fs_gro_rename_check_possible, |
980 | .gro_rename_check_permitted = ext2fs_gro_rename_check_permitted, |
981 | .gro_remove_check_possible = ext2fs_gro_remove_check_possible, |
982 | .gro_remove_check_permitted = ext2fs_gro_remove_check_permitted, |
983 | .gro_rename = ext2fs_gro_rename, |
984 | .gro_remove = ext2fs_gro_remove, |
985 | .gro_lookup = ext2fs_gro_lookup, |
986 | .gro_genealogy = ext2fs_gro_genealogy, |
987 | .gro_lock_directory = ext2fs_gro_lock_directory, |
988 | }; |
989 | |