summaryrefslogtreecommitdiffstats
path: root/usr.sbin/iscsid/control.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/iscsid/control.c')
-rw-r--r--usr.sbin/iscsid/control.c327
1 files changed, 327 insertions, 0 deletions
diff --git a/usr.sbin/iscsid/control.c b/usr.sbin/iscsid/control.c
new file mode 100644
index 00000000000..e95b1a45e81
--- /dev/null
+++ b/usr.sbin/iscsid/control.c
@@ -0,0 +1,327 @@
+/* $OpenBSD: control.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2010 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "iscsid.h"
+#include "log.h"
+
+struct control {
+ TAILQ_ENTRY(control) entry;
+ struct event ev;
+ struct pduq channel;
+ int fd;
+} *control_state;
+
+TAILQ_HEAD(, control) controls;
+
+#define CONTROL_BACKLOG 5
+
+void control_accept(int, short, void *);
+void control_close(struct control *);
+void control_dispatch(int, short, void *);
+struct pdu *control_getpdu(char *, size_t);
+
+int
+control_init(char *path)
+{
+ struct sockaddr_un sun;
+ int fd;
+ mode_t old_umask;
+
+ if ((control_state = calloc(1, sizeof(*control_state))) == NULL) {
+ log_warn("control_init: calloc");
+ return (-1);
+ }
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ log_warn("control_init: socket");
+ return (-1);
+ }
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+
+ if (unlink(path) == -1)
+ if (errno != ENOENT) {
+ log_warn("control_init: unlink %s", path);
+ close(fd);
+ return (-1);
+ }
+
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+ log_warn("control_init: bind: %s", path);
+ close(fd);
+ umask(old_umask);
+ return (-1);
+ }
+ umask(old_umask);
+
+ if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
+ log_warn("control_init: chmod");
+ close(fd);
+ (void)unlink(path);
+ return (-1);
+ }
+
+ socket_setblockmode(fd, 1);
+ control_state->fd = fd;
+ TAILQ_INIT(&control_state->channel);
+ TAILQ_INIT(&controls);
+
+ return (0);
+}
+
+void
+control_cleanup(char *path)
+{
+ struct control *c;
+
+ if (path)
+ unlink(path);
+
+ while ((c = TAILQ_FIRST(&controls)) != NULL) {
+ TAILQ_REMOVE(&controls, c, entry);
+ control_close(c);
+ }
+ control_close(control_state);
+}
+
+int
+control_listen(void)
+{
+ if (listen(control_state->fd, CONTROL_BACKLOG) == -1) {
+ log_warn("control_listen: listen");
+ return (-1);
+ }
+
+ event_set(&control_state->ev, control_state->fd, EV_READ | EV_PERSIST,
+ control_accept, NULL);
+ event_add(&control_state->ev, NULL);
+
+ return (0);
+}
+
+/* ARGSUSED */
+void
+control_accept(int listenfd, short event, void *bula)
+{
+ int connfd;
+ socklen_t len;
+ struct sockaddr_un sun;
+ struct control *c;
+
+ len = sizeof(sun);
+ if ((connfd = accept(listenfd,
+ (struct sockaddr *)&sun, &len)) == -1) {
+ if (errno != EWOULDBLOCK && errno != EINTR)
+ log_warn("control_accept");
+ return;
+ }
+
+ if ((c = malloc(sizeof(struct control))) == NULL) {
+ log_warn("control_accept");
+ close(connfd);
+ return;
+ }
+
+ TAILQ_INIT(&c->channel);
+ c->fd = connfd;
+ event_set(&c->ev, connfd, EV_READ, control_dispatch, c);
+ event_add(&c->ev, NULL);
+}
+
+void
+control_close(struct control *c)
+{
+ close(c->fd);
+ event_del(&c->ev);
+ pdu_free_queue(&c->channel);
+ free(c);
+}
+
+static char cbuf[CONTROL_READ_SIZE];
+
+/* ARGSUSED */
+void
+control_dispatch(int fd, short event, void *bula)
+{
+ struct iovec iov[PDU_MAXIOV];
+ struct msghdr msg;
+ struct control *c = bula;
+ struct pdu *pdu;
+ ssize_t n;
+ unsigned int niov = 0;
+ short flags = EV_READ;
+
+ if (event & EV_TIMEOUT) {
+ log_debug("control connection (fd %d) timed out.", fd);
+ control_close(c);
+ return;
+ }
+ if (event & EV_READ) {
+ if ((n = recv(fd, cbuf, sizeof(cbuf), 0)) == -1 &&
+ !(errno == EAGAIN || errno == EINTR)) {
+ control_close(c);
+ return;
+ }
+ if (n == 0) {
+ control_close(c);
+ return;
+ }
+log_debug("control_dispatch: recv %zd bytes", n);
+ pdu = control_getpdu(cbuf, n);
+ if (!pdu) {
+ log_debug("control connection (fd %d) bad msg.", fd);
+ control_close(c);
+ return;
+ }
+ iscsid_ctrl_dispatch(c, pdu);
+ }
+ if (event & EV_WRITE) {
+ if ((pdu = TAILQ_FIRST(&c->channel)) != NULL) {
+ TAILQ_REMOVE(&c->channel, pdu, entry);
+
+ for (niov = 0; niov < PDU_MAXIOV; niov++) {
+ iov[niov].iov_base = pdu->iov[niov].iov_base;
+ iov[niov].iov_len = pdu->iov[niov].iov_len;
+ }
+ bzero(&msg, sizeof(msg));
+ msg.msg_iov = iov;
+ msg.msg_iovlen = niov;
+log_debug("control_dispatch: send %d iov", niov);
+ if (sendmsg(fd, &msg, 0) == -1 &&
+ !(errno == EAGAIN || errno == ENOBUFS)) {
+ control_close(c);
+ return;
+ }
+ }
+ }
+ if (!TAILQ_EMPTY(&c->channel))
+ flags |= EV_WRITE;
+
+ event_del(&c->ev);
+ event_set(&c->ev, fd, flags, control_dispatch, c);
+ event_add(&c->ev, NULL);
+}
+
+struct pdu *
+control_getpdu(char *buf, size_t len)
+{
+ struct pdu *p;
+ struct ctrlmsghdr *cmh;
+ void *data;
+ size_t n;
+ int i;
+
+ if (len < sizeof(*cmh))
+ return NULL;
+
+ if (!(p = pdu_new()))
+ return NULL;
+
+ n = sizeof(*cmh);
+ cmh = pdu_alloc(n);
+ bcopy(buf, cmh, n);
+ buf += n;
+ len -= n;
+
+ if (pdu_addbuf(p, cmh, n, 0)) {
+ free(cmh);
+fail:
+ pdu_free(p);
+ return NULL;
+ }
+
+ for (i = 0; i < 3; i++) {
+ n = cmh->len[i];
+ if (n == 0)
+ continue;
+ if (PDU_LEN(n) > len)
+ goto fail;
+ if (!(data = pdu_alloc(n)))
+ goto fail;
+ bcopy(buf, data, n);
+ if (pdu_addbuf(p, data, n, i + 1)) {
+ free(data);
+ goto fail;
+ }
+ buf += PDU_LEN(n);
+ len -= PDU_LEN(n);
+ }
+
+ return p;
+}
+
+int
+control_queue(void *ch, struct pdu *pdu)
+{
+ struct control *c = ch;
+
+ TAILQ_INSERT_TAIL(&c->channel, pdu, entry);
+
+ event_del(&c->ev);
+ event_set(&c->ev, c->fd, EV_READ|EV_WRITE, control_dispatch, c);
+ event_add(&c->ev, NULL);
+
+ return 0;
+}
+
+int
+control_compose(void *ch, u_int16_t type, void *buf, size_t len)
+{
+ struct pdu *pdu;
+ struct ctrlmsghdr *cmh;
+ void *ptr;
+
+ if (PDU_LEN(len) > CONTROL_READ_SIZE - PDU_LEN(sizeof(*cmh)))
+ return -1;
+ if ((pdu = pdu_new()) == NULL)
+ return -1;
+ if ((cmh = pdu_alloc(sizeof(*cmh))) == NULL)
+ goto fail;
+ bzero(cmh, sizeof(*cmh));
+ cmh->type = type;
+ cmh->len[0] = len;
+ pdu_addbuf(pdu, cmh, sizeof(*cmh), 0);
+ if (len > 0) {
+ if ((ptr = pdu_alloc(len)) == NULL)
+ goto fail;
+ bcopy(buf, ptr, len);
+ pdu_addbuf(pdu, ptr, len, 1);
+ }
+
+ return control_queue(ch, pdu);
+fail:
+ pdu_free(pdu);
+ return -1;
+}