]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/hfs/hfs_lookup.c
xnu-2782.40.9.tar.gz
[apple/xnu.git] / bsd / hfs / hfs_lookup.c
index b59038b3b0b6dab0b17ad74359f62735cd2637a6..e198d3190c43eb61a7b7873f1d4faad16690b513 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999-2007 Apple Inc. All rights reserved.
+ * Copyright (c) 1999-2012 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
  * 
@@ -80,6 +80,7 @@
 #include <sys/kdebug.h>
 #include <sys/kauth.h>
 #include <sys/namei.h>
+#include <sys/user.h>
 
 #include "hfs.h"
 #include "hfs_catalog.h"
  *     When should we lock parent_hp in here ??
  */
 static int
-hfs_lookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, int *cnode_locked)
+hfs_lookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, int *cnode_locked, int force_casesensitive_lookup)
 {
        struct cnode *dcp;      /* cnode for directory being searched */
        struct vnode *tvp;      /* target vnode */
@@ -164,8 +165,10 @@ hfs_lookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, int
        struct cat_attr attr;
        struct cat_fork fork;
        int lockflags;
+       int newvnode_flags;
 
   retry:
+       newvnode_flags = 0;
        dcp = NULL;
        hfsmp = VTOHFS(dvp);
        *vpp = NULL;
@@ -187,7 +190,7 @@ hfs_lookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, int
                cnp->cn_flags &= ~MAKEENTRY;
                goto found;     /* We always know who we are */
        } else {
-               if (hfs_lock(VTOC(dvp), HFS_EXCLUSIVE_LOCK) != 0) {
+               if (hfs_lock(VTOC(dvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) != 0) {
                        retval = ENOENT;  /* The parent no longer exists ? */
                        goto exit;
                }
@@ -204,10 +207,15 @@ hfs_lookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, int
                    goto retry;
                }
 
-               /* No need to go to catalog if there are no children */
-               if (dcp->c_entries == 0) {
-                       goto notfound;
-               }
+
+               /*
+                * We shouldn't need to go to the catalog if there are no children.
+                * However, in the face of a minor disk corruption where the valence of
+                * the directory is off, we could infinite loop here if we return ENOENT
+                * even though there are actually items in the directory.  (create will
+                * see the ENOENT, try to create something, which will return with 
+                * EEXIST over and over again).  As a result, always check the catalog.
+                */
 
                bzero(&cndesc, sizeof(cndesc));
                cndesc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr;
@@ -217,7 +225,7 @@ hfs_lookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, int
 
                lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
 
-               retval = cat_lookup(hfsmp, &cndesc, 0, &desc, &attr, &fork, NULL);
+               retval = cat_lookup(hfsmp, &cndesc, 0, force_casesensitive_lookup, &desc, &attr, &fork, NULL);
                
                hfs_systemfile_unlock(hfsmp, lockflags);
 
@@ -227,11 +235,19 @@ hfs_lookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, int
                         * Note: We must drop the parent lock here before calling
                         * hfs_getnewvnode (which takes the child lock).
                         */
-                       hfs_unlock(dcp);
-                       dcp = NULL;
+                       hfs_unlock(dcp);
+                       dcp = NULL;
+                       
+                       /* Verify that the item just looked up isn't one of the hidden directories. */
+                       if (desc.cd_cnid == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid ||
+                               desc.cd_cnid == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
+                               retval = ENOENT;
+                               goto exit;
+                       }
+                       
                        goto found;
                }
-notfound:
+               
                /*
                 * ENAMETOOLONG supersedes other errors
                 *
@@ -244,6 +260,25 @@ notfound:
                        retval = ENAMETOOLONG;
                } else if (retval == 0) {
                        retval = ENOENT;
+               } else if (retval == ERESERVEDNAME) {
+                       /*
+                        * We found the name in the catalog, but it is unavailable
+                        * to us. The exact error to return to our caller depends
+                        * on the operation, and whether we've already reached the
+                        * last path component. In all cases, avoid a negative
+                        * cache entry, since someone else may be able to access
+                        * the name if their lookup is configured differently.
+                        */
+
+                       cnp->cn_flags &= ~MAKEENTRY;
+
+                       if (((flags & ISLASTCN) == 0) || ((nameiop == LOOKUP) || (nameiop == DELETE))) {
+                               /* A reserved name for a pure lookup is the same as the path not being present */
+                               retval = ENOENT;
+                       } else {
+                               /* A reserved name with intent to create must be rejected as impossible */
+                               retval = EEXIST;
+                       }
                }
                if (retval != ENOENT)
                        goto exit;
