blob: 59c54987ce8c1ef36e354f4bf4a2f57b996ff5cd [file] [log] [blame]
/* A utility program originally written for the Linux OS SCSI subsystem.
* Copyright (C) 1999-2015 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 send either device, bus or host resets to device,
* or bus or host associated with the given sg device.
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "sg_io_linux.h"
#define ME "sg_reset: "
static const char * version_str = "0.60 20151219";
#ifndef SG_SCSI_RESET
#define SG_SCSI_RESET 0x2284
#endif
#ifndef SG_SCSI_RESET_NOTHING
#define SG_SCSI_RESET_NOTHING 0
#define SG_SCSI_RESET_DEVICE 1
#define SG_SCSI_RESET_BUS 2
#define SG_SCSI_RESET_HOST 3
#endif
#ifndef SG_SCSI_RESET_TARGET
#define SG_SCSI_RESET_TARGET 4
#endif
#ifndef SG_SCSI_RESET_NO_ESCALATE
#define SG_SCSI_RESET_NO_ESCALATE 0x100
#endif
static struct option long_options[] = {
{"bus", no_argument, 0, 'b'},
{"device", no_argument, 0, 'd'},
{"help", no_argument, 0, 'z'},
{"host", no_argument, 0, 'H'},
{"no-esc", no_argument, 0, 'N'},
{"no-escalate", no_argument, 0, 'N'},
{"target", no_argument, 0, 't'},
{"verbose", no_argument, 0, 'v'},
{"version", no_argument, 0, 'V'},
{0, 0, 0, 0},
};
#ifdef __GNUC__
static int pr2serr(const char * fmt, ...)
__attribute__ ((format (printf, 1, 2)));
#else
static int pr2serr(const char * fmt, ...);
#endif
static int
pr2serr(const char * fmt, ...)
{
va_list args;
int n;
va_start(args, fmt);
n = vfprintf(stderr, fmt, args);
va_end(args);
return n;
}
static void
usage(int compat_mode)
{
pr2serr("Usage: sg_reset [--bus] [--device] [--help] [--host] [--no-esc] "
"[--target]\n"
" [--verbose] [--version] DEVICE\n"
" where:\n"
" --bus|-b SCSI bus reset (SPI concept), might be all "
"targets\n"
" --device|-d device (logical unit) reset\n");
if (compat_mode) {
pr2serr(" --help|-z print usage information then exit\n"
" --host|-h|-H host (bus adapter: HBA) reset\n");
} else {
pr2serr(" --help|-h print usage information then exit\n"
" --host|-H host (bus adapter: HBA) reset\n");
}
pr2serr(" --no-esc|-N overrides default action and only does "
"reset requested\n"
" --target|-t target reset. The target holds the DEVICE "
"and perhaps\n"
" other LUs\n"
" --verbose|-v increase the level of verbosity\n"
" --version|-V print version number then exit\n\n"
"Use SG_SCSI_RESET ioctl to send a reset to the "
"host/bus/target/device\nalong the DEVICE path. The DEVICE "
"itself is known as a logical unit (LU)\nin SCSI terminology.\n"
"Be warned: if the '-N' option is not given then if '-d' "
"fails then a\ntarget reset ('-t') is instigated. And it "
"'-t' fails then a bus reset\n('-b') is instigated. And if "
"'-b' fails then a host reset ('h') is\ninstigated. It is "
"recommended to use '-N' to stop the reset escalation.\n"
);
}
int main(int argc, char * argv[])
{
int c, sg_fd, res, k, hold_errno;
int do_device_reset = 0;
int do_bus_reset = 0;
int do_host_reset = 0;
int no_escalate = 0;
int do_target_reset = 0;
int verbose = 0;
char * device_name = NULL;
char * cp = NULL;
cp = getenv("SG3_UTILS_OLD_OPTS");
if (NULL == cp)
cp = getenv("SG_RESET_OLD_OPTS");
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "bdhHNtvVz", long_options,
&option_index);
if (c == -1)
break;
switch (c) {
case 'b':
++do_bus_reset;
break;
case 'd':
++do_device_reset;
break;
case 'h':
if (cp) {
++do_host_reset;
break;
} else {
usage(!!cp);
return 0;
}
case 'H':
++do_host_reset;
break;
case 'N':
++no_escalate;
break;
case 't':
++do_target_reset;
break;
case 'v':
++verbose;
break;
case 'V':
pr2serr(ME "version: %s\n", version_str);
return 0;
case 'z':
usage(!!cp);
return 0;
default:
pr2serr("unrecognised option code 0x%x ??\n", c);
usage(!!cp);
return SG_LIB_SYNTAX_ERROR;
}
}
if (optind < argc) {
if (NULL == device_name) {
device_name = argv[optind];
++optind;
}
if (optind < argc) {
for (; optind < argc; ++optind)
pr2serr("Unexpected extra argument: %s\n", argv[optind]);
usage(!!cp);
return SG_LIB_SYNTAX_ERROR;
}
}
if (NULL == device_name) {
pr2serr("Missing DEVICE name. Use '--help' to see usage.\n");
return SG_LIB_SYNTAX_ERROR;
}
if (cp && (0 == verbose))
++verbose; // older behaviour was more verbose
if ((!!do_device_reset + !!do_target_reset + !!do_bus_reset +
!!do_host_reset) > 1) {
pr2serr("Can only request one type of reset per invocation\n");
return 1;
}
sg_fd = open(device_name, O_RDWR | O_NONBLOCK);
if (sg_fd < 0) {
pr2serr(ME "open error: %s: ", device_name);
perror("");
return 1;
}
k = SG_SCSI_RESET_NOTHING;
if (do_device_reset) {
if (verbose)
printf(ME "starting device reset\n");
k = SG_SCSI_RESET_DEVICE;
}
else if (do_target_reset) {
if (verbose)
printf(ME "starting target reset\n");
k = SG_SCSI_RESET_TARGET;
}
else if (do_bus_reset) {
if (verbose)
printf(ME "starting bus reset\n");
k = SG_SCSI_RESET_BUS;
}
else if (do_host_reset) {
if (verbose)
printf(ME "starting host reset\n");
k = SG_SCSI_RESET_HOST;
}
if (no_escalate)
k += SG_SCSI_RESET_NO_ESCALATE;
if (verbose > 2)
pr2serr(" third argument to ioctl(SG_SCSI_RESET) is 0x%x\n", k);
res = ioctl(sg_fd, SG_SCSI_RESET, &k);
if (res < 0) {
hold_errno = errno;
switch (errno) {
case EBUSY:
pr2serr(ME "BUSY, may be resetting now\n");
break;
case ENODEV:
pr2serr(ME "'no device' error, may be temporary while device is "
"resetting\n");
break;
case EAGAIN:
pr2serr(ME "try again later, may be resetting now\n");
break;
case EIO:
pr2serr(ME "reset (for value=0x%x) may not be available\n", k);
break;
case EPERM:
case EACCES:
pr2serr(ME "reset requires CAP_SYS_ADMIN (root) permission\n");
break;
case EINVAL:
pr2serr(ME "SG_SCSI_RESET not supported (for value=0x%x)\n", k);
default:
perror(ME "SG_SCSI_RESET failed");
break;
}
if (verbose > 1)
pr2serr(ME "ioctl(SG_SCSI_RESET) returned %d, errno=%d\n", res,
hold_errno);
close(sg_fd);
return 1;
}
if (no_escalate)
k -= SG_SCSI_RESET_NO_ESCALATE;
if (verbose) {
if (SG_SCSI_RESET_NOTHING == k)
printf(ME "did nothing, device is normal mode\n");
else if (SG_SCSI_RESET_DEVICE == k)
printf(ME "completed device %sreset\n", (no_escalate ?
"" : "(or target or bus or host) "));
else if (SG_SCSI_RESET_TARGET == k)
printf(ME "completed target %sreset\n", (no_escalate ?
"" : "(or bus or host) "));
else if (SG_SCSI_RESET_BUS == k)
printf(ME "completed bus %sreset\n", (no_escalate ?
"" : "(or host) "));
else if (SG_SCSI_RESET_HOST == k)
printf(ME "completed host reset\n");
}
if (close(sg_fd) < 0) {
perror(ME "close error");
return 1;
}
return 0;
}