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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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