/*
- * Copyright (c) 2000-2007 Apple Inc. All rights reserved.
+ * Copyright (c) 2000-2008 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
#include <sys/systm.h>
#include <sys/kernel.h>
+#include <sys/param.h>
#include <sys/file_internal.h>
#include <sys/dirent.h>
#include <sys/stat.h>
struct proc *p = vfs_context_proc(ap->a_context);
struct hfsmount *hfsmp;
int busy;
+ int knownrefs = 0;
+ int tooktrunclock = 0;
if ( hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK) != 0)
return (0);
cp = VTOC(vp);
hfsmp = VTOHFS(vp);
+ /*
+ * If the rsrc fork is a named stream, it holds a usecount on
+ * the data fork, which prevents the data fork from getting recycled, which
+ * then prevents the de-allocation of its extra blocks.
+ * Do checks for truncation on close. Purge extra extents if they
+ * exist. Make sure the vp is not a directory, that it has a resource
+ * fork, and that rsrc fork is a named stream.
+ */
+
+ if ((vp->v_type == VREG) && (cp->c_rsrc_vp)
+ && (vnode_isnamedstream(cp->c_rsrc_vp))) {
+ uint32_t blks;
+
+ blks = howmany(VTOF(vp)->ff_size, VTOVCB(vp)->blockSize);
+ /*
+ * If there are any extra blocks and there are only 2 refs on
+ * this vp (ourselves + rsrc fork holding ref on us), go ahead
+ * and try to truncate the extra blocks away.
+ */
+ if ((blks < VTOF(vp)->ff_blocks) && (!vnode_isinuse(vp, 2))) {
+ // release cnode lock ; must acquire truncate lock BEFORE cnode lock
+ hfs_unlock (cp);
+
+ hfs_lock_truncate(cp, TRUE);
+ tooktrunclock = 1;
+
+ if (hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK) != 0) {
+ hfs_unlock_truncate(cp, TRUE);
+ return (0);
+ }
+
+ //now re-test to make sure it's still valid.
+ if (cp->c_rsrc_vp) {
+ knownrefs = 1 + vnode_isnamedstream(cp->c_rsrc_vp);
+ if (!vnode_isinuse(vp, knownrefs)) {
+ blks = howmany(VTOF(vp)->ff_size, VTOVCB(vp)->blockSize);
+ if (blks < VTOF(vp)->ff_blocks) {
+ (void) hfs_truncate(vp, VTOF(vp)->ff_size, IO_NDELAY, 0, ap->a_context);
+ }
+ }
+ }
+ }
+ }
+
// if we froze the fs and we're exiting, then "thaw" the fs
if (hfsmp->hfs_freezing_proc == p && proc_exiting(p)) {
hfsmp->hfs_freezing_proc = NULL;
} else if (vnode_issystem(vp) && !busy) {
vnode_recycle(vp);
}
-
+ if (tooktrunclock) {
+ hfs_unlock_truncate(cp, TRUE);
+ }
+
hfs_unlock(cp);
return (0);
}
struct vnode *vp = ap->a_vp;
struct vnode_attr *vap = ap->a_vap;
- struct vnode *rvp = NULL;
+ struct vnode *rvp = NULLVP;
struct hfsmount *hfsmp;
struct cnode *cp;
uint64_t data_size;
}
if (cp->c_blocks - VTOF(vp)->ff_blocks) {
+ /* We deal with resource fork vnode iocount at the end of the function */
error = hfs_vgetrsrc(hfsmp, vp, &rvp, TRUE);
if (error) {
goto out;
}
-
rcp = VTOC(rvp);
if (rcp && rcp->c_rsrcfork) {
total_size += rcp->c_rsrcfork->ff_size;
* which are hardlink-ignorant, will ask for va_linkid.
*/
vap->va_fileid = (u_int64_t)cp->c_fileid;
- /* Hardlinked directories have multiple cnids and parents (one per link). */
- if ((v_type == VDIR) && (cp->c_flag & C_HARDLINK)) {
+ /*
+ * We need to use the origin cache for both hardlinked files
+ * and directories. Hardlinked directories have multiple cnids
+ * and parents (one per link). Hardlinked files also have their
+ * own parents and link IDs separate from the indirect inode number.
+ * If we don't use the cache, we could end up vending the wrong ID
+ * because the cnode will only reflect the link that was looked up most recently.
+ */
+ if (cp->c_flag & C_HARDLINK) {
vap->va_linkid = (u_int64_t)hfs_currentcnid(cp);
vap->va_parentid = (u_int64_t)hfs_currentparent(cp);
} else {
VNODE_ATTR_va_encoding | VNODE_ATTR_va_rdev |
VNODE_ATTR_va_data_size;
- /* if this is the root, let VFS to find out the mount name, which may be different from the real name */
+ /* If this is the root, let VFS to find out the mount name, which may be different from the real name.
+ * Otherwise, we need to just take care for hardlinked files, which need to be looked up, if necessary
+ */
if (VATTR_IS_ACTIVE(vap, va_name) && (cp->c_cnid != kHFSRootFolderID)) {
- /* Return the name for ATTR_CMN_NAME */
- if (cp->c_desc.cd_namelen == 0) {
- if ((cp->c_flag & C_HARDLINK) && ((cp->c_flag & C_DELETED) == 0 || (cp->c_linkcount > 1))) {
- cnid_t nextlinkid;
- cnid_t prevlinkid;
- struct vnode *file_vp;
-
- if ((error = hfs_lookuplink(hfsmp, cp->c_fileid, &prevlinkid, &nextlinkid))) {
- goto out;
- }
-
- //
- // don't bother trying to get a linkid that's the same
- // as the current cnid
- //
- if (nextlinkid == VTOC(vp)->c_cnid) {
- if (prevlinkid == VTOC(vp)->c_cnid) {
- hfs_unlock(cp);
- goto out2;
- } else {
- nextlinkid = prevlinkid;
- }
- }
-
- hfs_unlock(cp);
-
- if (nextlinkid == 0 || (error = hfs_vget(hfsmp, nextlinkid, &file_vp, 1))) {
- if (prevlinkid == 0 || (error = hfs_vget(hfsmp, prevlinkid, &file_vp, 1))) {
- goto out2;
- }
+ struct cat_desc linkdesc;
+ int lockflags;
+ int uselinkdesc = 0;
+ cnid_t nextlinkid = 0;
+ cnid_t prevlinkid = 0;
+
+ /* Get the name for ATTR_CMN_NAME. We need to take special care for hardlinks
+ * here because the info. for the link ID requested by getattrlist may be
+ * different than what's currently in the cnode. This is because the cnode
+ * will be filled in with the information for the most recent link ID that went
+ * through namei/lookup(). If there are competing lookups for hardlinks that point
+ * to the same inode, one (or more) getattrlists could be vended incorrect name information.
+ * Also, we need to beware of open-unlinked files which could have a namelen of 0. Note
+ * that if another hardlink sibling of this file is being unlinked, that could also thrash
+ * the name fields but it should *not* be treated like an open-unlinked file here.
+ */
+ if ((cp->c_flag & C_HARDLINK) &&
+ ((cp->c_desc.cd_namelen == 0) || (vap->va_linkid != cp->c_cnid))) {
+ /* If we have no name and our linkID is the raw inode number, then we may
+ * have an open-unlinked file. Go to the next link in this case.
+ */
+ if ((cp->c_desc.cd_namelen == 0) && (vap->va_linkid == cp->c_fileid)) {
+ if ((error = hfs_lookuplink(hfsmp, vap->va_linkid, &prevlinkid, &nextlinkid))) {
+ goto out;
+ }
}
-
- cp = VTOC(file_vp);
- if (hfs_lock(cp, HFS_SHARED_LOCK) == 0) {
- if (cp->c_desc.cd_namelen) {
- strlcpy(vap->va_name, (const char *)cp->c_desc.cd_nameptr, MAXPATHLEN);
- }
- hfs_unlock(cp);
- vnode_put(file_vp);
- goto out2;
+ else {
+ nextlinkid = vap->va_linkid;
}
-
- if (vnode_name(file_vp)) {
- strlcpy(vap->va_name, vnode_name(file_vp), MAXPATHLEN);
- } else {
- error = ENOENT;
+ /* Now probe the catalog for the linkID. Note that we don't know if we have
+ * the exclusive lock here for the cnode, so we can't just update the descriptor.
+ * Instead, we should just store the descriptor's value locally and then use it to pass
+ * out the name value as needed below.
+ */
+ if (nextlinkid) {
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
+ error = cat_findname(hfsmp, nextlinkid, &linkdesc);
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ if (error == 0) {
+ uselinkdesc = 1;
+ }
}
- vnode_put(file_vp);
- goto out2;
- } else {
- error = ENOENT;
- goto out;
- }
- } else {
- strlcpy(vap->va_name, (const char *)cp->c_desc.cd_nameptr, MAXPATHLEN);
- VATTR_SET_SUPPORTED(vap, va_name);
+ }
+
+ /* By this point, we either patched the name above, and the c_desc points
+ * to correct data, or it already did, in which case we just proceed by copying
+ * the name into the VAP. Note that we will never set va_name to supported if
+ * nextlinkid is never initialized. This could happen in the degenerate case above
+ * involving the raw inode number, where it has no nextlinkid. In this case, we will
+ * simply not export the name as supported.
+ */
+ if (uselinkdesc) {
+ strlcpy(vap->va_name, (const char *)linkdesc.cd_nameptr, MAXPATHLEN);
+ VATTR_SET_SUPPORTED(vap, va_name);
+ cat_releasedesc(&linkdesc);
+ }
+ else if (cp->c_desc.cd_namelen) {
+ strlcpy(vap->va_name, (const char *)cp->c_desc.cd_nameptr, MAXPATHLEN);
+ VATTR_SET_SUPPORTED(vap, va_name);
}
}
out:
hfs_unlock(cp);
-out2:
+ /*
+ * We need to drop the iocount on the rsrc fork vnode only *after* we've
+ * released the cnode lock, since vnode_put can trigger an inactive call, which
+ * will go back into the HFS and try to acquire a cnode lock.
+ */
if (rvp) {
vnode_put(rvp);
}
hfs_lock_truncate(cp, TRUE);
- if ((error = hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK)))
- goto out;
-
+ if ((error = hfs_lockpair(dcp, cp, HFS_EXCLUSIVE_LOCK))) {
+ hfs_unlock_truncate(cp, TRUE);
+ return (error);
+ }
error = hfs_removefile(dvp, vp, ap->a_cnp, ap->a_flags, 0, 0);
//
recycle_rsrc = 1;
}
- hfs_unlockpair(dcp, cp);
-out:
+ /*
+ * Drop the truncate lock before unlocking the cnode
+ * (which can potentially perform a vnode_put and
+ * recycle the vnode which in turn might require the
+ * truncate lock)
+ */
hfs_unlock_truncate(cp, TRUE);
+ hfs_unlockpair(dcp, cp);
if (recycle_rsrc && vnode_getwithvid(rvp, rvid) == 0) {
+ vnode_ref(rvp);
+ vnode_rele(rvp);
vnode_recycle(rvp);
vnode_put(rvp);
}
int lockflags;
int error = 0;
int started_tr = 0;
- int isbigfile = 0, hasxattrs=0, isdir=0;
+ int isbigfile = 0, defer_remove=0, isdir=0;
cp = VTOC(vp);
dcp = VTOC(dvp);
* (needed for hfs_truncate)
*/
if (isdir == 0 && (cp->c_blocks - VTOF(vp)->ff_blocks)) {
- error = hfs_vgetrsrc(hfsmp, vp, &rvp, FALSE);
- if (error)
- goto out;
- /* Defer the vnode_put on rvp until the hfs_unlock(). */
- cp->c_flag |= C_NEED_RVNODE_PUT;
+ /*
+ * We must avoid calling hfs_vgetrsrc() when we have
+ * an active resource fork vnode to avoid deadlocks
+ * when that vnode is in the VL_TERMINATE state. We
+ * can defer removing the file and its resource fork
+ * until the call to hfs_vnop_inactive() occurs.
+ */
+ if (cp->c_rsrc_vp) {
+ defer_remove = 1;
+ } else {
+ error = hfs_vgetrsrc(hfsmp, vp, &rvp, FALSE);
+ if (error)
+ goto out;
+ /* Defer the vnode_put on rvp until the hfs_unlock(). */
+ cp->c_flag |= C_NEED_RVNODE_PUT;
+ }
}
/* Check if this file is being used. */
if (isdir == 0) {
individual transactions in case there are too many */
if ((hfsmp->hfs_attribute_vp != NULL) &&
(cp->c_attr.ca_recflags & kHFSHasAttributesMask) != 0) {
- hasxattrs = 1;
+ defer_remove = 1;
}
/*
/*
* There are two cases to consider:
- * 1. File is busy/big ==> move/rename the file
+ * 1. File is busy/big/defer_remove ==> move/rename the file
* 2. File is not in use ==> remove the file
*/
- if (dataforkbusy || rsrcforkbusy || isbigfile || hasxattrs) {
+ if (dataforkbusy || rsrcforkbusy || isbigfile || defer_remove) {
char delname[32];
struct cat_desc to_desc;
struct cat_desc todir_desc;
struct vnode *tdvp = ap->a_tdvp;
struct vnode *fvp = ap->a_fvp;
struct vnode *fdvp = ap->a_fdvp;
+ struct vnode *rvp = NULLVP;
struct componentname *tcnp = ap->a_tcnp;
struct componentname *fcnp = ap->a_fcnp;
struct proc *p = vfs_context_proc(ap->a_context);
int took_trunc_lock = 0;
int lockflags;
int error;
-
+ int rsrc_vid = 0;
+ int recycle_rsrc = 0;
+
/* When tvp exist, take the truncate lock for the hfs_removefile(). */
if (tvp && (vnode_isreg(tvp) || vnode_islnk(tvp))) {
hfs_lock_truncate(VTOC(tvp), TRUE);
if (vnode_isdir(tvp))
error = hfs_removedir(tdvp, tvp, tcnp, HFSRM_SKIP_RESERVE);
else {
+ if (tcp){
+ rvp = tcp->c_rsrc_vp;
+ }
error = hfs_removefile(tdvp, tvp, tcnp, 0, HFSRM_SKIP_RESERVE, 0);
+
+ /* If the destination file had a resource fork vnode, we couldn't do
+ * anything about it in hfs_removefile because we didn't have a reference on it.
+ * We need to take action here to prevent it from leaking blocks. If removefile
+ * succeeded, then squirrel away the vid of the resource fork vnode and force a
+ * recycle after dropping all of the locks. The vid is guaranteed not to change
+ * at this point because we still hold the cnode lock.
+ */
+ if ((error == 0) && (tcp->c_flag & C_DELETED) && rvp && !vnode_isinuse(rvp, 0)) {
+ rsrc_vid = vnode_vid(rvp);
+ recycle_rsrc = 1;
+ }
}
if (error)
hfs_unlockfour(fdcp, fcp, tdcp, tcp);
+ /* Now that we've dropped locks, see if we need to force recycle on the old
+ * destination's rsrc fork, preventing a leak of the rsrc fork's blocks. Note that
+ * doing the ref/rele is in order to twiddle the VL_INACTIVE bit to the vnode's flags
+ * so that on the last vnode_put for this vnode, we will force vnop_inactive to be triggered.
+ */
+ if ((recycle_rsrc) && (vnode_getwithvid(rvp, rsrc_vid) == 0)) {
+ vnode_ref(rvp);
+ vnode_rele(rvp);
+ vnode_recycle(rvp);
+ vnode_put (rvp);
+ }
+
+
/* After tvp is removed the only acceptable error is EIO */
if (error && tvp_deleted)
error = EIO;
struct cat_fork *dataforkp = NULL;
struct cat_fork *rsrcforkp = NULL;
struct cat_fork datafork;
+ struct cat_fork rsrcfork;
struct hfsmount *hfsmp;
int lockflags;
int error;
dataforkp = &datafork;
}
+ /*
+ * For resource forks with delayed allocations, make sure
+ * the block count and file size match the number of blocks
+ * actually allocated to the file on disk.
+ */
+ if (rsrcforkp && (cp->c_rsrcfork->ff_unallocblocks != 0)) {
+ bcopy(rsrcforkp, &rsrcfork, sizeof(rsrcfork));
+ rsrcfork.cf_blocks = (cp->c_rsrcfork->ff_blocks - cp->c_rsrcfork->ff_unallocblocks);
+ rsrcfork.cf_size = rsrcfork.cf_blocks * HFSTOVCB(hfsmp)->blockSize;
+ rsrcforkp = &rsrcfork;
+ }
+
/*
* Lock the Catalog b-tree file.
*/
hfs_vgetrsrc(struct hfsmount *hfsmp, struct vnode *vp, struct vnode **rvpp, int can_drop_lock)
{
struct vnode *rvp;
+ struct vnode *dvp = NULLVP;
struct cnode *cp = VTOC(vp);
int error;
int vid;
+restart:
/* Attempt to use exising vnode */
if ((rvp = cp->c_rsrc_vp)) {
vid = vnode_vid(rvp);
error = vnode_getwithvid(rvp, vid);
- if (can_drop_lock)
+ if (can_drop_lock) {
(void) hfs_lock(cp, HFS_FORCE_LOCK);
-
+ /*
+ * When our lock was relinquished, the resource fork
+ * could have been recycled. Check for this and try
+ * again.
+ */
+ if (error == ENOENT)
+ goto restart;
+ }
if (error) {
const char * name = (const char *)VTOC(vp)->c_desc.cd_nameptr;
if (name)
- printf("hfs_vgetrsrc: couldn't get"
- " resource fork for %s\n", name);
+ printf("hfs_vgetrsrc: couldn't get resource"
+ " fork for %s, err %d\n", name, error);
return (error);
}
} else {
"%s%s", cp->c_desc.cd_nameptr,
_PATH_RSRCFORKSPEC);
}
- error = hfs_getnewvnode(hfsmp, vnode_parent(vp), cn.cn_pnbuf ? &cn : NULL,
+ dvp = vnode_getparent(vp);
+ error = hfs_getnewvnode(hfsmp, dvp, cn.cn_pnbuf ? &cn : NULL,
&cp->c_desc, GNV_WANTRSRC | GNV_SKIPLOCK, &cp->c_attr,
&rsrcfork, &rvp);
+ if (dvp)
+ vnode_put(dvp);
if (cn.cn_pnbuf)
FREE_ZONE(cn.cn_pnbuf, cn.cn_pnlen, M_NAMEI);
if (error)