/* * 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; } }