blob: 890b696ae93d897aed2b5a6ccde4eb1d2b962100 [file] [log] [blame]
/*
* Copyright (C) 1999-2020 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.
*
* SPDX-License-Identifier: GPL-2.0-or-later
Start/Stop parameter by Kurt Garloff, 6/2000
Sync cache parameter by Kurt Garloff, 1/2001
Guard block device answering sg's ioctls.
<dgilbert at interlog dot com> 12/2002
Convert to SG_IO ioctl so can use sg or block devices in 2.6.* 3/2003
This utility was written for the Linux 2.4 kernel series. It now
builds for the Linux 2.6 and 3 kernel series and various other
Operating Systems.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <getopt.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "sg_lib.h"
#include "sg_cmds_basic.h"
#include "sg_pr2serr.h"
static const char * version_str = "0.67 20200930"; /* sbc3r14; mmc6r01a */
static struct option long_options[] = {
{"eject", no_argument, 0, 'e'},
{"fl", required_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{"immed", no_argument, 0, 'i'},
{"load", no_argument, 0, 'l'},
{"loej", no_argument, 0, 'L'},
{"mod", required_argument, 0, 'm'},
{"noflush", no_argument, 0, 'n'},
{"new", no_argument, 0, 'N'},
{"old", no_argument, 0, 'O'},
{"pc", required_argument, 0, 'p'},
{"readonly", no_argument, 0, 'r'},
{"start", no_argument, 0, 's'},
{"stop", no_argument, 0, 'S'},
{"verbose", no_argument, 0, 'v'},
{"version", no_argument, 0, 'V'},
{0, 0, 0, 0},
};
struct opts_t {
bool do_eject;
bool do_immed;
bool do_load;
bool do_loej;
bool do_noflush;
bool do_readonly;
bool do_start;
bool do_stop;
bool opt_new;
bool verbose_given;
bool version_given;
int do_fl;
int do_help;
int do_mod;
int do_pc;
int verbose;
const char * device_name;
};
static void
usage()
{
pr2serr("Usage: sg_start [--eject] [--fl=FL] [--help] "
"[--immed] [--load] [--loej]\n"
" [--mod=PC_MOD] [--noflush] [--pc=PC] "
"[--readonly]\n"
" [--start] [--stop] [--verbose] "
"[--version] DEVICE\n"
" where:\n"
" --eject|-e stop unit then eject the medium\n"
" --fl=FL|-f FL format layer number (mmc5)\n"
" --help|-h print usage message then exit\n"
" --immed|-i device should return control after "
"receiving cdb,\n"
" default action is to wait until action "
"is complete\n"
" --load|-l load medium then start the unit\n"
" --loej|-L load or eject, corresponds to LOEJ bit "
"in cdb;\n"
" load when START bit also set, else "
"eject\n"
" --mod=PC_MOD|-m PC_MOD power condition modifier "
"(def: 0) (sbc)\n"
" --noflush|-n no flush prior to operation that limits "
"access (sbc)\n"
" --pc=PC|-p PC power condition: 0 (default) -> no "
"power condition,\n"
" 1 -> active, 2 -> idle, 3 -> standby, "
"5 -> sleep (mmc)\n"
" --readonly|-r open DEVICE read-only (def: read-write)\n"
" recommended if DEVICE is ATA disk\n"
" --start|-s start unit, corresponds to START bit "
"in cdb,\n"
" default (START=1) if no other options "
"given\n"
" --stop|-S stop unit (e.g. spin down disk)\n"
" --verbose|-v increase verbosity\n"
" --old|-O use old interface (use as first option)\n"
" --version|-V print version string then exit\n\n"
" Example: 'sg_start --stop /dev/sdb' stops unit\n"
" 'sg_start --eject /dev/scd0' stops unit and "
"ejects medium\n\n"
"Performs a SCSI START STOP UNIT command\n"
);
}
static void
usage_old()
{
pr2serr("Usage: sg_start [0] [1] [--eject] [--fl=FL] "
"[-i] [--imm=0|1]\n"
" [--load] [--loej] [--mod=PC_MOD] "
"[--noflush] [--pc=PC]\n"
" [--readonly] [--start] [--stop] [-v] [-V]\n"
" DEVICE\n"
" where:\n"
" 0 stop unit (e.g. spin down a disk or a "
"cd/dvd)\n"
" 1 start unit (e.g. spin up a disk or a "
"cd/dvd)\n"
" --eject stop then eject the medium\n"
" --fl=FL format layer number (mmc5)\n"
" -i return immediately (same as '--imm=1')\n"
" --imm=0|1 0->await completion(def), 1->return "
"immediately\n"
" --load load then start the medium\n"
" --loej load the medium if '-start' option is "
"also given\n"
" or stop unit and eject\n"
" --mod=PC_MOD power condition modifier "
"(def: 0) (sbc)\n"
" --noflush no flush prior to operation that limits "
"access (sbc)\n"
" --pc=PC power condition (in hex, default 0 -> no "
"power condition)\n"
" 1 -> active, 2 -> idle, 3 -> standby, "
"5 -> sleep (mmc)\n"
" --readonly|-r open DEVICE read-only (def: read-write)\n"
" recommended if DEVICE is ATA disk\n"
" --start start unit (same as '1'), default "
"action\n"
" --stop stop unit (same as '0')\n"
" -v verbose (print out SCSI commands)\n"
" --new|-N use new interface\n"
" -V print version string then exit\n\n"
" Example: 'sg_start --stop /dev/sdb' stops unit\n"
" 'sg_start --eject /dev/scd0' stops unit and "
"ejects medium\n\n"
"Performs a SCSI START STOP UNIT command\n"
);
}
static int
new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
{
int c, n, err;
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "ef:hilLm:nNOp:rsSvV", long_options,
&option_index);
if (c == -1)
break;
switch (c) {
case 'e':
op->do_eject = true;
op->do_loej = true;
break;
case 'f':
n = sg_get_num(optarg);
if ((n < 0) || (n > 3)) {
pr2serr("bad argument to '--fl='\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
op->do_loej = true;
op->do_start = true;
op->do_fl = n;
break;
case 'h':
case '?':
++op->do_help;
break;
case 'i':
op->do_immed = true;
break;
case 'l':
op->do_load = true;
op->do_loej = true;
break;
case 'L':
op->do_loej = true;
break;
case 'm':
n = sg_get_num(optarg);
if ((n < 0) || (n > 15)) {
pr2serr("bad argument to '--mod='\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
op->do_mod = n;
break;
case 'n':
op->do_noflush = true;
break;
case 'N':
break; /* ignore */
case 'O':
op->opt_new = false;
return 0;
case 'p':
n = sg_get_num(optarg);
if ((n < 0) || (n > 15)) {
pr2serr("bad argument to '--pc='\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
op->do_pc = n;
break;
case 'r':
op->do_readonly = true;
break;
case 's':
op->do_start = true;
break;
case 'S':
op->do_stop = true;
break;
case 'v':
op->verbose_given = true;
++op->verbose;
break;
case 'V':
op->version_given = true;
break;
default:
pr2serr("unrecognised option code %c [0x%x]\n", c, c);
if (op->do_help)
break;
usage();
return SG_LIB_SYNTAX_ERROR;
}
}
err = 0;
for (; optind < argc; ++optind) {
if (1 == strlen(argv[optind])) {
if (0 == strcmp("0", argv[optind])) {
op->do_stop = true;
continue;
} else if (0 == strcmp("1", argv[optind])) {
op->do_start = true;
continue;
}
}
if (NULL == op->device_name)
op->device_name = argv[optind];
else {
pr2serr("Unexpected extra argument: %s\n", argv[optind]);
++err;
}
}
if (err) {
usage();
return SG_LIB_SYNTAX_ERROR;
} else
return 0;
}
static int
old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
{
bool ambigu = false;
bool jmp_out;
bool startstop = false;
bool startstop_set = false;
int k, plen, num;
unsigned int u;
const char * cp;
for (k = 1; k < argc; ++k) {
cp = argv[k];
plen = strlen(cp);
if (plen <= 0)
continue;
if ('-' == *cp) {
for (--plen, ++cp, jmp_out = false; plen > 0;
--plen, ++cp) {
switch (*cp) {
case 'i':
if ('\0' == *(cp + 1))
op->do_immed = true;
else
jmp_out = true;
break;
case 'r':
op->do_readonly = true;
break;
case 'v':
op->verbose_given = true;
++op->verbose;
break;
case 'V':
op->version_given = true;
break;
case 'h':
case '?':
++op->do_help;
break;
case 'N':
op->opt_new = true;
return 0;
case 'O':
break;
case '-':
++cp;
--plen;
jmp_out = true;
break;
default:
jmp_out = true;
break;
}
if (jmp_out)
break;
}
if (plen <= 0)
continue;
if (0 == strncmp(cp, "eject", 5)) {
op->do_loej = true;
if (startstop_set && startstop)
ambigu = true;
else {
startstop = false;
startstop_set = true;
}
} else if (0 == strncmp("fl=", cp, 3)) {
num = sscanf(cp + 3, "%x", &u);
if (1 != num) {
pr2serr("Bad value after 'fl=' option\n");
usage_old();
return SG_LIB_SYNTAX_ERROR;
}
startstop = true;
startstop_set = true;
op->do_loej = true;
op->do_fl = u;
} else if (0 == strncmp("imm=", cp, 4)) {
num = sscanf(cp + 4, "%x", &u);
if ((1 != num) || (u > 1)) {
pr2serr("Bad value after 'imm=' option\n");
usage_old();
return SG_LIB_SYNTAX_ERROR;
}
op->do_immed = !! u;
} else if (0 == strncmp(cp, "load", 4)) {
op->do_loej = true;
if (startstop_set && (! startstop))
ambigu = true;
else {
startstop = true;
startstop_set = true;
}
} else if (0 == strncmp(cp, "loej", 4))
op->do_loej = true;
else if (0 == strncmp("pc=", cp, 3)) {
num = sscanf(cp + 3, "%x", &u);
if ((1 != num) || (u > 15)) {
pr2serr("Bad value after after 'pc=' option\n");
usage_old();
return SG_LIB_SYNTAX_ERROR;
}
op->do_pc = u;
} else if (0 == strncmp("mod=", cp, 4)) {
num = sscanf(cp + 3, "%x", &u);
if (1 != num) {
pr2serr("Bad value after 'mod=' option\n");
usage_old();
return SG_LIB_SYNTAX_ERROR;
}
op->do_mod = u;
} else if (0 == strncmp(cp, "noflush", 7)) {
op->do_noflush = true;
} else if (0 == strncmp(cp, "start", 5)) {
if (startstop_set && (! startstop))
ambigu = true;
else {
startstop = true;
startstop_set = true;
}
} else if (0 == strncmp(cp, "stop", 4)) {
if (startstop_set && startstop)
ambigu = true;
else {
startstop = false;
startstop_set = true;
}
} else if (0 == strncmp(cp, "old", 3))
;
else if (jmp_out) {
pr2serr("Unrecognized option: %s\n", cp);
usage_old();
return SG_LIB_SYNTAX_ERROR;
}
} else if (0 == strcmp("0", cp)) {
if (startstop_set && startstop)
ambigu = true;
else {
startstop = false;
startstop_set = true;
}
} else if (0 == strcmp("1", cp)) {
if (startstop_set && (! startstop))
ambigu = true;
else {
startstop = true;
startstop_set = true;
}
} else if (0 == op->device_name)
op->device_name = cp;
else {
pr2serr("too many arguments, got: %s, not "
"expecting: %s\n", op->device_name, cp);
usage_old();
return SG_LIB_SYNTAX_ERROR;
}
if (ambigu) {
pr2serr("please, only one of 0, 1, --eject, "
"--load, --start or --stop\n");
usage_old();
return SG_LIB_CONTRADICT;
} else if (startstop_set) {
if (startstop)
op->do_start = true;
else
op->do_stop = true;
}
}
return 0;
}
static int
parse_cmd_line(struct opts_t * op, int argc, char * argv[])
{
int res;
char * cp;
cp = getenv("SG3_UTILS_OLD_OPTS");
if (cp) {
op->opt_new = false;
res = old_parse_cmd_line(op, argc, argv);
if ((0 == res) && op->opt_new)
res = new_parse_cmd_line(op, argc, argv);
} else {
op->opt_new = true;
res = new_parse_cmd_line(op, argc, argv);
if ((0 == res) && (! op->opt_new))
res = old_parse_cmd_line(op, argc, argv);
}
return res;
}
int
main(int argc, char * argv[])
{
int res;
int sg_fd = -1;
int ret = 0;
struct opts_t opts;
struct opts_t * op;
op = &opts;
memset(op, 0, sizeof(opts));
op->do_fl = -1; /* only when >= 0 set FL bit */
res = parse_cmd_line(op, argc, argv);
if (res)
return res;
if (op->do_help) {
if (op->opt_new)
usage();
else
usage_old();
return 0;
}
#ifdef DEBUG
pr2serr("In DEBUG mode, ");
if (op->verbose_given && op->version_given) {
pr2serr("but override: '-vV' given, zero verbose and continue\n");
op->verbose_given = false;
op->version_given = false;
op->verbose = 0;
} else if (! op->verbose_given) {
pr2serr("set '-vv'\n");
op->verbose = 2;
} else
pr2serr("keep verbose=%d\n", op->verbose);
#else
if (op->verbose_given && op->version_given)
pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
#endif
if (op->version_given) {
pr2serr("Version string: %s\n", version_str);
return 0;
}
if (op->do_start && op->do_stop) {
pr2serr("Ambiguous to give both '--start' and '--stop'\n");
return SG_LIB_CONTRADICT;
}
if (op->do_load && op->do_eject) {
pr2serr("Ambiguous to give both '--load' and '--eject'\n");
return SG_LIB_CONTRADICT;
}
if (op->do_load)
op->do_start = true;
else if ((op->do_eject) || op->do_stop)
op->do_start = false;
else if (op->opt_new && op->do_loej && (! op->do_start))
op->do_start = true; /* --loej alone in new interface is load */
else if ((! op->do_loej) && (-1 == op->do_fl) && (0 == op->do_pc))
op->do_start = true;
/* default action is to start when no other active options */
if (0 == op->device_name) {
pr2serr("No DEVICE argument given\n");
if (op->opt_new)
usage();
else
usage_old();
return SG_LIB_SYNTAX_ERROR;
}
if (op->do_fl >= 0) {
if (! op->do_start) {
pr2serr("Giving '--fl=FL' with '--stop' (or '--eject') is "
"invalid\n");
return SG_LIB_CONTRADICT;
}
if (op->do_pc > 0) {
pr2serr("Giving '--fl=FL' with '--pc=PC' when PC is non-zero "
"is invalid\n");
return SG_LIB_CONTRADICT;
}
}
sg_fd = sg_cmds_open_device(op->device_name, op->do_readonly,
op->verbose);
if (sg_fd < 0) {
if (op->verbose)
pr2serr("Error trying to open %s: %s\n", op->device_name,
safe_strerror(-sg_fd));
ret = sg_convert_errno(-sg_fd);
goto fini;
}
if (op->do_fl >= 0)
res = sg_ll_start_stop_unit(sg_fd, op->do_immed, op->do_fl, 0 /* pc */,
true /* fl */, true /* loej */,
true /*start */, true /* noisy */,
op->verbose);
else if (op->do_pc > 0)
res = sg_ll_start_stop_unit(sg_fd, op->do_immed, op->do_mod,
op->do_pc, op->do_noflush, false, false,
true, op->verbose);
else
res = sg_ll_start_stop_unit(sg_fd, op->do_immed, 0, false,
op->do_noflush, op->do_loej,
op->do_start, true, op->verbose);
ret = res;
if (res) {
if (op->verbose < 2) {
char b[80];
sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
pr2serr("%s\n", b);
}
pr2serr("START STOP UNIT command failed\n");
}
fini:
if (sg_fd >= 0) {
res = sg_cmds_close_device(sg_fd);
if (res < 0) {
if (0 == ret)
ret = sg_convert_errno(-res);
}
}
if (0 == op->verbose) {
if (! sg_if_can2stderr("sg_start failed: ", ret))
pr2serr("Some error occurred, try again with '-v' "
"or '-vv' for more information\n");
}
return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
}