]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/hfs/hfs_link.c
xnu-3247.1.106.tar.gz
[apple/xnu.git] / bsd / hfs / hfs_link.c
index 667bad9c6581434ffe2a494c35b7f81f4c29c330..2dd7fda4bf116bdfda1f62fcc361c6272f1bc0d4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999-2014 Apple Inc. All rights reserved.
+ * Copyright (c) 1999-2015 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
  * 
@@ -388,10 +388,9 @@ hfs_makelink(struct hfsmount *hfsmp, struct vnode *src_vp, struct cnode *cp,
                                }
                        }
                }
+                       cp->c_flag |= C_MODIFIED;
                cp->c_touch_chgtime = TRUE;
-               cp->c_flag |= C_FORCEUPDATE;
            }
-           dcp->c_flag |= C_FORCEUPDATE;
        }
 out:
        hfs_systemfile_unlock(hfsmp, lockflags);
@@ -450,6 +449,8 @@ hfs_vnop_link(struct vnop_link_args *ap)
        if (v_type == VLNK)
                return (ENOTSUP);
 
+       cp = VTOC(vp);
+
        if (v_type == VDIR) {
 #if CONFIG_HFS_DIRLINK
                /* Make sure our private directory exists. */
@@ -464,8 +465,10 @@ hfs_vnop_link(struct vnop_link_args *ap)
                if (hfsmp->jnl == NULL) {
                        return (EPERM);
                }
+
                /* Directory hardlinks also need the parent of the original directory. */
-               if ((error = hfs_vget(hfsmp, hfs_currentparent(VTOC(vp)), &fdvp, 1, 0))) {
+               if ((error = hfs_vget(hfsmp, hfs_currentparent(cp, /* have_lock: */ false),
+                                                         &fdvp, 1, 0))) {
                        return (error);
                }
 #else
@@ -503,9 +506,8 @@ hfs_vnop_link(struct vnop_link_args *ap)
                }
        }
        tdcp = VTOC(tdvp);
-       cp = VTOC(vp);
        /* grab the parent CNID from originlist after grabbing cnode locks */
-       parentcnid = hfs_currentparent(cp);
+       parentcnid = hfs_currentparent(cp, /* have_lock: */ true);
 
        /* 
         * Make sure we didn't race the src or dst parent directories with rmdir.
@@ -607,6 +609,7 @@ hfs_vnop_link(struct vnop_link_args *ap)
        lockflags = 0;
 
        cp->c_linkcount++;
+       cp->c_flag |= C_MODIFIED;
        cp->c_touch_chgtime = TRUE;
        error = hfs_makelink(hfsmp, vp, cp, tdcp, cnp);
        if (error) {
@@ -633,10 +636,10 @@ hfs_vnop_link(struct vnop_link_args *ap)
                        }
                }
                tdcp->c_dirchangecnt++;
+               tdcp->c_flag |= C_MODIFIED;
                hfs_incr_gencount(tdcp);
                tdcp->c_touch_chgtime = TRUE;
                tdcp->c_touch_modtime = TRUE;
-               tdcp->c_flag |= C_FORCEUPDATE;
 
                error = hfs_update(tdvp, 0);
                if (error) {
@@ -652,8 +655,8 @@ hfs_vnop_link(struct vnop_link_args *ap)
                    ((fdcp->c_attr.ca_recflags & kHFSHasChildLinkMask) == 0)) {
 
                        fdcp->c_attr.ca_recflags |= kHFSHasChildLinkMask;
+                       fdcp->c_flag |= C_MODIFIED;
                        fdcp->c_touch_chgtime = TRUE;
-                       fdcp->c_flag |= C_FORCEUPDATE;
                        error = hfs_update(fdvp, 0);
                        if (error) {
                                if (error != EIO && error != ENXIO) {
@@ -673,10 +676,8 @@ hfs_vnop_link(struct vnop_link_args *ap)
                hfs_volupdate(hfsmp, VOL_MKFILE,
                        (tdcp->c_cnid == kHFSRootFolderID));
        }
-       /* Make sure update occurs inside transaction */
-       cp->c_flag |= C_FORCEUPDATE;  
 
