#include <sys/errno.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/byteorder.h>
#include <sys/scsi/impl/uscsi.h>
#include <sys/scsi/scsi.h>
#include <tlm.h>
#include <pthread.h>
#include "tlm_proto.h"
int
read_scsi_page(scsi_link_t *slink, union scsi_cdb *cdb,
int command_size, caddr_t data, int size)
{
struct uscsi_cmd uscsi_cmd;
char *dname;
int dev;
if (slink == 0 || slink->sl_sa == 0)
return (EINVAL);
(void) memset(&uscsi_cmd, 0, sizeof (uscsi_cmd));
cdb->scc_lun = slink->sl_lun;
uscsi_cmd.uscsi_flags |= USCSI_READ | USCSI_ISOLATE;
uscsi_cmd.uscsi_bufaddr = data;
uscsi_cmd.uscsi_buflen = size;
uscsi_cmd.uscsi_timeout = 1000;
uscsi_cmd.uscsi_cdb = (char *)cdb;
if (cdb->scc_cmd == SCMD_READ_ELEMENT_STATUS) {
uscsi_cmd.uscsi_flags |= USCSI_RQENABLE;
uscsi_cmd.uscsi_rqbuf = data;
uscsi_cmd.uscsi_rqlen = size;
}
uscsi_cmd.uscsi_cdblen = command_size;
dname = sasd_slink_name(slink);
dev = open(dname, O_RDWR | O_NDELAY);
if (dev == -1) {
NDMP_LOG(LOG_DEBUG, "Open failed for %s err=%d",
dname, errno);
return (errno);
}
if (tlm_ioctl(dev, USCSICMD, &uscsi_cmd) < 0) {
NDMP_LOG(LOG_DEBUG, "SCSI cmd %d failed for %s err=%d",
cdb->scc_cmd, dname, errno);
(void) close(dev);
return (errno);
}
(void) close(dev);
return (uscsi_cmd.uscsi_status);
}
static int
read_inquiry_page(scsi_link_t *slink, struct scsi_inquiry *inq)
{
union scsi_cdb cdb;
(void) memset(&cdb, 0, sizeof (union scsi_cdb));
cdb.scc_cmd = SCMD_INQUIRY;
cdb.g0_count0 = sizeof (struct scsi_inquiry);
return (read_scsi_page(slink, &cdb, CDB_GROUP0,
(caddr_t)inq, sizeof (*inq)) ? -1 : 0);
}
static int
read_data_page(scsi_link_t *slink, int pcode, char *snum, int size)
{
char cmd[CDB_GROUP0];
(void) memset(cmd, 0, sizeof (cmd));
cmd[0] = SCMD_INQUIRY;
cmd[1] = pcode ? 0x01 : 0x00;
cmd[2] = pcode;
cmd[4] = size;
return (read_scsi_page(slink, (union scsi_cdb *)&cmd, CDB_GROUP0,
(caddr_t)snum, size) == -1 ? -1 : 0);
}
static int
read_serial_num_page(scsi_link_t *slink, char *snum, int size)
{
scsi_serial_t serial;
int rv;
(void) memset(&serial, 0, sizeof (scsi_serial_t));
rv = read_data_page(slink, SCSI_SERIAL_PAGE, (caddr_t)&serial,
sizeof (scsi_serial_t));
(void) strlcpy(snum, serial.sr_num, size);
return (rv == -1 ? -1 : 0);
}
static int
read_dev_name_page(scsi_link_t *slink, device_ident_header_t *devp, int len)
{
(void) memset(devp, 0, len);
if (read_data_page(slink, SCSI_DEVICE_IDENT_PAGE, (caddr_t)devp,
len) == -1)
return (-1);
if (devp->di_page_code != SCSI_DEVICE_IDENT_PAGE)
return (-1);
return (0);
}
static void
snprintf_wwn(char *buf, int size, uint8_t *wwn)
{
if (wwn == NULL || buf == NULL)
return;
(void) snprintf(buf, size, "0x%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X",
wwn[0], wwn[1], wwn[2], wwn[3], wwn[4], wwn[5], wwn[6], wwn[7]);
}
int
read_device_wwn(scsi_link_t *slink, char *wwnp, int wsize)
{
device_ident_header_t *header;
name_ident_t *ident;
uint16_t page_len = sizeof (device_ident_header_t);
uint16_t act_len;
int accessed;
uint8_t *designator_data;
(void) memset(wwnp, 0, wsize);
resize:
header = malloc(page_len);
if (header == NULL)
return (-1);
if (read_dev_name_page(slink, header, page_len) == -1) {
free(header);
return (-1);
}
act_len = BE_16(header->di_page_length);
if (act_len > page_len) {
free(header);
page_len = act_len;
goto resize;
}
ident = (name_ident_t *)&header[1];
accessed = sizeof (device_ident_header_t);
while (accessed < act_len) {
accessed += sizeof (name_ident_t);
accessed += ident->ni_ident_length;
designator_data = (uint8_t *)&ident[1];
if ((ident->ni_code_set == 1) &&
(ident->ni_ident_type == 3)) {
snprintf_wwn(wwnp, wsize, designator_data);
if (ident->ni_asso == 0)
break;
}
if ((ident->ni_code_set == 2) &&
(ident->ni_ident_type == 1) &&
(ident->ni_asso == 0) &&
(isprint(wwnp[0] == 0))) {
(void) snprintf(wwnp, wsize, "%.*s",
ident->ni_ident_length, designator_data);
}
ident =
(name_ident_t *)&designator_data[ident->ni_ident_length];
}
free(header);
if (isprint(wwnp[0]))
return (0);
return (-1);
}
static int
add_lib(scsi_link_t *slink, struct scsi_inquiry *sd, void *arg)
{
int l;
int *nlp;
sasd_drive_t *ssd;
if (!slink || !sd) {
NDMP_LOG(LOG_DEBUG, "Invalid argument %x %x %x",
slink, sd, arg);
return (-TLM_INVALID);
}
if (sd->inq_dtype == DTYPE_CHANGER) {
nlp = (int *)arg;
(*nlp)++;
l = tlm_insert_new_library(slink);
tlm_enable_barcode(l);
NDMP_LOG(LOG_DEBUG, "lib %d sid %d lun %d",
l, slink->sl_sid, slink->sl_lun);
if ((ssd = sasd_slink_drive(slink)) != NULL) {
(void) strlcpy(ssd->sd_vendor, sd->inq_vid,
sizeof (ssd->sd_vendor));
(void) strlcpy(ssd->sd_id, sd->inq_pid,
sizeof (ssd->sd_id));
(void) strlcpy(ssd->sd_rev, sd->inq_revision,
sizeof (ssd->sd_rev));
(void) read_serial_num_page(slink, ssd->sd_serial,
sizeof (ssd->sd_serial));
(void) read_device_wwn(slink, ssd->sd_wwn,
sizeof (ssd->sd_wwn));
}
}
return (TLM_NO_ERRORS);
}
static int
make_virtual_slot(int l, tlm_drive_t *dp)
{
int s;
tlm_slot_t *sp;
if (l <= 0 || !dp) {
NDMP_LOG(LOG_DEBUG, "Invalid argument %d, %x", l, dp);
return (-TLM_INVALID);
}
if ((s = tlm_insert_new_slot(l)) <= 0)
return (-TLM_NO_MEMORY);
if (!(sp = tlm_slot(l, s))) {
NDMP_LOG(LOG_DEBUG, "Internal error: slot not found %d", s);
return (-TLM_ERROR_INTERNAL);
}
sp->ts_element = 0;
sp->ts_status_full = TRUE;
return (TLM_NO_ERRORS);
}
static int
make_stand_alone_drive(scsi_link_t *slink, int l)
{
int d;
tlm_drive_t *dp;
if (!slink || l <= 0) {
NDMP_LOG(LOG_DEBUG, "Invalid argument %x %d", slink, l);
return (-TLM_INVALID);
}
d = tlm_insert_new_drive(l);
if (!(dp = tlm_drive(l, d))) {
NDMP_LOG(LOG_DEBUG, "Internal error: drive not found %d", d);
return (-TLM_ERROR_INTERNAL);
}
dp->td_element = d;
dp->td_slink = slink;
dp->td_scsi_id = slink->sl_sid;
dp->td_lun = slink->sl_lun;
dp->td_exists = TRUE;
(void) make_virtual_slot(l, dp);
return (d);
}
static int
new_drive(scsi_link_t *slink, int *lib)
{
int d;
tlm_drive_t *dp;
tlm_library_t *lp;
for (*lib = 1; *lib <= tlm_library_count(); (*lib)++) {
if (!(lp = tlm_library(*lib)))
continue;
for (d = 1; d <= lp->tl_drive_count; d++) {
if (!(dp = tlm_drive(*lib, d)))
continue;
if (dp->td_scsi_id == slink->sl_sid &&
dp->td_lun == slink->sl_lun)
return (d);
}
}
return (0);
}
static int
add_drv(scsi_link_t *slink, struct scsi_inquiry *sd, void *arg)
{
int l, d;
int *vlp;
sasd_drive_t *ssd;
tlm_library_t *library;
tlm_drive_t *drive;
if (!slink || !sd) {
NDMP_LOG(LOG_DEBUG, "Invalid argument %x %x %x",
slink, sd, arg);
return (-TLM_INVALID);
}
if (sd->inq_dtype == DTYPE_SEQUENTIAL) {
vlp = (int *)arg;
d = new_drive(slink, &l);
if (d == 0) {
if (*vlp == 0) {
*vlp = tlm_insert_new_library(slink);
if ((library = tlm_library(*vlp)) != NULL)
library->tl_capability_robot = FALSE;
}
if ((d = make_stand_alone_drive(slink, *vlp)) < 0) {
return (-TLM_INVALID);
}
l = *vlp;
NDMP_LOG(LOG_DEBUG, "vlib(%d, %d) sid %d lun %d",
l, d, slink->sl_sid, slink->sl_lun);
} else
NDMP_LOG(LOG_DEBUG, "(%d, %d) sid %d lun %d",
l, d, slink->sl_sid, slink->sl_lun);
if ((drive = tlm_drive(l, d)) != NULL) {
drive->td_exists = TRUE;
drive->td_slink = slink;
}
if ((ssd = sasd_slink_drive(slink)) != NULL) {
(void) strlcpy(ssd->sd_vendor,
sd->inq_vid, sizeof (ssd->sd_vendor));
(void) strlcpy(ssd->sd_id, sd->inq_pid,
sizeof (ssd->sd_id));
(void) strlcpy(ssd->sd_rev, sd->inq_revision,
sizeof (ssd->sd_rev));
(void) read_serial_num_page(slink, ssd->sd_serial,
sizeof (ssd->sd_serial));
(void) read_device_wwn(slink, ssd->sd_wwn,
sizeof (ssd->sd_wwn));
}
}
return (TLM_NO_ERRORS);
}
static int
scan_bus(scsi_adapter_t *sa, int(*hndlr)(), void *args)
{
int nerr;
scsi_link_t *slink;
struct scsi_inquiry scsi_data;
nerr = 0;
slink = sa->sa_link_head.sl_next;
for (; slink != &sa->sa_link_head; slink = slink->sl_next) {
(void) memset(&scsi_data, 0, sizeof (struct scsi_inquiry));
if (read_inquiry_page(slink, &scsi_data) == -1)
nerr++;
else
if ((*hndlr)(slink, &scsi_data, args) != TLM_NO_ERRORS)
nerr++;
}
return (nerr);
}
static void
inaccbl_drv_warn(int start, int max)
{
char *dname;
int l, d;
tlm_library_t *lp;
for (l = start; l < max; l++) {
if (!(lp = tlm_library(l)))
continue;
if (lp->tl_drive_count <= 0)
continue;
NDMP_LOG(LOG_DEBUG,
"Warning: The following drives are not accessible:");
for (d = 1; d <= lp->tl_drive_count; d++)
if (!(dname = tlm_get_tape_name(l, d))) {
NDMP_LOG(LOG_DEBUG,
"Error getting drive(%d, %d)", l, d);
} else
NDMP_LOG(LOG_DEBUG, "%s", dname);
lp->tl_slot_count = 0;
}
}
int
tlm_init(void)
{
static int nlibs;
int i, nsa;
int l, vlibs, d;
int rv;
scsi_adapter_t *sa;
tlm_library_t *lp;
tlm_drive_t *dp;
nlibs = 0;
if (probe_scsi() < 0)
return (-1);
nsa = scsi_get_adapter_count();
for (i = 0; i < nsa; i++)
if ((sa = scsi_get_adapter(i)))
(void) scan_bus(sa, add_lib, (void *)&nlibs);
NDMP_LOG(LOG_DEBUG, "nlibs %d", nlibs);
vlibs = 0;
for (i = 0; i < nsa; i++)
if ((sa = scsi_get_adapter(i)))
(void) scan_bus(sa, add_drv, (void *)&vlibs);
NDMP_LOG(LOG_DEBUG, "vlibs %d", vlibs);
if (nlibs > 0 && vlibs > 0)
inaccbl_drv_warn(nlibs + 1, vlibs + nlibs + 1);
for (l = 1; l <= tlm_library_count(); l++) {
if (!(lp = tlm_library(l))) {
NDMP_LOG(LOG_DEBUG, "can't find lib %d", l);
continue;
}
if (lp->tl_drive_count == 0)
continue;
for (d = 1; d <= lp->tl_drive_count; d++) {
dp = tlm_drive(l, d);
if (dp && !dp->td_exists) {
NDMP_LOG(LOG_DEBUG, "Ghost drive found %d.%d",
l, d);
lp->tl_ghost_drives = TRUE;
continue;
}
}
}
if (nlibs > 0)
rv = (vlibs > 0) ? 0 : nlibs;
else
rv = vlibs;
return (rv);
}