lf_hfs_lookup.c   [plain text]


//
//  lf_hfs_lookup.c
//  livefiles_hfs
//
//  Created by Yakov Ben Zaken on 25/03/2018.
//

#include "lf_hfs.h"
#include "lf_hfs_lookup.h"
#include "lf_hfs_cnode.h"
#include "lf_hfs_vfsutils.h"
#include "lf_hfs_link.h"

static int
hfs_lookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, int *cnode_locked)
{
    struct cnode *dcp;    /* cnode for directory being searched */
    struct vnode *tvp;    /* target vnode */
    struct hfsmount *hfsmp;
    int flags;
    int nameiop;
    int retval = 0;
    struct cat_desc desc;
    struct cat_desc cndesc;
    struct cat_attr attr;
    struct cat_fork fork;
    int lockflags;
    int newvnode_flags = 0;
    
retry:
    newvnode_flags = 0;
    dcp = NULL;
    hfsmp = VTOHFS(dvp);
    *vpp = NULL;
    *cnode_locked = 0;
    tvp = NULL;
    nameiop = cnp->cn_nameiop;
    flags = cnp->cn_flags;
    bzero(&desc, sizeof(desc));

    if (hfs_lock(VTOC(dvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) != 0) {
        retval = ENOENT;  /* The parent no longer exists ? */
        goto exit;
    }
    dcp = VTOC(dvp);

    /*
     * Need to understand if we need this check.. as we took exclusive lock..
     */
    if (dcp->c_flag & C_DIR_MODIFICATION){
        hfs_unlock(dcp);
        usleep( 1000 );
        goto retry;
    }

    /*
     * 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;
    cndesc.cd_namelen = cnp->cn_namelen;
    cndesc.cd_parentcnid = dcp->c_fileid;
    cndesc.cd_hint = dcp->c_childhint;

    lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
    retval = cat_lookup(hfsmp, &cndesc, 0, &desc, &attr, &fork, NULL);
    hfs_systemfile_unlock(hfsmp, lockflags);

    if (retval == 0) {
        dcp->c_childhint = desc.cd_hint;
        /*
         * Note: We must drop the parent lock here before calling
         * hfs_getnewvnode (which takes the child lock).
         */
        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;
    }

    if (retval == HFS_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;
    /*
     * This is a non-existing entry
     *
     * If creating, and at end of pathname and current
     * directory has not been removed, then can consider
     * allowing file to be created.
     */
    if ((nameiop == CREATE || nameiop == RENAME) &&
        (flags & ISLASTCN) &&
        !(ISSET(dcp->c_flag, C_DELETED | C_NOEXISTS))) {
        retval = EJUSTRETURN;
        goto exit;
    }
    
    goto exit;

found:
    if (flags & ISLASTCN) {
        switch(nameiop) {
            case DELETE:
                cnp->cn_flags &= ~MAKEENTRY;
                break;

            case RENAME:
                cnp->cn_flags &= ~MAKEENTRY;
                break;
            default:
                break;
        }
    }

    int type = (attr.ca_mode & S_IFMT);

    if (!(flags & ISLASTCN) && (type != S_IFDIR) && (type != S_IFLNK)) {
        retval = ENOTDIR;
        goto exit;
    }
    /* Don't cache directory hardlink names. */
    if (attr.ca_recflags & kHFSHasLinkChainMask) {
        cnp->cn_flags &= ~MAKEENTRY;
    }
    /* Names with composed chars are not cached. */
    if (cnp->cn_namelen != desc.cd_namelen)
        cnp->cn_flags &= ~MAKEENTRY;
    
    retval = hfs_getnewvnode(hfsmp, dvp, cnp, &desc, 0, &attr, &fork, &tvp, &newvnode_flags);
    
    if (retval) {
        /*
         * 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) || (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);
            goto retry;
        }

        /* skip to the error-handling code if we can't retry */
        goto exit;
    }

    /*
     * 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.
     */
    if (ISSET(VTOC(tvp)->c_flag, C_HARDLINK))
        hfs_savelinkorigin(VTOC(tvp), VTOC(dvp)->c_fileid);

    *cnode_locked = 1;
    *vpp = tvp;

exit:
    if (dcp) {
        hfs_unlock(dcp);
    }
    cat_releasedesc(&desc);

    return (retval);
}

int
hfs_vnop_lookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp)
{
    int error = 0;
    int cnode_locked = 0;
    *vpp = NULL;

    error = hfs_lookup(dvp, vpp, cnp, &cnode_locked);

    if (cnode_locked)
        hfs_unlock(VTOC(*vpp));
    
    return (error);
}