/* * Truncate a file. * * Locking: gets/releases vnode lock. * * Requires up to 4 buffers. */ static int sfs_truncate(struct vnode *v, off_t len) { struct sfs_vnode *sv = v->vn_data; int result; lock_acquire(sv->sv_lock); reserve_buffers(SFS_BLOCKSIZE); result = sfs_itrunc(sv, len); unreserve_buffers(SFS_BLOCKSIZE); lock_release(sv->sv_lock); return result; }
// Drop a reference to an in-memory inode. // If that was the last reference, the inode cache entry can // be recycled. // If that was the last reference and the inode has no links // to it, free the inode (and its content) on disk. void sfs_iput(struct inode *ip) { struct sfs_inode *sin = vop_info(ip, sfs_inode); acquire(&icache.lock); if(sin->ref == 1 && (sin->flags & I_VALID) && sin->nlink == 0){ // inode has no links: truncate and free inode. if(sin->flags & I_BUSY) panic("iput busy"); sin->flags |= I_BUSY; release(&icache.lock); sfs_itrunc(ip); sin->type = 0; sfs_iupdate(ip); acquire(&icache.lock); sin->flags = 0; wakeup(ip); } // cprintf("sin->inum = %d, sin->ref = %d\n", sin->inum, sin->ref); sin->ref--; // cprintf("sin->inum = %d, sin->ref = %d\n", sin->inum, sin->ref); release(&icache.lock); }
/* * Delete a directory. * * Locking: Acquires vnode lock for parent dir and then vnode lock for * victim dir. Releases both. * * Requires 4 buffers. */ static int sfs_rmdir(struct vnode *v, const char *name) { struct sfs_vnode *sv = v->vn_data; struct sfs_fs *sfs = sv->sv_absvn.vn_fs->fs_data; struct sfs_vnode *victim; struct sfs_dinode *dir_inodeptr; struct sfs_dinode *victim_inodeptr; int result, result2; int slot; /* Cannot remove the . or .. entries from a directory! */ if (!strcmp(name, ".") || !strcmp(name, "..")) { return EINVAL; } lock_acquire(sv->sv_lock); reserve_buffers(SFS_BLOCKSIZE); result = sfs_dinode_load(sv); if (result) { goto die_loadsv; } dir_inodeptr = sfs_dinode_map(sv); if (dir_inodeptr->sfi_linkcount == 0) { result = ENOENT; goto die_linkcount; } result = sfs_lookonce(sv, name, &victim, &slot); if (result) { goto die_linkcount; } lock_acquire(victim->sv_lock); result = sfs_dinode_load(victim); if (result) { goto die_loadvictim; } victim_inodeptr = sfs_dinode_map(victim); if (victim->sv_ino == SFS_ROOTDIR_INO) { result = EPERM; goto die_total; } /* Only allowed on directories */ if (victim_inodeptr->sfi_type != SFS_TYPE_DIR) { result = ENOTDIR; goto die_total; } result = sfs_dir_checkempty(victim); if (result) { goto die_total; } result = sfs_dir_unlink(sv, slot); if (result) { goto die_total; } KASSERT(dir_inodeptr->sfi_linkcount > 1); KASSERT(victim_inodeptr->sfi_linkcount==2); dir_inodeptr->sfi_linkcount--; sfs_dinode_mark_dirty(sv); victim_inodeptr->sfi_linkcount -= 2; sfs_dinode_mark_dirty(victim); result = sfs_itrunc(victim, 0); if (result) { victim_inodeptr->sfi_linkcount += 2; dir_inodeptr->sfi_linkcount++; result2 = sfs_dir_link(sv, name, victim->sv_ino, NULL); if (result2) { /* XXX: would be better if this case didn't exist */ panic("sfs: %s: rmdir: %s; while recovering: %s\n", sfs->sfs_sb.sb_volname, strerror(result), strerror(result2)); } goto die_total; } die_total: sfs_dinode_unload(victim); die_loadvictim: lock_release(victim->sv_lock); VOP_DECREF(&victim->sv_absvn); die_linkcount: sfs_dinode_unload(sv); die_loadsv: unreserve_buffers(SFS_BLOCKSIZE); lock_release(sv->sv_lock); return result; }
/* * 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; }