iscsi-pr: add persistent reservation tool
iscsi-pr uses libiscsi to connect target and issues PR command.
In most cases, iscsi-pr use the same parameters as sg_persist(from sg3-utils).
For example, use iscsi-pr to dump keys by command:
~# ./utils/iscsi-pr --read-keys iscsi://192.168.122.44/iqn.2003-01.org.linux-iscsi.bytedance.x8664:sn.a6b7bff3d509/0 -i iqn.2005-03.org.open-iscsi:12345678
PR generation=0x20, 19 registered reservation keys follow:
0xabcd1234
0xabcd
0xabcd
0xabc
A few command is not implemented in this patch, add them if necessary
in future.
Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
This commit is contained in:
committed by
Bart Van Assche
parent
30dd7c6429
commit
fb9c3f93ed
@@ -3,7 +3,7 @@ AM_CFLAGS = $(WARN_CFLAGS)
|
||||
AM_LDFLAGS = -no-undefined
|
||||
LIBS = ../lib/libiscsi.la
|
||||
|
||||
bin_PROGRAMS = iscsi-inq iscsi-ls iscsi-swp
|
||||
bin_PROGRAMS = iscsi-inq iscsi-ls iscsi-swp iscsi-pr
|
||||
if !TARGET_OS_IS_WIN32
|
||||
bin_PROGRAMS += iscsi-perf iscsi-readcapacity16
|
||||
endif
|
||||
|
||||
485
utils/iscsi-pr.c
Normal file
485
utils/iscsi-pr.c
Normal file
@@ -0,0 +1,485 @@
|
||||
/*
|
||||
Copyright (C) 2022 by zhenwei pi<pizhenwei@bytedance.com>
|
||||
|
||||
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 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <getopt.h>
|
||||
#include <inttypes.h>
|
||||
#include "iscsi.h"
|
||||
#include "scsi-lowlevel.h"
|
||||
|
||||
static void pr_in_read_keys(struct scsi_persistent_reserve_in_read_keys *rk)
|
||||
{
|
||||
int i;
|
||||
|
||||
printf(" PR generation=0x%x, %d registered reservation keys follow:\n",
|
||||
rk->prgeneration, rk->num_keys);
|
||||
for (i = 0; i < rk->num_keys; i++) {
|
||||
printf(" 0x%" PRIx64 "\n", rk->keys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void pr_in_read_reservation(struct scsi_persistent_reserve_in_read_reservation *rr)
|
||||
{
|
||||
printf(" PR generation=0x%x, Reservation follows:\n", rr->prgeneration);
|
||||
if (!rr->reserved) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf(" Key=0x%" PRIx64 "\n", rr->reservation_key);
|
||||
if (!rr->pr_scope) {
|
||||
printf(" scope: LU_SCOPE, type: %s\n", scsi_pr_type_str(rr->pr_type));
|
||||
} else {
|
||||
printf(" scope: %d, type: %s\n",
|
||||
rr->pr_scope, scsi_pr_type_str(rr->pr_type));
|
||||
}
|
||||
}
|
||||
|
||||
static void pr_in_report_capabilities(struct scsi_persistent_reserve_in_report_capabilities *rc)
|
||||
{
|
||||
uint16_t mask = (rc->persistent_reservation_type_mask & SCSI_PR_TYPE_MASK_ALL);
|
||||
|
||||
printf("Report capabilities response:\n");
|
||||
printf(" Compatible Reservation Handling(CRH): 0x%x\n", rc->crh);
|
||||
printf(" Specify Initiator Ports Capable(SIP_C): 0x%x\n", rc->sip_c);
|
||||
printf(" All Target Ports Capable(ATP_C): 0x%x\n", rc->atp_c);
|
||||
printf(" Persist Through Power Loss Capable(PTPL_C): 0x%x\n", rc->ptpl_c);
|
||||
printf(" Type Mask Valid(TMV): 0x%x\n", rc->tmv);
|
||||
printf(" Allow Commands: 0x%x\n", rc->allow_commands);
|
||||
printf(" Persist Through Power Loss Active(PTPL_A): 0x%x\n", rc->ptpl_a);
|
||||
printf(" Support indicated in Type mask:\n");
|
||||
if (mask & SCSI_PR_TYPE_MASK_EX_AC_AR) {
|
||||
printf(" Exclusive Access, All Registrants\n");
|
||||
}
|
||||
if (mask & SCSI_PR_TYPE_MASK_WR_EX) {
|
||||
printf(" Write Exclusive\n");
|
||||
}
|
||||
if (mask & SCSI_PR_TYPE_MASK_EX_AC) {
|
||||
printf(" Write Exclusive, Registrants Only\n");
|
||||
}
|
||||
if (mask & SCSI_PR_TYPE_MASK_WR_EX_RO) {
|
||||
printf(" Exclusive Access Registrants Only\n");
|
||||
}
|
||||
if (mask & SCSI_PR_TYPE_MASK_EX_AC_RO) {
|
||||
printf(" Write Exclusive, All Registrants\n");
|
||||
}
|
||||
if (mask & SCSI_PR_TYPE_MASK_WR_EX_AR) {
|
||||
printf(" Exclusive Access, All Registrants\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void do_pr_in(struct iscsi_context *iscsi, int lun, int sa)
|
||||
{
|
||||
struct scsi_task *task;
|
||||
int full_size;
|
||||
void *blob;
|
||||
|
||||
/* See how big this inquiry data is */
|
||||
task = iscsi_persistent_reserve_in_sync(iscsi, lun, sa, 1024);
|
||||
if (task == NULL || task->status != SCSI_STATUS_GOOD) {
|
||||
fprintf(stderr, "PR IN command failed : %s\n", iscsi_get_error(iscsi));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
full_size = scsi_datain_getfullsize(task);
|
||||
if (full_size > task->datain.size) {
|
||||
scsi_free_scsi_task(task);
|
||||
|
||||
if ((task = iscsi_persistent_reserve_in_sync(iscsi, lun, sa, full_size)) == NULL) {
|
||||
fprintf(stderr, "PR IN command failed : %s\n", iscsi_get_error(iscsi));
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
blob = scsi_datain_unmarshall(task);
|
||||
if (blob == NULL) {
|
||||
fprintf(stderr, "failed to unmarshall PR IN blob\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
switch (sa) {
|
||||
case SCSI_PERSISTENT_RESERVE_READ_KEYS:
|
||||
pr_in_read_keys(blob);
|
||||
break;
|
||||
|
||||
case SCSI_PERSISTENT_RESERVE_READ_RESERVATION:
|
||||
pr_in_read_reservation(blob);
|
||||
break;
|
||||
|
||||
case SCSI_PERSISTENT_RESERVE_REPORT_CAPABILITIES:
|
||||
pr_in_report_capabilities(blob);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Usupported PR IN sa: 0x%02x\n", sa);
|
||||
}
|
||||
|
||||
scsi_free_scsi_task(task);
|
||||
}
|
||||
|
||||
static const char *pr_out_sa_str(int sa)
|
||||
{
|
||||
switch (sa) {
|
||||
case SCSI_PERSISTENT_RESERVE_REGISTER:
|
||||
return "register";
|
||||
case SCSI_PERSISTENT_RESERVE_RESERVE:
|
||||
return "reserve";
|
||||
case SCSI_PERSISTENT_RESERVE_RELEASE:
|
||||
return "release";
|
||||
case SCSI_PERSISTENT_RESERVE_CLEAR:
|
||||
return "clear";
|
||||
case SCSI_PERSISTENT_RESERVE_PREEMPT:
|
||||
return "preempt";
|
||||
case SCSI_PERSISTENT_RESERVE_PREEMPT_AND_ABORT:
|
||||
return "preempt-and-abort";
|
||||
case SCSI_PERSISTENT_RESERVE_REGISTER_AND_IGNORE_EXISTING_KEY:
|
||||
return "register-and-ignore-existing-key";
|
||||
case SCSI_PERSISTENT_RESERVE_REGISTER_AND_MOVE:
|
||||
return "register-and-move";
|
||||
}
|
||||
|
||||
return "Unknown SA";
|
||||
}
|
||||
|
||||
static int do_pr_out_command(struct iscsi_context *iscsi, int lun, int sa, int type, uint64_t rk, uint64_t sark)
|
||||
{
|
||||
struct scsi_task *task;
|
||||
struct scsi_persistent_reserve_out_basic param = {0};
|
||||
|
||||
/* TODO other field in param */
|
||||
param.reservation_key = rk;
|
||||
param.service_action_reservation_key = sark;
|
||||
|
||||
task = iscsi_persistent_reserve_out_sync(iscsi, lun, sa, 0, type, ¶m);
|
||||
if (task == NULL || task->status != SCSI_STATUS_GOOD) {
|
||||
fprintf(stderr, "PR OUT command [%s] failed : %s\n",
|
||||
pr_out_sa_str(sa), iscsi_get_error(iscsi));
|
||||
return -1;
|
||||
}
|
||||
|
||||
scsi_free_scsi_task(task);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void do_pr_out(struct iscsi_context *iscsi, int lun, int sa, int type, uint64_t rk, uint64_t sark, unsigned char remove)
|
||||
{
|
||||
/* always do register */
|
||||
if ((do_pr_out_command(iscsi, lun, SCSI_PERSISTENT_RESERVE_REGISTER, type, 0, sark) < 0)
|
||||
|| sa == SCSI_PERSISTENT_RESERVE_REGISTER) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* clear all keys */
|
||||
if (sa == SCSI_PERSISTENT_RESERVE_CLEAR) {
|
||||
rk = sark;
|
||||
do_pr_out_command(iscsi, lun, sa, type, rk, sark);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sa == SCSI_PERSISTENT_RESERVE_RESERVE) {
|
||||
rk = sark;
|
||||
if (do_pr_out_command(iscsi, lun, sa, type, rk, sark)) {
|
||||
goto remove_self_rk;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa == SCSI_PERSISTENT_RESERVE_PREEMPT) {
|
||||
if (do_pr_out_command(iscsi, lun, sa, type, sark, rk)) {
|
||||
goto remove_self_rk;
|
||||
}
|
||||
|
||||
if (!remove) {
|
||||
return;
|
||||
} /* otherwise try to release */
|
||||
}
|
||||
|
||||
if (sa == SCSI_PERSISTENT_RESERVE_RELEASE) {
|
||||
rk = sark;
|
||||
do_pr_out_command(iscsi, lun, sa, type, rk, sark);
|
||||
goto remove_self_rk;
|
||||
}
|
||||
|
||||
remove_self_rk:
|
||||
do_pr_out_command(iscsi, lun, SCSI_PERSISTENT_RESERVE_REGISTER, type, sark, 0);
|
||||
}
|
||||
|
||||
static void print_help(const char *bin)
|
||||
{
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "Usage: %s [OPTION...] <iscsi-url>\n", bin);
|
||||
fprintf(stderr, " -i, --initiator-name=iqn-name Initiator name to use\n");
|
||||
fprintf(stderr, " -d, --debug Enable debug\n");
|
||||
fprintf(stderr, " -?, --help Show this help message\n");
|
||||
|
||||
fprintf(stderr, " -K, --param-rk=RK PR Out: parameter reservation key (RK is in hex)\n");
|
||||
fprintf(stderr, " -S, --param-sark=SARK PR Out: parameter service action reservation key (SARK is in hex)\n");
|
||||
fprintf(stderr, " -T, --prout-type=TYPE PR Out: type field\n");
|
||||
fprintf(stderr, " -G, --register PR Out: Register\n");
|
||||
fprintf(stderr, " -R, --reserve PR Out: Reserve, SARK only(register sark implicity)\n");
|
||||
fprintf(stderr, " -L, --release PR Out: Release, SARK only. This program releases TYPE 7 and TYPE 8 only\n");
|
||||
fprintf(stderr, " -C, --clear PR Out: Clear, SARK only\n");
|
||||
fprintf(stderr, " -P, --preempt PR Out: Preempt, use SARK to preempt reservation from RK\n");
|
||||
fprintf(stderr, " -X, --preempt-remove PR Out: Preempt, use SARK to preempt reservation from RK, then release SARK and remove SARK\n");
|
||||
#if 0
|
||||
fprintf(stderr, " -A, --preempt-abort PR Out: Preempt and Abort\n");
|
||||
fprintf(stderr, " -I, --register-ignore PR Out: Register and Ignore\n");
|
||||
fprintf(stderr, " -M, --register-move PR Out: Register and Move\n");
|
||||
#endif
|
||||
fprintf(stderr, " -k, --read-keys PR In: Read Keys (default)\n");
|
||||
fprintf(stderr, " -r, --read-reservation PR In: Read Reservation\n");
|
||||
fprintf(stderr, " -c, --report-capabilities PR In: Report Capabilities\n");
|
||||
fprintf(stderr, " -s, --read-full-status PR In: Read Full Status\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "iSCSI URL format : %s\n", ISCSI_URL_SYNTAX);
|
||||
fprintf(stderr, "<host> is either of:\n"
|
||||
" \"hostname\" iscsi.example\n"
|
||||
" \"ipv4-address\" 10.1.1.27\n"
|
||||
" \"ipv6-address\" [fce0::1]\n\n");
|
||||
|
||||
fprintf(stderr, "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");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct iscsi_context *iscsi;
|
||||
struct iscsi_url *iscsi_url;
|
||||
char *initiator = NULL;
|
||||
char *url = NULL;
|
||||
int debug = 0;
|
||||
int c, opt;
|
||||
int ret = -1;
|
||||
int pr_operations = 0;
|
||||
unsigned char pr_out = 1, remove = 0;
|
||||
int sa = -1;
|
||||
int type = 0;
|
||||
uint64_t rk = 0, sark = 0;
|
||||
|
||||
/* always keep aligned with sg_persist command */
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"debug", no_argument, NULL, 'd'},
|
||||
{"initiator-name", required_argument, NULL, 'i'},
|
||||
{"param-rk", required_argument, NULL, 'K'},
|
||||
{"param-sark", required_argument, NULL, 'S'},
|
||||
{"prout-type", required_argument, NULL, 'T'},
|
||||
/* PR out, order by sa enum from spec */
|
||||
{"register", no_argument, NULL, 'G'},
|
||||
{"reserve", no_argument, NULL, 'R'},
|
||||
{"release", no_argument, NULL, 'L'},
|
||||
{"clear", no_argument, NULL, 'C'},
|
||||
{"preempt", no_argument, NULL, 'P'},
|
||||
{"preempt-remove", no_argument, NULL, 'X'},
|
||||
#if 0
|
||||
{"preempt-abort", no_argument, NULL, 'A'},
|
||||
{"register-ignore", no_argument, NULL, 'I'},
|
||||
{"register-move", no_argument, NULL, 'M'},
|
||||
#endif
|
||||
/* PR in, order by sa enum from spec */
|
||||
{"read-keys", no_argument, NULL, 'k'},
|
||||
{"read-reservation", no_argument, NULL, 'r'},
|
||||
{"report-capabilities", no_argument, NULL, 'c'},
|
||||
{"read-full-status", no_argument, NULL, 's'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hdHi:K:S:T:GRLCPXAIMkrcs", long_options, &opt)) != -1) {
|
||||
switch (c) {
|
||||
case 'd':
|
||||
debug = 1;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
initiator = optarg;
|
||||
break;
|
||||
|
||||
/* PR out */
|
||||
case 'K':
|
||||
if (1 != sscanf(optarg, "%" SCNx64 "", &rk)) {
|
||||
fprintf(stderr, "bad argument to '--param-rk'\n");
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
if (1 != sscanf(optarg, "%" SCNx64 "", &sark)) {
|
||||
fprintf(stderr, "bad argument to '--param-sark'\n");
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
type = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'G':
|
||||
sa = SCSI_PERSISTENT_RESERVE_REGISTER;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
case 'R':
|
||||
sa = SCSI_PERSISTENT_RESERVE_RESERVE;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
sa = SCSI_PERSISTENT_RESERVE_RELEASE;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
sa = SCSI_PERSISTENT_RESERVE_CLEAR;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
sa = SCSI_PERSISTENT_RESERVE_PREEMPT;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
case 'X':
|
||||
remove = 1;
|
||||
sa = SCSI_PERSISTENT_RESERVE_PREEMPT;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
#if 0
|
||||
case 'A':
|
||||
sa = SCSI_PERSISTENT_RESERVE_PREEMPT_AND_ABORT;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
case 'I':
|
||||
sa = SCSI_PERSISTENT_RESERVE_REGISTER_AND_IGNORE_EXISTING_KEY;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
sa = SCSI_PERSISTENT_RESERVE_REGISTER_AND_MOVE;
|
||||
pr_operations++;
|
||||
break;
|
||||
#endif
|
||||
|
||||
/* PR in */
|
||||
case 'k':
|
||||
pr_out = 0;
|
||||
sa = SCSI_PERSISTENT_RESERVE_READ_KEYS;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
pr_out = 0;
|
||||
sa = SCSI_PERSISTENT_RESERVE_READ_RESERVATION;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
pr_out = 0;
|
||||
sa = SCSI_PERSISTENT_RESERVE_REPORT_CAPABILITIES;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
pr_out = 0;
|
||||
sa = SCSI_PERSISTENT_RESERVE_READ_FULL_STATUS;
|
||||
pr_operations++;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
case '?':
|
||||
default:
|
||||
print_help(argv[0]);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
if ((pr_operations != 1) || (sa == -1)) {
|
||||
fprintf(stderr, "You must specify one Persistent Reservation operation\n");
|
||||
print_help(argv[0]);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (initiator == NULL) {
|
||||
fprintf(stderr, "You must specify initiator by -i/--initiator-name\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
iscsi = iscsi_create_context(initiator);
|
||||
if (iscsi == NULL) {
|
||||
fprintf(stderr, "Failed to create context\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (debug > 0) {
|
||||
iscsi_set_log_level(iscsi, debug);
|
||||
iscsi_set_log_fn(iscsi, iscsi_log_to_stderr);
|
||||
}
|
||||
|
||||
if (argv[optind] != NULL) {
|
||||
url = argv[optind];
|
||||
} else {
|
||||
fprintf(stderr, "You must specify the URL\n");
|
||||
print_help(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
iscsi_url = iscsi_parse_full_url(iscsi, url);
|
||||
if (iscsi_url == NULL) {
|
||||
fprintf(stderr, "Failed to parse URL: %s\n", iscsi_get_error(iscsi));
|
||||
goto destroy_context;
|
||||
}
|
||||
|
||||
iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL);
|
||||
iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C);
|
||||
if (iscsi_full_connect_sync(iscsi, iscsi_url->portal, iscsi_url->lun) != 0) {
|
||||
fprintf(stderr, "Login Failed. %s\n", iscsi_get_error(iscsi));
|
||||
goto destroy_url;
|
||||
}
|
||||
|
||||
if (pr_out) {
|
||||
do_pr_out(iscsi, iscsi_url->lun, sa, type, rk, sark, remove);
|
||||
} else {
|
||||
do_pr_in(iscsi, iscsi_url->lun, sa);
|
||||
}
|
||||
|
||||
/* finish successfully */
|
||||
ret = 0;
|
||||
iscsi_logout_sync(iscsi);
|
||||
|
||||
destroy_url:
|
||||
iscsi_destroy_url(iscsi_url);
|
||||
|
||||
destroy_context:
|
||||
iscsi_destroy_context(iscsi);
|
||||
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user