/* $OpenBSD: pdu.c,v 1.9 2014/04/21 12:26:50 claudio Exp $ */ /* * Copyright (c) 2009 Claudio Jeker <claudio@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/socket.h> #include <sys/uio.h> #include <scsi/iscsi.h> #include <errno.h> #include <event.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <unistd.h> #include "iscsid.h" #include "log.h" size_t pdu_readbuf_read(struct pdu_readbuf *, void *, size_t); size_t pdu_readbuf_len(struct pdu_readbuf *); #define PDU_MIN(_x, _y) ((_x) < (_y) ? (_x) : (_y)) void * pdu_gethdr(struct pdu *p) { void *hdr; if (!(hdr = calloc(1, sizeof(struct iscsi_pdu)))) return NULL; if (pdu_addbuf(p, hdr, sizeof(struct iscsi_pdu), PDU_HEADER)) { free(hdr); return NULL; } return hdr; } int text_to_pdu(struct kvp *k, struct pdu *p) { char *buf, *s; size_t len = 0, rem; int n, nk; if (k == NULL) return 0; nk = 0; while(k[nk].key) { len += 2 + strlen(k[nk].key) + strlen(k[nk].value); nk++; } if (!(buf = pdu_alloc(len))) return -1; s = buf; rem = len; nk = 0; while(k[nk].key) { n = snprintf(s, rem, "%s=%s", k[nk].key, k[nk].value); if (n == -1 || (size_t)n >= rem) fatalx("text_to_pdu"); rem -= n + 1; s += n + 1; nk++; } if (pdu_addbuf(p, buf, len, PDU_DATA)) return -1; return len; } struct kvp * pdu_to_text(char *buf, size_t len) { struct kvp *k; size_t n; char *eq; unsigned int nkvp = 0, i; /* remove padding zeros */ for (n = len; n > 0 && buf[n - 1] == '\0'; n--) ; if (n == len) { log_debug("pdu_to_text: badly terminated text data"); return NULL; } len = n + 1; for(n = 0; n < len; n++) if (buf[n] == '\0') nkvp++; if (!(k = calloc(nkvp + 1, sizeof(*k)))) return NULL; for (i = 0; i < nkvp; i++) { eq = strchr(buf, '='); if (!eq) { log_debug("pdu_to_text: badly encoded text data"); free(k); return NULL; } *eq++ = '\0'; k[i].key = buf; k[i].value = eq; buf = eq + strlen(eq) + 1; } return k; } /* Modified version of strtonum() to fit iscsid's need * * Copyright (c) 2004 Ted Unangst and Todd Miller * All rights reserved. * * 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. */ u_int64_t text_to_num(const char *numstr, u_int64_t minval, u_int64_t maxval, const char **errstrp) { unsigned long long ull = 0; char *ep; int error = 0; struct errval { const char *errstr; int err; } ev[4] = { { NULL, 0 }, { "invalid", EINVAL }, { "too small", ERANGE }, { "too large", ERANGE } }; #define INVALID 1 #define TOOSMALL 2 #define TOOLARGE 3 ev[0].err = errno; errno = 0; if (minval > maxval) error = INVALID; else { ull = strtoull(numstr, &ep, 0); if (numstr == ep || *ep != '\0') error = INVALID; else if (ull < minval) error = TOOSMALL; else if ((ull == ULLONG_MAX && errno == ERANGE) || ull > maxval) error = TOOLARGE; } if (errstrp != NULL) *errstrp = ev[error].errstr; errno = ev[error].err; if (error) ull = 0; return ull; #undef INVALID #undef TOOSMALL #undef TOOLARGE } int text_to_bool(const char *buf, const char **errstrp) { int val = 0; if (!strcmp(buf, "Yes")) { val = 1; errno = 0; } else if (!strcmp(buf, "No")) errno = 0; else errno = EINVAL; if (errstrp != NULL) { if (errno == 0) *errstrp = NULL; else *errstrp = "invalid"; } return val; } /* * Internal functions to send/recv pdus. */ void pdu_free_queue(struct pduq *channel) { struct pdu *p; while ((p = TAILQ_FIRST(channel))) { TAILQ_REMOVE(channel, p, entry); pdu_free(p); } } ssize_t pdu_read(struct connection *c) { struct iovec iov[2]; unsigned int niov = 1; ssize_t n; bzero(&iov, sizeof(iov)); iov[0].iov_base = c->prbuf.buf + c->prbuf.wpos; if (c->prbuf.wpos < c->prbuf.rpos) iov[0].iov_len = c->prbuf.rpos - c->prbuf.wpos; else { iov[0].iov_len = c->prbuf.size - c->prbuf.wpos; if (c->prbuf.rpos > 0) { niov++; iov[1].iov_base = c->prbuf.buf; iov[1].iov_len = c->prbuf.rpos - 1; } } if ((n = readv(c->fd, iov, niov)) == -1) return -1; if (n == 0) /* XXX what should we do on close with remaining data? */ return 0; c->prbuf.wpos += n; if (c->prbuf.wpos >= c->prbuf.size) c->prbuf.wpos -= c->prbuf.size; return n; } ssize_t pdu_write(struct connection *c) { struct iovec iov[PDU_WRIOV]; struct pdu *b, *nb; unsigned int niov = 0, j; size_t off, resid, size; ssize_t n; TAILQ_FOREACH(b, &c->pdu_w, entry) { if (niov >= PDU_WRIOV) break; off = b->resid; for (j = 0; j < PDU_MAXIOV && niov < PDU_WRIOV; j++) { if (!b->iov[j].iov_len) continue; if (off >= b->iov[j].iov_len) { off -= b->iov[j].iov_len; continue; } iov[niov].iov_base = (char *)b->iov[j].iov_base + off; iov[niov++].iov_len = b->iov[j].iov_len - off; off = 0; } } if ((n = writev(c->fd, iov, niov)) == -1) { if (errno == EAGAIN || errno == ENOBUFS || errno == EINTR) /* try later */ return 0; else { log_warn("pdu_write"); return -1; } } if (n == 0) return 0; size = n; for (b = TAILQ_FIRST(&c->pdu_w); b != NULL && size > 0; b = nb) { nb = TAILQ_NEXT(b, entry); resid = b->resid; for (j = 0; j < PDU_MAXIOV; j++) { if (resid >= b->iov[j].iov_len) resid -= b->iov[j].iov_len; else if (size >= b->iov[j].iov_len - resid) { size -= b->iov[j].iov_len - resid; b->resid += b->iov[j].iov_len - resid; resid = 0; } else { b->resid += size; size = 0; break; } } if (j == PDU_MAXIOV) { /* all written */ TAILQ_REMOVE(&c->pdu_w, b, entry); pdu_free(b); } } return n; } int pdu_pending(struct connection *c) { if (TAILQ_EMPTY(&c->pdu_w)) return 0; else return 1; } void pdu_parse(struct connection *c) { struct pdu *p; struct iscsi_pdu *ipdu; char *ahb, *db; size_t ahslen, dlen, off; ssize_t n; unsigned int j; /* XXX XXX I DON'T LIKE YOU. CAN I REWRITE YOU? */ do { if (!(p = c->prbuf.wip)) { /* get and parse base header */ if (pdu_readbuf_len(&c->prbuf) < sizeof(*ipdu)) return; if (!(p = pdu_new())) goto fail; if (!(ipdu = pdu_gethdr(p))) goto fail; c->prbuf.wip = p; /* * XXX maybe a pdu_readbuf_peek() would allow a better * error handling. */ pdu_readbuf_read(&c->prbuf, ipdu, sizeof(*ipdu)); ahslen = ipdu->ahslen * sizeof(u_int32_t); if (ahslen != 0) { if (!(ahb = pdu_alloc(ahslen)) || pdu_addbuf(p, ahb, ahslen, PDU_AHS)) goto fail; } dlen = ipdu->datalen[0] << 16 | ipdu->datalen[1] << 8 | ipdu->datalen[2]; if (dlen != 0) { if (!(db = pdu_alloc(dlen)) || pdu_addbuf(p, db, dlen, PDU_DATA)) goto fail; } p->resid = sizeof(*ipdu); } else { off = p->resid; for (j = 0; j < PDU_MAXIOV; j++) { if (off >= p->iov[j].iov_len) off -= p->iov[j].iov_len; else { n = pdu_readbuf_read(&c->prbuf, (char *)p->iov[j].iov_base + off, p->iov[j].iov_len - off); p->resid += n; if (n == 0 || off + n != p->iov[j].iov_len) return; } } p->resid = 0; /* reset resid so pdu can be reused */ task_pdu_cb(c, p); c->prbuf.wip = NULL; } } while (1); fail: fatalx("pdu_parse hit a space oddity"); } size_t pdu_readbuf_read(struct pdu_readbuf *rb, void *ptr, size_t len) { size_t l; if (rb->rpos == rb->wpos) { return 0; } else if (rb->rpos < rb->wpos) { l = PDU_MIN(rb->wpos - rb->rpos, len); memcpy(ptr, rb->buf + rb->rpos, l); rb->rpos += l; return l; } else { l = PDU_MIN(rb->size - rb->rpos, len); memcpy(ptr, rb->buf + rb->rpos, l); rb->rpos += l; if (rb->rpos == rb->size) rb->rpos = 0; if (l < len) return l + pdu_readbuf_read(rb, (char *)ptr + l, len - l); return l; } } size_t pdu_readbuf_len(struct pdu_readbuf *rb) { if (rb->rpos <= rb->wpos) return rb->wpos - rb->rpos; else return rb->size - (rb->rpos - rb->wpos); } int pdu_readbuf_set(struct pdu_readbuf *rb, size_t bsize) { char *nb; if (bsize < rb->size) /* can't shrink */ return 0; if ((nb = realloc(rb->buf, bsize)) == NULL) { free(rb->buf); return -1; } rb->buf = nb; rb->size = bsize; return 0; } void pdu_readbuf_free(struct pdu_readbuf *rb) { free(rb->buf); }