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/*
23 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 *
26 * Copyright (c) 2018, Joyent, Inc.
27 */
28
29#include <stdlib.h>
30#include <strings.h>
31#include <unistd.h>
32#include <stddef.h>
33#include <assert.h>
34#include <sys/types.h>
35#include <sys/socket.h>
36#include <thread.h>
37#include <synch.h>
38#include <libilb_impl.h>
39#include <libilb.h>
40
41/* Assertion: the calling thread has a hold on the handle */
42static void
43i_ilb_socket_set_err(ilb_handle_t h, ilb_status_t err)
44{
45	ilb_handle_impl_t	*hi = (ilb_handle_impl_t *)h;
46
47	if (h == ILB_INVALID_HANDLE)
48		return;
49	hi->h_valid = B_FALSE;
50	hi->h_error = err;
51}
52
53ilb_status_t
54ilb_open(ilb_handle_t *hp)
55{
56	ilb_handle_impl_t	*hi = NULL;
57	int			s = -1;
58	struct sockaddr_un sa = {AF_UNIX, SOCKET_PATH};
59	ilb_status_t		rc = ILB_STATUS_OK;
60	int			sobufsz;
61
62	if (hp == NULL)
63		return (ILB_STATUS_EINVAL);
64
65	hi = calloc(1, sizeof (*hi));
66	if (hi == NULL)
67		return (ILB_STATUS_ENOMEM);
68
69	if (cond_init(&hi->h_cv, USYNC_THREAD, NULL) != 0) {
70		rc = ILB_STATUS_INTERNAL;
71		goto out;
72	}
73
74	if (mutex_init(&hi->h_lock, USYNC_THREAD | LOCK_ERRORCHECK, NULL)
75	    != 0) {
76		rc = ILB_STATUS_INTERNAL;
77		goto out;
78	}
79
80	hi->h_busy = B_FALSE;
81
82	if ((s = socket(PF_UNIX, SOCK_SEQPACKET, 0)) == -1 ||
83	    connect(s, (struct sockaddr *)&sa, sizeof (sa.sun_path))
84	    == -1) {
85		rc = ILB_STATUS_SOCKET;
86		goto out;
87	}
88
89	/* The socket buffer must be at least the max size of a message */
90	sobufsz = ILBD_MSG_SIZE;
91	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sobufsz,
92	    sizeof (sobufsz)) != 0) {
93		rc = ILB_STATUS_SOCKET;
94		(void) close(s);
95		goto out;
96	}
97	if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sobufsz,
98	    sizeof (sobufsz)) != 0) {
99		rc = ILB_STATUS_SOCKET;
100		(void) close(s);
101		goto out;
102	}
103
104	hi->h_socket = s;
105	hi->h_valid = B_TRUE;
106
107out:
108	if (rc != ILB_STATUS_OK && s != -1)
109		(void) close(s);
110
111	if (rc == ILB_STATUS_OK) {
112		*hp = (ilb_handle_t)hi;
113	} else {
114		free(hi);
115		*hp = ILB_INVALID_HANDLE;
116	}
117	return (rc);
118}
119
120ilb_status_t
121ilb_close(ilb_handle_t h)
122{
123	ilb_handle_impl_t	*hi = (ilb_handle_impl_t *)h;
124
125	if (h == ILB_INVALID_HANDLE)
126		return (ILB_STATUS_EINVAL);
127
128	if (mutex_lock(&hi->h_lock) != 0)
129		return (ILB_STATUS_INTERNAL);
130
131	/* Somebody has done a close, no need to do anything. */
132	if (hi->h_closing) {
133		return (ILB_STATUS_OK);
134	} else {
135		hi->h_closing = B_TRUE;
136		hi->h_error = ILB_STATUS_HANDLE_CLOSING;
137	}
138
139	/* Wait until there is nobody waiting. */
140	while (hi->h_waiter > 0) {
141		if (cond_wait(&hi->h_cv, &hi->h_lock) != 0) {
142			(void) mutex_unlock(&hi->h_lock);
143			return (ILB_STATUS_INTERNAL);
144		}
145	}
146	/* No one is waiting, proceed to free the handle. */
147
148	(void) close(hi->h_socket);
149	(void) mutex_destroy(&hi->h_lock);
150	(void) cond_destroy(&hi->h_cv);
151	free(hi);
152	return (ILB_STATUS_OK);
153}
154
155/*
156 * Unified routine to communicate with ilbd.
157 *
158 * If ic is non-NULL, it means that the caller wants to send something
159 * to ilbd and expects a reply.  If ic is NULL, it means that the caller
160 * only expects to receive from ilbd.
161 *
162 * The rbuf is the buffer supplied by the caller for receiving.  If it
163 * is NULL, it means that there is no reply expected.
164 *
165 * This function will not close() the socket to kernel unless there is
166 * an error.  If the transaction only consists of one exchange, the caller
167 * can use i_ilb_close_comm() to close() the socket when done.
168 */
169ilb_status_t
170i_ilb_do_comm(ilb_handle_t h, ilb_comm_t *ic, size_t ic_sz, ilb_comm_t *rbuf,
171    size_t *rbufsz)
172{
173	ilb_status_t		rc = ILB_STATUS_OK;
174	int			r, s;
175	ilb_handle_impl_t	*hi = (ilb_handle_impl_t *)h;
176
177	assert(rbuf != NULL);
178	if (h == ILB_INVALID_HANDLE)
179		return (ILB_STATUS_EINVAL);
180
181	if (mutex_lock(&hi->h_lock) != 0)
182		return (ILB_STATUS_INTERNAL);
183
184	hi->h_waiter++;
185	while (hi->h_busy) {
186		if (cond_wait(&hi->h_cv, &hi->h_lock) != 0) {
187			hi->h_waiter--;
188			(void) cond_signal(&hi->h_cv);
189			(void) mutex_unlock(&hi->h_lock);
190			return (ILB_STATUS_INTERNAL);
191		}
192	}
193
194	if (!hi->h_valid || hi->h_closing) {
195		hi->h_waiter--;
196		(void) cond_signal(&hi->h_cv);
197		(void) mutex_unlock(&hi->h_lock);
198		return (hi->h_error);
199	}
200
201	hi->h_busy = B_TRUE;
202	(void) mutex_unlock(&hi->h_lock);
203
204	s = hi->h_socket;
205
206	r = send(s, ic, ic_sz, 0);
207	if (r < ic_sz) {
208		rc = ILB_STATUS_WRITE;
209		goto socket_error;
210	}
211	rc = ILB_STATUS_OK;
212
213	if ((r = recv(s, rbuf, *rbufsz, 0)) <= 0) {
214		rc = ILB_STATUS_READ;
215	} else {
216		*rbufsz = r;
217		goto out;
218	}
219
220socket_error:
221	i_ilb_socket_set_err(h, rc);
222
223out:
224	(void) mutex_lock(&hi->h_lock);
225	hi->h_busy = B_FALSE;
226	hi->h_waiter--;
227	(void) cond_signal(&hi->h_cv);
228	(void) mutex_unlock(&hi->h_lock);
229
230	return (rc);
231}
232
233void
234i_ilb_close_comm(ilb_handle_t h)
235{
236	(void) ilb_close(h);
237}
238