root/usr/src/lib/libidmap/common/utils.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Utility routines
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <libintl.h>
#include <assert.h>
#include <ucontext.h>
#include <pthread.h>
#include "idmap_impl.h"

#define _UDT_SIZE_INCR  1

#define _GET_IDS_SIZE_INCR      1

static struct timeval TIMEOUT = { 25, 0 };

struct idmap_handle {
        CLIENT          *client;
        boolean_t       failed;
        rwlock_t        lock;
};

static struct idmap_handle idmap_handle = {
        NULL,           /* client */
        B_TRUE,         /* failed */
        DEFAULTRWLOCK,  /* lock */
};

static idmap_stat _idmap_clnt_connect(void);
static void _idmap_clnt_disconnect(void);

idmap_retcode
_udt_extend_batch(idmap_udt_handle_t *udthandle)
{
        idmap_update_op *tmplist;
        size_t          nsize;

        if (udthandle->next >= udthandle->batch.idmap_update_batch_len) {
                nsize = (udthandle->batch.idmap_update_batch_len +
                    _UDT_SIZE_INCR) * sizeof (*tmplist);
                tmplist = realloc(
                    udthandle->batch.idmap_update_batch_val, nsize);
                if (tmplist == NULL)
                        return (IDMAP_ERR_MEMORY);
                (void) memset((uchar_t *)tmplist +
                    (udthandle->batch.idmap_update_batch_len *
                    sizeof (*tmplist)), 0,
                    _UDT_SIZE_INCR * sizeof (*tmplist));
                udthandle->batch.idmap_update_batch_val = tmplist;
                udthandle->batch.idmap_update_batch_len += _UDT_SIZE_INCR;
        }
        udthandle->batch.idmap_update_batch_val[udthandle->next].opnum =
            OP_NONE;
        return (IDMAP_SUCCESS);
}

idmap_retcode
_get_ids_extend_batch(idmap_get_handle_t *gh)
{
        idmap_mapping   *t1;
        idmap_get_res_t *t2;
        size_t          nsize, len;

        len = gh->batch.idmap_mapping_batch_len;
        if (gh->next >= len) {
                /* extend the request array */
                nsize = (len + _GET_IDS_SIZE_INCR) * sizeof (*t1);
                t1 = realloc(gh->batch.idmap_mapping_batch_val, nsize);
                if (t1 == NULL)
                        return (IDMAP_ERR_MEMORY);
                (void) memset((uchar_t *)t1 + (len * sizeof (*t1)), 0,
                    _GET_IDS_SIZE_INCR * sizeof (*t1));
                gh->batch.idmap_mapping_batch_val = t1;

                /* extend the return list */
                nsize = (len + _GET_IDS_SIZE_INCR) * sizeof (*t2);
                t2 = realloc(gh->retlist, nsize);
                if (t2 == NULL)
                        return (IDMAP_ERR_MEMORY);
                (void) memset((uchar_t *)t2 + (len * sizeof (*t2)), 0,
                    _GET_IDS_SIZE_INCR * sizeof (*t2));
                gh->retlist = t2;

                gh->batch.idmap_mapping_batch_len += _GET_IDS_SIZE_INCR;
        }
        return (IDMAP_SUCCESS);
}

idmap_stat
_iter_get_next_list(int type, idmap_iter_t *iter,
                void *arg, uchar_t **list, size_t valsize,
                xdrproc_t xdr_arg_proc, xdrproc_t xdr_res_proc)
{
        idmap_stat rc;

        iter->next = 0;
        iter->retlist = NULL;

        /* init the result */
        if (*list) {
                xdr_free(xdr_res_proc, (caddr_t)*list);
        } else {
                if ((*list = malloc(valsize)) == NULL) {
                        errno = ENOMEM;
                        return (IDMAP_ERR_MEMORY);
                }
        }
        (void) memset(*list, 0, valsize);

        rc = _idmap_clnt_call(type,
            xdr_arg_proc, (caddr_t)arg,
            xdr_res_proc, (caddr_t)*list,
            TIMEOUT);
        if (rc != IDMAP_SUCCESS) {
                free(*list);
                return (rc);
        }
        iter->retlist = *list;
        return (IDMAP_SUCCESS);
}

/*
 * Convert the return values from an RPC request into an idmap return code.
 * Set errno on error.
 */
static
idmap_stat
_idmap_rpc2stat(enum clnt_stat clntstat, CLIENT *clnt)
{
        /*
         * We only deal with door_call(3C) errors here. We look at
         * r_err.re_errno instead of r_err.re_status because we need
         * to differentiate between RPC failures caused by bad door fd
         * and others.
         */
        struct rpc_err r_err;

        if (clntstat == RPC_SUCCESS)
                return (IDMAP_SUCCESS);

        clnt_geterr(clnt, &r_err);
        errno = r_err.re_errno;
        switch (r_err.re_errno) {
        case ENOMEM:
                return (IDMAP_ERR_MEMORY);
        case EBADF:
                return (IDMAP_ERR_RPC_HANDLE);
        default:
                return (IDMAP_ERR_RPC);
        }
}