-       if (error == 0 && (ret = hfs_update(vp, TRUE)) != 0) {
+       if (error == 0 && (ret = hfs_update(vp, 0)) != 0) {
                if (ret != EIO && ret != ENXIO)
                        printf("hfs_vnop_link: error %d updating vp @ %p\n", ret, vp);
                hfs_mark_inconsistent(hfsmp, HFS_OP_INCOMPLETE);
@@ -794,9 +795,9 @@ hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct c
        dcp->c_dirchangecnt++;
        hfs_incr_gencount(dcp);
        microtime(&tv);
-       dcp->c_ctime = tv.tv_sec;
-       dcp->c_mtime = tv.tv_sec;
-       (void ) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL);
+       dcp->c_touch_chgtime = dcp->c_touch_modtime = true;
+       dcp->c_flag |= C_MODIFIED;
+       hfs_update(dcp->c_vp, 0);
 
        /*
         * If this is the last link then we need to process the inode.
@@ -877,7 +878,7 @@ hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct c
                    firstlink == cndesc.cd_cnid) {
                        if (setfirstlink(hfsmp, cp->c_fileid, nextlinkid) == 0)
                                cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
-               } else if (vnode_isreg(vp) && cp->c_attr.ca_firstlink == cndesc.cd_cnid) {
+               } else if (cp->c_attr.ca_firstlink == cndesc.cd_cnid) {
                        cp->c_attr.ca_firstlink = nextlinkid;
                }
                /* Update previous link. */
@@ -888,22 +889,23 @@ hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct c
                if (nextlinkid) {
                        (void) cat_update_siblinglinks(hfsmp, nextlinkid, prevlinkid, HFS_IGNORABLE_LINK);
                }
-
-               /*
-                * The call to cat_releasedesc below will only release the name buffer;
-                * it does not zero out the rest of the fields in the 'cat_desc' data structure.
-                * 
-                * As a result, since there are still other links at this point, we need
-                * to make the current cnode descriptor point to the raw inode.  If a path-based
-                * system call comes along first, it will replace the descriptor with a valid link
-                * ID.  If a userland process already has a file descriptor open, then they will
-                * bypass that lookup, though.  Replacing the descriptor CNID with the raw
-                * inode will force it to generate a new full path.
-                */
-               cp->c_cnid = cp->c_fileid;
-
        }
 
+       /*
+        * The call to cat_releasedesc below will only release the name
+        * buffer; it does not zero out the rest of the fields in the
+        * 'cat_desc' data structure.
+        *
+        * As a result, since there are still other links at this point,
+        * we need to make the current cnode descriptor point to the raw
+        * inode.  If a path-based system call comes along first, it will
+        * replace the descriptor with a valid link ID.  If a userland
+        * process already has a file descriptor open, then they will
+        * bypass that lookup, though.  Replacing the descriptor CNID with
+        * the raw inode will force it to generate a new full path.
+        */
+       cp->c_cnid = cp->c_fileid;
+
        /* Push new link count to disk. */
        cp->c_ctime = tv.tv_sec;        
        (void) cat_update(hfsmp, &cp->c_desc, &cp->c_attr, NULL, NULL);
@@ -1198,11 +1200,22 @@ hfs_relorigin(struct cnode *cp, cnid_t parentcnid)
        thread_t thread = current_thread();
 
        TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, prev) {
-               if ((origin->lo_thread == thread) ||
-                   (origin->lo_parentcnid == parentcnid)) {
+               if (origin->lo_thread == thread) {
                        TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
                        FREE(origin, M_TEMP);
                        break;
+               } else if (origin->lo_parentcnid == parentcnid) {
+                       /*
+                        * If the threads don't match, then we don't want to
+                        * delete the entry because that might cause other threads
+                        * to fall back and use whatever happens to be in
+                        * c_parentcnid or the wrong link ID.  By setting the
+                        * values to zero here, it should serve as an indication
+                        * that the path is no longer valid and that's better than
+                        * using a random parent ID or link ID.
+                        */
+                       origin->lo_parentcnid = 0;
+                       origin->lo_cnid = 0;
                }
        }
 }
@@ -1222,7 +1235,7 @@ hfs_haslinkorigin(cnode_t *cp)
        
                TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
                        if (origin->lo_thread == thread) {
-                               return (1);
+                               return origin->lo_cnid != 0;
                        }
                }
        }
@@ -1236,17 +1249,25 @@ hfs_haslinkorigin(cnode_t *cp)
  */
 __private_extern__
 cnid_t
-hfs_currentparent(cnode_t *cp)
+hfs_currentparent(cnode_t *cp, bool have_lock)
 {
        if (cp->c_flag & C_HARDLINK) {
+               if (!have_lock)
+                       hfs_lock_always(cp, HFS_SHARED_LOCK);
+
                linkorigin_t *origin;
                thread_t thread = current_thread();
-       
+
                TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
                        if (origin->lo_thread == thread) {
+                               if (!have_lock)
+                                       hfs_unlock(cp);
                                return (origin->lo_parentcnid);
                        }
                }
+
+               if (!have_lock)
+                       hfs_unlock(cp);
        }
        return (cp->c_parentcnid);
 }
@@ -1387,3 +1408,24 @@ out:
        return MacToVFSError(result);
 }
 
+errno_t hfs_first_link(hfsmount_t *hfsmp, cnode_t *cp, cnid_t *link_id)
+{
+       errno_t error = 0;
+
+       if (S_ISDIR(cp->c_mode)) {
+               int lockf = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
+
+               error = getfirstlink(hfsmp, cp->c_fileid, link_id);
+
+               hfs_systemfile_unlock(hfsmp, lockf);
+       } else {
+               if (cp->c_attr.ca_firstlink)
+                       *link_id = cp->c_attr.ca_firstlink;
+               else {
+                       // This can happen if the cnode has been deleted
+                       error = ENOENT;
+               }
+       }
+
+       return error;
+}