1/*	$OpenBSD: iscsictl.c,v 1.13 2023/02/21 15:45:40 mbuhl Exp $ */
2
3/*
4 * Copyright (c) 2010 Claudio Jeker <claudio@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/queue.h>
20#include <sys/socket.h>
21#include <sys/uio.h>
22#include <sys/un.h>
23
24#include <event.h>
25#include <err.h>
26#include <errno.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31#include <util.h>
32
33#include "iscsid.h"
34#include "iscsictl.h"
35
36__dead void	 usage(void);
37void		 run(void);
38void		 run_command(struct pdu *);
39struct pdu	*ctl_getpdu(char *, size_t);
40int		 ctl_sendpdu(int, struct pdu *);
41void		 show_config(struct ctrlmsghdr *, struct pdu *);
42void		 show_vscsi_stats(struct ctrlmsghdr *, struct pdu *);
43void             poll_and_wait(void);
44void             poll_session_status(void);
45void             register_poll(struct ctrlmsghdr *, struct pdu *);
46void             poll_print(struct session_poll *);
47
48char		cbuf[CONTROL_READ_SIZE];
49
50struct control {
51	struct pduq	channel;
52	int		fd;
53} control;
54
55
56struct session_poll poll_result;
57#define POLL_DELAY_SEC	1
58#define POLL_ATTEMPTS	10
59
60__dead void
61usage(void)
62{
63	extern char *__progname;
64
65	fprintf(stderr,"usage: %s [-s socket] command [argument ...]\n",
66	    __progname);
67	exit(1);
68}
69
70int
71main (int argc, char* argv[])
72{
73	struct sockaddr_un sun;
74	struct session_config sc;
75	struct parse_result *res;
76	char *confname = ISCSID_CONFIG;
77	char *sockname = ISCSID_CONTROL;
78	struct session_ctlcfg *s;
79	struct iscsi_config *cf;
80	int ch, poll = 0, val = 0;
81
82	/* check flags */
83	while ((ch = getopt(argc, argv, "f:s:")) != -1) {
84		switch (ch) {
85		case 'f':
86			confname = optarg;
87			break;
88		case 's':
89			sockname = optarg;
90			break;
91		default:
92			usage();
93			/* NOTREACHED */
94		}
95	}
96	argc -= optind;
97	argv += optind;
98
99	/* parse options */
100	if ((res = parse(argc, argv)) == NULL)
101		exit(1);
102
103	/* connect to iscsid control socket */
104	TAILQ_INIT(&control.channel);
105	if ((control.fd = socket(AF_UNIX, SOCK_SEQPACKET, 0)) == -1)
106		err(1, "socket");
107
108	bzero(&sun, sizeof(sun));
109	sun.sun_family = AF_UNIX;
110	strlcpy(sun.sun_path, sockname, sizeof(sun.sun_path));
111
112	if (connect(control.fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
113		err(1, "connect: %s", sockname);
114
115	if (pledge("stdio rpath dns", NULL) == -1)
116		err(1, "pledge");
117
118	switch (res->action) {
119	case NONE:
120	case LOG_VERBOSE:
121		val = 1;
122		/* FALLTHROUGH */
123	case LOG_BRIEF:
124		if (control_compose(NULL, CTRL_LOG_VERBOSE,
125		    &val, sizeof(int)) == -1)
126			err(1, "control_compose");
127		break;
128	case SHOW_SUM:
129		if (control_compose(NULL, CTRL_SHOW_SUM, NULL, 0) == -1)
130			err(1, "control_compose");
131		break;
132	case SHOW_SESS:
133		usage();
134		/* NOTREACHED */
135	case SHOW_VSCSI_STATS:
136		if (control_compose(NULL, CTRL_VSCSI_STATS, NULL, 0) == -1)
137			err(1, "control_compose");
138		break;
139	case RELOAD:
140		if ((cf = parse_config(confname)) == NULL)
141			errx(1, "errors while loading configuration file.");
142		if (cf->initiator.isid_base != 0) {
143			if (control_compose(NULL, CTRL_INITIATOR_CONFIG,
144			    &cf->initiator, sizeof(cf->initiator)) == -1)
145				err(1, "control_compose");
146		}
147
148		SIMPLEQ_FOREACH(s, &cf->sessions, entry) {
149			struct ctrldata cdv[3];
150			bzero(cdv, sizeof(cdv));
151
152			cdv[0].buf = &s->session;
153			cdv[0].len = sizeof(s->session);
154
155			if (s->session.TargetName) {
156				cdv[1].buf = s->session.TargetName;
157				cdv[1].len =
158				    strlen(s->session.TargetName) + 1;
159			}
160			if (s->session.InitiatorName) {
161				cdv[2].buf = s->session.InitiatorName;
162				cdv[2].len =
163				    strlen(s->session.InitiatorName) + 1;
164			}
165
166			if (control_build(NULL, CTRL_SESSION_CONFIG,
167			    nitems(cdv), cdv) == -1)
168				err(1, "control_build");
169		}
170
171		/* Reloading, so poll for connection establishment. */
172		poll = 1;
173		break;
174	case DISCOVERY:
175		printf("discover %s\n", log_sockaddr(&res->addr));
176
177		bzero(&sc, sizeof(sc));
178		snprintf(sc.SessionName, sizeof(sc.SessionName),
179		    "discovery.%d", (int)getpid());
180		bcopy(&res->addr, &sc.connection.TargetAddr, res->addr.ss_len);
181		sc.SessionType = SESSION_TYPE_DISCOVERY;
182
183		if (control_compose(NULL, CTRL_SESSION_CONFIG,
184		    &sc, sizeof(sc)) == -1)
185			err(1, "control_compose");
186	}
187
188	run();
189
190	if (poll)
191		poll_and_wait();
192
193	close(control.fd);
194	return (0);
195}
196
197void
198control_queue(void *ch, struct pdu *pdu)
199{
200	TAILQ_INSERT_TAIL(&control.channel, pdu, entry);
201}
202
203void
204run(void)
205{
206	struct pdu *pdu;
207
208	while ((pdu = TAILQ_FIRST(&control.channel)) != NULL) {
209		TAILQ_REMOVE(&control.channel, pdu, entry);
210		run_command(pdu);
211	}
212}
213
214void
215run_command(struct pdu *pdu)
216{
217	struct ctrlmsghdr *cmh;
218	int done = 0;
219	ssize_t n;
220
221	if (ctl_sendpdu(control.fd, pdu) == -1)
222		err(1, "send");
223	while (!done) {
224		if ((n = recv(control.fd, cbuf, sizeof(cbuf), 0)) == -1 &&
225		    !(errno == EAGAIN || errno == EINTR))
226			err(1, "recv");
227
228		if (n == 0)
229			errx(1, "connection to iscsid closed");
230
231		pdu = ctl_getpdu(cbuf, n);
232		cmh = pdu_getbuf(pdu, NULL, 0);
233		if (cmh == NULL)
234			break;
235		switch (cmh->type) {
236		case CTRL_SUCCESS:
237			printf("command successful\n");
238			done = 1;
239			break;
240		case CTRL_FAILURE:
241			printf("command failed\n");
242			done = 1;
243			break;
244		case CTRL_INPROGRESS:
245			printf("command in progress...\n");
246			break;
247		case CTRL_INITIATOR_CONFIG:
248		case CTRL_SESSION_CONFIG:
249			show_config(cmh, pdu);
250			break;
251		case CTRL_VSCSI_STATS:
252			show_vscsi_stats(cmh, pdu);
253			done = 1;
254			break;
255		case CTRL_SESS_POLL:
256			register_poll(cmh, pdu);
257			done = 1;
258			break;
259
260		}
261	}
262}
263
264struct pdu *
265ctl_getpdu(char *buf, size_t len)
266{
267	struct pdu *p;
268	struct ctrlmsghdr *cmh;
269	void *data;
270	size_t n;
271	int i;
272
273	if (len < sizeof(*cmh))
274		return NULL;
275
276	if (!(p = pdu_new()))
277		return NULL;
278
279	n = sizeof(*cmh);
280	cmh = pdu_alloc(n);
281	bcopy(buf, cmh, n);
282	buf += n;
283	len -= n;
284
285	if (pdu_addbuf(p, cmh, n, 0)) {
286		free(cmh);
287fail:
288		pdu_free(p);
289		return NULL;
290	}
291
292	for (i = 0; i < 3; i++) {
293		n = cmh->len[i];
294		if (n == 0)
295			continue;
296		if (PDU_LEN(n) > len)
297			goto fail;
298		if (!(data = pdu_alloc(n)))
299			goto fail;
300		bcopy(buf, data, n);
301		if (pdu_addbuf(p, data, n, i + 1)) {
302			free(data);
303			goto fail;
304		}
305		buf += PDU_LEN(n);
306		len -= PDU_LEN(n);
307	}
308
309	return p;
310}
311
312int
313ctl_sendpdu(int fd, struct pdu *pdu)
314{
315	struct iovec iov[PDU_MAXIOV];
316	struct msghdr msg;
317	unsigned int niov = 0;
318
319	for (niov = 0; niov < PDU_MAXIOV; niov++) {
320		iov[niov].iov_base = pdu->iov[niov].iov_base;
321		iov[niov].iov_len = pdu->iov[niov].iov_len;
322	}
323	bzero(&msg, sizeof(msg));
324	msg.msg_iov = iov;
325	msg.msg_iovlen = niov;
326	if (sendmsg(fd, &msg, 0) == -1)
327		return -1;
328	return 0;
329}
330
331void
332show_config(struct ctrlmsghdr *cmh, struct pdu *pdu)
333{
334	struct initiator_config	*ic;
335	struct session_config	*sc;
336	char *name;
337
338	switch (cmh->type) {
339	case CTRL_INITIATOR_CONFIG:
340		if (cmh->len[0] != sizeof(*ic))
341			errx(1, "bad size of response");
342		ic = pdu_getbuf(pdu, NULL, 1);
343		if (ic == NULL)
344			return;
345
346		printf("Initiator: ISID base %x qualifier %hx\n",
347		    ic->isid_base, ic->isid_qual);
348		break;
349	case CTRL_SESSION_CONFIG:
350		if (cmh->len[0] != sizeof(*sc))
351			errx(1, "bad size of response");
352		sc = pdu_getbuf(pdu, NULL, 1);
353		if (sc == NULL)
354			return;
355
356		printf("\nSession '%s':%s\n", sc->SessionName,
357		    sc->disabled ? " disabled" : "");
358		printf("    SessionType: %s\tMaxConnections: %hd\n",
359		    sc->SessionType == SESSION_TYPE_DISCOVERY ? "discovery" :
360		    "normal", sc->MaxConnections);
361		if ((name = pdu_getbuf(pdu, NULL, 2)))
362			printf("    TargetName: %s\n", name);
363		printf("    TargetAddr: %s\n",
364		    log_sockaddr(&sc->connection.TargetAddr));
365		if ((name = pdu_getbuf(pdu, NULL, 3)))
366			printf("    InitiatorName: %s\n", name);
367		printf("    InitiatorAddr: %s\n",
368		    log_sockaddr(&sc->connection.LocalAddr));
369		break;
370	}
371}
372
373void
374show_vscsi_stats(struct ctrlmsghdr *cmh, struct pdu *pdu)
375{
376	struct vscsi_stats *vs;
377	char buf[FMT_SCALED_STRSIZE];
378
379	if (cmh->len[0] != sizeof(struct vscsi_stats))
380		errx(1, "bad size of response");
381	vs = pdu_getbuf(pdu, NULL, 1);
382	if (vs == NULL)
383		return;
384
385	printf("VSCSI ioctl statistics:\n");
386	printf("%u probe calls and %u detach calls\n",
387	    vs->cnt_probe, vs->cnt_detach);
388	printf("%llu I2T calls (%llu read, %llu writes)\n",
389	    vs->cnt_i2t,
390	    vs->cnt_i2t_dir[1],
391	    vs->cnt_i2t_dir[2]);
392
393	if (fmt_scaled(vs->bytes_rd, buf) != 0)
394		(void)strlcpy(buf, "NaN", sizeof(buf));
395	printf("%llu data reads (%s bytes read)\n", vs->cnt_read, buf);
396	if (fmt_scaled(vs->bytes_wr, buf) != 0)
397		(void)strlcpy(buf, "NaN", sizeof(buf));
398	printf("%llu data writes (%s bytes written)\n", vs->cnt_write, buf);
399
400	printf("%llu T2I calls (%llu done, %llu sense errors, %llu errors)\n",
401	    vs->cnt_t2i,
402	    vs->cnt_t2i_status[0],
403	    vs->cnt_t2i_status[1],
404	    vs->cnt_t2i_status[2]);
405}
406
407void
408poll_session_status(void)
409{
410	struct pdu *pdu;
411
412	if (control_compose(NULL, CTRL_SESS_POLL, NULL, 0) == -1)
413		err(1, "control_compose");
414
415	while ((pdu = TAILQ_FIRST(&control.channel)) != NULL) {
416		TAILQ_REMOVE(&control.channel, pdu, entry);
417		run_command(pdu);
418	}
419}
420
421void
422poll_and_wait(void)
423{
424	int attempts;
425
426	printf("waiting for config to settle..");
427	fflush(stdout);
428
429	for (attempts = 0; attempts < POLL_ATTEMPTS; attempts++) {
430
431		poll_session_status();
432
433		/* Poll says we are good to go. */
434		if (poll_result.sess_conn_status != 0) {
435			printf("ok\n");
436			/* wait a bit longer so all is settled. */
437			sleep(POLL_DELAY_SEC);
438			return;
439		}
440
441		/* Poll says we should wait... */
442		printf(".");
443		fflush(stdout);
444		sleep(POLL_DELAY_SEC);
445	}
446
447	printf("giving up.\n");
448
449	poll_print(&poll_result);
450}
451
452void
453register_poll(struct ctrlmsghdr *cmh, struct pdu *pdu)
454{
455	if (cmh->len[0] != sizeof(poll_result))
456		errx(1, "poll: bad size of response");
457
458	poll_result = *((struct session_poll *)pdu_getbuf(pdu, NULL, 1));
459}
460
461void
462poll_print(struct session_poll *p)
463{
464	printf("Configured sessions: %d\n", p->session_count);
465	printf("Sessions initializing: %d\n", p->session_init_count);
466	printf("Sessions started/failed: %d\n", p->session_running_count);
467	printf("Sessions logged in: %d\n", p->conn_logged_in_count);
468	printf("Sessions with failed connections: %d\n", p->conn_failed_count);
469	printf("Sessions still attempting to connect: %d\n",
470	    p->conn_waiting_count);
471}
472