1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23 */
24
25/*
26 * Utility routines
27 */
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <errno.h>
32#include <libintl.h>
33#include <assert.h>
34#include <ucontext.h>
35#include <pthread.h>
36#include "idmap_impl.h"
37
38#define	_UDT_SIZE_INCR	1
39
40#define	_GET_IDS_SIZE_INCR	1
41
42static struct timeval TIMEOUT = { 25, 0 };
43
44struct idmap_handle {
45	CLIENT		*client;
46	boolean_t	failed;
47	rwlock_t	lock;
48};
49
50static struct idmap_handle idmap_handle = {
51	NULL,		/* client */
52	B_TRUE,		/* failed */
53	DEFAULTRWLOCK,	/* lock */
54};
55
56static idmap_stat _idmap_clnt_connect(void);
57static void _idmap_clnt_disconnect(void);
58
59idmap_retcode
60_udt_extend_batch(idmap_udt_handle_t *udthandle)
61{
62	idmap_update_op	*tmplist;
63	size_t		nsize;
64
65	if (udthandle->next >= udthandle->batch.idmap_update_batch_len) {
66		nsize = (udthandle->batch.idmap_update_batch_len +
67		    _UDT_SIZE_INCR) * sizeof (*tmplist);
68		tmplist = realloc(
69		    udthandle->batch.idmap_update_batch_val, nsize);
70		if (tmplist == NULL)
71			return (IDMAP_ERR_MEMORY);
72		(void) memset((uchar_t *)tmplist +
73		    (udthandle->batch.idmap_update_batch_len *
74		    sizeof (*tmplist)), 0,
75		    _UDT_SIZE_INCR * sizeof (*tmplist));
76		udthandle->batch.idmap_update_batch_val = tmplist;
77		udthandle->batch.idmap_update_batch_len += _UDT_SIZE_INCR;
78	}
79	udthandle->batch.idmap_update_batch_val[udthandle->next].opnum =
80	    OP_NONE;
81	return (IDMAP_SUCCESS);
82}
83
84idmap_retcode
85_get_ids_extend_batch(idmap_get_handle_t *gh)
86{
87	idmap_mapping	*t1;
88	idmap_get_res_t	*t2;
89	size_t		nsize, len;
90
91	len = gh->batch.idmap_mapping_batch_len;
92	if (gh->next >= len) {
93		/* extend the request array */
94		nsize = (len + _GET_IDS_SIZE_INCR) * sizeof (*t1);
95		t1 = realloc(gh->batch.idmap_mapping_batch_val, nsize);
96		if (t1 == NULL)
97			return (IDMAP_ERR_MEMORY);
98		(void) memset((uchar_t *)t1 + (len * sizeof (*t1)), 0,
99		    _GET_IDS_SIZE_INCR * sizeof (*t1));
100		gh->batch.idmap_mapping_batch_val = t1;
101
102		/* extend the return list */
103		nsize = (len + _GET_IDS_SIZE_INCR) * sizeof (*t2);
104		t2 = realloc(gh->retlist, nsize);
105		if (t2 == NULL)
106			return (IDMAP_ERR_MEMORY);
107		(void) memset((uchar_t *)t2 + (len * sizeof (*t2)), 0,
108		    _GET_IDS_SIZE_INCR * sizeof (*t2));
109		gh->retlist = t2;
110
111		gh->batch.idmap_mapping_batch_len += _GET_IDS_SIZE_INCR;
112	}
113	return (IDMAP_SUCCESS);
114}
115
116idmap_stat
117_iter_get_next_list(int type, idmap_iter_t *iter,
118		void *arg, uchar_t **list, size_t valsize,
119		xdrproc_t xdr_arg_proc, xdrproc_t xdr_res_proc)
120{
121	idmap_stat rc;
122
123	iter->next = 0;
124	iter->retlist = NULL;
125
126	/* init the result */
127	if (*list) {
128		xdr_free(xdr_res_proc, (caddr_t)*list);
129	} else {
130		if ((*list = malloc(valsize)) == NULL) {
131			errno = ENOMEM;
132			return (IDMAP_ERR_MEMORY);
133		}
134	}
135	(void) memset(*list, 0, valsize);
136
137	rc = _idmap_clnt_call(type,
138	    xdr_arg_proc, (caddr_t)arg,
139	    xdr_res_proc, (caddr_t)*list,
140	    TIMEOUT);
141	if (rc != IDMAP_SUCCESS) {
142		free(*list);
143		return (rc);
144	}
145	iter->retlist = *list;
146	return (IDMAP_SUCCESS);
147}
148
149/*
150 * Convert the return values from an RPC request into an idmap return code.
151 * Set errno on error.
152 */
153static
154idmap_stat
155_idmap_rpc2stat(enum clnt_stat clntstat, CLIENT *clnt)
156{
157	/*
158	 * We only deal with door_call(3C) errors here. We look at
159	 * r_err.re_errno instead of r_err.re_status because we need
160	 * to differentiate between RPC failures caused by bad door fd
161	 * and others.
162	 */
163	struct rpc_err r_err;
164
165	if (clntstat == RPC_SUCCESS)
166		return (IDMAP_SUCCESS);
167
168	clnt_geterr(clnt, &r_err);
169	errno = r_err.re_errno;
170	switch (r_err.re_errno) {
171	case ENOMEM:
172		return (IDMAP_ERR_MEMORY);
173	case EBADF:
174		return (IDMAP_ERR_RPC_HANDLE);
175	default:
176		return (IDMAP_ERR_RPC);
177	}
178}
179
180/*
181 * Management of the connection to idmapd.
182 *
183 * The intent is that connections to idmapd are automatically maintained,
184 * reconnecting if necessary.  No attempt is made to retry connnection
185 * attempts; a failure to connect yields an immediate error return.
186 *
187 * State of the connection is maintained through the "client" and "failed"
188 * elements of the handle structure:
189 *
190 * client   failed
191 * NULL     true     Failed on a previous request and was not recovered.
192 * NULL     false    Should never happen.
193 * nonNULL  true     Structure exists, but an error has occurred.  Waiting
194 *                   for a chance to attempt to reconnect.
195 * nonNULL  false    Connection is good.
196 *
197 * Note that the initial state is NULL/true, so that the first request
198 * will establish the initial connection.
199 *
200 * Concurrency is managed through the rw lock "lock".  Only the writer is
201 * allowed to connect or disconnect, and thus only the writer can set
202 * "failed" to "false".  Readers are allowed to use the "client" pointer,
203 * and to set "failed" to "true", indicating that they have encountered a
204 * failure.  The "client" pointer is only valid while one holds a reader
205 * lock.  Once "failed" has been set to "true", all requests (including
206 * the retry of the failing request) will attempt to gain the writer lock.
207 * When they succeed, indicating that there are no requests in flight and
208 * thus no outstanding references to the CLIENT structure, they check
209 * again to see if the connection is still failed (since another thread
210 * might have fixed it), and then if it is still failed they disconnect
211 * and reconnect.
212 */
213
214/*
215 * Make an RPC call.  Automatically reconnect if the connection to idmapd
216 * fails.  Convert RPC results to idmap return codes.
217 */
218idmap_stat
219_idmap_clnt_call(
220    const rpcproc_t procnum,
221    const xdrproc_t inproc,
222    const caddr_t in,
223    const xdrproc_t outproc,
224    caddr_t out,
225    const struct timeval tout)
226{
227	enum clnt_stat	clntstat;
228	idmap_stat rc;
229
230	(void) rw_rdlock(&idmap_handle.lock);
231	for (;;) {
232		if (idmap_handle.failed) {
233			/* No connection.  Bid to see if we should fix it. */
234			(void) rw_unlock(&idmap_handle.lock);
235			/* Somebody else might fix it here. */
236			(void) rw_wrlock(&idmap_handle.lock);
237			/*
238			 * At this point, everybody else is asleep waiting
239			 * for us.  Check to see if somebody else has already
240			 * fixed the problem.
241			 */
242			if (idmap_handle.failed) {
243				/* It's our job to fix. */
244				_idmap_clnt_disconnect();
245				rc = _idmap_clnt_connect();
246				if (rc != IDMAP_SUCCESS) {
247					/* We couldn't fix it. */
248					assert(idmap_handle.failed);
249					assert(idmap_handle.client == NULL);
250					break;
251				}
252				/* We fixed it. */
253				idmap_handle.failed = B_FALSE;
254			}
255
256			/* It's fixed now. */
257			(void) rw_unlock(&idmap_handle.lock);
258			/*
259			 * Starting here, somebody might declare it failed
260			 * again.
261			 */
262			(void) rw_rdlock(&idmap_handle.lock);
263			continue;
264		}
265
266		clntstat = clnt_call(idmap_handle.client, procnum, inproc, in,
267		    outproc, out, tout);
268		rc = _idmap_rpc2stat(clntstat, idmap_handle.client);
269		if (rc == IDMAP_ERR_RPC_HANDLE) {
270			/* Failed.  Needs to be reconnected. */
271			idmap_handle.failed = B_TRUE;
272			continue;
273		}
274
275		/* Success or unrecoverable failure. */
276		break;
277	}
278	(void) rw_unlock(&idmap_handle.lock);
279	return (rc);
280}
281
282#define	MIN_STACK_NEEDS	65536
283
284/*
285 * Connect to idmapd.
286 * Must be single-threaded through rw_wrlock(&idmap_handle.lock).
287 */
288static
289idmap_stat
290_idmap_clnt_connect(void)
291{
292	uint_t			sendsz = 0;
293	stack_t			st;
294
295	/*
296	 * clnt_door_call() alloca()s sendsz bytes (twice too, once for
297	 * the call args buffer and once for the call result buffer), so
298	 * we want to pick a sendsz that will be large enough, but not
299	 * too large.
300	 */
301	if (stack_getbounds(&st) == 0) {
302		/*
303		 * Estimate how much stack space is left;
304		 * st.ss_sp is the top of stack.
305		 */
306		if ((char *)&sendsz < (char *)st.ss_sp)
307			/* stack grows up */
308			sendsz = ((char *)st.ss_sp - (char *)&sendsz);
309		else
310			/* stack grows down */
311			sendsz = ((char *)&sendsz - (char *)st.ss_sp);
312
313		if (sendsz <= MIN_STACK_NEEDS) {
314			sendsz = 0;	/* RPC call may fail */
315		} else {
316			/* Leave 64Kb (just a guess) for our needs */
317			sendsz -= MIN_STACK_NEEDS;
318
319			/* Divide the stack space left by two */
320			sendsz = RNDUP(sendsz / 2);
321
322			/* Limit sendsz to 256KB */
323			if (sendsz > IDMAP_MAX_DOOR_RPC)
324				sendsz = IDMAP_MAX_DOOR_RPC;
325		}
326	}
327
328	idmap_handle.client = clnt_door_create(IDMAP_PROG, IDMAP_V1, sendsz);
329	if (idmap_handle.client == NULL)
330		return (IDMAP_ERR_RPC);
331
332	return (IDMAP_SUCCESS);
333}
334
335/*
336 * Disconnect from idmapd, if we're connected.
337 */
338static
339void
340_idmap_clnt_disconnect(void)
341{
342	CLIENT *clnt;
343
344	clnt = idmap_handle.client;
345	if (clnt != NULL) {
346		if (clnt->cl_auth)
347			auth_destroy(clnt->cl_auth);
348		clnt_destroy(clnt);
349		idmap_handle.client = NULL;
350	}
351}
352