#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/param.h>
#include <libseslog.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/scsi/generic/commands.h>
#include <sys/scsi/generic/status.h>
#include <sys/scsi/impl/commands.h>
static int
open_device(const char *device_name)
{
int oflags = O_NONBLOCK | O_RDWR;
int fd;
fd = open(device_name, oflags);
if (fd < 0)
fd = -errno;
return (fd);
}
static void
construct_scsi_pt_obj(struct uscsi_cmd *uscsi)
{
(void) memset(uscsi, 0, sizeof (struct uscsi_cmd));
uscsi->uscsi_timeout = DEF_PT_TIMEOUT;
uscsi->uscsi_flags = USCSI_READ | USCSI_ISOLATE | USCSI_RQENABLE;
}
static void
set_scsi_pt_cdb(struct uscsi_cmd *uscsi, const unsigned char *cdb,
int cdb_len)
{
uscsi->uscsi_cdb = (char *)cdb;
uscsi->uscsi_cdblen = cdb_len;
}
static void
set_scsi_pt_sense(struct uscsi_cmd *uscsi, unsigned char *sense,
int max_sense_len)
{
(void) memset(sense, 0, max_sense_len);
uscsi->uscsi_rqbuf = (char *)sense;
uscsi->uscsi_rqlen = max_sense_len;
}
static void
set_scsi_pt_data_in(struct uscsi_cmd *uscsi, unsigned char *dxferp,
int dxfer_len)
{
if (dxfer_len > 0) {
uscsi->uscsi_bufaddr = (char *)dxferp;
uscsi->uscsi_buflen = dxfer_len;
uscsi->uscsi_flags = USCSI_READ | USCSI_ISOLATE |
USCSI_RQENABLE;
}
}
static int
do_scsi_pt(struct uscsi_cmd *uscsi, int fd, int time_secs)
{
if (time_secs > 0)
uscsi->uscsi_timeout = time_secs;
if (ioctl(fd, USCSICMD, uscsi)) {
return (errno);
}
return (0);
}
static int
read_log(int sg_fd, unsigned char *resp, int mx_resp_len)
{
int res, ret;
unsigned char logsCmdBlk[CDB_GROUP1] =
{SCMD_LOG_SENSE_G1, 0, 0, 0, 0, 0, 0, 0, 0, 0};
unsigned char sense_b[SENSE_BUFF_LEN];
struct uscsi_cmd uscsi;
if (mx_resp_len > 0xffff) {
return (-1);
}
logsCmdBlk[1] = 0;
logsCmdBlk[2] = 0x47;
logsCmdBlk[3] = 0;
logsCmdBlk[5] = 0;
logsCmdBlk[6] = 0;
logsCmdBlk[7] = (unsigned char) ((mx_resp_len >> 8) & 0xff);
logsCmdBlk[8] = (unsigned char) (mx_resp_len & 0xff);
construct_scsi_pt_obj(&uscsi);
set_scsi_pt_cdb(&uscsi, logsCmdBlk, sizeof (logsCmdBlk));
set_scsi_pt_sense(&uscsi, sense_b, sizeof (sense_b));
set_scsi_pt_data_in(&uscsi, resp, mx_resp_len);
res = do_scsi_pt(&uscsi, sg_fd, DEF_PT_TIMEOUT);
if (res) {
ret = res;
} else {
ret = uscsi.uscsi_status;
}
return (ret);
}
static int
save_logs(unsigned char *resp, ses_log_call_t *data)
{
int k;
int param_code;
int param_len = 0;
unsigned char *log_param_ptr;
unsigned char *log_str_ptr;
char log_code[ENTRY_MAX_SIZE];
char log_level[ENTRY_MAX_SIZE];
nvlist_t *entry;
char entry_num[15];
int match_found = 0;
char save_buffer[MAX_LOG_ENTRY_SZ];
char entry_added = 0;
int all_log_data_len;
all_log_data_len = SCSI_READ16(&resp[2]);
log_param_ptr = &resp[0] + 4;
if (strlen(data->last_log_entry) == SES_LOG_VALID_LOG_SIZE) {
for (k = 0; k < all_log_data_len; k += param_len) {
param_len = log_param_ptr[3] + 4;
if (param_len <= 4) {
log_param_ptr += param_len;
continue;
}
log_str_ptr = log_param_ptr + 4;
if (strncmp((char *)log_str_ptr, data->last_log_entry,
SES_LOG_VALID_LOG_SIZE) == 0) {
log_param_ptr += param_len;
k += param_len;
match_found = 1;
break;
}
log_param_ptr += param_len;
}
}
if (!match_found) {
log_param_ptr = &resp[0] + 4;
k = 0;
}
if (k == all_log_data_len) {
return (0);
}
if (nvlist_alloc(&data->log_data, NV_UNIQUE_NAME, 0) != 0) {
return (SES_LOG_FAILED_NVLIST_CREATE);
}
(void) memset(log_code, 0, sizeof (log_code));
(void) memset(save_buffer, 0, sizeof (save_buffer));
(void) memset(log_level, 0, sizeof (log_level));
for (; k < all_log_data_len; k += param_len) {
param_len = log_param_ptr[3] + 4;
if (param_len <= 4) {
log_param_ptr += param_len;
continue;
}
log_str_ptr = log_param_ptr + 4;
(void) strncpy(save_buffer,
(const char *)log_str_ptr,
SES_LOG_VALID_LOG_SIZE);
(void) strncpy(log_code,
(const char *)log_str_ptr+SES_LOG_CODE_START,
SES_LOG_SPECIFIC_ENTRY_SIZE);
(void) strncpy(log_level,
(const char *) log_str_ptr +
SES_LOG_LEVEL_START, 1);
if (nvlist_alloc(&entry, NV_UNIQUE_NAME, 0) != 0) {
return (SES_LOG_FAILED_NV_UNIQUE);
}
if (nvlist_add_string(entry, ENTRY_LOG, save_buffer) != 0) {
nvlist_free(entry);
return (SES_LOG_FAILED_NV_LOG);
}
if (nvlist_add_string(entry, ENTRY_CODE, log_code) != 0) {
nvlist_free(entry);
return (SES_LOG_FAILED_NV_CODE);
}
if (nvlist_add_string(entry, ENTRY_SEVERITY, log_level) != 0) {
nvlist_free(entry);
return (SES_LOG_FAILED_NV_SEV);
}
param_code = SCSI_READ16(&log_param_ptr[0]);
(void) snprintf(entry_num, sizeof (entry_num),
"%s%d", ENTRY_PREFIX, param_code);
if (nvlist_add_nvlist(data->log_data, entry_num, entry) != 0) {
nvlist_free(entry);
return (SES_LOG_FAILED_NV_ENTRY);
}
nvlist_free(entry);
entry_added = 1;
(data->number_log_entries)++;
log_param_ptr += param_len;
}
if (entry_added) {
(void) strncpy(data->last_log_entry, save_buffer, MAXNAMELEN);
}
return (0);
}
static void
set_scsi_pt_data_out(struct uscsi_cmd *uscsi, const unsigned char *dxferp,
int dxfer_len)
{
if (dxfer_len > 0) {
uscsi->uscsi_bufaddr = (char *)dxferp;
uscsi->uscsi_buflen = dxfer_len;
uscsi->uscsi_flags = USCSI_WRITE | USCSI_ISOLATE |
USCSI_RQENABLE;
}
}
static int
sg_ll_mode_sense10(int sg_fd, void * resp, int mx_resp_len)
{
int res, ret;
unsigned char modesCmdBlk[MODE_SENSE10_CMDLEN] =
{SCMD_MODE_SENSE_G1, 0, 0, 0, 0, 0, 0, 0, 0, 0};
unsigned char sense_b[SENSE_BUFF_LEN];
struct uscsi_cmd uscsi;
modesCmdBlk[1] = 0;
modesCmdBlk[2] = 0;
modesCmdBlk[3] = 0;
modesCmdBlk[7] = (unsigned char) ((mx_resp_len >> 8) & 0xff);
modesCmdBlk[8] = (unsigned char) (mx_resp_len & 0xff);
construct_scsi_pt_obj(&uscsi);
set_scsi_pt_cdb(&uscsi, modesCmdBlk, sizeof (modesCmdBlk));
set_scsi_pt_sense(&uscsi, sense_b, sizeof (sense_b));
set_scsi_pt_data_in(&uscsi, (unsigned char *) resp, mx_resp_len);
res = do_scsi_pt(&uscsi, sg_fd, DEF_PT_TIMEOUT);
if (res) {
ret = res;
} else {
ret = uscsi.uscsi_status;
}
return (ret);
}
static int
sg_ll_mode_select10(int sg_fd, void * paramp, int param_len)
{
int res, ret;
unsigned char modesCmdBlk[MODE_SELECT10_CMDLEN] =
{SCMD_MODE_SELECT_G1, 0, 0, 0, 0, 0, 0, 0, 0, 0};
unsigned char sense_b[SENSE_BUFF_LEN];
struct uscsi_cmd uscsi;
modesCmdBlk[1] = 0;
modesCmdBlk[7] = (unsigned char)((param_len >> 8) & 0xff);
modesCmdBlk[8] = (unsigned char)(param_len & 0xff);
construct_scsi_pt_obj(&uscsi);
set_scsi_pt_cdb(&uscsi, modesCmdBlk, sizeof (modesCmdBlk));
set_scsi_pt_sense(&uscsi, sense_b, sizeof (sense_b));
set_scsi_pt_data_out(&uscsi, (unsigned char *) paramp, param_len);
res = do_scsi_pt(&uscsi, sg_fd, DEF_PT_TIMEOUT);
if (res) {
ret = res;
} else {
ret = uscsi.uscsi_status;
}
return (ret);
}
static int
sg_mode_page_offset(const unsigned char *resp, int resp_len)
{
int bd_len;
int calc_len;
int offset;
if ((NULL == resp) || (resp_len < 8)) {
return (-1);
}
calc_len = (resp[0] << 8) + resp[1] + 2;
bd_len = (resp[6] << 8) + resp[7];
offset = bd_len + MODE10_RESP_HDR_LEN;
if ((offset + 2) > resp_len) {
offset = -1;
} else if ((offset + 2) > calc_len) {
offset = -1;
}
return (offset);
}
static int
clear_log(int sg_fd, ses_log_call_t *data)
{
int res, alloc_len, off;
int md_len;
int read_in_len = 0;
unsigned char ref_md[MAX_ALLOC_LEN];
struct log_clear_control_struct clear_data;
long myhostid;
int error = 0;
long poll_time;
char seq_num_str[10];
unsigned long seq_num = 0;
(void) memset(&clear_data, 0, sizeof (clear_data));
clear_data.pageControls = 0x40;
clear_data.subpage_code = 0;
clear_data.page_lengthLower = 0x16;
myhostid = gethostid();
clear_data.host_id[12] = (myhostid & 0xff000000) >> 24;
clear_data.host_id[13] = (myhostid & 0xff0000) >> 16;
clear_data.host_id[14] = (myhostid & 0xff00) >> 8;
clear_data.host_id[15] = myhostid & 0xff;
poll_time = data->poll_time / 1000000000;
poll_time = poll_time + 300;
clear_data.timeout[0] = (poll_time & 0xff00) >> 8;
clear_data.timeout[1] = poll_time & 0xff;
if (strlen(data->last_log_entry) == SES_LOG_VALID_LOG_SIZE) {
(void) strncpy(seq_num_str,
(const char *) data->last_log_entry +
SES_LOG_SEQ_NUM_START, 8);
seq_num = strtoul(seq_num_str, 0, 16);
}
clear_data.seq_clear[0] = (seq_num & 0xff000000) >> 24;
clear_data.seq_clear[1] = (seq_num & 0xff0000) >> 16;
clear_data.seq_clear[2] = (seq_num & 0xff00) >> 8;
clear_data.seq_clear[3] = (seq_num & 0xff);
read_in_len = sizeof (clear_data);
(void) memset(ref_md, 0, MAX_ALLOC_LEN);
alloc_len = MAX_ALLOC_LEN;
res = sg_ll_mode_sense10(sg_fd, ref_md, alloc_len);
if (0 != res) {
error = SES_LOG_FAILED_MODE_SENSE;
return (error);
}
off = sg_mode_page_offset(ref_md, alloc_len);
if (off < 0) {
error = SES_LOG_FAILED_MODE_SENSE_OFFSET;
return (error);
}
md_len = (ref_md[0] << 8) + ref_md[1] + 2;
ref_md[0] = 0;
ref_md[1] = 0;
if (md_len > alloc_len) {
error = SES_LOG_FAILED_BAD_DATA_LEN;
return (error);
}
if ((md_len - off) != read_in_len) {
error = SES_LOG_FAILED_BAD_CONTENT_LEN;
return (error);
}
if ((clear_data.pageControls & 0x40) != (ref_md[off] & 0x40)) {
error = SES_LOG_FAILED_FORMAT_PAGE_ERR;
return (error);
}
(void) memcpy(ref_md + off, (const void *) &clear_data,
sizeof (clear_data));
res = sg_ll_mode_select10(sg_fd, ref_md, md_len);
if (res != 0) {
error = SES_LOG_FAILED_MODE_SELECT;
return (error);
}
return (error);
}
static int
gather_data(char *device_name, ses_log_call_t *data)
{
int sg_fd;
int resp_len, res;
unsigned char rsp_buff[MAX_ALLOC_LEN];
int error;
if ((sg_fd = open_device(device_name)) < 0) {
return (SES_LOG_FAILED_TO_OPEN_DEVICE);
}
(void) memset(rsp_buff, 0, sizeof (rsp_buff));
resp_len = 0x8000;
res = read_log(sg_fd, rsp_buff, resp_len);
if (res != 0) {
(void) close(sg_fd);
return (SES_LOG_FAILED_TO_READ_DEVICE);
}
error = save_logs(rsp_buff, data);
if (error != 0) {
(void) close(sg_fd);
return (error);
}
error = clear_log(sg_fd, data);
(void) close(sg_fd);
return (error);
}
int
access_ses_log(ses_log_call_t *data)
{
char real_path[MAXPATHLEN];
struct stat buffer;
int error;
data->log_data = NULL;
data->number_log_entries = 0;
if (*data->target_path == '\0') {
return (SES_LOG_FAILED_NULL_TARGET_PATH);
}
(void) snprintf(real_path, sizeof (real_path), "/devices%s:ses",
data->target_path);
if (stat(real_path, &buffer) != 0) {
(void) snprintf(real_path, sizeof (real_path), "/devices%s:0",
data->target_path);
if (stat(real_path, &buffer) != 0) {
return (SES_LOG_FAILED_BAD_TARGET_PATH);
}
}
error = gather_data(real_path, data);
data->size_of_log_entries =
data->number_log_entries * SES_LOG_VALID_LOG_SIZE;
return (error);
}