/* * 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. 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 #include #include #include #include #include #include #include #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; }