/* * Unlink a name in a directory, by slot number. */ static int sfs_dir_unlink(struct sfs_vnode *sv, int slot) { struct sfs_dir sd; /* Initialize a suitable directory entry... */ bzero(&sd, sizeof(sd)); sd.sfd_ino = SFS_NOINO; /* ... and write it */ return sfs_writedir(sv, &sd, slot); }
/* * Create a link in a directory to the specified inode by number, with * the specified name, and optionally hand back the slot. */ static int sfs_dir_link(struct sfs_vnode *sv, const char *name, uint32_t ino, int *slot) { int emptyslot = -1; int result; struct sfs_dir sd; /* Look up the name. We want to make sure it *doesn't* exist. */ result = sfs_dir_findname(sv, name, NULL, NULL, &emptyslot); if (result!=0 && result!=ENOENT) { return result; } if (result==0) { return EEXIST; } if (strlen(name)+1 > sizeof(sd.sfd_name)) { return ENAMETOOLONG; } /* If we didn't get an empty slot, add the entry at the end. */ if (emptyslot < 0) { emptyslot = sfs_dir_nentries(sv); } /* Set up the entry. */ bzero(&sd, sizeof(sd)); sd.sfd_ino = ino; strcpy(sd.sfd_name, name); /* Hand back the slot, if so requested. */ if (slot) { *slot = emptyslot; } /* Write the entry. */ return sfs_writedir(sv, &sd, emptyslot); }
/* * Process a directory. INO is the inode number; PARENTINO is the * parent's inode number; PATHSOFAR is the path to this directory. * * Recursively checks its subdirs. * * In the FUTURE we might want to improve the handling of crosslinked * directories so it picks the parent that the .. entry points to, * instead of the first entry we recursively find. Beware of course * that the .. entry might not point to anywhere valid at all... */ static int pass2_dir(uint32_t ino, uint32_t parentino, const char *pathsofar) { struct sfs_dinode sfi; struct sfs_dir *direntries; int *sortvector; uint32_t dirsize, ndirentries, maxdirentries, subdircount, i; int ichanged=0, dchanged=0, dotseen=0, dotdotseen=0; if (inode_visitdir(ino)) { /* crosslinked dir; tell parent to remove the entry */ return 1; } /* Load the inode. */ sfs_readinode(ino, &sfi); /* * Load the directory. If there is any leftover room in the * last block, allocate space for it in case we want to insert * entries. */ ndirentries = sfi.sfi_size/sizeof(struct sfs_dir); maxdirentries = SFS_ROUNDUP(ndirentries, SFS_BLOCKSIZE/sizeof(struct sfs_dir)); dirsize = maxdirentries * sizeof(struct sfs_dir); direntries = domalloc(dirsize); sortvector = domalloc(ndirentries * sizeof(int)); sfs_readdir(&sfi, direntries, ndirentries); for (i=ndirentries; i<maxdirentries; i++) { direntries[i].sfd_ino = SFS_NOINO; bzero(direntries[i].sfd_name, sizeof(direntries[i].sfd_name)); } /* * Sort by name and check for duplicate names. */ sfsdir_sort(direntries, ndirentries, sortvector); /* don't use ndirentries-1 here, in case ndirentries == 0 */ for (i=0; i+1<ndirentries; i++) { struct sfs_dir *d1 = &direntries[sortvector[i]]; struct sfs_dir *d2 = &direntries[sortvector[i+1]]; assert(d1 != d2); if (d1->sfd_ino == SFS_NOINO || d2->sfd_ino == SFS_NOINO) { /* sfsdir_sort puts these last */ continue; } if (!strcmp(d1->sfd_name, d2->sfd_name)) { if (d1->sfd_ino == d2->sfd_ino) { setbadness(EXIT_RECOV); warnx("Directory %s: Duplicate entries for " "%s (merged)", pathsofar, d1->sfd_name); d1->sfd_ino = SFS_NOINO; d1->sfd_name[0] = 0; } else { /* XXX: what if FSCK.n.m already exists? */ snprintf(d1->sfd_name, sizeof(d1->sfd_name), "FSCK.%lu.%lu", (unsigned long) d1->sfd_ino, (unsigned long) uniqueid()); setbadness(EXIT_RECOV); warnx("Directory %s: Duplicate names %s " "(one renamed: %s)", pathsofar, d2->sfd_name, d1->sfd_name); } dchanged = 1; } } /* * Look for the . and .. entries. */ for (i=0; i<ndirentries; i++) { if (!strcmp(direntries[i].sfd_name, ".")) { if (direntries[i].sfd_ino != ino) { setbadness(EXIT_RECOV); warnx("Directory %s: Incorrect `.' entry " "(fixed)", pathsofar); direntries[i].sfd_ino = ino; dchanged = 1; } /* duplicates are checked above -> only one . here */ assert(dotseen==0); dotseen = 1; } else if (!strcmp(direntries[i].sfd_name, "..")) { if (direntries[i].sfd_ino != parentino) { setbadness(EXIT_RECOV); warnx("Directory %s: Incorrect `..' entry " "(fixed)", pathsofar); direntries[i].sfd_ino = parentino; dchanged = 1; } /* duplicates are checked above -> only one .. here */ assert(dotdotseen==0); dotdotseen = 1; } } /* * If no . entry, try to insert one. */ if (!dotseen) { if (sfsdir_tryadd(direntries, ndirentries, ".", ino)==0) { setbadness(EXIT_RECOV); warnx("Directory %s: No `.' entry (added)", pathsofar); dchanged = 1; } else if (sfsdir_tryadd(direntries, maxdirentries, ".", ino)==0) { setbadness(EXIT_RECOV); warnx("Directory %s: No `.' entry (added)", pathsofar); ndirentries++; dchanged = 1; sfi.sfi_size += sizeof(struct sfs_dir); ichanged = 1; } else { setbadness(EXIT_UNRECOV); warnx("Directory %s: No `.' entry (NOT FIXED)", pathsofar); } } /* * If no .. entry, try to insert one. */ if (!dotdotseen) { if (sfsdir_tryadd(direntries, ndirentries, "..", parentino)==0) { setbadness(EXIT_RECOV); warnx("Directory %s: No `..' entry (added)", pathsofar); dchanged = 1; } else if (sfsdir_tryadd(direntries, maxdirentries, "..", parentino)==0) { setbadness(EXIT_RECOV); warnx("Directory %s: No `..' entry (added)", pathsofar); ndirentries++; dchanged = 1; sfi.sfi_size += sizeof(struct sfs_dir); ichanged = 1; } else { setbadness(EXIT_UNRECOV); warnx("Directory %s: No `..' entry (NOT FIXED)", pathsofar); } } /* * Now load each inode in the directory. * * For regular files, count the number of links we see; for * directories, recurse. Count the number of subdirs seen * so we can correct our own link count if necessary. */ subdircount=0; for (i=0; i<ndirentries; i++) { if (direntries[i].sfd_ino == SFS_NOINO) { /* nothing */ } else if (!strcmp(direntries[i].sfd_name, ".")) { /* nothing */ } else if (!strcmp(direntries[i].sfd_name, "..")) { /* nothing */ } else { char path[strlen(pathsofar)+SFS_NAMELEN+1]; struct sfs_dinode subsfi; sfs_readinode(direntries[i].sfd_ino, &subsfi); snprintf(path, sizeof(path), "%s/%s", pathsofar, direntries[i].sfd_name); switch (subsfi.sfi_type) { case SFS_TYPE_FILE: inode_addlink(direntries[i].sfd_ino); break; case SFS_TYPE_DIR: if (pass2_dir(direntries[i].sfd_ino, ino, path)) { setbadness(EXIT_RECOV); warnx("Directory %s: Crosslink to " "other directory (removed)", path); direntries[i].sfd_ino = SFS_NOINO; direntries[i].sfd_name[0] = 0; dchanged = 1; } else { subdircount++; } break; default: setbadness(EXIT_RECOV); warnx("Object %s: Invalid inode type " "(removed)", path); direntries[i].sfd_ino = SFS_NOINO; direntries[i].sfd_name[0] = 0; dchanged = 1; break; } } } /* * Fix up the link count if needed. */ if (sfi.sfi_linkcount != subdircount+2) { setbadness(EXIT_RECOV); warnx("Directory %s: Link count %lu should be %lu (fixed)", pathsofar, (unsigned long) sfi.sfi_linkcount, (unsigned long) subdircount+2); sfi.sfi_linkcount = subdircount+2; ichanged = 1; } /* * Write back anything that changed, clean up, and return. */ if (dchanged) { sfs_writedir(&sfi, direntries, ndirentries); } if (ichanged) { sfs_writeinode(ino, &sfi); } free(direntries); free(sortvector); return 0; }
/* * Rename a file. * * Locking: * Locks sfs_renamelock. * Calls check_parent, which locks various directories one at a * time. * Locks the target vnodes and their parents in a complex fashion * (described in detail below) which is carefully arranged so * it won't deadlock with rmdir. Or at least I hope so. * Then unlocks everything. * * The rationale for all this is complex. See the comments below. * * Requires up to 7 buffers. */ static int sfs_rename(struct vnode *absdir1, const char *name1, struct vnode *absdir2, const char *name2) { struct sfs_fs *sfs = absdir1->vn_fs->fs_data; struct sfs_vnode *dir1 = absdir1->vn_data; struct sfs_vnode *dir2 = absdir2->vn_data; struct sfs_vnode *obj1=NULL, *obj2=NULL; struct sfs_dinode *dir1_inodeptr, *dir2_inodeptr; struct sfs_dinode *obj1_inodeptr, *obj2_inodeptr; int slot1=-1, slot2=-1; int result, result2; struct sfs_direntry sd; int found_dir1; /* make gcc happy */ obj2_inodeptr = NULL; /* The VFS layer is supposed to enforce this */ KASSERT(absdir1->vn_fs == absdir2->vn_fs); if (!strcmp(name1, ".") || !strcmp(name2, ".") || !strcmp(name1, "..") || !strcmp(name2, "..")) { return EINVAL; } if (strlen(name2)+1 > sizeof(sd.sfd_name)) { return ENAMETOOLONG; } /* * We only allow one rename to occur at a time. This appears * to be necessary to preserve the consistency of the * filesystem: once you do the parent check (that n1 is not an * ancestor of d2/n2) nothing may be allowed to happen that * might invalidate that result until all of the * rearrangements are complete. If other renames are allowed * to proceed, we'd need to lock every descendent of n1 to * make sure that some ancestor of d2/n2 doesn't get inserted * at some point deep down. This is impractical, so we use one * global lock. * * To prevent certain deadlocks while locking the vnodes we * need, the rename lock goes outside all the vnode locks. */ reserve_buffers(SFS_BLOCKSIZE); lock_acquire(sfs->sfs_renamelock); /* * Get the objects we're moving. * * Lock each directory temporarily. We'll check again later to * make sure they haven't disappeared and to find slots. */ lock_acquire(dir1->sv_lock); result = sfs_lookonce(dir1, name1, &obj1, NULL); lock_release(dir1->sv_lock); if (result) { goto out0; } lock_acquire(dir2->sv_lock); result = sfs_lookonce(dir2, name2, &obj2, NULL); lock_release(dir2->sv_lock); if (result && result != ENOENT) { goto out0; } if (result==ENOENT) { /* * sfs_lookonce returns a null vnode with ENOENT in * order to make our life easier. */ KASSERT(obj2==NULL); } /* * Prohibit the case where obj1 is a directory and it's a direct * ancestor in the tree of dir2 (or is the same as dir2). If * that were to be permitted, it'd create a detached chunk of * the directory tree, and we don't like that. * * If we see dir1 while checking up the tree, found_dir1 is * set to true. We use this info to choose the correct ordering * for locking dir1 and dir2. * * To prevent deadlocks, the parent check must be done without * holding locks on any other directories. */ result = check_parent(dir1, obj1, dir2, &found_dir1); if (result) { goto out0; } /* * Now check for cases where some of the four vnodes we have * are the same. * * These cases are, in the order they are handled below: * * dir1 == obj1 Already checked. * dir2 == obj2 Already checked. * dir2 == obj1 Already checked. * dir1 == obj2 Checked below. * dir1 == dir2 Legal. * obj1 == obj2 Legal, special handling. */ /* * A directory should have no entries for itself other than '.'. * Thus, since we explicitly reject '.' above, the names * within the directories should not refer to the directories * themselves. */ KASSERT(dir1 != obj1); KASSERT(dir2 != obj2); /* * The parent check should have caught this case. */ KASSERT(dir2 != obj1); /* * Check for dir1 == obj2. * * This is not necessarily wrong if obj1 is the last entry in * dir1 (this is essentially "mv ./foo/bar ./foo") but our * implementation doesn't tolerate it. Because we need to * unlink g2 before linking g1 in the new place, it will * always fail complaining that g2 (sv1) isn't empty. We could * just charge ahead and let this happen, but we'll get into * trouble with our locks if we do, so detect this as a * special case and return ENOTEMPTY. */ if (obj2==dir1) { result = ENOTEMPTY; goto out0; } /* * Now we can begin acquiring locks for real. * * If we saw dir1 while doing the parent check, it means * dir1 is higher in the tree than dir2. Thus, we should * lock dir1 before dir2. * * If on the other hand we didn't see dir1, either dir2 is * higher in the tree than dir1, in which case we should lock * dir2 first, or dir1 and dir2 are on disjoint branches of * the tree, in which case (because there's a single rename * lock for the whole fs) it doesn't matter what order we lock * in. * * If we lock dir1 first, we don't need to lock obj1 before * dir2, since (due to the parent check) obj1 cannot be an * ancestor of dir2. * * However, if we lock dir2 first, obj2 must be locked before * dir1, in case obj2 is an ancestor of dir1. (In this case we * will find that obj2 is not empty and cannot be removed, but * we must lock it before we can check that.) * * Thus we lock in this order: * * dir1 (if found_dir1) * dir2 * obj2 (if non-NULL) * dir1 (if !found_dir1) * obj1 * * Also, look out for the case where both dirs are the same. * (If this is true, found_dir1 will be set.) */ if (dir1==dir2) { /* This locks "both" dirs */ lock_acquire(dir1->sv_lock); KASSERT(found_dir1); } else { if (found_dir1) { lock_acquire(dir1->sv_lock); } lock_acquire(dir2->sv_lock); } /* * Now lock obj2. * * Note that we must redo the lookup and get a new obj2, as it * may have changed under us. Since we hold the rename lock * for the whole fs, the fs structure cannot have changed, so * we don't need to redo the parent check or any of the checks * for vnode aliasing with dir1 or dir2 above. Note however * that obj1 and obj2 may now be the same even if they weren't * before. */ KASSERT(lock_do_i_hold(dir2->sv_lock)); if (obj2) { VOP_DECREF(&obj2->sv_absvn); obj2 = NULL; } result = sfs_lookonce(dir2, name2, &obj2, &slot2); if (result==0) { KASSERT(obj2 != NULL); lock_acquire(obj2->sv_lock); result = sfs_dinode_load(obj2); if (result) { /* ENOENT would confuse us below; but it can't be */ KASSERT(result != ENOENT); lock_release(obj2->sv_lock); VOP_DECREF(&obj2->sv_absvn); /* continue to check below */ } else { obj2_inodeptr = sfs_dinode_map(obj2); } } else if (result==ENOENT) { /* * sfs_lookonce returns a null vnode and an empty slot * with ENOENT in order to make our life easier. */ KASSERT(obj2==NULL); KASSERT(slot2>=0); } if (!found_dir1) { lock_acquire(dir1->sv_lock); } /* Postpone this check to simplify the error cleanup. */ if (result != 0 && result != ENOENT) { goto out1; } /* * Now reload obj1. */ KASSERT(lock_do_i_hold(dir1->sv_lock)); VOP_DECREF(&obj1->sv_absvn); obj1 = NULL; result = sfs_lookonce(dir1, name1, &obj1, &slot1); if (result) { goto out1; } /* * POSIX mandates that if obj1==obj2, we succeed and nothing * happens. This is somewhat stupid if obj1==obj2 and dir1 != dir2, * but we'll go with POSIX anyway. */ if (obj1==obj2) { result = 0; VOP_DECREF(&obj1->sv_absvn); obj1 = NULL; goto out1; } lock_acquire(obj1->sv_lock); result = sfs_dinode_load(obj1); if (result) { lock_release(obj1->sv_lock); VOP_DECREF(&obj1->sv_absvn); obj1 = NULL; goto out1; } obj1_inodeptr = sfs_dinode_map(obj1); result = sfs_dinode_load(dir2); if (result) { goto out2; } dir2_inodeptr = sfs_dinode_map(dir2); result = sfs_dinode_load(dir1); if (result) { goto out3; } dir1_inodeptr = sfs_dinode_map(dir1); /* * One final piece of paranoia: make sure dir2 hasn't been rmdir'd. * (If dir1 was, the obj1 lookup above would have failed.) */ if (dir2_inodeptr->sfi_linkcount==0) { result = ENOENT; goto out4; } /* * Now we have all the locks we need and we can proceed with * the operation. */ /* At this point we should have valid slots in both dirs. */ KASSERT(slot1>=0); KASSERT(slot2>=0); if (obj2 != NULL) { /* * Target already exists. * Must be the same type (file or directory) as the source, * and if a directory, must be empty. Then unlink it. */ if (obj1_inodeptr->sfi_type == SFS_TYPE_DIR) { if (obj2_inodeptr->sfi_type != SFS_TYPE_DIR) { result = ENOTDIR; goto out4; } result = sfs_dir_checkempty(obj2); if (result) { goto out4; } /* Remove the name */ result = sfs_dir_unlink(dir2, slot2); if (result) { goto out4; } /* Dispose of the directory */ KASSERT(dir2_inodeptr->sfi_linkcount > 1); KASSERT(obj2_inodeptr->sfi_linkcount == 2); dir2_inodeptr->sfi_linkcount--; obj2_inodeptr->sfi_linkcount -= 2; sfs_dinode_mark_dirty(dir2); sfs_dinode_mark_dirty(obj2); /* ignore errors on this */ sfs_itrunc(obj2, 0); } else { KASSERT(obj1->sv_type == SFS_TYPE_FILE); if (obj2->sv_type != SFS_TYPE_FILE) { result = EISDIR; goto out4; } /* Remove the name */ result = sfs_dir_unlink(dir2, slot2); if (result) { goto out4; } /* Dispose of the file */ KASSERT(obj2_inodeptr->sfi_linkcount > 0); obj2_inodeptr->sfi_linkcount--; sfs_dinode_mark_dirty(obj2); } sfs_dinode_unload(obj2); lock_release(obj2->sv_lock); VOP_DECREF(&obj2->sv_absvn); obj2 = NULL; } /* * At this point the target should be nonexistent and we have * a slot in the target directory we can use. Create a link * there. Do it by hand instead of using sfs_dir_link to avoid * duplication of effort. */ KASSERT(obj2==NULL); bzero(&sd, sizeof(sd)); sd.sfd_ino = obj1->sv_ino; strcpy(sd.sfd_name, name2); result = sfs_writedir(dir2, slot2, &sd); if (result) { goto out4; } obj1_inodeptr->sfi_linkcount++; sfs_dinode_mark_dirty(obj1); if (obj1->sv_type == SFS_TYPE_DIR && dir1 != dir2) { /* Directory: reparent it */ result = sfs_readdir(obj1, DOTDOTSLOT, &sd); if (result) { goto recover1; } if (strcmp(sd.sfd_name, "..")) { panic("sfs: %s: rename: moving dir: .. is not " "in slot %d\n", sfs->sfs_sb.sb_volname, DOTDOTSLOT); } if (sd.sfd_ino != dir1->sv_ino) { panic("sfs: %s: rename: moving dir: .. is i%u " "and not i%u\n", sfs->sfs_sb.sb_volname, sd.sfd_ino, dir1->sv_ino); } sd.sfd_ino = dir2->sv_ino; result = sfs_writedir(obj1, DOTDOTSLOT, &sd); if (result) { goto recover1; } dir1_inodeptr->sfi_linkcount--; sfs_dinode_mark_dirty(dir1); dir2_inodeptr->sfi_linkcount++; sfs_dinode_mark_dirty(dir2); } result = sfs_dir_unlink(dir1, slot1); if (result) { goto recover2; } obj1_inodeptr->sfi_linkcount--; sfs_dinode_mark_dirty(obj1); KASSERT(result==0); if (0) { /* Only reached on error */ recover2: if (obj1->sv_type == SFS_TYPE_DIR) { sd.sfd_ino = dir1->sv_ino; result2 = sfs_writedir(obj1, DOTDOTSLOT, &sd); if (result2) { recovermsg(sfs->sfs_sb.sb_volname, result, result2); } dir1_inodeptr->sfi_linkcount++; sfs_dinode_mark_dirty(dir1); dir2_inodeptr->sfi_linkcount--; sfs_dinode_mark_dirty(dir2); } recover1: result2 = sfs_dir_unlink(dir2, slot2); if (result2) { recovermsg(sfs->sfs_sb.sb_volname, result, result2); } obj1_inodeptr->sfi_linkcount--; sfs_dinode_mark_dirty(obj1); } out4: sfs_dinode_unload(dir1); out3: sfs_dinode_unload(dir2); out2: sfs_dinode_unload(obj1); lock_release(obj1->sv_lock); out1: if (obj2) { sfs_dinode_unload(obj2); lock_release(obj2->sv_lock); } lock_release(dir1->sv_lock); if (dir1 != dir2) { lock_release(dir2->sv_lock); } out0: if (obj2 != NULL) { VOP_DECREF(&obj2->sv_absvn); } if (obj1 != NULL) { VOP_DECREF(&obj1->sv_absvn); } unreserve_buffers(SFS_BLOCKSIZE); lock_release(sfs->sfs_renamelock); return result; }
/* * Check a directory. INO is the inode number; PATHSOFAR is the path * to this directory. This traverses the volume directory tree * recursively. */ static void pass1_dir(uint32_t ino, const char *pathsofar) { struct sfs_dinode sfi; struct sfs_dir *direntries; uint32_t ndirentries, i; int ichanged=0, dchanged=0; sfs_readinode(ino, &sfi); if (sfi.sfi_size % sizeof(struct sfs_dir) != 0) { setbadness(EXIT_RECOV); warnx("Directory %s has illegal size %lu (fixed)", pathsofar, (unsigned long) sfi.sfi_size); sfi.sfi_size = SFS_ROUNDUP(sfi.sfi_size, sizeof(struct sfs_dir)); ichanged = 1; } count_dirs++; if (pass1_inode(ino, &sfi, ichanged)) { /* been here before; crosslinked dir, sort it out in pass 2 */ return; } ndirentries = sfi.sfi_size/sizeof(struct sfs_dir); direntries = domalloc(sfi.sfi_size); sfs_readdir(&sfi, direntries, ndirentries); for (i=0; i<ndirentries; i++) { if (pass1_direntry(pathsofar, i, &direntries[i])) { dchanged = 1; } } for (i=0; i<ndirentries; i++) { if (direntries[i].sfd_ino == SFS_NOINO) { /* nothing */ } else if (!strcmp(direntries[i].sfd_name, ".")) { /* nothing */ } else if (!strcmp(direntries[i].sfd_name, "..")) { /* nothing */ } else { char path[strlen(pathsofar)+SFS_NAMELEN+1]; struct sfs_dinode subsfi; uint32_t subino; subino = direntries[i].sfd_ino; sfs_readinode(subino, &subsfi); snprintf(path, sizeof(path), "%s/%s", pathsofar, direntries[i].sfd_name); switch (subsfi.sfi_type) { case SFS_TYPE_FILE: if (pass1_inode(subino, &subsfi, 0)) { /* been here before */ break; } count_files++; break; case SFS_TYPE_DIR: pass1_dir(subino, path); break; default: setbadness(EXIT_RECOV); warnx("Object %s: Invalid inode type " "(removed)", path); direntries[i].sfd_ino = SFS_NOINO; direntries[i].sfd_name[0] = 0; dchanged = 1; break; } } } if (dchanged) { sfs_writedir(&sfi, direntries, ndirentries); } free(direntries); }