iscsid.c revision fcf3ce441efd61da9bb2884968af01cb7c1452cc
/*
* 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 2008 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 <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 */
#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
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;
/*
* 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 the usual signals so we don't get killed unintentionally.
*/
(void) signal(SIGHUP, SIG_IGN);
(void) signal(SIGINT, SIG_IGN);
(void) signal(SIGQUIT, SIG_IGN);
/* 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_DOOR_HANDLE_SET,
&iscsi_kernel_door_handle) == -1) {
(void) close(iscsi_dev_handle);
perror(gettext("ioctl: set door handle"));
exit(SMF_EXIT_ERR_OTHER);
}
/* We have to wait for the discovery process to finish. */
(void) discovery_event_wait(iscsi_dev_handle);
/* We don't need to keep the device opened. */
(void) close(iscsi_dev_handle);
/* We let know the parent that everything is ok. */
call_child_door(SMF_EXIT_OK);
for (;;) {
(void) pause();
}
}
/*
* 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);
}