1 /* $NetBSD: msdosfs_rename.c,v 1.4 2024/05/04 05:49:39 mlelstv Exp $ */
2
3 /*-
4 * Copyright (c) 2011 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 * MS-DOS FS Rename
34 */
35
36 #include <sys/cdefs.h>
37 __KERNEL_RCSID(0, "$NetBSD: msdosfs_rename.c,v 1.4 2024/05/04 05:49:39 mlelstv 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/namei.h>
44 #include <sys/vnode.h>
45 #include <sys/vnode_if.h>
46
47 #include <miscfs/genfs/genfs.h>
48
49 #include <fs/msdosfs/bpb.h>
50 #include <fs/msdosfs/direntry.h>
51 #include <fs/msdosfs/denode.h>
52 #include <fs/msdosfs/msdosfsmount.h>
53 #include <fs/msdosfs/fat.h>
54
55 /*
56 * Forward declarations
57 */
58
59 static int msdosfs_sane_rename(struct vnode *, struct componentname *,
60 struct vnode *, struct componentname *,
61 kauth_cred_t, bool);
62 static bool msdosfs_rmdired_p(struct vnode *);
63 static int msdosfs_read_dotdot(struct vnode *, kauth_cred_t, unsigned long *);
64 static int msdosfs_rename_replace_dotdot(struct vnode *,
65 struct vnode *, struct vnode *, kauth_cred_t);
66 static int msdosfs_gro_lock_directory(struct mount *, struct vnode *);
67
68 static const struct genfs_rename_ops msdosfs_genfs_rename_ops;
69
70 /*
71 * msdosfs_rename: The hairiest vop, with the insanest API.
72 *
73 * Arguments:
74 *
75 * . fdvp (from directory vnode),
76 * . fvp (from vnode),
77 * . fcnp (from component name),
78 * . tdvp (to directory vnode),
79 * . tvp (to vnode, or NULL), and
80 * . tcnp (to component name).
81 *
82 * Any pair of vnode parameters may have the same vnode.
83 *
84 * On entry,
85 *
86 * . fdvp, fvp, tdvp, and tvp are referenced,
87 * . fdvp and fvp are unlocked, and
88 * . tdvp and tvp (if nonnull) are locked.
89 *
90 * On exit,
91 *
92 * . fdvp, fvp, tdvp, and tvp (if nonnull) are unreferenced, and
93 * . tdvp and tvp are unlocked.
94 */
95 int
msdosfs_rename(void * v)96 msdosfs_rename(void *v)
97 {
98 struct vop_rename_args /* {
99 struct vnode *a_fdvp;
100 struct vnode *a_fvp;
101 struct componentname *a_fcnp;
102 struct vnode *a_tdvp;
103 struct vnode *a_tvp;
104 struct componentname *a_tcnp;
105 } */ *ap = v;
106 struct vnode *fdvp = ap->a_fdvp;
107 struct vnode *fvp = ap->a_fvp;
108 struct componentname *fcnp = ap->a_fcnp;
109 struct vnode *tdvp = ap->a_tdvp;
110 struct vnode *tvp = ap->a_tvp;
111 struct componentname *tcnp = ap->a_tcnp;
112 kauth_cred_t cred;
113 int error;
114
115 KASSERT(fdvp != NULL);
116 KASSERT(fvp != NULL);
117 KASSERT(fcnp != NULL);
118 KASSERT(fcnp->cn_nameptr != NULL);
119 KASSERT(tdvp != NULL);
120 KASSERT(tcnp != NULL);
121 KASSERT(fcnp->cn_nameptr != NULL);
122 /* KASSERT(VOP_ISLOCKED(fdvp) != LK_EXCLUSIVE); */
123 /* KASSERT(VOP_ISLOCKED(fvp) != LK_EXCLUSIVE); */
124 KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE);
125 KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE));
126 KASSERT(fdvp->v_type == VDIR);
127 KASSERT(tdvp->v_type == VDIR);
128
129 cred = fcnp->cn_cred;
130 KASSERT(kauth_cred_uidmatch(cred, tcnp->cn_cred));
131
132 /*
133 * Sanitize our world from the VFS insanity. Unlock the target
134 * directory and node, which are locked. Release the children,
135 * which are referenced. Check for rename("x", "y/."), which
136 * it is our responsibility to reject, not the caller's. (But
137 * the caller does reject rename("x/.", "y"). Go figure.)
138 */
139
140 VOP_UNLOCK(tdvp);
141 if ((tvp != NULL) && (tvp != tdvp))
142 VOP_UNLOCK(tvp);
143
144 vrele(fvp);
145 if (tvp != NULL)
146 vrele(tvp);
147
148 if (tvp == tdvp) {
149 error = EINVAL;
150 goto out;
151 }
152
153 error = msdosfs_sane_rename(fdvp, fcnp, tdvp, tcnp, cred, false);
154
155 out: /*
156 * All done, whether with success or failure. Release the
157 * directory nodes now, as the caller expects from the VFS
158 * protocol.
159 */
160 vrele(fdvp);
161 vrele(tdvp);
162
163 return error;
164 }
165
166 /*
167 * msdosfs_sane_rename: The hairiest vop, with the saner API.
168 *
169 * Arguments:
170 *
171 * . fdvp (from directory vnode),
172 * . fcnp (from component name),
173 * . tdvp (to directory vnode), and
174 * . tcnp (to component name).
175 *
176 * fdvp and tdvp must be referenced and unlocked.
177 */
178 static int
msdosfs_sane_rename(struct vnode * fdvp,struct componentname * fcnp,struct vnode * tdvp,struct componentname * tcnp,kauth_cred_t cred,bool posixly_correct)179 msdosfs_sane_rename(
180 struct vnode *fdvp, struct componentname *fcnp,
181 struct vnode *tdvp, struct componentname *tcnp,
182 kauth_cred_t cred, bool posixly_correct)
183 {
184 struct msdosfs_lookup_results fmlr, tmlr;
185
186 return genfs_sane_rename(&msdosfs_genfs_rename_ops,
187 fdvp, fcnp, &fmlr, tdvp, tcnp, &tmlr,
188 cred, posixly_correct);
189 }
190
191 /*
192 * msdosfs_gro_directory_empty_p: Return true if the directory vp is
193 * empty. dvp is its parent.
194 *
195 * vp and dvp must be locked and referenced.
196 */
197 static bool
msdosfs_gro_directory_empty_p(struct mount * mp,kauth_cred_t cred,struct vnode * vp,struct vnode * dvp)198 msdosfs_gro_directory_empty_p(struct mount *mp, kauth_cred_t cred,
199 struct vnode *vp, struct vnode *dvp)
200 {
201
202 (void)mp;
203 (void)cred;
204 (void)dvp;
205 KASSERT(mp != NULL);
206 KASSERT(vp != NULL);
207 KASSERT(dvp != NULL);
208 KASSERT(vp != dvp);
209 KASSERT(vp->v_mount == mp);
210 KASSERT(dvp->v_mount == mp);
211 KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE);
212 KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
213
214 return msdosfs_dosdirempty(VTODE(vp));
215 }
216
217 /*
218 * Return a UFS-like mode for vp.
219 */
220 static mode_t
msdosfs_vnode_mode(struct vnode * vp)221 msdosfs_vnode_mode(struct vnode *vp)
222 {
223 struct msdosfsmount *pmp;
224 mode_t mode, mask;
225
226 KASSERT(vp != NULL);
227
228 pmp = VTODE(vp)->de_pmp;
229 KASSERT(pmp != NULL);
230
231 if (VTODE(vp)->de_Attributes & ATTR_READONLY)
232 mode = S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
233 else
234 mode = S_IRWXU|S_IRWXG|S_IRWXO;
235
236 if (vp->v_type == VDIR)
237 mask = pmp->pm_dirmask;
238 else
239 mask = pmp->pm_mask;
240
241 return (mode & mask);
242 }
243
244 /*
245 * msdosfs_gro_rename_check_possible: Check whether renaming fvp in fdvp
246 * to tvp in tdvp is possible independent of credentials.
247 */
248 static int
msdosfs_gro_rename_check_possible(struct mount * mp,struct vnode * fdvp,struct vnode * fvp,struct vnode * tdvp,struct vnode * tvp)249 msdosfs_gro_rename_check_possible(struct mount *mp,
250 struct vnode *fdvp, struct vnode *fvp,
251 struct vnode *tdvp, struct vnode *tvp)
252 {
253
254 (void)mp;
255 (void)fdvp;
256 (void)fvp;
257 (void)tdvp;
258 (void)tvp;
259 KASSERT(mp != NULL);
260 KASSERT(fdvp != NULL);
261 KASSERT(fvp != NULL);
262 KASSERT(tdvp != NULL);
263 KASSERT(fdvp != fvp);
264 KASSERT(fdvp != tvp);
265 KASSERT(tdvp != fvp);
266 KASSERT(tdvp != tvp);
267 KASSERT(fvp != tvp);
268 KASSERT(fdvp->v_mount == mp);
269 KASSERT(fvp->v_mount == mp);
270 KASSERT(tdvp->v_mount == mp);
271 KASSERT((tvp == NULL) || (tvp->v_mount == mp));
272 KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE);
273 KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE);
274 KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE);
275 KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE));
276
277 /* It's always possible: no error. */
278 return 0;
279 }
280
281 /*
282 * msdosfs_gro_rename_check_permitted: ...
283 */
284 static int
msdosfs_gro_rename_check_permitted(struct mount * mp,kauth_cred_t cred,struct vnode * fdvp,struct vnode * fvp,struct vnode * tdvp,struct vnode * tvp)285 msdosfs_gro_rename_check_permitted(struct mount *mp, kauth_cred_t cred,
286 struct vnode *fdvp, struct vnode *fvp,
287 struct vnode *tdvp, struct vnode *tvp)
288 {
289 struct msdosfsmount *pmp;
290
291 KASSERT(mp != NULL);
292 KASSERT(fdvp != NULL);
293 KASSERT(fvp != NULL);
294 KASSERT(tdvp != NULL);
295 KASSERT(fdvp != fvp);
296 KASSERT(fdvp != tvp);
297 KASSERT(tdvp != fvp);
298 KASSERT(tdvp != tvp);
299 KASSERT(fvp != tvp);
300 KASSERT(fdvp->v_mount == mp);
301 KASSERT(fvp->v_mount == mp);
302 KASSERT(tdvp->v_mount == mp);
303 KASSERT((tvp == NULL) || (tvp->v_mount == mp));
304 KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE);
305 KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE);
306 KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE);
307 KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE));
308
309 pmp = VFSTOMSDOSFS(mp);
310 KASSERT(pmp != NULL);
311
312 return genfs_ufslike_rename_check_permitted(cred,
313 fdvp, msdosfs_vnode_mode(fdvp), pmp->pm_uid,
314 fvp, pmp->pm_uid,
315 tdvp, msdosfs_vnode_mode(tdvp), pmp->pm_uid,
316 tvp, (tvp? pmp->pm_uid : 0));
317 }
318
319 /*
320 * msdosfs_gro_remove_check_possible: ...
321 */
322 static int
msdosfs_gro_remove_check_possible(struct mount * mp,struct vnode * dvp,struct vnode * vp)323 msdosfs_gro_remove_check_possible(struct mount *mp,
324 struct vnode *dvp, struct vnode *vp)
325 {
326
327 KASSERT(mp != NULL);
328 KASSERT(dvp != NULL);
329 KASSERT(vp != NULL);
330 KASSERT(dvp != vp);
331 KASSERT(dvp->v_mount == mp);
332 KASSERT(vp->v_mount == mp);
333 KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
334 KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE);
335
336 /* It's always possible: no error. */
337 return 0;
338 }
339
340 /*
341 * msdosfs_gro_remove_check_permitted: ...
342 */
343 static int
msdosfs_gro_remove_check_permitted(struct mount * mp,kauth_cred_t cred,struct vnode * dvp,struct vnode * vp)344 msdosfs_gro_remove_check_permitted(struct mount *mp, kauth_cred_t cred,
345 struct vnode *dvp, struct vnode *vp)
346 {
347 struct msdosfsmount *pmp;
348
349 KASSERT(mp != NULL);
350 KASSERT(dvp != NULL);
351 KASSERT(vp != NULL);
352 KASSERT(dvp != vp);
353 KASSERT(dvp->v_mount == mp);
354 KASSERT(vp->v_mount == mp);
355 KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
356 KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE);
357
358 pmp = VFSTOMSDOSFS(mp);
359 KASSERT(pmp != NULL);
360
361 return genfs_ufslike_remove_check_permitted(cred,
362 dvp, msdosfs_vnode_mode(dvp), pmp->pm_uid, vp, pmp->pm_uid);
363 }
364
365 /*
366 * msdosfs_gro_rename: Actually perform the rename operation.
367 */
368 static int
msdosfs_gro_rename(struct mount * mp,kauth_cred_t cred,struct vnode * fdvp,struct componentname * fcnp,void * fde,struct vnode * fvp,struct vnode * tdvp,struct componentname * tcnp,void * tde,struct vnode * tvp,nlink_t * tvp_nlinkp)369 msdosfs_gro_rename(struct mount *mp, kauth_cred_t cred,
370 struct vnode *fdvp, struct componentname *fcnp,
371 void *fde, struct vnode *fvp,
372 struct vnode *tdvp, struct componentname *tcnp,
373 void *tde, struct vnode *tvp, nlink_t *tvp_nlinkp)
374 {
375 struct msdosfs_lookup_results *fmlr = fde;
376 struct msdosfs_lookup_results *tmlr = tde;
377 struct msdosfsmount *pmp;
378 bool directory_p, reparent_p;
379 unsigned char toname[12], oldname[12];
380 int error;
381
382 KASSERT(mp != NULL);
383 KASSERT(fdvp != NULL);
384 KASSERT(fcnp != NULL);
385 KASSERT(fmlr != NULL);
386 KASSERT(fvp != NULL);
387 KASSERT(tdvp != NULL);
388 KASSERT(tcnp != NULL);
389 KASSERT(tmlr != NULL);
390 KASSERT(fmlr != tmlr);
391 KASSERT(fdvp != fvp);
392 KASSERT(fdvp != tvp);
393 KASSERT(tdvp != fvp);
394 KASSERT(tdvp != tvp);
395 KASSERT(fvp != tvp);
396 KASSERT(fdvp->v_mount == mp);
397 KASSERT(fvp->v_mount == mp);
398 KASSERT(tdvp->v_mount == mp);
399 KASSERT((tvp == NULL) || (tvp->v_mount == mp));
400 KASSERT(VOP_ISLOCKED(fdvp) == LK_EXCLUSIVE);
401 KASSERT(VOP_ISLOCKED(fvp) == LK_EXCLUSIVE);
402 KASSERT(VOP_ISLOCKED(tdvp) == LK_EXCLUSIVE);
403 KASSERT((tvp == NULL) || (VOP_ISLOCKED(tvp) == LK_EXCLUSIVE));
404
405 /*
406 * We shall need to temporarily bump the reference count, so
407 * make sure there is room to do so.
408 */
409 if (VTODE(fvp)->de_refcnt >= LONG_MAX)
410 return EMLINK;
411
412 /*
413 * XXX There is a pile of logic here to handle a voodoo flag
414 * DE_RENAME. I think this is a vestige of days when the file
415 * system hackers didn't understand concurrency or race
416 * conditions; I believe it serves no useful function
417 * whatsoever.
418 */
419
420 directory_p = (fvp->v_type == VDIR);
421 KASSERT(directory_p ==
422 ((VTODE(fvp)->de_Attributes & ATTR_DIRECTORY) != 0));
423 KASSERT((tvp == NULL) || (directory_p == (tvp->v_type == VDIR)));
424 KASSERT((tvp == NULL) || (directory_p ==
425 ((VTODE(fvp)->de_Attributes & ATTR_DIRECTORY) != 0)));
426 if (directory_p) {
427 if (VTODE(fvp)->de_flag & DE_RENAME)
428 return EINVAL;
429 VTODE(fvp)->de_flag |= DE_RENAME;
430 }
431
432 reparent_p = (fdvp != tdvp);
433 KASSERT(reparent_p == (VTODE(fdvp)->de_StartCluster !=
434 VTODE(tdvp)->de_StartCluster));
435
436 /*
437 * XXX Hold it right there -- surely if we crash after
438 * removede, we'll fail to provide rename's guarantee that
439 * there will be something at the target pathname?
440 */
441 if (tvp != NULL) {
442 error = msdosfs_removede(VTODE(tdvp), VTODE(tvp), tmlr);
443 if (error)
444 goto out;
445 }
446
447 /*
448 * Convert the filename in tcnp into a dos filename. We copy this
449 * into the denode and directory entry for the destination
450 * file/directory.
451 */
452 error = msdosfs_uniqdosname(VTODE(tdvp), tcnp, toname);
453 if (error)
454 goto out;
455
456 /*
457 * First write a new entry in the destination directory and
458 * mark the entry in the source directory as deleted. Then
459 * move the denode to the correct hash chain for its new
460 * location in the filesystem. And, if we moved a directory,
461 * then update its .. entry to point to the new parent
462 * directory.
463 */
464
465 /* Save the old name in case we need to back out. */
466 memcpy(oldname, VTODE(fvp)->de_Name, 11);
467 memcpy(VTODE(fvp)->de_Name, toname, 11);
468
469 error = msdosfs_createde(VTODE(fvp), VTODE(tdvp), tmlr, 0, tcnp);
470 if (error) {
471 /* Directory entry didn't take -- back out the name change. */
472 memcpy(VTODE(fvp)->de_Name, oldname, 11);
473 goto out;
474 }
475
476 /*
477 * createde doesn't increment de_refcnt, but removede
478 * decrements it. Go figure.
479 */
480 KASSERT(VTODE(fvp)->de_refcnt < LONG_MAX);
481 VTODE(fvp)->de_refcnt++;
482
483 /*
484 * XXX Yes, createde and removede have arguments swapped. Go figure.
485 */
486 error = msdosfs_removede(VTODE(fdvp), VTODE(fvp), fmlr);
487 if (error) {
488 #if 0 /* XXX Back out the new directory entry? Panic? */
489 (void)msdosfs_removede(VTODE(tdvp), VTODE(fvp), tmlr);
490 memcpy(VTODE(fvp)->de_Name, oldname, 11);
491 #endif
492 goto out;
493 }
494
495 pmp = VFSTOMSDOSFS(mp);
496
497 if (!directory_p) {
498 struct denode_key old_key = VTODE(fvp)->de_key;
499 struct denode_key new_key = VTODE(fvp)->de_key;
500
501 error = msdosfs_pcbmap(VTODE(tdvp),
502 de_cluster(pmp, tmlr->mlr_fndoffset), NULL,
503 &new_key.dk_dirclust, NULL);
504 if (error) /* XXX Back everything out? Panic? */
505 goto out;
506 new_key.dk_diroffset = tmlr->mlr_fndoffset;
507 if (new_key.dk_dirclust != MSDOSFSROOT)
508 new_key.dk_diroffset &= pmp->pm_crbomask;
509 vcache_rekey_enter(pmp->pm_mountp, fvp, &old_key,
510 sizeof(old_key), &new_key, sizeof(new_key));
511 VTODE(fvp)->de_key = new_key;
512 vcache_rekey_exit(pmp->pm_mountp, fvp, &old_key,
513 sizeof(old_key), &VTODE(fvp)->de_key,
514 sizeof(VTODE(fvp)->de_key));
515 }
516
517 /*
518 * If we moved a directory to a new parent directory, then we must
519 * fixup the ".." entry in the moved directory.
520 */
521 if (directory_p && reparent_p) {
522 error = msdosfs_rename_replace_dotdot(fvp, fdvp, tdvp, cred);
523 if (error)
524 goto out;
525 }
526
527 out:;
528 if (tvp != NULL)
529 *tvp_nlinkp = (error ? 1 : 0);
530
531 genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp);
532
533 if (directory_p)
534 VTODE(fvp)->de_flag &=~ DE_RENAME;
535
536 return error;
537 }
538
539 /*
540 * msdosfs_gro_remove: Rename an object over another link to itself,
541 * effectively removing just the original link.
542 */
543 static int
msdosfs_gro_remove(struct mount * mp,kauth_cred_t cred,struct vnode * dvp,struct componentname * cnp,void * de,struct vnode * vp,nlink_t * tvp_nlinkp)544 msdosfs_gro_remove(struct mount *mp, kauth_cred_t cred,
545 struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp,
546 nlink_t *tvp_nlinkp)
547 {
548 struct msdosfs_lookup_results *mlr = de;
549 int error;
550
551 KASSERT(mp != NULL);
552 KASSERT(dvp != NULL);
553 KASSERT(cnp != NULL);
554 KASSERT(mlr != NULL);
555 KASSERT(vp != NULL);
556 KASSERT(dvp != vp);
557 KASSERT(dvp->v_mount == mp);
558 KASSERT(vp->v_mount == mp);
559 KASSERT(dvp->v_type == VDIR);
560 KASSERT(vp->v_type != VDIR);
561 KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
562 KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE);
563
564 error = msdosfs_removede(VTODE(dvp), VTODE(vp), mlr);
565
566 *tvp_nlinkp = (error ? 1 : 0);
567
568 return error;
569 }
570
571 /*
572 * msdosfs_gro_lookup: Look up and save the lookup results.
573 */
574 static int
msdosfs_gro_lookup(struct mount * mp,struct vnode * dvp,struct componentname * cnp,void * de_ret,struct vnode ** vp_ret)575 msdosfs_gro_lookup(struct mount *mp, struct vnode *dvp,
576 struct componentname *cnp, void *de_ret, struct vnode **vp_ret)
577 {
578 struct msdosfs_lookup_results *mlr_ret = de_ret;
579 struct vnode *vp;
580 int error;
581
582 (void)mp;
583 KASSERT(mp != NULL);
584 KASSERT(dvp != NULL);
585 KASSERT(cnp != NULL);
586 KASSERT(mlr_ret != NULL);
587 KASSERT(vp_ret != NULL);
588 KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
589
590 /* Kludge cargo-culted from dholland's ufs_rename. */
591 cnp->cn_flags &=~ MODMASK;
592 cnp->cn_flags |= (LOCKPARENT | LOCKLEAF);
593
594 error = relookup(dvp, &vp, cnp, 0);
595 if ((error == 0) && (vp == NULL)) {
596 error = ENOENT;
597 goto out;
598 }
599 if (error)
600 return error;
601
602 /*
603 * Thanks to VFS insanity, relookup locks vp, which screws us
604 * in various ways.
605 */
606 VOP_UNLOCK(vp);
607
608 out:
609 *mlr_ret = VTODE(dvp)->de_crap;
610 *vp_ret = vp;
611 return error;
612 }
613
614 /*
615 * msdosfs_rmdired_p: Check whether the directory vp has been rmdired.
616 *
617 * vp must be locked and referenced.
618 */
619 static bool
msdosfs_rmdired_p(struct vnode * vp)620 msdosfs_rmdired_p(struct vnode *vp)
621 {
622
623 KASSERT(vp != NULL);
624 KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE);
625 KASSERT(vp->v_type == VDIR);
626
627 return (VTODE(vp)->de_FileSize == 0);
628 }
629
630 /*
631 * msdosfs_gro_genealogy: Analyze the genealogy of the source and target
632 * directories.
633 */
634 static int
msdosfs_gro_genealogy(struct mount * mp,kauth_cred_t cred,struct vnode * fdvp,struct vnode * tdvp,struct vnode ** intermediate_node_ret)635 msdosfs_gro_genealogy(struct mount *mp, kauth_cred_t cred,
636 struct vnode *fdvp, struct vnode *tdvp,
637 struct vnode **intermediate_node_ret)
638 {
639 struct msdosfsmount *pmp;
640 struct vnode *vp, *dvp;
641 unsigned long dotdot_cn;
642 int error;
643
644 KASSERT(mp != NULL);
645 KASSERT(fdvp != NULL);
646 KASSERT(tdvp != NULL);
647 KASSERT(fdvp != tdvp);
648 KASSERT(intermediate_node_ret != NULL);
649 KASSERT(fdvp->v_mount == mp);
650 KASSERT(tdvp->v_mount == mp);
651 KASSERT(fdvp->v_type == VDIR);
652 KASSERT(tdvp->v_type == VDIR);
653
654 pmp = VFSTOMSDOSFS(mp);
655 KASSERT(pmp != NULL);
656
657 /*
658 * We need to provisionally lock tdvp to keep rmdir from
659 * deleting it -- or any ancestor -- at an inopportune moment.
660 */
661 error = msdosfs_gro_lock_directory(mp, tdvp);
662 if (error)
663 return error;
664
665 vp = tdvp;
666 vref(vp);
667
668 for (;;) {
669 KASSERT(vp->v_type == VDIR);
670
671 /* Did we hit the root without finding fdvp? */
672 if ((vp->v_vflag & VV_ROOT) != 0) {
673 vput(vp);
674 *intermediate_node_ret = NULL;
675 return 0;
676 }
677
678 error = msdosfs_read_dotdot(vp, cred, &dotdot_cn);
679 if (error) {
680 vput(vp);
681 return error;
682 }
683
684 /* Did we find that fdvp is an ancestor? */
685 if (VTODE(fdvp)->de_StartCluster == dotdot_cn) {
686 /* Unlock vp, but keep it referenced. */
687 VOP_UNLOCK(vp);
688 *intermediate_node_ret = vp;
689 return 0;
690 }
691
692 /* Neither -- keep ascending. */
693
694 error = msdosfs_deget(pmp, dotdot_cn,
695 (dotdot_cn ? 0 : MSDOSFSROOT_OFS), &dvp);
696 vput(vp);
697 if (error)
698 return error;
699 error = vn_lock(dvp, LK_EXCLUSIVE);
700 if (error) {
701 vrele(dvp);
702 return error;
703 }
704
705 KASSERT(dvp != NULL);
706 KASSERT(dvp->v_type == VDIR);
707
708 vp = dvp;
709
710 if (msdosfs_rmdired_p(vp)) {
711 vput(vp);
712 return ENOENT;
713 }
714 }
715 }
716
717 /*
718 * msdosfs_read_dotdot: Store in *cn_ret the cluster number of the
719 * parent of the directory vp.
720 */
721 static int
msdosfs_read_dotdot(struct vnode * vp,kauth_cred_t cred,unsigned long * cn_ret)722 msdosfs_read_dotdot(struct vnode *vp, kauth_cred_t cred, unsigned long *cn_ret)
723 {
724 struct msdosfsmount *pmp;
725 unsigned long start_cn, cn;
726 struct buf *bp;
727 struct direntry *ep;
728 int error;
729
730 KASSERT(vp != NULL);
731 KASSERT(cn_ret != NULL);
732 KASSERT(vp->v_type == VDIR);
733 KASSERT(VTODE(vp) != NULL);
734
735 pmp = VTODE(vp)->de_pmp;
736 KASSERT(pmp != NULL);
737
738 start_cn = VTODE(vp)->de_StartCluster;
739 error = bread(pmp->pm_devvp, de_bn2kb(pmp, cntobn(pmp, start_cn)),
740 pmp->pm_bpcluster, 0, &bp);
741 if (error)
742 return error;
743
744 ep = (struct direntry *)bp->b_data + 1;
745 if (((ep->deAttributes & ATTR_DIRECTORY) == ATTR_DIRECTORY) &&
746 (memcmp(ep->deName, ".. ", 11) == 0)) {
747 cn = getushort(ep->deStartCluster);
748 if (FAT32(pmp))
749 cn |= getushort(ep->deHighClust) << 16;
750 *cn_ret = cn;
751 error = 0;
752 } else {
753 error = ENOTDIR;
754 }
755
756 brelse(bp, 0);
757
758 return error;
759 }
760
761 /*
762 * msdosfs_rename_replace_dotdot: Change the target of the `..' entry of
763 * the directory vp from fdvp to tdvp.
764 */
765 static int
msdosfs_rename_replace_dotdot(struct vnode * vp,struct vnode * fdvp,struct vnode * tdvp,kauth_cred_t cred)766 msdosfs_rename_replace_dotdot(struct vnode *vp,
767 struct vnode *fdvp, struct vnode *tdvp,
768 kauth_cred_t cred)
769 {
770 struct msdosfsmount *pmp;
771 struct direntry *dotdotp;
772 struct buf *bp;
773 daddr_t bn;
774 u_long cn;
775 int error;
776
777 pmp = VFSTOMSDOSFS(fdvp->v_mount);
778
779 cn = VTODE(vp)->de_StartCluster;
780 if (cn == MSDOSFSROOT) {
781 /* this should never happen */
782 panic("msdosfs_rename: updating .. in root directory?");
783 } else
784 bn = cntobn(pmp, cn);
785
786 error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn),
787 pmp->pm_bpcluster, B_MODIFY, &bp);
788 if (error)
789 return error;
790
791 dotdotp = (struct direntry *)bp->b_data + 1;
792 putushort(dotdotp->deStartCluster, VTODE(tdvp)->de_StartCluster);
793 if (FAT32(pmp)) {
794 putushort(dotdotp->deHighClust,
795 VTODE(tdvp)->de_StartCluster >> 16);
796 } else {
797 putushort(dotdotp->deHighClust, 0);
798 }
799
800 error = bwrite(bp);
801
802 return error;
803 }
804
805 /*
806 * msdosfs_gro_lock_directory: Lock the directory vp, but fail if it has
807 * been rmdir'd.
808 */
809 static int
msdosfs_gro_lock_directory(struct mount * mp,struct vnode * vp)810 msdosfs_gro_lock_directory(struct mount *mp, struct vnode *vp)
811 {
812 int error;
813
814 (void)mp;
815 KASSERT(vp != NULL);
816
817 error = vn_lock(vp, LK_EXCLUSIVE);
818 if (error)
819 return error;
820
821 KASSERT(mp != NULL);
822 KASSERT(vp->v_mount == mp);
823
824 if (msdosfs_rmdired_p(vp)) {
825 VOP_UNLOCK(vp);
826 return ENOENT;
827 }
828
829 return 0;
830 }
831
832 static const struct genfs_rename_ops msdosfs_genfs_rename_ops = {
833 .gro_directory_empty_p = msdosfs_gro_directory_empty_p,
834 .gro_rename_check_possible = msdosfs_gro_rename_check_possible,
835 .gro_rename_check_permitted = msdosfs_gro_rename_check_permitted,
836 .gro_remove_check_possible = msdosfs_gro_remove_check_possible,
837 .gro_remove_check_permitted = msdosfs_gro_remove_check_permitted,
838 .gro_rename = msdosfs_gro_rename,
839 .gro_remove = msdosfs_gro_remove,
840 .gro_lookup = msdosfs_gro_lookup,
841 .gro_genealogy = msdosfs_gro_genealogy,
842 .gro_lock_directory = msdosfs_gro_lock_directory,
843 };
844