]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/hfs/hfs_vnops.c
xnu-1228.7.58.tar.gz
[apple/xnu.git] / bsd / hfs / hfs_vnops.c
index cac1f5b755faec3ab3757d0243dd65f50bcfa4de..eef6b5e9660486b10484d49bd60c81c25a9bcf21 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000-2007 Apple Inc. All rights reserved.
+ * Copyright (c) 2000-2008 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
  * 
@@ -28,6 +28,7 @@
 
 #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>
@@ -292,12 +293,58 @@ hfs_vnop_close(ap)
        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;
@@ -315,7 +362,10 @@ hfs_vnop_close(ap)
        } else if (vnode_issystem(vp) && !busy) {
                vnode_recycle(vp);
        }
-
+       if (tooktrunclock) {
+               hfs_unlock_truncate(cp, TRUE);
+       }
+       
        hfs_unlock(cp);
        return (0);
 }
@@ -334,7 +384,7 @@ hfs_vnop_getattr(struct vnop_getattr_args *ap)
 
        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;
@@ -466,11 +516,11 @@ hfs_vnop_getattr(struct vnop_getattr_args *ap)
                        }
 
                        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;
@@ -542,8 +592,15 @@ hfs_vnop_getattr(struct vnop_getattr_args *ap)
         * 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 {
@@ -567,70 +624,79 @@ hfs_vnop_getattr(struct vnop_getattr_args *ap)
                             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);
        }
@@ -1728,9 +1794,10 @@ hfs_vnop_remove(ap)
 
        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);
 
        //
@@ -1748,11 +1815,18 @@ hfs_vnop_remove(ap)
            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);
        } 
@@ -1798,7 +1872,7 @@ hfs_removefile(struct vnode *dvp, struct vnode *vp, struct componentname *cnp,
        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);
@@ -1866,11 +1940,22 @@ hfs_removefile(struct vnode *dvp, struct vnode *vp, struct componentname *cnp,
         * (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) {
@@ -1887,7 +1972,7 @@ hfs_removefile(struct vnode *dvp, struct vnode *vp, struct componentname *cnp,
           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;
        }
 
        /*
@@ -1976,10 +2061,10 @@ hfs_removefile(struct vnode *dvp, struct vnode *vp, struct componentname *cnp,
 
        /*
         * 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;
@@ -2188,6 +2273,7 @@ hfs_vnop_rename(ap)
        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);
@@ -2205,7 +2291,9 @@ hfs_vnop_rename(ap)
        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);
@@ -2482,7 +2570,22 @@ hfs_vnop_rename(ap)
                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)
@@ -2585,6 +2688,19 @@ out:
 
        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;
@@ -3191,6 +3307,7 @@ hfs_update(struct vnode *vp, __unused int waitfor)
        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;
@@ -3272,6 +3389,18 @@ hfs_update(struct vnode *vp, __unused int waitfor)
                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.
         */
@@ -3581,10 +3710,12 @@ int
 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);
@@ -3607,15 +3738,22 @@ hfs_vgetrsrc(struct hfsmount *hfsmp, struct vnode *vp, struct vnode **rvpp, int
 
                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 {
@@ -3668,9 +3806,12 @@ hfs_vgetrsrc(struct hfsmount *hfsmp, struct vnode *vp, struct vnode **rvpp, int
                                                 "%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)