diff options
Diffstat (limited to 'src/sg_start.c')
-rw-r--r-- | src/sg_start.c | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/src/sg_start.c b/src/sg_start.c new file mode 100644 index 00000000..890b696a --- /dev/null +++ b/src/sg_start.c @@ -0,0 +1,618 @@ +/* + * 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; +} |