@@ -254,10 +289,7 @@ notfound:
                 * directory has not been removed, then can consider
                 * allowing file to be created.
                 */
-               if ((nameiop == CREATE || nameiop == RENAME ||
-                   (nameiop == DELETE &&
-                   (cnp->cn_flags & DOWHITEOUT) &&
-                   (cnp->cn_flags & ISWHITEOUT))) &&
+               if ((nameiop == CREATE || nameiop == RENAME) &&
                    (flags & ISLASTCN) &&
                    !(ISSET(dcp->c_flag, C_DELETED | C_NOEXISTS))) {
                        retval = EJUSTRETURN;
@@ -301,37 +333,14 @@ found:
                 * Directory hard links can have multiple parents so
                 * find the appropriate parent for the current thread.
                 */
-               if ((retval = hfs_vget(hfsmp, hfs_currentparent(VTOC(dvp)), &tvp, 0))) {
+               if ((retval = hfs_vget(hfsmp, hfs_currentparent(VTOC(dvp)), &tvp, 0, 0))) {
                        goto exit;
                }
                *cnode_locked = 1;
                *vpp = tvp;
        } else {
                int type = (attr.ca_mode & S_IFMT);
-#if NAMEDRSRCFORK
-               int rsrc_warn = 0;
 
-               /*
-                * Check if caller wants the resource fork but utilized
-                * the legacy "file/rsrc" access path.
-                *
-                * This is deprecated behavior and support for it will not
-                * be allowed beyond case insensitive HFS+ and even that
-                * support will be removed in the next major OS release.
-                */
-               if ((type == S_IFREG) &&
-                   ((flags & ISLASTCN) == 0) &&
-                   (cnp->cn_nameptr[cnp->cn_namelen] == '/') &&
-                   (bcmp(&cnp->cn_nameptr[cnp->cn_namelen+1], "rsrc", 5) == 0) &&
-                   ((hfsmp->hfs_flags & (HFS_STANDARD | HFS_CASE_SENSITIVE)) == 0)) {
-               
-                       cnp->cn_consume = 5;
-                       cnp->cn_flags |= CN_WANTSRSRCFORK | ISLASTCN | NOCACHE;
-                       cnp->cn_flags &= ~MAKEENTRY;
-                       flags |= ISLASTCN;
-                       rsrc_warn = 1;
-               }
-#endif
                if (!(flags & ISLASTCN) && (type != S_IFDIR) && (type != S_IFLNK)) {
                        retval = ENOTDIR;
                        goto exit;
@@ -344,38 +353,80 @@ found:
                if (cnp->cn_namelen != desc.cd_namelen)
                        cnp->cn_flags &= ~MAKEENTRY;
 
-               retval = hfs_getnewvnode(hfsmp, dvp, cnp, &desc, 0, &attr, &fork, &tvp);
+               retval = hfs_getnewvnode(hfsmp, dvp, cnp, &desc, 0, &attr, &fork, &tvp, &newvnode_flags);
 
                if (retval) {
                        /*
-                        * If this was a create operation lookup and another
-                        * process removed the object before we had a chance
-                        * to create the vnode, then just treat it as the not
-                        * found case above and return EJUSTRETURN.
-                        */
+                        * If this was a create/rename operation lookup, then by this point
+                        * we expected to see the item returned from hfs_getnewvnode above.  
+                        * In the create case, it would probably eventually bubble out an EEXIST 
+                        * because the item existed when we were trying to create it.  In the 
+                        * rename case, it would let us know that we need to go ahead and 
+                        * delete it as part of the rename.  However, if we hit the condition below
+                        * then it means that we found the element during cat_lookup above, but 
+                        * it is now no longer there.  We simply behave as though we never found
+                        * the element at all and return EJUSTRETURN.
+                        */  
                        if ((retval == ENOENT) &&
-                           (cnp->cn_nameiop == CREATE) &&
-                           (flags & ISLASTCN)) {
+                                       ((cnp->cn_nameiop == CREATE) || (cnp->cn_nameiop == RENAME)) &&
+                                       (flags & ISLASTCN)) {
                                retval = EJUSTRETURN;
                        }
+                       
+                       /*
+                        * If this was a straight lookup operation, we may need to redrive the entire 
+                        * lookup starting from cat_lookup if the element was deleted as the result of 
+                        * a rename operation.  Since rename is supposed to guarantee atomicity, then
+                        * lookups cannot fail because the underlying element is deleted as a result of
+                        * the rename call -- either they returned the looked up element prior to rename
+                        * or return the newer element.  If we are in this region, then all we can do is add
+                        * workarounds to guarantee the latter case. The element has already been deleted, so
+                        * we just re-try the lookup to ensure the caller gets the most recent element.
+                        */
+                       if ((retval == ENOENT) && (cnp->cn_nameiop == LOOKUP) &&
+                               (newvnode_flags & (GNV_CHASH_RENAMED | GNV_CAT_DELETED))) {
+                               if (dcp) {
+                                       hfs_unlock (dcp);
+                               }
+                               /* get rid of any name buffers that may have lingered from the cat_lookup call */
+                               cat_releasedesc (&desc);
+                               goto retry;
+                       }
+
+                       /* Also, re-drive the lookup if the item we looked up was a hardlink, and the number 
+                        * or name of hardlinks has changed in the interim between the cat_lookup above, and
+                        * our call to hfs_getnewvnode.  hfs_getnewvnode will validate the cattr we passed it
+                        * against what is actually in the catalog after the cnode is created.  If there were
+                        * any issues, it will bubble out ERECYCLE, which we need to swallow and use as the
+                        * key to redrive as well.  We need to special case this below because in this case, 
+                        * it needs to occur regardless of the type of lookup we're doing here.  
+                        */
+                       if ((retval == ERECYCLE) && (newvnode_flags & GNV_CAT_ATTRCHANGED)) {
+                               if (dcp) {
+                                       hfs_unlock (dcp);
+                               }
+                               /* get rid of any name buffers that may have lingered from the cat_lookup call */
+                               cat_releasedesc (&desc);
+                               retval = 0;
+                               goto retry;
+                       }
+
+                       /* skip to the error-handling code if we can't retry */
                        goto exit;
                }
 
-               /* Save the origin info of a directory link for future ".." requests. */
-               if (S_ISDIR(attr.ca_mode) && (attr.ca_recflags & kHFSHasLinkChainMask)) {
+               /* 
+                * Save the origin info for file and directory hardlinks.  Directory hardlinks 
+                * need the origin for '..' lookups, and file hardlinks need it to ensure that 
+                * competing lookups do not cause us to vend different hardlinks than the ones requested.
+                * We want to restrict saving the cache entries to LOOKUP namei operations, since
+                * we're really doing this to protect getattr.
+                */
+               if ((nameiop == LOOKUP) && (VTOC(tvp)->c_flag & C_HARDLINK)) {
                        hfs_savelinkorigin(VTOC(tvp), VTOC(dvp)->c_fileid);
                }
                *cnode_locked = 1;
                *vpp = tvp;
-#if NAMEDRSRCFORK
-               if (rsrc_warn) {
-                       if ((VTOC(tvp)->c_flag & C_WARNED_RSRC) == 0) {
-                               VTOC(tvp)->c_flag |= C_WARNED_RSRC;
-                               printf("%.200s: file access by '/rsrc' was deprecated in 10.4\n",
-                                      cnp->cn_nameptr);
-                       }
-               }
-#endif
        }
 exit:
        if (dcp) {
@@ -407,7 +458,6 @@ exit:
 
 #define        S_IXALL 0000111
 
-__private_extern__
 int
 hfs_vnop_lookup(struct vnop_lookup_args *ap)
 {
@@ -415,14 +465,19 @@ hfs_vnop_lookup(struct vnop_lookup_args *ap)
        struct vnode *vp;
        struct cnode *cp;
        struct cnode *dcp;
+       struct hfsmount *hfsmp;
        int error;
        struct vnode **vpp = ap->a_vpp;
        struct componentname *cnp = ap->a_cnp;
+       struct proc *p = vfs_context_proc(ap->a_context);
        int flags = cnp->cn_flags;
+       int force_casesensitive_lookup = proc_is_forcing_hfs_case_sensitivity(p);
        int cnode_locked;
 
        *vpp = NULL;
        dcp = VTOC(dvp);
+       
+       hfsmp = VTOHFS(dvp);
 
        /*
         * Lookup an entry in the cache
@@ -447,22 +502,55 @@ hfs_vnop_lookup(struct vnop_lookup_args *ap)
         */
        error = 0;
        vp = *vpp;
-
+       cp = VTOC(vp);
+       
+       /* We aren't allowed to vend out vp's via lookup to the hidden directory */
+       if (cp->c_cnid == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid ||
+               cp->c_cnid == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
+               /* Drop the iocount from cache_lookup */
+               vnode_put (vp);
+               error = ENOENT;
+               goto exit;
+       }
+       
+       
        /*
         * If this is a hard-link vnode then we need to update
         * the name (of the link), the parent ID, the cnid, the
         * text encoding and the catalog hint.  This enables
         * getattrlist calls to return the correct link info.
         */
-       cp = VTOC(vp);
 
-       if ((flags & ISLASTCN) && (cp->c_flag & C_HARDLINK)) {
-               hfs_lock(cp, HFS_FORCE_LOCK);
+       /*
+        * Alternatively, if we are forcing a case-sensitive lookup
+        * on a case-insensitive volume, the namecache entry
+        * may have been for an incorrect case. Since we cannot
+        * determine case vs. normalization, redrive the catalog
+        * lookup based on any byte mismatch.
+        */
+       if (((flags & ISLASTCN) && (cp->c_flag & C_HARDLINK))
+               || (force_casesensitive_lookup && !(hfsmp->hfs_flags & HFS_CASE_SENSITIVE))) {
+               int stale_link = 0;
+
+               hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS);      
                if ((cp->c_parentcnid != dcp->c_cnid) ||
+                   (cnp->cn_namelen != cp->c_desc.cd_namelen) ||
                    (bcmp(cnp->cn_nameptr, cp->c_desc.cd_nameptr, cp->c_desc.cd_namelen) != 0)) {
                        struct cat_desc desc;
+                       struct cat_attr lookup_attr;
                        int lockflags;
 
+                       if (force_casesensitive_lookup && !(hfsmp->hfs_flags & HFS_CASE_SENSITIVE)) {
+                               /*
+                                * Since the name in the cnode doesn't match our lookup
+                                * string exactly, do a full lookup.
+                                */
+                               hfs_unlock (cp);
+
+                               vnode_put(vp);
+                               goto lookup;
+                       }
+
                        /*
                         * Get an updated descriptor
                         */
@@ -473,42 +561,84 @@ hfs_vnop_lookup(struct vnop_lookup_args *ap)
                        desc.cd_encoding = 0;
                        desc.cd_cnid = 0;
                        desc.cd_flags = S_ISDIR(cp->c_mode) ? CD_ISDIR : 0;
-       
+
+                       /*
+                        * Because lookups call replace_desc to put a new descriptor in
+                        * the cnode we are modifying it is possible that this cnode's 
+                        * descriptor is out of date for the parent ID / name that
+                        * we are trying to look up. (It may point to a different hardlink).
+                        *
+                        * We need to be cautious that when re-supplying the 
+                        * descriptor below that the results of the catalog lookup
+                        * still point to the same raw inode for the hardlink.  This would 
+                        * not be the case if we found something in the cache above but 
+                        * the vnode it returned no longer has a valid hardlink for the 
+                        * parent ID/filename combo we are requesting.  (This is because 
+                        * hfs_unlink does not directly trigger namecache removal). 
+                        *
+                        * As a result, before vending out the vnode (and replacing
+                        * its descriptor) verify that the fileID is the same by comparing
+                        * the in-cnode attributes vs. the one returned from the lookup call
+                        * below.  If they do not match, treat this lookup as if we never hit
+                        * in the cache at all.
+                        */
+
                        lockflags = hfs_systemfile_lock(VTOHFS(dvp), SFL_CATALOG, HFS_SHARED_LOCK);             
-                       if (cat_lookup(VTOHFS(vp), &desc, 0, &desc, NULL, NULL, NULL) == 0)
-                               replace_desc(cp, &desc);
+               
+                       error = cat_lookup(VTOHFS(vp), &desc, 0, 0, &desc, &lookup_attr, NULL, NULL);   
+                       
                        hfs_systemfile_unlock(VTOHFS(dvp), lockflags);
-               }
-               hfs_unlock(cp);
-       }
-#if NAMEDRSRCFORK
-       /*
-        * Check if caller wants the resource fork but utilized
-        * the legacy "file/rsrc" access path.
-        *
-        * This is deprecated behavior and support for it will not
-        * be allowed beyond case insensitive HFS+ and even that
-        * support will be removed in the next major OS release.
-        */
-       if ((dvp != vp) &&
-           ((flags & ISLASTCN) == 0) &&
-           vnode_isreg(vp) &&
-           (cnp->cn_nameptr[cnp->cn_namelen] == '/') &&
-           (bcmp(&cnp->cn_nameptr[cnp->cn_namelen+1], "rsrc", 5) == 0) &&
-           ((VTOHFS(vp)->hfs_flags & (HFS_STANDARD | HFS_CASE_SENSITIVE)) == 0)) {             
-               cnp->cn_consume = 5;
-               cnp->cn_flags |= CN_WANTSRSRCFORK | ISLASTCN | NOCACHE;
-               cnp->cn_flags &= ~MAKEENTRY;
 
-               hfs_lock(cp, HFS_FORCE_LOCK);
-               if ((cp->c_flag & C_WARNED_RSRC) == 0) {
-                       cp->c_flag |= C_WARNED_RSRC;
-                       printf("%.200s: file access by '/rsrc' was deprecated in 10.4\n", cnp->cn_nameptr);
+                       /* 
+                        * Note that cat_lookup may fail to find something with the name provided in the
+                        * stack-based descriptor above. In that case, an ENOENT is a legitimate errno
+                        * to be placed in error, which will get returned in the fastpath below.
+                        */
+                       if (error == 0) {
+                               if (lookup_attr.ca_fileid == cp->c_attr.ca_fileid) {
+                                       /* It still points to the right raw inode.  Replacing the descriptor is fine */
+                                       replace_desc (cp, &desc);
+
+                                       /* 
+                                        * Save the origin info for file and directory hardlinks.  Directory hardlinks 
+                                        * need the origin for '..' lookups, and file hardlinks need it to ensure that 
+                                        * competing lookups do not cause us to vend different hardlinks than the ones requested.
+                                        * We want to restrict saving the cache entries to LOOKUP namei operations, since
+                                        * we're really doing this to protect getattr.
+                                        */
+                                       if (cnp->cn_nameiop == LOOKUP) {
+                                               hfs_savelinkorigin(cp, dcp->c_fileid);
+                                       }
+                               }
+                               else {
+                                       /* If the fileID does not match then do NOT replace the descriptor! */
+                                       stale_link = 1;
+                               }       
+                       }
                }
-               hfs_unlock(cp);
-       }
-#endif
-       return (error);
+               hfs_unlock (cp);
+               
+               if (stale_link) {
+                       /* 
+                        * If we had a stale_link, then we need to pretend as though
+                        * we never found this vnode and force a lookup through the 
+                        * traditional path.  Drop the iocount acquired through 
+                        * cache_lookup above and force a cat lookup / getnewvnode
+                        */
+                       vnode_put(vp);
+                       goto lookup;
+               }
+               
+               if (error) {
+                       /* 
+                        * If the cat_lookup failed then the caller will not expect 
+                        * a vnode with an iocount on it.
+                        */
+                       vnode_put(vp);
+               }
+
+       }       
+       goto exit;
        
 lookup:
        /*
@@ -518,11 +648,29 @@ lookup:
         */
        cnode_locked = 0;
 
-       error = hfs_lookup(dvp, vpp, cnp, &cnode_locked);
+       error = hfs_lookup(dvp, vpp, cnp, &cnode_locked, force_casesensitive_lookup);
        
        if (cnode_locked)
                hfs_unlock(VTOC(*vpp));
 exit:
+       {
+       uthread_t ut = (struct uthread *)get_bsdthread_info(current_thread());
+
+       /*
+        * check to see if we issued any I/O while completing this lookup and
+        * this thread/task is throttleable... if so, throttle now
+        *
+        * this allows us to throttle in between multiple meta data reads that
+        * might result due to looking up a long pathname (since we'll have to
+        * re-enter hfs_vnop_lookup for each component of the pathnam not in
+        * the VFS cache), instead of waiting until the entire path lookup has
+        * completed and throttling at the systemcall return
+        */
+       if (__improbable(ut->uu_lowpri_window)) {
+               throttle_lowpri_io(1);
+       }
+       }
+
        return (error);
 }