summaryrefslogtreecommitdiff
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
parentcb09e369fb1ed41b5724a45929a49b42c2718203 (diff)
downloadrockbox-3e1c4926806ca685ab591cb6948fa3aa5014591c.tar.gz
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
-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 @@
1DEFINES=
2CC=gcc
3LD=gcc
4CFLAGS=-g -std=c99 -W -Wall $(DEFINES)
5LDFLAGS=-lsgutils2
6BINS=scsitool
7
8all: $(BINS)
9
10%.o: %.c
11 $(CC) $(CFLAGS) -c -o $@ $<
12
13scsitool: scsitool.o misc.o
14 $(LD) -o $@ $^ $(LDFLAGS)
15
16clean:
17 rm -fr *.o
18
19veryclean:
20 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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2010 Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include <stdlib.h>
22#include <stdio.h>
23#include <time.h>
24#include <ctype.h>
25#include "misc.h"
26
27char OFF[] = { 0x1b, 0x5b, 0x31, 0x3b, '0', '0', 0x6d, '\0' };
28
29char GREY[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '0', 0x6d, '\0' };
30char RED[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '1', 0x6d, '\0' };
31char GREEN[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '2', 0x6d, '\0' };
32char YELLOW[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '3', 0x6d, '\0' };
33char BLUE[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '4', 0x6d, '\0' };
34
35static bool g_color_enable = true;
36
37void *xmalloc(size_t s)
38{
39 void * r = malloc(s);
40 if(!r) bugp("malloc");
41 return r;
42}
43
44void enable_color(bool enable)
45{
46 g_color_enable = enable;
47}
48
49void color(color_t c)
50{
51 if(g_color_enable)
52 printf("%s", (char *)c);
53}
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2010 Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef __MISC_H__
22#define __MISC_H__
23
24#include <stdbool.h>
25#include <stdio.h>
26
27#define _STR(a) #a
28#define STR(a) _STR(a)
29
30#define bug(...) do { fprintf(stderr,"["__FILE__":"STR(__LINE__)"]ERROR: "__VA_ARGS__); exit(1); } while(0)
31#define bugp(...) do { fprintf(stderr, __VA_ARGS__); perror(" "); exit(1); } while(0)
32
33#define ROUND_UP(val, round) ((((val) + (round) - 1) / (round)) * (round))
34
35typedef char color_t[];
36
37extern color_t OFF, GREY, RED, GREEN, YELLOW, BLUE;
38void *xmalloc(size_t s);
39void color(color_t c);
40void enable_color(bool enable);
41
42#ifndef MIN
43#define MIN(a,b) ((a) < (b) ? (a) : (b))
44#endif
45
46#define cprintf(col, ...) do {color(col); printf(__VA_ARGS__); }while(0)
47
48#define cprintf_field(str1, ...) do{ cprintf(GREEN, str1); cprintf(YELLOW, __VA_ARGS__); }while(0)
49
50#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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2012 Amaury Pouly
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include <stdio.h>
22#include <stdint.h>
23#include <stdbool.h>
24#include <stdlib.h>
25#include <stddef.h>
26#include <string.h>
27#include <getopt.h>
28#include <stdarg.h>
29#include <ctype.h>
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <fcntl.h>
33#include <unistd.h>
34#include <scsi/scsi.h>
35#include <scsi/sg_lib.h>
36#include <scsi/sg_pt.h>
37#include "misc.h"
38
39bool g_debug = false;
40bool g_force = false;
41char *g_out_prefix = NULL;
42int g_dev_fd = 0;
43
44#define let_the_force_flow(x) do { if(!g_force) return x; } while(0)
45#define continue_the_force(x) if(x) let_the_force_flow(x)
46
47#define check_field(v_exp, v_have, str_ok, str_bad) \
48 if((v_exp) != (v_have)) \
49 { cprintf(RED, str_bad); let_the_force_flow(__LINE__); } \
50 else { cprintf(RED, str_ok); }
51
52#define errorf(...) do { cprintf(GREY, __VA_ARGS__); return __LINE__; } while(0)
53
54#if 0
55void *buffer_alloc(int sz)
56{
57#ifdef SG_LIB_MINGW
58 unsigned psz = getpagesize();
59#else
60 unsigned psz = sysconf(_SC_PAGESIZE); /* was getpagesize() */
61#endif
62 void *buffer = malloc(sz + psz);
63 return (void *)(((ptrdiff_t)(buffer + psz - 1)) & ~(psz - 1));
64}
65#else
66void *buffer_alloc(int sz)
67{
68 return malloc(sz);
69}
70#endif
71
72static void print_hex(void *_buffer, int buffer_size)
73{
74 uint8_t *buffer = _buffer;
75 for(int i = 0; i < buffer_size; i += 16)
76 {
77 for(int j = 0; j < 16; j++)
78 {
79 if(i + j < buffer_size)
80 cprintf(YELLOW, " %02x", buffer[i + j]);
81 else
82 cprintf(YELLOW, " ");
83 }
84 printf(" ");
85 for(int j = 0; j < 16; j++)
86 {
87 if(i + j < buffer_size)
88 cprintf(RED, "%c", isprint(buffer[i + j]) ? buffer[i + j] : '.');
89 else
90 cprintf(RED, " ");
91 }
92 printf("\n");
93 }
94}
95
96/* Do read */
97#define DO_READ (1 << 1)
98/* Do write */
99#define DO_WRITE (1 << 2)
100
101/* returns <0 on error and status otherwise */
102int do_scsi(uint8_t *cdb, int cdb_size, unsigned flags, void *sense, int *sense_size, void *buffer, int *buf_size)
103{
104 char error[256];
105 struct sg_pt_base *obj = construct_scsi_pt_obj();
106 if(obj == NULL)
107 {
108 cprintf(GREY, "construct_scsi_pt_obj failed\n");
109 return 1;
110 }
111 set_scsi_pt_cdb(obj, cdb, cdb_size);
112 if(sense)
113 set_scsi_pt_sense(obj, sense, *sense_size);
114 if(flags & DO_READ)
115 set_scsi_pt_data_in(obj, buffer, *buf_size);
116 if(flags & DO_WRITE)
117 set_scsi_pt_data_out(obj, buffer, *buf_size);
118 int ret = do_scsi_pt(obj, g_dev_fd, 1, 0);
119 switch(get_scsi_pt_result_category(obj))
120 {
121 case SCSI_PT_RESULT_SENSE:
122 case SCSI_PT_RESULT_GOOD:
123 ret = get_scsi_pt_status_response(obj);
124 break;
125 case SCSI_PT_RESULT_STATUS:
126 cprintf(GREY, "Status error: %d (", get_scsi_pt_status_response(obj));
127 sg_print_scsi_status(get_scsi_pt_status_response(obj));
128 printf(")\n");
129 break;
130 case SCSI_PT_RESULT_TRANSPORT_ERR:
131 cprintf(GREY, "Transport error: %s\n", get_scsi_pt_transport_err_str(obj, 256, error));
132 ret = -2;
133 break;
134 case SCSI_PT_RESULT_OS_ERR:
135 cprintf(GREY, "OS error: %s\n", get_scsi_pt_os_err_str(obj, 256, error));
136 ret = -3;
137 break;
138 default:
139 cprintf(GREY, "Unknown error\n");
140 break;
141 }
142
143 if(sense)
144 *sense_size = get_scsi_pt_sense_len(obj);
145 if(flags & (DO_WRITE | DO_READ))
146 *buf_size -= get_scsi_pt_resid(obj);
147
148 destruct_scsi_pt_obj(obj);
149 return ret;
150}
151
152int do_sense_analysis(int status, uint8_t *sense, int sense_size)
153{
154 cprintf_field("Status:", " "); fflush(stdout);
155 sg_print_scsi_status(status);
156 cprintf_field("\nSense:", " "); fflush(stdout);
157 sg_print_sense(NULL, sense, sense_size, 0);
158 if(status == GOOD)
159 return 0;
160 return status;
161}
162
163int do_dnk_cmd(uint32_t cmd, uint8_t sub_cmd, void *buffer, int *buffer_size)
164{
165 uint8_t cdb[12] = {0xdd, 0, 0, 0, 0, 0, 0, 0xbc, 0, 0, 0, 0};
166 cdb[10] = cmd;
167 cdb[11] = sub_cmd;
168 cdb[8] = (*buffer_size) >> 8;
169 cdb[9] = (*buffer_size) & 0xff;
170
171 uint8_t sense[32];
172 int sense_size = 32;
173
174 int ret = do_scsi(cdb, 12, DO_READ, sense, &sense_size, buffer, buffer_size);
175 if(ret < 0)
176 return ret;
177 ret = do_sense_analysis(ret, sense, sense_size);
178 if(ret)
179 return ret;
180 return 0;
181}
182
183#define DNK_EXACT_LENGTH (1 << 0)
184#define DNK_STRING (1 << 1)
185#define DNK_UINT32 (1 << 2)
186
187struct dnk_prop_t
188{
189 char *name;
190 uint8_t cmd;
191 uint8_t subcmd;
192 int size;
193 unsigned flags;
194};
195
196struct dnk_prop_t dnk_prop_list[] =
197{
198 { "serial_num", 0x23, 1, 8, DNK_STRING},
199 { "model_id", 0x23, 4, 4, DNK_EXACT_LENGTH | DNK_UINT32},
200 { "product_id", 0x23, 6, 12, DNK_STRING},
201 { "destination", 0x23, 8, 4, DNK_EXACT_LENGTH | DNK_UINT32},
202 { "model_id2", 0x23, 9, 4, DNK_EXACT_LENGTH | DNK_UINT32},
203 { "dev_info", 0x12, 0, 64, DNK_STRING},
204};
205
206#define NR_DNK_PROPS (sizeof(dnk_prop_list) / sizeof(dnk_prop_list[0]))
207
208int get_dnk_prop(int argc, char **argv)
209{
210 if(argc != 1 && argc != 4)
211 {
212 printf("You must specify a known property name or a full property specification:\n");
213 printf("Full usage: <cmd> <subcmd> <size> <flags>\n");
214 printf("Property usage: <prop>\n");
215 printf("Properties:");
216 for(unsigned i = 0; i < NR_DNK_PROPS; i++)
217 printf(" %s", dnk_prop_list[i].name);
218 printf("\n");
219 return 1;
220 }
221
222 struct dnk_prop_t prop;
223 memset(&prop, 0, sizeof(prop));
224 if(argc == 1)
225 {
226 for(unsigned i = 0; i < NR_DNK_PROPS; i++)
227 if(strcmp(dnk_prop_list[i].name, argv[0]) == 0)
228 prop = dnk_prop_list[i];
229 if(prop.name == NULL)
230 {
231 cprintf(GREY, "Unknown property '%s'\n", argv[0]);
232 return 1;
233 }
234 }
235 else
236 {
237 prop.cmd = strtoul(argv[0], NULL, 0);
238 prop.subcmd = strtoul(argv[1], NULL, 0);
239 prop.size = strtoul(argv[2], NULL, 0);
240 prop.flags = strtoul(argv[3], NULL, 0);
241 }
242
243 char *buffer = buffer_alloc(prop.size + 1);
244 int buffer_size = prop.size;
245 int ret = do_dnk_cmd(prop.cmd, prop.subcmd, buffer, &buffer_size);
246 if(ret)
247 return ret;
248 if(buffer_size == 0)
249 {
250 cprintf(GREY, "Device didn't send any data\n");
251 return 1;
252 }
253 if((prop.flags & DNK_EXACT_LENGTH) && buffer_size != prop.size)
254 {
255 cprintf(GREY, "Device didn't send the expected amount of data\n");
256 return 2;
257 }
258 buffer[buffer_size] = 0;
259 if(prop.flags & DNK_STRING)
260 cprintf_field("Property: ", "%s\n", buffer);
261 else if(prop.flags & DNK_UINT32)
262 cprintf_field("Property: ", "0x%x\n", *(uint32_t *)buffer);
263 else
264 {
265 cprintf(GREEN, "Property:\n");
266 print_hex(buffer, buffer_size);
267 }
268 return 0;
269}
270
271struct dpcc_prop_t
272{
273 char *user_name;
274 char name[7];
275 uint8_t cdb1;
276 int size;
277};
278
279struct dpcc_prop_t dpcc_prop_list[] =
280{
281 { "dev_info", "DEVINFO", 0, 0x80 },
282};
283
284#define NR_DPCC_PROPS (sizeof(dpcc_prop_list) / sizeof(dpcc_prop_list[0]))
285
286int do_dpcc_cmd(uint32_t cmd, struct dpcc_prop_t *prop, void *buffer, int *buffer_size)
287{
288 uint8_t cdb[12] = {0xfb, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
289 cdb[2] = cmd;
290 if(cmd == 0)
291 {
292 strncpy((char *)(cdb + 3), prop->name, 7); // warning: erase cdb[10] !
293 cdb[1] = prop->cdb1;
294 if(prop->cdb1 & 1)
295 cdb[10] = (*buffer_size + 15) / 16;
296 else
297 cdb[10] = *buffer_size;
298 }
299
300 uint8_t sense[32];
301 int sense_size = 32;
302
303 int ret = do_scsi(cdb, 12, DO_READ, sense, &sense_size, buffer, buffer_size);
304 if(ret < 0)
305 return ret;
306 ret = do_sense_analysis(ret, sense, sense_size);
307 if(ret)
308 return ret;
309 return 0;
310}
311
312int get_dpcc_prop(int argc, char **argv)
313{
314 if(argc != 1 && argc != 3)
315 {
316 printf("You must specify a known property name or a full property specification:\n");
317 printf("Full usage: <prop code> <large> <size>\n");
318 printf("Property usage: <prop>\n");
319 printf("Properties:");
320 for(unsigned i = 0; i < NR_DPCC_PROPS; i++)
321 printf(" %s", dpcc_prop_list[i].user_name);
322 printf("\n");
323 return 1;
324 }
325
326 struct dpcc_prop_t prop;
327 memset(&prop, 0, sizeof(prop));
328 if(argc == 1)
329 {
330 for(unsigned i = 0; i < NR_DPCC_PROPS; i++)
331 if(strcmp(dpcc_prop_list[i].user_name, argv[0]) == 0)
332 prop = dpcc_prop_list[i];
333 if(prop.user_name[0] == 0)
334 {
335 cprintf(GREY, "Unknown property '%s'\n", argv[0]);
336 return 1;
337 }
338 }
339 else
340 {
341 strncpy(prop.name, argv[0], 7);
342 prop.cdb1 = strtoul(argv[1], NULL, 0);
343 prop.size = strtoul(argv[2], NULL, 0);
344 }
345
346 char *buffer = buffer_alloc(prop.size);
347 int buffer_size = prop.size;
348 int ret = do_dpcc_cmd(0, &prop, buffer, &buffer_size);
349 if(ret)
350 return ret;
351 if(buffer_size < prop.size)
352 buffer[buffer_size] = 0;
353 cprintf_field("Property: ", "%s\n", buffer);
354 return 0;
355}
356
357struct user_timer_t
358{
359 uint16_t magic;
360 uint8_t res[6];
361 uint8_t year[2]; // bcd
362 uint8_t month; // bcd
363 uint8_t day; // bcd
364 uint8_t hour; // bcd
365 uint8_t min; // bcd
366 uint8_t sec; // bcd
367 uint8_t res2[17];
368} __attribute__((packed));
369
370int get_user_time(int argc, char **argv)
371{
372 (void) argc;
373 (void )argv;
374
375 void *buffer = buffer_alloc(32);
376 int buffer_size = 32;
377 int ret = do_dpcc_cmd(1, NULL, buffer, &buffer_size);
378 if(ret)
379 return ret;
380 struct user_timer_t *time = buffer;
381 cprintf_field("User Time: ", "%02x/%02x/%02x%02x %02x:%02x:%02x\n",
382 time->day, time->month, time->year[0], time->year[1], time->hour,
383 time->min, time->sec);
384 return 0;
385}
386
387int get_dev_info(int argc, char **argv)
388{
389 (void) argc;
390 (void )argv;
391 uint8_t cdb[12] = {0xfc, 0, 0x20, 'd', 'b', 'm', 'n', 0, 0x80, 0, 0, 0};
392
393 char *buffer = buffer_alloc(0x81);
394 int buffer_size = 0x80;
395 uint8_t sense[32];
396 int sense_size = 32;
397
398 int ret = do_scsi(cdb, 12, DO_READ, sense, &sense_size, buffer, &buffer_size);
399 if(ret < 0)
400 return ret;
401 ret = do_sense_analysis(ret, sense, sense_size);
402 if(ret)
403 return ret;
404 buffer[buffer_size] = 0;
405 cprintf_field("Device Info:", "\n");
406 print_hex(buffer, buffer_size);
407 return 0;
408}
409
410typedef int (*cmd_fn_t)(int argc, char **argv);
411
412struct cmd_t
413{
414 const char *name;
415 const char *desc;
416 cmd_fn_t fn;
417};
418
419struct cmd_t cmd_list[] =
420{
421 { "get_dnk_prop", "Get DNK property", get_dnk_prop },
422 { "get_dpcc_prop", "Get DPCC property", get_dpcc_prop },
423 { "get_user_time", "Get user time", get_user_time },
424 { "get_dev_info", "Get device info", get_dev_info },
425};
426
427#define NR_CMDS (sizeof(cmd_list) / sizeof(cmd_list[0]))
428
429int process_cmd(const char *cmd, int argc, char **argv)
430{
431 for(unsigned i = 0; i < NR_CMDS; i++)
432 if(strcmp(cmd_list[i].name, cmd) == 0)
433 return cmd_list[i].fn(argc, argv);
434 cprintf(GREY, "Unknown command '%s'\n", cmd);
435 return 1;
436}
437
438static void usage(void)
439{
440 printf("Usage: emmctool [options] <dev> <command> [arguments]\n");
441 printf("Options:\n");
442 printf(" -o <prefix>\tSet output prefix\n");
443 printf(" -f/--force\tForce to continue on errors\n");
444 printf(" -?/--help\tDisplay this message\n");
445 printf(" -d/--debug\tDisplay debug messages\n");
446 printf(" -c/--no-color\tDisable color output\n");
447 printf("Commands:\n");
448 for(unsigned i = 0; i < NR_CMDS; i++)
449 printf(" %s\t%s\n", cmd_list[i].name, cmd_list[i].desc);
450 exit(1);
451}
452
453int main(int argc, char **argv)
454{
455 while(1)
456 {
457 static struct option long_options[] =
458 {
459 {"help", no_argument, 0, '?'},
460 {"debug", no_argument, 0, 'd'},
461 {"no-color", no_argument, 0, 'c'},
462 {"force", no_argument, 0, 'f'},
463 {0, 0, 0, 0}
464 };
465
466 int c = getopt_long(argc, argv, "?dcfo:", long_options, NULL);
467 if(c == -1)
468 break;
469 switch(c)
470 {
471 case -1:
472 break;
473 case 'c':
474 enable_color(false);
475 break;
476 case 'd':
477 g_debug = true;
478 break;
479 case 'f':
480 g_force = true;
481 break;
482 case '?':
483 usage();
484 break;
485 case 'o':
486 g_out_prefix = optarg;
487 break;
488 default:
489 abort();
490 }
491 }
492
493 if(argc - optind < 2)
494 {
495 usage();
496 return 1;
497 }
498
499 int ret = 0;
500 g_dev_fd = scsi_pt_open_device(argv[optind], false, true);
501 if(g_dev_fd < 0)
502 {
503 cprintf(GREY, "Cannot open device: %m\n");
504 ret = 1;
505 goto Lend;
506 }
507
508 ret = process_cmd(argv[optind + 1], argc - optind - 2, argv + optind + 2);
509
510 scsi_pt_close_device(g_dev_fd);
511Lend:
512 color(OFF);
513
514 return ret;
515}
516