]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/hfs/hfs_link.c
xnu-2050.48.11.tar.gz
[apple/xnu.git] / bsd / hfs / hfs_link.c
index f6c5e8409a82dd48ffe799fdbbf3a03f217cde2e..43b4a26e47739a6d5201101dbc1977706b65809d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999-2007 Apple Inc. All rights reserved.
+ * Copyright (c) 1999-2013 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
  * 
@@ -35,6 +35,7 @@
 #include <sys/vnode.h>
 #include <vfs/vfs_support.h>
 #include <libkern/libkern.h>
+#include <sys/fsctl.h>
 
 #include "hfs.h"
 #include "hfs_catalog.h"
@@ -61,6 +62,8 @@ const char *hfs_private_names[] = {
 static int  setfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t firstlink);
 static int  getfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t *firstlink);
 
+int hfs_makelink(struct hfsmount *hfsmp, struct vnode *src_vp, struct cnode *cp, 
+               struct cnode *dcp, struct componentname *cnp);
 /*
  * Create a new catalog link record
  *
@@ -83,7 +86,7 @@ createindirectlink(struct hfsmount *hfsmp, u_int32_t linknum, struct cat_desc *d
        struct cat_attr attr;
 
        if (linknum == 0) {
-               printf("createindirectlink: linknum is zero!\n");
+               printf("hfs: createindirectlink: linknum is zero!\n");
                return (EINVAL);
        }
 
@@ -92,7 +95,7 @@ createindirectlink(struct hfsmount *hfsmp, u_int32_t linknum, struct cat_desc *d
        
        /* Links are matched to inodes by link ID and to volumes by create date */
        attr.ca_linkref = linknum;
-       attr.ca_itime = hfsmp->hfs_itime;
+       attr.ca_itime = hfsmp->hfs_metadata_createdate;
        attr.ca_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
        attr.ca_recflags = kHFSHasLinkChainMask | kHFSThreadExistsMask;
        attr.ca_flags = UF_IMMUTABLE;
