aboutsummaryrefslogtreecommitdiff
path: root/src/sg_persist.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sg_persist.c')
-rw-r--r--src/sg_persist.c1324
1 files changed, 1324 insertions, 0 deletions
diff --git a/src/sg_persist.c b/src/sg_persist.c
new file mode 100644
index 00000000..872f16ee
--- /dev/null
+++ b/src/sg_persist.c
@@ -0,0 +1,1324 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 2004-2022 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
+ *
+ * This program issues the SCSI PERSISTENT IN and OUT commands.
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "0.69 20220118";
+
+
+#define PRIN_RKEY_SA 0x0
+#define PRIN_RRES_SA 0x1
+#define PRIN_RCAP_SA 0x2
+#define PRIN_RFSTAT_SA 0x3
+#define PROUT_REG_SA 0x0
+#define PROUT_RES_SA 0x1
+#define PROUT_REL_SA 0x2
+#define PROUT_CLEAR_SA 0x3
+#define PROUT_PREE_SA 0x4
+#define PROUT_PREE_AB_SA 0x5
+#define PROUT_REG_IGN_SA 0x6
+#define PROUT_REG_MOVE_SA 0x7
+#define PROUT_REPL_LOST_SA 0x8
+#define MX_ALLOC_LEN 8192
+#define MX_TIDS 32
+#define MX_TID_LEN 256
+
+#define ME "sg_persist"
+
+#define SG_PERSIST_IN_RDONLY "SG_PERSIST_IN_RDONLY"
+
+struct opts_t {
+ bool inquiry; /* set true by default (unlike most bools) */
+ bool param_alltgpt;
+ bool param_aptpl;
+ bool param_unreg;
+ bool pr_in; /* true: PR_IN (def); false: PR_OUT */
+ bool readonly;
+ bool readwrite_force;/* set when '-yy' given. Ooverrides environment
+ variable SG_PERSIST_IN_RDONLY and opens RW */
+ bool verbose_given;
+ bool version_given;
+ int hex;
+ int num_transportids;
+ int prin_sa;
+ int prout_sa;
+ int verbose;
+ uint32_t alloc_len;
+ uint32_t param_rtp;
+ uint32_t prout_type;
+ uint64_t param_rk;
+ uint64_t param_sark;
+ uint8_t transportid_arr[MX_TIDS * MX_TID_LEN];
+};
+
+
+static struct option long_options[] = {
+ {"alloc-length", required_argument, 0, 'l'},
+ {"alloc_length", required_argument, 0, 'l'},
+ {"clear", no_argument, 0, 'C'},
+ {"device", required_argument, 0, 'd'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"in", no_argument, 0, 'i'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"no-inquiry", no_argument, 0, 'n'},
+ {"no_inquiry", no_argument, 0, 'n'},
+ {"out", no_argument, 0, 'o'},
+ {"param-alltgpt", no_argument, 0, 'Y'},
+ {"param_alltgpt", no_argument, 0, 'Y'},
+ {"param-aptpl", no_argument, 0, 'Z'},
+ {"param_aptpl", no_argument, 0, 'Z'},
+ {"param-rk", required_argument, 0, 'K'},
+ {"param_rk", required_argument, 0, 'K'},
+ {"param-sark", required_argument, 0, 'S'},
+ {"param_sark", required_argument, 0, 'S'},
+ {"param-unreg", no_argument, 0, 'U'},
+ {"param_unreg", no_argument, 0, 'U'},
+ {"preempt", no_argument, 0, 'P'},
+ {"preempt-abort", no_argument, 0, 'A'},
+ {"preempt_abort", no_argument, 0, 'A'},
+ {"prout-type", required_argument, 0, 'T'},
+ {"prout_type", required_argument, 0, 'T'},
+ {"read-full-status", no_argument, 0, 's'},
+ {"read_full_status", no_argument, 0, 's'},
+ {"read-keys", no_argument, 0, 'k'},
+ {"read_keys", no_argument, 0, 'k'},
+ {"readonly", no_argument, 0, 'y'},
+ {"read-reservation", no_argument, 0, 'r'},
+ {"read_reservation", no_argument, 0, 'r'},
+ {"read-status", no_argument, 0, 's'},
+ {"read_status", no_argument, 0, 's'},
+ {"register", no_argument, 0, 'G'},
+ {"register-ignore", no_argument, 0, 'I'},
+ {"register_ignore", no_argument, 0, 'I'},
+ {"register-move", no_argument, 0, 'M'},
+ {"register_move", no_argument, 0, 'M'},
+ {"release", no_argument, 0, 'L'},
+ {"relative-target-port", required_argument, 0, 'Q'},
+ {"relative_target_port", required_argument, 0, 'Q'},
+ {"replace-lost", no_argument, 0, 'z'},
+ {"replace_lost", no_argument, 0, 'z'},
+ {"report-capabilities", no_argument, 0, 'c'},
+ {"report_capabilities", no_argument, 0, 'c'},
+ {"reserve", no_argument, 0, 'R'},
+ {"transport-id", required_argument, 0, 'X'},
+ {"transport_id", required_argument, 0, 'X'},
+ {"unreg", no_argument, 0, 'U'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0}
+};
+
+static const char * prin_sa_strs[] = {
+ "Read keys",
+ "Read reservation",
+ "Report capabilities",
+ "Read full status",
+ "[reserved 0x4]",
+ "[reserved 0x5]",
+ "[reserved 0x6]",
+ "[reserved 0x7]",
+};
+static const int num_prin_sa_strs = SG_ARRAY_SIZE(prin_sa_strs);
+
+static const char * prout_sa_strs[] = {
+ "Register",
+ "Reserve",
+ "Release",
+ "Clear",
+ "Preempt",
+ "Preempt and abort",
+ "Register and ignore existing key",
+ "Register and move",
+ "Replace lost reservation",
+ "[reserved 0x9]",
+};
+static const int num_prout_sa_strs = SG_ARRAY_SIZE(prout_sa_strs);
+
+static const char * pr_type_strs[] = {
+ "obsolete [0]",
+ "Write Exclusive",
+ "obsolete [2]",
+ "Exclusive Access",
+ "obsolete [4]",
+ "Write Exclusive, registrants only",
+ "Exclusive Access, registrants only",
+ "Write Exclusive, all registrants",
+ "Exclusive Access, all registrants",
+ "obsolete [9]", "obsolete [0xa]", "obsolete [0xb]", "obsolete [0xc]",
+ "obsolete [0xd]", "obsolete [0xe]", "obsolete [0xf]",
+};
+
+
+static void
+usage(int help)
+{
+ if (help < 2) {
+ pr2serr("Usage: sg_persist [OPTIONS] [DEVICE]\n"
+ " where the main OPTIONS are:\n"
+ " --clear|-C PR Out: Clear\n"
+ " --help|-h print usage message, "
+ "twice for more\n"
+ " --in|-i request PR In command "
+ "(default)\n"
+ " --out|-o request PR Out command\n"
+ " --param-rk=RK|-K RK PR Out parameter reservation "
+ "key\n"
+ " (RK is in hex)\n"
+ " --param-sark=SARK|-S SARK PR Out parameter service "
+ "action\n"
+ " reservation key (SARK is "
+ "in hex)\n"
+ " --preempt|-P PR Out: Preempt\n"
+ " --preempt-abort|-A PR Out: Preempt and Abort\n"
+ " --prout-type=TYPE|-T TYPE PR Out type field (see "
+ "'-hh')\n"
+ " --read-full-status|-s PR In: Read Full Status\n"
+ " --read-keys|-k PR In: Read Keys "
+ "(default)\n");
+ pr2serr(" --read-reservation|-r PR In: Read Reservation\n"
+ " --read-status|-s PR In: Read Full Status\n"
+ " --register|-G PR Out: Register\n"
+ " --register-ignore|-I PR Out: Register and Ignore\n"
+ " --register-move|-M PR Out: Register and Move\n"
+ " for '--register-move'\n"
+ " --release|-L PR Out: Release\n"
+ " --replace-lost|-x PR Out: Replace Lost "
+ "Reservation\n"
+ " --report-capabilities|-c PR In: Report Capabilities\n"
+ " --reserve|-R PR Out: Reserve\n"
+ " --unreg|-U optional with PR Out "
+ "Register and Move\n\n"
+ "Performs a SCSI PERSISTENT RESERVE (IN or OUT) command. "
+ "Invoking\n'sg_persist DEVICE' will do a PR In Read Keys "
+ "command. Use '-hh'\nfor more options and TYPE meanings.\n");
+ } else {
+ pr2serr("Usage: sg_persist [OPTIONS] [DEVICE]\n"
+ " where the other OPTIONS are:\n"
+ " --alloc-length=LEN|-l LEN allocation length hex "
+ "value (used with\n"
+ " PR In only) (default: 8192 "
+ "(2000 in hex))\n"
+ " --device=DEVICE|-d DEVICE supply DEVICE as an option "
+ "rather than\n"
+ " an argument\n"
+ " --hex|-H output response in hex (for "
+ "PR In commands)\n"
+ " --maxlen=LEN|-m LEN allocation length in "
+ "decimal, by default.\n"
+ " like --alloc-len= "
+ "(def: 8192, 8k, 2000h)\n"
+ " --no-inquiry|-n skip INQUIRY (default: do "
+ "INQUIRY)\n"
+ " --param-alltgpt|-Y PR Out parameter "
+ "'ALL_TG_PT'\n"
+ " --param-aptpl|-Z PR Out parameter 'APTPL'\n"
+ " --readonly|-y open DEVICE read-only (def: "
+ "read-write)\n"
+ " --relative-target-port=RTPI|-Q RTPI relative target "
+ "port "
+ "identifier\n"
+ " --transport-id=TIDS|-X TIDS one or more "
+ "TransportIDs can\n"
+ " be given in several "
+ "forms\n"
+ " --verbose|-v output additional debug "
+ "information\n"
+ " --version|-V output version string\n\n"
+ "For the main options use '--help' or '-h' once.\n\n\n");
+ pr2serr("PR Out TYPE field value meanings:\n"
+ " 0: obsolete (was 'read shared' in SPC)\n"
+ " 1: write exclusive\n"
+ " 2: obsolete (was 'read exclusive')\n"
+ " 3: exclusive access\n"
+ " 4: obsolete (was 'shared access')\n"
+ " 5: write exclusive, registrants only\n"
+ " 6: exclusive access, registrants only\n"
+ " 7: write exclusive, all registrants\n"
+ " 8: exclusive access, all registrants\n");
+ }
+}
+
+static int
+prin_work(int sg_fd, const struct opts_t * op)
+{
+ int k, j, num, add_len, add_desc_len;
+ int res = 0;
+ unsigned int pr_gen;
+ uint8_t * bp;
+ uint8_t * pr_buff = NULL;
+ uint8_t * free_pr_buff = NULL;
+
+ pr_buff = sg_memalign(op->alloc_len, 0 /* page aligned */, &free_pr_buff,
+ false);
+ if (NULL == pr_buff) {
+ pr2serr("%s: unable to allocate %d bytes on heap\n", __func__,
+ op->alloc_len);
+ return sg_convert_errno(ENOMEM);
+ }
+ res = sg_ll_persistent_reserve_in(sg_fd, op->prin_sa, pr_buff,
+ op->alloc_len, true, op->verbose);
+ if (res) {
+ char b[64];
+ char bb[80];
+
+ if (op->prin_sa < num_prin_sa_strs)
+ snprintf(b, sizeof(b), "%s", prin_sa_strs[op->prin_sa]);
+ else
+ snprintf(b, sizeof(b), "service action=0x%x", op->prin_sa);
+
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("PR in (%s): command not supported\n", b);
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("PR in (%s): bad field in cdb or parameter list (perhaps "
+ "unsupported service action)\n", b);
+ else {
+ sg_get_category_sense_str(res, sizeof(bb), bb, op->verbose);
+ pr2serr("PR in (%s): %s\n", b, bb);
+ }
+ goto fini;
+ }
+ if (PRIN_RCAP_SA == op->prin_sa) {
+ if (8 != pr_buff[1]) {
+ pr2serr("Unexpected response for PRIN Report Capabilities\n");
+ if (op->hex)
+ hex2stdout(pr_buff, pr_buff[1], 1);
+ res = SG_LIB_CAT_MALFORMED;
+ goto fini;
+ }
+ if (op->hex)
+ hex2stdout(pr_buff, 8, 1);
+ else {
+ printf("Report capabilities response:\n");
+ printf(" Replace Lost Reservation Capable(RLR_C): %d\n",
+ !!(pr_buff[2] & 0x80)); /* added spc4r26 */
+ printf(" Compatible Reservation Handling(CRH): %d\n",
+ !!(pr_buff[2] & 0x10));
+ printf(" Specify Initiator Ports Capable(SIP_C): %d\n",
+ !!(pr_buff[2] & 0x8));
+ printf(" All Target Ports Capable(ATP_C): %d\n",
+ !!(pr_buff[2] & 0x4));
+ printf(" Persist Through Power Loss Capable(PTPL_C): %d\n",
+ !!(pr_buff[2] & 0x1));
+ printf(" Type Mask Valid(TMV): %d\n", !!(pr_buff[3] & 0x80));
+ printf(" Allow Commands: %d\n", (pr_buff[3] >> 4) & 0x7);
+ printf(" Persist Through Power Loss Active(PTPL_A): %d\n",
+ !!(pr_buff[3] & 0x1));
+ if (pr_buff[3] & 0x80) {
+ printf(" Support indicated in Type mask:\n");
+ printf(" %s: %d\n", pr_type_strs[7],
+ !!(pr_buff[4] & 0x80)); /* WR_EX_AR */
+ printf(" %s: %d\n", pr_type_strs[6],
+ !!(pr_buff[4] & 0x40)); /* EX_AC_RO */
+ printf(" %s: %d\n", pr_type_strs[5],
+ !!(pr_buff[4] & 0x20)); /* WR_EX_RO */
+ printf(" %s: %d\n", pr_type_strs[3],
+ !!(pr_buff[4] & 0x8)); /* EX_AC */
+ printf(" %s: %d\n", pr_type_strs[1],
+ !!(pr_buff[4] & 0x2)); /* WR_EX */
+ printf(" %s: %d\n", pr_type_strs[8],
+ !!(pr_buff[5] & 0x1)); /* EX_AC_AR */
+ }
+ }
+ } else {
+ pr_gen = sg_get_unaligned_be32(pr_buff + 0);
+ add_len = sg_get_unaligned_be32(pr_buff + 4);
+ if (op->hex) {
+ if (op->hex > 1)
+ hex2stdout(pr_buff, add_len + 8, ((2 == op->hex) ? 1 : -1));
+ else {
+ printf(" PR generation=0x%x, ", pr_gen);
+ if (add_len <= 0)
+ printf("Additional length=%d\n", add_len);
+ if ((uint32_t)add_len > (op->alloc_len - 8)) {
+ printf("Additional length too large=%d, truncate\n",
+ add_len);
+ hex2stdout((pr_buff + 8), op->alloc_len - 8, 1);
+ } else {
+ printf("Additional length=%d\n", add_len);
+ hex2stdout((pr_buff + 8), add_len, 1);
+ }
+ }
+ } else if (PRIN_RKEY_SA == op->prin_sa) {
+ printf(" PR generation=0x%x, ", pr_gen);
+ num = add_len / 8;
+ if (num > 0) {
+ if (1 == num)
+ printf("1 registered reservation key follows:\n");
+ else
+ printf("%d registered reservation keys follow:\n", num);
+ bp = pr_buff + 8;
+ for (k = 0; k < num; ++k, bp += 8)
+ printf(" 0x%" PRIx64 "\n",
+ sg_get_unaligned_be64(bp + 0));
+ } else
+ printf("there are NO registered reservation keys\n");
+ } else if (PRIN_RRES_SA == op->prin_sa) {
+ printf(" PR generation=0x%x, ", pr_gen);
+ num = add_len / 16;
+ if (num > 0) {
+ printf("Reservation follows:\n");
+ bp = pr_buff + 8;
+ printf(" Key=0x%" PRIx64 "\n", sg_get_unaligned_be64(bp));
+ j = ((bp[13] >> 4) & 0xf);
+ if (0 == j)
+ printf(" scope: LU_SCOPE, ");
+ else
+ printf(" scope: %d ", j);
+ j = (bp[13] & 0xf);
+ printf(" type: %s\n", pr_type_strs[j]);
+ } else
+ printf("there is NO reservation held\n");
+ } else if (PRIN_RFSTAT_SA == op->prin_sa) {
+ printf(" PR generation=0x%x\n", pr_gen);
+ bp = pr_buff + 8;
+ if (0 == add_len) {
+ printf(" No full status descriptors\n");
+ if (op->verbose)
+ printf(" So there are no registered IT nexuses\n");
+ }
+ for (k = 0; k < add_len; k += num, bp += num) {
+ add_desc_len = sg_get_unaligned_be32(bp + 20);
+ num = 24 + add_desc_len;
+ printf(" Key=0x%" PRIx64 "\n", sg_get_unaligned_be64(bp));
+ if (bp[12] & 0x2)
+ printf(" All target ports bit set\n");
+ else {
+ printf(" All target ports bit clear\n");
+ printf(" Relative port address: 0x%x\n",
+ sg_get_unaligned_be16(bp + 18));
+ }
+ if (bp[12] & 0x1) {
+ printf(" << Reservation holder >>\n");
+ j = ((bp[13] >> 4) & 0xf);
+ if (0 == j)
+ printf(" scope: LU_SCOPE, ");
+ else
+ printf(" scope: %d ", j);
+ j = (bp[13] & 0xf);
+ printf(" type: %s\n", pr_type_strs[j]);
+ } else
+ printf(" not reservation holder\n");
+ if (add_desc_len > 0) {
+ char b[1024];
+
+ printf("%s", sg_decode_transportid_str(" ", bp + 24,
+ add_desc_len, true, sizeof(b), b));
+ }
+ }
+ }
+ }
+fini:
+ if (free_pr_buff)
+ free(free_pr_buff);
+ return res;
+}
+
+/* Compact the 2 dimensional transportid_arr into a one dimensional
+ * array in place returning the length. */
+static int
+compact_transportid_array(struct opts_t * op)
+{
+ int k, off, protocol_id, len;
+ int compact_len = 0;
+ uint8_t * bp = op->transportid_arr;
+
+ for (k = 0, off = 0; ((k < op->num_transportids) && (k < MX_TIDS));
+ ++k, off += MX_TID_LEN) {
+ protocol_id = bp[off] & 0xf;
+ if (TPROTO_ISCSI == protocol_id) {
+ len = sg_get_unaligned_be16(bp + off + 2) + 4;
+ if (len < 24)
+ len = 24;
+ if (off > compact_len)
+ memmove(bp + compact_len, bp + off, len);
+ compact_len += len;
+
+ } else {
+ if (off > compact_len)
+ memmove(bp + compact_len, bp + off, 24);
+ compact_len += 24;
+ }
+ }
+ return compact_len;
+}
+
+static int
+prout_work(int sg_fd, struct opts_t * op)
+{
+ int len, t_arr_len;
+ int res = 0;
+ uint8_t * pr_buff = NULL;
+ uint8_t * free_pr_buff = NULL;
+ char b[64];
+ char bb[80];
+
+ t_arr_len = compact_transportid_array(op);
+ pr_buff = sg_memalign(op->alloc_len, 0 /* page aligned */, &free_pr_buff,
+ false);
+ if (NULL == pr_buff) {
+ pr2serr("%s: unable to allocate %d bytes on heap\n", __func__,
+ op->alloc_len);
+ return sg_convert_errno(ENOMEM);
+ }
+ sg_put_unaligned_be64(op->param_rk, pr_buff + 0);
+ sg_put_unaligned_be64(op->param_sark, pr_buff + 8);
+ if (op->param_alltgpt)
+ pr_buff[20] |= 0x4;
+ if (op->param_aptpl)
+ pr_buff[20] |= 0x1;
+ len = 24;
+ if (t_arr_len > 0) {
+ pr_buff[20] |= 0x8; /* set SPEC_I_PT bit */
+ memcpy(&pr_buff[28], op->transportid_arr, t_arr_len);
+ len += (t_arr_len + 4);
+ sg_put_unaligned_be32((uint32_t)t_arr_len, pr_buff + 24);
+ }
+ res = sg_ll_persistent_reserve_out(sg_fd, op->prout_sa, 0 /* rq_scope */,
+ op->prout_type, pr_buff, len, true,
+ op->verbose);
+ if (res || op->verbose) {
+ if (op->prout_sa < num_prout_sa_strs)
+ snprintf(b, sizeof(b), "%s", prout_sa_strs[op->prout_sa]);
+ else
+ snprintf(b, sizeof(b), "service action=0x%x", op->prout_sa);
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("PR out (%s): command not supported\n", b);
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("PR out (%s): bad field in cdb or parameter list "
+ "(perhaps unsupported service action)\n", b);
+ else {
+ sg_get_category_sense_str(res, sizeof(bb), bb, op->verbose);
+ pr2serr("PR out (%s): %s\n", b, bb);
+ }
+ goto fini;
+ } else if (op->verbose)
+ pr2serr("PR out: command (%s) successful\n", b);
+ }
+fini:
+ if (free_pr_buff)
+ free(free_pr_buff);
+ return res;
+}
+
+static int
+prout_reg_move_work(int sg_fd, struct opts_t * op)
+{
+ int len, t_arr_len;
+ int res = 0;
+ uint8_t * pr_buff = NULL;
+ uint8_t * free_pr_buff = NULL;
+
+ t_arr_len = compact_transportid_array(op);
+ pr_buff = sg_memalign(op->alloc_len, 0 /* page aligned */, &free_pr_buff,
+ false);
+ if (NULL == pr_buff) {
+ pr2serr("%s: unable to allocate %d bytes on heap\n", __func__,
+ op->alloc_len);
+ return sg_convert_errno(ENOMEM);
+ }
+ sg_put_unaligned_be64(op->param_rk, pr_buff + 0);
+ sg_put_unaligned_be64(op->param_sark, pr_buff + 8);
+ if (op->param_unreg)
+ pr_buff[17] |= 0x2;
+ if (op->param_aptpl)
+ pr_buff[17] |= 0x1;
+ sg_put_unaligned_be16(op->param_rtp, pr_buff + 18);
+ len = 24;
+ if (t_arr_len > 0) {
+ memcpy(&pr_buff[24], op->transportid_arr, t_arr_len);
+ len += t_arr_len;
+ sg_put_unaligned_be32((uint32_t)t_arr_len, pr_buff + 20);
+ }
+ res = sg_ll_persistent_reserve_out(sg_fd, PROUT_REG_MOVE_SA,
+ 0 /* rq_scope */, op->prout_type,
+ pr_buff, len, true, op->verbose);
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("PR out (register and move): command not supported\n");
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("PR out (register and move): bad field in cdb or "
+ "parameter list (perhaps unsupported service action)\n");
+ else {
+ char bb[80];
+
+ sg_get_category_sense_str(res, sizeof(bb), bb, op->verbose);
+ pr2serr("PR out (register and move): %s\n", bb);
+ }
+ goto fini;
+ } else if (op->verbose)
+ pr2serr("PR out: 'register and move' command successful\n");
+fini:
+ if (free_pr_buff)
+ free(free_pr_buff);
+ return res;
+}
+
+/* Decode various symbolic forms of TransportIDs into SPC-4 format.
+ * Returns 1 if one found, else returns 0. */
+static int
+decode_sym_transportid(const char * lcp, uint8_t * tidp)
+{
+ int k, j, n, b, c, len, alen;
+ unsigned int ui;
+ const char * ecp;
+ const char * isip;
+
+ memset(tidp, 0, 24);
+ if ((0 == memcmp("sas,", lcp, 4)) || (0 == memcmp("SAS,", lcp, 4))) {
+ lcp += 4;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF");
+ if (16 != k) {
+ pr2serr("badly formed symbolic SAS TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_SAS;
+ for (k = 0, j = 0, b = 0; k < 16; ++k) {
+ c = lcp[k];
+ if (isdigit(c))
+ n = c - 0x30;
+ else if (isupper(c))
+ n = c - 0x37;
+ else
+ n = c - 0x57;
+ if (k & 1) {
+ tidp[4 + j] = b | n;
+ ++j;
+ } else
+ b = n << 4;
+ }
+ return 1;
+ } else if ((0 == memcmp("spi,", lcp, 4)) ||
+ (0 == memcmp("SPI,", lcp, 4))) {
+ lcp += 4;
+ if (2 != sscanf(lcp, "%d,%d", &b, &c)) {
+ pr2serr("badly formed symbolic SPI TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_SPI;
+ sg_put_unaligned_be16((uint16_t)b, tidp + 2);
+ sg_put_unaligned_be16((uint16_t)c, tidp + 6);
+ return 1;
+ } else if ((0 == memcmp("fcp,", lcp, 4)) ||
+ (0 == memcmp("FCP,", lcp, 4))) {
+ lcp += 4;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF");
+ if (16 != k) {
+ pr2serr("badly formed symbolic FCP TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_FCP;
+ for (k = 0, j = 0, b = 0; k < 16; ++k) {
+ c = lcp[k];
+ if (isdigit(c))
+ n = c - 0x30;
+ else if (isupper(c))
+ n = c - 0x37;
+ else
+ n = c - 0x57;
+ if (k & 1) {
+ tidp[8 + j] = b | n;
+ ++j;
+ } else
+ b = n << 4;
+ }
+ return 1;
+ } else if ((0 == memcmp("sbp,", lcp, 4)) ||
+ (0 == memcmp("SBP,", lcp, 4))) {
+ lcp += 4;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF");
+ if (16 != k) {
+ pr2serr("badly formed symbolic SBP TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_1394;
+ for (k = 0, j = 0, b = 0; k < 16; ++k) {
+ c = lcp[k];
+ if (isdigit(c))
+ n = c - 0x30;
+ else if (isupper(c))
+ n = c - 0x37;
+ else
+ n = c - 0x57;
+ if (k & 1) {
+ tidp[8 + j] = b | n;
+ ++j;
+ } else
+ b = n << 4;
+ }
+ return 1;
+ } else if ((0 == memcmp("srp,", lcp, 4)) ||
+ (0 == memcmp("SRP,", lcp, 4))) {
+ lcp += 4;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF");
+ if (16 != k) {
+ pr2serr("badly formed symbolic SRP TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_SRP;
+ for (k = 0, j = 0, b = 0; k < 32; ++k) {
+ c = lcp[k];
+ if (isdigit(c))
+ n = c - 0x30;
+ else if (isupper(c))
+ n = c - 0x37;
+ else
+ n = c - 0x57;
+ if (k & 1) {
+ tidp[8 + j] = b | n;
+ ++j;
+ } else
+ b = n << 4;
+ }
+ return 1;
+ } else if (0 == memcmp("iqn.", lcp, 4)) {
+ ecp = strpbrk(lcp, " \t");
+ isip = strstr(lcp, ",i,0x");
+ if (ecp && (isip > ecp))
+ isip = NULL;
+ len = ecp ? (ecp - lcp) : (int)strlen(lcp);
+ tidp[0] = TPROTO_ISCSI | (isip ? 0x40 : 0x0);
+ alen = len + 1; /* at least one trailing null */
+ if (alen < 20)
+ alen = 20;
+ else if (0 != (alen % 4))
+ alen = ((alen / 4) + 1) * 4;
+ if (alen > 241) { /* sam5r02.pdf A.2 (Annex) */
+ pr2serr("iSCSI name too long, alen=%d\n", alen);
+ return 0;
+ }
+ tidp[3] = alen & 0xff;
+ memcpy(tidp + 4, lcp, len);
+ return 1;
+ } else if ((0 == memcmp("sop,", lcp, 4)) ||
+ (0 == memcmp("SOP,", lcp, 4))) {
+ lcp += 4;
+ if (2 != sscanf(lcp, "%x", &ui)) {
+ pr2serr("badly formed symbolic SOP TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_SOP;
+ sg_put_unaligned_be16((uint16_t)ui, tidp + 2);
+ return 1;
+ }
+ pr2serr("unable to parse symbolic TransportID: %s\n", lcp);
+ return 0;
+}
+
+/* Read one or more TransportIDs from the given file or stdin. Reads from
+ * stdin when 'fnp' is NULL. Returns 0 if successful, 1 otherwise. */
+static int
+decode_file_tids(const char * fnp, struct opts_t * op)
+{
+ bool split_line;
+ int in_len, k, j, m;
+ int off = 0;
+ int num = 0;
+ unsigned int h;
+ FILE * fp = stdin;
+ const char * lcp;
+ uint8_t * tid_arr = op->transportid_arr;
+ char line[1024];
+ char carry_over[4];
+
+ if (fnp) {
+ fp = fopen(fnp, "r");
+ if (NULL == fp) {
+ pr2serr("%s: unable to open %s\n", __func__, fnp);
+ return 1;
+ }
+ }
+ carry_over[0] = 0;
+ for (j = 0, off = 0; j < 512; ++j) {
+ if (NULL == fgets(line, sizeof(line), fp))
+ break;
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ split_line = false;
+ } else
+ split_line = true;
+ }
+ if (in_len < 1) {
+ carry_over[0] = 0;
+ continue;
+ }
+ if (carry_over[0]) {
+ if (isxdigit((uint8_t)line[0])) {
+ carry_over[1] = line[0];
+ carry_over[2] = '\0';
+ if (1 == sscanf(carry_over, "%x", &h))
+ tid_arr[off - 1] = h; /* back up and overwrite */
+ else {
+ pr2serr("%s: carry_over error ['%s'] around line %d\n",
+ __func__, carry_over, j + 1);
+ goto bad;
+ }
+ lcp = line + 1;
+ --in_len;
+ } else
+ lcp = line;
+ carry_over[0] = 0;
+ } else
+ lcp = line;
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp)
+ continue;
+ if (decode_sym_transportid(lcp, tid_arr + off))
+ goto my_cont_a;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t");
+ if ((k < in_len) && ('#' != lcp[k])) {
+ pr2serr("%s: syntax error at line %d, pos %d\n", __func__, j + 1,
+ m + k + 1);
+ goto bad;
+ }
+ for (k = 0; k < 1024; ++k) {
+ if (1 == sscanf(lcp, "%x", &h)) {
+ if (h > 0xff) {
+ pr2serr("%s: hex number larger than 0xff in line %d, pos "
+ "%d\n", __func__, j + 1, (int)(lcp - line + 1));
+ goto bad;
+ }
+ if (split_line && (1 == strlen(lcp))) {
+ /* single trailing hex digit might be a split pair */
+ carry_over[0] = *lcp;
+ }
+ if ((off + k) >= (int)sizeof(op->transportid_arr)) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ goto bad;
+ }
+ op->transportid_arr[off + k] = h;/* keep code checker happy */
+ lcp = strpbrk(lcp, " ,\t");
+ if (NULL == lcp)
+ break;
+ lcp += strspn(lcp, " ,\t");
+ if ('\0' == *lcp)
+ break;
+ } else {
+ if ('#' == *lcp) {
+ --k;
+ break;
+ }
+ pr2serr("%s: error in line %d, at pos %d\n", __func__, j + 1,
+ (int)(lcp - line + 1));
+ goto bad;
+ }
+ }
+my_cont_a:
+ off += MX_TID_LEN;
+ if (off >= (MX_TIDS * MX_TID_LEN)) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ goto bad;
+ }
+ ++num;
+ }
+ op->num_transportids = num;
+ if (fnp)
+ fclose(fp);
+ return 0;
+
+bad:
+ if (fnp)
+ fclose(fp);
+ return 1;
+}
+
+/* Build transportid array which may contain one or more TransportIDs.
+ * A single TransportID can appear on the command line either as a list of
+ * comma (or single space) separated ASCII hex bytes, or in some transport
+ * protocol specific form (e.g. "sas,5000c50005b32001"). One or more
+ * TransportIDs may be given in a file (syntax: "file=<name>") or read from
+ * stdin in (when "-" is given). Fuller description in manpage of
+ * sg_persist(8). Returns 0 if successful, else 1 .
+ */
+static int
+build_transportid(const char * inp, struct opts_t * op)
+{
+ int in_len;
+ int k = 0;
+ unsigned int h;
+ const char * lcp;
+ uint8_t * tid_arr = op->transportid_arr;
+ char * cp;
+ char * c2p;
+
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len) {
+ op->num_transportids = 0;
+ }
+ if (('-' == inp[0]) ||
+ (0 == memcmp("file=", inp, 5)) ||
+ (0 == memcmp("FILE=", inp, 5))) {
+ if ('-' == inp[0])
+ lcp = NULL; /* read from stdin */
+ else
+ lcp = inp + 5; /* read from given file */
+ return decode_file_tids(lcp, op);
+ } else { /* TransportID given directly on command line */
+ if (decode_sym_transportid(lcp, tid_arr))
+ goto my_cont_b;
+ k = strspn(inp, "0123456789aAbBcCdDeEfF, ");
+ if (in_len != k) {
+ pr2serr("%s: error at pos %d\n", __func__, k + 1);
+ return 1;
+ }
+ for (k = 0; k < (int)sizeof(op->transportid_arr); ++k) {
+ if (1 == sscanf(lcp, "%x", &h)) {
+ if (h > 0xff) {
+ pr2serr("%s: hex number larger than 0xff at pos %d\n",
+ __func__, (int)(lcp - inp + 1));
+ return 1;
+ }
+ tid_arr[k] = h;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp)
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ pr2serr("%s: error at pos %d\n", __func__,
+ (int)(lcp - inp + 1));
+ return 1;
+ }
+ }
+my_cont_b:
+ op->num_transportids = 1;
+ if (k >= (int)sizeof(op->transportid_arr)) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool got_maxlen, ok;
+ bool flagged = false;
+ bool want_prin = false;
+ bool want_prout = false;
+ int c, k, res;
+ int help = 0;
+ int num_prin_sa = 0;
+ int num_prout_sa = 0;
+ int num_prout_param = 0;
+ int peri_type = 0;
+ int sg_fd = -1;
+ int ret = 0;
+ const char * cp;
+ const char * device_name = NULL;
+ struct opts_t * op;
+ char buff[48];
+ struct opts_t opts;
+ struct sg_simple_inquiry_resp inq_resp;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ op->pr_in = true;
+ op->prin_sa = -1;
+ op->prout_sa = -1;
+ op->inquiry = true;
+ op->alloc_len = MX_ALLOC_LEN;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv,
+ "AcCd:GHhiIkK:l:Lm:MnoPQ:rRsS:T:UvVX:yYzZ",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'A':
+ op->prout_sa = PROUT_PREE_AB_SA;
+ ++num_prout_sa;
+ break;
+ case 'c':
+ op->prin_sa = PRIN_RCAP_SA;
+ ++num_prin_sa;
+ break;
+ case 'C':
+ op->prout_sa = PROUT_CLEAR_SA;
+ ++num_prout_sa;
+ break;
+ case 'd':
+ device_name = optarg;
+ break;
+ case 'G':
+ op->prout_sa = PROUT_REG_SA;
+ ++num_prout_sa;
+ break;
+ case 'h':
+ ++help;
+ break;
+ case 'H':
+ ++op->hex;
+ break;
+ case 'i':
+ want_prin = true;
+ break;
+ case 'I':
+ op->prout_sa = PROUT_REG_IGN_SA;
+ ++num_prout_sa;
+ break;
+ case 'k':
+ op->prin_sa = PRIN_RKEY_SA;
+ ++num_prin_sa;
+ break;
+ case 'K':
+ if (1 != sscanf(optarg, "%" SCNx64 "", &op->param_rk)) {
+ pr2serr("bad argument to '--param-rk'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ++num_prout_param;
+ break;
+ case 'm': /* --maxlen= and --alloc_length= are similar */
+ case 'l':
+ got_maxlen = ('m' == c);
+ cp = (got_maxlen ? "maxlen" : "alloc-length");
+ if (got_maxlen) {
+ k = sg_get_num(optarg);
+ ok = (-1 != k);
+ op->alloc_len = (unsigned int)k;
+ } else
+ ok = (1 == sscanf(optarg, "%x", &op->alloc_len));
+ if (! ok) {
+ pr2serr("bad argument to '--%s'\n", cp);
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (MX_ALLOC_LEN < op->alloc_len) {
+ pr2serr("'--%s' argument exceeds maximum value (%d)\n", cp,
+ MX_ALLOC_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'L':
+ op->prout_sa = PROUT_REL_SA;
+ ++num_prout_sa;
+ break;
+ case 'M':
+ op->prout_sa = PROUT_REG_MOVE_SA;
+ ++num_prout_sa;
+ break;
+ case 'n':
+ op->inquiry = false;
+ break;
+ case 'o':
+ want_prout = true;
+ break;
+ case 'P':
+ op->prout_sa = PROUT_PREE_SA;
+ ++num_prout_sa;
+ break;
+ case 'Q':
+ if (1 != sscanf(optarg, "%x", &op->param_rtp)) {
+ pr2serr("bad argument to '--relative-target-port'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (op->param_rtp > 0xffff) {
+ pr2serr("argument to '--relative-target-port' 0 to ffff "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ++num_prout_param;
+ break;
+ case 'r':
+ op->prin_sa = PRIN_RRES_SA;
+ ++num_prin_sa;
+ break;
+ case 'R':
+ op->prout_sa = PROUT_RES_SA;
+ ++num_prout_sa;
+ break;
+ case 's':
+ op->prin_sa = PRIN_RFSTAT_SA;
+ ++num_prin_sa;
+ break;
+ case 'S':
+ if (1 != sscanf(optarg, "%" SCNx64 "", &op->param_sark)) {
+ pr2serr("bad argument to '--param-sark'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ++num_prout_param;
+ break;
+ case 'T':
+ if (1 != sscanf(optarg, "%x", &op->prout_type)) {
+ pr2serr("bad argument to '--prout-type'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ++num_prout_param;
+ break;
+ case 'U':
+ op->param_unreg = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'X':
+ if (0 != build_transportid(optarg, op)) {
+ pr2serr("bad argument to '--transport-id'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ++num_prout_param;
+ break;
+ case 'y': /* differentiates -y, -yy and -yyy */
+ if (! op->readwrite_force) {
+ if (op->readonly) {
+ op->readwrite_force = true;
+ op->readonly = false;
+ } else
+ op->readonly = true;
+ }
+ break;
+ case 'Y':
+ op->param_alltgpt = true;
+ ++num_prout_param;
+ break;
+ case 'z':
+ op->prout_sa = PROUT_REPL_LOST_SA;
+ ++num_prout_sa;
+ break;
+ case 'Z':
+ op->param_aptpl = true;
+ ++num_prout_param;
+ break;
+ case '?':
+ usage(1);
+ return 0;
+ default:
+ pr2serr("unrecognised switch code 0x%x ??\n", c);
+ usage(1);
+ 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(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (help > 0) {
+ usage(help);
+ 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: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("No device name given\n");
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (want_prout && want_prin) {
+ pr2serr("choose '--in' _or_ '--out' (not both)\n");
+ usage(1);
+ return SG_LIB_CONTRADICT;
+ } else if (want_prout) { /* syntax check on PROUT arguments */
+ op->pr_in = false;
+ if ((1 != num_prout_sa) || (0 != num_prin_sa)) {
+ pr2serr(">> For Persistent Reserve Out one and only one "
+ "appropriate\n>> service action must be chosen (e.g. "
+ "'--register')\n");
+ return SG_LIB_CONTRADICT;
+ }
+ } else { /* syntax check on PRIN arguments */
+ if (num_prout_sa > 0) {
+ pr2serr(">> When a service action for Persistent Reserve Out "
+ "is chosen the\n>> '--out' option must be given (as a "
+ "safeguard)\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (0 == num_prin_sa) {
+ pr2serr(">> No service action given; assume Persistent Reserve "
+ "In command\n>> with Read Keys service action\n");
+ op->prin_sa = 0;
+ ++num_prin_sa;
+ } else if (num_prin_sa > 1) {
+ pr2serr("Too many service actions given; choose one only\n");
+ usage(1);
+ return SG_LIB_CONTRADICT;
+ }
+ }
+ if ((op->param_unreg || op->param_rtp) &&
+ (PROUT_REG_MOVE_SA != op->prout_sa)) {
+ pr2serr("--unreg or --relative-target-port only useful with "
+ "--register-move\n");
+ usage(1);
+ return SG_LIB_CONTRADICT;
+ }
+ if ((PROUT_REG_MOVE_SA == op->prout_sa) &&
+ (1 != op->num_transportids)) {
+ pr2serr("with --register-move one (and only one) --transport-id "
+ "should be given\n");
+ usage(1);
+ return SG_LIB_CONTRADICT;
+ }
+ if (((PROUT_RES_SA == op->prout_sa) ||
+ (PROUT_REL_SA == op->prout_sa) ||
+ (PROUT_PREE_SA == op->prout_sa) ||
+ (PROUT_PREE_AB_SA == op->prout_sa)) &&
+ (0 == op->prout_type)) {
+ pr2serr("warning>>> --prout-type probably needs to be given\n");
+ }
+ if ((op->verbose > 2) && op->num_transportids) {
+ char b[1024];
+ uint8_t * bp;
+
+ pr2serr("number of tranport-ids decoded from command line (or "
+ "stdin): %d\n", op->num_transportids);
+ pr2serr(" Decode given transport-ids:\n");
+ for (k = 0; k < op->num_transportids; ++k) {
+ bp = op->transportid_arr + (MX_TID_LEN * k);
+ printf("%s", sg_decode_transportid_str(" ", bp, MX_TID_LEN,
+ true, sizeof(b), b));
+ }
+ }
+
+ if (op->inquiry) {
+ if ((sg_fd = sg_cmds_open_device(device_name, true /* ro */,
+ op->verbose)) < 0) {
+ pr2serr("%s: error opening file (ro): %s: %s\n", ME,
+ device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ flagged = true;
+ goto fini;
+ }
+ ret = sg_simple_inquiry(sg_fd, &inq_resp, true, op->verbose);
+ if (0 == ret) {
+ printf(" %.8s %.16s %.4s\n", inq_resp.vendor, inq_resp.product,
+ inq_resp.revision);
+ peri_type = inq_resp.peripheral_type;
+ cp = sg_get_pdt_str(peri_type, sizeof(buff), buff);
+ if (strlen(cp) > 0)
+ printf(" Peripheral device type: %s\n", cp);
+ else
+ printf(" Peripheral device type: 0x%x\n", peri_type);
+ } else {
+ printf("%s: SCSI INQUIRY failed on %s", ME, device_name);
+ if (ret < 0) {
+ ret = -ret;
+ printf(": %s\n", safe_strerror(ret));
+ ret = sg_convert_errno(ret);
+ } else
+ printf("\n");
+ flagged = true;
+ goto fini;
+ }
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0)
+ pr2serr("%s: sg_cmds_close_device() failed res=%d\n", ME, res);
+ }
+
+ if (! op->readwrite_force) {
+ cp = getenv(SG_PERSIST_IN_RDONLY);
+ if (cp && op->pr_in)
+ op->readonly = true; /* SG_PERSIST_IN_RDONLY overrides default
+ which is open(RW) */
+ } else
+ op->readonly = false; /* '-yy' force open(RW) */
+ sg_fd = sg_cmds_open_device(device_name, op->readonly, op->verbose);
+ if (sg_fd < 0) {
+ pr2serr("%s: error opening file %s (r%s): %s\n", ME, device_name,
+ (op->readonly ? "o" : "w"), safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ flagged = true;
+ goto fini;
+ }
+
+ if (op->pr_in)
+ ret = prin_work(sg_fd, op);
+ else if (PROUT_REG_MOVE_SA == op->prout_sa)
+ ret = prout_reg_move_work(sg_fd, op);
+ else /* PROUT commands other than 'register and move' */
+ ret = prout_work(sg_fd, op);
+
+fini:
+ if (ret && (0 == op->verbose) && (! flagged)) {
+ if (! sg_if_can2stderr("sg_persist failed: ", ret))
+ pr2serr("Some error occurred [%d]\n", ret);
+ }
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}