helper.c revision f3861e1a2ceec23a5b699c24d814b7775a9e0b52
/*
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <widec.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <libintl.h>
#include <limits.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/iscsi_protocol.h>
#include <door.h>
#include <sys/scsi/generic/inquiry.h>
#include <sys/mman.h>
#include <sys/filio.h>
#include <libxml/xmlreader.h>
#include <libscf.h>
#include <fcntl.h>
#include <iscsitgt_impl.h>
#include "cmdparse.h"
#include "utility.h"
#include "helper.h"
extern char *cmdName;
static stat_delta_t *stat_head;
/*
* []----
* | buffer_xml -- buffer incoming XML response until complete
* |
* | Incoming data from target may not be a complete XML message. So,
* | we need to wait until we've got everything otherwise the XML routines
* | will generate a parsing error for a short buffer.
* []----
*/
Boolean_t
buffer_xml(char *s, char **storage, tgt_node_t **np)
{
tgt_node_t *node = NULL;
xmlTextReaderPtr r;
char *p,
*e,
*end_tag,
hold_ch;
p = *storage;
if (s != NULL) {
if (p == NULL) {
p = strdup(s);
} else {
p = realloc(p, strlen(p) + strlen(s) + 1);
(void) strcat(p, s);
}
}
if (p == NULL) {
return (False);
}
if (*p != '<') {
return (False);
}
if ((e = strchr(p, '>')) == NULL) {
return (False);
}
/*
* The +3 is for the slash, closing tag character and null
* For example if p is pointing at a string which starts with
* "<foo>...."
* p will point at '<' and e will point at '>'. e - p is 4, yet
* the tag length is really 5 characters. We will need to create
* the end tag which also has a slash and NULL byte.
*/
if ((end_tag = malloc(e - p + 3)) == NULL) {
return (False);
}
end_tag[0] = '<';
end_tag[1] = '/';
/*
* Copy in the tag value and the closing tag character '>'.
*/
bcopy(p + 1, &end_tag[2], e - p);
/*
* Add the null byte
*/
end_tag[e - p + 2] = '\0';
/*
* Do we have the closing string yet? If not, just return
*/
if ((e = strstr(p, end_tag)) == NULL) {
*storage = p;
return (False);
}
/*
* Move past the closing tag and free the end_tag memory
*/
e += strlen(end_tag);
free(end_tag);
/*
* NULL terminate the string and remember to save that character
* so that we can restore it later.
*/
hold_ch = *e;
*e = '\0';
if ((r = (xmlTextReaderPtr)xmlReaderForMemory(p, strlen(p), NULL,
NULL, 0)) == NULL)
return (False);
while (xmlTextReaderRead(r) == 1) {
if (tgt_node_process(r, &node) == False)
break;
}
*np = node;
xmlFreeTextReader(r);
*e = hold_ch;
for (; isspace(*e); e++)
;
if (*e != '\0') {
*storage = strdup(e);
} else
*storage = NULL;
free(p);
return (True);
}
/*
* Retrieve CHAP secret from input
*/
int
getSecret(char *secret, int *secretLen, int minSecretLen, int maxSecretLen)
{
char *chapSecret;
/* XXX Should we prompt for hex or ascii printable input? */
/* get password */
chapSecret = getpassphrase(gettext("Enter secret:"));
if (strlen(chapSecret) > maxSecretLen) {
(void) fprintf(stderr, "%s: %s\n", cmdName,
gettext("secret too long"));
*secret = NULL;
return (1);
}
if (strlen(chapSecret) < minSecretLen) {
(void) fprintf(stderr, "%s: %s\n", cmdName,
gettext("secret too short"));
*secret = NULL;
return (1);
}
(void) strcpy(secret, chapSecret);
chapSecret = getpassphrase(gettext("Re-enter secret:"));
if (strcmp(secret, chapSecret) != 0) {
(void) fprintf(stderr, "%s: %s\n", cmdName,
gettext("secret not changed"));
*secret = NULL;
return (1);
}
*secretLen = strlen(chapSecret);
return (0);
}
void
iSCSINameCheckStatusDisplay(iSCSINameCheckStatusType status)
{
switch (status) {
case iSCSINameLenZero:
(void) fprintf(stderr, "%s: %s\n",
cmdName, gettext("empty iSCSI name."));
break;
case iSCSINameLenExceededMax:
(void) fprintf(stderr, "%s: %s\n", cmdName,
gettext("iSCSI name exceeded maximum length."));
break;
case iSCSINameUnknownType:
(void) fprintf(stderr, "%s: %s\n", cmdName,
gettext("unknown iSCSI name type."));
break;
case iSCSINameIqnFormatError:
(void) fprintf(stderr, "%s: %s\n", cmdName,
gettext("iqn formatting error."));
break;
case iSCSINameEUIFormatError:
(void) fprintf(stderr, "%s: %s\n", cmdName,
gettext("eui formatting error."));
break;
}
}
/*
* This helper function could go into a utility module for general use.
*/
int
parseAddress(char *address_port_str,
uint16_t defaultPort,
char *address_str,
size_t address_str_len,
uint16_t *port,
boolean_t *isIpv6)
{
char port_str[64];
int tmp_port;
if (address_port_str[0] == '[') {
/* IPv6 address */
char *close_bracket_pos;
close_bracket_pos = strchr(address_port_str, ']');
if (!close_bracket_pos) {
syslog(LOG_USER|LOG_DEBUG,
"IP address format error: %s\n", address_str);
return (PARSE_ADDR_MISSING_CLOSING_BRACKET);
}
*close_bracket_pos = NULL;
(void) strlcpy(address_str, &address_port_str[1],
address_str_len);
/* Extract the port number */
close_bracket_pos++;
if (*close_bracket_pos == ':') {
close_bracket_pos++;
if (*close_bracket_pos != NULL) {
(void) strlcpy(port_str, close_bracket_pos,
64);
tmp_port = atoi(port_str);
if (((tmp_port > 0) &&
(tmp_port > USHRT_MAX)) ||
(tmp_port < 0)) {
/* Port number out of range */
syslog(LOG_USER|LOG_DEBUG,
"Specified port out of range: %d",
tmp_port);
return (PARSE_ADDR_PORT_OUT_OF_RANGE);
} else {
*port = (uint16_t)tmp_port;
}
} else {
*port = defaultPort;
}
} else {
*port = defaultPort;
}
*isIpv6 = B_TRUE;
} else {
/* IPv4 address */
char *colon_pos;
colon_pos = strchr(address_port_str, ':');
if (!colon_pos) {
/* No port number specified. */
*port = defaultPort;
(void) strlcpy(address_str, address_port_str,
address_str_len);
} else {
*colon_pos = (char)NULL;
(void) strlcpy(address_str, address_port_str,
address_str_len);
/* Extract the port number */
colon_pos++;
if (*colon_pos != NULL) {
(void) strlcpy(port_str, colon_pos, 64);
tmp_port = atoi(port_str);
if (((tmp_port > 0) &&
(tmp_port > USHRT_MAX)) ||
(tmp_port < 0)) {
/* Port number out of range */
syslog(LOG_USER|LOG_DEBUG,
"Specified port out of range: %d",
tmp_port);
return (PARSE_ADDR_PORT_OUT_OF_RANGE);
} else {
*port = (uint16_t)tmp_port;
}
} else {
*port = defaultPort;
}
}
*isIpv6 = B_FALSE;
}
return (PARSE_ADDR_OK);
}
/*
* []----
* | Following routine (number_to_scaled_string) is lifted
* | from usr/src/cmd/fs.d/df.c
* []----
*/
/*
* Convert an unsigned long long to a string representation and place the
* result in the caller-supplied buffer.
* The given number is in units of "unit_from" size,
* this will first be converted to a number in 1024 or 1000 byte size,
* depending on the scaling factor.
* Then the number is scaled down until it is small enough to be in a good
* human readable format i.e. in the range 0 thru scale-1.
* If it's smaller than 10 there's room enough to provide one decimal place.
* The value "(unsigned long long)-1" is a special case and is always
* converted to "-1".
* Returns a pointer to the caller-supplied buffer.
*/
char *
number_to_scaled_string(
char *buf, /* put the result here */
unsigned long long number, /* convert this number */
int unit_from,
int scale)
{
unsigned long long save = 0;
char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */
char *uom = M; /* unit of measurement, initially 'K' (=M[0]) */
if ((long long)number == (long long)-1) {
(void) strcpy(buf, "-1");
return (buf);
}
if ((number < scale) && (unit_from == 1)) {
(void) sprintf(buf, "%4llu", number);
return (buf);
}
/*
* Convert number from unit_from to given scale (1024 or 1000).
* This means multiply number by unit_from and divide by scale.
*
* Would like to multiply by unit_from and then divide by scale,
* but if the first multiplication would overflow, then need to
* divide by scale and then multiply by unit_from.
*/
if (number > (UINT64_MAX / (unsigned long long)unit_from)) {
number = (number / (unsigned long long)scale) *
(unsigned long long)unit_from;
} else {
number = (number * (unsigned long long)unit_from) /
(unsigned long long)scale;
}
/*
* Now we have number as a count of scale units.
* Stop scaling when we reached exa bytes, then something is
* probably wrong with our number.
*/
while ((number >= scale) && (*uom != 'E')) {
uom++; /* next unit of measurement */
save = number;
number = (number + (scale / 2)) / scale;
}
/* check if we should output a decimal place after the point */
if (save && ((save / scale) < 10)) {
/* sprintf() will round for us */
float fnum = (float)save / scale;
(void) sprintf(buf, "%2.1f%c", fnum, *uom);
} else {
(void) sprintf(buf, "%4llu%c", number, *uom);
}
return (buf);
}
void
stats_load_counts(tgt_node_t *n, stat_delta_t *d)
{
tgt_node_t *conn = NULL,
*lun;
char *val;
bzero(d, sizeof (*d));
d->device = n->x_value;
while (conn = tgt_node_next(n, XML_ELEMENT_CONN, conn)) {
lun = NULL;
while (lun = tgt_node_next(conn, XML_ELEMENT_LUN, lun)) {
if (tgt_find_value_str(lun, XML_ELEMENT_READCMDS,
&val) == True) {
d->read_cmds += strtoll(val, NULL, 0);
free(val);
}
if (tgt_find_value_str(lun, XML_ELEMENT_WRITECMDS,
&val) == True) {
d->write_cmds += strtoll(val, NULL, 0);
free(val);
}
if (tgt_find_value_str(lun, XML_ELEMENT_READBLKS,
&val) == True) {
d->read_blks += strtoll(val, NULL, 0);
free(val);
}
if (tgt_find_value_str(lun, XML_ELEMENT_WRITEBLKS,
&val) == True) {
d->write_blks += strtoll(val, NULL, 0);
free(val);
}
}
}
}
stat_delta_t *
stats_prev_counts(stat_delta_t *cp)
{
stat_delta_t *n;
for (n = stat_head; n; n = n->next) {
if (strcmp(n->device, cp->device) == 0)
return (n);
}
if ((n = calloc(1, sizeof (*n))) == NULL)
return (NULL);
n->device = strdup(cp->device);
if (stat_head == NULL)
stat_head = n;
else {
n->next = stat_head;
stat_head = n;
}
return (n);
}
void
stats_update_counts(stat_delta_t *p, stat_delta_t *c)
{
p->read_cmds += c->read_cmds - p->read_cmds;
p->write_cmds += c->write_cmds - p->write_cmds;
p->read_blks += c->read_blks - p->read_blks;
p->write_blks += c->write_blks - p->write_blks;
}
void
stats_free()
{
stat_delta_t *n;
/* CSTYLED */
for (;stat_head;) {
n = stat_head->next;
free(stat_head->device);
free(stat_head);
stat_head = n;
}
}
static char spaces[128];
/*
* []----
* | dospace -- generate a string which has the appropriate number of spaces
* |
* | NOTE: Since this function modifies a static buffer usage of this
* | function may not be what's expected. For example:
* | printf("%sfoo%sbar\n", dospace(1), dospace(2)); would produce
* | ' foo bar'
* | instead of
* | ' foo bar'
* []----
*/
char *
dospace(int n)
{
(void) memset(spaces, ' ', sizeof (spaces));
spaces[sizeof (spaces) - 1] = '\0';
if (n < sizeof (spaces))
spaces[n * 4] = '\0';
return (spaces);
}