@@ -121,13 +124,15 @@ createindirectlink(struct hfsmount *hfsmp, u_int32_t linknum, struct cat_desc *d
 
 /*
  * Make a link to the cnode cp in the directory dp
- * using the name in cnp.
+ * using the name in cnp.  src_vp is the vnode that 
+ * corresponds to 'cp' which was part of the arguments to
+ * hfs_vnop_link.
  *
  * The cnodes cp and dcp must be locked.
  */
-static int
-hfs_makelink(struct hfsmount *hfsmp, struct cnode *cp, struct cnode *dcp,
-               struct componentname *cnp)
+int
+hfs_makelink(struct hfsmount *hfsmp, struct vnode *src_vp, struct cnode *cp, 
+               struct cnode *dcp, struct componentname *cnp)
 {
        vfs_context_t ctx = cnp->cn_context;
        struct proc *p = vfs_context_proc(ctx);
@@ -291,7 +296,7 @@ hfs_makelink(struct hfsmount *hfsmp, struct cnode *cp, struct cnode *dcp,
 
            /* Update the original first link to point back to the new first link. */
            if (cp->c_attr.ca_recflags & kHFSHasLinkChainMask) {
-               (void) cat_updatelink(hfsmp, orig_firstlink, linkcnid, HFS_IGNORABLE_LINK);
+               (void) cat_update_siblinglinks(hfsmp, orig_firstlink, linkcnid, HFS_IGNORABLE_LINK);
 
                /* Update the inode's first link value. */
                if (type == DIR_HARDLINKS) {
@@ -327,17 +332,46 @@ hfs_makelink(struct hfsmount *hfsmp, struct cnode *cp, struct cnode *dcp,
                    panic("hfs_makelink: cat_update of privdir failed! (%d)\n", retval);
                }
                cp->c_flag |= C_HARDLINK;
+
+               /*
+                * Now we need to mark the vnodes as being hardlinks via the vnode_setmultipath call.
+                * Note that we're calling vnode_get here, which should simply add an iocount if possible, without
+                * doing much checking.  It's safe to call this because we are protected by the cnode lock, which
+                * ensures that anyone trying to reclaim it will block until we release it.  vnode_get will usually 
+                * give us an extra iocount, unless the vnode is about to be reclaimed (and has no iocounts).  
+                * In that case, we'd error out, but we'd also not care if we added the VISHARDLINK bit to the vnode.  
+                * 
+                * As for the iocount we're about to add, we can't necessarily always call vnode_put here.  
+                * If the one we add is the only iocount on the vnode, and there was
+                * sufficient vnode pressure, it could go through VNOP_INACTIVE immediately, which would
+                * require the cnode lock and cause us to double-lock panic.  We can only call vnode_put if we know
+                * that the vnode we're operating on is the one with which we came into hfs_vnop_link, because
+                * that means VFS took an iocount on it for us.  If it's *not* the one that we came into the call 
+                * with, then mark it as NEED_VNODE_PUT to have hfs_unlock drop it for us.  hfs_vnop_link will 
+                * unlock the cnode when it is finished.
+                */
                if ((vp = cp->c_vp) != NULLVP) {
-                   if (vnode_get(vp) == 0) {
-                       vnode_setmultipath(vp);
-                       vnode_put(vp);
-                   }
+                       if (vnode_get(vp) == 0) {
+                               vnode_setmultipath(vp);
+                               if (vp == src_vp) {
+                                       /* we have an iocount on data fork vnode already. */
+                                       vnode_put(vp);
+                               }
+                               else {
+                                       cp->c_flag |= C_NEED_DVNODE_PUT;
+                               }
+                       }
                }
                if ((vp = cp->c_rsrc_vp) != NULLVP) {
-                   if (vnode_get(vp) == 0) {
-                       vnode_setmultipath(vp);
-                       vnode_put(vp);
-                   }
+                       if (vnode_get(vp) == 0) {
+                               vnode_setmultipath(vp);
+                               if (vp == src_vp) {
+                                       vnode_put(vp);
+                               }
+                               else {
+                                       cp->c_flag |= C_NEED_RVNODE_PUT;
+                               }
+                       }
                }
                cp->c_touch_chgtime = TRUE;
                cp->c_flag |= C_FORCEUPDATE;
@@ -364,7 +398,6 @@ out:
  *  IN struct componentname  *a_cnp;
  *  IN vfs_context_t  a_context;
  */
-__private_extern__
 int
 hfs_vnop_link(struct vnop_link_args *ap)
 {
@@ -377,6 +410,7 @@ hfs_vnop_link(struct vnop_link_args *ap)
        struct cnode *tdcp;
        struct cnode *fdcp = NULL;
        struct cat_desc todesc;
+       cnid_t parentcnid;
        int lockflags = 0;
        int intrans = 0;
        enum vtype v_type;
@@ -407,7 +441,7 @@ hfs_vnop_link(struct vnop_link_args *ap)
                        return (EPERM);
                }
                /* Directory hardlinks also need the parent of the original directory. */
-               if ((error = hfs_vget(hfsmp, hfs_currentparent(VTOC(vp)), &fdvp, 1))) {
+               if ((error = hfs_vget(hfsmp, hfs_currentparent(VTOC(vp)), &fdvp, 1, 0))) {
                        return (error);
                }
        } else {
@@ -422,9 +456,13 @@ hfs_vnop_link(struct vnop_link_args *ap)
                }
                return (ENOSPC);
        }
+
+       check_for_tracked_file(vp, VTOC(vp)->c_ctime, NAMESPACE_HANDLER_LINK_CREATE, NULL);
+
+
        /* Lock the cnodes. */
        if (fdvp) {
-               if ((error = hfs_lockfour(VTOC(tdvp), VTOC(vp), VTOC(fdvp), NULL, HFS_EXCLUSIVE_LOCK))) {
+               if ((error = hfs_lockfour(VTOC(tdvp), VTOC(vp), VTOC(fdvp), NULL, HFS_EXCLUSIVE_LOCK, NULL))) {
                        if (fdvp) {
                                vnode_put(fdvp);
                        }
@@ -438,11 +476,34 @@ 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);
+
+       /* 
+        * Make sure we didn't race the src or dst parent directories with rmdir.
+        * Note that we should only have a src parent directory cnode lock 
+        * if we're dealing with a directory hardlink here.
+        */
+       if (fdcp) {
+               if (fdcp->c_flag & (C_NOEXISTS | C_DELETED)) {
+                       error = ENOENT;
+                       goto out;
+               }
+       }
+
+       if (tdcp->c_flag & (C_NOEXISTS | C_DELETED)) {
+               error = ENOENT;
+               goto out;
+       }
+
+       /* Check the source for errors: 
+        * too many links, immutable, race with unlink
+        */
        if (cp->c_linkcount >= HFS_LINK_MAX) {
                error = EMLINK;
                goto out;
        }
-       if (cp->c_flags & (IMMUTABLE | APPEND)) {
+       if (cp->c_bsdflags & (IMMUTABLE | APPEND)) {
                error = EPERM;
                goto out;
        }
@@ -478,7 +539,7 @@ hfs_vnop_link(struct vnop_link_args *ap)
                struct cat_attr cattr;
 
                /* If inode is missing then we lost a race with unlink. */
-               if ((cat_idlookup(hfsmp, cp->c_fileid, 0, NULL, &cattr, NULL) != 0) ||
+               if ((cat_idlookup(hfsmp, cp->c_fileid, 0, 0, NULL, &cattr, NULL) != 0) ||
                    (cattr.ca_fileid != cp->c_fileid)) {
                        error = ENOENT;
                        goto out;
@@ -506,9 +567,9 @@ hfs_vnop_link(struct vnop_link_args *ap)
                 * - No ancestor of the new directory hard link (destination) 
                 *   is a directory hard link.
                 */
-               if ((cp->c_parentcnid == tdcp->c_fileid) ||
+               if ((parentcnid == tdcp->c_fileid) ||
                    (tdcp->c_fileid == kHFSRootFolderID) ||
-                   (cp->c_parentcnid == kHFSRootFolderID) ||
+                   (parentcnid == kHFSRootFolderID) ||
                    cat_check_link_ancestry(hfsmp, tdcp->c_fileid, cp->c_fileid)) {
                        error = EPERM;  /* abide by the rules, you did not */
                        goto out;
@@ -519,7 +580,7 @@ hfs_vnop_link(struct vnop_link_args *ap)
 
        cp->c_linkcount++;
        cp->c_touch_chgtime = TRUE;
-       error = hfs_makelink(hfsmp, cp, tdcp, cnp);
+       error = hfs_makelink(hfsmp, vp, cp, tdcp, cnp);
        if (error) {
                cp->c_linkcount--;
                hfs_volupdate(hfsmp, VOL_UPDATE, 0);
@@ -550,7 +611,7 @@ hfs_vnop_link(struct vnop_link_args *ap)
 
                error = hfs_update(tdvp, 0);
                if (error && error != EIO && error != ENXIO) {
-                       panic("hfs_vnop_link: error updating tdvp %p\n", tdvp);
+                       panic("hfs_vnop_link: error %d updating tdvp %p\n", error, tdvp);
                }
                
                if ((v_type == VDIR) && 
@@ -562,7 +623,7 @@ hfs_vnop_link(struct vnop_link_args *ap)
                        fdcp->c_flag |= C_FORCEUPDATE;
                        error = hfs_update(fdvp, 0);
                        if (error && error != EIO && error != ENXIO) {
-                               panic("hfs_vnop_link: error updating fdvp %p\n", fdvp);
+                               panic("hfs_vnop_link: error %d updating fdvp %p\n", error, fdvp);
                        }
 
                        /* Set kHFSHasChildLinkBit in the source hierarchy */
@@ -582,8 +643,6 @@ hfs_vnop_link(struct vnop_link_args *ap)
                panic("hfs_vnop_link: error %d updating vp @ %p\n", ret, vp);
        }
 
-       HFS_KNOTE(vp, NOTE_LINK);
-       HFS_KNOTE(tdvp, NOTE_WRITE);
 out:
        if (lockflags) {
                hfs_systemfile_unlock(hfsmp, lockflags);
@@ -612,7 +671,6 @@ out:
  *
  * Note: dvp and vp cnodes are already locked.
  */
-__private_extern__
 int
 hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct componentname *cnp, int skip_reserve)
 {
@@ -625,7 +683,6 @@ hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct c
        cnid_t  nextlinkid;
        int lockflags = 0;
        int started_tr;
-       int rm_priv_file = 0;
        int error;
        
        if (hfsmp->hfs_flags & HFS_STANDARD) {
@@ -785,11 +842,11 @@ hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct c
                }
                /* Update previous link. */
                if (prevlinkid) {
-                       (void) cat_updatelink(hfsmp, prevlinkid, HFS_IGNORABLE_LINK, nextlinkid);
+                       (void) cat_update_siblinglinks(hfsmp, prevlinkid, HFS_IGNORABLE_LINK, nextlinkid);
                }
                /* Update next link. */
                if (nextlinkid) {
-                       (void) cat_updatelink(hfsmp, nextlinkid, prevlinkid, HFS_IGNORABLE_LINK);
+                       (void) cat_update_siblinglinks(hfsmp, nextlinkid, prevlinkid, HFS_IGNORABLE_LINK);
                }
        }
 
@@ -803,10 +860,7 @@ hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct c
 
        /* Update file system stats. */
        hfs_volupdate(hfsmp, VOL_RMFILE, (dcp->c_cnid == kHFSRootFolderID));
-       /* The last link of a directory removed the inode. */
-       if (rm_priv_file) {
-               hfs_volupdate(hfsmp, VOL_RMFILE, 0);
-       }
+
        /*
         * All done with this cnode's descriptor...
         *
@@ -816,8 +870,6 @@ hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct c
         */
        cat_releasedesc(&cp->c_desc);
 
-       HFS_KNOTE(dvp, NOTE_WRITE);
-       HFS_KNOTE(vp, NOTE_DELETE);
 out:
        if (lockflags) {
                hfs_systemfile_unlock(hfsmp, lockflags);
@@ -844,7 +896,6 @@ out:
  *
  * This call is assumed to be made during mount.
  */
-__private_extern__
 void
 hfs_privatedir_init(struct hfsmount * hfsmp, enum privdirtype type)
 {
@@ -893,7 +944,7 @@ hfs_privatedir_init(struct hfsmount * hfsmp, enum privdirtype type)
        }
 
        /* Grab the root directory so we can update it later. */
-       if (hfs_vget(hfsmp, kRootDirID, &dvp, 0) != 0) {
+       if (hfs_vget(hfsmp, kRootDirID, &dvp, 0, 0) != 0) {
                goto exit;
        }
        dcp = VTOC(dvp);
@@ -949,7 +1000,7 @@ hfs_privatedir_init(struct hfsmount * hfsmp, enum privdirtype type)
                goto exit;
        }
        if (type == FILE_HARDLINKS) {
-               hfsmp->hfs_metadata_createdate = hfsmp->hfs_itime;
+               hfsmp->hfs_metadata_createdate = priv_attrp->ca_itime;
        }
        hfs_volupdate(hfsmp, VOL_MKDIR, 1);
 exit:
@@ -969,9 +1020,8 @@ exit:
 /*
  * Lookup a hardlink link (from chain)
  */
-__private_extern__
 int
-hfs_lookuplink(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *prevlinkid,  cnid_t *nextlinkid)
+hfs_lookup_siblinglinks(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *prevlinkid,  cnid_t *nextlinkid)
 {
        int lockflags;
        int error;
@@ -981,7 +1031,7 @@ hfs_lookuplink(struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *prevlinkid,  c
 
        lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
 
-       error = cat_lookuplinkbyid(hfsmp, linkfileid, prevlinkid, nextlinkid);
+       error = cat_lookup_siblinglinks(hfsmp, linkfileid, prevlinkid, nextlinkid);
        if (error == ENOLINK) {
                hfs_systemfile_unlock(hfsmp, lockflags);
                lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK);
@@ -1006,7 +1056,6 @@ hfs_savelinkorigin(cnode_t *cp, cnid_t parentcnid)
        void * thread = current_thread();
        int count = 0;
        int maxorigins = (S_ISDIR(cp->c_mode)) ? MAX_CACHED_ORIGINS : MAX_CACHED_FILE_ORIGINS;
-
        /*
         *  Look for an existing origin first.  If not found, create/steal one.
         */