/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 * 
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <afs/param.h>
#include <afs/stds.h>

#ifndef DJGPP
#include <windows.h>
#include <winsock2.h>
#endif
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#ifndef DJGPP
#include <nb30.h>
#endif
#include <osi.h>

#include "afsd.h"

/* called with scp write-locked, check to see if we have the ACL info we need
 * and can get it w/o blocking for any locks.
 *
 * Never drops the scp lock, but may fail if the access control info comes from
 * the parent directory, and the parent's scache entry can't be found, or it
 * can't be locked.  Thus, this must always be called in a while loop to stabilize
 * things, since we can always lose the race condition getting to the parent vnode.
 */
int cm_HaveAccessRights(struct cm_scache *scp, struct cm_user *up, long rights,
                        long *outRightsp)
{
    cm_scache_t *aclScp;
    long code;
    cm_fid_t tfid;
    int didLock;
    long trights;
	
    didLock = 0;
    if (scp->fileType == CM_SCACHETYPE_DIRECTORY) {
        aclScp = scp;
        cm_HoldSCache(scp);
    } else {
        tfid.cell = scp->fid.cell;
        tfid.volume = scp->fid.volume;
        tfid.vnode = scp->parentVnode;
        tfid.unique = scp->parentUnique;
        aclScp = cm_FindSCache(&tfid);
        if (!aclScp) 
            return 0;
        if (aclScp != scp) {
            code = lock_TryMutex(&aclScp->mx);
            if (code == 0) {
                /* can't get lock safely and easily */
                cm_ReleaseSCache(aclScp);
                return 0;
            }

            /* check that we have a callback, too */
            if (!cm_HaveCallback(aclScp)) {
                /* can't use it */
                lock_ReleaseMutex(&aclScp->mx);
                cm_ReleaseSCache(aclScp);
                return 0;
            }
            didLock = 1;
        }
    }

    lock_AssertMutex(&aclScp->mx);
        
    /* now if rights is a subset of the public rights, we're done.
     * Otherwise, if we an explicit acl entry, we're also in good shape,
     * and can definitively answer.
     */
    if ((~aclScp->anyAccess & rights) == 0) {
        *outRightsp = rights;
    } else {
        /* we have to check the specific rights info */
        code = cm_FindACLCache(aclScp, up, &trights);
        if (code) {
            code = 0;
            goto done;
        }
        *outRightsp = trights;
    }

    /* check mode bits */
    if (!(scp->unixModeBits & 0400))
        *outRightsp &= ~PRSFS_READ;
    if (!(scp->unixModeBits & 0200))
        *outRightsp &= ~PRSFS_WRITE;

    code = 1;
    /* fall through */

  done:
    if (didLock) 
        lock_ReleaseMutex(&aclScp->mx);
    cm_ReleaseSCache(aclScp);
    return code;
}

/* called with locked scp; ensures that we have an ACL cache entry for the
 * user specified by the parameter "up."
 * In pathological race conditions, this function may return success without
 * having loaded the entry properly (due to a racing callback revoke), so this
 * function must also be called in a while loop to make sure that we eventually
 * succeed.
 */
long cm_GetAccessRights(struct cm_scache *scp, struct cm_user *up,
                        struct cm_req *reqp)
{
    long code;
    cm_fid_t tfid;
    cm_scache_t *aclScp;

    /* pretty easy: just force a pass through the fetch status code */
        
    osi_Log2(afsd_logp, "GetAccess scp %x user %x", scp, up);

    /* first, start by finding out whether we have a directory or something
     * else, so we can find what object's ACL we need.
     */
    code = cm_SyncOp(scp, NULL, up, reqp, 0, CM_SCACHESYNC_GETSTATUS
                      | CM_SCACHESYNC_NEEDCALLBACK);
    if (code) 
        return code;
        
    if (scp->fileType != CM_SCACHETYPE_DIRECTORY) {
        /* not a dir, use parent dir's acl */
        tfid.cell = scp->fid.cell;
        tfid.volume = scp->fid.volume;
        tfid.vnode = scp->parentVnode;
        tfid.unique = scp->parentUnique;
        lock_ReleaseMutex(&scp->mx);
        code = cm_GetSCache(&tfid, &aclScp, up, reqp);
        if (code) {
            lock_ObtainMutex(&scp->mx);
            return code;
        }       
                
        osi_Log1(afsd_logp, "GetAccess parent %x", aclScp);
        lock_ObtainMutex(&aclScp->mx);
        code = cm_GetCallback(aclScp, up, reqp, 1);
        lock_ReleaseMutex(&aclScp->mx);
        cm_ReleaseSCache(aclScp);
        lock_ObtainMutex(&scp->mx);
    } else {
        code = cm_GetCallback(scp, up, reqp, 1);
    }

    return code;
}
