#include <libnvpair.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <fm/topo_list.h>
#include <fm/topo_mod.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <libnvpair.h>
#include <sys/debug.h>
#include <ctype.h>
#include <unistd.h>
#include "topo_usb.h"
#include "topo_usb_int.h"
#define TOPO_USB_META_LINE_MAX 1000
#define USB_TOPO_META_DEFAULT_FLAGS TOPO_USB_M_ACPI_MATCH
typedef enum {
TOPO_USB_P_START,
TOPO_USB_P_PORT,
TOPO_USB_P_LABEL,
TOPO_USB_P_PORT_TYPE,
TOPO_USB_P_ACPI_PATH
} topo_usb_parse_state_t;
typedef struct topo_usb_parse {
topo_usb_parse_state_t tp_state;
topo_list_t *tp_ports;
topo_usb_meta_port_t *tp_cport;
topo_usb_meta_flags_t tp_flags;
} topo_usb_parse_t;
static int
topo_usb_getline(topo_mod_t *mod, char *buf, size_t len, FILE *f, char **first)
{
while (fgets(buf, len, f) != NULL) {
char *c;
size_t i;
if ((c = strrchr(buf, '\n')) == NULL) {
topo_mod_dprintf(mod, "failed to find new line in "
"metadata file");
return (-1);
}
while (isspace(*c) != 0 && c >= buf) {
*c = '\0';
c--;
continue;
}
if ((c = strchr(buf, '#')) != 0) {
*c = '\0';
}
for (i = 0; buf[i] != '\0'; i++) {
if (isspace(buf[i]) == 0)
break;
}
if (buf[i] == '\0')
continue;
*first = &buf[i];
return (1);
}
return (0);
}
static boolean_t
topo_usb_parse_start(topo_mod_t *mod, topo_usb_parse_t *parse, const char *line)
{
topo_usb_meta_port_t *port;
VERIFY3S(parse->tp_state, ==, TOPO_USB_P_START);
VERIFY3P(parse->tp_cport, ==, NULL);
if (strcasecmp(line, "disable-acpi") == 0) {
parse->tp_flags |= TOPO_USB_M_NO_ACPI;
parse->tp_flags &= ~TOPO_USB_M_ACPI_MATCH;
return (B_TRUE);
} else if (strcasecmp(line, "disable-acpi-match") == 0) {
parse->tp_flags &= ~TOPO_USB_M_ACPI_MATCH;
return (B_TRUE);
} else if (strcasecmp(line, "enable-acpi-match") == 0) {
parse->tp_flags |= TOPO_USB_M_ACPI_MATCH;
return (B_TRUE);
} else if (strcasecmp(line, "enable-metadata-match") == 0) {
parse->tp_flags |= TOPO_USB_M_METADATA_MATCH;
return (B_TRUE);
} else if (strcasecmp(line, "port") != 0) {
topo_mod_dprintf(mod, "expected 'port', encountered %s",
line);
return (B_FALSE);
}
if ((port = topo_mod_zalloc(mod, sizeof (topo_usb_meta_port_t))) ==
NULL) {
topo_mod_dprintf(mod, "failed to allocate metadata port");
return (B_FALSE);
}
port->tmp_port_type = 0xff;
parse->tp_cport = port;
parse->tp_state = TOPO_USB_P_PORT;
return (B_TRUE);
}
static boolean_t
topo_usb_parse_port(topo_mod_t *mod, topo_usb_parse_t *parse, const char *line)
{
VERIFY3S(parse->tp_state, ==, TOPO_USB_P_PORT);
VERIFY3P(parse->tp_cport, !=, NULL);
if (strcasecmp(line, "label") == 0) {
parse->tp_state = TOPO_USB_P_LABEL;
} else if (strcasecmp(line, "chassis") == 0) {
parse->tp_cport->tmp_flags |= TOPO_USB_F_CHASSIS;
} else if (strcasecmp(line, "external") == 0) {
parse->tp_cport->tmp_flags |= TOPO_USB_F_EXTERNAL;
} else if (strcasecmp(line, "internal") == 0) {
parse->tp_cport->tmp_flags |= TOPO_USB_F_INTERNAL;
} else if (strcasecmp(line, "port-type") == 0) {
parse->tp_state = TOPO_USB_P_PORT_TYPE;
} else if (strcasecmp(line, "acpi-path") == 0) {
parse->tp_state = TOPO_USB_P_ACPI_PATH;
} else if (strcasecmp(line, "end-port") == 0) {
topo_list_append(parse->tp_ports, parse->tp_cport);
parse->tp_cport = NULL;
parse->tp_state = TOPO_USB_P_START;
} else {
topo_mod_dprintf(mod, "illegal directive in port block: %s",
line);
return (B_FALSE);
}
return (B_TRUE);
}
static boolean_t
topo_usb_parse_label(topo_mod_t *mod, topo_usb_parse_t *parse, const char *line)
{
size_t i, len;
VERIFY3S(parse->tp_state, ==, TOPO_USB_P_LABEL);
len = strlen(line);
for (i = 0; i < len; i++) {
if (isascii(line[i]) == 0 || isprint(line[i]) == 0) {
topo_mod_dprintf(mod, "label character %zu is "
"invalid: 0x%x", i, line[i]);
return (B_FALSE);
}
}
if (parse->tp_cport->tmp_label != NULL) {
topo_mod_strfree(mod, parse->tp_cport->tmp_label);
}
if ((parse->tp_cport->tmp_label = topo_mod_strdup(mod, line)) == NULL) {
topo_mod_dprintf(mod, "failed to duplicate label for port");
return (B_FALSE);
}
parse->tp_state = TOPO_USB_P_PORT;
return (B_TRUE);
}
static boolean_t
topo_usb_parse_port_type(topo_mod_t *mod, topo_usb_parse_t *parse,
const char *line)
{
unsigned long val;
char *eptr;
VERIFY3S(parse->tp_state, ==, TOPO_USB_P_PORT_TYPE);
errno = 0;
val = strtoul(line, &eptr, 0);
if (errno != 0 || *eptr != '\0' || val >= UINT_MAX) {
topo_mod_dprintf(mod, "encountered bad value for port-type "
"line: %s", line);
return (B_FALSE);
}
parse->tp_cport->tmp_port_type = (uint_t)val;
parse->tp_state = TOPO_USB_P_PORT;
return (B_TRUE);
}
static boolean_t
topo_usb_parse_path(topo_mod_t *mod, topo_usb_parse_t *parse,
topo_usb_path_type_t ptype, const char *line)
{
char *fspath;
topo_usb_meta_port_path_t *path;
VERIFY(parse->tp_state == TOPO_USB_P_ACPI_PATH);
VERIFY3P(parse->tp_cport, !=, NULL);
if ((fspath = topo_mod_strdup(mod, line)) == NULL) {
topo_mod_dprintf(mod, "failed to duplicate path");
return (B_FALSE);
}
if ((path = topo_mod_zalloc(mod, sizeof (topo_usb_meta_port_path_t))) ==
NULL) {
topo_mod_dprintf(mod, "failed to allocate meta port path "
"structure");
topo_mod_strfree(mod, fspath);
return (B_FALSE);
}
path->tmpp_type = ptype;
path->tmpp_path = fspath;
topo_list_append(&parse->tp_cport->tmp_paths, path);
parse->tp_state = TOPO_USB_P_PORT;
return (B_TRUE);
}
void
topo_usb_free_metadata(topo_mod_t *mod, topo_list_t *metadata)
{
topo_usb_meta_port_t *mp;
while ((mp = topo_list_next(metadata)) != NULL) {
topo_usb_meta_port_path_t *path;
while ((path = topo_list_next((&mp->tmp_paths))) != NULL) {
topo_list_delete(&mp->tmp_paths, path);
topo_mod_strfree(mod, path->tmpp_path);
topo_mod_free(mod, path,
sizeof (topo_usb_meta_port_path_t));
}
topo_list_delete(metadata, mp);
topo_mod_strfree(mod, mp->tmp_label);
topo_mod_free(mod, mp, sizeof (topo_usb_meta_port_t));
}
}
int
topo_usb_load_metadata(topo_mod_t *mod, tnode_t *pnode, topo_list_t *list,
topo_usb_meta_flags_t *flagsp)
{
int fd;
FILE *f = NULL;
char buf[TOPO_USB_META_LINE_MAX], *first, *prod;
int ret;
topo_usb_parse_t parse;
char pbuf[PATH_MAX];
*flagsp = USB_TOPO_META_DEFAULT_FLAGS;
if ((topo_prop_get_string(pnode, FM_FMRI_AUTHORITY,
FM_FMRI_AUTH_PRODUCT, &prod, &ret)) != 0) {
topo_mod_dprintf(mod, "skipping metadata load: failed to get "
"auth");
return (0);
}
if (snprintf(pbuf, sizeof (pbuf), "maps/%s-usb.usbtopo", prod) >=
sizeof (pbuf)) {
topo_mod_dprintf(mod, "skipping metadata load: product name "
"too long");
topo_mod_strfree(mod, prod);
return (0);
}
topo_mod_strfree(mod, prod);
if ((fd = topo_mod_file_search(mod, pbuf, O_RDONLY)) < 0) {
topo_mod_dprintf(mod, "skipping metadata load: couldn't find "
"%s", pbuf);
return (0);
}
if ((f = fdopen(fd, "r")) == NULL) {
topo_mod_dprintf(mod, "failed to fdopen metadata file %s: %s",
pbuf, strerror(errno));
VERIFY0(close(fd));
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto err;
}
bzero(&parse, sizeof (parse));
parse.tp_ports = list;
parse.tp_state = TOPO_USB_P_START;
while ((ret = topo_usb_getline(mod, buf, sizeof (buf), f, &first)) !=
0) {
if (ret == -1) {
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto err;
}
switch (parse.tp_state) {
case TOPO_USB_P_START:
if (!topo_usb_parse_start(mod, &parse, first)) {
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto err;
}
break;
case TOPO_USB_P_PORT:
if (!topo_usb_parse_port(mod, &parse, first)) {
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto err;
}
break;
case TOPO_USB_P_LABEL:
if (!topo_usb_parse_label(mod, &parse, first)) {
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto err;
}
break;
case TOPO_USB_P_PORT_TYPE:
if (!topo_usb_parse_port_type(mod, &parse, first)) {
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto err;
}
break;
case TOPO_USB_P_ACPI_PATH:
if (!topo_usb_parse_path(mod, &parse, TOPO_USB_T_ACPI,
first)) {
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto err;
}
break;
}
}
if (parse.tp_state != TOPO_USB_P_START) {
topo_mod_dprintf(mod, "metadata file didn't end in correct "
"state, failing");
ret = topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
goto err;
}
topo_mod_dprintf(mod, "successfully loaded metadata %s", pbuf);
VERIFY0(fclose(f));
*flagsp = parse.tp_flags;
return (0);
err:
if (f != NULL)
VERIFY0(fclose(f));
topo_usb_free_metadata(mod, list);
return (ret);
}