aboutsummaryrefslogtreecommitdiff
path: root/src/sg_start.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sg_start.c')
-rw-r--r--src/sg_start.c618
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;
+}