/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <signal.h> #include <locale.h> #include <syslog.h> #include <netdb.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <errno.h> #include <door.h> #include <meta.h> #include <libsysevent.h> #include <wait.h> #include <semaphore.h> #include <libscf.h> #include <sys/scsi/adapters/iscsi_door.h> #include <sys/scsi/adapters/iscsi_if.h> /* * Local Defines * ------------- */ #define ISCSI_DOOR_DAEMON_SYSLOG_PP "iscsid" #define ISCSI_DISCOVERY_POLL_DELAY1 1 /* Seconds */ #define ISCSI_DISCOVERY_POLL_DELAY2 60 /* Seconds */ #define ISCSI_SMF_OFFLINE_DELAY 10 /* Seconds */ #define ISCSI_SMF_OFFLINE_MAX_RETRY_TIMES 60 #if !defined(SMF_EXIT_ERR_OTHER) #define SMF_EXIT_ERR_OTHER -1 #endif /* * Global Variables related to the synchronization of the child process * -------------------------------------------------------------------- */ static pid_t iscsi_child_pid; static sem_t iscsi_child_sem; static int iscsi_child_door_handle; static int iscsi_child_smf_exit_code; /* * Global Variables related to the door accessed by the kernel * ----------------------------------------------------------- */ static int iscsi_dev_handle; static int iscsi_kernel_door_handle; /* * Prototypes of Functions the body of which is defined farther down * in this file. * ----------------------------------------------------------------- */ static void call_child_door(int value); static void sigchld_handler(int sig); static boolean_t discovery_event_wait(int did); static void signone(int, siginfo_t *, void *); static void iscsi_child_door( void *cookie, char *args, size_t alen, door_desc_t *ddp, uint_t ndid ); static void iscsi_kernel_door( void *cookie, char *args, size_t alen, door_desc_t *ddp, uint_t ndid ); static iscsi_door_cnf_t * _getipnodebyname_req( getipnodebyname_req_t *req, int req_len, size_t *pcnf_len ); /* * main -- Entry point of the iSCSI door server daemon * * This function forks, waits for the child process feedback and exits. */ /* ARGSUSED */ int main( int argc, char *argv[] ) { int i; int sig; int ret = -1; int retry = 0; sigset_t sigs, allsigs; struct sigaction act; uint32_t rval; /* * Get the locale set up before calling any other routines * with messages to ouput. */ (void) setlocale(LC_ALL, ""); openlog("ISCSI_DOOR_DAEMON_SYSLOG_PP", LOG_PID, LOG_DAEMON); /* The child semaphore is created. */ if (sem_init(&iscsi_child_sem, 0, 0) == -1) { exit(SMF_EXIT_ERR_OTHER); } /* The door for the child is created. */ iscsi_child_door_handle = door_create(iscsi_child_door, NULL, 0); if (iscsi_child_door_handle == -1) { (void) sem_destroy(&iscsi_child_sem); exit(SMF_EXIT_ERR_OTHER); } /* A signal handler is set for SIGCHLD. */ (void) signal(SIGCHLD, sigchld_handler); /* * Here begins the daemonizing code * -------------------------------- */ iscsi_child_pid = fork(); if (iscsi_child_pid < 0) { /* The fork failed. */ syslog(LOG_DAEMON | LOG_ERR, gettext("Cannot fork")); (void) sem_destroy(&iscsi_child_sem); exit(SMF_EXIT_ERR_OTHER); } if (iscsi_child_pid) { /* * The parent exits after the child has provided feedback. This * waiting phase is to meet one of greenline's requirements. * We shouldn't return till we are sure the service is ready to * be provided. */ (void) sem_wait(&iscsi_child_sem); (void) sem_destroy(&iscsi_child_sem); exit(iscsi_child_smf_exit_code); } /* * stdout and stderr are redirected to "/dev/null". */ i = open("/dev/null", O_RDWR); (void) dup2(i, 1); (void) dup2(i, 2); /* * Here ends the daemonizing code * ------------------------------ */ /* * Block out all signals */ (void) sigfillset(&allsigs); (void) pthread_sigmask(SIG_BLOCK, &allsigs, NULL); /* setup the door handle */ iscsi_kernel_door_handle = door_create(iscsi_kernel_door, NULL, 0); if (iscsi_kernel_door_handle == -1) { perror(gettext("door_create failed")); syslog(LOG_DAEMON | LOG_ERR, gettext("door_create failed")); exit(SMF_EXIT_ERR_OTHER); } /* * The iSCSI driver is opened. */ iscsi_dev_handle = open(ISCSI_DRIVER_DEVCTL, O_RDWR); if (iscsi_dev_handle == -1) { /* The driver couldn't be opened. */ perror(gettext("iscsi device open failed")); exit(SMF_EXIT_ERR_OTHER); } if (ioctl( iscsi_dev_handle, ISCSI_SMF_ONLINE, &iscsi_kernel_door_handle) == -1) { (void) close(iscsi_dev_handle); perror(gettext("ioctl: enable iscsi initiator")); exit(SMF_EXIT_ERR_OTHER); } /* * Keep the dev open, so to keep iscsi module from unloaded. * This is crutial to guarantee the consistency of the * door_handle and service state in kernel. */ /* We have to wait for the discovery process to finish. */ (void) discovery_event_wait(iscsi_dev_handle); /* We let the parent know that everything is ok. */ call_child_door(SMF_EXIT_OK); /* now set up signals we care about */ (void) sigemptyset(&sigs); (void) sigaddset(&sigs, SIGTERM); (void) sigaddset(&sigs, SIGINT); (void) sigaddset(&sigs, SIGQUIT); /* make sure signals to be enqueued */ act.sa_flags = SA_SIGINFO; act.sa_sigaction = signone; (void) sigaction(SIGTERM, &act, NULL); (void) sigaction(SIGINT, &act, NULL); (void) sigaction(SIGQUIT, &act, NULL); /* wait and process signals */ for (;;) { sig = sigwait(&sigs); if (sig < 0) continue; switch (sig) { case SIGQUIT: case SIGINT: case SIGTERM: do { ret = ioctl(iscsi_dev_handle, ISCSI_SMF_OFFLINE, &rval); if (ret == -1) { /* * Keep retrying if unable * to stop */ (void) sleep(ISCSI_SMF_OFFLINE_DELAY); retry++; } } while ((ret == -1) && (retry < ISCSI_SMF_OFFLINE_MAX_RETRY_TIMES)); (void) close(iscsi_dev_handle); if (rval == B_FALSE) { syslog(LOG_DAEMON, gettext("iSCSI initiator" " service exited with sessions left.")); } return (0); break; default: break; } } } /* * sigchld_handler -- SIGCHLD Handler * */ /* ARGSUSED */ static void sigchld_handler( int sig ) { int status; pid_t ret_pid; /* This is the default code. */ iscsi_child_smf_exit_code = SMF_EXIT_ERR_OTHER; ret_pid = waitpid(iscsi_child_pid, &status, WNOHANG); if (ret_pid == iscsi_child_pid) { if (WIFEXITED(status)) { iscsi_child_smf_exit_code = WEXITSTATUS(status); } } (void) sem_post(&iscsi_child_sem); } /* * iscsi_child_door -- Child process door entry point * * This function is executed when a driver calls door_ki_upcall(). */ /* ARGSUSED */ static void iscsi_child_door( void *cookie, char *args, size_t alen, door_desc_t *ddp, uint_t ndid ) { int *ptr = (int *)args; iscsi_child_smf_exit_code = SMF_EXIT_ERR_OTHER; if (alen >= sizeof (iscsi_child_smf_exit_code)) { iscsi_child_smf_exit_code = *ptr; } (void) sem_post(&iscsi_child_sem); (void) door_return(NULL, 0, NULL, 0); } /* * iscsi_kernel_door -- Kernel door entry point * * This function is executed when a driver calls door_ki_upcall(). */ /* ARGSUSED */ static void iscsi_kernel_door( void *cookie, char *args, size_t alen, door_desc_t *ddp, uint_t ndid ) { iscsi_door_msg_hdr_t err_ind; iscsi_door_req_t *req; iscsi_door_cnf_t *cnf; size_t cnf_len; char *err_txt; int err_code; /* Local variables pre-initialization */ err_ind.signature = ISCSI_DOOR_REQ_SIGNATURE; err_ind.version = ISCSI_DOOR_REQ_VERSION_1; err_ind.opcode = ISCSI_DOOR_ERROR_IND; req = (iscsi_door_req_t *)args; cnf = (iscsi_door_cnf_t *)&err_ind; cnf_len = sizeof (err_ind); /* * The validity of the request is checked before going any farther. */ if (req == NULL) { /* * A request has to be passed. */ err_ind.status = ISCSI_DOOR_STATUS_REQ_INVALID; } else if (alen < sizeof (iscsi_door_msg_hdr_t)) { /* * The buffer containing the request must be at least as big * as message header. */ err_ind.status = ISCSI_DOOR_STATUS_REQ_LENGTH; } else if (req->hdr.signature != ISCSI_DOOR_REQ_SIGNATURE) { /* * The request must be correctly signed. */ err_ind.status = ISCSI_DOOR_STATUS_REQ_INVALID; } else if (req->hdr.version != ISCSI_DOOR_REQ_VERSION_1) { /* * The version of the request must be supported by the server. */ err_ind.status = ISCSI_DOOR_STATUS_REQ_VERSION; } else { /* * The request is treated according to the opcode. */ switch (req->hdr.opcode) { case ISCSI_DOOR_GETIPNODEBYNAME_REQ: cnf = _getipnodebyname_req( &req->ginbn_req, alen, &cnf_len); break; default: err_ind.status = ISCSI_DOOR_STATUS_REQ_INVALID; break; } } err_code = door_return((char *)cnf, cnf_len, NULL, 0); switch (err_code) { case E2BIG: err_txt = "E2BIG"; break; case EFAULT: err_txt = "EFAULT"; break; case EINVAL: err_txt = "EINVAL"; break; case EMFILE: err_txt = "EMFILE"; break; default: err_txt = "?"; break; } (void) fprintf(stderr, "door_return error(%s,%d)", err_txt, err_code); syslog( LOG_DAEMON | LOG_ERR, gettext("!door_return error(%s,%d)"), err_txt, err_code); } /* * _getipnodebyname_req * * This function executes the request ISCSI_DOOR_GETIPNODEBYNAME_REQ. It * calls getipnodebyname() but doesn't return all the information. The * confirmation structure only contains one IP address of the list returned * by getipnodebyname(). */ static iscsi_door_cnf_t * _getipnodebyname_req( getipnodebyname_req_t *req, int req_len, size_t *pcnf_len ) { getipnodebyname_cnf_t *cnf = (getipnodebyname_cnf_t *)req; size_t cnf_len; struct hostent *hptr; char *name; /* The opcode is changed immediately. */ cnf->hdr.opcode = ISCSI_DOOR_GETIPNODEBYNAME_CNF; /* The size of the request is checked against the minimum required. */ if (req_len < sizeof (getipnodebyname_cnf_t)) { cnf->hdr.status = ISCSI_DOOR_STATUS_REQ_FORMAT; *pcnf_len = req_len; return ((iscsi_door_cnf_t *)cnf); } name = (char *)req + req->name_offset; /* * The pointer to the name has to stay inside the request but * after the header. */ if ((name < ((char *)req + sizeof (getipnodebyname_req_t))) || ((name + req->name_length) > ((char *)req + req_len))) { cnf->hdr.status = ISCSI_DOOR_STATUS_REQ_FORMAT; *pcnf_len = req_len; return ((iscsi_door_cnf_t *)cnf); } /* The library function is called. */ hptr = getipnodebyname( name, (int)req->af, (int)req->flags, (int *)&cnf->error_num); if (hptr) { /* * The call was successful. Now starts the painful work of * parsing the data. However, for version 1 we will only * return the first address. */ cnf_len = sizeof (getipnodebyname_cnf_t); cnf->h_size_needed = sizeof (getipnodebyname_cnf_t); cnf->h_alias_list_length = 0; cnf->h_alias_list_offset = 0; cnf->h_name_len = 0; cnf->h_name_offset = 0; cnf->h_addrlen = (uint32_t)hptr->h_length; cnf->h_addrtype = (uint32_t)hptr->h_addrtype; cnf->h_addr_list_offset = sizeof (getipnodebyname_cnf_t); if (*hptr->h_addr_list != NULL) { (void) memcpy( ((char *)cnf + sizeof (getipnodebyname_cnf_t)), *hptr->h_addr_list, hptr->h_length); cnf->h_addr_list_length = 1; cnf->h_size_needed += cnf->h_addrlen; cnf_len += hptr->h_length; } else { cnf->h_addr_list_length = 0; cnf->h_size_needed += hptr->h_length; } *pcnf_len = cnf_len; cnf->hdr.status = ISCSI_DOOR_STATUS_SUCCESS; freehostent(hptr); } else { cnf->hdr.status = ISCSI_DOOR_STATUS_SUCCESS; cnf->h_addrlen = 0; cnf->h_addrtype = 0; cnf->h_addr_list_offset = sizeof (getipnodebyname_cnf_t); cnf->h_addr_list_length = 0; cnf->h_name_offset = sizeof (getipnodebyname_cnf_t); cnf->h_name_len = 0; cnf->h_alias_list_offset = sizeof (getipnodebyname_cnf_t); cnf->h_alias_list_length = 0; cnf->h_size_needed = sizeof (getipnodebyname_cnf_t); *pcnf_len = sizeof (getipnodebyname_cnf_t); } return ((iscsi_door_cnf_t *)cnf); } /* * call_child_door -- This function calls the child door with the value * provided by the caller. * */ static void call_child_door( int value ) { door_arg_t door_arg; (void) memset(&door_arg, 0, sizeof (door_arg)); door_arg.data_ptr = (char *)&value; door_arg.data_size = sizeof (value); (void) door_call(iscsi_child_door_handle, &door_arg); } /* * get_luns_count -- */ static uint32_t get_luns_count( int did ) { iscsi_lun_list_t *lun_list; iscsi_lun_list_t *tmp; size_t len; uint32_t lun_count; lun_list = (iscsi_lun_list_t *)malloc(sizeof (*lun_list)); (void) memset(lun_list, 0, sizeof (*lun_list)); lun_list->ll_vers = ISCSI_INTERFACE_VERSION; lun_list->ll_in_cnt = 1; lun_list->ll_all_tgts = B_TRUE; for (;;) { if (ioctl( did, ISCSI_LUN_OID_LIST_GET, lun_list) == -1) { free(lun_list); /* The Ioctl didn't go well. */ return (0); } if (lun_list->ll_in_cnt >= lun_list->ll_out_cnt) { /* We got it all. */ break; } /* * We didn't get all the targets. Let's build a new Ioctl with * a new size. */ tmp = lun_list; len = tmp->ll_out_cnt * sizeof (tmp->ll_luns); len += sizeof (*tmp) - sizeof (tmp->ll_luns); lun_list = (iscsi_lun_list_t *)malloc(len); if (lun_list == NULL) { /* No resources. */ free(tmp); return (0); } (void) memset(lun_list, 0, len); lun_list->ll_vers = ISCSI_INTERFACE_VERSION; lun_list->ll_in_cnt = tmp->ll_out_cnt; lun_list->ll_all_tgts = B_TRUE; free(tmp); } lun_count = lun_list->ll_out_cnt; free(lun_list); return (lun_count); } /* * discovery_event_wait -- Waits for the discovery process to finish. * */ static boolean_t discovery_event_wait( int did ) { boolean_t rc; uint32_t lun_count; uint32_t lun_timer; uint32_t tmp; iSCSIDiscoveryMethod_t discovery_flags; iSCSIDiscoveryMethod_t discovery_all; rc = B_FALSE; lun_count = 0; lun_timer = 0; discovery_flags = 0; discovery_all = iSCSIDiscoveryMethodStatic | iSCSIDiscoveryMethodSLP | iSCSIDiscoveryMethodISNS | iSCSIDiscoveryMethodSendTargets; for (;;) { /* The status discovery flags are read. */ if (ioctl( did, ISCSI_DISCOVERY_EVENTS, &discovery_flags) == -1) { /* IO problem */ break; } if (discovery_flags == discovery_all) { /* Discovery over */ rc = B_TRUE; break; } if (lun_timer >= ISCSI_DISCOVERY_POLL_DELAY2) { /* Let's check if the driver is making progress. */ tmp = get_luns_count(did); if (tmp <= lun_count) { /* No progress */ break; } lun_count = tmp; lun_timer = 0; } (void) sleep(ISCSI_DISCOVERY_POLL_DELAY1); lun_timer += ISCSI_DISCOVERY_POLL_DELAY1; } return (rc); } /*ARGSUSED*/ static void signone(int sig, siginfo_t *sip, void *utp) { }