2018-05-03 13:47:57 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
|
|
|
// for Raspberry Pi
|
|
|
|
//
|
|
|
|
// Powered by XM6 TypeG Technology.
|
2020-07-04 14:57:44 +00:00
|
|
|
// Copyright (C) 2016-2020 GIMONS
|
2021-02-07 19:00:48 +00:00
|
|
|
// [ HDD dump utility (initiator mode) ]
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#include "os.h"
|
|
|
|
#include "xm6.h"
|
|
|
|
#include "fileio.h"
|
|
|
|
#include "filepath.h"
|
|
|
|
#include "gpiobus.h"
|
2021-01-25 16:07:30 +00:00
|
|
|
#include "rascsi_version.h"
|
2018-05-03 13:47:57 +00:00
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Constant Declaration
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
2021-02-07 19:00:48 +00:00
|
|
|
#define BUFSIZE 1024 * 64 // Buffer size of about 64KB
|
2018-05-03 13:47:57 +00:00
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Variable Declaration
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
2021-02-07 19:00:48 +00:00
|
|
|
GPIOBUS bus; // Bus
|
|
|
|
int targetid; // Target ID
|
|
|
|
int boardid; // Board ID (own ID)
|
|
|
|
Filepath hdsfile; // HDS file
|
|
|
|
BOOL restore; // Restore flag
|
|
|
|
BYTE buffer[BUFSIZE]; // Work Buffer
|
|
|
|
int result; // Result Code
|
2018-05-03 13:47:57 +00:00
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Cleanup() Function declaration
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
void Cleanup();
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Signal processing
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
void KillHandler(int sig)
|
|
|
|
{
|
2021-02-07 19:00:48 +00:00
|
|
|
// Stop running
|
2018-05-03 13:47:57 +00:00
|
|
|
Cleanup();
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Banner Output
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
BOOL Banner(int argc, char* argv[])
|
|
|
|
{
|
|
|
|
printf("RaSCSI hard disk dump utility ");
|
2021-01-25 16:07:30 +00:00
|
|
|
printf("version %s (%s, %s)\n",
|
|
|
|
rascsi_get_version_string(),
|
|
|
|
__DATE__,
|
|
|
|
__TIME__);
|
2018-05-03 13:47:57 +00:00
|
|
|
|
|
|
|
if (argc < 2 || strcmp(argv[1], "-h") == 0) {
|
|
|
|
printf("Usage: %s -i ID [-b BID] -f FILE [-r]\n", argv[0]);
|
|
|
|
printf(" ID is target device SCSI ID {0|1|2|3|4|5|6|7}.\n");
|
|
|
|
printf(" BID is rascsi board SCSI ID {0|1|2|3|4|5|6|7}. Default is 7.\n");
|
|
|
|
printf(" FILE is HDS file path.\n");
|
|
|
|
printf(" -r is restore operation.\n");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Initialization
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
BOOL Init()
|
|
|
|
{
|
2021-02-07 19:00:48 +00:00
|
|
|
// Interrupt handler setting
|
2018-05-03 13:47:57 +00:00
|
|
|
if (signal(SIGINT, KillHandler) == SIG_ERR) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
if (signal(SIGHUP, KillHandler) == SIG_ERR) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
if (signal(SIGTERM, KillHandler) == SIG_ERR) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// GPIO Initialization
|
2018-05-03 13:47:57 +00:00
|
|
|
if (!bus.Init(BUS::INITIATOR)) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Work Intitialization
|
2018-05-03 13:47:57 +00:00
|
|
|
targetid = -1;
|
|
|
|
boardid = 7;
|
|
|
|
restore = FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Cleanup
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
void Cleanup()
|
|
|
|
{
|
2021-02-07 19:00:48 +00:00
|
|
|
// Cleanup the bus
|
2018-05-03 13:47:57 +00:00
|
|
|
bus.Cleanup();
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Reset
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
void Reset()
|
|
|
|
{
|
2021-02-07 19:00:48 +00:00
|
|
|
// Reset the bus signal line
|
2018-05-03 13:47:57 +00:00
|
|
|
bus.Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Argument processing
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
BOOL ParseArgument(int argc, char* argv[])
|
|
|
|
{
|
|
|
|
int opt;
|
|
|
|
char *file;
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Initialization
|
2018-05-03 13:47:57 +00:00
|
|
|
file = NULL;
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Argument Parsing
|
2018-05-03 13:47:57 +00:00
|
|
|
opterr = 0;
|
|
|
|
while ((opt = getopt(argc, argv, "i:b:f:r")) != -1) {
|
|
|
|
switch (opt) {
|
|
|
|
case 'i':
|
|
|
|
targetid = optarg[0] - '0';
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'b':
|
|
|
|
boardid = optarg[0] - '0';
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'f':
|
|
|
|
file = optarg;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'r':
|
|
|
|
restore = TRUE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// TARGET ID check
|
2018-05-03 13:47:57 +00:00
|
|
|
if (targetid < 0 || targetid > 7) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Error : Invalid target id range\n");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// BOARD ID check
|
2018-05-03 13:47:57 +00:00
|
|
|
if (boardid < 0 || boardid > 7) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Error : Invalid board id range\n");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Target and Board ID duplication check
|
2018-05-03 13:47:57 +00:00
|
|
|
if (targetid == boardid) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Error : Invalid target or board id\n");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// File Check
|
2018-05-03 13:47:57 +00:00
|
|
|
if (!file) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Error : Invalid file path\n");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdsfile.SetPath(file);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Wait Phase
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
BOOL WaitPhase(BUS::phase_t phase)
|
|
|
|
{
|
2020-07-04 14:57:44 +00:00
|
|
|
DWORD now;
|
2018-05-03 13:47:57 +00:00
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Timeout (3000ms)
|
2020-07-04 14:57:44 +00:00
|
|
|
now = SysTimer::GetTimerLow();
|
|
|
|
while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) {
|
2018-05-03 13:47:57 +00:00
|
|
|
bus.Aquire();
|
2020-07-04 14:57:44 +00:00
|
|
|
if (bus.GetREQ() && bus.GetPhase() == phase) {
|
|
|
|
return TRUE;
|
2018-05-03 13:47:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Bus Free Phase
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
void BusFree()
|
|
|
|
{
|
2021-02-07 19:00:48 +00:00
|
|
|
// Bus Reset
|
2018-05-03 13:47:57 +00:00
|
|
|
bus.Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Selection Phase
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
BOOL Selection(int id)
|
|
|
|
{
|
|
|
|
BYTE data;
|
|
|
|
int count;
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// ID setting and SEL assert
|
2018-05-03 13:47:57 +00:00
|
|
|
data = 0;
|
|
|
|
data |= (1 << boardid);
|
|
|
|
data |= (1 << id);
|
|
|
|
bus.SetDAT(data);
|
|
|
|
bus.SetSEL(TRUE);
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// wait for busy
|
2018-05-03 13:47:57 +00:00
|
|
|
count = 10000;
|
|
|
|
do {
|
|
|
|
usleep(20);
|
|
|
|
bus.Aquire();
|
|
|
|
if (bus.GetBSY()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while (count--);
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// SEL negate
|
2018-05-03 13:47:57 +00:00
|
|
|
bus.SetSEL(FALSE);
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Success if the target is busy
|
2018-05-03 13:47:57 +00:00
|
|
|
return bus.GetBSY();
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Command Phase
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
BOOL Command(BYTE *buf, int length)
|
|
|
|
{
|
|
|
|
int count;
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Waiting for Phase
|
2018-05-03 13:47:57 +00:00
|
|
|
if (!WaitPhase(BUS::command)) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Send Command
|
2018-05-03 13:47:57 +00:00
|
|
|
count = bus.SendHandShake(buf, length);
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Success if the transmission result is the same as the number
|
|
|
|
// of requests
|
2018-05-03 13:47:57 +00:00
|
|
|
if (count == length) {
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Return error
|
2018-05-03 13:47:57 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Data in phase
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DataIn(BYTE *buf, int length)
|
|
|
|
{
|
2021-02-07 19:00:48 +00:00
|
|
|
// Wait for phase
|
2018-05-03 13:47:57 +00:00
|
|
|
if (!WaitPhase(BUS::datain)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Data reception
|
2018-05-03 13:47:57 +00:00
|
|
|
return bus.ReceiveHandShake(buf, length);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Data out phase
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DataOut(BYTE *buf, int length)
|
|
|
|
{
|
2021-02-07 19:00:48 +00:00
|
|
|
// Wait for phase
|
2018-05-03 13:47:57 +00:00
|
|
|
if (!WaitPhase(BUS::dataout)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Data transmission
|
2018-05-03 13:47:57 +00:00
|
|
|
return bus.SendHandShake(buf, length);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Status Phase
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int Status()
|
|
|
|
{
|
|
|
|
BYTE buf[256];
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Wait for phase
|
2018-05-03 13:47:57 +00:00
|
|
|
if (!WaitPhase(BUS::status)) {
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Data reception
|
2018-05-03 13:47:57 +00:00
|
|
|
if (bus.ReceiveHandShake(buf, 1) == 1) {
|
|
|
|
return (int)buf[0];
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Return error
|
2018-05-03 13:47:57 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Message in phase
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int MessageIn()
|
|
|
|
{
|
|
|
|
BYTE buf[256];
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Wait for phase
|
2018-05-03 13:47:57 +00:00
|
|
|
if (!WaitPhase(BUS::msgin)) {
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Data reception
|
2018-05-03 13:47:57 +00:00
|
|
|
if (bus.ReceiveHandShake(buf, 1) == 1) {
|
|
|
|
return (int)buf[0];
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Return error
|
2018-05-03 13:47:57 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// TEST UNIT READY
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int TestUnitReady(int id)
|
|
|
|
{
|
|
|
|
BYTE cmd[256];
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Result code initialization
|
2018-05-03 13:47:57 +00:00
|
|
|
result = 0;
|
|
|
|
|
|
|
|
// SELECTION
|
|
|
|
if (!Selection(id)) {
|
|
|
|
result = -1;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// COMMAND
|
|
|
|
memset(cmd, 0x00, 6);
|
|
|
|
cmd[0] = 0x00;
|
|
|
|
if (!Command(cmd, 6)) {
|
|
|
|
result = -2;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// STATUS
|
|
|
|
if (Status() < 0) {
|
|
|
|
result = -4;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// MESSAGE IN
|
|
|
|
if (MessageIn() < 0) {
|
|
|
|
result = -5;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
2021-02-07 19:00:48 +00:00
|
|
|
// Bus free
|
2018-05-03 13:47:57 +00:00
|
|
|
BusFree();
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// REQUEST SENSE
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int RequestSense(int id, BYTE *buf)
|
|
|
|
{
|
|
|
|
BYTE cmd[256];
|
|
|
|
int count;
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Result code initialization
|
2018-05-03 13:47:57 +00:00
|
|
|
result = 0;
|
2020-07-04 14:57:44 +00:00
|
|
|
count = 0;
|
2018-05-03 13:47:57 +00:00
|
|
|
|
|
|
|
// SELECTION
|
|
|
|
if (!Selection(id)) {
|
|
|
|
result = -1;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// COMMAND
|
|
|
|
memset(cmd, 0x00, 6);
|
|
|
|
cmd[0] = 0x03;
|
|
|
|
cmd[4] = 0xff;
|
|
|
|
if (!Command(cmd, 6)) {
|
|
|
|
result = -2;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// DATAIN
|
2020-07-04 14:57:44 +00:00
|
|
|
memset(buf, 0x00, 256);
|
2018-05-03 13:47:57 +00:00
|
|
|
count = DataIn(buf, 256);
|
|
|
|
if (count <= 0) {
|
|
|
|
result = -3;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// STATUS
|
|
|
|
if (Status() < 0) {
|
|
|
|
result = -4;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// MESSAGE IN
|
|
|
|
if (MessageIn() < 0) {
|
|
|
|
result = -5;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
2021-02-07 19:00:48 +00:00
|
|
|
// Bus Free
|
2018-05-03 13:47:57 +00:00
|
|
|
BusFree();
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Returns the number of transfers if successful
|
2018-05-03 13:47:57 +00:00
|
|
|
if (result == 0) {
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// MODE SENSE
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int ModeSense(int id, BYTE *buf)
|
|
|
|
{
|
|
|
|
BYTE cmd[256];
|
|
|
|
int count;
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Result code initialization
|
2018-05-03 13:47:57 +00:00
|
|
|
result = 0;
|
2020-07-04 14:57:44 +00:00
|
|
|
count = 0;
|
2018-05-03 13:47:57 +00:00
|
|
|
|
|
|
|
// SELECTION
|
|
|
|
if (!Selection(id)) {
|
|
|
|
result = -1;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// COMMAND
|
|
|
|
memset(cmd, 0x00, 6);
|
|
|
|
cmd[0] = 0x1a;
|
|
|
|
cmd[2] = 0x3f;
|
|
|
|
cmd[4] = 0xff;
|
|
|
|
if (!Command(cmd, 6)) {
|
|
|
|
result = -2;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// DATAIN
|
2020-07-04 14:57:44 +00:00
|
|
|
memset(buf, 0x00, 256);
|
2018-05-03 13:47:57 +00:00
|
|
|
count = DataIn(buf, 256);
|
|
|
|
if (count <= 0) {
|
|
|
|
result = -3;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// STATUS
|
|
|
|
if (Status() < 0) {
|
|
|
|
result = -4;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// MESSAGE IN
|
|
|
|
if (MessageIn() < 0) {
|
|
|
|
result = -5;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
2021-02-07 19:00:48 +00:00
|
|
|
// Bus free
|
2018-05-03 13:47:57 +00:00
|
|
|
BusFree();
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Returns the number of transfers if successful
|
2018-05-03 13:47:57 +00:00
|
|
|
if (result == 0) {
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// INQUIRY
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int Inquiry(int id, BYTE *buf)
|
|
|
|
{
|
|
|
|
BYTE cmd[256];
|
|
|
|
int count;
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Result code initialization
|
2018-05-03 13:47:57 +00:00
|
|
|
result = 0;
|
2020-07-04 14:57:44 +00:00
|
|
|
count = 0;
|
2018-05-03 13:47:57 +00:00
|
|
|
|
|
|
|
// SELECTION
|
|
|
|
if (!Selection(id)) {
|
|
|
|
result = -1;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// COMMAND
|
|
|
|
memset(cmd, 0x00, 6);
|
|
|
|
cmd[0] = 0x12;
|
|
|
|
cmd[4] = 0xff;
|
|
|
|
if (!Command(cmd, 6)) {
|
|
|
|
result = -2;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// DATAIN
|
2020-07-04 14:57:44 +00:00
|
|
|
memset(buf, 0x00, 256);
|
|
|
|
count = DataIn(buf, 256);
|
2018-05-03 13:47:57 +00:00
|
|
|
if (count <= 0) {
|
|
|
|
result = -3;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// STATUS
|
|
|
|
if (Status() < 0) {
|
|
|
|
result = -4;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// MESSAGE IN
|
|
|
|
if (MessageIn() < 0) {
|
|
|
|
result = -5;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
2021-02-07 19:00:48 +00:00
|
|
|
// Bus free
|
2018-05-03 13:47:57 +00:00
|
|
|
BusFree();
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Returns the number of transfers if successful
|
2018-05-03 13:47:57 +00:00
|
|
|
if (result == 0) {
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// READ CAPACITY
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int ReadCapacity(int id, BYTE *buf)
|
|
|
|
{
|
|
|
|
BYTE cmd[256];
|
|
|
|
int count;
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Result code initialization
|
2018-05-03 13:47:57 +00:00
|
|
|
result = 0;
|
2020-07-04 14:57:44 +00:00
|
|
|
count = 0;
|
2018-05-03 13:47:57 +00:00
|
|
|
|
|
|
|
// SELECTION
|
|
|
|
if (!Selection(id)) {
|
|
|
|
result = -1;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// COMMAND
|
|
|
|
memset(cmd, 0x00, 10);
|
|
|
|
cmd[0] = 0x25;
|
|
|
|
if (!Command(cmd, 10)) {
|
|
|
|
result = -2;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// DATAIN
|
2020-07-04 14:57:44 +00:00
|
|
|
memset(buf, 0x00, 8);
|
2018-05-03 13:47:57 +00:00
|
|
|
count = DataIn(buf, 8);
|
|
|
|
if (count <= 0) {
|
|
|
|
result = -3;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// STATUS
|
|
|
|
if (Status() < 0) {
|
|
|
|
result = -4;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// MESSAGE IN
|
|
|
|
if (MessageIn() < 0) {
|
|
|
|
result = -5;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
2021-02-07 19:00:48 +00:00
|
|
|
// Bus free
|
2018-05-03 13:47:57 +00:00
|
|
|
BusFree();
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Returns the number of transfers if successful
|
2018-05-03 13:47:57 +00:00
|
|
|
if (result == 0) {
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// READ10
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int Read10(int id, DWORD bstart, DWORD blength, DWORD length, BYTE *buf)
|
|
|
|
{
|
|
|
|
BYTE cmd[256];
|
|
|
|
int count;
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Result code initialization
|
2018-05-03 13:47:57 +00:00
|
|
|
result = 0;
|
2020-07-04 14:57:44 +00:00
|
|
|
count = 0;
|
2018-05-03 13:47:57 +00:00
|
|
|
|
|
|
|
// SELECTION
|
|
|
|
if (!Selection(id)) {
|
|
|
|
result = -1;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// COMMAND
|
|
|
|
memset(cmd, 0x00, 10);
|
|
|
|
cmd[0] = 0x28;
|
|
|
|
cmd[2] = (BYTE)(bstart >> 24);
|
|
|
|
cmd[3] = (BYTE)(bstart >> 16);
|
|
|
|
cmd[4] = (BYTE)(bstart >> 8);
|
|
|
|
cmd[5] = (BYTE)bstart;
|
|
|
|
cmd[7] = (BYTE)(blength >> 8);
|
|
|
|
cmd[8] = (BYTE)blength;
|
|
|
|
if (!Command(cmd, 10)) {
|
|
|
|
result = -2;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// DATAIN
|
|
|
|
count = DataIn(buf, length);
|
|
|
|
if (count <= 0) {
|
|
|
|
result = -3;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// STATUS
|
|
|
|
if (Status() < 0) {
|
|
|
|
result = -4;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// MESSAGE IN
|
|
|
|
if (MessageIn() < 0) {
|
|
|
|
result = -5;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
2021-02-07 19:00:48 +00:00
|
|
|
// Bus free
|
2018-05-03 13:47:57 +00:00
|
|
|
BusFree();
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Returns the number of transfers if successful
|
2018-05-03 13:47:57 +00:00
|
|
|
if (result == 0) {
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// WRITE10
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int Write10(int id, DWORD bstart, DWORD blength, DWORD length, BYTE *buf)
|
|
|
|
{
|
|
|
|
BYTE cmd[256];
|
|
|
|
int count;
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Result code initialization
|
2018-05-03 13:47:57 +00:00
|
|
|
result = 0;
|
2020-07-04 14:57:44 +00:00
|
|
|
count = 0;
|
2018-05-03 13:47:57 +00:00
|
|
|
|
|
|
|
// SELECTION
|
|
|
|
if (!Selection(id)) {
|
|
|
|
result = -1;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// COMMAND
|
|
|
|
memset(cmd, 0x00, 10);
|
|
|
|
cmd[0] = 0x2a;
|
|
|
|
cmd[2] = (BYTE)(bstart >> 24);
|
|
|
|
cmd[3] = (BYTE)(bstart >> 16);
|
|
|
|
cmd[4] = (BYTE)(bstart >> 8);
|
|
|
|
cmd[5] = (BYTE)bstart;
|
|
|
|
cmd[7] = (BYTE)(blength >> 8);
|
|
|
|
cmd[8] = (BYTE)blength;
|
|
|
|
if (!Command(cmd, 10)) {
|
|
|
|
result = -2;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// DATAOUT
|
|
|
|
count = DataOut(buf, length);
|
|
|
|
if (count <= 0) {
|
|
|
|
result = -3;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// STATUS
|
|
|
|
if (Status() < 0) {
|
|
|
|
result = -4;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// MESSAGE IN
|
|
|
|
if (MessageIn() < 0) {
|
|
|
|
result = -5;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
2021-02-07 19:00:48 +00:00
|
|
|
// Bus free
|
2018-05-03 13:47:57 +00:00
|
|
|
BusFree();
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Returns the number of transfers if successful
|
2018-05-03 13:47:57 +00:00
|
|
|
if (result == 0) {
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-02-07 19:00:48 +00:00
|
|
|
// Main process
|
2018-05-03 13:47:57 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int main(int argc, char* argv[])
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int count;
|
|
|
|
char str[32];
|
|
|
|
DWORD bsiz;
|
|
|
|
DWORD bnum;
|
|
|
|
DWORD duni;
|
|
|
|
DWORD dsiz;
|
|
|
|
DWORD dnum;
|
|
|
|
Fileio fio;
|
|
|
|
Fileio::OpenMode omode;
|
|
|
|
off64_t size;
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Banner output
|
2018-05-03 13:47:57 +00:00
|
|
|
if (!Banner(argc, argv)) {
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Initialization
|
2018-05-03 13:47:57 +00:00
|
|
|
if (!Init()) {
|
2021-02-07 19:00:48 +00:00
|
|
|
fprintf(stderr, "Error : Initializing. Are you root?\n");
|
2018-05-03 13:47:57 +00:00
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Probably not root
|
2018-05-03 13:47:57 +00:00
|
|
|
exit(EPERM);
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Prase Argument
|
2018-05-03 13:47:57 +00:00
|
|
|
if (!ParseArgument(argc, argv)) {
|
2021-02-07 19:00:48 +00:00
|
|
|
// Cleanup
|
2018-05-03 13:47:57 +00:00
|
|
|
Cleanup();
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Exit with invalid argument error
|
2018-05-03 13:47:57 +00:00
|
|
|
exit(EINVAL);
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Reset the SCSI bus
|
2018-05-03 13:47:57 +00:00
|
|
|
Reset();
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// File Open
|
2018-05-03 13:47:57 +00:00
|
|
|
if (restore) {
|
|
|
|
omode = Fileio::ReadOnly;
|
|
|
|
} else {
|
|
|
|
omode = Fileio::WriteOnly;
|
|
|
|
}
|
|
|
|
if (!fio.Open(hdsfile.GetPath(), omode)) {
|
|
|
|
fprintf(stderr, "Error : Can't open hds file\n");
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Cleanup
|
2018-05-03 13:47:57 +00:00
|
|
|
Cleanup();
|
|
|
|
exit(EPERM);
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Bus free
|
2018-05-03 13:47:57 +00:00
|
|
|
BusFree();
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Assert reset signal
|
2018-05-03 13:47:57 +00:00
|
|
|
bus.SetRST(TRUE);
|
|
|
|
usleep(1000);
|
|
|
|
bus.SetRST(FALSE);
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Start dump
|
2018-05-03 13:47:57 +00:00
|
|
|
printf("TARGET ID : %d\n", targetid);
|
|
|
|
printf("BORAD ID : %d\n", boardid);
|
|
|
|
|
|
|
|
// TEST UNIT READY
|
|
|
|
count = TestUnitReady(targetid);
|
|
|
|
if (count < 0) {
|
|
|
|
fprintf(stderr, "TEST UNIT READY ERROR %d\n", count);
|
|
|
|
goto cleanup_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// REQUEST SENSE(for CHECK CONDITION)
|
|
|
|
count = RequestSense(targetid, buffer);
|
|
|
|
if (count < 0) {
|
|
|
|
fprintf(stderr, "REQUEST SENSE ERROR %d\n", count);
|
|
|
|
goto cleanup_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// INQUIRY
|
|
|
|
count = Inquiry(targetid, buffer);
|
|
|
|
if (count < 0) {
|
|
|
|
fprintf(stderr, "INQUIRY ERROR %d\n", count);
|
|
|
|
goto cleanup_exit;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Display INQUIRY information
|
2018-05-03 13:47:57 +00:00
|
|
|
memset(str, 0x00, sizeof(str));
|
|
|
|
memcpy(str, &buffer[8], 8);
|
|
|
|
printf("Vendor : %s\n", str);
|
|
|
|
memset(str, 0x00, sizeof(str));
|
|
|
|
memcpy(str, &buffer[16], 16);
|
|
|
|
printf("Product : %s\n", str);
|
|
|
|
memset(str, 0x00, sizeof(str));
|
|
|
|
memcpy(str, &buffer[32], 4);
|
|
|
|
printf("Revison : %s\n", str);
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Get drive capacity
|
2018-05-03 13:47:57 +00:00
|
|
|
count = ReadCapacity(targetid, buffer);
|
|
|
|
if (count < 0) {
|
|
|
|
fprintf(stderr, "READ CAPACITY ERROR %d\n", count);
|
|
|
|
goto cleanup_exit;
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Display block size and number of blocks
|
2018-05-03 13:47:57 +00:00
|
|
|
bsiz =
|
|
|
|
(buffer[4] << 24) | (buffer[5] << 16) |
|
|
|
|
(buffer[6] << 8) | buffer[7];
|
|
|
|
bnum =
|
|
|
|
(buffer[0] << 24) | (buffer[1] << 16) |
|
|
|
|
(buffer[2] << 8) | buffer[3];
|
|
|
|
bnum++;
|
2020-07-04 14:57:44 +00:00
|
|
|
printf("Number of blocks : %d Blocks\n", (int)bnum);
|
|
|
|
printf("Block length : %d Bytes\n", (int)bsiz);
|
2018-05-03 13:47:57 +00:00
|
|
|
printf("Unit Capacity : %d MBytes %d Bytes\n",
|
2020-07-04 14:57:44 +00:00
|
|
|
(int)(bsiz * bnum / 1024 / 1024),
|
|
|
|
(int)(bsiz * bnum));
|
2018-05-03 13:47:57 +00:00
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Get the restore file size
|
2018-05-03 13:47:57 +00:00
|
|
|
if (restore) {
|
|
|
|
size = fio.GetFileSize();
|
|
|
|
printf("Restore file size : %d bytes", (int)size);
|
2020-07-04 14:57:44 +00:00
|
|
|
if (size > (off64_t)(bsiz * bnum)) {
|
|
|
|
printf("(WARNING : File size is larger than disk size)");
|
|
|
|
} else if (size < (off64_t)(bsiz * bnum)) {
|
|
|
|
printf("(ERROR : File size is smaller than disk size)\n");
|
|
|
|
goto cleanup_exit;
|
2018-05-03 13:47:57 +00:00
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Dump by buffer size
|
2018-05-03 13:47:57 +00:00
|
|
|
duni = BUFSIZE;
|
|
|
|
duni /= bsiz;
|
|
|
|
dsiz = BUFSIZE;
|
|
|
|
dnum = bnum * bsiz;
|
|
|
|
dnum /= BUFSIZE;
|
|
|
|
|
|
|
|
if (restore) {
|
|
|
|
printf("Restore progress : ");
|
|
|
|
} else {
|
|
|
|
printf("Dump progress : ");
|
|
|
|
}
|
|
|
|
|
2020-07-04 14:57:44 +00:00
|
|
|
for (i = 0; i < (int)dnum; i++) {
|
2018-05-03 13:47:57 +00:00
|
|
|
if (i > 0) {
|
|
|
|
printf("\033[21D");
|
|
|
|
printf("\033[0K");
|
|
|
|
}
|
2020-07-04 14:57:44 +00:00
|
|
|
printf("%3d%%(%7d/%7d)",
|
|
|
|
(int)((i + 1) * 100 / dnum),
|
|
|
|
(int)(i * duni),
|
|
|
|
(int)bnum);
|
2018-05-03 13:47:57 +00:00
|
|
|
fflush(stdout);
|
|
|
|
|
|
|
|
if (restore) {
|
|
|
|
if (fio.Read(buffer, dsiz)) {
|
|
|
|
if (Write10(targetid, i * duni, duni, dsiz, buffer) >= 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (Read10(targetid, i * duni, duni, dsiz, buffer) >= 0) {
|
|
|
|
if (fio.Write(buffer, dsiz)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("\n");
|
|
|
|
printf("Error occured and aborted... %d\n", result);
|
|
|
|
goto cleanup_exit;
|
|
|
|
}
|
|
|
|
|
2020-07-04 14:57:44 +00:00
|
|
|
if (dnum > 0) {
|
|
|
|
printf("\033[21D");
|
|
|
|
printf("\033[0K");
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Rounding on capacity
|
2018-05-03 13:47:57 +00:00
|
|
|
dnum = bnum % duni;
|
|
|
|
dsiz = dnum * bsiz;
|
|
|
|
if (dnum > 0) {
|
|
|
|
if (restore) {
|
|
|
|
if (fio.Read(buffer, dsiz)) {
|
2020-07-04 14:57:44 +00:00
|
|
|
Write10(targetid, i * duni, dnum, dsiz, buffer);
|
2018-05-03 13:47:57 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (Read10(targetid, i * duni, dnum, dsiz, buffer) >= 0) {
|
|
|
|
fio.Write(buffer, dsiz);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Completion Message
|
2020-07-04 14:57:44 +00:00
|
|
|
printf("%3d%%(%7d/%7d)\n", 100, (int)bnum, (int)bnum);
|
2018-05-03 13:47:57 +00:00
|
|
|
|
|
|
|
cleanup_exit:
|
2021-02-07 19:00:48 +00:00
|
|
|
// File close
|
2018-05-03 13:47:57 +00:00
|
|
|
fio.Close();
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// Cleanup
|
2018-05-03 13:47:57 +00:00
|
|
|
Cleanup();
|
|
|
|
|
2021-02-07 19:00:48 +00:00
|
|
|
// end
|
2018-05-03 13:47:57 +00:00
|
|
|
exit(0);
|
|
|
|
}
|