main.c revision a9fd9a9e12bea66c9ea9b31f4b6f0ef584933f59
/*
* 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
* 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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#define FD_SETSIZE 65536
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <stdint.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <door.h>
#include <signal.h>
#include <siginfo.h>
#include <sys/ethernet.h>
#include <libscf.h>
#include <syslog.h>
#include <synch.h>
#include <libxml/xmlreader.h>
#include <sys/resource.h>
#include <syslog.h>
#include <iscsitgt_impl.h>
#include <umem.h>
#include "queue.h"
#include "port.h"
#include "iscsi_conn.h"
#include "target.h"
#include "utility.h"
#include "iscsi_ffp.h"
#include "errcode.h"
#include "t10.h"
#include "mgmt_scf.h"
#include "isns_client.h"
#define EMPTY_CONFIG "<config version='1.0'>\n</config>\n"
/* ---- Forward declarations ---- */
ucred_t *);
/* ---- Global configuration data. ---- */
char *target_basedir = NULL;
char *target_log = DEFAULT_TARGET_LOG;
char *config_file = DEFAULT_CONFIG_LOCATION;
dbg_lvl = 0,
pgr_persist = True;
int targets_vers_maj,
typedef struct var_table {
char *v_name;
int *v_value;
} var_table_t;
typedef struct cmd_table {
char *c_name;
ucred_t *);
} cmd_table_t;
admin_table_t admin_prop_list[] = {
{XML_ELEMENT_CHAPSECRET, 0},
{XML_ELEMENT_CHAPNAME, 0},
{XML_ELEMENT_RAD_ACCESS, 0},
{XML_ELEMENT_RAD_SECRET, 0},
{XML_ELEMENT_ISNS_ACCESS, 0},
{XML_ELEMENT_FAST, 0},
{0, 0}
};
/*
* Global variables which can be set via the management XML interface
* with the syntax of "<variable><dbg_lvl>0x033</dbg_lvl></variable>"
*/
var_table_t var_table[] = {
{ "dbg_lvl", &dbg_lvl },
{ "qlog_lvl", &qlog_lvl },
/* ---- End of Table marker ---- */
{ NULL, 0 }
};
/*
* Commands which are run via the management XML interface
*/
cmd_table_t cmd_table[] = {
{ "variable", variable_handler },
{ "create", create_func },
{ "modify", modify_func },
{ "delete", remove_func },
{ "list", list_func },
/* ---- End of Table marker ---- */
};
/*
* []----
* | process_config -- parse the main configuration file
* |
* | Everything in the configuratin file is optional. That's because
* | the management CLI can set the value to everything and update
* | the configuration.
* []----
*/
static Boolean_t
{
#ifndef lint
#endif
return (False);
}
False) {
return (False);
}
/*
* The base directory is optional in the sense that the daemon
* can start without it, but the daemon can't really do
* anything until the administrator sets the value.
*/
/*
* These are optional settings for the target. Each of
* these has a default value which can be overwritten in
* the configuration.
*/
&target_log);
&iscsi_port);
&disable_tpgs);
&pgr_persist);
main_config = node;
return (True);
}
/*
* []----
* | logout_cleanup -- see if the initiator did what was requested
* |
* | When a target issues an asynchrouns event with the code set to
* | "logout requested" the initiator is supposed to respond with
* | a LogoutRequested PDU within a certain amount of time. If it
* | fails to do so, it's the targets responsibility to clean up.
* | After ASYNC_LOGOUT_TIMEOUT seconds (currently 10) we look to
* | see if any connections are still in the S7_LOGOUT_REQUESTED
* | state. If so, reissue the management request to logout which
* | will cause the connections to close.
* []----
*/
static void *
logout_cleanup(void *v)
{
int msg_sent, i;
char *targ = (char *)v;
extern pthread_mutex_t port_mutex;
bzero(&m, sizeof (m));
m.m_request = mgmt_logout;
m.m_q = queue_alloc();
msg_sent = 0;
(void) sleep(ASYNC_LOGOUT_TIMEOUT);
(void) pthread_mutex_lock(&port_mutex);
msg_mgmt_rqst, &m);
msg_sent++;
}
}
(void) pthread_mutex_unlock(&port_mutex);
/*
* Wait to see if they received the message.
*/
for (i = 0; i < msg_sent; i++)
(void *)(uintptr_t)pthread_self());
return ((void *)0);
}
void
logout_targ(char *targ)
{
int i, msg_sent;
extern pthread_mutex_t port_mutex;
/*
* Now we look for connections to this target and issue
* a request to asynchronously logout.
*/
bzero(&m, sizeof (m));
m.m_request = mgmt_logout;
m.m_q = queue_alloc();
msg_sent = 0;
(void) pthread_mutex_lock(&port_mutex);
msg_sent++;
}
}
(void) pthread_mutex_unlock(&port_mutex);
/* ---- Wait to see if they received the message. ---- */
for (i = 0; i < msg_sent; i++)
/* ---- Start housecleaning thread ---- */
}
/*
* [] ---- XML Management Handlers ---- []
*/
/*
* []----
* | variable_handler -- used to set a couple of internal global variables
* []----
*/
/*ARGSUSED*/
void
{
var_table_t *v;
tgt_node_t *c;
return;
}
break;
}
}
}
}
/*
* []----
* | parse_xml -- incoming management requests are sent here for processing
* []----
*/
static void
{
cmd_table_t *c;
return;
}
break;
} else {
}
}
/*
* space_message -- create a message indicating the amount of space needed.
*
* This code could have been inline in the server_for_door function, but
* at system startup we'll determine the minimum amount of space needed for
* a door call return pointer. This minimum amount of space will be either
* a "need more space" or a "success" message. So, have this code as a function
* reduces any duplication.
*/
static void
{
char lbuf[16];
}
/*ARGSUSED*/
static void
{
/*
* A well written application will always give us enough space
* to send either a "success" message or a "more space needed".
* If the minimum amount of space isn't given then just return
* with a NULL pointer.
*/
if (arg_size < door_min_space) {
return;
}
/*
* Pick up the user credentials of the client application. Used to
* validate that the effective user ID is either root or the process
* has the privilege to complete the operation.
*/
if (door_ucred(&uc) != 0) {
return;
}
bzero(&m, sizeof (m));
while (xmlTextReaderRead(r)) {
break;
}
m.m_q = queue_alloc();
m.m_request = mgmt_parse_xml;
m.m_targ_name = NULL;
ucred_free(uc);
}
/*
* Check to see if the response can fit into the
* incoming argument buffer. If so, copy the response
* to that buffer so that we can free the data.
* If it's not big enough we'll return the pointer to
* the message response and have to free the data
* at another time.
*/
} else {
/*
* err_rply will be copied to argp at the end
*/
}
} else {
}
} else {
}
}
ucred_free(uc);
}
/*
* []----
* | setup_door -- Create a door portal for management requests
* |
* | First check to see if another daemon is already running by attempting
* | to send an empty request to the door. If successful it means this
* | daemon should exit.
* []----
*/
static void
{
struct stat s;
door_arg_t d;
/*
* Figure out what the minimum amount of space required to send back
* either a success message or a need more space one.
*
* Commands fall into one of three categories.
* (1) Idempotent commands, like list operations, which can
* return large ammounts of data. If there's not enough room
* the client is informed and the command is run again with a
* larger buffer.
* (2) Create, modify, or delete operations that fail. These
* commands may return an error message which is larger than
* the incoming request buffer. If so, the client is informed
* that a larger buffer is needed and the command is rerun. This
* time it'll recieve the full error message. So, these commands
* are idempotent because no state changes occur on failure.
* (3) Create, modify, or delete operations which are successful.
* Since successful completion means state has change the daemon
* must always be able to report success, other wise a second
* attempt with a larger buffer would then fail, where the first
* one actually succeeded.
*/
/*
* Use an impossibly big number which will create the longest string
*/
door_min_space++; /* add 1 for the NULL byte */
/*
* DOOR_MIN_SPACE will be the amount used by the library as the default.
* Currently this will be set to 128 (check iscsitgt_impl.h for value).
* Since this will be a compiled value and the "success" or "more space"
* messages could grown we need to detect if there will be a problem.
* By failing here consistently, SMF will put the daemon in maintenance
* state and this will be caught during testing. Otherwise the library
* would receive a NULL back, but not know how much space is really
* required.
*/
if (door_min_space > DOOR_MIN_SPACE) {
"Calculated min space (%d) is larger than default (%d)",
assert(0);
}
/*
* There's at least a file with the same name as our
* door. Let's see if someone is currently answering
* by sending an empty XML request.
*/
d.data_ptr = "<config></config>";
d.desc_num = 0;
d.rsize = 0;
/*
* If the door_call succeeds that means another
* daemon is already running so let's just exit.
*/
exit(0);
}
}
exit(1);
}
int newfd;
exit(1);
}
}
/*
* Open the door for general access. As the calls come in we'll
* get the credentials and validate within each operation as to the
* required privileges.
*/
exit(2);
}
}
/*ARGSUSED*/
void
{
}
int
{
char c, *p, *door_name;
target_queue_t *q;
void *thr_status;
switch (c) {
case 'c':
break;
case 'd':
break;
}
}
/*
* If the initiator closes the socket because of a protocol error
* or bad digest on the header packet we'll receive a SIGPIPE if we're
* in the middle of a write operation. There's no need to receive
* a signal when a -1 from the write will handle things correctly.
* So, ignore SIGPIPE's.
*/
/*
* Setup memory caches
*/
NULL) {
perror("cache create");
}
perror("cache create");
}
perror("cache create");
}
/*
* Look at the function lu_buserr_handler() in t10_sam.c to see the
* details of why we need to handle segmentation violations.
*/
perror("sigaction");
}
if (mgmt_convert_conf() == CONVERT_FAIL)
if (process_config() == False)
/*
* During the normal corse of events 'target_basedir' will be
* free when the administrator changes the base directory. Instead
* of trying to check for the initial case of this value being set
* to some static string, always allocate that string.
* NOTE: This must be done *after* process_config() is called since
* it's possible and very likely that this value will be set at that
* time.
*/
if (target_basedir == NULL)
&daemonize);
q = queue_alloc();
closefrom(0);
/*
* Set up a signal handler to catch SIGUSR2. Once the child
* has setup the door, it will signal the parent that it's
* safe to exit. Without doing this it's possible that the
* daemon will start and the parent exit before the child has
* setup the door. If that happens 'zfs share -a iscsi' which
* is run from svc-iscsitgt will fail to open the door and try
* to wait for the iscsitgt service to come online. Since
* the zfs command is part of the service start, the service
* will not come online and we'll fail to share any ZVOLs.
*/
perror("sigaction");
}
switch (fork()) {
case 0:
/*
* As the child process, setup the door and then
* signal the parent that it can exit since the child
* is now ready to start accepting requests on the
* door.
*/
setup_door(q, door_name);
break;
case -1:
/* ---- Failed to fork!. Trouble ---- */
default:
/*
* If pause() returns with an error something
* interrupted the process which was not a SIGUSR2.
* Exit with an error code such that SMF can flag
* this problem.
*/
if (pause() == -1)
}
} else {
/*
* The daemon is working in debug mode, so go ahead and
* setup the door now.
*/
setup_door(q, door_name);
}
/*
* Initialize the various subsystems. In most cases these are
* just initializing mutexs.
*/
session_init();
t10_init(q);
port_init();
queue_init();
util_init();
isns_init(q);
/*
* If there's no MAC address currently available don't worry about
* it. The first time an initiator connects the SAM-3 layer will
* attempt to create a GUID and force another look for a MAC address.
*/
if (if_find_mac(q) == False)
/*
* At a minimum we need two file descriptors for each target, one for
* the socket and one for the backing store. If there's more than one
* initiator attached to a given target than that number goes up by 1.
* Once we have multiple sessions per connection that to will cause
* an increase.
*/
"Can't set new limit for open files");
}
port1.port_mgmtq = q;
port2.port_mgmtq = q;
}
do {
msg = queue_message_get(q);
case msg_pthread_join:
&thr_status);
if (thr_status != 0)
queue_prt(q, Q_GEN_ERRS,
"Thread %d exit with %d",
break;
case msg_log:
*p = '\0';
break;
msg_log, p);
if (console_output == True)
(void) printf("%s\n", p);
break;
case msg_mgmt_rqst:
break;
case msg_status:
/*
* NOTE:
* These are real error conditons being sent from
* the other threads and should be logged in
* some manner, either syslog() or using a FMA
* interface.
*/
(void) printf("STATUS: %s\n", p);
break;
default:
break;
}
/*CONSTANTCONDITION*/
} while (1);
return (0);
}