#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <err.h>
#include <event.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "iscsid.h"
#include "log.h"
void main_sig_handler(int, short, void *);
__dead void usage(void);
void shutdown_cb(int, short, void *);
struct event exit_ev;
int exit_rounds;
#define ISCSI_EXIT_WAIT 5
const struct session_params iscsi_sess_defaults = {
.MaxBurstLength = 262144,
.FirstBurstLength = 65536,
.DefaultTime2Wait = 2,
.DefaultTime2Retain = 20,
.MaxOutstandingR2T = 1,
.MaxConnections = 1,
.InitialR2T = 1,
.ImmediateData = 1,
.DataPDUInOrder = 1,
.DataSequenceInOrder = 1,
.ErrorRecoveryLevel = 0,
};
const struct connection_params iscsi_conn_defaults = {
.MaxRecvDataSegmentLength = 8192,
.HeaderDigest = DIGEST_NONE,
.DataDigest = DIGEST_NONE,
};
int
main(int argc, char *argv[])
{
struct event ev_sigint, ev_sigterm, ev_sighup;
struct passwd *pw;
char *ctrlsock = ISCSID_CONTROL;
char *vscsidev = ISCSID_DEVICE;
int name[] = { CTL_KERN, KERN_PROC_NOBROADCASTKILL, 0 };
int ch, debug = 0, verbose = 0, nobkill = 1;
log_procname = getprogname();
log_init(1);
log_verbose(1);
while ((ch = getopt(argc, argv, "dn:s:v")) != -1) {
switch (ch) {
case 'd':
debug = 1;
break;
case 'n':
vscsidev = optarg;
break;
case 's':
ctrlsock = optarg;
break;
case 'v':
verbose = 1;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc > 0)
usage();
if (geteuid())
errx(1, "need root privileges");
log_init(debug);
log_verbose(verbose);
if (control_init(ctrlsock) == -1)
fatalx("control socket setup failed");
if (!debug)
daemon(1, 0);
log_info("startup");
name[2] = getpid();
if (sysctl(name, 3, NULL, 0, &nobkill, sizeof(nobkill)) != 0)
fatal("sysctl");
event_init();
vscsi_open(vscsidev);
if ((pw = getpwnam(ISCSID_USER)) == NULL)
errx(1, "unknown user %s", ISCSID_USER);
if (chroot(pw->pw_dir) == -1)
fatal("chroot");
if (chdir("/") == -1)
fatal("chdir(\"/\")");
if (setgroups(1, &pw->pw_gid) ||
setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
fatal("can't drop privileges");
signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
signal_add(&ev_sigint, NULL);
signal_add(&ev_sigterm, NULL);
signal_add(&ev_sighup, NULL);
signal(SIGPIPE, SIG_IGN);
control_event_init();
initiator_init();
event_dispatch();
control_cleanup(ctrlsock);
initiator_cleanup();
log_info("exiting.");
return 0;
}
void
shutdown_cb(int fd, short event, void *arg)
{
struct timeval tv;
if (exit_rounds++ >= ISCSI_EXIT_WAIT || initiator_isdown())
event_loopexit(NULL);
timerclear(&tv);
tv.tv_sec = 1;
if (evtimer_add(&exit_ev, &tv) == -1)
fatal("shutdown_cb");
}
void
main_sig_handler(int sig, short event, void *arg)
{
struct timeval tv;
switch (sig) {
case SIGTERM:
case SIGINT:
case SIGHUP:
initiator_shutdown();
evtimer_set(&exit_ev, shutdown_cb, NULL);
timerclear(&tv);
if (evtimer_add(&exit_ev, &tv) == -1)
fatal("main_sig_handler");
break;
default:
fatalx("unexpected signal");
}
}
__dead void
usage(void)
{
extern char *__progname;
fprintf(stderr, "usage: %s [-dv] [-n device] [-s socket]\n",
__progname);
exit(1);
}
void
iscsid_ctrl_dispatch(void *ch, struct pdu *pdu)
{
struct ctrlmsghdr *cmh;
struct initiator_config *ic;
struct session_head *sh;
struct session_config *sc;
struct session *s;
struct session_poll p = { 0 };
int *valp;
cmh = pdu_getbuf(pdu, NULL, 0);
if (cmh == NULL)
goto done;
switch (cmh->type) {
case CTRL_INITIATOR_CONFIG:
if (cmh->len[0] != sizeof(*ic)) {
log_warnx("CTRL_INITIATOR_CONFIG bad size");
control_compose(ch, CTRL_FAILURE, NULL, 0);
break;
}
ic = pdu_getbuf(pdu, NULL, 1);
initiator_set_config(ic);
control_compose(ch, CTRL_SUCCESS, NULL, 0);
break;
case CTRL_SESSION_CONFIG:
if (cmh->len[0] != sizeof(*sc)) {
log_warnx("CTRL_SESSION_CONFIG bad size");
control_compose(ch, CTRL_FAILURE, NULL, 0);
break;
}
sc = pdu_getbuf(pdu, NULL, 1);
if (cmh->len[1])
sc->TargetName = pdu_getbuf(pdu, NULL, 2);
else if (sc->SessionType != SESSION_TYPE_DISCOVERY) {
control_compose(ch, CTRL_FAILURE, NULL, 0);
goto done;
} else
sc->TargetName = NULL;
if (cmh->len[2])
sc->InitiatorName = pdu_getbuf(pdu, NULL, 3);
else
sc->InitiatorName = NULL;
s = initiator_find_session(sc->SessionName);
if (s == NULL) {
s = initiator_new_session(sc->SessionType);
if (s == NULL) {
control_compose(ch, CTRL_FAILURE, NULL, 0);
goto done;
}
}
session_config(s, sc);
if (s->state == SESS_INIT)
session_fsm(&s->sev, SESS_EV_START, 0);
control_compose(ch, CTRL_SUCCESS, NULL, 0);
break;
case CTRL_LOG_VERBOSE:
if (cmh->len[0] != sizeof(int)) {
log_warnx("CTRL_LOG_VERBOSE bad size");
control_compose(ch, CTRL_FAILURE, NULL, 0);
break;
}
valp = pdu_getbuf(pdu, NULL, 1);
log_verbose(*valp);
control_compose(ch, CTRL_SUCCESS, NULL, 0);
break;
case CTRL_VSCSI_STATS:
control_compose(ch, CTRL_VSCSI_STATS, vscsi_stats(),
sizeof(struct vscsi_stats));
break;
case CTRL_SHOW_SUM:
ic = initiator_get_config();
control_compose(ch, CTRL_INITIATOR_CONFIG, ic, sizeof(*ic));
sh = initiator_get_sessions();
TAILQ_FOREACH(s, sh, entry) {
struct ctrldata cdv[3];
bzero(cdv, sizeof(cdv));
cdv[0].buf = &s->config;
cdv[0].len = sizeof(s->config);
if (s->config.TargetName) {
cdv[1].buf = s->config.TargetName;
cdv[1].len =
strlen(s->config.TargetName) + 1;
}
if (s->config.InitiatorName) {
cdv[2].buf = s->config.InitiatorName;
cdv[2].len =
strlen(s->config.InitiatorName) + 1;
}
control_build(ch, CTRL_SESSION_CONFIG,
nitems(cdv), cdv);
}
control_compose(ch, CTRL_SUCCESS, NULL, 0);
break;
case CTRL_SESS_POLL:
sh = initiator_get_sessions();
TAILQ_FOREACH(s, sh, entry)
poll_session(&p, s);
poll_finalize(&p);
control_compose(ch, CTRL_SESS_POLL, &p, sizeof(p));
break;
default:
log_warnx("unknown control message type %d", cmh->type);
control_compose(ch, CTRL_FAILURE, NULL, 0);
break;
}
done:
pdu_free(pdu);
}
#define MERGE_MIN(r, a, b, v) \
r->v = (a->v < b->v ? a->v : b->v)
#define MERGE_MAX(r, a, b, v) \
r->v = (a->v > b->v ? a->v : b->v)
#define MERGE_OR(r, a, b, v) \
r->v = (a->v || b->v)
#define MERGE_AND(r, a, b, v) \
r->v = (a->v && b->v)
void
iscsi_merge_sess_params(struct session_params *res,
struct session_params *mine, struct session_params *his)
{
memset(res, 0, sizeof(*res));
MERGE_MIN(res, mine, his, MaxBurstLength);
MERGE_MIN(res, mine, his, FirstBurstLength);
MERGE_MAX(res, mine, his, DefaultTime2Wait);
MERGE_MIN(res, mine, his, DefaultTime2Retain);
MERGE_MIN(res, mine, his, MaxOutstandingR2T);
res->TargetPortalGroupTag = his->TargetPortalGroupTag;
MERGE_MIN(res, mine, his, MaxConnections);
MERGE_OR(res, mine, his, InitialR2T);
MERGE_AND(res, mine, his, ImmediateData);
MERGE_OR(res, mine, his, DataPDUInOrder);
MERGE_OR(res, mine, his, DataSequenceInOrder);
MERGE_MIN(res, mine, his, ErrorRecoveryLevel);
}
void
iscsi_merge_conn_params(struct connection_params *res,
struct connection_params *mine, struct connection_params *his)
{
int mask;
memset(res, 0, sizeof(*res));
res->MaxRecvDataSegmentLength = his->MaxRecvDataSegmentLength;
mask = mine->HeaderDigest & his->HeaderDigest;
mask = ffs(mask) - 1;
if (mask == -1)
res->HeaderDigest = 0;
else
res->HeaderDigest = 1 << mask;
mask = mine->DataDigest & his->DataDigest;
mask = ffs(mask) - 1;
if (mask == -1)
res->DataDigest = 0;
else
res->DataDigest = 1 << mask;
}
#undef MERGE_MIN
#undef MERGE_MAX
#undef MERGE_OR
#undef MERGE_AND