blob: 8e15f1fd6aacd66f9faca3b69802ef8a91e31e74 [file] [log] [blame]
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2014 by Amaury Pouly
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#define _XOPEN_SOURCE 500
#define _DEFAULT_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include "rbscsi.h"
static void misc_std_printf(void *user, const char *fmt, ...)
{
(void) user;
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
#if defined(_WIN32) || defined(__WIN32__)
#define RB_SCSI_WINDOWS
#include <windows.h>
#include <ntddscsi.h>
#include <stdint.h>
typedef HANDLE rb_scsi_handle_t;
#elif defined(__linux) || defined(__linux__) || defined(linux)
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <scsi/sg.h>
#include <dirent.h>
#include <linux/limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define RB_SCSI_LINUX
typedef int rb_scsi_handle_t;
#else
typedef void *rb_scsi_handle_t;
#endif
struct rb_scsi_device_t
{
rb_scsi_handle_t handle;
void *user;
rb_scsi_printf_t printf;
unsigned flags;
};
/* Linux */
#ifdef RB_SCSI_LINUX
/* the values for hdr.driver_status are not defined in public headers */
#define DRIVER_SENSE 0x08
rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user,
rb_scsi_printf_t printf)
{
if(printf == NULL)
printf = misc_std_printf;
int fd = open(path, (flags & RB_SCSI_READ_ONLY) ? O_RDONLY : O_RDWR);
if(fd < 0)
{
if(flags & RB_SCSI_DEBUG)
printf(user, "rb_scsi: open failed: %s\n", strerror(errno));
return NULL;
}
rb_scsi_device_t dev = malloc(sizeof(struct rb_scsi_device_t));
dev->flags = flags;
dev->handle = fd;
dev->user = user;
dev->printf = printf ? printf : misc_std_printf;
return dev;
}
int rb_scsi_raw_xfer(rb_scsi_device_t dev, struct rb_scsi_raw_cmd_t *raw)
{
#define rb_printf(...) \
do{ if(dev->flags & RB_SCSI_DEBUG) dev->printf(dev->user, __VA_ARGS__); }while(0)
sg_io_hdr_t hdr;
memset(&hdr, 0, sizeof(hdr));
/* prepare transfer descriptor */
hdr.interface_id = 'S';
switch(raw->dir)
{
case RB_SCSI_NONE: hdr.dxfer_direction = SG_DXFER_NONE; break;
case RB_SCSI_READ: hdr.dxfer_direction = SG_DXFER_FROM_DEV; break;
case RB_SCSI_WRITE: hdr.dxfer_direction = SG_DXFER_TO_DEV; break;
default: rb_printf("rb_scsi: invalid direction"); return -1;
}
hdr.cmd_len = raw->cdb_len;
hdr.mx_sb_len = raw->sense_len;
hdr.dxfer_len = raw->buf_len;
hdr.dxferp = raw->buf;
hdr.cmdp = raw->cdb;
hdr.sbp = raw->sense;
hdr.timeout = raw->tmo * 1000;
hdr.flags = SG_FLAG_LUN_INHIBIT;
/* do raw command */
if(ioctl(dev->handle, SG_IO, &hdr) < 0)
{
raw->status = errno;
rb_printf("rb_scsi: ioctl failed: %s\n", strerror(raw->status));
return RB_SCSI_OS_ERROR;
}
/* error handling is weird: host status has the priority */
if(hdr.host_status)
{
rb_printf("rb_scsi: host error: %d\n", hdr.host_status);
raw->status = hdr.host_status;
return RB_SCSI_ERROR;
}
raw->status = hdr.status & 0x7e;
raw->buf_len -= hdr.resid;
raw->sense_len = hdr.sb_len_wr;
/* driver error can report sense */
if((hdr.driver_status & 0xf) == DRIVER_SENSE)
{
rb_printf("rb_scsi: driver status reported sense\n");
return RB_SCSI_SENSE;
}
/* otherwise errors */
if(hdr.driver_status)
{
rb_printf("rb_scsi: driver error: %d\n", hdr.driver_status);
return RB_SCSI_ERROR;
}
/* scsi status can imply sense */
if(raw->status == RB_SCSI_CHECK_CONDITION || raw->status == RB_SCSI_COMMAND_TERMINATED)
return RB_SCSI_SENSE;
return raw->status ? RB_SCSI_STATUS : RB_SCSI_OK;
#undef rb_printf
}
void rb_scsi_close(rb_scsi_device_t dev)
{
close(dev->handle);
free(dev);
}
static int is_hctl(const char *name)
{
char *end;
strtoul(name, &end, 0); /* h */
if(*end != ':')
return 0;
strtoul(end + 1, &end, 0); /* c */
if(*end != ':')
return 0;
strtoul(end + 1, &end, 0); /* t */
if(*end != ':')
return 0;
strtoul(end + 1, &end, 0); /* l */
return *end == 0;
}
static int _resolve_link_dev_path(char *path, size_t pathsz)
{
/* make sure it is a directory */
struct stat st;
if(stat(path, &st) < 0)
return 0;
if(!S_ISDIR(st.st_mode))
return 0;
if(chdir(path) < 0)
return 0;
if(getcwd(path, pathsz) == NULL)
return 0;
return 1;
}
static int resolve_link_dev_path(char *path, size_t pathsz)
{
/* change directory, ask the current path and resolve current directory */
char curdir[PATH_MAX];
if(getcwd(curdir, sizeof(curdir)) == NULL)
return 0;
int ret = _resolve_link_dev_path(path, pathsz);
chdir(curdir);
return ret;
}
static int scan_resolve_first_dev_path(char *path, size_t pathsz)
{
size_t pathlen = strlen(path);
char *pathend = path + pathlen;
DIR *dir = opendir(path);
if(dir == NULL)
return 0;
struct dirent *d;
int ret = 0;
while((d = readdir(dir)))
{
if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
/* we found the entry, if it is a symlink, resolve it, otherwise it must be a directory */
if(d->d_type == DT_LNK)
{
snprintf(pathend, pathsz - pathlen, "/%s", d->d_name);
ret = resolve_link_dev_path(path, pathsz);
}
else if(d->d_type == DT_DIR)
{
snprintf(path, pathsz, "/dev/%s", d->d_name);
ret = 1;
}
break;
}
closedir(dir);
return ret;
}
static int scan_resolve_dev_path(char *path, size_t pathsz, const char *match)
{
size_t pathlen = strlen(path);
char *pathend = path + pathlen;
DIR *dir = opendir(path);
if(dir == NULL)
return 0;
struct dirent *d;
int ret = 0;
while((d = readdir(dir)))
{
if(strcmp(d->d_name, match))
continue;
/* we found the entry, there are two case:
* - directory: we need to scan it and find the first entry
* - symlink: we need to see where it goes and extract the basename */
snprintf(pathend, pathsz - pathlen, "/%s", d->d_name);
if(d->d_type == DT_DIR)
ret = scan_resolve_first_dev_path(path, pathsz);
else if(d->d_type == DT_LNK)
ret = resolve_link_dev_path(path, pathsz);
break;
}
closedir(dir);
return ret;
}
static char *read_file_or_null(const char *path)
{
FILE *f = fopen(path, "r");
if(f == NULL)
return NULL;
char buffer[1024];
if(fgets(buffer, sizeof(buffer), f) == NULL)
{
fclose(f);
return NULL;
}
fclose(f);
/* the kernel appends a '\n' at the end, remove it */
size_t len = strlen(buffer);
if(len > 0 && buffer[len - 1] == '\n')
buffer[len - 1] = 0;
return strdup(buffer);
}
struct rb_scsi_devent_t *rb_scsi_list(void)
{
/* list devices in /sys/bus/scsi/devices
* we only keep entries of the form h:c:t:l */
#define SYS_SCSI_DEV_PATH "/sys/bus/scsi/devices"
DIR *dir = opendir(SYS_SCSI_DEV_PATH);
if(dir == NULL)
return NULL;
struct dirent *d;
struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t));
dev[0].scsi_path = NULL;
dev[0].block_path = NULL;
int nr_dev = 0;
while((d = readdir(dir)))
{
if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
/* make sure the name is of the form h:c:t:l, we do not want targets or hosts */
if(!is_hctl(d->d_name))
continue;
/* we now need to scan the directory to find the block and scsi generic device path:
* block: there should be a 'block' entry
* scsi: there should be a 'scsi_generic' entry
* Both entries can either be a symlink to the devide, or a directory with a single entry */
char scsi_path[PATH_MAX];
snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s", d->d_name);
if(!scan_resolve_dev_path(scsi_path, sizeof(scsi_path), "scsi_generic"))
continue;
char block_path[PATH_MAX];
snprintf(block_path, sizeof(block_path), SYS_SCSI_DEV_PATH "/%s", d->d_name);
if(!scan_resolve_dev_path(block_path, sizeof(block_path), "block"))
block_path[0] = 0;
dev = realloc(dev, (2 + nr_dev) * sizeof(struct rb_scsi_devent_t));
dev[nr_dev].scsi_path = strdup(scsi_path);
dev[nr_dev].block_path = block_path[0] == 0 ? NULL : strdup(block_path);
/* fill vendor/model/rev */
snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s/vendor", d->d_name);
dev[nr_dev].vendor = read_file_or_null(scsi_path);
snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s/model", d->d_name);
dev[nr_dev].model = read_file_or_null(scsi_path);
snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s/rev", d->d_name);
dev[nr_dev].rev = read_file_or_null(scsi_path);
/* sentinel */
dev[++nr_dev].scsi_path = NULL;
dev[nr_dev].block_path = NULL;
}
closedir(dir);
return dev;
}
/* Windows */
#elif defined(RB_SCSI_WINDOWS)
/* return either path or something allocated with malloc() */
static const char *map_to_physical_drive(const char *path, unsigned flags, void *user,
rb_scsi_printf_t printf)
{
/* don't do anything if path starts with '\' */
if(path[0] == '\\')
return path;
/* Convert to UNC path (C: -> \\.\C:) otherwise it won't work) */
char *unc_path = malloc(strlen(path) + 5);
sprintf(unc_path, "\\\\.\\%s", path);
if(flags & RB_SCSI_DEBUG)
printf(user, "rb_scsi: map to UNC path: %s\n", unc_path);
return unc_path;
}
rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user,
rb_scsi_printf_t printf)
{
if(printf == NULL)
printf = misc_std_printf;
/* magic to auto-detect physical drive */
const char *open_path = map_to_physical_drive(path, flags, user, printf);
HANDLE h = CreateFileA(open_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING, NULL);
/* free path if it was allocated */
if(open_path != path)
free((char *)open_path);
if(h == INVALID_HANDLE_VALUE)
{
if(flags & RB_SCSI_DEBUG)
{
LPSTR msg = NULL;
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL);
printf(user, "rb_scsi: open failed: %s\n", msg);
LocalFree(msg);
}
return NULL;
}
rb_scsi_device_t dev = malloc(sizeof(struct rb_scsi_device_t));
dev->flags = flags;
dev->handle = h;
dev->user = user;
dev->printf = printf ? printf : misc_std_printf;
return dev;
}
int rb_scsi_raw_xfer(rb_scsi_device_t dev, struct rb_scsi_raw_cmd_t *raw)
{
#define rb_printf(...) \
do{ if(dev->flags & RB_SCSI_DEBUG) dev->printf(dev->user, __VA_ARGS__); }while(0)
/* we need to allocate memory for SCSI_PASS_THROUGH_WITH_BUFFERS and the
* data and sense */
DWORD length = sizeof(SCSI_PASS_THROUGH) + raw->buf_len + raw->sense_len;
DWORD returned = 0;
SCSI_PASS_THROUGH *spt = malloc(length);
memset(spt, 0, length);
spt->Length = sizeof(SCSI_PASS_THROUGH);
spt->PathId = 0;
spt->TargetId = 0;
spt->Lun = 0;
spt->CdbLength = raw->cdb_len;
memcpy(spt->Cdb, raw->cdb, raw->cdb_len);
spt->SenseInfoLength = raw->sense_len;
spt->SenseInfoOffset = spt->Length; /* Sense after header */
switch(raw->dir)
{
case RB_SCSI_NONE: spt->DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; break;
case RB_SCSI_READ: spt->DataIn = SCSI_IOCTL_DATA_IN; break;
case RB_SCSI_WRITE: spt->DataIn = SCSI_IOCTL_DATA_OUT; break;
default:
free(spt);
rb_printf("rb_scsi: invalid direction"); return -1;
}
spt->DataTransferLength = raw->buf_len;
spt->DataBufferOffset = spt->SenseInfoOffset + spt->SenseInfoLength; /* Data after Sense */
spt->TimeOutValue = raw->tmo;
/* on write, copy data to buffer */
if(raw->dir == RB_SCSI_WRITE)
memcpy((BYTE *)spt + spt->DataBufferOffset, raw->buf, raw->buf_len);
BOOL status = DeviceIoControl(dev->handle, IOCTL_SCSI_PASS_THROUGH,
spt, length, spt, length, &returned, FALSE);
if(!status)
{
if(dev->flags & RB_SCSI_DEBUG)
{
LPSTR msg = NULL;
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL);
printf(dev->user, "rb_scsi: ioctl failed: %s\n", msg);
LocalFree(msg);
}
free(spt);
return RB_SCSI_OS_ERROR;
}
/* on read, copy data from buffer */
if(raw->dir == RB_SCSI_READ)
{
raw->buf_len = spt->DataTransferLength;
memcpy(raw->buf, (BYTE *)spt + spt->DataBufferOffset, raw->buf_len);
}
/* check status */
raw->status = spt->ScsiStatus;
if(raw->status == RB_SCSI_CHECK_CONDITION || raw->status == RB_SCSI_COMMAND_TERMINATED)
{
memcpy(raw->sense, (BYTE *)spt + spt->SenseInfoOffset, raw->sense_len);
free(spt);
return RB_SCSI_SENSE;
}
else
raw->sense_len = 0;
free(spt);
return raw->status ? RB_SCSI_STATUS : RB_SCSI_OK;
#undef rb_printf
}
void rb_scsi_close(rb_scsi_device_t dev)
{
CloseHandle(dev->handle);
free(dev);
}
struct rb_scsi_devent_t *rb_scsi_list(void)
{
/* unimplemented */
struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t));
dev[0].scsi_path = NULL;
dev[0].block_path = NULL;
return dev;
}
/* other targets */
#else
rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user,
rb_scsi_printf_t printf)
{
if(printf == NULL)
printf = misc_std_printf;
(void) path;
if(flags & RB_SCSI_DEBUG)
printf(user, "rb_scsi: unimplemented on this platform\n");
return NULL;
}
int rb_scsi_raw_xfer(rb_scsi_device_t dev, struct rb_scsi_raw_cmd_t *raw)
{
return RB_SCSI_ERROR;
}
void rb_scsi_close(rb_scsi_device_t dev)
{
free(dev);
}
struct rb_scsi_devent_t *rb_scsi_list(void)
{
/* unimplemented */
struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t));
dev[0].scsi_path = NULL;
dev[0].block_path = NULL;
return dev;
}
#endif
void rb_scsi_decode_sense(rb_scsi_device_t dev, void *_sense, int sense_len)
{
#define rb_printf(...) \
do{ if(dev->flags & RB_SCSI_DEBUG) dev->printf(dev->user, __VA_ARGS__); }while(0)
uint8_t *sense = _sense;
uint8_t type = sense[0] & 0x7f;
switch(type)
{
case 0x70: case 0x73: rb_printf("Current sense: "); break;
case 0x71: case 0x72: rb_printf("Previous sense: "); break;
default: rb_printf("Unknown sense\n"); return;
}
unsigned key = sense[2] & 0xf;
switch(key)
{
case 0: rb_printf("no sense"); break;
case 1: rb_printf("recovered error"); break;
case 2: rb_printf("not ready"); break;
case 3: rb_printf("medium error"); break;
case 4: rb_printf("hardware error"); break;
case 5: rb_printf("illegal request"); break;
case 6: rb_printf("unit attention"); break;
case 7: rb_printf("data protect"); break;
case 8: rb_printf("blank check"); break;
case 9: rb_printf("vendor specific"); break;
case 10: rb_printf("copy aborted"); break;
case 11: rb_printf("aborted command"); break;
default: rb_printf("unknown key"); break;
}
rb_printf("\n");
#undef rb_printf
}
void rb_scsi_free_list(struct rb_scsi_devent_t *list)
{
if(list == NULL)
return;
for(struct rb_scsi_devent_t *p = list; p->scsi_path; p++)
{
free(p->scsi_path);
if(p->block_path)
free(p->block_path);
if(p->vendor)
free(p->vendor);
if(p->model)
free(p->model);
if(p->rev)
free(p->rev);
}
free(list);
}