/* * ufs_fiosatime * set access time w/o altering change time. This ioctl is tailored * to metamucil's needs and may change at any time. */ int ufs_fiosatime( struct vnode *vp, /* file's vnode */ struct timeval *tvu, /* struct timeval in userland */ int flag, /* flag from VOP_IOCTL() */ struct cred *cr) /* credentials from ufs_ioctl */ { struct inode *ip; /* inode for vp */ struct timeval32 tv; /* copy of user's timeval */ int now = 0; /* * must have sufficient privileges */ if (secpolicy_fs_config(cr, vp->v_vfsp) != 0) return (EPERM); /* * get user's copy of timeval struct and check values * if input is NULL, will set time to now */ if (tvu == NULL) { now = 1; } else { if ((flag & DATAMODEL_MASK) == DATAMODEL_ILP32) { if (copyin(tvu, &tv, sizeof (tv))) return (EFAULT); } else { struct timeval tv64; if (copyin(tvu, &tv64, sizeof (tv64))) return (EFAULT); if (TIMEVAL_OVERFLOW(&tv64)) return (EOVERFLOW); TIMEVAL_TO_TIMEVAL32(&tv, &tv64); } if (tv.tv_usec < 0 || tv.tv_usec >= 1000000) return (EINVAL); } /* * update access time */ ip = VTOI(vp); rw_enter(&ip->i_contents, RW_WRITER); ITIMES_NOLOCK(ip); if (now) { mutex_enter(&ufs_iuniqtime_lock); ip->i_atime = iuniqtime; mutex_exit(&ufs_iuniqtime_lock); } else { ip->i_atime = tv; } ip->i_flag |= IMODACC; rw_exit(&ip->i_contents); return (0); }
/* * Unhook an attribute directory from a parent file/dir * Only do so, if we are the only user of the vnode. */ void ufs_unhook_shadow(struct inode *ip, struct inode *sip) { struct vnode *datavp = ITOV(ip); struct vnode *dirvp = ITOV(sip); int hno; kmutex_t *ihm; ASSERT(RW_WRITE_HELD(&sip->i_contents)); ASSERT(RW_WRITE_HELD(&ip->i_contents)); if (vn_is_readonly(ITOV(ip))) return; if (ip->i_ufsvfs == NULL || sip->i_ufsvfs == NULL) return; hno = INOHASH(ip->i_number); ihm = &ih_lock[hno]; mutex_enter(ihm); mutex_enter(&datavp->v_lock); mutex_enter(&dirvp->v_lock); if (dirvp->v_count != 1 && datavp->v_count != 1) { mutex_exit(&dirvp->v_lock); mutex_exit(&datavp->v_lock); mutex_exit(ihm); return; } /* * Delete shadow from ip */ sip->i_nlink -= 2; ufs_setreclaim(sip); TRANS_INODE(sip->i_ufsvfs, sip); sip->i_flag |= ICHG; sip->i_seq++; ITIMES_NOLOCK(sip); /* * Update src file */ ip->i_oeftflag = 0; TRANS_INODE(ip->i_ufsvfs, ip); ip->i_flag |= ICHG; ip->i_seq++; ufs_iupdat(ip, 1); mutex_exit(&dirvp->v_lock); mutex_exit(&datavp->v_lock); mutex_exit(ihm); }
/* * Locking i_contents in this * function seems to be really weird */ int ud_dirremove( struct ud_inode *dp, char *namep, struct ud_inode *oip, struct vnode *cdir, enum dr_op op, struct cred *cr, caller_context_t *ctp) { struct udf_vfs *udf_vfsp; int32_t namelen, err = 0; struct slot slot; struct ud_inode *ip; mode_t mode; struct file_id *fid; uint8_t *buf = NULL; uint32_t tbno; ud_printf("ud_dirremove\n"); ASSERT(RW_WRITE_HELD(&dp->i_rwlock)); udf_vfsp = dp->i_udf; namelen = (int)strlen(namep); if (namelen == 0) { cmn_err(CE_WARN, "name length == 0 in ud_dirremove"); return (EINVAL); } /* * return err when removing . and .. */ if (namep[0] == '.') { if (namelen == 1) { return (EINVAL); } else if (namelen == 2 && namep[1] == '.') { return (EEXIST); /* SIGH should be ENOTEMPTY */ } } ASSERT(RW_WRITE_HELD(&dp->i_rwlock)); /* * Check accessibility of directory. */ if (dp->i_type != VDIR) { return (ENOTDIR); } ip = NULL; slot.status = FOUND; /* don't need to look for empty slot */ slot.offset = 0; slot.size = 0; slot.fbp = NULL; slot.ep = NULL; slot.endoff = 0; /* * Execute access is required to search the directory. * Access for write is interpreted as allowing * deletion of files in the directory. */ if (err = ud_iaccess(dp, IEXEC|IWRITE, cr)) { return (err); } buf = (uint8_t *)kmem_zalloc(udf_vfsp->udf_lbsize, KM_SLEEP); rw_enter(&dp->i_contents, RW_WRITER); if (err = ud_dircheckforname(dp, namep, namelen, &slot, &ip, buf, cr)) { goto out_novfs; } if (ip == NULL) { err = ENOENT; goto out_novfs; } if (oip && oip != ip) { err = ENOENT; goto out_novfs; } if ((mode = ip->i_type) == VDIR) { /* * vn_vfswlock() prevents races between mount and rmdir. */ if (vn_vfswlock(ITOV(ip))) { err = EBUSY; goto out_novfs; } if (vn_mountedvfs(ITOV(ip)) != NULL && op != DR_RENAME) { err = EBUSY; goto out; } /* * If we are removing a directory, get a lock on it. * If the directory is empty, it will stay empty until * we can remove it. */ rw_enter(&ip->i_rwlock, RW_READER); } /* We must be holding i_contents */ rw_enter(&ip->i_contents, RW_READER); if (err = ud_sticky_remove_access(dp, ip, cr)) { rw_exit(&ip->i_contents); if (mode == VDIR) { rw_exit(&ip->i_rwlock); } goto out; } if (op == DR_RMDIR) { /* * For rmdir(2), some special checks are required. * (a) Don't remove any alias of the parent (e.g. "."). * (b) Don't remove the current directory. * (c) Make sure the entry is (still) a directory. * (d) Make sure the directory is empty. */ if (dp == ip || ITOV(ip) == cdir) { err = EINVAL; } else if (ip->i_type != VDIR) { err = ENOTDIR; } else if ((ip->i_nlink != 1) || (!ud_dirempty(ip, dp->i_uniqid, cr))) { /* * Directories do not have an * entry for "." so only one link * will be there */ err = EEXIST; /* SIGH should be ENOTEMPTY */ } if (err) { rw_exit(&ip->i_contents); if (mode == VDIR) { rw_exit(&ip->i_rwlock); } goto out; } } else if (op == DR_REMOVE) { /* * unlink(2) requires a different check: allow only * privileged processes to unlink a directory. */ struct vnode *vp = ITOV(ip); if (vp->v_type == VDIR && secpolicy_fs_linkdir(cr, vp->v_vfsp)) { err = EPERM; rw_exit(&ip->i_contents); rw_exit(&ip->i_rwlock); goto out; } } rw_exit(&ip->i_contents); /* * Remove the cache'd entry, if any. */ dnlc_remove(ITOV(dp), namep); /* * We can collapse all the directory * entries that are deleted into one big entry * but the better way is to * defer it till next directory entry * creation. where we can do this * in a more efficient way */ fid = slot.ep; /* * If this is the last entry * just truncate the file instead * of marking it deleted */ if ((slot.offset + FID_LEN(fid)) == dp->i_size) { fbrelse(slot.fbp, S_OTHER); if ((err = ud_itrunc(dp, slot.offset, 0, cr)) != 0) { goto out; } } else { fid->fid_flags |= FID_DELETED; if ((err = ud_ip_off2bno(dp, slot.offset, &tbno)) != 0) { goto out; } ud_make_tag(dp->i_udf, &fid->fid_tag, UD_FILE_ID_DESC, tbno, FID_LEN(fid)); err = ud_write_fid(dp, &slot, buf); } slot.fbp = NULL; /* * If we were removing a directory, it is 'gone' now so we can * unlock it. */ if (mode == VDIR) { rw_exit(&ip->i_rwlock); } mutex_enter(&dp->i_tlock); dp->i_flag |= IUPD|ICHG; mutex_exit(&dp->i_tlock); mutex_enter(&ip->i_tlock); ip->i_flag |= ICHG; mutex_exit(&ip->i_tlock); if (err != 0) { goto out; } rw_enter(&ip->i_contents, RW_WRITER); /* * Now dispose of the inode. */ if (ip->i_nlink > 0) { if ((op == DR_RMDIR) && (ip->i_type == VDIR)) { /* * Decrement by 1 because there is no "." * Clear the inode, but there may be other hard * links so don't free the inode. * Decrement the dp linkcount because we're * trashing the ".." entry. */ ip->i_nlink --; dp->i_nlink--; dnlc_remove(ITOV(ip), "."); dnlc_remove(ITOV(ip), ".."); /* * (void) ud_itrunc(ip, 0, 0, cr); */ } else { ip->i_nlink--; } } ITIMES_NOLOCK(dp); ITIMES_NOLOCK(ip); rw_exit(&ip->i_contents); out: if (mode == VDIR) { vn_vfsunlock(ITOV(ip)); } out_novfs: ASSERT(RW_WRITE_HELD(&dp->i_contents)); if (slot.fbp != NULL) { fbrelse(slot.fbp, S_OTHER); } rw_exit(&dp->i_contents); if (ip) { /* * If no errors, send any events after locks are dropped, * but before the VN_RELE(). */ if (err == 0) { if (op == DR_REMOVE) { vnevent_remove(ITOV(ip), ITOV(dp), namep, ctp); } else if (op == DR_RMDIR) { vnevent_rmdir(ITOV(ip), ITOV(dp), namep, ctp); } } VN_RELE(ITOV(ip)); } kmem_free(buf, udf_vfsp->udf_lbsize); return (err); }
/* * 1. When we find a slot that belonged to a file which was deleted * and is in the middle of the directory * 2. There is not empty slot available. The new entry * will be at the end of the directory and fits in the same block. * 3. There is no empty slot available. The new * entry will not fit the left over directory * so we need to allocate a new block. If * we cannot allocate a proximity block we need * to allocate a new icb, and data block. */ int ud_dirprepareentry(struct ud_inode *dp, struct slot *slotp, uint8_t *buf, struct cred *cr) { struct fbuf *fbp; uint16_t old_dtype; int32_t error = 0; uint32_t entrysize, count, offset, tbno, old_size, off; struct file_id *fid; int32_t lbsize, lbmask, mask; ASSERT(RW_WRITE_HELD(&dp->i_rwlock)); ASSERT((slotp->status == NONE) || (slotp->status == FOUND)); ud_printf("ud_dirprepareentry\n"); lbsize = dp->i_udf->udf_lbsize; lbmask = dp->i_udf->udf_lbmask; mask = ~lbmask; fid = (struct file_id *)buf; entrysize = FID_LEN(fid); /* * If we didn't find a slot, then indicate that the * new slot belongs at the end of the directory. * If we found a slot, then the new entry can be * put at slotp->offset. */ if (slotp->status == NONE) { /* * We did not find a slot, the next * entry will be in the end of the directory * see if we can fit the new entry inside * the old block. If not allocate a new block. */ if (entrysize > slotp->size) { /* * extend the directory * size by one new block */ old_dtype = dp->i_desc_type; old_size = (uint32_t)dp->i_size; error = ud_bmap_write(dp, slotp->offset, blkoff(dp->i_udf, slotp->offset) + entrysize, 0, cr); if (error != 0) { return (error); } if (old_dtype != dp->i_desc_type) { /* * oops we changed the astrat * of the file, we have to * recaliculate tags * fortunately we donot have more * than one lbsize to handle here */ if ((error = ud_ip_off2bno(dp, 0, &tbno)) != 0) { return (error); } if ((error = fbread(ITOV(dp), 0, dp->i_udf->udf_lbsize, S_WRITE, &fbp)) != 0) { return (error); } off = 0; while (off < old_size) { struct file_id *tfid; tfid = (struct file_id *) (fbp->fb_addr + off); ud_make_tag(dp->i_udf, &tfid->fid_tag, UD_FILE_ID_DESC, tbno, FID_LEN(tfid)); off += FID_LEN(tfid); } if (error = ud_fbwrite(fbp, dp)) { return (error); } } } else { /* Extend the directory size */ if (dp->i_desc_type != ICB_FLAG_ONE_AD) { ASSERT(dp->i_ext); dp->i_ext[dp->i_ext_used - 1].ib_count += entrysize; } } dp->i_size += entrysize; dp->i_flag |= IUPD|ICHG|IATTCHG; ITIMES_NOLOCK(dp); } else if (slotp->status != FOUND) { cmn_err(CE_WARN, "status is not NONE/FOUND"); return (EINVAL); } if ((error = ud_ip_off2bno(dp, slotp->offset, &tbno)) != 0) { return (error); } ud_make_tag(dp->i_udf, &fid->fid_tag, UD_FILE_ID_DESC, tbno, FID_LEN(fid)); /* * fbread cannot cross a * MAXBSIZE boundary so handle it here */ offset = slotp->offset; if ((error = fbread(ITOV(dp), offset & mask, lbsize, S_WRITE, &fbp)) != 0) { return (error); } if ((offset & mask) != ((offset + entrysize) & mask)) { count = entrysize - ((offset + entrysize) & lbmask); } else { count = entrysize; } bcopy((caddr_t)buf, fbp->fb_addr + (offset & lbmask), count); if (error = ud_fbwrite(fbp, dp)) { return (error); } if (entrysize > count) { if ((error = fbread(ITOV(dp), (offset + entrysize) & mask, lbsize, S_WRITE, &fbp)) != 0) { return (error); } bcopy((caddr_t)(buf + count), fbp->fb_addr, entrysize - count); if (error = ud_fbwrite(fbp, dp)) { return (error); } } dp->i_flag |= IUPD|ICHG|IATTCHG; ITIMES_NOLOCK(dp); return (error); }
int ud_dirrename(struct ud_inode *sdp, struct ud_inode *sip, struct ud_inode *tdp, struct ud_inode *tip, char *namep, uint8_t *buf, struct slot *slotp, struct cred *cr) { int32_t error = 0, doingdirectory; struct file_id *fid; ud_printf("ud_dirrename\n"); ASSERT(sdp->i_udf != NULL); ASSERT(MUTEX_HELD(&sdp->i_udf->udf_rename_lck)); ASSERT(RW_WRITE_HELD(&tdp->i_rwlock)); ASSERT(buf); ASSERT(slotp->ep); fid = slotp->ep; /* * Short circuit rename of something to itself. */ if (sip->i_icb_lbano == tip->i_icb_lbano) { return (ESAME); /* special KLUDGE error code */ } /* * Everything is protected under the vfs_rename_lock so the ordering * of i_contents locks doesn't matter here. */ rw_enter(&sip->i_contents, RW_READER); rw_enter(&tip->i_contents, RW_READER); /* * Check that everything is on the same filesystem. */ if ((ITOV(tip)->v_vfsp != ITOV(tdp)->v_vfsp) || (ITOV(tip)->v_vfsp != ITOV(sip)->v_vfsp)) { error = EXDEV; /* XXX archaic */ goto out; } /* * Must have write permission to rewrite target entry. */ if ((error = ud_iaccess(tdp, IWRITE, cr)) != 0 || (error = ud_sticky_remove_access(tdp, tip, cr)) != 0) goto out; /* * Ensure source and target are compatible (both directories * or both not directories). If target is a directory it must * be empty and have no links to it; in addition it must not * be a mount point, and both the source and target must be * writable. */ doingdirectory = (sip->i_type == VDIR); if (tip->i_type == VDIR) { if (!doingdirectory) { error = EISDIR; goto out; } /* * vn_vfswlock will prevent mounts from using the directory * until we are done. */ if (vn_vfswlock(ITOV(tip))) { error = EBUSY; goto out; } if (vn_mountedvfs(ITOV(tip)) != NULL) { vn_vfsunlock(ITOV(tip)); error = EBUSY; goto out; } if (!ud_dirempty(tip, tdp->i_uniqid, cr) || tip->i_nlink > 2) { vn_vfsunlock(ITOV(tip)); error = EEXIST; /* SIGH should be ENOTEMPTY */ goto out; } } else if (doingdirectory) { error = ENOTDIR; goto out; } /* * Rewrite the inode pointer for target name entry * from the target inode (ip) to the source inode (sip). * This prevents the target entry from disappearing * during a crash. Mark the directory inode to reflect the changes. */ dnlc_remove(ITOV(tdp), namep); fid->fid_icb.lad_ext_prn = SWAP_16(sip->i_icb_prn); fid->fid_icb.lad_ext_loc = SWAP_32(sip->i_icb_block); dnlc_enter(ITOV(tdp), namep, ITOV(sip)); ud_make_tag(tdp->i_udf, &fid->fid_tag, UD_FILE_ID_DESC, SWAP_32(fid->fid_tag.tag_loc), FID_LEN(fid)); error = ud_write_fid(tdp, slotp, buf); if (error) { if (doingdirectory) { vn_vfsunlock(ITOV(tip)); } goto out; } /* * Upgrade to write lock on tip */ rw_exit(&tip->i_contents); rw_enter(&tip->i_contents, RW_WRITER); mutex_enter(&tdp->i_tlock); tdp->i_flag |= IUPD|ICHG; mutex_exit(&tdp->i_tlock); /* * Decrement the link count of the target inode. * Fix the ".." entry in sip to point to dp. * This is done after the new entry is on the disk. */ tip->i_nlink--; mutex_enter(&tip->i_tlock); tip->i_flag |= ICHG; mutex_exit(&tip->i_tlock); if (doingdirectory) { /* * The entry for tip no longer exists so I can unlock the * vfslock. */ vn_vfsunlock(ITOV(tip)); /* * Decrement target link count once more if it was a directory. */ if (tip->i_nlink != 0) { cmn_err(CE_WARN, "ud_direnter: target directory link count != 0"); rw_exit(&tip->i_contents); rw_exit(&sip->i_contents); return (EINVAL); } /* * Renaming a directory with the parent different * requires that ".." be rewritten. The window is * still there for ".." to be inconsistent, but this * is unavoidable, and a lot shorter than when it was * done in a user process. We decrement the link * count in the new parent as appropriate to reflect * the just-removed target. If the parent is the * same, this is appropriate since the original * directory is going away. If the new parent is * different, dirfixdotdot() will bump the link count * back. */ tdp->i_nlink--; mutex_enter(&tdp->i_tlock); tdp->i_flag |= ICHG; mutex_exit(&tdp->i_tlock); ITIMES_NOLOCK(tdp); if (sdp != tdp) { rw_exit(&tip->i_contents); rw_exit(&sip->i_contents); error = ud_dirfixdotdot(sip, sdp, tdp); return (error); } } out: rw_exit(&tip->i_contents); rw_exit(&sip->i_contents); return (error); }
/* ARGSUSED2 */ int ud_dirmakedirect(struct ud_inode *ip, struct ud_inode *dp, struct cred *cr) { int32_t err; uint32_t blkno, size, parent_len, tbno; struct fbuf *fbp; struct file_id *fid; struct icb_ext *iext; ud_printf("ud_dirmakedirect\n"); ASSERT(RW_WRITE_HELD(&ip->i_contents)); ASSERT(RW_WRITE_HELD(&dp->i_rwlock)); parent_len = sizeof (struct file_id); if ((ip->i_desc_type != ICB_FLAG_ONE_AD) || (parent_len > ip->i_max_emb)) { ASSERT(ip->i_ext); /* * Allocate space for the directory we're creating. */ if ((err = ud_alloc_space(ip->i_vfs, ip->i_icb_prn, 0, 1, &blkno, &size, 0, 0)) != 0) { return (err); } /* * init with the size of * directory with just the * parent */ ip->i_size = sizeof (struct file_id); ip->i_flag |= IUPD|ICHG|IATTCHG; iext = ip->i_ext; iext->ib_prn = ip->i_icb_prn; iext->ib_block = blkno; iext->ib_count = ip->i_size; iext->ib_offset = 0; ip->i_ext_used = 1; } else { ip->i_size = sizeof (struct file_id); ip->i_flag |= IUPD|ICHG|IATTCHG; } ITIMES_NOLOCK(ip); /* * Update the dp link count and write out the change. * This reflects the ".." entry we'll soon write. */ if (dp->i_nlink == MAXLINK) { return (EMLINK); } dp->i_nlink++; dp->i_flag |= ICHG; ud_iupdat(dp, 1); /* * Initialize directory with ".." * Since the parent directory is locked, we don't have to * worry about anything changing when we drop the write * lock on (ip). */ rw_exit(&ip->i_contents); if ((err = fbread(ITOV(ip), (offset_t)0, ip->i_udf->udf_lbsize, S_WRITE, &fbp)) != 0) { rw_enter(&ip->i_contents, RW_WRITER); return (err); } bzero(fbp->fb_addr, ip->i_udf->udf_lbsize); fid = (struct file_id *)fbp->fb_addr; fid->fid_ver = SWAP_16(1); fid->fid_flags = FID_DIR | FID_PARENT; fid->fid_icb.lad_ext_len = SWAP_32(dp->i_udf->udf_lbsize); fid->fid_icb.lad_ext_loc = SWAP_32(dp->i_icb_block); fid->fid_icb.lad_ext_prn = SWAP_16(dp->i_icb_prn); /* * fid_idlen, fid_iulen and fid_spec are zero * due to bzero above */ if ((err = ud_ip_off2bno(ip, 0, &tbno)) == 0) { ud_make_tag(ip->i_udf, &fid->fid_tag, UD_FILE_ID_DESC, tbno, FID_LEN(fid)); } err = ud_fbwrite(fbp, ip); rw_enter(&ip->i_contents, RW_WRITER); return (err); }