/* 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 #include #include #include #include #include #include #include #include #include #include #define __STDC_FORMAT_MACROS 1 #include #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=") 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; }