/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct slot { enum {NONE, COMPACT, FOUND, EXIST} status; off_t offset; /* offset of area with free space */ int size; /* size of area at slotoffset */ struct fbuf *fbp; /* dir buf where slot is */ struct file_id *ep; /* pointer to slot */ off_t endoff; /* last useful location found in search */ }; int32_t ud_dircheckforname(struct ud_inode *, char *, int, struct slot *, struct ud_inode **, uint8_t *, struct cred *); int32_t ud_dirempty(struct ud_inode *, uint64_t, struct cred *); int32_t str2cmp(char *, int32_t, char *, int32_t, char *, int32_t); int32_t ud_dircheckpath(int32_t, struct ud_inode *, struct cred *); int32_t ud_dirmakeinode(struct ud_inode *, struct ud_inode **, struct vattr *, enum de_op, struct cred *); int32_t ud_diraddentry(struct ud_inode *, char *, enum de_op, int, struct slot *, struct ud_inode *, struct ud_inode *, struct cred *); int32_t ud_dirmakedirect(struct ud_inode *, struct ud_inode *, struct cred *); int32_t ud_dirrename(struct ud_inode *, struct ud_inode *, struct ud_inode *, struct ud_inode *, char *, uint8_t *, struct slot *, struct cred *); int32_t ud_dirprepareentry(struct ud_inode *, struct slot *, uint8_t *, struct cred *); int32_t ud_dirfixdotdot(struct ud_inode *, struct ud_inode *, struct ud_inode *); int32_t ud_write_fid(struct ud_inode *, struct slot *, uint8_t *); int ud_dirlook(struct ud_inode *dip, char *namep, struct ud_inode **ipp, struct cred *cr, int32_t skipdnlc) { struct udf_vfs *udf_vfsp; int32_t error = 0, namelen, adhoc_search; u_offset_t offset, adhoc_offset, dirsize, end; struct vnode *dvp, *vp; struct fbuf *fbp; struct file_id *fid; uint8_t *fname, dummy[3]; int32_t id_len, doingchk; uint32_t old_loc; uint16_t old_prn; uint8_t *dname; uint8_t *buf = NULL; ud_printf("ud_dirlook\n"); udf_vfsp = dip->i_udf; restart: doingchk = 0; old_prn = 0xFFFF; old_loc = 0; dvp = ITOV(dip); /* * Check accessibility of directory. */ if (dip->i_type != VDIR) { return (ENOTDIR); } if (error = ud_iaccess(dip, IEXEC, cr)) { return (error); } /* * Null component name is synonym for directory being searched. */ if (*namep == '\0') { VN_HOLD(dvp); *ipp = dip; return (0); } namelen = strlen(namep); if ((namelen == 1) && (namep[0] == '.') && (namep[1] == '\0')) { /* Current directory */ VN_HOLD(dvp); *ipp = dip; dnlc_enter(dvp, namep, ITOV(*ipp)); return (0); } if ((!skipdnlc) && (vp = dnlc_lookup(dvp, namep))) { /* vp is already held from dnlc_lookup */ *ipp = VTOI(vp); return (0); } dname = kmem_zalloc(1024, KM_SLEEP); buf = kmem_zalloc(udf_vfsp->udf_lbsize, KM_SLEEP); /* * Read lock the inode we are searching. You will notice that we * didn't hold the read lock while searching the dnlc. This means * that the entry could now be in the dnlc. This doesn't cause any * problems because dnlc_enter won't add an entry if it is already * there. */ rw_enter(&dip->i_rwlock, RW_READER); /* * Take care to look at dip->i_diroff only once, as it * may be changing due to other threads/cpus. */ recheck: offset = dip->i_diroff; end = dirsize = dip->i_size; if (offset > dirsize) { offset = 0; } adhoc_offset = offset; adhoc_search = (offset == 0) ? 1 : 2; fbp = NULL; while (adhoc_search--) { while (offset < end) { error = ud_get_next_fid(dip, &fbp, offset, &fid, &fname, buf); if (error != 0) { break; } if ((fid->fid_flags & FID_DELETED) == 0) { if (fid->fid_flags & FID_PARENT) { id_len = 2; fname = dummy; dummy[0] = '.'; dummy[1] = '.'; dummy[2] = '\0'; } else { if ((error = ud_uncompress( fid->fid_idlen, &id_len, fname, dname)) != 0) { break; } fname = (uint8_t *)dname; fname[id_len] = '\0'; } if ((namelen == id_len) && (strncmp(namep, (caddr_t)fname, namelen) == 0)) { uint32_t loc; uint16_t prn; loc = SWAP_32(fid->fid_icb.lad_ext_loc); prn = SWAP_16(fid->fid_icb.lad_ext_prn); dip->i_diroff = offset + FID_LEN(fid); if (doingchk) { if ((loc == old_loc) && (prn == old_prn)) { goto checkok; } else { if (fbp != NULL) { fbrelse(fbp, S_READ); fbp = NULL; } VN_RELE(ITOV(*ipp)); rw_exit(&dip->i_rwlock); goto restart; } /* NOTREACHED */ } if (namelen == 2 && fname[0] == '.' && fname[1] == '.') { struct timespec32 omtime; omtime = dip->i_mtime; rw_exit(&dip->i_rwlock); error = ud_iget(dip->i_vfs, prn, loc, ipp, NULL, cr); rw_enter(&dip->i_rwlock, RW_READER); if (error) { goto done; } if ((omtime.tv_sec != dip->i_mtime.tv_sec) || (omtime.tv_nsec != dip->i_mtime.tv_nsec)) { doingchk = 1; old_prn = prn; old_loc = loc; dip->i_diroff = 0; if (fbp != NULL) { fbrelse(fbp, S_READ); fbp = NULL; } goto recheck; } } else { error = ud_iget(dip->i_vfs, prn, loc, ipp, NULL, cr); } checkok: if (error == 0) { dnlc_enter(dvp, namep, ITOV(*ipp)); } goto done; } } offset += FID_LEN(fid); } if (fbp != NULL) { fbrelse(fbp, S_READ); fbp = NULL; } end = adhoc_offset; offset = 0; } error = ENOENT; done: kmem_free(buf, udf_vfsp->udf_lbsize); kmem_free(dname, 1024); if (fbp != NULL) { fbrelse(fbp, S_READ); } rw_exit(&dip->i_rwlock); return (error); } int ud_direnter(struct ud_inode *tdp, char *namep, enum de_op op, struct ud_inode *sdp, struct ud_inode *sip, struct vattr *vap, struct ud_inode **ipp, struct cred *cr) { struct udf_vfs *udf_vfsp; struct ud_inode *tip; struct slot slot; int32_t namlen, err; char *s; uint8_t *buf = NULL; ud_printf("ud_direnter\n"); udf_vfsp = tdp->i_udf; /* don't allow '/' characters in pathname component */ for (s = namep, namlen = 0; *s; s++, namlen++) { if (*s == '/') { return (EACCES); } } if (namlen == 0) { cmn_err(CE_WARN, "name length == 0 in ud_direnter"); return (EINVAL); } ASSERT(RW_WRITE_HELD(&tdp->i_rwlock)); /* * If name is "." or ".." then if this is a create look it up * and return EEXIST. Rename or link TO "." or ".." is forbidden. */ if (namep[0] == '.' && (namlen == 1 || (namlen == 2 && namep[1] == '.'))) { if (op == DE_RENAME) { return (EINVAL); /* *SIGH* should be ENOTEMPTY */ } if (ipp) { /* * ud_dirlook will acquire the i_rwlock */ rw_exit(&tdp->i_rwlock); if (err = ud_dirlook(tdp, namep, ipp, cr, 0)) { rw_enter(&tdp->i_rwlock, RW_WRITER); return (err); } rw_enter(&tdp->i_rwlock, RW_WRITER); } return (EEXIST); } tip = NULL; slot.status = NONE; slot.offset = 0; slot.size = 0; slot.fbp = NULL; slot.ep = NULL; slot.endoff = 0; /* * For link and rename lock the source entry and check the link count * to see if it has been removed while it was unlocked. If not, we * increment the link count and force the inode to disk to make sure * that it is there before any directory entry that points to it. */ if (op == DE_LINK || op == DE_RENAME) { rw_enter(&sip->i_contents, RW_WRITER); if (sip->i_nlink == 0) { rw_exit(&sip->i_contents); return (ENOENT); } if (sip->i_nlink == MAXLINK) { rw_exit(&sip->i_contents); return (EMLINK); } sip->i_nlink++; mutex_enter(&sip->i_tlock); sip->i_flag |= ICHG; mutex_exit(&sip->i_tlock); ud_iupdat(sip, 1); rw_exit(&sip->i_contents); } /* * If target directory has not been removed, then we can consider * allowing file to be created. */ if (tdp->i_nlink == 0) { err = ENOENT; goto out2; } /* * Check accessibility of directory. */ if (tdp->i_type != VDIR) { err = ENOTDIR; goto out2; } /* * Execute access is required to search the directory. */ if (err = ud_iaccess(tdp, IEXEC, cr)) { goto out2; } /* * If this is a rename of a directory and the parent is * different (".." must be changed), then the source * directory must not be in the directory hierarchy * above the target, as this would orphan everything * below the source directory. Also the user must have * write permission in the source so as to be able to * change "..". */ if (op == DE_RENAME) { if (sip == tdp) { err = EINVAL; goto out2; } rw_enter(&sip->i_contents, RW_READER); if ((sip->i_type == VDIR) && (sdp != tdp)) { uint32_t blkno; if ((err = ud_iaccess(sip, IWRITE, cr))) { rw_exit(&sip->i_contents); goto out2; } blkno = sip->i_icb_lbano; rw_exit(&sip->i_contents); if ((err = ud_dircheckpath(blkno, tdp, cr))) { goto out2; } } else { rw_exit(&sip->i_contents); } } /* * Search for the entry. Return VN_HELD tip if found. */ buf = kmem_zalloc(udf_vfsp->udf_lbsize, KM_SLEEP); rw_enter(&tdp->i_contents, RW_WRITER); if (err = ud_dircheckforname(tdp, namep, namlen, &slot, &tip, buf, cr)) { goto out; } if (tip) { switch (op) { case DE_CREATE : case DE_MKDIR : if (ipp) { *ipp = tip; err = EEXIST; } else { VN_RELE(ITOV(tip)); } break; case DE_RENAME : err = ud_dirrename(sdp, sip, tdp, tip, namep, buf, &slot, cr); /* * We used to VN_RELE() here, but this * was moved down so that we could send * a vnevent after the locks were dropped. */ break; case DE_LINK : /* * Can't link to an existing file. */ VN_RELE(ITOV(tip)); err = EEXIST; break; } } else { /* * The entry does not exist. Check write permission in * directory to see if entry can be created. */ if (err = ud_iaccess(tdp, IWRITE, cr)) { goto out; } if ((op == DE_CREATE) || (op == DE_MKDIR)) { /* * Make new inode and directory entry as required. */ if (err = ud_dirmakeinode(tdp, &sip, vap, op, cr)) goto out; } if (err = ud_diraddentry(tdp, namep, op, namlen, &slot, sip, sdp, cr)) { if ((op == DE_CREATE) || (op == DE_MKDIR)) { /* * Unmake the inode we just made. */ rw_enter(&sip->i_contents, RW_WRITER); if (sip->i_type == VDIR) { tdp->i_nlink--; } sip->i_nlink = 0; mutex_enter(&sip->i_tlock); sip->i_flag |= ICHG; mutex_exit(&sip->i_tlock); rw_exit(&sip->i_contents); VN_RELE(ITOV(sip)); sip = NULL; } } else if (ipp) { *ipp = sip; } else if ((op == DE_CREATE) || (op == DE_MKDIR)) { VN_RELE(ITOV(sip)); } } out: if (buf != NULL) { kmem_free(buf, udf_vfsp->udf_lbsize); } if (slot.fbp) { fbrelse(slot.fbp, S_OTHER); } rw_exit(&tdp->i_contents); if (op == DE_RENAME) { /* * If it's all good, send events after locks are dropped * but before vnodes are released. */ if (err == 0) { if (tip) { vnevent_rename_dest(ITOV(tip), ITOV(tdp), namep); } if (sdp != tdp) { vnevent_rename_dest_dir(ITOV(tdp)); } } /* * The following VN_RELE() was moved from the * DE_RENAME case above */ if (tip) { VN_RELE(ITOV(tip)); } } out2: if (err && ((op == DE_LINK) || (op == DE_RENAME))) { /* * Undo bumped link count. */ rw_enter(&sip->i_contents, RW_WRITER); sip->i_nlink--; rw_exit(&sip->i_contents); mutex_enter(&sip->i_tlock); sip->i_flag |= ICHG; mutex_exit(&sip->i_tlock); } return (err); } /* * 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) { 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); } else if (op == DR_RMDIR) { vnevent_rmdir(ITOV(ip), ITOV(dp), namep); } } VN_RELE(ITOV(ip)); } kmem_free(buf, udf_vfsp->udf_lbsize); return (err); } int ud_dircheckforname(struct ud_inode *tdp, char *namep, int32_t namelen, struct slot *slotp, struct ud_inode **ipp, uint8_t *buf, struct cred *cr) { struct udf_vfs *udf_vfsp; uint32_t dirsize, offset; struct fbuf *fbp; struct file_id *fid; int32_t sz, error = 0, sz_req, matched = 0; uint8_t *nm; uint8_t *dname; int32_t id_len; ud_printf("ud_dircheckforname\n"); ASSERT(RW_WRITE_HELD(&tdp->i_rwlock)); fbp = NULL; dname = (uint8_t *)kmem_zalloc(1024, KM_SLEEP); udf_vfsp = tdp->i_udf; offset = 0; dirsize = tdp->i_size; if (slotp->status != FOUND) { int32_t temp; temp = 1024; /* set to size of dname allocated above */ if ((error = ud_compress(namelen, &temp, (uint8_t *)namep, dname)) != 0) { goto end; } sz_req = F_LEN + temp; sz_req = (sz_req + 3) & ~3; } while (offset < dirsize) { if ((error = ud_get_next_fid(tdp, &fbp, offset, &fid, &nm, buf)) != 0) { break; } if ((error = ud_uncompress(fid->fid_idlen, &id_len, nm, dname)) != 0) { break; } if ((fid->fid_flags & FID_DELETED) == 0) { /* Check for name match */ if (((namelen == id_len) && (strncmp(namep, (caddr_t)dname, namelen) == 0)) || ((fid->fid_flags & FID_PARENT) && (namep[0] == '.' && (namelen == 1 || (namelen == 2 && namep[1] == '.'))))) { tdp->i_diroff = offset; if ((fid->fid_flags & FID_PARENT) && (namelen == 1) && (namep[0] == '.')) { struct vnode *vp = ITOV(tdp); *ipp = tdp; VN_HOLD(vp); } else { uint16_t prn; uint32_t loc; prn = SWAP_16(fid->fid_icb.lad_ext_prn); loc = SWAP_32(fid->fid_icb.lad_ext_loc); if ((error = ud_iget(tdp->i_vfs, prn, loc, ipp, NULL, cr)) != 0) { fbrelse(fbp, S_OTHER); goto end; } } slotp->status = EXIST; slotp->offset = offset; slotp->size = FID_LEN(fid); slotp->fbp = fbp; slotp->ep = fid; slotp->endoff = 0; goto end; } } else { /* * see if we need to find an * empty slot and the current slot * matches */ if ((slotp->status != FOUND) || (matched == 0)) { sz = FID_LEN(fid); if (sz == sz_req) { slotp->status = FOUND; slotp->offset = offset; slotp->size = sz; } if (matched == 0) { if ((namelen == id_len) && (strncmp(namep, (caddr_t)dname, namelen) == 0)) { matched = 1; slotp->status = FOUND; slotp->offset = offset; slotp->size = sz; } } } } offset += FID_LEN(fid); } if (fbp) { fbrelse(fbp, S_OTHER); } if (slotp->status == NONE) { /* * We didn't find a slot; the new directory entry should be put * at the end of the directory. Return an indication of where * this is, and set "endoff" to zero; since we're going to have * to extend the directory, we're certainly not going to * trucate it. */ slotp->offset = dirsize; if (tdp->i_desc_type == ICB_FLAG_ONE_AD) { slotp->size = tdp->i_max_emb - tdp->i_size; } else { slotp->size = udf_vfsp->udf_lbsize - slotp->offset & udf_vfsp->udf_lbmask; } slotp->endoff = 0; } *ipp = NULL; end: kmem_free((caddr_t)dname, 1024); return (error); } /* * Return 1 if the dir has all files * deleted except the parent * else return 0 */ /* ARGSUSED */ int ud_dirempty(struct ud_inode *ip, uint64_t ino, struct cred *cr) { offset_t off; int32_t empty = 1, error, count, entry_len, rcount; struct file_id *fid; caddr_t addr; uint32_t tbno; int32_t desc_len; ud_printf("ud_dirempty\n"); ASSERT(RW_LOCK_HELD(&ip->i_contents)); if (ip->i_size == 0) { return (empty); } desc_len = 1024; addr = kmem_zalloc(desc_len, KM_SLEEP); fid = (struct file_id *)addr; for (off = 0; off < ip->i_size; off += entry_len) { /* * First read fid * and verify checksum */ rcount = sizeof (struct file_id); error = ud_rdwri(UIO_READ, FREAD, ip, addr, rcount, off, UIO_SYSSPACE, &count, cr); if ((error != 0) || (count != 0)) { empty = 0; break; } if ((error = ud_ip_off2bno(ip, off, &tbno)) != 0) { empty = 0; break; } /* * We verify the tag id and also the FID_LEN. * FID_LEN should be <= desc_len. */ if (ud_verify_tag_and_desc(&fid->fid_tag, UD_FILE_ID_DESC, tbno, 0, desc_len) != 0) { /* Corrupted directory */ empty = 0; break; } /* * Read the fid + iulen + len * Now verify both checksum andCRC */ rcount = FID_LEN(fid); error = ud_rdwri(UIO_READ, FREAD, ip, addr, rcount, off, UIO_SYSSPACE, &count, cr); if ((error != 0) || (count != 0)) { empty = 0; break; } /* * Now that the entire decsriptor is read we verify the * crc. */ if (ud_verify_tag_and_desc(&fid->fid_tag, UD_FILE_ID_DESC, tbno, 1, rcount) != 0) { /* Corrupted directory */ empty = 0; break; } /* * Is the file deleted */ if ((fid->fid_flags & FID_DELETED) == 0) { if ((fid->fid_flags & FID_PARENT) == 0) { empty = 0; break; } } entry_len = FID_LEN(fid); } kmem_free(addr, 1024); return (empty); } int ud_dircheckpath(int32_t blkno, struct ud_inode *target, struct cred *cr) { int32_t err = 0; struct vfs *vfsp; struct udf_vfs *udf_vfsp; struct fbuf *fbp; struct file_id *fid; struct ud_inode *ip, *tip; uint16_t prn; uint32_t lbno, dummy, tbno; daddr_t parent_icb_loc; ud_printf("ud_dircheckpath\n"); udf_vfsp = target->i_udf; ip = target; ASSERT(udf_vfsp != NULL); ASSERT(MUTEX_HELD(&target->i_udf->udf_rename_lck)); ASSERT(RW_WRITE_HELD(&ip->i_rwlock)); if (ip->i_icb_lbano == blkno) { err = EINVAL; goto out; } if (ip->i_icb_lbano == udf_vfsp->udf_root_blkno) { goto out; } /* * Search back through the directory tree, using the PARENT entries * Fail any attempt to move a directory into an ancestor directory. */ for (;;) { if ((err = fbread(ITOV(ip), 0, udf_vfsp->udf_lbsize, S_READ, &fbp)) != 0) { break; } if ((err = ud_ip_off2bno(ip, 0, &tbno)) != 0) { break; } fid = (struct file_id *)fbp->fb_addr; /* IS this a valid file_identifier */ if (ud_verify_tag_and_desc(&fid->fid_tag, UD_FILE_ID_DESC, tbno, 1, udf_vfsp->udf_lbsize) != 0) { break; } if ((fid->fid_flags & FID_DELETED) != 0) { break; } if ((fid->fid_flags & FID_PARENT) == 0) { /* * This cannot happen unless * something is grossly wrong * First entry has to be parent */ break; } prn = SWAP_16(fid->fid_icb.lad_ext_prn); lbno = SWAP_32(fid->fid_icb.lad_ext_loc); parent_icb_loc = ud_xlate_to_daddr(udf_vfsp, prn, lbno, 1, &dummy); ASSERT(dummy == 1); if (parent_icb_loc == blkno) { err = EINVAL; break; } vfsp = ip->i_vfs; udf_vfsp = ip->i_udf; if (parent_icb_loc == udf_vfsp->udf_root_blkno) { break; } if (fbp != NULL) { fbrelse(fbp, S_OTHER); fbp = NULL; } if (ip != target) { rw_exit(&ip->i_rwlock); VN_RELE(ITOV(ip)); } /* * Race to get the inode. */ if (err = ud_iget(vfsp, prn, lbno, &tip, NULL, cr)) { ip = NULL; break; } ip = tip; rw_enter(&ip->i_rwlock, RW_READER); } if (fbp) { fbrelse(fbp, S_OTHER); } out: if (ip) { if (ip != target) { rw_exit(&ip->i_rwlock); VN_RELE(ITOV(ip)); } } return (err); } int ud_dirmakeinode(struct ud_inode *tdp, struct ud_inode **ipp, struct vattr *vap, enum de_op op, struct cred *cr) { struct ud_inode *ip; int32_t error; ASSERT(vap != NULL); ASSERT(op == DE_CREATE || op == DE_MKDIR); ASSERT((vap->va_mask & (AT_TYPE|AT_MODE)) == (AT_TYPE|AT_MODE)); ASSERT(RW_WRITE_HELD(&tdp->i_rwlock)); /* * Allocate a new inode. */ if ((error = ud_ialloc(tdp, &ip, vap, cr)) != 0) { return (error); } ASSERT(ip != NULL); rw_enter(&ip->i_contents, RW_WRITER); if (op == DE_MKDIR) { error = ud_dirmakedirect(ip, tdp, cr); } ip->i_flag |= IACC|IUPD|ICHG; /* * Clear IACC and/or IUPD if the caller specified the atime and/or * mtime fields. They were set from the passed in attributes in * ud_ialloc(). */ if (vap->va_mask & AT_ATIME) ip->i_flag &= ~IACC; if (vap->va_mask & AT_MTIME) ip->i_flag &= ~IUPD; /* * push inode before it's name appears in a directory */ ud_iupdat(ip, 1); *ipp = ip; rw_exit(&ip->i_contents); return (error); } /* * Enter the file sip in the directory tdp with name namep. */ int ud_diraddentry(struct ud_inode *tdp, char *namep, enum de_op op, int32_t namelen, struct slot *slotp, struct ud_inode *sip, struct ud_inode *sdp, struct cred *cr) { struct udf_vfs *udf_vfsp; int32_t error, temp; struct file_id *fid; uint8_t *buf = NULL; ASSERT(RW_WRITE_HELD(&tdp->i_rwlock)); ud_printf("ud_diraddentry\n"); udf_vfsp = sip->i_udf; /* * Check inode to be linked to see if it is in the * same filesystem. */ if (ITOV(tdp)->v_vfsp != ITOV(sip)->v_vfsp) { error = EXDEV; goto bad; } if ((op == DE_RENAME) && (sip->i_type == VDIR)) { if ((error = ud_dirfixdotdot(sip, sdp, tdp)) != 0) { goto bad; } } buf = (uint8_t *)kmem_zalloc(udf_vfsp->udf_lbsize, KM_SLEEP); /* * Fill in entry data. */ fid = (struct file_id *)buf; fid->fid_ver = SWAP_16(1); if (sip->i_type == VDIR) { fid->fid_flags = FID_DIR; } else { fid->fid_flags = 0; } fid->fid_iulen = 0; fid->fid_icb.lad_ext_len = SWAP_32(sip->i_udf->udf_lbsize); fid->fid_icb.lad_ext_loc = SWAP_32(sip->i_icb_block); fid->fid_icb.lad_ext_prn = SWAP_16(sip->i_icb_prn); fid->fid_iulen = 0; temp = udf_vfsp->udf_lbsize - F_LEN; if ((error = ud_compress(namelen, &temp, (uint8_t *)namep, fid->fid_spec)) == 0) { fid->fid_idlen = (uint8_t)temp; error = ud_dirprepareentry(tdp, slotp, buf, cr); } kmem_free(buf, udf_vfsp->udf_lbsize); bad: return (error); } /* * Write a prototype directory into the empty inode ip, whose parent is dp. */ /* 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); } 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); } /* * 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); } /* * Fix the FID_PARENT entry of the child directory so that it points * to the new parent directory instead of the old one. Routine * assumes that dp is a directory and that all the inodes are on * the same file system. */ int ud_dirfixdotdot(struct ud_inode *dp, struct ud_inode *opdp, struct ud_inode *npdp) { int32_t err = 0; struct fbuf *fbp; struct file_id *fid; uint32_t loc, dummy, tbno; ud_printf("ud_dirfixdotdot\n"); ASSERT(opdp->i_type == VDIR); ASSERT(npdp->i_type == VDIR); ASSERT(RW_WRITE_HELD(&npdp->i_rwlock)); err = fbread(ITOV(dp), (offset_t)0, dp->i_udf->udf_lbsize, S_WRITE, &fbp); if (err || dp->i_nlink == 0 || dp->i_size < sizeof (struct file_id)) { goto bad; } if ((err = ud_ip_off2bno(dp, 0, &tbno)) != 0) { goto bad; } fid = (struct file_id *)fbp->fb_addr; if ((ud_verify_tag_and_desc(&fid->fid_tag, UD_FILE_ID_DESC, tbno, 1, dp->i_udf->udf_lbsize) != 0) || ((fid->fid_flags & (FID_DIR | FID_PARENT)) != (FID_DIR | FID_PARENT))) { err = ENOTDIR; goto bad; } loc = ud_xlate_to_daddr(dp->i_udf, SWAP_16(fid->fid_icb.lad_ext_prn), SWAP_32(fid->fid_icb.lad_ext_loc), 1, &dummy); ASSERT(dummy == 1); if (loc == npdp->i_icb_lbano) { goto bad; } /* * Increment the link count in the new parent inode and force it out. */ if (npdp->i_nlink == MAXLINK) { err = EMLINK; goto bad; } npdp->i_nlink++; mutex_enter(&npdp->i_tlock); npdp->i_flag |= ICHG; mutex_exit(&npdp->i_tlock); ud_iupdat(npdp, 1); /* * Rewrite the child FID_PARENT entry and force it out. */ dnlc_remove(ITOV(dp), ".."); fid->fid_icb.lad_ext_loc = SWAP_32(npdp->i_icb_block); fid->fid_icb.lad_ext_prn = SWAP_16(npdp->i_icb_prn); ud_make_tag(npdp->i_udf, &fid->fid_tag, UD_FILE_ID_DESC, tbno, FID_LEN(fid)); dnlc_enter(ITOV(dp), "..", ITOV(npdp)); err = ud_fbwrite(fbp, dp); fbp = NULL; if (err != 0) { goto bad; } /* * Decrement the link count of the old parent inode and force * it out. If opdp is NULL, then this is a new directory link; * it has no parent, so we need not do anything. */ if (opdp != NULL) { rw_enter(&opdp->i_contents, RW_WRITER); if (opdp->i_nlink != 0) { opdp->i_nlink--; mutex_enter(&opdp->i_tlock); opdp->i_flag |= ICHG; mutex_exit(&opdp->i_tlock); ud_iupdat(opdp, 1); } rw_exit(&opdp->i_contents); } return (0); bad: if (fbp) { fbrelse(fbp, S_OTHER); } return (err); } int32_t ud_write_fid(struct ud_inode *dp, struct slot *slot, uint8_t *buf) { struct udf_vfs *udf_vfsp; struct fbuf *lfbp; struct file_id *fid; int32_t error = 0; uint32_t lbsize, lbmask, count, old_count; ASSERT(slot->fbp); ASSERT(slot->ep); udf_vfsp = dp->i_udf; fid = slot->ep; lbsize = dp->i_udf->udf_lbsize; lbmask = dp->i_udf->udf_lbmask; if (((uint8_t *)fid >= buf) && ((uint8_t *)fid < &buf[udf_vfsp->udf_lbsize])) { if ((error = fbread(ITOV(dp), (offset_t)(slot->offset & ~lbmask), lbsize, S_WRITE, &lfbp)) != 0) { goto out; } /* * We do not need to write the * file name. So check if the entry * does not cross a block boundary * and write only required portions */ if (((slot->offset & lbmask) + sizeof (struct file_id)) > lbsize) { if ((slot->offset & lbmask) != 0) { old_count = lbsize - (slot->offset & lbmask); count = (slot->offset + sizeof (struct file_id)) & lbmask; } else { old_count = 0; count = sizeof (struct file_id); } bcopy(buf, lfbp->fb_addr + (slot->offset & lbmask), old_count); bcopy(buf + old_count, slot->fbp->fb_addr, count); error = ud_fbwrite(lfbp, dp); error = ud_fbwrite(slot->fbp, dp); } else { bcopy(buf, lfbp->fb_addr + (slot->offset & lbmask), sizeof (struct file_id)); error = ud_fbwrite(lfbp, dp); fbrelse(slot->fbp, S_OTHER); } } else { if ((error = ud_fbwrite(slot->fbp, dp)) != 0) { fid->fid_flags &= ~FID_DELETED; ud_make_tag(dp->i_udf, &fid->fid_tag, UD_FILE_ID_DESC, SWAP_32(fid->fid_tag.tag_loc), FID_LEN(fid)); } } slot->fbp = NULL; out: return (error); }