/*
 * Management of the connection to idmapd.
 *
 * The intent is that connections to idmapd are automatically maintained,
 * reconnecting if necessary.  No attempt is made to retry connnection
 * attempts; a failure to connect yields an immediate error return.
 *
 * State of the connection is maintained through the "client" and "failed"
 * elements of the handle structure:
 *
 * client   failed
 * NULL     true     Failed on a previous request and was not recovered.
 * NULL     false    Should never happen.
 * nonNULL  true     Structure exists, but an error has occurred.  Waiting
 *                   for a chance to attempt to reconnect.
 * nonNULL  false    Connection is good.
 *
 * Note that the initial state is NULL/true, so that the first request
 * will establish the initial connection.
 *
 * Concurrency is managed through the rw lock "lock".  Only the writer is
 * allowed to connect or disconnect, and thus only the writer can set
 * "failed" to "false".  Readers are allowed to use the "client" pointer,
 * and to set "failed" to "true", indicating that they have encountered a
 * failure.  The "client" pointer is only valid while one holds a reader
 * lock.  Once "failed" has been set to "true", all requests (including
 * the retry of the failing request) will attempt to gain the writer lock.
 * When they succeed, indicating that there are no requests in flight and
 * thus no outstanding references to the CLIENT structure, they check
 * again to see if the connection is still failed (since another thread
 * might have fixed it), and then if it is still failed they disconnect
 * and reconnect.
 */

/*
 * Make an RPC call.  Automatically reconnect if the connection to idmapd
 * fails.  Convert RPC results to idmap return codes.
 */
idmap_stat
_idmap_clnt_call(
    const rpcproc_t procnum,
    const xdrproc_t inproc,
    const caddr_t in,
    const xdrproc_t outproc,
    caddr_t out,
    const struct timeval tout)
{
        enum clnt_stat  clntstat;
        idmap_stat rc;

        (void) rw_rdlock(&idmap_handle.lock);
        for (;;) {
                if (idmap_handle.failed) {
                        /* No connection.  Bid to see if we should fix it. */
                        (void) rw_unlock(&idmap_handle.lock);
                        /* Somebody else might fix it here. */
                        (void) rw_wrlock(&idmap_handle.lock);
                        /*
                         * At this point, everybody else is asleep waiting
                         * for us.  Check to see if somebody else has already
                         * fixed the problem.
                         */
                        if (idmap_handle.failed) {
                                /* It's our job to fix. */
                                _idmap_clnt_disconnect();
                                rc = _idmap_clnt_connect();
                                if (rc != IDMAP_SUCCESS) {
                                        /* We couldn't fix it. */
                                        assert(idmap_handle.failed);
                                        assert(idmap_handle.client == NULL);
                                        break;
                                }
                                /* We fixed it. */
                                idmap_handle.failed = B_FALSE;
                        }

                        /* It's fixed now. */
                        (void) rw_unlock(&idmap_handle.lock);
                        /*
                         * Starting here, somebody might declare it failed
                         * again.
                         */
                        (void) rw_rdlock(&idmap_handle.lock);
                        continue;
                }

                clntstat = clnt_call(idmap_handle.client, procnum, inproc, in,
                    outproc, out, tout);
                rc = _idmap_rpc2stat(clntstat, idmap_handle.client);
                if (rc == IDMAP_ERR_RPC_HANDLE) {
                        /* Failed.  Needs to be reconnected. */
                        idmap_handle.failed = B_TRUE;
                        continue;
                }

                /* Success or unrecoverable failure. */
                break;
        }
        (void) rw_unlock(&idmap_handle.lock);
        return (rc);
}

#define MIN_STACK_NEEDS 65536

/*
 * Connect to idmapd.
 * Must be single-threaded through rw_wrlock(&idmap_handle.lock).
 */
static
idmap_stat
_idmap_clnt_connect(void)
{
        uint_t                  sendsz = 0;
        stack_t                 st;

        /*
         * clnt_door_call() alloca()s sendsz bytes (twice too, once for
         * the call args buffer and once for the call result buffer), so
         * we want to pick a sendsz that will be large enough, but not
         * too large.
         */
        if (stack_getbounds(&st) == 0) {
                /*
                 * Estimate how much stack space is left;
                 * st.ss_sp is the top of stack.
                 */
                if ((char *)&sendsz < (char *)st.ss_sp)
                        /* stack grows up */
                        sendsz = ((char *)st.ss_sp - (char *)&sendsz);
                else
                        /* stack grows down */
                        sendsz = ((char *)&sendsz - (char *)st.ss_sp);

                if (sendsz <= MIN_STACK_NEEDS) {
                        sendsz = 0;     /* RPC call may fail */
                } else {
                        /* Leave 64Kb (just a guess) for our needs */
                        sendsz -= MIN_STACK_NEEDS;

                        /* Divide the stack space left by two */
                        sendsz = RNDUP(sendsz / 2);

                        /* Limit sendsz to 256KB */
                        if (sendsz > IDMAP_MAX_DOOR_RPC)
                                sendsz = IDMAP_MAX_DOOR_RPC;
                }
        }

        idmap_handle.client = clnt_door_create(IDMAP_PROG, IDMAP_V1, sendsz);
        if (idmap_handle.client == NULL)
                return (IDMAP_ERR_RPC);

        return (IDMAP_SUCCESS);
}

/*
 * Disconnect from idmapd, if we're connected.
 */
static
void
_idmap_clnt_disconnect(void)
{
        CLIENT *clnt;

        clnt = idmap_handle.client;
        if (clnt != NULL) {
                if (clnt->cl_auth)
                        auth_destroy(clnt->cl_auth);
                clnt_destroy(clnt);
                idmap_handle.client = NULL;
        }
}