blob: e1496ee991aaf9b4cd45bcb61ef8cd7b9e6f3b01 [file] [log] [blame]
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "sg_include.h"
#include "sg_lib.h"
/* A utility program for the Linux OS SCSI subsystem.
* Copyright (C) 2004-2005 D. Gilbert
* 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, or (at your option)
* any later version.
This program issues the SCSI command WRITE LONG to a given SCSI device.
It sends the command with the logical block address passed as the lba
argument, and the transfer length set to the xfer_len argument. the
buffer to be writen to the device filled with 0xff, this buffer includes
the sector data and the ECC bytes.
This code was contributed by Saeed Bishara
*/
static char * version_str = "1.06 20050808";
#define WRITE_LONG_OPCODE 0x3F
#define WRITE_LONG_CMD_LEN 10
#define MAX_XFER_LEN 10000
#define SENSE_BUFF_LEN 64
/* #define SG_DEBUG */
#define ME "sg_write_long: "
#define EBUFF_SZ 256
static struct option long_options[] = {
{"help", 0, 0, 'h'},
{"in", 1, 0, 'i'},
{"lba", 1, 0, 'l'},
{"verbose", 0, 0, 'v'},
{"version", 0, 0, 'V'},
{"xfer_len", 1, 0, 'x'},
{0, 0, 0, 0},
};
static void usage()
{
fprintf(stderr, "Usage: "
"sg_write_long [--help] [--in=<name>] [--lba=<num>] [--verbose]\n"
" [--version] [--xfer_len=<num>] <scsi_device>\n"
" where: --help print out usage message\n"
" --in=<name> input from file <name> (default write "
"0xff bytes)\n"
" --lba=<num>|-l <num> logical block address (default 0)\n"
" --verbose|-v increase verbosity\n"
" --version|-V print version string then exit\n"
" --xfer_len=<num>|-x <num> transfer length (< 10000) "
"default 520\n"
"\n To read from a defected sector use:\n"
" sg_dd if=<scsi_device> skip=<lba> of=/dev/null bs=512 "
"count=1\n"
" To write to a defected sector use:\n"
" sg_dd of=<scsi_device> seek=<lba> if=/dev/zero bs=512 "
"count=1\n\n"
"Performs a WRITE LONG SCSI command\n"
);
}
static int info_offset(unsigned char * sensep, int sb_len)
{
int resp_code;
if (sb_len < 8)
return 0;
resp_code = (0x7f & sensep[0]);
if (resp_code>= 0x72) { /* descriptor format */
unsigned long long ull = 0;
/* if Information field, fetch it; contains signed number */
if (sg_get_sense_info_fld(sensep, sb_len, &ull))
return (int)(long long)ull;
} else if (sensep[0] & 0x80) { /* fixed, valid set */
if ((0 == sensep[3]) && (0 == sensep[4]))
return ((sensep[5] << 8) + sensep[6]);
else if ((0xff == sensep[3]) && (0xff == sensep[4]))
return ((sensep[5] << 8) + sensep[6] - (int)0x10000);
}
return 0;
}
static int has_blk_ili(unsigned char * sensep, int sb_len)
{
int resp_code;
const unsigned char * cup;
if (sb_len < 8)
return 0;
resp_code = (0x7f & sensep[0]);
if (resp_code>= 0x72) { /* descriptor format */
/* find block command descriptor */
if ((cup = sg_scsi_sense_desc_find(sensep, sb_len, 0x5)))
return ((cup[3] & 0x20) ? 1 : 0);
} else /* fixed */
return ((sensep[2] & 0x20) ? 1 : 0);
return 0;
}
int main(int argc, char * argv[])
{
int sg_fd, res, c, infd, sb_len, offset, k;
unsigned char writeLongCmdBlk [WRITE_LONG_CMD_LEN];
unsigned char * writeLongBuff = NULL;
void * rawp = NULL;
unsigned char sense_buffer[SENSE_BUFF_LEN];
int xfer_len = 520;
unsigned int lba = 0;
int verbose = 0;
int got_stdin;
char device_name[256];
char file_name[256];
char ebuff[EBUFF_SZ];
struct sg_io_hdr io_hdr;
struct sg_scsi_sense_hdr ssh;
int ret = 1;
memset(device_name, 0, sizeof device_name);
memset(file_name, 0, sizeof file_name);
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "hi:l:vVx:", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'h':
case '?':
usage();
return 0;
case 'i':
strncpy(file_name, optarg, sizeof(file_name));
break;
case 'l':
lba = sg_get_num(optarg);
if ((unsigned int)(-1) == lba) {
fprintf(stderr, "bad argument to '--lba'\n");
return 1;
}
break;
case 'v':
++verbose;
break;
case 'V':
fprintf(stderr, ME "version: %s\n", version_str);
return 0;
case 'x':
xfer_len = sg_get_num(optarg);
if (-1 == xfer_len) {
fprintf(stderr, "bad argument to '--xfer_len'\n");
return 1;
}
break;
default:
fprintf(stderr, "unrecognised switch code 0x%x ??\n", c);
usage();
return 1;
}
}
if (optind < argc) {
if ('\0' == device_name[0]) {
strncpy(device_name, argv[optind], sizeof(device_name) - 1);
device_name[sizeof(device_name) - 1] = '\0';
++optind;
}
if (optind < argc) {
for (; optind < argc; ++optind)
fprintf(stderr, "Unexpected extra argument: %s\n",
argv[optind]);
usage();
return 1;
}
}
if (0 == device_name[0]) {
fprintf(stderr, "missing device name!\n");
usage();
return 1;
}
if (xfer_len >= MAX_XFER_LEN){
fprintf(stderr, "xfer_len (%d) is out of range ( < %d)\n",
xfer_len, MAX_XFER_LEN);
usage();
return 1;
}
sg_fd = open(device_name, O_RDWR | O_NONBLOCK);
if (sg_fd < 0) {
fprintf(stderr, ME "open error: %s: ", device_name);
perror("");
return 1;
}
if (NULL == (rawp = malloc(MAX_XFER_LEN))) {
fprintf(stderr, ME "out of memory (query)\n");
close(sg_fd);
return 1;
}
writeLongBuff = rawp;
memset(rawp, 0xff, MAX_XFER_LEN);
if (file_name[0]) {
got_stdin = (0 == strcmp(file_name, "-")) ? 1 : 0;
if (got_stdin)
infd = 0;
else {
if ((infd = open(file_name, O_RDONLY)) < 0) {
snprintf(ebuff, EBUFF_SZ,
ME "could not open %s for reading", file_name);
perror(ebuff);
goto err_out;
}
}
res = read(infd, writeLongBuff, xfer_len);
if (res < 0) {
snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s", file_name);
perror(ebuff);
goto err_out;
}
if (res < xfer_len) {
fprintf(stderr, "tried to read %d bytes from %s, got %d bytes\n",
xfer_len, file_name, res);
fprintf(stderr, "pad with 0xff bytes and continue\n");
}
if (! got_stdin)
close(infd);
}
memset(writeLongCmdBlk, 0, WRITE_LONG_CMD_LEN);
writeLongCmdBlk[0] = WRITE_LONG_OPCODE;
/*lba*/
writeLongCmdBlk[2] = (lba & 0xff000000) >> 24;
writeLongCmdBlk[3] = (lba & 0x00ff0000) >> 16;
writeLongCmdBlk[4] = (lba & 0x0000ff00) >> 8;
writeLongCmdBlk[5] = (lba & 0x000000ff);
/*size*/
writeLongCmdBlk[7] = (xfer_len & 0x0000ff00) >> 8;
writeLongCmdBlk[8] = (xfer_len & 0x000000ff);
fprintf(stderr, ME "issue write long to device %s\n\t\txfer_len= %d "
"(0x%x), lba=%d (0x%x)\n", device_name, xfer_len, xfer_len,
lba, lba);
if (verbose) {
fprintf(stderr, " Write Long (10) cmd: ");
for (k = 0; k < WRITE_LONG_CMD_LEN; ++k)
fprintf(stderr, "%02x ", writeLongCmdBlk[k]);
fprintf(stderr, "\n");
}
memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
io_hdr.interface_id = 'S';
io_hdr.cmd_len = sizeof(writeLongCmdBlk);
io_hdr.mx_sb_len = sizeof(sense_buffer);
io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
io_hdr.dxfer_len = xfer_len;
io_hdr.dxferp = writeLongBuff;
io_hdr.cmdp = writeLongCmdBlk;
io_hdr.sbp = sense_buffer;
io_hdr.timeout = 60000; /* 60000 millisecs == 60 seconds */
/* do normal IO to find RB size (not dio or mmap-ed at this stage) */
if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
perror(ME "SG_IO ioctl WRITE LONG error");
goto err_out;
}
sb_len = io_hdr.sb_len_wr;
/* now for the error processing */
switch (sg_err_category3(&io_hdr)) {
case SG_LIB_CAT_RECOVERED:
sg_chk_n_print3("WRITE LONG, continuing", &io_hdr, verbose);
/* fall through */
case SG_LIB_CAT_CLEAN:
break;
default: /* won't bother decoding other categories */
if ((sg_normalize_sense(&io_hdr, &ssh)) &&
(ssh.sense_key == ILLEGAL_REQUEST) &&
((offset = info_offset(io_hdr.sbp, io_hdr.sb_len_wr)))) {
if (verbose)
sg_chk_n_print3("WRITE LONG command problem", &io_hdr, 1);
fprintf(stderr, "<<< nothing written to device >>>\n");
fprintf(stderr, "<<< device indicates 'xfer_len' should be %d "
">>>\n", xfer_len - offset);
if (! has_blk_ili(io_hdr.sbp, io_hdr.sb_len_wr))
fprintf(stderr, " [Invalid Length Indication (ILI) flag "
"expected but not found]\n");
goto err_out;
}
sg_chk_n_print3("WRITE LONG problem error", &io_hdr, verbose);
goto err_out;
}
ret = 0;
err_out:
if (rawp) free(rawp);
res = close(sg_fd);
if (res < 0) {
perror(ME "close error");
return 1;
}
return ret;
}