/*************************************************************************** * __________ __ ___. * 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 #include #include #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 #include #include typedef HANDLE rb_scsi_handle_t; #elif defined(__linux) || defined(__linux__) || defined(linux) #include #include #include #include #include #include #include #include #include #include #include #include #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); }