xref: /illumos-gate/usr/src/lib/libidmap/common/utils.c (revision e86372a01d2d16a5dd4a64e144ed978ba17fe7dd)
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 
42 static struct timeval TIMEOUT = { 25, 0 };
43 
44 struct idmap_handle {
45 	CLIENT		*client;
46 	boolean_t	failed;
47 	rwlock_t	lock;
48 };
49 
50 static struct idmap_handle idmap_handle = {
51 	NULL,		/* client */
52 	B_TRUE,		/* failed */
53 	DEFAULTRWLOCK,	/* lock */
54 };
55 
56 static idmap_stat _idmap_clnt_connect(void);
57 static void _idmap_clnt_disconnect(void);
58 
59 idmap_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 
84 idmap_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 
116 idmap_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  */
153 static
154 idmap_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  */
218 idmap_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  */
288 static
289 idmap_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  */
338 static
339 void
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