/*
 * sdremap <dev> <blkno> - tell a SCSI disk to remap the bad block
 *                         specified on the command line.
 *
 * Compile this on your favorite NetBSD 1.1 system with:
 *
 *	cc -I/sys -DOLD_SCSI_BYTES -o sdremap sdremap.c
 *
 * or on newer NetBSD systems with:
 *
 *	cc -I/sys -o sdremap sdremap.c
 *
 * It will be pretty obvious if you need to add or remove the OLD_SCSI_BYTES
 * define; this program won't compile if the setting is incorrect.
 *
 * When you use this program, make sure the disk you're mucking with
 * has no mounted filesystems (unless that's just not possible, in which
 * case, make sure they're mounted read-only, and you're in single-user
 * mode).  You must use the "raw partition" of the disk.  On an i386, this
 * is `d', and everywhere else `c'.
 *
 * I.e.:
 *	./sdremap /dev/rsd1d 356642
 *
 * Written by Jason R. Thorpe <thorpej@NetBSD.ORG>.
 * This program is in the public domain.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * TODO:
 *	Handle multiple blocks with a single command.  I'm just lazy.
 */

#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/scsiio.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <scsi/scsi_all.h>
#include <scsi/scsi_disk.h>

extern	char *__progname;		/* from crt0.o */

static	void usage __P((void));
static	void cleanup __P((void));

static	int disk_fd;

int
main(argc, argv)
	int argc;
	char **argv;
{
	struct scsi_reassign_blocks cmd;
	struct scsi_reassign_blocks_data desc;
	scsireq_t req;
	char *disk, *cp;
	u_int32_t blkno;
	u_int16_t desc_data_len;

	if (argc != 3)
		usage();

	disk = argv[1];
	blkno = (u_int32_t)strtol(argv[2], &cp, 10);
	if (*cp != '\0')
		errx(1, "invalid block number `%s'", argv[2]);

	/* Register cleanup function. */
	if (atexit(cleanup))
		err(1, "can't register cleanup function");

	/* Open the device... */
	if ((disk_fd = open(disk, O_RDWR, 0600)) == -1)
		err(1, "%s: open", disk);

	/* Build SCSI command. */
	bzero(&cmd, sizeof(cmd));
	cmd.opcode = REASSIGN_BLOCKS;

	/*
	 * Build the block descriptor.
	 */
	desc_data_len = sizeof(desc.defect_descriptor[0]);
	bzero(&desc, sizeof(desc));
	/* Descriptor length. */
#ifdef OLD_SCSI_BYTES
	desc.length_msb = (desc_data_len >> 8) & 0xff;
	desc.length_lsb = desc_data_len & 0xff;
#else
	desc.length[0] = (desc_data_len >> 8) & 0xff;
	desc.length[1] = desc_data_len & 0xff;
#endif
	/* Block number. */
#ifdef OLD_SCSI_BYTES
	desc.defect_descriptor[0].dlbaddr_3 = (blkno >> 24) & 0xff;
	desc.defect_descriptor[0].dlbaddr_2 = (blkno >> 16) & 0xff;
	desc.defect_descriptor[0].dlbaddr_1 = (blkno >> 8) & 0xff;
	desc.defect_descriptor[0].dlbaddr_0 = blkno & 0xff;
#else
	desc.defect_descriptor[0].dlbaddr[0] = (blkno >> 24) & 0xff;
	desc.defect_descriptor[0].dlbaddr[1] = (blkno >> 16) & 0xff;
	desc.defect_descriptor[0].dlbaddr[2] = (blkno >> 8) & 0xff;
	desc.defect_descriptor[0].dlbaddr[3] = blkno & 0xff;
#endif

	/* Fill out user-level SCSI request. */
	bzero(&req, sizeof(req));
	bcopy((struct scsi_generic *)&cmd, &req.cmd, sizeof(cmd));
	req.cmdlen = (u_char)sizeof(cmd);
	req.databuf = (void *)&desc;
	req.datalen = (u_long)sizeof(desc);
	req.flags |= SCCMD_WRITE;
	req.timeout = 20000;			/* XXX */

	/* Send the request to the SCSI subsystem. */
	if (ioctl(disk_fd, SCIOCCOMMAND, (char *)&req) == -1)
		err(1, "SCIOCCOMMAND");

	/*
	 * XXX should check for error condition and print
	 * sense data.
	 */

	exit(0);
}

static void
cleanup()
{

	/* ...simple enough... */
	(void)close(disk_fd);
}

static void
usage()
{

	fprintf(stderr, "usage: %s disk blkno\n", __progname);
	exit(1);
}