summaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
authorAmaury Pouly <amaury.pouly@gmail.com>2012-11-03 02:29:00 +0100
committerAmaury Pouly <amaury.pouly@gmail.com>2012-11-03 02:29:00 +0100
commit3e1c4926806ca685ab591cb6948fa3aa5014591c (patch)
tree50c6a54ce071eb2e0313e8b32ff28a5c9097f80f /utils
parentcb09e369fb1ed41b5724a45929a49b42c2718203 (diff)
downloadrockbox-3e1c4926806ca685ab591cb6948fa3aa5014591c.tar.gz
rockbox-3e1c4926806ca685ab591cb6948fa3aa5014591c.tar.bz2
rockbox-3e1c4926806ca685ab591cb6948fa3aa5014591c.zip
Introduce scsitool for sony nwz players
This tool can send vendor specific scsi commands to sony nwz players such as getting serial number, model id, device info, and others. It can potentially be used to get some private keys stored on the device but probably not the KAS used to encrypt firmware upgrades images(UPG). Change-Id: Ia49c1edf8d421b20c4e9afeb1192e00e06eb6047
Diffstat (limited to 'utils')
-rw-r--r--utils/nwztools/scsitools/Makefile20
-rw-r--r--utils/nwztools/scsitools/misc.c53
-rw-r--r--utils/nwztools/scsitools/misc.h50
-rw-r--r--utils/nwztools/scsitools/scsitool.c516
4 files changed, 639 insertions, 0 deletions
diff --git a/utils/nwztools/scsitools/Makefile b/utils/nwztools/scsitools/Makefile
new file mode 100644
index 0000000000..ed4bc88d0b
--- /dev/null
+++ b/utils/nwztools/scsitools/Makefile
@@ -0,0 +1,20 @@
+DEFINES=
+CC=gcc
+LD=gcc
+CFLAGS=-g -std=c99 -W -Wall $(DEFINES)
+LDFLAGS=-lsgutils2
+BINS=scsitool
+
+all: $(BINS)
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+scsitool: scsitool.o misc.o
+ $(LD) -o $@ $^ $(LDFLAGS)
+
+clean:
+ rm -fr *.o
+
+veryclean:
+ rm -rf $(BINS)
diff --git a/utils/nwztools/scsitools/misc.c b/utils/nwztools/scsitools/misc.c
new file mode 100644
index 0000000000..108235e7fd
--- /dev/null
+++ b/utils/nwztools/scsitools/misc.c
@@ -0,0 +1,53 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2010 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.
+ *
+ ****************************************************************************/
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <ctype.h>
+#include "misc.h"
+
+char OFF[] = { 0x1b, 0x5b, 0x31, 0x3b, '0', '0', 0x6d, '\0' };
+
+char GREY[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '0', 0x6d, '\0' };
+char RED[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '1', 0x6d, '\0' };
+char GREEN[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '2', 0x6d, '\0' };
+char YELLOW[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '3', 0x6d, '\0' };
+char BLUE[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '4', 0x6d, '\0' };
+
+static bool g_color_enable = true;
+
+void *xmalloc(size_t s)
+{
+ void * r = malloc(s);
+ if(!r) bugp("malloc");
+ return r;
+}
+
+void enable_color(bool enable)
+{
+ g_color_enable = enable;
+}
+
+void color(color_t c)
+{
+ if(g_color_enable)
+ printf("%s", (char *)c);
+}
diff --git a/utils/nwztools/scsitools/misc.h b/utils/nwztools/scsitools/misc.h
new file mode 100644
index 0000000000..035b0ef8c1
--- /dev/null
+++ b/utils/nwztools/scsitools/misc.h
@@ -0,0 +1,50 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2010 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.
+ *
+ ****************************************************************************/
+#ifndef __MISC_H__
+#define __MISC_H__
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#define _STR(a) #a
+#define STR(a) _STR(a)
+
+#define bug(...) do { fprintf(stderr,"["__FILE__":"STR(__LINE__)"]ERROR: "__VA_ARGS__); exit(1); } while(0)
+#define bugp(...) do { fprintf(stderr, __VA_ARGS__); perror(" "); exit(1); } while(0)
+
+#define ROUND_UP(val, round) ((((val) + (round) - 1) / (round)) * (round))
+
+typedef char color_t[];
+
+extern color_t OFF, GREY, RED, GREEN, YELLOW, BLUE;
+void *xmalloc(size_t s);
+void color(color_t c);
+void enable_color(bool enable);
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#define cprintf(col, ...) do {color(col); printf(__VA_ARGS__); }while(0)
+
+#define cprintf_field(str1, ...) do{ cprintf(GREEN, str1); cprintf(YELLOW, __VA_ARGS__); }while(0)
+
+#endif /* __MISC_H__ */
diff --git a/utils/nwztools/scsitools/scsitool.c b/utils/nwztools/scsitools/scsitool.c
new file mode 100644
index 0000000000..6848fb90de
--- /dev/null
+++ b/utils/nwztools/scsitools/scsitool.c
@@ -0,0 +1,516 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2012 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.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <scsi/scsi.h>
+#include <scsi/sg_lib.h>
+#include <scsi/sg_pt.h>
+#include "misc.h"
+
+bool g_debug = false;
+bool g_force = false;
+char *g_out_prefix = NULL;
+int g_dev_fd = 0;
+
+#define let_the_force_flow(x) do { if(!g_force) return x; } while(0)
+#define continue_the_force(x) if(x) let_the_force_flow(x)
+
+#define check_field(v_exp, v_have, str_ok, str_bad) \
+ if((v_exp) != (v_have)) \
+ { cprintf(RED, str_bad); let_the_force_flow(__LINE__); } \
+ else { cprintf(RED, str_ok); }
+
+#define errorf(...) do { cprintf(GREY, __VA_ARGS__); return __LINE__; } while(0)
+
+#if 0
+void *buffer_alloc(int sz)
+{
+#ifdef SG_LIB_MINGW
+ unsigned psz = getpagesize();
+#else
+ unsigned psz = sysconf(_SC_PAGESIZE); /* was getpagesize() */
+#endif
+ void *buffer = malloc(sz + psz);
+ return (void *)(((ptrdiff_t)(buffer + psz - 1)) & ~(psz - 1));
+}
+#else
+void *buffer_alloc(int sz)
+{
+ return malloc(sz);
+}
+#endif
+
+static void print_hex(void *_buffer, int buffer_size)
+{
+ uint8_t *buffer = _buffer;
+ for(int i = 0; i < buffer_size; i += 16)
+ {
+ for(int j = 0; j < 16; j++)
+ {
+ if(i + j < buffer_size)
+ cprintf(YELLOW, " %02x", buffer[i + j]);
+ else
+ cprintf(YELLOW, " ");
+ }
+ printf(" ");
+ for(int j = 0; j < 16; j++)
+ {
+ if(i + j < buffer_size)
+ cprintf(RED, "%c", isprint(buffer[i + j]) ? buffer[i + j] : '.');
+ else
+ cprintf(RED, " ");
+ }
+ printf("\n");
+ }
+}
+
+/* Do read */
+#define DO_READ (1 << 1)
+/* Do write */
+#define DO_WRITE (1 << 2)
+
+/* returns <0 on error and status otherwise */
+int do_scsi(uint8_t *cdb, int cdb_size, unsigned flags, void *sense, int *sense_size, void *buffer, int *buf_size)
+{
+ char error[256];
+ struct sg_pt_base *obj = construct_scsi_pt_obj();
+ if(obj == NULL)
+ {
+ cprintf(GREY, "construct_scsi_pt_obj failed\n");
+ return 1;
+ }
+ set_scsi_pt_cdb(obj, cdb, cdb_size);
+ if(sense)
+ set_scsi_pt_sense(obj, sense, *sense_size);
+ if(flags & DO_READ)
+ set_scsi_pt_data_in(obj, buffer, *buf_size);
+ if(flags & DO_WRITE)
+ set_scsi_pt_data_out(obj, buffer, *buf_size);
+ int ret = do_scsi_pt(obj, g_dev_fd, 1, 0);
+ switch(get_scsi_pt_result_category(obj))
+ {
+ case SCSI_PT_RESULT_SENSE:
+ case SCSI_PT_RESULT_GOOD:
+ ret = get_scsi_pt_status_response(obj);
+ break;
+ case SCSI_PT_RESULT_STATUS:
+ cprintf(GREY, "Status error: %d (", get_scsi_pt_status_response(obj));
+ sg_print_scsi_status(get_scsi_pt_status_response(obj));
+ printf(")\n");
+ break;
+ case SCSI_PT_RESULT_TRANSPORT_ERR:
+ cprintf(GREY, "Transport error: %s\n", get_scsi_pt_transport_err_str(obj, 256, error));
+ ret = -2;
+ break;
+ case SCSI_PT_RESULT_OS_ERR:
+ cprintf(GREY, "OS error: %s\n", get_scsi_pt_os_err_str(obj, 256, error));
+ ret = -3;
+ break;
+ default:
+ cprintf(GREY, "Unknown error\n");
+ break;
+ }
+
+ if(sense)
+ *sense_size = get_scsi_pt_sense_len(obj);
+ if(flags & (DO_WRITE | DO_READ))
+ *buf_size -= get_scsi_pt_resid(obj);
+
+ destruct_scsi_pt_obj(obj);
+ return ret;
+}
+
+int do_sense_analysis(int status, uint8_t *sense, int sense_size)
+{
+ cprintf_field("Status:", " "); fflush(stdout);
+ sg_print_scsi_status(status);
+ cprintf_field("\nSense:", " "); fflush(stdout);
+ sg_print_sense(NULL, sense, sense_size, 0);
+ if(status == GOOD)
+ return 0;
+ return status;
+}
+
+int do_dnk_cmd(uint32_t cmd, uint8_t sub_cmd, void *buffer, int *buffer_size)
+{
+ uint8_t cdb[12] = {0xdd, 0, 0, 0, 0, 0, 0, 0xbc, 0, 0, 0, 0};
+ cdb[10] = cmd;
+ cdb[11] = sub_cmd;
+ cdb[8] = (*buffer_size) >> 8;
+ cdb[9] = (*buffer_size) & 0xff;
+
+ uint8_t sense[32];
+ int sense_size = 32;
+
+ int ret = do_scsi(cdb, 12, DO_READ, sense, &sense_size, buffer, buffer_size);
+ if(ret < 0)
+ return ret;
+ ret = do_sense_analysis(ret, sense, sense_size);
+ if(ret)
+ return ret;
+ return 0;
+}
+
+#define DNK_EXACT_LENGTH (1 << 0)
+#define DNK_STRING (1 << 1)
+#define DNK_UINT32 (1 << 2)
+
+struct dnk_prop_t
+{
+ char *name;
+ uint8_t cmd;
+ uint8_t subcmd;
+ int size;
+ unsigned flags;
+};
+
+struct dnk_prop_t dnk_prop_list[] =
+{
+ { "serial_num", 0x23, 1, 8, DNK_STRING},
+ { "model_id", 0x23, 4, 4, DNK_EXACT_LENGTH | DNK_UINT32},
+ { "product_id", 0x23, 6, 12, DNK_STRING},
+ { "destination", 0x23, 8, 4, DNK_EXACT_LENGTH | DNK_UINT32},
+ { "model_id2", 0x23, 9, 4, DNK_EXACT_LENGTH | DNK_UINT32},
+ { "dev_info", 0x12, 0, 64, DNK_STRING},
+};
+
+#define NR_DNK_PROPS (sizeof(dnk_prop_list) / sizeof(dnk_prop_list[0]))
+
+int get_dnk_prop(int argc, char **argv)
+{
+ if(argc != 1 && argc != 4)
+ {
+ printf("You must specify a known property name or a full property specification:\n");
+ printf("Full usage: <cmd> <subcmd> <size> <flags>\n");
+ printf("Property usage: <prop>\n");
+ printf("Properties:");
+ for(unsigned i = 0; i < NR_DNK_PROPS; i++)
+ printf(" %s", dnk_prop_list[i].name);
+ printf("\n");
+ return 1;
+ }
+
+ struct dnk_prop_t prop;
+ memset(&prop, 0, sizeof(prop));
+ if(argc == 1)
+ {
+ for(unsigned i = 0; i < NR_DNK_PROPS; i++)
+ if(strcmp(dnk_prop_list[i].name, argv[0]) == 0)
+ prop = dnk_prop_list[i];
+ if(prop.name == NULL)
+ {
+ cprintf(GREY, "Unknown property '%s'\n", argv[0]);
+ return 1;
+ }
+ }
+ else
+ {
+ prop.cmd = strtoul(argv[0], NULL, 0);
+ prop.subcmd = strtoul(argv[1], NULL, 0);
+ prop.size = strtoul(argv[2], NULL, 0);
+ prop.flags = strtoul(argv[3], NULL, 0);
+ }
+
+ char *buffer = buffer_alloc(prop.size + 1);
+ int buffer_size = prop.size;
+ int ret = do_dnk_cmd(prop.cmd, prop.subcmd, buffer, &buffer_size);
+ if(ret)
+ return ret;
+ if(buffer_size == 0)
+ {
+ cprintf(GREY, "Device didn't send any data\n");
+ return 1;
+ }
+ if((prop.flags & DNK_EXACT_LENGTH) && buffer_size != prop.size)
+ {
+ cprintf(GREY, "Device didn't send the expected amount of data\n");
+ return 2;
+ }
+ buffer[buffer_size] = 0;
+ if(prop.flags & DNK_STRING)
+ cprintf_field("Property: ", "%s\n", buffer);
+ else if(prop.flags & DNK_UINT32)
+ cprintf_field("Property: ", "0x%x\n", *(uint32_t *)buffer);
+ else
+ {
+ cprintf(GREEN, "Property:\n");
+ print_hex(buffer, buffer_size);
+ }
+ return 0;
+}
+
+struct dpcc_prop_t
+{
+ char *user_name;
+ char name[7];
+ uint8_t cdb1;
+ int size;
+};
+
+struct dpcc_prop_t dpcc_prop_list[] =
+{
+ { "dev_info", "DEVINFO", 0, 0x80 },
+};
+
+#define NR_DPCC_PROPS (sizeof(dpcc_prop_list) / sizeof(dpcc_prop_list[0]))
+
+int do_dpcc_cmd(uint32_t cmd, struct dpcc_prop_t *prop, void *buffer, int *buffer_size)
+{
+ uint8_t cdb[12] = {0xfb, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ cdb[2] = cmd;
+ if(cmd == 0)
+ {
+ strncpy((char *)(cdb + 3), prop->name, 7); // warning: erase cdb[10] !
+ cdb[1] = prop->cdb1;
+ if(prop->cdb1 & 1)
+ cdb[10] = (*buffer_size + 15) / 16;
+ else
+ cdb[10] = *buffer_size;
+ }
+
+ uint8_t sense[32];
+ int sense_size = 32;
+
+ int ret = do_scsi(cdb, 12, DO_READ, sense, &sense_size, buffer, buffer_size);
+ if(ret < 0)
+ return ret;
+ ret = do_sense_analysis(ret, sense, sense_size);
+ if(ret)
+ return ret;
+ return 0;
+}
+
+int get_dpcc_prop(int argc, char **argv)
+{
+ if(argc != 1 && argc != 3)
+ {
+ printf("You must specify a known property name or a full property specification:\n");
+ printf("Full usage: <prop code> <large> <size>\n");
+ printf("Property usage: <prop>\n");
+ printf("Properties:");
+ for(unsigned i = 0; i < NR_DPCC_PROPS; i++)
+ printf(" %s", dpcc_prop_list[i].user_name);
+ printf("\n");
+ return 1;
+ }
+
+ struct dpcc_prop_t prop;
+ memset(&prop, 0, sizeof(prop));
+ if(argc == 1)
+ {
+ for(unsigned i = 0; i < NR_DPCC_PROPS; i++)
+ if(strcmp(dpcc_prop_list[i].user_name, argv[0]) == 0)
+ prop = dpcc_prop_list[i];
+ if(prop.user_name[0] == 0)
+ {
+ cprintf(GREY, "Unknown property '%s'\n", argv[0]);
+ return 1;
+ }
+ }
+ else
+ {
+ strncpy(prop.name, argv[0], 7);
+ prop.cdb1 = strtoul(argv[1], NULL, 0);
+ prop.size = strtoul(argv[2], NULL, 0);
+ }
+
+ char *buffer = buffer_alloc(prop.size);
+ int buffer_size = prop.size;
+ int ret = do_dpcc_cmd(0, &prop, buffer, &buffer_size);
+ if(ret)
+ return ret;
+ if(buffer_size < prop.size)
+ buffer[buffer_size] = 0;
+ cprintf_field("Property: ", "%s\n", buffer);
+ return 0;
+}
+
+struct user_timer_t
+{
+ uint16_t magic;
+ uint8_t res[6];
+ uint8_t year[2]; // bcd
+ uint8_t month; // bcd
+ uint8_t day; // bcd
+ uint8_t hour; // bcd
+ uint8_t min; // bcd
+ uint8_t sec; // bcd
+ uint8_t res2[17];
+} __attribute__((packed));
+
+int get_user_time(int argc, char **argv)
+{
+ (void) argc;
+ (void )argv;
+
+ void *buffer = buffer_alloc(32);
+ int buffer_size = 32;
+ int ret = do_dpcc_cmd(1, NULL, buffer, &buffer_size);
+ if(ret)
+ return ret;
+ struct user_timer_t *time = buffer;
+ cprintf_field("User Time: ", "%02x/%02x/%02x%02x %02x:%02x:%02x\n",
+ time->day, time->month, time->year[0], time->year[1], time->hour,
+ time->min, time->sec);
+ return 0;
+}
+
+int get_dev_info(int argc, char **argv)
+{
+ (void) argc;
+ (void )argv;
+ uint8_t cdb[12] = {0xfc, 0, 0x20, 'd', 'b', 'm', 'n', 0, 0x80, 0, 0, 0};
+
+ char *buffer = buffer_alloc(0x81);
+ int buffer_size = 0x80;
+ uint8_t sense[32];
+ int sense_size = 32;
+
+ int ret = do_scsi(cdb, 12, DO_READ, sense, &sense_size, buffer, &buffer_size);
+ if(ret < 0)
+ return ret;
+ ret = do_sense_analysis(ret, sense, sense_size);
+ if(ret)
+ return ret;
+ buffer[buffer_size] = 0;
+ cprintf_field("Device Info:", "\n");
+ print_hex(buffer, buffer_size);
+ return 0;
+}
+
+typedef int (*cmd_fn_t)(int argc, char **argv);
+
+struct cmd_t
+{
+ const char *name;
+ const char *desc;
+ cmd_fn_t fn;
+};
+
+struct cmd_t cmd_list[] =
+{
+ { "get_dnk_prop", "Get DNK property", get_dnk_prop },
+ { "get_dpcc_prop", "Get DPCC property", get_dpcc_prop },
+ { "get_user_time", "Get user time", get_user_time },
+ { "get_dev_info", "Get device info", get_dev_info },
+};
+
+#define NR_CMDS (sizeof(cmd_list) / sizeof(cmd_list[0]))
+
+int process_cmd(const char *cmd, int argc, char **argv)
+{
+ for(unsigned i = 0; i < NR_CMDS; i++)
+ if(strcmp(cmd_list[i].name, cmd) == 0)
+ return cmd_list[i].fn(argc, argv);
+ cprintf(GREY, "Unknown command '%s'\n", cmd);
+ return 1;
+}
+
+static void usage(void)
+{
+ printf("Usage: emmctool [options] <dev> <command> [arguments]\n");
+ printf("Options:\n");
+ printf(" -o <prefix>\tSet output prefix\n");
+ printf(" -f/--force\tForce to continue on errors\n");
+ printf(" -?/--help\tDisplay this message\n");
+ printf(" -d/--debug\tDisplay debug messages\n");
+ printf(" -c/--no-color\tDisable color output\n");
+ printf("Commands:\n");
+ for(unsigned i = 0; i < NR_CMDS; i++)
+ printf(" %s\t%s\n", cmd_list[i].name, cmd_list[i].desc);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ while(1)
+ {
+ static struct option long_options[] =
+ {
+ {"help", no_argument, 0, '?'},
+ {"debug", no_argument, 0, 'd'},
+ {"no-color", no_argument, 0, 'c'},
+ {"force", no_argument, 0, 'f'},
+ {0, 0, 0, 0}
+ };
+
+ int c = getopt_long(argc, argv, "?dcfo:", long_options, NULL);
+ if(c == -1)
+ break;
+ switch(c)
+ {
+ case -1:
+ break;
+ case 'c':
+ enable_color(false);
+ break;
+ case 'd':
+ g_debug = true;
+ break;
+ case 'f':
+ g_force = true;
+ break;
+ case '?':
+ usage();
+ break;
+ case 'o':
+ g_out_prefix = optarg;
+ break;
+ default:
+ abort();
+ }
+ }
+
+ if(argc - optind < 2)
+ {
+ usage();
+ return 1;
+ }
+
+ int ret = 0;
+ g_dev_fd = scsi_pt_open_device(argv[optind], false, true);
+ if(g_dev_fd < 0)
+ {
+ cprintf(GREY, "Cannot open device: %m\n");
+ ret = 1;
+ goto Lend;
+ }
+
+ ret = process_cmd(argv[optind + 1], argc - optind - 2, argv + optind + 2);
+
+ scsi_pt_close_device(g_dev_fd);
+Lend:
+ color(OFF);
+
+ return ret;
+}
+