xref: /illumos-gate/usr/src/lib/libslp/clib/slp_ipc.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1999-2001 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <string.h>
32 #include <errno.h>
33 #include <arpa/inet.h>
34 #include <unistd.h>
35 #include <syslog.h>
36 #include <thread.h>
37 #include <synch.h>
38 #include <netinet/in.h>
39 #include <signal.h>
40 #include <slp-internal.h>
41 
42 #define	IPC_FD_LIFETIME	30
43 
44 /*
45  * Cached parameters and thread synchronization
46  */
47 static int slpdfd;			/* cached FD to slpd */
48 static mutex_t ipc_lock = DEFAULTMUTEX;	/* serializes IPC */
49 
50 /* synch for the FD management thread */
51 static mutex_t ipc_wait_lock = DEFAULTMUTEX;
52 static cond_t ipc_wait_var;
53 static int ipc_used;
54 static int ipc_thr_running;
55 
56 static struct sockaddr_in *local_sin;	/* slpd addr, set on first use */
57 
58 static SLPError open_ipc();
59 static void close_ipc();
60 static void get_localhost_sin();
61 static void *ipc_manage_thr(void *);
62 
63 /*
64  * Locking should be handled by the caller
65  */
66 static SLPError open_ipc() {
67 	int terr;
68 	int retries = 0;
69 
70 	if (slpdfd)
71 		return (SLP_OK);
72 
73 	/* Make sure the local host's sockaddr_in is set */
74 	if (!local_sin) {
75 		get_localhost_sin();
76 		if (!local_sin) {
77 			slpdfd = 0;
78 			return (SLP_INTERNAL_SYSTEM_ERROR);
79 		}
80 	}
81 
82 	for (;;) {
83 	    int errno_kept;
84 
85 	    if ((slpdfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
86 		slp_err(LOG_CRIT, 0, "slp_open_ipc",
87 			"could not create socket: %s", strerror(errno));
88 		slpdfd = 0;
89 		return (SLP_INTERNAL_SYSTEM_ERROR);
90 	    }
91 
92 
93 	    if (connect(slpdfd, (struct sockaddr *)local_sin,
94 			sizeof (*local_sin)) == 0) {
95 		break;
96 	    }
97 
98 	    /* else error condition */
99 	    errno_kept = errno; /* in case errno is reset by slp_err */
100 	    if (retries++ == 2) {
101 		slp_err(LOG_INFO, 0, "slp_open_ipc",
102 			"could not connect to slpd: %s", strerror(errno));
103 		if (errno_kept == ECONNREFUSED)
104 		    slp_err(LOG_INFO, 0, "slp_open_ipc",
105 			    "is slpd running?");
106 		(void) close(slpdfd);
107 		slpdfd = 0;
108 		return (SLP_NETWORK_ERROR);
109 	    } else {
110 		/* back off a little */
111 		(void) close(slpdfd);
112 		(void) sleep(1);
113 	    }
114 	}
115 
116 	/* We now know slpd is reachable; start the management thread */
117 	if (!ipc_thr_running) {
118 		if ((terr = thr_create(0, 0, ipc_manage_thr,
119 		    NULL, 0, NULL)) != 0) {
120 			slp_err(LOG_CRIT, 0, "slp_open_ipc",
121 				"could not start thread: %s",
122 				strerror(terr));
123 			return (SLP_INTERNAL_SYSTEM_ERROR);
124 		}
125 	}
126 	ipc_thr_running = 1;
127 
128 	return (SLP_OK);
129 }
130 
131 static void close_ipc() {
132 	(void) mutex_lock(&ipc_lock);
133 	if (!slpdfd) {
134 		(void) mutex_unlock(&ipc_lock);
135 		return;
136 	}
137 	(void) close(slpdfd);
138 	slpdfd = 0;
139 	(void) mutex_unlock(&ipc_lock);
140 }
141 
142 /*
143  * Sends 'msg' to slpd, placing the response in 'reply'. Caller should
144  * free memory associated with 'reply'. All IPC is handled transparantly
145  * by this call. Note that this call is a wrapper for slp_send2slpd_iov.
146  * Returns SLP_NETWORK_ERROR if slpd is unreachable, SLP_OK otherwise.
147  */
148 SLPError slp_send2slpd(const char *msg, char **reply) {
149 	struct iovec iov[1];
150 	iov->iov_base = (caddr_t)msg;
151 	iov->iov_len = slp_get_length(msg);
152 
153 	return (slp_send2slpd_iov(iov, 1, reply));
154 }
155 
156 SLPError slp_send2slpd_iov(struct iovec *msg, int iovlen, char **reply) {
157 	SLPError err;
158 	int retries = 0;
159 	struct msghdr msghdr[1];
160 	struct sigaction new, old;
161 
162 	*reply = NULL;
163 
164 	(void) mutex_lock(&ipc_lock);
165 	/* is the connection open? */
166 	if (!slpdfd) {
167 		if ((err = open_ipc()) != SLP_OK) {
168 			(void) mutex_unlock(&ipc_lock);
169 			return (err);
170 		}
171 	}
172 
173 	/* populate the msghdr for sendmsg */
174 	msghdr->msg_name = NULL;
175 	msghdr->msg_namelen = 0;
176 	msghdr->msg_iov = msg;
177 	msghdr->msg_iovlen = iovlen;
178 	msghdr->msg_accrights = NULL;
179 	msghdr->msg_accrightslen = 0;
180 
181 	/*
182 	 * If slpd has been restarted while this connection is
183 	 * still open, we will get a SIGPIPE when we try to write
184 	 * to it. So we need to ignore SIGPIPEs for the duration of
185 	 * the communication with slpd.
186 	 */
187 	new.sa_handler = SIG_IGN;
188 	new.sa_flags = 0;
189 	(void) sigemptyset(&new.sa_mask);
190 	(void) sigaction(SIGPIPE, &new, &old);	/* preserve old disposition */
191 
192 	while (sendmsg(slpdfd, msghdr, 0) == -1) {
193 		int errno_kept = errno;
194 
195 		switch (errno) {
196 		case EINTR:
197 		case ENOBUFS:
198 		case ENOSR:
199 			continue;
200 		case EBADF:
201 		case ECONNRESET:
202 		case ENOTCONN:
203 		default:
204 			(void) mutex_unlock(&ipc_lock);
205 			close_ipc();
206 			if (retries++) {
207 				slp_err(LOG_CRIT, 0, "slp_send2slpd",
208 					"could not talk to slpd: %s",
209 					strerror(errno_kept));
210 				err = SLP_NETWORK_ERROR;
211 				goto done;
212 			}
213 			/* try re-opening the connection to slpd */
214 			if (open_ipc() == SLP_OK) {
215 				(void) mutex_lock(&ipc_lock);
216 				continue;
217 			} else {
218 				err = SLP_NETWORK_ERROR;
219 				goto done;
220 			}
221 		}
222 	}
223 
224 	err = slp_tcp_read(slpdfd, reply);
225 
226 	/*
227 	 * On error slpd may close the socket; there can be a race
228 	 * condition here where a following call (attempting to reuse
229 	 * the socket) may send to slpd before it has closed the socket.
230 	 * To prevent this, we must also close the socket on error.
231 	 */
232 	if (err == SLP_OK && slp_get_errcode(*reply) != 0) {
233 		(void) mutex_unlock(&ipc_lock);
234 		close_ipc();
235 		(void) mutex_lock(&ipc_lock);
236 	}
237 
238 	/* notify ipc thread of call */
239 	(void) mutex_lock(&ipc_wait_lock);
240 	ipc_used = 1;
241 	(void) cond_signal(&ipc_wait_var);
242 	(void) mutex_unlock(&ipc_wait_lock);
243 
244 	(void) mutex_unlock(&ipc_lock);
245 
246 done:
247 	/* restore original signal disposition for SIGPIPE */
248 	(void) sigaction(SIGPIPE, &old, NULL);
249 	return (err);
250 }
251 
252 /*
253  * Sets up a sockaddr_in pointing at slpd.
254  * After the first call, the address of slpd is cached in local_sin.
255  *
256  * side effect: local_sin is set to an address for slpd.
257  */
258 static void get_localhost_sin() {
259 	struct sockaddr_in *sin;
260 	static mutex_t lhlock = DEFAULTMUTEX;
261 
262 	(void) mutex_lock(&lhlock);
263 	if (local_sin) {
264 		(void) mutex_unlock(&lhlock);
265 		return;
266 	}
267 
268 	if (!(sin = calloc(1, sizeof (*sin)))) {
269 		slp_err(LOG_CRIT, 0, "get_localhost_sin", "out of memory");
270 		goto done;
271 	}
272 
273 	IN_SET_LOOPBACK_ADDR(sin);
274 	sin->sin_family = AF_INET;
275 	sin->sin_port = htons(SLP_PORT);
276 
277 done:
278 	local_sin = sin;
279 	(void) mutex_unlock(&lhlock);
280 }
281 
282 /*
283  * IPC management: the FD to slpd is kept open and cached to improve
284  * performance on successive calls. The IPC management thread waits
285  * on a condition variable; the condition is if an IPC call has been
286  * made. If so, the thread advances the FD's expiration by IPC_FD_LIFETIME
287  * and continues waiting for the next IPC call. After the FD has expired,
288  * the thread closes IPC and shuts itself down.
289  */
290 static void *
291 ipc_manage_thr(void *arg __unused)
292 {
293 	timestruc_t timeout;
294 
295 	timeout.tv_nsec = 0;
296 	(void) mutex_lock(&ipc_wait_lock);
297 	ipc_used = 0;
298 
299 	while (ipc_used == 0) {
300 		int err;
301 
302 		timeout.tv_sec = IPC_FD_LIFETIME;
303 		err = cond_reltimedwait(&ipc_wait_var, &ipc_wait_lock,
304 		    &timeout);
305 
306 		if (err == ETIME) {
307 			/* shutdown */
308 			close_ipc();
309 			ipc_thr_running = 0;
310 			(void) mutex_unlock(&ipc_wait_lock);
311 			thr_exit(NULL);
312 		} else {
313 			/* reset condition variable */
314 			ipc_used = 0;
315 		}
316 	}
317 	return (NULL);
318 }
319